dorfl 0.2.1 → 0.3.1
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/cli.d.ts.map +1 -1
- package/dist/cli.js +43 -5
- package/dist/cli.js.map +1 -1
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +3 -0
- package/dist/complete.js.map +1 -1
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +8 -5
- package/dist/do.js.map +1 -1
- package/dist/gate-readiness.d.ts +22 -8
- package/dist/gate-readiness.d.ts.map +1 -1
- package/dist/gate-readiness.js +21 -5
- package/dist/gate-readiness.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/prepare.d.ts +9 -4
- package/dist/prepare.d.ts.map +1 -1
- package/dist/prepare.js +3 -0
- package/dist/prepare.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +4 -2
- package/dist/run.js.map +1 -1
- package/dist/verify.d.ts +46 -9
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +54 -9
- package/dist/verify.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +49 -5
- package/src/complete.ts +3 -0
- package/src/do.ts +8 -5
- package/src/gate-readiness.ts +37 -8
- package/src/index.ts +2 -1
- package/src/prepare.ts +9 -4
- package/src/run.ts +4 -2
- package/src/verify.ts +71 -13
package/src/gate-readiness.ts
CHANGED
|
@@ -2,6 +2,11 @@ import {existsSync} from 'node:fs';
|
|
|
2
2
|
import {join} from 'node:path';
|
|
3
3
|
import {run} from './git.js';
|
|
4
4
|
import {resolvePrepareCommands, type PrepareConfig} from './prepare.js';
|
|
5
|
+
import {
|
|
6
|
+
resolveVerifyCommands,
|
|
7
|
+
VerifyNotConfiguredError,
|
|
8
|
+
type VerifyConfig,
|
|
9
|
+
} from './verify.js';
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* Pre-claim / pre-build startup GUARD for the fresh-worktree acceptance gate
|
|
@@ -21,12 +26,15 @@ import {resolvePrepareCommands, type PrepareConfig} from './prepare.js';
|
|
|
21
26
|
* the guard KEYS off LOCKFILE-PRESENT evidence — never off `prepare`-unset
|
|
22
27
|
* alone — and a genuinely dep-free repo (no lockfile) PROCEEDS unchanged.
|
|
23
28
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
29
|
+
* VERIFY-UNSET is ALSO a static stop, and — unlike the deps case — it is
|
|
30
|
+
* MODE-INDEPENDENT: Dorfl has no default gate, so a repo with no `verify`
|
|
31
|
+
* declared can never pass an acceptance gate in ANY mode (fresh-worktree or
|
|
32
|
+
* in-place). {@link resolveVerifyCommands} throws {@link VerifyNotConfiguredError}
|
|
33
|
+
* when `verify` is unset / empty / all-blank; the guard detects that at second
|
|
34
|
+
* zero and STOPS before a wasted claim + build. This check runs regardless of
|
|
35
|
+
* `freshWorktreeGate`.
|
|
28
36
|
*
|
|
29
|
-
* The guard is also gated on `freshWorktreeGate === true` for THIS invocation:
|
|
37
|
+
* The DEPS guard below is also gated on `freshWorktreeGate === true` for THIS invocation:
|
|
30
38
|
* when the gate is OFF (`--no-fresh-worktree-gate`) the acceptance gate runs in
|
|
31
39
|
* the agent's BUILD worktree (which HAS deps), so the throwaway-worktree
|
|
32
40
|
* reasoning does not apply.
|
|
@@ -92,6 +100,12 @@ export interface GatePreconditionInput {
|
|
|
92
100
|
freshWorktreeGate: boolean | undefined;
|
|
93
101
|
/** The resolved `prepare` config (unset / string / list). */
|
|
94
102
|
prepare?: PrepareConfig;
|
|
103
|
+
/**
|
|
104
|
+
* The resolved `verify` config (unset / string / list). When this is unset
|
|
105
|
+
* (or all-blank) there is NO gate to run — a MODE-INDEPENDENT static stop,
|
|
106
|
+
* checked before the deps guard.
|
|
107
|
+
*/
|
|
108
|
+
verify?: VerifyConfig;
|
|
95
109
|
/**
|
|
96
110
|
* The lockfile basename probed for this repo (one of
|
|
97
111
|
* {@link LOCKFILE_BASENAMES}), or `undefined` for the intentional dep-free
|
|
@@ -103,9 +117,13 @@ export interface GatePreconditionInput {
|
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
export interface GatePreconditionFailure {
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
/**
|
|
121
|
+
* The lockfile basename (from {@link LOCKFILE_BASENAMES}) that tripped the
|
|
122
|
+
* DEPS guard, or `undefined` for the verify-unset failure (which is not tied
|
|
123
|
+
* to any lockfile).
|
|
124
|
+
*/
|
|
125
|
+
lockfile?: string;
|
|
126
|
+
/** A precise, actionable error message naming the cause and the way(s) out. */
|
|
109
127
|
message: string;
|
|
110
128
|
}
|
|
111
129
|
|
|
@@ -119,6 +137,17 @@ export interface GatePreconditionFailure {
|
|
|
119
137
|
export function checkGatePreconditions(
|
|
120
138
|
input: GatePreconditionInput,
|
|
121
139
|
): GatePreconditionFailure | undefined {
|
|
140
|
+
// VERIFY-UNSET — MODE-INDEPENDENT (checked FIRST, before the fresh-worktree
|
|
141
|
+
// short-circuit): Dorfl has no default gate, so an unconfigured `verify` can
|
|
142
|
+
// never pass in ANY mode. `resolveVerifyCommands` throws when unset/all-blank.
|
|
143
|
+
try {
|
|
144
|
+
resolveVerifyCommands(input.verify);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (err instanceof VerifyNotConfiguredError) {
|
|
147
|
+
return {message: err.message};
|
|
148
|
+
}
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
122
151
|
// Gate OFF for THIS invocation ⇒ no throwaway worktree ⇒ the deps reasoning
|
|
123
152
|
// does not apply. (The default is ON; the guard fires when ON.)
|
|
124
153
|
if (input.freshWorktreeGate !== true) {
|
package/src/index.ts
CHANGED
|
@@ -572,7 +572,8 @@ export type {RunVerifyOptions, RunVerifyResult} from './verify.js';
|
|
|
572
572
|
export {
|
|
573
573
|
runVerify,
|
|
574
574
|
resolveVerifyCommands,
|
|
575
|
-
|
|
575
|
+
VerifyNotConfiguredError,
|
|
576
|
+
VERIFY_NOT_CONFIGURED_MESSAGE,
|
|
576
577
|
} from './verify.js';
|
|
577
578
|
|
|
578
579
|
export type {
|
package/src/prepare.ts
CHANGED
|
@@ -16,10 +16,12 @@ import type {VerifyConfig} from './verify.js';
|
|
|
16
16
|
* cheaply-re-runnable acceptance check and make every gate run pay the install
|
|
17
17
|
* cost). `prepare` is where install belongs; `verify` stays the gate.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
* genuine NO-OP (a repo with no deps
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* Like `verify`, `prepare` has NO default command — but the two differ in what
|
|
20
|
+
* "unset" MEANS. An unset `prepare` is a genuine NO-OP (a repo with no deps
|
|
21
|
+
* needs no install — we never invent a default that would run `pnpm install` in
|
|
22
|
+
* a repo that has no lockfile). An unset `verify`, by contrast, is a hard error
|
|
23
|
+
* (a repo MUST declare its acceptance gate; there is no gate to invent). So both
|
|
24
|
+
* are default-free, but unset-prepare passes vacuously while unset-verify FAILS.
|
|
23
25
|
*
|
|
24
26
|
* Like `verify` it is a DECLARED, deterministic shell step (a single command or
|
|
25
27
|
* an ordered list, all must pass) — no model in the loop. It is read-only with
|
|
@@ -40,6 +42,9 @@ export type PrepareConfig = VerifyConfig;
|
|
|
40
42
|
* difference from {@link resolveVerifyCommands}). A string ⇒ a single command. A
|
|
41
43
|
* list ⇒ the list, with blank/whitespace-only entries dropped (which can leave
|
|
42
44
|
* an empty list ⇒ still a no-op). Each command is run in sequence; all must pass.
|
|
45
|
+
*
|
|
46
|
+
* Contrast {@link resolveVerifyCommands}, which THROWS on the unset/all-blank
|
|
47
|
+
* case (there is no default gate) rather than resolving to a no-op.
|
|
43
48
|
*/
|
|
44
49
|
export function resolvePrepareCommands(
|
|
45
50
|
prepare: PrepareConfig | undefined,
|
package/src/run.ts
CHANGED
|
@@ -630,8 +630,9 @@ async function runOneItem(
|
|
|
630
630
|
// same `config-error` axis the PR-intent guard above uses (a wiring/config
|
|
631
631
|
// fault, not a task fault). The repo discovery is either a working checkout
|
|
632
632
|
// (in-place tests) OR a BARE hub mirror (production fleet); detect the
|
|
633
|
-
// lockfile from whichever shape `repoPath` is.
|
|
634
|
-
// verify-
|
|
633
|
+
// lockfile from whichever shape `repoPath` is. ALSO fails fast when
|
|
634
|
+
// `verify` is unset/all-blank (a MODE-INDEPENDENT stop — Dorfl has no
|
|
635
|
+
// default gate, so an unconfigured `verify` can never pass in any mode).
|
|
635
636
|
{
|
|
636
637
|
const lockfile =
|
|
637
638
|
detectLockfileOnDisk(repoPath) ??
|
|
@@ -639,6 +640,7 @@ async function runOneItem(
|
|
|
639
640
|
const guard = checkGatePreconditions({
|
|
640
641
|
freshWorktreeGate: config.freshWorktreeGate,
|
|
641
642
|
prepare: config.prepare,
|
|
643
|
+
verify: config.verify,
|
|
642
644
|
lockfile,
|
|
643
645
|
});
|
|
644
646
|
if (guard !== undefined) {
|
package/src/verify.ts
CHANGED
|
@@ -12,43 +12,77 @@ import {spawn} from 'node:child_process';
|
|
|
12
12
|
*
|
|
13
13
|
* It is read-only with respect to `work/`: it runs the declared check and never
|
|
14
14
|
* moves or commits anything.
|
|
15
|
+
*
|
|
16
|
+
* There is NO default gate. A repo MUST declare its own `verify`; an unset gate
|
|
17
|
+
* is a loud failure (`notConfigured`), never a silent `pnpm -r …` fallback that
|
|
18
|
+
* could run the wrong check or pass vacuously.
|
|
15
19
|
*/
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* The per-repo gate, as declared in config. A single shell command, or an
|
|
19
23
|
* ordered list of commands run in sequence (each must pass for the gate to
|
|
20
|
-
* pass). `undefined` (unset)
|
|
24
|
+
* pass). `undefined` (unset) is NOT valid: there is NO default gate — a repo
|
|
25
|
+
* MUST declare its own `verify` (see {@link VerifyNotConfiguredError}).
|
|
21
26
|
*/
|
|
22
27
|
export type VerifyConfig = string | string[];
|
|
23
28
|
|
|
24
29
|
/**
|
|
25
|
-
* The
|
|
26
|
-
*
|
|
30
|
+
* The precise, actionable message emitted whenever a `verify` gate is required
|
|
31
|
+
* but the repo declares none (unset, empty string, or an all-blank list). There
|
|
32
|
+
* is deliberately NO default gate: a silent `pnpm -r build && …` fallback runs
|
|
33
|
+
* the WRONG check in a repo whose real gate is something else (and passes
|
|
34
|
+
* vacuously in a repo pnpm knows nothing about — e.g. `pnpm -r` printing
|
|
35
|
+
* "No projects found" and exiting 0, a FALSE green). Failing loud here forces
|
|
36
|
+
* the repo to declare the gate it actually means.
|
|
37
|
+
*/
|
|
38
|
+
export const VERIFY_NOT_CONFIGURED_MESSAGE =
|
|
39
|
+
'no `verify` gate is configured for this repo. Dorfl has no default ' +
|
|
40
|
+
'acceptance gate: declare the exact command(s) in `dorfl.json` ' +
|
|
41
|
+
'(e.g. "verify": "pnpm -r build && pnpm -r test && pnpm format:check"), ' +
|
|
42
|
+
'as a single string or an ordered list of commands.';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Thrown by {@link resolveVerifyCommands} when a gate is required but none is
|
|
46
|
+
* declared. A typed error (not a bare `Error`) so callers that must translate
|
|
47
|
+
* an unconfigured gate into a routed gate-FAILURE (rather than an uncaught
|
|
48
|
+
* crash) can detect it precisely — see {@link runVerify}, which catches it and
|
|
49
|
+
* returns a failing {@link RunVerifyResult} with `notConfigured: true`.
|
|
27
50
|
*/
|
|
28
|
-
export
|
|
29
|
-
|
|
51
|
+
export class VerifyNotConfiguredError extends Error {
|
|
52
|
+
constructor(message: string = VERIFY_NOT_CONFIGURED_MESSAGE) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = 'VerifyNotConfiguredError';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
30
57
|
|
|
31
58
|
/**
|
|
32
59
|
* Resolve the declared gate into an ordered, non-empty list of shell commands.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
60
|
+
* A string ⇒ a single command. A list ⇒ the list, with blank/whitespace-only
|
|
61
|
+
* entries dropped. Each command is run in sequence; all must pass.
|
|
62
|
+
*
|
|
63
|
+
* There is NO default: unset, an empty string, or a list that is empty/all-blank
|
|
64
|
+
* THROWS {@link VerifyNotConfiguredError}. Dorfl never invents an acceptance
|
|
65
|
+
* gate — a repo must declare the exact check it means, so the gate can never
|
|
66
|
+
* silently run the wrong command (or pass vacuously).
|
|
36
67
|
*/
|
|
37
68
|
export function resolveVerifyCommands(
|
|
38
69
|
verify: VerifyConfig | undefined,
|
|
39
70
|
): string[] {
|
|
40
71
|
if (verify === undefined) {
|
|
41
|
-
|
|
72
|
+
throw new VerifyNotConfiguredError();
|
|
42
73
|
}
|
|
43
74
|
const list = Array.isArray(verify) ? verify : [verify];
|
|
44
75
|
const commands = list.filter((command) => command.trim() !== '');
|
|
45
|
-
|
|
76
|
+
if (commands.length === 0) {
|
|
77
|
+
throw new VerifyNotConfiguredError();
|
|
78
|
+
}
|
|
79
|
+
return commands;
|
|
46
80
|
}
|
|
47
81
|
|
|
48
82
|
export interface RunVerifyOptions {
|
|
49
83
|
/** The repo to run the gate in (its working directory). */
|
|
50
84
|
cwd: string;
|
|
51
|
-
/** The declared gate (string | list). Unset ⇒
|
|
85
|
+
/** The declared gate (string | list). Unset/blank ⇒ a failing, not-configured result. */
|
|
52
86
|
verify?: VerifyConfig;
|
|
53
87
|
/** Environment for the gate's child processes. */
|
|
54
88
|
env?: NodeJS.ProcessEnv;
|
|
@@ -61,10 +95,17 @@ export interface RunVerifyOptions {
|
|
|
61
95
|
export interface RunVerifyResult {
|
|
62
96
|
/** The exit code of the gate: 0 iff every command passed. */
|
|
63
97
|
exitCode: number;
|
|
64
|
-
/** The ordered commands that were run (resolved from config). */
|
|
98
|
+
/** The ordered commands that were run (resolved from config). Empty when not configured. */
|
|
65
99
|
commands: string[];
|
|
66
100
|
/** Whether the gate passed (exitCode === 0). */
|
|
67
101
|
passed: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* True iff the gate could not run because NO `verify` is declared (unset /
|
|
104
|
+
* empty / all-blank). A distinct, always-failing outcome (`passed: false`)
|
|
105
|
+
* so runner callers route it as a normal gate FAILURE with a clear reason
|
|
106
|
+
* rather than crashing on the thrown {@link VerifyNotConfiguredError}.
|
|
107
|
+
*/
|
|
108
|
+
notConfigured?: boolean;
|
|
68
109
|
}
|
|
69
110
|
|
|
70
111
|
/**
|
|
@@ -73,11 +114,28 @@ export interface RunVerifyResult {
|
|
|
73
114
|
* the first non-zero exit short-circuits the rest and becomes the result
|
|
74
115
|
* (mirroring `&&` semantics). Each command is run through `bash -c` so that
|
|
75
116
|
* shell operators in the declared gate (`&&`, pipes, etc.) behave as written.
|
|
117
|
+
*
|
|
118
|
+
* A repo with NO `verify` declared yields a failing result with
|
|
119
|
+
* `notConfigured: true` (never throws): the shared runner call sites
|
|
120
|
+
* (`do`/`run`/`complete` → `performIntegration`) already route a non-passing
|
|
121
|
+
* gate to needs-attention, so an unconfigured gate surfaces the same way with a
|
|
122
|
+
* precise reason instead of an uncaught crash.
|
|
76
123
|
*/
|
|
77
124
|
export async function runVerify(
|
|
78
125
|
options: RunVerifyOptions,
|
|
79
126
|
): Promise<RunVerifyResult> {
|
|
80
|
-
|
|
127
|
+
let commands: string[];
|
|
128
|
+
try {
|
|
129
|
+
commands = resolveVerifyCommands(options.verify);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (err instanceof VerifyNotConfiguredError) {
|
|
132
|
+
const onStderr =
|
|
133
|
+
options.onStderr ?? ((chunk: string) => process.stderr.write(chunk));
|
|
134
|
+
onStderr(`${err.message}\n`);
|
|
135
|
+
return {exitCode: 1, commands: [], passed: false, notConfigured: true};
|
|
136
|
+
}
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
81
139
|
const onStdout =
|
|
82
140
|
options.onStdout ?? ((chunk: string) => process.stdout.write(chunk));
|
|
83
141
|
const onStderr =
|