From e3e1b48031ca427b085d2d230c9d7cd606859c2c Mon Sep 17 00:00:00 2001 From: Cody Kaup Date: Wed, 11 Sep 2024 14:12:49 -0500 Subject: [PATCH 1/6] Add ESLint unicorn --- eslint.config.mjs | 64 +++++++++++---------- package.json | 1 + yarn.lock | 143 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 175 insertions(+), 33 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index ada6af808..1d6585ee2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -6,7 +6,7 @@ import prettier from 'eslint-plugin-prettier'; import security from 'eslint-plugin-security'; import simpleImportSort from 'eslint-plugin-simple-import-sort'; import sortClassMembers from 'eslint-plugin-sort-class-members'; -// import unicorn from 'eslint-plugin-unicorn'; +import unicorn from 'eslint-plugin-unicorn'; import globals from 'globals'; import tseslint from 'typescript-eslint'; @@ -106,37 +106,39 @@ export default [ }, }, // additional lints from unicorn - // unicorn.configs['flat/recommended'], - // { - // rules: { - // 'unicorn/filename-case': ['error', { case: 'camelCase' }], - // // Chromatic uses err as our catch convention. - // // This is baked into pino transforms as well. - // 'unicorn/prevent-abbreviations': [ - // 'error', - // { - // allowList: { - // err: true, - // }, - // }, - // ], - // 'unicorn/catch-error-name': [ - // 'error', - // { - // name: 'err', - // ignore: ['^err.*$'], - // }, - // ], - // 'unicorn/switch-case-braces': 'off', - // }, - // }, + unicorn.configs['flat/recommended'], + { + rules: { + 'unicorn/filename-case': ['error', { case: 'camelCase' }], + // Chromatic uses err as our catch convention. + // This is baked into pino transforms as well. + 'unicorn/prevent-abbreviations': [ + 'error', + { + allowList: { + err: true, + props: true, + ctx: true, + }, + }, + ], + 'unicorn/catch-error-name': [ + 'error', + { + name: 'err', + ignore: ['^err.*$'], + }, + ], + 'unicorn/switch-case-braces': 'off', + }, + }, // prefer TS to complain when we miss an arg vs. sending an intentional undefined - // { - // files: ['**/*.ts'], - // rules: { - // 'unicorn/no-useless-undefined': 'off', - // }, - // }, + { + files: ['**/*.ts'], + rules: { + 'unicorn/no-useless-undefined': 'off', + }, + }, // security related lints security.configs.recommended, { diff --git a/package.json b/package.json index 6487503e2..6851c5210 100644 --- a/package.json +++ b/package.json @@ -148,6 +148,7 @@ "eslint-plugin-security": "^3.0.0", "eslint-plugin-simple-import-sort": "^12.1.0", "eslint-plugin-sort-class-members": "^1.20.0", + "eslint-plugin-unicorn": "^53.0.0", "esm": "^3.2.25", "execa": "^7.2.0", "fake-tag": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index fca3b12ae..e415a6edc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2277,7 +2277,7 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^3.1.0": +"@eslint/eslintrc@npm:^3.0.2, @eslint/eslintrc@npm:^3.1.0": version: 3.1.0 resolution: "@eslint/eslintrc@npm:3.1.0" dependencies: @@ -6529,6 +6529,20 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.23.3": + version: 4.23.3 + resolution: "browserslist@npm:4.23.3" + dependencies: + caniuse-lite: "npm:^1.0.30001646" + electron-to-chromium: "npm:^1.5.4" + node-releases: "npm:^2.0.18" + update-browserslist-db: "npm:^1.1.0" + bin: + browserslist: cli.js + checksum: 10c0/3063bfdf812815346447f4796c8f04601bf5d62003374305fd323c2a463e42776475bcc5309264e39bcf9a8605851e53560695991a623be988138b3ff8c66642 + languageName: node + linkType: hard + "buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" @@ -6553,6 +6567,13 @@ __metadata: languageName: node linkType: hard +"builtin-modules@npm:^3.3.0": + version: 3.3.0 + resolution: "builtin-modules@npm:3.3.0" + checksum: 10c0/2cb3448b4f7306dc853632a4fcddc95e8d4e4b9868c139400027b71938fc6806d4ff44007deffb362ac85724bd40c2c6452fb6a0aa4531650eeddb98d8e5ee8a + languageName: node + linkType: hard + "bundle-require@npm:^4.0.0": version: 4.1.0 resolution: "bundle-require@npm:4.1.0" @@ -6713,6 +6734,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001646": + version: 1.0.30001660 + resolution: "caniuse-lite@npm:1.0.30001660" + checksum: 10c0/d28900b56c597176d515c3175ca75c454f2d30cb2c09a44d7bdb009bb0c4d8a2557905adb77642889bbe9feb85fbfe9d974c8b8e53521fb4b50ee16ab246104e + languageName: node + linkType: hard + "case-sensitive-paths-webpack-plugin@npm:^2.4.0": version: 2.4.0 resolution: "case-sensitive-paths-webpack-plugin@npm:2.4.0" @@ -6887,6 +6915,7 @@ __metadata: eslint-plugin-security: "npm:^3.0.0" eslint-plugin-simple-import-sort: "npm:^12.1.0" eslint-plugin-sort-class-members: "npm:^1.20.0" + eslint-plugin-unicorn: "npm:^53.0.0" esm: "npm:^3.2.25" execa: "npm:^7.2.0" fake-tag: "npm:^2.0.0" @@ -7026,6 +7055,15 @@ __metadata: languageName: node linkType: hard +"clean-regexp@npm:^1.0.0": + version: 1.0.0 + resolution: "clean-regexp@npm:1.0.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + checksum: 10c0/fd9c7446551b8fc536f95e8a286d431017cd4ba1ec2e53997ec9159385e9c317672f6dfc4d49fdb97449fdb53b0bacd0a8bab9343b8fdd2e46c7ddf6173d0db7 + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -7396,6 +7434,15 @@ __metadata: languageName: node linkType: hard +"core-js-compat@npm:^3.37.0": + version: 3.38.1 + resolution: "core-js-compat@npm:3.38.1" + dependencies: + browserslist: "npm:^4.23.3" + checksum: 10c0/d8bc8a35591fc5fbf3e376d793f298ec41eb452619c7ef9de4ea59b74be06e9fda799e0dcbf9ba59880dae87e3b41fb191d744ffc988315642a1272bb9442b31 + languageName: node + linkType: hard + "core-util-is@npm:~1.0.0": version: 1.0.3 resolution: "core-util-is@npm:1.0.3" @@ -8132,6 +8179,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.4": + version: 1.5.19 + resolution: "electron-to-chromium@npm:1.5.19" + checksum: 10c0/73e25a928e041d707565fb08371b6ba5f3d7d99c78adddfd553660f28809ca22f1608ff295da3c764903a8eae890cbdd701b1b64b3e1eaeb298cb8aecbf21937 + languageName: node + linkType: hard + "elegant-spinner@npm:^1.0.1": version: 1.0.1 resolution: "elegant-spinner@npm:1.0.1" @@ -8999,6 +9053,32 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-unicorn@npm:^53.0.0": + version: 53.0.0 + resolution: "eslint-plugin-unicorn@npm:53.0.0" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.24.5" + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@eslint/eslintrc": "npm:^3.0.2" + ci-info: "npm:^4.0.0" + clean-regexp: "npm:^1.0.0" + core-js-compat: "npm:^3.37.0" + esquery: "npm:^1.5.0" + indent-string: "npm:^4.0.0" + is-builtin-module: "npm:^3.2.1" + jsesc: "npm:^3.0.2" + pluralize: "npm:^8.0.0" + read-pkg-up: "npm:^7.0.1" + regexp-tree: "npm:^0.1.27" + regjsparser: "npm:^0.10.0" + semver: "npm:^7.6.1" + strip-indent: "npm:^3.0.0" + peerDependencies: + eslint: ">=8.56.0" + checksum: 10c0/da48048c21a68dd5410ed1f8abb1a80a7dabb5cf751e690dae435cb5359b3150f92682722bdb2e07258689ad1fdceaa9bd2dd08f211aaeb7ed19c62b98a32db5 + languageName: node + linkType: hard + "eslint-scope@npm:5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -10910,6 +10990,15 @@ __metadata: languageName: node linkType: hard +"is-builtin-module@npm:^3.2.1": + version: 3.2.1 + resolution: "is-builtin-module@npm:3.2.1" + dependencies: + builtin-modules: "npm:^3.3.0" + checksum: 10c0/5a66937a03f3b18803381518f0ef679752ac18cdb7dd53b5e23ee8df8d440558737bd8dcc04d2aae555909d2ecb4a81b5c0d334d119402584b61e6a003e31af1 + languageName: node + linkType: hard + "is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.1.5, is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" @@ -11574,6 +11663,15 @@ __metadata: languageName: node linkType: hard +"jsesc@npm:^3.0.2": + version: 3.0.2 + resolution: "jsesc@npm:3.0.2" + bin: + jsesc: bin/jsesc + checksum: 10c0/ef22148f9e793180b14d8a145ee6f9f60f301abf443288117b4b6c53d0ecd58354898dc506ccbb553a5f7827965cd38bc5fb726575aae93c5e8915e2de8290e1 + languageName: node + linkType: hard + "jsesc@npm:~0.5.0": version: 0.5.0 resolution: "jsesc@npm:0.5.0" @@ -13701,6 +13799,13 @@ __metadata: languageName: node linkType: hard +"node-releases@npm:^2.0.18": + version: 2.0.18 + resolution: "node-releases@npm:2.0.18" + checksum: 10c0/786ac9db9d7226339e1dc84bbb42007cb054a346bd9257e6aa154d294f01bc6a6cddb1348fa099f079be6580acbb470e3c048effd5f719325abd0179e566fd27 + languageName: node + linkType: hard + "node.extend@npm:^2.0.0": version: 2.0.3 resolution: "node.extend@npm:2.0.3" @@ -15431,7 +15536,7 @@ __metadata: languageName: node linkType: hard -"regexp-tree@npm:~0.1.1": +"regexp-tree@npm:^0.1.27, regexp-tree@npm:~0.1.1": version: 0.1.27 resolution: "regexp-tree@npm:0.1.27" bin: @@ -15475,6 +15580,17 @@ __metadata: languageName: node linkType: hard +"regjsparser@npm:^0.10.0": + version: 0.10.0 + resolution: "regjsparser@npm:0.10.0" + dependencies: + jsesc: "npm:~0.5.0" + bin: + regjsparser: bin/parser + checksum: 10c0/0f0508c142eddbceae55dab9715e714305c19e1e130db53168e8fa5f9f7ff9a4901f674cf6f71e04a0973b2f883882ba05808c80778b2d52b053d925050010f4 + languageName: node + linkType: hard + "regjsparser@npm:^0.9.1": version: 0.9.1 resolution: "regjsparser@npm:0.9.1" @@ -16300,6 +16416,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.6.1": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -18398,6 +18523,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.1.0": + version: 1.1.0 + resolution: "update-browserslist-db@npm:1.1.0" + dependencies: + escalade: "npm:^3.1.2" + picocolors: "npm:^1.0.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/a7452de47785842736fb71547651c5bbe5b4dc1e3722ccf48a704b7b34e4dcf633991eaa8e4a6a517ffb738b3252eede3773bef673ef9021baa26b056d63a5b9 + languageName: node + linkType: hard + "uri-js@npm:^4.2.2, uri-js@npm:^4.4.1": version: 4.4.1 resolution: "uri-js@npm:4.4.1" From 4e270a7912fa82e52348845ae2559c810bc8d1e8 Mon Sep 17 00:00:00 2001 From: Cody Kaup Date: Wed, 11 Sep 2024 14:38:46 -0500 Subject: [PATCH 2/6] Temporarily disable some unicorn rules --- eslint.config.mjs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 1d6585ee2..341d2bbbf 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -51,7 +51,7 @@ export default [ // lint comments comments.recommended, { - files: ['**/*.js', '**/*.ts'], + files: ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx'], rules: { '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }], }, @@ -109,16 +109,20 @@ export default [ unicorn.configs['flat/recommended'], { rules: { - 'unicorn/filename-case': ['error', { case: 'camelCase' }], + // TODO: Switch this to 'error' when we are ready to enforce this rule + 'unicorn/filename-case': ['off', { case: 'camelCase' }], // Chromatic uses err as our catch convention. // This is baked into pino transforms as well. 'unicorn/prevent-abbreviations': [ - 'error', + 'off', // TODO: Switch this to 'error' when we are ready to enforce this rule { allowList: { err: true, props: true, ctx: true, + str: true, + args: true, + docsUrl: true, }, }, ], @@ -130,6 +134,18 @@ export default [ }, ], 'unicorn/switch-case-braces': 'off', + 'unicorn/no-process-exit': 'off', + 'unicorn/prefer-node-protocol': 'off', // This will error our Webpack build + // TODO: remove the following lines when we are ready to enforce this rule + 'unicorn/no-anonymous-default-export': 'off', + 'unicorn/no-null': 'off', + 'unicorn/better-regex': 'off', + 'unicorn/no-array-reduce': 'off', + 'unicorn/no-array-callback-reference': 'off', + 'unicorn/prefer-string-raw': 'off', + 'unicorn/prefer-module': 'off', + 'unicorn/no-array-for-each': 'off', + 'unicorn/prefer-spread': 'off', }, }, // prefer TS to complain when we miss an arg vs. sending an intentional undefined From e1ad13ce95abf3f98cc9d33fd3b14ad796e67a03 Mon Sep 17 00:00:00 2001 From: Cody Kaup Date: Wed, 11 Sep 2024 15:53:20 -0500 Subject: [PATCH 3/6] Fix a bunch of ESLint unicorn errors --- bin-src/init.ts | 4 +- bin-src/trim-stats-file.ts | 8 ++-- isChromatic.js | 4 +- isChromatic.mjs | 4 +- .../__mocks__/storybookBaseDir/subdir/test.js | 2 + .../storybookBaseDir/subdir/test.jsx | 2 + .../storybookBaseDir/subdir/test.tsx | 2 + node-src/__mocks__/storybookBaseDir/test.ts | 2 + node-src/git/generateGitRepository.ts | 23 +++++----- .../getChangedFilesWithReplacement.test.ts | 2 +- .../git/getChangedFilesWithReplacement.ts | 2 +- node-src/git/getCommitAndBranch.test.ts | 6 +-- node-src/git/getParentCommits.test.ts | 4 +- node-src/git/getParentCommits.ts | 6 ++- node-src/git/git.test.ts | 4 +- node-src/git/git.ts | 39 ++++++++-------- node-src/git/mocks/long-line.ts | 8 ++-- node-src/git/mocks/mock-index.ts | 9 ++-- node-src/index.test.ts | 6 +-- node-src/index.ts | 17 ++++--- node-src/io/HTTPClient.ts | 6 +-- node-src/lib/checkForUpdates.ts | 4 +- node-src/lib/checkPackageJson.ts | 4 +- node-src/lib/checkStorybookBaseDir.ts | 4 +- node-src/lib/compareBaseline.ts | 2 +- node-src/lib/e2e.ts | 6 +-- node-src/lib/findChangedDependencies.ts | 6 +-- node-src/lib/findChangedPackageFiles.ts | 7 ++- node-src/lib/getConfiguration.ts | 6 +-- node-src/lib/getDependencies.test.ts | 6 +-- node-src/lib/getDependencies.ts | 4 +- node-src/lib/getDependentStoryFiles.ts | 44 +++++++++--------- node-src/lib/getEnv.ts | 16 +++---- node-src/lib/getFileHashes.ts | 4 +- node-src/lib/getOptions.ts | 7 ++- node-src/lib/getStorybookInfo.ts | 4 +- node-src/lib/getStorybookMetadata.ts | 45 ++++++++++--------- node-src/lib/log.ts | 8 ++-- node-src/lib/logSerializers.test.ts | 4 +- node-src/lib/tasks.ts | 4 +- node-src/lib/upload.ts | 12 ++--- node-src/lib/uploadFiles.ts | 2 +- node-src/lib/uploadMetadataFiles.ts | 6 +-- node-src/lib/utils.ts | 5 ++- node-src/lib/waitForSentinel.ts | 4 +- node-src/lib/writeChromaticDiagnostics.ts | 8 ++-- node-src/tasks/build.ts | 25 +++++------ node-src/tasks/gitInfo.test.ts | 2 +- node-src/tasks/gitInfo.ts | 37 +++++++-------- node-src/tasks/initialize.ts | 6 +-- node-src/tasks/prepareWorkspace.test.ts | 2 +- node-src/tasks/report.ts | 4 +- node-src/tasks/upload.test.ts | 6 ++- node-src/tasks/upload.ts | 41 ++++++++--------- node-src/tasks/verify.ts | 2 +- node-src/types.ts | 4 +- node-src/ui/messages/errors/fatalError.ts | 6 +-- .../errors/invalidConfigurationFile.ts | 2 +- .../errors/maxFileSizeExceeded.stories.ts | 2 +- node-src/ui/messages/errors/runtimeError.ts | 9 ++-- .../messages/errors/uploadFailed.stories.ts | 2 +- node-src/ui/messages/errors/uploadFailed.ts | 6 +-- node-src/ui/messages/info/replacedBuild.ts | 2 +- .../ui/messages/info/tracedAffectedFiles.ts | 4 +- .../ui/messages/warnings/deprecatedOption.ts | 2 +- node-src/ui/tasks/build.stories.ts | 2 +- node-src/ui/tasks/gitInfo.ts | 6 +-- node-src/ui/tasks/prepareWorkspace.ts | 4 +- node-src/ui/tasks/snapshot.stories.ts | 2 +- node-src/ui/tasks/upload.stories.ts | 10 ++--- test-stories/timing.stories-disabled.js | 4 +- 71 files changed, 294 insertions(+), 280 deletions(-) diff --git a/bin-src/init.ts b/bin-src/init.ts index 50635b8f9..d3a7a2e48 100644 --- a/bin-src/init.ts +++ b/bin-src/init.ts @@ -30,8 +30,8 @@ export const addChromaticScriptToPackageJson = async ({ packageJson, packagePath }, }; await writeFile(packagePath, json, { spaces: 2 }); - } catch (e) { - console.warn(e); + } catch (err) { + console.warn(err); } }; diff --git a/bin-src/trim-stats-file.ts b/bin-src/trim-stats-file.ts index 71365b920..fecffdb71 100644 --- a/bin-src/trim-stats-file.ts +++ b/bin-src/trim-stats-file.ts @@ -2,11 +2,11 @@ import { outputFile } from 'fs-extra'; import { readStatsFile } from '../node-src/tasks/read-stats-file'; -const dedupe = (arr: T[]) => Array.from(new Set(arr)); +const dedupe = (arr: T[]) => [...new Set(arr)]; const isUserCode = ({ name, moduleName = name }: { name?: string; moduleName?: string }) => moduleName && !moduleName.startsWith('(webpack)') && - !moduleName.match(/(node_modules|webpack\/runtime)\//); + !/(node_modules|webpack\/runtime)\//.test(moduleName); /** * Utility to trim down a `preview-stats.json` file to the bare minimum, so that it can be used to @@ -41,8 +41,8 @@ export async function main([statsFile = './storybook-static/preview-stats.json'] await outputFile( targetFile, JSON.stringify({ modules: trimmedModules }, null, 2) - .replace(/{\n {10}/g, '{ ') - .replace(/\n {8}}/g, ' }') + .replaceAll(/{\n {10}/g, '{ ') + .replaceAll(/\n {8}}/g, ' }') ); console.log(`Wrote ${targetFile}`); diff --git a/isChromatic.js b/isChromatic.js index 6c9ad6daa..c2567af7b 100644 --- a/isChromatic.js +++ b/isChromatic.js @@ -4,7 +4,7 @@ module.exports = function isChromatic(windowArg) { const windowToCheck = windowArg || (typeof window !== 'undefined' && window); return !!( windowToCheck && - (windowToCheck.navigator.userAgent.match(/Chromatic/) || - windowToCheck.location.href.match(/chromatic=true/)) + (/Chromatic/.test(windowToCheck.navigator.userAgent) || + /chromatic=true/.test(windowToCheck.location.href)) ); }; diff --git a/isChromatic.mjs b/isChromatic.mjs index 545fd33e6..b6d0309d1 100644 --- a/isChromatic.mjs +++ b/isChromatic.mjs @@ -4,7 +4,7 @@ export default function isChromatic(windowArg) { const windowToCheck = windowArg || (typeof window !== 'undefined' && window); return !!( windowToCheck && - (windowToCheck.navigator.userAgent.match(/Chromatic/) || - windowToCheck.location.href.match(/chromatic=true/)) + (/Chromatic/.test(windowToCheck.navigator.userAgent) || + /chromatic=true/.test(windowToCheck.location.href)) ); } diff --git a/node-src/__mocks__/storybookBaseDir/subdir/test.js b/node-src/__mocks__/storybookBaseDir/subdir/test.js index e69de29bb..ddb4144af 100644 --- a/node-src/__mocks__/storybookBaseDir/subdir/test.js +++ b/node-src/__mocks__/storybookBaseDir/subdir/test.js @@ -0,0 +1,2 @@ +/* eslint-disable unicorn/no-empty-file */ +// This file is intentionally left blank diff --git a/node-src/__mocks__/storybookBaseDir/subdir/test.jsx b/node-src/__mocks__/storybookBaseDir/subdir/test.jsx index e69de29bb..ddb4144af 100644 --- a/node-src/__mocks__/storybookBaseDir/subdir/test.jsx +++ b/node-src/__mocks__/storybookBaseDir/subdir/test.jsx @@ -0,0 +1,2 @@ +/* eslint-disable unicorn/no-empty-file */ +// This file is intentionally left blank diff --git a/node-src/__mocks__/storybookBaseDir/subdir/test.tsx b/node-src/__mocks__/storybookBaseDir/subdir/test.tsx index e69de29bb..ddb4144af 100644 --- a/node-src/__mocks__/storybookBaseDir/subdir/test.tsx +++ b/node-src/__mocks__/storybookBaseDir/subdir/test.tsx @@ -0,0 +1,2 @@ +/* eslint-disable unicorn/no-empty-file */ +// This file is intentionally left blank diff --git a/node-src/__mocks__/storybookBaseDir/test.ts b/node-src/__mocks__/storybookBaseDir/test.ts index e69de29bb..ddb4144af 100644 --- a/node-src/__mocks__/storybookBaseDir/test.ts +++ b/node-src/__mocks__/storybookBaseDir/test.ts @@ -0,0 +1,2 @@ +/* eslint-disable unicorn/no-empty-file */ +// This file is intentionally left blank diff --git a/node-src/git/generateGitRepository.ts b/node-src/git/generateGitRepository.ts index 1560d1709..446d55ab3 100644 --- a/node-src/git/generateGitRepository.ts +++ b/node-src/git/generateGitRepository.ts @@ -18,12 +18,12 @@ async function generateCommit( [name, parentNames]: Line, commitMap: CommitMap ) { - const parentCommits = [] - .concat(parentNames) - .filter((parentName) => commitMap[parentName]) - .map((parentName) => commitMap[parentName].hash); + const parentCommits = [parentNames] + .flat() + .filter((parentName) => parentName && commitMap[parentName]) + .map((parentName) => parentName && commitMap[parentName].hash); - const randomBranchName = `temp-${Math.random().toString().substring(2)}`; + const randomBranchName = `temp-${Math.random().toString().slice(2)}`; const commitEnv = `GIT_COMMITTER_DATE='${nextDate()}'`; // No parent, check out nothing if (parentCommits.length === 0) { @@ -34,15 +34,14 @@ async function generateCommit( await runGit(`git checkout ${parentCommits[0]}`); // If more parents, create merge commit - if (parentCommits.length > 1) { - await runGit(`${commitEnv} git merge -m ${name} ${parentCommits.slice(1).join(' ')}`); - } else { - await runGit(`${commitEnv} git commit -m ${name} --allow-empty`); - } + await (parentCommits.length > 1 + ? runGit(`${commitEnv} git merge -m ${name} ${parentCommits.slice(1).join(' ')}`) + : runGit(`${commitEnv} git commit -m ${name} --allow-empty`)); } - const [hash, committedAt] = (await runGit(`git show --format=%H,%ct`)).stdout.trim().split(','); + const gitShowStr = await runGit(`git show --format=%H,%ct`); + const [hash, committedAt] = gitShowStr.stdout.trim().split(','); - return { hash, committedAt: parseInt(committedAt, 10) }; + return { hash, committedAt: Number.parseInt(committedAt, 10) }; } // Take a repository description in the following format: diff --git a/node-src/git/getChangedFilesWithReplacement.test.ts b/node-src/git/getChangedFilesWithReplacement.test.ts index 3cb853ecf..266243fc9 100644 --- a/node-src/git/getChangedFilesWithReplacement.test.ts +++ b/node-src/git/getChangedFilesWithReplacement.test.ts @@ -5,7 +5,7 @@ import { getChangedFilesWithReplacement } from './getChangedFilesWithReplacement vi.mock('./git', () => ({ getChangedFiles: (hash) => { - if (hash.match(/exists/)) return ['changed', 'files']; + if (/exists/.test(hash)) return ['changed', 'files']; throw new Error(`fatal: bad object ${hash}`); }, commitExists: (hash) => hash.match(/exists/), diff --git a/node-src/git/getChangedFilesWithReplacement.ts b/node-src/git/getChangedFilesWithReplacement.ts index c9cce393d..bae2d3fd4 100644 --- a/node-src/git/getChangedFilesWithReplacement.ts +++ b/node-src/git/getChangedFilesWithReplacement.ts @@ -34,7 +34,7 @@ export async function getChangedFilesWithReplacement( `Got error fetching commit for #${build.number}(${build.commit}): ${err.message}` ); - if (err.message.match(/(bad object|uncommitted changes)/)) { + if (/(bad object|uncommitted changes)/.test(err.message)) { const replacementBuild = await findAncestorBuildWithCommit(context, build.number); if (replacementBuild) { diff --git a/node-src/git/getCommitAndBranch.test.ts b/node-src/git/getCommitAndBranch.test.ts index 4384f79eb..4ab2f43dd 100644 --- a/node-src/git/getCommitAndBranch.test.ts +++ b/node-src/git/getCommitAndBranch.test.ts @@ -24,7 +24,7 @@ beforeEach(() => { getBranch.mockResolvedValue('main'); getCommit.mockResolvedValue({ commit: '48e0c83fadbf504c191bc868040b7a969a4f1feb', - committedAt: 1640094096000, + committedAt: 1_640_094_096_000, committerName: 'GitHub', committerEmail: 'noreply@github.com', }); @@ -40,7 +40,7 @@ afterEach(() => { }); const commitInfo = { - committedAt: 1640131292, + committedAt: 1_640_131_292, committerName: 'Gert Hengeveld', committerEmail: 'gert@chromatic.com', }; @@ -51,7 +51,7 @@ describe('getCommitAndBranch', () => { expect(info).toMatchObject({ branch: 'main', commit: '48e0c83fadbf504c191bc868040b7a969a4f1feb', - committedAt: 1640094096000, + committedAt: 1_640_094_096_000, committerName: 'GitHub', committerEmail: 'noreply@github.com', slug: undefined, diff --git a/node-src/git/getParentCommits.test.ts b/node-src/git/getParentCommits.test.ts index bc0d133b6..3ae528a52 100644 --- a/node-src/git/getParentCommits.test.ts +++ b/node-src/git/getParentCommits.test.ts @@ -56,7 +56,7 @@ function expectCommitsToEqualNames(hashes, names, { commitMap }) { async function checkoutCommit(name, branch, { dirname, runGit, commitMap }) { process.chdir(dirname); - await runGit(`git checkout ${branch !== 'HEAD' ? `-B ${branch}` : ''} ${commitMap[name].hash}`); + await runGit(`git checkout ${branch === 'HEAD' ? '' : `-B ${branch}`} ${commitMap[name].hash}`); return commitMap[name].hash; } @@ -77,7 +77,7 @@ const repositories: Record = {}; beforeAll(async () => { await Promise.all( Object.keys(descriptions).map(async (key) => { - const dirname = (await tmp.dir({ unsafeCleanup: true, prefix: `chromatictest-` })).path; + const { path: dirname } = await tmp.dir({ unsafeCleanup: true, prefix: `chromatictest-` }); const runGit = makeRunGit(dirname); const commitMap = await generateGitRepository(runGit, descriptions[key]); repositories[key] = { dirname, runGit, commitMap }; diff --git a/node-src/git/getParentCommits.ts b/node-src/git/getParentCommits.ts index e33fbf2b3..66451f616 100644 --- a/node-src/git/getParentCommits.ts +++ b/node-src/git/getParentCommits.ts @@ -107,7 +107,8 @@ async function nextCommits( ${firstCommittedAtSeconds ? `--since ${firstCommittedAtSeconds}` : ''} \ -n ${limit + commitsWithoutBuilds.length} --not ${commitsForCLI(commitsWithBuilds)}`; log.debug(`running ${command}`); - const commits = (await execGitCommand(command)).split('\n').filter(Boolean); + const commitsString = await execGitCommand(command); + const commits = commitsString.split('\n').filter(Boolean); log.debug(`command output: ${commits}`); // Later on we want to know which commits we visited on the way to finding the ancestor commits @@ -190,7 +191,8 @@ async function maximallyDescendentCommits({ log }: Pick, commits // This just filters any commits that are ancestors of other commits const command = `git rev-list ${commitsForCLI(commits)} --not ${commitsForCLI(parentCommits)}`; log.debug(`running ${command}`); - const maxCommits = (await execGitCommand(command)).split('\n').filter(Boolean); + const maxCommitsString = await execGitCommand(command); + const maxCommits = maxCommitsString.split('\n').filter(Boolean); log.debug(`command output: ${maxCommits}`); return maxCommits; diff --git a/node-src/git/git.test.ts b/node-src/git/git.test.ts index 475d2e6ac..22a7f022d 100644 --- a/node-src/git/git.test.ts +++ b/node-src/git/git.test.ts @@ -28,7 +28,7 @@ describe('getCommit', () => { ); expect(await getCommit()).toEqual({ commit: '19b6c9c5b3d34d9fc55627fcaf8a85bd5d5e5b2a', - committedAt: 1696588814 * 1000, + committedAt: 1_696_588_814 * 1000, committerEmail: 'info@ghengeveld.nl', committerName: 'Gert Hengeveld', }); @@ -48,7 +48,7 @@ gpg: Can't check signature: No public key ); expect(await getCommit()).toEqual({ commit: '19b6c9c5b3d34d9fc55627fcaf8a85bd5d5e5b2a', - committedAt: 1696588814 * 1000, + committedAt: 1_696_588_814 * 1000, committerEmail: 'info@ghengeveld.nl', committerName: 'Gert Hengeveld', }); diff --git a/node-src/git/git.ts b/node-src/git/git.ts index 08d84f4ad..c2627fc71 100644 --- a/node-src/git/git.ts +++ b/node-src/git/git.ts @@ -15,7 +15,7 @@ export async function execGitCommand(command: string) { try { const { all } = await execaCommand(command, { env: { LANG: 'C', LC_ALL: 'C' }, // make sure we're speaking English - timeout: 20000, // 20 seconds + timeout: 20_000, // 20 seconds all: true, // interleave stdout and stderr shell: true, // we'll deal with escaping ourselves (for now) }); @@ -84,13 +84,13 @@ export async function getBranch() { // Yields an empty string when in detached HEAD state const branch = await execGitCommand('git branch --show-current'); return branch || 'HEAD'; - } catch (_err) { + } catch { try { // Git v1.8 and above // Throws when in detached HEAD state const ref = await execGitCommand('git symbolic-ref HEAD'); return ref.replace(/^refs\/heads\//, ''); // strip the "refs/heads/" prefix - } catch (_ex) { + } catch { // Git v1.7 and above // Yields 'HEAD' when in detached HEAD state const ref = await execGitCommand('git rev-parse --abbrev-ref HEAD'); @@ -108,14 +108,13 @@ export async function getUncommittedHash() { const listUntrackedFiles = 'git ls-files --others --exclude-standard'; const listUncommittedFiles = [listStagedFiles, listUnstagedFiles, listUntrackedFiles].join(';'); - const uncommittedHash = ( - await execGitCommand( - // Pass the combined list of filenames to hash-object to retrieve a list of hashes. Then pass - // the list of hashes to hash-object again to retrieve a single hash of all hashes. We use - // stdin to avoid the limit on command line arguments. - `(${listUncommittedFiles}) | git hash-object --stdin-paths | git hash-object --stdin` - ) - ).trim(); + let uncommittedHash = await execGitCommand( + // Pass the combined list of filenames to hash-object to retrieve a list of hashes. Then pass + // the list of hashes to hash-object again to retrieve a single hash of all hashes. We use + // stdin to avoid the limit on command line arguments. + `(${listUncommittedFiles}) | git hash-object --stdin-paths | git hash-object --stdin` + ); + uncommittedHash = uncommittedHash.trim(); // In case there are no uncommited changes (empty list), we always get this same hash. const noChangesHash = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'; @@ -135,7 +134,7 @@ export async function commitExists(commit: string) { try { await execGitCommand(`git cat-file -e "${commit}^{commit}"`); return true; - } catch (_err) { + } catch { return false; } } @@ -153,8 +152,8 @@ export async function getChangedFiles(baseCommit: string, headCommit = '') { export async function isUpToDate({ log }: Pick) { try { await execGitCommand(`git remote update`); - } catch (e) { - log.warn(e); + } catch (err) { + log.warn(err); return true; } @@ -162,8 +161,8 @@ export async function isUpToDate({ log }: Pick) { try { localCommit = await execGitCommand('git rev-parse HEAD'); if (!localCommit) throw new Error('Failed to retrieve last local commit hash'); - } catch (e) { - log.warn(e); + } catch (err) { + log.warn(err); return true; } @@ -171,8 +170,8 @@ export async function isUpToDate({ log }: Pick) { try { remoteCommit = await execGitCommand('git rev-parse "@{upstream}"'); if (!remoteCommit) throw new Error('Failed to retrieve last remote commit hash'); - } catch (e) { - log.warn(e); + } catch (err) { + log.warn(err); return true; } @@ -245,7 +244,7 @@ export async function findMergeBase(headRef: string, baseRef: string) { return name.replace(/~[0-9]+$/, ''); // Drop the potential suffix }) ); - const baseRefIndex = branchNames.findIndex((branch) => branch === baseRef); + const baseRefIndex = branchNames.indexOf(baseRef); return mergeBases[baseRefIndex] || mergeBases[0]; } @@ -260,7 +259,7 @@ export async function checkoutFile({ log }: Pick, ref: string, f if (!fileCache[pathspec]) { fileCache[pathspec] = limitConcurrency(async () => { const { path: targetFileName } = await tmpFile({ - postfix: `-${fileName.replace(/\//g, '--')}`, + postfix: `-${fileName.replaceAll('/', '--')}`, }); log.debug(`Checking out file ${pathspec} at ${targetFileName}`); await execGitCommand(`git show ${pathspec} > ${targetFileName}`); diff --git a/node-src/git/mocks/long-line.ts b/node-src/git/mocks/long-line.ts index d78ddc94f..0fa358654 100644 --- a/node-src/git/mocks/long-line.ts +++ b/node-src/git/mocks/long-line.ts @@ -8,14 +8,14 @@ // | // Z -const ACode = 'A'.charCodeAt(0); +const ACode = 'A'.codePointAt(0); // [commit, parent(s)] export default [ ['A', false], ['z', false], - ...[...Array(25)].map((_, index) => [ - String.fromCharCode(index + 1 + ACode), // e.g. 'B' - String.fromCharCode(index + ACode), // e.g. 'A' + ...Array.from({ length: 25 }).map((_, index) => [ + String.fromCodePoint(index + 1 + ACode), // e.g. 'B' + String.fromCodePoint(index + ACode), // e.g. 'A' ]), ]; diff --git a/node-src/git/mocks/mock-index.ts b/node-src/git/mocks/mock-index.ts index 901ae01c3..f34b4d799 100644 --- a/node-src/git/mocks/mock-index.ts +++ b/node-src/git/mocks/mock-index.ts @@ -27,10 +27,7 @@ interface MergeInfo { } function lastBuildOnBranch(builds: Build[], findingBranch: string) { - return builds - .slice() - .reverse() - .find((b) => b.branch === findingBranch); + return [...builds].reverse().find((b) => b.branch === findingBranch); } const mocks = { @@ -50,7 +47,7 @@ const mocks = { }, HasBuildsWithCommitsQuery: (builds: Build[], _prs: PR[], { commits }: { commits: string[] }) => ({ app: { - hasBuildsWithCommits: commits.filter((commit) => !!builds.find((b) => b.commit === commit)), + hasBuildsWithCommits: commits.filter((commit) => !!builds.some((b) => b.commit === commit)), }, }), MergeCommitsQuery: ( @@ -83,7 +80,7 @@ export default function createMockIndex({ commitMap }, buildDescriptions, prDesc if (commitMap[name]) { const commitInfo = commitMap[name]; hash = commitInfo.hash; - committedAt = parseInt(commitInfo.committedAt, 10) * 1000; + committedAt = Number.parseInt(commitInfo.committedAt, 10) * 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 dfecdce9e..bfd5e2db3 100644 --- a/node-src/index.test.ts +++ b/node-src/index.test.ts @@ -77,7 +77,7 @@ vi.mock('node-fetch', () => ({ const data = JSON.parse(body); query = data.query; variables = data.variables; - } catch (_err) { + } catch { // Do nothing } @@ -174,7 +174,7 @@ vi.mock('node-fetch', () => ({ if (query?.match('SnapshotBuildQuery')) { return { data: { - app: { build: { status: 'PENDING', changeCount: 1, completedAt: 12345 } }, + app: { build: { status: 'PENDING', changeCount: 1, completedAt: 12_345 } }, }, }; } @@ -198,7 +198,7 @@ vi.mock('node-fetch', () => ({ status: 'PASSED', commit: 'baseline', committedAt: 1234, - completedAt: 12345, + completedAt: 12_345, changeCount: 1, }, ], diff --git a/node-src/index.ts b/node-src/index.ts index 802da9291..77256efee 100644 --- a/node-src/index.ts +++ b/node-src/index.ts @@ -144,9 +144,9 @@ export async function runAll(ctx: InitialContext) { const onError = (e: Error | Error[]) => { ctx.log.info(''); - ctx.log.error(fatalError(ctx, [].concat(e))); + ctx.log.error(fatalError(ctx, [e].flat())); ctx.extraOptions?.experimental_onTaskError?.(ctx, { - formattedError: fatalError(ctx, [].concat(e)), + formattedError: fatalError(ctx, [e].flat()), originalError: e, }); setExitCode(ctx, exitCodes.INVALID_OPTIONS, true); @@ -169,8 +169,8 @@ export async function runAll(ctx: InitialContext) { ctx.log.setLogFile(options.logFile); setExitCode(ctx, exitCodes.OK); - } catch (e) { - return onError(e); + } catch (err) { + return onError(err); } if (!isContext(ctx)) { @@ -235,7 +235,10 @@ async function runBuild(ctx: Context) { } finally { // Handle potential runtime errors from JSDOM const { runtimeErrors, runtimeWarnings } = ctx; - if ((runtimeErrors && runtimeErrors.length) || (runtimeWarnings && runtimeWarnings.length)) { + if ( + (runtimeErrors && runtimeErrors.length > 0) || + (runtimeWarnings && runtimeWarnings.length > 0) + ) { ctx.log.info(''); ctx.log.error(runtimeError(ctx)); } @@ -243,7 +246,7 @@ async function runBuild(ctx: Context) { ctx.log.flush(); } } catch (error) { - const errors = [].concat(error); // GraphQLClient might throw an array of errors + const errors = [error].flat(); // GraphQLClient might throw an array of errors const formattedError = fatalError(ctx, errors); ctx.options.experimental_onTaskError?.(ctx, { @@ -289,7 +292,7 @@ export async function getGitInfo(): Promise { const repositoryRootDir = await getRepositoryRoot(); const [ownerName, repoName, ...rest] = slug ? slug.split('/') : []; - const isValidSlug = !!ownerName && !!repoName && !rest.length; + const isValidSlug = !!ownerName && !!repoName && rest.length === 0; const uncommittedHash = await getUncommittedHash(); return { diff --git a/node-src/io/HTTPClient.ts b/node-src/io/HTTPClient.ts index f929ca1bf..9484318bf 100644 --- a/node-src/io/HTTPClient.ts +++ b/node-src/io/HTTPClient.ts @@ -60,14 +60,14 @@ export default class HTTPClient { async fetch(url: string, options: RequestInit = {}, opts: HTTPClientFetchOptions = {}) { let agent = options.agent || getProxyAgent({ env: this.env, log: this.log }, url, opts.proxy); - if (this.env.CHROMATIC_DNS_SERVERS.length) { + if (this.env.CHROMATIC_DNS_SERVERS.length > 0) { this.log.debug(`Using custom DNS servers: ${this.env.CHROMATIC_DNS_SERVERS.join(', ')}`); dns.setServers(this.env.CHROMATIC_DNS_SERVERS); agent = getDNSResolveAgent(); } // The user can override retries and set it to 0 - const retries = typeof opts.retries !== 'undefined' ? opts.retries : this.retries; + const retries = opts.retries === undefined ? this.retries : opts.retries; const onRetry = (err, n) => { this.log.debug({ url, err }, `Fetch failed; retrying ${n}/${retries}`); // node-fetch includes ENOTFOUND in the message, but undici (native fetch in ts-node) doesn't. @@ -75,7 +75,7 @@ export default class HTTPClient { if (!agent) { this.log.warn('Fetch failed due to DNS lookup; switching to custom DNS resolver'); agent = getDNSResolveAgent(); - } else if (this.env.CHROMATIC_DNS_FAILOVER_SERVERS.length) { + } else if (this.env.CHROMATIC_DNS_FAILOVER_SERVERS.length > 0) { this.log.warn('Fetch failed due to DNS lookup; switching to failover DNS servers'); dns.setServers(this.env.CHROMATIC_DNS_FAILOVER_SERVERS); } diff --git a/node-src/lib/checkForUpdates.ts b/node-src/lib/checkForUpdates.ts index 65f8b37d2..6d820af59 100644 --- a/node-src/lib/checkForUpdates.ts +++ b/node-src/lib/checkForUpdates.ts @@ -37,9 +37,9 @@ export default async function checkForUpdates(ctx: Context) { return; } latestVersion = distTags.latest; - } catch (e) { + } catch (err) { ctx.log.warn(`Could not retrieve package info from registry; skipping update check`); - ctx.log.warn(e); + ctx.log.warn(err); return; } diff --git a/node-src/lib/checkPackageJson.ts b/node-src/lib/checkPackageJson.ts index d7149f04d..e2a95e09d 100644 --- a/node-src/lib/checkPackageJson.ts +++ b/node-src/lib/checkPackageJson.ts @@ -38,7 +38,7 @@ export default async function checkPackageJson({ log.info(''); log.info(notAddedScript(scriptName, scriptCommand)); } - } catch (e) { - log.warn(e); + } catch (err) { + log.warn(err); } } diff --git a/node-src/lib/checkStorybookBaseDir.ts b/node-src/lib/checkStorybookBaseDir.ts index 5e9c19206..64cd314a5 100644 --- a/node-src/lib/checkStorybookBaseDir.ts +++ b/node-src/lib/checkStorybookBaseDir.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import pLimit from 'p-limit'; -import * as path from 'path'; +import path from 'path'; import { getRepositoryRoot } from '../git/git'; import { Context, Stats } from '../types'; @@ -40,7 +40,7 @@ export async function checkStorybookBaseDir(ctx: Context, stats: Stats) { }); }) ); - } catch (_err) { + } catch { ctx.log.debug(`Invalid storybookBaseDir: ${storybookBaseDir}`); setExitCode(ctx, exitCodes.INVALID_OPTIONS, true); throw new Error(invalidStorybookBaseDir()); diff --git a/node-src/lib/compareBaseline.ts b/node-src/lib/compareBaseline.ts index 60d679352..ab4f658c9 100644 --- a/node-src/lib/compareBaseline.ts +++ b/node-src/lib/compareBaseline.ts @@ -3,7 +3,7 @@ import { getDependencies } from './getDependencies'; // Retrieve a set of values which is in either set, but not both. const xor = (left: Set, right: Set) => - Array.from(right.values()).reduce((acc, value) => { + [...right.values()].reduce((acc, value) => { if (acc.has(value)) acc.delete(value); else acc.add(value); return acc; diff --git a/node-src/lib/e2e.ts b/node-src/lib/e2e.ts index 9227324e3..2c4cce53f 100644 --- a/node-src/lib/e2e.ts +++ b/node-src/lib/e2e.ts @@ -7,6 +7,9 @@ import { exitCodes, setExitCode } from './setExitCode'; export const buildBinName = 'build-archive-storybook'; +const quote = (arg: string) => + !arg.startsWith('--') && arg.includes(' ') ? JSON.stringify(arg) : arg; + // ni doesn't currently have a "exec" command (equivalent to `npm exec`). // It has a "download & exec" command (equivalent to `npx`). // We should probably PR this up to ni @@ -20,9 +23,6 @@ const parseNexec = ((agent, args) => { bun: 'bun run {0}', }; - const quote = (arg: string) => - !arg.startsWith('--') && arg.includes(' ') ? JSON.stringify(arg) : arg; - const command = map[agent]; return command.replace('{0}', args.map(quote).join(' ')).trim(); }) as Runner; diff --git a/node-src/lib/findChangedDependencies.ts b/node-src/lib/findChangedDependencies.ts index 605f2c0c1..b8ecdbaf0 100644 --- a/node-src/lib/findChangedDependencies.ts +++ b/node-src/lib/findChangedDependencies.ts @@ -16,7 +16,7 @@ export const findChangedDependencies = async (ctx: Context) => { const { packageMetadataChanges } = ctx.git; const { untraced = [] } = ctx.options; - if (!packageMetadataChanges.length) { + if (packageMetadataChanges.length === 0) { ctx.log.debug('No package metadata changed found'); return []; } @@ -56,7 +56,7 @@ export const findChangedDependencies = async (ctx: Context) => { if (rootManifestPath && rootLockfilePath) { metadataPathPairs.unshift([rootManifestPath, rootLockfilePath]); - } else if (!metadataPathPairs.length) { + } else if (metadataPathPairs.length === 0) { throw new Error(`Could not find any pairs of ${PACKAGE_JSON} + ${PACKAGE_LOCK} / ${YARN_LOCK}`); } @@ -116,5 +116,5 @@ export const findChangedDependencies = async (ctx: Context) => { }) ); - return Array.from(changedDependencyNames); + return [...changedDependencyNames]; }; diff --git a/node-src/lib/findChangedPackageFiles.ts b/node-src/lib/findChangedPackageFiles.ts index 3bca2e689..1fc481b53 100644 --- a/node-src/lib/findChangedPackageFiles.ts +++ b/node-src/lib/findChangedPackageFiles.ts @@ -20,8 +20,7 @@ const isEqual = (left: unknown = {}, right: unknown = {}) => { } // depends on always having consistent ordering of keys - for (let i = 0; i < entriesA.length; i++) { - const [keyA, valueA] = entriesA[i]; + for (const [i, [keyA, valueA]] of entriesA.entries()) { const [keyB, valueB] = entriesB[i]; // values might be objects, so recursively compare @@ -69,7 +68,7 @@ const getManifestFilesWithChangedDependencies = async (manifestFiles: string[], const base = await readGitFile(fileName, commit); const head = await readGitFile(fileName); return arePackageDependenciesEqual(JSON.parse(base), JSON.parse(head)) ? [] : [fileName]; - } catch (_err) { + } catch { // Consider the dependencies changed if we failed to compare files. return [fileName]; } @@ -90,5 +89,5 @@ export const findChangedPackageFiles = async ( }) ); // Remove duplicate entries (in case multiple ancestor builds changed the same package.json) - return Array.from(new Set(changedManifestFiles.flat())); + return [...new Set(changedManifestFiles.flat())]; }; diff --git a/node-src/lib/getConfiguration.ts b/node-src/lib/getConfiguration.ts index a5df308bc..73e74cf55 100644 --- a/node-src/lib/getConfiguration.ts +++ b/node-src/lib/getConfiguration.ts @@ -57,7 +57,7 @@ export async function getConfiguration( return { configFile: usedConfigFile, ...configuration }; } catch (err) { // Config file does not exist - if (err.message.match(/ENOENT/)) { + if (/ENOENT/.test(err.message)) { // The user passed no configFile option so it's OK for the file not to exist if (!configFile) { return {}; @@ -67,10 +67,10 @@ export async function getConfiguration( } } if (err instanceof SyntaxError) { - throw new Error(unparseableConfigurationFile(usedConfigFile, err)); + throw new TypeError(unparseableConfigurationFile(usedConfigFile, err)); } if (err instanceof ZodError) { - throw new Error(invalidConfigurationFile(usedConfigFile, err)); + throw new TypeError(invalidConfigurationFile(usedConfigFile, err)); } throw err; } diff --git a/node-src/lib/getDependencies.test.ts b/node-src/lib/getDependencies.test.ts index 77a18851c..c411fbf1c 100644 --- a/node-src/lib/getDependencies.test.ts +++ b/node-src/lib/getDependencies.test.ts @@ -19,7 +19,7 @@ describe('getDependencies', () => { const [dep] = dependencies; expect(dep).toMatch(/^[\w@/-]+@@[\d.]+$/); - const dependencyNames = Array.from(dependencies).map((dependency) => dependency.split('@@')[0]); + const dependencyNames = [...dependencies].map((dependency) => dependency.split('@@')[0]); expect(dependencyNames).toEqual( expect.arrayContaining([ ...Object.keys(packageJson.dependencies), @@ -35,7 +35,7 @@ describe('getDependencies', () => { lockfilePath: await checkoutFile(ctx, 'HEAD', 'yarn.lock'), }); - const dependencyNames = Array.from(dependencies).map((dependency) => dependency.split('@@')[0]); + const dependencyNames = [...dependencies].map((dependency) => dependency.split('@@')[0]); expect(dependencyNames).toEqual( expect.arrayContaining([ ...Object.keys(packageJson.dependencies), @@ -54,7 +54,7 @@ describe('getDependencies', () => { lockfilePath: await checkoutFile(ctx, commit, 'yarn.lock'), }); - const dependencyNames = Array.from(dependencies).map((dependency) => dependency.split('@@')[0]); + const dependencyNames = [...dependencies].map((dependency) => dependency.split('@@')[0]); expect(dependencyNames).toEqual( expect.arrayContaining([ // @see https://github.com/chromaui/chromatic-cli/blob/e61c2688597a6fda61a7057c866ebfabde955784/package.json#L75-L170 diff --git a/node-src/lib/getDependencies.ts b/node-src/lib/getDependencies.ts index 687d1cfd2..d4482f8eb 100644 --- a/node-src/lib/getDependencies.ts +++ b/node-src/lib/getDependencies.ts @@ -36,8 +36,8 @@ export const getDependencies = async ( strictOutOfSync ); return flattenDependencyTree(headTree.dependencies); - } catch (e) { + } catch (err) { ctx.log.debug({ rootPath, manifestPath, lockfilePath }, 'Failed to get dependencies'); - throw e; + throw err; } }; diff --git a/node-src/lib/getDependentStoryFiles.ts b/node-src/lib/getDependentStoryFiles.ts index 903f6cb50..65976c836 100644 --- a/node-src/lib/getDependentStoryFiles.ts +++ b/node-src/lib/getDependentStoryFiles.ts @@ -87,7 +87,7 @@ export async function getDependentStoryFiles( const newPath = normalizePath(posixPath, rootPath, baseDir); // Trim query params such as `?ngResource` which are sometimes present return URL_PARAM_REGEX.test(newPath) && !CSF_REGEX.test(newPath) - ? newPath.replace(URL_PARAM_REGEX, '') + ? newPath.replaceAll(URL_PARAM_REGEX, '') : newPath; }; @@ -99,20 +99,22 @@ export async function getDependentStoryFiles( // NOTE: this only works with `main:stories` -- if stories are imported from files in `.storybook/preview.js` // we'll need a different approach to figure out CSF files (maybe the user should pass a glob?). - const storiesEntryFiles = [ - // v6 store (SB <= 6.3) - `${storybookConfigDir}/generated-stories-entry.js`, - // v6 store (SB 6.4 or SB <= 6.3 with root as config dir) - `./generated-stories-entry.js`, - // v6 store with .cjs extension (SB 6.5) - `./generated-stories-entry.cjs`, - // v7 store (SB >= 6.4) - `./storybook-stories.js`, - // vite builder - `/virtual:/@storybook/builder-vite/vite-app.js`, - // rspack builder - `./node_modules/.cache/storybook/default/dev-server/storybook-stories.js`, - ].map(normalize); + const storiesEntryFiles = new Set( + [ + // v6 store (SB <= 6.3) + `${storybookConfigDir}/generated-stories-entry.js`, + // v6 store (SB 6.4 or SB <= 6.3 with root as config dir) + `./generated-stories-entry.js`, + // v6 store with .cjs extension (SB 6.5) + `./generated-stories-entry.cjs`, + // v7 store (SB >= 6.4) + `./storybook-stories.js`, + // vite builder + `/virtual:/@storybook/builder-vite/vite-app.js`, + // rspack builder + `./node_modules/.cache/storybook/default/dev-server/storybook-stories.js`, + ].map(normalize) + ); const modulesByName = new Map(); const nodeModules = new Map(); @@ -145,7 +147,7 @@ export async function getDependentStoryFiles( .filter((reasonName) => reasonName && reasonName !== normalizedName); reasonsById.set(mod.id, normalizedReasons); - if (reasonsById.get(mod.id).some((reason) => storiesEntryFiles.includes(reason))) { + if (reasonsById.get(mod.id).some((reason) => storiesEntryFiles.has(reason))) { csfGlobsByName.add(normalizedName); } }); @@ -155,7 +157,7 @@ export async function getDependentStoryFiles( // does not use configDir in the entry file path so there's no fix to recommend there. const storiesEntryRegExp = /^(.+\/)?generated-stories-entry\.js$/; const foundEntry = stats.modules.find( - (mod) => storiesEntryRegExp.test(mod.name) && !storiesEntryFiles.includes(normalize(mod.name)) + (mod) => storiesEntryRegExp.test(mod.name) && !storiesEntryFiles.has(normalize(mod.name)) ); const entryFile = foundEntry && normalize(foundEntry.name); ctx.log.error(noCSFGlobs({ statsPath, storybookDir, storybookBuildDir, entryFile, viewLayer })); @@ -164,7 +166,7 @@ export async function getDependentStoryFiles( const isCsfGlob = (name: NormalizedName) => csfGlobsByName.has(name); const isStorybookFile = (name: string) => - name && name.startsWith(`${storybookDir}/`) && !storiesEntryFiles.includes(name); + name && name.startsWith(`${storybookDir}/`) && !storiesEntryFiles.has(name); const isStaticFile = (name: string) => staticDirs.some((dir) => name && name.startsWith(`${dir}/`)); @@ -199,8 +201,8 @@ export async function getDependentStoryFiles( baseDir, storybookDir, staticDirs, - globs: Array.from(csfGlobsByName), - modules: Array.from(modulesByName.keys()), + globs: [...csfGlobsByName], + modules: [...modulesByName.keys()], tracedFiles, tracedPaths, affectedModuleIds, @@ -268,7 +270,7 @@ export async function getDependentStoryFiles( } const affectedModules = Object.fromEntries( // The id will be compared against the result of the stories' `.parameters.filename` values (stories retrieved from getStoriesJsonData()) - Array.from(affectedModuleIds).map((id) => [String(id), files(namesById.get(id))]) + [...affectedModuleIds].map((id) => [String(id), files(namesById.get(id))]) ); if (ctx.options.traceChanged) { diff --git a/node-src/lib/getEnv.ts b/node-src/lib/getEnv.ts index 59cd27074..c1ffa9dde 100644 --- a/node-src/lib/getEnv.ts +++ b/node-src/lib/getEnv.ts @@ -59,21 +59,21 @@ export default () => CHROMATIC_DNS_SERVERS: CHROMATIC_DNS_SERVERS.split(',') .map((ip) => ip.trim()) .filter(Boolean), - CHROMATIC_HASH_CONCURRENCY: parseInt(CHROMATIC_HASH_CONCURRENCY, 10), + CHROMATIC_HASH_CONCURRENCY: Number.parseInt(CHROMATIC_HASH_CONCURRENCY, 10), CHROMATIC_INDEX_URL, - CHROMATIC_OUTPUT_INTERVAL: parseInt(CHROMATIC_OUTPUT_INTERVAL, 10), - CHROMATIC_POLL_INTERVAL: parseInt(CHROMATIC_POLL_INTERVAL, 10), + CHROMATIC_OUTPUT_INTERVAL: Number.parseInt(CHROMATIC_OUTPUT_INTERVAL, 10), + CHROMATIC_POLL_INTERVAL: Number.parseInt(CHROMATIC_POLL_INTERVAL, 10), CHROMATIC_PROJECT_TOKEN, - CHROMATIC_RETRIES: parseInt(CHROMATIC_RETRIES, 10), + CHROMATIC_RETRIES: Number.parseInt(CHROMATIC_RETRIES, 10), CHROMATIC_STORYBOOK_VERSION, - CHROMATIC_TIMEOUT: parseInt(CHROMATIC_TIMEOUT, 10), - CHROMATIC_UPGRADE_TIMEOUT: parseInt(CHROMATIC_UPGRADE_TIMEOUT, 10), + CHROMATIC_TIMEOUT: Number.parseInt(CHROMATIC_TIMEOUT, 10), + CHROMATIC_UPGRADE_TIMEOUT: Number.parseInt(CHROMATIC_UPGRADE_TIMEOUT, 10), ENVIRONMENT_WHITELIST, HTTP_PROXY, HTTPS_PROXY, LOGGLY_CUSTOMER_TOKEN, - STORYBOOK_BUILD_TIMEOUT: parseInt(STORYBOOK_BUILD_TIMEOUT, 10), + STORYBOOK_BUILD_TIMEOUT: Number.parseInt(STORYBOOK_BUILD_TIMEOUT, 10), STORYBOOK_CLI_FLAGS_BY_VERSION, - STORYBOOK_VERIFY_TIMEOUT: parseInt(STORYBOOK_VERIFY_TIMEOUT, 10), + STORYBOOK_VERIFY_TIMEOUT: Number.parseInt(STORYBOOK_VERIFY_TIMEOUT, 10), STORYBOOK_NODE_ENV, }) as Env; diff --git a/node-src/lib/getFileHashes.ts b/node-src/lib/getFileHashes.ts index 7a67ea603..706e89cb8 100644 --- a/node-src/lib/getFileHashes.ts +++ b/node-src/lib/getFileHashes.ts @@ -1,6 +1,6 @@ import { close, open, read } from 'fs'; import pLimit from 'p-limit'; -import { join } from 'path'; +import path from 'path'; import xxHashWasm, { XXHash, XXHashAPI } from 'xxhash-wasm'; const hashFile = (buffer: Buffer, path: string, xxhash: XXHashAPI): Promise => { @@ -64,7 +64,7 @@ export const getFileHashes = async (files: string[], dir: string, concurrency: n const hashes = await Promise.all( buffers.map(([buffer, file]) => - limit(async () => [file, await hashFile(buffer, join(dir, file), xxhash)] as const) + limit(async () => [file, await hashFile(buffer, path.join(dir, file), xxhash)] as const) ) ); diff --git a/node-src/lib/getOptions.ts b/node-src/lib/getOptions.ts index 6bab5d79b..a11bc7611 100644 --- a/node-src/lib/getOptions.ts +++ b/node-src/lib/getOptions.ts @@ -16,8 +16,7 @@ import missingProjectToken from '../ui/messages/errors/missingProjectToken'; import deprecatedOption from '../ui/messages/warnings/deprecatedOption'; import { isE2EBuild } from './e2e'; -const takeLast = (input: string | string[]) => - Array.isArray(input) ? input[input.length - 1] : input; +const takeLast = (input: string | string[]) => (Array.isArray(input) ? input.at(-1) : input); const ensureArray = (input: string | string[]) => (Array.isArray(input) ? input : [input]); @@ -27,7 +26,7 @@ const defaultUnlessSet = (value: T, fallback: T) => ['', true, undefined].includes(value as any) ? fallback : value; const undefinedIfEmpty = (array: T[]) => { const filtered = array.filter(Boolean); - return filtered.length ? filtered : undefined; + return filtered.length > 0 ? filtered : undefined; }; const stripUndefined = (object: Partial): Partial => @@ -186,7 +185,7 @@ export default function getOptions({ throw new Error(missingProjectToken()); } - if (repositoryOwner && (!repositoryName || rest.length)) { + if (repositoryOwner && (!repositoryName || rest.length > 0)) { throw new Error(invalidRepositorySlug()); } diff --git a/node-src/lib/getStorybookInfo.ts b/node-src/lib/getStorybookInfo.ts index 016c4ebc5..161a58960 100644 --- a/node-src/lib/getStorybookInfo.ts +++ b/node-src/lib/getStorybookInfo.ts @@ -22,8 +22,8 @@ export default async function getStorybookInfo( } // Same for this await. return await getStorybookMetadata(ctx); - } catch (e) { - ctx.log.debug(e); + } catch (err) { + ctx.log.debug(err); return { viewLayer: null, version: null, addons: [], builder: null }; } } diff --git a/node-src/lib/getStorybookMetadata.ts b/node-src/lib/getStorybookMetadata.ts index 70eed77c3..954b33ef2 100644 --- a/node-src/lib/getStorybookMetadata.ts +++ b/node-src/lib/getStorybookMetadata.ts @@ -2,7 +2,7 @@ import { readConfig } from '@storybook/csf-tools'; import { readdir } from 'fs/promises'; import { readJson } from 'fs-extra'; import meow from 'meow'; -import path, { join } from 'path'; +import path from 'path'; import semver from 'semver'; import { parseArgsStringToArgv } from 'string-argv'; @@ -82,8 +82,10 @@ const findViewlayer = async ({ env, log, options, packageJson }) => { return Promise.race([ resolvePackageJson(pkg) .then((json) => ({ viewLayer, version: json.version })) - .catch(() => Promise.reject(new Error(packageDoesNotExist(pkg)))), - timeout(10000), + .catch(() => { + throw new Error(packageDoesNotExist(pkg)); + }), + timeout(10_000), ]); } @@ -100,8 +102,10 @@ const findViewlayer = async ({ env, log, options, packageJson }) => { const json = await resolvePackageJson(key); return { viewLayer: value, version: json.version }; }) - ).catch(() => Promise.reject(new Error(packageDoesNotExist(pkg)))), - timeout(10000), + ).catch(() => { + throw new Error(packageDoesNotExist(pkg)); + }), + timeout(10_000), ]); }; @@ -124,12 +128,7 @@ const findAddons = async (ctx, mainConfig, v7) => { }; return { addons: addons.map((addon) => { - let name: string; - if (typeof addon === 'string') { - name = addon.replace('/register', ''); - } else { - name = addon.name; - } + const name = typeof addon === 'string' ? addon.replace('/register', '') : addon.name; return { name: supportedAddons[name], @@ -178,8 +177,10 @@ export const findBuilder = async (mainConfig, v7) => { return Promise.race([ resolvePackageJson(sbV7BuilderName) .then((json) => ({ builder: { name: sbV7BuilderName, packageVersion: json.version } })) - .catch(() => Promise.reject(new Error(packageDoesNotExist(sbV7BuilderName)))), - timeout(10000), + .catch(() => { + throw new Error(packageDoesNotExist(sbV7BuilderName)); + }), + timeout(10_000), ]); } @@ -192,8 +193,10 @@ export const findBuilder = async (mainConfig, v7) => { return Promise.race([ resolvePackageJson(builders[name]) .then((json) => ({ builder: { name, packageVersion: json.version } })) - .catch(() => Promise.reject(new Error(packageDoesNotExist(builders[name])))), - timeout(10000), + .catch(() => { + throw new Error(packageDoesNotExist(builders[name])); + }), + timeout(10_000), ]); }; @@ -201,26 +204,26 @@ export const findStorybookConfigFile = async (ctx: Context, pattern: RegExp) => const configDir = ctx.options.storybookConfigDir ?? '.storybook'; const files = await readdir(configDir); const configFile = files.find((file) => pattern.test(file)); - return configFile && join(configDir, configFile); + return configFile && path.join(configDir, configFile); }; export const getStorybookMetadata = async (ctx: Context) => { const configDir = ctx.options.storybookConfigDir ?? '.storybook'; - const r = typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : require; + const r = typeof __non_webpack_require__ === 'undefined' ? require : __non_webpack_require__; let mainConfig; let v7 = false; try { mainConfig = await r(path.resolve(configDir, 'main')); ctx.log.debug({ configDir, mainConfig }); - } catch (storybookV6error) { - ctx.log.debug({ storybookV6error }); + } catch (err) { + ctx.log.debug({ storybookV6error: err }); try { mainConfig = await readConfig(await findStorybookConfigFile(ctx, /^main\.[jt]sx?$/)); ctx.log.debug({ configDir, mainConfig }); v7 = true; - } catch (storybookV7error) { - ctx.log.debug({ storybookV7error }); + } catch (err) { + ctx.log.debug({ storybookV7error: err }); mainConfig = null; } } diff --git a/node-src/lib/log.ts b/node-src/lib/log.ts index 9aed03dad..9e370a074 100644 --- a/node-src/lib/log.ts +++ b/node-src/lib/log.ts @@ -32,7 +32,7 @@ const withTime = (messages: string[], color = false) => { return [ time + ' ', ...messages.map((msg) => - typeof msg === 'string' ? msg.replace(/\n/g, `\n `) : msg + typeof msg === 'string' ? msg.replaceAll('\n', `\n `) : msg ), ]; }; @@ -86,8 +86,8 @@ export const createLogger = () => { let level = (LOG_LEVEL.toLowerCase() as keyof typeof LOG_LEVELS) || DEFAULT_LEVEL; if (DISABLE_LOGGING === 'true') level = 'silent'; - const args = process.argv.slice(2); - let interactive = !args.includes('--debug') && !args.includes('--no-interactive'); + const args = new Set(process.argv.slice(2)); + let interactive = !args.has('--debug') && !args.has('--no-interactive'); let enqueue = false; const queue = []; @@ -101,7 +101,7 @@ export const createLogger = () => { if (logFileOnly) return; const messages = interactive ? logInteractive(args) : withTime(logs, true); - if (!messages.length) return; + if (messages.length === 0) return; // Queue up the logs or print them right away if (enqueue) queue.push({ type, messages }); diff --git a/node-src/lib/logSerializers.test.ts b/node-src/lib/logSerializers.test.ts index 80e14d90d..e222fd59d 100644 --- a/node-src/lib/logSerializers.test.ts +++ b/node-src/lib/logSerializers.test.ts @@ -7,8 +7,8 @@ it('strips off envPairs', () => { let err; try { execSync('some hot garbage', { stdio: 'ignore' }); - } catch (e) { - err = e; + } catch (err_) { + err = err_; } expect((errorSerializer(err) as any).envPairs).toBeUndefined(); }); diff --git a/node-src/lib/tasks.ts b/node-src/lib/tasks.ts index 93cbf8bd2..d764d533c 100644 --- a/node-src/lib/tasks.ts +++ b/node-src/lib/tasks.ts @@ -21,7 +21,7 @@ export const createTask = ({ task: async (ctx: Context, task: Listr.ListrTaskWrapper) => { ctx.task = name; ctx.title = title; - ctx.startedAt = Number.isInteger(ctx.now) ? ctx.now : new Date().getTime(); + ctx.startedAt = Number.isInteger(ctx.now) ? ctx.now : Date.now(); ctx.options.experimental_onTaskStart?.({ ...ctx }); @@ -54,7 +54,7 @@ export const transitionTo = }; export const getDuration = (ctx: Context) => { - const now = Number.isInteger(ctx.now) ? ctx.now : new Date().getTime(); + const now = Number.isInteger(ctx.now) ? ctx.now : Date.now(); 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 2ee181850..d334c7306 100644 --- a/node-src/lib/upload.ts +++ b/node-src/lib/upload.ts @@ -120,7 +120,7 @@ export async function uploadBuild( } ); - if (uploadBuild.userErrors.length) { + if (uploadBuild.userErrors.length > 0) { uploadBuild.userErrors.forEach((e) => { if (e.__typename === 'MaxFileCountExceededError') { ctx.log.error(maxFileCountExceeded(e)); @@ -148,7 +148,7 @@ export async function uploadBuild( } } - if (!targets.length) { + if (targets.length === 0) { ctx.log.debug('No new files to upload, continuing'); return; } @@ -176,10 +176,10 @@ export async function uploadBuild( await uploadFiles(ctx, targets, (progress) => options.onProgress?.(progress, totalBytes)); ctx.uploadedBytes += totalBytes; ctx.uploadedFiles += targets.length; - } catch (e) { - const target = targets.find((target) => target.localPath === e.message); + } 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?.(e, target.localPath); + return options.onError?.(err, target.localPath); } } @@ -236,7 +236,7 @@ export async function uploadMetadata(ctx: Context, files: FileDesc[]) { await uploadFiles(ctx, targets); } - if (uploadMetadata.userErrors.length) { + if (uploadMetadata.userErrors.length > 0) { uploadMetadata.userErrors.forEach((e) => ctx.log.warn(e.message)); } } diff --git a/node-src/lib/uploadFiles.ts b/node-src/lib/uploadFiles.ts index abd45944c..cc6c4352b 100644 --- a/node-src/lib/uploadFiles.ts +++ b/node-src/lib/uploadFiles.ts @@ -45,7 +45,7 @@ export async function uploadFiles( { retries: 0 } // already retrying the whole operation ); ctx.log.debug(`Uploaded ${filePath} (${filesize(contentLength)})`); - } catch (_err) { + } catch { throw new Error(localPath); } }, diff --git a/node-src/lib/uploadMetadataFiles.ts b/node-src/lib/uploadMetadataFiles.ts index bbca940b3..b2fefb0c9 100644 --- a/node-src/lib/uploadMetadataFiles.ts +++ b/node-src/lib/uploadMetadataFiles.ts @@ -1,5 +1,5 @@ import { stat, writeFileSync } from 'fs'; -import { basename } from 'path'; +import path from 'path'; import { withFile } from 'tmp-promise'; import { main as trimStatsFile } from '../../bin-src/trim-stats-file'; @@ -30,7 +30,7 @@ export async function uploadMetadataFiles(ctx: Context) { const files = await Promise.all( metadataFiles.map(async (localPath) => { const contentLength = await fileSize(localPath); - const targetPath = `.chromatic/${basename(localPath)}`; + const targetPath = `.chromatic/${path.basename(localPath)}`; return contentLength && { contentLength, localPath, targetPath }; }) ).then((files) => @@ -39,7 +39,7 @@ export async function uploadMetadataFiles(ctx: Context) { .sort((a, b) => a.targetPath.localeCompare(b.targetPath, 'en', { numeric: true })) ); - if (!files.length) { + if (files.length === 0) { ctx.log.warn('No metadata files found, skipping metadata upload.'); return; } diff --git a/node-src/lib/utils.ts b/node-src/lib/utils.ts index 3f6515216..6bd215df4 100644 --- a/node-src/lib/utils.ts +++ b/node-src/lib/utils.ts @@ -21,7 +21,8 @@ export const throttle = (fn: (...args: any[]) => void, wait: number) => { }; }; -export const repeat = (n: number, char: string) => [...new Array(Math.round(n))].map(() => char); +export const repeat = (n: number, char: string) => + Array.from({ length: Math.round(n) }).map(() => char); export const progressBar = (percentage: number, size = 20) => { const track = repeat(size, ' '); const completed = repeat((percentage / 100) * size || 0, '='); @@ -39,7 +40,7 @@ export const rewriteErrorMessage = (err: Error, message: string) => { // DOMException doesn't allow setting the message, so this might fail err.message = message; return err; - } catch (_err) { + } catch { const error = new Error(message); error.stack = err.stack; // try to preserve the original stack return error; diff --git a/node-src/lib/waitForSentinel.ts b/node-src/lib/waitForSentinel.ts index 65d25a288..d13fbe14e 100644 --- a/node-src/lib/waitForSentinel.ts +++ b/node-src/lib/waitForSentinel.ts @@ -26,8 +26,8 @@ export async function waitForSentinel(ctx: Context, { name, url }: { name: strin return bail(new Error(`Sentinel file '${name}' not OK.`)); } ctx.log.debug(`Sentinel file '${name}' OK.`); - } catch (e) { - const { message, response = {} } = e; + } catch (err) { + const { message, response = {} } = err; if (response.status === 403) { return bail(new Error('Provided signature expired.')); } diff --git a/node-src/lib/writeChromaticDiagnostics.ts b/node-src/lib/writeChromaticDiagnostics.ts index 4a741a437..6dd1d2c8f 100644 --- a/node-src/lib/writeChromaticDiagnostics.ts +++ b/node-src/lib/writeChromaticDiagnostics.ts @@ -12,9 +12,11 @@ export function getDiagnostics(ctx: Context) { const data = redact(rest, 'projectToken', 'reportToken', 'userToken'); // Sort top-level fields alphabetically - return Object.keys(data) - .sort((a, b) => a.localeCompare(b)) - .reduce((acc, key) => ({ ...acc, [key]: data[key] }), {}); + return Object.fromEntries( + Object.keys(data) + .sort((a, b) => a.localeCompare(b)) + .map((key) => [key, data[key]]) + ); } // Extract important information from ctx, sort it and output into a json file diff --git a/node-src/tasks/build.ts b/node-src/tasks/build.ts index e2b061b2f..11ae6dc59 100644 --- a/node-src/tasks/build.ts +++ b/node-src/tasks/build.ts @@ -42,18 +42,13 @@ export const setBuildCommand = async (ctx: Context) => { ctx.git.changedFiles && webpackStatsSupported && `--webpack-stats-json=${ctx.sourceDir}`, ].filter(Boolean); - if (isE2EBuild(ctx.options)) { - ctx.buildCommand = await getE2EBuildCommand( - ctx, - ctx.options.playwright ? 'playwright' : 'cypress', - buildCommandOptions - ); - } else { - ctx.buildCommand = await getPackageManagerRunCommand([ - ctx.options.buildScriptName, - ...buildCommandOptions, - ]); - } + ctx.buildCommand = await (isE2EBuild(ctx.options) + ? getE2EBuildCommand( + ctx, + ctx.options.playwright ? 'playwright' : 'cypress', + buildCommandOptions + ) + : getPackageManagerRunCommand([ctx.options.buildScriptName, ...buildCommandOptions])); }; const timeoutAfter = (ms) => @@ -128,12 +123,12 @@ export const buildStorybook = async (ctx: Context) => { env: { CI: '1', NODE_ENV: ctx.env.STORYBOOK_NODE_ENV || 'production' }, }); await Promise.race([subprocess, timeoutAfter(ctx.env.STORYBOOK_BUILD_TIMEOUT)]); - } catch (e) { + } catch (err) { // If we tried to run the E2E package's bin directly (due to being in the action) // and it failed, that means we couldn't find it. This probably means they haven't // installed the right dependency or run from the right directory if (isE2EBuild(ctx.options)) { - const errorInfo = e2eBuildErrorMessage(e, process.cwd(), ctx); + const errorInfo = e2eBuildErrorMessage(err, process.cwd(), ctx); ctx.log.error(errorInfo.message); setExitCode(ctx, errorInfo.exitCode, true); throw new Error(failed(ctx).output); @@ -142,7 +137,7 @@ export const buildStorybook = async (ctx: Context) => { signal?.throwIfAborted(); const buildLog = ctx.buildLogFile && readFileSync(ctx.buildLogFile, 'utf8'); - ctx.log.error(buildFailed(ctx, e, buildLog)); + ctx.log.error(buildFailed(ctx, err, buildLog)); setExitCode(ctx, exitCodes.NPM_BUILD_STORYBOOK_FAILED, true); throw new Error(failed(ctx).output); } finally { diff --git a/node-src/tasks/gitInfo.test.ts b/node-src/tasks/gitInfo.test.ts index 8906e37e5..ea9c383bf 100644 --- a/node-src/tasks/gitInfo.test.ts +++ b/node-src/tasks/gitInfo.test.ts @@ -26,7 +26,7 @@ const log = { info: vi.fn(), warn: vi.fn(), debug: vi.fn() }; const commitInfo = { commit: '123asdf', - committedAt: 1640131292, + committedAt: 1_640_131_292, committerName: 'Gert Hengeveld', committerEmail: 'gert@chromatic.com', branch: 'something', diff --git a/node-src/tasks/gitInfo.ts b/node-src/tasks/gitInfo.ts index 07e6a3692..105405687 100644 --- a/node-src/tasks/gitInfo.ts +++ b/node-src/tasks/gitInfo.ts @@ -69,12 +69,12 @@ export const setGitInfo = async (ctx: Context, task: Task) => { ctx.git = { version: await getVersion(), - gitUserEmail: await getUserEmail().catch((e) => { - ctx.log.debug('Failed to retrieve Git user email', e); + gitUserEmail: await getUserEmail().catch((err) => { + ctx.log.debug('Failed to retrieve Git user email', err); return null; }), - uncommittedHash: await getUncommittedHash().catch((e) => { - ctx.log.warn('Failed to retrieve uncommitted files hash', e); + uncommittedHash: await getUncommittedHash().catch((err) => { + ctx.log.warn('Failed to retrieve uncommitted files hash', err); return null; }), ...commitAndBranchInfo, @@ -87,8 +87,8 @@ export const setGitInfo = async (ctx: Context, task: Task) => { if (!ctx.git.slug) { try { ctx.git.slug = await getSlug(); - } catch (e) { - ctx.log.debug('Failed to retrieve Git repository slug', e); + } catch (err) { + ctx.log.debug('Failed to retrieve Git repository slug', err); } } @@ -100,7 +100,7 @@ export const setGitInfo = async (ctx: Context, task: Task) => { const { branch, commit, slug } = ctx.git; ctx.git.matchesBranch = (glob: true | string) => - typeof glob === 'string' && glob.length ? picomatch(glob, { bash: true })(branch) : !!glob; + typeof glob === 'string' && glob.length > 0 ? picomatch(glob, { bash: true })(branch) : !!glob; if (ctx.git.matchesBranch(ctx.options.skip)) { transitionTo(skippingBuild)(ctx, task); @@ -189,9 +189,9 @@ export const setGitInfo = async (ctx: Context, task: Task) => { ); // Take the distinct union of changed files across all baselines. - ctx.git.changedFiles = Array.from( - new Set(changedFilesWithInfo.flatMap(({ changedFiles }) => changedFiles)) - ); + ctx.git.changedFiles = [ + ...new Set(changedFilesWithInfo.flatMap(({ changedFiles }) => changedFiles)), + ]; // Track changed package manifest files along with the commit they were changed in. const { untraced = [] } = ctx.options; @@ -201,7 +201,7 @@ export const setGitInfo = async (ctx: Context, task: Task) => { .filter((f) => !untraced.some((glob) => matchesFile(glob, f))) .filter(isPackageMetadataFile); - return metadataFiles.length + return metadataFiles.length > 0 ? [{ changedFiles: metadataFiles, commit: replacementBuild?.commit ?? build.commit }] : []; } @@ -217,23 +217,24 @@ export const setGitInfo = async (ctx: Context, task: Task) => { }); if (!interactive) { - const list = ctx.git.changedFiles.length - ? `:\n${ctx.git.changedFiles.map((f) => ` ${f}`).join('\n')}` - : ''; + const list = + ctx.git.changedFiles.length > 0 + ? `:\n${ctx.git.changedFiles.map((f) => ` ${f}`).join('\n')}` + : ''; ctx.log.info(`Found ${ctx.git.changedFiles.length} changed files${list}`); } - } catch (e) { + } catch (err) { ctx.turboSnap.bailReason = { invalidChangedFiles: true }; ctx.git.changedFiles = null; ctx.git.replacementBuildIds = null; ctx.log.warn(invalidChangedFiles()); - ctx.log.debug(e); + ctx.log.debug(err); } - if (ctx.options.externals && ctx.git.changedFiles && ctx.git.changedFiles.length) { + if (ctx.options.externals && ctx.git.changedFiles && ctx.git.changedFiles.length > 0) { for (const glob of ctx.options.externals) { const matches = ctx.git.changedFiles.filter((filepath) => matchesFile(glob, filepath)); - if (matches.length) { + if (matches.length > 0) { ctx.turboSnap.bailReason = { changedExternalFiles: matches }; ctx.log.warn(externalsChanged(matches)); ctx.git.changedFiles = null; diff --git a/node-src/tasks/initialize.ts b/node-src/tasks/initialize.ts index 9853bafc6..2aa22f7b7 100644 --- a/node-src/tasks/initialize.ts +++ b/node-src/tasks/initialize.ts @@ -31,7 +31,7 @@ export const setEnvironment = async (ctx: Context) => { // We don't want to send up *all* environment vars as they could include sensitive information // about the user's build environment ctx.environment = Object.entries(process.env).reduce((acc, [key, value]) => { - if (ctx.env.ENVIRONMENT_WHITELIST.find((regex) => key.match(regex))) { + if (ctx.env.ENVIRONMENT_WHITELIST.some((regex) => key.match(regex))) { acc[key] = value; } return acc; @@ -51,8 +51,8 @@ export const setRuntimeMetadata = async (ctx: Context) => { ctx.runtimeMetadata.packageManager = packageManager as any; const packageManagerVersion = await getPackageManagerVersion(packageManager); ctx.runtimeMetadata.packageManagerVersion = packageManagerVersion; - } catch (e) { - ctx.log.debug(`Failed to set runtime metadata: ${e.message}`); + } catch (err) { + ctx.log.debug(`Failed to set runtime metadata: ${err.message}`); } }; diff --git a/node-src/tasks/prepareWorkspace.test.ts b/node-src/tasks/prepareWorkspace.test.ts index 0c8a1a8ff..5c19d4775 100644 --- a/node-src/tasks/prepareWorkspace.test.ts +++ b/node-src/tasks/prepareWorkspace.test.ts @@ -72,7 +72,7 @@ describe('runPrepareWorkspace', () => { isClean.mockResolvedValue(true); isUpToDate.mockResolvedValue(true); findMergeBase.mockResolvedValue('1234asd'); - installDependencies.mockRejectedValue(new Error('')); + installDependencies.mockRejectedValue(new Error('some error')); const ctx = { log, options: { patchHeadRef: 'head', patchBaseRef: 'base' } } as any; await expect(runPrepareWorkspace(ctx, {} as any)).rejects.toThrow( diff --git a/node-src/tasks/report.ts b/node-src/tasks/report.ts index 254042892..4b22026b9 100644 --- a/node-src/tasks/report.ts +++ b/node-src/tasks/report.ts @@ -78,7 +78,7 @@ export const generateReport = async (ctx: Context) => { typeof junitReport === 'boolean' && junitReport ? 'chromatic-build-{buildNumber}.xml' : junitReport; - ctx.reportPath = path.resolve(file.replace(/{buildNumber}/g, String(buildNumber))); + ctx.reportPath = path.resolve(file.replaceAll('{buildNumber}', String(buildNumber))); const { app: { build }, @@ -104,7 +104,7 @@ export const generateReport = async (ctx: Context) => { const suffix = parameters.viewportIsDefault ? '' : testSuffixName; const testCase = suite .testCase() - .className(spec.component.name.replace(/[|/]/g, '.')) // transform story path to class path + .className(spec.component.name.replaceAll(/[|/]/g, '.')) // transform story path to class path .name(`${spec.name} ${suffix}`); switch (status) { diff --git a/node-src/tasks/upload.test.ts b/node-src/tasks/upload.test.ts index 4918f4dde..39ca5d425 100644 --- a/node-src/tasks/upload.test.ts +++ b/node-src/tasks/upload.test.ts @@ -278,7 +278,9 @@ describe('traceChangedFiles', () => { findChangedDependencies.mockResolvedValue([]); findChangedPackageFiles.mockResolvedValue([]); getDependentStoryFiles.mockResolvedValue(deps); - accessMock.mockImplementation((_path, callback) => Promise.resolve(callback(new Error()))); + accessMock.mockImplementation((_path, callback) => + Promise.resolve(callback(new Error('some error'))) + ); const ctx = { env, @@ -576,7 +578,7 @@ describe('uploadStorybook', () => { expect.objectContaining({ body: expect.any(FormData), method: 'POST' }), expect.objectContaining({ retries: 0 }) ); - expect(ctx.uploadedBytes).toBe(500500); + expect(ctx.uploadedBytes).toBe(500_500); expect(ctx.uploadedFiles).toBe(1001); }); diff --git a/node-src/tasks/upload.ts b/node-src/tasks/upload.ts index d94782d86..5e7354c2d 100644 --- a/node-src/tasks/upload.ts +++ b/node-src/tasks/upload.ts @@ -1,5 +1,5 @@ import { readdirSync, readFileSync, statSync } from 'fs'; -import { join } from 'path'; +import path from 'path'; import semver from 'semver'; import slash from 'slash'; @@ -51,16 +51,16 @@ function getPathsInDir(ctx: Context, rootDir: string, dirname = '.'): PathSpec[] } try { - return readdirSync(join(rootDir, dirname)).flatMap((p: string) => { - const pathname = join(dirname, p); - const stats = statSync(join(rootDir, pathname)); + return readdirSync(path.join(rootDir, dirname)).flatMap((p: string) => { + const pathname = path.join(dirname, p); + const stats = statSync(path.join(rootDir, pathname)); return stats.isDirectory() ? getPathsInDir(ctx, rootDir, pathname) : [{ pathname, contentLength: stats.size }]; }); - } catch (e) { - ctx.log.debug(e); - throw new Error(invalid({ sourceDir: rootDir } as any, e).output); + } catch (err) { + ctx.log.debug(err); + throw new Error(invalid({ sourceDir: rootDir } as any, err).output); } } @@ -80,7 +80,7 @@ function getFileInfo(ctx: Context, sourceDir: string) { const paths: string[] = []; let statsPath: string; for (const { knownAs } of lengths) { - if (knownAs.endsWith('preview-stats.json')) statsPath = join(sourceDir, knownAs); + if (knownAs.endsWith('preview-stats.json')) statsPath = path.join(sourceDir, knownAs); else if (!knownAs.endsWith('manager-stats.json')) paths.push(knownAs); } return { lengths, paths, statsPath, total }; @@ -101,8 +101,8 @@ export const validateFiles = async (ctx: Context) => { ctx.sourceDir = outputDir; ctx.fileInfo = getFileInfo(ctx, ctx.sourceDir); } - } catch (e) { - ctx.log.debug(e); + } catch (err) { + ctx.log.debug(err); } } @@ -141,9 +141,10 @@ export const traceChangedFiles = async (ctx: Context, task: Task) => { if (changedDependencyNames) { ctx.git.changedDependencyNames = changedDependencyNames; if (!ctx.options.interactive) { - const list = changedDependencyNames.length - ? `:\n${changedDependencyNames.map((f) => ` ${f}`).join('\n')}` - : ''; + const list = + changedDependencyNames.length > 0 + ? `:\n${changedDependencyNames.map((f) => ` ${f}`).join('\n')}` + : ''; ctx.log.info(`Found ${changedDependencyNames.length} changed dependencies${list}`); } } else { @@ -172,7 +173,7 @@ export const traceChangedFiles = async (ctx: Context, task: Task) => { if (onlyStoryFiles) { // Escape special characters in the filename so it does not conflict with picomatch ctx.onlyStoryFiles = Object.keys(onlyStoryFiles).map((key) => - key.replace(SPECIAL_CHARS_REGEXP, '\\$1') + key.replaceAll(SPECIAL_CHARS_REGEXP, '\\$1') ); if (!ctx.options.interactive) { @@ -183,7 +184,7 @@ export const traceChangedFiles = async (ctx: Context, task: Task) => { .join('\n')}` ); } - if (ctx.untracedFiles && ctx.untracedFiles.length) { + if (ctx.untracedFiles && ctx.untracedFiles.length > 0) { ctx.log.info( `Encountered ${ctx.untracedFiles.length} untraced files:\n${ctx.untracedFiles .map((f) => ` ${f}`) @@ -225,11 +226,11 @@ export const uploadStorybook = async (ctx: Context, task: Task) => { if (ctx.skip) return; transitionTo(starting)(ctx, task); - const files = ctx.fileInfo.paths.map((path) => ({ - ...(ctx.fileInfo.hashes && { contentHash: ctx.fileInfo.hashes[path] }), - contentLength: ctx.fileInfo.lengths.find(({ knownAs }) => knownAs === path).contentLength, - localPath: join(ctx.sourceDir, path), - targetPath: path, + 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, })); await uploadBuild(ctx, files, { diff --git a/node-src/tasks/verify.ts b/node-src/tasks/verify.ts index 1df8680e9..82fa03e5d 100644 --- a/node-src/tasks/verify.ts +++ b/node-src/tasks/verify.ts @@ -57,7 +57,7 @@ export const publishBuild = async (ctx: Context) => { id, input: { ...(onlyStoryFiles && { onlyStoryFiles }), - ...(onlyStoryNames && { onlyStoryNames: [].concat(onlyStoryNames) }), + ...(onlyStoryNames && { onlyStoryNames: [onlyStoryNames].flat() }), ...(replacementBuildIds && { replacementBuildIds }), // GraphQL does not support union input types (yet), so we send an object // @see https://github.com/graphql/graphql-spec/issues/488 diff --git a/node-src/types.ts b/node-src/types.ts index bc9dd0e2f..dc6af7740 100644 --- a/node-src/types.ts +++ b/node-src/types.ts @@ -145,8 +145,6 @@ export interface Options extends Configuration { skipUpdateCheck: Flags['skipUpdateCheck']; } -export { Configuration }; - export type TaskName = | 'auth' | 'gitInfo' @@ -389,3 +387,5 @@ export interface TargetInfo { formAction: string; formFields: Record; } + +export { type Configuration } from './lib/getConfiguration'; diff --git a/node-src/ui/messages/errors/fatalError.ts b/node-src/ui/messages/errors/fatalError.ts index 5f7938e68..393e8c9f0 100644 --- a/node-src/ui/messages/errors/fatalError.ts +++ b/node-src/ui/messages/errors/fatalError.ts @@ -25,7 +25,7 @@ export default function fatalError( const { scripts = {} } = packageJson; const email = link(pkg.bugs.email); const website = link(pkg.docs); - const errors = [].concat(error); + const errors = [error].flat(); const { git, @@ -77,7 +77,7 @@ export default function fatalError( const stacktraces = errors.map((err) => err.stack).filter(Boolean); return [ errors.map((err) => err.message).join('\n'), - stacktraces.length + stacktraces.length > 0 ? chalk`{dim → View the full ${pluralize('stacktrace', stacktraces.length)} below}\n` : '', dedent(chalk` @@ -87,6 +87,6 @@ export default function fatalError( Please provide us with the above CLI output and the following info: `), chalk`{bold ${JSON.stringify(debugInfo, null, 2)}}`, - stacktraces.length ? chalk`\n{dim ${stacktraces.join('\n\n')}}` : '', + stacktraces.length > 0 ? chalk`\n{dim ${stacktraces.join('\n\n')}}` : '', ].join('\n'); } diff --git a/node-src/ui/messages/errors/invalidConfigurationFile.ts b/node-src/ui/messages/errors/invalidConfigurationFile.ts index 3d3edc24a..a4ecd3c6b 100644 --- a/node-src/ui/messages/errors/invalidConfigurationFile.ts +++ b/node-src/ui/messages/errors/invalidConfigurationFile.ts @@ -10,7 +10,7 @@ export const invalidConfigurationFile = (configFile: string, err: ZodError) => { return dedent(chalk` ${error} Configuration file {bold ${configFile}} was invalid, please check the allowed keys. ${ - formErrors.length + formErrors.length > 0 ? `\n${formErrors.map((msg) => chalk`- {bold ${msg}}`).join('\n ')}\n\n` : '' } diff --git a/node-src/ui/messages/errors/maxFileSizeExceeded.stories.ts b/node-src/ui/messages/errors/maxFileSizeExceeded.stories.ts index a7cb75430..39a17ba1c 100644 --- a/node-src/ui/messages/errors/maxFileSizeExceeded.stories.ts +++ b/node-src/ui/messages/errors/maxFileSizeExceeded.stories.ts @@ -5,4 +5,4 @@ export default { }; export const MaxFileSizeExceeded = () => - maxFileSizeExceeded({ filePaths: ['index.js', 'main.js'], maxFileSize: 12345 }); + maxFileSizeExceeded({ filePaths: ['index.js', 'main.js'], maxFileSize: 12_345 }); diff --git a/node-src/ui/messages/errors/runtimeError.ts b/node-src/ui/messages/errors/runtimeError.ts index 89eb41d74..1897a605f 100644 --- a/node-src/ui/messages/errors/runtimeError.ts +++ b/node-src/ui/messages/errors/runtimeError.ts @@ -18,9 +18,10 @@ export default function runtimeError({ const stacktraces = [...runtimeErrors, ...runtimeWarnings] .map((err) => err.stack) .filter(Boolean); - const viewStacktraces = stacktraces.length - ? chalk`\n{dim → View the full ${pluralize('stacktrace', stacktraces.length)} below}` - : ''; + const viewStacktraces = + stacktraces.length > 0 + ? chalk`\n{dim → View the full ${pluralize('stacktrace', stacktraces.length)} below}` + : ''; const errorCount = runtimeErrors.length; const warningCount = runtimeWarnings.length; @@ -51,6 +52,6 @@ export default function runtimeError({ Run your Storybook locally and check your browser console for errors. ${errorCount ? errorHint : warningHint} - ${stacktraces.length ? chalk`\n{dim ${stacktraces.join('\n\n')}}` : ''} + ${stacktraces.length > 0 ? chalk`\n{dim ${stacktraces.join('\n\n')}}` : ''} `); } diff --git a/node-src/ui/messages/errors/uploadFailed.stories.ts b/node-src/ui/messages/errors/uploadFailed.stories.ts index 76b7a4bd1..c77c82546 100644 --- a/node-src/ui/messages/errors/uploadFailed.stories.ts +++ b/node-src/ui/messages/errors/uploadFailed.stories.ts @@ -5,7 +5,7 @@ export default { }; const target = { - contentLength: 12345, + contentLength: 12_345, localPath: 'local/path/to/file.js', targetPath: 'file.js', contentType: 'text/javascript', diff --git a/node-src/ui/messages/errors/uploadFailed.ts b/node-src/ui/messages/errors/uploadFailed.ts index e8c7efd14..9112747ae 100644 --- a/node-src/ui/messages/errors/uploadFailed.ts +++ b/node-src/ui/messages/errors/uploadFailed.ts @@ -8,9 +8,9 @@ const encode = (path: string) => path.split('/').map(encodeURIComponent).join('/ export function uploadFailed({ target }: { target: FileDesc & TargetInfo }, debug = false) { const diagnosis = - encode(target.targetPath) !== target.targetPath - ? 'It seems the file path may contain illegal characters.' - : 'The file may have been modified during the upload process.'; + encode(target.targetPath) === target.targetPath + ? 'The file may have been modified during the upload process.' + : 'It seems the file path may contain illegal characters.'; const message = dedent(chalk` ${icon} Failed to upload {bold ${target.localPath}} to {bold ${target.targetPath}} ${diagnosis} diff --git a/node-src/ui/messages/info/replacedBuild.ts b/node-src/ui/messages/info/replacedBuild.ts index cdb62c240..0713c6825 100644 --- a/node-src/ui/messages/info/replacedBuild.ts +++ b/node-src/ui/messages/info/replacedBuild.ts @@ -4,7 +4,7 @@ import { dedent } from 'ts-dedent'; import { info } from '../../components/icons'; function commit(build) { - return build.commit.substr(0, 7); + return build.commit.slice(0, 7); } export default ({ replacedBuild, replacementBuild }) => diff --git a/node-src/ui/messages/info/tracedAffectedFiles.ts b/node-src/ui/messages/info/tracedAffectedFiles.ts index 50720c343..544260909 100644 --- a/node-src/ui/messages/info/tracedAffectedFiles.ts +++ b/node-src/ui/messages/info/tracedAffectedFiles.ts @@ -75,7 +75,7 @@ export default ( if (ctx.options.traceChanged === 'compact') { let submodules = false; const affectedFiles = Object.values(affectedModules).map(([firstFile, ...otherFiles]) => { - if (!otherFiles.length) return firstFile; + if (otherFiles.length === 0) return firstFile; submodules = true; return `${firstFile} + ${otherFiles.length} modules`; }); @@ -95,7 +95,7 @@ export default ( }; const seen = new Set(); - const traces = Array.from(ctx.turboSnap.tracedPaths).map((p) => { + const traces = [...ctx.turboSnap.tracedPaths].map((p) => { const parts = p.split('\n'); return parts .reduce((acc, part, index) => { diff --git a/node-src/ui/messages/warnings/deprecatedOption.ts b/node-src/ui/messages/warnings/deprecatedOption.ts index 5ee84f117..2ea382e71 100644 --- a/node-src/ui/messages/warnings/deprecatedOption.ts +++ b/node-src/ui/messages/warnings/deprecatedOption.ts @@ -7,7 +7,7 @@ import link from '../../components/link'; const changelogUrl = 'https://github.com/chromaui/chromatic-cli/blob/main/CHANGELOG.md'; -const snakify = (option: string) => `--${option.replace(/[A-Z]/g, '-$&').toLowerCase()}`; +const snakify = (option: string) => `--${option.replaceAll(/[A-Z]/g, '-$&').toLowerCase()}`; export default ({ flag, replacement }: { flag: keyof Flags; replacement?: keyof Flags }) => dedent(chalk` diff --git a/node-src/ui/tasks/build.stories.ts b/node-src/ui/tasks/build.stories.ts index 2870f79de..3d6e7d5d6 100644 --- a/node-src/ui/tasks/build.stories.ts +++ b/node-src/ui/tasks/build.stories.ts @@ -15,7 +15,7 @@ export const Building = () => pending({ buildCommand } as any); export const Built = () => success({ now: 0, - startedAt: -32100, + startedAt: -32_100, buildLogFile: '/users/me/project/build-storybook.log', } as any); diff --git a/node-src/ui/tasks/gitInfo.ts b/node-src/ui/tasks/gitInfo.ts index 33dfcc335..1e40806e8 100644 --- a/node-src/ui/tasks/gitInfo.ts +++ b/node-src/ui/tasks/gitInfo.ts @@ -9,7 +9,7 @@ const infoMessage = ( ) => { const turboSnapStatus = turboSnap.bailReason ? '; TurboSnap disabled' : ''; const branchName = ownerName ? `${ownerName}:${branch}` : branch; - let message = `Commit '${commit.substr(0, 7)}' on branch '${branchName}'`; + let message = `Commit '${commit.slice(0, 7)}' on branch '${branchName}'`; if (parentCommits.length > 0) { message += `; found ${pluralize('parent build', parentCommits.length, true)}`; if (changedFiles) { @@ -33,13 +33,13 @@ export const pending = () => ({ export const skippingBuild = (ctx: Context) => ({ status: 'pending', title: 'Skipping build', - output: `Skipping build for commit ${ctx.git.commit.substr(0, 7)}`, + output: `Skipping build for commit ${ctx.git.commit.slice(0, 7)}`, }); export const skippedForCommit = (ctx: Context) => ({ status: 'success', title: 'Skipping build', - output: `Skipped build for commit ${ctx.git.commit.substr(0, 7)} due to --skip`, + output: `Skipped build for commit ${ctx.git.commit.slice(0, 7)} due to --skip`, }); export const skipFailed = () => ({ diff --git a/node-src/ui/tasks/prepareWorkspace.ts b/node-src/ui/tasks/prepareWorkspace.ts index 767cc306a..bf16e35ba 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.substr(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.substr(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/snapshot.stories.ts b/node-src/ui/tasks/snapshot.stories.ts index b66a7cdd5..a82807541 100644 --- a/node-src/ui/tasks/snapshot.stories.ts +++ b/node-src/ui/tasks/snapshot.stories.ts @@ -31,7 +31,7 @@ const build = { const options = {}; const now = 0; -const startedAt = -123456; +const startedAt = -123_456; export const Initial = () => initial; diff --git a/node-src/ui/tasks/upload.stories.ts b/node-src/ui/tasks/upload.stories.ts index 6be7151a6..4433e4c40 100644 --- a/node-src/ui/tasks/upload.stories.ts +++ b/node-src/ui/tasks/upload.stories.ts @@ -32,7 +32,7 @@ export const Invalid = () => buildLogFile: '/var/folders/h3/ff9kk23958l99z2qbzfjdlxc0000gn/T/build-storybook.log', } as any); -export const Tracing = () => tracing({ git: { changedFiles: new Array(3) } } as any); +export const Tracing = () => tracing({ git: { changedFiles: Array.from({ length: 3 }) } } as any); export const BailedPackageFile = () => bailed({ turboSnap: { bailReason: { changedPackageFiles: ['package.json'] } } } as any); @@ -60,8 +60,8 @@ export const Finalizing = () => finalizing(); export const Success = () => success({ now: 0, - startedAt: -54321, - uploadedBytes: 1234567, + startedAt: -54_321, + uploadedBytes: 1_234_567, uploadedFiles: 42, fileInfo: { paths: { length: 42 } }, } as any); @@ -69,8 +69,8 @@ export const Success = () => export const SuccessSkippedFiles = () => success({ now: 0, - startedAt: -54321, - uploadedBytes: 1234567, + startedAt: -54_321, + uploadedBytes: 1_234_567, uploadedFiles: 42, fileInfo: { paths: { length: 100 } }, } as any); diff --git a/test-stories/timing.stories-disabled.js b/test-stories/timing.stories-disabled.js index 2f786c8d4..b051a2adf 100644 --- a/test-stories/timing.stories-disabled.js +++ b/test-stories/timing.stories-disabled.js @@ -18,7 +18,7 @@ const WaitFor = ({ seconds }) => { return (
- {Array.from(new Array(seconds - count)).map((_, index) => ( + {Array.from({ length: seconds - count }).map((_, index) => ( `; From 1a881280eb97d403d76776a32c3ab40a6aa7d06d Mon Sep 17 00:00:00 2001 From: Cody Kaup <35509748+codykaup@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:58:05 -0500 Subject: [PATCH 4/6] err_ -> execError Co-authored-by: John Hobbs --- node-src/lib/logSerializers.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-src/lib/logSerializers.test.ts b/node-src/lib/logSerializers.test.ts index e222fd59d..c39437783 100644 --- a/node-src/lib/logSerializers.test.ts +++ b/node-src/lib/logSerializers.test.ts @@ -7,8 +7,8 @@ it('strips off envPairs', () => { let err; try { execSync('some hot garbage', { stdio: 'ignore' }); - } catch (err_) { - err = err_; + } catch (execError) { + err = execError; } expect((errorSerializer(err) as any).envPairs).toBeUndefined(); }); From e5d058db1d3d0061d628ee1ef06dcf85437fa925 Mon Sep 17 00:00:00 2001 From: Cody Kaup Date: Wed, 11 Sep 2024 16:59:34 -0500 Subject: [PATCH 5/6] Use const for uncommittedHash --- node-src/git/git.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-src/git/git.ts b/node-src/git/git.ts index c2627fc71..b9ad31ede 100644 --- a/node-src/git/git.ts +++ b/node-src/git/git.ts @@ -108,13 +108,13 @@ export async function getUncommittedHash() { const listUntrackedFiles = 'git ls-files --others --exclude-standard'; const listUncommittedFiles = [listStagedFiles, listUnstagedFiles, listUntrackedFiles].join(';'); - let uncommittedHash = await execGitCommand( + const uncommittedHashWithPadding = await execGitCommand( // Pass the combined list of filenames to hash-object to retrieve a list of hashes. Then pass // the list of hashes to hash-object again to retrieve a single hash of all hashes. We use // stdin to avoid the limit on command line arguments. `(${listUncommittedFiles}) | git hash-object --stdin-paths | git hash-object --stdin` ); - uncommittedHash = uncommittedHash.trim(); + const uncommittedHash = uncommittedHashWithPadding.trim(); // In case there are no uncommited changes (empty list), we always get this same hash. const noChangesHash = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'; From 0b68b734b8da67fa2c1b20211bddb6e132011d5a Mon Sep 17 00:00:00 2001 From: Cody Kaup Date: Wed, 11 Sep 2024 17:11:44 -0500 Subject: [PATCH 6/6] Allow descriptive error names in unicorn/catch-error-name --- eslint.config.mjs | 3 +-- .../ui/messages/errors/invalidConfigurationFile.stories.ts | 4 ++-- .../messages/errors/unparseableConfigurationFile.stories.ts | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 341d2bbbf..b1c2bc7d0 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -129,8 +129,7 @@ export default [ 'unicorn/catch-error-name': [ 'error', { - name: 'err', - ignore: ['^err.*$'], + ignore: ['err'], }, ], 'unicorn/switch-case-braces': 'off', diff --git a/node-src/ui/messages/errors/invalidConfigurationFile.stories.ts b/node-src/ui/messages/errors/invalidConfigurationFile.stories.ts index 7d8f6a835..4e6cdf222 100644 --- a/node-src/ui/messages/errors/invalidConfigurationFile.stories.ts +++ b/node-src/ui/messages/errors/invalidConfigurationFile.stories.ts @@ -12,8 +12,8 @@ try { a: z.string(), b: z.number(), }).parse({ a: 1, b: '1' }); -} catch (aErr) { - err = aErr; +} catch (error) { + err = error; } export const InvalidConfigurationFile = () => invalidConfigurationFile('./my.config.json', err); diff --git a/node-src/ui/messages/errors/unparseableConfigurationFile.stories.ts b/node-src/ui/messages/errors/unparseableConfigurationFile.stories.ts index 4f41b0181..84b37af0c 100644 --- a/node-src/ui/messages/errors/unparseableConfigurationFile.stories.ts +++ b/node-src/ui/messages/errors/unparseableConfigurationFile.stories.ts @@ -7,8 +7,8 @@ export default { let err; try { JSON.parse('foo'); -} catch (aErr) { - err = aErr; +} catch (error) { + err = error; } export const UnparseableConfigurationFile = () =>