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/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/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/getOptions.ts b/node-src/lib/getOptions.ts index 104f28df1..eb2cc9cec 100644 --- a/node-src/lib/getOptions.ts +++ b/node-src/lib/getOptions.ts @@ -125,7 +125,7 @@ export default function getOptions(ctx: InitialContext): Options { debug: flags.debug, diagnosticsFile: defaultIfSet(flags.diagnosticsFile, DEFAULT_DIAGNOSTICS_FILE) || - defaultIfSet(flags.diagnostics && '', DEFAULT_DIAGNOSTICS_FILE), // for backwards compatibility + defaultIfSet(flags.diagnostics?.toString(), DEFAULT_DIAGNOSTICS_FILE), // for backwards compatibility junitReport: defaultIfSet(flags.junitReport, DEFAULT_REPORT_FILE), zip: flags.zip, skipUpdateCheck: flags.skipUpdateCheck, 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/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/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/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..47ad2b58a 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(', ')}`); 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..92b9c318a 100644 --- a/node-src/tasks/verify.ts +++ b/node-src/tasks/verify.ts @@ -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/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..ae70ba4d6 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', @@ -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', 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 {