Skip to content

Commit

Permalink
feat(angular): add plugin for inferring nodes from angular.json files (
Browse files Browse the repository at this point in the history
…#27804)

<!-- 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 NXC-887 -->

Fixes #

---------

Co-authored-by: Jack Hsu <[email protected]>
  • Loading branch information
leosvelperez and jaysoo authored Sep 17, 2024
1 parent 2be7424 commit e0b3e73
Show file tree
Hide file tree
Showing 14 changed files with 2,256 additions and 44 deletions.
172 changes: 172 additions & 0 deletions e2e/angular/src/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import {
checkFilesExist,
cleanupProject,
getPackageManagerCommand,
getSelectedPackageManager,
isVerbose,
isVerboseE2ERun,
logInfo,
newProject,
runCLI,
runCommand,
tmpProjPath,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';
import { angularCliVersion } from '@nx/workspace/src/utils/versions';
import { ensureDirSync } from 'fs-extra';
import { execSync } from 'node:child_process';
import { join } from 'node:path';

describe('Angular Crystal Plugin', () => {
let proj: string;

beforeAll(() => {
proj = newProject({
packages: ['@nx/angular'],
unsetProjectNameAndRootFormat: false,
});

if (getSelectedPackageManager() === 'pnpm') {
updateFile(
'pnpm-workspace.yaml',
`packages:
- 'projects/*'
`
);
} else {
updateJson('package.json', (json) => {
json.workspaces = ['projects/*'];
return json;
});
}
});

afterAll(() => cleanupProject());

it('should infer tasks from multiple angular.json files', () => {
const ngOrg1App1 = uniq('ng-org1-app1');
const ngOrg1Lib1 = uniq('ng-org1-lib1');
const org1Root = join(tmpProjPath(), 'projects', ngOrg1App1);
const ngOrg2App1 = uniq('ng-org2-app1');
const ngOrg2Lib1 = uniq('ng-org2-lib1');
const org2Root = join(tmpProjPath(), 'projects', ngOrg2App1);
const pmc = getPackageManagerCommand();

// first angular inner repo (e.g. imported with nx import)
runNgNew(ngOrg1App1, 'projects');
// exclude scripts from nx, to prevent them to override the inferred tasks
updateJson(`projects/${ngOrg1App1}/package.json`, (json) => {
json.nx = { includedScripts: [] };
return json;
});
runCommand(pmc.run(`ng g @schematics/angular:library ${ngOrg1Lib1}`, ''), {
cwd: org1Root,
});

// second angular inner repo
runNgNew(ngOrg2App1, 'projects');
// exclude scripts from nx
updateJson(`projects/${ngOrg2App1}/package.json`, (json) => {
json.nx = { includedScripts: [] };
return json;
});
runCommand(pmc.run(`ng g @schematics/angular:library ${ngOrg2Lib1}`, ''), {
cwd: org2Root,
});

// add Angular Crystal plugin
updateJson('nx.json', (json) => {
json.plugins ??= [];
json.plugins.push('@nx/angular/plugin');
return json;
});

// check org1 tasks

// build
runCLI(`build ${ngOrg1App1} --output-hashing none`);
checkFilesExist(
`projects/${ngOrg1App1}/dist/${ngOrg1App1}/browser/main.js`
);
expect(runCLI(`build ${ngOrg1App1} --output-hashing none`)).toContain(
'Nx read the output from the cache instead of running the command for 1 out of 1 tasks'
);
runCLI(`build ${ngOrg1Lib1}`);
checkFilesExist(
`projects/${ngOrg1App1}/dist/${ngOrg1Lib1}/fesm2022/${ngOrg1Lib1}.mjs`
);
expect(runCLI(`build ${ngOrg1Lib1}`)).toContain(
'Nx read the output from the cache instead of running the command for 1 out of 1 tasks'
);

// test
expect(
runCLI(
`run-many -t test -p ${ngOrg1App1},${ngOrg1Lib1} --no-watch --browsers=ChromeHeadless`
)
).toContain('Successfully ran target test for 2 projects');
expect(
runCLI(
`run-many -t test -p ${ngOrg1App1},${ngOrg1Lib1} --no-watch --browsers=ChromeHeadless`
)
).toContain(
'Nx read the output from the cache instead of running the command for 2 out of 2 tasks'
);

// check org2 tasks

// build
runCLI(`build ${ngOrg2App1} --output-hashing none`);
checkFilesExist(
`projects/${ngOrg2App1}/dist/${ngOrg2App1}/browser/main.js`
);
expect(runCLI(`build ${ngOrg2App1} --output-hashing none`)).toContain(
'Nx read the output from the cache instead of running the command for 1 out of 1 tasks'
);
runCLI(`build ${ngOrg2Lib1}`);
checkFilesExist(
`projects/${ngOrg2App1}/dist/${ngOrg2Lib1}/fesm2022/${ngOrg2Lib1}.mjs`
);
expect(runCLI(`build ${ngOrg2Lib1}`)).toContain(
'Nx read the output from the cache instead of running the command for 1 out of 1 tasks'
);

// test
expect(
runCLI(
`run-many -t test -p ${ngOrg2App1},${ngOrg2Lib1} --no-watch --browsers=ChromeHeadless`
)
).toContain('Successfully ran target test for 2 projects');
expect(
runCLI(
`run-many -t test -p ${ngOrg2App1},${ngOrg2Lib1} --no-watch --browsers=ChromeHeadless`
)
).toContain(
'Nx read the output from the cache instead of running the command for 2 out of 2 tasks'
);
});
});

function runNgNew(projectName: string, cwd: string): void {
const packageManager = getSelectedPackageManager();
const pmc = getPackageManagerCommand({ packageManager });

const command = `${pmc.runUninstalledPackage} @angular/cli@${angularCliVersion} new ${projectName} --package-manager=${packageManager}`;
cwd = join(tmpProjPath(), cwd);
ensureDirSync(cwd);
execSync(command, {
cwd,
stdio: isVerbose() ? 'inherit' : 'pipe',
env: process.env,
encoding: 'utf-8',
});

if (isVerboseE2ERun()) {
logInfo(
`NX`,
`E2E created an Angular CLI project at ${join(cwd, projectName)}`
);
}
}
9 changes: 7 additions & 2 deletions e2e/nx/src/import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getSelectedPackageManager,
newProject,
runCLI,
runCommand,
updateJson,
updateFile,
e2eCwd,
Expand Down Expand Up @@ -38,7 +39,11 @@ describe('Nx Import', () => {
try {
rmdirSync(join(tempImportE2ERoot));
} catch {}

runCommand(`git add .`);
runCommand(`git commit -am "Update" --allow-empty`);
});

afterAll(() => cleanupProject());

it('should be able to import a vite app', () => {
Expand Down Expand Up @@ -111,15 +116,15 @@ describe('Nx Import', () => {
});
mkdirSync(join(repoPath, 'packages/a'), { recursive: true });
writeFileSync(join(repoPath, 'packages/a/README.md'), `# A`);
execSync(`git add packages/a`, {
execSync(`git add .`, {
cwd: repoPath,
});
execSync(`git commit -m "add package a"`, {
cwd: repoPath,
});
mkdirSync(join(repoPath, 'packages/b'), { recursive: true });
writeFileSync(join(repoPath, 'packages/b/README.md'), `# B`);
execSync(`git add packages/b`, {
execSync(`git add .`, {
cwd: repoPath,
});
execSync(`git commit -m "add package b"`, {
Expand Down
1 change: 1 addition & 0 deletions packages/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"./executors.json": "./executors.json",
"./generators": "./generators.js",
"./executors": "./executors.js",
"./plugin": "./plugin.js",
"./tailwind": "./tailwind.js",
"./module-federation": "./module-federation/index.js",
"./src/utils": "./src/utils/index.js",
Expand Down
1 change: 1 addition & 0 deletions packages/angular/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { createNodesV2 } from './src/plugins/plugin';
24 changes: 23 additions & 1 deletion packages/angular/src/generators/init/init.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import {
addDependenciesToPackageJson,
createProjectGraphAsync,
ensurePackage,
formatFiles,
type GeneratorCallback,
logger,
readNxJson,
type GeneratorCallback,
type Tree,
} from '@nx/devkit';
import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
import { getInstalledPackageVersion, versions } from '../utils/version-utils';
import { createNodesV2 } from '../../plugins/plugin';
import { Schema } from './schema';

export async function angularInitGenerator(
Expand All @@ -17,6 +20,25 @@ export async function angularInitGenerator(
ignoreAngularCacheDirectory(tree);
const installTask = installAngularDevkitCoreIfMissing(tree, options);

// For Angular inference plugin, we only want it during import since our
// generators do not use `angular.json`, and `nx init` should split
// `angular.json` into multiple `project.json` files -- as this is preferred
// by most folks we've talked to.
options.addPlugin ??= process.env.NX_RUNNING_NX_IMPORT === 'true';

if (options.addPlugin) {
await addPlugin(
tree,
await createProjectGraphAsync(),
'@nx/angular/plugin',
createNodesV2,
{
targetNamePrefix: ['', 'angular:', 'angular-'],
},
options.updatePackageScripts
);
}

if (!options.skipFormat) {
await formatFiles(tree);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/angular/src/generators/init/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ export interface Schema {
skipInstall?: boolean;
skipPackageJson?: boolean;
keepExistingVersions?: boolean;
/* internal */
addPlugin?: boolean;
updatePackageScripts?: boolean;
}
Loading

0 comments on commit e0b3e73

Please sign in to comment.