Skip to content

Commit db19110

Browse files
fix(cli): cdk init creates README.md with wrong package manager when --package-manager option is used (#989)
When initialize project using `cdk init -l ts --package-manager <package-manager>` command, the README.md should contain the following: (that when selected pnpm) ```md * `pnpm build` compile typescript to js * `pnpm watch` watch for changes and compile * `pnpm test` perform the jest unit tests ``` But actually README.md contain following: ```md * `npm run build` compile typescript to js * `npm run watch` watch for changes and compile * `npm run test` perform the jest unit tests ``` I fixed it in this PR. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license
1 parent 48bad5d commit db19110

File tree

10 files changed

+133
-42
lines changed

10 files changed

+133
-42
lines changed

packages/aws-cdk/lib/cli/cli-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ export async function makeConfig(): Promise<CliConfig> {
408408
'lib-version': { type: 'string', alias: 'V', default: undefined, desc: 'The version of the CDK library (aws-cdk-lib) to initialize built-in templates with. Defaults to the version that was current when this CLI was built.' },
409409
'from-path': { type: 'string', desc: 'Path to a local custom template directory or multi-template repository', requiresArg: true, conflicts: ['lib-version'] },
410410
'template-path': { type: 'string', desc: 'Path to a specific template within a multi-template repository', requiresArg: true },
411-
'package-manager': { type: 'string', desc: 'The package manager to use to install dependencies. Only applicable for TypeScript and JavaScript projects. Defaults to npm in TypeScript and JavaScript projects.', choices: JS_PACKAGE_MANAGERS },
411+
'package-manager': { type: 'string', desc: 'The package manager to use to install dependencies. Only applicable for TypeScript and JavaScript projects. Defaults to npm in TypeScript and JavaScript projects.', choices: JS_PACKAGE_MANAGERS.map(({ name }) => name) },
412412
},
413413
implies: { 'template-path': 'from-path' },
414414
},

packages/aws-cdk/lib/commands/init/init-hooks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export async function invokeBuiltinHooks(ioHelper: IoHelper, target: HookTarget,
7676
case 'javascript':
7777
case 'typescript':
7878
// See above, but for 'package.json'.
79-
await context.substitutePlaceholdersIn('package.json');
79+
await context.substitutePlaceholdersIn('package.json', 'README.md');
8080
}
8181
}
8282

packages/aws-cdk/lib/commands/init/init.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { versionNumber } from '../../cli/version';
1010
import { cdkHomeDir, formatErrorMessage, rangeFromSemver } from '../../util';
1111
import type { LanguageInfo } from '../language';
1212
import { getLanguageAlias, getLanguageExtensions, SUPPORTED_LANGUAGES } from '../language';
13-
import type { JsPackageManager } from './package-manager';
13+
import { getPmCmdPrefix, type JsPackageManager } from './package-manager';
1414

1515
/* eslint-disable @typescript-eslint/no-var-requires */ // Packages don't have @types module
1616
// eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -423,7 +423,14 @@ export class InitTemplate {
423423
* @param libVersion - the version of the CDK library to use
424424
* @default undefined
425425
*/
426-
public async install(ioHelper: IoHelper, language: string, targetDirectory: string, stackName?: string, libVersion?: string) {
426+
public async install(
427+
ioHelper: IoHelper,
428+
language: string,
429+
targetDirectory: string,
430+
stackName?: string,
431+
libVersion?: string,
432+
packageManager?: JsPackageManager,
433+
): Promise<void> {
427434
if (this.languages.indexOf(language) === -1) {
428435
await ioHelper.defaults.error(
429436
`The ${chalk.blue(language)} language is not supported for ${chalk.green(this.name)} ` +
@@ -455,7 +462,7 @@ export class InitTemplate {
455462
await this.installFilesWithoutProcessing(sourceDirectory, targetDirectory);
456463
} else {
457464
// For built-in templates, process placeholders as usual
458-
await this.installFiles(sourceDirectory, targetDirectory, language, projectInfo);
465+
await this.installFiles(sourceDirectory, targetDirectory, language, projectInfo, packageManager);
459466
await this.applyFutureFlags(targetDirectory);
460467
await invokeBuiltinHooks(
461468
ioHelper,
@@ -465,27 +472,33 @@ export class InitTemplate {
465472
const fileProcessingPromises = fileNames.map(async (fileName) => {
466473
const fullPath = path.join(targetDirectory, fileName);
467474
const template = await fs.readFile(fullPath, { encoding: 'utf-8' });
468-
await fs.writeFile(fullPath, expandPlaceholders(template, language, projectInfo));
475+
await fs.writeFile(fullPath, expandPlaceholders(template, language, projectInfo, packageManager));
469476
});
470477
/* eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism */ // Processing a small, known set of template files
471478
await Promise.all(fileProcessingPromises);
472479
},
473-
placeholder: (ph: string) => expandPlaceholders(`%${ph}%`, language, projectInfo),
480+
placeholder: (ph: string) => expandPlaceholders(`%${ph}%`, language, projectInfo, packageManager),
474481
},
475482
);
476483
}
477484
}
478485

479-
private async installFiles(sourceDirectory: string, targetDirectory: string, language: string, project: ProjectInfo) {
486+
private async installFiles(
487+
sourceDirectory: string,
488+
targetDirectory: string,
489+
language: string,
490+
project: ProjectInfo,
491+
packageManager?: JsPackageManager,
492+
): Promise<void> {
480493
for (const file of await fs.readdir(sourceDirectory)) {
481494
const fromFile = path.join(sourceDirectory, file);
482-
const toFile = path.join(targetDirectory, expandPlaceholders(file, language, project));
495+
const toFile = path.join(targetDirectory, expandPlaceholders(file, language, project, packageManager));
483496
if ((await fs.stat(fromFile)).isDirectory()) {
484497
await fs.mkdir(toFile);
485-
await this.installFiles(fromFile, toFile, language, project);
498+
await this.installFiles(fromFile, toFile, language, project, packageManager);
486499
continue;
487500
} else if (file.match(/^.*\.template\.[^.]+$/)) {
488-
await this.installProcessed(fromFile, toFile.replace(/\.template(\.[^.]+)$/, '$1'), language, project);
501+
await this.installProcessed(fromFile, toFile.replace(/\.template(\.[^.]+)$/, '$1'), language, project, packageManager);
489502
continue;
490503
} else if (file.match(/^.*\.hook\.(d.)?[^.]+$/)) {
491504
// Ignore
@@ -496,9 +509,15 @@ export class InitTemplate {
496509
}
497510
}
498511

499-
private async installProcessed(templatePath: string, toFile: string, language: string, project: ProjectInfo) {
512+
private async installProcessed(
513+
templatePath: string,
514+
toFile: string,
515+
language: string,
516+
project: ProjectInfo,
517+
packageManager?: JsPackageManager,
518+
) {
500519
const template = await fs.readFile(templatePath, { encoding: 'utf-8' });
501-
await fs.writeFile(toFile, expandPlaceholders(template, language, project));
520+
await fs.writeFile(toFile, expandPlaceholders(template, language, project, packageManager));
502521
}
503522

504523
/**
@@ -548,7 +567,7 @@ export class InitTemplate {
548567
}
549568
}
550569

551-
export function expandPlaceholders(template: string, language: string, project: ProjectInfo) {
570+
export function expandPlaceholders(template: string, language: string, project: ProjectInfo, packageManager?: JsPackageManager) {
552571
const cdkVersion = project.versions['aws-cdk-lib'];
553572
const cdkCliVersion = project.versions['aws-cdk'];
554573
let constructsVersion = project.versions.constructs;
@@ -582,7 +601,8 @@ export function expandPlaceholders(template: string, language: string, project:
582601
.replace(/%cdk-home%/g, cdkHomeDir())
583602
.replace(/%name\.PythonModule%/g, project.name.replace(/-/g, '_'))
584603
.replace(/%python-executable%/g, pythonExecutable())
585-
.replace(/%name\.StackName%/g, project.name.replace(/[^A-Za-z0-9-]/g, '-'));
604+
.replace(/%name\.StackName%/g, project.name.replace(/[^A-Za-z0-9-]/g, '-'))
605+
.replace(/%pm-cmd%/g, getPmCmdPrefix(packageManager ?? 'npm'));
586606
}
587607

588608
interface ProjectInfo {
@@ -679,7 +699,7 @@ async function initializeProject(
679699

680700
// Step 2: Copy template files
681701
await ioHelper.defaults.info(`Applying project template ${chalk.green(template.name)} for ${chalk.blue(language)}`);
682-
await template.install(ioHelper, language, workDir, stackName, cdkVersion);
702+
await template.install(ioHelper, language, workDir, stackName, cdkVersion, packageManager);
683703

684704
if (migrate) {
685705
await template.addMigrateContext(workDir);
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1-
export const JS_PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'bun'] as const;
1+
export const JS_PACKAGE_MANAGERS = [
2+
{ name: 'npm', commandPrefix: 'npm run' },
3+
{ name: 'yarn', commandPrefix: 'yarn' },
4+
{ name: 'pnpm', commandPrefix: 'pnpm' },
5+
{ name: 'bun', commandPrefix: 'bun run' },
6+
] as const;
27

3-
export type JsPackageManager = (typeof JS_PACKAGE_MANAGERS)[number];
8+
export type JsPackageManager = (typeof JS_PACKAGE_MANAGERS)[number]['name'];
9+
10+
export const getPmCmdPrefix = (packageManager: JsPackageManager): string => {
11+
return JS_PACKAGE_MANAGERS.find(pm => pm.name === packageManager)!.commandPrefix;
12+
};

packages/aws-cdk/lib/init-templates/app/javascript/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The `cdk.json` file tells the CDK Toolkit how to execute your app. The build ste
66

77
## Useful commands
88

9-
* `npm run test` perform the jest unit tests
9+
* `%pm-cmd% test` perform the jest unit tests
1010
* `npx cdk deploy` deploy this stack to your default AWS account/region
1111
* `npx cdk diff` compare deployed stack with current state
1212
* `npx cdk synth` emits the synthesized CloudFormation template

packages/aws-cdk/lib/init-templates/app/typescript/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ The `cdk.json` file tells the CDK Toolkit how to execute your app.
66

77
## Useful commands
88

9-
* `npm run build` compile typescript to js
10-
* `npm run watch` watch for changes and compile
11-
* `npm run test` perform the jest unit tests
9+
* `%pm-cmd% build` compile typescript to js
10+
* `%pm-cmd% watch` watch for changes and compile
11+
* `%pm-cmd% test` perform the jest unit tests
1212
* `npx cdk deploy` deploy this stack to your default AWS account/region
1313
* `npx cdk diff` compare deployed stack with current state
1414
* `npx cdk synth` emits the synthesized CloudFormation template

packages/aws-cdk/lib/init-templates/lib/typescript/README.template.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ The construct defines an interface (`%name.PascalCased%Props`) to configure the
77

88
## Useful commands
99

10-
* `npm run build` compile typescript to js
11-
* `npm run watch` watch for changes and compile
12-
* `npm run test` perform the jest unit tests
10+
* `%pm-cmd% build` compile typescript to js
11+
* `%pm-cmd% watch` watch for changes and compile
12+
* `%pm-cmd% test` perform the jest unit tests

packages/aws-cdk/lib/init-templates/sample-app/javascript/README.template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ The `cdk.json` file tells the CDK Toolkit how to execute your app. The build ste
77

88
## Useful commands
99

10-
* `npm run test` perform the jest unit tests
10+
* `%pm-cmd% test` perform the jest unit tests
1111
* `cdk deploy` deploy this stack to your default AWS account/region
1212
* `cdk diff` compare deployed stack with current state
1313
* `cdk synth` emits the synthesized CloudFormation template

packages/aws-cdk/lib/init-templates/sample-app/typescript/README.template.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ The `cdk.json` file tells the CDK Toolkit how to execute your app.
77

88
## Useful commands
99

10-
* `npm run build` compile typescript to js
11-
* `npm run watch` watch for changes and compile
12-
* `npm run test` perform the jest unit tests
10+
* `%pm-cmd% build` compile typescript to js
11+
* `%pm-cmd% watch` watch for changes and compile
12+
* `%pm-cmd% test` perform the jest unit tests
1313
* `cdk deploy` deploy this stack to your default AWS account/region
1414
* `cdk diff` compare deployed stack with current state
1515
* `cdk synth` emits the synthesized CloudFormation template

packages/aws-cdk/test/commands/init.test.ts

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as path from 'path';
44
import * as cxapi from '@aws-cdk/cx-api';
55
import * as fs from 'fs-extra';
66
import { availableInitLanguages, availableInitTemplates, cliInit, currentlyRecommendedAwsCdkLibFlags, expandPlaceholders, printAvailableTemplates } from '../../lib/commands/init';
7-
import type { JsPackageManager } from '../../lib/commands/init/package-manager';
7+
import { type JsPackageManager } from '../../lib/commands/init/package-manager';
88
import { createSingleLanguageTemplate, createMultiLanguageTemplate, createMultiTemplateRepository } from '../_fixtures/init-templates/template-helpers';
99
import { TestIoHost } from '../_helpers/io-host';
1010

@@ -1370,15 +1370,15 @@ describe('constructs version', () => {
13701370
});
13711371

13721372
test.each([
1373-
{ language: 'typescript', packageManager: 'npm' },
1374-
{ language: 'typescript', packageManager: 'yarn' },
1375-
{ language: 'typescript', packageManager: 'pnpm' },
1376-
{ language: 'typescript', packageManager: 'bun' },
1377-
{ language: 'javascript', packageManager: 'npm' },
1378-
{ language: 'javascript', packageManager: 'yarn' },
1379-
{ language: 'javascript', packageManager: 'pnpm' },
1380-
{ language: 'javascript', packageManager: 'bun' },
1381-
])('uses $packageManager for $language project', async ({ language, packageManager }) => {
1373+
{ language: 'typescript', packageManager: 'npm', pmCmdPrefix: 'npm run' },
1374+
{ language: 'typescript', packageManager: 'yarn', pmCmdPrefix: 'yarn' },
1375+
{ language: 'typescript', packageManager: 'pnpm', pmCmdPrefix: 'pnpm' },
1376+
{ language: 'typescript', packageManager: 'bun', pmCmdPrefix: 'bun run' },
1377+
{ language: 'javascript', packageManager: 'npm', pmCmdPrefix: 'npm run' },
1378+
{ language: 'javascript', packageManager: 'yarn', pmCmdPrefix: 'yarn' },
1379+
{ language: 'javascript', packageManager: 'pnpm', pmCmdPrefix: 'pnpm' },
1380+
{ language: 'javascript', packageManager: 'bun', pmCmdPrefix: 'bun run' },
1381+
])('uses $packageManager for $language project', async ({ language, packageManager, pmCmdPrefix }) => {
13821382
await withTempDir(async (workDir) => {
13831383
await cliInit({
13841384
ioHelper,
@@ -1388,39 +1388,101 @@ describe('constructs version', () => {
13881388
workDir,
13891389
});
13901390

1391+
const readme = await fs.readFile(path.join(workDir, 'README.md'), 'utf-8');
13911392
const installCalls = spawnSpy.mock.calls.filter(
13921393
([cmd, args]) => cmd === packageManager && args.includes('install'),
13931394
);
1395+
1396+
expect(installCalls.length).toBeGreaterThan(0);
1397+
expect(readme).toContain(pmCmdPrefix);
1398+
});
1399+
});
1400+
1401+
cliTest('init type `lib` also respects package manager option', async () => {
1402+
const packageManager = 'pnpm';
1403+
const pmCmdPrefix = 'pnpm';
1404+
1405+
await withTempDir(async (workDir) => {
1406+
await cliInit({
1407+
ioHelper,
1408+
type: 'app',
1409+
language: 'typescript',
1410+
packageManager: packageManager as JsPackageManager,
1411+
workDir,
1412+
});
1413+
1414+
const readme = await fs.readFile(path.join(workDir, 'README.md'), 'utf-8');
1415+
const installCalls = spawnSpy.mock.calls.filter(
1416+
([cmd, args]) => cmd === packageManager && args.includes('install'),
1417+
);
1418+
1419+
expect(installCalls.length).toBeGreaterThan(0);
1420+
expect(readme).toContain(pmCmdPrefix);
1421+
});
1422+
});
1423+
1424+
cliTest('init type `sample-app` also respects package manager option', async () => {
1425+
const packageManager = 'pnpm';
1426+
const pmCmdPrefix = 'pnpm';
1427+
1428+
await withTempDir(async (workDir) => {
1429+
await cliInit({
1430+
ioHelper,
1431+
type: 'sample-app',
1432+
language: 'typescript',
1433+
packageManager: packageManager as JsPackageManager,
1434+
workDir,
1435+
});
1436+
1437+
const readme = await fs.readFile(path.join(workDir, 'README.md'), 'utf-8');
1438+
const installCalls = spawnSpy.mock.calls.filter(
1439+
([cmd, args]) => cmd === packageManager && args.includes('install'),
1440+
);
1441+
13941442
expect(installCalls.length).toBeGreaterThan(0);
1443+
expect(readme).toContain(pmCmdPrefix);
13951444
});
13961445
});
13971446

13981447
cliTest('uses npm as default when package manager not specified', async (workDir) => {
1448+
const defaultPackageManager = 'npm';
1449+
const pmCmdPrefix = 'npm run';
1450+
13991451
await cliInit({
14001452
ioHelper,
14011453
type: 'app',
14021454
language: 'typescript',
14031455
workDir,
14041456
});
14051457

1458+
const readme = await fs.readFile(path.join(workDir, 'README.md'), 'utf-8');
14061459
const installCalls = spawnSpy.mock.calls.filter(
1407-
([cmd, args]) => cmd === 'npm' && args.includes('install'),
1460+
([cmd, args]) => cmd === defaultPackageManager && args.includes('install'),
14081461
);
1462+
14091463
expect(installCalls.length).toBeGreaterThan(0);
1464+
expect(readme).toContain(pmCmdPrefix);
14101465
});
14111466

14121467
cliTest('ignores package manager option for non-JavaScript languages', async (workDir) => {
1468+
const packageManager = 'yarn';
1469+
const pmCmdPrefix = 'yarn';
1470+
14131471
await cliInit({
14141472
ioHelper,
14151473
type: 'app',
14161474
language: 'python',
1417-
packageManager: 'yarn',
1475+
packageManager,
14181476
canUseNetwork: false,
14191477
generateOnly: true,
14201478
workDir,
14211479
});
14221480

1423-
expect(await fs.pathExists(path.join(workDir, 'requirements.txt'))).toBeTruthy();
1481+
const requirementsExists = await fs.pathExists(path.join(workDir, 'requirements.txt'));
1482+
const readme = await fs.readFile(path.join(workDir, 'README.md'), 'utf-8');
1483+
1484+
expect(requirementsExists).toBeTruthy();
1485+
expect(readme).not.toContain(pmCmdPrefix);
14241486
});
14251487
});
14261488

0 commit comments

Comments
 (0)