feat(angular): add plugin for inferring nodes from angular.json files (#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 <jack.hsu@gmail.com>
This commit is contained in:
parent
2be7424fc4
commit
e0b3e73d7a
172
e2e/angular/src/plugin.test.ts
Normal file
172
e2e/angular/src/plugin.test.ts
Normal file
@ -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)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import {
|
||||
getSelectedPackageManager,
|
||||
newProject,
|
||||
runCLI,
|
||||
runCommand,
|
||||
updateJson,
|
||||
updateFile,
|
||||
e2eCwd,
|
||||
@ -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', () => {
|
||||
@ -111,7 +116,7 @@ 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"`, {
|
||||
@ -119,7 +124,7 @@ describe('Nx Import', () => {
|
||||
});
|
||||
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"`, {
|
||||
|
||||
@ -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",
|
||||
|
||||
1
packages/angular/plugin.ts
Normal file
1
packages/angular/plugin.ts
Normal file
@ -0,0 +1 @@
|
||||
export { createNodesV2 } from './src/plugins/plugin';
|
||||
@ -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(
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -3,4 +3,7 @@ export interface Schema {
|
||||
skipInstall?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
keepExistingVersions?: boolean;
|
||||
/* internal */
|
||||
addPlugin?: boolean;
|
||||
updatePackageScripts?: boolean;
|
||||
}
|
||||
|
||||
1197
packages/angular/src/plugins/plugin.spec.ts
Normal file
1197
packages/angular/src/plugins/plugin.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
763
packages/angular/src/plugins/plugin.ts
Normal file
763
packages/angular/src/plugins/plugin.ts
Normal file
@ -0,0 +1,763 @@
|
||||
import {
|
||||
type CreateNodesContextV2,
|
||||
createNodesFromFiles,
|
||||
type CreateNodesResult,
|
||||
type CreateNodesV2,
|
||||
detectPackageManager,
|
||||
getPackageManagerCommand,
|
||||
type ProjectConfiguration,
|
||||
readJsonFile,
|
||||
type Target,
|
||||
type TargetConfiguration,
|
||||
writeJsonFile,
|
||||
} from '@nx/devkit';
|
||||
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
||||
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
|
||||
import { getLockFileName } from '@nx/js';
|
||||
import { existsSync, readdirSync, statSync } from 'node:fs';
|
||||
import { dirname, join, relative } from 'node:path';
|
||||
import * as posix from 'node:path/posix';
|
||||
import { hashObject } from 'nx/src/devkit-internals';
|
||||
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||
|
||||
export interface AngularPluginOptions {
|
||||
targetNamePrefix?: string;
|
||||
}
|
||||
|
||||
type AngularProjects = Record<
|
||||
string,
|
||||
Pick<ProjectConfiguration, 'projectType' | 'sourceRoot' | 'targets'>
|
||||
>;
|
||||
|
||||
type AngularTargetConfiguration = {
|
||||
builder: string;
|
||||
options?: Record<string, any>;
|
||||
configurations?: Record<string, any>;
|
||||
defaultConfiguration?: string;
|
||||
};
|
||||
export type AngularProjectConfiguration = {
|
||||
projectType: 'application' | 'library';
|
||||
root: string;
|
||||
sourceRoot?: string;
|
||||
architect?: Record<string, AngularTargetConfiguration>;
|
||||
targets?: Record<string, AngularTargetConfiguration>;
|
||||
};
|
||||
type AngularJson = { projects?: Record<string, AngularProjectConfiguration> };
|
||||
|
||||
const knownExecutors = {
|
||||
appShell: new Set(['@angular-devkit/build-angular:app-shell']),
|
||||
build: new Set([
|
||||
'@angular-devkit/build-angular:application',
|
||||
'@angular/build:application',
|
||||
'@angular-devkit/build-angular:browser-esbuild',
|
||||
'@angular-devkit/build-angular:browser',
|
||||
'@angular-devkit/build-angular:ng-packagr',
|
||||
]),
|
||||
devServer: new Set(['@angular-devkit/build-angular:dev-server']),
|
||||
extractI18n: new Set(['@angular-devkit/build-angular:extract-i18n']),
|
||||
prerender: new Set([
|
||||
'@angular-devkit/build-angular:prerender',
|
||||
'@nguniversal/builders:prerender',
|
||||
]),
|
||||
server: new Set(['@angular-devkit/build-angular:server']),
|
||||
serveSsr: new Set([
|
||||
'@angular-devkit/build-angular:ssr-dev-server',
|
||||
'@nguniversal/builders:ssr-dev-server',
|
||||
]),
|
||||
test: new Set(['@angular-devkit/build-angular:karma']),
|
||||
};
|
||||
|
||||
const pmc = getPackageManagerCommand();
|
||||
|
||||
function readProjectsCache(cachePath: string): Record<string, AngularProjects> {
|
||||
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
|
||||
}
|
||||
|
||||
function writeProjectsToCache(
|
||||
cachePath: string,
|
||||
results: Record<string, AngularProjects>
|
||||
) {
|
||||
writeJsonFile(cachePath, results);
|
||||
}
|
||||
|
||||
export const createNodesV2: CreateNodesV2<AngularPluginOptions> = [
|
||||
'**/angular.json',
|
||||
async (configFiles, options, context) => {
|
||||
const optionsHash = hashObject(options);
|
||||
const cachePath = join(
|
||||
workspaceDataDirectory,
|
||||
`angular-${optionsHash}.hash`
|
||||
);
|
||||
const projectsCache = readProjectsCache(cachePath);
|
||||
try {
|
||||
return await createNodesFromFiles(
|
||||
(configFile, options, context) =>
|
||||
createNodesInternal(configFile, options, context, projectsCache),
|
||||
configFiles,
|
||||
options,
|
||||
context
|
||||
);
|
||||
} finally {
|
||||
writeProjectsToCache(cachePath, projectsCache);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
async function createNodesInternal(
|
||||
configFilePath: string,
|
||||
options: {} | undefined,
|
||||
context: CreateNodesContextV2,
|
||||
projectsCache: Record<string, AngularProjects>
|
||||
): Promise<CreateNodesResult> {
|
||||
const angularWorkspaceRoot = dirname(configFilePath);
|
||||
|
||||
// Do not create a project if package.json isn't there
|
||||
const siblingFiles = readdirSync(
|
||||
join(context.workspaceRoot, angularWorkspaceRoot)
|
||||
);
|
||||
if (!siblingFiles.includes('package.json')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const hash = await calculateHashForCreateNodes(
|
||||
angularWorkspaceRoot,
|
||||
options,
|
||||
context,
|
||||
[getLockFileName(detectPackageManager(context.workspaceRoot))]
|
||||
);
|
||||
|
||||
projectsCache[hash] ??= await buildAngularProjects(
|
||||
configFilePath,
|
||||
options,
|
||||
angularWorkspaceRoot,
|
||||
context
|
||||
);
|
||||
|
||||
return { projects: projectsCache[hash] };
|
||||
}
|
||||
|
||||
async function buildAngularProjects(
|
||||
configFilePath: string,
|
||||
options: AngularPluginOptions,
|
||||
angularWorkspaceRoot: string,
|
||||
context: CreateNodesContextV2
|
||||
): Promise<AngularProjects> {
|
||||
const projects: Record<string, AngularProjects[string] & { root: string }> =
|
||||
{};
|
||||
|
||||
const absoluteConfigFilePath = join(context.workspaceRoot, configFilePath);
|
||||
const angularJson = readJsonFile<AngularJson>(absoluteConfigFilePath);
|
||||
|
||||
const appShellTargets: Target[] = [];
|
||||
const prerenderTargets: Target[] = [];
|
||||
for (const [projectName, project] of Object.entries(
|
||||
angularJson.projects ?? {}
|
||||
)) {
|
||||
const targets: Record<string, TargetConfiguration> = {};
|
||||
|
||||
const projectTargets = getAngularJsonProjectTargets(project);
|
||||
if (!projectTargets) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const namedInputs = getNamedInputs(project.root, context);
|
||||
|
||||
for (const [angularTargetName, angularTarget] of Object.entries(
|
||||
projectTargets
|
||||
)) {
|
||||
const nxTargetName = options?.targetNamePrefix
|
||||
? `${options.targetNamePrefix}${angularTargetName}`
|
||||
: angularTargetName;
|
||||
const externalDependencies = ['@angular/cli'];
|
||||
|
||||
targets[nxTargetName] = {
|
||||
command:
|
||||
// For targets that are also Angular CLI commands, infer the simplified form.
|
||||
// Otherwise, use `ng run` to support non-command targets so that they will run.
|
||||
angularTargetName === 'build' ||
|
||||
angularTargetName === 'deploy' ||
|
||||
angularTargetName === 'extract-i18n' ||
|
||||
angularTargetName === 'e2e' ||
|
||||
angularTargetName === 'lint' ||
|
||||
angularTargetName === 'serve' ||
|
||||
angularTargetName === 'test'
|
||||
? `ng ${angularTargetName}`
|
||||
: `ng run ${projectName}:${angularTargetName}`,
|
||||
options: { cwd: angularWorkspaceRoot },
|
||||
metadata: {
|
||||
technologies: ['angular'],
|
||||
description: `Run the "${angularTargetName}" target for "${projectName}".`,
|
||||
help: {
|
||||
command: `${pmc.exec} ng run ${projectName}:${angularTargetName} --help`,
|
||||
example: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (knownExecutors.appShell.has(angularTarget.builder)) {
|
||||
appShellTargets.push({ target: nxTargetName, project: projectName });
|
||||
} else if (knownExecutors.build.has(angularTarget.builder)) {
|
||||
await updateBuildTarget(
|
||||
nxTargetName,
|
||||
targets[nxTargetName],
|
||||
angularTarget,
|
||||
context,
|
||||
angularWorkspaceRoot,
|
||||
project.root,
|
||||
namedInputs
|
||||
);
|
||||
} else if (knownExecutors.devServer.has(angularTarget.builder)) {
|
||||
targets[nxTargetName].metadata.help.example.options = { port: 4201 };
|
||||
} else if (knownExecutors.extractI18n.has(angularTarget.builder)) {
|
||||
targets[nxTargetName].metadata.help.example.options = {
|
||||
format: 'json',
|
||||
};
|
||||
} else if (knownExecutors.test.has(angularTarget.builder)) {
|
||||
updateTestTarget(
|
||||
targets[nxTargetName],
|
||||
angularTarget,
|
||||
context,
|
||||
angularWorkspaceRoot,
|
||||
project.root,
|
||||
namedInputs,
|
||||
externalDependencies
|
||||
);
|
||||
} else if (knownExecutors.server.has(angularTarget.builder)) {
|
||||
updateServerTarget(
|
||||
targets[nxTargetName],
|
||||
angularTarget,
|
||||
context,
|
||||
angularWorkspaceRoot,
|
||||
project.root,
|
||||
namedInputs
|
||||
);
|
||||
} else if (knownExecutors.serveSsr.has(angularTarget.builder)) {
|
||||
targets[nxTargetName].metadata.help.example.options = { port: 4201 };
|
||||
} else if (knownExecutors.prerender.has(angularTarget.builder)) {
|
||||
prerenderTargets.push({ target: nxTargetName, project: projectName });
|
||||
}
|
||||
|
||||
if (targets[nxTargetName].inputs?.length) {
|
||||
targets[nxTargetName].inputs.push({ externalDependencies });
|
||||
}
|
||||
|
||||
if (angularTarget.configurations) {
|
||||
for (const configurationName of Object.keys(
|
||||
angularTarget.configurations
|
||||
)) {
|
||||
targets[nxTargetName].configurations = {
|
||||
...targets[nxTargetName].configurations,
|
||||
[configurationName]: {
|
||||
command: `ng run ${projectName}:${angularTargetName}:${configurationName}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (angularTarget.defaultConfiguration) {
|
||||
targets[nxTargetName].defaultConfiguration =
|
||||
angularTarget.defaultConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
projects[projectName] = {
|
||||
projectType: project.projectType,
|
||||
root: posix.join(angularWorkspaceRoot, project.root),
|
||||
sourceRoot: project.sourceRoot
|
||||
? posix.join(angularWorkspaceRoot, project.sourceRoot)
|
||||
: undefined,
|
||||
targets,
|
||||
};
|
||||
}
|
||||
|
||||
for (const { project, target } of appShellTargets) {
|
||||
updateAppShellTarget(
|
||||
project,
|
||||
target,
|
||||
projects,
|
||||
angularJson,
|
||||
angularWorkspaceRoot,
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
for (const { project, target } of prerenderTargets) {
|
||||
updatePrerenderTarget(project, target, projects, angularJson);
|
||||
}
|
||||
|
||||
return Object.entries(projects).reduce((acc, [projectName, project]) => {
|
||||
acc[project.root] = {
|
||||
projectType: project.projectType,
|
||||
sourceRoot: project.sourceRoot,
|
||||
targets: project.targets,
|
||||
};
|
||||
return acc;
|
||||
}, {} as AngularProjects);
|
||||
}
|
||||
|
||||
function updateAppShellTarget(
|
||||
projectName: string,
|
||||
targetName: string,
|
||||
projects: AngularProjects,
|
||||
angularJson: AngularJson,
|
||||
angularWorkspaceRoot: string,
|
||||
context: CreateNodesContextV2
|
||||
): void {
|
||||
// it must exist since we collected it when processing it
|
||||
const target = projects[projectName].targets[targetName];
|
||||
|
||||
target.metadata.help.example.options = { route: '/some/route' };
|
||||
|
||||
const { inputs, outputs } = getBrowserAndServerTargetInputsAndOutputs(
|
||||
projectName,
|
||||
targetName,
|
||||
projects,
|
||||
angularJson
|
||||
);
|
||||
|
||||
const outputIndexPath = getAngularJsonProjectTargets(
|
||||
angularJson.projects[projectName]
|
||||
)[targetName].options?.outputIndexPath;
|
||||
if (outputIndexPath) {
|
||||
const fullOutputIndexPath = join(
|
||||
context.workspaceRoot,
|
||||
angularWorkspaceRoot,
|
||||
outputIndexPath
|
||||
);
|
||||
outputs.push(
|
||||
getOutput(
|
||||
fullOutputIndexPath,
|
||||
context.workspaceRoot,
|
||||
angularWorkspaceRoot,
|
||||
angularJson.projects[projectName].root
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!outputs.length) {
|
||||
// no outputs were identified for the build or server target, so we don't
|
||||
// set any Nx cache options
|
||||
return;
|
||||
}
|
||||
|
||||
target.cache = true;
|
||||
target.inputs = inputs;
|
||||
target.outputs = outputs;
|
||||
}
|
||||
|
||||
async function updateBuildTarget(
|
||||
targetName: string,
|
||||
target: TargetConfiguration,
|
||||
angularTarget: AngularTargetConfiguration,
|
||||
context: CreateNodesContextV2,
|
||||
angularWorkspaceRoot: string,
|
||||
projectRoot: string,
|
||||
namedInputs: ReturnType<typeof getNamedInputs>
|
||||
): Promise<void> {
|
||||
target.dependsOn = [`^${targetName}`];
|
||||
|
||||
if (angularTarget.options?.outputPath) {
|
||||
const fullOutputPath = join(
|
||||
context.workspaceRoot,
|
||||
angularWorkspaceRoot,
|
||||
angularTarget.options.outputPath
|
||||
);
|
||||
target.outputs = [
|
||||
getOutput(
|
||||
fullOutputPath,
|
||||
context.workspaceRoot,
|
||||
angularWorkspaceRoot,
|
||||
projectRoot
|
||||
),
|
||||
];
|
||||
} else if (
|
||||
angularTarget.builder === '@angular-devkit/build-angular:ng-packagr'
|
||||
) {
|
||||
const outputs = await getNgPackagrOutputs(
|
||||
angularTarget,
|
||||
angularWorkspaceRoot,
|
||||
projectRoot,
|
||||
context
|
||||
);
|
||||
if (outputs.length) {
|
||||
target.outputs = outputs;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.outputs?.length) {
|
||||
// make it cacheable if we were able to identify outputs
|
||||
target.cache = true;
|
||||
target.inputs =
|
||||
'production' in namedInputs
|
||||
? ['production', '^production']
|
||||
: ['default', '^default'];
|
||||
}
|
||||
|
||||
if (angularTarget.builder === '@angular-devkit/build-angular:ng-packagr') {
|
||||
target.metadata.help.example.options = { watch: true };
|
||||
} else {
|
||||
target.metadata.help.example.options = { localize: true };
|
||||
}
|
||||
}
|
||||
|
||||
function updateTestTarget(
|
||||
target: TargetConfiguration,
|
||||
angularTarget: AngularTargetConfiguration,
|
||||
context: CreateNodesContextV2,
|
||||
angularWorkspaceRoot: string,
|
||||
projectRoot: string,
|
||||
namedInputs: ReturnType<typeof getNamedInputs>,
|
||||
externalDependencies: string[]
|
||||
): void {
|
||||
target.cache = true;
|
||||
target.inputs =
|
||||
'production' in namedInputs
|
||||
? ['default', '^production']
|
||||
: ['default', '^default'];
|
||||
target.outputs = getKarmaTargetOutputs(
|
||||
angularTarget,
|
||||
angularWorkspaceRoot,
|
||||
projectRoot,
|
||||
context
|
||||
);
|
||||
externalDependencies.push('karma');
|
||||
|
||||
target.metadata.help.example.options = { codeCoverage: true };
|
||||
}
|
||||
|
||||
function updateServerTarget(
|
||||
target: TargetConfiguration,
|
||||
angularTarget: AngularTargetConfiguration,
|
||||
context: CreateNodesContextV2,
|
||||
angularWorkspaceRoot: string,
|
||||
projectRoot: string,
|
||||
namedInputs: ReturnType<typeof getNamedInputs>
|
||||
): void {
|
||||
target.metadata.help.example.options = { localize: true };
|
||||
|
||||
if (!angularTarget.options?.outputPath) {
|
||||
// only make it cacheable if we were able to identify outputs
|
||||
return;
|
||||
}
|
||||
|
||||
target.cache = true;
|
||||
target.inputs =
|
||||
'production' in namedInputs
|
||||
? ['production', '^production']
|
||||
: ['default', '^default'];
|
||||
|
||||
const fullOutputPath = join(
|
||||
context.workspaceRoot,
|
||||
angularWorkspaceRoot,
|
||||
angularTarget.options.outputPath
|
||||
);
|
||||
target.outputs = [
|
||||
getOutput(
|
||||
fullOutputPath,
|
||||
context.workspaceRoot,
|
||||
angularWorkspaceRoot,
|
||||
projectRoot
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function updatePrerenderTarget(
|
||||
projectName: string,
|
||||
targetName: string,
|
||||
projects: AngularProjects,
|
||||
angularJson: AngularJson
|
||||
): void {
|
||||
// it must exist since we collected it when processing it
|
||||
const target = projects[projectName].targets[targetName];
|
||||
|
||||
target.metadata.help.example.options =
|
||||
getAngularJsonProjectTargets(angularJson.projects[projectName])[targetName]
|
||||
.builder === '@angular-devkit/build-angular:prerender'
|
||||
? { discoverRoutes: false }
|
||||
: { guessRoutes: false };
|
||||
|
||||
const { inputs, outputs } = getBrowserAndServerTargetInputsAndOutputs(
|
||||
projectName,
|
||||
targetName,
|
||||
projects,
|
||||
angularJson
|
||||
);
|
||||
|
||||
if (!outputs.length) {
|
||||
// no outputs were identified for the build or server target, so we don't
|
||||
// set any Nx cache options
|
||||
return;
|
||||
}
|
||||
|
||||
target.cache = true;
|
||||
target.inputs = inputs;
|
||||
target.outputs = outputs;
|
||||
}
|
||||
|
||||
async function getNgPackagrOutputs(
|
||||
target: AngularTargetConfiguration,
|
||||
angularWorkspaceRoot: string,
|
||||
projectRoot: string,
|
||||
context: CreateNodesContextV2
|
||||
): Promise<string[]> {
|
||||
let ngPackageJsonPath = join(
|
||||
context.workspaceRoot,
|
||||
angularWorkspaceRoot,
|
||||
target.options.project
|
||||
);
|
||||
|
||||
const readConfig = async (configPath: string) => {
|
||||
if (!existsSync(configPath)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
if (configPath.endsWith('.js')) {
|
||||
const result = await import(configPath);
|
||||
|
||||
return result['default'] ?? result;
|
||||
}
|
||||
|
||||
return readJsonFile(configPath);
|
||||
} catch {}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
let ngPackageJson: { dest?: string };
|
||||
let basePath: string;
|
||||
if (statSync(ngPackageJsonPath).isDirectory()) {
|
||||
basePath = ngPackageJsonPath;
|
||||
ngPackageJson = await readConfig(
|
||||
join(ngPackageJsonPath, 'ng-package.json')
|
||||
);
|
||||
if (!ngPackageJson) {
|
||||
ngPackageJson = await readConfig(
|
||||
join(ngPackageJsonPath, 'ng-package.js')
|
||||
);
|
||||
}
|
||||
} else {
|
||||
basePath = dirname(ngPackageJsonPath);
|
||||
ngPackageJson = await readConfig(ngPackageJsonPath);
|
||||
}
|
||||
|
||||
if (!ngPackageJson) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const destination = ngPackageJson.dest
|
||||
? join(basePath, ngPackageJson.dest)
|
||||
: join(basePath, 'dist');
|
||||
|
||||
return [
|
||||
getOutput(
|
||||
destination,
|
||||
context.workspaceRoot,
|
||||
angularWorkspaceRoot,
|
||||
projectRoot
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function getKarmaTargetOutputs(
|
||||
target: AngularTargetConfiguration,
|
||||
angularWorkspaceRoot: string,
|
||||
projectRoot: string,
|
||||
context: CreateNodesContextV2
|
||||
): string[] {
|
||||
const defaultOutput = posix.join(
|
||||
'{workspaceRoot}',
|
||||
angularWorkspaceRoot,
|
||||
'coverage/{projectName}'
|
||||
);
|
||||
if (!target.options?.karmaConfig) {
|
||||
return [defaultOutput];
|
||||
}
|
||||
|
||||
try {
|
||||
const { parseConfig } = require('karma/lib/config');
|
||||
|
||||
const karmaConfigPath = join(
|
||||
context.workspaceRoot,
|
||||
angularWorkspaceRoot,
|
||||
projectRoot,
|
||||
target.options.karmaConfig
|
||||
);
|
||||
const config = parseConfig(karmaConfigPath);
|
||||
|
||||
if (config.coverageReporter.dir) {
|
||||
return [
|
||||
getOutput(
|
||||
config.coverageReporter.dir,
|
||||
context.workspaceRoot,
|
||||
angularWorkspaceRoot,
|
||||
projectRoot
|
||||
),
|
||||
];
|
||||
}
|
||||
} catch {
|
||||
// we silently ignore any error here and fall back to the default output
|
||||
}
|
||||
|
||||
return [defaultOutput];
|
||||
}
|
||||
|
||||
function getBrowserAndServerTargetInputsAndOutputs(
|
||||
projectName: string,
|
||||
targetName: string,
|
||||
projects: AngularProjects,
|
||||
angularJson: AngularJson
|
||||
) {
|
||||
const { browserTarget, serverTarget } = extractBrowserAndServerTargets(
|
||||
angularJson,
|
||||
projectName,
|
||||
targetName
|
||||
);
|
||||
if (!browserTarget || !serverTarget) {
|
||||
// if any of these are missing, the target is invalid so we return empty values
|
||||
return { inputs: [], outputs: [] };
|
||||
}
|
||||
|
||||
const browserTargetInputs =
|
||||
projects[browserTarget.project]?.targets?.[browserTarget.target]?.inputs ??
|
||||
[];
|
||||
const serverTargetInputs =
|
||||
projects[serverTarget.project]?.targets?.[serverTarget.target]?.inputs ??
|
||||
[];
|
||||
const browserTargetOutputs =
|
||||
projects[browserTarget.project]?.targets?.[browserTarget.target]?.outputs ??
|
||||
[];
|
||||
const serverTargetOutputs =
|
||||
projects[serverTarget.project]?.targets?.[serverTarget.target]?.outputs ??
|
||||
[];
|
||||
|
||||
return {
|
||||
inputs: mergeInputs(...browserTargetInputs, ...serverTargetInputs),
|
||||
outputs: Array.from(
|
||||
new Set([...browserTargetOutputs, ...serverTargetOutputs])
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function extractBrowserAndServerTargets(
|
||||
angularJson: AngularJson,
|
||||
projectName: string,
|
||||
targetName: string
|
||||
): {
|
||||
browserTarget: Target;
|
||||
serverTarget: Target;
|
||||
} {
|
||||
let browserTarget: Target | undefined;
|
||||
let serverTarget: Target | undefined;
|
||||
|
||||
try {
|
||||
const targets = getAngularJsonProjectTargets(
|
||||
angularJson.projects[projectName]
|
||||
);
|
||||
const target = targets[targetName];
|
||||
|
||||
let browserTargetSpecifier = target.options?.browserTarget;
|
||||
if (!browserTargetSpecifier) {
|
||||
const configuration = Object.values(target.configurations ?? {}).find(
|
||||
(config) => !!config.browserTarget
|
||||
);
|
||||
browserTargetSpecifier = configuration?.browserTarget;
|
||||
}
|
||||
|
||||
if (browserTargetSpecifier) {
|
||||
browserTarget = targetFromTargetString(
|
||||
browserTargetSpecifier,
|
||||
projectName,
|
||||
targetName
|
||||
);
|
||||
}
|
||||
|
||||
let serverTargetSpecifier = target.options?.serverTarget;
|
||||
if (!serverTargetSpecifier) {
|
||||
serverTargetSpecifier = Object.values(target.configurations ?? {}).find(
|
||||
(config) => !!config.serverTarget
|
||||
)?.serverTarget;
|
||||
}
|
||||
|
||||
if (serverTargetSpecifier) {
|
||||
serverTarget = targetFromTargetString(
|
||||
serverTargetSpecifier,
|
||||
projectName,
|
||||
targetName
|
||||
);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return { browserTarget: browserTarget, serverTarget };
|
||||
}
|
||||
|
||||
function mergeInputs(
|
||||
...inputs: TargetConfiguration['inputs']
|
||||
): TargetConfiguration['inputs'] {
|
||||
const stringInputs = new Set<string>();
|
||||
const externalDependencies = new Set<string>();
|
||||
|
||||
for (const input of inputs) {
|
||||
if (typeof input === 'string') {
|
||||
stringInputs.add(input);
|
||||
} else if ('externalDependencies' in input) {
|
||||
// we only infer external dependencies, so we don't need to handle the other input definitions
|
||||
for (const externalDependency of input.externalDependencies) {
|
||||
externalDependencies.add(externalDependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
...stringInputs,
|
||||
...(externalDependencies.size
|
||||
? [{ externalDependencies: Array.from(externalDependencies) }]
|
||||
: []),
|
||||
];
|
||||
}
|
||||
|
||||
// angular support abbreviated target specifiers, this is adapter from:
|
||||
// https://github.com/angular/angular-cli/blob/7d9ce246a33c60ec96eb4bf99520f5475716a910/packages/angular_devkit/architect/src/api.ts#L336
|
||||
function targetFromTargetString(
|
||||
specifier: string,
|
||||
abbreviatedProjectName?: string,
|
||||
abbreviatedTargetName?: string
|
||||
) {
|
||||
const tuple = specifier.split(':', 3);
|
||||
if (tuple.length < 2) {
|
||||
// invalid target, ignore
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// we only care about project and target
|
||||
return {
|
||||
project: tuple[0] || abbreviatedProjectName || '',
|
||||
target: tuple[1] || abbreviatedTargetName || '',
|
||||
};
|
||||
}
|
||||
|
||||
function getOutput(
|
||||
path: string,
|
||||
workspaceRoot: string,
|
||||
angularWorkspaceRoot: string,
|
||||
projectRoot: string
|
||||
): string {
|
||||
const relativePath = relative(
|
||||
join(workspaceRoot, angularWorkspaceRoot, projectRoot),
|
||||
path
|
||||
);
|
||||
if (relativePath.startsWith('..')) {
|
||||
return posix.join(
|
||||
'{workspaceRoot}',
|
||||
join(angularWorkspaceRoot, projectRoot, relativePath)
|
||||
);
|
||||
} else {
|
||||
return posix.join('{projectRoot}', relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
function getAngularJsonProjectTargets(
|
||||
project: AngularProjectConfiguration
|
||||
): Record<string, AngularTargetConfiguration> {
|
||||
return project.architect ?? project.targets;
|
||||
}
|
||||
@ -104,21 +104,64 @@ async function _addPluginInternal<PluginOptions>(
|
||||
|
||||
let pluginOptions: PluginOptions;
|
||||
let projConfigs: ConfigurationResult;
|
||||
const combinations = generateCombinations(options);
|
||||
optionsLoop: for (const _pluginOptions of combinations) {
|
||||
pluginOptions = _pluginOptions as PluginOptions;
|
||||
|
||||
nxJson.plugins ??= [];
|
||||
if (
|
||||
nxJson.plugins.some((p) =>
|
||||
typeof p === 'string'
|
||||
? p === pluginName
|
||||
: p.plugin === pluginName && !p.include
|
||||
)
|
||||
) {
|
||||
// Plugin has already been added
|
||||
return;
|
||||
if (Object.keys(options).length > 0) {
|
||||
const combinations = generateCombinations(options);
|
||||
optionsLoop: for (const _pluginOptions of combinations) {
|
||||
pluginOptions = _pluginOptions as PluginOptions;
|
||||
|
||||
nxJson.plugins ??= [];
|
||||
if (
|
||||
nxJson.plugins.some((p) =>
|
||||
typeof p === 'string'
|
||||
? p === pluginName
|
||||
: p.plugin === pluginName && !p.include
|
||||
)
|
||||
) {
|
||||
// Plugin has already been added
|
||||
return;
|
||||
}
|
||||
global.NX_GRAPH_CREATION = true;
|
||||
try {
|
||||
projConfigs = await retrieveProjectConfigurations(
|
||||
[pluginFactory(pluginOptions)],
|
||||
tree.root,
|
||||
nxJson
|
||||
);
|
||||
} catch (e) {
|
||||
// Errors are okay for this because we're only running 1 plugin
|
||||
if (e instanceof ProjectConfigurationsError) {
|
||||
projConfigs = e.partialProjectConfigurationsResult;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
global.NX_GRAPH_CREATION = false;
|
||||
|
||||
for (const projConfig of Object.values(projConfigs.projects)) {
|
||||
const node = graphNodes.find(
|
||||
(node) => node.data.root === projConfig.root
|
||||
);
|
||||
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const targetName in projConfig.targets) {
|
||||
if (node.data.targets[targetName]) {
|
||||
// Conflicting Target Name, check the next one
|
||||
pluginOptions = null;
|
||||
continue optionsLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// If the plugin does not take in options, we add the plugin with empty options.
|
||||
nxJson.plugins ??= [];
|
||||
pluginOptions = {} as unknown as PluginOptions;
|
||||
global.NX_GRAPH_CREATION = true;
|
||||
try {
|
||||
projConfigs = await retrieveProjectConfigurations(
|
||||
@ -135,26 +178,6 @@ async function _addPluginInternal<PluginOptions>(
|
||||
}
|
||||
}
|
||||
global.NX_GRAPH_CREATION = false;
|
||||
|
||||
for (const projConfig of Object.values(projConfigs.projects)) {
|
||||
const node = graphNodes.find(
|
||||
(node) => node.data.root === projConfig.root
|
||||
);
|
||||
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const targetName in projConfig.targets) {
|
||||
if (node.data.targets[targetName]) {
|
||||
// Conflicting Target Name, check the next one
|
||||
pluginOptions = null;
|
||||
continue optionsLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pluginOptions) {
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
import { join } from 'path';
|
||||
import { CreateNodesContext, hashArray } from 'nx/src/devkit-exports';
|
||||
import {
|
||||
CreateNodesContext,
|
||||
CreateNodesContextV2,
|
||||
hashArray,
|
||||
} from 'nx/src/devkit-exports';
|
||||
|
||||
import { hashObject, hashWithWorkspaceContext } from 'nx/src/devkit-internals';
|
||||
|
||||
export async function calculateHashForCreateNodes(
|
||||
projectRoot: string,
|
||||
options: object,
|
||||
context: CreateNodesContext,
|
||||
context: CreateNodesContext | CreateNodesContextV2,
|
||||
additionalGlobs: string[] = []
|
||||
): Promise<string> {
|
||||
return hashArray([
|
||||
|
||||
@ -6,6 +6,7 @@ import type { InputDefinition } from 'nx/src/config/workspace-json-project-json'
|
||||
|
||||
import {
|
||||
CreateNodesContext,
|
||||
CreateNodesContextV2,
|
||||
ProjectConfiguration,
|
||||
readJsonFile,
|
||||
} from 'nx/src/devkit-exports';
|
||||
@ -15,7 +16,7 @@ import {
|
||||
*/
|
||||
export function getNamedInputs(
|
||||
directory: string,
|
||||
context: CreateNodesContext
|
||||
context: CreateNodesContext | CreateNodesContextV2
|
||||
): { [inputName: string]: (string | InputDefinition)[] } {
|
||||
const projectJsonPath = join(directory, 'project.json');
|
||||
const projectJson: ProjectConfiguration = existsSync(projectJsonPath)
|
||||
|
||||
@ -59,7 +59,15 @@ export interface ImportOptions {
|
||||
}
|
||||
|
||||
export async function importHandler(options: ImportOptions) {
|
||||
process.env.NX_RUNNING_NX_IMPORT = 'true';
|
||||
let { sourceRepository, ref, source, destination } = options;
|
||||
const destinationGitClient = new GitRepository(process.cwd());
|
||||
|
||||
if (await destinationGitClient.hasUncommittedChanges()) {
|
||||
throw new Error(
|
||||
`You have uncommitted changes in the destination repository. Commit or revert the changes and try again.`
|
||||
);
|
||||
}
|
||||
|
||||
output.log({
|
||||
title:
|
||||
@ -186,7 +194,6 @@ export async function importHandler(options: ImportOptions) {
|
||||
|
||||
const absDestination = join(process.cwd(), destination);
|
||||
|
||||
const destinationGitClient = new GitRepository(process.cwd());
|
||||
await assertDestinationEmpty(destinationGitClient, absDestination);
|
||||
|
||||
const tempImportBranch = getTempImportBranch(ref);
|
||||
@ -259,7 +266,8 @@ export async function importHandler(options: ImportOptions) {
|
||||
|
||||
const { plugins, updatePackageScripts } = await detectPlugins(
|
||||
nxJson,
|
||||
options.interactive
|
||||
options.interactive,
|
||||
true
|
||||
);
|
||||
|
||||
if (packageManager !== sourcePackageManager) {
|
||||
|
||||
@ -157,7 +157,7 @@ export async function initHandler(options: InitArgs): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
const npmPackageToPluginMap: Record<string, string> = {
|
||||
const npmPackageToPluginMap: Record<string, `@nx/${string}`> = {
|
||||
// Generic JS tools
|
||||
eslint: '@nx/eslint',
|
||||
storybook: '@nx/storybook',
|
||||
@ -181,7 +181,8 @@ const npmPackageToPluginMap: Record<string, string> = {
|
||||
|
||||
export async function detectPlugins(
|
||||
nxJson: NxJsonConfiguration,
|
||||
interactive: boolean
|
||||
interactive: boolean,
|
||||
includeAngularCli?: boolean
|
||||
): Promise<{
|
||||
plugins: string[];
|
||||
updatePackageScripts: boolean;
|
||||
@ -214,7 +215,13 @@ export async function detectPlugins(
|
||||
...packageJson.devDependencies,
|
||||
};
|
||||
|
||||
for (const [dep, plugin] of Object.entries(npmPackageToPluginMap)) {
|
||||
const _npmPackageToPluginMap = {
|
||||
...npmPackageToPluginMap,
|
||||
};
|
||||
if (includeAngularCli) {
|
||||
_npmPackageToPluginMap['@angular/cli'] = '@nx/angular';
|
||||
}
|
||||
for (const [dep, plugin] of Object.entries(_npmPackageToPluginMap)) {
|
||||
if (deps[dep]) {
|
||||
detectedPlugins.add(plugin);
|
||||
}
|
||||
|
||||
@ -45,6 +45,11 @@ export class GitRepository {
|
||||
.trim();
|
||||
}
|
||||
|
||||
async hasUncommittedChanges() {
|
||||
const data = await this.execAsync(`git status --porcelain`);
|
||||
return data.trim() !== '';
|
||||
}
|
||||
|
||||
async addFetchRemote(remoteName: string, branch: string) {
|
||||
return await this.execAsync(
|
||||
`git config --add remote.${remoteName}.fetch "+refs/heads/${branch}:refs/remotes/${remoteName}/${branch}"`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user