@delegance/claude-autopilot 7.6.0 → 7.8.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 +101 -0
- package/README.md +19 -0
- package/bin/_launcher.js +310 -6
- package/dist/src/cli/dashboard/missing-package.d.ts +48 -0
- package/dist/src/cli/dashboard/missing-package.js +85 -0
- package/dist/src/cli/dashboard/upload.d.ts +1 -1
- package/dist/src/cli/dashboard/upload.js +20 -1
- package/dist/src/cli/help-text.d.ts +1 -1
- package/dist/src/cli/help-text.js +12 -2
- package/dist/src/cli/index.js +33 -2
- package/dist/src/cli/scaffold/rust.d.ts +38 -0
- package/dist/src/cli/scaffold/rust.js +281 -0
- package/dist/src/cli/scaffold/types.d.ts +7 -4
- package/dist/src/cli/scaffold.d.ts +1 -1
- package/dist/src/cli/scaffold.js +48 -17
- package/dist/src/cli/tsx-resolver.d.ts +42 -0
- package/dist/src/cli/tsx-resolver.js +376 -0
- package/package.json +4 -7
- package/scripts/autoregress.ts +0 -402
- package/scripts/snapshots/impact-selector.ts +0 -60
- package/scripts/snapshots/import-scanner.ts +0 -44
- package/scripts/snapshots/serializer.ts +0 -24
- package/tests/snapshots/baselines/.gitkeep +0 -0
- package/tests/snapshots/baselines/src-formatters-sarif.json +0 -210
- package/tests/snapshots/baselines/src-snapshots-impact-selector.json +0 -32
- package/tests/snapshots/baselines/src-snapshots-import-scanner.json +0 -21
- package/tests/snapshots/baselines/src-snapshots-serializer.json +0 -39
- package/tests/snapshots/import-map.json +0 -138
- package/tests/snapshots/index.json +0 -14
- package/tests/snapshots/src-formatters-sarif.snap.ts +0 -132
- package/tests/snapshots/src-snapshots-impact-selector.snap.ts +0 -95
- package/tests/snapshots/src-snapshots-import-scanner.snap.ts +0 -126
- package/tests/snapshots/src-snapshots-serializer.snap.ts +0 -64
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,107 @@
|
|
|
2
2
|
|
|
3
3
|
- v5.6 Phase 7 (docs reconciliation) — pending.
|
|
4
4
|
|
|
5
|
+
## 7.8.0 — 2026-05-11
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- `tsx` resolution now prefers project-local installation, then `tsx` on
|
|
9
|
+
`$PATH`, then the bundled copy. The bundled `tsx` is scheduled for removal
|
|
10
|
+
in v8.0.0 — a once-per-day deprecation warning surfaces when falling back
|
|
11
|
+
to the bundled copy. Silence with `CLAUDE_AUTOPILOT_NO_TSX_DEPRECATION=1`.
|
|
12
|
+
Override resolution source with `CLAUDE_AUTOPILOT_TSX=bundled|project|path`
|
|
13
|
+
or the new `--tsx-source` CLI flag.
|
|
14
|
+
- `@supabase/supabase-js` is now lazy-loaded only when invoking
|
|
15
|
+
`claude-autopilot dashboard upload`. Moved from `dependencies` to
|
|
16
|
+
`optionalDependencies` — default install footprint is unchanged in npm 10
|
|
17
|
+
(which installs optional deps), but `npm install --omit=optional` now
|
|
18
|
+
works for local-only usage. A new `omit-optional-smoke.yml` CI workflow
|
|
19
|
+
exercises this install path on every PR. **The `--omit=optional` guarantee
|
|
20
|
+
is verified for npm only**; pnpm/yarn behavior is best-effort and not
|
|
21
|
+
gated in CI for v7.8.0 (per spec amendment A8).
|
|
22
|
+
|
|
23
|
+
### Removed (from published tarball)
|
|
24
|
+
- `tests/snapshots/`, `scripts/snapshots/`, `scripts/autoregress.ts` —
|
|
25
|
+
dev-only. The `autoregress` CLI verb continues to work from the repo
|
|
26
|
+
itself, but the snapshot fixtures are no longer shipped to npm consumers.
|
|
27
|
+
|
|
28
|
+
Published tarball file count drops by ~15 files (the snapshot fixtures and
|
|
29
|
+
the autoregress harness); runtime behavior is unchanged for default local-only
|
|
30
|
+
usage except for the bundled `tsx` deprecation warning.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- `--tsx-source <bundled|project|path>` CLI flag and `CLAUDE_AUTOPILOT_TSX`
|
|
34
|
+
env var for explicit resolution overrides (escape-hatch for users whose
|
|
35
|
+
project-local tsx is broken / incompatible).
|
|
36
|
+
- `CLAUDE_AUTOPILOT_NO_TSX_DEPRECATION=1` env opt-out for the deprecation
|
|
37
|
+
warning (CI / log-hygiene).
|
|
38
|
+
- `XDG_STATE_HOME` + `CLAUDE_AUTOPILOT_STATE_DIR` support for the warning
|
|
39
|
+
dedup state file (defaults to `~/.claude-autopilot/`).
|
|
40
|
+
- `scripts/audit-supabase-imports.ts` — AST-based audit (TypeScript compiler
|
|
41
|
+
API) that fails CI if a static value-import of `@supabase/supabase-js`
|
|
42
|
+
appears outside `src/cli/dashboard/**`. Runs via `npm run audit:supabase`.
|
|
43
|
+
- `.github/workflows/omit-optional-smoke.yml` — install-and-probe smoke
|
|
44
|
+
test for the `npm install --omit=optional` install path.
|
|
45
|
+
- `src/cli/tsx-resolver.ts` + `src/cli/dashboard/missing-package.ts` (new
|
|
46
|
+
modules, fully unit-tested — see `tests/cli/tsx-resolver.test.ts`,
|
|
47
|
+
`tests/cli/dashboard/missing-package.test.ts`, and
|
|
48
|
+
`tests/cli/tsx-source-flag.test.ts`).
|
|
49
|
+
|
|
50
|
+
### Spec
|
|
51
|
+
- `docs/specs/v7.8.0-decouple-runtime-deps.md` — full spec with two folded
|
|
52
|
+
codex reviews (pass-1 portability + escape hatch; pass-2 amendments
|
|
53
|
+
A1-A8 covering ESM/CJS safety, CLI parser scope, PATH self-pointer,
|
|
54
|
+
AST audit, type-only imports, hand-rolled PATH lookup dropping the
|
|
55
|
+
`which` dep, XDG state dir, npm-only --omit=optional documentation).
|
|
56
|
+
|
|
57
|
+
## 7.7.0 (2026-05-11)
|
|
58
|
+
|
|
59
|
+
**v7.7.0 — Rust scaffold support.** Minor release. Promotes Rust from
|
|
60
|
+
"detected-but-unsupported" (exit 3 in v7.4–v7.6) to a first-class
|
|
61
|
+
scaffold target, matching the Node + Python + FastAPI + Go shape.
|
|
62
|
+
|
|
63
|
+
**New:** `claude-autopilot scaffold --from-spec <spec.md> --stack rust`
|
|
64
|
+
(or auto-detected when the spec's `## Files` section lists `Cargo.toml`,
|
|
65
|
+
`src/main.rs`, or `src/lib.rs`).
|
|
66
|
+
|
|
67
|
+
**Lib-vs-bin fork.** Rust adds a fork the Go scaffolder doesn't need:
|
|
68
|
+
|
|
69
|
+
- spec lists ONLY `src/lib.rs` (no main.rs) → **library crate**
|
|
70
|
+
(`src/lib.rs` with a public `hello()` + inline `#[cfg(test)] mod tests`,
|
|
71
|
+
Cargo.lock added to `.gitignore`)
|
|
72
|
+
- spec lists `src/main.rs` (with or without lib.rs) → **binary crate**
|
|
73
|
+
(`src/main.rs` with `println!` + `tests/integration_test.rs` smoke
|
|
74
|
+
test, Cargo.lock NOT in `.gitignore`)
|
|
75
|
+
- spec lists BOTH → **mixed mode** (both targets generated; Cargo.lock
|
|
76
|
+
excluded since the binary target wins per Cargo's documented convention)
|
|
77
|
+
- spec hints neither → defaults to binary (matches `cargo init` default)
|
|
78
|
+
|
|
79
|
+
**Cargo.lock heuristic.** Library-only crates omit `Cargo.lock` from the
|
|
80
|
+
commit per Cargo docs; binary + mixed crates commit it. The Rust
|
|
81
|
+
scaffolder's `.gitignore` augmentation reflects this — `target/` is
|
|
82
|
+
always added; `Cargo.lock` is added only when the spec resolves to
|
|
83
|
+
library-only.
|
|
84
|
+
|
|
85
|
+
**Crate name normalization.** Cargo identifiers are `[a-z0-9_]` only and
|
|
86
|
+
must not start with a digit. The scaffolder lowercases `basename(cwd)`,
|
|
87
|
+
replaces any non-allowed char with `_`, collapses underscore runs, and
|
|
88
|
+
prefixes `_` when the result starts with a digit. Examples: `my-pkg-2`
|
|
89
|
+
→ `my_pkg_2`, `2cool` → `_2cool`, `My App` → `my_app`, `foo.bar` →
|
|
90
|
+
`foo_bar`.
|
|
91
|
+
|
|
92
|
+
**Never overwrites.** `Cargo.toml`, `src/main.rs`, `src/lib.rs`, and
|
|
93
|
+
`tests/integration_test.rs` are preserved if they already exist — matches
|
|
94
|
+
the Go + Python scaffolder pattern.
|
|
95
|
+
|
|
96
|
+
**Polyglot detection.** `detectStack()` now includes Rust signals
|
|
97
|
+
(`Cargo.toml` / `src/main.rs` / `src/lib.rs`) in the polyglot count. So
|
|
98
|
+
e.g. `package.json` + `Cargo.toml` together correctly fail-loud with
|
|
99
|
+
`polyglot spec — pass --stack to disambiguate` instead of silently
|
|
100
|
+
picking one.
|
|
101
|
+
|
|
102
|
+
**`--list-stacks`** now shows Rust under Supported and drops it from
|
|
103
|
+
Recognized-but-unsupported. Ruby remains the lone detection-only stack
|
|
104
|
+
(would detect via `Gemfile`).
|
|
105
|
+
|
|
5
106
|
## 7.6.0 (2026-05-10)
|
|
6
107
|
|
|
7
108
|
**v7.6.0 — Go scaffold support.** Minor release. Promotes Go from
|
package/README.md
CHANGED
|
@@ -407,6 +407,25 @@ ANTHROPIC_API_KEY=sk-ant-... claude-autopilot scan --all
|
|
|
407
407
|
|
|
408
408
|
We do not claim 13/13 reflects every real-world repo — it's a reproducible upper bound on a fixture that exercises the categories we explicitly target.
|
|
409
409
|
|
|
410
|
+
## What's Next (v8.0.0)
|
|
411
|
+
|
|
412
|
+
v7.8.0 begins decoupling runtime deps so local-only users can install a
|
|
413
|
+
leaner package:
|
|
414
|
+
|
|
415
|
+
- **`tsx` is being removed from `dependencies` in v8.0.0.** Today it ships
|
|
416
|
+
bundled and the launcher prefers a project-local install if you have one,
|
|
417
|
+
falling back to the bundled copy with a once-per-day deprecation warning.
|
|
418
|
+
In v8.0.0 the bundled fallback goes away — install `tsx` locally
|
|
419
|
+
(`npm install -D tsx`) or set `CLAUDE_AUTOPILOT_TSX=path` to use a global
|
|
420
|
+
install.
|
|
421
|
+
- **The hosted-dashboard upload is moving to a separate optional package
|
|
422
|
+
(`@delegance/claude-autopilot-cloud`)** so you can skip Supabase entirely
|
|
423
|
+
with `npm install --omit=optional` today, and skip the dep entirely with
|
|
424
|
+
v8.0.0. The smoke workflow `.github/workflows/omit-optional-smoke.yml`
|
|
425
|
+
verifies the install-with-omit path on every PR.
|
|
426
|
+
|
|
427
|
+
Track the v8.0.0 plan and request comments at https://github.com/axledbetter/claude-autopilot/issues
|
|
428
|
+
|
|
410
429
|
## Contributing
|
|
411
430
|
|
|
412
431
|
Issues and PRs welcome — https://github.com/axledbetter/claude-autopilot/issues. The pipeline literally builds itself; many features in this repo were implemented by autopilot running against autopilot ([DEMO.md](DEMO.md) walks through six self-eat PRs with cost trajectory $10 → ~$2.50). Read [CONTRIBUTING.md](CONTRIBUTING.md) if it exists, otherwise: clone, `npm install`, `npm test`, open a PR.
|
package/bin/_launcher.js
CHANGED
|
@@ -23,12 +23,308 @@ function resolveEntry() {
|
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// v7.8.0 — three-tier tsx resolution (project-local → PATH → bundled) with
|
|
27
|
+
// a once-per-day deprecation warning on the bundled fallthrough. This is a
|
|
28
|
+
// JS port of src/cli/tsx-resolver.ts (which remains the testable source of
|
|
29
|
+
// truth used by autoregress, --tsx-source flag handling, and other call
|
|
30
|
+
// sites). The launcher can't import the TS resolver directly because it
|
|
31
|
+
// runs BEFORE tsx is available, so the two implementations are kept in
|
|
32
|
+
// sync by hand. See docs/specs/v7.8.0-decouple-runtime-deps.md.
|
|
33
|
+
//
|
|
34
|
+
// Escape hatches:
|
|
35
|
+
// --tsx-source=<bundled|project|path> (parsed from argv, then stripped)
|
|
36
|
+
// CLAUDE_AUTOPILOT_TSX=<bundled|project|path>
|
|
37
|
+
// CLAUDE_AUTOPILOT_NO_TSX_DEPRECATION=1 (silences bundled-fallthrough warning)
|
|
38
|
+
const TSX_VALID_SOURCES = ['bundled', 'project', 'path'];
|
|
39
|
+
|
|
40
|
+
function readTsxFlagOverride(argv) {
|
|
41
|
+
// Find --tsx-source=foo or --tsx-source foo without disturbing other argv
|
|
42
|
+
// (we surface "invalid value" diagnostics from the CLI's own arg parser
|
|
43
|
+
// when it sees the still-present flag — only strip and use it here).
|
|
44
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
45
|
+
const a = argv[i];
|
|
46
|
+
if (!a) continue;
|
|
47
|
+
if (a.startsWith('--tsx-source=')) {
|
|
48
|
+
const v = a.slice('--tsx-source='.length);
|
|
49
|
+
return TSX_VALID_SOURCES.includes(v) ? v : null;
|
|
50
|
+
}
|
|
51
|
+
if (a === '--tsx-source' && i + 1 < argv.length) {
|
|
52
|
+
const v = argv[i + 1];
|
|
53
|
+
return TSX_VALID_SOURCES.includes(v) ? v : null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function bundledTsxPath() {
|
|
60
|
+
// Our own bundled tsx — relative to the package root (two levels up from bin/).
|
|
61
|
+
const p = path.resolve(__dirname, '..', 'node_modules', '.bin', 'tsx');
|
|
62
|
+
return fs.existsSync(p) ? p : null;
|
|
63
|
+
}
|
|
64
|
+
function projectLocalTsxPath() {
|
|
65
|
+
// Consumer project — when installed as a dep, npm hoists peer bins to
|
|
66
|
+
// <consumer>/node_modules/.bin/tsx. ONLY classify as project-local if
|
|
67
|
+
// the consumer EXPLICITLY declared tsx in their package.json. Without
|
|
68
|
+
// this gate, npm's hoisting of our own bundled tsx would be mislabeled
|
|
69
|
+
// as project-local, suppressing the deprecation warning that drives
|
|
70
|
+
// the v8.0.0 migration.
|
|
71
|
+
//
|
|
72
|
+
// Layout when installed as a dep:
|
|
73
|
+
// <consumer>/package.json
|
|
74
|
+
// <consumer>/node_modules/@delegance/claude-autopilot/bin/_launcher.js <-- __dirname/..
|
|
75
|
+
// <consumer>/node_modules/.bin/tsx
|
|
76
|
+
// So the consumer package root is four dirs up from bin/.
|
|
77
|
+
const consumerPkgRoot = path.resolve(__dirname, '..', '..', '..', '..');
|
|
78
|
+
if (!consumerDeclaresTsx(consumerPkgRoot)) return null;
|
|
79
|
+
const p = path.resolve(__dirname, '..', '..', '..', '.bin', 'tsx');
|
|
80
|
+
return fs.existsSync(p) ? p : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* True iff the consumer's `package.json` declares `tsx` in dependencies,
|
|
85
|
+
* devDependencies, or peerDependencies. Mirrors the TS resolver's
|
|
86
|
+
* `consumerDeclaresTsx` — see src/cli/tsx-resolver.ts. Missing or
|
|
87
|
+
* malformed package.json → false (conservative: better to fall through
|
|
88
|
+
* to PATH/bundled than to mislabel hoisted deps as project-local).
|
|
89
|
+
*/
|
|
90
|
+
function consumerDeclaresTsx(consumerPkgRoot) {
|
|
91
|
+
try {
|
|
92
|
+
const pkgPath = path.join(consumerPkgRoot, 'package.json');
|
|
93
|
+
const raw = fs.readFileSync(pkgPath, 'utf8');
|
|
94
|
+
const pkg = JSON.parse(raw);
|
|
95
|
+
const dd = (pkg && pkg.dependencies) || {};
|
|
96
|
+
const ddv = (pkg && pkg.devDependencies) || {};
|
|
97
|
+
const pd = (pkg && pkg.peerDependencies) || {};
|
|
98
|
+
return 'tsx' in dd || 'tsx' in ddv || 'tsx' in pd;
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function pathTsxPath() {
|
|
104
|
+
const PATH = process.env.PATH || process.env.Path || '';
|
|
105
|
+
if (!PATH) return null;
|
|
106
|
+
const isWin = process.platform === 'win32';
|
|
107
|
+
const sep = isWin ? ';' : ':';
|
|
108
|
+
const exts = isWin ? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';') : [''];
|
|
109
|
+
const bundled = bundledTsxPath();
|
|
110
|
+
for (const dir of PATH.split(sep)) {
|
|
111
|
+
if (!dir) continue;
|
|
112
|
+
// Windows NTFS is case-insensitive — `tsx.cmd`, `TSX.CMD`, `Tsx.Cmd` all
|
|
113
|
+
// resolve to the same file. Use a case-insensitive readdir match instead
|
|
114
|
+
// of existsSync(tsx + ext), which mismatches when on-disk filename casing
|
|
115
|
+
// differs from PATHEXT casing.
|
|
116
|
+
const cand = isWin
|
|
117
|
+
? findTsxCaseInsensitive(dir, exts)
|
|
118
|
+
: (() => {
|
|
119
|
+
const c = path.join(dir, 'tsx');
|
|
120
|
+
return fs.existsSync(c) ? c : null;
|
|
121
|
+
})();
|
|
122
|
+
if (!cand) continue;
|
|
123
|
+
// A3 self-pointer: if PATH-resolved bin's package root is OUR own
|
|
124
|
+
// bundled tsx package root, skip this candidate and keep searching.
|
|
125
|
+
// npm prepends `node_modules/.bin` to PATH for `npm run`, so the
|
|
126
|
+
// bundled-pointing entry is typically hit first. `return null` here
|
|
127
|
+
// would abort the whole PATH search and hide a user's globally-
|
|
128
|
+
// installed tsx later in PATH. `continue` instead — if no other PATH
|
|
129
|
+
// entry hits, the loop falls off the end and the caller falls
|
|
130
|
+
// through to bundled with the deprecation warning.
|
|
131
|
+
if (isInBundledPackage(cand, bundled)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// On Windows, `.cmd`/`.bat` shims can't be executed directly by
|
|
135
|
+
// spawn() — callers must pass `shell: true`. Mark the resolution
|
|
136
|
+
// with the metadata they need.
|
|
137
|
+
if (isWin && /\.(cmd|bat)$/i.test(cand)) {
|
|
138
|
+
return { path: cand, shell: true };
|
|
139
|
+
}
|
|
140
|
+
return cand;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Case-insensitive lookup for `tsx` + each PATHEXT entry inside `dir`.
|
|
147
|
+
* Mirrors src/cli/tsx-resolver.ts findTsxCaseInsensitive(). Returns the
|
|
148
|
+
* actual on-disk filename joined onto `dir`, or null if no match.
|
|
149
|
+
*/
|
|
150
|
+
function findTsxCaseInsensitive(dir, exts) {
|
|
151
|
+
let entries;
|
|
152
|
+
try {
|
|
153
|
+
entries = fs.readdirSync(dir);
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const wanted = new Set(exts.map((ext) => `tsx${ext}`.toLowerCase()));
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
if (wanted.has(entry.toLowerCase())) {
|
|
160
|
+
return path.join(dir, entry);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* True iff `candidatePath` resolves into our bundled tsx package directory
|
|
168
|
+
* (after realpath). `bundled` is the path to our own `node_modules/.bin/tsx`
|
|
169
|
+
* (or null if it can't be located). We compare PACKAGE ROOTS — `node_modules/
|
|
170
|
+
* tsx/` — not bin dirs, since `.bin/tsx` and `tsx/dist/cli.mjs` live in
|
|
171
|
+
* different directories.
|
|
172
|
+
*/
|
|
173
|
+
function isInBundledPackage(candidatePath, bundled) {
|
|
174
|
+
if (!bundled) return false;
|
|
175
|
+
try {
|
|
176
|
+
const realCandidate = fs.realpathSync(candidatePath);
|
|
177
|
+
const realBundled = fs.realpathSync(bundled);
|
|
178
|
+
// realBundled now points at the real tsx entry (e.g. .../node_modules/
|
|
179
|
+
// tsx/dist/cli.mjs on Unix, or remains the .cmd shim on Windows). Walk
|
|
180
|
+
// up to the tsx package root.
|
|
181
|
+
const bundledPkgRoot = packageRootContaining(realBundled);
|
|
182
|
+
if (!bundledPkgRoot) return false;
|
|
183
|
+
return realCandidate.startsWith(bundledPkgRoot + path.sep) || realCandidate === bundledPkgRoot;
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Walk upward from a file path looking for `node_modules/tsx`. Returns the
|
|
191
|
+
* absolute path to the tsx package root, or null if not found within 5 levels.
|
|
192
|
+
*
|
|
193
|
+
* PARITY: keep in sync with src/cli/tsx-resolver.ts packageRootContaining().
|
|
194
|
+
* The TS version is parameterized over `pkgName`; this launcher copy is
|
|
195
|
+
* specialized to `tsx` since the launcher only resolves tsx.
|
|
196
|
+
*/
|
|
197
|
+
function packageRootContaining(filePath) {
|
|
198
|
+
let dir = path.dirname(filePath);
|
|
199
|
+
for (let i = 0; i < 6; i += 1) {
|
|
200
|
+
const base = path.basename(dir);
|
|
201
|
+
if (base === 'tsx' && path.basename(path.dirname(dir)) === 'node_modules') {
|
|
202
|
+
return dir;
|
|
203
|
+
}
|
|
204
|
+
const parent = path.dirname(dir);
|
|
205
|
+
if (parent === dir) return null;
|
|
206
|
+
dir = parent;
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function stateDir() {
|
|
212
|
+
if (process.env.CLAUDE_AUTOPILOT_STATE_DIR) return process.env.CLAUDE_AUTOPILOT_STATE_DIR;
|
|
213
|
+
if (process.platform !== 'win32' && process.env.XDG_STATE_HOME) {
|
|
214
|
+
return path.join(process.env.XDG_STATE_HOME, 'claude-autopilot');
|
|
215
|
+
}
|
|
216
|
+
return path.join(os.homedir(), '.claude-autopilot');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const TSX_DEPRECATION_MESSAGE =
|
|
220
|
+
'\n' +
|
|
221
|
+
'[deprecation] @delegance/claude-autopilot is using its bundled `tsx` to run\n' +
|
|
222
|
+
' your TypeScript scripts. In v8.0.0, `tsx` will be removed from\n' +
|
|
223
|
+
' runtime deps and you will need to install it yourself:\n' +
|
|
224
|
+
'\n' +
|
|
225
|
+
' npm install -D tsx\n' +
|
|
226
|
+
'\n' +
|
|
227
|
+
' To silence this warning now and prepare for v8.0.0:\n' +
|
|
228
|
+
' 1. Add `tsx` to your project devDependencies, OR\n' +
|
|
229
|
+
' 2. Set `CLAUDE_AUTOPILOT_NO_TSX_DEPRECATION=1` in your env.\n' +
|
|
230
|
+
'\n' +
|
|
231
|
+
' Override resolution: `CLAUDE_AUTOPILOT_TSX=bundled|project|path`\n' +
|
|
232
|
+
' See docs/specs/v7.8.0-decouple-runtime-deps.md for details.\n';
|
|
233
|
+
|
|
234
|
+
function emitTsxDeprecationWarningSafe() {
|
|
235
|
+
if (process.env.CLAUDE_AUTOPILOT_NO_TSX_DEPRECATION === '1') return;
|
|
236
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
237
|
+
const dedupPath = path.join(stateDir(), '.tsx-deprecation-shown');
|
|
238
|
+
try {
|
|
239
|
+
if (fs.existsSync(dedupPath)) {
|
|
240
|
+
const lastShown = fs.readFileSync(dedupPath, 'utf8').trim();
|
|
241
|
+
if (lastShown === today) return;
|
|
242
|
+
}
|
|
243
|
+
} catch { /* unreadable — fall through and print */ }
|
|
244
|
+
try {
|
|
245
|
+
fs.mkdirSync(path.dirname(dedupPath), { recursive: true });
|
|
246
|
+
fs.writeFileSync(dedupPath, today);
|
|
247
|
+
} catch { /* non-fatal */ }
|
|
248
|
+
process.stderr.write(TSX_DEPRECATION_MESSAGE);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Resolve a tsx executable using the v7.8.0 precedence ladder. Returns
|
|
253
|
+
* `{path, shell?}`. Honors --tsx-source / CLAUDE_AUTOPILOT_TSX overrides,
|
|
254
|
+
* with --tsx-source taking priority. On bundled fallthrough (no override),
|
|
255
|
+
* emits the once-per-day deprecation warning unless silenced.
|
|
256
|
+
*
|
|
257
|
+
* Forced overrides MUST fail fast: if the user explicitly asked for a
|
|
258
|
+
* source and it can't be resolved, exit 2 with an actionable stderr message.
|
|
259
|
+
* Silently falling back to a global `tsx` was a footgun — the user has no
|
|
260
|
+
* indication their override was ignored.
|
|
261
|
+
*/
|
|
26
262
|
function findTsx() {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
263
|
+
const flagOverride = readTsxFlagOverride(process.argv.slice(2));
|
|
264
|
+
const envOverride = process.env.CLAUDE_AUTOPILOT_TSX;
|
|
265
|
+
const override = flagOverride
|
|
266
|
+
|| (TSX_VALID_SOURCES.includes(envOverride) ? envOverride : null);
|
|
267
|
+
const forcedBy = flagOverride ? '--tsx-source' : (override ? 'CLAUDE_AUTOPILOT_TSX' : null);
|
|
268
|
+
|
|
269
|
+
if (override === 'project') {
|
|
270
|
+
const p = projectLocalTsxPath();
|
|
271
|
+
if (!p) {
|
|
272
|
+
process.stderr.write(
|
|
273
|
+
`[claude-autopilot] Error: ${forcedBy}=project requested but no project-local tsx found.\n` +
|
|
274
|
+
' Install tsx in your project: npm install -D tsx\n' +
|
|
275
|
+
' Or unset the override.\n',
|
|
276
|
+
);
|
|
277
|
+
process.exit(2);
|
|
278
|
+
}
|
|
279
|
+
return toResolution(p);
|
|
280
|
+
}
|
|
281
|
+
if (override === 'path') {
|
|
282
|
+
const p = pathTsxPath();
|
|
283
|
+
if (!p) {
|
|
284
|
+
process.stderr.write(
|
|
285
|
+
`[claude-autopilot] Error: ${forcedBy}=path requested but no tsx found on PATH.\n` +
|
|
286
|
+
' Install tsx globally (npm install -g tsx) or unset the override.\n',
|
|
287
|
+
);
|
|
288
|
+
process.exit(2);
|
|
289
|
+
}
|
|
290
|
+
return toResolution(p);
|
|
291
|
+
}
|
|
292
|
+
if (override === 'bundled') {
|
|
293
|
+
const p = bundledTsxPath();
|
|
294
|
+
if (!p) {
|
|
295
|
+
process.stderr.write(
|
|
296
|
+
`[claude-autopilot] Error: ${forcedBy}=bundled requested but the bundled tsx is missing.\n` +
|
|
297
|
+
' Reinstall @delegance/claude-autopilot or unset the override.\n',
|
|
298
|
+
);
|
|
299
|
+
process.exit(2);
|
|
300
|
+
}
|
|
301
|
+
return toResolution(p);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Default precedence: project → PATH → bundled (with deprecation warning).
|
|
305
|
+
const project = projectLocalTsxPath();
|
|
306
|
+
if (project) return toResolution(project);
|
|
307
|
+
const fromPath = pathTsxPath();
|
|
308
|
+
if (fromPath) return toResolution(fromPath);
|
|
309
|
+
emitTsxDeprecationWarningSafe();
|
|
310
|
+
const bundled = bundledTsxPath();
|
|
311
|
+
if (!bundled) {
|
|
312
|
+
process.stderr.write(
|
|
313
|
+
'[claude-autopilot] Error: bundled tsx is missing — reinstall @delegance/claude-autopilot.\n',
|
|
314
|
+
);
|
|
315
|
+
process.exit(2);
|
|
316
|
+
}
|
|
317
|
+
return toResolution(bundled);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Normalize the result of a tsx-path lookup into a `{path, shell?}`
|
|
322
|
+
* resolution. `pathTsxPath()` may return either a string or an object with
|
|
323
|
+
* `{path, shell}` for `.cmd`/`.bat` shims that require `shell: true`.
|
|
324
|
+
*/
|
|
325
|
+
function toResolution(value) {
|
|
326
|
+
if (typeof value === 'string') return { path: value };
|
|
327
|
+
return value;
|
|
32
328
|
}
|
|
33
329
|
|
|
34
330
|
// v7.1.7 — Per-calendar-day deprecation dedup, keyed in the user's home dir.
|
|
@@ -118,7 +414,15 @@ export function launch(opts) {
|
|
|
118
414
|
} else {
|
|
119
415
|
// Dev path — run source via tsx. Used from the repo itself, or by users who
|
|
120
416
|
// installed from git or linked a local copy.
|
|
121
|
-
|
|
417
|
+
//
|
|
418
|
+
// On Windows, PATH-resolved `.cmd`/`.bat` shims must be spawned with
|
|
419
|
+
// `shell: true` — Node's exec syscalls can't launch them directly.
|
|
420
|
+
// Bundled / project-local hits run as bare JS via node and don't need
|
|
421
|
+
// a shell. The resolver tells us which mode to use.
|
|
422
|
+
const tsx = findTsx();
|
|
423
|
+
const spawnOpts = { stdio: 'inherit' };
|
|
424
|
+
if (tsx.shell) spawnOpts.shell = true;
|
|
425
|
+
result = spawnSync(tsx.path, [entry.path, ...process.argv.slice(2)], spawnOpts);
|
|
122
426
|
}
|
|
123
427
|
|
|
124
428
|
if (result.error) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface MissingPackageOpts {
|
|
2
|
+
/**
|
|
3
|
+
* Whether to also match CommonJS-style `Cannot find module '<pkg>'`
|
|
4
|
+
* messages in addition to the ESM-style `Cannot find package '<pkg>'`.
|
|
5
|
+
*
|
|
6
|
+
* Default **true**. Node's error format depends on the loader path
|
|
7
|
+
* (ESM vs CJS) — `import()` of a missing package can surface either
|
|
8
|
+
* depending on the consumer's module type and how the dep is loaded
|
|
9
|
+
* by the runtime. Accepting both is the safer default for our
|
|
10
|
+
* install-hint surface: a false negative here means the user sees a
|
|
11
|
+
* raw `ERR_MODULE_NOT_FOUND` instead of the actionable hint.
|
|
12
|
+
*
|
|
13
|
+
* Set to `false` to require ESM form specifically (used by the
|
|
14
|
+
* standalone `extractMissingSpecifier` helper in some tests).
|
|
15
|
+
*/
|
|
16
|
+
acceptCjsForm?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns true iff `err` is a "module/package not found" error whose missing
|
|
20
|
+
* specifier matches `pkgName` exactly. Transitive misses (specifier !==
|
|
21
|
+
* pkgName) return false so we don't falsely advise the user to install
|
|
22
|
+
* `pkgName` when something `pkgName` itself depends on is broken.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isMissingOptionalPackageError(err: unknown, pkgName: string, opts?: MissingPackageOpts): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Extract the missing specifier from a Node module-not-found message.
|
|
27
|
+
*
|
|
28
|
+
* ESM: "Cannot find package '<pkg>' imported from <url>"
|
|
29
|
+
* CJS: "Cannot find module '<pkg>'"
|
|
30
|
+
*
|
|
31
|
+
* Returns `undefined` if no specifier can be extracted (the message format
|
|
32
|
+
* may shift across Node versions; caller treats this as "not our package").
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractMissingSpecifier(msg: string, acceptCjsForm?: boolean): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Standard install-hint error message for `@supabase/supabase-js`. Shared by
|
|
37
|
+
* dashboard upload and the omit-optional smoke test so the assertion string
|
|
38
|
+
* stays in one place.
|
|
39
|
+
*/
|
|
40
|
+
export declare const SUPABASE_INSTALL_HINT = "Dashboard upload requires @supabase/supabase-js. Install with: npm install @supabase/supabase-js";
|
|
41
|
+
/**
|
|
42
|
+
* Probe that lazy-loads `@supabase/supabase-js`. Returns the imported module
|
|
43
|
+
* on success. Throws an Error with `SUPABASE_INSTALL_HINT` if the package is
|
|
44
|
+
* not installed; re-throws everything else (transitive failures, syntax
|
|
45
|
+
* errors, etc.) so they aren't misclassified.
|
|
46
|
+
*/
|
|
47
|
+
export declare function loadSupabaseOrInstallHint(): Promise<typeof import('@supabase/supabase-js')>;
|
|
48
|
+
//# sourceMappingURL=missing-package.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// src/cli/dashboard/missing-package.ts
|
|
2
|
+
//
|
|
3
|
+
// Disambiguate "user needs to install <pkg>" from "transitive dep of <pkg> is
|
|
4
|
+
// missing." When `@supabase/supabase-js` is lazy-loaded with `await import()`
|
|
5
|
+
// and the package itself isn't installed, Node throws an ERR_MODULE_NOT_FOUND
|
|
6
|
+
// whose specifier is the package name. If a transitive dep is missing instead
|
|
7
|
+
// (e.g. `@supabase/postgrest-js`), the specifier will be the transitive name —
|
|
8
|
+
// we should NOT instruct the user to install supabase in that case.
|
|
9
|
+
//
|
|
10
|
+
// Used by dashboard upload (the only feature reachable from the CLI that
|
|
11
|
+
// requires `@supabase/supabase-js`). Moved to `optionalDependencies` in v7.8.0
|
|
12
|
+
// so local-only `npm install --omit=optional` users can skip it.
|
|
13
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
14
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
15
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
16
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return path;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Returns true iff `err` is a "module/package not found" error whose missing
|
|
23
|
+
* specifier matches `pkgName` exactly. Transitive misses (specifier !==
|
|
24
|
+
* pkgName) return false so we don't falsely advise the user to install
|
|
25
|
+
* `pkgName` when something `pkgName` itself depends on is broken.
|
|
26
|
+
*/
|
|
27
|
+
export function isMissingOptionalPackageError(err, pkgName, opts = {}) {
|
|
28
|
+
if (!(err instanceof Error))
|
|
29
|
+
return false;
|
|
30
|
+
const e = err;
|
|
31
|
+
if (e.code !== 'ERR_MODULE_NOT_FOUND' && e.code !== 'MODULE_NOT_FOUND') {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const specifier = extractMissingSpecifier(e.message, opts.acceptCjsForm ?? true);
|
|
35
|
+
return specifier === pkgName;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Extract the missing specifier from a Node module-not-found message.
|
|
39
|
+
*
|
|
40
|
+
* ESM: "Cannot find package '<pkg>' imported from <url>"
|
|
41
|
+
* CJS: "Cannot find module '<pkg>'"
|
|
42
|
+
*
|
|
43
|
+
* Returns `undefined` if no specifier can be extracted (the message format
|
|
44
|
+
* may shift across Node versions; caller treats this as "not our package").
|
|
45
|
+
*/
|
|
46
|
+
export function extractMissingSpecifier(msg, acceptCjsForm = true) {
|
|
47
|
+
const esm = msg.match(/Cannot find package '([^']+)'/);
|
|
48
|
+
if (esm)
|
|
49
|
+
return esm[1];
|
|
50
|
+
if (acceptCjsForm) {
|
|
51
|
+
const cjs = msg.match(/Cannot find module '([^']+)'/);
|
|
52
|
+
if (cjs)
|
|
53
|
+
return cjs[1];
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Standard install-hint error message for `@supabase/supabase-js`. Shared by
|
|
59
|
+
* dashboard upload and the omit-optional smoke test so the assertion string
|
|
60
|
+
* stays in one place.
|
|
61
|
+
*/
|
|
62
|
+
export const SUPABASE_INSTALL_HINT = 'Dashboard upload requires @supabase/supabase-js. Install with: npm install @supabase/supabase-js';
|
|
63
|
+
/**
|
|
64
|
+
* Probe that lazy-loads `@supabase/supabase-js`. Returns the imported module
|
|
65
|
+
* on success. Throws an Error with `SUPABASE_INSTALL_HINT` if the package is
|
|
66
|
+
* not installed; re-throws everything else (transitive failures, syntax
|
|
67
|
+
* errors, etc.) so they aren't misclassified.
|
|
68
|
+
*/
|
|
69
|
+
export async function loadSupabaseOrInstallHint() {
|
|
70
|
+
try {
|
|
71
|
+
// Suppress TS error when `@supabase/supabase-js` isn't installed in
|
|
72
|
+
// smoke-test environments — the failure surface is exactly what we're
|
|
73
|
+
// probing for, and the static type-only side is fine because callers
|
|
74
|
+
// who care about the typed shape import `type { ... }` separately.
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
76
|
+
return (await import(__rewriteRelativeImportExtension('@supabase/supabase-js')));
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (isMissingOptionalPackageError(err, '@supabase/supabase-js')) {
|
|
80
|
+
throw new Error(SUPABASE_INSTALL_HINT);
|
|
81
|
+
}
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=missing-package.js.map
|
|
@@ -2,11 +2,25 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Locates run dir at <homeDir>/runs/<runId>, calls uploadRun() directly,
|
|
4
4
|
// and prints the result. Intended for resuming interrupted auto-uploads.
|
|
5
|
+
//
|
|
6
|
+
// v7.8.0: probes `@supabase/supabase-js` availability before any upload work.
|
|
7
|
+
// Supabase is now an optionalDependency (so `npm install --omit=optional`
|
|
8
|
+
// works for local-only users); if a user invokes a dashboard verb without
|
|
9
|
+
// it installed, we surface an actionable install hint instead of a raw
|
|
10
|
+
// `ERR_MODULE_NOT_FOUND`. Wired through `loadSupabaseOrInstallHint` so the
|
|
11
|
+
// transitive-dep miss case is correctly distinguished (see
|
|
12
|
+
// missing-package.ts).
|
|
5
13
|
import * as path from 'node:path';
|
|
6
14
|
import { promises as fs } from 'node:fs';
|
|
7
15
|
import { readConfig, getConfigDir } from "../../dashboard/config.js";
|
|
8
|
-
import {
|
|
16
|
+
import { loadSupabaseOrInstallHint } from "./missing-package.js";
|
|
9
17
|
export async function runDashboardUpload(opts) {
|
|
18
|
+
// v7.8.0 — supabase is an optionalDependency. Probe availability before
|
|
19
|
+
// doing any upload work; if missing, surface the actionable install hint
|
|
20
|
+
// (Error message defined in missing-package.ts). Local-only users who
|
|
21
|
+
// installed with `npm install --omit=optional` will hit this on first
|
|
22
|
+
// attempted upload and know exactly how to fix it.
|
|
23
|
+
await loadSupabaseOrInstallHint();
|
|
10
24
|
const cfg = await readConfig();
|
|
11
25
|
if (!cfg) {
|
|
12
26
|
if (!opts.silent) {
|
|
@@ -31,6 +45,11 @@ export async function runDashboardUpload(opts) {
|
|
|
31
45
|
...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
|
|
32
46
|
...(opts.signal !== undefined ? { signal: opts.signal } : {}),
|
|
33
47
|
};
|
|
48
|
+
// Dynamic import — evaluated AFTER the supabase probe at the top of
|
|
49
|
+
// this function. Keeps the install-hint path correct even if any
|
|
50
|
+
// future change inside `uploader.ts` (or its transitive imports)
|
|
51
|
+
// adds a static value-import of `@supabase/supabase-js`.
|
|
52
|
+
const { uploadRun } = await import("../../dashboard/upload/uploader.js");
|
|
34
53
|
const res = await uploadRun(opts.runId, runDir, uploadOpts);
|
|
35
54
|
if (!opts.silent) {
|
|
36
55
|
if (res.ok && res.url) {
|
|
@@ -42,7 +42,7 @@ export declare const HELP_OPTIONS: Record<string, string>;
|
|
|
42
42
|
* support varies; v6.0.1 wires them into `scan` first, additional verbs land
|
|
43
43
|
* in subsequent v6.0.x point releases per docs/v6/wrapping-pipeline-phases.md).
|
|
44
44
|
*/
|
|
45
|
-
export declare const GLOBAL_FLAGS_BLOCK = "Global flags:\n --json Emit a structured JSON envelope on stdout (most verbs)\n --engine [v7.0 deprecated no-op] engine is always on; flag emits a warning\n v7.0 removed the engine-off opt-out flag and code path entirely.\n CLAUDE_AUTOPILOT_ENGINE=off is now ignored (warns once per process)\n instead of disabling the engine. See docs/v7/breaking-changes.md.";
|
|
45
|
+
export declare const GLOBAL_FLAGS_BLOCK = "Global flags:\n --json Emit a structured JSON envelope on stdout (most verbs)\n --engine [v7.0 deprecated no-op] engine is always on; flag emits a warning\n v7.0 removed the engine-off opt-out flag and code path entirely.\n CLAUDE_AUTOPILOT_ENGINE=off is now ignored (warns once per process)\n instead of disabling the engine. See docs/v7/breaking-changes.md.\n\nResolution overrides (v7.8.0):\n --tsx-source <bundled|project|path>\n Override how the dev launcher picks the tsx binary used\n to run .ts files. Default precedence is project-local\n \u2192 PATH \u2192 bundled (with deprecation warning on the\n bundled fallthrough). Env equivalent: CLAUDE_AUTOPILOT_TSX.\n Silence the bundled-fallthrough warning with\n CLAUDE_AUTOPILOT_NO_TSX_DEPRECATION=1. `tsx` will be\n removed from runtime deps in v8.0.0.";
|
|
46
46
|
/** Build the full two-level help text. Returned as a string so tests can assert against it without spawning. */
|
|
47
47
|
export declare function buildHelpText(): string;
|
|
48
48
|
/** Build help text for a single verb. Returns null if the verb is unknown. */
|