cleanup(core): inline angular cli nested project migration logic into nx init (#15396)

This commit is contained in:
Leosvel Pérez Espinosa 2023-03-07 23:08:10 +00:00 committed by GitHub
parent fca1f97ab7
commit e1a2c98556
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 644 additions and 27 deletions

View File

@ -121,6 +121,7 @@
/packages/nx/src/adapter @AgentEnder @leosvelperez
/packages/nx/src/native @vsavkin @FrozenPandaz @Cammisuli
/packages/nx/src/lock-file @meeroslav @FrozenPandaz
/packages/nx/src/nx-init/angular/** @Coly010 @leosvelperez @FrozenPandaz
/e2e/nx*/** @FrozenPandaz @AgentEnder @vsavkin
/packages/workspace/** @FrozenPandaz @AgentEnder @vsavkin
/e2e/workspace-create/** @FrozenPandaz @AgentEnder @vsavkin

View File

@ -0,0 +1,65 @@
import { PackageManager } from 'nx/src/utils/package-manager';
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
getPackageManagerCommand,
getPublishedVersion,
getSelectedPackageManager,
runCLI,
runCommand,
runNgNew,
uniq,
} from '../../utils';
describe('nx init (Angular CLI)', () => {
let project: string;
let packageManager: PackageManager;
let pmc: ReturnType<typeof getPackageManagerCommand>;
beforeEach(() => {
project = uniq('proj');
packageManager = getSelectedPackageManager();
// TODO: solve issues with pnpm and remove this fallback
packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager;
pmc = getPackageManagerCommand({ packageManager });
});
afterEach(() => {
cleanupProject();
});
it('should successfully convert an Angular CLI workspace to an Nx workspace', () => {
runNgNew(project, packageManager);
const output = runCommand(
`${pmc.runUninstalledPackage} nx@${getPublishedVersion()} init -y`
);
expect(output).toContain('Nx is now enabled in your workspace!');
// angular.json should have been deleted
checkFilesDoNotExist('angular.json');
// check nx config files exist
checkFilesExist('nx.json', 'project.json');
// check build
const coldBuildOutput = runCLI(`build ${project} --outputHashing none`);
expect(coldBuildOutput).toContain(
`> nx run ${project}:build:production --outputHashing none`
);
expect(coldBuildOutput).toContain(
`Successfully ran target build for project ${project}`
);
checkFilesExist(`dist/${project}/main.js`);
// run build again to check is coming from cache
const cachedBuildOutput = runCLI(`build ${project} --outputHashing none`);
expect(cachedBuildOutput).toContain(
`> nx run ${project}:build:production --outputHashing none [local cache]`
);
expect(cachedBuildOutput).toContain('Nx read the output from the cache');
expect(cachedBuildOutput).toContain(
`Successfully ran target build for project ${project}`
);
});
});

View File

@ -15,7 +15,7 @@ import {
} from './get-env-info';
import * as isCI from 'is-ci';
import { angularCliVersion } from '@nrwl/workspace/src/utils/versions';
import { angularCliVersion as defaultAngularCliVersion } from '@nrwl/workspace/src/utils/versions';
import { dump } from '@zkochan/js-yaml';
import { execSync, ExecSyncOptions } from 'child_process';
@ -247,7 +247,8 @@ export function packageInstall(
export function runNgNew(
projectName: string,
packageManager = getSelectedPackageManager()
packageManager = getSelectedPackageManager(),
angularCliVersion = defaultAngularCliVersion
): string {
projName = projectName;

View File

@ -101,8 +101,7 @@ async function addNxToMonorepo() {
repoRoot,
targetDefaults,
cacheableOperations,
scriptOutputs,
undefined
scriptOutputs
);
addDepsToPackageJson(repoRoot, useCloud);
@ -111,7 +110,7 @@ async function addNxToMonorepo() {
runInstall(repoRoot);
if (useCloud) {
initCloud(repoRoot);
initCloud(repoRoot, 'nx-init-monorepo');
}
printFinalMessage();

View File

@ -1,12 +1,13 @@
import { execSync } from 'child_process';
import { existsSync } from 'fs';
import { prerelease } from 'semver';
import * as parser from 'yargs-parser';
import { addNxToNest } from '../nx-init/add-nx-to-nest';
import { addNxToNpmRepo } from '../nx-init/add-nx-to-npm-repo';
import { addNxToAngularCliRepo } from '../nx-init/angular';
import { generateEncapsulatedNxSetup } from '../nx-init/encapsulated/add-nx-scripts';
import { directoryExists, readJsonFile } from '../utils/fileutils';
import { PackageJson } from '../utils/package-json';
import * as parser from 'yargs-parser';
import { generateEncapsulatedNxSetup } from '../nx-init/encapsulated/add-nx-scripts';
import { prerelease } from 'semver';
export async function initHandler() {
const args = process.argv.slice(2).join(' ');
@ -43,10 +44,7 @@ export async function initHandler() {
} else if (existsSync('package.json')) {
const packageJson: PackageJson = readJsonFile('package.json');
if (existsSync('angular.json')) {
// TODO(leo): remove make-angular-cli-faster
execSync(`npx --yes make-angular-cli-faster@${version} ${args}`, {
stdio: [0, 1, 2],
});
await addNxToAngularCliRepo();
} else if (isCRA(packageJson)) {
// TODO(jack): remove cra-to-nx
execSync(`npx --yes cra-to-nx@${version} ${args}`, {

View File

@ -108,8 +108,7 @@ export async function addNxToNest(packageJson: PackageJson) {
repoRoot,
[],
[...cacheableOperations, ...nestCacheableScripts],
{},
packageJson.name
{}
);
const pmc = getPackageManagerCommand();
@ -136,7 +135,7 @@ export async function addNxToNest(packageJson: PackageJson) {
runInstall(repoRoot);
if (useCloud) {
initCloud(repoRoot);
initCloud(repoRoot, 'nx-init-nest');
}
printFinalMessage();

View File

@ -76,7 +76,7 @@ export async function addNxToNpmRepo() {
useCloud = false;
}
createNxJsonFile(repoRoot, [], cacheableOperations, {}, packageJson.name);
createNxJsonFile(repoRoot, [], cacheableOperations, {});
const pmc = getPackageManagerCommand();
@ -93,7 +93,7 @@ export async function addNxToNpmRepo() {
runInstall(repoRoot, pmc);
if (useCloud) {
initCloud(repoRoot);
initCloud(repoRoot, 'nx-init-npm-repo');
}
printFinalMessage();

View File

@ -0,0 +1,388 @@
import { prompt } from 'enquirer';
import { unlinkSync } from 'fs';
import { dirname, join, relative, resolve } from 'path';
import { toNewFormat } from '../../adapter/angular-json';
import { NxJsonConfiguration } from '../../config/nx-json';
import {
ProjectConfiguration,
TargetConfiguration,
} from '../../config/workspace-json-project-json';
import { fileExists, readJsonFile, writeJsonFile } from '../../utils/fileutils';
import { sortObjectByKeys } from '../../utils/object-sort';
import { output } from '../../utils/output';
import { PackageJson } from '../../utils/package-json';
import { normalizePath } from '../../utils/path';
import {
addDepsToPackageJson,
addVsCodeRecommendedExtensions,
askAboutNxCloud,
createNxJsonFile,
initCloud,
runInstall,
} from '../utils';
import { getLegacyMigrationFunctionIfApplicable } from './legacy-angular-versions';
import yargsParser = require('yargs-parser');
const defaultCacheableOperations: string[] = [
'build',
'server',
'test',
'lint',
];
const parsedArgs = yargsParser(process.argv, {
boolean: ['yes'],
string: ['cacheable'], // only used for testing
alias: {
yes: ['y'],
},
});
let repoRoot: string;
let workspaceTargets: string[];
export async function addNxToAngularCliRepo() {
repoRoot = process.cwd();
output.log({ title: '🧐 Checking versions compatibility' });
const legacyMigrationFn = await getLegacyMigrationFunctionIfApplicable(
repoRoot,
parsedArgs.yes !== true
);
if (legacyMigrationFn) {
output.log({ title: '💽 Running migration for a legacy Angular version' });
await legacyMigrationFn();
process.exit(0);
}
output.success({
title:
'✅ The Angular version is compatible with the latest version of Nx!',
});
output.log({ title: '🐳 Nx initialization' });
const cacheableOperations = await collectCacheableOperations();
const useNxCloud = parsedArgs.yes !== true ? await askAboutNxCloud() : false;
output.log({ title: '📦 Installing dependencies' });
installDependencies(useNxCloud);
output.log({ title: '📝 Setting up workspace' });
await setupWorkspace(cacheableOperations);
if (useNxCloud) {
output.log({ title: '🛠️ Setting up Nx Cloud' });
initCloud(repoRoot, 'nx-init-angular');
}
output.success({
title: '🎉 Nx is now enabled in your workspace!',
bodyLines: [
`Execute 'npx nx build' twice to see the computation caching in action.`,
'Learn more about the changes done to your workspace at https://nx.dev/recipes/adopting-nx/migration-angular.',
],
});
}
async function collectCacheableOperations(): Promise<string[]> {
let cacheableOperations: string[];
workspaceTargets = getWorkspaceTargets();
const defaultCacheableTargetsInWorkspace = defaultCacheableOperations.filter(
(t) => workspaceTargets.includes(t)
);
if (parsedArgs.yes !== true) {
output.log({
title: `🧑‍🔧 Please answer the following questions about the targets found in your angular.json in order to generate task runner configuration`,
});
cacheableOperations = (
(await prompt([
{
type: 'multiselect',
name: 'cacheableOperations',
initial: defaultCacheableTargetsInWorkspace as any,
message:
'Which of the following targets are cacheable? (Produce the same output given the same input, e.g. build, test and lint usually are, serve and start are not)',
// enquirer mutates the array below, create a new one to avoid it
choices: [...workspaceTargets],
},
])) as any
).cacheableOperations;
} else {
cacheableOperations = parsedArgs.cacheable
? parsedArgs.cacheable.split(',')
: defaultCacheableTargetsInWorkspace;
}
return cacheableOperations;
}
function installDependencies(useNxCloud: boolean): void {
addDepsToPackageJson(repoRoot, useNxCloud);
addPluginDependencies();
runInstall(repoRoot);
}
function addPluginDependencies(): void {
const packageJsonPath = join(repoRoot, 'package.json');
const packageJson = readJsonFile<PackageJson>(packageJsonPath);
const nxVersion = require('../../../package.json').version;
packageJson.devDependencies ??= {};
packageJson.devDependencies['@nrwl/angular'] = nxVersion;
packageJson.devDependencies['@nrwl/workspace'] = nxVersion;
const peerDepsToInstall = [
'@angular-devkit/core',
'@angular-devkit/schematics',
'@schematics/angular',
];
const angularCliVersion =
packageJson.devDependencies['@angular/cli'] ??
packageJson.dependencies?.['@angular/cli'] ??
packageJson.devDependencies['@angular-devkit/build-angular'] ??
packageJson.dependencies?.['@angular-devkit/build-angular'];
for (const dep of peerDepsToInstall) {
if (!packageJson.devDependencies[dep] && !packageJson.dependencies?.[dep]) {
packageJson.devDependencies[dep] = angularCliVersion;
}
}
packageJson.devDependencies = sortObjectByKeys(packageJson.devDependencies);
writeJsonFile(packageJsonPath, packageJson);
}
type AngularJsonConfigTargetConfiguration = Exclude<
TargetConfiguration,
'command' | 'executor' | 'outputs' | 'dependsOn' | 'inputs'
> & {
builder: string;
};
type AngularJsonProjectConfiguration = {
root: string;
sourceRoot: string;
architect?: Record<string, AngularJsonConfigTargetConfiguration>;
};
interface AngularJsonConfig {
projects: Record<string, AngularJsonProjectConfiguration>;
defaultProject?: string;
}
async function setupWorkspace(cacheableOperations: string[]): Promise<void> {
const angularJsonPath = join(repoRoot, 'angular.json');
const angularJson = readJsonFile<AngularJsonConfig>(angularJsonPath);
const workspaceCapabilities = getWorkspaceCapabilities(angularJson.projects);
createNxJson(angularJson, cacheableOperations, workspaceCapabilities);
addVsCodeRecommendedExtensions(
repoRoot,
[
'nrwl.angular-console',
'angular.ng-template',
workspaceCapabilities.eslintProjectConfigFile
? 'dbaeumer.vscode-eslint'
: undefined,
].filter(Boolean)
);
replaceNgWithNxInPackageJsonScripts();
// convert workspace config format to standalone project configs
// update its targets outputs and delete angular.json
const projects = toNewFormat(angularJson).projects;
for (const [projectName, project] of Object.entries(projects)) {
updateProjectOutputs(project);
writeJsonFile(join(project.root, 'project.json'), {
$schema: normalizePath(
relative(
join(repoRoot, project.root),
join(repoRoot, 'node_modules/nx/schemas/project-schema.json')
)
),
name: projectName,
...project,
root: undefined,
});
}
unlinkSync(angularJsonPath);
}
type WorkspaceCapabilities = {
eslintProjectConfigFile: boolean;
test: boolean;
karmaProjectConfigFile: boolean;
};
function createNxJson(
angularJson: AngularJsonConfig,
cacheableOperations: string[],
{
eslintProjectConfigFile,
test,
karmaProjectConfigFile,
}: WorkspaceCapabilities
): void {
createNxJsonFile(repoRoot, [], cacheableOperations, {});
const nxJson = readJsonFile<NxJsonConfiguration>(join(repoRoot, 'nx.json'));
nxJson.namedInputs = {
sharedGlobals: [],
default: ['{projectRoot}/**/*', 'sharedGlobals'],
production: [
'default',
...(test
? [
'!{projectRoot}/tsconfig.spec.json',
'!{projectRoot}/**/*.spec.[jt]s',
karmaProjectConfigFile ? '!{projectRoot}/karma.conf.js' : undefined,
].filter(Boolean)
: []),
eslintProjectConfigFile ? '!{projectRoot}/.eslintrc.json' : undefined,
].filter(Boolean),
};
nxJson.targetDefaults = {};
if (workspaceTargets.includes('build')) {
nxJson.targetDefaults.build = {
dependsOn: ['^build'],
inputs: ['production', '^production'],
};
}
if (workspaceTargets.includes('server')) {
nxJson.targetDefaults.server = { inputs: ['production', '^production'] };
}
if (workspaceTargets.includes('test')) {
const inputs = ['default', '^production'];
if (fileExists(join(repoRoot, 'karma.conf.js'))) {
inputs.push('{workspaceRoot}/karma.conf.js');
}
nxJson.targetDefaults.test = { inputs };
}
if (workspaceTargets.includes('lint')) {
const inputs = ['default'];
if (fileExists(join(repoRoot, '.eslintrc.json'))) {
inputs.push('{workspaceRoot}/.eslintrc.json');
}
nxJson.targetDefaults.lint = { inputs };
}
if (workspaceTargets.includes('e2e')) {
nxJson.targetDefaults.e2e = { inputs: ['default', '^production'] };
}
// Angular 14 workspaces support defaultProject, keep it until we drop support
nxJson.defaultProject = angularJson.defaultProject;
writeJsonFile(join(repoRoot, 'nx.json'), nxJson);
}
function updateProjectOutputs(project: ProjectConfiguration): void {
Object.values(project.targets ?? {}).forEach((target) => {
if (
[
'@angular-devkit/build-angular:browser',
'@angular-builders/custom-webpack:browser',
'ngx-build-plus:browser',
'@angular-devkit/build-angular:server',
'@angular-builders/custom-webpack:server',
'ngx-build-plus:server',
].includes(target.executor)
) {
target.outputs = ['{options.outputPath}'];
} else if (target.executor === '@angular-eslint/builder:lint') {
target.outputs = ['{options.outputFile}'];
} else if (target.executor === '@angular-devkit/build-angular:ng-packagr') {
try {
const ngPackageJsonPath = join(repoRoot, target.options.project);
const ngPackageJson = readJsonFile(ngPackageJsonPath);
const outputPath = relative(
repoRoot,
resolve(dirname(ngPackageJsonPath), ngPackageJson.dest)
);
target.outputs = [`{workspaceRoot}/${normalizePath(outputPath)}`];
} catch {}
}
});
}
function getWorkspaceTargets(): string[] {
const { projects } = readJsonFile<AngularJsonConfig>(
join(repoRoot, 'angular.json')
);
const targets = new Set<string>();
for (const project of Object.values(projects ?? {})) {
for (const target of Object.keys(project.architect ?? {})) {
targets.add(target);
}
}
return Array.from(targets);
}
function getWorkspaceCapabilities(
projects: Record<string, AngularJsonProjectConfiguration>
): WorkspaceCapabilities {
const capabilities = {
eslintProjectConfigFile: false,
test: false,
karmaProjectConfigFile: false,
};
for (const project of Object.values(projects)) {
if (
!capabilities.eslintProjectConfigFile &&
projectHasEslintConfig(project)
) {
capabilities.eslintProjectConfigFile = true;
}
if (!capabilities.test && projectUsesKarmaBuilder(project)) {
capabilities.test = true;
}
if (
!capabilities.karmaProjectConfigFile &&
projectHasKarmaConfig(project)
) {
capabilities.karmaProjectConfigFile = true;
}
if (
capabilities.eslintProjectConfigFile &&
capabilities.test &&
capabilities.karmaProjectConfigFile
) {
return capabilities;
}
}
return capabilities;
}
function projectUsesKarmaBuilder(
project: AngularJsonProjectConfiguration
): boolean {
return Object.values(project.architect ?? {}).some(
(target) => target.builder === '@angular-devkit/build-angular:karma'
);
}
function projectHasKarmaConfig(
project: AngularJsonProjectConfiguration
): boolean {
return fileExists(join(project.root, 'karma.conf.js'));
}
function projectHasEslintConfig(
project: AngularJsonProjectConfiguration
): boolean {
return fileExists(join(project.root, '.eslintrc.json'));
}
function replaceNgWithNxInPackageJsonScripts(): void {
const packageJsonPath = join(repoRoot, 'package.json');
const packageJson = readJsonFile<PackageJson>(packageJsonPath);
packageJson.scripts ??= {};
Object.keys(packageJson.scripts).forEach((script) => {
packageJson.scripts[script] = packageJson.scripts[script]
.replace(/^nx$/, 'nx')
.replace(/^ng /, 'nx ')
.replace(/ ng /g, ' nx ');
});
writeJsonFile(packageJsonPath, packageJson);
}

View File

@ -0,0 +1,133 @@
import { execSync } from 'child_process';
import { join } from 'path';
import { gte, major } from 'semver';
import { readJsonFile, writeJsonFile } from '../../utils/fileutils';
import { sortObjectByKeys } from '../../utils/object-sort';
import { output } from '../../utils/output';
import { readModulePackageJson } from '../../utils/package-json';
import {
PackageManagerCommands,
getPackageManagerCommand,
resolvePackageVersionUsingInstallation,
resolvePackageVersionUsingRegistry,
} from '../../utils/package-manager';
import { askAboutNxCloud, initCloud } from '../utils';
// map of Angular major versions to Nx versions to use for legacy `nx init` migrations,
// key is major Angular version and value is Nx version to use
const nxAngularLegacyVersionMap: Record<number, string> = {};
// min major angular version supported in latest Nx
const minMajorAngularVersionSupported = 14;
// version when the Nx CLI changed from @nrwl/tao & @nrwl/cli to nx
const versionWithConsolidatedPackages = '13.9.0';
export async function getLegacyMigrationFunctionIfApplicable(
repoRoot: string,
interactive: boolean
): Promise<() => Promise<void> | null> {
const angularVersion =
readModulePackageJson('@angular/core').packageJson.version;
const majorAngularVersion = major(angularVersion);
if (majorAngularVersion > minMajorAngularVersionSupported) {
// non-legacy
return null;
}
let legacyMigrationCommand: string;
let pkgName: string;
let pkgVersion: string;
if (majorAngularVersion < 13) {
// for versions lower than 13, the migration was in @nrwl/workspace:ng-add
pkgName = '@nrwl/workspace';
pkgVersion = await resolvePackageVersion(
pkgName,
`^${majorAngularVersion}.0.0`
);
legacyMigrationCommand = `ng g ${pkgName}:ng-add --preserveAngularCLILayout`;
} else if (majorAngularVersion < 14) {
// for v13, the migration was in @nrwl/angular:ng-add
pkgName = '@nrwl/angular';
pkgVersion = await resolvePackageVersion(pkgName, '~14.1.0');
legacyMigrationCommand = `ng g ${pkgName}:ng-add --preserve-angular-cli-layout`;
} else {
// use the latest Nx version that supported the Angular version
legacyMigrationCommand = `nx@${
nxAngularLegacyVersionMap[majorAngularVersion]
} init ${process.argv.slice(2).join(' ')}`;
}
return async () => {
output.log({ title: '🐳 Nx initialization' });
const useNxCloud = interactive ? await askAboutNxCloud() : false;
output.log({ title: '📦 Installing dependencies' });
const pmc = getPackageManagerCommand();
await installDependencies(repoRoot, pkgName, pkgVersion, useNxCloud, pmc);
output.log({ title: '📝 Setting up workspace' });
execSync(`${pmc.exec} ${legacyMigrationCommand}`, { stdio: [0, 1, 2] });
if (useNxCloud) {
output.log({ title: '🛠️ Setting up Nx Cloud' });
initCloud(repoRoot, 'nx-init-angular');
}
output.success({
title: '🎉 Nx is now enabled in your workspace!',
bodyLines: [
`Execute 'npx nx build' twice to see the computation caching in action.`,
'Learn more about the changes done to your workspace at https://nx.dev/recipes/adopting-nx/migration-angular.',
],
});
};
}
async function installDependencies(
repoRoot: string,
pkgName: string,
pkgVersion: string,
useNxCloud: boolean,
pmc: PackageManagerCommands
): Promise<void> {
const json = readJsonFile(join(repoRoot, 'package.json'));
json.devDependencies ??= {};
json.devDependencies['@nrwl/workspace'] = pkgVersion;
if (gte(pkgVersion, versionWithConsolidatedPackages)) {
json.devDependencies['nx'] = pkgVersion;
} else {
json.devDependencies['@nrwl/cli'] = pkgVersion;
json.devDependencies['@nrwl/tao'] = pkgVersion;
}
if (useNxCloud) {
// get the latest @nrwl/nx-cloud version compatible with the Nx major
// version being installed
json.devDependencies['@nrwl/nx-cloud'] = await resolvePackageVersion(
'@nrwl/nx-cloud',
`^${major(pkgVersion)}.0.0`
);
}
json.devDependencies = sortObjectByKeys(json.devDependencies);
if (pkgName === '@nrwl/angular') {
json.dependencies ??= {};
json.dependencies['@nrwl/angular'] = pkgVersion;
json.dependencies = sortObjectByKeys(json.dependencies);
}
writeJsonFile(`package.json`, json);
execSync(pmc.install, { stdio: [0, 1, 2] });
}
async function resolvePackageVersion(
packageName: string,
version: string
): Promise<string> {
try {
return await resolvePackageVersionUsingRegistry(packageName, version);
} catch {
return await resolvePackageVersionUsingInstallation(packageName, version);
}
}

View File

@ -1,12 +1,13 @@
import { joinPathFragments } from '../utils/path';
import { readJsonFile, writeJsonFile } from '../utils/fileutils';
import * as enquirer from 'enquirer';
import { execSync } from 'child_process';
import * as enquirer from 'enquirer';
import { join } from 'path';
import { fileExists, readJsonFile, writeJsonFile } from '../utils/fileutils';
import {
getPackageManagerCommand,
PackageManagerCommands,
} from '../utils/package-manager';
import { runNxSync } from '../utils/child-process';
import { joinPathFragments } from '../utils/path';
export function askAboutNxCloud() {
return enquirer
@ -35,8 +36,7 @@ export function createNxJsonFile(
repoRoot: string,
targetDefaults: string[],
cacheableOperations: string[],
scriptOutputs: { [name: string]: string },
defaultProject: string | undefined
scriptOutputs: { [name: string]: string }
) {
const nxJsonPath = joinPathFragments(repoRoot, 'nx.json');
let nxJson = {} as any;
@ -121,9 +121,42 @@ export function runInstall(
execSync(pmc.install, { stdio: [0, 1, 2], cwd: repoRoot });
}
export function initCloud(repoRoot: string) {
runNxSync(`g @nrwl/nx-cloud:init --installationSource=add-nx-to-monorepo`, {
stdio: [0, 1, 2],
cwd: repoRoot,
});
export function initCloud(
repoRoot: string,
installationSource:
| 'nx-init-angular'
| 'nx-init-cra'
| 'nx-init-monorepo'
| 'nx-init-nest'
| 'nx-init-npm-repo'
) {
runNxSync(
`g @nrwl/nx-cloud:init --installationSource=${installationSource}`,
{
stdio: [0, 1, 2],
cwd: repoRoot,
}
);
}
export function addVsCodeRecommendedExtensions(
repoRoot: string,
extensions: string[]
): void {
const vsCodeExtensionsPath = join(repoRoot, '.vscode/extensions.json');
if (fileExists(vsCodeExtensionsPath)) {
const vsCodeExtensionsJson = readJsonFile(vsCodeExtensionsPath);
vsCodeExtensionsJson.recommendations ??= [];
extensions.forEach((extension) => {
if (!vsCodeExtensionsJson.recommendations.includes(extension)) {
vsCodeExtensionsJson.recommendations.push(extension);
}
});
writeJsonFile(vsCodeExtensionsPath, vsCodeExtensionsJson);
} else {
writeJsonFile(vsCodeExtensionsPath, { recommendations: extensions });
}
}