Detect used package manager in extension CLI (#15909)

* Detect used package manager in extension CLI

* Add tests

Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
Nicola Krumschmidt
2022-11-17 16:53:54 +01:00
committed by GitHub
parent 0bb34f0f0d
commit 5d25c19836
8 changed files with 128 additions and 10 deletions

View File

@@ -44,6 +44,9 @@
"build": "run-p \"build:* {@}\"",
"build:esm": "tsc --project ./tsconfig.json --module ES2015 --outDir ./dist/esm",
"build:cjs": "tsc --project ./tsconfig.json --module CommonJS --outDir ./dist/cjs",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"cleanup": "rimraf ./dist",
"dev": "pnpm build -- -w --preserveWatchOutput --incremental"
},
@@ -70,9 +73,11 @@
"devDependencies": {
"@types/fs-extra": "9.0.13",
"@types/inquirer": "8.2.1",
"@vitest/coverage-c8": "0.25.2",
"npm-run-all": "4.1.5",
"rimraf": "3.0.2",
"typescript": "4.9.3"
"typescript": "4.9.3",
"vitest": "0.25.2"
},
"engines": {
"node": ">=12.20.0"

View File

@@ -25,6 +25,7 @@ import execa from 'execa';
import ora from 'ora';
import copyTemplate from './helpers/copy-template';
import detectJsonIndent from '../utils/detect-json-indent';
import getPackageManager from '../utils/get-package-manager';
export default async function add(): Promise<void> {
const extensionPath = process.cwd();
@@ -126,7 +127,9 @@ export default async function add(): Promise<void> {
await fse.writeJSON(packagePath, newExtensionManifest, { spaces: indent ?? '\t' });
await execa('npm', ['install'], { cwd: extensionPath });
const packageManager = getPackageManager();
await execa(packageManager, ['install'], { cwd: extensionPath });
spinner.succeed(chalk.bold('Done'));
} else {
@@ -263,7 +266,9 @@ export default async function add(): Promise<void> {
await fse.writeJSON(packagePath, newExtensionManifest, { spaces: indent ?? '\t' });
await execa('npm', ['install'], { cwd: extensionPath });
const packageManager = getPackageManager();
await execa(packageManager, ['install'], { cwd: extensionPath });
spinner.succeed(chalk.bold('Done'));
}

View File

@@ -18,6 +18,7 @@ import { isLanguage, languageToShort } from '../utils/languages';
import getSdkVersion from '../utils/get-sdk-version';
import getExtensionDevDeps from './helpers/get-extension-dev-deps';
import copyTemplate from './helpers/copy-template';
import getPackageManager from '../utils/get-package-manager';
type CreateOptions = { language?: string };
@@ -88,11 +89,13 @@ async function createPackageExtension({
await fse.writeJSON(path.join(targetPath, 'package.json'), packageManifest, { spaces: '\t' });
await execa('npm', ['install'], { cwd: targetPath });
const packageManager = getPackageManager();
await execa(packageManager, ['install'], { cwd: targetPath });
spinner.succeed(chalk.bold('Done'));
log(getDoneMessage(type, targetDir, targetPath));
log(getDoneMessage(type, targetDir, targetPath, packageManager));
}
async function createLocalExtension({
@@ -141,11 +144,13 @@ async function createLocalExtension({
await fse.writeJSON(path.join(targetPath, 'package.json'), packageManifest, { spaces: '\t' });
await execa('npm', ['install'], { cwd: targetPath });
const packageManager = getPackageManager();
await execa(packageManager, ['install'], { cwd: targetPath });
spinner.succeed(chalk.bold('Done'));
log(getDoneMessage(type, targetDir, targetPath));
log(getDoneMessage(type, targetDir, targetPath, packageManager));
}
function getPackageManifest(name: string, options: ExtensionOptions, deps: Record<string, string>) {
@@ -162,15 +167,15 @@ function getPackageManifest(name: string, options: ExtensionOptions, deps: Recor
};
}
function getDoneMessage(type: ExtensionPackageType, targetDir: string, targetPath: string) {
function getDoneMessage(type: ExtensionPackageType, targetDir: string, targetPath: string, packageManager: string) {
return `
Your ${type} extension has been created at ${chalk.green(targetPath)}
To start developing, run:
${chalk.blue('cd')} ${targetDir}
${chalk.blue('npm run')} dev
${chalk.blue(`${packageManager} run`)} dev
and then to build for production, run:
${chalk.blue('npm run')} build
${chalk.blue(`${packageManager} run`)} build
`;
}

View File

@@ -0,0 +1,24 @@
import { afterEach, expect, test } from 'vitest';
import getPackageManagerAgent from './get-package-manager-agent';
const envCopy = { ...process.env };
afterEach(() => {
process.env = envCopy;
});
test('Returns null if user agent cannot be extracted from env', () => {
delete process.env.npm_config_user_agent;
expect(getPackageManagerAgent()).toBe(null);
});
test('Returns information object from parsed user agent', () => {
process.env.npm_config_user_agent = 'pnpm/7.16.0 npm/? node/v18.12.1 darwin arm64';
expect(getPackageManagerAgent()).toStrictEqual({
node: 'v18.12.1',
npm: '?',
os: 'darwin (arm64)',
pnpm: '7.16.0',
});
});

View File

@@ -0,0 +1,14 @@
/**
* Extract the npm user agent from the npm_config_user_agent environment variable string
*/
export default function getPackageManagerAgent(): Record<string, string> | null {
const userAgent = process.env.npm_config_user_agent;
if (!userAgent) return null;
const values = userAgent.split(' ');
const fields = values.filter((field) => field.includes('/'));
const [platform, arch] = values.filter((field) => !field.includes('/'));
return Object.fromEntries(fields.map((field) => field.split('/')).concat([['os', `${platform} (${arch})`]]));
}

View File

@@ -0,0 +1,46 @@
import { vi, afterEach, expect, test } from 'vitest';
import getPackageManagerAgent from './get-package-manager-agent';
import getPackageManager from './get-package-manager';
vi.mock('./get-package-manager-agent');
afterEach(() => {
vi.clearAllMocks();
});
test('Returns npm is agent data is unavailable', () => {
vi.mocked(getPackageManagerAgent).mockReturnValueOnce(null);
expect(getPackageManager()).toBe('npm');
});
test('Returns pnpm if pnpm exists in agent and is not ?', () => {
vi.mocked(getPackageManagerAgent).mockReturnValueOnce({
node: 'v18.12.1',
npm: '?',
os: 'darwin (arm64)',
pnpm: '7.16.0',
});
expect(getPackageManager()).toBe('pnpm');
});
test('Returns yarn if yarn exists in agent and is not ?', () => {
vi.mocked(getPackageManagerAgent).mockReturnValueOnce({
node: 'v18.12.1',
npm: '?',
os: 'darwin (arm64)',
yarn: '2',
});
expect(getPackageManager()).toBe('yarn');
});
test('Returns npm is neither pnpm or yarn exist', () => {
vi.mocked(getPackageManagerAgent).mockReturnValueOnce({
node: 'v18.12.1',
npm: '8.19.2',
os: 'darwin (arm64)',
});
expect(getPackageManager()).toBe('npm');
});

View File

@@ -0,0 +1,15 @@
import getPackageManagerAgent from './get-package-manager-agent';
/**
* Determine whether to use pnpm, yarn, or npm based on the parsed package manager agent info
*/
export default function getPackageManager(): string {
const agent = getPackageManagerAgent();
if (agent !== null) {
if ('pnpm' in agent && agent.pnpm !== '?') return 'pnpm';
if ('yarn' in agent && agent.yarn !== '?') return 'yarn';
}
return 'npm';
}

4
pnpm-lock.yaml generated
View File

@@ -761,6 +761,7 @@ importers:
'@rollup/plugin-virtual': 3.0.1
'@types/fs-extra': 9.0.13
'@types/inquirer': 8.2.1
'@vitest/coverage-c8': 0.25.2
'@vue/compiler-sfc': 3.2.45
chalk: 4.1.1
commander: 9.4.1
@@ -775,6 +776,7 @@ importers:
rollup-plugin-typescript2: 0.34.1
rollup-plugin-vue: 6.0.0
typescript: 4.9.3
vitest: 0.25.2
dependencies:
'@directus/shared': link:../shared
'@rollup/plugin-commonjs': 23.0.2_rollup@3.3.0
@@ -797,9 +799,11 @@ importers:
devDependencies:
'@types/fs-extra': 9.0.13
'@types/inquirer': 8.2.1
'@vitest/coverage-c8': 0.25.2
npm-run-all: 4.1.5
rimraf: 3.0.2
typescript: 4.9.3
vitest: 0.25.2
packages/schema:
specifiers: