From 6379ff8dbe57d329af2aae50e894b60823d3391b Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Tue, 28 Nov 2023 13:18:22 -0500 Subject: [PATCH] Infer package manager from user agent --- code/lib/cli/src/helpers.test.ts | 36 ++++++++++++++++++++++++++++++++ code/lib/cli/src/helpers.ts | 27 ++++++++++++++++++++++++ code/lib/cli/src/initiate.ts | 7 +++++-- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/code/lib/cli/src/helpers.test.ts b/code/lib/cli/src/helpers.test.ts index 01897444db11..5b3492233a34 100644 --- a/code/lib/cli/src/helpers.test.ts +++ b/code/lib/cli/src/helpers.test.ts @@ -168,4 +168,40 @@ describe('Helpers', () => { }).toThrowError("Couldn't find any official storybook packages in package.json"); }); }); + + describe('inferPackageManagerFromUserAgent', () => { + afterEach(() => { + delete process.env.npm_config_user_agent; + }); + + it('should return undefined for invalid user agent', () => { + process.env.npm_config_user_agent = ''; + expect(helpers.inferPackageManagerFromUserAgent()).toBeUndefined(); + }); + + it('should infer pnpm from user agent', () => { + process.env.npm_config_user_agent = 'pnpm/7.4.0'; + expect(helpers.inferPackageManagerFromUserAgent()).toBe('pnpm'); + }); + + it('should infer npm from user agent', () => { + process.env.npm_config_user_agent = 'npm/7.24.0'; + expect(helpers.inferPackageManagerFromUserAgent()).toBe('npm'); + }); + + it('should infer yarn 1 from user agent', () => { + process.env.npm_config_user_agent = 'yarn/1.22.11'; + expect(helpers.inferPackageManagerFromUserAgent()).toBe('yarn1'); + }); + + it('should infer yarn 2 from user agent', () => { + process.env.npm_config_user_agent = 'yarn/2.4.0'; + expect(helpers.inferPackageManagerFromUserAgent()).toBe('yarn2'); + }); + + it('should return undefined for unknown package manager', () => { + process.env.npm_config_user_agent = 'unknown'; + expect(helpers.inferPackageManagerFromUserAgent()).toBeUndefined(); + }); + }); }); diff --git a/code/lib/cli/src/helpers.ts b/code/lib/cli/src/helpers.ts index db5cfb34d1ac..78bcfd1f790b 100644 --- a/code/lib/cli/src/helpers.ts +++ b/code/lib/cli/src/helpers.ts @@ -12,6 +12,7 @@ import type { JsPackageManager, PackageJson, PackageJsonWithDepsAndDevDeps, + PackageManagerName, } from './js-package-manager'; import type { SupportedFrameworks, SupportedRenderers } from './project_types'; import { SupportedLanguage } from './project_types'; @@ -303,3 +304,29 @@ export function getStorybookVersionSpecifier(packageJson: PackageJsonWithDepsAnd export async function isNxProject() { return findUp.sync('nx.json'); } + +/** + * Infer the package manager based on the command the user is running. + * Each package manager sets the `npm_config_user_agent` environment variable with its name and version e.g. "npm/7.24.0" + * Which is really useful when invoking commands via npx/pnpx/yarn create/etc. + */ +export function inferPackageManagerFromUserAgent(): PackageManagerName { + const userAgent = process.env.npm_config_user_agent; + if (!userAgent) return undefined; + const packageSpec = userAgent.split(' ')[0]; + const [pkgMgrName, pkgMgrVersion] = packageSpec.split('/'); + + if (pkgMgrName === 'pnpm') { + return 'pnpm'; + } + + if (pkgMgrName === 'npm') { + return 'npm'; + } + + if (pkgMgrName === 'yarn') { + return `yarn${pkgMgrVersion?.startsWith('1.') ? '1' : '2'}`; + } + + return undefined; +} diff --git a/code/lib/cli/src/initiate.ts b/code/lib/cli/src/initiate.ts index 3fb821818fff..7d15df966055 100644 --- a/code/lib/cli/src/initiate.ts +++ b/code/lib/cli/src/initiate.ts @@ -11,7 +11,7 @@ import boxen from 'boxen'; import { readdirSync } from 'fs-extra'; import { installableProjectTypes, ProjectType } from './project_types'; import { detect, isStorybookInstantiated, detectLanguage, detectPnp } from './detect'; -import { commandLog, codeLog, paddedLog } from './helpers'; +import { commandLog, codeLog, paddedLog, inferPackageManagerFromUserAgent } from './helpers'; import angularGenerator from './generators/ANGULAR'; import emberGenerator from './generators/EMBER'; import reactGenerator from './generators/REACT'; @@ -257,7 +257,10 @@ async function doInitiate( const isEmptyDir = cwdFolderEntries.length === 0 || cwdFolderEntries.every((entry) => entry.startsWith('.')); - const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); + const packageManager = JsPackageManagerFactory.getPackageManager({ + force: pkgMgr || inferPackageManagerFromUserAgent(), + }); + const welcomeMessage = 'storybook init - the simplest way to add a Storybook to your project.'; logger.log(chalk.inverse(`\n ${welcomeMessage} \n`));