Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI: Improve dependency metadata detection in storybook doctor #25037

Merged
merged 2 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions code/lib/cli/src/doctor/getDuplicatedDepsWarnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function getDuplicatedDepsWarnings(
): string[] | undefined {
try {
if (
!installationMetadata ||
!installationMetadata?.duplicatedDependencies ||
Object.keys(installationMetadata.duplicatedDependencies).length === 0
) {
Expand Down
17 changes: 14 additions & 3 deletions code/lib/cli/src/doctor/getMismatchingVersionsWarning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,20 @@ export function getMismatchingVersionsWarnings(
installationMetadata?: InstallationMetadata,
allDependencies?: Record<string, string>
): string | undefined {
if (!installationMetadata) {
return undefined;
}

const messages: string[] = [];
try {
const frameworkPackageName = Object.keys(installationMetadata?.dependencies || []).find(
(packageName) => {
return Object.keys(frameworkPackages).includes(packageName);
}
);
const cliVersion = getPrimaryVersion('@storybook/cli', installationMetadata);
const cliVersion =
getPrimaryVersion('@storybook/cli', installationMetadata) ||
getPrimaryVersion('storybook', installationMetadata);
const frameworkVersion = getPrimaryVersion(frameworkPackageName, installationMetadata);

if (!cliVersion || !frameworkVersion || semver.eq(cliVersion, frameworkVersion)) {
Expand Down Expand Up @@ -68,15 +74,20 @@ export function getMismatchingVersionsWarnings(
);

if (filteredDependencies.length > 0) {
const packageJsonSuffix = '(in your package.json)';
messages.push(
`Based on your lockfile, these dependencies should be upgraded:`,
filteredDependencies
.map(
([name, dep]) =>
`${chalk.hex('#ff9800')(name)}: ${dep[0].version} ${
allDependencies?.[name] ? '(in your package.json)' : ''
allDependencies?.[name] ? packageJsonSuffix : ''
}`
)
.sort(
(a, b) =>
(b.includes(packageJsonSuffix) ? 1 : 0) - (a.includes(packageJsonSuffix) ? 1 : 0)
)
.join('\n')
);
}
Expand All @@ -85,7 +96,7 @@ export function getMismatchingVersionsWarnings(
`You can run ${chalk.cyan(
'npx storybook@latest upgrade'
)} to upgrade all of your Storybook packages to the latest version.

Alternatively you can try manually changing the versions to match in your package.json. We also recommend regenerating your lockfile, or running the following command to possibly deduplicate your Storybook package versions: ${chalk.cyan(
installationMetadata?.dedupeCommand
)}`
Expand Down
35 changes: 24 additions & 11 deletions code/lib/cli/src/js-package-manager/NPMProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { sync as findUpSync } from 'find-up';
import { existsSync, readFileSync } from 'fs';
import path from 'path';
import semver from 'semver';
import { logger } from '@storybook/node-logger';
import { JsPackageManager } from './JsPackageManager';
import type { PackageJson } from './PackageJson';
import type { InstallationMetadata, PackageMetadata } from './types';
Expand Down Expand Up @@ -136,22 +137,34 @@ export class NPMProxy extends JsPackageManager {
}

public async findInstallations() {
const pipeToNull = platform() === 'win32' ? '2>NUL' : '2>/dev/null';
const commandResult = await this.executeCommand({
command: 'npm',
args: ['ls', '--json', '--depth=99', pipeToNull],
// ignore errors, because npm ls will exit with code 1 if there are e.g. unmet peer dependencies
ignoreError: true,
env: {
FORCE_COLOR: 'false',
},
});
const exec = async ({ depth }: { depth: number }) => {
const pipeToNull = platform() === 'win32' ? '2>NUL' : '2>/dev/null';
return this.executeCommand({
command: 'npm',
args: ['ls', '--json', `--depth=${depth}`, pipeToNull],
env: {
FORCE_COLOR: 'false',
},
});
};

try {
const commandResult = await exec({ depth: 99 });
const parsedOutput = JSON.parse(commandResult);

return this.mapDependencies(parsedOutput);
} catch (e) {
return undefined;
// when --depth is higher than 0, npm can return a non-zero exit code
// in case the user's project has peer dependency issues. So we try again with no depth
try {
const commandResult = await exec({ depth: 0 });
const parsedOutput = JSON.parse(commandResult);

return this.mapDependencies(parsedOutput);
} catch (err) {
logger.warn(`An issue occurred while trying to find dependencies metadata using npm.`);
return undefined;
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion code/lib/cli/src/js-package-manager/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// input: @storybook/addon-essentials@npm:7.0.0
// output: { name: '@storybook/addon-essentials', value: { version : '7.0.0', location: '' } }
export const parsePackageData = (packageName = '') => {
const [first, second, third] = packageName.trim().split('@');
const [first, second, third] = packageName
.replace(/[└─├]+/g, '')
.trim()
.split('@');
const version = (third || second).replace('npm:', '');
const name = third ? `@${second}` : first;

Expand Down