Skip to content

Commit

Permalink
feat(angular): support migrating angular cli workspaces with multiple…
Browse files Browse the repository at this point in the history
… projects when keeping the angular cli layout (#9649)
  • Loading branch information
leosvelperez committed Apr 1, 2022
1 parent 803d5ff commit 557b241
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 53 deletions.
52 changes: 46 additions & 6 deletions e2e/angular-core/src/ng-add.test.ts
Expand Up @@ -387,14 +387,54 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
});
});

it('should support preserveAngularCliLayout', () => {
it('should support --preserve-angular-cli-layout', () => {
// add another app and a library
runCommand(`ng g @schematics/angular:application app2`);
runCommand(`ng g @schematics/angular:library lib1`);

runNgAdd('@nrwl/angular', '--preserve-angular-cli-layout');

const updatedAngularCLIJson = readJson('angular.json');
expect(updatedAngularCLIJson.projects[project].root).toEqual('');
expect(updatedAngularCLIJson.projects[project].sourceRoot).toEqual('src');
// check config still uses Angular CLI layout
const updatedAngularJson = readJson('angular.json');
expect(updatedAngularJson.projects[project].root).toEqual('');
expect(updatedAngularJson.projects[project].sourceRoot).toEqual('src');
expect(updatedAngularJson.projects.app2.root).toEqual('projects/app2');
expect(updatedAngularJson.projects.app2.sourceRoot).toEqual(
'projects/app2/src'
);
expect(updatedAngularJson.projects.lib1.root).toEqual('projects/lib1');
expect(updatedAngularJson.projects.lib1.sourceRoot).toEqual(
'projects/lib1/src'
);

// check building an app
let output = runCLI(`build ${project} --outputHashing none`);
expect(output).toContain(
`> nx run ${project}:build:production --outputHashing=none`
);
expect(output).toContain(
`Successfully ran target build for project ${project}`
);
checkFilesExist(`dist/${project}/main.js`);

const output = runCLI('build');
expect(output).toContain(`> nx run ${project}:build:production`);
output = runCLI(`build ${project} --outputHashing none`);
expect(output).toContain(
`> nx run ${project}:build:production --outputHashing=none [existing outputs match the cache, left as is]`
);
expect(output).toContain(
`Successfully ran target build for project ${project}`
);

// check building lib1
output = runCLI('build lib1');
expect(output).toContain('> nx run lib1:build:production');
expect(output).toContain('Successfully ran target build for project lib1');
checkFilesExist('dist/lib1/package.json');

output = runCLI('build lib1');
expect(output).toContain(
'> nx run lib1:build:production [existing outputs match the cache, left as is]'
);
expect(output).toContain('Successfully ran target build for project lib1');
});
});
@@ -1,5 +1,87 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`workspace --preserve-angular-cli-layout should create nx.json 1`] = `
Object {
"affected": Object {
"defaultBase": "main",
},
"implicitDependencies": Object {
".eslintrc.json": "*",
"package.json": Object {
"dependencies": "*",
"devDependencies": "*",
},
},
"npmScope": "my-scope",
"targetDependencies": Object {
"build": Array [
Object {
"projects": "dependencies",
"target": "build",
},
],
},
"tasksRunnerOptions": Object {
"default": Object {
"options": Object {
"cacheableOperations": Array [
"build",
"lint",
"test",
"e2e",
],
},
"runner": "nx/tasks-runners/default",
},
},
"workspaceLayout": Object {
"appsDir": "",
"libsDir": "",
},
}
`;

exports[`workspace --preserve-angular-cli-layout should support multiple projects 1`] = `
Object {
"affected": Object {
"defaultBase": "main",
},
"implicitDependencies": Object {
".eslintrc.json": "*",
"package.json": Object {
"dependencies": "*",
"devDependencies": "*",
},
},
"npmScope": "my-scope",
"targetDependencies": Object {
"build": Array [
Object {
"projects": "dependencies",
"target": "build",
},
],
},
"tasksRunnerOptions": Object {
"default": Object {
"options": Object {
"cacheableOperations": Array [
"build",
"lint",
"test",
"e2e",
],
},
"runner": "nx/tasks-runners/default",
},
},
"workspaceLayout": Object {
"appsDir": "projects",
"libsDir": "projects",
},
}
`;

exports[`workspace move to nx layout cypress should handle project configuration without cypress-run or cypress-open 1`] = `
Object {
"implicitDependencies": Array [
Expand Down
Expand Up @@ -632,41 +632,102 @@ describe('workspace', () => {
});
});

describe('preserve angular cli layout', () => {
describe('--preserve-angular-cli-layout', () => {
beforeEach(() => {
tree.write('/package.json', JSON.stringify({ devDependencies: {} }));
tree.write('/angular.json', JSON.stringify({ projects: { myproj: {} } }));
tree.write('/tsconfig.json', '{"compilerOptions": {}}');
tree.write(
'package.json',
JSON.stringify({ name: 'my-scope', devDependencies: {} })
);
tree.write('angular.json', JSON.stringify({ projects: { myproj: {} } }));
tree.write('tsconfig.json', '{"compilerOptions": {}}');
});

it('should update package.json', async () => {
await migrateFromAngularCli(tree, {
preserveAngularCliLayout: true,
});
await migrateFromAngularCli(tree, { preserveAngularCliLayout: true });

const d = readJson(tree, '/package.json').devDependencies;
expect(d['@nrwl/workspace']).toBeDefined();
expect(d['@nrwl/angular']).not.toBeDefined();
const { devDependencies } = readJson(tree, 'package.json');
expect(devDependencies['@nrwl/workspace']).toBeDefined();
expect(devDependencies['nx']).toBeDefined();
});

it('should create nx.json', async () => {
await migrateFromAngularCli(tree, {
preserveAngularCliLayout: true,
});
await migrateFromAngularCli(tree, { preserveAngularCliLayout: true });

const nxJson = readJson(tree, '/nx.json');
expect(nxJson.npmScope).toEqual('myproj');
expect(nxJson.workspaceLayout).toBeTruthy();
expect(readJson(tree, 'nx.json')).toMatchSnapshot();
});

it('should create decorate-angular-cli.js', async () => {
await migrateFromAngularCli(tree, {
preserveAngularCliLayout: true,
});
const s = readJson(tree, '/package.json').scripts;
await migrateFromAngularCli(tree, { preserveAngularCliLayout: true });

expect(tree.exists('/decorate-angular-cli.js')).toBe(true);
const { scripts } = readJson(tree, 'package.json');
expect(scripts.postinstall).toBe('node ./decorate-angular-cli.js');
});

it('should support multiple projects', async () => {
const angularJson = {
$schema: './node_modules/@angular/cli/lib/config/schema.json',
version: 1,
defaultProject: 'app1',
newProjectRoot: 'projects',
projects: {
app1: {
root: '',
sourceRoot: 'src',
architect: {
build: {
options: { tsConfig: 'tsconfig.app.json' },
},
test: {
options: { tsConfig: 'tsconfig.spec.json' },
},
e2e: {
builder: '@angular-devkit/build-angular:protractor',
options: { protractorConfig: 'e2e/protractor.conf.js' },
},
},
},
app2: {
root: 'projects/app2',
sourceRoot: 'projects/app2/src',
architect: {
build: {
options: { tsConfig: 'projects/app2/tsconfig.app.json' },
},
test: {
options: { tsConfig: 'projects/app2/tsconfig.spec.json' },
},
e2e: {
builder: '@angular-devkit/build-angular:protractor',
options: {
protractorConfig: 'projects/app2/e2e/protractor.conf.js',
},
},
},
},
lib1: {
root: 'projects/lib1',
sourceRoot: 'projects/lib1/src',
architect: {
build: {
options: { tsConfig: 'projects/lib1/tsconfig.lib.json' },
},
test: {
options: { tsConfig: 'projects/lib1/tsconfig.spec.json' },
},
},
},
},
};
tree.write('/angular.json', JSON.stringify(angularJson));

await migrateFromAngularCli(tree, { preserveAngularCliLayout: true });

expect(tree.read('/decorate-angular-cli.js')).not.toBe(null);
expect(s.postinstall).toEqual('node ./decorate-angular-cli.js');
expect(readJson(tree, 'angular.json')).toStrictEqual(angularJson);
expect(tree.exists('/decorate-angular-cli.js')).toBe(true);
const { scripts } = readJson(tree, 'package.json');
expect(scripts.postinstall).toBe('node ./decorate-angular-cli.js');
expect(readJson(tree, 'nx.json')).toMatchSnapshot();
});
});
});
Expand Up @@ -28,7 +28,7 @@ export async function migrateFromAngularCli(
rawOptions: GeneratorOptions
) {
const projects = getAllProjects(tree);
const options = normalizeOptions(rawOptions, projects);
const options = normalizeOptions(tree, rawOptions, projects);

if (options.preserveAngularCliLayout) {
addDependenciesToPackageJson(
Expand Down
@@ -1,24 +1,33 @@
import { names } from '@nrwl/devkit';
import { joinPathFragments, names, readJson, Tree } from '@nrwl/devkit';
import { GeneratorOptions } from '../schema';
import { WorkspaceProjects } from './types';

export function normalizeOptions(
tree: Tree,
options: GeneratorOptions,
projects: WorkspaceProjects
): GeneratorOptions {
// TODO: this restrictions will be removed, it's here temporarily to
// execute for both a full migration and a minimal one to maintain
// the current behavior
const hasLibraries = projects.libs.length > 0;
if (projects.apps.length > 2 || hasLibraries) {
throw new Error('Can only convert projects with one app');
}

let npmScope = options.npmScope ?? options.name;
if (npmScope) {
npmScope = names(npmScope).fileName;
} else {
npmScope = projects.apps[0].name;
} else if (projects.libs.length > 0) {
// try get the scope from any library that have one
for (const lib of projects.libs) {
const { name } = readJson(
tree,
joinPathFragments(lib.config.root, 'package.json')
);
if (name.startsWith('@')) {
npmScope = name.split('/')[0].substring(1);
break;
}
}
}

if (!npmScope) {
// use the name (scope if exists) in the root package.json
const { name } = readJson(tree, 'package.json');
npmScope = name.startsWith('@') ? name.split('/')[0].substring(1) : name;
}

return { ...options, npmScope };
Expand Down
20 changes: 7 additions & 13 deletions packages/angular/src/generators/ng-add/utilities/workspace.ts
Expand Up @@ -43,6 +43,12 @@ export function validateWorkspace(
throw new Error('Cannot find angular.json');
}

// TODO: this restrictions will be removed when support for multiple
// projects is added
if (projects.apps.length > 2 || projects.libs.length > 0) {
throw new Error('Can only convert projects with one app');
}

const e2eKey = getE2eKey(projects);
const e2eApp = getE2eProject(projects);

Expand Down Expand Up @@ -97,19 +103,7 @@ export function createNxJson(
options: GeneratorOptions,
setWorkspaceLayoutAsNewProjectRoot: boolean = false
): void {
const { projects = {}, newProjectRoot = '' } = readJson(tree, 'angular.json');
// TODO: temporarily leaving this here because it's the old behavior for a
// minimal migration, will be removed in a later PR
const hasLibraries = Object.keys(projects).find(
(project) =>
projects[project].projectType &&
projects[project].projectType !== 'application'
);
if (Object.keys(projects).length !== 1 || hasLibraries) {
throw new Error(
`The schematic can only be used with Angular CLI workspaces with a single application.`
);
}
const { newProjectRoot = '' } = readJson(tree, 'angular.json');

writeJson<NxJsonConfiguration>(tree, 'nx.json', {
npmScope: options.npmScope,
Expand Down

0 comments on commit 557b241

Please sign in to comment.