mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
perf(test): faster local test loop and cold-worktree changed runs (#33421)
- test:changed exits before starting Vitest when Git reports no changed files. - Unrecorded tests beyond 100 in scope use Vitest's static import graph instead of all running, so a fresh worktree's first changed run no longer runs the whole suite (patched RecordedModulesResolver; CI does not record, unchanged). - Bun's slow config load was the SQLite import scan statting every unit test; filtered runs now classify only the tests their filters can select, and the scan walks directories instead of fs.globSync. - Silence the native config loader notice on every run. - Zod's test bundle is content-addressed and reused, so a killed run leaks nothing; leaked per-run directories older than 6h are swept. - build:packages skips compiling when package inputs, compiler, and dist are unchanged; test scripts no longer nest pnpm invocations.
This commit is contained in:
+12
-5
@@ -78,14 +78,16 @@ pnpm test src/shared/app-version.test.ts --maxWorkers=2
|
||||
`test:changed` considers staged, unstaged, and untracked files. `--changed=origin/main`
|
||||
also includes committed changes since the branch's merge base. Update that ref when
|
||||
you need a newer baseline. Both accept the same path filters and options as `pnpm test`.
|
||||
When Git reports no changed files at all, the launcher exits before starting Vitest.
|
||||
|
||||
Local runs enable Vitest's [dependency recording](https://vitest.dev/config/experimental#experimental-recorddependencies).
|
||||
Ordinary test runs populate the cache; later changed-file runs read it instead of
|
||||
transforming every test's import graph. Records are separate for the Bun and Node
|
||||
projects. A test without a record always runs, so a cold cache runs everything in
|
||||
the requested scope. Start with a narrow path to avoid a full cold run. Keep Vite's
|
||||
local test cache between runs. Use `--experimental.recordDependencies=false` to
|
||||
return to Vitest's static import graph.
|
||||
projects and live in each checkout's `node_modules/.vite`. A test without a record
|
||||
runs when at most 100 such tests are in scope, which records them. Beyond that, as in
|
||||
a new worktree, unrecorded tests are selected with Vitest's static import graph
|
||||
instead of running all of them. Keep Vite's local test cache between runs. Use
|
||||
`--experimental.recordDependencies=false` to use the static import graph for every test.
|
||||
|
||||
This is local feedback, not a complete regression check. Vitest records loaded
|
||||
imports, including executed dynamic imports, but cannot infer arbitrary filesystem
|
||||
@@ -94,7 +96,12 @@ for changed fixtures, generated files, CLI entry points, and other indirect inpu
|
||||
After changing imports, run the affected suites before committing those changes so
|
||||
later edits use current records. Configuration, lockfile, patch, and workspace-package
|
||||
changes rerun every test within the requested scope. CI does not enable recording;
|
||||
the existing CI selection policy is unchanged.
|
||||
the existing CI selection policy is unchanged. The static graph has the same blind spots
|
||||
and also misses dynamic imports with computed specifiers, so the first changed-file run
|
||||
in a new worktree is a little less complete than later ones.
|
||||
|
||||
`pnpm build:packages` (which `pnpm test` runs first) skips compiling workspace packages
|
||||
when their sources, build script, and compiler are unchanged and `dist` is intact.
|
||||
|
||||
Vitest also supports [`related --run <source-files>`](https://vitest.dev/guide/cli#vitest-related)
|
||||
and dependency-aware watch mode. `pnpm test --changed` uses Git to provide the source
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
diff --git a/dist/chunks/cli-api.De07GUeT.js b/dist/chunks/cli-api.De07GUeT.js
|
||||
index ff6e63043ff8fa0b821d4b0b382d1fd998d85dbb..a95172c2a451f615f9472a526d7ed8a670256b47 100644
|
||||
--- a/dist/chunks/cli-api.De07GUeT.js
|
||||
+++ b/dist/chunks/cli-api.De07GUeT.js
|
||||
@@ -10250,6 +10250,7 @@ function groupFilters(filters) {
|
||||
* Finds test specifications that loaded any of the changed files during their last recorded run.
|
||||
* A specification without a record is always affected.
|
||||
*/
|
||||
+const UNRECORDED_RUN_LIMIT = 100;
|
||||
class RecordedModulesResolver {
|
||||
vitest;
|
||||
related;
|
||||
@@ -10267,13 +10268,30 @@ class RecordedModulesResolver {
|
||||
projectSpecs.push(spec);
|
||||
}
|
||||
const affected = /* @__PURE__ */ new Set();
|
||||
- for (const [project, projectSpecs] of specsByProject) this.findAffectedInProject(project, projectSpecs).forEach((spec) => affected.add(spec));
|
||||
+ const unrecorded = [];
|
||||
+ for (const [project, projectSpecs] of specsByProject) {
|
||||
+ const result = this.findAffectedInProject(project, projectSpecs);
|
||||
+ result.affected.forEach((spec) => affected.add(spec));
|
||||
+ unrecorded.push(...result.unrecorded);
|
||||
+ }
|
||||
+ // Orca: running a few unrecorded files records them; many (a fresh checkout) use the static graph instead of running everything.
|
||||
+ const unrecordedAffected = unrecorded.length > UNRECORDED_RUN_LIMIT ? await new AffectedModulesResolver(this.vitest, this.related).resolve(unrecorded) : unrecorded;
|
||||
+ unrecordedAffected.forEach((spec) => affected.add(spec));
|
||||
return specs.filter((spec) => affected.has(spec));
|
||||
}
|
||||
findAffectedInProject(project, specs) {
|
||||
- if (this.dependsOnConfig(project.vite.config.configFileDependencies) || this.dependsOnConfig(getEnvFiles(project))) return specs;
|
||||
+ if (this.dependsOnConfig(project.vite.config.configFileDependencies) || this.dependsOnConfig(getEnvFiles(project))) return {
|
||||
+ affected: specs,
|
||||
+ unrecorded: []
|
||||
+ };
|
||||
const dependencies = this.vitest.cache._dependencies.get(project);
|
||||
- if (!dependencies) return specs;
|
||||
+ if (!dependencies) return project.config.globalSetup.length ? {
|
||||
+ affected: specs,
|
||||
+ unrecorded: []
|
||||
+ } : {
|
||||
+ affected: [],
|
||||
+ unrecorded: specs
|
||||
+ };
|
||||
const changed = /* @__PURE__ */ new Set();
|
||||
for (const file of this.related) {
|
||||
const index = dependencies.moduleIndex.get(file);
|
||||
@@ -10281,13 +10299,23 @@ class RecordedModulesResolver {
|
||||
}
|
||||
if (project.config.globalSetup.length) {
|
||||
const globalSetup = dependencies.globalSetup;
|
||||
- if (!globalSetup || globalSetup.some((index) => changed.has(index))) return specs;
|
||||
+ if (!globalSetup || globalSetup.some((index) => changed.has(index))) return {
|
||||
+ affected: specs,
|
||||
+ unrecorded: []
|
||||
+ };
|
||||
}
|
||||
const root = this.vitest.config.root;
|
||||
- return specs.filter((spec) => {
|
||||
+ const affected = [];
|
||||
+ const unrecorded = [];
|
||||
+ for (const spec of specs) {
|
||||
const loaded = dependencies.files.get(getRootRelativePath(root, spec.moduleId));
|
||||
- return !loaded || loaded.some((index) => changed.has(index));
|
||||
- });
|
||||
+ if (!loaded) unrecorded.push(spec);
|
||||
+ else if (loaded.some((index) => changed.has(index))) affected.push(spec);
|
||||
+ }
|
||||
+ return {
|
||||
+ affected,
|
||||
+ unrecorded
|
||||
+ };
|
||||
}
|
||||
dependsOnConfig(configDependencies) {
|
||||
return !!configDependencies?.some((file) => this.related.includes(file));
|
||||
@@ -0,0 +1,122 @@
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { createHash } from 'node:crypto'
|
||||
import {
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
realpathSync,
|
||||
renameSync,
|
||||
statSync,
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { dirname, join, relative, resolve } from 'node:path'
|
||||
|
||||
const root = resolve(import.meta.dirname, '../..')
|
||||
const PACKAGES = [{ name: '@orca/process-host', dir: 'src/packages/process-host' }]
|
||||
// Outputs and build scratch space, not inputs.
|
||||
const NON_INPUTS = new Set(['dist', 'node_modules', '.dist-build.lock'])
|
||||
const COMPILER_PACKAGES = ['typescript', 'tsx', '@types/node']
|
||||
|
||||
function listFiles(directory, base = directory) {
|
||||
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
||||
if (directory === base && (NON_INPUTS.has(entry.name) || entry.name.startsWith('.dist-'))) {
|
||||
return []
|
||||
}
|
||||
const path = join(directory, entry.name)
|
||||
return entry.isDirectory()
|
||||
? listFiles(path, base)
|
||||
: [relative(base, path).replaceAll('\\', '/')]
|
||||
})
|
||||
}
|
||||
|
||||
/** Sources, manifest, build script, and the exact installed compiler and Node types. */
|
||||
export function hashPackageInputs(packageDir) {
|
||||
const hash = createHash('sha256')
|
||||
for (const file of listFiles(packageDir).sort()) {
|
||||
hash
|
||||
.update(`${file}\0`)
|
||||
.update(readFileSync(join(packageDir, file)))
|
||||
.update('\0')
|
||||
}
|
||||
const require = createRequire(join(packageDir, 'package.json'))
|
||||
for (const name of COMPILER_PACKAGES) {
|
||||
const manifest = realpathSync(require.resolve(`${name}/package.json`))
|
||||
hash.update(`${manifest}\0`).update(readFileSync(manifest)).update('\0')
|
||||
}
|
||||
return hash.digest('hex')
|
||||
}
|
||||
|
||||
/** Size and mtime of every output, so a deleted or edited dist file forces a rebuild. */
|
||||
function describeOutputs(packageDir) {
|
||||
const dist = join(packageDir, 'dist')
|
||||
try {
|
||||
return listFiles(dist)
|
||||
.sort()
|
||||
.map((file) => {
|
||||
const { size, mtimeMs } = statSync(join(dist, file))
|
||||
return `${file}:${size}:${mtimeMs}`
|
||||
})
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function stampPath(packageDir) {
|
||||
return join(packageDir, 'node_modules', '.cache', 'orca-dist-stamp.json')
|
||||
}
|
||||
|
||||
export function isPackageBuildCurrent(packageDir, inputs) {
|
||||
try {
|
||||
const stamp = JSON.parse(readFileSync(stampPath(packageDir), 'utf8'))
|
||||
const outputs = describeOutputs(packageDir)
|
||||
return (
|
||||
stamp.inputs === inputs &&
|
||||
outputs.length > 0 &&
|
||||
JSON.stringify(stamp.outputs) === JSON.stringify(outputs)
|
||||
)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function writeStamp(packageDir, inputs) {
|
||||
const path = stampPath(packageDir)
|
||||
mkdirSync(dirname(path), { recursive: true })
|
||||
const temporary = `${path}.${process.pid}`
|
||||
writeFileSync(temporary, JSON.stringify({ inputs, outputs: describeOutputs(packageDir) }))
|
||||
renameSync(temporary, path)
|
||||
}
|
||||
|
||||
function runPackageBuild(name) {
|
||||
// Why not @orca/process-host: this builds it, so its dist may not exist yet.
|
||||
const pnpmEntry = process.env.npm_execpath
|
||||
const [program, args] = /\.[cm]?js$/.test(pnpmEntry ?? '')
|
||||
? [process.execPath, [pnpmEntry, '--filter', name, 'build']]
|
||||
: ['pnpm', ['--filter', name, 'build']]
|
||||
const result = spawnSync(program, args, {
|
||||
cwd: root,
|
||||
stdio: 'inherit',
|
||||
shell: program === 'pnpm' && process.platform === 'win32'
|
||||
})
|
||||
if (result.error) {
|
||||
throw result.error
|
||||
}
|
||||
return result.status ?? 1
|
||||
}
|
||||
|
||||
if (process.argv[1] && realpathSync(process.argv[1]) === import.meta.filename) {
|
||||
for (const { name, dir } of PACKAGES) {
|
||||
const packageDir = join(root, dir)
|
||||
// Hashed before building: an edit during the build leaves a stale stamp, never a skipped build.
|
||||
const inputs = hashPackageInputs(packageDir)
|
||||
if (isPackageBuildCurrent(packageDir, inputs)) {
|
||||
continue
|
||||
}
|
||||
const status = runPackageBuild(name)
|
||||
if (status !== 0) {
|
||||
process.exit(status)
|
||||
}
|
||||
writeStamp(packageDir, inputs)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { resolve } from 'node:path'
|
||||
import { runProcessSync } from '@orca/process-host'
|
||||
import { runProcess, runProcessSync } from '@orca/process-host'
|
||||
import { hasNoChangedFiles } from './vitest-changed-precheck.mjs'
|
||||
import { RUN_ARGS_ENV } from './vitest-run-filters.mjs'
|
||||
import { createVitestZodBundle } from './vitest-zod-bundle.mjs'
|
||||
import {
|
||||
checkBunVersion,
|
||||
@@ -9,43 +11,48 @@ import {
|
||||
} from './pinned-bun-executable.mjs'
|
||||
|
||||
const root = resolve(import.meta.dirname, '../..')
|
||||
const pinnedBun = resolvePinnedBunExecutable(root, runProcessSync)
|
||||
const bunCheck = pinnedBun
|
||||
? { ok: true }
|
||||
: checkBunVersion(probeBunVersion('bun', root, runProcessSync), readPinnedBunVersion(root))
|
||||
if (bunCheck.message) {
|
||||
console.error(bunCheck.message)
|
||||
}
|
||||
if (!bunCheck.ok) {
|
||||
process.exitCode = 1
|
||||
} else {
|
||||
let zodBundle
|
||||
const vitestArgs = process.argv.slice(2)
|
||||
|
||||
function runVitest() {
|
||||
const pinnedBun = resolvePinnedBunExecutable(root, runProcessSync)
|
||||
const bunCheck = pinnedBun
|
||||
? { ok: true }
|
||||
: checkBunVersion(probeBunVersion('bun', root, runProcessSync), readPinnedBunVersion(root))
|
||||
if (bunCheck.message) {
|
||||
console.error(bunCheck.message)
|
||||
}
|
||||
if (!bunCheck.ok) {
|
||||
return 1
|
||||
}
|
||||
try {
|
||||
zodBundle = createVitestZodBundle(root)
|
||||
const zodBundle = createVitestZodBundle(root)
|
||||
const result = runProcessSync({
|
||||
program: pinnedBun ?? 'bun',
|
||||
args: ['--bun', resolve(root, 'node_modules/vitest/vitest.mjs'), ...process.argv.slice(2)],
|
||||
args: ['--bun', resolve(root, 'node_modules/vitest/vitest.mjs'), ...vitestArgs],
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
ORCA_BACKGROUND_LAUNCH: '1',
|
||||
ORCA_TEST_NODE_EXECUTABLE: process.execPath,
|
||||
ORCA_TEST_NODE_VERSION: process.versions.node,
|
||||
ORCA_UNIT_ZOD_BUNDLE: zodBundle.path
|
||||
ORCA_UNIT_ZOD_BUNDLE: zodBundle.path,
|
||||
[RUN_ARGS_ENV]: JSON.stringify(vitestArgs),
|
||||
// The config's TypeScript and CommonJS package scope are intended; the notice is noise.
|
||||
VITE_CONFIG_NATIVE_IGNORE_WARNING: 'true'
|
||||
},
|
||||
stdio: 'inherit',
|
||||
timeoutMs: null
|
||||
})
|
||||
|
||||
process.exitCode = result.code ?? 1
|
||||
return result.code ?? 1
|
||||
} catch (error) {
|
||||
console.error('Could not prepare or start Vitest:', error)
|
||||
process.exitCode = 1
|
||||
} finally {
|
||||
try {
|
||||
zodBundle?.cleanup()
|
||||
} catch (error) {
|
||||
console.warn('Could not remove the temporary Vitest Zod bundle:', error)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if (await hasNoChangedFiles(vitestArgs, root, runProcess)) {
|
||||
// Vitest's own result for this case, without paying its startup.
|
||||
console.log('No changed files found, exiting with code 0')
|
||||
} else {
|
||||
process.exitCode = runVitest()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
const WATCH_ARGS = new Set(['-w', '--watch'])
|
||||
|
||||
/** The `--changed` ref of a one-shot run: true for the bare flag, undefined when absent or ambiguous. */
|
||||
export function parseChangedArg(args) {
|
||||
if (args[0] !== 'run' || args.some((arg) => WATCH_ARGS.has(arg.split('=')[0]))) {
|
||||
return undefined
|
||||
}
|
||||
const index = args.findIndex((arg) => arg === '--changed' || arg.startsWith('--changed='))
|
||||
if (index === -1) {
|
||||
return undefined
|
||||
}
|
||||
const arg = args[index]
|
||||
if (arg !== '--changed') {
|
||||
return arg.slice('--changed='.length) || undefined
|
||||
}
|
||||
// Vitest would read a following positional as the ref; leave that form to Vitest.
|
||||
const next = args[index + 1]
|
||||
return next === undefined || next.startsWith('-') ? true : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether Git reports no changed files for Vitest's `--changed`, which then runs nothing, so the
|
||||
* caller can skip starting it. Any doubt returns false.
|
||||
*/
|
||||
export async function hasNoChangedFiles(args, root, runProcess) {
|
||||
const changed = parseChangedArg(args)
|
||||
if (changed === undefined) {
|
||||
return false
|
||||
}
|
||||
const queries = [
|
||||
// Covers Vitest's staged, modified, and untracked queries. Unlike `ls-files --modified`, it
|
||||
// refreshes stale index stat data instead of rehashing files on every run.
|
||||
['status', '--porcelain', '-z', '--untracked-files=all', '--ignore-submodules=none'],
|
||||
...(typeof changed === 'string' ? [['diff', '--name-only', '-z', `${changed}...HEAD`]] : [])
|
||||
]
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
queries.map((args) =>
|
||||
runProcess({ program: 'git', args, cwd: root, timeoutMs: 30_000, maxOutputBytes: 4096 })
|
||||
)
|
||||
)
|
||||
return results.every((result) => result.code === 0 && result.stdout === '')
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { hasNoChangedFiles } from './vitest-changed-precheck.mjs'
|
||||
|
||||
const clean = () => vi.fn(async () => ({ code: 0, stdout: '' }))
|
||||
|
||||
describe('hasNoChangedFiles', () => {
|
||||
it('also checks commits since the ref', async () => {
|
||||
const runProcess = clean()
|
||||
await expect(
|
||||
hasNoChangedFiles(['run', '--changed=origin/main'], '/repo', runProcess)
|
||||
).resolves.toBe(true)
|
||||
expect(runProcess.mock.calls.map(([spec]) => spec.args)).toContainEqual([
|
||||
'diff',
|
||||
'--name-only',
|
||||
'-z',
|
||||
'origin/main...HEAD'
|
||||
])
|
||||
})
|
||||
|
||||
it('starts Vitest when Git reports a file or cannot answer', async () => {
|
||||
for (const result of [
|
||||
{ code: 0, stdout: '?? src/a.ts\0' },
|
||||
{ code: 128, stdout: '' }
|
||||
]) {
|
||||
const runProcess = vi.fn(async () => result)
|
||||
await expect(hasNoChangedFiles(['run', '--changed'], '/repo', runProcess)).resolves.toBe(
|
||||
false
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it.each([
|
||||
[['run', 'src/a']],
|
||||
[['run', '--changed', 'src/a']],
|
||||
[['run', '--changed', '--watch']],
|
||||
[['related', '--changed']]
|
||||
])('leaves %j to Vitest', async (args) => {
|
||||
const runProcess = clean()
|
||||
await expect(hasNoChangedFiles(args, '/repo', runProcess)).resolves.toBe(false)
|
||||
expect(runProcess).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -197,6 +197,7 @@ const NODE_RUNTIME_INCLUDE = [
|
||||
'config/scripts/vitest-node-runtime-pool.test.ts'
|
||||
]
|
||||
|
||||
export function getNodeRuntimeInclude() {
|
||||
return [...NODE_RUNTIME_INCLUDE, ...findSqliteRuntimeTests()]
|
||||
/** `filters` limits the SQLite import scan to the tests a filtered CLI run can select. */
|
||||
export function getNodeRuntimeInclude({ filters } = {}) {
|
||||
return [...NODE_RUNTIME_INCLUDE, ...findSqliteRuntimeTests({ filters })]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Options that always consume the next argument. Anything else is treated as a flag, so an
|
||||
// unlisted option's value becomes an extra filter: a wider scan, never a missed file.
|
||||
const VALUE_OPTIONS = new Set([
|
||||
'-c',
|
||||
'--config',
|
||||
'--maxWorkers',
|
||||
'--project',
|
||||
'--reporter',
|
||||
'--outputFile',
|
||||
'--shard',
|
||||
'--pool',
|
||||
'--environment',
|
||||
'-t',
|
||||
'--testNamePattern',
|
||||
'--testTimeout',
|
||||
'--hookTimeout',
|
||||
'--retry',
|
||||
'--bail',
|
||||
'--sequence.seed'
|
||||
])
|
||||
// Modes where Vitest can select files beyond the command-line filters.
|
||||
const UNSCOPED_OPTIONS = new Set(['-w', '--watch', '--dir', '-r', '--root', '--related'])
|
||||
|
||||
export const RUN_ARGS_ENV = 'ORCA_VITEST_RUN_ARGS'
|
||||
|
||||
/** Positional filters of a one-shot `vitest run`, or undefined when it may select any file. */
|
||||
export function parseRunFilters(args) {
|
||||
if (args[0] !== 'run') {
|
||||
return undefined
|
||||
}
|
||||
const filters = []
|
||||
for (let index = 1; index < args.length; index += 1) {
|
||||
const arg = args[index]
|
||||
if (arg === '--') {
|
||||
return undefined
|
||||
}
|
||||
if (!arg.startsWith('-')) {
|
||||
filters.push(arg)
|
||||
continue
|
||||
}
|
||||
const name = arg.split('=')[0]
|
||||
if (UNSCOPED_OPTIONS.has(name)) {
|
||||
return undefined
|
||||
}
|
||||
if (!arg.includes('=') && VALUE_OPTIONS.has(name)) {
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
return filters.length ? filters : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters of the Vitest CLI run that run-vitest.mjs launched in this process. The argv check
|
||||
* keeps a nested Vitest that inherits the environment from using its parent's filters.
|
||||
*/
|
||||
export function readRunFilters(env = process.env, argv = process.argv) {
|
||||
const launched = env[RUN_ARGS_ENV]
|
||||
if (!launched || launched !== JSON.stringify(argv.slice(2))) {
|
||||
return undefined
|
||||
}
|
||||
return parseRunFilters(argv.slice(2))
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { parseRunFilters, readRunFilters, RUN_ARGS_ENV } from './vitest-run-filters.mjs'
|
||||
import { matchesRunFilter } from './vitest-sqlite-runtime-scan.mjs'
|
||||
|
||||
describe('parseRunFilters', () => {
|
||||
it('returns the positional filters of a one-shot run', () => {
|
||||
expect(
|
||||
parseRunFilters([
|
||||
'run',
|
||||
'--config',
|
||||
'config/vitest.config.ts',
|
||||
'src/main/runtime',
|
||||
'--maxWorkers=2',
|
||||
'-t',
|
||||
'name',
|
||||
'src/shared/a.test.ts:12'
|
||||
])
|
||||
).toEqual(['src/main/runtime', 'src/shared/a.test.ts:12'])
|
||||
})
|
||||
|
||||
it('treats an unknown option value as a filter, widening rather than narrowing the scan', () => {
|
||||
expect(parseRunFilters(['run', '--changed', 'src/a', '--unknown', '7'])).toEqual(['src/a', '7'])
|
||||
})
|
||||
|
||||
it.each([
|
||||
[['run', '--config', 'x']],
|
||||
[['watch', 'src/a']],
|
||||
[['related', 'src/a.ts']],
|
||||
[['run', '--watch', 'src/a']],
|
||||
[['run', '--dir', 'src', 'a']],
|
||||
[['run', 'src/a', '--', 'b']]
|
||||
])('scans every file for %j', (args) => {
|
||||
expect(parseRunFilters(args)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('ignores inherited arguments that this process was not launched with', () => {
|
||||
const args = ['run', 'src/a']
|
||||
const env = { [RUN_ARGS_ENV]: JSON.stringify(args) }
|
||||
expect(readRunFilters(env, ['bun', 'vitest.mjs', ...args])).toEqual(['src/a'])
|
||||
expect(readRunFilters(env, ['bun', 'vitest.mjs', 'run', 'src/b'])).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('matchesRunFilter', () => {
|
||||
it('matches every file a Vitest substring or line filter selects', () => {
|
||||
const file = 'src/shared/worktree/Branch-Name.test.ts'
|
||||
expect(matchesRunFilter(file, ['src/shared/worktree'])).toBe(true)
|
||||
expect(matchesRunFilter(file, ['branch-name'])).toBe(true)
|
||||
expect(matchesRunFilter(file, [`${file}:12`])).toBe(true)
|
||||
expect(matchesRunFilter(file, [`${process.cwd()}/src/shared/`])).toBe(true)
|
||||
expect(matchesRunFilter(file, ['src/main'])).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -7,14 +7,17 @@ it('re-derives SQLite assignments from malformed disposable cache data', async (
|
||||
const version = createHash('sha256')
|
||||
.update(readFileSync(new URL('./vitest-sqlite-runtime-scan.mjs', import.meta.url)))
|
||||
.digest('hex')
|
||||
for (const files of [null, { 'fixture.test.ts': [1, 2, 'false'] }]) {
|
||||
for (const files of [null, { 'src/fixture.test.ts': [1, 2, 'false'] }]) {
|
||||
for (const [source, expected] of [
|
||||
['export {}', []],
|
||||
["import './src/main/sqlite/sync-database'", ['fixture.test.ts']]
|
||||
["import './main/sqlite/sync-database'", ['src/fixture.test.ts']]
|
||||
] as const) {
|
||||
vi.doMock('node:fs', async (original) => ({
|
||||
...(await original<typeof FileSystem>()),
|
||||
globSync: () => ['fixture.test.ts'],
|
||||
readdirSync: (path: string) =>
|
||||
String(path).endsWith('src')
|
||||
? [{ name: 'fixture.test.ts', isDirectory: () => false }]
|
||||
: [],
|
||||
statSync: () => ({ mtimeMs: 1, size: 2 }),
|
||||
readFileSync: (path: Parameters<typeof readFileSync>[0]) =>
|
||||
String(path).endsWith('.json')
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { globSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { basename, dirname, join, resolve } from 'node:path'
|
||||
import { defaultExclude } from 'vitest/config'
|
||||
import { basename, dirname, isAbsolute, join, posix, relative, resolve } from 'node:path'
|
||||
import { UNIT_INCLUDE } from './ci-unit-files.mjs'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
@@ -88,17 +87,70 @@ function readCache(version) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Unit tests that import a SQLite module or harness directly, as repo-relative paths. */
|
||||
export function findSqliteRuntimeTests() {
|
||||
function walkFiles(directory, files) {
|
||||
let entries
|
||||
try {
|
||||
entries = readdirSync(resolve(root, directory), { withFileTypes: true })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const path = `${directory}/${entry.name}`
|
||||
if (!entry.isDirectory()) {
|
||||
files.push(path)
|
||||
} else if (entry.name !== 'node_modules' && entry.name !== '.git') {
|
||||
// Vitest's defaultExclude, pruned instead of matched per file.
|
||||
walkFiles(path, files)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** UNIT_INCLUDE files as repo-relative paths; a plain walk is several times faster than fs.globSync. */
|
||||
export function listUnitTestFiles() {
|
||||
const files = []
|
||||
for (const base of new Set(UNIT_INCLUDE.map((pattern) => pattern.split('/**')[0]))) {
|
||||
walkFiles(base, files)
|
||||
}
|
||||
return files.filter((file) => UNIT_INCLUDE.some((pattern) => posix.matchesGlob(file, pattern)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Vitest's CLI filter match (a case-insensitive path substring). Also tries each filter without
|
||||
* a `:line` suffix, so the selection is never narrower than the one Vitest runs.
|
||||
*/
|
||||
export function matchesRunFilter(file, filters) {
|
||||
const testFile = file.toLowerCase()
|
||||
const absolute = resolve(root, file).replaceAll('\\', '/')
|
||||
return filters.some((raw) =>
|
||||
[raw, raw.replace(/(?::\d+)+$/, '')].some((filter) => {
|
||||
const slashed = filter.replaceAll('\\', '/')
|
||||
const relativePath = relative(process.cwd(), filter).replaceAll('\\', '/')
|
||||
return (
|
||||
(isAbsolute(filter) && absolute.startsWith(slashed)) ||
|
||||
testFile.includes(slashed.toLowerCase()) ||
|
||||
testFile.includes(relativePath.toLowerCase())
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit tests that import a SQLite module or harness directly, as repo-relative paths. With CLI
|
||||
* `filters`, only the tests those filters can select are classified.
|
||||
*/
|
||||
export function findSqliteRuntimeTests({ filters } = {}) {
|
||||
// Why: reading every test costs about a second; unchanged files reuse their last verdict.
|
||||
const version = createHash('sha256')
|
||||
.update(readFileSync(import.meta.filename))
|
||||
.digest('hex')
|
||||
const cached = readCache(version)
|
||||
const scoped = Boolean(filters?.length)
|
||||
const candidates = listUnitTestFiles()
|
||||
const files = {}
|
||||
let changed = false
|
||||
for (const file of globSync(UNIT_INCLUDE, { cwd: root, exclude: defaultExclude })) {
|
||||
const path = file.replaceAll('\\', '/')
|
||||
for (const path of scoped
|
||||
? candidates.filter((file) => matchesRunFilter(file, filters))
|
||||
: candidates) {
|
||||
const { mtimeMs, size } = statSync(resolve(root, path))
|
||||
const entry = cached[path]
|
||||
if (
|
||||
@@ -117,11 +169,13 @@ export function findSqliteRuntimeTests() {
|
||||
]
|
||||
changed = true
|
||||
}
|
||||
if (changed || Object.keys(cached).length !== Object.keys(files).length) {
|
||||
if (changed || (!scoped && Object.keys(cached).length !== Object.keys(files).length)) {
|
||||
try {
|
||||
mkdirSync(dirname(CACHE_FILE), { recursive: true })
|
||||
const temporary = `${CACHE_FILE}.${process.pid}`
|
||||
writeFileSync(temporary, JSON.stringify({ version, files }))
|
||||
// Only a full scan knows which files were deleted; a scoped one keeps verdicts it skipped.
|
||||
const merged = scoped ? { ...cached, ...files } : files
|
||||
writeFileSync(temporary, JSON.stringify({ version, files: merged }))
|
||||
// Why: parallel runs each rename a whole file, so a reader never sees a partial write.
|
||||
renameSync(temporary, CACHE_FILE)
|
||||
} catch {
|
||||
|
||||
@@ -1,28 +1,89 @@
|
||||
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { createHash } from 'node:crypto'
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
renameSync,
|
||||
rmSync,
|
||||
statSync
|
||||
} from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { buildSync } from 'esbuild'
|
||||
import { buildSync, version as esbuildVersion } from 'esbuild'
|
||||
|
||||
const CACHE_DIRECTORY = 'vitest-zod-cache'
|
||||
// No run lasts this long, so an older bundle directory is not in use.
|
||||
const STALE_AGE_MS = 6 * 60 * 60 * 1000
|
||||
|
||||
function removeIfStale(path, now) {
|
||||
try {
|
||||
if (now - statSync(path).mtimeMs > STALE_AGE_MS) {
|
||||
rmSync(path, { recursive: true, force: true, maxRetries: 3 })
|
||||
}
|
||||
} catch {
|
||||
// Another launcher may be removing it too.
|
||||
}
|
||||
}
|
||||
|
||||
/** Per-run directories that older launchers leaked when killed, and superseded cache keys. */
|
||||
function sweepStaleBundles(temporary, cache, key) {
|
||||
const now = Date.now()
|
||||
for (const name of readdirSync(temporary)) {
|
||||
if (name.startsWith('vitest-zod-') && name !== CACHE_DIRECTORY) {
|
||||
removeIfStale(join(temporary, name), now)
|
||||
}
|
||||
}
|
||||
for (const name of readdirSync(cache)) {
|
||||
if (name !== key && !name.startsWith(`.${key}-`)) {
|
||||
removeIfStale(join(cache, name), now)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds Zod into one ESM module that every Vitest worker aliases. The output is keyed by the
|
||||
* installed Zod, esbuild, and this builder, so repeat runs reuse it and a killed run leaks nothing.
|
||||
*/
|
||||
export function createVitestZodBundle(root) {
|
||||
const entry = fileURLToPath(import.meta.resolve('zod'))
|
||||
const manifest = createRequire(entry).resolve('zod/package.json')
|
||||
const key = createHash('sha256')
|
||||
.update(JSON.stringify([entry, readFileSync(manifest, 'utf8'), esbuildVersion]))
|
||||
.update(readFileSync(import.meta.filename))
|
||||
.digest('hex')
|
||||
.slice(0, 16)
|
||||
const temporary = join(root, '.tmp')
|
||||
mkdirSync(temporary, { recursive: true })
|
||||
const directory = mkdtempSync(join(temporary, 'vitest-zod-'))
|
||||
const path = join(directory, 'zod.mjs')
|
||||
const cleanup = () => rmSync(directory, { recursive: true, force: true, maxRetries: 3 })
|
||||
const cache = join(temporary, CACHE_DIRECTORY)
|
||||
const path = join(cache, key, 'zod.mjs')
|
||||
mkdirSync(cache, { recursive: true })
|
||||
sweepStaleBundles(temporary, cache, key)
|
||||
if (existsSync(path)) {
|
||||
return { path }
|
||||
}
|
||||
// Built beside the final directory, then renamed, so readers never see a partial bundle.
|
||||
const staging = join(cache, `.${key}-${process.pid}-${Date.now()}`)
|
||||
try {
|
||||
buildSync({
|
||||
absWorkingDir: root,
|
||||
entryPoints: [fileURLToPath(import.meta.resolve('zod'))],
|
||||
outfile: path,
|
||||
entryPoints: [entry],
|
||||
outfile: join(staging, 'zod.mjs'),
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
target: 'node24',
|
||||
sourcemap: true
|
||||
})
|
||||
return { path, cleanup }
|
||||
} catch (error) {
|
||||
cleanup()
|
||||
throw error
|
||||
try {
|
||||
renameSync(staging, dirname(path))
|
||||
} catch (error) {
|
||||
if (!existsSync(path)) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
return { path }
|
||||
} finally {
|
||||
rmSync(staging, { recursive: true, force: true, maxRetries: 3 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, utimesSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { createVitestZodBundle } from './vitest-zod-bundle.mjs'
|
||||
|
||||
let root
|
||||
afterEach(() => rmSync(root, { recursive: true, force: true }))
|
||||
|
||||
it('reuses one bundle across runs and removes directories leaked by killed runs', () => {
|
||||
root = mkdtempSync(join(tmpdir(), 'orca-zod-bundle-'))
|
||||
const leaked = join(root, '.tmp', 'vitest-zod-leaked')
|
||||
const active = join(root, '.tmp', 'vitest-zod-active')
|
||||
mkdirSync(leaked, { recursive: true })
|
||||
mkdirSync(active)
|
||||
const hoursAgo = new Date(Date.now() - 7 * 60 * 60 * 1000)
|
||||
utimesSync(leaked, hoursAgo, hoursAgo)
|
||||
|
||||
const first = createVitestZodBundle(root)
|
||||
const second = createVitestZodBundle(root)
|
||||
|
||||
expect(second.path).toBe(first.path)
|
||||
expect(existsSync(first.path)).toBe(true)
|
||||
expect(existsSync(leaked)).toBe(false)
|
||||
expect(existsSync(active)).toBe(true)
|
||||
})
|
||||
@@ -7,9 +7,12 @@ import TimingSequencer from './scripts/ci-unit-sequencer.mjs'
|
||||
import RuntimeSequencer from './scripts/vitest-runtime-sequencer.mjs'
|
||||
import { getNodeRuntimeInclude } from './scripts/vitest-node-runtime-files.mjs'
|
||||
import { nodeRuntimePool } from './scripts/vitest-node-runtime-pool'
|
||||
import { readRunFilters } from './scripts/vitest-run-filters.mjs'
|
||||
import { createWorkspaceSourceResolver } from './scripts/workspace-source-exports.mjs'
|
||||
|
||||
const nodeRuntimeInclude = process.versions.bun ? getNodeRuntimeInclude() : []
|
||||
const nodeRuntimeInclude = process.versions.bun
|
||||
? getNodeRuntimeInclude({ filters: readRunFilters() })
|
||||
: []
|
||||
const balancedShards = process.env.ORCA_BALANCE_UNIT_SHARDS === '1'
|
||||
const measurementFile = 'src/main/foreign-sqlite-readers/foreign-sqlite-reader-event-loop.test.ts'
|
||||
const paletteMeasurementFile =
|
||||
|
||||
+3
-3
@@ -31,8 +31,8 @@
|
||||
"lint:react-doctor": "oxlint --config config/oxlint-react-doctor.json",
|
||||
"lint:react-doctor:changed": "node config/scripts/lint-react-doctor-changed.mjs",
|
||||
"prepare": "husky",
|
||||
"test": "pnpm run build:packages && node config/scripts/ensure-native-runtime.mjs --runtime=node && node config/scripts/run-vitest.mjs run --config config/vitest.config.ts",
|
||||
"test:changed": "pnpm test --changed=HEAD",
|
||||
"test": "node config/scripts/build-workspace-packages.mjs && node config/scripts/ensure-native-runtime.mjs --runtime=node && node config/scripts/run-vitest.mjs run --config config/vitest.config.ts",
|
||||
"test:changed": "node config/scripts/build-workspace-packages.mjs && node config/scripts/ensure-native-runtime.mjs --runtime=node && node config/scripts/run-vitest.mjs run --config config/vitest.config.ts --changed=HEAD",
|
||||
"test:node": "pnpm run build:packages && node config/scripts/ensure-native-runtime.mjs --runtime=node && vitest run --config config/vitest.config.ts",
|
||||
"test:node-server": "node config/scripts/run-node-server-tests.mjs",
|
||||
"test:skill-sharing:release": "vitest run --config config/vitest.config.ts src/main/skills src/main/runtime/rpc/methods/skills.test.ts src/shared/skill-bundle-install-contract.test.ts src/shared/skill-install-contract.test.ts src/shared/skill-install-failure.test.ts src/shared/skill-package-manifest.test.ts",
|
||||
@@ -195,7 +195,7 @@
|
||||
"repro:live-remote-realistic-freeze": "node config/scripts/live-remote-realistic-freeze-repro.mjs",
|
||||
"audit:anti-slop": "node config/scripts/sync-anti-slop-plugin.mjs && oxlint --config config/oxlint-anti-slop.json src config tests mobile --deny-warnings",
|
||||
"sync:anti-slop-plugin": "node config/scripts/sync-anti-slop-plugin.mjs",
|
||||
"build:packages": "pnpm --filter @orca/process-host build",
|
||||
"build:packages": "node config/scripts/build-workspace-packages.mjs",
|
||||
"check:process-host-imports": "node config/scripts/check-process-host-imports.mjs",
|
||||
"check:main-sync-spawn-ratchet": "node config/scripts/check-main-sync-spawn-ratchet.mjs",
|
||||
"check:compat-boundary-imports": "node config/scripts/check-compat-boundary-imports.mjs"
|
||||
|
||||
Generated
+8
-7
@@ -187,6 +187,7 @@ patchedDependencies:
|
||||
node-pty@1.1.0: d3e7df08a1b7c11400cf9db039ece2b7c8a8d0ce4e2f79d81d52c10a67dc0440
|
||||
oxlint-plugin-anti-slop@0.1.2: c10c871702365a0c2c59e9f1c5fb92df97cae5755d534c0a3d83c6368f6feafe
|
||||
sprintf-js@1.1.3: 05316a8b01f155fe19a1e0cb4042c81d43d798a76755c407684e1199a18b1e08
|
||||
vitest@5.0.5: e53d360918437da97f766d322f487aabfbe10d1450ed98543491573b4d500cd2
|
||||
ws@8.22.0: 9393e04dd9a90b01eb7aa35fcd5b1431b5038ce999fbbd82651136193ba51f14
|
||||
|
||||
importers:
|
||||
@@ -322,7 +323,7 @@ importers:
|
||||
version: 3.14.13(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(@types/react@19.2.18)(react@19.2.8))(react@19.2.8)
|
||||
'@testing-library/jest-dom':
|
||||
specifier: ^6.9.1
|
||||
version: 6.9.1(vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@24.19.2)(happy-dom@21.0.4)(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)))
|
||||
version: 6.9.1(vitest@5.0.5(patch_hash=e53d360918437da97f766d322f487aabfbe10d1450ed98543491573b4d500cd2)(@opentelemetry/api@1.9.1)(@types/node@24.19.2)(happy-dom@21.0.4)(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)))
|
||||
'@testing-library/react':
|
||||
specifier: ^16.3.3
|
||||
version: 16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(@types/react@19.2.18)(react@19.2.8))(react@19.2.8)
|
||||
@@ -619,7 +620,7 @@ importers:
|
||||
version: 8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)
|
||||
vitest:
|
||||
specifier: 5.0.5
|
||||
version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@24.19.2)(happy-dom@21.0.4)(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
|
||||
version: 5.0.5(patch_hash=e53d360918437da97f766d322f487aabfbe10d1450ed98543491573b4d500cd2)(@opentelemetry/api@1.9.1)(@types/node@24.19.2)(happy-dom@21.0.4)(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
|
||||
vscode-oniguruma:
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
@@ -680,7 +681,7 @@ importers:
|
||||
version: 7.0.2
|
||||
vitest:
|
||||
specifier: 5.0.5
|
||||
version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(happy-dom@21.0.4)(msw@2.14.3(@types/node@18.19.130)(typescript@7.0.2))(vite@8.3.4(@types/node@18.19.130)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
|
||||
version: 5.0.5(patch_hash=e53d360918437da97f766d322f487aabfbe10d1450ed98543491573b4d500cd2)(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(happy-dom@21.0.4)(msw@2.14.3(@types/node@18.19.130)(typescript@7.0.2))(vite@8.3.4(@types/node@18.19.130)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
|
||||
|
||||
packages:
|
||||
|
||||
@@ -10366,7 +10367,7 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
pretty-format: 27.5.1
|
||||
|
||||
'@testing-library/jest-dom@6.9.1(vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@24.19.2)(happy-dom@21.0.4)(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)))':
|
||||
'@testing-library/jest-dom@6.9.1(vitest@5.0.5(patch_hash=e53d360918437da97f766d322f487aabfbe10d1450ed98543491573b4d500cd2)(@opentelemetry/api@1.9.1)(@types/node@24.19.2)(happy-dom@21.0.4)(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)))':
|
||||
dependencies:
|
||||
'@adobe/css-tools': 4.5.0
|
||||
'@types/aria-query': 5.0.4
|
||||
@@ -10376,7 +10377,7 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
redent: 3.0.0
|
||||
optionalDependencies:
|
||||
vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@24.19.2)(happy-dom@21.0.4)(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
|
||||
vitest: 5.0.5(patch_hash=e53d360918437da97f766d322f487aabfbe10d1450ed98543491573b4d500cd2)(@opentelemetry/api@1.9.1)(@types/node@24.19.2)(happy-dom@21.0.4)(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
|
||||
|
||||
'@testing-library/react@16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(@types/react@19.2.18)(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
@@ -15099,7 +15100,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@types/picomatch'
|
||||
|
||||
vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(happy-dom@21.0.4)(msw@2.14.3(@types/node@18.19.130)(typescript@7.0.2))(vite@8.3.4(@types/node@18.19.130)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)):
|
||||
vitest@5.0.5(patch_hash=e53d360918437da97f766d322f487aabfbe10d1450ed98543491573b4d500cd2)(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(happy-dom@21.0.4)(msw@2.14.3(@types/node@18.19.130)(typescript@7.0.2))(vite@8.3.4(@types/node@18.19.130)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/mocker': 5.0.5(msw@2.14.3(@types/node@18.19.130)(typescript@7.0.2))(vite@8.3.4(@types/node@18.19.130)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
|
||||
@@ -15124,7 +15125,7 @@ snapshots:
|
||||
- '@types/picomatch'
|
||||
- msw
|
||||
|
||||
vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@24.19.2)(happy-dom@21.0.4)(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)):
|
||||
vitest@5.0.5(patch_hash=e53d360918437da97f766d322f487aabfbe10d1450ed98543491573b4d500cd2)(@opentelemetry/api@1.9.1)(@types/node@24.19.2)(happy-dom@21.0.4)(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/mocker': 5.0.5(msw@2.14.3(@types/node@24.19.2)(typescript@7.0.2))(vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
|
||||
|
||||
@@ -139,3 +139,4 @@ patchedDependencies:
|
||||
monaco-editor@0.55.1: config/patches/monaco-editor@0.55.1.patch
|
||||
sprintf-js@1.1.3: config/patches/sprintf-js@1.1.3.patch
|
||||
'@tiptap/extension-list@3.31.3': config/patches/@tiptap__extension-list@3.31.3.patch
|
||||
vitest@5.0.5: config/patches/vitest@5.0.5.patch
|
||||
|
||||
Reference in New Issue
Block a user