mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
fa5af59931
## What & why
Swept the repo for hand-rolled regex/string parsers that reimplement a
solved problem, and replaced the genuinely fragile ones with
battle-tested packages (or deps already in the tree). Each swap is
paired with a regression test for an input the old code got wrong.
| Site | Was | Now |
|------|-----|-----|
| `adb.ts` `resolveAvdPath`, `linux-preflight.ts` `readMb` | per-key
anchored regex over AVD `.ini` / `config.ini` | **`ini`** |
| `workspace-reader.ts` `extractEnvKeys` | split-on-first-`=` line loop
| **`dotenv`** (`parse`) |
| `uninstall.ts` `readBundledSkillName` | `^name:(.+)$` capture +
outer-quote strip | **`yaml`** (already a dep) |
| `mcp-configs.ts` `escapeRegExp` | hand-rolled char-class escape |
**`escape-string-regexp`** |
| `export.ts` xctrace version, `session-context.ts` RN minor |
`match(/(\d+)\./)` / `split(".")[1]` | **`semver`** `coerce` (already a
dep) |
| `source-resolver.ts` `parseDebugStack` | `at fn (file:l:c)` regex
(required both line+col) | **`stacktrace-parser`** |
### Bugs these fix (covered by new tests)
- **ini**: a quoted `path = "/Users/My Name/.../X.avd"` kept its quotes,
failed the `startsWith("/")` guard → AVD unresolved. Inline comments /
trailing junk after a sizing value made the anchored regex return `null`
→ sizing warning silently lost.
- **dotenv**: `export FOO=bar` produced the key `"export FOO"` (dropped
by the identifier filter); a multi-line quoted value leaked its
continuation lines (`FAKE=...`) as phantom keys.
- **yaml**: a `name:` value with a trailing `# comment` was taken
verbatim (comment included); odd quoting only had one outer pair
stripped.
- **stacktrace-parser**: a `:line`-only frame (no column, as Hermes/JSC
can emit) became an unusable `unknown` frame.
## Deliberately left alone (not "flaky", or no safe swap)
To avoid churn and regressions, I did **not** touch:
- **`uiautomator-parser.ts`** hand-rolled XML parser — a deliberate,
documented choice with a dedicated hardening suite, including a
*leniency* test for malformed dumps (stray `</node>`). A spec XML parser
would reject inputs this is built to recover from.
- **`ios-profiler/pipeline/xml-parser.ts`** (xctrace XML) — the most
tangled parser, but it has no real `.trace` fixtures and only thin
entity-decode tests. A rewrite can't be proven equivalent without
device-generated traces, so swapping it blind risks silently breaking
iOS CPU/hang/leak reports. Best done separately with a captured fixture.
- **`http.ts` `extractHostname`** — DNS-rebinding guard; `URL.hostname`
keeps the IPv6 brackets the loopback check compares without, so the
"fix" isn't simpler and the path is security-sensitive.
- **`adb.ts` `shellQuote`** — the POSIX single-quote escape is the
standard correct form.
- **`parseAdbDevices`, `android-screen.ts` `wm size`, `detect-app.ts`
dumpsys** — tool-specific CLI-output scraping with no clean package
equivalent (and documented edge-case handling).
- **`workspace-reader.ts` `extractMetroPort`** — a best-effort heuristic
over JS config; a correct replacement needs an AST parse, out of scope
here.
- **`config-parse.ts` Yarn/npm age-gate parsing** — a precise,
unit-tested domain parser; `ms` would introduce a bare-number ambiguity.
## Verification
- `tsc --build` clean across the workspace graph.
- Tests: tool-server **1205**, installer **264**, update-core **31** —
all pass (incl. the new regression tests).
- `prettier --check` clean on all changed files.
- Production esbuild bundle (`bundle-tools.cjs`) emits a valid
`tool-server.cjs`/`mcp-server.mjs` with
`ini`/`dotenv`/`stacktrace-parser` inlined (`node --check` OK). *(The
script's final step errored only on the auth-gated
`trace-processor.wasm` download, unrelated to bundling.)*
New runtime deps: `ini`, `dotenv`, `stacktrace-parser` (tool-server),
`escape-string-regexp` (installer) — all small, pure-JS. Lockfile change
is purely additive.
103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
makeNetworkLogReadScript,
|
|
makeNetworkDetailReadScript,
|
|
} from "../../src/utils/debugger/scripts/network-interceptor";
|
|
|
|
describe("makeNetworkLogReadScript", () => {
|
|
it("returns a string containing the start and limit values", () => {
|
|
const script = makeNetworkLogReadScript(10, 50, 8081);
|
|
expect(script).toContain("var start = 10");
|
|
expect(script).toContain("var limit = 50");
|
|
});
|
|
|
|
it("embeds the metro port for filtering", () => {
|
|
const script = makeNetworkLogReadScript(0, 50, 8081);
|
|
expect(script).toContain("localhost:8081");
|
|
expect(script).toContain("127.0.0.1:8081");
|
|
});
|
|
|
|
it("uses different metro port values correctly", () => {
|
|
const script3000 = makeNetworkLogReadScript(0, 50, 3000);
|
|
expect(script3000).toContain("localhost:3000");
|
|
expect(script3000).toContain("127.0.0.1:3000");
|
|
expect(script3000).not.toContain("localhost:8081");
|
|
});
|
|
|
|
it("reads from __argent_network_log", () => {
|
|
const script = makeNetworkLogReadScript(0, 50, 8081);
|
|
expect(script).toContain("globalThis.__argent_network_log");
|
|
});
|
|
|
|
it("returns interceptorInstalled: false when no log exists", () => {
|
|
const script = makeNetworkLogReadScript(0, 50, 8081);
|
|
expect(script).toContain("interceptorInstalled: false");
|
|
});
|
|
|
|
it("strips responseBody from list view entries", () => {
|
|
const script = makeNetworkLogReadScript(0, 50, 8081);
|
|
// The script builds entries without responseBody to avoid large payloads
|
|
expect(script).not.toContain("responseBody: s.responseBody");
|
|
});
|
|
|
|
it("is a valid IIFE", () => {
|
|
const script = makeNetworkLogReadScript(0, 50, 8081);
|
|
expect(script.trim()).toMatch(/^\(function\(\)/);
|
|
expect(script.trim()).toMatch(/\)\(\)$/);
|
|
});
|
|
});
|
|
|
|
describe("makeNetworkDetailReadScript", () => {
|
|
it("includes the requestId in the script", () => {
|
|
const script = makeNetworkDetailReadScript("rn-net-42");
|
|
expect(script).toContain("rn-net-42");
|
|
});
|
|
|
|
it("embeds the requestId as a JSON string literal (safe against quotes/backslashes/injection)", () => {
|
|
// The requestId is interpolated via JSON.stringify, so for any input the
|
|
// byId lookup is exactly `byId[<json-literal>]` — no break-out is possible.
|
|
for (const rid of ["rn-net-1", "rn-net-'q", 'rn-net-"x', "rn-net-\\b", `x"]; evil(); //`]) {
|
|
const script = makeNetworkDetailReadScript(rid);
|
|
expect(script).toContain(`byId[${JSON.stringify(rid)}]`);
|
|
}
|
|
});
|
|
|
|
it("encodes a control character instead of injecting it raw (the hand-escaper crashed the parse)", () => {
|
|
const script = makeNetworkDetailReadScript("rn-net-\n5");
|
|
expect(script).toContain('byId["rn-net-\\n5"]');
|
|
// never a raw newline inside the string literal (which would be a SyntaxError)
|
|
expect(script).not.toMatch(/byId\["rn-net-\n/);
|
|
});
|
|
|
|
it("escapes standalone backslashes in requestId", () => {
|
|
const script = makeNetworkDetailReadScript("rn-net-\\test");
|
|
expect(script).toContain("rn-net-\\\\test");
|
|
});
|
|
|
|
it("reads from __argent_network_by_id", () => {
|
|
const script = makeNetworkDetailReadScript("rn-net-1");
|
|
expect(script).toContain("globalThis.__argent_network_by_id");
|
|
});
|
|
|
|
it("includes responseBody in the detail output", () => {
|
|
const script = makeNetworkDetailReadScript("rn-net-1");
|
|
expect(script).toContain("responseBody: entry.responseBody");
|
|
});
|
|
|
|
it("returns an error if interceptor is not installed", () => {
|
|
const script = makeNetworkDetailReadScript("rn-net-1");
|
|
expect(script).toContain("Network interceptor not installed");
|
|
});
|
|
|
|
it("returns an error if request is not found", () => {
|
|
const script = makeNetworkDetailReadScript("rn-net-1");
|
|
expect(script).toContain("Request not found");
|
|
});
|
|
|
|
it("is a valid IIFE", () => {
|
|
const script = makeNetworkDetailReadScript("rn-net-1");
|
|
expect(script.trim()).toMatch(/^\(function\(\)/);
|
|
expect(script.trim()).toMatch(/\)\(\)$/);
|
|
});
|
|
});
|