mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
f1331cf80d
## Problem Field report (pnpm setup): `npx @swmansion/argent init --local` - ✅ "updated argent 14→15", ✅ created `.mcp.json` + `skills-lock.json` - 🔴 did **not** add argent to `package.json` - 🔴 re-running the same command → `Local install failed.` + advice to run `pnpm add -D ...` manually - 🔴 init "persistently detects" Cursor and Codex on a fresh machine that never had them ## Root causes (each reproduced; confirmed against installer telemetry) 1. **Stale-binary hijack** — bare `npx @swmansion/argent` prefers an existing **global install** over the registry. A global 0.14 predates `--local` and `parseInitArgs` silently ignored unknown flags, so run 1 was a plain global-mode 0.14 init: it offered the 14→15 update, wrote mcp/skills files, and never touched package.json. That's symptoms 1–3 in one. 2. **pm detection missed pnpm's modern markers** — `pnpm init` (pnpm 10/11) writes only `devEngines.packageManager`, and no lockfile exists before the first install. Detection fell back to npm, whose own devEngines gate instantly rejects the install (`EBADDEVENGINES`, ~250 ms). This is the 221–537 ms `INSTALL_LOCAL_PACKAGE_FAILED` cluster in PostHog (8 events / 7 users last week). 3. **Missing pm binary** (cloned pnpm repo, machine has only npm) surfaced as raw `spawn pnpm ENOENT` plus manual advice that fails identically in the user's shell. 4. **Self-fulfilling detection** — Cursor/Codex `detect()` keyed on bare `~/.cursor` / `~/.codex` dirs, which argent itself creates (an `init -y` with nothing detected configures *all* editors; allowlist/skills writes too). Telemetry also shows a ~19 s failure shape where an identical manual retry succeeds (transient registry / pnpm first-run state errors). ## Fixes - **Init flag parsing stays lenient**: unknown flags remain silently ignored — aborting on them could break existing scripts/setups that pass flags an installed version doesn't know. The parser now also accepts the `--from=path` spelling, ignores a dangling `--from`, and keeps first-`--from`-wins semantics. The stale-binary hijack is addressed on the docs side (`@latest` in the README) rather than by a hard abort. - **`detectProjectPackageManager`** honors `devEngines.packageManager` (object + array forms) and `pnpm-workspace.yaml` — the latter ranked *below* real lockfiles so a stray file left by a pnpm→yarn migration can't outrank `yarn.lock`. - **Missing-binary failures** name the real problem ("This project uses pnpm, but the pnpm command was not found on PATH" + corepack hint). Windows detected via cmd.exe's "is not recognized" text, since `shell: true` never ENOENTs there. Other pm failures **retry once**. - **Editor detection requires real evidence**: a `.cursor`/`.codex` dir counts only if it holds something beyond argent's own artifacts (argent mcp.json entry, `permissions.json` allowlist, managed `developer_instructions`, bundled rules/agents/skills). Corrupt, unparseable, or empty content conservatively counts as user evidence. The stale-config sweep now scans **all** adapters so dead entries in no-longer-detected dirs still get pruned. - **README**: recommend `npx @swmansion/argent@latest init` and a `pnpm dlx` alternative (npm's devEngines gate blocks the *outer* npx in fresh pnpm-init projects). ## Testing - Live-tested a packed tgz under the exact repro conditions: fresh pnpm-init project A/B (published 0.15.0 fails EBADDEVENGINES in ~250 ms; this branch installs the devDependency correctly), missing-pnpm message, flag-parsing edges (`--from=` spelling, dangling `--from`), retry-on-transient-failure (flaky pm shim), detection flips (argent-only dirs incl. permissions.json → not detected; `argv.json`/`sessions/`/corrupt configs → detected), plus regressions: npm project local init, global-mode `init -y`, recovery of the broken post-0.14 state. - High-effort multi-agent code review of the diff surfaced 10 confirmed defects in the first iteration (permissions.json not whitelisted, corrupt configs misclassified as argent-only, pnpm-workspace.yaml outranking yarn.lock, `--from=` rejected, Windows ENOENT never firing, stale-sweep interaction, …) — all fixed and re-verified. - 493 argent-installer + 87 registry unit tests green (new suites for flag parsing, devEngines/workspace detection, detection evidence). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { parseInitArgs } from "../src/init-args.js";
|
|
|
|
describe("parseInitArgs", () => {
|
|
it("parses the full known flag set", () => {
|
|
const parsed = parseInitArgs(["--yes", "--no-telemetry", "--local", "--from", "./argent.tgz"]);
|
|
expect(parsed.nonInteractive).toBe(true);
|
|
expect(parsed.noTelemetry).toBe(true);
|
|
expect(parsed.wantsLocal).toBe(true);
|
|
expect(parsed.wantsGlobal).toBe(false);
|
|
expect(parsed.fromTar).toBe("./argent.tgz");
|
|
});
|
|
|
|
it("accepts -y and --global", () => {
|
|
const parsed = parseInitArgs(["-y", "--global"]);
|
|
expect(parsed.nonInteractive).toBe(true);
|
|
expect(parsed.wantsGlobal).toBe(true);
|
|
});
|
|
|
|
it("silently ignores unknown flags (never breaks an existing invocation)", () => {
|
|
const parsed = parseInitArgs(["--locl", "--verbose", "-y"]);
|
|
expect(parsed.nonInteractive).toBe(true);
|
|
expect(parsed.wantsLocal).toBe(false);
|
|
});
|
|
|
|
it("does not treat the --from value as a flag, even when it starts with a dash", () => {
|
|
const parsed = parseInitArgs(["--from", "-weird-dir/argent.tgz"]);
|
|
expect(parsed.fromTar).toBe("-weird-dir/argent.tgz");
|
|
});
|
|
|
|
it("accepts the --from=<path> spelling", () => {
|
|
const parsed = parseInitArgs(["--from=./argent.tgz", "-y"]);
|
|
expect(parsed.fromTar).toBe("./argent.tgz");
|
|
});
|
|
|
|
it("ignores a dangling --from (no value)", () => {
|
|
expect(parseInitArgs(["--local", "--from"]).fromTar).toBeNull();
|
|
expect(parseInitArgs(["--from=", "-y"]).fromTar).toBeNull();
|
|
});
|
|
|
|
it("keeps the first --from when the flag is repeated (previous parser semantics)", () => {
|
|
const parsed = parseInitArgs(["--from", "./pinned.tgz", "--from", "./other.tgz"]);
|
|
expect(parsed.fromTar).toBe("./pinned.tgz");
|
|
});
|
|
|
|
it("ignores positional (non-dash) tokens", () => {
|
|
const parsed = parseInitArgs(["extra"]);
|
|
expect(parsed.fromTar).toBeNull();
|
|
expect(parsed.nonInteractive).toBe(false);
|
|
});
|
|
});
|