mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
a3312cab97
Fixes #1024 **Cause.** `launch-app`, `restart-app`, `reinstall-app`, `settings-permissions` and `open-url` each carried their own copy of `/^[A-Za-z_][A-Za-z0-9._-]*$/`, which rejects an iOS bundle id such as `9gag.app` - with a message that says digits are allowed, so the agent reading it has nothing to act on. **Fix.** One shared `BUNDLE_ID_PATTERN` / `BUNDLE_ID_MESSAGE` in `src/utils/bundle-id.ts`. The head class becomes `[A-Za-z0-9_]` (the same class `native-devtools`' handshake validator already uses); `-` and `.` stay out, which is the whole of the flag-injection guard the old comments cited. The message names the head rule it enforces. **Class check.** No copy of the pattern remains (`grep BUNDLE_ID_PATTERN`). `open-url` was a fifth copy that landed on main while this branch was open - the catalogue-wide test caught it. `ACTIVITY_PATTERN` in `launch-app` / `restart-app` keeps its letter-only head deliberately: an activity is a Java class name and cannot start with a digit. **Docs.** No docs change needed - `reference/tools.mdx` lists these tools by one-line summary and documents no parameter constraints. <details> <summary>Verification</summary> New `test/bundle-id-pattern.test.ts` walks the whole catalogue rather than one tool, so a sixth `bundleId` constraint has to satisfy the same rules. Discrimination proved by restoring the old pattern and message in the shared module and re-running: ``` × derives a sane MCP JSON schema with bundleId required for every action × launch-app: admits a digit-leading bundle id and still refuses a flag × launch-app: names the head rule when it rejects one × restart-app / reinstall-app / settings-permissions / open-url: both, each Tests 11 failed | 61 passed (72) ``` With the fix in place, from the worktree root: ``` npx tsc --build OK npm run typecheck:tests -w @argent/tool-server OK npm test -w @argent/tool-server 434 files, 6174 passed | 1 skipped npx prettier --write <changed files> unchanged npx knip bundle-id.ts absent from the report ``` `npx eslint` could not run in this checkout (`packages/docs/tsconfig.json` cannot resolve `@docusaurus/tsconfig` without a separate `npm ci` in that workspace, a pre-existing local-environment issue); the ESLint CI job passes. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
50 lines
2.1 KiB
TypeScript
50 lines
2.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { ZodObject, ZodTypeAny } from "zod";
|
|
import { createRegistry } from "../src/utils/setup-registry";
|
|
import { advertisedSchema, definitionsById } from "./helpers/catalog";
|
|
|
|
// Asserted over the whole catalog rather than per tool, so a fifth bundleId
|
|
// constraint has to satisfy the same rules instead of drifting (issue #1024).
|
|
describe("advertised bundleId pattern", () => {
|
|
const constrained = [...definitionsById(createRegistry())]
|
|
.map(([id, definition]) => {
|
|
const properties = advertisedSchema(definition)?.properties as
|
|
| Record<string, { pattern?: string } | undefined>
|
|
| undefined;
|
|
const shape = (definition.zodSchema as ZodObject<Record<string, ZodTypeAny>> | undefined)
|
|
?.shape;
|
|
return { id, pattern: properties?.bundleId?.pattern, field: shape?.bundleId };
|
|
})
|
|
.filter((entry) => typeof entry.pattern === "string");
|
|
|
|
it("is advertised by exactly the tools that take an app identifier", () => {
|
|
expect(constrained.map(({ id }) => id).sort()).toEqual([
|
|
"launch-app",
|
|
"open-url",
|
|
"reinstall-app",
|
|
"restart-app",
|
|
"settings-permissions",
|
|
]);
|
|
});
|
|
|
|
for (const { id, pattern, field } of constrained) {
|
|
it(`${id}: admits a digit-leading bundle id and still refuses a flag`, () => {
|
|
const re = new RegExp(pattern!);
|
|
// An iOS CFBundleIdentifier may begin with a digit; `9gag.app` ships.
|
|
expect(re.test("9gag.app")).toBe(true);
|
|
expect(re.test("3d.tools.app")).toBe(true);
|
|
expect(re.test("com.example.app")).toBe(true);
|
|
// The head keeps out anything argv could read as a flag or a dotfile.
|
|
expect(re.test("--user")).toBe(false);
|
|
expect(re.test("-user")).toBe(false);
|
|
expect(re.test(".hidden")).toBe(false);
|
|
});
|
|
|
|
it(`${id}: names the head rule when it rejects one`, () => {
|
|
const rejected = field!.safeParse("--user");
|
|
expect(rejected.success).toBe(false);
|
|
expect(rejected.error!.issues[0]!.message).toContain("may not start with '-' or '.'");
|
|
});
|
|
}
|
|
});
|