diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad7389b22..2f6bcca03 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -154,6 +154,103 @@ jobs: if: steps.retry0.outcome=='failure' run: echo "::warning title=Flaky tests::${{ matrix.package }} flaked on the first attempt and was recovered by a spec-level retry (tracked in PER-9011)." + # The Snyk-backed lockfile-diff path (resolveAffectedDeps in + # packages/cli-command/src/lockfileDiff.js) and its IntelliStory callers + # require snyk-nodejs-lockfile-parser, which needs Node >=18. On the Node 14 + # matrix above those tests are xdescribe'd / bail, so this leg runs the + # @percy/cli-command suite on Node 20 to exercise that code in CI. See PPLT-5844. + test-node20: + name: Test ${{ matrix.package }} (Node ${{ matrix.node }}) + # Skip only for the automated release PR (see the build job for rationale). + if: >- + ${{ !(github.event_name == 'pull_request' + && github.event.pull_request.user.login == 'github-actions[bot]' + && github.event.pull_request.head.repo.full_name == github.repository + && startsWith(github.head_ref, 'release/')) }} + needs: [build] + strategy: + matrix: + os: [ubuntu-latest] + node: [20] + package: + - '@percy/cli-command' + runs-on: ${{ matrix.os }} + # Collect failed node-test spec names so retries re-run only the specs that + # flaked instead of the whole suite. Non-PERCY_ name so it doesn't trip + # cli-doctor's env-audit tests. See PER-9011. + env: + CLI_TEST_FAILURES_FILE: ${{ github.workspace }}/.cli-test-failures.json + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1 + with: + node-version: ${{ matrix.node }} + - uses: actions/cache@f4b3439a656ba812b8cb417d2d49f9c810103092 # v3.4.0 + with: + path: | + node_modules + packages/*/node_modules + packages/core/.local-chromium + key: > + ${{ runner.os }}/node-${{ matrix.node }}/ + ${{ hashFiles('.github/.cache-key') }}/ + ${{ hashFiles('**/yarn.lock') }} + restore-keys: > + ${{ runner.os }}/node-${{ matrix.node }}/ + ${{ hashFiles('.github/.cache-key') }}/ + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: dist + path: packages + - run: yarn + - name: Install browser dependencies + run: | + sudo apt-get update + sudo apt-get install -y --fix-missing libgbm-dev + if: ${{ matrix.os == 'ubuntu-latest' }} + # First attempt runs the full suite WITH coverage (enforces the 100% + # gate) and records any failed specs. + - name: Run tests + continue-on-error: true + id: retry0 + run: yarn workspace ${{ matrix.package }} test:coverage --colors + # Retries re-run ONLY the specs that failed in the previous attempt, and + # WITHOUT coverage (a subset can't hit the 100% threshold). If retry0 + # failed with no recorded spec failures (e.g. a real coverage drop), the + # runner preserves that failure instead of masking it. See PER-9011. + - name: Run tests Retry (1/4) + continue-on-error: true + id: retry1 + if: steps.retry0.outcome=='failure' + env: + CLI_TEST_ONLY_FAILED: '1' + run: yarn workspace ${{ matrix.package }} test --colors + - name: Run tests Retry (2/4) + continue-on-error: true + id: retry2 + if: steps.retry1.outcome=='failure' + env: + CLI_TEST_ONLY_FAILED: '1' + run: yarn workspace ${{ matrix.package }} test --colors + - name: Run tests Retry (3/4) + continue-on-error: true + id: retry3 + if: steps.retry2.outcome=='failure' + env: + CLI_TEST_ONLY_FAILED: '1' + run: yarn workspace ${{ matrix.package }} test --colors + - name: Run tests Retry (4/4) + id: retry4 + if: steps.retry3.outcome=='failure' + env: + CLI_TEST_ONLY_FAILED: '1' + run: yarn workspace ${{ matrix.package }} test --colors + # Keep "green via retry" honest: surface a warning whenever the first + # attempt failed and a retry recovered it, so flakiness stays visible. + - name: Flag flaky tests + if: steps.retry0.outcome=='failure' + run: echo "::warning title=Flaky tests::${{ matrix.package }} flaked on the first attempt and was recovered by a spec-level retry (tracked in PER-9011)." + regression: name: Regression # Skip only for the automated release PR (see the build job for rationale). diff --git a/.semgrepignore b/.semgrepignore index fc58ffd32..42057c32f 100644 --- a/.semgrepignore +++ b/.semgrepignore @@ -46,3 +46,18 @@ packages/core/src/api.js # in the file-load helper anyway. No user input flows here. packages/core/test/unit/maestro-hierarchy.test.js packages/core/test/unit/maestro-hierarchy.parity.test.js + +# intelliStory.js joins/resolves paths for the affected-story graph from +# trusted, locally-derived sources: buildDir/manifestDir come from the CLI +# config, projectRoot from `git rev-parse --show-toplevel`, and the stats +# filename is validated to a bare `.json` basename (/^[\w.-]+\.json$/) before +# any join. No external request input reaches these paths. semgrep's +# path-join-resolve-traversal rule cannot follow those validations, and inline +# `// nosemgrep` is not honored by the CI semgrep version — suppress at the +# file level with this rationale. +packages/cli-command/src/intelliStory.js + +# noRequireBinding.test.js walks repo source files by hardcoded relative paths +# to assert no `require()` bindings leak in; the traversal roots are static +# literals, no user input flows here. semgrep flags the path.join() anyway. +packages/cli-command/test/noRequireBinding.test.js diff --git a/packages/cli-command/package.json b/packages/cli-command/package.json index 8239e9e16..b97807814 100644 --- a/packages/cli-command/package.json +++ b/packages/cli-command/package.json @@ -27,6 +27,7 @@ ".": "./dist/index.js", "./flags": "./dist/flags.js", "./utils": "./dist/utils.js", + "./intelliStory": "./dist/intelliStory.js", "./test/helpers": "./test/helpers.js" }, "scripts": { @@ -38,6 +39,10 @@ "dependencies": { "@percy/config": "1.32.5-beta.0", "@percy/core": "1.32.5-beta.0", - "@percy/logger": "1.32.5-beta.0" + "@percy/logger": "1.32.5-beta.0", + "glob-to-regexp": "^0.4.1" + }, + "optionalDependencies": { + "snyk-nodejs-lockfile-parser": "2.7.1" } } diff --git a/packages/cli-command/src/graphTrace.js b/packages/cli-command/src/graphTrace.js new file mode 100644 index 000000000..6561d9703 --- /dev/null +++ b/packages/cli-command/src/graphTrace.js @@ -0,0 +1,117 @@ +import fs from 'fs'; +import path from 'path'; +import url from 'url'; + +const TEMPLATE_PATH = path.resolve(url.fileURLToPath(import.meta.url), '../graphTraceTemplate.html'); + +function templateKindOf(v) { + if (v.changed) return 'is_relevant'; + switch (v.kind) { + case 'dependency': return 'package'; + case 'component': return 'component'; + case 'story': return 'story'; + default: return 'component'; + } +} + +const KIND_RANK = { package: 0, component: 1, is_relevant: 1, story: 2 }; + +function computeLayout(rawVertices, edges, transitiveClosure) { + const n = rawVertices.length; + const vertices = rawVertices.map((v, i) => ({ + index: i, + name: v.file_path, + kind: v.kind, + changed: !!v.changed, + row: 0, + col: 0 + })); + + const incomingMax = new Array(n).fill(0); + for (const triple of transitiveClosure) { + const [u, v, val] = triple; + if (u === v || val <= 0) continue; + if (v < 0 || v >= n) continue; + if (val > incomingMax[v]) incomingMax[v] = val; + } + for (let i = 0; i < n; i++) { + vertices[i].col = vertices[i].kind === 'dependency' ? 0 : incomingMax[i] + 1; + } + + const iterations = n + 2; + for (let iter = 0; iter < iterations; iter++) { + let changed = false; + for (const [s, t] of edges) { + if (s < 0 || s >= n || t < 0 || t >= n) continue; + if (vertices[s].col < vertices[t].col) continue; + vertices[t].col = vertices[s].col + 1; + changed = true; + } + if (!changed) break; + } + + let furthestNonStory = 0; + for (const v of vertices) { + if (v.kind === 'story') continue; + if (v.col > furthestNonStory) furthestNonStory = v.col; + } + for (const v of vertices) { + if (v.kind !== 'story') continue; + if (v.col < furthestNonStory + 1) v.col = furthestNonStory + 1; + } + + const groups = new Map(); + for (const v of vertices) { + let list = groups.get(v.col); + if (!list) groups.set(v.col, list = []); + list.push(v); + } + const rankOf = v => { + const r = KIND_RANK[templateKindOf(v)]; + /* istanbul ignore next */ + return r === undefined ? 99 : r; + }; + for (const list of groups.values()) { + list.sort((a, b) => { + const ra = rankOf(a); + const rb = rankOf(b); + if (ra !== rb) return ra - rb; + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }); + list.forEach((v, row) => { v.row = row; }); + } + + return vertices.map(v => ({ + index: v.index, + name: v.name, + row: v.row, + col: v.col, + kind: templateKindOf(v) + })); +} + +const LS = String.fromCharCode(0x2028); +const PS = String.fromCharCode(0x2029); +function safeJson(obj) { + return JSON.stringify(obj) + .replace(/<\//g, '<\\/') + .replace(/`; + + function hostileLine() { + return embeddedJson(renderGraphTraceHtml({ + vertices: [{ kind: 'component', file_path: hostile }], + edges: [], + transitiveClosureMatrixSparse: [] + }), 'vertices'); + } + + it('escapes " { + let line = hostileLine(); + expect(line).not.toContain(''); + expect(line).toContain('<\\/script>'); + }); + + it('escapes HTML comment open and close markers', () => { + let line = hostileLine(); + expect(line).toContain('<\\!--'); + expect(line).toContain('--\\>'); + }); + + it('escapes U+2028 and U+2029 line/paragraph separators', () => { + let line = hostileLine(); + expect(line).not.toContain(LS); + expect(line).not.toContain(PS); + expect(line).toContain('\\u2028'); + expect(line).toContain('\\u2029'); + }); + + it('escapes only the dangerous sequences, leaving the payload intact', () => { + let restored = hostileLine() + .split('<\\!--').join(''); + expect(JSON.parse(restored)[0].name).toEqual(hostile); + }); + }); +}); diff --git a/packages/cli-command/test/index.test.js b/packages/cli-command/test/index.test.js new file mode 100644 index 000000000..c92773f27 --- /dev/null +++ b/packages/cli-command/test/index.test.js @@ -0,0 +1,15 @@ +import * as cliCommand from '../src/index.js'; + +describe('index (public exports)', () => { + it('re-exports the command, legacy, intelliStory and common-package surface', () => { + expect(typeof cliCommand.default).toBe('function'); + expect(typeof cliCommand.command).toBe('function'); + expect(cliCommand._resetShutdownForTest).toBeDefined(); + expect(cliCommand.legacyCommand).toBeDefined(); + expect(cliCommand.flags).toBeDefined(); + expect(typeof cliCommand.applyIntelliStory).toBe('function'); + expect(cliCommand.IntelliStoryBailError.prototype).toBeInstanceOf(Error); + expect(cliCommand.PercyConfig).toBeDefined(); + expect(cliCommand.logger).toBeDefined(); + }); +}); diff --git a/packages/cli-command/test/intelliStory.test.js b/packages/cli-command/test/intelliStory.test.js new file mode 100644 index 000000000..25283cb2f --- /dev/null +++ b/packages/cli-command/test/intelliStory.test.js @@ -0,0 +1,842 @@ +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import cp from 'child_process'; +import { mockfs } from './helpers.js'; +import { + IntelliStoryBailError, + validateAndReadStats, + getBaselineAndAffectedNodes, + assertNoDotStorybookChange, + assertNoBailOnChanges, + enforceUntraced, + getAffectedPackages, + getAffectedFileLocations, + extractStorybookPaths, + runGraphGeneration, + maybeWriteTrace, + applyIntelliStory, + writeIntelliStoryTrace +} from '../src/intelliStory.js'; + +const NODE_MAJOR = parseInt(process.versions.node.split('.')[0], 10); + +const itPosix = path.sep === '/' ? it : xit; + +function git(args, cwd) { + let r = cp.spawnSync('git', args, { cwd, encoding: 'utf8' }); + if (r.status !== 0) throw new Error(`git ${args.join(' ')} failed: ${r.stderr || r.stdout}`); + return r.stdout; +} + +function makeRepo(seed, changed) { + let dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'intelliStory-'))); + git(['init', '-q'], dir); + git(['config', 'user.email', 'test@example.com'], dir); + git(['config', 'user.name', 'Test'], dir); + let writeAll = files => { + for (let [rel, content] of Object.entries(files)) { + let abs = path.join(dir, rel); + fs.mkdirSync(path.dirname(abs), { recursive: true }); + fs.writeFileSync(abs, content); + } + }; + writeAll(seed); + git(['add', '-A'], dir); + git(['commit', '-qm', 'base'], dir); + let baseSha = git(['rev-parse', 'HEAD'], dir).trim(); + if (changed) { + writeAll(changed); + git(['add', '-A'], dir); + git(['commit', '-qm', 'change'], dir); + } + return { dir, baseSha }; +} + +function mockLog() { + return { + debug: jasmine.createSpy('debug'), + info: jasmine.createSpy('info'), + warn: jasmine.createSpy('warn') + }; +} + +async function expectBail(fn, substr) { + let err; + try { + await fn(); + } catch (e) { + err = e; + } + expect(err).toBeInstanceOf(IntelliStoryBailError); + if (substr) expect(err.message).toContain(substr); + return err; +} + +const identity = p => p; + +describe('intelliStory', () => { + describe('validateAndReadStats()', () => { + const log = mockLog(); + + it('bails when the statsFile is not a .json filename', async () => { + await expectBail( + () => validateAndReadStats('/build', 'stats.txt', '/root', log), + 'invalid statsFile'); + }); + + it('bails when the stats file is missing from the build dir', async () => { + await mockfs({ '/build': null }); + await expectBail( + () => validateAndReadStats('/build', undefined, '/root', log), + 'not found in build directory'); + }); + + it('bails when the resolved stats path is a directory', async () => { + await mockfs({ '/build/enriched-stats.json': null }); + await expectBail( + () => validateAndReadStats('/build', undefined, '/root', log), + 'is not a regular file'); + }); + + it('reads files and modules from a valid stats file (buildId no longer required)', async () => { + await mockfs({ '/build/enriched-stats.json': JSON.stringify({ modules: [] }) }); + let res = await validateAndReadStats('/build', undefined, '/root', log); + expect(res).toEqual({ files: [], modules: [] }); + }); + + it('bails when the stats file contains malformed JSON', async () => { + await mockfs({ '/build/enriched-stats.json': '{ not valid json' }); + await expectBail( + () => validateAndReadStats('/build', undefined, '/root', log), + 'failed to parse stats file'); + }); + + it('anchors a traversal-prefixed statsFile inside the build dir via basename', async () => { + await mockfs({ '/build/foo.json': JSON.stringify({ modules: [] }) }); + let res = await validateAndReadStats('/build', '../../etc/foo.json', '/root', log); + expect(res).toEqual({ files: [], modules: [] }); + }); + + it('streams modules: indexes src refs, leaves module refs, drops node_modules/string-id and id-less entries', async () => { + await mockfs({ + '/build/enriched-stats.json': JSON.stringify({ + buildId: 'b', + modules: [ + { + id: '/root/src/A.js', + imports: [ + { type: 'src', source: '/root/src/B.js', loc: [{ start: 38, end: 38 }, { start: 40, end: 42 }] }, + { type: 'src', source: '/root/src/B.js' }, + { type: 'src', source: 'lib/rel.js' }, + { type: 'module', source: 'react' } + ], + passThroughExports: [{ type: 'src', source: '/root/src/C.js', loc: [{ start: 5, end: 5 }] }], + nonPassThroughExports: [{ type: 'module', source: 'lodash' }] + }, + { id: '/root/node_modules/dep/index.js' }, + {} + ] + }) + }); + + let res = await validateAndReadStats('/build', undefined, '/root', log); + + expect(res.files).toEqual([path.join('src', 'A.js'), path.join('src', 'B.js'), path.join('src', 'C.js')]); + expect(res.modules.length).toEqual(2); + expect(res.modules[0].id).toEqual(0); + expect(res.modules[0].imports[0].source).toEqual(1); + expect(res.modules[0].imports[1].source).toEqual(1); + expect(res.modules[0].imports[2].source).toEqual('lib/rel.js'); + expect(res.modules[0].imports[3].source).toEqual('react'); + expect(res.modules[0].imports[0].loc).toEqual([[38, 38], [40, 42]]); + expect(res.modules[0].passThroughExports[0].source).toEqual(2); + expect(res.modules[0].passThroughExports[0].loc).toEqual([[5, 5]]); + expect(res.modules[0].nonPassThroughExports).toEqual([{ type: 'module', source: 'lodash' }]); + expect(res.modules[1]).toEqual({}); + }); + }); + + describe('getBaselineAndAffectedNodes()', () => { + const log = mockLog(); + + it('uses an explicit baseline but still calls the API to check for a browser upgrade', async () => { + let lookup = jasmine.createSpy('getIntelliStorySnapshotNameToCommit') + .and.resolveTo({ browser_upgrade: false }); + let percy = { client: { getIntelliStorySnapshotNameToCommit: lookup } }; + + let res = await getBaselineAndAffectedNodes(percy, 'HEAD', log); + + expect(res.baseRef).toEqual('HEAD'); + expect(res.baselineSnapshots).toBeNull(); + expect(res.affectedNodes).toEqual([]); + expect(lookup).toHaveBeenCalled(); + }); + + it('tolerates the API returning no base lookup when an explicit baseline is set', async () => { + let percy = { client: { getIntelliStorySnapshotNameToCommit: async () => undefined } }; + + let res = await getBaselineAndAffectedNodes(percy, 'HEAD', log); + + expect(res.baseRef).toEqual('HEAD'); + expect(res.baselineSnapshots).toBeNull(); + }); + + it('bails when the base lookup reports a browser upgrade, even with an explicit baseline', async () => { + let percy = { + client: { + getIntelliStorySnapshotNameToCommit: async () => ({ + browser_upgrade: true, + base_build_commit_sha: 'HEAD' + }) + } + }; + await expectBail( + () => getBaselineAndAffectedNodes(percy, 'HEAD', log), + 'this build corresponds to a browser upgrade'); + }); + + it('falls back to the predicted base build commit when no baseline is set', async () => { + let percy = { + client: { + getIntelliStorySnapshotNameToCommit: async () => ({ + base_build_commit_sha: 'HEAD', + snapshots: { 'Button: primary': 'approved' } + }) + } + }; + + let res = await getBaselineAndAffectedNodes(percy, undefined, log); + + expect(res.baseRef).toEqual('HEAD'); + expect(res.affectedNodes).toEqual([]); + expect(res.baselineSnapshots).toEqual({ 'Button: primary': 'approved' }); + }); + + it('defaults baselineSnapshots to {} when the API omits the snapshot map', async () => { + let percy = { client: { getIntelliStorySnapshotNameToCommit: async () => ({ base_build_commit_sha: 'HEAD' }) } }; + let res = await getBaselineAndAffectedNodes(percy, undefined, log); + expect(res.baseRef).toEqual('HEAD'); + expect(res.baselineSnapshots).toEqual({}); + }); + + it('bails when the API predicts no base commit and no baseline is set', async () => { + let percy = { client: { getIntelliStorySnapshotNameToCommit: async () => ({}) } }; + await expectBail( + () => getBaselineAndAffectedNodes(percy, undefined, log), + 'could not predict a base build commit'); + }); + + it('bails on an unsafe baseline ref before shelling out to git', async () => { + let percy = { client: { getIntelliStorySnapshotNameToCommit: async () => ({}) } }; + await expectBail( + () => getBaselineAndAffectedNodes(percy, '--upload-pack=evil', log), + 'unsafe baseline ref'); + }); + }); + + describe('assertNoDotStorybookChange()', () => { + it('throws when a changed path lives under .storybook', () => { + expect(() => assertNoDotStorybookChange(['src/a.js', '.storybook/preview.js'])) + .toThrowMatching(e => e instanceof IntelliStoryBailError && e.message.includes('.storybook')); + }); + + it('matches a .storybook segment regardless of separator', () => { + expect(() => assertNoDotStorybookChange(['a\\.storybook\\main.js'])).toThrow(); + }); + + it('does not throw when nothing touches .storybook', () => { + expect(() => assertNoDotStorybookChange(['src/a.js', 'src/b.css'])).not.toThrow(); + }); + }); + + describe('assertNoBailOnChanges()', () => { + it('is a no-op when no patterns are configured', () => { + expect(() => assertNoBailOnChanges(['yarn.lock'], undefined)).not.toThrow(); + expect(() => assertNoBailOnChanges(['yarn.lock'], [])).not.toThrow(); + }); + + it('bails when a changed file matches a glob pattern', () => { + expect(() => assertNoBailOnChanges(['yarn.lock'], ['*.lock'])) + .toThrowMatching(e => e instanceof IntelliStoryBailError && e.message.includes('yarn.lock')); + }); + + it('bails on an exact (non-glob) pattern match', () => { + expect(() => assertNoBailOnChanges(['config/settings.js'], ['config/settings.js'])).toThrow(); + }); + + it('bails when a changed file matches a brace-expansion glob', () => { + expect(() => assertNoBailOnChanges(['src/b.js'], ['src/{a,b}.js'])) + .toThrowMatching(e => e instanceof IntelliStoryBailError && e.message.includes('src/b.js')); + }); + + it('bails when a changed file matches a bracket-set glob', () => { + expect(() => assertNoBailOnChanges(['src/a.ts'], ['src/a.[jt]s'])) + .toThrowMatching(e => e instanceof IntelliStoryBailError && e.message.includes('src/a.ts')); + }); + + it('does not bail when nothing matches', () => { + expect(() => assertNoBailOnChanges(['src/index.js'], ['*.css'])).not.toThrow(); + }); + + it('treats an over-long glob as non-matching instead of throwing', () => { + expect(() => assertNoBailOnChanges(['yarn.lock'], ['*'.repeat(600)])).not.toThrow(); + }); + }); + + describe('enforceUntraced()', () => { + it('returns the list unchanged when no patterns are configured', () => { + let nodes = ['src/a.js', 'docs/readme.md']; + expect(enforceUntraced(nodes, undefined)).toEqual(nodes); + expect(enforceUntraced(nodes, [])).toEqual(nodes); + }); + + it('drops paths matching an untraced glob', () => { + let nodes = ['src/a.js', 'docs/readme.md', 'CHANGELOG.md']; + expect(enforceUntraced(nodes, ['**/*.md'])).toEqual(['src/a.js']); + }); + + it('keeps paths that do not match', () => { + let nodes = ['src/a.snap', 'src/a.js']; + expect(enforceUntraced(nodes, ['*.snap'])).toEqual(['src/a.snap', 'src/a.js']); + }); + + it('drops paths matching a brace-expansion glob', () => { + let nodes = ['src/a.js', 'src/b.js', 'src/c.js']; + expect(enforceUntraced(nodes, ['src/{a,b}.js'])).toEqual(['src/c.js']); + }); + + it('drops paths matching a bracket-set glob', () => { + let nodes = ['docs/a.md', 'docs/b.md', 'src/a.js']; + expect(enforceUntraced(nodes, ['docs/[ab].md'])).toEqual(['src/a.js']); + }); + }); + + describe('getAffectedPackages()', () => { + const log = mockLog(); + + it('returns [] when no manifest files changed', async () => { + expect(await getAffectedPackages(['src/a.js', 'src/b.css'], 'HEAD', '/root', log)).toEqual([]); + }); + + it('bails when manifest changes span multiple directories', async () => { + await expectBail( + () => getAffectedPackages(['package.json', 'sub/package.json'], 'HEAD', '/root', log), + 'span multiple directories'); + }); + + it('bails when the manifest dir has no lockfile', async () => { + await mockfs({ '/root/pkg': null }); + await expectBail( + () => getAffectedPackages(['pkg/package.json'], 'HEAD', '/root', log), + 'no lockfile present there'); + }); + + it('bails when the manifest dir has multiple lockfiles', async () => { + await mockfs({ + '/root/pkg/yarn.lock': 'yarn', + '/root/pkg/package-lock.json': '{}' + }); + await expectBail( + () => getAffectedPackages(['pkg/package.json'], 'HEAD', '/root', log), + 'multiple lockfiles'); + }); + }); + + describe('extractStorybookPaths()', () => { + it('maps, dedupes and drops snapshots without an importPath', () => { + let log = mockLog(); + let snapshots = [ + { importPath: 'src/A.stories.js' }, + { importPath: 'src/A.stories.js' }, + { importPath: 'src/B.stories.js' }, + { name: 'no-path' } + ]; + expect(extractStorybookPaths(snapshots, identity, log)) + .toEqual(['src/A.stories.js', 'src/B.stories.js']); + }); + + it('warns when no snapshot carries an importPath', () => { + let log = mockLog(); + expect(extractStorybookPaths([{ name: 'x' }], identity, log)).toEqual([]); + expect(log.warn).toHaveBeenCalledTimes(1); + }); + + it('warns with an empty sample when given no snapshots at all', () => { + let log = mockLog(); + expect(extractStorybookPaths([], identity, log)).toEqual([]); + expect(log.warn).toHaveBeenCalledTimes(1); + }); + }); + + describe('runGraphGeneration()', () => { + it('starts the job and resolves once the graph is done', async () => { + let log = mockLog(); + let generate = jasmine.createSpy('generateIntelliStoryGraph'); + let data = { affected_stories: ['src/A.stories.js'] }; + let percy = { + client: { + generateIntelliStoryGraph: generate, + getStatus: async () => ({ status: 'done', data }) + } + }; + + let payload = { + files: ['f'], + modules: [{ id: 0 }], + storybookPaths: ['p'], + affectedNodes: ['a'], + affectedFileLocations: { 0: [[3, 3], [6, 7]] } + }; + + // selection is server-side now, so nothing is returned — it just + // enqueues generation and resolves once the job reaches `done`. + await runGraphGeneration(percy, 'bld-1', payload, log); + + expect(generate).toHaveBeenCalledWith('bld-1', payload); + }); + + it('bails when the job does not reach done', async () => { + let log = mockLog(); + let percy = { + client: { + generateIntelliStoryGraph: async () => {}, + getStatus: async () => ({ status: 'failed' }) + } + }; + await expectBail( + () => runGraphGeneration(percy, 'bld-1', { files: [], modules: [], storybookPaths: [], affectedNodes: [] }, log), + 'did not complete'); + }); + }); + + describe('maybeWriteTrace()', () => { + const fullData = { + affected_stories: [], + vertices: [{ kind: 'component', file_path: 'A.jsx' }], + edges: [], + transitive_closure_matrix_sparse: [] + }; + + it('renders and writes trace.html when enabled with a complete payload', () => { + let log = mockLog(); + let write = spyOn(fs, 'writeFileSync'); + + maybeWriteTrace(true, fullData, log); + + expect(write).toHaveBeenCalledTimes(1); + let [tracePath, html] = write.calls.mostRecent().args; + expect(tracePath).toEqual(path.resolve(process.cwd(), 'trace.html')); + expect(html).toContain('const vertices'); + expect(log.info).toHaveBeenCalled(); + }); + + it('does nothing when trace is disabled', () => { + let log = mockLog(); + let write = spyOn(fs, 'writeFileSync'); + maybeWriteTrace(false, fullData, log); + expect(write).not.toHaveBeenCalled(); + }); + + it('does nothing when the graph payload is incomplete', () => { + let log = mockLog(); + let write = spyOn(fs, 'writeFileSync'); + maybeWriteTrace(true, { affected_stories: [], vertices: [], edges: [] }, log); + expect(write).not.toHaveBeenCalled(); + }); + + it('warns (without throwing) when the write fails', () => { + let log = mockLog(); + spyOn(fs, 'writeFileSync').and.throwError('disk full'); + expect(() => maybeWriteTrace(true, fullData, log)).not.toThrow(); + expect(log.warn).toHaveBeenCalled(); + }); + }); + + describe('writeIntelliStoryTrace()', () => { + beforeEach(() => jasmine.clock().install()); + afterEach(() => jasmine.clock().uninstall()); + + const fullData = { + affected_stories: [], + vertices: [{ kind: 'component', file_path: 'A.jsx' }], + edges: [], + transitive_closure_matrix_sparse: [] + }; + + // Flush microtasks between clock ticks so the poll loop advances. + async function drainPolls(promise, rounds = 20) { + for (let i = 0; i < rounds; i++) { + await Promise.resolve(); + await Promise.resolve(); + jasmine.clock().tick(5000); + } + return promise; + } + + it('is a no-op when trace is disabled (defaults its logger and config)', async () => { + let getStatus = jasmine.createSpy('getStatus'); + // no config and no log arg — exercises `intelliStoryConfig || {}` and the default logger param + await writeIntelliStoryTrace({ build: { id: '1' }, client: { getStatus } }); + expect(getStatus).not.toHaveBeenCalled(); + }); + + it('is a no-op when the Percy build was never created', async () => { + let log = mockLog(); + let getStatus = jasmine.createSpy('getStatus'); + await writeIntelliStoryTrace({ client: { getStatus } }, { trace: true }, log); + expect(getStatus).not.toHaveBeenCalled(); + }); + + it('skips the trace when the graph reports failed', async () => { + let log = mockLog(); + let write = spyOn(fs, 'writeFileSync'); + let percy = { build: { id: '1' }, client: { getStatus: async () => ({ status: 'failed' }) } }; + await writeIntelliStoryTrace(percy, { trace: true }, log); + expect(write).not.toHaveBeenCalled(); + expect(log.debug).toHaveBeenCalled(); + }); + + it('skips the trace when polling times out', async () => { + let log = mockLog(); + let write = spyOn(fs, 'writeFileSync'); + let percy = { build: { id: '1' }, client: { getStatus: async () => ({ status: 'in_progress' }) } }; + await drainPolls(writeIntelliStoryTrace(percy, { trace: true }, log)); + expect(write).not.toHaveBeenCalled(); + }); + + it('fetches the finalized graph data and writes the trace when done', async () => { + let log = mockLog(); + let write = spyOn(fs, 'writeFileSync'); + let percy = { build: { id: '1' }, client: { getStatus: async () => ({ status: 'done', data: fullData }) } }; + await writeIntelliStoryTrace(percy, { trace: true }, log); + expect(write).toHaveBeenCalledTimes(1); + }); + }); + + describe('runGraphGeneration() polling', () => { + beforeEach(() => jasmine.clock().install()); + afterEach(() => jasmine.clock().uninstall()); + + async function drainPolls(promise, rounds = 20) { + for (let i = 0; i < rounds; i++) { + await Promise.resolve(); + await Promise.resolve(); + jasmine.clock().tick(5000); + } + return promise; + } + + it('retries while in_progress and resolves once the job is done', async () => { + let log = mockLog(); + let data = { affected_stories: [] }; + let seq = ['in_progress', 'in_progress', 'done']; + let i = 0; + let percy = { + client: { + generateIntelliStoryGraph: async () => {}, + getStatus: async () => { + let s = seq[Math.min(i++, seq.length - 1)]; + return s === 'done' ? { status: 'done', data } : { status: s }; + } + } + }; + + let p = runGraphGeneration(percy, 'bld-1', { files: [], modules: [], storybookPaths: [], affectedNodes: [] }, log); + await expectAsync(drainPolls(p)).toBeResolved(); + }); + + it('bails after the poll loop times out without reaching done', async () => { + let log = mockLog(); + let percy = { + client: { + generateIntelliStoryGraph: async () => {}, + getStatus: async () => ({ status: 'in_progress' }) + } + }; + + let p = runGraphGeneration(percy, 'bld-1', { files: [], modules: [], storybookPaths: [], affectedNodes: [] }, log); + let err; + await drainPolls(p).catch(e => { err = e; }); + expect(err).toBeInstanceOf(IntelliStoryBailError); + expect(err.message).toContain('did not complete'); + }); + }); + + describe('getAffectedPackages() lockfile diff', () => { + let origCwd = process.cwd(); + let repos = []; + afterEach(() => { + process.chdir(origCwd); + for (let d of repos.splice(0)) fs.rmSync(d, { recursive: true, force: true }); + }); + + it('reads both lockfile sides and runs the diff (bails on Node <18 where snyk is unavailable)', async () => { + let log = mockLog(); + let { dir, baseSha } = makeRepo( + { + 'pkg/package.json': JSON.stringify({ name: 'x', dependencies: { 'left-pad': '^1.0.0' } }), + 'pkg/yarn.lock': 'left-pad@^1.0.0:\n version "1.1.0"\n' + }, + { 'pkg/yarn.lock': 'left-pad@^1.0.0:\n version "1.2.0"\n' }); + repos.push(dir); + process.chdir(dir); + + let res; + try { + res = await getAffectedPackages(['pkg/yarn.lock'], baseSha, dir, log); + } catch (e) { + res = e; + } + + if (NODE_MAJOR >= 18) { + expect(res).toBeDefined(); + } else { + expect(res).toBeInstanceOf(IntelliStoryBailError); + expect(res.message).toContain('snyk-nodejs-lockfile-parser is not available'); + } + }); + + it('returns [] when only package.json (no lockfile content) changed', async () => { + let log = mockLog(); + let { dir, baseSha } = makeRepo( + { 'pkg/package.json': '{"name":"x"}', 'pkg/yarn.lock': 'left-pad@^1.0.0:\n version "1.1.0"\n' }, + { 'pkg/package.json': '{"name":"x","version":"2.0.0"}' }); + repos.push(dir); + process.chdir(dir); + + expect(await getAffectedPackages(['pkg/package.json'], baseSha, dir, log)).toEqual([]); + }); + + it('bails when the lockfile was not tracked at the base ref', async () => { + let log = mockLog(); + let { dir, baseSha } = makeRepo( + { 'pkg/package.json': '{"name":"x"}' }, + { 'pkg/yarn.lock': 'left-pad@^1.0.0:\n version "1.2.0"\n' }); + repos.push(dir); + process.chdir(dir); + + await expectBail( + () => getAffectedPackages(['pkg/yarn.lock'], baseSha, dir, log), + 'not present at base ref'); + }); + + it('handles a lockfile at the repo root (manifestDir ".")', async () => { + let log = mockLog(); + let { dir, baseSha } = makeRepo( + { 'package.json': JSON.stringify({ name: 'x', dependencies: {} }), 'yarn.lock': 'a:\n version "1.0.0"\n' }, + { 'yarn.lock': 'a:\n version "2.0.0"\n' }); + repos.push(dir); + process.chdir(dir); + + let res; + try { + res = await getAffectedPackages(['yarn.lock'], baseSha, dir, log); + } catch (e) { + res = e; + } + if (NODE_MAJOR >= 18) expect(res).toBeDefined(); + else expect(res).toBeInstanceOf(IntelliStoryBailError); + }); + + it('bails when the current lockfile cannot be read', async () => { + let log = mockLog(); + let { dir, baseSha } = makeRepo( + { 'pkg/package.json': '{"name":"x"}', 'pkg/yarn.lock': 'a:\n version "1.0.0"\n' }, + { 'pkg/yarn.lock': 'a:\n version "2.0.0"\n' }); + repos.push(dir); + process.chdir(dir); + + spyOn(fs, 'readFileSync').and.throwError('EIO'); + await expectBail( + () => getAffectedPackages(['pkg/yarn.lock'], baseSha, dir, log), + 'failed to read lockfile'); + }); + + it('bails when package.json cannot be read', async () => { + let log = mockLog(); + let { dir, baseSha } = makeRepo( + { 'pkg/package.json': '{"name":"x"}', 'pkg/yarn.lock': 'a:\n version "1.0.0"\n' }, + { 'pkg/yarn.lock': 'a:\n version "2.0.0"\n' }); + repos.push(dir); + process.chdir(dir); + + let realRead = fs.readFileSync; + spyOn(fs, 'readFileSync').and.callFake((p, ...rest) => { + if (String(p).endsWith('package.json')) throw new Error('EIO'); + return realRead(p, ...rest); + }); + await expectBail( + () => getAffectedPackages(['pkg/yarn.lock'], baseSha, dir, log), + 'failed to read "package.json"'); + }); + }); + + describe('getAffectedFileLocations()', () => { + let origCwd = process.cwd(); + let repos = []; + afterEach(() => { + process.chdir(origCwd); + for (let d of repos.splice(0)) fs.rmSync(d, { recursive: true, force: true }); + }); + + function setup(seed, changed) { + let info = makeRepo(seed, changed); + repos.push(info.dir); + process.chdir(info.dir); + return info; + } + + it('maps changed line ranges to file index, skipping unindexed and deleted files', () => { + let { dir, baseSha } = setup( + { + 'src/A.js': 'a\nb\nc\nd\ne\n', + 'src/del.js': 'x\n' + }, + { + 'src/A.js': 'a\nb\nC\nd\ne\nf\ng\n', + 'src/B.js': 'new\n' + }); + + fs.rmSync(path.join(dir, 'src/del.js')); + git(['add', '-A'], dir); + git(['commit', '-qm', 'remove del'], dir); + + let res = getAffectedFileLocations(baseSha, ['src/A.js', 'src/del.js']); + + expect(res).toEqual({ 0: [[3, 3], [6, 7]] }); + }); + + it('ignores pure-deletion hunks that add no new lines', () => { + let { baseSha } = setup( + { 'src/A.js': '1\n2\n3\n' }, + { 'src/A.js': '1\n3\n' }); + + expect(getAffectedFileLocations(baseSha, ['src/A.js'])).toEqual({}); + }); + + it('returns an empty map when there is no diff', () => { + let { baseSha } = setup({ 'src/A.js': 'a\n' }); + + expect(getAffectedFileLocations(baseSha, ['src/A.js'])).toEqual({}); + }); + + it('bails on an unsafe base ref before shelling out to git', () => { + expect(() => getAffectedFileLocations('--upload-pack=evil', [])) + .toThrowMatching(e => e instanceof IntelliStoryBailError && e.message.includes('unsafe baseline ref')); + }); + }); + + describe('applyIntelliStory() [integration]', () => { + let origCwd = process.cwd(); + let repos = []; + afterEach(() => { + process.chdir(origCwd); + for (let d of repos.splice(0)) fs.rmSync(d, { recursive: true, force: true }); + }); + + function setup(seed, changed) { + let info = makeRepo(seed, changed); + repos.push(info.dir); + process.chdir(info.dir); + return info; + } + + const STATS = JSON.stringify({ buildId: 'bld-1', modules: [] }); + + it('bails when no build directory is provided', async () => { + await expectBail( + () => applyIntelliStory({ client: {} }, [], undefined, undefined), + 'requires the Storybook build directory'); + }); + + it('bails when the Percy build has not been created', async () => { + let { dir } = setup({ 'sb/enriched-stats.json': STATS, 'src/A.stories.jsx': 'v1' }); + await expectBail( + () => applyIntelliStory({ client: {} }, [{ name: 'A', importPath: 'src/A.stories.jsx' }], + { baseline: 'HEAD' }, path.join(dir, 'sb')), + 'Percy build was not created'); + }); + + it('bails when nothing is affected after filtering', async () => { + let { dir } = setup({ 'sb/enriched-stats.json': STATS, 'src/A.stories.jsx': 'v1' }); + await expectBail( + () => applyIntelliStory( + { client: { getIntelliStorySnapshotNameToCommit: async () => ({}) }, build: { id: '123' } }, + [{ name: 'A', importPath: 'src/A.stories.jsx' }], + { baseline: 'HEAD' }, path.join(dir, 'sb')), + 'no affected files or packages detected'); + }); + + itPosix('tags every snapshot for server-side selection and enqueues graph generation against the Percy build id', async () => { + let { dir, baseSha } = setup( + { 'sb/enriched-stats.json': STATS, 'src/A.stories.jsx': 'v1' }, + { 'src/A.stories.jsx': 'v2' }); + + let generate = jasmine.createSpy('generateIntelliStoryGraph'); + let percy = { + build: { id: '456' }, + client: { + generateIntelliStoryGraph: generate, + // job status no longer returns affected_stories during the run + getStatus: async () => ({ status: 'done', data: {} }), + // an explicit baseline is set, but the base lookup is always called + // now (to surface browser_upgrade) + getIntelliStorySnapshotNameToCommit: async () => ({}) + } + }; + let snapshots = [ + { name: 'A', importPath: 'src/A.stories.jsx' }, + { name: 'Dot', importPath: './src/Dot.stories.jsx' }, + { name: 'NoPath' }, + { name: 'Empty', importPath: '' } + ]; + + let result = await applyIntelliStory(percy, snapshots, { baseline: baseSha, trace: false }, path.join(dir, 'sb')); + + // all snapshots are returned (the API performs selection when they post) + expect(result.map(s => s.name).sort()).toEqual(['A', 'Dot', 'Empty', 'NoPath']); + // each is tagged for IntelliStory with its normalized storybook path + expect(result.every(s => s.intelliStory === true)).toBe(true); + expect(result.find(s => s.name === 'A').storybookPath).toEqual(path.join('src', 'A.stories.jsx')); + expect(result.find(s => s.name === 'Dot').storybookPath).toEqual(path.join('src', 'Dot.stories.jsx')); + // the graph is enqueued against the real Percy build id, not the stats UUID + expect(generate).toHaveBeenCalledWith('456', jasmine.any(Object)); + }); + + itPosix('disables IntelliStory for snapshots with a missing/failed/rejected baseline so they are always captured', async () => { + let { dir, baseSha } = setup( + { 'sb/enriched-stats.json': STATS, 'src/A.stories.jsx': 'v1' }, + { 'src/A.stories.jsx': 'v2' }); + + let percy = { + build: { id: '789' }, + client: { + generateIntelliStoryGraph: jasmine.createSpy('generateIntelliStoryGraph'), + getStatus: async () => ({ status: 'done', data: {} }), + // no explicit baseline: base commit + per-snapshot states come from the API + getIntelliStorySnapshotNameToCommit: async () => ({ + base_build_commit_sha: baseSha, + snapshots: { Approved: 'approved', Failed: 'failed', Rejected: 'rejected' } + }) + } + }; + let snapshots = [ + { name: 'Approved', importPath: 'src/A.stories.jsx' }, + { name: 'Failed', importPath: 'src/A.stories.jsx' }, + { name: 'Rejected', importPath: 'src/A.stories.jsx' }, + { name: 'Missing', importPath: 'src/A.stories.jsx' } + ]; + + let result = await applyIntelliStory(percy, snapshots, { trace: false }, path.join(dir, 'sb')); + let byName = Object.fromEntries(result.map(s => [s.name, s.intelliStory])); + + // approved baseline => IntelliStory selection applies + expect(byName.Approved).toBe(true); + // failed / rejected / missing baselines => always captured + expect(byName.Failed).toBe(false); + expect(byName.Rejected).toBe(false); + expect(byName.Missing).toBe(false); + }); + }); +}); diff --git a/packages/cli-command/test/lockfileDiff.test.js b/packages/cli-command/test/lockfileDiff.test.js new file mode 100644 index 000000000..c5a4c5dd0 --- /dev/null +++ b/packages/cli-command/test/lockfileDiff.test.js @@ -0,0 +1,103 @@ +import { diffLockfileDeps } from '../src/lockfileDiff.js'; + +function dep(version) { + return { version, resolved: `https://registry/${version}`, integrity: `sha512-${version}` }; +} + +function lockfile(deps) { + return JSON.stringify({ + name: 'fixture', + version: '1.0.0', + lockfileVersion: 1, + requires: true, + dependencies: deps + }); +} + +function packageJson(fields) { + return JSON.stringify({ name: 'fixture', version: '1.0.0', ...fields }); +} + +const nodeMajor = parseInt(process.versions.node.split('.')[0], 10); +const describeSnyk = nodeMajor >= 18 ? describe : xdescribe; + +describe('lockfileDiff', () => { + describe('diffLockfileDeps()', () => { + it('throws on an unsupported lockfile type', async () => { + await expectAsync(diffLockfileDeps({ + packageJson: packageJson({}), + oldPackageJson: packageJson({}), + oldLockfile: lockfile({}), + newLockfile: lockfile({}), + lockfileType: 'composer.lock' + })).toBeRejectedWithError(/Unsupported lockfile type: composer\.lock/); + }); + }); + + describeSnyk('diffLockfileDeps() with snyk parser', () => { + const diff = opts => diffLockfileDeps({ lockfileType: 'package-lock.json', ...opts }); + + it('flags a top-level dependency whose resolved version changed', async () => { + await expectAsync(diff({ + oldPackageJson: packageJson({ dependencies: { 'left-pad': '^1.0.0' } }), + packageJson: packageJson({ dependencies: { 'left-pad': '^1.0.0' } }), + oldLockfile: lockfile({ 'left-pad': dep('1.1.0') }), + newLockfile: lockfile({ 'left-pad': dep('1.2.0') }) + })).toBeResolvedTo(['left-pad']); + }); + + it('flags an added top-level dependency', async () => { + await expectAsync(diff({ + oldPackageJson: packageJson({ dependencies: {} }), + packageJson: packageJson({ dependencies: { 'left-pad': '^1.0.0' } }), + oldLockfile: lockfile({}), + newLockfile: lockfile({ 'left-pad': dep('1.2.0') }) + })).toBeResolvedTo(['left-pad']); + }); + + it('flags a removed top-level dependency', async () => { + await expectAsync(diff({ + oldPackageJson: packageJson({ dependencies: { 'left-pad': '^1.0.0' } }), + packageJson: packageJson({ dependencies: {} }), + oldLockfile: lockfile({ 'left-pad': dep('1.2.0') }), + newLockfile: lockfile({}) + })).toBeResolvedTo(['left-pad']); + }); + + it('flags a range-only bump even when the resolved version is identical', async () => { + await expectAsync(diff({ + oldPackageJson: packageJson({ dependencies: { 'left-pad': '^1.0.0' } }), + packageJson: packageJson({ dependencies: { 'left-pad': '^1.2.0' } }), + oldLockfile: lockfile({ 'left-pad': dep('1.2.0') }), + newLockfile: lockfile({ 'left-pad': dep('1.2.0') }) + })).toBeResolvedTo(['left-pad']); + }); + + it('returns an empty list when nothing changed', async () => { + await expectAsync(diff({ + oldPackageJson: packageJson({ dependencies: { 'left-pad': '^1.0.0' } }), + packageJson: packageJson({ dependencies: { 'left-pad': '^1.0.0' } }), + oldLockfile: lockfile({ 'left-pad': dep('1.2.0') }), + newLockfile: lockfile({ 'left-pad': dep('1.2.0') }) + })).toBeResolvedTo([]); + }); + + it('ignores changes to devDependencies', async () => { + await expectAsync(diff({ + oldPackageJson: packageJson({ devDependencies: { 'left-pad': '^1.0.0' } }), + packageJson: packageJson({ devDependencies: { 'left-pad': '^1.0.0' } }), + oldLockfile: lockfile({ 'left-pad': dep('1.1.0') }), + newLockfile: lockfile({ 'left-pad': dep('1.2.0') }) + })).toBeResolvedTo([]); + }); + + it('includes peerDependencies in the top-level gate', async () => { + await expectAsync(diff({ + oldPackageJson: packageJson({ peerDependencies: { react: '^17.0.0' } }), + packageJson: packageJson({ peerDependencies: { react: '^18.0.0' } }), + oldLockfile: lockfile({ react: dep('17.0.2') }), + newLockfile: lockfile({ react: dep('17.0.2') }) + })).toBeResolvedTo(['react']); + }); + }); +}); diff --git a/packages/cli-command/test/noRequireBinding.test.js b/packages/cli-command/test/noRequireBinding.test.js new file mode 100644 index 000000000..e1988b670 --- /dev/null +++ b/packages/cli-command/test/noRequireBinding.test.js @@ -0,0 +1,70 @@ +import fs from 'fs'; +import path from 'path'; + +function findRepoRoot() { + let dir = process.cwd(); + for (;;) { + if (fs.existsSync(path.join(dir, 'lerna.json')) && + fs.existsSync(path.join(dir, 'packages'))) return dir; + let parent = path.dirname(dir); + if (parent === dir) throw new Error('could not locate monorepo root'); + dir = parent; + } +} + +function collectSourceFiles(root) { + const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', 'coverage', 'test', '.nyc_output']); + const SRC_EXT = new Set(['.js', '.cjs', '.mjs']); + const files = []; + + const walk = (dir) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (entry.isDirectory()) { + if (!SKIP_DIRS.has(entry.name)) walk(path.join(dir, entry.name)); + } else if (SRC_EXT.has(path.extname(entry.name))) { + files.push(path.join(dir, entry.name)); + } + } + }; + + const packagesDir = path.join(root, 'packages'); + for (const pkg of fs.readdirSync(packagesDir, { withFileTypes: true })) { + if (!pkg.isDirectory()) continue; + const src = path.join(packagesDir, pkg.name, 'src'); + if (fs.existsSync(src)) walk(src); + } + + return files; +} + +const FORBIDDEN = /\b(?:const|let|var)\s+require\s*=\s*createRequire\b/; + +describe('source: no `require = createRequire` binding', () => { + const root = findRepoRoot(); + const files = collectSourceFiles(root); + + it('scans a non-trivial number of source files', () => { + expect(files.length).toBeGreaterThan(20); + }); + + it('never binds createRequire to a name `require` (breaks the packaged binary)', () => { + const violations = []; + + for (const file of files) { + const lines = fs.readFileSync(file, 'utf8').split('\n'); + lines.forEach((line, i) => { + if (FORBIDDEN.test(line)) { + violations.push(`${path.relative(root, file)}:${i + 1}: ${line.trim()}`); + } + }); + } + + expect(violations) + .withContext( + 'Bind createRequire to a non-`require` name (e.g. `const cjsRequire = ' + + 'createRequire(import.meta.url)`). Naming it `require` collides with Babel ' + + 'transforms and crashes the pkg binary with "_require is not a function":\n' + + violations.join('\n')) + .toEqual([]); + }); +}); diff --git a/packages/cli-upload/test/upload.test.js b/packages/cli-upload/test/upload.test.js index 503796e7b..c66af5c92 100644 --- a/packages/cli-upload/test/upload.test.js +++ b/packages/cli-upload/test/upload.test.js @@ -97,7 +97,9 @@ describe('percy upload', () => { regions: null, 'enable-layout': false, 'th-test-case-execution-id': null, - browsers: null + browsers: null, + 'intelli-story': null, + 'storybook-path': null }, relationships: { resources: { diff --git a/packages/client/src/client.js b/packages/client/src/client.js index a4aa6f396..6dbb78389 100644 --- a/packages/client/src/client.js +++ b/packages/client/src/client.js @@ -421,11 +421,48 @@ export class PercyClient { // Retrieves snapshot/comparison data by id. Requires a read access token. async getStatus(type, ids) { - if (!['snapshot', 'comparison'].includes(type)) throw new Error('Invalid type passed'); + if (!['snapshot', 'comparison', 'intelli_story_graph'].includes(type)) throw new Error('Invalid type passed'); this.log.debug(`Getting ${type} status for ids ${ids}`); return this.get(`job_status?sync=true&type=${type}&id=${ids.join()}`); } + async getIntelliStorySnapshotNameToCommit(buildId) { + this.log.debug('IntelliStory: looking up baselines...'); + const qs = new URLSearchParams(); + + if (buildId) qs.append('build_id', buildId); + if (this.env.git?.branch) qs.append('branch', this.env.git.branch); + if (this.env.target?.branch) qs.append('target_branch', this.env.target.branch); + if (this.env.git?.sha) qs.append('commit_sha', this.env.git.sha); + if (this.env.target?.commit) qs.append('target_commit_sha', this.env.target.commit); + if (this.env.pullRequest != null) qs.append('pull_request_number', String(this.env.pullRequest)); + if (this.env.partial) qs.append('partial', 'true'); + + const query = qs.toString(); + return this.get( + query ? `intelli_story/snapshot-name-to-commit?${query}` : 'intelli_story/snapshot-name-to-commit', + { identifier: 'intelli_story.snapshot_name_to_commit' } + ); + } + + async generateIntelliStoryGraph(buildId, { + files, + modules, + storybookPaths, + affectedNodes, + affectedFileLocations + } = {}) { + this.log.debug(`IntelliStory: enqueueing graph build for build ${buildId}...`); + return this.post('intelli_story/generate-graph', { + build_id: buildId, + files, + modules, + storybook_paths: storybookPaths, + affected_nodes: affectedNodes, + affected_file_locations: affectedFileLocations + }, { identifier: 'intelli_story.generate_graph' }); + } + // Returns device details enabled on project associated with given token async getDeviceDetails(buildId) { try { @@ -588,6 +625,8 @@ export class PercyClient { regions, algorithm, algorithmConfiguration, + intelliStory, + storybookPath, resources = [], meta } = {}) { @@ -626,7 +665,11 @@ export class PercyClient { 'enable-javascript': enableJavaScript || null, 'enable-layout': enableLayout || false, 'th-test-case-execution-id': thTestCaseExecutionId || null, - browsers: normalizeBrowsers(browsers) || null + browsers: normalizeBrowsers(browsers) || null, + // IntelliStory: when enabled, the API selects affected snapshots + // server-side using the story's source path. + 'intelli-story': intelliStory || null, + 'storybook-path': storybookPath || null }, relationships: { resources: { @@ -658,6 +701,21 @@ export class PercyClient { async sendSnapshot(buildId, options) { let { meta = {} } = options; let snapshot = await this.createSnapshot(buildId, options); + + // The API always creates the snapshot record now; when server-side SmartSnap + // selection skips it, the response carries `skipped-via-smartsnap: true` and + // there is nothing to upload or finalize. Tally kept vs skipped so the + // storybook flow can print an IntelliStory summary. + let skipped = !!snapshot?.data?.attributes?.['skipped-via-smartsnap']; + if (typeof options.intelliStory === 'boolean') { + this.intelliStoryStats ??= { kept: 0, skipped: 0 }; + this.intelliStoryStats[skipped ? 'skipped' : 'kept'] += 1; + } + + if (skipped) { + this.log.debug(`Snapshot skipped via SmartSnap, skipping upload: ${options.name}...`, meta); + return snapshot; + } meta.snapshotId = snapshot.data.id; let missing = snapshot.data.relationships?.['missing-resources']?.data; diff --git a/packages/client/test/client.test.js b/packages/client/test/client.test.js index 54406c81b..e4b10adc8 100644 --- a/packages/client/test/client.test.js +++ b/packages/client/test/client.test.js @@ -827,6 +827,138 @@ describe('PercyClient', () => { await expectAsync(client.getStatus('comparison', [3, 4])).toBeResolvedTo({ data: '<>' }); await expectAsync(client.getStatus('comparison', [5])).toBeResolvedTo({ data: '<>' }); }); + + it('gets intelli_story_graph status (sync response carries graph payload on completion)', async () => { + const path = '/job_status?sync=true&type=intelli_story_graph&id=build-1'; + const body = { + status: 'done', + data: { + affected_stories: ['src/Foo.stories.tsx'], + vertices: [{ kind: 'story', file_path: 'src/Foo.stories.tsx', changed: true }], + edges: [], + transitive_closure_matrix_sparse: [] + } + }; + api.reply(path, () => [200, body]); + + await expectAsync(client.getStatus('intelli_story_graph', ['build-1'])).toBeResolvedTo(body); + expect(api.requests[path][0].method).toBe('GET'); + }); + }); + + describe('#getIntelliStorySnapshotNameToCommit()', () => { + const stubEnv = (overrides = {}) => { + Object.defineProperty(client.env, 'git', { value: overrides.git ?? {}, configurable: true }); + Object.defineProperty(client.env, 'target', { value: overrides.target ?? {}, configurable: true }); + Object.defineProperty(client.env, 'pullRequest', { value: overrides.pullRequest ?? null, configurable: true }); + Object.defineProperty(client.env, 'partial', { value: overrides.partial ?? false, configurable: true }); + }; + + beforeEach(() => stubEnv()); + + it('issues a GET with no query params when env is empty', async () => { + const path = '/intelli_story/snapshot-name-to-commit'; + api.reply(path, () => [200, { data: { foo: 'sha-foo' } }]); + + await expectAsync( + client.getIntelliStorySnapshotNameToCommit() + ).toBeResolvedTo({ data: { foo: 'sha-foo' } }); + + expect(api.requests[path]).toBeDefined(); + expect(api.requests[path][0].method).toBe('GET'); + }); + + it('appends git/target/PR/partial context when present in env', async () => { + stubEnv({ + git: { branch: 'feature/x', sha: 'commit-sha-1' }, + target: { branch: 'main', commit: 'commit-sha-2' }, + pullRequest: 42, + partial: true + }); + + const expectedPath = '/intelli_story/snapshot-name-to-commit?' + [ + 'branch=feature%2Fx', + 'target_branch=main', + 'commit_sha=commit-sha-1', + 'target_commit_sha=commit-sha-2', + 'pull_request_number=42', + 'partial=true' + ].join('&'); + + api.reply(expectedPath, () => [200, { data: { a: 'sha-a' } }]); + + await expectAsync( + client.getIntelliStorySnapshotNameToCommit() + ).toBeResolvedTo({ data: { a: 'sha-a' } }); + + expect(api.requests[expectedPath]).toBeDefined(); + expect(api.requests[expectedPath][0].method).toBe('GET'); + }); + + it('includes pull_request_number=0 when env.pullRequest is 0 (not null)', async () => { + stubEnv({ pullRequest: 0 }); + + const expectedPath = '/intelli_story/snapshot-name-to-commit?pull_request_number=0'; + api.reply(expectedPath, () => [200, { data: {} }]); + + await expectAsync( + client.getIntelliStorySnapshotNameToCommit() + ).toBeResolvedTo({ data: {} }); + + expect(api.requests[expectedPath]).toBeDefined(); + }); + + it('includes the build_id when provided', async () => { + const expectedPath = '/intelli_story/snapshot-name-to-commit?build_id=bld-123'; + api.reply(expectedPath, () => [200, { data: { b: 'sha-b' } }]); + + await expectAsync( + client.getIntelliStorySnapshotNameToCommit('bld-123') + ).toBeResolvedTo({ data: { b: 'sha-b' } }); + + expect(api.requests[expectedPath]).toBeDefined(); + expect(api.requests[expectedPath][0].method).toBe('GET'); + }); + }); + + describe('#generateIntelliStoryGraph()', () => { + it('POSTs build_id and graph payload to intelli_story/generate-graph', async () => { + api.reply('/intelli_story/generate-graph', () => [202, { status: 'queued' }]); + + await expectAsync(client.generateIntelliStoryGraph('build-1', { + files: ['a.js', 'b.js'], + modules: [{ id: 1, name: 'mod' }], + storybookPaths: ['stories/a.js'], + affectedNodes: ['node-1'], + affectedFileLocations: { 0: [[3, 3], [6, 7]], 1: [[1, 1]] } + })).toBeResolvedTo({ status: 'queued' }); + + expect(api.requests['/intelli_story/generate-graph']).toBeDefined(); + expect(api.requests['/intelli_story/generate-graph'][0].method).toBe('POST'); + expect(api.requests['/intelli_story/generate-graph'][0].body).toEqual({ + build_id: 'build-1', + files: ['a.js', 'b.js'], + modules: [{ id: 1, name: 'mod' }], + storybook_paths: ['stories/a.js'], + affected_nodes: ['node-1'], + affected_file_locations: { 0: [[3, 3], [6, 7]], 1: [[1, 1]] } + }); + }); + + it('sends undefined fields when called without payload', async () => { + api.reply('/intelli_story/generate-graph', () => [202, { status: 'queued' }]); + + await expectAsync(client.generateIntelliStoryGraph('build-2')).toBeResolvedTo({ status: 'queued' }); + + expect(api.requests['/intelli_story/generate-graph'][0].body).toEqual({ build_id: 'build-2' }); + }); + + it('rejects when API returns an error', async () => { + api.reply('/intelli_story/generate-graph', () => [500, { error: 'boom' }]); + + await expectAsync(client.generateIntelliStoryGraph('build-3', {})) + .toBeRejected(); + }); }); describe('#getDeviceDetails()', () => { @@ -1227,7 +1359,9 @@ describe('PercyClient', () => { 'enable-javascript': true, 'enable-layout': true, 'th-test-case-execution-id': 'random-uuid', - browsers: null + browsers: null, + 'intelli-story': null, + 'storybook-path': null }, relationships: { resources: { @@ -1319,7 +1453,9 @@ describe('PercyClient', () => { 'enable-javascript': true, 'enable-layout': true, 'th-test-case-execution-id': 'random-uuid', - browsers: ['chrome', 'firefox', 'safari_on_iphone'] + browsers: ['chrome', 'firefox', 'safari_on_iphone'], + 'intelli-story': null, + 'storybook-path': null }, relationships: { resources: { @@ -1370,7 +1506,9 @@ describe('PercyClient', () => { 'enable-layout': false, regions: null, 'th-test-case-execution-id': null, - browsers: null + browsers: null, + 'intelli-story': null, + 'storybook-path': null }, relationships: { resources: { @@ -1443,7 +1581,9 @@ describe('PercyClient', () => { regions: null, 'enable-layout': false, 'th-test-case-execution-id': null, - browsers: null + browsers: null, + 'intelli-story': null, + 'storybook-path': null }, relationships: { resources: { @@ -1491,6 +1631,37 @@ describe('PercyClient', () => { await expectAsync(client.sendSnapshot(123, { name: 'test snapshot name' })).toBeResolved(); expect(api.requests['/snapshots/4567/finalize']).toBeDefined(); }); + + it('tallies IntelliStory kept snapshots and still finalizes them', async () => { + await expectAsync( + client.sendSnapshot(123, { name: 'kept one', intelliStory: true }) + ).toBeResolved(); + await expectAsync( + client.sendSnapshot(123, { name: 'kept two', intelliStory: false }) + ).toBeResolved(); + + expect(api.requests['/snapshots/4567/finalize']).toBeDefined(); + expect(client.intelliStoryStats).toEqual({ kept: 2, skipped: 0 }); + }); + + it('tallies IntelliStory skipped snapshots and does not upload or finalize', async () => { + api.reply('/builds/123/snapshots', () => [201, { + data: { id: '4567', attributes: { 'skipped-via-smartsnap': true } } + }]); + + await expectAsync( + client.sendSnapshot(123, { name: 'skipped one', intelliStory: true }) + ).toBeResolved(); + + expect(api.requests['/builds/123/resources']).toBeUndefined(); + expect(api.requests['/snapshots/4567/finalize']).toBeUndefined(); + expect(client.intelliStoryStats).toEqual({ kept: 0, skipped: 1 }); + }); + + it('does not tally when intelliStory is not set', async () => { + await expectAsync(client.sendSnapshot(123, { name: 'plain' })).toBeResolved(); + expect(client.intelliStoryStats).toBeUndefined(); + }); }); describe('#createComparison()', () => { @@ -2221,7 +2392,9 @@ describe('PercyClient', () => { regions: null, 'enable-layout': false, 'th-test-case-execution-id': null, - browsers: null + browsers: null, + 'intelli-story': null, + 'storybook-path': null }, relationships: { resources: { diff --git a/packages/core/src/config.js b/packages/core/src/config.js index 167d028ba..0fcf5abf9 100644 --- a/packages/core/src/config.js +++ b/packages/core/src/config.js @@ -557,6 +557,11 @@ export const snapshotSchema = { testCase: { $ref: '/config/snapshot#/properties/testCase' }, labels: { $ref: '/config/snapshot#/properties/labels' }, thTestCaseExecutionId: { $ref: '/config/snapshot#/properties/thTestCaseExecutionId' }, + // IntelliStory: injected per-snapshot by @percy/storybook so the API can + // do affected-story selection server-side. `storybookPath` is the + // project-relative path of the story's source module. + intelliStory: { type: 'boolean' }, + storybookPath: { type: 'string' }, browsers: { $ref: '/config/snapshot#/properties/browsers' }, reshuffleInvalidTags: { $ref: '/config/snapshot#/properties/reshuffleInvalidTags' }, regions: { $ref: '/config/snapshot#/properties/regions' }, diff --git a/packages/core/src/percy.js b/packages/core/src/percy.js index f8cfa6625..933215e9b 100644 --- a/packages/core/src/percy.js +++ b/packages/core/src/percy.js @@ -165,7 +165,7 @@ export class Percy { }; // generator methods are wrapped to autorun and return promises - for (let m of ['start', 'stop', 'flush', 'idle', 'snapshot', 'upload', 'replaySnapshot']) { + for (let m of ['start', 'stop', 'flush', 'idle', 'snapshot', 'upload', 'replaySnapshot', 'startBuild']) { // the original generator can be referenced with percy.yield. let method = (this.yield ||= {})[m] = this[m].bind(this); this[m] = (...args) => generatePromise(method(...args)); @@ -363,6 +363,19 @@ export class Percy { this._lockHandle = null; } + // Forces the snapshots queue to start, which creates the Percy build up + // front and populates `percy.build.id`. Normally, when uploads are delayed + // or deferred, the build is created lazily on the first flush. IntelliStory + // needs the real build id before any snapshots are taken so it can enqueue + // the affected-story graph against it. Safe to call more than once — the + // queue memoizes its start task, so the build is only created once. + async *startBuild() { + if (!this.readyState) return this.build; + if (this.build?.id || this.build?.error) return this.build; + yield this.#snapshots.start(); + return this.build; + } + // Resolves once snapshot and upload queues are idle async *idle() { yield* this.#discovery.idle(); diff --git a/packages/core/test/percy.test.js b/packages/core/test/percy.test.js index 2001b65a1..0b93c1be3 100644 --- a/packages/core/test/percy.test.js +++ b/packages/core/test/percy.test.js @@ -841,6 +841,55 @@ describe('Percy', () => { }); }); + describe('#startBuild()', () => { + it('returns the current build without creating one when percy has not started', async () => { + // readyState is null before start() + let result = await generatePromise(percy.yield.startBuild()); + expect(result).toBe(percy.build); + expect(api.requests['/builds']).toBeUndefined(); + }); + + it('returns the existing build without re-creating it when one already exists', async () => { + spyOn(percy.browser, 'launch'); + await percy.start(); + expect(percy.build.id).toBeDefined(); + let buildCount = api.requests['/builds'].length; + + let result = await generatePromise(percy.yield.startBuild()); + + expect(result).toBe(percy.build); + // the memoized queue start means no additional build is created + expect(api.requests['/builds'].length).toEqual(buildCount); + }); + + it('returns the build without re-creating it when a prior creation errored', async () => { + percy.readyState = 1; + percy.build = { error: 'build creation failed' }; + + let result = await generatePromise(percy.yield.startBuild()); + + expect(result).toEqual({ error: 'build creation failed' }); + expect(api.requests['/builds']).toBeUndefined(); + + // neutralize afterEach stop() for this hand-set state + percy.readyState = null; + }); + + it('creates the build up front when uploads are deferred', async () => { + percy = new Percy({ token: 'PERCY_TOKEN', snapshot: { widths: [1000] }, deferUploads: true }); + spyOn(percy.browser, 'launch'); + await percy.start(); + // deferred: the build is not created during start() + expect(percy.build?.id).toBeUndefined(); + + let result = await generatePromise(percy.yield.startBuild()); + + expect(percy.build.id).toBeDefined(); + expect(result).toBe(percy.build); + expect(api.requests['/builds']).toBeDefined(); + }); + }); + describe('#stop()', () => { // stop the previously started instance and clear requests async function reset(options) { diff --git a/yarn.lock b/yarn.lock index 09117dd1a..46b0f213e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,13 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@arcanis/slice-ansi@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@arcanis/slice-ansi/-/slice-ansi-1.1.1.tgz#0ee328a68996ca45854450033a3d161421dc4f55" + integrity sha512-xguP2WR2Dv0gQ7Ykbdb7BNCnPnIPB94uTi0Z2NvkRBEnhbwjOQ7QyQKJXrVQg4qDpiD9hA5l5cCwy/z2OXgc3w== + dependencies: + grapheme-splitter "^1.0.4" + "@babel/cli@^7.11.6": version "7.24.7" resolved "https://registry.npmjs.org/@babel/cli/-/cli-7.24.7.tgz" @@ -1180,6 +1187,13 @@ resolved "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz" integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + "@isaacs/string-locale-compare@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz" @@ -2260,6 +2274,18 @@ dependencies: esquery "^1.0.1" +"@pnpm/crypto.base32-hash@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@pnpm/crypto.base32-hash/-/crypto.base32-hash-1.0.1.tgz#e0eeff4ae736d2a781e41041206a65fe78704ffd" + integrity sha512-pzAXNn6KxTA3kbcI3iEnYs4vtH51XEVqmK/1EiD18MaPKylhqy8UvMJK3zKG+jeP82cqQbozcTGm4yOQ8i3vNw== + dependencies: + rfc4648 "^1.5.1" + +"@pnpm/types@8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@pnpm/types/-/types-8.9.0.tgz#9636d5f0642793432f72609b79458ca9be049b02" + integrity sha512-3MYHYm8epnciApn6w5Fzx6sepawmsNU7l6lvIq+ER22/DPSrr83YMhU/EQWnf4lORn2YyiXFj0FJSyJzEtIGmw== + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -2378,11 +2404,77 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz" integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== +"@sindresorhus/is@^4.0.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + +"@snyk/dep-graph@^2.12.0": + version "2.16.9" + resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-2.16.9.tgz#7d8e58c42d4596c184b98326b085c51493bf9198" + integrity sha512-sXjyY0+r+jwS1Tu5MSg2HyT4E/nEeo5zl9lxcmQIzfFl3jIUum5qxOj24XszJO8X3HgdmOByGQvi62Rkg9fePg== + dependencies: + event-loop-spinner "^2.1.0" + lodash.clone "^4.5.0" + lodash.constant "^3.0.0" + lodash.filter "^4.6.0" + lodash.foreach "^4.5.0" + lodash.isempty "^4.4.0" + lodash.isequal "^4.5.0" + lodash.isfunction "^3.0.9" + lodash.isundefined "^3.0.1" + lodash.map "^4.6.0" + lodash.reduce "^4.6.0" + lodash.size "^4.2.0" + lodash.transform "^4.6.0" + lodash.union "^4.6.0" + lodash.values "^4.3.0" + object-hash "^3.0.0" + packageurl-js "2.0.1" + semver "^7.0.0" + tslib "^2" + +"@snyk/error-catalog-nodejs-public@^5.16.0": + version "5.82.1" + resolved "https://registry.yarnpkg.com/@snyk/error-catalog-nodejs-public/-/error-catalog-nodejs-public-5.82.1.tgz#3d838523d6df309a8ab02e77a22c38820900ac69" + integrity sha512-fchNDV+LtJGEJVqtiPyOG3kaUT/LALohZygf32Uzly3UTaxbJvT7wJn5PZiDtP6A8+YJT8klyiYiH3lvdIRCgQ== + dependencies: + tslib "^2.8.1" + uuid "^11.1.0" + +"@snyk/graphlib@2.1.9-patch.3": + version "2.1.9-patch.3" + resolved "https://registry.yarnpkg.com/@snyk/graphlib/-/graphlib-2.1.9-patch.3.tgz#b8edb2335af1978db7f3cb1f28f5d562960acf23" + integrity sha512-bBY9b9ulfLj0v2Eer0yFYa3syVeIxVKl2EpxSrsVeT4mjA0CltZyHsF0JjoaGXP27nItTdJS5uVsj1NA+3aE+Q== + dependencies: + lodash.clone "^4.5.0" + lodash.constant "^3.0.0" + lodash.filter "^4.6.0" + lodash.foreach "^4.5.0" + lodash.has "^4.5.2" + lodash.isempty "^4.4.0" + lodash.isfunction "^3.0.9" + lodash.isundefined "^3.0.1" + lodash.keys "^4.2.0" + lodash.map "^4.6.0" + lodash.reduce "^4.6.0" + lodash.size "^4.2.0" + lodash.transform "^4.6.0" + lodash.union "^4.6.0" + lodash.values "^4.3.0" + "@socket.io/component-emitter@~3.1.0": version "3.1.2" resolved "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz" integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" @@ -2398,6 +2490,16 @@ resolved "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.4.5.tgz" integrity sha512-saiCxzHRhUrRxQV2JhH580aQUZiKQUXI38FcAcikcfOomAil4G4lxT0RfrrKywoAYP/rqAdYXYmNRLppcd+hQQ== +"@types/cacheable-request@^6.0.1": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" + integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "^3.1.4" + "@types/node" "*" + "@types/responselike" "^1.0.0" + "@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz" @@ -2408,6 +2510,11 @@ resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== +"@types/emscripten@^1.39.6": + version "1.41.5" + resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.41.5.tgz#5670e4b52b098691cb844b84ee48c9176699b68d" + integrity sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q== + "@types/eslint@^7.2.13": version "7.28.1" resolved "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.1.tgz" @@ -2431,6 +2538,11 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz" integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== +"@types/http-cache-semantics@*": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#f6a7788f438cbfde15f29acad46512b4c01913b3" + integrity sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q== + "@types/json-schema@*": version "7.0.9" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz" @@ -2441,6 +2553,13 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + "@types/minimatch@^3.0.3": version "3.0.3" resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz" @@ -2483,6 +2602,78 @@ resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz" integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== +"@types/responselike@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" + integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== + dependencies: + "@types/node" "*" + +"@types/semver@^7.1.0": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" + integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== + +"@types/treeify@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/treeify/-/treeify-1.0.3.tgz#f502e11e851b1464d5e80715d5ce3705ad864638" + integrity sha512-hx0o7zWEUU4R2Amn+pjCBQQt23Khy/Dk56gQU5xi5jtPL1h83ACJCeFaB2M/+WO1AntvWrSoVnnCAfI1AQH4Cg== + +"@types/yauzl@^2.9.1": + version "2.9.1" + resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz" + integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== + dependencies: + "@types/node" "*" + +"@yarnpkg/core@^4.4.1": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/core/-/core-4.9.0.tgz#9ffefd7a67f56222829aa556397dd7f013960013" + integrity sha512-vhJEVo423jAZBtU5CDe2HEkyNkEYfgMfukNQk1uyYFkP3OmCsuLzpyqbJCEXIg6Fy3YTrQg7kSCnjHbLed3toA== + dependencies: + "@arcanis/slice-ansi" "^1.1.1" + "@types/semver" "^7.1.0" + "@types/treeify" "^1.0.0" + "@yarnpkg/fslib" "^3.1.5" + "@yarnpkg/libzip" "^3.2.2" + "@yarnpkg/parsers" "^3.0.3" + "@yarnpkg/shell" "^4.1.3" + camelcase "^5.3.1" + chalk "^4.1.2" + ci-info "^4.0.0" + clipanion "^4.0.0-rc.2" + cross-spawn "^7.0.3" + diff "^5.1.0" + dotenv "^16.3.1" + es-toolkit "^1.39.7" + fast-glob "^3.2.2" + got "^11.7.0" + hpagent "^1.2.0" + micromatch "^4.0.2" + p-limit "^2.2.0" + semver "^7.1.2" + strip-ansi "^6.0.0" + tar "^7.5.3" + tinylogic "^2.0.0" + treeify "^1.1.0" + tslib "^2.4.0" + +"@yarnpkg/fslib@^3.1.2", "@yarnpkg/fslib@^3.1.3", "@yarnpkg/fslib@^3.1.5": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@yarnpkg/fslib/-/fslib-3.1.5.tgz#e06924ab0bb312abe4e1e23a462cd481c446741e" + integrity sha512-hXaPIWl5GZA+rXcx+yaKWUuePJruZuD+3A5A2X6paEBfFsyCD7oEp88lSMj1ym1ehBWUmhNH/YGOp+SrbmSBPg== + dependencies: + tslib "^2.4.0" + +"@yarnpkg/libzip@^3.2.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@yarnpkg/libzip/-/libzip-3.2.2.tgz#075a25ff850e898dfb1c68df515ddaf5809bbcbe" + integrity sha512-Kqxgjfy6SwwC4tTGQYToIWtUhIORTpkowqgd9kkMiBixor0eourHZZAggt/7N4WQKt9iCyPSkO3Xvr44vXUBAw== + dependencies: + "@types/emscripten" "^1.39.6" + "@yarnpkg/fslib" "^3.1.3" + tslib "^2.4.0" + "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz" @@ -2496,6 +2687,28 @@ js-yaml "^3.10.0" tslib "^2.4.0" +"@yarnpkg/parsers@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.3.tgz#624f35f242c1115a48beb1fd12aed610bcd8e823" + integrity sha512-mQZgUSgFurUtA07ceMjxrWkYz8QtDuYkvPlu0ZqncgjopQ0t6CNEo/OSealkmnagSUx8ZD5ewvezUwUuMqutQg== + dependencies: + js-yaml "^3.10.0" + tslib "^2.4.0" + +"@yarnpkg/shell@^4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@yarnpkg/shell/-/shell-4.1.3.tgz#a99a1bcfb8ca1d896440046b71d2b254d0712948" + integrity sha512-5igwsHbPtSAlLdmMdKqU3atXjwhtLFQXsYAG0sn1XcPb3yF8WxxtWxN6fycBoUvFyIHFz1G0KeRefnAy8n6gdw== + dependencies: + "@yarnpkg/fslib" "^3.1.2" + "@yarnpkg/parsers" "^3.0.3" + chalk "^4.1.2" + clipanion "^4.0.0-rc.2" + cross-spawn "^7.0.3" + fast-glob "^3.2.2" + micromatch "^4.0.2" + tslib "^2.4.0" + "@zkochan/js-yaml@0.0.6": version "0.0.6" resolved "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz" @@ -2792,6 +3005,11 @@ astral-regex@^2.0.0: resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +async@^3.2.2: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + async@^3.2.3: version "3.2.4" resolved "https://registry.npmjs.org/async/-/async-3.2.4.tgz" @@ -3051,6 +3269,24 @@ cacache@^16.0.0, cacache@^16.0.6, cacache@^16.1.0: tar "^6.1.11" unique-filename "^1.1.1" +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" + integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + caching-transform@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz" @@ -3121,7 +3357,7 @@ chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3154,11 +3390,21 @@ chownr@^2.0.0: resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-info@^4.0.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" + integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" @@ -3186,6 +3432,13 @@ cli-width@^3.0.0: resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== +clipanion@^4.0.0-rc.2: + version "4.0.0-rc.4" + resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-4.0.0-rc.4.tgz#7191a940e47ef197e5f18c9cbbe419278b5f5903" + integrity sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q== + dependencies: + typanion "^3.8.0" + cliui@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz" @@ -3222,6 +3475,13 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + clone@^1.0.2: version "1.0.4" resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" @@ -3616,6 +3876,13 @@ decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" @@ -3645,6 +3912,11 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" @@ -3697,6 +3969,16 @@ depd@^1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +dependency-path@^9.2.8: + version "9.2.8" + resolved "https://registry.yarnpkg.com/dependency-path/-/dependency-path-9.2.8.tgz#9fe05be8d69ad1943a2084e4d86f3063c4b50c01" + integrity sha512-S0OhIK7sIyAsph8hVH/LMCTDL3jozKtlrPx3dMQrlE2nAlXTquTT+AcOufphDMTQqLkfn4acvfiem9I1IWZ4jQ== + dependencies: + "@pnpm/crypto.base32-hash" "1.0.1" + "@pnpm/types" "8.9.0" + encode-registry "^3.0.0" + semver "^7.3.8" + deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz" @@ -3735,6 +4017,11 @@ diff-sequences@^29.4.3: resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff@^5.1.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.2.tgz#0a4742797281d09cfa699b79ea32d27723623bad" + integrity sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" @@ -3780,6 +4067,11 @@ dot-prop@^6.0.1: dependencies: is-obj "^2.0.0" +dotenv@^16.3.1: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + dotenv@~10.0.0: version "10.0.0" resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" @@ -3821,6 +4113,13 @@ emoji-regex@^8.0.0: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +encode-registry@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/encode-registry/-/encode-registry-3.0.1.tgz#cb925d0db14ce59b18882b62c67133721b0846d1" + integrity sha512-6qOwkl1g0fv0DN3Y3ggr2EaZXN71aoAqPp3p/pVaWSBSIo+YjLOWN61Fva43oVyQNPf7kgm8lkudzlzojwE2jw== + dependencies: + mem "^8.0.0" + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" @@ -4020,6 +4319,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es-toolkit@^1.39.7: + version "1.49.0" + resolved "https://registry.yarnpkg.com/es-toolkit/-/es-toolkit-1.49.0.tgz#93c5b031865792fc03cbf5bd20c132a4f976a52a" + integrity sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g== + es6-error@^4.0.1: version "4.1.1" resolved "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz" @@ -4286,6 +4590,13 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-loop-spinner@^2.0.0, event-loop-spinner@^2.1.0: + version "2.3.3" + resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-2.3.3.tgz#5730c7353e16dcc1cbf0cb485882906d27429332" + integrity sha512-1mFCR39pkNh0agtlKPXVOBM5Bq2qOC5Pz+wlqizcKPKq2XTreVGDgWBi+Iyb4mdA5nF+oLd6oqePI9AZ2K7xMw== + dependencies: + tslib "^2.6.3" + eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" @@ -4336,7 +4647,7 @@ fast-glob@3.2.7: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.2.11, fast-glob@^3.2.9: +fast-glob@^3.2.11, fast-glob@^3.2.2, fast-glob@^3.2.9: version "3.3.3" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz" integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== @@ -4773,6 +5084,11 @@ glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + glob@7.1.4: version "7.1.4" resolved "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz" @@ -4882,11 +5198,33 @@ gopd@^1.2.0: resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== +got@^11.7.0: + version "11.8.6" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.9" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + handlebars@^4.7.7: version "4.7.9" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.9.tgz#6f139082ab58dc4e5a0e51efe7db5ae890d56a0f" @@ -5001,11 +5339,21 @@ hosted-git-info@^5.0.0: dependencies: lru-cache "^7.5.1" +hpagent@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hpagent/-/hpagent-1.2.0.tgz#0ae417895430eb3770c03443456b8d90ca464903" + integrity sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA== + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +http-cache-semantics@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" + integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== + http-cache-semantics@^4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" @@ -5048,6 +5396,14 @@ http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + https-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz" @@ -5661,6 +6017,11 @@ jsesc@~0.5.0: resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" @@ -5811,6 +6172,13 @@ karma@^6.0.2: ua-parser-js "^0.7.30" yargs "^16.1.1" +keyv@^4.0.0: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" @@ -5927,36 +6295,126 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.clone@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg== + lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.constant@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash.constant/-/lodash.constant-3.0.0.tgz#bfe05cce7e515b3128925d6362138420bd624910" + integrity sha512-X5XMrB+SdI1mFa81162NSTo/YNd23SLdLOLzcXTwS4inDZ5YCL8X67UFzZJAH4CqIa6R8cr56CShfA5K5MFiYQ== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.filter@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" + integrity sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ== + +lodash.flatmap@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" + integrity sha512-/OcpcAGWlrZyoHGeHh3cAoa6nGdX6QYtmzNP84Jqol6UEQQ2gIaU3H+0eICcjcKGl0/XF8LWOujNn9lffsnaOg== + lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= +lodash.foreach@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ== + +lodash.has@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" + integrity sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g== + +lodash.isempty@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz" integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= +lodash.isundefined@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" + integrity sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA== + +lodash.keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" + integrity sha512-J79MkJcp7Df5mizHiVNpjoHXLi4HLjh9VLS/M7lQSGoQ+0oQ+lWEigREkqKyizPB1IawvQLLKY8mzEcm1tkyxQ== + +lodash.map@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + integrity sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.reduce@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" + integrity sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw== + +lodash.size@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.size/-/lodash.size-4.2.0.tgz#71fe75ed3eabdb2bcb73a1b0b4f51c392ee27b86" + integrity sha512-wbu3SF1XC5ijqm0piNxw59yCbuUf2kaShumYBLWUrcCvwh6C8odz6SY/wGVzCWTQTFL/1Ygbvqg2eLtspUVVAQ== + +lodash.topairs@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64" + integrity sha512-qrRMbykBSEGdOgQLJJqVSdPWMD7Q+GJJ5jMRfQYb+LTLsw3tYVIabnCzRqTJb2WTo17PG5gNzXuFaZgYH/9SAQ== + +lodash.transform@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.transform/-/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0" + integrity sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ== + lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== + +lodash.values@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q== + lodash@^4.17.15, lodash@^4.17.21, lodash@~4.17.10: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" @@ -6000,6 +6458,11 @@ long@^5.0.0: resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" @@ -6068,6 +6531,13 @@ make-fetch-happen@^10.0.3, make-fetch-happen@^10.0.6: socks-proxy-agent "^7.0.0" ssri "^9.0.0" +map-age-cleaner@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + map-obj@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" @@ -6088,6 +6558,14 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +mem@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/mem/-/mem-8.1.1.tgz#cf118b357c65ab7b7e0817bdf00c8062297c0122" + integrity sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA== + dependencies: + map-age-cleaner "^0.1.3" + mimic-fn "^3.1.0" + memfs@^3.4.0: version "3.4.12" resolved "https://registry.npmjs.org/memfs/-/memfs-3.4.12.tgz" @@ -6140,7 +6618,7 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^4.0.4, micromatch@^4.0.8: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -6170,6 +6648,21 @@ mimic-fn@^2.1.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" + integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== + +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" @@ -6300,6 +6793,11 @@ minipass@^5.0.0: resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== +minipass@^7.0.4, minipass@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" + integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== + minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" @@ -6308,6 +6806,13 @@ minizlib@^2.1.1, minizlib@^2.1.2: minipass "^3.0.0" yallist "^4.0.0" +minizlib@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c" + integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw== + dependencies: + minipass "^7.1.2" + mkdirp-infer-owner@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz" @@ -6477,6 +6982,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-bundled@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz" @@ -6664,6 +7174,11 @@ object-assign@^4: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-inspect@^1.13.1, object-inspect@^1.9.0: version "1.13.1" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz" @@ -6781,6 +7296,16 @@ os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" @@ -6914,6 +7439,11 @@ package-hash@^4.0.0: lodash.flattendeep "^4.4.0" release-zalgo "^1.0.0" +packageurl-js@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/packageurl-js/-/packageurl-js-2.0.1.tgz#a8fa43a64971b5dd0dca5fb904b950a6cc317a6f" + integrity sha512-N5ixXjzTy4QDQH0Q9YFjqIWd6zH6936Djpl2m9QNFmDv5Fum8q8BjkpAcHNMzOFE0IwQrFhJWex3AN6kS0OSwg== + pacote@^13.0.3, pacote@^13.6.1: version "13.6.1" resolved "https://registry.npmjs.org/pacote/-/pacote-13.6.1.tgz" @@ -7260,6 +7790,11 @@ quick-lru@^4.0.1: resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + range-parser@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" @@ -7483,6 +8018,11 @@ reselect@^4.1.7: resolved "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz" integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" @@ -7509,6 +8049,13 @@ resolve@^1.10.0, resolve@^1.10.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.2 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== + dependencies: + lowercase-keys "^2.0.0" + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" @@ -7527,6 +8074,11 @@ reusify@^1.0.4: resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfc4648@^1.5.1: + version "1.5.4" + resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.4.tgz#1174c0afba72423a0b70c386ecfeb80aa61b05ca" + integrity sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg== + rfdc@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz" @@ -7621,6 +8173,11 @@ semver@^7.0.0, semver@^7.1.1, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semve resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +semver@^7.1.2, semver@^7.3.8, semver@^7.6.0: + version "7.8.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" + integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" @@ -7725,6 +8282,40 @@ smart-buffer@^4.2.0: resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +snyk-config@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-5.3.0.tgz#5ea906ad1c9c6151dd87c4e523c1d9659f5cf0e3" + integrity sha512-YPxhYZXBXgnYdvovwlKf5JYcOp+nxB7lel3tWLarYqZ4hwxN118FodIFb8nSqMrepsPdyOaQYKKnrTYqvQeaJA== + dependencies: + async "^3.2.2" + debug "^4.3.4" + lodash.merge "^4.6.2" + minimist "^1.2.6" + +snyk-nodejs-lockfile-parser@2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-2.7.1.tgz#e175501c9691f70c6a32b590956e680ce0c0048b" + integrity sha512-ViG434ZhiWXRtAEXVS2yjkHKz6Yk1lj9FxyMYWjRDZN/VplvrTGhkI/6BISgKotkR9h+QPsmVHiwWdoeVoqRog== + dependencies: + "@snyk/dep-graph" "^2.12.0" + "@snyk/error-catalog-nodejs-public" "^5.16.0" + "@snyk/graphlib" "2.1.9-patch.3" + "@yarnpkg/core" "^4.4.1" + "@yarnpkg/lockfile" "^1.1.0" + dependency-path "^9.2.8" + event-loop-spinner "^2.0.0" + js-yaml "^4.1.0" + lodash.clonedeep "^4.5.0" + lodash.flatmap "^4.5.0" + lodash.isempty "^4.4.0" + lodash.topairs "^4.3.0" + micromatch "^4.0.8" + p-map "^4.0.0" + semver "^7.6.0" + snyk-config "^5.2.0" + tslib "^1.9.3" + uuid "^8.3.0" + socket.io-adapter@~2.5.2: version "2.5.5" resolved "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz" @@ -8102,6 +8693,17 @@ tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: mkdirp "^1.0.3" yallist "^4.0.0" +tar@^7.5.3: + version "7.5.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.19.tgz#d8915e6b717f8036a79d839ca9448198b002b0a7" + integrity sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.1.0" + yallist "^5.0.0" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz" @@ -8146,6 +8748,11 @@ through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +tinylogic@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tinylogic/-/tinylogic-2.0.0.tgz#0d2409c492b54c0663082ac1e3f16be64497bb47" + integrity sha512-dljTkiLLITtsjqBvTA1MRZQK/sGP4kI3UJKc3yA9fMzYbMF2RhcN04SeROVqJBIYYOoJMM8u0WDnhFwMSFQotw== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" @@ -8182,6 +8789,11 @@ tr46@~0.0.3: resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +treeify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" + integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== + treeverse@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/treeverse/-/treeverse-2.0.0.tgz" @@ -8220,11 +8832,21 @@ tsd@^0.31.2: path-exists "^4.0.0" read-pkg-up "^7.0.0" -tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.8.1: +tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.6.3, tslib@^2.8.1: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +typanion@^3.8.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/typanion/-/typanion-3.14.0.tgz#a766a91810ce8258033975733e836c43a2929b94" + integrity sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" @@ -8438,12 +9060,17 @@ utils-merge@1.0.1: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid@^11.1.0: + version "11.1.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.1.tgz#f6d81d2e1c65d00762e5e29b16c5d2d995e208ad" + integrity sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ== + uuid@^3.3.3: version "3.4.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.2: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -8698,6 +9325,11 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== + yaml@^1.10.0: version "1.10.2" resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"