Skip to content

Commit 411fb31

Browse files
committed
fix(cli): keep extra arguments and the repo root value out of telemetry
1 parent 11e99d3 commit 411fb31

5 files changed

Lines changed: 84 additions & 8 deletions

File tree

‎packages/cli/src/commands/lambda/repoRoot.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ describe("repoRoot", () => {
2828
it("throws on a HYPERFRAMES_REPO_ROOT that is not a checkout instead of ignoring it", () => {
2929
const root = tempDir();
3030
vi.stubEnv("HYPERFRAMES_REPO_ROOT", root);
31-
expect(() => repoRoot()).toThrow(`HYPERFRAMES_REPO_ROOT=${root} is not a hyperframes checkout`);
31+
expect(() => repoRoot()).toThrow("HYPERFRAMES_REPO_ROOT is not a hyperframes checkout");
32+
expect(() => repoRoot()).not.toThrow(root);
3233
});
3334
});

‎packages/cli/src/commands/lambda/repoRoot.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function repoRoot(): string {
1717
if (override) {
1818
if (existsSync(resolve(override, "packages", "aws-lambda", "package.json"))) return override;
1919
throw new Error(
20-
`[hyperframes lambda] HYPERFRAMES_REPO_ROOT=${override} is not a hyperframes checkout ` +
20+
"[hyperframes lambda] HYPERFRAMES_REPO_ROOT is not a hyperframes checkout " +
2121
"(no packages/aws-lambda/package.json there). Point it at the repo root, or unset it.",
2222
);
2323
}

‎packages/cli/src/telemetry/events.test.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,25 @@ describe("trackCliError", () => {
11301130
});
11311131

11321132
describe("trackCommandFailure", () => {
1133+
it("reports an extra-positional usage error by count, never the arguments", async () => {
1134+
const { resolveExtraPositionals } = await import("../utils/reject-extra-positionals.js");
1135+
const quiet = vi.spyOn(console, "error").mockImplementation(() => {});
1136+
const cmd = { args: { dir: { type: "positional" } } } as Parameters<
1137+
typeof resolveExtraPositionals
1138+
>[0];
1139+
let thrown: unknown;
1140+
try {
1141+
resolveExtraPositionals(cmd, "render", { _: ["./p", "Jane", "555-0100"] }, []);
1142+
} catch (error) {
1143+
thrown = error;
1144+
}
1145+
quiet.mockRestore();
1146+
trackCommandFailure("render", thrown);
1147+
const payload = JSON.stringify(trackEvent.mock.calls.at(-1));
1148+
expect(payload).toContain("2 unexpected extra arguments for hyperframes render");
1149+
expect(payload).not.toMatch(/Jane|555-0100/);
1150+
});
1151+
11331152
beforeEach(() => {
11341153
trackEvent.mockClear();
11351154
});

‎packages/cli/src/utils/command-failure-tracking.test.ts‎

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { mkdtempSync, writeFileSync } from "node:fs";
1+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
22
import { tmpdir } from "node:os";
33
import { join } from "node:path";
44
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
55
import { runCommand, type CommandDef } from "citty";
66
import { CliUsageError } from "./commandResult.js";
7+
import { resolveExtraPositionals } from "./reject-extra-positionals.js";
78

89
const trackCommandFailure = vi.fn();
910
vi.mock("../telemetry/events.js", () => ({
@@ -140,6 +141,12 @@ describe("trackCommandFailures: extra positionals", () => {
140141
const wrap = (cmd: CommandDef) => trackCommandFailures(() => Promise.resolve(cmd))();
141142
let errorSpy: ReturnType<typeof vi.spyOn>;
142143
let logSpy: ReturnType<typeof vi.spyOn>;
144+
const tempDirs: string[] = [];
145+
const tempDir = () => {
146+
const dir = mkdtempSync(join(tmpdir(), "hf-join-"));
147+
tempDirs.push(dir);
148+
return dir;
149+
};
143150

144151
beforeEach(() => {
145152
errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
@@ -149,6 +156,45 @@ describe("trackCommandFailures: extra positionals", () => {
149156
afterEach(() => {
150157
errorSpy.mockRestore();
151158
logSpy.mockRestore();
159+
for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true });
160+
});
161+
162+
it("throws a presented error whose message carries a count, never the arguments", async () => {
163+
const error = await runCommand(await wrap(leaf("render")), {
164+
rawArgs: ["./proj", "secret-launch", "Jane"],
165+
}).catch((e: unknown) => e);
166+
expect(error).toBeInstanceOf(CliUsageError);
167+
expect((error as CliUsageError).result.presented).toBe(true);
168+
expect((error as Error).message).toBe("2 unexpected extra arguments for hyperframes render");
169+
});
170+
171+
it("labels a required positional <NAME> and an optional one [NAME]", async () => {
172+
const cmd = {
173+
meta: { name: "keyframes" },
174+
args: { file: { type: "positional" }, at: { type: "positional", required: false } },
175+
run: vi.fn(),
176+
};
177+
await expect(
178+
runCommand(await wrap(cmd as CommandDef), { rawArgs: ["a", "b", "c"] }),
179+
).rejects.toThrow(CliUsageError);
180+
expect(errorSpy.mock.calls.flat().join("\n")).toContain(
181+
"Usage: hyperframes keyframes <FILE> [AT]",
182+
);
183+
});
184+
185+
it.each([
186+
["figma asset", () => import("../commands/figma/asset.js")],
187+
[
188+
"skills update",
189+
async () => ({
190+
default: (await import("../commands/skills.js")).default.subCommands!.update,
191+
}),
192+
],
193+
])("lets the real %s command read several positionals", async (path, load) => {
194+
const def = (await (load as () => Promise<{ default: unknown }>)()).default;
195+
const cmd = (typeof def === "function" ? await def() : def) as CommandDef;
196+
const parsed: Record<string, unknown> = { _: ["one", "two", "three"] };
197+
expect(() => resolveExtraPositionals(cmd, path as string, parsed, [])).not.toThrow();
152198
});
153199

154200
it("rejects a leaf's extra positional, naming it and the usage line, before run()", async () => {
@@ -178,17 +224,18 @@ describe("trackCommandFailures: extra positionals", () => {
178224

179225
it("rejects instead of joining when the first word names a file", async () => {
180226
const run = vi.fn();
181-
const file = join(mkdtempSync(join(tmpdir(), "hf-join-")), "script.txt");
227+
const file = join(tempDir(), "script.txt");
182228
writeFileSync(file, "hello");
183229
await expect(
184230
runCommand(await wrap(leaf("tts", run)), { rawArgs: [file, "extra"] }),
185-
).rejects.toThrow(/extra argument for hyperframes tts: extra/);
231+
).rejects.toThrow(CliUsageError);
232+
expect(errorSpy.mock.calls.flat().join("\n")).toContain("hyperframes tts: extra");
186233
expect(run).not.toHaveBeenCalled();
187234
});
188235

189236
it("still joins catalog words when the first one names a folder", async () => {
190237
const run = vi.fn();
191-
const folder = mkdtempSync(join(tmpdir(), "hf-join-"));
238+
const folder = tempDir();
192239
await runCommand(await wrap(leaf("catalog", run)), { rawArgs: [folder, "player"] });
193240
expect(run.mock.calls[0]![0].args.dir).toBe(`${folder} player`);
194241
});
@@ -227,7 +274,10 @@ describe("trackCommandFailures: extra positionals", () => {
227274
run: vi.fn(),
228275
} as CommandDef;
229276
await expect(runCommand(await wrap(group), { rawArgs: ["get", "id1", "id2"] })).rejects.toThrow(
230-
/hyperframes cloud get: id2\nUsage: hyperframes cloud get \[DIR\]/,
277+
CliUsageError,
278+
);
279+
expect(errorSpy.mock.calls.flat().join("\n")).toContain(
280+
"hyperframes cloud get: id2\nUsage: hyperframes cloud get [DIR]",
231281
);
232282
});
233283

‎packages/cli/src/utils/reject-extra-positionals.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,11 @@ export function resolveExtraPositionals(
6565
if (parsed?.json === true || rawArgs.some((tok) => tok === "--json" || tok === "--json=true"))
6666
console.log(JSON.stringify({ ok: false, error: message }));
6767
else console.error(message);
68-
throw new CliUsageError(message, { presented: true });
68+
// Telemetry reads the thrown message: the command path and a count, never the arguments.
69+
throw new CliUsageError(
70+
`${extra.length} unexpected extra argument${plural} for hyperframes ${path}`,
71+
{
72+
presented: true,
73+
},
74+
);
6975
}

0 commit comments

Comments
 (0)