feat(remix): add remix (#20641)
This commit is contained in:
parent
79cab697bf
commit
3b5bf6d1d1
@ -56,6 +56,11 @@ rust-toolchain @nrwl/nx-native-reviewers
|
|||||||
/packages/react-native/** @nrwl/nx-react-reviewers
|
/packages/react-native/** @nrwl/nx-react-reviewers
|
||||||
/e2e/react-native/** @nrwl/nx-react-reviewers
|
/e2e/react-native/** @nrwl/nx-react-reviewers
|
||||||
|
|
||||||
|
## remix
|
||||||
|
/docs/generated/packages/remix/** @nrwl/nx-react-reviewers @nrwl/nx-docs-reviewers @Coly010
|
||||||
|
/packages/remix/** @nrwl/nx-react-reviewers @Coly010
|
||||||
|
/e2e/remix/** @nrwl/nx-react-reviewers @Coly010
|
||||||
|
|
||||||
# Vue
|
# Vue
|
||||||
/packages/vue/** @nrwl/nx-vue-reviewers
|
/packages/vue/** @nrwl/nx-vue-reviewers
|
||||||
/e2e/vue/** @nrwl/nx-vue-reviewers
|
/e2e/vue/** @nrwl/nx-vue-reviewers
|
||||||
|
|||||||
@ -8941,6 +8941,186 @@
|
|||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "remix",
|
||||||
|
"path": "/nx-api/remix",
|
||||||
|
"name": "remix",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "documents",
|
||||||
|
"path": "/nx-api/remix/documents",
|
||||||
|
"name": "documents",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "Overview",
|
||||||
|
"path": "/nx-api/remix/documents/overview",
|
||||||
|
"id": "overview",
|
||||||
|
"isExternal": false,
|
||||||
|
"children": [],
|
||||||
|
"disableCollapsible": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "executors",
|
||||||
|
"path": "/nx-api/remix/executors",
|
||||||
|
"name": "executors",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "serve",
|
||||||
|
"path": "/nx-api/remix/executors/serve",
|
||||||
|
"name": "serve",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "build",
|
||||||
|
"path": "/nx-api/remix/executors/build",
|
||||||
|
"name": "build",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "generators",
|
||||||
|
"path": "/nx-api/remix/generators",
|
||||||
|
"name": "generators",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "preset",
|
||||||
|
"path": "/nx-api/remix/generators/preset",
|
||||||
|
"name": "preset",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "setup",
|
||||||
|
"path": "/nx-api/remix/generators/setup",
|
||||||
|
"name": "setup",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "application",
|
||||||
|
"path": "/nx-api/remix/generators/application",
|
||||||
|
"name": "application",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cypress-component-configuration",
|
||||||
|
"path": "/nx-api/remix/generators/cypress-component-configuration",
|
||||||
|
"name": "cypress-component-configuration",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "library",
|
||||||
|
"path": "/nx-api/remix/generators/library",
|
||||||
|
"name": "library",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "route",
|
||||||
|
"path": "/nx-api/remix/generators/route",
|
||||||
|
"name": "route",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "resource-route",
|
||||||
|
"path": "/nx-api/remix/generators/resource-route",
|
||||||
|
"name": "resource-route",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "action",
|
||||||
|
"path": "/nx-api/remix/generators/action",
|
||||||
|
"name": "action",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "loader",
|
||||||
|
"path": "/nx-api/remix/generators/loader",
|
||||||
|
"name": "loader",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "style",
|
||||||
|
"path": "/nx-api/remix/generators/style",
|
||||||
|
"name": "style",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "setup-tailwind",
|
||||||
|
"path": "/nx-api/remix/generators/setup-tailwind",
|
||||||
|
"name": "setup-tailwind",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "storybook-configuration",
|
||||||
|
"path": "/nx-api/remix/generators/storybook-configuration",
|
||||||
|
"name": "storybook-configuration",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "meta",
|
||||||
|
"path": "/nx-api/remix/generators/meta",
|
||||||
|
"name": "meta",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "error-boundary",
|
||||||
|
"path": "/nx-api/remix/generators/error-boundary",
|
||||||
|
"name": "error-boundary",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cypress",
|
||||||
|
"path": "/nx-api/remix/generators/cypress",
|
||||||
|
"name": "cypress",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "rollup",
|
"id": "rollup",
|
||||||
"path": "/nx-api/rollup",
|
"path": "/nx-api/rollup",
|
||||||
|
|||||||
@ -2414,6 +2414,185 @@
|
|||||||
},
|
},
|
||||||
"path": "/nx-api/react-native"
|
"path": "/nx-api/react-native"
|
||||||
},
|
},
|
||||||
|
"remix": {
|
||||||
|
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||||
|
"name": "remix",
|
||||||
|
"packageName": "@nx/remix",
|
||||||
|
"description": "The Remix plugin for Nx contains executors and generators for managing Remix applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Vitest, Jest, Cypress, and Storybook.\n\n- Generators for applications, libraries, routes, loaders, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
|
||||||
|
"documents": {
|
||||||
|
"/nx-api/remix/documents/overview": {
|
||||||
|
"id": "overview",
|
||||||
|
"name": "Overview",
|
||||||
|
"description": "The Remix plugin for Nx contains executors and generators for managing Remix applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Vitest, Jest, Cypress, and Storybook.\n\n- Generators for applications, libraries, routes, loaders, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
|
||||||
|
"file": "generated/packages/remix/documents/overview",
|
||||||
|
"itemList": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"path": "/nx-api/remix/documents/overview",
|
||||||
|
"tags": [],
|
||||||
|
"originalFilePath": "shared/packages/remix/remix-plugin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "/packages/remix",
|
||||||
|
"source": "/packages/remix/src",
|
||||||
|
"executors": {
|
||||||
|
"/nx-api/remix/executors/serve": {
|
||||||
|
"description": "Serve a Remix application.",
|
||||||
|
"file": "generated/packages/remix/executors/serve.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "serve",
|
||||||
|
"originalFilePath": "/packages/remix/src/executors/serve/schema.json",
|
||||||
|
"path": "/nx-api/remix/executors/serve",
|
||||||
|
"type": "executor"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/executors/build": {
|
||||||
|
"description": "Build a Remix application.",
|
||||||
|
"file": "generated/packages/remix/executors/build.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "build",
|
||||||
|
"originalFilePath": "/packages/remix/src/executors/build/schema.json",
|
||||||
|
"path": "/nx-api/remix/executors/build",
|
||||||
|
"type": "executor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"generators": {
|
||||||
|
"/nx-api/remix/generators/preset": {
|
||||||
|
"description": "Generate a new Remix workspace",
|
||||||
|
"file": "generated/packages/remix/generators/preset.json",
|
||||||
|
"hidden": true,
|
||||||
|
"name": "preset",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/preset/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/preset",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/setup": {
|
||||||
|
"description": "Setup a Remix in an existing workspace",
|
||||||
|
"file": "generated/packages/remix/generators/setup.json",
|
||||||
|
"hidden": true,
|
||||||
|
"name": "setup",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/setup/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/setup",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/application": {
|
||||||
|
"description": "Generate a new Remix application",
|
||||||
|
"file": "generated/packages/remix/generators/application.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "application",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/application/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/application",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/cypress-component-configuration": {
|
||||||
|
"description": "Generate a Cypress Component Testing configuration for a Remix project",
|
||||||
|
"file": "generated/packages/remix/generators/cypress-component-configuration.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "cypress-component-configuration",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/cypress-component-configuration/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/cypress-component-configuration",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/library": {
|
||||||
|
"description": "Generate a new library",
|
||||||
|
"file": "generated/packages/remix/generators/library.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "library",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/library/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/library",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/route": {
|
||||||
|
"description": "Generate a new route",
|
||||||
|
"file": "generated/packages/remix/generators/route.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "route",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/route/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/route",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/resource-route": {
|
||||||
|
"description": "Generate a new resource route",
|
||||||
|
"file": "generated/packages/remix/generators/resource-route.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "resource-route",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/resource-route/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/resource-route",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/action": {
|
||||||
|
"description": "Add an action function to an existing route",
|
||||||
|
"file": "generated/packages/remix/generators/action.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "action",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/action/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/action",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/loader": {
|
||||||
|
"description": "Add a loader function to an existing route",
|
||||||
|
"file": "generated/packages/remix/generators/loader.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "loader",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/loader/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/loader",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/style": {
|
||||||
|
"description": "Generates a new stylesheet and adds it to an existing route",
|
||||||
|
"file": "generated/packages/remix/generators/style.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "style",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/style/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/style",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/setup-tailwind": {
|
||||||
|
"description": "Generates a TailwindCSS configuration for the Remix application",
|
||||||
|
"file": "generated/packages/remix/generators/setup-tailwind.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "setup-tailwind",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/setup-tailwind/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/setup-tailwind",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/storybook-configuration": {
|
||||||
|
"description": "Generates a Storybook configuration for a Remix application",
|
||||||
|
"file": "generated/packages/remix/generators/storybook-configuration.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "storybook-configuration",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/storybook-configuration/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/storybook-configuration",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/meta": {
|
||||||
|
"description": "Add a meta function to an existing route",
|
||||||
|
"file": "generated/packages/remix/generators/meta.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "meta",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/meta/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/meta",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/error-boundary": {
|
||||||
|
"description": "Add an ErrorBoundary to an existing route",
|
||||||
|
"file": "generated/packages/remix/generators/error-boundary.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "error-boundary",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/error-boundary/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/error-boundary",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/remix/generators/cypress": {
|
||||||
|
"description": "Generate a project for testing Remix apps using Cypress",
|
||||||
|
"file": "generated/packages/remix/generators/cypress.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "cypress",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/cypress/schema.json",
|
||||||
|
"path": "/nx-api/remix/generators/cypress",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path": "/nx-api/remix"
|
||||||
|
},
|
||||||
"rollup": {
|
"rollup": {
|
||||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||||
"name": "rollup",
|
"name": "rollup",
|
||||||
|
|||||||
@ -2392,6 +2392,184 @@
|
|||||||
"root": "/packages/react-native",
|
"root": "/packages/react-native",
|
||||||
"source": "/packages/react-native/src"
|
"source": "/packages/react-native/src"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "The Remix plugin for Nx contains executors and generators for managing Remix applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Vitest, Jest, Cypress, and Storybook.\n\n- Generators for applications, libraries, routes, loaders, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"id": "overview",
|
||||||
|
"name": "Overview",
|
||||||
|
"description": "The Remix plugin for Nx contains executors and generators for managing Remix applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Vitest, Jest, Cypress, and Storybook.\n\n- Generators for applications, libraries, routes, loaders, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
|
||||||
|
"file": "generated/packages/remix/documents/overview",
|
||||||
|
"itemList": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"path": "remix/documents/overview",
|
||||||
|
"tags": [],
|
||||||
|
"originalFilePath": "shared/packages/remix/remix-plugin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executors": [
|
||||||
|
{
|
||||||
|
"description": "Serve a Remix application.",
|
||||||
|
"file": "generated/packages/remix/executors/serve.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "serve",
|
||||||
|
"originalFilePath": "/packages/remix/src/executors/serve/schema.json",
|
||||||
|
"path": "remix/executors/serve",
|
||||||
|
"type": "executor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Build a Remix application.",
|
||||||
|
"file": "generated/packages/remix/executors/build.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "build",
|
||||||
|
"originalFilePath": "/packages/remix/src/executors/build/schema.json",
|
||||||
|
"path": "remix/executors/build",
|
||||||
|
"type": "executor"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"generators": [
|
||||||
|
{
|
||||||
|
"description": "Generate a new Remix workspace",
|
||||||
|
"file": "generated/packages/remix/generators/preset.json",
|
||||||
|
"hidden": true,
|
||||||
|
"name": "preset",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/preset/schema.json",
|
||||||
|
"path": "remix/generators/preset",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Setup a Remix in an existing workspace",
|
||||||
|
"file": "generated/packages/remix/generators/setup.json",
|
||||||
|
"hidden": true,
|
||||||
|
"name": "setup",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/setup/schema.json",
|
||||||
|
"path": "remix/generators/setup",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Generate a new Remix application",
|
||||||
|
"file": "generated/packages/remix/generators/application.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "application",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/application/schema.json",
|
||||||
|
"path": "remix/generators/application",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Generate a Cypress Component Testing configuration for a Remix project",
|
||||||
|
"file": "generated/packages/remix/generators/cypress-component-configuration.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "cypress-component-configuration",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/cypress-component-configuration/schema.json",
|
||||||
|
"path": "remix/generators/cypress-component-configuration",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Generate a new library",
|
||||||
|
"file": "generated/packages/remix/generators/library.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "library",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/library/schema.json",
|
||||||
|
"path": "remix/generators/library",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Generate a new route",
|
||||||
|
"file": "generated/packages/remix/generators/route.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "route",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/route/schema.json",
|
||||||
|
"path": "remix/generators/route",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Generate a new resource route",
|
||||||
|
"file": "generated/packages/remix/generators/resource-route.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "resource-route",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/resource-route/schema.json",
|
||||||
|
"path": "remix/generators/resource-route",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Add an action function to an existing route",
|
||||||
|
"file": "generated/packages/remix/generators/action.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "action",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/action/schema.json",
|
||||||
|
"path": "remix/generators/action",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Add a loader function to an existing route",
|
||||||
|
"file": "generated/packages/remix/generators/loader.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "loader",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/loader/schema.json",
|
||||||
|
"path": "remix/generators/loader",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Generates a new stylesheet and adds it to an existing route",
|
||||||
|
"file": "generated/packages/remix/generators/style.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "style",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/style/schema.json",
|
||||||
|
"path": "remix/generators/style",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Generates a TailwindCSS configuration for the Remix application",
|
||||||
|
"file": "generated/packages/remix/generators/setup-tailwind.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "setup-tailwind",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/setup-tailwind/schema.json",
|
||||||
|
"path": "remix/generators/setup-tailwind",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Generates a Storybook configuration for a Remix application",
|
||||||
|
"file": "generated/packages/remix/generators/storybook-configuration.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "storybook-configuration",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/storybook-configuration/schema.json",
|
||||||
|
"path": "remix/generators/storybook-configuration",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Add a meta function to an existing route",
|
||||||
|
"file": "generated/packages/remix/generators/meta.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "meta",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/meta/schema.json",
|
||||||
|
"path": "remix/generators/meta",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Add an ErrorBoundary to an existing route",
|
||||||
|
"file": "generated/packages/remix/generators/error-boundary.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "error-boundary",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/error-boundary/schema.json",
|
||||||
|
"path": "remix/generators/error-boundary",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Generate a project for testing Remix apps using Cypress",
|
||||||
|
"file": "generated/packages/remix/generators/cypress.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "cypress",
|
||||||
|
"originalFilePath": "/packages/remix/src/generators/cypress/schema.json",
|
||||||
|
"path": "remix/generators/cypress",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||||
|
"name": "remix",
|
||||||
|
"packageName": "@nx/remix",
|
||||||
|
"root": "/packages/remix",
|
||||||
|
"source": "/packages/remix/src"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "The Nx Plugin for Rollup contains executors and generators that support building applications using Rollup.",
|
"description": "The Nx Plugin for Rollup contains executors and generators that support building applications using Rollup.",
|
||||||
"documents": [],
|
"documents": [],
|
||||||
|
|||||||
236
docs/generated/packages/remix/documents/overview.md
Normal file
236
docs/generated/packages/remix/documents/overview.md
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
The Nx Plugin for Remix contains executors, generators, and utilities for managing Remix applications and libraries
|
||||||
|
within an Nx workspace. It provides:
|
||||||
|
|
||||||
|
- Integration with libraries such as Storybook, Jest, Vitest and Cypress.
|
||||||
|
- Generators to help scaffold code quickly, including:
|
||||||
|
- Libraries, both internal to your codebase and publishable to npm
|
||||||
|
- Routes
|
||||||
|
- Loaders
|
||||||
|
- Actions
|
||||||
|
- Meta
|
||||||
|
- Utilities for automatic workspace refactoring.
|
||||||
|
|
||||||
|
## Setting up the Remix plugin
|
||||||
|
|
||||||
|
{% callout type="note" title="Keep Nx Package Versions In Sync" %}
|
||||||
|
Make sure to install the `@nx/remix` version that matches the version of `nx` in your repository. If the version
|
||||||
|
numbers get out of sync, you can encounter some difficult to debug errors. You
|
||||||
|
can [fix Nx version mismatches with this recipe](/recipes/tips-n-tricks/keep-nx-versions-in-sync).
|
||||||
|
{% /callout %}
|
||||||
|
|
||||||
|
Adding the Remix plugin to an existing Nx workspace can be done with the following:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn add -D @nx/remix
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install -D @nx/remix
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the Remix Plugin
|
||||||
|
|
||||||
|
## Generate a Remix Application
|
||||||
|
|
||||||
|
{% callout type="note" title="Directory Flag Behavior Changes" %}
|
||||||
|
The command below uses the `as-provided` directory flag behavior, which is the default in Nx 16.8.0. If you're on an earlier version of Nx or using the `derived` option, omit the `--directory` flag. See the [as-provided vs. derived documentation](/deprecated/as-provided-vs-derived) for more details.
|
||||||
|
{% /callout %}
|
||||||
|
|
||||||
|
```{% command="nx g @nx/remix:app myapp --directory=apps/myapp" path="~/acme" %}
|
||||||
|
> NX Generating @nx/remix:application
|
||||||
|
|
||||||
|
✔ What unit test runner should be used? · vitest
|
||||||
|
|
||||||
|
CREATE apps/myapp/project.json
|
||||||
|
UPDATE package.json
|
||||||
|
CREATE apps/myapp/README.md
|
||||||
|
CREATE apps/myapp/app/root.tsx
|
||||||
|
CREATE apps/myapp/app/routes/_index.tsx
|
||||||
|
CREATE apps/myapp/public/favicon.ico
|
||||||
|
CREATE apps/myapp/remix.config.js
|
||||||
|
CREATE apps/myapp/remix.env.d.ts
|
||||||
|
CREATE apps/myapp/tsconfig.json
|
||||||
|
CREATE apps/myapp/.gitignore
|
||||||
|
CREATE apps/myapp/package.json
|
||||||
|
UPDATE nx.json
|
||||||
|
CREATE tsconfig.base.json
|
||||||
|
CREATE .prettierrc
|
||||||
|
CREATE .prettierignore
|
||||||
|
UPDATE .vscode/extensions.json
|
||||||
|
CREATE apps/myapp/vite.config.ts
|
||||||
|
CREATE apps/myapp/tsconfig.spec.json
|
||||||
|
CREATE apps/myapp/test-setup.ts
|
||||||
|
CREATE apps/myapp-e2e/cypress.config.ts
|
||||||
|
CREATE apps/myapp-e2e/src/e2e/app.cy.ts
|
||||||
|
CREATE apps/myapp-e2e/src/fixtures/example.json
|
||||||
|
CREATE apps/myapp-e2e/src/support/commands.ts
|
||||||
|
CREATE apps/myapp-e2e/src/support/e2e.ts
|
||||||
|
CREATE apps/myapp-e2e/tsconfig.json
|
||||||
|
CREATE apps/myapp-e2e/project.json
|
||||||
|
CREATE .eslintrc.json
|
||||||
|
CREATE .eslintignore
|
||||||
|
CREATE apps/myapp-e2e/.eslintrc.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build, Serve and Test your Application
|
||||||
|
|
||||||
|
1. To build your application run:
|
||||||
|
|
||||||
|
```{% command="nx build myapp" path="~/acme" %}
|
||||||
|
> nx run myapp:build
|
||||||
|
|
||||||
|
Building Remix app in production mode...
|
||||||
|
|
||||||
|
Built in 857ms
|
||||||
|
|
||||||
|
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||||
|
|
||||||
|
> NX Successfully ran target build for project myapp (3s)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. To serve your application for use during development run:
|
||||||
|
|
||||||
|
```{% command="nx serve myapp" path="~/acme" %}
|
||||||
|
> nx run myapp:serve
|
||||||
|
|
||||||
|
💿 Building...
|
||||||
|
💿 Rebuilt in 377ms
|
||||||
|
Remix App Server started at http://localhost:3000 (http://192.168.0.14:3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. To test the application using vitest run:
|
||||||
|
|
||||||
|
```{% command="nx test myapp" path="~/acme" %}
|
||||||
|
> nx run myapp:test
|
||||||
|
|
||||||
|
RUN v0.31.4 /Users/columferry/dev/nrwl/issues/remixguide/acme/apps/myapp
|
||||||
|
stderr | app/routes/index.spec.ts > test > should render
|
||||||
|
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
|
||||||
|
✓ app/routes/index.spec.ts (1 test) 10ms
|
||||||
|
Test Files 1 passed (1)
|
||||||
|
Tests 1 passed (1)
|
||||||
|
Start at 16:15:45
|
||||||
|
Duration 1.20s (transform 51ms, setup 139ms, collect 180ms, tests 10ms, environment 379ms, prepare 103ms)
|
||||||
|
|
||||||
|
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||||
|
|
||||||
|
> NX Successfully ran target test for project myapp (2s)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generating an Nx Library
|
||||||
|
|
||||||
|
When developing your application, it often makes sense to split your codebase into smaller more focused libraries.
|
||||||
|
|
||||||
|
To generate a library to use in your Remix application run:
|
||||||
|
|
||||||
|
```{% command="nx g @nx/remix:lib login --directory=libs/login" path="~/acme" %}
|
||||||
|
> NX Generating @nx/remix:library
|
||||||
|
|
||||||
|
✔ What test runner should be used? · vitest
|
||||||
|
UPDATE nx.json
|
||||||
|
UPDATE package.json
|
||||||
|
CREATE babel.config.json
|
||||||
|
CREATE libs/login/project.json
|
||||||
|
CREATE libs/login/.eslintrc.json
|
||||||
|
CREATE libs/login/README.md
|
||||||
|
CREATE libs/login/src/index.ts
|
||||||
|
CREATE libs/login/tsconfig.lib.json
|
||||||
|
CREATE libs/login/tsconfig.json
|
||||||
|
CREATE libs/login/vite.config.ts
|
||||||
|
CREATE libs/login/tsconfig.spec.json
|
||||||
|
CREATE libs/login/src/lib/login.module.css
|
||||||
|
CREATE libs/login/src/lib/login.spec.tsx
|
||||||
|
CREATE libs/login/src/lib/login.tsx
|
||||||
|
UPDATE tsconfig.base.json
|
||||||
|
CREATE libs/login/src/test-setup.ts
|
||||||
|
CREATE libs/login/src/server.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then use the library by importing one of the exports into your application:
|
||||||
|
|
||||||
|
`apps/myapp/app/routes/index.tsx`
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Login } from '@acme/login';
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Login />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run test on your library:
|
||||||
|
|
||||||
|
`nx test login`
|
||||||
|
|
||||||
|
## Generating a Route
|
||||||
|
|
||||||
|
To generate a route for your application:
|
||||||
|
|
||||||
|
```{% command="nx g @nx/remix:route admin --path=apps/myapp/app/routes" path="~/acme" %}
|
||||||
|
> NX Generating @nx/remix:route
|
||||||
|
|
||||||
|
CREATE apps/myapp/app/routes/admin.tsx
|
||||||
|
CREATE apps/myapp/app/styles/admin.css
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using a loader from your Library
|
||||||
|
|
||||||
|
To use a Route Loader where the logic lives in your library, follow the steps below.
|
||||||
|
|
||||||
|
1. Generate a loader for your route:
|
||||||
|
|
||||||
|
```{% command="nx g @nx/remix:loader admin --path=apps/myapp/app/routes" path="~/acme" %}
|
||||||
|
> NX Generating @nx/remix:loader
|
||||||
|
|
||||||
|
UPDATE apps/myapp/app/routes/admin.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add a new file in your `login` lib
|
||||||
|
|
||||||
|
`libs/login/src/lib/admin/admin.loader.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { json, LoaderFunctionArgs } from '@remix-run/node';
|
||||||
|
|
||||||
|
export const adminLoader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
|
return json({
|
||||||
|
message: 'Hello, world!',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Export the function from the `libs/login/src/server.ts` file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export * from './lib/admin/admin.loader';
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Use the loader in your `apps/myapp/app/routes/admin.tsx`
|
||||||
|
|
||||||
|
Replace the default loader code:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
|
return json({
|
||||||
|
message: 'Hello, world!',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
with
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { adminLoader } from '@acme/login/server';
|
||||||
|
|
||||||
|
export const loader = adminLoader;
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitHub Repository with Example
|
||||||
|
|
||||||
|
You can see an example of an Nx Workspace using Remix by clicking below.
|
||||||
|
|
||||||
|
{% github-repository url="https://github.com/nrwl/nx-recipes/tree/main/remix" /%}
|
||||||
48
docs/generated/packages/remix/executors/build.json
Normal file
48
docs/generated/packages/remix/executors/build.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "build",
|
||||||
|
"implementation": "/packages/remix/src/executors/build/build.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"version": 2,
|
||||||
|
"outputCapture": "pipe",
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"cli": "nx",
|
||||||
|
"title": "Remix Build",
|
||||||
|
"description": "Build a Remix app.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"outputPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The output path of the generated files.",
|
||||||
|
"x-completion-type": "directory",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"includeDevDependenciesInPackageJson": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Include `devDependencies` in the generated package.json file. By default only production `dependencies` are included.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"generatePackageJson": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate package.json file in the output folder.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"generateLockfile": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate a lockfile (e.g. package-lock.json) that matches the workspace lockfile to ensure package versions match.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"sourcemap": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate source maps for production.",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["outputPath"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Build a Remix application.",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/executors/build/schema.json",
|
||||||
|
"type": "executor"
|
||||||
|
}
|
||||||
51
docs/generated/packages/remix/executors/serve.json
Normal file
51
docs/generated/packages/remix/executors/serve.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "serve",
|
||||||
|
"implementation": "/packages/remix/src/executors/serve/serve.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"version": 2,
|
||||||
|
"outputCapture": "pipe",
|
||||||
|
"cli": "nx",
|
||||||
|
"title": "Remix Serve",
|
||||||
|
"description": "Serve a Remix app.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"port": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Set PORT environment variable that can be used to serve the Remix application.",
|
||||||
|
"default": 4200
|
||||||
|
},
|
||||||
|
"devServerPort": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Port to start the dev server on."
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Attach a Node.js inspector.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Command used to run your app server."
|
||||||
|
},
|
||||||
|
"manual": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Enable manual mode",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"tlsKey": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to TLS key (key.pem)."
|
||||||
|
},
|
||||||
|
"tlsCert": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to TLS certificate (cert.pem)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Serve a Remix application.",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/executors/serve/schema.json",
|
||||||
|
"type": "executor"
|
||||||
|
}
|
||||||
38
docs/generated/packages/remix/generators/action.json
Normal file
38
docs/generated/packages/remix/generators/action.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "action",
|
||||||
|
"implementation": "/packages/remix/src/generators/action/action.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "action",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Generate an action for a given route.",
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The route path or path to the filename of the route.",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What is the path of the route? (e.g. 'apps/demo/app/routes/foo/bar.tsx')"
|
||||||
|
},
|
||||||
|
"nameAndDirectoryFormat": {
|
||||||
|
"description": "Whether to generate the action in the directory as provided, relative to the current working directory and ignoring the project (`as-provided`) or generate it using the project and directory relative to the workspace root (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": { "$source": "projectName" },
|
||||||
|
"x-prompt": "What project is this route for?",
|
||||||
|
"pattern": "^[a-zA-Z].*$",
|
||||||
|
"x-deprecated": "Provide the `path` option instead and use the `as-provided` format. The project will be determined from the path provided. It will be removed in Nx v18."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["path"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Add an action function to an existing route",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/action/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
76
docs/generated/packages/remix/generators/application.json
Normal file
76
docs/generated/packages/remix/generators/application.json
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"name": "application",
|
||||||
|
"implementation": "/packages/remix/src/generators/application/application.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixApplication",
|
||||||
|
"title": "Create an Application",
|
||||||
|
"description": "Generate a new Remix application.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What is the name of the application?"
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate JavaScript files rather than TypeScript files.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"directory": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A directory where the app is placed.",
|
||||||
|
"alias": "dir",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"projectNameAndRootFormat": {
|
||||||
|
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"description": "The tool to use for running lint checks.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["eslint", "none"],
|
||||||
|
"default": "eslint"
|
||||||
|
},
|
||||||
|
"unitTestRunner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["vitest", "jest", "none"],
|
||||||
|
"default": "vitest",
|
||||||
|
"description": "Test runner to use for unit tests.",
|
||||||
|
"x-prompt": "What unit test runner should be used?"
|
||||||
|
},
|
||||||
|
"e2eTestRunner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["cypress", "none"],
|
||||||
|
"default": "cypress",
|
||||||
|
"description": "Test runner to use for e2e tests"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Add tags to the project (used for linting)",
|
||||||
|
"alias": "t"
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip formatting files",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"rootProject": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-priority": "internal",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generate a new Remix application",
|
||||||
|
"aliases": ["app"],
|
||||||
|
"x-type": "application",
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/application/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "cypress-component-configuration",
|
||||||
|
"implementation": "/packages/remix/src/generators/cypress-component-configuration/cypress-component-configuration.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "https://json-schema.org/schema",
|
||||||
|
"cli": "nx",
|
||||||
|
"$id": "NxRemixCypressComponentTestConfiguration",
|
||||||
|
"title": "Add Cypress component testing",
|
||||||
|
"description": "Add a Cypress component testing configuration to an existing project.",
|
||||||
|
"type": "object",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "nx g @nx/remix:cypress-component-configuration --project=my-remix-project",
|
||||||
|
"description": "Add component testing to your Remix project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "nx g @nx/remix:cypress-component-configuration --project=my-remix-project --generate-tests",
|
||||||
|
"description": "Add component testing to your Remix project and generate component tests for your existing components"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project to add cypress component testing configuration to",
|
||||||
|
"x-dropdown": "projects",
|
||||||
|
"x-prompt": "What project should we add Cypress component testing to?",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"generateTests": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate default component tests for existing components in the project",
|
||||||
|
"x-prompt": "Automatically generate tests for components declared in this project?",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip formatting files",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["project"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generate a Cypress Component Testing configuration for a Remix project",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/cypress-component-configuration/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
66
docs/generated/packages/remix/generators/cypress.json
Normal file
66
docs/generated/packages/remix/generators/cypress.json
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"name": "cypress",
|
||||||
|
"implementation": "/packages/remix/src/generators/cypress/cypress.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixCypress",
|
||||||
|
"title": "",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Generate a Cypress e2e project for a given application.",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the frontend project to test.",
|
||||||
|
"$default": { "$source": "projectName" }
|
||||||
|
},
|
||||||
|
"projectNameAndRootFormat": {
|
||||||
|
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"baseUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "URL to access the application on",
|
||||||
|
"default": "http://localhost:3000"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the E2E Project",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What name would you like to use for the e2e project?"
|
||||||
|
},
|
||||||
|
"directory": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A directory where the project is placed"
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"description": "The tool to use for running lint checks.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["eslint", "none"],
|
||||||
|
"default": "eslint"
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"description": "Generate JavaScript files rather than TypeScript files",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"description": "Skip formatting files",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"setParserOptionsProject": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generate a project for testing Remix apps using Cypress",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/cypress/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
49
docs/generated/packages/remix/generators/error-boundary.json
Normal file
49
docs/generated/packages/remix/generators/error-boundary.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "error-boundary",
|
||||||
|
"implementation": "/packages/remix/src/generators/error-boundary/error-boundary.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixErrorBoundary",
|
||||||
|
"title": "Create an ErrorBoundary for a Route",
|
||||||
|
"description": "Generate an ErrorBoundary for a given route.",
|
||||||
|
"type": "object",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "g error-boundary --routePath=apps/demo/app/routes/my-route.tsx",
|
||||||
|
"description": "Generate an ErrorBoundary for my-route.tsx"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The path to route file relative to the project root."
|
||||||
|
},
|
||||||
|
"nameAndDirectoryFormat": {
|
||||||
|
"description": "Whether to generate the error boundary in the path as provided, relative to the current working directory and ignoring the project (`as-provided`) or generate it using the project and directory relative to the workspace root (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": { "$source": "projectName" },
|
||||||
|
"x-prompt": "What project contains the route file that this ErrorBoundary is for?",
|
||||||
|
"pattern": "^[a-zA-Z].*$",
|
||||||
|
"x-deprecated": "Provide the `path` option instead and use the `as-provided` format. The project will be determined from the path provided. It will be removed in Nx v18."
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip formatting files after generation.",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["path"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Add an ErrorBoundary to an existing route",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/error-boundary/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
82
docs/generated/packages/remix/generators/library.json
Normal file
82
docs/generated/packages/remix/generators/library.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"name": "library",
|
||||||
|
"implementation": "/packages/remix/src/generators/library/library.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixLibrary",
|
||||||
|
"title": "Create a Library",
|
||||||
|
"description": "Generate a Remix library to help structure workspace and application.",
|
||||||
|
"type": "object",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "g lib mylib --directory=myapp",
|
||||||
|
"description": "Generate libs/myapp/mylib"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Library name",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What name would you like to use for the library?",
|
||||||
|
"pattern": "^[a-zA-Z].*$"
|
||||||
|
},
|
||||||
|
"directory": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A directory where the lib is placed.",
|
||||||
|
"alias": "dir",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"projectNameAndRootFormat": {
|
||||||
|
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Add tags to the library (used for linting)"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Generate a stylesheet",
|
||||||
|
"enum": ["none", "css"],
|
||||||
|
"default": "css"
|
||||||
|
},
|
||||||
|
"buildable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Should the library be buildable?",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"unitTestRunner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["jest", "vitest", "none"],
|
||||||
|
"description": "Test Runner to use for Unit Tests",
|
||||||
|
"x-prompt": "What test runner should be used?",
|
||||||
|
"default": "vitest"
|
||||||
|
},
|
||||||
|
"importPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The library name used to import it, like @myorg/my-awesome-lib"
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate JavaScript files rather than TypeScript files",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip formatting files after generator runs",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generate a new library",
|
||||||
|
"aliases": ["lib"],
|
||||||
|
"x-type": "library",
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/library/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
38
docs/generated/packages/remix/generators/loader.json
Normal file
38
docs/generated/packages/remix/generators/loader.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "loader",
|
||||||
|
"implementation": "/packages/remix/src/generators/loader/loader.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "data-loader",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Generate an loader for a given route.",
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The route path or path to the filename of the route.",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What is the path of the route? (e.g. 'apps/demo/app/routes/foo/bar.tsx')"
|
||||||
|
},
|
||||||
|
"nameAndDirectoryFormat": {
|
||||||
|
"description": "Whether to generate the loader in the path as provided, relative to the current working directory and ignoring the project (`as-provided`) or generate it using the project and directory relative to the workspace root (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": { "$source": "projectName" },
|
||||||
|
"x-prompt": "What project is this route for?",
|
||||||
|
"pattern": "^[a-zA-Z].*$",
|
||||||
|
"x-deprecated": "Provide the `path` option instead and use the `as-provided` format. The project will be determined from the path provided. It will be removed in Nx v18."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["path"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Add a loader function to an existing route",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/loader/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
38
docs/generated/packages/remix/generators/meta.json
Normal file
38
docs/generated/packages/remix/generators/meta.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "meta",
|
||||||
|
"implementation": "/packages/remix/src/generators/meta/meta.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "meta",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Generate a meta function for a given route.",
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The route path or path to the filename of the route.",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What is the path of the route? (e.g. 'apps/demo/app/routes/foo/bar.tsx')"
|
||||||
|
},
|
||||||
|
"nameAndDirectoryFormat": {
|
||||||
|
"description": "Whether to generate the meta function in the path as provided, relative to the current working directory and ignoring the project (`as-provided`) or generate it using the project and directory relative to the workspace root (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": { "$source": "projectName" },
|
||||||
|
"x-prompt": "What project is this route for?",
|
||||||
|
"pattern": "^[a-zA-Z].*$",
|
||||||
|
"x-deprecated": "Provide the `path` option instead and use the `as-provided` format. The project will be determined from the path provided. It will be removed in Nx v18."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["path"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Add a meta function to an existing route",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/meta/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
24
docs/generated/packages/remix/generators/preset.json
Normal file
24
docs/generated/packages/remix/generators/preset.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "preset",
|
||||||
|
"implementation": "/packages/remix/src/generators/preset/preset.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "Remix",
|
||||||
|
"title": "",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Generate a Remix application in a standalone workspace. Can be used with `create-nx-workspace --preset=@nx/remix`.",
|
||||||
|
"properties": {
|
||||||
|
"tags": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Add tags to the app (used for linting).",
|
||||||
|
"alias": "t"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generate a new Remix workspace",
|
||||||
|
"hidden": true,
|
||||||
|
"aliases": [],
|
||||||
|
"path": "/packages/remix/src/generators/preset/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
60
docs/generated/packages/remix/generators/resource-route.json
Normal file
60
docs/generated/packages/remix/generators/resource-route.json
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "resource-route",
|
||||||
|
"implementation": "/packages/remix/src/generators/resource-route/resource-route.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixResourceRoute",
|
||||||
|
"title": "Create a Resource Route",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Generate a resource route.",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "g resource-route 'path/to/page'",
|
||||||
|
"description": "Generate resource route at /path/to/page"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The route path or path to the filename of the route.",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What is the path of the route? (e.g. 'apps/demo/app/routes/foo/bar')"
|
||||||
|
},
|
||||||
|
"nameAndDirectoryFormat": {
|
||||||
|
"description": "Whether to generate the styles in the path as provided, relative to the current working directory and ignoring the project (`as-provided`) or generate it using the project and directory relative to the workspace root (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": { "$source": "projectName" },
|
||||||
|
"x-prompt": "What project is this route for?",
|
||||||
|
"pattern": "^[a-zA-Z].*$",
|
||||||
|
"x-deprecated": "Provide the `path` option instead and use the `as-provided` format. The project will be determined from the path provided. It will be removed in Nx v18."
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate an action function",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"loader": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate a loader function",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"skipChecks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip route error detection",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["path"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generate a new resource route",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/resource-route/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
71
docs/generated/packages/remix/generators/route.json
Normal file
71
docs/generated/packages/remix/generators/route.json
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"name": "route",
|
||||||
|
"implementation": "/packages/remix/src/generators/route/route.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixRoute",
|
||||||
|
"title": "Create a Route",
|
||||||
|
"description": "Generate a route.",
|
||||||
|
"type": "object",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "g route 'path/to/page'",
|
||||||
|
"description": "Generate route at /path/to/page"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The route path or path to the filename of the route. When `--nameAndDirectoryFormat=as-provided`, it will be relative to the current working directory. Otherwise, it will be relative to the workspace root.",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What is the path of the route? (e.g. 'apps/demo/app/routes/foo/bar')"
|
||||||
|
},
|
||||||
|
"nameAndDirectoryFormat": {
|
||||||
|
"description": "Whether to generate the route in the path as provided, relative to the current working directory and ignoring the project (`as-provided`) or generate it using the project and path relative to the workspace root (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": { "$source": "projectName" },
|
||||||
|
"x-prompt": "What project is this route for?",
|
||||||
|
"pattern": "^[a-zA-Z].*$",
|
||||||
|
"x-deprecated": "Provide the `path` option instead and use the `as-provided` format. The project will be determined from the path provided. It will be removed in Nx v18."
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Generate a stylesheet",
|
||||||
|
"enum": ["none", "css"],
|
||||||
|
"default": "css"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate a meta function",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate an action function",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"loader": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate a loader function",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipChecks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip route error detection",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["path"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generate a new route",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/route/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
44
docs/generated/packages/remix/generators/setup-tailwind.json
Normal file
44
docs/generated/packages/remix/generators/setup-tailwind.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "setup-tailwind",
|
||||||
|
"implementation": "/packages/remix/src/generators/setup-tailwind/setup-tailwind.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixTailwind",
|
||||||
|
"title": "Add TailwindCSS to a Remix App",
|
||||||
|
"description": "Setup tailwindcss for a given project.",
|
||||||
|
"type": "object",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "g setup-tailwind --project=myapp",
|
||||||
|
"description": "Generate a TailwindCSS config for your Remix app"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project to add tailwind to",
|
||||||
|
"$default": { "$source": "projectName" },
|
||||||
|
"x-prompt": "What project would you like to add Tailwind to?",
|
||||||
|
"pattern": "^[a-zA-Z].*$"
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate a JavaScript config file instead of a TypeScript config file",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip formatting files after generator runs",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["project"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generates a TailwindCSS configuration for the Remix application",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/setup-tailwind/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
24
docs/generated/packages/remix/generators/setup.json
Normal file
24
docs/generated/packages/remix/generators/setup.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "setup",
|
||||||
|
"implementation": "/packages/remix/src/generators/setup/setup.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixSetup",
|
||||||
|
"title": "",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Generate initial files required for Remix to work within the workspace.",
|
||||||
|
"properties": {
|
||||||
|
"packageManager": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The package manager to setup for",
|
||||||
|
"enum": ["yarn", "npm", "pnpm"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Setup a Remix in an existing workspace",
|
||||||
|
"hidden": true,
|
||||||
|
"aliases": [],
|
||||||
|
"path": "/packages/remix/src/generators/setup/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"name": "storybook-configuration",
|
||||||
|
"implementation": "/packages/remix/src/generators/storybook-configuration/storybook-configuration.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"cli": "nx",
|
||||||
|
"$id": "NxRemixStorybookConfigure",
|
||||||
|
"title": "Remix Storybook Configuration",
|
||||||
|
"description": "Set up Storybook for a Remix library.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"aliases": ["name", "projectName"],
|
||||||
|
"description": "Project for which to generate Storybook configuration.",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "For which project do you want to generate Storybook configuration?",
|
||||||
|
"x-dropdown": "projects",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"configureCypress": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Run the cypress-configure generator.",
|
||||||
|
"x-prompt": "Configure a cypress e2e app to run against the storybook instance?",
|
||||||
|
"default": true,
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"generateStories": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Automatically generate `*.stories.ts` files for components declared in this project?",
|
||||||
|
"x-prompt": "Automatically generate *.stories.ts files for components declared in this project?",
|
||||||
|
"default": true,
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"generateCypressSpecs": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Automatically generate test files in the Cypress E2E app generated by the `cypress-configure` generator.",
|
||||||
|
"x-prompt": "Automatically generate test files in the Cypress E2E app generated by the cypress-configure generator?",
|
||||||
|
"default": true,
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"configureStaticServe": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Specifies whether to configure a static file server target for serving storybook. Helpful for speeding up CI build/test times.",
|
||||||
|
"x-prompt": "Configure a static file server for the storybook instance?",
|
||||||
|
"default": true,
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"cypressDirectory": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A directory where the Cypress project will be placed. Placed at the root by default."
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate JavaScript story files rather than TypeScript story files.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"tsConfiguration": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Configure your project with TypeScript. Generate main.ts and preview.ts files, instead of main.js and preview.js.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"description": "The tool to use for running lint checks.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["eslint"],
|
||||||
|
"default": "eslint"
|
||||||
|
},
|
||||||
|
"ignorePaths": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Paths to ignore when looking for components.",
|
||||||
|
"items": { "type": "string", "description": "Path to ignore." },
|
||||||
|
"examples": [
|
||||||
|
"**/**/src/**/not-stories/**",
|
||||||
|
"libs/my-lib/**/*.something.ts",
|
||||||
|
"**/**/src/**/*.other.*",
|
||||||
|
"libs/my-lib/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"configureTestRunner": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Add a Storybook Test-Runner target."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generates a Storybook configuration for a Remix application",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/storybook-configuration/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
45
docs/generated/packages/remix/generators/style.json
Normal file
45
docs/generated/packages/remix/generators/style.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "style",
|
||||||
|
"implementation": "/packages/remix/src/generators/style/style.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixRouteStyle",
|
||||||
|
"title": "Add style import to a route",
|
||||||
|
"description": "Generate a style import and file for a given route.",
|
||||||
|
"type": "object",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "g style --path='apps/demo/app/routes/path/to/page.tsx'",
|
||||||
|
"description": "Generate route at apps/demo/app/routes/path/to/page.tsx"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Route path",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What is the path of the route? (e.g. 'apps/demo/app/routes/foo/bar.tsx')"
|
||||||
|
},
|
||||||
|
"nameAndDirectoryFormat": {
|
||||||
|
"description": "Whether to generate the styles in the path as provided, relative to the current working directory and ignoring the project (`as-provided`) or generate it using the project and directory relative to the workspace root (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": { "$source": "projectName" },
|
||||||
|
"x-prompt": "What project is this route in?",
|
||||||
|
"pattern": "^[a-zA-Z].*$",
|
||||||
|
"x-deprecated": "Provide the `path` option instead and use the `as-provided` format. The project will be determined from the path provided. It will be removed in Nx v18."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["path"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generates a new stylesheet and adds it to an existing route",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/remix/src/generators/style/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
@ -2390,6 +2390,19 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "remix",
|
||||||
|
"id": "remix",
|
||||||
|
"description": "Remix package.",
|
||||||
|
"itemList": [
|
||||||
|
{
|
||||||
|
"id": "overview",
|
||||||
|
"name": "Overview",
|
||||||
|
"path": "/nx-api/remix",
|
||||||
|
"file": "shared/packages/remix/remix-plugin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "detox",
|
"name": "detox",
|
||||||
"id": "detox",
|
"id": "detox",
|
||||||
|
|||||||
@ -62,7 +62,7 @@ npx create-nx-workspace@latest
|
|||||||
{% link-card title="Next" appearance="small" url="/nx-api/next" icon="nextjs" /%}
|
{% link-card title="Next" appearance="small" url="/nx-api/next" icon="nextjs" /%}
|
||||||
{% link-card title="Nuxt" appearance="small" url="/showcase/example-repos/add-nuxt" icon="nuxt" /%}
|
{% link-card title="Nuxt" appearance="small" url="/showcase/example-repos/add-nuxt" icon="nuxt" /%}
|
||||||
{% link-card title="Nest" appearance="small" url="/nx-api/nest" icon="nestjs" /%}
|
{% link-card title="Nest" appearance="small" url="/nx-api/nest" icon="nestjs" /%}
|
||||||
{% link-card title="Remix" appearance="small" url="/recipes/react/remix" icon="remix" /%}
|
{% link-card title="Remix" appearance="small" url="/nx-api/remix" icon="remix" /%}
|
||||||
{% link-card title="Expo" appearance="small" url="/nx-api/expo" icon="expo" /%}
|
{% link-card title="Expo" appearance="small" url="/nx-api/expo" icon="expo" /%}
|
||||||
{% link-card title="React Native" appearance="small" url="/nx-api/react-native" icon="react" /%}
|
{% link-card title="React Native" appearance="small" url="/nx-api/react-native" icon="react" /%}
|
||||||
{% link-card title="Fastify" appearance="small" url="/showcase/example-repos/mongo-fastify" icon="fastify" /%}
|
{% link-card title="Fastify" appearance="small" url="/showcase/example-repos/mongo-fastify" icon="fastify" /%}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ In this recipe, we'll show you how to create a [Remix](https://remix.run) applic
|
|||||||
## Install Nx Remix Plugin
|
## Install Nx Remix Plugin
|
||||||
|
|
||||||
{% callout type="note" title="Keep Nx Package Versions In Sync" %}
|
{% callout type="note" title="Keep Nx Package Versions In Sync" %}
|
||||||
Make sure to install the `@nx/remix` version that is on the same minor version as the `nx` version in your repository. If the version numbers get out of sync, you can encounter some difficult to debug errors. You can [fix Nx version mismatches with this recipe](/recipes/tips-n-tricks/keep-nx-versions-in-sync). The `@nx/remix` package is still being developed under [nx-labs](https://github.com/nrwl/nx-labs), so the publishing cadence is not perfectly coordinated with the other Nx packages.
|
Make sure to install the `@nx/remix` version that is on the same minor version as the `nx` version in your repository. If the version numbers get out of sync, you can encounter some difficult to debug errors. You can [fix Nx version mismatches with this recipe](/recipes/tips-n-tricks/keep-nx-versions-in-sync).
|
||||||
{% /callout %}
|
{% /callout %}
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@ -50,7 +50,7 @@ CREATE apps/myapp/project.json
|
|||||||
UPDATE package.json
|
UPDATE package.json
|
||||||
CREATE apps/myapp/README.md
|
CREATE apps/myapp/README.md
|
||||||
CREATE apps/myapp/app/root.tsx
|
CREATE apps/myapp/app/root.tsx
|
||||||
CREATE apps/myapp/app/routes/index.tsx
|
CREATE apps/myapp/app/routes/_index.tsx
|
||||||
CREATE apps/myapp/public/favicon.ico
|
CREATE apps/myapp/public/favicon.ico
|
||||||
CREATE apps/myapp/remix.config.js
|
CREATE apps/myapp/remix.config.js
|
||||||
CREATE apps/myapp/remix.env.d.ts
|
CREATE apps/myapp/remix.env.d.ts
|
||||||
@ -175,7 +175,7 @@ You can also run test on your library:
|
|||||||
|
|
||||||
To generate a route for your application:
|
To generate a route for your application:
|
||||||
|
|
||||||
```{% command="nx g @nx/remix:route admin --project=myapp" path="~/acme" %}
|
```{% command="nx g @nx/remix:route admin --path=apps/myapp/app/routes" path="~/acme" %}
|
||||||
> NX Generating @nx/remix:route
|
> NX Generating @nx/remix:route
|
||||||
|
|
||||||
CREATE apps/myapp/app/routes/admin.tsx
|
CREATE apps/myapp/app/routes/admin.tsx
|
||||||
@ -188,7 +188,7 @@ To use a Route Loader where the logic lives in your library, follow the steps be
|
|||||||
|
|
||||||
1. Generate a loader for your route:
|
1. Generate a loader for your route:
|
||||||
|
|
||||||
```{% command="nx g @nx/remix:loader admin --project=myapp" path="~/acme" %}
|
```{% command="nx g @nx/remix:loader admin --path=apps/myapp/app/routes" path="~/acme" %}
|
||||||
> NX Generating @nx/remix:loader
|
> NX Generating @nx/remix:loader
|
||||||
|
|
||||||
UPDATE apps/myapp/app/routes/admin.tsx
|
UPDATE apps/myapp/app/routes/admin.tsx
|
||||||
@ -199,9 +199,9 @@ UPDATE apps/myapp/app/routes/admin.tsx
|
|||||||
`libs/login/src/lib/admin/admin.loader.ts`
|
`libs/login/src/lib/admin/admin.loader.ts`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { json, LoaderArgs } from '@remix-run/node';
|
import { json, LoaderFunctionArgs } from '@remix-run/node';
|
||||||
|
|
||||||
export const adminLoader = async ({ request }: LoaderArgs) => {
|
export const adminLoader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
return json({
|
return json({
|
||||||
message: 'Hello, world!',
|
message: 'Hello, world!',
|
||||||
});
|
});
|
||||||
@ -219,7 +219,7 @@ export * from './lib/admin/admin.loader';
|
|||||||
Replace the default loader code:
|
Replace the default loader code:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
export const loader = async ({ request }: LoaderArgs) => {
|
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
return json({
|
return json({
|
||||||
message: 'Hello, world!',
|
message: 'Hello, world!',
|
||||||
});
|
});
|
||||||
|
|||||||
236
docs/shared/packages/remix/remix-plugin.md
Normal file
236
docs/shared/packages/remix/remix-plugin.md
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
The Nx Plugin for Remix contains executors, generators, and utilities for managing Remix applications and libraries
|
||||||
|
within an Nx workspace. It provides:
|
||||||
|
|
||||||
|
- Integration with libraries such as Storybook, Jest, Vitest and Cypress.
|
||||||
|
- Generators to help scaffold code quickly, including:
|
||||||
|
- Libraries, both internal to your codebase and publishable to npm
|
||||||
|
- Routes
|
||||||
|
- Loaders
|
||||||
|
- Actions
|
||||||
|
- Meta
|
||||||
|
- Utilities for automatic workspace refactoring.
|
||||||
|
|
||||||
|
## Setting up the Remix plugin
|
||||||
|
|
||||||
|
{% callout type="note" title="Keep Nx Package Versions In Sync" %}
|
||||||
|
Make sure to install the `@nx/remix` version that matches the version of `nx` in your repository. If the version
|
||||||
|
numbers get out of sync, you can encounter some difficult to debug errors. You
|
||||||
|
can [fix Nx version mismatches with this recipe](/recipes/tips-n-tricks/keep-nx-versions-in-sync).
|
||||||
|
{% /callout %}
|
||||||
|
|
||||||
|
Adding the Remix plugin to an existing Nx workspace can be done with the following:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn add -D @nx/remix
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install -D @nx/remix
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the Remix Plugin
|
||||||
|
|
||||||
|
## Generate a Remix Application
|
||||||
|
|
||||||
|
{% callout type="note" title="Directory Flag Behavior Changes" %}
|
||||||
|
The command below uses the `as-provided` directory flag behavior, which is the default in Nx 16.8.0. If you're on an earlier version of Nx or using the `derived` option, omit the `--directory` flag. See the [as-provided vs. derived documentation](/deprecated/as-provided-vs-derived) for more details.
|
||||||
|
{% /callout %}
|
||||||
|
|
||||||
|
```{% command="nx g @nx/remix:app myapp --directory=apps/myapp" path="~/acme" %}
|
||||||
|
> NX Generating @nx/remix:application
|
||||||
|
|
||||||
|
✔ What unit test runner should be used? · vitest
|
||||||
|
|
||||||
|
CREATE apps/myapp/project.json
|
||||||
|
UPDATE package.json
|
||||||
|
CREATE apps/myapp/README.md
|
||||||
|
CREATE apps/myapp/app/root.tsx
|
||||||
|
CREATE apps/myapp/app/routes/_index.tsx
|
||||||
|
CREATE apps/myapp/public/favicon.ico
|
||||||
|
CREATE apps/myapp/remix.config.js
|
||||||
|
CREATE apps/myapp/remix.env.d.ts
|
||||||
|
CREATE apps/myapp/tsconfig.json
|
||||||
|
CREATE apps/myapp/.gitignore
|
||||||
|
CREATE apps/myapp/package.json
|
||||||
|
UPDATE nx.json
|
||||||
|
CREATE tsconfig.base.json
|
||||||
|
CREATE .prettierrc
|
||||||
|
CREATE .prettierignore
|
||||||
|
UPDATE .vscode/extensions.json
|
||||||
|
CREATE apps/myapp/vite.config.ts
|
||||||
|
CREATE apps/myapp/tsconfig.spec.json
|
||||||
|
CREATE apps/myapp/test-setup.ts
|
||||||
|
CREATE apps/myapp-e2e/cypress.config.ts
|
||||||
|
CREATE apps/myapp-e2e/src/e2e/app.cy.ts
|
||||||
|
CREATE apps/myapp-e2e/src/fixtures/example.json
|
||||||
|
CREATE apps/myapp-e2e/src/support/commands.ts
|
||||||
|
CREATE apps/myapp-e2e/src/support/e2e.ts
|
||||||
|
CREATE apps/myapp-e2e/tsconfig.json
|
||||||
|
CREATE apps/myapp-e2e/project.json
|
||||||
|
CREATE .eslintrc.json
|
||||||
|
CREATE .eslintignore
|
||||||
|
CREATE apps/myapp-e2e/.eslintrc.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build, Serve and Test your Application
|
||||||
|
|
||||||
|
1. To build your application run:
|
||||||
|
|
||||||
|
```{% command="nx build myapp" path="~/acme" %}
|
||||||
|
> nx run myapp:build
|
||||||
|
|
||||||
|
Building Remix app in production mode...
|
||||||
|
|
||||||
|
Built in 857ms
|
||||||
|
|
||||||
|
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||||
|
|
||||||
|
> NX Successfully ran target build for project myapp (3s)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. To serve your application for use during development run:
|
||||||
|
|
||||||
|
```{% command="nx serve myapp" path="~/acme" %}
|
||||||
|
> nx run myapp:serve
|
||||||
|
|
||||||
|
💿 Building...
|
||||||
|
💿 Rebuilt in 377ms
|
||||||
|
Remix App Server started at http://localhost:3000 (http://192.168.0.14:3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. To test the application using vitest run:
|
||||||
|
|
||||||
|
```{% command="nx test myapp" path="~/acme" %}
|
||||||
|
> nx run myapp:test
|
||||||
|
|
||||||
|
RUN v0.31.4 /Users/columferry/dev/nrwl/issues/remixguide/acme/apps/myapp
|
||||||
|
stderr | app/routes/index.spec.ts > test > should render
|
||||||
|
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
|
||||||
|
✓ app/routes/index.spec.ts (1 test) 10ms
|
||||||
|
Test Files 1 passed (1)
|
||||||
|
Tests 1 passed (1)
|
||||||
|
Start at 16:15:45
|
||||||
|
Duration 1.20s (transform 51ms, setup 139ms, collect 180ms, tests 10ms, environment 379ms, prepare 103ms)
|
||||||
|
|
||||||
|
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||||
|
|
||||||
|
> NX Successfully ran target test for project myapp (2s)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generating an Nx Library
|
||||||
|
|
||||||
|
When developing your application, it often makes sense to split your codebase into smaller more focused libraries.
|
||||||
|
|
||||||
|
To generate a library to use in your Remix application run:
|
||||||
|
|
||||||
|
```{% command="nx g @nx/remix:lib login --directory=libs/login" path="~/acme" %}
|
||||||
|
> NX Generating @nx/remix:library
|
||||||
|
|
||||||
|
✔ What test runner should be used? · vitest
|
||||||
|
UPDATE nx.json
|
||||||
|
UPDATE package.json
|
||||||
|
CREATE babel.config.json
|
||||||
|
CREATE libs/login/project.json
|
||||||
|
CREATE libs/login/.eslintrc.json
|
||||||
|
CREATE libs/login/README.md
|
||||||
|
CREATE libs/login/src/index.ts
|
||||||
|
CREATE libs/login/tsconfig.lib.json
|
||||||
|
CREATE libs/login/tsconfig.json
|
||||||
|
CREATE libs/login/vite.config.ts
|
||||||
|
CREATE libs/login/tsconfig.spec.json
|
||||||
|
CREATE libs/login/src/lib/login.module.css
|
||||||
|
CREATE libs/login/src/lib/login.spec.tsx
|
||||||
|
CREATE libs/login/src/lib/login.tsx
|
||||||
|
UPDATE tsconfig.base.json
|
||||||
|
CREATE libs/login/src/test-setup.ts
|
||||||
|
CREATE libs/login/src/server.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then use the library by importing one of the exports into your application:
|
||||||
|
|
||||||
|
`apps/myapp/app/routes/index.tsx`
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Login } from '@acme/login';
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Login />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run test on your library:
|
||||||
|
|
||||||
|
`nx test login`
|
||||||
|
|
||||||
|
## Generating a Route
|
||||||
|
|
||||||
|
To generate a route for your application:
|
||||||
|
|
||||||
|
```{% command="nx g @nx/remix:route admin --path=apps/myapp/app/routes" path="~/acme" %}
|
||||||
|
> NX Generating @nx/remix:route
|
||||||
|
|
||||||
|
CREATE apps/myapp/app/routes/admin.tsx
|
||||||
|
CREATE apps/myapp/app/styles/admin.css
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using a loader from your Library
|
||||||
|
|
||||||
|
To use a Route Loader where the logic lives in your library, follow the steps below.
|
||||||
|
|
||||||
|
1. Generate a loader for your route:
|
||||||
|
|
||||||
|
```{% command="nx g @nx/remix:loader admin --path=apps/myapp/app/routes" path="~/acme" %}
|
||||||
|
> NX Generating @nx/remix:loader
|
||||||
|
|
||||||
|
UPDATE apps/myapp/app/routes/admin.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add a new file in your `login` lib
|
||||||
|
|
||||||
|
`libs/login/src/lib/admin/admin.loader.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { json, LoaderFunctionArgs } from '@remix-run/node';
|
||||||
|
|
||||||
|
export const adminLoader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
|
return json({
|
||||||
|
message: 'Hello, world!',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Export the function from the `libs/login/src/server.ts` file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export * from './lib/admin/admin.loader';
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Use the loader in your `apps/myapp/app/routes/admin.tsx`
|
||||||
|
|
||||||
|
Replace the default loader code:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
|
return json({
|
||||||
|
message: 'Hello, world!',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
with
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { adminLoader } from '@acme/login/server';
|
||||||
|
|
||||||
|
export const loader = adminLoader;
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitHub Repository with Example
|
||||||
|
|
||||||
|
You can see an example of an Nx Workspace using Remix by clicking below.
|
||||||
|
|
||||||
|
{% github-repository url="https://github.com/nrwl/nx-recipes/tree/main/remix" /%}
|
||||||
@ -612,6 +612,28 @@
|
|||||||
- [component-story](/nx-api/react-native/generators/component-story)
|
- [component-story](/nx-api/react-native/generators/component-story)
|
||||||
- [stories](/nx-api/react-native/generators/stories)
|
- [stories](/nx-api/react-native/generators/stories)
|
||||||
- [upgrade-native](/nx-api/react-native/generators/upgrade-native)
|
- [upgrade-native](/nx-api/react-native/generators/upgrade-native)
|
||||||
|
- [remix](/nx-api/remix)
|
||||||
|
- [documents](/nx-api/remix/documents)
|
||||||
|
- [Overview](/nx-api/remix/documents/overview)
|
||||||
|
- [executors](/nx-api/remix/executors)
|
||||||
|
- [serve](/nx-api/remix/executors/serve)
|
||||||
|
- [build](/nx-api/remix/executors/build)
|
||||||
|
- [generators](/nx-api/remix/generators)
|
||||||
|
- [preset](/nx-api/remix/generators/preset)
|
||||||
|
- [setup](/nx-api/remix/generators/setup)
|
||||||
|
- [application](/nx-api/remix/generators/application)
|
||||||
|
- [cypress-component-configuration](/nx-api/remix/generators/cypress-component-configuration)
|
||||||
|
- [library](/nx-api/remix/generators/library)
|
||||||
|
- [route](/nx-api/remix/generators/route)
|
||||||
|
- [resource-route](/nx-api/remix/generators/resource-route)
|
||||||
|
- [action](/nx-api/remix/generators/action)
|
||||||
|
- [loader](/nx-api/remix/generators/loader)
|
||||||
|
- [style](/nx-api/remix/generators/style)
|
||||||
|
- [setup-tailwind](/nx-api/remix/generators/setup-tailwind)
|
||||||
|
- [storybook-configuration](/nx-api/remix/generators/storybook-configuration)
|
||||||
|
- [meta](/nx-api/remix/generators/meta)
|
||||||
|
- [error-boundary](/nx-api/remix/generators/error-boundary)
|
||||||
|
- [cypress](/nx-api/remix/generators/cypress)
|
||||||
- [rollup](/nx-api/rollup)
|
- [rollup](/nx-api/rollup)
|
||||||
- [executors](/nx-api/rollup/executors)
|
- [executors](/nx-api/rollup/executors)
|
||||||
- [rollup](/nx-api/rollup/executors/rollup)
|
- [rollup](/nx-api/rollup/executors/rollup)
|
||||||
|
|||||||
13
e2e/remix/jest.config.ts
Normal file
13
e2e/remix/jest.config.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
displayName: 'e2e-remix',
|
||||||
|
preset: '../../jest.preset.js',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
|
||||||
|
maxWorkers: 1,
|
||||||
|
globals: {},
|
||||||
|
globalSetup: '../utils/global-setup.ts',
|
||||||
|
globalTeardown: '../utils/global-teardown.ts',
|
||||||
|
};
|
||||||
10
e2e/remix/project.json
Normal file
10
e2e/remix/project.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "e2e-remix",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "e2e/remix",
|
||||||
|
"projectType": "application",
|
||||||
|
"targets": {
|
||||||
|
"e2e": {}
|
||||||
|
},
|
||||||
|
"implicitDependencies": ["remix"]
|
||||||
|
}
|
||||||
175
e2e/remix/tests/nx-remix.test.ts
Normal file
175
e2e/remix/tests/nx-remix.test.ts
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import {
|
||||||
|
cleanupProject,
|
||||||
|
killPorts,
|
||||||
|
newProject,
|
||||||
|
runCLI,
|
||||||
|
checkFilesExist,
|
||||||
|
readJson,
|
||||||
|
uniq,
|
||||||
|
updateFile,
|
||||||
|
runCommandAsync,
|
||||||
|
} from '@nx/e2e/utils';
|
||||||
|
|
||||||
|
describe('remix e2e', () => {
|
||||||
|
let proj: string;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
proj = newProject({ packages: ['@nx/remix'] });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
killPorts();
|
||||||
|
cleanupProject();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a standalone remix app', async () => {
|
||||||
|
const appName = uniq('remix');
|
||||||
|
runCLI(`generate @nx/remix:preset --name ${appName} --verbose`);
|
||||||
|
|
||||||
|
// Can import using ~ alias like a normal Remix setup.
|
||||||
|
updateFile(`app/foo.ts`, `export const foo = 'foo';`);
|
||||||
|
updateFile(
|
||||||
|
`app/routes/index.tsx`,
|
||||||
|
`
|
||||||
|
import { foo } from '~/foo';
|
||||||
|
export default function Index() {
|
||||||
|
return (
|
||||||
|
<h1>{foo}</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = runCLI(`build ${appName}`);
|
||||||
|
expect(result).toContain('Successfully ran target build');
|
||||||
|
}, 120_000);
|
||||||
|
|
||||||
|
it('should create app', async () => {
|
||||||
|
const plugin = uniq('remix');
|
||||||
|
runCLI(`generate @nx/remix:app ${plugin}`);
|
||||||
|
|
||||||
|
const buildResult = runCLI(`build ${plugin}`);
|
||||||
|
expect(buildResult).toContain('Successfully ran target build');
|
||||||
|
|
||||||
|
const testResult = runCLI(`test ${plugin}`);
|
||||||
|
expect(testResult).toContain('Successfully ran target test');
|
||||||
|
}, 120000);
|
||||||
|
|
||||||
|
describe('--directory', () => {
|
||||||
|
it('should create src in the specified directory --projectNameAndRootFormat=derived', async () => {
|
||||||
|
const plugin = uniq('remix');
|
||||||
|
const appName = `sub-${plugin}`;
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/remix:app ${plugin} --directory=sub --projectNameAndRootFormat=derived --rootProject=false`
|
||||||
|
);
|
||||||
|
const project = readJson(`sub/${plugin}/project.json`);
|
||||||
|
expect(project.targets.build.options.outputPath).toEqual(
|
||||||
|
`dist/sub/${plugin}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = runCLI(`build ${appName}`);
|
||||||
|
expect(result).toContain('Successfully ran target build');
|
||||||
|
}, 120000);
|
||||||
|
|
||||||
|
it('should create src in the specified directory --projectNameAndRootFormat=as-provided', async () => {
|
||||||
|
const plugin = uniq('remix');
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/remix:app ${plugin} --directory=subdir --projectNameAndRootFormat=as-provided --rootProject=false`
|
||||||
|
);
|
||||||
|
const project = readJson(`subdir/project.json`);
|
||||||
|
expect(project.targets.build.options.outputPath).toEqual(`dist/subdir`);
|
||||||
|
|
||||||
|
const result = runCLI(`build ${plugin}`);
|
||||||
|
expect(result).toContain('Successfully ran target build');
|
||||||
|
}, 120000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--tags', () => {
|
||||||
|
it('should add tags to the project', async () => {
|
||||||
|
const plugin = uniq('remix');
|
||||||
|
runCLI(`generate @nx/remix:app ${plugin} --tags e2etag,e2ePackage`);
|
||||||
|
const project = readJson(`${plugin}/project.json`);
|
||||||
|
expect(project.tags).toEqual(['e2etag', 'e2ePackage']);
|
||||||
|
}, 120000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--js', () => {
|
||||||
|
it('should create js app and build correctly', async () => {
|
||||||
|
const plugin = uniq('remix');
|
||||||
|
runCLI(`generate @nx/remix:app ${plugin} --js=true`);
|
||||||
|
|
||||||
|
const result = runCLI(`build ${plugin}`);
|
||||||
|
expect(result).toContain('Successfully ran target build');
|
||||||
|
}, 120000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--unitTestRunner', () => {
|
||||||
|
it('should generate a library with vitest and test correctly', async () => {
|
||||||
|
const plugin = uniq('remix');
|
||||||
|
runCLI(`generate @nx/remix:library ${plugin} --unitTestRunner=vitest`);
|
||||||
|
|
||||||
|
const result = runCLI(`test ${plugin}`);
|
||||||
|
expect(result).toContain(`Successfully ran target test`);
|
||||||
|
}, 120_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('error checking', () => {
|
||||||
|
const plugin = uniq('remix');
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
runCLI(`generate @nx/remix:app ${plugin} --tags e2etag,e2ePackage`);
|
||||||
|
}, 120000);
|
||||||
|
|
||||||
|
it('should check for un-escaped dollar signs in routes', async () => {
|
||||||
|
await expect(async () =>
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/remix:route --project ${plugin} --path my.route.$withParams.tsx`
|
||||||
|
)
|
||||||
|
).rejects.toThrow();
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/remix:route --project ${plugin} --path my.route.\\$withParams.tsx`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
checkFilesExist(`${plugin}/app/routes/my.route.$withParams.tsx`)
|
||||||
|
).not.toThrow();
|
||||||
|
}, 120000);
|
||||||
|
|
||||||
|
it('should pass un-escaped dollar signs in routes with skipChecks flag', async () => {
|
||||||
|
await runCommandAsync(
|
||||||
|
`someWeirdUseCase=route-segment && yarn nx generate @nx/remix:route --project ${plugin} --path my.route.$someWeirdUseCase.tsx --force`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
checkFilesExist(`${plugin}/app/routes/my.route.route-segment.tsx`)
|
||||||
|
).not.toThrow();
|
||||||
|
}, 120000);
|
||||||
|
|
||||||
|
it('should check for un-escaped dollar signs in resource routes', async () => {
|
||||||
|
await expect(async () =>
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/remix:resource-route --project ${plugin} --path my.route.$withParams.ts`
|
||||||
|
)
|
||||||
|
).rejects.toThrow();
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/remix:resource-route --project ${plugin} --path my.route.\\$withParams.ts`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
checkFilesExist(`${plugin}/app/routes/my.route.$withParams.ts`)
|
||||||
|
).not.toThrow();
|
||||||
|
}, 120000);
|
||||||
|
|
||||||
|
it('should pass un-escaped dollar signs in resource routes with skipChecks flag', async () => {
|
||||||
|
await runCommandAsync(
|
||||||
|
`someWeirdUseCase=route-segment && yarn nx generate @nx/remix:resource-route --project ${plugin} --path my.route.$someWeirdUseCase.ts --force`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
checkFilesExist(`${plugin}/app/routes/my.route.route-segment.ts`)
|
||||||
|
).not.toThrow();
|
||||||
|
}, 120000);
|
||||||
|
});
|
||||||
|
});
|
||||||
13
e2e/remix/tsconfig.json
Normal file
13
e2e/remix/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["node", "jest"]
|
||||||
|
},
|
||||||
|
"include": [],
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
e2e/remix/tsconfig.spec.json
Normal file
20
e2e/remix/tsconfig.spec.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"**/*.test.js",
|
||||||
|
"**/*.spec.jsx",
|
||||||
|
"**/*.test.jsx",
|
||||||
|
"**/*.d.ts",
|
||||||
|
"jest.config.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -53,6 +53,7 @@ const nxPackages = [
|
|||||||
`@nx/playwright`,
|
`@nx/playwright`,
|
||||||
`@nx/rollup`,
|
`@nx/rollup`,
|
||||||
`@nx/react`,
|
`@nx/react`,
|
||||||
|
`@nx/remix`,
|
||||||
`@nx/storybook`,
|
`@nx/storybook`,
|
||||||
`@nx/vue`,
|
`@nx/vue`,
|
||||||
`@nx/vite`,
|
`@nx/vite`,
|
||||||
|
|||||||
10
nx-dev/nx-dev/public/images/icons/remix.svg
Normal file
10
nx-dev/nx-dev/public/images/icons/remix.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(0.5,0,0,0.5,0,0)">
|
||||||
|
<g transform="matrix(1.33333,0,0,1.33333,-133,-133.667)">
|
||||||
|
<path d="M587.947,527.768C592.201,582.418 592.201,608.036 592.201,636L465.756,636C465.756,629.909 465.865,624.337 465.975,618.687C466.317,601.123 466.674,582.807 463.828,545.819C460.067,491.667 436.748,479.634 393.871,479.634L195,479.634L195,381.109L399.889,381.109C454.049,381.109 481.13,364.633 481.13,321.011C481.13,282.654 454.049,259.41 399.889,259.41L195,259.41L195,163L422.456,163C545.069,163 606,220.912 606,313.42C606,382.613 563.123,427.739 505.201,435.26C554.096,445.037 582.681,472.865 587.947,527.768Z" style="fill:rgb(18,18,18);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.33333,0,0,1.33333,-133,-133.667)">
|
||||||
|
<path d="M195,636L195,562.553L328.697,562.553C351.029,562.553 355.878,579.116 355.878,588.994L355.878,636L195,636Z" style="fill:rgb(18,18,18);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1015 B |
@ -22,6 +22,7 @@ export const iconsMap: Record<string, string> = {
|
|||||||
plugin: '/images/icons/nx.svg',
|
plugin: '/images/icons/nx.svg',
|
||||||
react: '/images/icons/react.svg',
|
react: '/images/icons/react.svg',
|
||||||
'react-native': '/images/icons/react.svg',
|
'react-native': '/images/icons/react.svg',
|
||||||
|
remix: '/images/icons/remix.svg',
|
||||||
rollup: '/images/icons/rollup.svg',
|
rollup: '/images/icons/rollup.svg',
|
||||||
rspack: '/images/icons/rspack.svg',
|
rspack: '/images/icons/rspack.svg',
|
||||||
storybook: '/images/icons/storybook.svg',
|
storybook: '/images/icons/storybook.svg',
|
||||||
|
|||||||
@ -84,6 +84,8 @@
|
|||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||||
"@pnpm/lockfile-types": "^5.0.0",
|
"@pnpm/lockfile-types": "^5.0.0",
|
||||||
"@reduxjs/toolkit": "1.9.0",
|
"@reduxjs/toolkit": "1.9.0",
|
||||||
|
"@remix-run/dev": "^2.3.0",
|
||||||
|
"@remix-run/node": "^2.3.0",
|
||||||
"@rollup/plugin-babel": "^5.3.0",
|
"@rollup/plugin-babel": "^5.3.0",
|
||||||
"@rollup/plugin-commonjs": "^20.0.0",
|
"@rollup/plugin-commonjs": "^20.0.0",
|
||||||
"@rollup/plugin-image": "^2.1.0",
|
"@rollup/plugin-image": "^2.1.0",
|
||||||
|
|||||||
11
packages-legacy/remix/README.md
Normal file
11
packages-legacy/remix/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
## @nrwl/remix has been deprecated!
|
||||||
|
|
||||||
|
@nrwl/remix has been deprecated in favor of [@nx/remix](https://www.npmjs.com/package/@nx/remix). Please use that instead.
|
||||||
|
|
||||||
|
@nrwl/remix will no longer be published in Nx v17.
|
||||||
|
|
||||||
|
<p style="text-align: center;"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx.png" width="600" alt="Nx - Smart, Fast and Extensible Build System"></p>
|
||||||
|
|
||||||
|
# Nx: Smart, Fast and Extensible Build System
|
||||||
|
|
||||||
|
Nx is a next generation build system with first class monorepo support and powerful integrations.
|
||||||
4
packages-legacy/remix/generators.json
Normal file
4
packages-legacy/remix/generators.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": ["@nx/remix"],
|
||||||
|
"schematics": {}
|
||||||
|
}
|
||||||
1
packages-legacy/remix/index.ts
Normal file
1
packages-legacy/remix/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from '@nx/remix';
|
||||||
35
packages-legacy/remix/package.json
Normal file
35
packages-legacy/remix/package.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "@nrwl/remix",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "The Remix plugin for Nx contains executors and generators for managing Remix applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Vitest, Jest, Cypress, and Storybook.\n\n- Generators for applications, libraries, routes, loaders, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/nrwl/nx.git",
|
||||||
|
"directory": "packages-legacy/remix"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Monorepo",
|
||||||
|
"Remix",
|
||||||
|
"React",
|
||||||
|
"Web",
|
||||||
|
"CLI"
|
||||||
|
],
|
||||||
|
"author": "Victor Savkin",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/nrwl/nx/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://nx.dev",
|
||||||
|
"main": "index.js",
|
||||||
|
"typings": "./index.d.ts",
|
||||||
|
"generators": "./generators.json",
|
||||||
|
"dependencies": {
|
||||||
|
"@nx/remix": "file:../../packages/remix"
|
||||||
|
},
|
||||||
|
"nx-migrations": {
|
||||||
|
"migrations": "@nx/remix/migrations.json"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
38
packages-legacy/remix/project.json
Normal file
38
packages-legacy/remix/project.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "remix-legacy",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "packages-legacy/remix",
|
||||||
|
"projectType": "library",
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"outputs": ["{workspaceRoot}/build/packages/{projectName}/README.md"],
|
||||||
|
"command": "node ./scripts/copy-readme.js react-legacy"
|
||||||
|
},
|
||||||
|
"build-base": {
|
||||||
|
"executor": "@nrwl/js:tsc",
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"options": {
|
||||||
|
"main": "packages-legacy/remix/index.ts",
|
||||||
|
"tsConfig": "packages-legacy/remix/tsconfig.json",
|
||||||
|
"outputPath": "build/packages/remix-legacy",
|
||||||
|
"updateBuildableProjectDepsInPackageJson": false,
|
||||||
|
"assets": [
|
||||||
|
"packages-legacy/remix/*.md",
|
||||||
|
{
|
||||||
|
"input": "packages-legacy/remix",
|
||||||
|
"glob": "**/*.json",
|
||||||
|
"ignore": ["**/tsconfig*.json", "project.json"],
|
||||||
|
"output": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "packages-legacy/remix",
|
||||||
|
"glob": "**/*.d.ts",
|
||||||
|
"output": "/"
|
||||||
|
},
|
||||||
|
"LICENSE"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
9
packages-legacy/remix/tsconfig.json
Normal file
9
packages-legacy/remix/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"include": ["**/*.ts"],
|
||||||
|
"files": ["index.ts"]
|
||||||
|
}
|
||||||
@ -134,6 +134,8 @@
|
|||||||
"@nrwl/react-native",
|
"@nrwl/react-native",
|
||||||
"@nx/rollup",
|
"@nx/rollup",
|
||||||
"@nrwl/rollup",
|
"@nrwl/rollup",
|
||||||
|
"@nx/remix",
|
||||||
|
"@nrwl/remix",
|
||||||
"@nx/storybook",
|
"@nx/storybook",
|
||||||
"@nrwl/storybook",
|
"@nrwl/storybook",
|
||||||
"@nrwl/tao",
|
"@nrwl/tao",
|
||||||
|
|||||||
@ -68,6 +68,10 @@ export function fetchCorePlugins(): CorePlugin[] {
|
|||||||
name: '@nx/react-native',
|
name: '@nx/react-native',
|
||||||
capabilities: 'executors,generators',
|
capabilities: 'executors,generators',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: '@nx/remix',
|
||||||
|
capabilities: 'executors,generators',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: '@nx/rollup',
|
name: '@nx/rollup',
|
||||||
capabilities: 'executors,generators',
|
capabilities: 'executors,generators',
|
||||||
|
|||||||
92
packages/remix/.eslintrc.json
Normal file
92
packages/remix/.eslintrc.json
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-var-requires": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"./package.json",
|
||||||
|
"./generators.json",
|
||||||
|
"./executors.json",
|
||||||
|
"./migrations.json"
|
||||||
|
],
|
||||||
|
"parser": "jsonc-eslint-parser",
|
||||||
|
"rules": {
|
||||||
|
"@nx/nx-plugin-checks": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["./package.json"],
|
||||||
|
"parser": "jsonc-eslint-parser",
|
||||||
|
"rules": {
|
||||||
|
"@nx/dependency-checks": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"buildTargets": ["build-base"],
|
||||||
|
"ignoredDependencies": [
|
||||||
|
"nx",
|
||||||
|
"eslint",
|
||||||
|
"prettier",
|
||||||
|
"typescript",
|
||||||
|
"react",
|
||||||
|
"@nx/workspace",
|
||||||
|
"fs-extra",
|
||||||
|
"@remix-run/dev",
|
||||||
|
"@nx/web",
|
||||||
|
"@nx/eslint",
|
||||||
|
// These are installed by ensurePackage so missing in package.json
|
||||||
|
"@nx/cypress",
|
||||||
|
"@nx/playwright",
|
||||||
|
"@nx/jest",
|
||||||
|
"@nx/rollup",
|
||||||
|
"@nx/storybook",
|
||||||
|
"@nx/vite",
|
||||||
|
"@nx/webpack",
|
||||||
|
// These are brought in by the webpack, rollup, or vite packages via init generators.
|
||||||
|
"@babel/preset-react",
|
||||||
|
"@phenomnomnominal/tsquery",
|
||||||
|
"@pmmmwh/react-refresh-webpack-plugin",
|
||||||
|
"@svgr/rollup",
|
||||||
|
"@rollup/plugin-url",
|
||||||
|
"@svgr/webpack",
|
||||||
|
"@swc/jest",
|
||||||
|
"babel-jest",
|
||||||
|
"babel-loader",
|
||||||
|
"babel-plugin-emotion",
|
||||||
|
"babel-plugin-styled-components",
|
||||||
|
"css-loader",
|
||||||
|
"file-loader",
|
||||||
|
"less-loader",
|
||||||
|
"react-refresh",
|
||||||
|
"rollup",
|
||||||
|
"sass",
|
||||||
|
"sass-loader",
|
||||||
|
"style-loader",
|
||||||
|
"stylus-loader",
|
||||||
|
"swc-loader",
|
||||||
|
"tsconfig-paths-webpack-plugin",
|
||||||
|
"url-loader",
|
||||||
|
"webpack",
|
||||||
|
"webpack-merge",
|
||||||
|
// used via the CT react plugin installed via vite plugin
|
||||||
|
"vite"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
packages/remix/README.md
Normal file
13
packages/remix/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<p style="text-align: center;"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx.png" width="600" alt="Nx - Smart Monorepos · Fast CI"></p>
|
||||||
|
|
||||||
|
{{links}}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
# Nx: Smart Monorepos · Fast CI
|
||||||
|
|
||||||
|
Nx is a build system with built-in tooling and advanced CI capabilities. It helps you maintain and scale monorepos, both locally and on CI.
|
||||||
|
|
||||||
|
This package is a [Remix plugin for Nx](https://nx.dev/packages/remix).
|
||||||
|
|
||||||
|
{{content}}
|
||||||
14
packages/remix/executors.json
Normal file
14
packages/remix/executors.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"executors": {
|
||||||
|
"serve": {
|
||||||
|
"implementation": "./src/executors/serve/serve.impl",
|
||||||
|
"schema": "./src/executors/serve/schema.json",
|
||||||
|
"description": "Serve a Remix application."
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"implementation": "./src/executors/build/build.impl",
|
||||||
|
"schema": "./src/executors/build/schema.json",
|
||||||
|
"description": "Build a Remix application."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
packages/remix/generators.json
Normal file
89
packages/remix/generators.json
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"name": "NxRemix",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"extends": ["@nx/react"],
|
||||||
|
"generators": {
|
||||||
|
"preset": {
|
||||||
|
"implementation": "./src/generators/preset/preset.impl",
|
||||||
|
"schema": "./src/generators/preset/schema.json",
|
||||||
|
"description": "Generate a new Remix workspace",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"implementation": "./src/generators/setup/setup.impl",
|
||||||
|
"schema": "./src/generators/setup/schema.json",
|
||||||
|
"description": "Setup a Remix in an existing workspace",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
"application": {
|
||||||
|
"implementation": "./src/generators/application/application.impl",
|
||||||
|
"schema": "./src/generators/application/schema.json",
|
||||||
|
"description": "Generate a new Remix application",
|
||||||
|
"aliases": ["app"],
|
||||||
|
"x-type": "application"
|
||||||
|
},
|
||||||
|
"cypress-component-configuration": {
|
||||||
|
"implementation": "./src/generators/cypress-component-configuration/cypress-component-configuration.impl",
|
||||||
|
"schema": "./src/generators/cypress-component-configuration/schema.json",
|
||||||
|
"description": "Generate a Cypress Component Testing configuration for a Remix project"
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"implementation": "./src/generators/library/library.impl",
|
||||||
|
"schema": "./src/generators/library/schema.json",
|
||||||
|
"description": "Generate a new library",
|
||||||
|
"aliases": ["lib"],
|
||||||
|
"x-type": "library"
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"implementation": "./src/generators/route/route.impl",
|
||||||
|
"schema": "./src/generators/route/schema.json",
|
||||||
|
"description": "Generate a new route"
|
||||||
|
},
|
||||||
|
"resource-route": {
|
||||||
|
"implementation": "./src/generators/resource-route/resource-route.impl",
|
||||||
|
"schema": "./src/generators/resource-route/schema.json",
|
||||||
|
"description": "Generate a new resource route"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"implementation": "./src/generators/action/action.impl",
|
||||||
|
"schema": "./src/generators/action/schema.json",
|
||||||
|
"description": "Add an action function to an existing route"
|
||||||
|
},
|
||||||
|
"loader": {
|
||||||
|
"implementation": "./src/generators/loader/loader.impl",
|
||||||
|
"schema": "./src/generators/loader/schema.json",
|
||||||
|
"description": "Add a loader function to an existing route"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"implementation": "./src/generators/style/style.impl",
|
||||||
|
"schema": "./src/generators/style/schema.json",
|
||||||
|
"description": "Generates a new stylesheet and adds it to an existing route"
|
||||||
|
},
|
||||||
|
"setup-tailwind": {
|
||||||
|
"implementation": "./src/generators/setup-tailwind/setup-tailwind.impl",
|
||||||
|
"schema": "./src/generators/setup-tailwind/schema.json",
|
||||||
|
"description": "Generates a TailwindCSS configuration for the Remix application"
|
||||||
|
},
|
||||||
|
"storybook-configuration": {
|
||||||
|
"implementation": "./src/generators/storybook-configuration/storybook-configuration.impl",
|
||||||
|
"schema": "./src/generators/storybook-configuration/schema.json",
|
||||||
|
"description": "Generates a Storybook configuration for a Remix application"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"implementation": "./src/generators/meta/meta.impl",
|
||||||
|
"schema": "./src/generators/meta/schema.json",
|
||||||
|
"description": "Add a meta function to an existing route"
|
||||||
|
},
|
||||||
|
"error-boundary": {
|
||||||
|
"implementation": "./src/generators/error-boundary/error-boundary.impl",
|
||||||
|
"schema": "./src/generators/error-boundary/schema.json",
|
||||||
|
"description": "Add an ErrorBoundary to an existing route"
|
||||||
|
},
|
||||||
|
"cypress": {
|
||||||
|
"implementation": "./src/generators/cypress/cypress.impl",
|
||||||
|
"schema": "./src/generators/cypress/schema.json",
|
||||||
|
"description": "Generate a project for testing Remix apps using Cypress"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
packages/remix/generators.ts
Normal file
14
packages/remix/generators.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export * from './src/generators/action/action.impl';
|
||||||
|
export * from './src/generators/application/application.impl';
|
||||||
|
export * from './src/generators/cypress-component-configuration/cypress-component-configuration.impl';
|
||||||
|
export * from './src/generators/cypress/cypress.impl';
|
||||||
|
export * from './src/generators/error-boundary/error-boundary.impl';
|
||||||
|
export * from './src/generators/library/library.impl';
|
||||||
|
export * from './src/generators/loader/loader.impl';
|
||||||
|
export * from './src/generators/meta/meta.impl';
|
||||||
|
export * from './src/generators/preset/preset.impl';
|
||||||
|
export * from './src/generators/resource-route/resource-route.impl';
|
||||||
|
export * from './src/generators/route/route.impl';
|
||||||
|
export * from './src/generators/setup-tailwind/setup-tailwind.impl';
|
||||||
|
export * from './src/generators/storybook-configuration/storybook-configuration.impl';
|
||||||
|
export * from './src/generators/style/style.impl';
|
||||||
1
packages/remix/index.ts
Normal file
1
packages/remix/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { createWatchPaths } from './src/utils/create-watch-paths';
|
||||||
15
packages/remix/jest.config.ts
Normal file
15
packages/remix/jest.config.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
displayName: 'remix',
|
||||||
|
preset: '../../jest.preset.js',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': [
|
||||||
|
'ts-jest',
|
||||||
|
{
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||||
|
coverageDirectory: '../../coverage/packages/remix',
|
||||||
|
};
|
||||||
54
packages/remix/migrations.json
Normal file
54
packages/remix/migrations.json
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"generators": {},
|
||||||
|
"packageJsonUpdates": {
|
||||||
|
"17.2.1": {
|
||||||
|
"version": "17.2.1-beta.0",
|
||||||
|
"packages": {
|
||||||
|
"@remix-run/node": {
|
||||||
|
"version": "^2.3.0",
|
||||||
|
"alwaysAddToPackageJson": true
|
||||||
|
},
|
||||||
|
"@remix-run/react": {
|
||||||
|
"version": "^2.3.0",
|
||||||
|
"alwaysAddToPackageJson": true
|
||||||
|
},
|
||||||
|
"@remix-run/serve": {
|
||||||
|
"version": "^2.3.0",
|
||||||
|
"alwaysAddToPackageJson": true
|
||||||
|
},
|
||||||
|
"@remix-run/dev": {
|
||||||
|
"version": "^2.3.0",
|
||||||
|
"alwaysAddToPackageJson": true
|
||||||
|
},
|
||||||
|
"@remix-run/css-bundle": {
|
||||||
|
"version": "^2.3.0",
|
||||||
|
"alwaysAddToPackageJson": true
|
||||||
|
},
|
||||||
|
"@remix-run/eslint-config": {
|
||||||
|
"version": "^2.3.0",
|
||||||
|
"alwaysAddToPackageJson": true
|
||||||
|
},
|
||||||
|
"isbot": {
|
||||||
|
"version": "^3.6.8",
|
||||||
|
"alwaysAddToPackageJson": true
|
||||||
|
},
|
||||||
|
"eslint": {
|
||||||
|
"version": "^8.38.0",
|
||||||
|
"alwaysAddToPackageJson": true
|
||||||
|
},
|
||||||
|
"@testing-library/react": {
|
||||||
|
"version": "^14.1.2",
|
||||||
|
"alwaysAddToPackageJson": false
|
||||||
|
},
|
||||||
|
"@testing-library/jest-dom": {
|
||||||
|
"version": "^6.1.4",
|
||||||
|
"alwaysAddToPackageJson": false
|
||||||
|
},
|
||||||
|
"@testing-library/user-event": {
|
||||||
|
"version": "^14.5.1",
|
||||||
|
"alwaysAddToPackageJson": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
packages/remix/nx-remix.png
Normal file
BIN
packages/remix/nx-remix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 201 KiB |
41
packages/remix/package.json
Normal file
41
packages/remix/package.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "@nx/remix",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "The Remix plugin for Nx contains executors and generators for managing Remix applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Vitest, Jest, Cypress, and Storybook.\n\n- Generators for applications, libraries, routes, loaders, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/nrwl/nx.git",
|
||||||
|
"directory": "packages/remix"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Monorepo",
|
||||||
|
"Remix",
|
||||||
|
"React",
|
||||||
|
"Web",
|
||||||
|
"CLI"
|
||||||
|
],
|
||||||
|
"author": "Victor Savkin",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/nrwl/nx/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://nx.dev",
|
||||||
|
"main": "./index.js",
|
||||||
|
"typings": "./index.d.ts",
|
||||||
|
"generators": "./generators.json",
|
||||||
|
"executors": "./executors.json",
|
||||||
|
"nx-migrations": {
|
||||||
|
"migrations": "./migrations.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nx/devkit": "file:../devkit",
|
||||||
|
"@nx/js": "file:../js",
|
||||||
|
"@nx/react": "file:../react",
|
||||||
|
"tslib": "^2.3.1",
|
||||||
|
"@phenomnomnominal/tsquery": "~5.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
85
packages/remix/plugins/component-testing/index.ts
Normal file
85
packages/remix/plugins/component-testing/index.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { nxBaseCypressPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
import { joinPathFragments, workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
|
||||||
|
type ViteDevServer = {
|
||||||
|
framework: 'react';
|
||||||
|
bundler: 'vite';
|
||||||
|
viteConfig?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remix nx preset for Cypress Component Testing
|
||||||
|
*
|
||||||
|
* This preset contains the base configuration
|
||||||
|
* for your component tests that nx recommends.
|
||||||
|
* including a devServer that supports nx workspaces.
|
||||||
|
* you can easily extend this within your cypress config via spreading the preset
|
||||||
|
* @example
|
||||||
|
* export default defineConfig({
|
||||||
|
* component: {
|
||||||
|
* ...nxComponentTestingPreset(__dirname)
|
||||||
|
* // add your own config here
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* @param pathToConfig will be used for loading project options and to construct the output paths for videos and screenshots
|
||||||
|
*/
|
||||||
|
export function nxComponentTestingPreset(pathToConfig: string): {
|
||||||
|
specPattern: string;
|
||||||
|
devServer: ViteDevServer;
|
||||||
|
videosFolder: string;
|
||||||
|
screenshotsFolder: string;
|
||||||
|
chromeWebSecurity: boolean;
|
||||||
|
} {
|
||||||
|
const normalizedProjectRootPath = ['.ts', '.js'].some((ext) =>
|
||||||
|
pathToConfig.endsWith(ext)
|
||||||
|
)
|
||||||
|
? pathToConfig
|
||||||
|
: dirname(pathToConfig);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...nxBaseCypressPreset(pathToConfig),
|
||||||
|
specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',
|
||||||
|
devServer: {
|
||||||
|
...({ framework: 'react', bundler: 'vite' } as const),
|
||||||
|
viteConfig: async () => {
|
||||||
|
const viteConfigPath = findViteConfig(normalizedProjectRootPath);
|
||||||
|
|
||||||
|
const { mergeConfig, loadConfigFromFile, searchForWorkspaceRoot } =
|
||||||
|
await import('vite');
|
||||||
|
|
||||||
|
const resolved = await loadConfigFromFile(
|
||||||
|
{
|
||||||
|
mode: 'watch',
|
||||||
|
command: 'serve',
|
||||||
|
},
|
||||||
|
viteConfigPath
|
||||||
|
);
|
||||||
|
return mergeConfig(resolved.config, {
|
||||||
|
server: {
|
||||||
|
fs: {
|
||||||
|
allow: [
|
||||||
|
searchForWorkspaceRoot(normalizedProjectRootPath),
|
||||||
|
workspaceRoot,
|
||||||
|
joinPathFragments(workspaceRoot, 'node_modules/vite'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function findViteConfig(projectRootFullPath: string): string {
|
||||||
|
const allowsExt = ['js', 'mjs', 'ts', 'cjs', 'mts', 'cts'];
|
||||||
|
|
||||||
|
for (const ext of allowsExt) {
|
||||||
|
if (existsSync(join(projectRootFullPath, `vite.config.${ext}`))) {
|
||||||
|
return join(projectRootFullPath, `vite.config.${ext}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
packages/remix/project.json
Normal file
53
packages/remix/project.json
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "remix",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "packages/remix/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"command": "node ./scripts/copy-readme.js remix",
|
||||||
|
"outputs": ["{workspaceRoot}/build/packages/remix"]
|
||||||
|
},
|
||||||
|
"lint": {},
|
||||||
|
"test": {},
|
||||||
|
"build-base": {
|
||||||
|
"executor": "@nx/js:tsc",
|
||||||
|
"outputs": ["{options.outputPath}"],
|
||||||
|
"options": {
|
||||||
|
"outputPath": "build/packages/remix",
|
||||||
|
"tsConfig": "packages/remix/tsconfig.lib.json",
|
||||||
|
"packageJson": "packages/remix/package.json",
|
||||||
|
"main": "packages/remix/index.ts",
|
||||||
|
"generateExportsField": true,
|
||||||
|
"additionalEntryPoints": [
|
||||||
|
"{projectRoot}/{executors,generators,migrations}.json",
|
||||||
|
"{projectRoot}/generators.ts"
|
||||||
|
],
|
||||||
|
"assets": [
|
||||||
|
"packages/remix/*.md",
|
||||||
|
{
|
||||||
|
"input": "./packages/remix/src",
|
||||||
|
"glob": "**/!(*.ts)",
|
||||||
|
"output": "./src"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "./packages/remix/src",
|
||||||
|
"glob": "**/*.d.ts",
|
||||||
|
"output": "./src"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "./packages/remix",
|
||||||
|
"glob": "**.json",
|
||||||
|
"output": ".",
|
||||||
|
"ignore": ["**/tsconfig*.json", "project.json", ".eslintrc.json"]
|
||||||
|
},
|
||||||
|
"LICENSE"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"add-extra-dependencies": {
|
||||||
|
"command": "node ./scripts/add-dependency-to-build.js remix @nrwl/remix"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
141
packages/remix/src/executors/build/build.impl.ts
Normal file
141
packages/remix/src/executors/build/build.impl.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import {
|
||||||
|
detectPackageManager,
|
||||||
|
logger,
|
||||||
|
readJsonFile,
|
||||||
|
writeJsonFile,
|
||||||
|
type ExecutorContext,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { createLockFile, createPackageJson, getLockFileName } from '@nx/js';
|
||||||
|
import { directoryExists } from '@nx/workspace/src/utilities/fileutils';
|
||||||
|
import { fork } from 'child_process';
|
||||||
|
import { copySync, mkdir, writeFileSync } from 'fs-extra';
|
||||||
|
import { type PackageJson } from 'nx/src/utils/package-json';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { type RemixBuildSchema } from './schema';
|
||||||
|
|
||||||
|
function buildRemixBuildArgs(options: RemixBuildSchema) {
|
||||||
|
const args = ['build'];
|
||||||
|
|
||||||
|
if (options.sourcemap) {
|
||||||
|
args.push(`--sourcemap`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runBuild(
|
||||||
|
options: RemixBuildSchema,
|
||||||
|
context: ExecutorContext
|
||||||
|
): Promise<void> {
|
||||||
|
const projectRoot = context.projectGraph.nodes[context.projectName].data.root;
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const remixBin = require.resolve('@remix-run/dev/dist/cli');
|
||||||
|
const args = buildRemixBuildArgs(options);
|
||||||
|
const p = fork(remixBin, args, {
|
||||||
|
cwd: join(context.root, projectRoot),
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
p.on('exit', (code) => {
|
||||||
|
if (code === 0) resolve();
|
||||||
|
else reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function buildExecutor(
|
||||||
|
options: RemixBuildSchema,
|
||||||
|
context: ExecutorContext
|
||||||
|
) {
|
||||||
|
const projectRoot = context.projectGraph.nodes[context.projectName].data.root;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runBuild(options, context);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`Error occurred while trying to build application. See above for more details.`
|
||||||
|
);
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!directoryExists(options.outputPath)) {
|
||||||
|
mkdir(options.outputPath);
|
||||||
|
}
|
||||||
|
let packageJson: PackageJson;
|
||||||
|
if (options.generatePackageJson) {
|
||||||
|
packageJson = createPackageJson(context.projectName, context.projectGraph, {
|
||||||
|
target: context.targetName,
|
||||||
|
root: context.root,
|
||||||
|
isProduction: !options.includeDevDependenciesInPackageJson, // By default we remove devDependencies since this is a production build.
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update `package.json` to reflect how users should run the build artifacts
|
||||||
|
packageJson.scripts ??= {};
|
||||||
|
// Don't override existing custom script since project may have its own server.
|
||||||
|
if (!packageJson.scripts.start) {
|
||||||
|
packageJson.scripts['start'] = 'remix-serve ./build';
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePackageJson(packageJson, context);
|
||||||
|
writeJsonFile(`${options.outputPath}/package.json`, packageJson);
|
||||||
|
} else {
|
||||||
|
packageJson = readJsonFile(join(projectRoot, 'package.json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.generateLockfile) {
|
||||||
|
const packageManager = detectPackageManager(context.root);
|
||||||
|
const lockFile = createLockFile(
|
||||||
|
packageJson,
|
||||||
|
context.projectGraph,
|
||||||
|
packageManager
|
||||||
|
);
|
||||||
|
writeFileSync(
|
||||||
|
`${options.outputPath}/${getLockFileName(packageManager)}`,
|
||||||
|
lockFile,
|
||||||
|
{
|
||||||
|
encoding: 'utf-8',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If output path is different from source path, then copy over the config and public files.
|
||||||
|
// This is the default behavior when running `nx build <app>`.
|
||||||
|
if (options.outputPath.replace(/\/$/, '') !== projectRoot) {
|
||||||
|
copySync(join(projectRoot, 'public'), join(options.outputPath, 'public'), {
|
||||||
|
dereference: true,
|
||||||
|
});
|
||||||
|
copySync(join(projectRoot, 'build'), join(options.outputPath, 'build'), {
|
||||||
|
dereference: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePackageJson(packageJson: PackageJson, context: ExecutorContext) {
|
||||||
|
if (!packageJson.scripts) {
|
||||||
|
packageJson.scripts = {};
|
||||||
|
}
|
||||||
|
if (!packageJson.scripts.start) {
|
||||||
|
packageJson.scripts.start = 'remix-serve build';
|
||||||
|
}
|
||||||
|
|
||||||
|
packageJson.dependencies ??= {};
|
||||||
|
|
||||||
|
// These are always required for a production Remix app to run.
|
||||||
|
const requiredPackages = [
|
||||||
|
'react',
|
||||||
|
'react-dom',
|
||||||
|
'isbot',
|
||||||
|
'@remix-run/css-bundle',
|
||||||
|
'@remix-run/node',
|
||||||
|
'@remix-run/react',
|
||||||
|
'@remix-run/serve',
|
||||||
|
'@remix-run/dev',
|
||||||
|
];
|
||||||
|
for (const pkg of requiredPackages) {
|
||||||
|
const externalNode = context.projectGraph.externalNodes[`npm:${pkg}`];
|
||||||
|
if (externalNode) {
|
||||||
|
packageJson.dependencies[pkg] ??= externalNode.data.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/remix/src/executors/build/schema.d.ts
vendored
Normal file
7
packages/remix/src/executors/build/schema.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface RemixBuildSchema {
|
||||||
|
outputPath: string;
|
||||||
|
includeDevDependenciesInPackageJson?: boolean;
|
||||||
|
generatePackageJson?: boolean;
|
||||||
|
generateLockfile?: boolean;
|
||||||
|
sourcemap?: boolean;
|
||||||
|
}
|
||||||
38
packages/remix/src/executors/build/schema.json
Normal file
38
packages/remix/src/executors/build/schema.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"outputCapture": "pipe",
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"cli": "nx",
|
||||||
|
"title": "Remix Build",
|
||||||
|
"description": "Build a Remix app.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"outputPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The output path of the generated files.",
|
||||||
|
"x-completion-type": "directory",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"includeDevDependenciesInPackageJson": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Include `devDependencies` in the generated package.json file. By default only production `dependencies` are included.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"generatePackageJson": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate package.json file in the output folder.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"generateLockfile": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate a lockfile (e.g. package-lock.json) that matches the workspace lockfile to ensure package versions match.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"sourcemap": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate source maps for production.",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["outputPath"]
|
||||||
|
}
|
||||||
9
packages/remix/src/executors/serve/schema.d.ts
vendored
Normal file
9
packages/remix/src/executors/serve/schema.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface RemixServeSchema {
|
||||||
|
port: number;
|
||||||
|
devServerPort?: number;
|
||||||
|
debug?: boolean;
|
||||||
|
command?: string;
|
||||||
|
manual?: boolean;
|
||||||
|
tlsKey?: string;
|
||||||
|
tlsCert?: string;
|
||||||
|
}
|
||||||
41
packages/remix/src/executors/serve/schema.json
Normal file
41
packages/remix/src/executors/serve/schema.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"outputCapture": "pipe",
|
||||||
|
"cli": "nx",
|
||||||
|
"title": "Remix Serve",
|
||||||
|
"description": "Serve a Remix app.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"port": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Set PORT environment variable that can be used to serve the Remix application.",
|
||||||
|
"default": 4200
|
||||||
|
},
|
||||||
|
"devServerPort": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Port to start the dev server on."
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Attach a Node.js inspector.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Command used to run your app server."
|
||||||
|
},
|
||||||
|
"manual": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Enable manual mode",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"tlsKey": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to TLS key (key.pem)."
|
||||||
|
},
|
||||||
|
"tlsCert": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to TLS certificate (cert.pem)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
packages/remix/src/executors/serve/serve.impl.ts
Normal file
95
packages/remix/src/executors/serve/serve.impl.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import { workspaceRoot, type ExecutorContext } from '@nx/devkit';
|
||||||
|
import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable';
|
||||||
|
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||||
|
import { fork } from 'node:child_process';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { type RemixServeSchema } from './schema';
|
||||||
|
|
||||||
|
function normalizeOptions(schema: RemixServeSchema) {
|
||||||
|
return {
|
||||||
|
...schema,
|
||||||
|
port: schema.port ?? 4200,
|
||||||
|
debug: schema.debug ?? false,
|
||||||
|
manual: schema.manual ?? false,
|
||||||
|
} as RemixServeSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRemixDevArgs(options: RemixServeSchema) {
|
||||||
|
const args = [];
|
||||||
|
|
||||||
|
if (options.command) {
|
||||||
|
args.push(`--command=${options.command}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.devServerPort) {
|
||||||
|
args.push(`--port=${options.devServerPort}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.debug) {
|
||||||
|
args.push(`--debug`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.manual) {
|
||||||
|
args.push(`--manual`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.tlsKey) {
|
||||||
|
args.push(`--tls-key=${options.tlsKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.tlsCert) {
|
||||||
|
args.push(`--tls-cert=${options.tlsCert}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function* serveExecutor(
|
||||||
|
schema: RemixServeSchema,
|
||||||
|
context: ExecutorContext
|
||||||
|
) {
|
||||||
|
const options = normalizeOptions(schema);
|
||||||
|
const projectRoot = context.workspace.projects[context.projectName].root;
|
||||||
|
|
||||||
|
const remixBin = require.resolve('@remix-run/dev/dist/cli');
|
||||||
|
const args = buildRemixDevArgs(options);
|
||||||
|
// Cast to any to overwrite NODE_ENV
|
||||||
|
(process.env as any).NODE_ENV = process.env.NODE_ENV
|
||||||
|
? process.env.NODE_ENV
|
||||||
|
: 'development';
|
||||||
|
process.env.PORT = `${options.port}`;
|
||||||
|
|
||||||
|
yield* createAsyncIterable<{ success: boolean; baseUrl: string }>(
|
||||||
|
async ({ done, next, error }) => {
|
||||||
|
const server = fork(remixBin, ['dev', ...args], {
|
||||||
|
cwd: join(workspaceRoot, projectRoot),
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
server.once('exit', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
done();
|
||||||
|
} else {
|
||||||
|
error(new Error(`Remix app exited with code ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const killServer = () => {
|
||||||
|
if (server.connected) {
|
||||||
|
server.kill('SIGTERM');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
process.on('exit', () => killServer());
|
||||||
|
process.on('SIGINT', () => killServer());
|
||||||
|
process.on('SIGTERM', () => killServer());
|
||||||
|
process.on('SIGHUP', () => killServer());
|
||||||
|
|
||||||
|
await waitForPortOpen(options.port);
|
||||||
|
|
||||||
|
next({
|
||||||
|
success: true,
|
||||||
|
baseUrl: `http://localhost:${options.port}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
90
packages/remix/src/generators/action/action.impl.spec.ts
Normal file
90
packages/remix/src/generators/action/action.impl.spec.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { Tree } from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import applicationGenerator from '../application/application.impl';
|
||||||
|
import routeGenerator from '../route/route.impl';
|
||||||
|
import actionGenerator from './action.impl';
|
||||||
|
|
||||||
|
describe('action', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
tree.write('.gitignore', `/node_modules/dist`);
|
||||||
|
|
||||||
|
await applicationGenerator(tree, { name: 'demo' });
|
||||||
|
await routeGenerator(tree, {
|
||||||
|
path: 'example',
|
||||||
|
project: 'demo',
|
||||||
|
style: 'none',
|
||||||
|
loader: false,
|
||||||
|
action: false,
|
||||||
|
meta: false,
|
||||||
|
skipChecks: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: 'apps/demo/app/routes/example.tsx',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'example',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'example.tsx',
|
||||||
|
},
|
||||||
|
].forEach((config) => {
|
||||||
|
describe(`Generating action using path ${config.path}`, () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await actionGenerator(tree, {
|
||||||
|
path: config.path,
|
||||||
|
// path: 'apps/demo/app/routes/example.tsx',
|
||||||
|
project: 'demo',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should add imports', async () => {
|
||||||
|
const content = tree.read('apps/demo/app/routes/example.tsx', 'utf-8');
|
||||||
|
expect(content).toMatch(`import { json } from '@remix-run/node';`);
|
||||||
|
expect(content).toMatch(
|
||||||
|
`import type { ActionFunctionArgs } from '@remix-run/node';`
|
||||||
|
);
|
||||||
|
expect(content).toMatch(
|
||||||
|
`import { useActionData } from '@remix-run/react';`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add action function', () => {
|
||||||
|
const actionFunction = `export const action = async ({ request }: ActionFunctionArgs)`;
|
||||||
|
const content = tree.read('apps/demo/app/routes/example.tsx', 'utf-8');
|
||||||
|
expect(content).toMatch(actionFunction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add useActionData to component', () => {
|
||||||
|
const useActionData = `const actionMessage = useActionData<typeof action>();`;
|
||||||
|
|
||||||
|
const content = tree.read('apps/demo/app/routes/example.tsx', 'utf-8');
|
||||||
|
expect(content).toMatch(useActionData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('--nameAndDirectoryFormat=as-provided', async () => {
|
||||||
|
// ACT
|
||||||
|
await actionGenerator(tree, {
|
||||||
|
path: 'apps/demo/app/routes/example.tsx',
|
||||||
|
});
|
||||||
|
// ASSERT
|
||||||
|
const content = tree.read('apps/demo/app/routes/example.tsx', 'utf-8');
|
||||||
|
const useActionData = `const actionMessage = useActionData<typeof action>();`;
|
||||||
|
const actionFunction = `export const action = async ({ request }: ActionFunctionArgs)`;
|
||||||
|
expect(content).toMatch(`import { json } from '@remix-run/node';`);
|
||||||
|
expect(content).toMatch(
|
||||||
|
`import type { ActionFunctionArgs } from '@remix-run/node';`
|
||||||
|
);
|
||||||
|
expect(content).toMatch(
|
||||||
|
`import { useActionData } from '@remix-run/react';`
|
||||||
|
);
|
||||||
|
expect(content).toMatch(useActionData);
|
||||||
|
expect(content).toMatch(actionFunction);
|
||||||
|
});
|
||||||
|
});
|
||||||
48
packages/remix/src/generators/action/action.impl.ts
Normal file
48
packages/remix/src/generators/action/action.impl.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { formatFiles, Tree } from '@nx/devkit';
|
||||||
|
import { insertImport } from '../../utils/insert-import';
|
||||||
|
import { insertStatementAfterImports } from '../../utils/insert-statement-after-imports';
|
||||||
|
import { insertStatementInDefaultFunction } from '../../utils/insert-statement-in-default-function';
|
||||||
|
import { resolveRemixRouteFile } from '../../utils/remix-route-utils';
|
||||||
|
import { LoaderSchema } from './schema';
|
||||||
|
|
||||||
|
export default async function (tree: Tree, schema: LoaderSchema) {
|
||||||
|
const routeFilePath =
|
||||||
|
schema.nameAndDirectoryFormat === 'as-provided'
|
||||||
|
? schema.path
|
||||||
|
: await resolveRemixRouteFile(tree, schema.path, schema.project);
|
||||||
|
|
||||||
|
if (!tree.exists(routeFilePath)) {
|
||||||
|
throw new Error(
|
||||||
|
`Route path does not exist: ${routeFilePath}. Please generate a Remix route first.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
insertImport(tree, routeFilePath, 'ActionFunctionArgs', '@remix-run/node', {
|
||||||
|
typeOnly: true,
|
||||||
|
});
|
||||||
|
insertImport(tree, routeFilePath, 'json', '@remix-run/node');
|
||||||
|
insertImport(tree, routeFilePath, 'useActionData', '@remix-run/react');
|
||||||
|
|
||||||
|
insertStatementAfterImports(
|
||||||
|
tree,
|
||||||
|
routeFilePath,
|
||||||
|
`
|
||||||
|
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
|
let formData = await request.formData();
|
||||||
|
|
||||||
|
return json({message: formData.toString()}, { status: 200 });
|
||||||
|
};
|
||||||
|
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
const statement = `\nconst actionMessage = useActionData<typeof action>();`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
insertStatementInDefaultFunction(tree, routeFilePath, statement);
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} finally {
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
packages/remix/src/generators/action/schema.d.ts
vendored
Normal file
10
packages/remix/src/generators/action/schema.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { NameAndDirectoryFormat } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
||||||
|
|
||||||
|
export interface LoaderSchema {
|
||||||
|
path: string;
|
||||||
|
nameAndDirectoryFormat?: NameAndDirectoryFormat;
|
||||||
|
/**
|
||||||
|
* @deprecated Provide the `path` option instead. The project will be determined from the path provided. It will be removed in Nx v18.
|
||||||
|
*/
|
||||||
|
project?: string;
|
||||||
|
}
|
||||||
33
packages/remix/src/generators/action/schema.json
Normal file
33
packages/remix/src/generators/action/schema.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "action",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Generate an action for a given route.",
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The route path or path to the filename of the route.",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "What is the path of the route? (e.g. 'apps/demo/app/routes/foo/bar.tsx')"
|
||||||
|
},
|
||||||
|
"nameAndDirectoryFormat": {
|
||||||
|
"description": "Whether to generate the action in the directory as provided, relative to the current working directory and ignoring the project (`as-provided`) or generate it using the project and directory relative to the workspace root (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": {
|
||||||
|
"$source": "projectName"
|
||||||
|
},
|
||||||
|
"x-prompt": "What project is this route for?",
|
||||||
|
"pattern": "^[a-zA-Z].*$",
|
||||||
|
"x-deprecated": "Provide the `path` option instead and use the `as-provided` format. The project will be determined from the path provided. It will be removed in Nx v18."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["path"]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,328 @@
|
|||||||
|
import type { Tree } from '@nx/devkit';
|
||||||
|
import { joinPathFragments, readJson } from '@nx/devkit';
|
||||||
|
import { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import applicationGenerator from './application.impl';
|
||||||
|
|
||||||
|
describe('Remix Application', () => {
|
||||||
|
describe('Standalone Project Repo', () => {
|
||||||
|
it('should create the application correctly', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
rootProject: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, '.');
|
||||||
|
|
||||||
|
expect(tree.read('remix.config.cjs', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('app/root.tsx', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('app/routes/_index.tsx', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('tests/routes/_index.spec.tsx', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('vite.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`--js`, () => {
|
||||||
|
it('should create the application correctly', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
js: true,
|
||||||
|
rootProject: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, '.');
|
||||||
|
|
||||||
|
expect(tree.read('remix.config.cjs', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('app/root.js', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('app/routes/_index.js', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--unitTestRunner', () => {
|
||||||
|
it('should generate the correct files for testing using vitest', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
rootProject: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, '.');
|
||||||
|
|
||||||
|
expect(tree.read('remix.config.cjs', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('vite.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('tests/routes/_index.spec.tsx', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('test-setup.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate the correct files for testing using jest', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
unitTestRunner: 'jest',
|
||||||
|
rootProject: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, '.');
|
||||||
|
|
||||||
|
expect(tree.read('remix.config.cjs', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('jest.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test-setup.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('tests/routes/_index.spec.tsx', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.exists('jest.preset.cjs')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--e2eTestRunner', () => {
|
||||||
|
it('should generate an e2e application for the app', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
e2eTestRunner: 'cypress',
|
||||||
|
rootProject: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, '.');
|
||||||
|
|
||||||
|
expect(tree.read('e2e/cypress.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([
|
||||||
|
['derived', 'apps/test', 'apps/test-e2e'],
|
||||||
|
['as-provided', 'test', 'test-e2e'],
|
||||||
|
])(
|
||||||
|
'Integrated Repo --projectNameAndRootFormat=%s',
|
||||||
|
(projectNameAndRootFormat: ProjectNameAndRootFormat, appDir, e2eDir) => {
|
||||||
|
it('should create the application correctly', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
projectNameAndRootFormat,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, appDir);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}/remix.config.cjs`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read(`${appDir}/app/root.tsx`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}/app/routes/_index.tsx`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--js', () => {
|
||||||
|
it('should create the application correctly', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
js: true,
|
||||||
|
projectNameAndRootFormat,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, appDir);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}/remix.config.cjs`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read(`${appDir}/app/root.js`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}/app/routes/_index.js`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('--directory', () => {
|
||||||
|
it('should create the application correctly', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
const newAppDir =
|
||||||
|
projectNameAndRootFormat === 'as-provided'
|
||||||
|
? 'demo'
|
||||||
|
: 'apps/demo/test';
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
directory: 'demo',
|
||||||
|
projectNameAndRootFormat,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, newAppDir);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tree.read(`${newAppDir}/remix.config.cjs`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`${newAppDir}/app/root.tsx`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`${newAppDir}/app/routes/_index.tsx`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract the layout directory from the directory options if it exists', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
const newAppDir =
|
||||||
|
projectNameAndRootFormat === 'as-provided'
|
||||||
|
? 'apps/demo'
|
||||||
|
: 'apps/demo/test';
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
directory: 'apps/demo',
|
||||||
|
projectNameAndRootFormat,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, newAppDir);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tree.read(`${newAppDir}/remix.config.cjs`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`${newAppDir}/app/root.tsx`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`${newAppDir}/app/routes/_index.tsx`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--unitTestRunner', () => {
|
||||||
|
it('should generate the correct files for testing using vitest', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
projectNameAndRootFormat,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, appDir);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}/remix.config.cjs`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}/vite.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}/test-setup.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate the correct files for testing using jest', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
unitTestRunner: 'jest',
|
||||||
|
projectNameAndRootFormat,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, appDir);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}/remix.config.cjs`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}/jest.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}/test-setup.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--e2eTestRunner', () => {
|
||||||
|
it('should generate an e2e application for the app', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
e2eTestRunner: 'cypress',
|
||||||
|
projectNameAndRootFormat,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expectTargetsToBeCorrect(tree, appDir);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tree.read(`${appDir}-e2e/cypress.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function expectTargetsToBeCorrect(tree: Tree, projectRoot: string) {
|
||||||
|
const { targets } = readJson(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(projectRoot === '.' ? '/' : projectRoot, 'project.json')
|
||||||
|
);
|
||||||
|
expect(targets.lint).toBeTruthy();
|
||||||
|
expect(targets.build).toBeTruthy();
|
||||||
|
expect(targets.build.executor).toEqual('@nx/remix:build');
|
||||||
|
expect(targets.build.options.outputPath).toEqual(
|
||||||
|
joinPathFragments('dist', projectRoot)
|
||||||
|
);
|
||||||
|
expect(targets.serve).toBeTruthy();
|
||||||
|
expect(targets.serve.executor).toEqual('@nx/remix:serve');
|
||||||
|
expect(targets.serve.options.port).toEqual(4200);
|
||||||
|
expect(targets.start).toBeTruthy();
|
||||||
|
expect(targets.start.command).toEqual('remix-serve build/index.js');
|
||||||
|
expect(targets.start.options.cwd).toEqual(projectRoot);
|
||||||
|
expect(targets.typecheck).toBeTruthy();
|
||||||
|
expect(targets.typecheck.command).toEqual('tsc');
|
||||||
|
expect(targets.typecheck.options.cwd).toEqual(projectRoot);
|
||||||
|
}
|
||||||
289
packages/remix/src/generators/application/application.impl.ts
Normal file
289
packages/remix/src/generators/application/application.impl.ts
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
addProjectConfiguration,
|
||||||
|
ensurePackage,
|
||||||
|
formatFiles,
|
||||||
|
generateFiles,
|
||||||
|
GeneratorCallback,
|
||||||
|
getPackageManagerCommand,
|
||||||
|
joinPathFragments,
|
||||||
|
offsetFromRoot,
|
||||||
|
readJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
runTasksInSerial,
|
||||||
|
toJS,
|
||||||
|
Tree,
|
||||||
|
updateJson,
|
||||||
|
updateProjectConfiguration,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { extractTsConfigBase } from '@nx/js/src/utils/typescript/create-ts-config';
|
||||||
|
import {
|
||||||
|
eslintVersion,
|
||||||
|
getPackageVersion,
|
||||||
|
isbotVersion,
|
||||||
|
reactDomVersion,
|
||||||
|
reactVersion,
|
||||||
|
remixVersion,
|
||||||
|
typescriptVersion,
|
||||||
|
typesReactDomVersion,
|
||||||
|
typesReactVersion,
|
||||||
|
} from '../../utils/versions';
|
||||||
|
import {
|
||||||
|
NormalizedSchema,
|
||||||
|
normalizeOptions,
|
||||||
|
updateUnitTestConfig,
|
||||||
|
} from './lib';
|
||||||
|
import { NxRemixGeneratorSchema } from './schema';
|
||||||
|
|
||||||
|
export default async function (tree: Tree, _options: NxRemixGeneratorSchema) {
|
||||||
|
const options = await normalizeOptions(tree, _options);
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
|
||||||
|
addProjectConfiguration(tree, options.projectName, {
|
||||||
|
root: options.projectRoot,
|
||||||
|
sourceRoot: `${options.projectRoot}`,
|
||||||
|
projectType: 'application',
|
||||||
|
tags: options.parsedTags,
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: '@nx/remix:build',
|
||||||
|
outputs: ['{options.outputPath}'],
|
||||||
|
options: {
|
||||||
|
outputPath: joinPathFragments('dist', options.projectRoot),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
serve: {
|
||||||
|
executor: `@nx/remix:serve`,
|
||||||
|
options: {
|
||||||
|
command: `${
|
||||||
|
getPackageManagerCommand().exec
|
||||||
|
} remix-serve build/index.js`,
|
||||||
|
manual: true,
|
||||||
|
port: 4200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
dependsOn: ['build'],
|
||||||
|
command: `remix-serve build/index.js`,
|
||||||
|
options: {
|
||||||
|
cwd: options.projectRoot,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typecheck: {
|
||||||
|
command: `tsc`,
|
||||||
|
options: {
|
||||||
|
cwd: options.projectRoot,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const installTask = addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{
|
||||||
|
'@remix-run/node': remixVersion,
|
||||||
|
'@remix-run/react': remixVersion,
|
||||||
|
'@remix-run/serve': remixVersion,
|
||||||
|
isbot: isbotVersion,
|
||||||
|
react: reactVersion,
|
||||||
|
'react-dom': reactDomVersion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'@remix-run/dev': remixVersion,
|
||||||
|
'@remix-run/eslint-config': remixVersion,
|
||||||
|
'@types/react': typesReactVersion,
|
||||||
|
'@types/react-dom': typesReactDomVersion,
|
||||||
|
eslint: eslintVersion,
|
||||||
|
typescript: typescriptVersion,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
tasks.push(installTask);
|
||||||
|
|
||||||
|
const vars = {
|
||||||
|
...options,
|
||||||
|
tmpl: '',
|
||||||
|
offsetFromRoot: offsetFromRoot(options.projectRoot),
|
||||||
|
remixVersion,
|
||||||
|
isbotVersion,
|
||||||
|
reactVersion,
|
||||||
|
reactDomVersion,
|
||||||
|
typesReactVersion,
|
||||||
|
typesReactDomVersion,
|
||||||
|
eslintVersion,
|
||||||
|
typescriptVersion,
|
||||||
|
};
|
||||||
|
|
||||||
|
generateFiles(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(__dirname, 'files/common'),
|
||||||
|
options.projectRoot,
|
||||||
|
vars
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.rootProject) {
|
||||||
|
const gitignore = tree.read('.gitignore', 'utf-8');
|
||||||
|
tree.write(
|
||||||
|
'.gitignore',
|
||||||
|
`${gitignore}\n.cache\nbuild\npublic/build\n.env\n`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
generateFiles(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(__dirname, 'files/integrated'),
|
||||||
|
options.projectRoot,
|
||||||
|
vars
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.unitTestRunner !== 'none') {
|
||||||
|
if (options.unitTestRunner === 'vitest') {
|
||||||
|
const { vitestGenerator } = ensurePackage<typeof import('@nx/vite')>(
|
||||||
|
'@nx/vite',
|
||||||
|
getPackageVersion(tree, 'nx')
|
||||||
|
);
|
||||||
|
const vitestTask = await vitestGenerator(tree, {
|
||||||
|
uiFramework: 'react',
|
||||||
|
project: options.projectName,
|
||||||
|
coverageProvider: 'v8',
|
||||||
|
inSourceTests: false,
|
||||||
|
skipFormat: true,
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
});
|
||||||
|
tasks.push(vitestTask);
|
||||||
|
} else {
|
||||||
|
const { configurationGenerator: jestConfigurationGenerator } =
|
||||||
|
ensurePackage<typeof import('@nx/jest')>(
|
||||||
|
'@nx/jest',
|
||||||
|
getPackageVersion(tree, 'nx')
|
||||||
|
);
|
||||||
|
const jestTask = await jestConfigurationGenerator(tree, {
|
||||||
|
project: options.projectName,
|
||||||
|
setupFile: 'none',
|
||||||
|
supportTsx: true,
|
||||||
|
skipSerializers: false,
|
||||||
|
skipPackageJson: false,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
const projectConfig = readProjectConfiguration(tree, options.projectName);
|
||||||
|
projectConfig.targets['test'].options.passWithNoTests = true;
|
||||||
|
updateProjectConfiguration(tree, options.projectName, projectConfig);
|
||||||
|
|
||||||
|
tasks.push(jestTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkgInstallTask = updateUnitTestConfig(
|
||||||
|
tree,
|
||||||
|
options.projectRoot,
|
||||||
|
options.unitTestRunner
|
||||||
|
);
|
||||||
|
tasks.push(pkgInstallTask);
|
||||||
|
} else {
|
||||||
|
tree.delete(
|
||||||
|
joinPathFragments(options.projectRoot, `tests/routes/_index.spec.tsx`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.linter !== 'none') {
|
||||||
|
const { lintProjectGenerator } = ensurePackage<typeof import('@nx/eslint')>(
|
||||||
|
'@nx/eslint',
|
||||||
|
getPackageVersion(tree, 'nx')
|
||||||
|
);
|
||||||
|
const eslintTask = await lintProjectGenerator(tree, {
|
||||||
|
linter: options.linter,
|
||||||
|
project: options.projectName,
|
||||||
|
tsConfigPaths: [
|
||||||
|
joinPathFragments(options.projectRoot, 'tsconfig.app.json'),
|
||||||
|
],
|
||||||
|
unitTestRunner: options.unitTestRunner,
|
||||||
|
skipFormat: true,
|
||||||
|
rootProject: options.rootProject,
|
||||||
|
});
|
||||||
|
tasks.push(eslintTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.js) {
|
||||||
|
toJS(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.rootProject && tree.exists('tsconfig.base.json')) {
|
||||||
|
// If this is a standalone project, merge tsconfig.json and tsconfig.base.json.
|
||||||
|
const tsConfigBaseJson = readJson(tree, 'tsconfig.base.json');
|
||||||
|
updateJson(tree, 'tsconfig.json', (json) => {
|
||||||
|
delete json.extends;
|
||||||
|
json.compilerOptions = {
|
||||||
|
...tsConfigBaseJson.compilerOptions,
|
||||||
|
...json.compilerOptions,
|
||||||
|
// Taken from remix default setup
|
||||||
|
// https://github.com/remix-run/remix/blob/68c8982/templates/remix/tsconfig.json#L15-L17
|
||||||
|
paths: {
|
||||||
|
'~/*': ['./app/*'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
json.include = [
|
||||||
|
...(tsConfigBaseJson.include ?? []),
|
||||||
|
...(json.include ?? []),
|
||||||
|
];
|
||||||
|
json.exclude = [
|
||||||
|
...(tsConfigBaseJson.exclude ?? []),
|
||||||
|
...(json.exclude ?? []),
|
||||||
|
];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
tree.delete('tsconfig.base.json');
|
||||||
|
} else {
|
||||||
|
// Otherwise, extract the tsconfig.base.json from tsconfig.json so we can share settings.
|
||||||
|
extractTsConfigBase(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.e2eTestRunner === 'cypress') {
|
||||||
|
const { configurationGenerator } = ensurePackage<
|
||||||
|
typeof import('@nx/cypress')
|
||||||
|
>('@nx/cypress', getPackageVersion(tree, 'nx'));
|
||||||
|
addFileServerTarget(tree, options, 'serve-static');
|
||||||
|
addProjectConfiguration(tree, options.e2eProjectName, {
|
||||||
|
projectType: 'application',
|
||||||
|
root: options.e2eProjectRoot,
|
||||||
|
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
|
||||||
|
targets: {},
|
||||||
|
tags: [],
|
||||||
|
implicitDependencies: [options.projectName],
|
||||||
|
});
|
||||||
|
tasks.push(
|
||||||
|
await configurationGenerator(tree, {
|
||||||
|
project: options.e2eProjectName,
|
||||||
|
directory: 'src',
|
||||||
|
skipFormat: true,
|
||||||
|
devServerTarget: `${options.projectName}:serve:development`,
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.skipFormat) {
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
return runTasksInSerial(...tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFileServerTarget(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema,
|
||||||
|
targetName: string
|
||||||
|
) {
|
||||||
|
addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
{ '@nx/web': getPackageVersion(tree, 'nx') }
|
||||||
|
);
|
||||||
|
|
||||||
|
const projectConfig = readProjectConfiguration(tree, options.projectName);
|
||||||
|
projectConfig.targets[targetName] = {
|
||||||
|
executor: '@nx/web:file-server',
|
||||||
|
options: {
|
||||||
|
buildTarget: `${options.projectName}:build`,
|
||||||
|
port: 4200,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
updateProjectConfiguration(tree, options.projectName, projectConfig);
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
# Welcome to Nx + Remix!
|
||||||
|
|
||||||
|
- [Remix Docs](https://remix.run/docs)
|
||||||
|
- [Nx Docs](https://nx.dev)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
From your terminal:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx nx dev <%= projectName %>
|
||||||
|
```
|
||||||
|
|
||||||
|
This starts your app in development mode, rebuilding assets on file changes.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
First, build your app for production:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx nx build <%= projectName %>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the app in production mode:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx nx start <%= projectName %>
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you'll need to pick a host to deploy it to.
|
||||||
|
|
||||||
|
### DIY
|
||||||
|
|
||||||
|
If you're familiar with deploying node applications, the built-in Remix app server is production-ready.
|
||||||
|
|
||||||
|
Make sure to deploy the output of `remix build`
|
||||||
|
|
||||||
|
- `packages/<%= projectName %>/build/`
|
||||||
|
- `packages/<%= projectName %>/public/build/`
|
||||||
|
|
||||||
|
### Using a Template
|
||||||
|
|
||||||
|
When you ran `npx create-remix@latest` there were a few choices for hosting. You can run that again to create a new project, then copy over your `app/` folder to the new project that's pre-configured for your target server.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ..
|
||||||
|
# create a new project, and pick a pre-configured host
|
||||||
|
npx create-remix@latest
|
||||||
|
cd my-new-remix-app
|
||||||
|
# remove the new project's app (not the old one!)
|
||||||
|
rm -rf app
|
||||||
|
# copy your app over
|
||||||
|
cp -R ../my-old-remix-app/app app
|
||||||
|
```
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import type { MetaFunction } from "@remix-run/node";
|
||||||
|
import {
|
||||||
|
Links,
|
||||||
|
LiveReload,
|
||||||
|
Meta,
|
||||||
|
Outlet,
|
||||||
|
Scripts,
|
||||||
|
ScrollRestoration,
|
||||||
|
} from "@remix-run/react";
|
||||||
|
|
||||||
|
export const meta: MetaFunction = () => ([{
|
||||||
|
charset: "utf-8",
|
||||||
|
title: "New Remix App",
|
||||||
|
viewport: "width=device-width,initial-scale=1",
|
||||||
|
}]);
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<Meta />
|
||||||
|
<Links />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Outlet />
|
||||||
|
<ScrollRestoration />
|
||||||
|
<Scripts />
|
||||||
|
<LiveReload />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
export default function Index() {
|
||||||
|
return (
|
||||||
|
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
|
||||||
|
<h1>Welcome to Remix</h1>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://remix.run/tutorials/blog"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
15m Quickstart Blog Tutorial
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://remix.run/tutorials/jokes"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Deep Dive Jokes App Tutorial
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" href="https://remix.run/docs" rel="noreferrer">
|
||||||
|
Remix Docs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @type {import('@remix-run/dev').AppConfig}
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
ignoredRouteFiles: ["**/.*"],
|
||||||
|
// appDirectory: "app",
|
||||||
|
// assetsBuildDirectory: "public/build",
|
||||||
|
// serverBuildPath: "build/index.js",
|
||||||
|
// publicPath: "/build/",
|
||||||
|
watchPaths: () => require("@nx/remix").createWatchPaths(__dirname),
|
||||||
|
};
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="@remix-run/dev" />
|
||||||
|
/// <reference types="@remix-run/node" />
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { createRemixStub } from '@remix-run/testing';
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import Index from '../../app/routes/_index';
|
||||||
|
|
||||||
|
test('renders loader data', async () => {
|
||||||
|
const RemixStub = createRemixStub([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
Component: Index,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
render(<RemixStub />);
|
||||||
|
|
||||||
|
await waitFor(() => screen.findByText('Welcome to Remix'));
|
||||||
|
});
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "<%= offsetFromRoot %>tsconfig.base.json",
|
||||||
|
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ES2019"],
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"target": "ES2019",
|
||||||
|
"strict": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
// Remix takes care of building everything in `remix build`.
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
.cache
|
||||||
|
build
|
||||||
|
public/build
|
||||||
|
.env
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "<%= projectName %>",
|
||||||
|
"description": "",
|
||||||
|
"license": "",
|
||||||
|
"scripts": {},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/node": "<%= remixVersion %>",
|
||||||
|
"@remix-run/react": "<%= remixVersion %>",
|
||||||
|
"@remix-run/serve": "<%= remixVersion %>",
|
||||||
|
"isbot": "<%= isbotVersion %>",
|
||||||
|
"react": "<%= reactVersion %>",
|
||||||
|
"react-dom": "<%= reactDomVersion %>"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@remix-run/dev": "<%= remixVersion %>",
|
||||||
|
"@remix-run/eslint-config": "<%= remixVersion %>",
|
||||||
|
"@types/react": "<%= typesReactVersion %>",
|
||||||
|
"@types/react-dom": "<%= typesReactDomVersion %>",
|
||||||
|
"eslint": "<%= eslintVersion %>",
|
||||||
|
"typescript": "<%= typescriptVersion %>"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"sideEffects": false
|
||||||
|
}
|
||||||
2
packages/remix/src/generators/application/lib/index.ts
Normal file
2
packages/remix/src/generators/application/lib/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './normalize-options';
|
||||||
|
export * from './update-unit-test-config';
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import { type Tree } from '@nx/devkit';
|
||||||
|
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
|
import { type NxRemixGeneratorSchema } from '../schema';
|
||||||
|
import { Linter } from '@nx/eslint';
|
||||||
|
|
||||||
|
export interface NormalizedSchema extends NxRemixGeneratorSchema {
|
||||||
|
projectName: string;
|
||||||
|
projectRoot: string;
|
||||||
|
e2eProjectName: string;
|
||||||
|
e2eProjectRoot: string;
|
||||||
|
parsedTags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function normalizeOptions(
|
||||||
|
tree: Tree,
|
||||||
|
options: NxRemixGeneratorSchema
|
||||||
|
): Promise<NormalizedSchema> {
|
||||||
|
const { projectName, projectRoot, projectNameAndRootFormat } =
|
||||||
|
await determineProjectNameAndRootOptions(tree, {
|
||||||
|
name: options.name,
|
||||||
|
projectType: 'application',
|
||||||
|
directory: options.directory,
|
||||||
|
projectNameAndRootFormat: options.projectNameAndRootFormat,
|
||||||
|
rootProject: options.rootProject,
|
||||||
|
callingGenerator: '@nx/remix:application',
|
||||||
|
});
|
||||||
|
options.rootProject = projectRoot === '.';
|
||||||
|
options.projectNameAndRootFormat = projectNameAndRootFormat;
|
||||||
|
const e2eProjectName = options.rootProject ? 'e2e' : `${projectName}-e2e`;
|
||||||
|
const e2eProjectRoot = options.rootProject ? 'e2e' : `${projectRoot}-e2e`;
|
||||||
|
|
||||||
|
const parsedTags = options.tags
|
||||||
|
? options.tags.split(',').map((s) => s.trim())
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
linter: options.linter ?? Linter.EsLint,
|
||||||
|
projectName,
|
||||||
|
projectRoot,
|
||||||
|
e2eProjectName,
|
||||||
|
e2eProjectRoot,
|
||||||
|
parsedTags,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
joinPathFragments,
|
||||||
|
stripIndents,
|
||||||
|
type Tree,
|
||||||
|
workspaceRoot,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import {
|
||||||
|
updateJestTestSetup,
|
||||||
|
updateViteTestIncludes,
|
||||||
|
updateViteTestSetup,
|
||||||
|
} from '../../../utils/testing-config-utils';
|
||||||
|
import {
|
||||||
|
getRemixVersion,
|
||||||
|
testingLibraryJestDomVersion,
|
||||||
|
testingLibraryReactVersion,
|
||||||
|
testingLibraryUserEventsVersion,
|
||||||
|
} from '../../../utils/versions';
|
||||||
|
|
||||||
|
export function updateUnitTestConfig(
|
||||||
|
tree: Tree,
|
||||||
|
pathToRoot: string,
|
||||||
|
unitTestRunner: 'vitest' | 'jest'
|
||||||
|
) {
|
||||||
|
const pathToTestSetup = joinPathFragments(pathToRoot, `test-setup.ts`);
|
||||||
|
tree.write(
|
||||||
|
pathToTestSetup,
|
||||||
|
stripIndents`
|
||||||
|
import { installGlobals } from '@remix-run/node';
|
||||||
|
import '@testing-library/jest-dom/matchers';
|
||||||
|
installGlobals();`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (unitTestRunner === 'vitest') {
|
||||||
|
const pathToViteConfig = joinPathFragments(pathToRoot, 'vite.config.ts');
|
||||||
|
updateViteTestIncludes(
|
||||||
|
tree,
|
||||||
|
pathToViteConfig,
|
||||||
|
'./app/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'
|
||||||
|
);
|
||||||
|
updateViteTestIncludes(
|
||||||
|
tree,
|
||||||
|
pathToViteConfig,
|
||||||
|
'./tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'
|
||||||
|
);
|
||||||
|
updateViteTestSetup(tree, pathToViteConfig, './test-setup.ts');
|
||||||
|
} else if (unitTestRunner === 'jest') {
|
||||||
|
const pathToJestConfig = joinPathFragments(pathToRoot, 'jest.config.ts');
|
||||||
|
tree.rename('jest.preset.js', 'jest.preset.cjs');
|
||||||
|
updateJestTestSetup(tree, pathToJestConfig, `<rootDir>/test-setup.ts`);
|
||||||
|
tree.write(
|
||||||
|
pathToJestConfig,
|
||||||
|
tree
|
||||||
|
.read(pathToJestConfig, 'utf-8')
|
||||||
|
.replace('jest.preset.js', 'jest.preset.cjs')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
'@testing-library/jest-dom': testingLibraryJestDomVersion,
|
||||||
|
'@testing-library/react': testingLibraryReactVersion,
|
||||||
|
'@testing-library/user-event': testingLibraryUserEventsVersion,
|
||||||
|
'@remix-run/node': getRemixVersion(tree),
|
||||||
|
'@remix-run/testing': getRemixVersion(tree),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
15
packages/remix/src/generators/application/schema.d.ts
vendored
Normal file
15
packages/remix/src/generators/application/schema.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
|
import type { Linter } from '@nx/eslint';
|
||||||
|
|
||||||
|
export interface NxRemixGeneratorSchema {
|
||||||
|
name: string;
|
||||||
|
tags?: string;
|
||||||
|
js?: boolean;
|
||||||
|
directory?: string;
|
||||||
|
projectNameAndRootFormat?: ProjectNameAndRootFormat;
|
||||||
|
linter?: Linter;
|
||||||
|
unitTestRunner?: 'vitest' | 'jest' | 'none';
|
||||||
|
e2eTestRunner?: 'cypress' | 'none';
|
||||||
|
skipFormat?: boolean;
|
||||||
|
rootProject?: boolean;
|
||||||
|
}
|
||||||
68
packages/remix/src/generators/application/schema.json
Normal file
68
packages/remix/src/generators/application/schema.json
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixApplication",
|
||||||
|
"title": "Create an Application",
|
||||||
|
"description": "Generate a new Remix application.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "What is the name of the application?"
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate JavaScript files rather than TypeScript files.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"directory": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A directory where the app is placed.",
|
||||||
|
"alias": "dir",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"projectNameAndRootFormat": {
|
||||||
|
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"description": "The tool to use for running lint checks.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["eslint", "none"],
|
||||||
|
"default": "eslint"
|
||||||
|
},
|
||||||
|
"unitTestRunner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["vitest", "jest", "none"],
|
||||||
|
"default": "vitest",
|
||||||
|
"description": "Test runner to use for unit tests.",
|
||||||
|
"x-prompt": "What unit test runner should be used?"
|
||||||
|
},
|
||||||
|
"e2eTestRunner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["cypress", "none"],
|
||||||
|
"default": "cypress",
|
||||||
|
"description": "Test runner to use for e2e tests"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Add tags to the project (used for linting)",
|
||||||
|
"alias": "t"
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip formatting files",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"rootProject": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-priority": "internal",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { joinPathFragments, readProjectConfiguration } from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import libraryGenerator from '../library/library.impl';
|
||||||
|
import cypressComponentConfigurationGenerator from './cypress-component-configuration.impl';
|
||||||
|
|
||||||
|
describe('CypressComponentConfiguration', () => {
|
||||||
|
it('should create the cypress configuration correctly', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
name: 'cypress-test',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
style: 'css',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await cypressComponentConfigurationGenerator(tree, {
|
||||||
|
project: 'cypress-test',
|
||||||
|
generateTests: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const project = readProjectConfiguration(tree, 'cypress-test');
|
||||||
|
expect(
|
||||||
|
tree.read(joinPathFragments(project.root, 'cypress.config.ts'), 'utf-8')
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"import { defineConfig } from 'cypress';
|
||||||
|
import { nxComponentTestingPreset } from '@nx/remix/plugins/component-testing';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: nxComponentTestingPreset(__filename),
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(project.targets['component-test']).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"executor": "@nx/cypress:cypress",
|
||||||
|
"options": {
|
||||||
|
"cypressConfig": "cypress-test/cypress.config.ts",
|
||||||
|
"devServerTarget": "",
|
||||||
|
"skipServe": true,
|
||||||
|
"testingType": "component",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
expect(
|
||||||
|
tree.exists(joinPathFragments(project.root, 'cypress'))
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import {
|
||||||
|
formatFiles,
|
||||||
|
generateFiles,
|
||||||
|
readProjectConfiguration,
|
||||||
|
type Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { type CypressComponentConfigurationSchema } from './schema';
|
||||||
|
import { cypressComponentConfigGenerator } from '@nx/react';
|
||||||
|
|
||||||
|
export default async function cypressComponentConfigurationGenerator(
|
||||||
|
tree: Tree,
|
||||||
|
options: CypressComponentConfigurationSchema
|
||||||
|
) {
|
||||||
|
await cypressComponentConfigGenerator(tree, {
|
||||||
|
project: options.project,
|
||||||
|
generateTests: options.generateTests,
|
||||||
|
skipFormat: true,
|
||||||
|
bundler: 'vite',
|
||||||
|
buildTarget: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const project = readProjectConfiguration(tree, options.project);
|
||||||
|
|
||||||
|
generateFiles(tree, join(__dirname, './files'), project.root, { tmpl: '' });
|
||||||
|
|
||||||
|
if (!options.skipFormat) {
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import {defineConfig} from 'cypress';
|
||||||
|
import {nxComponentTestingPreset} from '@nx/remix/plugins/component-testing';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: nxComponentTestingPreset(__filename),
|
||||||
|
});
|
||||||
5
packages/remix/src/generators/cypress-component-configuration/schema.d.ts
vendored
Normal file
5
packages/remix/src/generators/cypress-component-configuration/schema.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface CypressComponentConfigurationSchema {
|
||||||
|
project: string;
|
||||||
|
generateTests?: boolean;
|
||||||
|
skipFormat?: boolean;
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/schema",
|
||||||
|
"cli": "nx",
|
||||||
|
"$id": "NxRemixCypressComponentTestConfiguration",
|
||||||
|
"title": "Add Cypress component testing",
|
||||||
|
"description": "Add a Cypress component testing configuration to an existing project.",
|
||||||
|
"type": "object",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "nx g @nx/remix:cypress-component-configuration --project=my-remix-project",
|
||||||
|
"description": "Add component testing to your Remix project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "nx g @nx/remix:cypress-component-configuration --project=my-remix-project --generate-tests",
|
||||||
|
"description": "Add component testing to your Remix project and generate component tests for your existing components"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project to add cypress component testing configuration to",
|
||||||
|
"x-dropdown": "projects",
|
||||||
|
"x-prompt": "What project should we add Cypress component testing to?",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"generateTests": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate default component tests for existing components in the project",
|
||||||
|
"x-prompt": "Automatically generate tests for components declared in this project?",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip formatting files",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["project"]
|
||||||
|
}
|
||||||
35
packages/remix/src/generators/cypress/cypress.impl.spec.ts
Normal file
35
packages/remix/src/generators/cypress/cypress.impl.spec.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { readProjectConfiguration, Tree } from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import generator from './cypress.impl';
|
||||||
|
import applicationGenerator from '../application/application.impl';
|
||||||
|
|
||||||
|
describe('Cypress generator', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate cypress project', async () => {
|
||||||
|
await applicationGenerator(tree, { name: 'demo', e2eTestRunner: 'none' });
|
||||||
|
await generator(tree, { project: 'demo', name: 'demo-e2e' });
|
||||||
|
|
||||||
|
const config = readProjectConfiguration(tree, 'demo-e2e');
|
||||||
|
expect(config.targets).toEqual({
|
||||||
|
e2e: {
|
||||||
|
executor: '@nx/cypress:cypress',
|
||||||
|
options: {
|
||||||
|
cypressConfig: 'demo-e2e/cypress.config.ts',
|
||||||
|
testingType: 'e2e',
|
||||||
|
devServerTarget: 'demo:serve:development',
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
ci: {
|
||||||
|
devServerTarget: 'demo:serve-static',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lint: { executor: '@nx/eslint:lint', outputs: ['{options.outputFile}'] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
113
packages/remix/src/generators/cypress/cypress.impl.ts
Normal file
113
packages/remix/src/generators/cypress/cypress.impl.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
addProjectConfiguration,
|
||||||
|
GeneratorCallback,
|
||||||
|
joinPathFragments,
|
||||||
|
readProjectConfiguration,
|
||||||
|
runTasksInSerial,
|
||||||
|
Tree,
|
||||||
|
updateProjectConfiguration,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { configurationGenerator } from '@nx/cypress';
|
||||||
|
import { CypressGeneratorSchema } from './schema';
|
||||||
|
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
|
import { nxVersion } from '../../utils/versions';
|
||||||
|
|
||||||
|
export default async function (
|
||||||
|
tree: Tree,
|
||||||
|
options: CypressGeneratorSchema
|
||||||
|
): Promise<GeneratorCallback> {
|
||||||
|
const { projectName: e2eProjectName, projectRoot: e2eProjectRoot } =
|
||||||
|
await determineProjectNameAndRootOptions(tree, {
|
||||||
|
name: options.name,
|
||||||
|
projectType: 'application',
|
||||||
|
directory: options.directory,
|
||||||
|
projectNameAndRootFormat: options.projectNameAndRootFormat,
|
||||||
|
callingGenerator: '@nx/remix:cypress',
|
||||||
|
});
|
||||||
|
const rootProject = e2eProjectRoot === '.';
|
||||||
|
let projectConfig = readProjectConfiguration(tree, options.project);
|
||||||
|
options.baseUrl ??= `http://localhost:${projectConfig.targets['serve'].options.port}`;
|
||||||
|
|
||||||
|
addFileServerTarget(tree, options, 'serve-static');
|
||||||
|
addProjectConfiguration(tree, e2eProjectName, {
|
||||||
|
projectType: 'application',
|
||||||
|
root: e2eProjectRoot,
|
||||||
|
sourceRoot: joinPathFragments(e2eProjectRoot, 'src'),
|
||||||
|
targets: {},
|
||||||
|
tags: [],
|
||||||
|
implicitDependencies: [options.name],
|
||||||
|
});
|
||||||
|
const installTask = await configurationGenerator(tree, {
|
||||||
|
project: e2eProjectName,
|
||||||
|
directory: 'src',
|
||||||
|
linter: options.linter,
|
||||||
|
skipPackageJson: false,
|
||||||
|
skipFormat: true,
|
||||||
|
devServerTarget: `${options.project}:serve:development`,
|
||||||
|
baseUrl: options.baseUrl,
|
||||||
|
rootProject,
|
||||||
|
});
|
||||||
|
|
||||||
|
projectConfig = readProjectConfiguration(tree, e2eProjectName);
|
||||||
|
|
||||||
|
tree.delete(
|
||||||
|
joinPathFragments(projectConfig.sourceRoot, 'support', 'app.po.ts')
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
joinPathFragments(projectConfig.sourceRoot, 'e2e', 'app.cy.ts'),
|
||||||
|
`describe('webapp', () => {
|
||||||
|
beforeEach(() => cy.visit('/'));
|
||||||
|
|
||||||
|
it('should display welcome message', () => {
|
||||||
|
cy.get('h1').contains('Welcome to Remix');
|
||||||
|
});
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
const supportFilePath = joinPathFragments(
|
||||||
|
projectConfig.sourceRoot,
|
||||||
|
'support',
|
||||||
|
'e2e.ts'
|
||||||
|
);
|
||||||
|
const supportContent = tree.read(supportFilePath, 'utf-8');
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
supportFilePath,
|
||||||
|
`${supportContent}
|
||||||
|
|
||||||
|
// from https://github.com/remix-run/indie-stack
|
||||||
|
Cypress.on("uncaught:exception", (err) => {
|
||||||
|
// Cypress and React Hydrating the document don't get along
|
||||||
|
// for some unknown reason. Hopefully we figure out why eventually
|
||||||
|
// so we can remove this.
|
||||||
|
if (
|
||||||
|
/hydrat/i.test(err.message) ||
|
||||||
|
/Minified React error #418/.test(err.message) ||
|
||||||
|
/Minified React error #423/.test(err.message)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
return runTasksInSerial(installTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFileServerTarget(
|
||||||
|
tree: Tree,
|
||||||
|
options: CypressGeneratorSchema,
|
||||||
|
targetName: string
|
||||||
|
) {
|
||||||
|
addDependenciesToPackageJson(tree, {}, { '@nx/web': nxVersion });
|
||||||
|
|
||||||
|
const projectConfig = readProjectConfiguration(tree, options.project);
|
||||||
|
projectConfig.targets[targetName] = {
|
||||||
|
executor: '@nx/web:file-server',
|
||||||
|
options: {
|
||||||
|
buildTarget: `${options.project}:build`,
|
||||||
|
port: projectConfig.targets['serve'].options.port,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
updateProjectConfiguration(tree, options.project, projectConfig);
|
||||||
|
}
|
||||||
14
packages/remix/src/generators/cypress/schema.d.ts
vendored
Normal file
14
packages/remix/src/generators/cypress/schema.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { type ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
|
import { Linter } from '@nx/eslint';
|
||||||
|
|
||||||
|
export interface CypressGeneratorSchema {
|
||||||
|
project: string;
|
||||||
|
name: string;
|
||||||
|
baseUrl?: string;
|
||||||
|
directory?: string;
|
||||||
|
projectNameAndRootFormat?: ProjectNameAndRootFormat;
|
||||||
|
linter?: Linter;
|
||||||
|
js?: boolean;
|
||||||
|
skipFormat?: boolean;
|
||||||
|
setParserOptionsProject?: boolean;
|
||||||
|
}
|
||||||
61
packages/remix/src/generators/cypress/schema.json
Normal file
61
packages/remix/src/generators/cypress/schema.json
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxRemixCypress",
|
||||||
|
"title": "",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Generate a Cypress e2e project for a given application.",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the frontend project to test.",
|
||||||
|
"$default": {
|
||||||
|
"$source": "projectName"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"projectNameAndRootFormat": {
|
||||||
|
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"baseUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "URL to access the application on",
|
||||||
|
"default": "http://localhost:3000"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the E2E Project",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "What name would you like to use for the e2e project?"
|
||||||
|
},
|
||||||
|
"directory": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A directory where the project is placed"
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"description": "The tool to use for running lint checks.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["eslint", "none"],
|
||||||
|
"default": "eslint"
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"description": "Generate JavaScript files rather than TypeScript files",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"description": "Skip formatting files",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"setParserOptionsProject": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"]
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`ErrorBoundary --nameAndDirectoryFormat=as-provided --apiVersion=2 should correctly add the ErrorBoundary to the route file 1`] = `
|
||||||
|
"import { useRouteError, isRouteErrorResponse } from '@remix-run/react';
|
||||||
|
export function ErrorBoundary() {
|
||||||
|
const error = useRouteError();
|
||||||
|
|
||||||
|
// when true, this is what used to go to 'CatchBoundary'
|
||||||
|
if (isRouteErrorResponse(error)) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Oops</h1>
|
||||||
|
<p>Status: {error.status}</p>
|
||||||
|
<p>{error.data.message}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (error instanceof Error) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Error</h1>
|
||||||
|
<p>{error.message}</p>
|
||||||
|
<p>The stack trace is:</p>
|
||||||
|
<pre>{error.stack}</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <h1>Unknown Error</h1>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ErrorBoundary --nameAndDirectoryFormat=as-provided --apiVersion=2 should correctly add the ErrorBoundary to the route file 2`] = `
|
||||||
|
"import { useRouteError, isRouteErrorResponse } from '@remix-run/react';
|
||||||
|
export function ErrorBoundary() {
|
||||||
|
const error = useRouteError();
|
||||||
|
|
||||||
|
// when true, this is what used to go to 'CatchBoundary'
|
||||||
|
if (isRouteErrorResponse(error)) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Oops</h1>
|
||||||
|
<p>Status: {error.status}</p>
|
||||||
|
<p>{error.data.message}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (error instanceof Error) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Error</h1>
|
||||||
|
<p>{error.message}</p>
|
||||||
|
<p>The stack trace is:</p>
|
||||||
|
<pre>{error.stack}</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <h1>Unknown Error</h1>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
import { addProjectConfiguration } from '@nx/devkit';
|
||||||
|
import { NameAndDirectoryFormat } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import errorBoundaryGenerator from './error-boundary.impl';
|
||||||
|
|
||||||
|
describe('ErrorBoundary', () => {
|
||||||
|
describe.each([
|
||||||
|
['derived', 'app/routes/test.tsx', 'demo'],
|
||||||
|
['as-provided', 'app/routes/test.tsx', ''],
|
||||||
|
])(
|
||||||
|
`--nameAndDirectoryFormat=as-provided`,
|
||||||
|
(
|
||||||
|
nameAndDirectoryFormat: NameAndDirectoryFormat,
|
||||||
|
routeFilePath: string,
|
||||||
|
project: string
|
||||||
|
) => {
|
||||||
|
describe('--apiVersion=2', () => {
|
||||||
|
it('should correctly add the ErrorBoundary to the route file', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
addProjectConfiguration(tree, 'demo', {
|
||||||
|
name: 'demo',
|
||||||
|
root: '.',
|
||||||
|
sourceRoot: '.',
|
||||||
|
projectType: 'application',
|
||||||
|
});
|
||||||
|
const routeFilePath = `app/routes/test.tsx`;
|
||||||
|
tree.write(routeFilePath, ``);
|
||||||
|
tree.write('remix.config.cjs', `module.exports = {}`);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await errorBoundaryGenerator(tree, {
|
||||||
|
project,
|
||||||
|
nameAndDirectoryFormat,
|
||||||
|
path: routeFilePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read(routeFilePath, 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error when the route file cannot be found', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
addProjectConfiguration(tree, 'demo', {
|
||||||
|
name: 'demo',
|
||||||
|
root: '.',
|
||||||
|
sourceRoot: '.',
|
||||||
|
projectType: 'application',
|
||||||
|
});
|
||||||
|
const routeFilePath = `app/routes/test.tsx`;
|
||||||
|
tree.write(routeFilePath, ``);
|
||||||
|
tree.write('remix.config.cjs', `module.exports = {}`);
|
||||||
|
|
||||||
|
// ACT & ASSERT
|
||||||
|
await expect(
|
||||||
|
errorBoundaryGenerator(tree, {
|
||||||
|
project,
|
||||||
|
nameAndDirectoryFormat,
|
||||||
|
path: `my-route.tsx`,
|
||||||
|
})
|
||||||
|
).rejects.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { formatFiles, type Tree } from '@nx/devkit';
|
||||||
|
import { addV2ErrorBoundary, normalizeOptions } from './lib';
|
||||||
|
import type { ErrorBoundarySchema } from './schema';
|
||||||
|
|
||||||
|
export default async function errorBoundaryGenerator(
|
||||||
|
tree: Tree,
|
||||||
|
schema: ErrorBoundarySchema
|
||||||
|
) {
|
||||||
|
const options = await normalizeOptions(tree, schema);
|
||||||
|
|
||||||
|
addV2ErrorBoundary(tree, options);
|
||||||
|
|
||||||
|
if (!options.skipFormat) {
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { stripIndents, type Tree } from '@nx/devkit';
|
||||||
|
import { insertImport } from '../../../utils/insert-import';
|
||||||
|
import { insertStatementAfterImports } from '../../../utils/insert-statement-after-imports';
|
||||||
|
import type { ErrorBoundarySchema } from '../schema';
|
||||||
|
|
||||||
|
export function addV2ErrorBoundary(tree: Tree, options: ErrorBoundarySchema) {
|
||||||
|
insertImport(tree, options.path, `useRouteError`, '@remix-run/react');
|
||||||
|
insertImport(tree, options.path, `isRouteErrorResponse`, '@remix-run/react');
|
||||||
|
|
||||||
|
insertStatementAfterImports(
|
||||||
|
tree,
|
||||||
|
options.path,
|
||||||
|
stripIndents`
|
||||||
|
export function ErrorBoundary() {
|
||||||
|
const error = useRouteError();
|
||||||
|
|
||||||
|
// when true, this is what used to go to 'CatchBoundary'
|
||||||
|
if (isRouteErrorResponse(error)) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Oops</h1>
|
||||||
|
<p>Status: {error.status}</p>
|
||||||
|
<p>{error.data.message}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (error instanceof Error) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Error</h1>
|
||||||
|
<p>{error.message}</p>
|
||||||
|
<p>The stack trace is:</p>
|
||||||
|
<pre>{error.stack}</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <h1>Unknown Error</h1>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './add-v2-error-boundary';
|
||||||
|
export * from './normalize-options';
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { type Tree } from '@nx/devkit';
|
||||||
|
import { resolveRemixRouteFile } from '../../../utils/remix-route-utils';
|
||||||
|
import type { ErrorBoundarySchema } from '../schema';
|
||||||
|
|
||||||
|
export async function normalizeOptions(
|
||||||
|
tree: Tree,
|
||||||
|
schema: ErrorBoundarySchema
|
||||||
|
): Promise<ErrorBoundarySchema> {
|
||||||
|
const pathToRouteFile =
|
||||||
|
schema.nameAndDirectoryFormat === 'as-provided'
|
||||||
|
? schema.path
|
||||||
|
: await resolveRemixRouteFile(tree, schema.path, schema.project);
|
||||||
|
|
||||||
|
if (!tree.exists(pathToRouteFile)) {
|
||||||
|
throw new Error(
|
||||||
|
`Route file specified does not exist "${pathToRouteFile}". Please ensure you pass a correct path to the file.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...schema,
|
||||||
|
path: pathToRouteFile,
|
||||||
|
};
|
||||||
|
}
|
||||||
11
packages/remix/src/generators/error-boundary/schema.d.ts
vendored
Normal file
11
packages/remix/src/generators/error-boundary/schema.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { NameAndDirectoryFormat } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
||||||
|
|
||||||
|
export interface ErrorBoundarySchema {
|
||||||
|
path: string;
|
||||||
|
skipFormat?: false;
|
||||||
|
nameAndDirectoryFormat?: NameAndDirectoryFormat;
|
||||||
|
/**
|
||||||
|
* @deprecated Provide the `path` option instead. The project will be determined from the path provided. It will be removed in Nx v18.
|
||||||
|
*/
|
||||||
|
project?: string;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user