FN-6042: raise dependency security floors
Harden dependency floors and update the Vitest toolchain to patched releases. - upgrade workspace vitest and @vitest/coverage-v8 dependencies to the 4.1 line across packages and plugins - pin transitive protobufjs via pnpm overrides and lockfile updates to patched versions - adapt Vitest configs, engine test helpers, and security-floor coverage for the new dependency baselines - add the published CLI changeset and related workspace/package metadata updates included in the task branch Files changed: .changeset/fn-6042-security-dependencies.md | 5 + AGENTS.md | 4 + Dockerfile | 3 + docs/PLUGIN_AUTHORING.md | 2 - package.json | 3 +- packages/cli/package.json | 4 +- packages/cli/vitest.config.ts | 2 +- packages/core/package.json | 4 +- packages/core/vitest.config.ts | 2 +- packages/dashboard/app/test/mockApi.ts | 4 +- packages/dashboard/package.json | 4 +- packages/dashboard/vitest.config.ts | 2 +- packages/desktop/package.json | 4 +- packages/desktop/vitest.config.ts | 2 +- packages/droid-cli/package.json | 2 +- packages/droid-cli/vitest.config.ts | 2 +- packages/engine/package.json | 4 +- .../engine/src/__tests__/executor-test-helpers.ts | 29 +- .../engine/src/__tests__/gridlock-detector.test.ts | 5 +- .../src/__tests__/heartbeat-scheduler.test.ts | 3 +- packages/engine/src/__tests__/scheduler.test.ts | 24 +- packages/engine/src/__tests__/self-healing.test.ts | 5 + packages/engine/tsconfig.json | 3 +- packages/engine/vitest.config.ts | 10 +- packages/i18n/package.json | 2 +- packages/i18n/vitest.config.ts | 7 + packages/mobile/package.json | 2 +- packages/mobile/vitest.config.ts | 2 +- packages/pi-claude-cli/package.json | 2 +- packages/pi-claude-cli/vitest.config.ts | 2 +- packages/pi-llama-cpp/package.json | 2 +- packages/pi-llama-cpp/vitest.config.ts | 2 +- packages/plugin-sdk/package.json | 2 +- packages/plugin-sdk/vitest.config.ts | 2 +- .../examples/fusion-plugin-auto-label/package.json | 2 +- .../fusion-plugin-auto-label/vitest.config.ts | 2 +- .../examples/fusion-plugin-ci-status/package.json | 2 +- .../fusion-plugin-ci-status/vitest.config.ts | 2 +- .../fusion-plugin-notification/package.json | 2 +- .../fusion-plugin-notification/vitest.config.ts | 2 +- .../fusion-plugin-settings-demo/package.json | 2 +- .../fusion-plugin-settings-demo/vitest.config.ts | 2 +- plugins/fusion-plugin-acp-runtime/package.json | 2 +- plugins/fusion-plugin-acp-runtime/vitest.config.ts | 2 +- plugins/fusion-plugin-agent-browser/package.json | 2 +- .../fusion-plugin-agent-browser/vitest.config.ts | 2 +- .../fusion-plugin-cli-printing-press/package.json | 2 +- .../vitest.config.ts | 2 +- .../package.json | 2 +- .../src/__tests__/orchestrator-live-output.test.ts | 4 +- .../vitest.config.ts | 2 +- plugins/fusion-plugin-cursor-runtime/package.json | 4 +- .../fusion-plugin-dependency-graph/package.json | 4 +- .../vitest.config.ts | 2 +- plugins/fusion-plugin-droid-runtime/package.json | 2 +- .../fusion-plugin-droid-runtime/vitest.config.ts | 2 +- plugins/fusion-plugin-even-cards/package.json | 2 +- plugins/fusion-plugin-even-cards/vitest.config.ts | 2 +- .../package.json | 2 +- .../vitest.config.ts | 2 +- plugins/fusion-plugin-hermes-runtime/package.json | 2 +- .../fusion-plugin-hermes-runtime/vitest.config.ts | 2 +- .../fusion-plugin-openclaw-runtime/package.json | 2 +- .../vitest.config.ts | 2 +- .../fusion-plugin-paperclip-runtime/package.json | 2 +- .../vitest.config.ts | 2 +- plugins/fusion-plugin-reports/package.json | 2 +- .../src/__tests__/review-panel.test.ts | 6 +- plugins/fusion-plugin-reports/vitest.config.ts | 2 +- plugins/fusion-plugin-roadmap/package.json | 2 +- plugins/fusion-plugin-roadmap/vitest.config.ts | 2 +- plugins/fusion-plugin-whatsapp-chat/package.json | 2 +- .../fusion-plugin-whatsapp-chat/vitest.config.ts | 2 +- pnpm-lock.yaml | 626 ++++++++------------- .../__tests__/dependency-security-floor.test.mjs | 95 ++++ 75 files changed, 475 insertions(+), 491 deletions(-) Fusion-Task-Id: FN-6042 Fusion-Task-Lineage: fff6a1cb-8937-435c-9a91-b7c7a59cc80e
This commit is contained in:
5
.changeset/fn-6042-security-dependencies.md
Normal file
5
.changeset/fn-6042-security-dependencies.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Harden dependency security floors by forcing protobufjs resolutions to patched versions and upgrading Vitest tooling to the patched 4.1 line.
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
## Essential rules
|
||||
|
||||
### STANDING DIRECTIVE: Buttons Are Frozen
|
||||
|
||||
Buttons Are Frozen (2026-05-13): do not rename, remove, reorder, or restyle established dashboard action buttons unless the task explicitly targets that button contract. Preserve icon affordances and width-aware labels when making adjacent UI changes.
|
||||
|
||||
### Spec Generation Hygiene
|
||||
|
||||
- Do not cite `.fusion/tasks/<id>/<file>` paths in Context/Steps/File Scope unless the file already exists, is explicitly created as a `(new)` Artifact, or is sibling `PROMPT.md`/`task.json`/`attachments/*`.
|
||||
|
||||
@@ -17,6 +17,7 @@ COPY packages/dashboard/package.json ./packages/dashboard/package.json
|
||||
COPY packages/desktop/package.json ./packages/desktop/package.json
|
||||
COPY packages/droid-cli/package.json ./packages/droid-cli/package.json
|
||||
COPY packages/engine/package.json ./packages/engine/package.json
|
||||
COPY packages/i18n/package.json ./packages/i18n/package.json
|
||||
COPY packages/mobile/package.json ./packages/mobile/package.json
|
||||
COPY packages/pi-claude-cli/package.json ./packages/pi-claude-cli/package.json
|
||||
COPY packages/pi-llama-cpp/package.json ./packages/pi-llama-cpp/package.json
|
||||
@@ -25,6 +26,8 @@ COPY plugins/examples/fusion-plugin-auto-label/package.json ./plugins/examples/f
|
||||
COPY plugins/examples/fusion-plugin-ci-status/package.json ./plugins/examples/fusion-plugin-ci-status/package.json
|
||||
COPY plugins/examples/fusion-plugin-notification/package.json ./plugins/examples/fusion-plugin-notification/package.json
|
||||
COPY plugins/examples/fusion-plugin-settings-demo/package.json ./plugins/examples/fusion-plugin-settings-demo/package.json
|
||||
COPY plugins/fusion-plugin-acp-runtime/package.json ./plugins/fusion-plugin-acp-runtime/package.json
|
||||
COPY plugins/fusion-plugin-compound-engineering/package.json ./plugins/fusion-plugin-compound-engineering/package.json
|
||||
COPY plugins/fusion-plugin-paperclip-runtime/package.json ./plugins/fusion-plugin-paperclip-runtime/package.json
|
||||
COPY plugins/fusion-plugin-dependency-graph/package.json ./plugins/fusion-plugin-dependency-graph/package.json
|
||||
COPY plugins/fusion-plugin-cli-printing-press/package.json ./plugins/fusion-plugin-cli-printing-press/package.json
|
||||
|
||||
@@ -20,8 +20,6 @@ A comprehensive guide to creating Fusion plugins that extend the task board with
|
||||
14. [Example Plugins](#14-example-plugins)
|
||||
15. [Registering Skills](#15-registering-skills)
|
||||
16. [Registering Workflow Steps](#16-registering-workflow-steps)
|
||||
16.5. [Contributing Column Traits](#165-contributing-column-traits)
|
||||
16.6. [Contributing Workflow Extensions](#166-contributing-workflow-extensions)
|
||||
17. [Contributing Prompt Modifications](#17-contributing-prompt-modifications)
|
||||
18. [Plugin Binary Setup Hooks](#18-plugin-binary-setup-hooks)
|
||||
|
||||
|
||||
@@ -85,7 +85,8 @@
|
||||
"protobufjs"
|
||||
],
|
||||
"overrides": {
|
||||
"@types/node": "^25.5.2"
|
||||
"@types/node": "^25.5.2",
|
||||
"protobufjs": "^7.5.8"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/ws": "^8.5.0",
|
||||
"@vitest/coverage-v8": "^3.1.0",
|
||||
"@vitest/coverage-v8": "^4.1.0",
|
||||
"cross-env": "^7.0.0",
|
||||
"esbuild": "^0.25.12",
|
||||
"ink-testing-library": "^4.0.0",
|
||||
@@ -106,7 +106,7 @@
|
||||
"tsx": "^4.19.0",
|
||||
"typebox": "^1.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.1.0",
|
||||
"vitest": "^4.1.0",
|
||||
"yaml": "^2.8.3"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -52,7 +52,7 @@ export default defineConfig({
|
||||
globalSetup: [resolve(__dirname, "../core/src/__test-utils__/vitest-teardown.ts")],
|
||||
pool: "forks",
|
||||
maxWorkers,
|
||||
poolOptions: { forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
fileParallelism: true,
|
||||
coverage: {
|
||||
enabled: false,
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
"devDependencies": {
|
||||
"@types/dockerode": "^3.3.41",
|
||||
"@types/node": "^25.5.0",
|
||||
"@vitest/coverage-v8": "^3.1.0",
|
||||
"@vitest/coverage-v8": "^4.1.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.1.0"
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.5.0"
|
||||
|
||||
@@ -28,7 +28,7 @@ export default defineConfig({
|
||||
// and cannot safely share mutable process state under worker_threads.
|
||||
pool: "forks",
|
||||
maxWorkers,
|
||||
poolOptions: { forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
fileParallelism: true,
|
||||
// Core runs a large SQLite-heavy suite while other workspace packages test concurrently.
|
||||
// Use a slightly higher timeout to reduce nondeterministic slow-machine flakes.
|
||||
|
||||
@@ -24,7 +24,7 @@ function getFallback(name: string): AnyFn {
|
||||
return fallbackFns.get(name)!;
|
||||
}
|
||||
|
||||
export const dashboardApiMocks = {
|
||||
export const dashboardApiMocks: Record<string, AnyFn> = {
|
||||
fetchTasks: vi.fn(async () => []),
|
||||
fetchSettings: vi.fn(async () => ({})),
|
||||
updateSettings: vi.fn(async () => ({})),
|
||||
@@ -34,7 +34,7 @@ export const dashboardApiMocks = {
|
||||
fetchTaskDetail: vi.fn(),
|
||||
fetchTaskReview: vi.fn(),
|
||||
fetchUnreadCount: vi.fn(async () => ({ unreadCount: 0 })),
|
||||
} satisfies Record<string, AnyFn>;
|
||||
};
|
||||
|
||||
export async function createDashboardApiMock(
|
||||
importActual: () => Promise<AnyModule>,
|
||||
|
||||
@@ -147,12 +147,12 @@
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@types/ws": "^8.5.0",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"@vitest/coverage-v8": "^3.1.0",
|
||||
"@vitest/coverage-v8": "^4.1.0",
|
||||
"jsdom": "^29.0.1",
|
||||
"tinyrainbow": "^2.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vite": "^6.0.0",
|
||||
"vitest": "^3.1.0"
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ export default defineConfig({
|
||||
// React suites; forks duplicated the entire renderer per worker (~500MB).
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
fileParallelism: true,
|
||||
isolate: true,
|
||||
// Dashboard route and integration-heavy suites can exceed the Vitest
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitest/coverage-v8": "^3.1.0",
|
||||
"@vitest/coverage-v8": "^4.1.0",
|
||||
"electron": "^35.0.0",
|
||||
"electron-builder": "^26.0.0",
|
||||
"esbuild": "^0.25.12",
|
||||
@@ -57,6 +57,6 @@
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vite": "^6.0.0",
|
||||
"vitest": "^3.1.0"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export default defineConfig({
|
||||
hookTimeout: 30_000,
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
fileParallelism: true,
|
||||
passWithNoTests: true,
|
||||
projects: [
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.0.0"
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run --reporter=dot",
|
||||
|
||||
@@ -14,7 +14,7 @@ export default defineConfig({
|
||||
globalSetup: [resolve(__dirname, "../core/src/__test-utils__/vitest-teardown.ts")],
|
||||
pool: "forks",
|
||||
maxWorkers,
|
||||
poolOptions: { forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
fileParallelism: true,
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
|
||||
@@ -52,9 +52,9 @@
|
||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/proper-lockfile": "^4.1.4",
|
||||
"@vitest/coverage-v8": "^3.1.0",
|
||||
"@vitest/coverage-v8": "^4.1.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.1.0"
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { vi } from "vitest";
|
||||
import type { Mock } from "vitest";
|
||||
import { installTaskWorktreeIdentityGuard } from "../worktree-hooks.js";
|
||||
|
||||
// Mock external dependencies
|
||||
@@ -220,16 +221,18 @@ vi.mock("node:fs", () => ({
|
||||
realpathSync: vi.fn((path: string) => path),
|
||||
}));
|
||||
|
||||
export const mockExecuteAll = vi.fn().mockResolvedValue([]);
|
||||
export const mockTerminateAllSessions = vi.fn().mockResolvedValue(undefined);
|
||||
export const mockCleanup = vi.fn().mockResolvedValue(undefined);
|
||||
export const mockExecuteAll: Mock<() => Promise<unknown[]>> = vi.fn().mockResolvedValue([]);
|
||||
export const mockTerminateAllSessions: Mock<() => Promise<void>> = vi.fn().mockResolvedValue(undefined);
|
||||
export const mockCleanup: Mock<() => Promise<void>> = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
vi.mock("../step-session-executor.js", () => ({
|
||||
StepSessionExecutor: vi.fn().mockImplementation(() => ({
|
||||
executeAll: mockExecuteAll,
|
||||
terminateAllSessions: mockTerminateAllSessions,
|
||||
cleanup: mockCleanup,
|
||||
})),
|
||||
StepSessionExecutor: vi.fn().mockImplementation(function () {
|
||||
return {
|
||||
executeAll: mockExecuteAll,
|
||||
terminateAllSessions: mockTerminateAllSessions,
|
||||
cleanup: mockCleanup,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../rate-limit-retry.js", () => ({
|
||||
@@ -257,10 +260,12 @@ vi.mock("@earendil-works/pi-coding-agent", () => {
|
||||
open: vi.fn().mockReturnValue(mockSessionManager),
|
||||
inMemory: vi.fn().mockReturnValue(mockSessionManager),
|
||||
},
|
||||
ModelRegistry: vi.fn().mockImplementation(() => ({
|
||||
find: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
})),
|
||||
ModelRegistry: vi.fn().mockImplementation(function () {
|
||||
return {
|
||||
find: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
};
|
||||
}),
|
||||
AuthStorage: {
|
||||
create: vi.fn().mockReturnValue({}),
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import type { Settings, Task, TaskStore } from "@fusion/core";
|
||||
import { GridlockDetector } from "../gridlock-detector.js";
|
||||
import type { GridlockEvent } from "../gridlock-detector.js";
|
||||
|
||||
function createTask(id: string, overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
@@ -34,8 +35,8 @@ describe("GridlockDetector", () => {
|
||||
let tasks: Task[];
|
||||
let settings: Settings;
|
||||
let scopes: Record<string, string[]>;
|
||||
let onGridlock: ReturnType<typeof vi.fn>;
|
||||
let onGridlockCleared: ReturnType<typeof vi.fn>;
|
||||
let onGridlock: ReturnType<typeof vi.fn<(event: GridlockEvent) => void>>;
|
||||
let onGridlockCleared: ReturnType<typeof vi.fn<() => void>>;
|
||||
let store: TaskStore;
|
||||
let detector: GridlockDetector;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
HeartbeatMonitor,
|
||||
HeartbeatTriggerScheduler,
|
||||
} from "../agent-heartbeat.js";
|
||||
import type { TriggerCallback } from "../agent-heartbeat.js";
|
||||
import type { AgentStore, TaskStore, Agent } from "@fusion/core";
|
||||
import { createBudgetStatus } from "./heartbeat-test-helpers.js";
|
||||
vi.mock("../logger.js", async () => {
|
||||
@@ -18,7 +19,7 @@ import { heartbeatLog } from "../logger.js";
|
||||
|
||||
describe("HeartbeatTriggerScheduler", () => {
|
||||
let store: AgentStore;
|
||||
let callback: ReturnType<typeof vi.fn>;
|
||||
let callback: ReturnType<typeof vi.fn<TriggerCallback>>;
|
||||
let scheduler: import("../agent-heartbeat.js").HeartbeatTriggerScheduler;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -52,21 +52,27 @@ vi.mock("../logger.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../stale-task-reporter.js", () => ({
|
||||
StaleTaskReporter: vi.fn().mockImplementation(() => ({
|
||||
report: staleReporterReportMock,
|
||||
})),
|
||||
StaleTaskReporter: vi.fn().mockImplementation(function () {
|
||||
return {
|
||||
report: staleReporterReportMock,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../backlog-pressure-reporter.js", () => ({
|
||||
BacklogPressureReporter: vi.fn().mockImplementation(() => ({
|
||||
report: backlogPressureReporterReportMock,
|
||||
})),
|
||||
BacklogPressureReporter: vi.fn().mockImplementation(function () {
|
||||
return {
|
||||
report: backlogPressureReporterReportMock,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../unlinked-missions-advisory-reporter.js", () => ({
|
||||
UnlinkedMissionsAdvisoryReporter: vi.fn().mockImplementation(() => ({
|
||||
report: unlinkedMissionsAdvisoryReporterReportMock,
|
||||
})),
|
||||
UnlinkedMissionsAdvisoryReporter: vi.fn().mockImplementation(function () {
|
||||
return {
|
||||
report: unlinkedMissionsAdvisoryReporterReportMock,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
// Helper to create mock tasks
|
||||
|
||||
@@ -7891,6 +7891,11 @@ describe("SelfHealingManager reclaimSelfOwnedBranchConflicts", () => {
|
||||
mockedIsUsableTaskWorktree.mockResolvedValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
manager.stop();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("reclaims fully-subsumed branch conflicts and emits subsumed audit metadata", async () => {
|
||||
store = createMockStore({
|
||||
getSettings: vi.fn().mockResolvedValue({ globalPause: false, enginePaused: false } as any),
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"node-pty": ["./src/types/node-pty/index.d.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/__tests__/**/*"]
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default defineConfig({
|
||||
globalSetup: [resolve(__dirname, "../core/src/__test-utils__/vitest-teardown.ts")],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
fileParallelism: true,
|
||||
// Enable isolate to allow parallel execution of tests with conflicting mocks
|
||||
isolate: true,
|
||||
@@ -117,7 +117,9 @@ export default defineConfig({
|
||||
// SQLite rowid interleaving (e.g. FN-5521 hit
|
||||
// `expected 24 to be less than 19` in merge-reuse-task-worktree).
|
||||
// Serialize at the file level; within-file order is already linear.
|
||||
poolOptions: { threads: { singleThread: true } },
|
||||
minWorkers: 1,
|
||||
maxWorkers: 1,
|
||||
fileParallelism: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -130,7 +132,9 @@ export default defineConfig({
|
||||
// and inflating wall time further. Excluded from the default
|
||||
// `pnpm test` lane; run via `pnpm test:slow` / `pnpm test:all`.
|
||||
include: ["src/**/*.slow.test.ts"],
|
||||
poolOptions: { threads: { singleThread: true } },
|
||||
minWorkers: 1,
|
||||
maxWorkers: 1,
|
||||
fileParallelism: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.1.0"
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.5.0"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { resolve } from "node:path";
|
||||
import { defineConfig } from "vitest/config";
|
||||
import { computeMaxWorkers } from "../core/src/__test-utils__/vitest-workers";
|
||||
|
||||
const maxWorkers = computeMaxWorkers();
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
@@ -10,5 +13,9 @@ export default defineConfig({
|
||||
},
|
||||
test: {
|
||||
include: ["src/**/*.test.ts"],
|
||||
setupFiles: ["../core/src/__test-utils__/vitest-setup.ts"],
|
||||
globalSetup: ["../core/src/__test-utils__/vitest-teardown.ts"],
|
||||
maxWorkers,
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
"@types/node": "^22.0.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.1.0"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default defineConfig({
|
||||
globalSetup: [resolve(__dirname, "../core/src/__test-utils__/vitest-teardown.ts")],
|
||||
pool: "forks",
|
||||
maxWorkers,
|
||||
poolOptions: { forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
fileParallelism: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.0.0"
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run --reporter=dot",
|
||||
|
||||
@@ -14,7 +14,7 @@ export default defineConfig({
|
||||
globalSetup: [resolve(__dirname, "../core/src/__test-utils__/vitest-teardown.ts")],
|
||||
pool: "forks",
|
||||
maxWorkers,
|
||||
poolOptions: { forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
fileParallelism: true,
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.0.0"
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run --reporter=dot",
|
||||
|
||||
@@ -11,7 +11,7 @@ export default defineConfig({
|
||||
globalSetup: [resolve(__dirname, "../core/src/__test-utils__/vitest-teardown.ts")],
|
||||
pool: "forks",
|
||||
maxWorkers,
|
||||
poolOptions: { forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
fileParallelism: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.1.0"
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fusion/core": "workspace:*"
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [resolve(__dirname, "../core/src/__test-utils__/vitest-teardown.ts")],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,6 +23,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,6 +23,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,6 +23,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,6 +23,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,6 +37,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -38,6 +38,6 @@
|
||||
"@types/node": "^25.5.2",
|
||||
"@types/react": "^19.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,6 +32,6 @@
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
/** A factory exposing the onProgress hook and a controllable nextEvent. */
|
||||
function progressFactory(nextEvent: () => Promise<InteractiveAiSessionEvent>) {
|
||||
const captured: { progress?: (e: InteractiveAiSessionProgressEvent) => void; dispose: ReturnType<typeof vi.fn> } = {
|
||||
dispose: vi.fn(),
|
||||
const captured: { progress?: (e: InteractiveAiSessionProgressEvent) => void; dispose: ReturnType<typeof vi.fn<() => void>> } = {
|
||||
dispose: vi.fn<() => void>(),
|
||||
};
|
||||
const factory: CreateInteractiveAiSessionFactory = vi.fn(async (opts) => {
|
||||
captured.progress = opts.onProgress;
|
||||
|
||||
@@ -38,7 +38,7 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
projects: [
|
||||
{
|
||||
extends: true,
|
||||
|
||||
@@ -33,6 +33,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,6 @@
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.2.4",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -33,6 +33,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -28,6 +28,6 @@
|
||||
"@types/node": "^25.5.2",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,6 +27,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,6 +27,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,6 +37,6 @@
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +83,9 @@ describe("runReviewPanel", () => {
|
||||
if (systemPrompt.includes("Operations")) {
|
||||
return {
|
||||
session: {
|
||||
prompt: vi.fn(async () => new Promise(() => {})),
|
||||
prompt: vi.fn(async () => new Promise<void>(() => {})),
|
||||
state: { messages: [] },
|
||||
dispose: vi.fn(),
|
||||
dispose: vi.fn<() => void>(),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -93,7 +93,7 @@ describe("runReviewPanel", () => {
|
||||
session: {
|
||||
prompt: vi.fn(async () => {}),
|
||||
state: { messages: [{ role: "assistant", content: JSON.stringify({ verdict: "approve", summary: "ok", highlights: [], lowlights: [], suggestions: [] }) }] },
|
||||
dispose: vi.fn(),
|
||||
dispose: vi.fn<() => void>(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
projects: [
|
||||
{
|
||||
extends: true,
|
||||
|
||||
@@ -43,6 +43,6 @@
|
||||
"@types/node": "^25.5.2",
|
||||
"@types/react": "^19.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
projects: [
|
||||
{
|
||||
extends: true,
|
||||
|
||||
@@ -29,6 +29,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,6 @@ export default defineConfig({
|
||||
globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))],
|
||||
pool: "threads",
|
||||
maxWorkers,
|
||||
poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } },
|
||||
minWorkers: 1,
|
||||
},
|
||||
});
|
||||
|
||||
626
pnpm-lock.yaml
generated
626
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
95
scripts/__tests__/dependency-security-floor.test.mjs
Normal file
95
scripts/__tests__/dependency-security-floor.test.mjs
Normal file
@@ -0,0 +1,95 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import YAML from "yaml";
|
||||
|
||||
const root = process.cwd();
|
||||
|
||||
const dependencyFloors = [
|
||||
{ name: "protobufjs", minimum: "7.5.5", manifestSections: ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"], lockfileMatcher: /^protobufjs@(.*)$/ },
|
||||
{ name: "vitest", minimum: "4.1.0", manifestSections: ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"], lockfileMatcher: /^vitest@(.*)$/ },
|
||||
{ name: "@vitest/coverage-v8", minimum: "4.1.0", manifestSections: ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"], lockfileMatcher: /^@vitest\/coverage-v8@(.*)$/ },
|
||||
];
|
||||
|
||||
const manifestRoots = ["packages", "plugins"];
|
||||
const ignoredPathParts = new Set(["node_modules", "dist", "build", "coverage", ".turbo"]);
|
||||
|
||||
function compareVersions(a, b) {
|
||||
const pa = a.split(".").map((part) => Number.parseInt(part, 10));
|
||||
const pb = b.split(".").map((part) => Number.parseInt(part, 10));
|
||||
for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
|
||||
const ai = Number.isFinite(pa[i]) ? pa[i] : 0;
|
||||
const bi = Number.isFinite(pb[i]) ? pb[i] : 0;
|
||||
if (ai !== bi) return ai - bi;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function minVersionFromRange(range) {
|
||||
const version = String(range).match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/u)?.[0];
|
||||
return version?.split(/[+-]/u)[0] ?? null;
|
||||
}
|
||||
|
||||
function assertRangeMeetsFloor({ location, name, range, minimum }) {
|
||||
const minVersion = minVersionFromRange(range);
|
||||
assert.ok(minVersion, `${location}: ${name} range ${range} must include an explicit semver version`);
|
||||
assert.ok(compareVersions(minVersion, minimum) >= 0, `${location}: ${name} range ${range} is below required floor ${minimum}`);
|
||||
}
|
||||
|
||||
async function collectPackageManifests(dir) {
|
||||
const entries = await readdir(dir, { withFileTypes: true });
|
||||
const manifests = [];
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (ignoredPathParts.has(entry.name)) continue;
|
||||
if (entry.isDirectory()) {
|
||||
manifests.push(...(await collectPackageManifests(fullPath)));
|
||||
} else if (entry.name === "package.json") {
|
||||
manifests.push(fullPath);
|
||||
}
|
||||
}
|
||||
return manifests;
|
||||
}
|
||||
|
||||
async function sourceManifestPaths() {
|
||||
const manifests = [path.join(root, "package.json")];
|
||||
for (const manifestRoot of manifestRoots) {
|
||||
manifests.push(...(await collectPackageManifests(path.join(root, manifestRoot))));
|
||||
}
|
||||
return manifests.sort();
|
||||
}
|
||||
|
||||
test("source manifests keep vulnerable dependency floors out", async () => {
|
||||
for (const manifestPath of await sourceManifestPaths()) {
|
||||
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
||||
const relativePath = path.relative(root, manifestPath);
|
||||
for (const floor of dependencyFloors) {
|
||||
for (const section of floor.manifestSections) {
|
||||
const range = manifest[section]?.[floor.name];
|
||||
if (range) assertRangeMeetsFloor({ location: `${relativePath} ${section}`, name: floor.name, range, minimum: floor.minimum });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("pnpm overrides pin transitive protobufjs to a safe floor", async () => {
|
||||
const manifest = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
|
||||
assertRangeMeetsFloor({ location: "package.json pnpm.overrides", name: "protobufjs", range: manifest.pnpm?.overrides?.protobufjs, minimum: "7.5.5" });
|
||||
});
|
||||
|
||||
test("lockfile resolutions satisfy dependency security floors", async () => {
|
||||
const lockfile = YAML.parse(await readFile(path.join(root, "pnpm-lock.yaml"), "utf8"));
|
||||
const packageKeys = Object.keys(lockfile.packages ?? {});
|
||||
for (const floor of dependencyFloors) {
|
||||
const matches = packageKeys
|
||||
.map((key) => key.replace(/^\//u, ""))
|
||||
.map((key) => key.match(floor.lockfileMatcher)?.[1])
|
||||
.filter(Boolean)
|
||||
.map((suffix) => suffix.split("(")[0]);
|
||||
assert.ok(matches.length > 0, `pnpm-lock.yaml must include at least one ${floor.name} resolution`);
|
||||
for (const version of matches) {
|
||||
assert.ok(compareVersions(version, floor.minimum) >= 0, `pnpm-lock.yaml resolves ${floor.name}@${version}, below required floor ${floor.minimum}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user