feat(remix): add convert-to-inferred generator (#26601)
- feat(remix): add convert-to-inferred migration - feat(remix): add serve executor logic <!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
efd0994ee6
commit
b1713be2c3
@ -9325,6 +9325,14 @@
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "convert-to-inferred",
|
||||
"path": "/nx-api/remix/generators/convert-to-inferred",
|
||||
"name": "convert-to-inferred",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
|
||||
@ -2723,6 +2723,15 @@
|
||||
"originalFilePath": "/packages/remix/src/generators/error-boundary/schema.json",
|
||||
"path": "/nx-api/remix/generators/error-boundary",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/remix/generators/convert-to-inferred": {
|
||||
"description": "Convert existing Remix project(s) using `@nx/remix:*` executors to use `@nx/remix/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"file": "generated/packages/remix/generators/convert-to-inferred.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-inferred",
|
||||
"originalFilePath": "/packages/remix/src/generators/convert-to-inferred/schema.json",
|
||||
"path": "/nx-api/remix/generators/convert-to-inferred",
|
||||
"type": "generator"
|
||||
}
|
||||
},
|
||||
"path": "/nx-api/remix"
|
||||
|
||||
@ -2695,6 +2695,15 @@
|
||||
"originalFilePath": "/packages/remix/src/generators/error-boundary/schema.json",
|
||||
"path": "remix/generators/error-boundary",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Convert existing Remix project(s) using `@nx/remix:*` executors to use `@nx/remix/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"file": "generated/packages/remix/generators/convert-to-inferred.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-inferred",
|
||||
"originalFilePath": "/packages/remix/src/generators/convert-to-inferred/schema.json",
|
||||
"path": "remix/generators/convert-to-inferred",
|
||||
"type": "generator"
|
||||
}
|
||||
],
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "convert-to-inferred",
|
||||
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxRemixConvertToInferred",
|
||||
"description": "Convert existing Remix project(s) using `@nx/remix:*` executors to use `@nx/remix/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"title": "Convert Remix project from executor to plugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to convert from using the `@nx/remix:*` executors to use `@nx/remix/plugin`.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to format files at the end of the migration.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"presets": []
|
||||
},
|
||||
"description": "Convert existing Remix project(s) using `@nx/remix:*` executors to use `@nx/remix/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"implementation": "/packages/remix/src/generators/convert-to-inferred/convert-to-inferred.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/remix/src/generators/convert-to-inferred/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@ -644,6 +644,7 @@
|
||||
- [storybook-configuration](/nx-api/remix/generators/storybook-configuration)
|
||||
- [meta](/nx-api/remix/generators/meta)
|
||||
- [error-boundary](/nx-api/remix/generators/error-boundary)
|
||||
- [convert-to-inferred](/nx-api/remix/generators/convert-to-inferred)
|
||||
- [rollup](/nx-api/rollup)
|
||||
- [executors](/nx-api/rollup/executors)
|
||||
- [rollup](/nx-api/rollup/executors/rollup)
|
||||
|
||||
@ -85,6 +85,11 @@
|
||||
"implementation": "./src/generators/error-boundary/error-boundary.impl",
|
||||
"schema": "./src/generators/error-boundary/schema.json",
|
||||
"description": "Add an ErrorBoundary to an existing route"
|
||||
},
|
||||
"convert-to-inferred": {
|
||||
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||
"schema": "./src/generators/convert-to-inferred/schema.json",
|
||||
"description": "Convert existing Remix project(s) using `@nx/remix:*` executors to use `@nx/remix/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,440 @@
|
||||
import {
|
||||
type ProjectGraph,
|
||||
type Tree,
|
||||
type ProjectConfiguration,
|
||||
joinPathFragments,
|
||||
writeJson,
|
||||
addProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
readNxJson,
|
||||
type ExpandedPluginConfiguration,
|
||||
updateNxJson,
|
||||
} from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { TempFs } from '@nx/devkit/internal-testing-utils';
|
||||
import { getRelativeProjectJsonSchemaPath } from 'nx/src/generators/utils/project-configuration';
|
||||
import { join } from 'path';
|
||||
import { convertToInferred } from './convert-to-inferred';
|
||||
|
||||
let fs: TempFs;
|
||||
let projectGraph: ProjectGraph;
|
||||
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual('@nx/devkit'),
|
||||
createProjectGraphAsync: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(projectGraph)),
|
||||
updateProjectConfiguration: jest
|
||||
.fn()
|
||||
.mockImplementation((tree, projectName, projectConfiguration) => {
|
||||
function handleEmptyTargets(
|
||||
projectName: string,
|
||||
projectConfiguration: ProjectConfiguration
|
||||
): void {
|
||||
if (
|
||||
projectConfiguration.targets &&
|
||||
!Object.keys(projectConfiguration.targets).length
|
||||
) {
|
||||
// Re-order `targets` to appear after the `// target` comment.
|
||||
delete projectConfiguration.targets;
|
||||
projectConfiguration[
|
||||
'// targets'
|
||||
] = `to see all targets run: nx show project ${projectName} --web`;
|
||||
projectConfiguration.targets = {};
|
||||
} else {
|
||||
delete projectConfiguration['// targets'];
|
||||
}
|
||||
}
|
||||
|
||||
const projectConfigFile = joinPathFragments(
|
||||
projectConfiguration.root,
|
||||
'project.json'
|
||||
);
|
||||
|
||||
if (!tree.exists(projectConfigFile)) {
|
||||
throw new Error(
|
||||
`Cannot update Project ${projectName} at ${projectConfiguration.root}. It either doesn't exist yet, or may not use project.json for configuration. Use \`addProjectConfiguration()\` instead if you want to create a new project.`
|
||||
);
|
||||
}
|
||||
handleEmptyTargets(projectName, projectConfiguration);
|
||||
writeJson(tree, projectConfigFile, {
|
||||
name: projectConfiguration.name ?? projectName,
|
||||
$schema: getRelativeProjectJsonSchemaPath(tree, projectConfiguration),
|
||||
...projectConfiguration,
|
||||
root: undefined,
|
||||
});
|
||||
projectGraph.nodes[projectName].data = projectConfiguration;
|
||||
}),
|
||||
}));
|
||||
|
||||
function addProject(tree: Tree, name: string, project: ProjectConfiguration) {
|
||||
addProjectConfiguration(tree, name, project);
|
||||
projectGraph.nodes[name] = {
|
||||
name,
|
||||
type: project.projectType === 'application' ? 'app' : 'lib',
|
||||
data: {
|
||||
projectType: project.projectType,
|
||||
root: project.root,
|
||||
targets: project.targets,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
interface TestProjectOptions {
|
||||
appName: string;
|
||||
appRoot: string;
|
||||
outputPath: string;
|
||||
buildTargetName: string;
|
||||
serveTargetName: string;
|
||||
}
|
||||
|
||||
const defaultTestProjectOptions: TestProjectOptions = {
|
||||
appName: 'app1',
|
||||
appRoot: 'apps/app1',
|
||||
outputPath: 'dist/apps/app1',
|
||||
buildTargetName: 'build',
|
||||
serveTargetName: 'serve',
|
||||
};
|
||||
|
||||
function writeRemixConfig(tree: Tree, projectRoot: string) {
|
||||
const remixConfig = {
|
||||
ignoredRouteFiles: ['**/.*'],
|
||||
};
|
||||
const remixConfigContents = `const config = ${JSON.stringify(remixConfig)};
|
||||
export default config;`;
|
||||
|
||||
tree.write(`${projectRoot}/remix.config.js`, remixConfigContents);
|
||||
fs.createFileSync(`${projectRoot}/remix.config.js`, remixConfigContents);
|
||||
tree.write(`${projectRoot}/package.json`, `{"type":"module"}`);
|
||||
fs.createFileSync(`${projectRoot}/package.json`, `{"type":"module"}`);
|
||||
jest.doMock(
|
||||
join(fs.tempDir, projectRoot, 'remix.config.js'),
|
||||
() => remixConfig,
|
||||
{ virtual: true }
|
||||
);
|
||||
}
|
||||
|
||||
function createTestProject(
|
||||
tree: Tree,
|
||||
opts: Partial<TestProjectOptions> = defaultTestProjectOptions,
|
||||
extraTargetOptions: any = {},
|
||||
extraConfigurations: any = {}
|
||||
) {
|
||||
let projectOpts = { ...defaultTestProjectOptions, ...opts };
|
||||
const project: ProjectConfiguration = {
|
||||
name: projectOpts.appName,
|
||||
root: projectOpts.appRoot,
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
[projectOpts.buildTargetName]: {
|
||||
executor: '@nx/remix:build',
|
||||
options: {
|
||||
outputPath: projectOpts.outputPath,
|
||||
...extraTargetOptions,
|
||||
},
|
||||
configurations: {
|
||||
...extraConfigurations,
|
||||
},
|
||||
},
|
||||
[projectOpts.serveTargetName]: {
|
||||
executor: '@nx/remix:serve',
|
||||
options: {
|
||||
port: 4200,
|
||||
...extraTargetOptions,
|
||||
},
|
||||
configurations: {
|
||||
...extraConfigurations,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
writeRemixConfig(tree, project.root);
|
||||
|
||||
addProject(tree, project.name, project);
|
||||
fs.createFileSync(
|
||||
`${projectOpts.appRoot}/project.json`,
|
||||
JSON.stringify(project)
|
||||
);
|
||||
return project;
|
||||
}
|
||||
|
||||
describe('Remix - Convert To Inferred', () => {
|
||||
let tree: Tree;
|
||||
beforeEach(() => {
|
||||
fs = new TempFs('remix');
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.root = fs.tempDir;
|
||||
|
||||
projectGraph = {
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
externalNodes: {},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.cleanup();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
describe('--project', () => {
|
||||
it('should correctly migrate a single project', async () => {
|
||||
// ARRANGE
|
||||
const project = createTestProject(tree);
|
||||
const project2 = createTestProject(tree, {
|
||||
appRoot: 'apps/project2',
|
||||
appName: 'project2',
|
||||
});
|
||||
|
||||
const project2Targets = project2.targets;
|
||||
|
||||
// ACT
|
||||
await convertToInferred(tree, {
|
||||
project: project.name,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
expect(updatedProject.targets).toMatchInlineSnapshot(`
|
||||
{
|
||||
"serve": {
|
||||
"options": {
|
||||
"env": {
|
||||
"PORT": "4200",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||
expect(updatedProject2.targets).toStrictEqual(project2Targets);
|
||||
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const remixPlugin = nxJsonPlugins.find(
|
||||
(plugin): plugin is ExpandedPluginConfiguration =>
|
||||
typeof plugin !== 'string' &&
|
||||
plugin.plugin === '@nx/remix/plugin' &&
|
||||
plugin.include?.length === 1
|
||||
);
|
||||
expect(remixPlugin).toBeTruthy();
|
||||
expect(remixPlugin.include).toEqual([`${project.root}/**/*`]);
|
||||
});
|
||||
|
||||
it('should add a new plugin registration when the target name differs', async () => {
|
||||
// ARRANGE
|
||||
const project = createTestProject(tree);
|
||||
const project2 = createTestProject(tree, {
|
||||
appRoot: 'apps/project2',
|
||||
appName: 'project2',
|
||||
});
|
||||
|
||||
const project2Targets = project2.targets;
|
||||
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/remix/plugin',
|
||||
options: {
|
||||
buildTargetName: 'build',
|
||||
devTargetName: defaultTestProjectOptions.serveTargetName,
|
||||
startTargetName: 'start',
|
||||
typecheckTargetName: 'typecheck',
|
||||
staticServeTargetName: 'static-serve',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
// ACT
|
||||
await convertToInferred(tree, {
|
||||
project: project.name,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
expect(updatedProject.targets).toMatchInlineSnapshot(`
|
||||
{
|
||||
"serve": {
|
||||
"options": {
|
||||
"env": {
|
||||
"PORT": "4200",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||
expect(updatedProject2.targets).toStrictEqual(project2Targets);
|
||||
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const remixPluginRegistrations = nxJsonPlugins.filter(
|
||||
(plugin): plugin is ExpandedPluginConfiguration =>
|
||||
typeof plugin !== 'string' && plugin.plugin === '@nx/remix/plugin'
|
||||
);
|
||||
expect(remixPluginRegistrations.length).toBe(2);
|
||||
expect(remixPluginRegistrations[1].include).toMatchInlineSnapshot(`
|
||||
[
|
||||
"apps/app1/**/*",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should merge target defaults', async () => {
|
||||
// ARRANGE
|
||||
const project = createTestProject(tree);
|
||||
const project2 = createTestProject(tree, {
|
||||
appRoot: 'apps/project2',
|
||||
appName: 'project2',
|
||||
});
|
||||
|
||||
const project2Targets = project2.targets;
|
||||
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.targetDefaults ??= {};
|
||||
nxJson.targetDefaults['@nx/remix:build'] = {
|
||||
options: {
|
||||
sourcemap: true,
|
||||
},
|
||||
};
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
// ACT
|
||||
await convertToInferred(tree, {
|
||||
project: project.name,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
expect(updatedProject.targets).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"options": {
|
||||
"sourcemap": true,
|
||||
},
|
||||
},
|
||||
"serve": {
|
||||
"options": {
|
||||
"env": {
|
||||
"PORT": "4200",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||
expect(updatedProject2.targets).toStrictEqual(project2Targets);
|
||||
});
|
||||
|
||||
it('should manage configurations correctly', async () => {
|
||||
// ARRANGE
|
||||
const project = createTestProject(tree, undefined, undefined, {
|
||||
dev: {
|
||||
outputPath: 'apps/dev/app1',
|
||||
},
|
||||
});
|
||||
const project2 = createTestProject(tree, {
|
||||
appRoot: 'apps/project2',
|
||||
appName: 'project2',
|
||||
});
|
||||
|
||||
const project2Targets = project2.targets;
|
||||
// ACT
|
||||
await convertToInferred(tree, {
|
||||
project: project.name,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
expect(updatedProject.targets).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"configurations": {
|
||||
"dev": {},
|
||||
},
|
||||
},
|
||||
"serve": {
|
||||
"configurations": {
|
||||
"dev": {
|
||||
"outputPath": "apps/dev/app1",
|
||||
},
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
"PORT": "4200",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||
expect(updatedProject2.targets).toStrictEqual(project2Targets);
|
||||
|
||||
const remixConfigContents = tree.read(
|
||||
`${project.root}/remix.config.js`,
|
||||
'utf-8'
|
||||
);
|
||||
expect(remixConfigContents).toMatchInlineSnapshot(`
|
||||
"const config = {"ignoredRouteFiles":["**/.*"]};
|
||||
export default config;"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('all projects', () => {
|
||||
it('should correctly migrate all projects', async () => {
|
||||
// ARRANGE
|
||||
const project = createTestProject(tree);
|
||||
const project2 = createTestProject(tree, {
|
||||
appRoot: 'apps/project2',
|
||||
appName: 'project2',
|
||||
});
|
||||
// ACT
|
||||
await convertToInferred(tree, {
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
expect(updatedProject.targets).toMatchInlineSnapshot(`
|
||||
{
|
||||
"serve": {
|
||||
"options": {
|
||||
"env": {
|
||||
"PORT": "4200",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||
expect(updatedProject2.targets).toMatchInlineSnapshot(`
|
||||
{
|
||||
"serve": {
|
||||
"options": {
|
||||
"env": {
|
||||
"PORT": "4200",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const remixPlugin = nxJsonPlugins.find(
|
||||
(plugin): plugin is ExpandedPluginConfiguration =>
|
||||
typeof plugin !== 'string' && plugin.plugin === '@nx/remix/plugin'
|
||||
);
|
||||
expect(remixPlugin).toBeTruthy();
|
||||
expect(remixPlugin.include).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,71 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
createProjectGraphAsync,
|
||||
formatFiles,
|
||||
runTasksInSerial,
|
||||
type Tree,
|
||||
} from '@nx/devkit';
|
||||
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { migrateExecutorToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
|
||||
import { buildPostTargetTransformer } from './lib/build-post-target-transformer';
|
||||
import { servePostTargetTransformer } from './lib/serve-post-target-transformer';
|
||||
import { createNodes } from '../../plugins/plugin';
|
||||
|
||||
interface Schema {
|
||||
project?: string;
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
|
||||
export async function convertToInferred(tree: Tree, options: Schema) {
|
||||
const projectGraph = await createProjectGraphAsync();
|
||||
const migrationLogs = new AggregatedLog();
|
||||
const migratedBuildProjects = await migrateExecutorToPluginV1(
|
||||
tree,
|
||||
projectGraph,
|
||||
'@nx/remix:build',
|
||||
'@nx/remix/plugin',
|
||||
(targetName) => ({
|
||||
buildTargetName: targetName,
|
||||
devTargetName: 'dev',
|
||||
startTargetName: 'start',
|
||||
typecheckTargetName: 'typecheck',
|
||||
staticServeTargetName: 'static-serve',
|
||||
}),
|
||||
buildPostTargetTransformer(migrationLogs),
|
||||
createNodes,
|
||||
options.project
|
||||
);
|
||||
|
||||
const migratedServeProjects = await migrateExecutorToPluginV1(
|
||||
tree,
|
||||
projectGraph,
|
||||
'@nx/remix:serve',
|
||||
'@nx/remix/plugin',
|
||||
(targetName) => ({
|
||||
buildTargetName: 'build',
|
||||
devTargetName: targetName,
|
||||
startTargetName: 'start',
|
||||
typecheckTargetName: 'typecheck',
|
||||
staticServeTargetName: 'static-serve',
|
||||
}),
|
||||
servePostTargetTransformer(migrationLogs),
|
||||
createNodes,
|
||||
options.project
|
||||
);
|
||||
|
||||
const migratedProjects =
|
||||
migratedBuildProjects.size + migratedServeProjects.size;
|
||||
if (migratedProjects === 0) {
|
||||
throw new Error('Could not find any targets to migrate.');
|
||||
}
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return () => {
|
||||
migrationLogs.flushLogs();
|
||||
};
|
||||
}
|
||||
|
||||
export default convertToInferred;
|
||||
@ -0,0 +1,137 @@
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { buildPostTargetTransformer } from './build-post-target-transformer';
|
||||
|
||||
describe('buildPostTargetTransformer', () => {
|
||||
it('should migrate outputPath correctly', () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
const targetConfiguration = {
|
||||
options: {
|
||||
outputPath: 'dist/apps/myapp',
|
||||
},
|
||||
};
|
||||
|
||||
const inferredTargetConfiguration = {};
|
||||
|
||||
const migrationLogs = new AggregatedLog();
|
||||
|
||||
tree.write('apps/myapp/remix.config.js', remixConfig);
|
||||
tree.write('apps/myapp/package.json', `{"type": "module"}`);
|
||||
|
||||
// ACT
|
||||
const target = buildPostTargetTransformer(migrationLogs)(
|
||||
targetConfiguration,
|
||||
tree,
|
||||
{ projectName: 'myapp', root: 'apps/myapp' },
|
||||
inferredTargetConfiguration
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const configFile = tree.read('apps/myapp/remix.config.js', 'utf-8');
|
||||
expect(configFile).toMatchInlineSnapshot(`
|
||||
"import { createWatchPaths } from '@nx/remix';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* @type {import('@remix-run/dev').AppConfig}
|
||||
*/
|
||||
export default {
|
||||
ignoredRouteFiles: ['**/.*'],
|
||||
// appDirectory: "app",
|
||||
// assetsBuildDirectory: "public/build",
|
||||
// serverBuildPath: "build/index.js",
|
||||
// publicPath: "/build/",
|
||||
watchPaths: () => createWatchPaths(__dirname),
|
||||
};"
|
||||
`);
|
||||
expect(target).toMatchInlineSnapshot(`
|
||||
{
|
||||
"options": {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle configurations correctly', () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
const targetConfiguration = {
|
||||
options: {
|
||||
outputPath: 'dist/apps/myapp',
|
||||
},
|
||||
configurations: {
|
||||
dev: {
|
||||
outputPath: 'dist/dev/apps/myapp',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const inferredTargetConfiguration = {};
|
||||
|
||||
const migrationLogs = new AggregatedLog();
|
||||
|
||||
tree.write('apps/myapp/remix.config.js', remixConfig);
|
||||
tree.write('apps/myapp/package.json', `{"type": "module"}`);
|
||||
|
||||
// ACT
|
||||
const target = buildPostTargetTransformer(migrationLogs)(
|
||||
targetConfiguration,
|
||||
tree,
|
||||
{ projectName: 'myapp', root: 'apps/myapp' },
|
||||
inferredTargetConfiguration
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const configFile = tree.read('apps/myapp/remix.config.js', 'utf-8');
|
||||
expect(configFile).toMatchInlineSnapshot(`
|
||||
"import { createWatchPaths } from '@nx/remix';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* @type {import('@remix-run/dev').AppConfig}
|
||||
*/
|
||||
export default {
|
||||
ignoredRouteFiles: ['**/.*'],
|
||||
// appDirectory: "app",
|
||||
// assetsBuildDirectory: "public/build",
|
||||
// serverBuildPath: "build/index.js",
|
||||
// publicPath: "/build/",
|
||||
watchPaths: () => createWatchPaths(__dirname),
|
||||
};"
|
||||
`);
|
||||
expect(target).toMatchInlineSnapshot(`
|
||||
{
|
||||
"configurations": {
|
||||
"dev": {},
|
||||
},
|
||||
"options": {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
const remixConfig = `import { createWatchPaths } from '@nx/remix';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* @type {import('@remix-run/dev').AppConfig}
|
||||
*/
|
||||
export default {
|
||||
ignoredRouteFiles: ['**/.*'],
|
||||
// appDirectory: "app",
|
||||
// assetsBuildDirectory: "public/build",
|
||||
// serverBuildPath: "build/index.js",
|
||||
// publicPath: "/build/",
|
||||
watchPaths: () => createWatchPaths(__dirname),
|
||||
};`;
|
||||
@ -0,0 +1,111 @@
|
||||
import { type Tree, type TargetConfiguration } from '@nx/devkit';
|
||||
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { getConfigFilePath } from './utils';
|
||||
import { processTargetOutputs } from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils';
|
||||
|
||||
export function buildPostTargetTransformer(migrationLogs: AggregatedLog) {
|
||||
return (
|
||||
target: TargetConfiguration,
|
||||
tree: Tree,
|
||||
projectDetails: { projectName: string; root: string },
|
||||
inferredTargetConfiguration: TargetConfiguration
|
||||
) => {
|
||||
const remixConfigPath = getConfigFilePath(tree, projectDetails.root);
|
||||
|
||||
if (target.options) {
|
||||
handlePropertiesFromTargetOptions(
|
||||
tree,
|
||||
target.options,
|
||||
projectDetails.projectName,
|
||||
projectDetails.root,
|
||||
migrationLogs
|
||||
);
|
||||
}
|
||||
|
||||
if (target.configurations) {
|
||||
for (const configurationName in target.configurations) {
|
||||
const configuration = target.configurations[configurationName];
|
||||
handlePropertiesFromTargetOptions(
|
||||
tree,
|
||||
configuration,
|
||||
projectDetails.projectName,
|
||||
projectDetails.root,
|
||||
migrationLogs
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(target.configurations).length === 0) {
|
||||
if ('defaultConfiguration' in target) {
|
||||
delete target.defaultConfiguration;
|
||||
}
|
||||
delete target.configurations;
|
||||
}
|
||||
|
||||
if (
|
||||
'defaultConfiguration' in target &&
|
||||
!target.configurations[target.defaultConfiguration]
|
||||
) {
|
||||
delete target.defaultConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.outputs) {
|
||||
target.outputs = target.outputs.filter(
|
||||
(out) => !out.includes('options.outputPath')
|
||||
);
|
||||
processTargetOutputs(target, [], inferredTargetConfiguration, {
|
||||
projectName: projectDetails.projectName,
|
||||
projectRoot: projectDetails.root,
|
||||
});
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
}
|
||||
|
||||
function handlePropertiesFromTargetOptions(
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
migrationLogs: AggregatedLog
|
||||
) {
|
||||
if ('outputPath' in options) {
|
||||
migrationLogs.addLog({
|
||||
project: projectName,
|
||||
executorName: '@nx/remix:build',
|
||||
log: "Unable to migrate 'outputPath'. The Remix Config will contain the locations the build artifact will be output to.",
|
||||
});
|
||||
delete options.outputPath;
|
||||
}
|
||||
|
||||
if ('includeDevDependenciesInPackageJson' in options) {
|
||||
migrationLogs.addLog({
|
||||
project: projectName,
|
||||
executorName: '@nx/remix:build',
|
||||
log: "Unable to migrate `includeDevDependenciesInPackageJson` to Remix Config. Use the `@nx/dependency-checks` ESLint rule to update your project's package.json.",
|
||||
});
|
||||
|
||||
delete options.includeDevDependenciesInPackageJson;
|
||||
}
|
||||
|
||||
if ('generatePackageJson' in options) {
|
||||
migrationLogs.addLog({
|
||||
project: projectName,
|
||||
executorName: '@nx/remix:build',
|
||||
log: "Unable to migrate `generatePackageJson` to Remix Config. Use the `@nx/dependency-checks` ESLint rule to update your project's package.json.",
|
||||
});
|
||||
|
||||
delete options.generatePackageJson;
|
||||
}
|
||||
|
||||
if ('generateLockfile' in options) {
|
||||
migrationLogs.addLog({
|
||||
project: projectName,
|
||||
executorName: '@nx/remix:build',
|
||||
log: 'Unable to migrate `generateLockfile` to Remix Config. This option is not supported.',
|
||||
});
|
||||
|
||||
delete options.generateLockfile;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
import { type Tree, type TargetConfiguration } from '@nx/devkit';
|
||||
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { REMIX_PROPERTY_MAPPINGS } from './utils';
|
||||
|
||||
export function servePostTargetTransformer(migrationLogs: AggregatedLog) {
|
||||
return (
|
||||
target: TargetConfiguration,
|
||||
tree: Tree,
|
||||
projectDetails: { projectName: string; root: string },
|
||||
inferredTargetConfiguration: TargetConfiguration
|
||||
) => {
|
||||
if (target.options) {
|
||||
handlePropertiesFromTargetOptions(
|
||||
tree,
|
||||
target.options,
|
||||
projectDetails.projectName,
|
||||
projectDetails.root
|
||||
);
|
||||
}
|
||||
|
||||
if (target.configurations) {
|
||||
for (const configurationName in target.configurations) {
|
||||
const configuration = target.configurations[configurationName];
|
||||
|
||||
handlePropertiesFromTargetOptions(
|
||||
tree,
|
||||
configuration,
|
||||
projectDetails.projectName,
|
||||
projectDetails.root
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(target.configurations).length === 0) {
|
||||
if ('defaultConfiguration' in target) {
|
||||
delete target.defaultConfiguration;
|
||||
}
|
||||
delete target.configurations;
|
||||
}
|
||||
|
||||
if (
|
||||
'defaultConfiguration' in target &&
|
||||
!target.configurations[target.defaultConfiguration]
|
||||
) {
|
||||
delete target.defaultConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
}
|
||||
|
||||
function handlePropertiesFromTargetOptions(
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string
|
||||
) {
|
||||
if ('debug' in options) {
|
||||
delete options.debug;
|
||||
}
|
||||
|
||||
if ('port' in options) {
|
||||
options.env ??= {};
|
||||
options.env.PORT = `${options.port}`;
|
||||
delete options.port;
|
||||
}
|
||||
|
||||
for (const [prevKey, newKey] of Object.entries(REMIX_PROPERTY_MAPPINGS)) {
|
||||
if (prevKey in options) {
|
||||
let prevValue = options[prevKey];
|
||||
delete options[prevKey];
|
||||
options[newKey] = prevValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import { type Tree, joinPathFragments } from '@nx/devkit';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
|
||||
export const REMIX_PROPERTY_MAPPINGS = {
|
||||
sourcemap: 'sourcemap',
|
||||
devServerPort: 'port',
|
||||
command: 'command',
|
||||
manual: 'manual',
|
||||
tlsKey: 'tls-key',
|
||||
tlsCert: 'tls-cert',
|
||||
};
|
||||
|
||||
export function getConfigFilePath(tree: Tree, root: string) {
|
||||
return [
|
||||
joinPathFragments(root, `remix.config.js`),
|
||||
joinPathFragments(root, `remix.config.cjs`),
|
||||
joinPathFragments(root, `remix.config.mjs`),
|
||||
].find((f) => tree.exists(f));
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxRemixConvertToInferred",
|
||||
"description": "Convert existing Remix project(s) using `@nx/remix:*` executors to use `@nx/remix/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"title": "Convert Remix project from executor to plugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to convert from using the `@nx/remix:*` executors to use `@nx/remix/plugin`.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to format files at the end of the migration.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user