Skip to content

Commit ea222ac

Browse files
committed
feat(@schematics/angular): set packageManager in package.json on new projects
When creating a new project, the `packageManager` field in `package.json` will be automatically set to the package manager used to create the project. This helps to ensure that the same package manager is used by all developers working on the project. The package manager is detected in the following order: 1. `packageManager` field in `package.json` 2. Angular CLI configuration (`angular.json`) 3. Lock files (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`)
1 parent 881e42d commit ea222ac

File tree

4 files changed

+62
-15
lines changed

4 files changed

+62
-15
lines changed

packages/angular/cli/src/utilities/package-manager.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { isJsonObject, json } from '@angular-devkit/core';
1010
import { execSync, spawn } from 'node:child_process';
11-
import { promises as fs, readdirSync, realpathSync, rmSync } from 'node:fs';
11+
import { promises as fs, readFileSync, readdirSync, realpathSync, rmSync } from 'node:fs';
1212
import { tmpdir } from 'node:os';
1313
import { join } from 'node:path';
1414
import { PackageManager } from '../../lib/config/workspace-schema';
@@ -233,7 +233,6 @@ export class PackageManagerUtils {
233233
}
234234

235235
const filesInRoot = readdirSync(this.context.root);
236-
237236
const hasNpmLock = this.hasLockfile(PackageManager.Npm, filesInRoot);
238237
const hasYarnLock = this.hasLockfile(PackageManager.Yarn, filesInRoot);
239238
const hasPnpmLock = this.hasLockfile(PackageManager.Pnpm, filesInRoot);
@@ -311,6 +310,18 @@ export class PackageManagerUtils {
311310

312311
let result: PackageManager | undefined;
313312
const { workspace: localWorkspace, globalConfiguration: globalWorkspace } = this.context;
313+
try {
314+
const pkgJson = JSON.parse(
315+
readFileSync(join(this.context.root, 'package.json'), 'utf-8'),
316+
) as {
317+
packageManager?: string;
318+
};
319+
const result = getPackageManager(pkgJson);
320+
if (result) {
321+
return result;
322+
}
323+
} catch {}
324+
314325
if (localWorkspace) {
315326
const project = getProjectByCwd(localWorkspace);
316327
if (project) {

packages/schematics/angular/workspace/files/package.json.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
]
2222
},
2323
"private": true,
24+
<% if (packageManagerWithVersion) { %>"packageManager": "<%= packageManagerWithVersion %>",<% } %>
2425
"dependencies": {
2526
"@angular/common": "<%= latestVersions.Angular %>",
2627
"@angular/compiler": "<%= latestVersions.Angular %>",

packages/schematics/angular/workspace/index.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,46 @@ import {
1616
strings,
1717
url,
1818
} from '@angular-devkit/schematics';
19+
import { execSync } from 'node:child_process';
1920
import { latestVersions } from '../utility/latest-versions';
2021
import { Schema as WorkspaceOptions } from './schema';
2122

2223
export default function (options: WorkspaceOptions): Rule {
23-
return mergeWith(
24-
apply(url('./files'), [
25-
options.minimal ? filter((path) => !path.endsWith('editorconfig.template')) : noop(),
26-
applyTemplates({
27-
utils: strings,
28-
...options,
29-
'dot': '.',
30-
latestVersions,
31-
}),
32-
]),
33-
);
24+
return () => {
25+
const packageManager = options.packageManager;
26+
let packageManagerWithVersion: string | undefined;
27+
28+
if (packageManager) {
29+
let packageManagerVersion: string | undefined;
30+
try {
31+
packageManagerVersion = execSync(`${packageManager} --version`, {
32+
encoding: 'utf8',
33+
stdio: 'pipe',
34+
env: {
35+
...process.env,
36+
// NPM updater notifier will prevents the child process from closing until it timeout after 3 minutes.
37+
NO_UPDATE_NOTIFIER: '1',
38+
NPM_CONFIG_UPDATE_NOTIFIER: 'false',
39+
},
40+
}).trim();
41+
} catch {}
42+
43+
if (packageManagerVersion) {
44+
packageManagerWithVersion = `${packageManager}@${packageManagerVersion}`;
45+
}
46+
}
47+
48+
return mergeWith(
49+
apply(url('./files'), [
50+
options.minimal ? filter((path) => !path.endsWith('editorconfig.template')) : noop(),
51+
applyTemplates({
52+
utils: strings,
53+
...options,
54+
'dot': '.',
55+
latestVersions,
56+
packageManagerWithVersion,
57+
}),
58+
]),
59+
);
60+
};
3461
}

tests/legacy-cli/e2e/initialize/500-create-project.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { join } from 'node:path';
22
import { getGlobalVariable } from '../utils/env';
33
import { expectFileToExist } from '../utils/fs';
44
import { gitClean } from '../utils/git';
5-
import { setRegistry as setNPMConfigRegistry } from '../utils/packages';
5+
import { getActivePackageManager, setRegistry as setNPMConfigRegistry } from '../utils/packages';
66
import { ng } from '../utils/process';
77
import { prepareProjectForE2e, updateJsonFile } from '../utils/project';
88

@@ -20,7 +20,15 @@ export default async function () {
2020
// Ensure local test registry is used when outside a project
2121
await setNPMConfigRegistry(true);
2222

23-
await ng('new', 'test-project', '--skip-install', '--no-zoneless');
23+
await ng(
24+
'new',
25+
'test-project',
26+
'--skip-install',
27+
'--no-zoneless',
28+
'--package-manager',
29+
getActivePackageManager(),
30+
);
31+
2432
await expectFileToExist(join(process.cwd(), 'test-project'));
2533
process.chdir('./test-project');
2634

0 commit comments

Comments
 (0)