autonomous-sdlc-harness 0.1.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/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +24 -0
- package/dist/cli.js +194 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.js +561 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/daemon.js +791 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/doctor.js +336 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +2023 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/registry.js +42 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/config/check.js +505 -0
- package/dist/config/check.js.map +1 -0
- package/dist/config/io.js +177 -0
- package/dist/config/io.js.map +1 -0
- package/dist/config/model.js +406 -0
- package/dist/config/model.js.map +1 -0
- package/dist/core/errors.js +71 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/git.js +537 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/json.js +125 -0
- package/dist/core/json.js.map +1 -0
- package/dist/core/layerCoverage.js +141 -0
- package/dist/core/layerCoverage.js.map +1 -0
- package/dist/core/layerGapRemedy.js +62 -0
- package/dist/core/layerGapRemedy.js.map +1 -0
- package/dist/core/nameList.js +23 -0
- package/dist/core/nameList.js.map +1 -0
- package/dist/core/paths.js +153 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/prompt.js +206 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/repoPaths.js +55 -0
- package/dist/core/repoPaths.js.map +1 -0
- package/dist/core/report.js +150 -0
- package/dist/core/report.js.map +1 -0
- package/dist/core/templating.js +88 -0
- package/dist/core/templating.js.map +1 -0
- package/dist/core/writer.js +479 -0
- package/dist/core/writer.js.map +1 -0
- package/dist/daemon/backend.js +180 -0
- package/dist/daemon/backend.js.map +1 -0
- package/dist/daemon/units.js +380 -0
- package/dist/daemon/units.js.map +1 -0
- package/dist/detect/nestedApplication.js +79 -0
- package/dist/detect/nestedApplication.js.map +1 -0
- package/dist/detect/presets.js +2033 -0
- package/dist/detect/presets.js.map +1 -0
- package/dist/detect/signals.js +1368 -0
- package/dist/detect/signals.js.map +1 -0
- package/dist/doctor/checks.js +3530 -0
- package/dist/doctor/checks.js.map +1 -0
- package/dist/generators/claudeContext.js +588 -0
- package/dist/generators/claudeContext.js.map +1 -0
- package/dist/generators/githooks.js +446 -0
- package/dist/generators/githooks.js.map +1 -0
- package/dist/generators/harnessConfig.js +632 -0
- package/dist/generators/harnessConfig.js.map +1 -0
- package/dist/generators/notifications.js +191 -0
- package/dist/generators/notifications.js.map +1 -0
- package/dist/generators/outerLoopScripts.js +165 -0
- package/dist/generators/outerLoopScripts.js.map +1 -0
- package/dist/generators/permissionProfile.js +1172 -0
- package/dist/generators/permissionProfile.js.map +1 -0
- package/dist/generators/projectSettings.js +322 -0
- package/dist/generators/projectSettings.js.map +1 -0
- package/dist/generators/repoRoot.js +417 -0
- package/dist/generators/repoRoot.js.map +1 -0
- package/dist/generators/scripts.js +557 -0
- package/dist/generators/scripts.js.map +1 -0
- package/dist/generators/stateDir.js +221 -0
- package/dist/generators/stateDir.js.map +1 -0
- package/dist/machine/paths.js +111 -0
- package/dist/machine/paths.js.map +1 -0
- package/dist/machine/plugins.js +224 -0
- package/dist/machine/plugins.js.map +1 -0
- package/dist/machine/registry.js +330 -0
- package/dist/machine/registry.js.map +1 -0
- package/package.json +23 -0
- package/scripts/README.md +13 -0
- package/scripts/daemon/launchd.plist.template +59 -0
- package/scripts/daemon/systemd.service.template +58 -0
- package/templates/README.md +15 -0
- package/templates/claude/CLAUDE.md +54 -0
- package/templates/claude/README.md +5 -0
- package/templates/claude/context/api.md +29 -0
- package/templates/claude/context/conventions.md +23 -0
- package/templates/claude/context/data-layer.md +28 -0
- package/templates/claude/context/data-storage.md +29 -0
- package/templates/claude/context/docs-catalog.md +29 -0
- package/templates/claude/context/domain.md +28 -0
- package/templates/claude/context/layer.md +20 -0
- package/templates/claude/context/module.md +30 -0
- package/templates/claude/context/package.md +29 -0
- package/templates/claude/context/presentation.md +32 -0
- package/templates/claude/context/state-slices.md +28 -0
- package/templates/claude/context/tests.md +28 -0
- package/templates/claude/harness-task-offer.md +58 -0
- package/templates/claude/push-notify.env.example +21 -0
- package/templates/claude/qa-accounts.env.example +38 -0
- package/templates/claude/qa_test_scenarios.md +110 -0
- package/templates/claude/settings.autonomous.json +93 -0
- package/templates/claude/settings.autonomous.qa.json +36 -0
- package/templates/githooks/README.md +3 -0
- package/templates/githooks/pre-push +72 -0
- package/templates/repo/README.md +3 -0
- package/templates/repo/gitattributes +16 -0
- package/templates/repo/gitignore +61 -0
- package/templates/repo/gitignore.qa +25 -0
- package/templates/repo/mcp.json +17 -0
- package/templates/scripts/README.md +5 -0
- package/templates/scripts/autonomous-format-stream.sh +95 -0
- package/templates/scripts/autonomous-notify.sh +337 -0
- package/templates/scripts/autonomous-watcher.sh +3087 -0
- package/templates/scripts/cleanup-merged-worktrees.sh +327 -0
- package/templates/scripts/commit-on-branch.sh +288 -0
- package/templates/scripts/create-worktree.sh +360 -0
- package/templates/scripts/deploy.sh +47 -0
- package/templates/scripts/lib/harness-run-lib.sh +1481 -0
- package/templates/scripts/push-branch.sh +140 -0
- package/templates/scripts/refresh-branch.sh +244 -0
- package/templates/scripts/restart-watcher.sh +401 -0
- package/templates/scripts/scratch-run.sh +302 -0
- package/templates/scripts/setup-worktree.sh +262 -0
- package/templates/scripts/start-dev-server.sh +99 -0
- package/templates/scripts/test.sh +50 -0
- package/templates/scripts/typecheck.sh +50 -0
- package/templates/state-dir/README-root.md +13 -0
- package/templates/state-dir/README.md +9 -0
- package/templates/state-dir/architecture_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/architecture_branch_reviews/README.md +9 -0
- package/templates/state-dir/architecture_reviews/README.md +9 -0
- package/templates/state-dir/architecture_user_review_reviews/README.md +9 -0
- package/templates/state-dir/autonomous_inbox/README.md +9 -0
- package/templates/state-dir/autonomous_logs/README.md +9 -0
- package/templates/state-dir/branch_statistics/README.md +9 -0
- package/templates/state-dir/business_parity_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_branch_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_user_review_reviews/README.md +9 -0
- package/templates/state-dir/clarification_digests/README.md +9 -0
- package/templates/state-dir/clarifications/README.md +9 -0
- package/templates/state-dir/code_reviews/README.md +9 -0
- package/templates/state-dir/dispatch_additions/README.md +19 -0
- package/templates/state-dir/docs_catalog/README.md +9 -0
- package/templates/state-dir/flow_progress/README.md +9 -0
- package/templates/state-dir/improvement_observations/README.md +19 -0
- package/templates/state-dir/improvement_suggestions.md +29 -0
- package/templates/state-dir/lessons.md +23 -0
- package/templates/state-dir/qa_review_point_reviews/README.md +9 -0
- package/templates/state-dir/qa_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_reviews/README.md +9 -0
- package/templates/state-dir/scratch/README.md +11 -0
- package/templates/state-dir/skeptic_review_plan_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_review_point_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_reviews/README.md +9 -0
- package/templates/state-dir/story_plans/README.md +9 -0
- package/templates/state-dir/task_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/task_plan_reviews/README.md +9 -0
- package/templates/state-dir/task_plans/README.md +9 -0
- package/templates/state-dir/task_prompts/README.md +9 -0
- package/templates/state-dir/ui_test_plan_reviews/README.md +9 -0
- package/templates/state-dir/ui_test_plans/README.md +9 -0
- package/templates/state-dir/user_review_fix_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/user_reviews/README.md +9 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generator: the project-command wrapper scripts `init` writes into the configured `scriptsDir`.
|
|
3
|
+
*
|
|
4
|
+
* **The rule this module exists to enforce: {@link WRAPPER_SCRIPTS} is the single source for the
|
|
5
|
+
* wrapper set, and {@link scriptInvocation} is the single producer of the string that names *any*
|
|
6
|
+
* script `init` writes into `scriptsDir`.** The generated config, the wrapper files and the
|
|
7
|
+
* generated permission profile are three views of one string; drift between them is invisible until
|
|
8
|
+
* an unattended run stalls on a command that matches neither `allow` nor `deny`, which in `-p` mode
|
|
9
|
+
* is a stall rather than a prompt. So the config generator and the profile generator import from
|
|
10
|
+
* here rather than formatting `bash …/….sh` themselves; {@link wrapperInvocation} is the typed
|
|
11
|
+
* wrapper-only spelling of the same producer, and `generators/outerLoopScripts.ts` — whose files
|
|
12
|
+
* land in the same directory and whose agent-invocable rows reach the same profile — calls
|
|
13
|
+
* {@link scriptInvocation} directly rather than becoming a second producer of the shape.
|
|
14
|
+
*
|
|
15
|
+
* ## Three non-obvious choices, and where each comes from
|
|
16
|
+
*
|
|
17
|
+
* 1. **`commands.*` holds the wrapper *invocation*; the wrapper file holds the *raw* command
|
|
18
|
+
* line.** The `commands` object of `examples/harness.config.json` is the worked shape:
|
|
19
|
+
* `"typecheck": "bash packages/storefront/scripts/typecheck.sh"` in the config, with the
|
|
20
|
+
* `npm run …` / `pytest` / `go vet …` line inside the script. `deploy.command` (that
|
|
21
|
+
* file's `deploy` object) takes the same wrapper form on its own object. The wrappers
|
|
22
|
+
* exist because an unattended run's permission profile can allow-list a literal script
|
|
23
|
+
* path far more safely than an arbitrary command line (`cli/templates/scripts/README.md`).
|
|
24
|
+
* 2. **`build` and `depInstall` are deliberately not wrapped**, and therefore never allow-listed:
|
|
25
|
+
* the same example config leaves both raw (`"npm run build --workspace …"`, `"npm ci"`),
|
|
26
|
+
* because they are orchestrator- or human-run rather than agent-run. That is the same reason
|
|
27
|
+
* the reference profile gates the package manager outright, which the generated profile's
|
|
28
|
+
* rationale block states. Wrapping them would hand an agent a script path it is allowed to
|
|
29
|
+
* run, which is exactly what those two must not have.
|
|
30
|
+
* 3. **Each script prints its own verdict line** — `PASS: <name>` / `FAIL: <name> (exit n)` —
|
|
31
|
+
* so a caller never appends `; echo $?` to it. That compound form is what stalls an
|
|
32
|
+
* unattended run on a permission prompt, and a wrapper that reports its own result removes
|
|
33
|
+
* the reason to write one.
|
|
34
|
+
*
|
|
35
|
+
* ## Scope
|
|
36
|
+
*
|
|
37
|
+
* This module is the **writer**, and the templates it renders now carry their finished bodies —
|
|
38
|
+
* self-anchoring to their own checkout, forwarding their arguments, and (for the dev server)
|
|
39
|
+
* detaching the process and reporting its pid and log path. What they never carry is the command
|
|
40
|
+
* line itself: that arrives from `commands.*` / `deploy.command` at render time. A wrapper this
|
|
41
|
+
* module selects is written with its body rather than left for later, because `commands.*` points
|
|
42
|
+
* at it and the permission profile allow-lists its literal path — writing a path the file does not
|
|
43
|
+
* back would generate a profile naming a file that does not exist.
|
|
44
|
+
*
|
|
45
|
+
* The one thing here that does not write is {@link wrapperCommandLine}, which reads a written
|
|
46
|
+
* wrapper's command line back out; it sits in this module rather than beside its caller for the
|
|
47
|
+
* reason its own doc gives.
|
|
48
|
+
*/
|
|
49
|
+
import { join } from 'node:path';
|
|
50
|
+
import { answersNone, DEFAULTS, isPlaceholder } from '../config/model.js';
|
|
51
|
+
import { HarnessError } from '../core/errors.js';
|
|
52
|
+
import { readTemplate } from '../core/paths.js';
|
|
53
|
+
import { normalizeRepoDir } from '../core/repoPaths.js';
|
|
54
|
+
import { renderTemplate } from '../core/templating.js';
|
|
55
|
+
import { devServerPrestart } from '../detect/presets.js';
|
|
56
|
+
/**
|
|
57
|
+
* The whole wrapper set, in the order `init` writes it.
|
|
58
|
+
*
|
|
59
|
+
* **Declared once, here.** A `.sh` name that appears as a string literal anywhere else in the CLI
|
|
60
|
+
* is a second source for this table and will drift from it; the template path and the invocation
|
|
61
|
+
* string are both derived from a row rather than spelled out again.
|
|
62
|
+
*/
|
|
63
|
+
export const WRAPPER_SCRIPTS = Object.freeze([
|
|
64
|
+
Object.freeze({ key: 'typecheck', file: 'typecheck.sh' }),
|
|
65
|
+
Object.freeze({ key: 'test', file: 'test.sh' }),
|
|
66
|
+
Object.freeze({ key: 'devServer', file: 'start-dev-server.sh' }),
|
|
67
|
+
Object.freeze({ key: 'deploy', file: 'deploy.sh' }),
|
|
68
|
+
]);
|
|
69
|
+
/** The two wrappers written whatever the repository looks like: the flow's verification pair. */
|
|
70
|
+
const ALWAYS_SELECTED = Object.freeze(['typecheck', 'test']);
|
|
71
|
+
/** Executable, because `commands.*` invokes the file rather than sourcing it. */
|
|
72
|
+
const SCRIPT_MODE = 0o755;
|
|
73
|
+
/** The templates' subdirectory under `cli/templates/`, addressed as {@link readTemplate} wants it. */
|
|
74
|
+
const TEMPLATE_DIR = 'scripts';
|
|
75
|
+
/** `<dir>/<file>`, POSIX-style, with a `.`-valued directory meaning the repository root. */
|
|
76
|
+
function posixJoin(dir, file) {
|
|
77
|
+
const normalized = normalizeRepoDir(dir);
|
|
78
|
+
return normalized === '.' ? file : `${normalized}/${file}`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The interpreter {@link scriptInvocation} prefixes every path with, and the one string
|
|
82
|
+
* {@link invokedPath} takes back off to name the file an invocation runs.
|
|
83
|
+
*/
|
|
84
|
+
const INVOCATION_PREFIX = 'bash ';
|
|
85
|
+
/**
|
|
86
|
+
* The repo-relative path inside an invocation **this module produced** — never inside a configured
|
|
87
|
+
* value. It is the inverse of {@link scriptInvocation} over that one producer's own output, which is
|
|
88
|
+
* why it may slice a fixed prefix; a `commands.*` value may legitimately be a raw command line, and
|
|
89
|
+
* parsing one back into a path is how the three views drift apart (`docs/cli.md` §6).
|
|
90
|
+
*/
|
|
91
|
+
function invokedPath(invocation) {
|
|
92
|
+
return invocation.slice(INVOCATION_PREFIX.length);
|
|
93
|
+
}
|
|
94
|
+
/** The `scriptsDir` every wrapper path is built from: the config's value or the schema default. */
|
|
95
|
+
function resolvedScriptsDir(config) {
|
|
96
|
+
return normalizeRepoDir(config.scriptsDir ?? DEFAULTS.scriptsDir);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* **The one producer of `bash <scriptsDir>/<file>`**, for every script `init` writes into the
|
|
100
|
+
* configured `scriptsDir` — the wrappers here and the outer-loop scripts alike.
|
|
101
|
+
*
|
|
102
|
+
* Repo-relative, forward-slashed and unquoted, because commands are executed from the repository
|
|
103
|
+
* root and every path in the config file is repo-relative. The profile's allow entry (i) is this
|
|
104
|
+
* exact string; its repo-root-absolute and sibling-worktree twins are the profile's to derive and
|
|
105
|
+
* are deliberately not produced here.
|
|
106
|
+
*
|
|
107
|
+
* `file` is a path relative to `scriptsDir`, so a script in a subdirectory is named by passing
|
|
108
|
+
* `<subdir>/<file>` rather than by joining the pieces at the call site.
|
|
109
|
+
*/
|
|
110
|
+
export function scriptInvocation(scriptsDir, file) {
|
|
111
|
+
return `${INVOCATION_PREFIX}${posixJoin(scriptsDir, file)}`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The literal `commands.*` (or `deploy.command`) holds for one wrapper: {@link scriptInvocation}
|
|
115
|
+
* narrowed to {@link WrapperFile}.
|
|
116
|
+
*
|
|
117
|
+
* It delegates rather than formatting the string itself, so the wrapper set and the outer-loop set
|
|
118
|
+
* cannot come to be named in two subtly different ways, and the config, the profile and this
|
|
119
|
+
* generator keep reading one function's answer. The narrower parameter type is the point of keeping
|
|
120
|
+
* it: a caller that means "a wrapper" cannot pass an arbitrary name through it by mistake.
|
|
121
|
+
*/
|
|
122
|
+
export function wrapperInvocation(scriptsDir, file) {
|
|
123
|
+
return scriptInvocation(scriptsDir, file);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* One wrapper's repo-relative path, forward-slashed — {@link wrapperInvocation} without the
|
|
127
|
+
* interpreter, for a message that names the **file** rather than a line to run.
|
|
128
|
+
*
|
|
129
|
+
* Exported so a caller that has to speak about a wrapper on disk composes the path from this
|
|
130
|
+
* module's own `scriptsDir` and {@link WrapperFile} instead of restating the join or slicing it back
|
|
131
|
+
* out of an invocation ({@link invokedPath}'s standing prohibition).
|
|
132
|
+
*/
|
|
133
|
+
export function wrapperPath(scriptsDir, file) {
|
|
134
|
+
return posixJoin(scriptsDir, file);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Where a wrapper's command line is configured, as a message should name it.
|
|
138
|
+
*
|
|
139
|
+
* Exported for the `doctor` check that reports on the same keys: `deploy`'s command lives on its own
|
|
140
|
+
* object, so a caller spelling the key family itself would name a `commands.deploy` no config has.
|
|
141
|
+
*/
|
|
142
|
+
export function configKeyPath(key) {
|
|
143
|
+
return key === 'deploy' ? 'deploy.command' : `commands.${key}`;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The configured command line for one key, trimmed, or `undefined` when the key carries none.
|
|
147
|
+
*
|
|
148
|
+
* Exported for the caller that reports the lines this run put in effect, which covers the unwrapped
|
|
149
|
+
* `build` and `depInstall` too. `deploy` reads `deploy.command`, which is the same special case
|
|
150
|
+
* {@link configKeyPath} exists for — a second copy of it elsewhere is a second answer to "where is
|
|
151
|
+
* this key's line configured".
|
|
152
|
+
*/
|
|
153
|
+
export function configuredCommand(config, key) {
|
|
154
|
+
const value = key === 'deploy' ? config.deploy?.command : config.commands[key];
|
|
155
|
+
if (value === undefined)
|
|
156
|
+
return undefined;
|
|
157
|
+
const trimmed = value.trim();
|
|
158
|
+
return trimmed === '' ? undefined : trimmed;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* **The one owner of "a wrapped `commands.*` key holds a raw command line rather than its wrapper
|
|
162
|
+
* invocation".** `init` (through {@link resolveBody}), `doctor` and `config set` all ask this
|
|
163
|
+
* function rather than each restating the condition, so the three cannot come to disagree about what
|
|
164
|
+
* the defect is — the state a nested application's hand-fill path produces, where the permission
|
|
165
|
+
* profile allow-lists the wrapper and the configured string is refused.
|
|
166
|
+
*
|
|
167
|
+
* It compares **byte-equality against {@link wrapperInvocation}**, the single producer of the form,
|
|
168
|
+
* and never parses the configured value back into a path: that value may legitimately be a raw
|
|
169
|
+
* command line, and parsing it is how the three views drift apart (`docs/cli.md` §6).
|
|
170
|
+
*
|
|
171
|
+
* Four states are deliberately **not** this defect:
|
|
172
|
+
*
|
|
173
|
+
* - **unset or empty** — there is nothing to compare, and nothing was inlined into a wrapper;
|
|
174
|
+
* - **still the placeholder** — reported by the config check and by {@link writeWrapperScripts}, and
|
|
175
|
+
* no wrapper is written for it at all, so there is no allow-listed form for it to disagree with;
|
|
176
|
+
* - **`commands.typecheck` answered `none`** — the adopter's statement that this repository has no
|
|
177
|
+
* type check, for which no wrapper is written at all, so there is again no allow-listed form for it
|
|
178
|
+
* to disagree with. The exemption is made **here** rather than left to {@link selectWrapper}'s arm
|
|
179
|
+
* because `config set` — the supported route to writing that answer — calls this predicate
|
|
180
|
+
* directly, and would otherwise print {@link wrappedKeyMismatchMessage}, an instruction to undo the
|
|
181
|
+
* answer. The exemption is key-gated like the arm, so it holds on that key only: `commands.test`
|
|
182
|
+
* holding the same string keeps its wrapper and still gets its mismatch report;
|
|
183
|
+
* - **holding the invocation** — the normal state after a first `init`, and the state every correct
|
|
184
|
+
* adoption stays in.
|
|
185
|
+
*/
|
|
186
|
+
export function wrappedKeyMismatch(config, key) {
|
|
187
|
+
const configured = configuredCommand(config, key);
|
|
188
|
+
if (configured === undefined || isPlaceholder(configured) || answersNone(key, configured))
|
|
189
|
+
return undefined;
|
|
190
|
+
// Read from the table rather than from a second `.sh` literal. A `WrapperKey` is that table's own
|
|
191
|
+
// union, so the lookup cannot miss; `undefined` there is the same answer this function gives for
|
|
192
|
+
// every other "nothing to report", which keeps a predicate from carrying an error path.
|
|
193
|
+
const script = WRAPPER_SCRIPTS.find((entry) => entry.key === key);
|
|
194
|
+
if (script === undefined)
|
|
195
|
+
return undefined;
|
|
196
|
+
const invocation = wrapperInvocation(resolvedScriptsDir(config), script.file);
|
|
197
|
+
return configured === invocation ? undefined : { configured, invocation, file: script.file };
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The one sentence every consumer of {@link wrappedKeyMismatch} prints, so `init`, `doctor` and
|
|
201
|
+
* `config set` cannot state one defect three ways.
|
|
202
|
+
*
|
|
203
|
+
* {@link configKeyPath} is applied here rather than by the caller, which is what names the `deploy`
|
|
204
|
+
* row `deploy.command` instead of the `commands.deploy` that key does not have.
|
|
205
|
+
*
|
|
206
|
+
* **The re-run has a condition, and the sentence has to carry it.** Wrappers are written
|
|
207
|
+
* `create-if-absent` ({@link writeWrapperScripts}), so a re-run inlines this line **only** where the
|
|
208
|
+
* file is not there yet; over a wrapper already on disk the write engine keeps the file and the body
|
|
209
|
+
* {@link resolveBody} just computed is discarded, leaving a message that named a plain re-run
|
|
210
|
+
* prescribing a step that silently loses the adopter's line. Both arms are therefore named: the plain
|
|
211
|
+
* re-run for the absent file, and `init --force` — which backs the file up to a `.bak` first — or
|
|
212
|
+
* deleting it for the one that exists.
|
|
213
|
+
*
|
|
214
|
+
* **The two steps are ordered, and the sentence has to carry the order too** — because one of the
|
|
215
|
+
* three sites sees this state *before* the wrapper exists. `config set` is the hand-fill boundary a
|
|
216
|
+
* nested application reaches with the key still holding the placeholder, for which
|
|
217
|
+
* {@link writeWrapperScripts} wrote no wrapper at all; that file is created by the **next** `init`,
|
|
218
|
+
* from the raw line just stored, which is the absent arm. So the wrapper is named as the file that
|
|
219
|
+
* line goes into rather than as one that already runs it, and the corrective `set` is named after the
|
|
220
|
+
* write that has to precede it: performing it first leaves {@link resolveBody} nothing to inline —
|
|
221
|
+
* precedence 1 no longer sees a raw line, precedence 2 finds only the placeholder detection produced
|
|
222
|
+
* — and the wrapper is written to fail with the adopter's command line gone. That is the same
|
|
223
|
+
* sequence `init`'s own undetected-command warning prescribes (`detect/presets.ts`, arm 1), so the
|
|
224
|
+
* two read as one path.
|
|
225
|
+
*/
|
|
226
|
+
export function wrappedKeyMismatchMessage(key, { configured, invocation }) {
|
|
227
|
+
const keyPath = configKeyPath(key);
|
|
228
|
+
const wrapperPath = invokedPath(invocation);
|
|
229
|
+
return (`${keyPath} holds a raw command line (${configured}) rather than the wrapper invocation; ` +
|
|
230
|
+
`${wrapperPath} is the wrapper that line belongs in, and the invocation is the form the permission ` +
|
|
231
|
+
`profile allow-lists. Wrappers are create-if-absent, so init writes ${wrapperPath} from this line ` +
|
|
232
|
+
`only while that file does not exist: re-run init where it is absent, and where it is already on ` +
|
|
233
|
+
`disk run init --force (which backs it up to ${wrapperPath}.bak first) or delete it and re-run. Then set ` +
|
|
234
|
+
`${keyPath} to ${invocation} — setting it before the wrapper holds the line discards the line. ` +
|
|
235
|
+
`An agent told to run the configured string as-is is refused on the raw line`);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* **The wrapper-selection rule, and the only place it is written down.** Its two callers —
|
|
239
|
+
* {@link writeWrapperScripts}, which writes the files, and the permission profile's
|
|
240
|
+
* `selectWrapperScripts`, which decides what may be allow-listed — both run this function rather
|
|
241
|
+
* than each restating the rule:
|
|
242
|
+
*
|
|
243
|
+
* - `typecheck` and `test` always — the two commands the flow treats as "this change is done"
|
|
244
|
+
* (`config/check.ts`), so a repository always has both wrappers to point at;
|
|
245
|
+
* - `start-dev-server.sh` only when a dev-server command resolved: a project without one has no
|
|
246
|
+
* server to start, and writing the wrapper anyway would put a path in the permission profile that
|
|
247
|
+
* nothing runs;
|
|
248
|
+
* - `deploy.sh` only when the config carries `deploy.command`. `deploy` is an **adopter-added**
|
|
249
|
+
* section that `init` does not generate this release, so a fresh `init` selects no deploy wrapper;
|
|
250
|
+
* - **nothing at all for a key still holding the placeholder**, which is reported separately so the
|
|
251
|
+
* caller that writes files can warn about it while the caller that only lists them stays quiet;
|
|
252
|
+
* - **nothing at all for `commands.typecheck` answered `none`**, which is reported differently
|
|
253
|
+
* because the fix is not to fill it in — and only that key, because the same string on any other
|
|
254
|
+
* key is a command line this rule still writes a wrapper for.
|
|
255
|
+
*
|
|
256
|
+
* It is exported for exactly that second caller, which has to reach the same answer from a config
|
|
257
|
+
* alone and no write plan. A selection rule with two producers is precisely how a profile comes to
|
|
258
|
+
* allow-list a wrapper that was never written — an entry naming a file that does not exist — so the
|
|
259
|
+
* two answers are the same code rather than two statements of it.
|
|
260
|
+
*/
|
|
261
|
+
export function selectWrapper(config, key) {
|
|
262
|
+
const configured = configuredCommand(config, key);
|
|
263
|
+
if (configured === undefined) {
|
|
264
|
+
return { selected: ALWAYS_SELECTED.includes(key), placeholder: false, answeredNone: false };
|
|
265
|
+
}
|
|
266
|
+
if (isPlaceholder(configured))
|
|
267
|
+
return { selected: false, placeholder: true, answeredNone: false, configured };
|
|
268
|
+
if (answersNone(key, configured))
|
|
269
|
+
return { selected: false, placeholder: false, answeredNone: true, configured };
|
|
270
|
+
return { selected: true, placeholder: false, answeredNone: false, configured };
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* What an adopter has to do for this wrapper to run something, as a message should say it.
|
|
274
|
+
*
|
|
275
|
+
* `deploy` gets its own sentence because the config key is never the missing piece there: the
|
|
276
|
+
* wrapper is selected only when `deploy.command` is already set, and no deploy command is ever
|
|
277
|
+
* detected, so naming `deploy.command` would name a key the adopter has set. The raw line goes in
|
|
278
|
+
* the wrapper.
|
|
279
|
+
*/
|
|
280
|
+
function unresolvedFix(key, file) {
|
|
281
|
+
return key === 'deploy'
|
|
282
|
+
? `put the deploy command line in ${file}`
|
|
283
|
+
: `set ${configKeyPath(key)} in harness.config.json`;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* The body written when no command line resolved: it says what to fix and fails, rather than
|
|
287
|
+
* appearing to have run a check it never ran.
|
|
288
|
+
*
|
|
289
|
+
* This is the one body that exits before the template's verdict line, and deliberately: there is
|
|
290
|
+
* no verdict to report because nothing was verified, and the message is the diagnostic. Letting
|
|
291
|
+
* it fall through to `FAIL: <name>` would report a failed check where no check exists.
|
|
292
|
+
*
|
|
293
|
+
* It calls the template's own `harness_fail` rather than spelling out `echo … && exit 1`, and has
|
|
294
|
+
* to: every template appends `"$@"` to this line, so a compound would hand the caller's arguments
|
|
295
|
+
* to its last command — and `exit` given extra arguments refuses them, prints nothing but a shell
|
|
296
|
+
* error and does not exit, which is the fall-through this body exists to avoid. A single command
|
|
297
|
+
* that ignores the extra arguments is the shape that holds, and it is the shape the template's own
|
|
298
|
+
* comment above that line asks for.
|
|
299
|
+
*/
|
|
300
|
+
function unresolvedBody(key, file) {
|
|
301
|
+
return `harness_fail "harness: ${unresolvedFix(key, file)}"`;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Render a wrapper template through {@link renderTemplate}.
|
|
305
|
+
*
|
|
306
|
+
* **`assertNoneSurvive` is deliberately left off here**, and this is the one generator for which
|
|
307
|
+
* that is true: the value substituted in is the adopter's own command line, which is allowed to
|
|
308
|
+
* contain `{{…}}`. The pre-substitution check that the shared renderer always performs is what
|
|
309
|
+
* covers the real fault — the template and this module disagreeing about the token set — without
|
|
310
|
+
* refusing a legal command.
|
|
311
|
+
*/
|
|
312
|
+
function render(file, values) {
|
|
313
|
+
return renderTemplate(readTemplate(`${TEMPLATE_DIR}/${file}`), values, {
|
|
314
|
+
describe: `the wrapper template ${file}`,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/** The pre-start line for a wrapper with nothing to run before its command: a shell no-op. */
|
|
318
|
+
const NO_PRESTART = ':';
|
|
319
|
+
/**
|
|
320
|
+
* The `{{prestart}}` value for one wrapper — the line `start-dev-server.sh` runs to completion before
|
|
321
|
+
* it launches, or {@link NO_PRESTART} for every other wrapper and every dev server with nothing to
|
|
322
|
+
* build first.
|
|
323
|
+
*
|
|
324
|
+
* **It answers only for {@link resolveBody}'s second arm**, which the `body === rawCommands.devServer`
|
|
325
|
+
* test is: an adopter whose `commands.devServer` holds a raw line chose that line, and prefixing a
|
|
326
|
+
* build to somebody's own command is not this generator's call. Whether a *detected* line needs one is
|
|
327
|
+
* `detect/presets.ts`'s fact, asked through {@link devServerPrestart} rather than re-derived here.
|
|
328
|
+
*
|
|
329
|
+
* The line carries no `"$@"`, so {@link wrapperCommandLine} still reads the launch line back out as
|
|
330
|
+
* the wrapper's command — the property `doctor`'s `daemon-path` check depends on.
|
|
331
|
+
*/
|
|
332
|
+
function prestartLine(file, body, rawCommands) {
|
|
333
|
+
if (file !== 'start-dev-server.sh' || body !== rawCommands.devServer)
|
|
334
|
+
return NO_PRESTART;
|
|
335
|
+
return devServerPrestart(rawCommands) ?? NO_PRESTART;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Resolve the command line a wrapper runs, in the one precedence that cannot produce a circular
|
|
339
|
+
* pair:
|
|
340
|
+
*
|
|
341
|
+
* 1. **A raw line already in the config wins** — an adopter who edited `commands.typecheck` to a
|
|
342
|
+
* real command line meant it, and that is the reading under which
|
|
343
|
+
* `templates/scripts/README.md`'s "command lines come from `commands.*`" holds. It wins and is
|
|
344
|
+
* **reported**: the profile allow-lists the wrapper this line was just inlined into, not the line
|
|
345
|
+
* itself, so the arm carries {@link wrappedKeyMismatchMessage} as its warning.
|
|
346
|
+
* 2. **Otherwise, when the configured value *is* this wrapper's own invocation** — the normal
|
|
347
|
+
* state after a first `init` — the body is stack detection's raw line. Inlining the
|
|
348
|
+
* self-reference instead would produce a script that runs itself forever, so
|
|
349
|
+
* {@link writeWrapperScripts} asserts against it explicitly rather than trusting this branch.
|
|
350
|
+
* 3. **When neither resolves**, the body says what to fix and exits non-zero — for `deploy`, which
|
|
351
|
+
* step 2 can never supply, that is the wrapper rather than the config key.
|
|
352
|
+
*/
|
|
353
|
+
function resolveBody({ key, file }, configured, invocation, rawCommands) {
|
|
354
|
+
const detected = key === 'deploy' ? undefined : rawCommands[key];
|
|
355
|
+
// Precedence 1, and the moment Finding 65 describes: the raw line is inlined into the wrapper the
|
|
356
|
+
// profile allow-lists, so this is the one place that holds both halves. The warning is built from
|
|
357
|
+
// the strings this arm already has rather than by re-calling `wrappedKeyMismatch` — the same
|
|
358
|
+
// condition and the same two strings, read once, so the report cannot disagree with the body that
|
|
359
|
+
// was written. `deploy` reaches it too, and correctly: `deploy.command` takes the same wrapper
|
|
360
|
+
// form on its own object (`docs/cli.md` §5).
|
|
361
|
+
if (configured !== undefined && configured !== invocation) {
|
|
362
|
+
return { body: configured, warning: wrappedKeyMismatchMessage(key, { configured, invocation, file }) };
|
|
363
|
+
}
|
|
364
|
+
if (detected !== undefined && detected.trim() !== '' && !isPlaceholder(detected))
|
|
365
|
+
return { body: detected.trim() };
|
|
366
|
+
const fix = unresolvedFix(key, file);
|
|
367
|
+
return {
|
|
368
|
+
body: unresolvedBody(key, file),
|
|
369
|
+
unresolved: true,
|
|
370
|
+
warning: key === 'deploy'
|
|
371
|
+
? `deploy.command holds ${file}'s own invocation and no deploy command is ever detected, so ${file} was written as a wrapper that fails with a message: ${fix}`
|
|
372
|
+
: `${configKeyPath(key)} could not be resolved to a command line, so ${key} was written as a wrapper that fails with a message: ${fix} and put the command line in the wrapper`,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/** Shell operators that end a command, so the token after one is a command name again. */
|
|
376
|
+
const COMMAND_SEPARATORS = new Set([';', '&&', '||', '|', '&', '(', '{', '!']);
|
|
377
|
+
/** Interpreters that run their first non-flag argument as a script file. */
|
|
378
|
+
const SCRIPT_INTERPRETERS = new Set(['bash', 'sh', 'zsh', 'dash', 'ksh']);
|
|
379
|
+
/** One shell word as a path: quotes and a `./` prefix name the same file, a trailing `;` does not. */
|
|
380
|
+
function pathToken(token) {
|
|
381
|
+
const bare = token.replace(/^['"]/, '').replace(/['"]$/, '').replace(/;+$/, '');
|
|
382
|
+
return bare.startsWith('./') ? bare.slice(2) : bare;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* True when `body` **runs** the repo-relative `path` — as a bare command (`./deploy.sh`,
|
|
386
|
+
* `scripts/deploy.sh`) or as an interpreter's script argument (`bash scripts/deploy.sh`, `sh -x
|
|
387
|
+
* scripts/deploy.sh`) — and false when it merely mentions it, as an argument to something else.
|
|
388
|
+
*
|
|
389
|
+
* A substring test cannot tell the two apart, and gets the difference wrong in a supported
|
|
390
|
+
* configuration: with `scriptsDir: "."` the wrapper's path is its bare file name, so
|
|
391
|
+
* `bash scripts/typecheck.sh` — a *different* file — contains it. Only a token in command position
|
|
392
|
+
* counts.
|
|
393
|
+
*/
|
|
394
|
+
function invokesScript(body, path) {
|
|
395
|
+
let position = 'command';
|
|
396
|
+
for (const raw of body.split(/\s+/)) {
|
|
397
|
+
if (raw === '')
|
|
398
|
+
continue;
|
|
399
|
+
const token = pathToken(raw);
|
|
400
|
+
if (COMMAND_SEPARATORS.has(token)) {
|
|
401
|
+
position = 'command';
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (position === 'argument')
|
|
405
|
+
continue;
|
|
406
|
+
if (position === 'interpreterArg' && token.startsWith('-'))
|
|
407
|
+
continue;
|
|
408
|
+
if (token === path)
|
|
409
|
+
return true;
|
|
410
|
+
position = position === 'command' && SCRIPT_INTERPRETERS.has(token) ? 'interpreterArg' : 'argument';
|
|
411
|
+
}
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Enqueue the wrapper scripts for the configured `scriptsDir`: `ensure-dir` on the directory,
|
|
416
|
+
* then one `create-if-absent` write per wrapper {@link selectWrapper} selects — which is the rule,
|
|
417
|
+
* stated once there and applied identically by the permission profile's own fallback.
|
|
418
|
+
*
|
|
419
|
+
* Two unselected cases are the ones this caller handles differently, each warning rather than staying
|
|
420
|
+
* silent, and each in its own words:
|
|
421
|
+
*
|
|
422
|
+
* - **the placeholder** — the key stays visible in `commands.*`, where the config check and `doctor`
|
|
423
|
+
* both report it, and the command deliberately fails rather than running something that was never
|
|
424
|
+
* verified, so the warning says to fill the key in;
|
|
425
|
+
* - **`commands.typecheck` answered `none`** — an answer rather than an omission, so its warning
|
|
426
|
+
* states what was decided and what follows from it, and must not prescribe filling the key in,
|
|
427
|
+
* which would be an instruction to undo the answer. That branch is unreachable for any other key:
|
|
428
|
+
* {@link selectWrapper} never sets `answeredNone` for one.
|
|
429
|
+
*
|
|
430
|
+
* `create-if-absent` is the re-run contract for this artifact: the raw command line in each wrapper
|
|
431
|
+
* is the adopter's to correct, so a second `init` leaves an edited wrapper byte-identical — which
|
|
432
|
+
* also means a wrapper-body improvement shipped by this package reaches an already-wired repository
|
|
433
|
+
* only on an explicit `--force`. `--force` overwrites only after the write engine has taken a
|
|
434
|
+
* `.bak`.
|
|
435
|
+
*/
|
|
436
|
+
export function writeWrapperScripts({ repoRoot, config, rawCommands, plan, }) {
|
|
437
|
+
const scriptsDir = resolvedScriptsDir(config);
|
|
438
|
+
const warnings = [];
|
|
439
|
+
const written = [];
|
|
440
|
+
plan.add({ path: join(repoRoot, scriptsDir), policy: 'ensure-dir', label: 'wrapper scripts directory' });
|
|
441
|
+
for (const script of WRAPPER_SCRIPTS) {
|
|
442
|
+
const { key, file } = script;
|
|
443
|
+
const { selected, placeholder, answeredNone, configured } = selectWrapper(config, key);
|
|
444
|
+
if (placeholder) {
|
|
445
|
+
warnings.push(`${configKeyPath(key)} still holds the placeholder init wrote, so no ${file} was written and none is allow-listed: set it in harness.config.json and re-run init`);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (answeredNone) {
|
|
449
|
+
// What this run did, not what is on disk. An adopter who answers an already-filled key keeps
|
|
450
|
+
// the `${file}` an earlier run wrote — wrappers are `create-if-absent` and nothing here
|
|
451
|
+
// deletes — and its allow entries until a profile is regenerated, so a warning phrased as a
|
|
452
|
+
// state would assert the opposite of the record's stated residue.
|
|
453
|
+
warnings.push(`${configKeyPath(key)} states this repository has no such command, so this run wrote no ${file} and allow-listed none. A ${file} an earlier run wrote is left where it is, and the permission entries naming it are removed only by init --force, which regenerates the profile`);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (!selected)
|
|
457
|
+
continue;
|
|
458
|
+
const invocation = wrapperInvocation(scriptsDir, file);
|
|
459
|
+
const { body, warning, unresolved } = resolveBody(script, configured, invocation, rawCommands);
|
|
460
|
+
// The assertion the precedence above exists to make impossible, stated anyway: a wrapper
|
|
461
|
+
// whose body runs the wrapper is an infinite recursion, and the failure mode of not catching
|
|
462
|
+
// it here is a run that hangs rather than one that reports anything. It is asked only of a
|
|
463
|
+
// body that runs something: the unresolved body runs nothing and quotes its own file name.
|
|
464
|
+
if (!unresolved && invokesScript(body, posixJoin(scriptsDir, file))) {
|
|
465
|
+
throw new HarnessError(`${configKeyPath(key)} resolved to a command line that runs ${file} itself (${body}), which would recurse forever: set it to the raw command line the wrapper should run`);
|
|
466
|
+
}
|
|
467
|
+
if (warning !== undefined)
|
|
468
|
+
warnings.push(warning);
|
|
469
|
+
plan.add({
|
|
470
|
+
path: join(repoRoot, scriptsDir, file),
|
|
471
|
+
policy: 'create-if-absent',
|
|
472
|
+
content: render(file, { command: body, name: key, prestart: prestartLine(file, body, rawCommands) }),
|
|
473
|
+
label: `wrapper script ${file}`,
|
|
474
|
+
mode: SCRIPT_MODE,
|
|
475
|
+
});
|
|
476
|
+
written.push({ key, file, invocation, command: body, unresolved: unresolved === true });
|
|
477
|
+
}
|
|
478
|
+
return { scriptsDir, written, warnings };
|
|
479
|
+
}
|
|
480
|
+
/** The argument forwarding every wrapper template appends to the command line it was given. */
|
|
481
|
+
const FORWARDED_ARGUMENTS = '"$@"';
|
|
482
|
+
/** The first token of {@link unresolvedBody}, and the whole of what identifies that body. */
|
|
483
|
+
const UNRESOLVED_HEAD = 'harness_fail';
|
|
484
|
+
/**
|
|
485
|
+
* The raw command line a wrapper runs, read back out of the file this module wrote.
|
|
486
|
+
*
|
|
487
|
+
* The rule keys on the templates' argument-forwarding convention — stated in `typecheck.sh`,
|
|
488
|
+
* `test.sh` and `deploy.sh`'s own comment (*"They attach to the *last* command of the line below, so
|
|
489
|
+
* keep that line one command rather than a compound"*) and followed by `start-dev-server.sh` — as the
|
|
490
|
+
* **last** line with no leading whitespace that does not start with `#` and carries
|
|
491
|
+
* {@link FORWARDED_ARGUMENTS}, truncated there and trimmed. Each exclusion buys one thing:
|
|
492
|
+
*
|
|
493
|
+
* - `#` drops the comment lines that quote `"$@"` while explaining the convention: every `"$@"` in the
|
|
494
|
+
* four wrapper templates other than the forwarding line is inside a comment;
|
|
495
|
+
* - **last** rather than first keeps a preamble an adopter added above the command line from being
|
|
496
|
+
* read as the command;
|
|
497
|
+
* - the indentation test is **defensive rather than template-driven**: no wrapper template renders an
|
|
498
|
+
* indented forwarding line, so it buys nothing today and exists so that an adopter-edited wrapper
|
|
499
|
+
* that forwards from inside a conditional or a function is reported `unrecognised` rather than
|
|
500
|
+
* mis-read.
|
|
501
|
+
*
|
|
502
|
+
* {@link unresolvedBody} is substituted into `{{command}}` like any other body, so that wrapper's
|
|
503
|
+
* rendered line *is* a line this rule matches. It is classified by its first token rather than
|
|
504
|
+
* excluded, because the distinction the caller needs is that it runs no command.
|
|
505
|
+
*
|
|
506
|
+
* **It lives beside the render it inverts**, for the reason `daemon/units.ts` gives for keeping
|
|
507
|
+
* `unitEnvValue` beside `environmentStanza`: a change to the templates and a change to this parse are
|
|
508
|
+
* then one edit in one file, and the round trip — the property this parse claims and cannot show on
|
|
509
|
+
* its own — has both sides in one module.
|
|
510
|
+
*
|
|
511
|
+
* The wrapper is the **adopter's** file from `init` on ({@link writeWrapperScripts}: *"the raw command
|
|
512
|
+
* line in each wrapper is the adopter's to correct"*), which is why an unreadable body is a reported
|
|
513
|
+
* state rather than a fallback. Its one caller is `doctor`'s `daemon-path` check, which must treat
|
|
514
|
+
* neither `unresolved` nor `unrecognised` as a binary the run needs.
|
|
515
|
+
*/
|
|
516
|
+
export function wrapperCommandLine(text) {
|
|
517
|
+
// Reversed rather than indexed backwards, because `for…of` over the copy yields a `string` where
|
|
518
|
+
// an index into it yields `string | undefined` under `noUncheckedIndexedAccess`.
|
|
519
|
+
for (const line of text.split('\n').reverse()) {
|
|
520
|
+
if (/^\s/.test(line) || line.startsWith('#'))
|
|
521
|
+
continue;
|
|
522
|
+
const forwarded = line.indexOf(FORWARDED_ARGUMENTS);
|
|
523
|
+
if (forwarded === -1)
|
|
524
|
+
continue;
|
|
525
|
+
const command = line.slice(0, forwarded).trim();
|
|
526
|
+
return command.split(/\s+/)[0] === UNRESOLVED_HEAD ? { kind: 'unresolved' } : { kind: 'command', command };
|
|
527
|
+
}
|
|
528
|
+
return { kind: 'unrecognised' };
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* The wrapper file `key`'s configured value invokes, or `undefined` when that value is not this
|
|
532
|
+
* wrapper's invocation — unset, empty, still the placeholder, or a raw command line alike.
|
|
533
|
+
*
|
|
534
|
+
* The path is **composed** from {@link WRAPPER_SCRIPTS} and {@link wrapperInvocation} and the
|
|
535
|
+
* configured value compared to it byte-for-byte, never parsed back into a path: that is
|
|
536
|
+
* {@link invokedPath}'s standing prohibition, and on a key holding a command line this predicate is
|
|
537
|
+
* the exact complement of {@link wrappedKeyMismatch} — one answers *"the value is the wrapper"*, the
|
|
538
|
+
* other *"the value is a raw line"*, off the same comparison, so the two cannot come to disagree about
|
|
539
|
+
* what the wrapper form is.
|
|
540
|
+
*
|
|
541
|
+
* `path` is repo-relative and forward-slashed like every path in the config; joining it to a
|
|
542
|
+
* repository root is the caller's. Its one caller is `doctor`'s `daemon-path` check, which reads the
|
|
543
|
+
* file it names through {@link wrapperCommandLine}.
|
|
544
|
+
*/
|
|
545
|
+
export function configuredWrapperFile(config, key) {
|
|
546
|
+
// Read from the table rather than from a second `.sh` literal. A `WrapperKey` is that table's own
|
|
547
|
+
// union, so the lookup cannot miss; `undefined` there is the same answer this function gives for
|
|
548
|
+
// "the value is not this wrapper's invocation", which keeps a predicate from carrying an error path.
|
|
549
|
+
const script = WRAPPER_SCRIPTS.find((entry) => entry.key === key);
|
|
550
|
+
if (script === undefined)
|
|
551
|
+
return undefined;
|
|
552
|
+
const scriptsDir = resolvedScriptsDir(config);
|
|
553
|
+
return configuredCommand(config, key) === wrapperInvocation(scriptsDir, script.file)
|
|
554
|
+
? { file: script.file, path: wrapperPath(scriptsDir, script.file) }
|
|
555
|
+
: undefined;
|
|
556
|
+
}
|
|
557
|
+
//# sourceMappingURL=scripts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../src/generators/scripts.ts"],"names":[],"mappings":"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"}
|