repo-contract 0.1.1 → 0.2.0
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/CHANGELOG.md +11 -0
- package/README.md +55 -1
- package/dist/.dts/config/validate-config.d.ts.map +1 -1
- package/dist/.dts/execution/process-tree.d.ts +14 -3
- package/dist/.dts/execution/process-tree.d.ts.map +1 -1
- package/dist/.dts/execution/run-checks.d.ts +3 -1
- package/dist/.dts/execution/run-checks.d.ts.map +1 -1
- package/dist/.dts/execution/spawn-check.d.ts +21 -2
- package/dist/.dts/execution/spawn-check.d.ts.map +1 -1
- package/dist/.dts/index.d.ts +1 -1
- package/dist/.dts/index.d.ts.map +1 -1
- package/dist/.dts/run-repo-contract.d.ts.map +1 -1
- package/dist/.dts/types.d.ts +70 -3
- package/dist/.dts/types.d.ts.map +1 -1
- package/dist/index.cjs +60 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -23
- package/dist/index.js.map +1 -1
- package/package.json +6 -7
- package/src/config/validate-config.ts +63 -8
- package/src/execution/process-tree.ts +22 -6
- package/src/execution/run-checks.ts +4 -2
- package/src/execution/spawn-check.ts +63 -23
- package/src/index.ts +2 -0
- package/src/run-repo-contract.ts +12 -1
- package/src/types.ts +86 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0](https://github.com/MaverickCER/repo-contract/compare/repo-contract-v0.1.1...repo-contract-v0.2.0) (2026-09-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* defineRepoContract/runRepoContract now require spawn and env on the config (e.g. spawn: child_process.spawn, env: process.env). See ADR 0011 and README's "Supplying spawn/env" section for migration.
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* make process spawning and env access consumer-supplied capabilities ([640a961](https://github.com/MaverickCER/repo-contract/commit/640a961b65fd73511586c3b4f6274284bf5172a9))
|
|
13
|
+
|
|
3
14
|
## [0.1.1](https://github.com/MaverickCER/repo-contract/compare/repo-contract-v0.1.0...repo-contract-v0.1.1) (2026-09-01)
|
|
4
15
|
|
|
5
16
|
|
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ npm install --save-dev yaml
|
|
|
92
92
|
|
|
93
93
|
### Runtime support matrix
|
|
94
94
|
|
|
95
|
-
repo-contract
|
|
95
|
+
repo-contract's checks run as spawned processes and read the ambient environment — but repo-contract never imports a process-spawning implementation or reads `process.env` itself (see [Supplying `spawn`/`env`](#supplying-spawnenv) below); it is server/CLI-only by design regardless, not an isomorphic/browser package, since the capability a consumer supplies is itself always a real Node-only spawning mechanism.
|
|
96
96
|
|
|
97
97
|
| Environment | Supported |
|
|
98
98
|
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -113,9 +113,14 @@ Define your repository's standards as checks:
|
|
|
113
113
|
|
|
114
114
|
```ts
|
|
115
115
|
// repo-contract.config.ts
|
|
116
|
+
import { spawn } from "node:child_process"
|
|
116
117
|
import { defineRepoContract } from "repo-contract"
|
|
117
118
|
|
|
118
119
|
export default defineRepoContract({
|
|
120
|
+
// repo-contract never spawns a process or reads process.env itself -- see
|
|
121
|
+
// "Supplying spawn/env" below for why, and what to pass on Windows.
|
|
122
|
+
spawn,
|
|
123
|
+
env: process.env,
|
|
119
124
|
checks: {
|
|
120
125
|
tests: {
|
|
121
126
|
run: "npm test",
|
|
@@ -179,6 +184,49 @@ process.exitCode = verdict.passed ? 0 : 1
|
|
|
179
184
|
|
|
180
185
|
There is no CLI, no config-file discovery magic, and no hidden state.
|
|
181
186
|
|
|
187
|
+
### Supplying `spawn`/`env`
|
|
188
|
+
|
|
189
|
+
`spawn` and `env` are required fields on the config — repo-contract never imports a process-spawning implementation or reads `process.env` internally (see [ADR 0011](specs/decisions/0011-process-spawning-and-ambient-environment-access-are-consumer-supplied-capabilities-not-package-owned.md)). You supply both, as trusted capabilities repo-contract calls with a resolved command/argv/options — it does not inspect, wrap, or sanitize them.
|
|
190
|
+
|
|
191
|
+
**macOS/Linux** — plain `node:child_process` is enough:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { spawn } from "node:child_process"
|
|
195
|
+
|
|
196
|
+
export default defineRepoContract({
|
|
197
|
+
spawn,
|
|
198
|
+
env: process.env,
|
|
199
|
+
checks: {/* ... */},
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Windows** — most npm-installed CLI tools (`eslint`, `prettier`, `tsc`, …) resolve to `.cmd` shims, and plain `node:child_process.spawn` refuses to run those at all without `shell: true` (Node's own CVE-2024-27980 mitigation). Install [`cross-spawn`](https://www.npmjs.com/package/cross-spawn) and pass it instead — it resolves `.cmd`/`.bat` shims and quotes arguments safely for `cmd.exe`, **without** turning on shell metacharacter interpretation:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import crossSpawn, { sync as crossSpawnSync } from "cross-spawn"
|
|
207
|
+
|
|
208
|
+
export default defineRepoContract({
|
|
209
|
+
spawn: crossSpawn,
|
|
210
|
+
env: process.env,
|
|
211
|
+
// Optional: lets a timed-out/aborted/Ctrl+C-killed check's full process
|
|
212
|
+
// tree (not just its immediate process) get cleaned up on Windows too.
|
|
213
|
+
killProcessTree: crossSpawnSync,
|
|
214
|
+
checks: {/* ... */},
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**`cross-spawn` does not mean "shell execution."** These are two independent choices:
|
|
219
|
+
|
|
220
|
+
| `Spawner` choice | `shell` option | Result |
|
|
221
|
+
| ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
222
|
+
| native `spawn` | `false` (default) | argv-only, no shell interpretation |
|
|
223
|
+
| `cross-spawn` | `false` (default) | Windows `.cmd`/`.bat` resolution + safe `cmd.exe` quoting, still argv-only |
|
|
224
|
+
| either | `true` | shell metacharacters (`&&`, `\|`, …) interpreted — a per-check or global opt-in, see [`shell`](docs/api-report/repo-contract.api.md) |
|
|
225
|
+
|
|
226
|
+
Passing `cross-spawn` fixes Windows command resolution; it does not by itself enable shell metacharacter interpretation. `check.shell` (or the config-level `shell` default) is the separate, explicit opt-in for that — see `SECURITY.md` before enabling it.
|
|
227
|
+
|
|
228
|
+
`repo-contract` doesn't ship a ready-made spawner of its own, on purpose (see ADR 0011's Alternatives) — the two snippets above are the whole integration.
|
|
229
|
+
|
|
182
230
|
## The model
|
|
183
231
|
|
|
184
232
|
```text
|
|
@@ -641,10 +689,13 @@ It does **not** encode your repository's definition of quality.
|
|
|
641
689
|
Import a preset, spread it into your own `checks` record, and override whatever you need — most often `policy`:
|
|
642
690
|
|
|
643
691
|
```ts
|
|
692
|
+
import { spawn } from "node:child_process"
|
|
644
693
|
import { defineRepoContract } from "repo-contract"
|
|
645
694
|
import { format, typecheck, license } from "repo-contract/presets"
|
|
646
695
|
|
|
647
696
|
export default defineRepoContract({
|
|
697
|
+
spawn,
|
|
698
|
+
env: process.env,
|
|
648
699
|
checks: {
|
|
649
700
|
format,
|
|
650
701
|
typecheck: {
|
|
@@ -662,10 +713,13 @@ export default defineRepoContract({
|
|
|
662
713
|
Some presets are factories because they expose options that change what gets executed:
|
|
663
714
|
|
|
664
715
|
```ts
|
|
716
|
+
import { spawn } from "node:child_process"
|
|
665
717
|
import { defineRepoContract } from "repo-contract"
|
|
666
718
|
import { lint, deadCode } from "repo-contract/presets"
|
|
667
719
|
|
|
668
720
|
export default defineRepoContract({
|
|
721
|
+
spawn,
|
|
722
|
+
env: process.env,
|
|
669
723
|
checks: {
|
|
670
724
|
lint: lint({ path: "src" }),
|
|
671
725
|
deadCode: deadCode({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-config.d.ts","sourceRoot":"","sources":["../../../src/config/validate-config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAgB,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAKnE;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"validate-config.d.ts","sourceRoot":"","sources":["../../../src/config/validate-config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAgB,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAKnE;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAuE3E;AAqSD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,GACxD,IAAI,CAoBN"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SyncSpawner } from "../types.js";
|
|
1
2
|
/**
|
|
2
3
|
* Whether a check's process should be spawned with `detached: true`. On
|
|
3
4
|
* POSIX this makes the spawned process the leader of a new process group
|
|
@@ -20,11 +21,20 @@ export declare function shouldSpawnDetached(): boolean;
|
|
|
20
21
|
*
|
|
21
22
|
* On POSIX, sends `signal` to the whole process group via the negative-pid
|
|
22
23
|
* convention (requires the process to have been spawned with
|
|
23
|
-
* `detached: true`, see `shouldSpawnDetached`)
|
|
24
|
+
* `detached: true`, see `shouldSpawnDetached`) -- no process spawning
|
|
25
|
+
* required, a single syscall via `process.kill`. On Windows, process groups
|
|
24
26
|
* don't work the same way, so this shells out to `taskkill /pid <pid> /t
|
|
25
27
|
* /f` -- the same technique the `tree-kill` package uses internally -- which
|
|
26
28
|
* walks the system process table for descendants of `pid` regardless of how
|
|
27
|
-
* it was spawned
|
|
29
|
+
* it was spawned; that requires a synchronous spawn, which -- like every
|
|
30
|
+
* other process-spawning capability in this package -- is a trusted
|
|
31
|
+
* capability the caller supplies (`killProcessTree`, threaded from
|
|
32
|
+
* `RepoContractConfig.killProcessTree`), never imported internally (see
|
|
33
|
+
* specs/decisions/0011-process-spawning-and-ambient-environment-access-are-consumer-supplied-capabilities-not-package-owned.md).
|
|
34
|
+
* When `killProcessTree` is omitted on Windows, this function is a
|
|
35
|
+
* documented no-op -- the caller (`spawn-check.ts`) falls back to
|
|
36
|
+
* terminating just the tracked child process handle directly, which needs
|
|
37
|
+
* no spawn at all.
|
|
28
38
|
*
|
|
29
39
|
* Swallows the expected best-effort-cleanup failures, but not every failure: a
|
|
30
40
|
* process that has already exited (POSIX `ESRCH`, or `taskkill`'s "not found"
|
|
@@ -43,6 +53,7 @@ export declare function shouldSpawnDetached(): boolean;
|
|
|
43
53
|
* more-cooperative alternative to fall back to here the way there is on POSIX.
|
|
44
54
|
* @param pid - the pid of the tree's root process (the process group id on POSIX, since it was spawned detached)
|
|
45
55
|
* @param signal - the POSIX signal to send (on Windows, ignored -- see doc comment above)
|
|
56
|
+
* @param killProcessTree - the consumer-supplied synchronous spawner used only on Windows; a no-op there when omitted (see doc comment above)
|
|
46
57
|
*/
|
|
47
|
-
export declare function killTree(pid: number, signal: NodeJS.Signals): void;
|
|
58
|
+
export declare function killTree(pid: number, signal: NodeJS.Signals, killProcessTree?: SyncSpawner): void;
|
|
48
59
|
//# sourceMappingURL=process-tree.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-tree.d.ts","sourceRoot":"","sources":["../../../src/execution/process-tree.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"process-tree.d.ts","sourceRoot":"","sources":["../../../src/execution/process-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,eAAe,CAAC,EAAE,WAAW,GAAG,IAAI,CAoDjG"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CheckDefinition, CheckEvidence, CheckSchema, RunRepoContractOptions } from "../types.js";
|
|
2
|
+
import type { ExecutionCapability } from "./spawn-check.js";
|
|
2
3
|
/**
|
|
3
4
|
* One check's id, its original definition, and its raw execution evidence,
|
|
4
5
|
* threaded together as a triple rather than three separately-keyed maps --
|
|
@@ -22,8 +23,9 @@ export declare const SELF_TERMINATE_DELAY_MS: number;
|
|
|
22
23
|
* simply absent from the result, not present with some placeholder value.
|
|
23
24
|
* @param checks - the full set of configured checks, keyed by id
|
|
24
25
|
* @param concurrency - the maximum number of checks to run in parallel at once
|
|
26
|
+
* @param execution - the run's trusted execution capabilities (`spawn`, `env`, resolved global `shell` default), forwarded to every `spawnCheck` call
|
|
25
27
|
* @param options - run options; `options.checks` restricts execution to those ids (plus their dependencies), `options.signal` cancels the whole run
|
|
26
28
|
* @returns each executed check's id, definition, and raw evidence, one entry per resolved check regardless of whether it actually spawned
|
|
27
29
|
*/
|
|
28
|
-
export declare function runChecks(checks: CheckSchema, concurrency: number, options?: RunRepoContractOptions): Promise<readonly CheckExecutionEntry[]>;
|
|
30
|
+
export declare function runChecks(checks: CheckSchema, concurrency: number, execution: ExecutionCapability, options?: RunRepoContractOptions): Promise<readonly CheckExecutionEntry[]>;
|
|
29
31
|
//# sourceMappingURL=run-checks.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-checks.d.ts","sourceRoot":"","sources":["../../../src/execution/run-checks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,WAAW,EACX,sBAAsB,EACvB,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"run-checks.d.ts","sourceRoot":"","sources":["../../../src/execution/run-checks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,WAAW,EACX,sBAAsB,EACvB,MAAM,aAAa,CAAA;AAIpB,OAAO,KAAK,EAAqB,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAG9E;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAAG,SAAS,CAAC,MAAM,EAAE,eAAe,EAAE,aAAa,CAAC,CAAA;AAYnF,eAAO,MAAM,uBAAuB,QAAgC,CAAA;AAoJpE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,WAAW,EACnB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,mBAAmB,EAC9B,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAkJzC"}
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
import type { CheckDefinition, CheckEvidence } from "../types.js";
|
|
1
|
+
import type { CheckDefinition, CheckEvidence, Spawner, SyncSpawner } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The trusted execution capabilities a run supplies once (see
|
|
4
|
+
* specs/decisions/0011-process-spawning-and-ambient-environment-access-are-consumer-supplied-capabilities-not-package-owned.md)
|
|
5
|
+
* and every spawned check draws from -- `spawn`/`env` verbatim from
|
|
6
|
+
* `RepoContractConfig`, `shell` already resolved to its effective global
|
|
7
|
+
* default (`config.shell ?? false`). Internal only: not exported from
|
|
8
|
+
* `src/types.ts` or `src/index.ts`. Grouped into one object, rather than
|
|
9
|
+
* threaded as independent parameters through `run-repo-contract.ts` ->
|
|
10
|
+
* `run-checks.ts` -> `spawn-check.ts`, purely to keep related
|
|
11
|
+
* execution-capability concerns from drifting into unrelated positional
|
|
12
|
+
* parameters as more get added later.
|
|
13
|
+
*/
|
|
14
|
+
export interface ExecutionCapability {
|
|
15
|
+
readonly spawn: Spawner;
|
|
16
|
+
readonly env: NodeJS.ProcessEnv;
|
|
17
|
+
readonly shell: boolean;
|
|
18
|
+
readonly killProcessTree?: SyncSpawner;
|
|
19
|
+
}
|
|
2
20
|
export declare const SIGKILL_GRACE_PERIOD_MS = 2000;
|
|
3
21
|
/** Handle a caller can use to forcibly terminate one in-flight check's process tree, independent of that check's own timeout/abort wiring -- used by run-checks.ts to clean up every active check when the host process itself receives SIGINT/SIGTERM. */
|
|
4
22
|
export interface ActiveCheckHandle {
|
|
@@ -24,7 +42,8 @@ export interface ActiveCheckHandle {
|
|
|
24
42
|
* @param check - the check definition to run (command, timeout, env, cwd, shell, etc.)
|
|
25
43
|
* @param runSignal - the whole run's abort signal, if any; already-aborted before this is called means the check never spawns
|
|
26
44
|
* @param activeHandles - the shared registry this check's kill handle is added to while running, so a host-process SIGINT/SIGTERM can terminate it
|
|
45
|
+
* @param execution - the run's trusted execution capabilities (`spawn`, `env`, resolved global `shell` default) -- see `ExecutionCapability`
|
|
27
46
|
* @returns a fully-formed `CheckEvidence` reflecting however the process ended; this function itself never rejects
|
|
28
47
|
*/
|
|
29
|
-
export declare function spawnCheck(checkId: string, check: CheckDefinition, runSignal: AbortSignal | undefined, activeHandles: Set<ActiveCheckHandle
|
|
48
|
+
export declare function spawnCheck(checkId: string, check: CheckDefinition, runSignal: AbortSignal | undefined, activeHandles: Set<ActiveCheckHandle>, execution: ExecutionCapability): Promise<CheckEvidence>;
|
|
30
49
|
//# sourceMappingURL=spawn-check.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-check.d.ts","sourceRoot":"","sources":["../../../src/execution/spawn-check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"spawn-check.d.ts","sourceRoot":"","sources":["../../../src/execution/spawn-check.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAe,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAIpG;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,eAAe,CAAC,EAAE,WAAW,CAAA;CACvC;AA2BD,eAAO,MAAM,uBAAuB,OAAO,CAAA;AA6D3C,2PAA2P;AAC3P,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;CACnC;AAyHD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,EACtB,SAAS,EAAE,WAAW,GAAG,SAAS,EAClC,aAAa,EAAE,GAAG,CAAC,iBAAiB,CAAC,EACrC,SAAS,EAAE,mBAAmB,GAC7B,OAAO,CAAC,aAAa,CAAC,CA4QxB"}
|
package/dist/.dts/index.d.ts
CHANGED
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
export { defineRepoContract } from "./config/define-repo-contract.js";
|
|
10
10
|
export { runRepoContract } from "./run-repo-contract.js";
|
|
11
11
|
export { DependencyDeclaredLaterError, InvalidCheckConfigError, InvalidRepoContractConfigError, ParserDependencyMissingError, PolicyReadFailedParseValueError, PolicyReadUnrequestedOutputError, PolicyThrewError, RepoContractError, UnknownCheckIdError, } from "./errors.js";
|
|
12
|
-
export type { CheckDefinition, CheckDefinitionConfig, CheckEvidence, CheckSchema, CheckStatus, Evidence, OutputFormat, ParsedOutput, ParsedOutputFailure, ParsedOutputSuccess, Policy, PolicyContext, PolicyOutcome, PolicyResult, RepoContractConfig, RunRepoContractOptions, ValidatedCheckSchema, Verdict, } from "./types.js";
|
|
12
|
+
export type { CheckDefinition, CheckDefinitionConfig, CheckEvidence, CheckSchema, CheckStatus, Evidence, OutputFormat, ParsedOutput, ParsedOutputFailure, ParsedOutputSuccess, Policy, PolicyContext, PolicyOutcome, PolicyResult, RepoContractConfig, RunRepoContractOptions, Spawner, SyncSpawner, ValidatedCheckSchema, Verdict, } from "./types.js";
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/.dts/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,8BAA8B,EAC9B,4BAA4B,EAC5B,+BAA+B,EAC/B,gCAAgC,EAChC,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,aAAa,CAAA;AAEpB,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,WAAW,EACX,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,MAAM,EACN,aAAa,EACb,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,OAAO,GACR,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,8BAA8B,EAC9B,4BAA4B,EAC5B,+BAA+B,EAC/B,gCAAgC,EAChC,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,aAAa,CAAA;AAEpB,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,WAAW,EACX,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,MAAM,EACN,aAAa,EACb,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,EACtB,OAAO,EACP,WAAW,EACX,oBAAoB,EACpB,OAAO,GACR,MAAM,YAAY,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-repo-contract.d.ts","sourceRoot":"","sources":["../../src/run-repo-contract.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run-repo-contract.d.ts","sourceRoot":"","sources":["../../src/run-repo-contract.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EACR,kBAAkB,EAClB,sBAAsB,EACtB,OAAO,EACR,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,eAAe,CAAC,KAAK,CAAC,OAAO,SAAS,WAAW,EAC/D,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,EACnC,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAGrE"}
|
package/dist/.dts/types.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* separate concepts (see specs/architecture.md) even though
|
|
5
5
|
* `runRepoContract` returns both together.
|
|
6
6
|
*/
|
|
7
|
+
import type { ChildProcess, SpawnOptions, SpawnSyncOptions, SpawnSyncReturns } from "node:child_process";
|
|
7
8
|
/** Output interpretation a check can explicitly request. No format requested means no parsing -- the consumer gets raw stdout/stderr only. */
|
|
8
9
|
export type OutputFormat = "json" | "yaml" | "text";
|
|
9
10
|
/**
|
|
@@ -202,9 +203,11 @@ export interface CheckDefinitionConfig {
|
|
|
202
203
|
readonly run: string | readonly string[];
|
|
203
204
|
/**
|
|
204
205
|
* Opt into real shell execution instead of the safe argv-only default.
|
|
205
|
-
* When `true
|
|
206
|
-
*
|
|
207
|
-
*
|
|
206
|
+
* When `true` (or left unset with `RepoContractConfig.shell: true` as the
|
|
207
|
+
* run-wide default -- see that field), `run` must be a `string` and is
|
|
208
|
+
* passed to the platform shell as-is (via the supplied `Spawner`'s own
|
|
209
|
+
* `shell` option) -- shell metacharacter rejection does not apply. See
|
|
210
|
+
* SECURITY.md before enabling this.
|
|
208
211
|
*/
|
|
209
212
|
readonly shell?: boolean;
|
|
210
213
|
/** Working directory for the spawned process. Defaults to the current process's `cwd`. */
|
|
@@ -285,12 +288,76 @@ export type ValidatedCheckSchema<T> = {
|
|
|
285
288
|
readonly dependsOn?: readonly (Exclude<keyof T, K> & string)[];
|
|
286
289
|
} : never;
|
|
287
290
|
};
|
|
291
|
+
/**
|
|
292
|
+
* Spawns a child process, given a resolved command, argv, and options --
|
|
293
|
+
* modeled directly on `node:child_process`'s own `spawn(command, args,
|
|
294
|
+
* options)` signature so both `node:child_process.spawn` and cross-spawn's
|
|
295
|
+
* exported `spawn` are valid, drop-in values with no adapter code required
|
|
296
|
+
* (see specs/decisions/0011-process-spawning-and-ambient-environment-access-are-consumer-supplied-capabilities-not-package-owned.md).
|
|
297
|
+
* repo-contract treats whatever function is supplied as a trusted
|
|
298
|
+
* capability: it calls it with a resolved command/argv/options and does not
|
|
299
|
+
* inspect, wrap, or sanitize it -- the security properties of the spawned
|
|
300
|
+
* process are entirely the supplied function's own.
|
|
301
|
+
*/
|
|
302
|
+
export type Spawner = (command: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
|
|
303
|
+
/**
|
|
304
|
+
* Synchronously spawns a child process and waits for it to exit -- modeled directly on
|
|
305
|
+
* `node:child_process`'s own `spawnSync(command, args, options)` signature, the same
|
|
306
|
+
* drop-in-compatibility approach as `Spawner`. Used only for `RepoContractConfig.killProcessTree`
|
|
307
|
+
* (Windows process-tree cleanup via `taskkill`, which fundamentally needs to run synchronously from
|
|
308
|
+
* a signal-handling context that cannot `await` anything else -- see
|
|
309
|
+
* specs/decisions/0011-process-spawning-and-ambient-environment-access-are-consumer-supplied-capabilities-not-package-owned.md).
|
|
310
|
+
* `node:child_process.spawnSync` and cross-spawn's exported `sync` are both valid, drop-in values.
|
|
311
|
+
*/
|
|
312
|
+
export type SyncSpawner = (command: string, args: readonly string[], options: SpawnSyncOptions) => SpawnSyncReturns<Buffer | string>;
|
|
288
313
|
/** Top-level configuration passed to `defineRepoContract`/`runRepoContract`. */
|
|
289
314
|
export interface RepoContractConfig<TChecks extends CheckSchema = CheckSchema> {
|
|
290
315
|
/** Every check to run, keyed by check id. */
|
|
291
316
|
readonly checks: TChecks;
|
|
292
317
|
/** Maximum number of checks to execute concurrently. Defaults to `os.availableParallelism()`. Must be a positive integer. */
|
|
293
318
|
readonly concurrency?: number;
|
|
319
|
+
/**
|
|
320
|
+
* The trusted capability repo-contract calls to spawn every check's
|
|
321
|
+
* process -- e.g. `child_process.spawn` (from `"node:child_process"`) or
|
|
322
|
+
* cross-spawn's exported `spawn`. Required: repo-contract never imports a
|
|
323
|
+
* process-spawning implementation itself (see ADR 0011 above `Spawner`).
|
|
324
|
+
* `child_process.spawn` alone does not resolve Windows `.cmd`/`.bat`
|
|
325
|
+
* shims (most npm-installed CLI tools on Windows) without `shell: true`;
|
|
326
|
+
* pass cross-spawn instead for that correctness without enabling shell
|
|
327
|
+
* metacharacter interpretation -- cross-spawn is a spawn implementation
|
|
328
|
+
* choice, not a `shell: true` equivalent. See `shell` below.
|
|
329
|
+
*/
|
|
330
|
+
readonly spawn: Spawner;
|
|
331
|
+
/**
|
|
332
|
+
* The ambient environment repo-contract treats as inheritable by each
|
|
333
|
+
* check whose `inheritEnv` is not `false` (the default) -- typically
|
|
334
|
+
* `process.env`, passed straight through by reference (never copied
|
|
335
|
+
* internally) so a consumer that mutates `process.env` mid-run still sees
|
|
336
|
+
* that reflected in later-spawned checks, exactly as if repo-contract had
|
|
337
|
+
* read `process.env` itself. Required: repo-contract never reads
|
|
338
|
+
* `process.env` internally (see ADR 0011 above `Spawner`). Typed as
|
|
339
|
+
* `NodeJS.ProcessEnv` so `env: process.env` needs no casting.
|
|
340
|
+
*/
|
|
341
|
+
readonly env: NodeJS.ProcessEnv;
|
|
342
|
+
/**
|
|
343
|
+
* Global default for every check's own `shell` when that check doesn't
|
|
344
|
+
* set one itself (`check.shell ?? shell ?? false`). Defaults to `false`,
|
|
345
|
+
* the safe argv-only mode -- unrelated to which `spawn` is supplied; see
|
|
346
|
+
* `spawn`'s own doc comment for that distinction.
|
|
347
|
+
*/
|
|
348
|
+
readonly shell?: boolean;
|
|
349
|
+
/**
|
|
350
|
+
* The trusted capability repo-contract calls, on Windows only, to forcibly terminate a check's
|
|
351
|
+
* entire process tree (not just its immediate process) on a timeout, an aborted run, or a
|
|
352
|
+
* host-process SIGINT/SIGTERM -- e.g. `child_process.spawnSync` (from `"node:child_process"`) or
|
|
353
|
+
* cross-spawn's exported `sync`. Optional, unlike `spawn`/`env`: when omitted, Windows cleanup
|
|
354
|
+
* falls back to terminating only the check's own immediate process (not any subprocess it spawned
|
|
355
|
+
* internally) -- correct for the common case, but a check that spawns its own descendants (e.g.
|
|
356
|
+
* `npm test` spawning the real test runner) may leave them running. POSIX cleanup never needs
|
|
357
|
+
* this at all (`process.kill(-pid, signal)` reaches the whole process group directly, no spawning
|
|
358
|
+
* required) -- see specs/decisions/0011-process-spawning-and-ambient-environment-access-are-consumer-supplied-capabilities-not-package-owned.md.
|
|
359
|
+
*/
|
|
360
|
+
readonly killProcessTree?: SyncSpawner;
|
|
294
361
|
}
|
|
295
362
|
/** Optional per-run controls for `runRepoContract`. */
|
|
296
363
|
export interface RunRepoContractOptions {
|
package/dist/.dts/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,oBAAoB,CAAA;AAE3B,8IAA8I;AAC9I,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AAEnD;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,WAAW,GACrB,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,iBAAiB,GAAG,aAAa,GAAG,SAAS,CAAA;AAExF,uDAAuD;AACvD,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,qBAAqB;IACrB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,wBAAwB;IACxB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,sBAAsB;IACtB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,iCAAiC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,oIAAoI;AACpI,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,mBAAmB,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAA;AAE1E;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,aAAa;IAC5B,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,mGAAmG;IACnG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;IACtC,6JAA6J;IAC7J,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,4JAA4J;IAC5J,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,0JAA0J;IAC1J,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,6XAA6X;IAC7X,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,uGAAuG;IACvG,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;CACxC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ,CAAC,OAAO,SAAS,WAAW,GAAG,WAAW;IACjE,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACnB,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,CAAC,IAAI,MAAM,OAAO,GAAG,aAAa;KAAE,CAAA;CAClE;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa,CAAC,OAAO,SAAS,WAAW,GAAG,WAAW;IACtE,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IACpC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAA;CAC/D;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AAEpD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;IAC/B,+FAA+F;IAC/F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,MAAM,CAAC,OAAO,SAAS,WAAW,GAAG,WAAW,IAAI,CAC9D,GAAG,EAAE,aAAa,CAAC,OAAO,CAAC,KACxB,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;AAEzC,8JAA8J;AAC9J,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IACxC;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,0JAA0J;IAC1J,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAC/C,mSAAmS;IACnS,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;IAC7B,2IAA2I;IAC3I,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,qHAAqH;IACrH,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;KAAE,CAAA;IACnD;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B,kGAAkG;IAClG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,0JAA0J;AAC1J,MAAM,WAAW,eAAgB,SAAQ,qBAAqB;IAC5D;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC;AAED,2EAA2E;AAC3E,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;AAEzD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI;IACpC,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,qBAAqB,GACvD,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,GAAG;QACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,CAAA;KAC/D,GACD,KAAK;CACV,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,OAAO,GAAG,CACpB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,YAAY,KAClB,YAAY,CAAA;AAEjB;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,gBAAgB,KACtB,gBAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,CAAA;AAEtC,gFAAgF;AAChF,MAAM,WAAW,kBAAkB,CAAC,OAAO,SAAS,WAAW,GAAG,WAAW;IAC3E,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,6HAA6H;IAC7H,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB;;;;;;;;;OASG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,WAAW,CAAA;CACvC;AAED,uDAAuD;AACvD,MAAM,WAAW,sBAAsB;IACrC,oOAAoO;IACpO,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;IAC7B,yIAAyI;IACzI,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,OAAO,CAAC,OAAO,SAAS,WAAW,GAAG,WAAW;IAChE,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACnB,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,CAAC,IAAI,MAAM,OAAO,GAAG,YAAY;KAAE,CAAA;CACjE"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var os = require('os');
|
|
4
|
-
var crossSpawn = require('cross-spawn');
|
|
5
4
|
var string_decoder = require('string_decoder');
|
|
6
|
-
var child_process = require('child_process');
|
|
7
|
-
|
|
8
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
5
|
|
|
10
6
|
function _interopNamespace(e) {
|
|
11
7
|
if (e && e.__esModule) return e;
|
|
@@ -26,7 +22,6 @@ function _interopNamespace(e) {
|
|
|
26
22
|
}
|
|
27
23
|
|
|
28
24
|
var os__namespace = /*#__PURE__*/_interopNamespace(os);
|
|
29
|
-
var crossSpawn__default = /*#__PURE__*/_interopDefault(crossSpawn);
|
|
30
25
|
|
|
31
26
|
// src/config/define-repo-contract.ts
|
|
32
27
|
function defineRepoContract(config) {
|
|
@@ -236,7 +231,7 @@ function validateRepoContractConfig(config) {
|
|
|
236
231
|
if (untrusted === null || typeof untrusted !== "object") {
|
|
237
232
|
throw new InvalidRepoContractConfigError("config must be an object.");
|
|
238
233
|
}
|
|
239
|
-
const { checks, concurrency } = untrusted;
|
|
234
|
+
const { checks, concurrency, spawn, env, shell, killProcessTree } = untrusted;
|
|
240
235
|
if (checks === null || typeof checks !== "object" || Array.isArray(checks)) {
|
|
241
236
|
throw new InvalidRepoContractConfigError(
|
|
242
237
|
"checks must be an object mapping check id to check definition."
|
|
@@ -249,12 +244,35 @@ function validateRepoContractConfig(config) {
|
|
|
249
244
|
);
|
|
250
245
|
}
|
|
251
246
|
}
|
|
247
|
+
if (typeof spawn !== "function") {
|
|
248
|
+
throw new InvalidRepoContractConfigError("spawn must be a function.");
|
|
249
|
+
}
|
|
250
|
+
validateConfigEnv(env);
|
|
251
|
+
if (shell !== void 0 && typeof shell !== "boolean") {
|
|
252
|
+
throw new InvalidRepoContractConfigError("shell must be a boolean when provided.");
|
|
253
|
+
}
|
|
254
|
+
const globalShell = shell === true;
|
|
255
|
+
if (killProcessTree !== void 0 && typeof killProcessTree !== "function") {
|
|
256
|
+
throw new InvalidRepoContractConfigError("killProcessTree must be a function when provided.");
|
|
257
|
+
}
|
|
252
258
|
for (const [checkId, check] of Object.entries(checks)) {
|
|
253
|
-
validateCheckDefinition(checkId, check);
|
|
259
|
+
validateCheckDefinition(checkId, check, globalShell);
|
|
254
260
|
}
|
|
255
261
|
validateDependencyGraph(checks);
|
|
256
262
|
}
|
|
257
|
-
function
|
|
263
|
+
function validateConfigEnv(env) {
|
|
264
|
+
if (env === null || typeof env !== "object" || Array.isArray(env)) {
|
|
265
|
+
throw new InvalidRepoContractConfigError(
|
|
266
|
+
"env must be an object mapping variable name to value."
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
for (const [key, value] of Object.entries(env)) {
|
|
270
|
+
if (typeof value !== "string" && value !== void 0) {
|
|
271
|
+
throw new InvalidRepoContractConfigError(`env["${key}"] must be a string or undefined.`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function validateCheckDefinition(checkId, check, globalShell) {
|
|
258
276
|
if (/^(?:0|[1-9]\d*)$/.test(checkId)) {
|
|
259
277
|
throw new InvalidCheckConfigError(
|
|
260
278
|
checkId,
|
|
@@ -265,7 +283,7 @@ function validateCheckDefinition(checkId, check) {
|
|
|
265
283
|
throw new InvalidCheckConfigError(checkId, "check definition must be an object.");
|
|
266
284
|
}
|
|
267
285
|
const fields = check;
|
|
268
|
-
const usesShell = validateShell(checkId, fields.shell);
|
|
286
|
+
const usesShell = validateShell(checkId, fields.shell, globalShell);
|
|
269
287
|
validateRun(checkId, fields.run, usesShell);
|
|
270
288
|
validateCwd(checkId, fields.cwd);
|
|
271
289
|
validateEnv(checkId, fields.env);
|
|
@@ -276,11 +294,11 @@ function validateCheckDefinition(checkId, check) {
|
|
|
276
294
|
validateIsolated(checkId, fields.isolated);
|
|
277
295
|
validatePolicy(checkId, fields.policy);
|
|
278
296
|
}
|
|
279
|
-
function validateShell(checkId, shell) {
|
|
297
|
+
function validateShell(checkId, shell, globalShell) {
|
|
280
298
|
if (shell !== void 0 && typeof shell !== "boolean") {
|
|
281
299
|
throw new InvalidCheckConfigError(checkId, "shell must be a boolean when provided.");
|
|
282
300
|
}
|
|
283
|
-
return shell ===
|
|
301
|
+
return typeof shell === "boolean" ? shell : globalShell;
|
|
284
302
|
}
|
|
285
303
|
function validateRun(checkId, run, usesShell) {
|
|
286
304
|
if (typeof run !== "string" && !Array.isArray(run)) {
|
|
@@ -627,18 +645,23 @@ async function runWithConcurrencyGraph(items, concurrency, dependencyIndexes, wo
|
|
|
627
645
|
launchNext();
|
|
628
646
|
});
|
|
629
647
|
}
|
|
648
|
+
|
|
649
|
+
// src/execution/process-tree.ts
|
|
630
650
|
function shouldSpawnDetached() {
|
|
631
651
|
return process.platform !== "win32";
|
|
632
652
|
}
|
|
633
653
|
function isErrnoException(error) {
|
|
634
654
|
return error instanceof Error && "code" in error;
|
|
635
655
|
}
|
|
636
|
-
function killTree(pid, signal) {
|
|
656
|
+
function killTree(pid, signal, killProcessTree) {
|
|
637
657
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
638
658
|
return;
|
|
639
659
|
}
|
|
640
660
|
if (process.platform === "win32") {
|
|
641
|
-
|
|
661
|
+
if (killProcessTree === void 0) return;
|
|
662
|
+
const result = killProcessTree("taskkill", ["/pid", String(pid), "/t", "/f"], {
|
|
663
|
+
stdio: "ignore"
|
|
664
|
+
});
|
|
642
665
|
if (result.error !== void 0) throw result.error;
|
|
643
666
|
return;
|
|
644
667
|
}
|
|
@@ -672,7 +695,7 @@ function createBoundedCollector() {
|
|
|
672
695
|
value: () => value
|
|
673
696
|
};
|
|
674
697
|
}
|
|
675
|
-
function resolveCommand(checkId, check) {
|
|
698
|
+
function resolveCommand(checkId, check, effectiveShell) {
|
|
676
699
|
const run = check.run;
|
|
677
700
|
if (typeof run !== "string") {
|
|
678
701
|
const [command2, ...args2] = run;
|
|
@@ -681,7 +704,7 @@ function resolveCommand(checkId, check) {
|
|
|
681
704
|
}
|
|
682
705
|
return { command: command2, args: args2 };
|
|
683
706
|
}
|
|
684
|
-
if (
|
|
707
|
+
if (effectiveShell) {
|
|
685
708
|
return { command: run, args: [] };
|
|
686
709
|
}
|
|
687
710
|
const [command, ...args] = tokenizeRunString(run, checkId);
|
|
@@ -690,10 +713,10 @@ function resolveCommand(checkId, check) {
|
|
|
690
713
|
}
|
|
691
714
|
return { command, args };
|
|
692
715
|
}
|
|
693
|
-
function buildEnv(check) {
|
|
716
|
+
function buildEnv(check, ambientEnv) {
|
|
694
717
|
const base = {};
|
|
695
718
|
if (check.inheritEnv !== false) {
|
|
696
|
-
for (const [key, value] of Object.entries(
|
|
719
|
+
for (const [key, value] of Object.entries(ambientEnv)) {
|
|
697
720
|
if (value !== void 0) base[key] = value;
|
|
698
721
|
}
|
|
699
722
|
}
|
|
@@ -716,13 +739,14 @@ function terminalEvidence(command, args, startedAt, status, exitCode, signal, st
|
|
|
716
739
|
...spawnErrorCode !== void 0 ? { spawnErrorCode } : {}
|
|
717
740
|
};
|
|
718
741
|
}
|
|
719
|
-
async function spawnCheck(checkId, check, runSignal, activeHandles) {
|
|
742
|
+
async function spawnCheck(checkId, check, runSignal, activeHandles, execution) {
|
|
720
743
|
const startedAt = /* @__PURE__ */ new Date();
|
|
721
|
-
const
|
|
744
|
+
const effectiveShell = check.shell ?? execution.shell;
|
|
745
|
+
const { command, args } = resolveCommand(checkId, check, effectiveShell);
|
|
722
746
|
if (runSignal?.aborted === true) {
|
|
723
747
|
return terminalEvidence(command, args, startedAt, "aborted", null, null);
|
|
724
748
|
}
|
|
725
|
-
const env = buildEnv(check);
|
|
749
|
+
const env = buildEnv(check, execution.env);
|
|
726
750
|
let terminationReason = null;
|
|
727
751
|
let timeoutHandle;
|
|
728
752
|
const timeoutController = new AbortController();
|
|
@@ -737,10 +761,10 @@ async function spawnCheck(checkId, check, runSignal, activeHandles) {
|
|
|
737
761
|
runSignal !== void 0 ? [runSignal, timeoutController.signal] : [timeoutController.signal]
|
|
738
762
|
);
|
|
739
763
|
return new Promise((resolve) => {
|
|
740
|
-
const child =
|
|
764
|
+
const child = execution.spawn(command, args, {
|
|
741
765
|
cwd: check.cwd,
|
|
742
766
|
env,
|
|
743
|
-
shell:
|
|
767
|
+
shell: effectiveShell,
|
|
744
768
|
detached: shouldSpawnDetached(),
|
|
745
769
|
// Windows-only cosmetic behavior (suppresses a console window flash)
|
|
746
770
|
// with no effect on stdout/stderr/exitCode/signal on any platform, and
|
|
@@ -755,7 +779,11 @@ async function spawnCheck(checkId, check, runSignal, activeHandles) {
|
|
|
755
779
|
let hostTerminated = false;
|
|
756
780
|
const bestEffortKillTree = (pid, signal) => {
|
|
757
781
|
try {
|
|
758
|
-
killTree(pid, signal);
|
|
782
|
+
killTree(pid, signal, execution.killProcessTree);
|
|
783
|
+
} catch {
|
|
784
|
+
}
|
|
785
|
+
try {
|
|
786
|
+
child.kill(signal);
|
|
759
787
|
} catch {
|
|
760
788
|
}
|
|
761
789
|
};
|
|
@@ -894,7 +922,7 @@ function resolveCheckDependencies(checks, requestedChecks) {
|
|
|
894
922
|
for (const checkId of requestedChecks) visit(checkId);
|
|
895
923
|
return Object.entries(checks).filter(([checkId]) => required.has(checkId));
|
|
896
924
|
}
|
|
897
|
-
async function runChecks(checks, concurrency, options) {
|
|
925
|
+
async function runChecks(checks, concurrency, execution, options) {
|
|
898
926
|
const entries = options?.checks ? resolveCheckDependencies(checks, options.checks) : Object.entries(checks);
|
|
899
927
|
const activeHandles = /* @__PURE__ */ new Set();
|
|
900
928
|
const hostAbortController = new AbortController();
|
|
@@ -915,7 +943,7 @@ async function runChecks(checks, concurrency, options) {
|
|
|
915
943
|
)
|
|
916
944
|
);
|
|
917
945
|
const worker = async ([checkId, check]) => {
|
|
918
|
-
const evidence = await spawnCheck(checkId, check, runSignal, activeHandles);
|
|
946
|
+
const evidence = await spawnCheck(checkId, check, runSignal, activeHandles, execution);
|
|
919
947
|
return [checkId, check, evidence];
|
|
920
948
|
};
|
|
921
949
|
try {
|
|
@@ -1047,7 +1075,13 @@ function runRepoContract(config, options) {
|
|
|
1047
1075
|
async function runRepoContractAfterValidation(config, options) {
|
|
1048
1076
|
const concurrency = config.concurrency ?? os__namespace.availableParallelism();
|
|
1049
1077
|
const startedAt = /* @__PURE__ */ new Date();
|
|
1050
|
-
const
|
|
1078
|
+
const execution = {
|
|
1079
|
+
spawn: config.spawn,
|
|
1080
|
+
killProcessTree: config.killProcessTree,
|
|
1081
|
+
env: config.env,
|
|
1082
|
+
shell: config.shell ?? false
|
|
1083
|
+
};
|
|
1084
|
+
const results = await runChecks(config.checks, concurrency, execution, options);
|
|
1051
1085
|
const completedAt = /* @__PURE__ */ new Date();
|
|
1052
1086
|
const { evidence, entries } = await buildEvidence(results, startedAt, completedAt);
|
|
1053
1087
|
const verdict = await runPolicies(entries, evidence);
|