repo-contract 0.1.1 → 0.3.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 +38 -0
- package/README.md +56 -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/dist/.dts/config/define-repo-contract.d.ts +0 -36
- package/dist/.dts/config/define-repo-contract.d.ts.map +0 -1
- package/dist/.dts/config/tokenize-command.d.ts +0 -33
- package/dist/.dts/config/tokenize-command.d.ts.map +0 -1
- package/dist/.dts/config/validate-config.d.ts +0 -32
- package/dist/.dts/config/validate-config.d.ts.map +0 -1
- package/dist/.dts/errors.d.ts +0 -155
- package/dist/.dts/errors.d.ts.map +0 -1
- package/dist/.dts/evidence/build-evidence.d.ts +0 -26
- package/dist/.dts/evidence/build-evidence.d.ts.map +0 -1
- package/dist/.dts/execution/abort-signals.d.ts +0 -29
- package/dist/.dts/execution/abort-signals.d.ts.map +0 -1
- package/dist/.dts/execution/concurrency-pool.d.ts +0 -14
- package/dist/.dts/execution/concurrency-pool.d.ts.map +0 -1
- package/dist/.dts/execution/dependency-scheduler.d.ts +0 -30
- package/dist/.dts/execution/dependency-scheduler.d.ts.map +0 -1
- package/dist/.dts/execution/process-tree.d.ts +0 -48
- package/dist/.dts/execution/process-tree.d.ts.map +0 -1
- package/dist/.dts/execution/run-checks.d.ts +0 -29
- package/dist/.dts/execution/run-checks.d.ts.map +0 -1
- package/dist/.dts/execution/spawn-check.d.ts +0 -30
- package/dist/.dts/execution/spawn-check.d.ts.map +0 -1
- package/dist/.dts/index.d.ts +0 -13
- package/dist/.dts/index.d.ts.map +0 -1
- package/dist/.dts/parsing/parse-json.d.ts +0 -8
- package/dist/.dts/parsing/parse-json.d.ts.map +0 -1
- package/dist/.dts/parsing/parse-output.d.ts +0 -10
- package/dist/.dts/parsing/parse-output.d.ts.map +0 -1
- package/dist/.dts/parsing/parse-text.d.ts +0 -8
- package/dist/.dts/parsing/parse-text.d.ts.map +0 -1
- package/dist/.dts/parsing/parse-yaml.d.ts +0 -21
- package/dist/.dts/parsing/parse-yaml.d.ts.map +0 -1
- package/dist/.dts/policy/run-policies.d.ts +0 -38
- package/dist/.dts/policy/run-policies.d.ts.map +0 -1
- package/dist/.dts/presets/arethetypeswrong.d.ts +0 -39
- package/dist/.dts/presets/arethetypeswrong.d.ts.map +0 -1
- package/dist/.dts/presets/broken-links.d.ts +0 -16
- package/dist/.dts/presets/broken-links.d.ts.map +0 -1
- package/dist/.dts/presets/commitlint.d.ts +0 -22
- package/dist/.dts/presets/commitlint.d.ts.map +0 -1
- package/dist/.dts/presets/dead-code.d.ts +0 -23
- package/dist/.dts/presets/dead-code.d.ts.map +0 -1
- package/dist/.dts/presets/duplication.d.ts +0 -14
- package/dist/.dts/presets/duplication.d.ts.map +0 -1
- package/dist/.dts/presets/e2e.d.ts +0 -4
- package/dist/.dts/presets/e2e.d.ts.map +0 -1
- package/dist/.dts/presets/format.d.ts +0 -4
- package/dist/.dts/presets/format.d.ts.map +0 -1
- package/dist/.dts/presets/index.d.ts +0 -31
- package/dist/.dts/presets/index.d.ts.map +0 -1
- package/dist/.dts/presets/license.d.ts +0 -4
- package/dist/.dts/presets/license.d.ts.map +0 -1
- package/dist/.dts/presets/lint.d.ts +0 -20
- package/dist/.dts/presets/lint.d.ts.map +0 -1
- package/dist/.dts/presets/markdownlint.d.ts +0 -23
- package/dist/.dts/presets/markdownlint.d.ts.map +0 -1
- package/dist/.dts/presets/publint.d.ts +0 -13
- package/dist/.dts/presets/publint.d.ts.map +0 -1
- package/dist/.dts/presets/security-deps.d.ts +0 -4
- package/dist/.dts/presets/security-deps.d.ts.map +0 -1
- package/dist/.dts/presets/security-secrets.d.ts +0 -4
- package/dist/.dts/presets/security-secrets.d.ts.map +0 -1
- package/dist/.dts/presets/shared/error-warning-pass-policy.d.ts +0 -13
- package/dist/.dts/presets/shared/error-warning-pass-policy.d.ts.map +0 -1
- package/dist/.dts/presets/shared/exit-code-fail-rationale.d.ts +0 -23
- package/dist/.dts/presets/shared/exit-code-fail-rationale.d.ts.map +0 -1
- package/dist/.dts/presets/shared/missing-dependency.d.ts +0 -19
- package/dist/.dts/presets/shared/missing-dependency.d.ts.map +0 -1
- package/dist/.dts/presets/shared/read-json-report.d.ts +0 -33
- package/dist/.dts/presets/shared/read-json-report.d.ts.map +0 -1
- package/dist/.dts/presets/shared/terminal-status.d.ts +0 -25
- package/dist/.dts/presets/shared/terminal-status.d.ts.map +0 -1
- package/dist/.dts/presets/shared/vitest-json-policy.d.ts +0 -20
- package/dist/.dts/presets/shared/vitest-json-policy.d.ts.map +0 -1
- package/dist/.dts/presets/stylelint.d.ts +0 -17
- package/dist/.dts/presets/stylelint.d.ts.map +0 -1
- package/dist/.dts/presets/test.d.ts +0 -4
- package/dist/.dts/presets/test.d.ts.map +0 -1
- package/dist/.dts/presets/typecheck.d.ts +0 -4
- package/dist/.dts/presets/typecheck.d.ts.map +0 -1
- package/dist/.dts/run-repo-contract.d.ts +0 -38
- package/dist/.dts/run-repo-contract.d.ts.map +0 -1
- package/dist/.dts/types.d.ts +0 -324
- package/dist/.dts/types.d.ts.map +0 -1
- package/dist/index.d.cts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/presets.d.cts +0 -1
- package/dist/presets.d.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0](https://github.com/MaverickCER/repo-contract/compare/repo-contract-v0.2.1...repo-contract-v0.3.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
|
+
* contract engine, self-hosting checks and CI workflows ([97ae128](https://github.com/MaverickCER/repo-contract/commit/97ae128bd96f2c631057f1150068af58cc66b224))
|
|
13
|
+
* contract engine, self-hosting checks and CI workflows ([22b2a3b](https://github.com/MaverickCER/repo-contract/commit/22b2a3b03fe4e44ad7a01b82342d86583926416a))
|
|
14
|
+
* make process spawning and env access consumer-supplied capabilities ([640a961](https://github.com/MaverickCER/repo-contract/commit/640a961b65fd73511586c3b4f6274284bf5172a9))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* clear Socket supply-chain alerts (unminified dist, no prepare script) ([71c9230](https://github.com/MaverickCER/repo-contract/commit/71c92306fcf00e3858f0596c2d56f3272199339d))
|
|
20
|
+
* publish the dist bundle unminified ([266ae47](https://github.com/MaverickCER/repo-contract/commit/266ae47919753c55f10364713fe0c7c5876815b7))
|
|
21
|
+
* remove the prepare install script from the published package ([9153540](https://github.com/MaverickCER/repo-contract/commit/9153540e4f0f1a069e07c12ddfee8ba62827ce5b))
|
|
22
|
+
|
|
23
|
+
## [0.2.1](https://github.com/MaverickCER/repo-contract/compare/repo-contract-v0.2.0...repo-contract-v0.2.1) (2026-09-03)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Documentation
|
|
27
|
+
|
|
28
|
+
* add Socket security badge to README
|
|
29
|
+
|
|
30
|
+
## [0.2.0](https://github.com/MaverickCER/repo-contract/compare/repo-contract-v0.1.1...repo-contract-v0.2.0) (2026-09-03)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### ⚠ BREAKING CHANGES
|
|
34
|
+
|
|
35
|
+
* 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.
|
|
36
|
+
|
|
37
|
+
### Features
|
|
38
|
+
|
|
39
|
+
* make process spawning and env access consumer-supplied capabilities ([640a961](https://github.com/MaverickCER/repo-contract/commit/640a961b65fd73511586c3b4f6274284bf5172a9))
|
|
40
|
+
|
|
3
41
|
## [0.1.1](https://github.com/MaverickCER/repo-contract/compare/repo-contract-v0.1.0...repo-contract-v0.1.1) (2026-09-01)
|
|
4
42
|
|
|
5
43
|
|
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
[](scripts/coverage-thresholds.mjs)
|
|
7
7
|
[](tsconfig.json)
|
|
8
8
|
[](package.json)
|
|
9
|
+
[](https://badge.socket.dev/npm/package/repo-contract/latest)
|
|
9
10
|
|
|
10
11
|
**Turn your repository's engineering standards into enforceable contracts.**
|
|
11
12
|
|
|
@@ -92,7 +93,7 @@ npm install --save-dev yaml
|
|
|
92
93
|
|
|
93
94
|
### Runtime support matrix
|
|
94
95
|
|
|
95
|
-
repo-contract
|
|
96
|
+
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
97
|
|
|
97
98
|
| Environment | Supported |
|
|
98
99
|
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -113,9 +114,14 @@ Define your repository's standards as checks:
|
|
|
113
114
|
|
|
114
115
|
```ts
|
|
115
116
|
// repo-contract.config.ts
|
|
117
|
+
import { spawn } from "node:child_process"
|
|
116
118
|
import { defineRepoContract } from "repo-contract"
|
|
117
119
|
|
|
118
120
|
export default defineRepoContract({
|
|
121
|
+
// repo-contract never spawns a process or reads process.env itself -- see
|
|
122
|
+
// "Supplying spawn/env" below for why, and what to pass on Windows.
|
|
123
|
+
spawn,
|
|
124
|
+
env: process.env,
|
|
119
125
|
checks: {
|
|
120
126
|
tests: {
|
|
121
127
|
run: "npm test",
|
|
@@ -179,6 +185,49 @@ process.exitCode = verdict.passed ? 0 : 1
|
|
|
179
185
|
|
|
180
186
|
There is no CLI, no config-file discovery magic, and no hidden state.
|
|
181
187
|
|
|
188
|
+
### Supplying `spawn`/`env`
|
|
189
|
+
|
|
190
|
+
`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.
|
|
191
|
+
|
|
192
|
+
**macOS/Linux** — plain `node:child_process` is enough:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { spawn } from "node:child_process"
|
|
196
|
+
|
|
197
|
+
export default defineRepoContract({
|
|
198
|
+
spawn,
|
|
199
|
+
env: process.env,
|
|
200
|
+
checks: {/* ... */},
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**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:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import crossSpawn, { sync as crossSpawnSync } from "cross-spawn"
|
|
208
|
+
|
|
209
|
+
export default defineRepoContract({
|
|
210
|
+
spawn: crossSpawn,
|
|
211
|
+
env: process.env,
|
|
212
|
+
// Optional: lets a timed-out/aborted/Ctrl+C-killed check's full process
|
|
213
|
+
// tree (not just its immediate process) get cleaned up on Windows too.
|
|
214
|
+
killProcessTree: crossSpawnSync,
|
|
215
|
+
checks: {/* ... */},
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**`cross-spawn` does not mean "shell execution."** These are two independent choices:
|
|
220
|
+
|
|
221
|
+
| `Spawner` choice | `shell` option | Result |
|
|
222
|
+
| ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
223
|
+
| native `spawn` | `false` (default) | argv-only, no shell interpretation |
|
|
224
|
+
| `cross-spawn` | `false` (default) | Windows `.cmd`/`.bat` resolution + safe `cmd.exe` quoting, still argv-only |
|
|
225
|
+
| either | `true` | shell metacharacters (`&&`, `\|`, …) interpreted — a per-check or global opt-in, see [`shell`](docs/api-report/repo-contract.api.md) |
|
|
226
|
+
|
|
227
|
+
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.
|
|
228
|
+
|
|
229
|
+
`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.
|
|
230
|
+
|
|
182
231
|
## The model
|
|
183
232
|
|
|
184
233
|
```text
|
|
@@ -641,10 +690,13 @@ It does **not** encode your repository's definition of quality.
|
|
|
641
690
|
Import a preset, spread it into your own `checks` record, and override whatever you need — most often `policy`:
|
|
642
691
|
|
|
643
692
|
```ts
|
|
693
|
+
import { spawn } from "node:child_process"
|
|
644
694
|
import { defineRepoContract } from "repo-contract"
|
|
645
695
|
import { format, typecheck, license } from "repo-contract/presets"
|
|
646
696
|
|
|
647
697
|
export default defineRepoContract({
|
|
698
|
+
spawn,
|
|
699
|
+
env: process.env,
|
|
648
700
|
checks: {
|
|
649
701
|
format,
|
|
650
702
|
typecheck: {
|
|
@@ -662,10 +714,13 @@ export default defineRepoContract({
|
|
|
662
714
|
Some presets are factories because they expose options that change what gets executed:
|
|
663
715
|
|
|
664
716
|
```ts
|
|
717
|
+
import { spawn } from "node:child_process"
|
|
665
718
|
import { defineRepoContract } from "repo-contract"
|
|
666
719
|
import { lint, deadCode } from "repo-contract/presets"
|
|
667
720
|
|
|
668
721
|
export default defineRepoContract({
|
|
722
|
+
spawn,
|
|
723
|
+
env: process.env,
|
|
669
724
|
checks: {
|
|
670
725
|
lint: lint({ path: "src" }),
|
|
671
726
|
deadCode: deadCode({
|
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);
|