Skip to content

Commit e175ea3

Browse files
committed
feat(pnpm): support pnpm as package manager
1 parent 986c087 commit e175ea3

File tree

7 files changed

+248
-30
lines changed

7 files changed

+248
-30
lines changed

packages/contentful--create-contentful-app/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ npx create-contentful-app <app-name>
3030
# npm
3131
npm init contentful-app <app-name>
3232

33+
# pnpm
34+
pnpm init contentful-app <app-name>
35+
3336
# Yarn
3437
yarn create contentful-app <app-name>
3538
```
@@ -38,11 +41,9 @@ yarn create contentful-app <app-name>
3841

3942
### Package Manager
4043

41-
`--npm` or `--yarn`
42-
43-
Use npm or Yarn to manage dependencies. If omitted, defaults to the manager used to run `create-contentful-app`.
44+
`--npm` or `--pnpm` or `--yarn`
4445

45-
Both flags are mutually exclusive.
46+
Use npm, pnpm, or Yarn to manage dependencies. If omitted, or if more than one flag is passed, will default to the manager used to run `create-contentful-app`.
4647

4748
### Template
4849

packages/contentful--create-contentful-app/src/analytics.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Analytics } from '@segment/analytics-node';
2+
import type { PackageManager } from './types';
23

34
// Public write key scoped to data source
45
const SEGMENT_WRITE_KEY = 'IzCq3j4dQlTAgLdMykRW9oBHQKUy1xMm';
56

67
interface CCAEventProperties {
78
template?: string; // can be example, source, or JS or TS
8-
manager: 'npm' | 'yarn';
9+
manager: PackageManager;
910
interactive: boolean;
1011
}
1112

packages/contentful--create-contentful-app/src/index.ts

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ import { program } from 'commander';
77
import inquirer from 'inquirer';
88
import tildify from 'tildify';
99
import { cloneTemplateIn } from './template';
10-
import { detectManager, exec, normalizeOptions, isContentfulTemplate } from './utils';
11-
import { CLIOptions } from './types';
10+
import {
11+
detectActivePackageManager,
12+
getNormalizedPackageManager,
13+
exec,
14+
normalizeOptions,
15+
isContentfulTemplate,
16+
} from './utils';
17+
import type { CLIOptions, PackageManager } from './types';
1218
import { code, error, highlight, success, warn, wrapInBlanks } from './logger';
1319
import chalk from 'chalk';
1420
import { CREATE_APP_DEFINITION_GUIDE_URL, EXAMPLES_REPO_URL } from './constants';
@@ -19,7 +25,15 @@ import fs from 'fs';
1925

2026
const DEFAULT_APP_NAME = 'contentful-app';
2127

22-
function successMessage(folder: string, useYarn: boolean) {
28+
function successMessage(folder: string, packageManager: PackageManager) {
29+
let command = '';
30+
if (packageManager === 'yarn') {
31+
command = 'yarn create-app-definition';
32+
} else if (packageManager === 'pnpm') {
33+
command = 'pnpm create-app-definition';
34+
} else {
35+
command = 'npm run create-app-definition';
36+
}
2337
console.log(`
2438
${success('Success!')} Created a new Contentful app in ${highlight(tildify(folder))}.`);
2539

@@ -28,7 +42,7 @@ ${success('Success!')} Created a new Contentful app in ${highlight(tildify(folde
2842
console.log(`Now create an app definition for your app by running
2943
3044
${code(`cd ${tildify(folder)}`)}
31-
${code(useYarn ? 'yarn create-app-definition' : 'npm run create-app-definition')}
45+
${code(command)}
3246
3347
or you can create it manually in web app:
3448
${highlight(CREATE_APP_DEFINITION_GUIDE_URL)}
@@ -37,7 +51,7 @@ ${success('Success!')} Created a new Contentful app in ${highlight(tildify(folde
3751
console.log(`Then kick it off by running
3852
3953
${code(`cd ${tildify(folder)}`)}
40-
${code(`${useYarn ? 'yarn' : 'npm'} start`)}
54+
${code(`${packageManager} start`)}
4155
`);
4256
}
4357

@@ -100,6 +114,9 @@ async function validateAppName(appName: string): Promise<string> {
100114

101115
async function initProject(appName: string, options: CLIOptions) {
102116
const normalizedOptions = normalizeOptions(options);
117+
const activePackageManager = detectActivePackageManager();
118+
const packageManager = getNormalizedPackageManager(normalizedOptions, activePackageManager);
119+
103120
try {
104121
appName = await validateAppName(appName);
105122

@@ -115,28 +132,28 @@ async function initProject(appName: string, options: CLIOptions) {
115132

116133
updatePackageName(fullAppFolder);
117134

118-
const useYarn = normalizedOptions.yarn || detectManager() === 'yarn';
119-
120135
wrapInBlanks(
121136
highlight(
122-
`---- Installing the dependencies for your app (using ${chalk.cyan(
123-
useYarn ? 'yarn' : 'npm'
124-
)})...`
137+
`---- Installing the dependencies for your app (using ${chalk.cyan(packageManager)})...`
125138
)
126139
);
127-
if (useYarn) {
140+
141+
if (packageManager === 'yarn') {
128142
await exec('yarn', [], { cwd: fullAppFolder });
143+
} else if (packageManager === 'pnpm') {
144+
await exec('pnpm', ['install'], { cwd: fullAppFolder });
129145
} else {
130146
await exec('npm', ['install', '--no-audit', '--no-fund'], { cwd: fullAppFolder });
131147
}
132-
successMessage(fullAppFolder, useYarn);
148+
successMessage(fullAppFolder, packageManager);
133149
} catch (err) {
134150
error(`Failed to create ${highlight(chalk.cyan(appName))}`, err);
135151
process.exit(1);
136152
}
137153

138154
async function addAppExample(fullAppFolder: string) {
139-
const isInteractive = !normalizedOptions.example &&
155+
const isInteractive =
156+
!normalizedOptions.example &&
140157
!normalizedOptions.source &&
141158
!normalizedOptions.javascript &&
142159
!normalizedOptions.typescript &&
@@ -146,7 +163,7 @@ async function initProject(appName: string, options: CLIOptions) {
146163

147164
track({
148165
template: templateSource,
149-
manager: normalizedOptions.npm ? 'npm' : 'yarn',
166+
manager: packageManager,
150167
interactive: isInteractive,
151168
});
152169

@@ -163,7 +180,7 @@ async function initProject(appName: string, options: CLIOptions) {
163180
}
164181

165182
async function addFunctionTemplate(fullAppFolder: string) {
166-
if (!fs.existsSync(fullAppFolder)) {
183+
if (!fs.existsSync(fullAppFolder)) {
167184
fs.mkdirSync(fullAppFolder, { recursive: true });
168185
}
169186

@@ -188,7 +205,7 @@ async function initProject(appName: string, options: CLIOptions) {
188205
example: normalizedOptions.function,
189206
language: normalizedOptions.javascript ? 'javascript' : 'typescript',
190207
name: functionName,
191-
keepPackageJson: normalizedOptions.skipUi === true
208+
keepPackageJson: normalizedOptions.skipUi === true,
192209
} as any);
193210
}
194211
}
@@ -212,6 +229,7 @@ async function initProject(appName: string, options: CLIOptions) {
212229
)
213230
.argument('[app-name]', 'app name')
214231
.option('--npm', 'use npm')
232+
.option('--pnpm', 'use pnpm')
215233
.option('--yarn', 'use Yarn')
216234
.option('-ts, --typescript', 'use TypeScript template (default)')
217235
.option('-js, --javascript', 'use JavaScript template')

packages/contentful--create-contentful-app/src/template.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ function validate(destination: string): void {
3636
}
3737

3838
function cleanUp(destination: string) {
39+
rmIfExists(resolve(destination, 'pnpm-lock.json'));
3940
rmIfExists(resolve(destination, 'package-lock.json'));
4041
rmIfExists(resolve(destination, 'yarn.lock'));
4142
}

packages/contentful--create-contentful-app/src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export type CLIOptions = Partial<{
22
npm: boolean;
33
yarn: boolean;
4+
pnpm: boolean;
45
javascript: boolean;
56
typescript: boolean;
67
source: string;
@@ -9,6 +10,8 @@ export type CLIOptions = Partial<{
910
skipUi: boolean;
1011
}>;
1112

13+
export type PackageManager = 'npm' | 'yarn' | 'pnpm';
14+
1215
export const ContentfulExample = {
1316
Javascript: 'javascript',
1417
Typescript: 'typescript',

packages/contentful--create-contentful-app/src/utils.ts

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { spawn, SpawnOptionsWithoutStdio } from 'child_process';
22
import { existsSync, rmSync } from 'fs';
33
import { basename } from 'path';
44
import { choice, highlight, warn } from './logger';
5-
import { CLIOptions, ContentfulExample } from './types';
5+
import { CLIOptions, ContentfulExample, PackageManager } from './types';
66
import { EXAMPLES_PATH } from './constants';
77

88
const MUTUALLY_EXCLUSIVE_OPTIONS = ['source', 'example', 'typescript', 'javascript'] as const;
@@ -26,31 +26,65 @@ export function rmIfExists(path: string) {
2626
}
2727
}
2828

29-
export function detectManager() {
29+
export function detectActivePackageManager(): PackageManager {
3030
switch (basename(process.env.npm_execpath || '')) {
3131
case 'yarn.js':
3232
return 'yarn';
33+
case 'pnpm.cjs':
34+
return 'pnpm';
3335
case 'npx-cli.js':
3436
case 'npm-cli.js':
3537
default:
3638
return 'npm';
3739
}
3840
}
3941

42+
// By the time this function is called, the options have already been normalized
43+
// so we would not need to consider multiple package manager flags at once
44+
export function getNormalizedPackageManager(
45+
options: CLIOptions,
46+
activePackageManager: PackageManager
47+
): PackageManager {
48+
// Prefer to get the package manager from options
49+
if (options.pnpm) {
50+
return 'pnpm';
51+
} else if (options.yarn) {
52+
return 'yarn';
53+
} else if (options.npm) {
54+
return 'npm';
55+
}
56+
57+
// Fallback to active package manager
58+
return activePackageManager;
59+
}
60+
4061
export function normalizeOptions(options: CLIOptions): CLIOptions {
4162
const normalizedOptions: CLIOptions = { ...options };
4263

43-
if (normalizedOptions.npm && normalizedOptions.yarn) {
64+
const selectedPackageManagers = [
65+
['npm', normalizedOptions.npm],
66+
['pnpm', normalizedOptions.pnpm],
67+
['yarn', normalizedOptions.yarn],
68+
].filter(([, n]) => n);
69+
const activePackageManager = detectActivePackageManager();
70+
71+
if (selectedPackageManagers.length > 1) {
4472
warn(
45-
`Provided both ${highlight('--yarn')} and ${highlight('--npm')} flags, using ${choice(
46-
'--npm'
47-
)}.`
73+
`Too many package manager flags were provided, we will use ${choice(`--${activePackageManager}`)}.`
4874
);
49-
delete normalizedOptions.yarn;
75+
76+
// Delete all package manager options
77+
selectedPackageManagers.forEach(([packageManager]) => {
78+
delete normalizedOptions[packageManager as keyof CLIOptions];
79+
});
80+
81+
// Select active package manager
82+
(normalizedOptions as Record<string, boolean>)[activePackageManager] = true;
5083
}
5184

52-
if (!normalizedOptions.yarn) {
53-
normalizedOptions.npm = true;
85+
// No package manager flags were provided, use active package manager
86+
if (selectedPackageManagers.length === 0) {
87+
(normalizedOptions as Record<string, boolean>)[activePackageManager] = true;
5488
}
5589

5690
let fallbackOption = '--typescript';

0 commit comments

Comments
 (0)