@ornexus/neocortex 4.60.73 → 4.60.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/release-source.json +1 -1
- package/dist/sbom.cdx.json +1216 -1210
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +2 -2
- package/packages/client/dist/adapters/adapter-registry.js +1 -108
- package/packages/client/dist/adapters/antigravity-adapter.js +2 -77
- package/packages/client/dist/adapters/claude-code-adapter.js +3 -79
- package/packages/client/dist/adapters/codex-adapter.js +2 -80
- package/packages/client/dist/adapters/cursor-adapter.js +4 -115
- package/packages/client/dist/adapters/gemini-adapter.js +2 -71
- package/packages/client/dist/adapters/grok-adapter.js +2 -42
- package/packages/client/dist/adapters/index.js +1 -22
- package/packages/client/dist/adapters/platform-detector.js +1 -212
- package/packages/client/dist/adapters/target-adapter.js +0 -12
- package/packages/client/dist/adapters/vscode-adapter.js +2 -72
- package/packages/client/dist/agent/refresh-stubs.js +2 -417
- package/packages/client/dist/agent/update-agent-yaml.js +1 -102
- package/packages/client/dist/agent/update-description.js +1 -251
- package/packages/client/dist/cache/crypto-utils.js +1 -78
- package/packages/client/dist/cache/encrypted-cache.d.ts +27 -0
- package/packages/client/dist/cache/encrypted-cache.js +1 -108
- package/packages/client/dist/cache/in-memory-asset-cache.js +1 -73
- package/packages/client/dist/cache/index.js +1 -13
- package/packages/client/dist/cache/protected-pi-boundary.js +1 -44
- package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -127
- package/packages/client/dist/checkpoint/index.js +1 -11
- package/packages/client/dist/checkpoint/shared-checkpoint-types.js +1 -32
- package/packages/client/dist/cli.js +5 -358
- package/packages/client/dist/commands/activate.js +8 -394
- package/packages/client/dist/commands/architecture-answers.js +7 -274
- package/packages/client/dist/commands/architecture-memory-terminal.js +4 -253
- package/packages/client/dist/commands/architecture-non-ui-continuation.js +1 -100
- package/packages/client/dist/commands/architecture-root-binding.js +1 -51
- package/packages/client/dist/commands/cache-status.js +2 -112
- package/packages/client/dist/commands/config.js +1 -88
- package/packages/client/dist/commands/doctor.js +2 -635
- package/packages/client/dist/commands/invoke.d.ts +38 -1
- package/packages/client/dist/commands/invoke.js +143 -7200
- package/packages/client/dist/commands/legacy-epic-terminal.js +1 -71
- package/packages/client/dist/commands/legacy-story-branch.js +1 -96
- package/packages/client/dist/commands/legacy-story-pr.js +1 -276
- package/packages/client/dist/commands/mutation-qa-checkpoint.js +5 -285
- package/packages/client/dist/commands/mutation-qa-gate.js +1 -54
- package/packages/client/dist/commands/mutation-qa-git.js +1 -41
- package/packages/client/dist/commands/mutation-qa-policy.js +1 -102
- package/packages/client/dist/commands/mutation-qa-run.js +3 -219
- package/packages/client/dist/commands/mutation-qa-verdict.js +1 -149
- package/packages/client/dist/commands/mutation-qa.js +1 -220
- package/packages/client/dist/commands/native-agent.js +2 -136
- package/packages/client/dist/commands/planning-diagnosis.js +1 -43
- package/packages/client/dist/commands/planning-generation.js +1 -53
- package/packages/client/dist/commands/planning-memory.js +1 -40
- package/packages/client/dist/commands/planning-research.js +1 -42
- package/packages/client/dist/commands/private-session.js +1 -75
- package/packages/client/dist/commands/property-qa.js +1 -139
- package/packages/client/dist/commands/qa-control.js +1 -16
- package/packages/client/dist/commands/qa-scan-browser-launch.js +1 -55
- package/packages/client/dist/commands/qa-scan-browser-route.js +1 -28
- package/packages/client/dist/commands/qa-scan-capture.js +5 -663
- package/packages/client/dist/commands/qa-scan-discovery.js +3 -376
- package/packages/client/dist/commands/qa-scan-review.js +5 -826
- package/packages/client/dist/commands/qa-synthetic-backend-capture.js +1 -266
- package/packages/client/dist/commands/qa-synthetic-backend-review.js +1 -297
- package/packages/client/dist/commands/qa-synthetic-backend.js +2 -340
- package/packages/client/dist/commands/qa-synthetic-broker-port.js +1 -85
- package/packages/client/dist/commands/qa-synthetic-browser.js +1 -39
- package/packages/client/dist/commands/refactor-characterization.js +21 -671
- package/packages/client/dist/commands/refactor-commit.js +13 -399
- package/packages/client/dist/commands/refactor-control.js +3 -142
- package/packages/client/dist/commands/refactor-cpp-assertions.js +3 -93
- package/packages/client/dist/commands/refactor-review.js +5 -283
- package/packages/client/dist/commands/refactor-root.js +7 -588
- package/packages/client/dist/commands/refresh-memory.js +3 -156
- package/packages/client/dist/commands/shared-architecture-non-ui.js +1 -24
- package/packages/client/dist/commands/story-checkpoint.js +1 -171
- package/packages/client/dist/config/host-auth-broker.d.ts +31 -0
- package/packages/client/dist/config/host-auth-broker.js +1 -0
- package/packages/client/dist/config/host-auth-reference.d.ts +32 -0
- package/packages/client/dist/config/host-auth-reference.js +1 -0
- package/packages/client/dist/config/profile-directory.js +1 -13
- package/packages/client/dist/config/resolver-selection.js +1 -278
- package/packages/client/dist/config/secure-config.js +12 -281
- package/packages/client/dist/config.js +2 -223
- package/packages/client/dist/constants.js +1 -25
- package/packages/client/dist/context/context-collector.js +2 -222
- package/packages/client/dist/context/context-sanitizer.js +1 -145
- package/packages/client/dist/continuity/autonomous-governance.js +1 -285
- package/packages/client/dist/continuity/continuity-client-state-store.js +1 -1587
- package/packages/client/dist/continuity/invoke-hooks.js +1 -129
- package/packages/client/dist/continuity/migrations/001-initial-schema.js +2 -12
- package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +2 -12
- package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +2 -12
- package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +2 -12
- package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +2 -12
- package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +2 -10
- package/packages/client/dist/continuity/native-agent-store.js +1 -69
- package/packages/client/dist/continuity/native-agent-types.d.ts +11 -0
- package/packages/client/dist/continuity/native-agent-types.js +1 -90
- package/packages/client/dist/continuity/outbox.js +1 -117
- package/packages/client/dist/continuity/runner-bootstrap-policy.js +1 -172
- package/packages/client/dist/continuity/sqlite-store.js +81 -2612
- package/packages/client/dist/effects/invoke-effect-gateways.js +1 -62
- package/packages/client/dist/errors/error-messages.d.ts +3 -0
- package/packages/client/dist/errors/error-messages.js +2 -228
- package/packages/client/dist/errors/frontmatter-diagnostics.js +1 -125
- package/packages/client/dist/evidence/collector.js +1 -347
- package/packages/client/dist/evidence/index.js +1 -1
- package/packages/client/dist/evidence/types.js +0 -2
- package/packages/client/dist/fs/atomic-state-replace.js +1 -152
- package/packages/client/dist/generated/command-registry.generated.js +1 -4076
- package/packages/client/dist/generated/public-projections.generated.js +1 -149
- package/packages/client/dist/git/direct-yolo-c01-retry.js +4 -138
- package/packages/client/dist/git/direct-yolo-checkpoint.d.ts +11 -1
- package/packages/client/dist/git/direct-yolo-checkpoint.js +4 -1234
- package/packages/client/dist/git/epic-operational-state.js +2 -672
- package/packages/client/dist/git/git-lifecycle-step-ids.js +1 -14
- package/packages/client/dist/git/git-lifecycle-store.d.ts +23 -0
- package/packages/client/dist/git/git-lifecycle-store.js +10 -1981
- package/packages/client/dist/git/legacy-story-branch-checkpoint.js +9 -259
- package/packages/client/dist/git/legacy-story-branch-setup.js +4 -348
- package/packages/client/dist/git/legacy-story-pr.js +70 -3538
- package/packages/client/dist/git/secure-filesystem-boundary.js +5 -182
- package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -284
- package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +1 -237
- package/packages/client/dist/i18n/first-run.js +2 -60
- package/packages/client/dist/index.d.ts +2 -0
- package/packages/client/dist/index.js +1 -47
- package/packages/client/dist/invocation/invoke-input.js +1 -24
- package/packages/client/dist/license/host-auth-recovery.d.ts +158 -0
- package/packages/client/dist/license/host-auth-recovery.js +1 -0
- package/packages/client/dist/license/index.js +1 -5
- package/packages/client/dist/license/license-client.d.ts +10 -1
- package/packages/client/dist/license/license-client.js +1 -304
- package/packages/client/dist/machine/fingerprint.js +2 -163
- package/packages/client/dist/machine/index.js +1 -5
- package/packages/client/dist/memory/project-memory-path-scope.js +1 -62
- package/packages/client/dist/memory/project-memory-writer.js +40 -1007
- package/packages/client/dist/memory/shared-project-memory-types.js +2 -253
- package/packages/client/dist/migrations/epic-lifecycle-backfill.js +3 -1060
- package/packages/client/dist/migrations/project-identity-capability.js +1 -50
- package/packages/client/dist/migrations/project-identity.js +7 -493
- package/packages/client/dist/migrations/state-json.js +4 -393
- package/packages/client/dist/policy/architecture-policy.js +2 -184
- package/packages/client/dist/policy/index.js +1 -7
- package/packages/client/dist/policy/shared-policy-types.js +0 -38
- package/packages/client/dist/policy/subagent-approval-policy.js +1 -55
- package/packages/client/dist/private-execution/clean-baseline.js +2 -279
- package/packages/client/dist/private-execution/codex-executor.js +4 -574
- package/packages/client/dist/private-execution/codex-host-runtime.js +1 -137
- package/packages/client/dist/private-execution/codex-lifecycle-guard.js +1 -97
- package/packages/client/dist/private-execution/codex-scoped-permissions.js +1 -128
- package/packages/client/dist/private-execution/delivery.js +13 -410
- package/packages/client/dist/private-execution/diagnosis-root-review.js +2 -294
- package/packages/client/dist/private-execution/frozen-handoff.js +6 -19
- package/packages/client/dist/private-execution/implementation-validation.js +2 -106
- package/packages/client/dist/private-execution/isolated-admission.js +23 -1432
- package/packages/client/dist/private-execution/isolated-session.js +5 -725
- package/packages/client/dist/private-execution/isolated-workspace.js +1 -122
- package/packages/client/dist/private-execution/pact-consumer-runtime.js +3 -409
- package/packages/client/dist/private-execution/pact-test-source.js +1 -258
- package/packages/client/dist/private-execution/pact-toolchain.js +3 -290
- package/packages/client/dist/private-execution/pact-write-set.js +1 -172
- package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +5 -406
- package/packages/client/dist/private-execution/planning-generation-checkpoint.js +5 -462
- package/packages/client/dist/private-execution/planning-memory-checkpoint.js +3 -139
- package/packages/client/dist/private-execution/planning-research-checkpoint.js +4 -325
- package/packages/client/dist/private-execution/private-report-store.js +1 -42
- package/packages/client/dist/private-execution/property-test-candidate.js +1 -103
- package/packages/client/dist/private-execution/property-test-choices.js +3 -147
- package/packages/client/dist/private-execution/property-test-plan.js +7 -271
- package/packages/client/dist/private-execution/property-test-review.js +4 -258
- package/packages/client/dist/private-execution/property-test-runtime.js +9 -372
- package/packages/client/dist/private-execution/property-test-source.js +1 -139
- package/packages/client/dist/private-execution/root-checkpoint.js +1 -80
- package/packages/client/dist/private-execution/root-memory-git-evidence.js +3 -135
- package/packages/client/dist/private-execution/root-memory-recovery.js +1 -93
- package/packages/client/dist/private-execution/root-memory-request.js +1 -29
- package/packages/client/dist/private-execution/root-memory-scope.js +1 -178
- package/packages/client/dist/private-execution/root-memory-writer.js +1 -102
- package/packages/client/dist/private-execution/session-lease.js +1 -63
- package/packages/client/dist/private-execution/shared-private-admission-types.js +1 -50
- package/packages/client/dist/private-execution/shared-project-validation.js +1 -66
- package/packages/client/dist/private-execution/shared-root-memory-types.js +1 -61
- package/packages/client/dist/private-execution/ux-source-validation.js +1 -372
- package/packages/client/dist/private-execution/ux-write-set.js +2 -191
- package/packages/client/dist/registry/registry-reconciliation.js +1 -234
- package/packages/client/dist/resilience/circuit-breaker.js +1 -170
- package/packages/client/dist/resilience/degradation-manager.js +1 -164
- package/packages/client/dist/resilience/freshness-indicator.js +1 -100
- package/packages/client/dist/resilience/index.js +1 -8
- package/packages/client/dist/resilience/recovery-detector.js +1 -74
- package/packages/client/dist/resolvers/asset-resolver.js +0 -13
- package/packages/client/dist/resolvers/local-resolver.js +8 -255
- package/packages/client/dist/resolvers/remote-resolver.js +1 -233
- package/packages/client/dist/runner/binary.js +1 -404
- package/packages/client/dist/runner/cli.js +17 -1571
- package/packages/client/dist/runner/epic-background-dispatcher.d.ts +61 -0
- package/packages/client/dist/runner/epic-background-dispatcher.js +1 -0
- package/packages/client/dist/runner/headless/adapters.js +1 -49
- package/packages/client/dist/runner/headless/background-root.d.ts +65 -0
- package/packages/client/dist/runner/headless/background-root.js +1 -0
- package/packages/client/dist/runner/headless/claude-code-adapter.js +1 -79
- package/packages/client/dist/runner/headless/continuous-story-workspace.js +11 -1628
- package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +1 -69
- package/packages/client/dist/runner/headless/epic-merge-candidate-qa.d.ts +1 -1
- package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +2 -110
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.d.ts +2 -0
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +10 -1236
- package/packages/client/dist/runner/headless/epic-terminal-git-runner.d.ts +27 -2
- package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +2 -318
- package/packages/client/dist/runner/headless/fake-adapter.js +1 -62
- package/packages/client/dist/runner/headless/managed-environment-cleanup.d.ts +95 -0
- package/packages/client/dist/runner/headless/managed-environment-cleanup.js +1 -0
- package/packages/client/dist/runner/headless/managed-profile.js +1 -132
- package/packages/client/dist/runner/headless/opencode-adapter.js +2 -145
- package/packages/client/dist/runner/headless/prepared-story-bundle.js +1 -43
- package/packages/client/dist/runner/headless/resolver.js +1 -231
- package/packages/client/dist/runner/headless/run-yolo.js +1 -885
- package/packages/client/dist/runner/headless/run-yoloop.d.ts +1 -0
- package/packages/client/dist/runner/headless/run-yoloop.js +2 -2574
- package/packages/client/dist/runner/headless/types.js +2 -574
- package/packages/client/dist/runner/local-console.js +13 -396
- package/packages/client/dist/runner/portal-sync-worker.js +1 -1487
- package/packages/client/dist/runner/project-identity.js +1 -106
- package/packages/client/dist/runner/scheduler.js +7 -995
- package/packages/client/dist/runner-cli.js +2 -18
- package/packages/client/dist/state/invocation-shape.js +1 -13
- package/packages/client/dist/state/project-state-snapshot.d.ts +7 -0
- package/packages/client/dist/state/project-state-snapshot.js +1 -1731
- package/packages/client/dist/state/state-json-repair.js +3 -785
- package/packages/client/dist/state/structural-dag-projection.js +1 -118
- package/packages/client/dist/telemetry/index.js +1 -5
- package/packages/client/dist/telemetry/offline-queue.js +1 -131
- package/packages/client/dist/tier/index.js +1 -5
- package/packages/client/dist/tier/tier-aware-client.js +1 -405
- package/packages/client/dist/types/index.js +1 -38
- package/packages/client/dist/version.js +1 -27
- package/packages/client/dist/yolo/change-evidence-collector.d.ts +21 -1
- package/packages/client/dist/yolo/change-evidence-collector.js +1 -126
- package/packages/client/dist/yolo/invoke-envelope-capability.js +1 -62
- package/packages/client/dist/yolo/spec-envelope-collector.js +5 -184
- package/packages/client/dist/yoloop/discovery-hook.js +2 -300
- package/packages/client/dist/yoloop/index.js +1 -8
- package/packages/client/dist/yoloop/invoke-hooks.js +12 -669
- package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1007
- package/packages/client/dist/yoloop/shared-yoloop-types.js +1 -33
- package/packages/client/dist/yoloop/target-normalization.js +1 -180
- package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1920
- package/packages/shared/dist/invocation/explicit-intent-recovery.d.ts +83 -0
- package/packages/shared/dist/invocation/explicit-intent-recovery.js +187 -0
- package/packages/shared/dist/invocation/index.d.ts +7 -0
- package/packages/shared/dist/invocation/index.js +22 -0
- package/packages/shared/dist/yolo/change-evidence.js +1 -1
- package/packages/shared/dist/yoloop/epic-terminal-git.js +144 -2
- package/targets-stubs/antigravity/gemini.md +1 -1
- package/targets-stubs/antigravity/skill/SKILL.md +1 -1
- package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex-root.md +2 -2
- package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex.md +2 -2
- package/targets-stubs/codex/AGENTS.md +24 -21
- package/targets-stubs/codex/README.md +6 -1
- package/targets-stubs/codex/neocortex.toml +7 -2
- package/targets-stubs/cursor/agent.md +2 -2
- package/targets-stubs/gemini-cli/agent.md +2 -2
- package/targets-stubs/opencode/neocortex-root.md +16 -1
- package/targets-stubs/opencode/neocortex.md +6 -0
- package/targets-stubs/vscode/neocortex.agent.md +2 -2
|
@@ -1,258 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Static first-pass contract for isolated Pact artifacts. Parsing never runs
|
|
3
|
-
* generated code. A passing result is not a Pact execution/QA receipt.
|
|
4
|
-
*/
|
|
5
|
-
import { parse } from '@babel/parser';
|
|
6
|
-
import { lstatSync, realpathSync } from 'node:fs';
|
|
7
|
-
import { join, posix, resolve } from 'node:path';
|
|
8
|
-
const MAX_SOURCE_BYTES = 256 * 1024;
|
|
9
|
-
const MAX_AST_NODES = 100_000;
|
|
10
|
-
function record(value) {
|
|
11
|
-
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
12
|
-
}
|
|
13
|
-
function importedNames(node) {
|
|
14
|
-
const names = new Set();
|
|
15
|
-
if (!Array.isArray(node.specifiers))
|
|
16
|
-
return names;
|
|
17
|
-
for (const specifier of node.specifiers) {
|
|
18
|
-
if (!record(specifier) || specifier.type !== 'ImportSpecifier'
|
|
19
|
-
|| !record(specifier.imported) || typeof specifier.imported.name !== 'string')
|
|
20
|
-
continue;
|
|
21
|
-
names.add(specifier.imported.name);
|
|
22
|
-
}
|
|
23
|
-
return names;
|
|
24
|
-
}
|
|
25
|
-
function projectImportNames(node, testPath, projectRoot) {
|
|
26
|
-
const importPath = record(node.source) ? node.source.value : undefined;
|
|
27
|
-
if (typeof importPath !== 'string' || importPath.length > 240
|
|
28
|
-
|| !/^\.\.?\/[A-Za-z0-9_./-]+$/.test(importPath)
|
|
29
|
-
|| node.importKind === 'type' || !Array.isArray(node.specifiers)
|
|
30
|
-
|| node.specifiers.length < 1 || node.specifiers.length > 16) {
|
|
31
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
32
|
-
}
|
|
33
|
-
const relative = posix.normalize(posix.join(posix.dirname(testPath), importPath));
|
|
34
|
-
if (!/^(?:src|app|lib|packages|clients)\/[A-Za-z0-9_./-]+$/.test(relative)
|
|
35
|
-
|| relative.split('/').some((segment) => segment === '..' || segment === '.')) {
|
|
36
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
37
|
-
}
|
|
38
|
-
const root = resolve(projectRoot);
|
|
39
|
-
if (realpathSync(root) !== root || !lstatSync(root).isDirectory()) {
|
|
40
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
41
|
-
}
|
|
42
|
-
const candidates = /\.(?:ts|tsx|js|mjs)$/.test(relative)
|
|
43
|
-
? [relative, ...(relative.endsWith('.js') ? [`${relative.slice(0, -3)}.ts`,
|
|
44
|
-
`${relative.slice(0, -3)}.tsx`] : [])]
|
|
45
|
-
: [`${relative}.ts`, `${relative}.tsx`, `${relative}.js`, `${relative}.mjs`,
|
|
46
|
-
`${relative}/index.ts`, `${relative}/index.tsx`, `${relative}/index.js`];
|
|
47
|
-
const matches = [];
|
|
48
|
-
for (const candidate of candidates) {
|
|
49
|
-
let current = root;
|
|
50
|
-
try {
|
|
51
|
-
const parts = candidate.split('/');
|
|
52
|
-
for (const [index, part] of parts.entries()) {
|
|
53
|
-
current = join(current, part);
|
|
54
|
-
const entry = lstatSync(current);
|
|
55
|
-
if (entry.isSymbolicLink() || (index === parts.length - 1
|
|
56
|
-
? !entry.isFile() || entry.size < 1 || entry.size > 1024 * 1024
|
|
57
|
-
: !entry.isDirectory()))
|
|
58
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
59
|
-
}
|
|
60
|
-
matches.push(candidate);
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
if (error.code !== 'ENOENT') {
|
|
64
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if (matches.length !== 1)
|
|
69
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
70
|
-
const names = new Set();
|
|
71
|
-
for (const specifier of node.specifiers) {
|
|
72
|
-
if (!record(specifier) || !['ImportSpecifier', 'ImportDefaultSpecifier'].includes(specifier.type)
|
|
73
|
-
|| specifier.importKind === 'type' || !record(specifier.local)
|
|
74
|
-
|| specifier.local.type !== 'Identifier' || typeof specifier.local.name !== 'string') {
|
|
75
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
76
|
-
}
|
|
77
|
-
names.add(specifier.local.name);
|
|
78
|
-
}
|
|
79
|
-
return names;
|
|
80
|
-
}
|
|
81
|
-
function memberName(node) {
|
|
82
|
-
if (!record(node) || node.type !== 'MemberExpression' || node.computed === true
|
|
83
|
-
|| !record(node.property) || node.property.type !== 'Identifier')
|
|
84
|
-
return undefined;
|
|
85
|
-
return typeof node.property.name === 'string' ? node.property.name : undefined;
|
|
86
|
-
}
|
|
87
|
-
function requestLiterals(node) {
|
|
88
|
-
if (!record(node) || node.type !== 'ObjectExpression' || !Array.isArray(node.properties))
|
|
89
|
-
return undefined;
|
|
90
|
-
let method;
|
|
91
|
-
let path;
|
|
92
|
-
for (const property of node.properties) {
|
|
93
|
-
if (!record(property) || property.type !== 'ObjectProperty' || property.computed === true
|
|
94
|
-
|| !record(property.key) || !record(property.value))
|
|
95
|
-
return undefined;
|
|
96
|
-
const name = property.key.type === 'Identifier' ? property.key.name
|
|
97
|
-
: property.key.type === 'StringLiteral' ? property.key.value : undefined;
|
|
98
|
-
if (name === 'method' || name === 'path') {
|
|
99
|
-
if (property.value.type !== 'StringLiteral' || typeof property.value.value !== 'string')
|
|
100
|
-
return undefined;
|
|
101
|
-
if (name === 'method') {
|
|
102
|
-
if (method !== undefined)
|
|
103
|
-
return undefined;
|
|
104
|
-
method = property.value.value;
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
if (path !== undefined)
|
|
108
|
-
return undefined;
|
|
109
|
-
path = property.value.value;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return method !== undefined && path !== undefined ? { method, path } : undefined;
|
|
114
|
-
}
|
|
115
|
-
function matchesEndpointPath(template, actual) {
|
|
116
|
-
if (template.length > 180 || actual.length > 2048 || /[?#]/.test(actual))
|
|
117
|
-
return false;
|
|
118
|
-
const expected = template.split('/');
|
|
119
|
-
const candidate = actual.split('/');
|
|
120
|
-
if (expected.length !== candidate.length)
|
|
121
|
-
return false;
|
|
122
|
-
return expected.every((segment, index) => {
|
|
123
|
-
const value = candidate[index];
|
|
124
|
-
if (!segment.includes('{') && !segment.includes('}'))
|
|
125
|
-
return segment === value;
|
|
126
|
-
// One parameter per segment keeps matching linear and rejects ambiguous
|
|
127
|
-
// adjacent templates instead of relying on a backtracking regex.
|
|
128
|
-
const parameter = /^([^{}]*)\{[^{}]+\}([^{}]*)$/.exec(segment);
|
|
129
|
-
if (!parameter || !value.startsWith(parameter[1]) || !value.endsWith(parameter[2]))
|
|
130
|
-
return false;
|
|
131
|
-
return value.length > parameter[1].length + parameter[2].length;
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
/** Reject comment-only, malformed and non-Pact files before a root patch effect. */
|
|
135
|
-
export function validatePactTestSource(path, source, brokerUrl, expectedInteraction, projectRoot, matchersAggressive) {
|
|
136
|
-
const kind = path === 'tests/pact/provider/verify.test.ts' ? 'provider' : 'consumer';
|
|
137
|
-
if ((kind === 'consumer' && !/^tests\/pact\/consumer\/[a-z0-9-]+\.test\.ts$/.test(path))
|
|
138
|
-
|| typeof source !== 'string' || Buffer.byteLength(source, 'utf8') < 80
|
|
139
|
-
|| Buffer.byteLength(source, 'utf8') > MAX_SOURCE_BYTES
|
|
140
|
-
|| brokerUrl !== null && source.includes(brokerUrl)) {
|
|
141
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
142
|
-
}
|
|
143
|
-
let ast;
|
|
144
|
-
try {
|
|
145
|
-
ast = parse(source, { sourceType: 'module', plugins: ['typescript'] });
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
149
|
-
}
|
|
150
|
-
const pactImports = new Set();
|
|
151
|
-
const vitestImports = new Set();
|
|
152
|
-
const appImports = new Set();
|
|
153
|
-
for (const statement of ast.program.body) {
|
|
154
|
-
if (statement.type !== 'ImportDeclaration')
|
|
155
|
-
continue;
|
|
156
|
-
const importPath = statement.source.value;
|
|
157
|
-
if (importPath !== '@pact-foundation/pact' && importPath !== 'vitest') {
|
|
158
|
-
if (kind !== 'consumer' || !projectRoot) {
|
|
159
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
160
|
-
}
|
|
161
|
-
for (const name of projectImportNames(statement, path, projectRoot)) {
|
|
162
|
-
if (appImports.has(name) || ['fetch', 'describe', 'it', 'expect',
|
|
163
|
-
'PactV3', 'MatchersV3', 'Verifier'].includes(name)) {
|
|
164
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
165
|
-
}
|
|
166
|
-
appImports.add(name);
|
|
167
|
-
}
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const names = importedNames(statement);
|
|
171
|
-
for (const name of names)
|
|
172
|
-
(importPath === '@pact-foundation/pact' ? pactImports : vitestImports).add(name);
|
|
173
|
-
}
|
|
174
|
-
if (!vitestImports.has('describe') || !vitestImports.has('it')
|
|
175
|
-
|| (kind === 'consumer' && (!pactImports.has('PactV3') || !vitestImports.has('expect')))
|
|
176
|
-
|| (kind === 'provider' && !pactImports.has('Verifier'))
|
|
177
|
-
|| [...appImports].some((name) => pactImports.has(name) || vitestImports.has(name))) {
|
|
178
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
179
|
-
}
|
|
180
|
-
// The private root decision is authoritative; generated tests cannot broaden
|
|
181
|
-
// literal contracts with matcher helpers when exact correspondence was chosen.
|
|
182
|
-
if (kind === 'consumer' && matchersAggressive === false
|
|
183
|
-
&& [...pactImports].some((name) => name !== 'PactV3')) {
|
|
184
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
185
|
-
}
|
|
186
|
-
const calls = new Set();
|
|
187
|
-
const constructors = new Set();
|
|
188
|
-
let brokerEnv = false;
|
|
189
|
-
let expectedRequest = false;
|
|
190
|
-
let appClientCalled = false;
|
|
191
|
-
const stack = [
|
|
192
|
-
{ node: ast.program, inExecuteTest: false },
|
|
193
|
-
];
|
|
194
|
-
let visited = 0;
|
|
195
|
-
while (stack.length > 0) {
|
|
196
|
-
const item = stack.pop();
|
|
197
|
-
const current = item.node;
|
|
198
|
-
if (!record(current))
|
|
199
|
-
continue;
|
|
200
|
-
if (++visited > MAX_AST_NODES)
|
|
201
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
202
|
-
if (current.type === 'NewExpression' && record(current.callee)
|
|
203
|
-
&& current.callee.type === 'Identifier' && typeof current.callee.name === 'string') {
|
|
204
|
-
constructors.add(current.callee.name);
|
|
205
|
-
if (item.inExecuteTest && appImports.has(current.callee.name))
|
|
206
|
-
appClientCalled = true;
|
|
207
|
-
}
|
|
208
|
-
if (current.type === 'CallExpression') {
|
|
209
|
-
const callee = current.callee;
|
|
210
|
-
if (record(callee) && callee.type === 'Identifier' && typeof callee.name === 'string') {
|
|
211
|
-
calls.add(callee.name);
|
|
212
|
-
if (item.inExecuteTest && appImports.has(callee.name))
|
|
213
|
-
appClientCalled = true;
|
|
214
|
-
}
|
|
215
|
-
const member = memberName(callee);
|
|
216
|
-
if (member)
|
|
217
|
-
calls.add(member);
|
|
218
|
-
if (member === 'withRequest' && expectedInteraction && Array.isArray(current.arguments)) {
|
|
219
|
-
const request = requestLiterals(current.arguments[0]);
|
|
220
|
-
expectedRequest ||= request?.method.toUpperCase() === expectedInteraction.method
|
|
221
|
-
&& matchesEndpointPath(expectedInteraction.endpointPath, request?.path ?? '');
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
if (current.type === 'MemberExpression' && memberName(current) === 'PACT_BROKER_URL'
|
|
225
|
-
&& record(current.object) && memberName(current.object) === 'env'
|
|
226
|
-
&& record(current.object.object) && current.object.object.type === 'Identifier'
|
|
227
|
-
&& current.object.object.name === 'process')
|
|
228
|
-
brokerEnv = true;
|
|
229
|
-
for (const [key, value] of Object.entries(current)) {
|
|
230
|
-
if (key === 'loc' || key === 'extra' || key === 'comments'
|
|
231
|
-
|| key === 'leadingComments' || key === 'trailingComments' || key === 'innerComments')
|
|
232
|
-
continue;
|
|
233
|
-
const inExecuteTest = item.inExecuteTest
|
|
234
|
-
|| current.type === 'CallExpression' && memberName(current.callee) === 'executeTest'
|
|
235
|
-
&& key === 'arguments';
|
|
236
|
-
if (Array.isArray(value)) {
|
|
237
|
-
for (const node of value)
|
|
238
|
-
stack.push({ node, inExecuteTest });
|
|
239
|
-
}
|
|
240
|
-
else if (record(value))
|
|
241
|
-
stack.push({ node: value, inExecuteTest });
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
const requiredCalls = kind === 'consumer'
|
|
245
|
-
? ['describe', 'it', 'withRequest', 'willRespondWith', 'executeTest', 'expect']
|
|
246
|
-
: ['describe', 'it', 'verifyProvider'];
|
|
247
|
-
if (requiredCalls.some((call) => !calls.has(call))
|
|
248
|
-
|| !constructors.has(kind === 'consumer' ? 'PactV3' : 'Verifier')
|
|
249
|
-
|| kind === 'consumer' && expectedInteraction !== undefined
|
|
250
|
-
&& (expectedInteraction.filePath !== path || !expectedRequest)
|
|
251
|
-
|| kind === 'provider' && brokerUrl !== null && !brokerEnv) {
|
|
252
|
-
throw new Error('private-execution-pact-source-invalid');
|
|
253
|
-
}
|
|
254
|
-
return Object.freeze({ schemaVersion: 1,
|
|
255
|
-
consumerBinding: kind === 'provider' ? 'provider-template'
|
|
256
|
-
: appClientCalled ? 'app-import-candidate' : 'mock-only-scaffold',
|
|
257
|
-
executionVerified: false, publicSafe: true });
|
|
258
|
-
}
|
|
1
|
+
import{parse as T}from"@babel/parser";import{lstatSync as A,realpathSync as C}from"node:fs";import{join as k,posix as S,resolve as I}from"node:path";const V=256*1024,_=1e5;function a(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function O(e){const n=new Set;if(!Array.isArray(e.specifiers))return n;for(const s of e.specifiers)!a(s)||s.type!=="ImportSpecifier"||!a(s.imported)||typeof s.imported.name!="string"||n.add(s.imported.name);return n}function q(e,n,s){const t=a(e.source)?e.source.value:void 0;if(typeof t!="string"||t.length>240||!/^\.\.?\/[A-Za-z0-9_./-]+$/.test(t)||e.importKind==="type"||!Array.isArray(e.specifiers)||e.specifiers.length<1||e.specifiers.length>16)throw new Error("private-execution-pact-source-invalid");const r=S.normalize(S.join(S.dirname(n),t));if(!/^(?:src|app|lib|packages|clients)\/[A-Za-z0-9_./-]+$/.test(r)||r.split("/").some(o=>o===".."||o==="."))throw new Error("private-execution-pact-source-invalid");const m=I(s);if(C(m)!==m||!A(m).isDirectory())throw new Error("private-execution-pact-source-invalid");const c=/\.(?:ts|tsx|js|mjs)$/.test(r)?[r,...r.endsWith(".js")?[`${r.slice(0,-3)}.ts`,`${r.slice(0,-3)}.tsx`]:[]]:[`${r}.ts`,`${r}.tsx`,`${r}.js`,`${r}.mjs`,`${r}/index.ts`,`${r}/index.tsx`,`${r}/index.js`],l=[];for(const o of c){let d=m;try{const y=o.split("/");for(const[x,E]of y.entries()){d=k(d,E);const v=A(d);if(v.isSymbolicLink()||(x===y.length-1?!v.isFile()||v.size<1||v.size>1024*1024:!v.isDirectory()))throw new Error("private-execution-pact-source-invalid")}l.push(o)}catch(y){if(y.code!=="ENOENT")throw new Error("private-execution-pact-source-invalid")}}if(l.length!==1)throw new Error("private-execution-pact-source-invalid");const h=new Set;for(const o of e.specifiers){if(!a(o)||!["ImportSpecifier","ImportDefaultSpecifier"].includes(o.type)||o.importKind==="type"||!a(o.local)||o.local.type!=="Identifier"||typeof o.local.name!="string")throw new Error("private-execution-pact-source-invalid");h.add(o.local.name)}return h}function b(e){if(!(!a(e)||e.type!=="MemberExpression"||e.computed===!0||!a(e.property)||e.property.type!=="Identifier"))return typeof e.property.name=="string"?e.property.name:void 0}function z(e){if(!a(e)||e.type!=="ObjectExpression"||!Array.isArray(e.properties))return;let n,s;for(const t of e.properties){if(!a(t)||t.type!=="ObjectProperty"||t.computed===!0||!a(t.key)||!a(t.value))return;const r=t.key.type==="Identifier"?t.key.name:t.key.type==="StringLiteral"?t.key.value:void 0;if(r==="method"||r==="path"){if(t.value.type!=="StringLiteral"||typeof t.value.value!="string")return;if(r==="method"){if(n!==void 0)return;n=t.value.value}else{if(s!==void 0)return;s=t.value.value}}}return n!==void 0&&s!==void 0?{method:n,path:s}:void 0}function L(e,n){if(e.length>180||n.length>2048||/[?#]/.test(n))return!1;const s=e.split("/"),t=n.split("/");return s.length!==t.length?!1:s.every((r,m)=>{const c=t[m];if(!r.includes("{")&&!r.includes("}"))return r===c;const l=/^([^{}]*)\{[^{}]+\}([^{}]*)$/.exec(r);return!l||!c.startsWith(l[1])||!c.endsWith(l[2])?!1:c.length>l[1].length+l[2].length})}function M(e,n,s,t,r,m){const c=e==="tests/pact/provider/verify.test.ts"?"provider":"consumer";if(c==="consumer"&&!/^tests\/pact\/consumer\/[a-z0-9-]+\.test\.ts$/.test(e)||typeof n!="string"||Buffer.byteLength(n,"utf8")<80||Buffer.byteLength(n,"utf8")>V||s!==null&&n.includes(s))throw new Error("private-execution-pact-source-invalid");let l;try{l=T(n,{sourceType:"module",plugins:["typescript"]})}catch{throw new Error("private-execution-pact-source-invalid")}const h=new Set,o=new Set,d=new Set;for(const p of l.program.body){if(p.type!=="ImportDeclaration")continue;const i=p.source.value;if(i!=="@pact-foundation/pact"&&i!=="vitest"){if(c!=="consumer"||!r)throw new Error("private-execution-pact-source-invalid");for(const f of q(p,e,r)){if(d.has(f)||["fetch","describe","it","expect","PactV3","MatchersV3","Verifier"].includes(f))throw new Error("private-execution-pact-source-invalid");d.add(f)}continue}const u=O(p);for(const f of u)(i==="@pact-foundation/pact"?h:o).add(f)}if(!o.has("describe")||!o.has("it")||c==="consumer"&&(!h.has("PactV3")||!o.has("expect"))||c==="provider"&&!h.has("Verifier")||[...d].some(p=>h.has(p)||o.has(p)))throw new Error("private-execution-pact-source-invalid");if(c==="consumer"&&m===!1&&[...h].some(p=>p!=="PactV3"))throw new Error("private-execution-pact-source-invalid");const y=new Set,x=new Set;let E=!1,v=!1,j=!1;const g=[{node:l.program,inExecuteTest:!1}];let $=0;for(;g.length>0;){const p=g.pop(),i=p.node;if(a(i)){if(++$>_)throw new Error("private-execution-pact-source-invalid");if(i.type==="NewExpression"&&a(i.callee)&&i.callee.type==="Identifier"&&typeof i.callee.name=="string"&&(x.add(i.callee.name),p.inExecuteTest&&d.has(i.callee.name)&&(j=!0)),i.type==="CallExpression"){const u=i.callee;a(u)&&u.type==="Identifier"&&typeof u.name=="string"&&(y.add(u.name),p.inExecuteTest&&d.has(u.name)&&(j=!0));const f=b(u);if(f&&y.add(f),f==="withRequest"&&t&&Array.isArray(i.arguments)){const w=z(i.arguments[0]);v||=w?.method.toUpperCase()===t.method&&L(t.endpointPath,w?.path??"")}}i.type==="MemberExpression"&&b(i)==="PACT_BROKER_URL"&&a(i.object)&&b(i.object)==="env"&&a(i.object.object)&&i.object.object.type==="Identifier"&&i.object.object.name==="process"&&(E=!0);for(const[u,f]of Object.entries(i)){if(u==="loc"||u==="extra"||u==="comments"||u==="leadingComments"||u==="trailingComments"||u==="innerComments")continue;const w=p.inExecuteTest||i.type==="CallExpression"&&b(i.callee)==="executeTest"&&u==="arguments";if(Array.isArray(f))for(const P of f)g.push({node:P,inExecuteTest:w});else a(f)&&g.push({node:f,inExecuteTest:w})}}}if((c==="consumer"?["describe","it","withRequest","willRespondWith","executeTest","expect"]:["describe","it","verifyProvider"]).some(p=>!y.has(p))||!x.has(c==="consumer"?"PactV3":"Verifier")||c==="consumer"&&t!==void 0&&(t.filePath!==e||!v)||c==="provider"&&s!==null&&!E)throw new Error("private-execution-pact-source-invalid");return Object.freeze({schemaVersion:1,consumerBinding:c==="provider"?"provider-template":j?"app-import-candidate":"mock-only-scaffold",executionVerified:!1,publicSafe:!0})}export{M as validatePactTestSource};
|
|
@@ -1,290 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* test-execution/QA receipt.
|
|
5
|
-
*/
|
|
6
|
-
import { spawnSync } from 'node:child_process';
|
|
7
|
-
import { createHash } from 'node:crypto';
|
|
8
|
-
import { constants } from 'node:fs';
|
|
9
|
-
import { closeSync, fstatSync, lstatSync, mkdtempSync, openSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
|
-
import { tmpdir } from 'node:os';
|
|
11
|
-
import { join, resolve } from 'node:path';
|
|
12
|
-
import { parseDocument } from 'yaml';
|
|
13
|
-
function result(outcome, reasonCode) {
|
|
14
|
-
return Object.freeze({ schemaVersion: 1, outcome, reasonCode,
|
|
15
|
-
executionVerified: false, publicSafe: true });
|
|
16
|
-
}
|
|
17
|
-
function record(value) {
|
|
18
|
-
return !!value && typeof value === 'object' && !Array.isArray(value)
|
|
19
|
-
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
20
|
-
}
|
|
21
|
-
function readRegular(root, relative, maxBytes) {
|
|
22
|
-
const path = join(root, relative);
|
|
23
|
-
let handle;
|
|
24
|
-
try {
|
|
25
|
-
handle = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
if (error.code === 'ENOENT')
|
|
29
|
-
return undefined;
|
|
30
|
-
throw new Error('pact-toolchain-path-unsafe');
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
const entry = fstatSync(handle);
|
|
34
|
-
if (!entry.isFile() || entry.size < 1 || entry.size > maxBytes) {
|
|
35
|
-
throw new Error('pact-toolchain-path-unsafe');
|
|
36
|
-
}
|
|
37
|
-
return new TextDecoder('utf-8', { fatal: true }).decode(readFileSync(handle));
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
closeSync(handle);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function parseStrictJson(text) {
|
|
44
|
-
try {
|
|
45
|
-
const document = parseDocument(text, { strict: true, uniqueKeys: true,
|
|
46
|
-
schema: 'core', customTags: [] });
|
|
47
|
-
if (document.errors.length || document.warnings.length)
|
|
48
|
-
return undefined;
|
|
49
|
-
const parsed = JSON.parse(text);
|
|
50
|
-
return record(parsed) ? parsed : undefined;
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/** No project mutation, dependency installation, script execution, or private values. */
|
|
57
|
-
export function inspectPactToolchain(projectRoot, generateProvider) {
|
|
58
|
-
if (typeof generateProvider !== 'boolean') {
|
|
59
|
-
return result('unsupported', 'manifest-invalid');
|
|
60
|
-
}
|
|
61
|
-
const root = resolve(projectRoot);
|
|
62
|
-
try {
|
|
63
|
-
if (realpathSync(root) !== root || !lstatSync(root).isDirectory()) {
|
|
64
|
-
return result('unsupported', 'manifest-invalid');
|
|
65
|
-
}
|
|
66
|
-
for (const alternate of ['pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb']) {
|
|
67
|
-
if (readRegular(root, alternate, 8 * 1024 * 1024) !== undefined) {
|
|
68
|
-
return result('unsupported', 'package-manager-unsupported');
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
const manifestText = readRegular(root, 'package.json', 1024 * 1024);
|
|
72
|
-
if (manifestText === undefined)
|
|
73
|
-
return result('requires-root-integration', 'manifest-missing');
|
|
74
|
-
const manifest = parseStrictJson(manifestText);
|
|
75
|
-
if (!manifest)
|
|
76
|
-
return result('unsupported', 'manifest-invalid');
|
|
77
|
-
if (manifest.packageManager !== undefined
|
|
78
|
-
&& (typeof manifest.packageManager !== 'string'
|
|
79
|
-
|| !/^npm@[0-9]+(?:\.[0-9]+){0,2}$/.test(manifest.packageManager))) {
|
|
80
|
-
return result('unsupported', 'package-manager-unsupported');
|
|
81
|
-
}
|
|
82
|
-
const scripts = manifest.scripts;
|
|
83
|
-
if (!record(scripts) || typeof scripts['pact:consumer'] !== 'string'
|
|
84
|
-
|| !/^vitest run(?: --maxWorkers=1)? tests\/pact\/consumer\/$/.test(scripts['pact:consumer'])
|
|
85
|
-
|| generateProvider && (typeof scripts['pact:provider'] !== 'string'
|
|
86
|
-
|| !/^vitest run(?: --maxWorkers=1)? tests\/pact\/provider\/$/.test(scripts['pact:provider']))) {
|
|
87
|
-
return result('requires-root-integration', 'scripts-missing');
|
|
88
|
-
}
|
|
89
|
-
const devDependencies = manifest.devDependencies;
|
|
90
|
-
if (!record(devDependencies)
|
|
91
|
-
|| typeof devDependencies['@pact-foundation/pact'] !== 'string'
|
|
92
|
-
|| typeof devDependencies.vitest !== 'string') {
|
|
93
|
-
return result('requires-root-integration', 'dependencies-missing');
|
|
94
|
-
}
|
|
95
|
-
const boundedVersion = /^(?:\^|~)?[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?$/;
|
|
96
|
-
if (!boundedVersion.test(devDependencies['@pact-foundation/pact'])
|
|
97
|
-
|| !boundedVersion.test(devDependencies.vitest)) {
|
|
98
|
-
return result('unsupported', 'dependency-spec-unsupported');
|
|
99
|
-
}
|
|
100
|
-
const lockText = readRegular(root, 'package-lock.json', 8 * 1024 * 1024);
|
|
101
|
-
if (lockText === undefined)
|
|
102
|
-
return result('requires-root-integration', 'lockfile-missing');
|
|
103
|
-
const lock = parseStrictJson(lockText);
|
|
104
|
-
if (!lock || !record(lock.packages) || !record(lock.packages[''])
|
|
105
|
-
|| ![2, 3].includes(lock.lockfileVersion)) {
|
|
106
|
-
return result('unsupported', 'lockfile-invalid');
|
|
107
|
-
}
|
|
108
|
-
const lockedDeps = lock.packages[''].devDependencies;
|
|
109
|
-
const lockedPact = lock.packages['node_modules/@pact-foundation/pact'];
|
|
110
|
-
const lockedVitest = lock.packages['node_modules/vitest'];
|
|
111
|
-
if (!record(lockedDeps)
|
|
112
|
-
|| lockedDeps['@pact-foundation/pact'] !== devDependencies['@pact-foundation/pact']
|
|
113
|
-
|| lockedDeps.vitest !== devDependencies.vitest
|
|
114
|
-
|| !record(lockedPact) || typeof lockedPact.version !== 'string'
|
|
115
|
-
|| !boundedVersion.test(lockedPact.version)
|
|
116
|
-
|| !record(lockedVitest) || typeof lockedVitest.version !== 'string'
|
|
117
|
-
|| !boundedVersion.test(lockedVitest.version)) {
|
|
118
|
-
return result('requires-root-integration', 'lockfile-drift');
|
|
119
|
-
}
|
|
120
|
-
return result('manifest-aligned', 'manifest-aligned-unverified');
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
return result('unsupported', 'manifest-invalid');
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const PACT_VERSION = '17.1.4';
|
|
127
|
-
const VITEST_VERSION = '3.2.4';
|
|
128
|
-
const CONSUMER_SCRIPT = 'vitest run --maxWorkers=1 tests/pact/consumer/';
|
|
129
|
-
const PROVIDER_SCRIPT = 'vitest run --maxWorkers=1 tests/pact/provider/';
|
|
130
|
-
const BOUNDED_NPM_SPEC = /^(?:\^|~)?[0-9]+\.[0-9]+\.[0-9]+$/;
|
|
131
|
-
function safeRegistryLock(text) {
|
|
132
|
-
const lock = parseStrictJson(text);
|
|
133
|
-
if (!lock || ![2, 3].includes(lock.lockfileVersion)
|
|
134
|
-
|| !record(lock.packages) || Object.keys(lock.packages).length > 10_000)
|
|
135
|
-
return false;
|
|
136
|
-
for (const value of Object.values(lock.packages)) {
|
|
137
|
-
if (!record(value) || value.link === true)
|
|
138
|
-
return false;
|
|
139
|
-
if (value.resolved === undefined)
|
|
140
|
-
continue;
|
|
141
|
-
if (typeof value.resolved !== 'string' || typeof value.integrity !== 'string'
|
|
142
|
-
|| !/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(value.integrity))
|
|
143
|
-
return false;
|
|
144
|
-
try {
|
|
145
|
-
const url = new URL(value.resolved);
|
|
146
|
-
if (url.protocol !== 'https:' || url.hostname !== 'registry.npmjs.org'
|
|
147
|
-
|| url.port || url.username || url.password || url.search || url.hash
|
|
148
|
-
|| !url.pathname.startsWith('/') || url.pathname.includes('..'))
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Root-owned manifest proposal. It preserves unrelated keys and refuses
|
|
159
|
-
* conflicting scripts, non-registry dependencies and unsupported workspaces.
|
|
160
|
-
* No filesystem effect occurs here.
|
|
161
|
-
*/
|
|
162
|
-
export function planPactToolchainManifest(projectRoot, generateProvider) {
|
|
163
|
-
try {
|
|
164
|
-
if (process.platform !== 'linux' || typeof generateProvider !== 'boolean') {
|
|
165
|
-
throw new Error('unsupported');
|
|
166
|
-
}
|
|
167
|
-
const root = resolve(projectRoot);
|
|
168
|
-
if (realpathSync(root) !== root || !lstatSync(root).isDirectory())
|
|
169
|
-
throw new Error('root');
|
|
170
|
-
for (const alternate of ['.npmrc', 'npm-shrinkwrap.json', 'pnpm-lock.yaml', 'yarn.lock',
|
|
171
|
-
'bun.lock', 'bun.lockb']) {
|
|
172
|
-
try {
|
|
173
|
-
lstatSync(join(root, alternate));
|
|
174
|
-
throw new Error('alternate');
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
if (error.code !== 'ENOENT')
|
|
178
|
-
throw error;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const sourceText = readRegular(root, 'package.json', 1024 * 1024);
|
|
182
|
-
if (!sourceText)
|
|
183
|
-
throw new Error('manifest');
|
|
184
|
-
const manifest = parseStrictJson(sourceText);
|
|
185
|
-
if (!manifest || typeof manifest.name !== 'string' || !/^[a-z0-9@][a-z0-9@._/-]{0,213}$/.test(manifest.name)
|
|
186
|
-
|| typeof manifest.version !== 'string' || !BOUNDED_NPM_SPEC.test(manifest.version)
|
|
187
|
-
|| manifest.workspaces !== undefined || manifest.overrides !== undefined
|
|
188
|
-
|| (manifest.packageManager !== undefined
|
|
189
|
-
&& (typeof manifest.packageManager !== 'string'
|
|
190
|
-
|| !/^npm@[0-9]+(?:\.[0-9]+){0,2}$/.test(manifest.packageManager)))) {
|
|
191
|
-
throw new Error('manifest');
|
|
192
|
-
}
|
|
193
|
-
for (const section of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {
|
|
194
|
-
const dependencies = manifest[section];
|
|
195
|
-
if (dependencies !== undefined && (!record(dependencies)
|
|
196
|
-
|| Object.values(dependencies).some((value) => typeof value !== 'string'
|
|
197
|
-
|| !BOUNDED_NPM_SPEC.test(value))))
|
|
198
|
-
throw new Error('dependency');
|
|
199
|
-
}
|
|
200
|
-
const runtimeDeps = manifest.dependencies;
|
|
201
|
-
if (record(runtimeDeps) && (runtimeDeps['@pact-foundation/pact'] !== undefined
|
|
202
|
-
|| runtimeDeps.vitest !== undefined))
|
|
203
|
-
throw new Error('placement');
|
|
204
|
-
const scripts = manifest.scripts;
|
|
205
|
-
if (scripts !== undefined && !record(scripts))
|
|
206
|
-
throw new Error('scripts');
|
|
207
|
-
const existingScripts = record(scripts) ? scripts : {};
|
|
208
|
-
for (const name of ['prepact:consumer', 'postpact:consumer', 'prepact:provider', 'postpact:provider']) {
|
|
209
|
-
if (existingScripts[name] !== undefined)
|
|
210
|
-
throw new Error('lifecycle');
|
|
211
|
-
}
|
|
212
|
-
if ((existingScripts['pact:consumer'] !== undefined
|
|
213
|
-
&& existingScripts['pact:consumer'] !== CONSUMER_SCRIPT)
|
|
214
|
-
|| (generateProvider && existingScripts['pact:provider'] !== undefined
|
|
215
|
-
&& existingScripts['pact:provider'] !== PROVIDER_SCRIPT))
|
|
216
|
-
throw new Error('script-conflict');
|
|
217
|
-
const devDependencies = record(manifest.devDependencies) ? manifest.devDependencies : {};
|
|
218
|
-
const pact = devDependencies['@pact-foundation/pact'] ?? PACT_VERSION;
|
|
219
|
-
const vitest = devDependencies.vitest ?? VITEST_VERSION;
|
|
220
|
-
if (typeof pact !== 'string' || !BOUNDED_NPM_SPEC.test(pact)
|
|
221
|
-
|| typeof vitest !== 'string' || !BOUNDED_NPM_SPEC.test(vitest))
|
|
222
|
-
throw new Error('version');
|
|
223
|
-
const candidate = { ...manifest,
|
|
224
|
-
scripts: { ...existingScripts, 'pact:consumer': CONSUMER_SCRIPT,
|
|
225
|
-
...(generateProvider ? { 'pact:provider': PROVIDER_SCRIPT } : {}) },
|
|
226
|
-
devDependencies: { ...devDependencies, '@pact-foundation/pact': pact, vitest },
|
|
227
|
-
};
|
|
228
|
-
const indentation = /^\{\r?\n([ \t]+)\S/m.exec(sourceText)?.[1] ?? ' ';
|
|
229
|
-
const candidateText = JSON.stringify(candidate, null, indentation)
|
|
230
|
-
+ (sourceText.endsWith('\n') ? '\n' : '');
|
|
231
|
-
const sourceLockText = readRegular(root, 'package-lock.json', 8 * 1024 * 1024) ?? null;
|
|
232
|
-
if (sourceLockText !== null && !safeRegistryLock(sourceLockText))
|
|
233
|
-
throw new Error('lock');
|
|
234
|
-
return Object.freeze({ sourceText, candidateText, sourceLockText });
|
|
235
|
-
}
|
|
236
|
-
catch {
|
|
237
|
-
throw new Error('pact-toolchain-manifest-unsupported');
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Resolve a lockfile from metadata alone in a private temporary directory.
|
|
242
|
-
* npm never sees project source, local credentials, .npmrc or generated tests;
|
|
243
|
-
* ignore-scripts and package-lock-only prevent package code execution.
|
|
244
|
-
*/
|
|
245
|
-
export function preparePactToolchainCandidate(projectRoot, generateProvider) {
|
|
246
|
-
const planned = planPactToolchainManifest(projectRoot, generateProvider);
|
|
247
|
-
const scratch = mkdtempSync(join(tmpdir(), 'p222-pact-toolchain-'));
|
|
248
|
-
try {
|
|
249
|
-
writeFileSync(join(scratch, 'package.json'), planned.candidateText, { mode: 0o600 });
|
|
250
|
-
writeFileSync(join(scratch, 'user.npmrc'), '', { mode: 0o600 });
|
|
251
|
-
writeFileSync(join(scratch, 'global.npmrc'), '', { mode: 0o600 });
|
|
252
|
-
if (planned.sourceLockText !== null) {
|
|
253
|
-
writeFileSync(join(scratch, 'package-lock.json'), planned.sourceLockText, { mode: 0o600 });
|
|
254
|
-
}
|
|
255
|
-
const command = spawnSync('npm', ['install', '--package-lock-only', '--ignore-scripts',
|
|
256
|
-
'--no-audit', '--no-fund', '--registry=https://registry.npmjs.org/',
|
|
257
|
-
`--userconfig=${join(scratch, 'user.npmrc')}`,
|
|
258
|
-
`--globalconfig=${join(scratch, 'global.npmrc')}`], {
|
|
259
|
-
cwd: scratch, encoding: 'utf8', timeout: 120_000, maxBuffer: 256 * 1024,
|
|
260
|
-
env: { PATH: process.env.PATH, HOME: scratch, TMPDIR: scratch,
|
|
261
|
-
npm_config_cache: join(scratch, '.npm-cache'), CI: 'true', NO_UPDATE_NOTIFIER: '1' },
|
|
262
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
263
|
-
});
|
|
264
|
-
if (command.error || command.status !== 0)
|
|
265
|
-
throw new Error('npm');
|
|
266
|
-
const manifestText = readRegular(scratch, 'package.json', 1024 * 1024);
|
|
267
|
-
const lockText = readRegular(scratch, 'package-lock.json', 8 * 1024 * 1024);
|
|
268
|
-
const inspection = inspectPactToolchain(scratch, generateProvider);
|
|
269
|
-
if (manifestText !== planned.candidateText || !lockText || !safeRegistryLock(lockText)
|
|
270
|
-
|| inspection.outcome !== 'manifest-aligned') {
|
|
271
|
-
throw new Error('candidate');
|
|
272
|
-
}
|
|
273
|
-
const root = resolve(projectRoot);
|
|
274
|
-
if (readRegular(root, 'package.json', 1024 * 1024) !== planned.sourceText
|
|
275
|
-
|| (readRegular(root, 'package-lock.json', 8 * 1024 * 1024) ?? null) !== planned.sourceLockText) {
|
|
276
|
-
throw new Error('source-raced');
|
|
277
|
-
}
|
|
278
|
-
const sha = (value) => createHash('sha256').update(value).digest('hex');
|
|
279
|
-
return Object.freeze({ schemaVersion: 1, outcome: 'candidate-unverified',
|
|
280
|
-
sourceManifestSha256: sha(planned.sourceText),
|
|
281
|
-
sourceLockSha256: planned.sourceLockText === null ? null : sha(planned.sourceLockText),
|
|
282
|
-
manifestText, lockText, executionVerified: false });
|
|
283
|
-
}
|
|
284
|
-
catch {
|
|
285
|
-
throw new Error('pact-toolchain-candidate-unavailable');
|
|
286
|
-
}
|
|
287
|
-
finally {
|
|
288
|
-
rmSync(scratch, { recursive: true, force: true });
|
|
289
|
-
}
|
|
290
|
-
}
|
|
1
|
+
import{spawnSync as M}from"node:child_process";import{createHash as R}from"node:crypto";import{constants as j}from"node:fs";import{closeSync as V,fstatSync as P,lstatSync as O,mkdtempSync as I,openSync as $,readFileSync as z,realpathSync as D,rmSync as A,writeFileSync as w}from"node:fs";import{tmpdir as C}from"node:os";import{join as d,resolve as S}from"node:path";import{parseDocument as W}from"yaml";function p(o,r){return Object.freeze({schemaVersion:1,outcome:o,reasonCode:r,executionVerified:!1,publicSafe:!0})}function i(o){return!!o&&typeof o=="object"&&!Array.isArray(o)&&Object.getPrototypeOf(o)===Object.prototype}function m(o,r,e){const t=d(o,r);let n;try{n=$(t,j.O_RDONLY|j.O_NOFOLLOW)}catch(s){if(s.code==="ENOENT")return;throw new Error("pact-toolchain-path-unsafe")}try{const s=P(n);if(!s.isFile()||s.size<1||s.size>e)throw new Error("pact-toolchain-path-unsafe");return new TextDecoder("utf-8",{fatal:!0}).decode(z(n))}finally{V(n)}}function v(o){try{const r=W(o,{strict:!0,uniqueKeys:!0,schema:"core",customTags:[]});if(r.errors.length||r.warnings.length)return;const e=JSON.parse(o);return i(e)?e:void 0}catch{return}}function q(o,r){if(typeof r!="boolean")return p("unsupported","manifest-invalid");const e=S(o);try{if(D(e)!==e||!O(e).isDirectory())return p("unsupported","manifest-invalid");for(const T of["pnpm-lock.yaml","yarn.lock","bun.lock","bun.lockb"])if(m(e,T,8*1024*1024)!==void 0)return p("unsupported","package-manager-unsupported");const t=m(e,"package.json",1024*1024);if(t===void 0)return p("requires-root-integration","manifest-missing");const n=v(t);if(!n)return p("unsupported","manifest-invalid");if(n.packageManager!==void 0&&(typeof n.packageManager!="string"||!/^npm@[0-9]+(?:\.[0-9]+){0,2}$/.test(n.packageManager)))return p("unsupported","package-manager-unsupported");const s=n.scripts;if(!i(s)||typeof s["pact:consumer"]!="string"||!/^vitest run(?: --maxWorkers=1)? tests\/pact\/consumer\/$/.test(s["pact:consumer"])||r&&(typeof s["pact:provider"]!="string"||!/^vitest run(?: --maxWorkers=1)? tests\/pact\/provider\/$/.test(s["pact:provider"])))return p("requires-root-integration","scripts-missing");const c=n.devDependencies;if(!i(c)||typeof c["@pact-foundation/pact"]!="string"||typeof c.vitest!="string")return p("requires-root-integration","dependencies-missing");const u=/^(?:\^|~)?[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?$/;if(!u.test(c["@pact-foundation/pact"])||!u.test(c.vitest))return p("unsupported","dependency-spec-unsupported");const f=m(e,"package-lock.json",8*1024*1024);if(f===void 0)return p("requires-root-integration","lockfile-missing");const a=v(f);if(!a||!i(a.packages)||!i(a.packages[""])||![2,3].includes(a.lockfileVersion))return p("unsupported","lockfile-invalid");const l=a.packages[""].devDependencies,k=a.packages["node_modules/@pact-foundation/pact"],h=a.packages["node_modules/vitest"];return!i(l)||l["@pact-foundation/pact"]!==c["@pact-foundation/pact"]||l.vitest!==c.vitest||!i(k)||typeof k.version!="string"||!u.test(k.version)||!i(h)||typeof h.version!="string"||!u.test(h.version)?p("requires-root-integration","lockfile-drift"):p("manifest-aligned","manifest-aligned-unverified")}catch{return p("unsupported","manifest-invalid")}}const F="17.1.4",H="3.2.4",_="vitest run --maxWorkers=1 tests/pact/consumer/",N="vitest run --maxWorkers=1 tests/pact/provider/",E=/^(?:\^|~)?[0-9]+\.[0-9]+\.[0-9]+$/;function L(o){const r=v(o);if(!r||![2,3].includes(r.lockfileVersion)||!i(r.packages)||Object.keys(r.packages).length>1e4)return!1;for(const e of Object.values(r.packages)){if(!i(e)||e.link===!0)return!1;if(e.resolved!==void 0){if(typeof e.resolved!="string"||typeof e.integrity!="string"||!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(e.integrity))return!1;try{const t=new URL(e.resolved);if(t.protocol!=="https:"||t.hostname!=="registry.npmjs.org"||t.port||t.username||t.password||t.search||t.hash||!t.pathname.startsWith("/")||t.pathname.includes(".."))return!1}catch{return!1}}}return!0}function U(o,r){try{if(process.platform!=="linux"||typeof r!="boolean")throw new Error("unsupported");const e=S(o);if(D(e)!==e||!O(e).isDirectory())throw new Error("root");for(const y of[".npmrc","npm-shrinkwrap.json","pnpm-lock.yaml","yarn.lock","bun.lock","bun.lockb"])try{throw O(d(e,y)),new Error("alternate")}catch(g){if(g.code!=="ENOENT")throw g}const t=m(e,"package.json",1024*1024);if(!t)throw new Error("manifest");const n=v(t);if(!n||typeof n.name!="string"||!/^[a-z0-9@][a-z0-9@._/-]{0,213}$/.test(n.name)||typeof n.version!="string"||!E.test(n.version)||n.workspaces!==void 0||n.overrides!==void 0||n.packageManager!==void 0&&(typeof n.packageManager!="string"||!/^npm@[0-9]+(?:\.[0-9]+){0,2}$/.test(n.packageManager)))throw new Error("manifest");for(const y of["dependencies","devDependencies","optionalDependencies","peerDependencies"]){const g=n[y];if(g!==void 0&&(!i(g)||Object.values(g).some(b=>typeof b!="string"||!E.test(b))))throw new Error("dependency")}const s=n.dependencies;if(i(s)&&(s["@pact-foundation/pact"]!==void 0||s.vitest!==void 0))throw new Error("placement");const c=n.scripts;if(c!==void 0&&!i(c))throw new Error("scripts");const u=i(c)?c:{};for(const y of["prepact:consumer","postpact:consumer","prepact:provider","postpact:provider"])if(u[y]!==void 0)throw new Error("lifecycle");if(u["pact:consumer"]!==void 0&&u["pact:consumer"]!==_||r&&u["pact:provider"]!==void 0&&u["pact:provider"]!==N)throw new Error("script-conflict");const f=i(n.devDependencies)?n.devDependencies:{},a=f["@pact-foundation/pact"]??F,l=f.vitest??H;if(typeof a!="string"||!E.test(a)||typeof l!="string"||!E.test(l))throw new Error("version");const k={...n,scripts:{...u,"pact:consumer":_,...r?{"pact:provider":N}:{}},devDependencies:{...f,"@pact-foundation/pact":a,vitest:l}},h=/^\{\r?\n([ \t]+)\S/m.exec(t)?.[1]??" ",T=JSON.stringify(k,null,h)+(t.endsWith(`
|
|
2
|
+
`)?`
|
|
3
|
+
`:""),x=m(e,"package-lock.json",8*1024*1024)??null;if(x!==null&&!L(x))throw new Error("lock");return Object.freeze({sourceText:t,candidateText:T,sourceLockText:x})}catch{throw new Error("pact-toolchain-manifest-unsupported")}}function X(o,r){const e=U(o,r),t=I(d(C(),"p222-pact-toolchain-"));try{w(d(t,"package.json"),e.candidateText,{mode:384}),w(d(t,"user.npmrc"),"",{mode:384}),w(d(t,"global.npmrc"),"",{mode:384}),e.sourceLockText!==null&&w(d(t,"package-lock.json"),e.sourceLockText,{mode:384});const n=M("npm",["install","--package-lock-only","--ignore-scripts","--no-audit","--no-fund","--registry=https://registry.npmjs.org/",`--userconfig=${d(t,"user.npmrc")}`,`--globalconfig=${d(t,"global.npmrc")}`],{cwd:t,encoding:"utf8",timeout:12e4,maxBuffer:256*1024,env:{PATH:process.env.PATH,HOME:t,TMPDIR:t,npm_config_cache:d(t,".npm-cache"),CI:"true",NO_UPDATE_NOTIFIER:"1"},stdio:["ignore","pipe","pipe"]});if(n.error||n.status!==0)throw new Error("npm");const s=m(t,"package.json",1024*1024),c=m(t,"package-lock.json",8*1024*1024),u=q(t,r);if(s!==e.candidateText||!c||!L(c)||u.outcome!=="manifest-aligned")throw new Error("candidate");const f=S(o);if(m(f,"package.json",1024*1024)!==e.sourceText||(m(f,"package-lock.json",8*1024*1024)??null)!==e.sourceLockText)throw new Error("source-raced");const a=l=>R("sha256").update(l).digest("hex");return Object.freeze({schemaVersion:1,outcome:"candidate-unverified",sourceManifestSha256:a(e.sourceText),sourceLockSha256:e.sourceLockText===null?null:a(e.sourceLockText),manifestText:s,lockText:c,executionVerified:!1})}catch{throw new Error("pact-toolchain-candidate-unavailable")}finally{A(t,{recursive:!0,force:!0})}}export{q as inspectPactToolchain,U as planPactToolchainManifest,X as preparePactToolchainCandidate};
|