From 387f26b10b5fa3bd2c79c1e30417a2527fbdf34f Mon Sep 17 00:00:00 2001 From: Cody Kaup Date: Mon, 30 Sep 2024 17:12:53 -0500 Subject: [PATCH 1/4] Add TS config from internal repo --- tsconfig.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 438b31817..818a48c3e 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,9 @@ "module": "es2022", "moduleResolution": "Bundler", "strict": false, - "noImplicitAny": false, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": false, // Let this be a warning from ESLint until it becomes required + "useUnknownInCatchVariables": false, // Treat caught errors as 'any' for now "resolveJsonModule": true, "types": ["webpack-env"], "lib": ["es5", "es6", "dom"] From b663e027b16b10b4b0b2350e9186ccae6bcb7972 Mon Sep 17 00:00:00 2001 From: Cody Kaup Date: Tue, 1 Oct 2024 15:43:04 -0500 Subject: [PATCH 2/4] Fix a handful of strict TS errors --- .../git/getChangedFilesWithReplacement.ts | 2 +- node-src/git/getParentCommits.test.ts | 6 ++-- node-src/git/getParentCommits.ts | 2 +- node-src/git/mocks/mockIndex.ts | 29 +++++++++++++------ node-src/index.test.ts | 7 +++-- node-src/io/getDNSResolveAgent.ts | 4 +-- node-src/io/httpClient.ts | 2 +- node-src/lib/findChangedDependencies.ts | 8 +++-- node-src/lib/findChangedPackageFiles.test.ts | 2 ++ node-src/lib/getDependentStoryFiles.ts | 4 +-- node-src/lib/getEnvironment.ts | 8 ++--- node-src/lib/getFileHashes.ts | 4 +-- node-src/lib/installDependencies.ts | 8 ++--- node-src/lib/log.ts | 9 ++++-- node-src/lib/spawn.ts | 4 +-- node-src/lib/tasks.ts | 2 +- node-src/lib/upload.ts | 14 ++++----- node-src/lib/uploadMetadataFiles.ts | 11 ++++--- node-src/lib/waitForSentinel.ts | 4 +-- node-src/lib/writeChromaticDiagnostics.ts | 4 +++ node-src/tasks/auth.ts | 2 +- node-src/tasks/build.ts | 4 +-- node-src/tasks/gitInfo.ts | 10 ++++--- node-src/tasks/initialize.ts | 2 +- node-src/tasks/snapshot.ts | 7 +++-- node-src/tasks/upload.test.ts | 2 +- node-src/tasks/upload.ts | 16 +++++----- node-src/tasks/verify.ts | 12 ++++---- node-src/types.ts | 4 +-- node-src/typings.d.ts | 2 +- node-src/ui/messages/errors/fatalError.ts | 21 +++++++++----- .../ui/messages/info/storybookPublished.ts | 4 +-- .../ui/messages/info/tracedAffectedFiles.ts | 6 ++-- node-src/ui/messages/warnings/bailFile.ts | 11 ++++--- .../warnings/deviatingOutputDirectory.ts | 5 ++-- node-src/ui/tasks/gitInfo.ts | 2 +- node-src/ui/tasks/prepareWorkspace.ts | 4 +-- node-src/ui/tasks/upload.ts | 12 ++++---- node-src/ui/tasks/verify.ts | 6 ++-- 39 files changed, 151 insertions(+), 115 deletions(-) diff --git a/node-src/git/getChangedFilesWithReplacement.ts b/node-src/git/getChangedFilesWithReplacement.ts index 34a486a9b..bb91e654a 100644 --- a/node-src/git/getChangedFilesWithReplacement.ts +++ b/node-src/git/getChangedFilesWithReplacement.ts @@ -2,7 +2,7 @@ import { Context } from '../types'; import { findAncestorBuildWithCommit } from './findAncestorBuildWithCommit'; import { getChangedFiles } from './git'; -interface BuildWithCommitInfo { +export interface BuildWithCommitInfo { id: string; number: number; commit: string; diff --git a/node-src/git/getParentCommits.test.ts b/node-src/git/getParentCommits.test.ts index ed5ee8661..f80c5b3c0 100644 --- a/node-src/git/getParentCommits.test.ts +++ b/node-src/git/getParentCommits.test.ts @@ -37,7 +37,7 @@ function createClient({ builds, prs, }: { - repository: Repository; + repository: Pick; builds: [string, string][]; prs?: [string, string][]; }) { @@ -67,7 +67,7 @@ const options = {}; // This is built in from TypeScript 4.5 type Awaited = T extends Promise ? U : T; -interface Repository { +export interface Repository { dirname: string; runGit: ReturnType; commitMap: Awaited>; @@ -334,8 +334,6 @@ describe('getParentCommits', () => { await checkoutCommit('D', 'main', repository); const client = createClient({ repository: { - dirname: undefined, - runGit: undefined, commitMap: { Z: { hash: 'b0ff6070903ff046f769f958830d2ebf989ff981', committedAt: 1234 }, }, diff --git a/node-src/git/getParentCommits.ts b/node-src/git/getParentCommits.ts index 0958b428b..1c5075cde 100644 --- a/node-src/git/getParentCommits.ts +++ b/node-src/git/getParentCommits.ts @@ -55,7 +55,7 @@ const MergeCommitsQuery = gql` } } `; -interface MergeCommitsQueryResult { +export interface MergeCommitsQueryResult { app: { mergedPullRequests: [ { diff --git a/node-src/git/mocks/mockIndex.ts b/node-src/git/mocks/mockIndex.ts index da3d82eb1..7002a440e 100644 --- a/node-src/git/mocks/mockIndex.ts +++ b/node-src/git/mocks/mockIndex.ts @@ -12,6 +12,9 @@ // create a mock set of responses to the queries we run as part of our git algorithm +import type { MergeCommitsQueryResult } from '../getParentCommits'; +import { Repository } from '../getParentCommits.test'; + interface Build { branch: string; commit: string; @@ -55,15 +58,17 @@ const mocks = { prs: PR[], { mergeInfoList }: { mergeInfoList: MergeInfo[] } ) => { - const mergedPrs = []; + const mergedPrs: MergeCommitsQueryResult['app']['mergedPullRequests'][0][] = []; for (const mergeInfo of mergeInfoList) { const pr = prs.find((p) => p.mergeCommitHash === mergeInfo.commit); const prLastBuild = pr && lastBuildOnBranch(builds, pr.headBranch); - mergedPrs.push({ - lastHeadBuild: prLastBuild && { - commit: prLastBuild.commit, - }, - }); + if (prLastBuild) { + mergedPrs.push({ + lastHeadBuild: { + commit: prLastBuild.commit, + }, + }); + } } return { @@ -84,13 +89,19 @@ const mocks = { * * @returns A mock Index service for testing. */ -export default function createMockIndex({ commitMap }, buildDescriptions, prDescriptions = []) { +export default function createMockIndex( + { commitMap }: Pick, + buildDescriptions: [string, string][], + prDescriptions: [string, string][] = [] +) { const builds = buildDescriptions.map(([name, branch], index) => { - let hash, committedAt; + let hash: string; + let committedAt: number; + if (commitMap[name]) { const commitInfo = commitMap[name]; hash = commitInfo.hash; - committedAt = Number.parseInt(commitInfo.committedAt, 10) * 1000; + committedAt = Math.floor(commitInfo.committedAt) * 1000; } else { // Allow for test cases with a commit that is no longer in the history hash = name; diff --git a/node-src/index.test.ts b/node-src/index.test.ts index 66ca06d87..0ba0b7c3c 100644 --- a/node-src/index.test.ts +++ b/node-src/index.test.ts @@ -72,7 +72,8 @@ vi.mock('node-fetch', () => ({ // TODO: refactor this function // eslint-disable-next-line complexity, max-statements json: async () => { - let query: string, variables: Record; + let query = ''; + let variables: Record = {}; try { const data = JSON.parse(body); query = data.query; @@ -406,7 +407,7 @@ it('passes options error to experimental_onTaskError', async () => { ctx.env.CHROMATIC_PROJECT_TOKEN = ''; await runAll(ctx); - expect(ctx.extraOptions.experimental_onTaskError).toHaveBeenCalledWith( + expect(ctx.extraOptions?.experimental_onTaskError).toHaveBeenCalledWith( expect.anything(), // Context expect.objectContaining({ formattedError: expect.stringContaining('Missing project token'), // Long formatted error fatalError https://github.com/chromaui/chromatic-cli/blob/217e77671179748eb4ddb8becde78444db93d067/node-src/ui/messages/errors/fatalError.ts#L11 @@ -764,7 +765,7 @@ it('should upload metadata files if --upload-metadata is passed', async () => { '--only-changed', ]); await runAll(ctx); - expect(upload.mock.calls.at(-1)[1]).toEqual( + expect(upload.mock.calls.at(-1)?.[1]).toEqual( expect.arrayContaining([ { contentLength: expect.any(Number), diff --git a/node-src/io/getDNSResolveAgent.ts b/node-src/io/getDNSResolveAgent.ts index b99d19390..9cd951a00 100644 --- a/node-src/io/getDNSResolveAgent.ts +++ b/node-src/io/getDNSResolveAgent.ts @@ -10,8 +10,8 @@ export class DNSResolveAgent extends Agent { ...options, lookup( hostname: string, - _options: dns.LookupOneOptions, - callback: (err: NodeJS.ErrnoException, address: string, family: number) => void + _options: dns.LookupOptions, + callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void ) { dns.resolve(hostname, (err, addresses) => callback(err, addresses?.[0], 4)); }, diff --git a/node-src/io/httpClient.ts b/node-src/io/httpClient.ts index 8593a6803..174226d77 100644 --- a/node-src/io/httpClient.ts +++ b/node-src/io/httpClient.ts @@ -53,7 +53,7 @@ export default class HTTPClient { constructor( { env, log }: Pick, - { headers, retries = 0 }: HTTPClientOptions = {} + { headers = {}, retries = 0 }: HTTPClientOptions = {} ) { if (!log) throw new Error(`Missing required option in HTTPClient: log`); this.env = env; diff --git a/node-src/lib/findChangedDependencies.ts b/node-src/lib/findChangedDependencies.ts index 288e8e82e..9d4ab3d33 100644 --- a/node-src/lib/findChangedDependencies.ts +++ b/node-src/lib/findChangedDependencies.ts @@ -12,18 +12,20 @@ const YARN_LOCK = 'yarn.lock'; // Yields a list of dependency names which have changed since the baseline. // E.g. ['react', 'react-dom', '@storybook/react'] +// TODO: refactor this function +// eslint-disable-next-line complexity export const findChangedDependencies = async (ctx: Context) => { const { packageMetadataChanges } = ctx.git; const { untraced = [] } = ctx.options; - if (packageMetadataChanges.length === 0) { + if (packageMetadataChanges?.length === 0) { ctx.log.debug('No package metadata changed found'); return []; } ctx.log.debug( { packageMetadataChanges }, - `Finding changed dependencies for ${packageMetadataChanges.length} baselines` + `Finding changed dependencies for ${packageMetadataChanges?.length} baselines` ); const rootPath = await getRepositoryRoot(); @@ -69,7 +71,7 @@ export const findChangedDependencies = async (ctx: Context) => { const filteredPathPairs = metadataPathPairs .map(([manifestPath, lockfilePath]) => { const commits = packageMetadataChanges - .filter(({ changedFiles }) => + ?.filter(({ changedFiles }) => changedFiles.some((file) => file === lockfilePath || file === manifestPath) ) .map(({ commit }) => commit); diff --git a/node-src/lib/findChangedPackageFiles.test.ts b/node-src/lib/findChangedPackageFiles.test.ts index 744cbaa3d..c12e89dbb 100644 --- a/node-src/lib/findChangedPackageFiles.test.ts +++ b/node-src/lib/findChangedPackageFiles.test.ts @@ -14,6 +14,8 @@ const execGitCommand = vi.mocked(git.execGitCommand); const mockFileContents = (packagesCommitsByFile) => { execGitCommand.mockImplementation(async (input) => { const regexResults = /show\s([^:]*):(.*)/g.exec(input); + if (!regexResults) return ''; + const commit = regexResults[1]; const fileName = regexResults[2]; diff --git a/node-src/lib/getDependentStoryFiles.ts b/node-src/lib/getDependentStoryFiles.ts index 6330ac1a0..27063b57a 100644 --- a/node-src/lib/getDependentStoryFiles.ts +++ b/node-src/lib/getDependentStoryFiles.ts @@ -251,7 +251,7 @@ export async function getDependentStoryFiles( // trace changed dependencies, so we bail just in case. ctx.turboSnap.bailReason = { changedPackageFiles: [ - ...ctx.git.changedFiles.filter((file) => isPackageManifestFile(file)), + ...(ctx.git.changedFiles?.filter((file) => isPackageManifestFile(file)) || []), ...changedPackageLockFiles, ], }; @@ -272,7 +272,7 @@ export async function getDependentStoryFiles( // TODO: refactor this function // eslint-disable-next-line complexity function traceName(name: string, tracePath: string[] = []) { - if (ctx.turboSnap.bailReason || isCsfGlob(name)) return; + if (ctx.turboSnap?.bailReason || isCsfGlob(name)) return; if (shouldBail(name)) return; const { id } = modulesByName.get(name) || {}; const normalizedName = namesById.get(id); diff --git a/node-src/lib/getEnvironment.ts b/node-src/lib/getEnvironment.ts index 0d2870998..f839288c2 100644 --- a/node-src/lib/getEnvironment.ts +++ b/node-src/lib/getEnvironment.ts @@ -5,14 +5,14 @@ export interface Environment { CHROMATIC_INDEX_URL: string; CHROMATIC_OUTPUT_INTERVAL: number; CHROMATIC_POLL_INTERVAL: number; - CHROMATIC_PROJECT_TOKEN: string; + CHROMATIC_PROJECT_TOKEN?: string; CHROMATIC_RETRIES: number; - CHROMATIC_STORYBOOK_VERSION: string; + CHROMATIC_STORYBOOK_VERSION?: string; CHROMATIC_TIMEOUT: number; CHROMATIC_UPGRADE_TIMEOUT: number; ENVIRONMENT_WHITELIST: RegExp[]; - HTTP_PROXY: string; - HTTPS_PROXY: string; + HTTP_PROXY?: string; + HTTPS_PROXY?: string; STORYBOOK_BUILD_TIMEOUT: number; STORYBOOK_CLI_FLAGS_BY_VERSION: typeof STORYBOOK_CLI_FLAGS_BY_VERSION; STORYBOOK_VERIFY_TIMEOUT: number; diff --git a/node-src/lib/getFileHashes.ts b/node-src/lib/getFileHashes.ts index 3f3968757..12534f9ee 100644 --- a/node-src/lib/getFileHashes.ts +++ b/node-src/lib/getFileHashes.ts @@ -18,7 +18,7 @@ const hashFile = (buffer: Buffer, path: string, xxhash: XXHashAPI): Promise) => { - read(fd, buffer, undefined, BUFFER_SIZE, -1, (readError, bytesRead) => { + read(fd, buffer, 0, BUFFER_SIZE, -1, (readError, bytesRead) => { if (readError) { return close(fd, () => reject(readError)); } @@ -36,7 +36,7 @@ const hashFile = (buffer: Buffer, path: string, xxhash: XXHashAPI): Promise { + read(fd, buffer, 0, BUFFER_SIZE, -1, (readError, bytesRead) => { if (readError) { return close(fd, () => reject(readError)); } diff --git a/node-src/lib/installDependencies.ts b/node-src/lib/installDependencies.ts index 3f56b0b86..5fe07f3b5 100644 --- a/node-src/lib/installDependencies.ts +++ b/node-src/lib/installDependencies.ts @@ -1,17 +1,15 @@ import { SpawnOptions } from 'child_process'; -import yon from 'yarn-or-npm'; - -const { spawn } = yon; +import { spawn } from 'yarn-or-npm'; const installDependencies = (options?: SpawnOptions) => new Promise((resolve, reject) => { let stdout = ''; let stderr = ''; const child = spawn(['install'], options); - child.stdout.on('data', (chunk) => { + child.stdout?.on('data', (chunk) => { stdout += chunk; }); - child.stderr.on('data', (chunk) => { + child.stderr?.on('data', (chunk) => { stderr += chunk; }); child.on('error', reject); diff --git a/node-src/lib/log.ts b/node-src/lib/log.ts index 45d436f0b..7ffaabedf 100644 --- a/node-src/lib/log.ts +++ b/node-src/lib/log.ts @@ -6,6 +6,11 @@ import { format } from 'util'; import { errorSerializer } from './logSerializers'; +interface QueueMessage { + type: LogType; + messages: string[]; +} + const { DISABLE_LOGGING, LOG_LEVEL = '' } = process.env; const LOG_LEVELS = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 }; const DEFAULT_LEVEL = 'info'; @@ -95,7 +100,7 @@ export const createLogger = () => { const args = new Set(process.argv.slice(2)); let interactive = !args.has('--debug') && !args.has('--no-interactive'); let enqueue = false; - const queue = []; + const queue: QueueMessage[] = []; const log = (type: LogType, logFileOnly?: boolean) => @@ -140,7 +145,7 @@ export const createLogger = () => { }, flush: () => { while (queue.length > 0) { - const { type, messages } = queue.shift(); + const { type, messages } = queue.shift() as QueueMessage; console.log(''); console[type](...messages); } diff --git a/node-src/lib/spawn.ts b/node-src/lib/spawn.ts index 2d37dfb0d..b2fcb60f2 100644 --- a/node-src/lib/spawn.ts +++ b/node-src/lib/spawn.ts @@ -16,10 +16,10 @@ export default function spawn( let stdout = ''; let stderr = ''; const child = packageCommand(args, options); - child.stdout.on('data', (chunk) => { + child.stdout?.on('data', (chunk) => { stdout += chunk; }); - child.stderr.on('data', (chunk) => { + child.stderr?.on('data', (chunk) => { stderr += chunk; }); child.on('error', reject); diff --git a/node-src/lib/tasks.ts b/node-src/lib/tasks.ts index 3def15366..cccb35fa2 100644 --- a/node-src/lib/tasks.ts +++ b/node-src/lib/tasks.ts @@ -55,7 +55,7 @@ export const transitionTo = }; export const getDuration = (ctx: Context) => { - const now = Number.isInteger(ctx.now) ? ctx.now : Date.now(); + const now = (Number.isInteger(ctx.now) ? ctx.now : Date.now()) as number; const duration = Math.round((now - ctx.startedAt) / 1000); const seconds = pluralize('second', Math.floor(duration % 60), true); if (duration < 60) return seconds; diff --git a/node-src/lib/upload.ts b/node-src/lib/upload.ts index 6b579e3fd..b7ce65b1e 100644 --- a/node-src/lib/upload.ts +++ b/node-src/lib/upload.ts @@ -148,17 +148,17 @@ export async function uploadBuild( return options.onError?.(new Error('Upload rejected due to user error')); } - ctx.sentinelUrls.push(...uploadBuild.info.sentinelUrls); + ctx.sentinelUrls.push(...(uploadBuild.info?.sentinelUrls || [])); targets.push( - ...uploadBuild.info.targets.map((target) => { + ...(uploadBuild.info?.targets.map((target) => { const file = batch.find((f) => f.targetPath === target.filePath); - return { ...file, ...target }; - }) + return { ...file, ...target } as FileDesc & TargetInfo; + }) || []) ); // Use the last received zipTarget, as it will have the largest allowed size. // If all files in the batch are copied rather than uploaded, we won't receive a zipTarget. - if (uploadBuild.info.zipTarget) { + if (uploadBuild.info?.zipTarget) { zipTarget = uploadBuild.info.zipTarget; } } @@ -194,7 +194,7 @@ export async function uploadBuild( } catch (err) { const target = targets.find((target) => target.localPath === err.message); if (target) ctx.log.error(uploadFailed({ target }, ctx.log.getLevel() === 'debug')); - return options.onError?.(err, target.localPath); + return options.onError?.(err, target?.localPath); } } @@ -254,7 +254,7 @@ export async function uploadMetadata(ctx: Context, files: FileDesc[]) { if (uploadMetadata.info) { const targets = uploadMetadata.info.targets.map((target) => { const file = files.find((f) => f.targetPath === target.filePath); - return { ...file, ...target }; + return { ...file, ...target } as FileDesc & TargetInfo; }); await uploadFiles(ctx, targets); } diff --git a/node-src/lib/uploadMetadataFiles.ts b/node-src/lib/uploadMetadataFiles.ts index b54a9cd19..7e2feaf75 100644 --- a/node-src/lib/uploadMetadataFiles.ts +++ b/node-src/lib/uploadMetadataFiles.ts @@ -32,19 +32,18 @@ export async function uploadMetadataFiles(ctx: Context) { await findStorybookConfigFile(ctx, /^main\.[jt]sx?$/).catch(() => undefined), await findStorybookConfigFile(ctx, /^preview\.[jt]sx?$/).catch(() => undefined), ctx.fileInfo?.statsPath && (await trimStatsFile([ctx.fileInfo.statsPath])), - ].filter(Boolean); + ].filter((m): m is string => !!m); - const files = await Promise.all( + let files = await Promise.all( metadataFiles.map(async (localPath) => { const contentLength = await fileSize(localPath); const targetPath = `.chromatic/${path.basename(localPath)}`; return contentLength && { contentLength, localPath, targetPath }; }) - ).then((files) => - files - .filter(Boolean) - .sort((a, b) => a.targetPath.localeCompare(b.targetPath, 'en', { numeric: true })) ); + files = files + .filter((f): f is FileDesc => !!f) + .sort((a, b) => a.targetPath.localeCompare(b.targetPath, 'en', { numeric: true })); if (files.length === 0) { ctx.log.warn('No metadata files found, skipping metadata upload.'); diff --git a/node-src/lib/waitForSentinel.ts b/node-src/lib/waitForSentinel.ts index 9ce55c9e2..ec9e6230a 100644 --- a/node-src/lib/waitForSentinel.ts +++ b/node-src/lib/waitForSentinel.ts @@ -49,8 +49,8 @@ export async function waitForSentinel(ctx: Context, { name, url }: { name: strin if (response.status === 404) { throw new Error(`Sentinel file '${name}' not present.`); } - if (this.log.getLevel() === 'debug') { - this.log.debug(await response.text()); + if (ctx.log.getLevel() === 'debug') { + ctx.log.debug(await response.text()); } return bail(new Error(message)); } diff --git a/node-src/lib/writeChromaticDiagnostics.ts b/node-src/lib/writeChromaticDiagnostics.ts index 0dc345e80..8fcd3a8d6 100644 --- a/node-src/lib/writeChromaticDiagnostics.ts +++ b/node-src/lib/writeChromaticDiagnostics.ts @@ -12,6 +12,10 @@ const { writeFile } = jsonfile; * @param ctx The context set when executing the CLI. */ export async function writeChromaticDiagnostics(ctx: Context) { + if (!ctx.options.diagnosticsFile) { + return; + } + try { await writeFile(ctx.options.diagnosticsFile, getDiagnostics(ctx), { spaces: 2 }); ctx.log.info(wroteReport(ctx.options.diagnosticsFile, 'Chromatic diagnostics')); diff --git a/node-src/tasks/auth.ts b/node-src/tasks/auth.ts index 3fcd1952f..382aa0414 100644 --- a/node-src/tasks/auth.ts +++ b/node-src/tasks/auth.ts @@ -50,7 +50,7 @@ export const setAuthorizationToken = async (ctx: Context) => { } catch (errors) { const message = errors[0]?.message; if (message?.match('Must login') || message?.match('No Access')) { - throw new Error(invalidProjectId({ projectId: ctx.options.projectId })); + throw new Error(invalidProjectId({ projectId: ctx.options.projectId || '' })); } if (message?.match('No app with code')) { throw new Error(invalidProjectToken({ projectToken: ctx.options.projectToken })); diff --git a/node-src/tasks/build.ts b/node-src/tasks/build.ts index 36b115e84..e9ab8c5fd 100644 --- a/node-src/tasks/build.ts +++ b/node-src/tasks/build.ts @@ -30,7 +30,7 @@ export const setSourceDirectory = async (ctx: Context) => { export const setBuildCommand = async (ctx: Context) => { const webpackStatsSupported = ctx.storybook && ctx.storybook.version - ? semver.gte(semver.coerce(ctx.storybook.version), '6.2.0') + ? semver.gte(semver.coerce(ctx.storybook.version) || '', '6.2.0') : true; if (ctx.git.changedFiles && !webpackStatsSupported) { @@ -40,7 +40,7 @@ export const setBuildCommand = async (ctx: Context) => { const buildCommandOptions = [ `--output-dir=${ctx.sourceDir}`, ctx.git.changedFiles && webpackStatsSupported && `--webpack-stats-json=${ctx.sourceDir}`, - ].filter(Boolean); + ].filter((c): c is string => !!c); ctx.buildCommand = await (isE2EBuild(ctx.options) ? getE2EBuildCommand( diff --git a/node-src/tasks/gitInfo.ts b/node-src/tasks/gitInfo.ts index 7fc553692..a4e6eb843 100644 --- a/node-src/tasks/gitInfo.ts +++ b/node-src/tasks/gitInfo.ts @@ -103,10 +103,10 @@ export const setGitInfo = async (ctx: Context, task: Task) => { const { branch, commit, slug } = ctx.git; - ctx.git.matchesBranch = (glob: true | string) => + ctx.git.matchesBranch = (glob: string | boolean) => typeof glob === 'string' && glob.length > 0 ? picomatch(glob, { bash: true })(branch) : !!glob; - if (ctx.git.matchesBranch(ctx.options.skip)) { + if (ctx.git.matchesBranch?.(ctx.options.skip)) { transitionTo(skippingBuild)(ctx, task); // The SkipBuildMutation ensures the commit is tagged properly. if (await ctx.client.runQuery(SkipBuildMutation, { commit, branch, slug })) { @@ -119,7 +119,7 @@ export const setGitInfo = async (ctx: Context, task: Task) => { } const parentCommits = await getParentCommits(ctx, { - ignoreLastBuildOnBranch: ctx.git.matchesBranch(ctx.options.ignoreLastBuildOnBranch), + ignoreLastBuildOnBranch: ctx.git.matchesBranch?.(ctx.options.ignoreLastBuildOnBranch || false), }); ctx.git.parentCommits = parentCommits; ctx.log.debug(`Found parentCommits: ${parentCommits.join(', ')}`); @@ -219,7 +219,9 @@ export const setGitInfo = async (ctx: Context, task: Task) => { .map(({ build, replacementBuild }) => { ctx.log.info(''); ctx.log.info(replacedBuild({ replacedBuild: build, replacementBuild })); - return [build.id, replacementBuild.id]; + // `replacementBuild` is filtered above + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return [build.id, replacementBuild!.id]; }); if (!interactive) { diff --git a/node-src/tasks/initialize.ts b/node-src/tasks/initialize.ts index a2a9cb697..79a3e0ceb 100644 --- a/node-src/tasks/initialize.ts +++ b/node-src/tasks/initialize.ts @@ -74,7 +74,7 @@ export const announceBuild = async (ctx: Context) => { ...commitInfo } = ctx.git; // omit some fields; const { rebuildForBuildId, turboSnap } = ctx; - const autoAcceptChanges = matchesBranch(ctx.options.autoAcceptChanges); + const autoAcceptChanges = matchesBranch?.(ctx.options.autoAcceptChanges); const { announceBuild: announcedBuild } = await ctx.client.runQuery( AnnounceBuildMutation, diff --git a/node-src/tasks/snapshot.ts b/node-src/tasks/snapshot.ts index a113ef902..42291f408 100644 --- a/node-src/tasks/snapshot.ts +++ b/node-src/tasks/snapshot.ts @@ -62,7 +62,7 @@ export const takeSnapshots = async (ctx: Context, task: Task) => { const testLabels = ctx.options.interactive && testCount === actualTestCount && - tests.map(({ spec, parameters, mode }) => { + tests?.map(({ spec, parameters, mode }) => { const testSuffixName = mode.name || `[${parameters.viewport}px]`; const suffix = parameters.viewportIsDefault ? '' : testSuffixName; return `${spec.component.displayName} › ${spec.name} ${suffix}`; @@ -113,7 +113,10 @@ export const takeSnapshots = async (ctx: Context, task: Task) => { case 'ACCEPTED': case 'PENDING': case 'DENIED': { - if (build.autoAcceptChanges || ctx.git.matchesBranch(ctx.options.exitZeroOnChanges)) { + if ( + build.autoAcceptChanges || + ctx.git.matchesBranch?.(ctx.options?.exitZeroOnChanges || false) + ) { setExitCode(ctx, exitCodes.OK); ctx.log.info(buildPassedMessage(ctx)); } else { diff --git a/node-src/tasks/upload.test.ts b/node-src/tasks/upload.test.ts index 393bb45a9..3a6e153b2 100644 --- a/node-src/tasks/upload.test.ts +++ b/node-src/tasks/upload.test.ts @@ -171,7 +171,7 @@ describe('traceChangedFiles', () => { findChangedDependencies.mockReset(); findChangedPackageFiles.mockReset(); getDependentStoryFiles.mockReset(); - accessMock.mockImplementation((_path, callback) => Promise.resolve(callback(undefined))); + accessMock.mockImplementation((_path, callback) => Promise.resolve(callback(null))); }); it('sets onlyStoryFiles on context', async () => { diff --git a/node-src/tasks/upload.ts b/node-src/tasks/upload.ts index 4bbd2128d..e99a36d4e 100644 --- a/node-src/tasks/upload.ts +++ b/node-src/tasks/upload.ts @@ -81,7 +81,7 @@ function getFileInfo(ctx: Context, sourceDirectory: string) { })); const total = lengths.map(({ contentLength }) => contentLength).reduce((a, b) => a + b, 0); const paths: string[] = []; - let statsPath: string; + let statsPath = ''; for (const { knownAs } of lengths) { if (knownAs.endsWith('preview-stats.json')) statsPath = path.join(sourceDirectory, knownAs); else if (!knownAs.endsWith('manager-stats.json')) paths.push(knownAs); @@ -119,10 +119,10 @@ export const validateFiles = async (ctx: Context) => { export const traceChangedFiles = async (ctx: Context, task: Task) => { if (!ctx.turboSnap || ctx.turboSnap.unavailable) return; if (!ctx.git.changedFiles) return; - if (!ctx.fileInfo.statsPath) { + if (!ctx.fileInfo?.statsPath) { // If we don't know the SB version, we should assume we don't support `--stats-json` const nonLegacyStatsSupported = - ctx.storybook?.version && semver.gte(semver.coerce(ctx.storybook.version), '8.0.0'); + ctx.storybook?.version && semver.gte(semver.coerce(ctx.storybook.version) || '', '8.0.0'); ctx.turboSnap.bailReason = { missingStatsFile: true }; throw new Error(missingStatsFile({ legacy: !nonLegacyStatsSupported })); @@ -136,7 +136,7 @@ export const traceChangedFiles = async (ctx: Context, task: Task) => { try { // eslint-disable-next-line @typescript-eslint/no-invalid-void-type let changedDependencyNames: void | string[] = []; - if (packageMetadataChanges?.length > 0) { + if (packageMetadataChanges?.length) { changedDependencyNames = await findChangedDependencies(ctx).catch((err) => { const { name, message, stack, code } = err; ctx.log.debug({ name, message, stack, code }); @@ -229,9 +229,9 @@ export const uploadStorybook = async (ctx: Context, task: Task) => { if (ctx.skip) return; transitionTo(starting)(ctx, task); - const files = ctx.fileInfo.paths.map((filePath) => ({ - ...(ctx.fileInfo.hashes && { contentHash: ctx.fileInfo.hashes[filePath] }), - contentLength: ctx.fileInfo.lengths.find(({ knownAs }) => knownAs === filePath).contentLength, + const files = ctx.fileInfo?.paths.map((filePath) => ({ + ...(ctx.fileInfo?.hashes && { contentHash: ctx.fileInfo.hashes[filePath] }), + contentLength: ctx.fileInfo?.lengths.find(({ knownAs }) => knownAs === filePath)?.contentLength, localPath: path.join(ctx.sourceDir, filePath), targetPath: filePath, })); @@ -260,7 +260,7 @@ export const waitForSentinels = async (ctx: Context, task: Task) => { const sentinels = Object.fromEntries( ctx.sentinelUrls.map((url) => { const { host, pathname } = new URL(url); - return [host + pathname, { name: pathname.split('/').at(-1), url }]; + return [host + pathname, { name: pathname.split('/').at(-1) || '', url }]; }) ); diff --git a/node-src/tasks/verify.ts b/node-src/tasks/verify.ts index 82fa03e5d..c5ac9be7a 100644 --- a/node-src/tasks/verify.ts +++ b/node-src/tasks/verify.ts @@ -235,9 +235,9 @@ export const verifyBuild = async (ctx: Context, task: Task) => { ), ]); - ctx.isPublishOnly = !ctx.build.features.uiReview && !ctx.build.features.uiTests; + ctx.isPublishOnly = !ctx.build.features?.uiReview && !ctx.build.features?.uiTests; - if (list) { + if (list && ctx.build.tests) { ctx.log.info(listingStories(ctx.build.tests)); } @@ -251,15 +251,15 @@ export const verifyBuild = async (ctx: Context, task: Task) => { if (ctx.build.wasLimited) { const { account } = ctx.build.app; - if (account.exceededThreshold) { + if (account?.exceededThreshold) { ctx.log.warn(snapshotQuotaReached(account)); setExitCode(ctx, exitCodes.ACCOUNT_QUOTA_REACHED, true); - } else if (account.paymentRequired) { + } else if (account?.paymentRequired) { ctx.log.warn(paymentRequired(account)); setExitCode(ctx, exitCodes.ACCOUNT_PAYMENT_REQUIRED, true); } else { // Future proofing for reasons we aren't aware of - ctx.log.warn(buildLimited(account)); + if (account) ctx.log.warn(buildLimited(account)); setExitCode(ctx, exitCodes.BUILD_WAS_LIMITED, true); } } @@ -268,7 +268,7 @@ export const verifyBuild = async (ctx: Context, task: Task) => { transitionTo(success, true)(ctx, task); - if (list || ctx.isPublishOnly || matchesBranch(ctx.options.exitOnceUploaded)) { + if (list || ctx.isPublishOnly || matchesBranch?.(ctx.options.exitOnceUploaded)) { setExitCode(ctx, exitCodes.OK); ctx.skipSnapshots = true; } diff --git a/node-src/types.ts b/node-src/types.ts index 23e1796e2..45fdf2f6d 100644 --- a/node-src/types.ts +++ b/node-src/types.ts @@ -207,7 +207,7 @@ export interface Context { git: { version: string; /** The current user's email as pre git config */ - gitUserEmail: string; + gitUserEmail?: string; branch: string; commit: string; committerEmail?: string; @@ -361,7 +361,7 @@ export interface Context { } export interface Task { - status: string; + status?: string; title: string; output?: string; } diff --git a/node-src/typings.d.ts b/node-src/typings.d.ts index 1ba615a78..9b6b1f63c 100644 --- a/node-src/typings.d.ts +++ b/node-src/typings.d.ts @@ -2,7 +2,7 @@ declare module 'yarn-or-npm' { import { SpawnOptions } from 'child_process'; import crossSpawn from 'cross-spawn'; - export function spawn(args: string[], options: SpawnOptions): ReturnType; + export function spawn(args: string[], options?: SpawnOptions): ReturnType; export const hasYarn: () => boolean; } diff --git a/node-src/ui/messages/errors/fatalError.ts b/node-src/ui/messages/errors/fatalError.ts index 3a04f2c92..cb2b00c7a 100644 --- a/node-src/ui/messages/errors/fatalError.ts +++ b/node-src/ui/messages/errors/fatalError.ts @@ -7,13 +7,6 @@ import { Context, InitialContext } from '../../..'; import { redact } from '../../../lib/utils'; import link from '../../components/link'; -const buildFields = ({ id, number, storybookUrl = undefined, webUrl = undefined }) => ({ - id, - number, - ...(storybookUrl && { storybookUrl }), - ...(webUrl && { webUrl }), -}); - /** * Generate an error message from a fatal error that occurred when executing the CLI. * @@ -99,3 +92,17 @@ export default function fatalError( stacktraces.length > 0 ? chalk`\n{dim ${stacktraces.join('\n\n')}}` : '', ].join('\n'); } + +function buildFields({ + id, + number, + storybookUrl = undefined, + webUrl = undefined, +}: { + id: string; + number: number; + storybookUrl?: string; + webUrl?: string; +}) { + return { id, number, ...(storybookUrl && { storybookUrl }), ...(webUrl && { webUrl }) }; +} diff --git a/node-src/ui/messages/info/storybookPublished.ts b/node-src/ui/messages/info/storybookPublished.ts index af3765435..3dd2424e9 100644 --- a/node-src/ui/messages/info/storybookPublished.ts +++ b/node-src/ui/messages/info/storybookPublished.ts @@ -15,12 +15,12 @@ export default ({ build, storybookUrl }: Pick return dedent(chalk` ${success} {bold Storybook published} We found ${components} with ${stories}. - ${info} View your Storybook at ${link(storybookUrl)} + ${info} View your Storybook at ${link(storybookUrl || '')} `); } return dedent(chalk` ${success} {bold Storybook published} - ${info} View your Storybook at ${link(storybookUrl)} + ${info} View your Storybook at ${link(storybookUrl || '')} `); }; diff --git a/node-src/ui/messages/info/tracedAffectedFiles.ts b/node-src/ui/messages/info/tracedAffectedFiles.ts index 5dfdf4dbc..ba94482a0 100644 --- a/node-src/ui/messages/info/tracedAffectedFiles.ts +++ b/node-src/ui/messages/info/tracedAffectedFiles.ts @@ -11,7 +11,7 @@ const printFilePath = (filepath: string, basedir: string, expanded: boolean) => .split('/') .map((part, index, parts) => { if (index < parts.length - 1) return part; - const [, file, suffix = ''] = part.match(/^(.+?)( \+ \d+ modules)?$/); + const [, file, suffix = ''] = part.match(/^(.+?)( \+ \d+ modules)?$/) || []; return chalk.bold(file) + (expanded ? chalk.magenta(suffix) : chalk.dim(suffix)); }) .join('/'); @@ -55,7 +55,7 @@ export default ( if (expanded) { const bailReason = ctx.turboSnap?.bailReason ? `${ctx.turboSnap.bailReason}\n\n` : ''; - const rootPath = `${chalk.magenta(rootDirectoryNote)} ${ctx.turboSnap.rootPath}\n\n`; + const rootPath = `${chalk.magenta(rootDirectoryNote)} ${ctx.turboSnap?.rootPath}\n\n`; const basePath = `${chalk.magenta(baseDirectoryNote)} ${basedir}\n\n`; const storybookPath = `${chalk.magenta(storybookDirectoryNote)} ${storybookConfigDirectory}\n\n`; const untracedNotice = @@ -100,7 +100,7 @@ export default ( }; const seen = new Set(); - const traces = [...ctx.turboSnap.tracedPaths].map((p) => { + const traces = [...(ctx.turboSnap?.tracedPaths || [])].map((p) => { const parts = p.split('\n'); let results = ''; diff --git a/node-src/ui/messages/warnings/bailFile.ts b/node-src/ui/messages/warnings/bailFile.ts index 708dbabf1..1b2840e28 100644 --- a/node-src/ui/messages/warnings/bailFile.ts +++ b/node-src/ui/messages/warnings/bailFile.ts @@ -8,16 +8,19 @@ import link from '../../components/link'; const docsUrl = 'https://www.chromatic.com/docs/turbosnap#how-it-works'; -export default ({ turboSnap }: { turboSnap: Pick }) => { - const { changedPackageFiles, changedStaticFiles, changedStorybookFiles } = turboSnap.bailReason; +// TODO: refactor this function +// eslint-disable-next-line complexity +export default ({ turboSnap }: { turboSnap: Context['turboSnap'] }) => { + const { changedPackageFiles, changedStaticFiles, changedStorybookFiles } = + turboSnap?.bailReason || {}; const changedFiles = changedPackageFiles || changedStorybookFiles || changedStaticFiles; // if all changed files are package.json, message this as a dependency change. - const allChangedFilesArePackageJson = changedFiles.every((changedFile) => + const allChangedFilesArePackageJson = changedFiles?.every((changedFile) => isPackageManifestFile(changedFile) ); - const [firstFile, ...files] = changedFiles; + const [firstFile, ...files] = changedFiles || []; let type = changedPackageFiles ? 'package file' : 'static file'; if (allChangedFilesArePackageJson) type = 'dependency'; diff --git a/node-src/ui/messages/warnings/deviatingOutputDirectory.ts b/node-src/ui/messages/warnings/deviatingOutputDirectory.ts index 99405d2bb..8d7feb7dd 100644 --- a/node-src/ui/messages/warnings/deviatingOutputDirectory.ts +++ b/node-src/ui/messages/warnings/deviatingOutputDirectory.ts @@ -4,7 +4,7 @@ import { dedent } from 'ts-dedent'; import { Context } from '../../../types'; import { warning } from '../../components/icons'; -const getHint = (buildScriptName: string, buildScript: string) => { +const getHint = (buildScriptName: string | undefined, buildScript: string) => { if (!buildScript) return ''; if (buildScript.includes('npm run ')) @@ -29,7 +29,8 @@ export default ( outputDirectory: string ) => { const { buildScriptName } = options; - const buildScript = packageJson.scripts && packageJson.scripts[buildScriptName]; + const buildScript = + packageJson.scripts && buildScriptName && packageJson.scripts[buildScriptName]; return dedent(chalk` ${warning} {bold Unexpected build directory} diff --git a/node-src/ui/tasks/gitInfo.ts b/node-src/ui/tasks/gitInfo.ts index 1e40806e8..bc00b9565 100644 --- a/node-src/ui/tasks/gitInfo.ts +++ b/node-src/ui/tasks/gitInfo.ts @@ -10,7 +10,7 @@ const infoMessage = ( const turboSnapStatus = turboSnap.bailReason ? '; TurboSnap disabled' : ''; const branchName = ownerName ? `${ownerName}:${branch}` : branch; let message = `Commit '${commit.slice(0, 7)}' on branch '${branchName}'`; - if (parentCommits.length > 0) { + if (parentCommits?.length) { message += `; found ${pluralize('parent build', parentCommits.length, true)}`; if (changedFiles) { message += ` and ${pluralize('changed file', changedFiles.length, true)}`; diff --git a/node-src/ui/tasks/prepareWorkspace.ts b/node-src/ui/tasks/prepareWorkspace.ts index bf16e35ba..569e6d277 100644 --- a/node-src/ui/tasks/prepareWorkspace.ts +++ b/node-src/ui/tasks/prepareWorkspace.ts @@ -20,7 +20,7 @@ export const lookupMergeBase = (ctx: Context) => ({ export const checkoutMergeBase = (ctx: Context) => ({ status: 'pending', title: 'Preparing your workspace', - output: `Checking out merge base commit '${ctx.mergeBase.slice(0, 7)}'`, + output: `Checking out merge base commit '${ctx.mergeBase?.slice(0, 7)}'`, }); export const installingDependencies = () => ({ @@ -32,5 +32,5 @@ export const installingDependencies = () => ({ export const success = (ctx: Context) => ({ status: 'success', title: `Prepared your workspace`, - output: `Checked out commit '${ctx.mergeBase.slice(0, 7)}' on '${ctx.options.patchBaseRef}'`, + output: `Checked out commit '${ctx.mergeBase?.slice(0, 7)}' on '${ctx.options.patchBaseRef}'`, }); diff --git a/node-src/ui/tasks/upload.ts b/node-src/ui/tasks/upload.ts index ee55fe64c..4753d3442 100644 --- a/node-src/ui/tasks/upload.ts +++ b/node-src/ui/tasks/upload.ts @@ -34,7 +34,7 @@ export const invalid = (ctx: Context, error?: Error) => { }; export const tracing = (ctx: Context) => { - const files = pluralize('file', ctx.git.changedFiles.length, true); + const files = pluralize('file', ctx.git.changedFiles?.length, true); return { status: 'pending', title: 'Retrieving story files affected by recent changes', @@ -44,17 +44,17 @@ export const tracing = (ctx: Context) => { export const bailed = (ctx: Context) => { const { changedPackageFiles, changedStorybookFiles, changedStaticFiles } = - ctx.turboSnap.bailReason; + ctx.turboSnap?.bailReason || {}; const changedFiles = changedPackageFiles || changedStorybookFiles || changedStaticFiles; // if all changed files are package.json, message this as a dependency change. - const allChangedFilesArePackageJson = changedFiles.every((changedFile) => + const allChangedFilesArePackageJson = changedFiles?.every((changedFile) => isPackageManifestFile(changedFile) ); const type = allChangedFilesArePackageJson ? 'dependency ' : ''; - const [firstFile, ...otherFiles] = changedFiles; + const [firstFile, ...otherFiles] = changedFiles || []; const siblings = pluralize('sibling', otherFiles.length, true); let output = `Found a ${type}change in ${firstFile}`; if (otherFiles.length === 1) output += ' or its sibling'; @@ -67,7 +67,7 @@ export const bailed = (ctx: Context) => { }; export const traced = (ctx: Context) => { - const files = pluralize('story file', ctx.onlyStoryFiles.length, true); + const files = pluralize('story file', ctx.onlyStoryFiles?.length, true); return { status: 'pending', title: 'Retrieved story files affected by recent changes', @@ -103,7 +103,7 @@ export const success = (ctx: Context) => { const files = pluralize('file', ctx.uploadedFiles, true); const bytes = filesize(ctx.uploadedBytes || 0); const skipped = - ctx.fileInfo.paths.length > ctx.uploadedFiles + ctx.fileInfo?.paths.length && ctx.uploadedFiles && ctx.fileInfo.paths.length > ctx.uploadedFiles ? `, skipped ${pluralize('file', ctx.fileInfo.paths.length - ctx.uploadedFiles, true)}` : ''; diff --git a/node-src/ui/tasks/verify.ts b/node-src/ui/tasks/verify.ts index c17eab19c..cef2840d4 100644 --- a/node-src/ui/tasks/verify.ts +++ b/node-src/ui/tasks/verify.ts @@ -32,18 +32,18 @@ export const runOnlyFiles = (ctx: Context) => ({ ? `Snapshots will be limited to story files matching ${ctx.options.onlyStoryFiles .map((v) => `'${v}'`) .join(', ')}` - : `Snapshots will be limited to ${ctx.onlyStoryFiles.length} story files affected by recent changes`, + : `Snapshots will be limited to ${ctx.onlyStoryFiles?.length} story files affected by recent changes`, }); export const runOnlyNames = (ctx: Context) => ({ status: 'pending', title: 'Starting partial build', output: `Snapshots will be limited to stories matching ${ctx.options.onlyStoryNames - .map((v) => `'${v}'`) + ?.map((v) => `'${v}'`) .join(', ')}`, }); -export const awaitingUpgrades = (_: Context, upgradeBuilds: { completedAt?: number }[]) => { +export const awaitingUpgrades = (_: Context, upgradeBuilds: { completedAt?: number | null }[]) => { const pending = upgradeBuilds.filter((upgrade) => !upgrade.completedAt).length; const upgrades = pluralize('upgrade build', upgradeBuilds.length, true); return { From c715f9f674528f126c1b8a8d37b61e39abbb6489 Mon Sep 17 00:00:00 2001 From: Cody Kaup <35509748+codykaup@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:40:18 -0500 Subject: [PATCH 3/4] Fallback to Storybook version 0.0.0 when we can't parse semver Co-authored-by: John Hobbs --- node-src/tasks/build.ts | 2 +- node-src/tasks/upload.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/node-src/tasks/build.ts b/node-src/tasks/build.ts index e9ab8c5fd..5849676e2 100644 --- a/node-src/tasks/build.ts +++ b/node-src/tasks/build.ts @@ -30,7 +30,7 @@ export const setSourceDirectory = async (ctx: Context) => { export const setBuildCommand = async (ctx: Context) => { const webpackStatsSupported = ctx.storybook && ctx.storybook.version - ? semver.gte(semver.coerce(ctx.storybook.version) || '', '6.2.0') + ? semver.gte(semver.coerce(ctx.storybook.version) || '0.0.0', '6.2.0') : true; if (ctx.git.changedFiles && !webpackStatsSupported) { diff --git a/node-src/tasks/upload.ts b/node-src/tasks/upload.ts index e99a36d4e..a7cb777bd 100644 --- a/node-src/tasks/upload.ts +++ b/node-src/tasks/upload.ts @@ -122,7 +122,8 @@ export const traceChangedFiles = async (ctx: Context, task: Task) => { if (!ctx.fileInfo?.statsPath) { // If we don't know the SB version, we should assume we don't support `--stats-json` const nonLegacyStatsSupported = - ctx.storybook?.version && semver.gte(semver.coerce(ctx.storybook.version) || '', '8.0.0'); + ctx.storybook?.version && + semver.gte(semver.coerce(ctx.storybook.version) || '0.0.0', '8.0.0'); ctx.turboSnap.bailReason = { missingStatsFile: true }; throw new Error(missingStatsFile({ legacy: !nonLegacyStatsSupported })); From 54311d9fa7dbccd7e45023e9ce8f24aaa2cf8f8b Mon Sep 17 00:00:00 2001 From: Cody Kaup Date: Wed, 2 Oct 2024 14:49:41 -0500 Subject: [PATCH 4/4] Make storybookUrl required --- node-src/tasks/verify.ts | 9 ++++++++- node-src/ui/messages/info/storybookPublished.stories.ts | 1 + node-src/ui/messages/info/storybookPublished.ts | 8 ++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/node-src/tasks/verify.ts b/node-src/tasks/verify.ts index c5ac9be7a..81d30952c 100644 --- a/node-src/tasks/verify.ts +++ b/node-src/tasks/verify.ts @@ -264,7 +264,14 @@ export const verifyBuild = async (ctx: Context, task: Task) => { } } - ctx.log.info(storybookPublished(ctx)); + if (ctx.build && ctx.storybookUrl) { + ctx.log.info( + storybookPublished({ + build: ctx.build, + storybookUrl: ctx.storybookUrl, + }) + ); + } transitionTo(success, true)(ctx, task); diff --git a/node-src/ui/messages/info/storybookPublished.stories.ts b/node-src/ui/messages/info/storybookPublished.stories.ts index 7380c34a2..4a5c99b48 100644 --- a/node-src/ui/messages/info/storybookPublished.stories.ts +++ b/node-src/ui/messages/info/storybookPublished.stories.ts @@ -6,6 +6,7 @@ export default { export const StorybookPublished = () => storybookPublished({ + build: {}, storybookUrl: 'https://5d67dc0374b2e300209c41e7-pfkaemtlit.chromatic.com/', } as any); diff --git a/node-src/ui/messages/info/storybookPublished.ts b/node-src/ui/messages/info/storybookPublished.ts index 3dd2424e9..5ecc3b65c 100644 --- a/node-src/ui/messages/info/storybookPublished.ts +++ b/node-src/ui/messages/info/storybookPublished.ts @@ -6,21 +6,21 @@ import { info, success } from '../../components/icons'; import link from '../../components/link'; import { stats } from '../../tasks/snapshot'; -export default ({ build, storybookUrl }: Pick) => { +export default ({ build, storybookUrl }: Required>) => { // `ctx.build` is initialized and overwritten in many ways, which means that // this can be any kind of build without component and stories information, // like PASSED builds, for example - if (build?.componentCount && build?.specCount) { + if (build.componentCount && build.specCount) { const { components, stories } = stats({ build }); return dedent(chalk` ${success} {bold Storybook published} We found ${components} with ${stories}. - ${info} View your Storybook at ${link(storybookUrl || '')} + ${info} View your Storybook at ${link(storybookUrl)} `); } return dedent(chalk` ${success} {bold Storybook published} - ${info} View your Storybook at ${link(storybookUrl || '')} + ${info} View your Storybook at ${link(storybookUrl)} `); };