mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
b232114422
Audits every comment in `src/` and `scripts/` against the code it
describes.
**462 files, 57 commits, net −8,194 lines.** Comment text only — the
whole branch is code-identical to `main`.
## Method
One subagent per file, strictly sequential. Scope was `src/` +
`scripts/` (459 files); three more files were added at the end because
they carried dead references of the same kind — two test headers citing
design docs that do not exist, and `publish-npm.yml` citing a retired
workflow. Each agent verified every comment — line, block, JSDoc, file
header, trailing — against the surrounding code and the rest of the
repo, following identifiers, paths, tool ids, config keys, env vars and
issue links to see whether they still exist and still behave as
described. Rules:
- **A false or misleading comment is deleted, not reworded.** If a claim
could not be confirmed by reading the source, it went. That is why the
deletion count is so much larger than the rewrite count.
- Survivors are cut to the shortest form carrying something the code
does not already say. Restatement, preamble, hedging, changelog prose
and ASCII banners are gone; the non-obvious *why* stays.
- Preserved byte-identical: license headers, pragmas and directives
(`@ts-*`, `eslint-disable`, shebangs, `/// <reference>`), JSDoc tag
tokens, everything inside a string or template literal, and the sole
comment inside an otherwise empty block (ESLint `no-empty` counts a
comment-bearing block as non-empty).
## Verification
Every file passed two independent gates before being recorded as done:
1. `comments-only` — the required check.
2. A second comment-stripping comparator with a proper mode stack,
written for this pass because `comments-only`'s flat scanner desyncs on
nested template literals and quote-bearing regex literals and then
reports comment lines as code changes. Two files hit that false FAIL
(`utils/android-profiler/pipeline/index.ts`,
`scripts/extract-tools.mjs`); in both the "changed code" it printed was
literally `//` lines, and the second checker confirmed the code was
byte-identical.
After the last file, all 462 changed files were re-checked against
`main` with the same comparator, rather than trusting any agent's
self-report. **459 code-identical; 2 are non-code (`.svg`, `.md`); 1
intentional.**
The intentional one is `packages/argent/scripts/bundle-tools.cjs`: the
changed template literal *is* the comment header of the file it
generates, `packages/native-devtools-android/src/bundled-meta.ts`.
Fixing only the generated file would have been reverted by the next
build, so the generator changed too — and it has been verified to
reproduce the committed generated file byte-for-byte.
## Representative false claims removed
Not wording nits — statements a reader would have acted on:
- **Reversed directions.** `proxyStart`'s JSDoc had the tunnel backwards
(it is a reverse tunnel: the host binds first and the simulator dials
in). A `paste()` doc had the pasteboard copy direction reversed.
- **Contradicted by the code below it.** A timeout budget multiplied by
three where the probes run concurrently — the same comment said so six
lines later. A "warn once" that warns on every call. A "binary search"
that is a linear scan.
- **Named things that do not exist.** A `vega-fast-cli` binary, a
`finish-recording.ts`, a `publish-next.yml` workflow, two
`profiler-react19-*.md` design docs, a `DebuggerTarget.ts`, a commit
hash git does not know, two tool ids, an `ensureEnv` cycle.
- **Wrong by construction.** "Welford accumulators" across four files
where the code keeps naive `n`/`sum`/`sumSq`; `sum`/`sumSq` documented
over `actualDuration` when reduce sums `selfDuration`; a strict-mode
halving written `n/2` where the code ceils; field docs listing enum
values the producers never emit.
- **Guarantees the code does not make.** A validation matrix claiming to
cover "EVERY tool" that skips flagless ones; a Pareto cutoff that
`slice(0, 20)` makes inert; an idempotence claim where the real rule is
at-or-ahead; a capability note describing a clean 400 the shape-based
device resolver can never produce.
- **Unverifiable assertions** about prebuilt binaries, external CLIs and
the cloud SDK — deleted rather than kept as folklore, since nothing in
the repo can confirm them.
- **Stale numbers**: invented Android tool versions, hard-coded tool
counts and description lengths that had drifted.
## Review
A Fable agent reviewed both halves adversarially for over-deletion,
misread code, `no-empty` hazards and byte-identity violations.
Second-half verdict: **SHIP**, with two one-line restores, both applied
in the final commit — the `npm view ""` rationale behind a blank-token
guard, and the note that `argent-mcp` keeps a copy of
`SECRET_PLACEHOLDER_MARKER` it cannot import.
## Code issues surfaced but deliberately not fixed
This pass changes comments only. Eight genuine findings are logged for a
follow-up:
1. `telemetry/src/consent.ts` — a non-ENOENT read error returns null and
falls through to the default-on path, so file errors *can* silently flip
telemetry on.
2. `chromium-server/navigation.ts` — `navigate()` is reachable from
`POST /api/navigate` with only a `typeof === "string"` check; open-url's
schema is a bare `z.string()`, so the "already validated by zod" premise
never held.
3. `http.ts` — `constantTimeEqual` returns early on a length mismatch,
so the auth token's length is observable.
4. `describe/index.ts:~114` — the ios-remote branch passes `{ isTvOs:
false }` unconditionally, so a remote tvOS simulator takes the iOS
ax-service path, though `isRemoteTvOsSimulator` exists and shake/paste
do use it.
5. `devices/boot-device.ts` — `-crash-report-mode never` is passed
unconditionally *and* appended again by the feature-detecting path, so
every emulator spawn passes it twice.
6. `react-profiler/pipeline/04-rank.ts` — `PARETO_THRESHOLD_PCT` is
dead: `slice(0, 20)` always wins.
7. `reaped-sessions.ts` — a user-facing hint string tells the agent that
`react-profiler-start { force: true }` disposes the debugger and
profiler session; it does not. Left byte-identical because it is a
string literal, not a comment.
8. `utils/simctl-backend.ts` — `localSimctl` is exported with no
importers anywhere.
## Docs
No documentation change is needed: this pass touches only source
comments, and no user-facing capability, tool, CLI flag, config key or
flow-file behaviour changed.
---------
Co-authored-by: filip131311 <f.kaminski2000@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
604 lines
24 KiB
JavaScript
604 lines
24 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const esbuild = require("esbuild");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const WORKSPACE_ROOT = path.resolve(__dirname, "../../..");
|
|
|
|
const TOOLS_ENTRY = path.resolve(WORKSPACE_ROOT, "packages/tool-server/src/index.ts");
|
|
const ARCHIVE_ENTRY = path.resolve(WORKSPACE_ROOT, "packages/archive/src/index.ts");
|
|
const REGISTRY_ENTRY = path.resolve(WORKSPACE_ROOT, "packages/registry/src/index.ts");
|
|
const TELEMETRY_ENTRY = path.resolve(WORKSPACE_ROOT, "packages/telemetry/src/index.ts");
|
|
const NATIVE_DEVTOOLS_IOS_ENTRY = path.resolve(
|
|
WORKSPACE_ROOT,
|
|
"packages/native-devtools-ios/src/index.ts"
|
|
);
|
|
const NATIVE_DEVTOOLS_ANDROID_ENTRY = path.resolve(
|
|
WORKSPACE_ROOT,
|
|
"packages/native-devtools-android/src/index.ts"
|
|
);
|
|
const TOOLS_CLIENT_ENTRY = path.resolve(
|
|
WORKSPACE_ROOT,
|
|
"packages/argent-tools-client/src/index.ts"
|
|
);
|
|
const INSTALLER_ENTRY = path.resolve(WORKSPACE_ROOT, "packages/argent-installer/src/index.ts");
|
|
const MCP_ENTRY = path.resolve(WORKSPACE_ROOT, "packages/argent-mcp/src/index.ts");
|
|
const CLI_ENTRY = path.resolve(WORKSPACE_ROOT, "packages/argent-cli/src/index.ts");
|
|
const PREVIEW_WINDOW_ENTRY = path.resolve(WORKSPACE_ROOT, "packages/preview-window/src/main.ts");
|
|
const CONFIGURATION_ENTRY = path.resolve(
|
|
WORKSPACE_ROOT,
|
|
"packages/configuration-core/src/index.ts"
|
|
);
|
|
const OUT_FILE = path.resolve(__dirname, "../dist/tool-server.cjs");
|
|
const INSTALLER_OUT_FILE = path.resolve(__dirname, "../dist/installer.mjs");
|
|
const MCP_OUT_FILE = path.resolve(__dirname, "../dist/mcp-server.mjs");
|
|
const CLI_OUT_FILE = path.resolve(__dirname, "../dist/cli-cmds.mjs");
|
|
const PREVIEW_WINDOW_OUT_FILE = path.resolve(__dirname, "../dist/preview-window/main.cjs");
|
|
|
|
// Resolve workspace deps from source rather than each package's compiled dist/,
|
|
// so bundles don't depend on build order or freshness.
|
|
const ALIASES = {
|
|
"@argent/archive": ARCHIVE_ENTRY,
|
|
"@argent/registry": REGISTRY_ENTRY,
|
|
"@argent/native-devtools-ios": NATIVE_DEVTOOLS_IOS_ENTRY,
|
|
"@argent/native-devtools-android": NATIVE_DEVTOOLS_ANDROID_ENTRY,
|
|
"@argent/tools-client": TOOLS_CLIENT_ENTRY,
|
|
"@argent/installer": INSTALLER_ENTRY,
|
|
"@argent/mcp": MCP_ENTRY,
|
|
"@argent/cli": CLI_ENTRY,
|
|
"@argent/configuration-core": CONFIGURATION_ENTRY,
|
|
"@argent/telemetry": TELEMETRY_ENTRY,
|
|
};
|
|
|
|
// Build-time constants for @argent/telemetry. An unset ARGENT_OTEL_INGEST_TOKEN
|
|
// defines "", which leaves the telemetry client unconstructed and telemetry
|
|
// inert (see packages/telemetry/src/otel.ts).
|
|
const TELEMETRY_CLI_VERSION = (() => {
|
|
try {
|
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
|
|
return String(pkg.version ?? "0.0.0");
|
|
} catch {
|
|
return "0.0.0";
|
|
}
|
|
})();
|
|
|
|
const TELEMETRY_OTEL_INGEST_TOKEN = process.env.ARGENT_OTEL_INGEST_TOKEN ?? "";
|
|
|
|
const TELEMETRY_DEFINE = {
|
|
ARGENT_CLI_VERSION: JSON.stringify(TELEMETRY_CLI_VERSION),
|
|
ARGENT_OTEL_INGEST_TOKEN: JSON.stringify(TELEMETRY_OTEL_INGEST_TOKEN),
|
|
};
|
|
|
|
// Prefer "module" over esbuild's node default ["main","module"]: UMD entries
|
|
// (e.g. jsonc-parser's) use runtime `require("./impl/...")` that can't be
|
|
// statically resolved, and impl/ isn't shipped next to the bundle.
|
|
const MAIN_FIELDS = ["module", "main"];
|
|
|
|
// Lets inlined CJS dependencies use `require()`, `__dirname` and `__filename`
|
|
// inside an ESM bundle (e.g. @argent/native-devtools-ios resolves bin/ off
|
|
// `__dirname`). The shimmed values point at the bundle's own location.
|
|
const ESM_REQUIRE_BANNER = {
|
|
js:
|
|
"import { createRequire as __createRequire } from 'node:module'; " +
|
|
"import { fileURLToPath as __fileURLToPath } from 'node:url'; " +
|
|
"import { dirname as __pathDirname } from 'node:path'; " +
|
|
"const require = __createRequire(import.meta.url); " +
|
|
"const __filename = __fileURLToPath(import.meta.url); " +
|
|
"const __dirname = __pathDirname(__filename);",
|
|
};
|
|
|
|
// Source layout mirrors what `scripts/download-simulator-server.sh` writes:
|
|
// platform-keyed subdirectories of bin/.
|
|
const BIN_SRC_ROOT = path.resolve(WORKSPACE_ROOT, "packages/native-devtools-ios/bin");
|
|
const AX_BIN_SRC = path.resolve(BIN_SRC_ROOT, "darwin/ax-service");
|
|
// Platform-NEUTRAL bin/tcp/ (not bin/darwin/tcp/): this binary is uploaded to the
|
|
// remote macOS orchestrator, so it must resolve from any host platform. Matches
|
|
// tcpBinDir() in native-devtools-ios/src/index.ts.
|
|
const AX_TCP_BIN_SRC = path.resolve(BIN_SRC_ROOT, "tcp/ax-service");
|
|
const BIN_DIR = path.resolve(__dirname, "../bin");
|
|
const AX_BIN_DEST = path.resolve(BIN_DIR, "darwin/ax-service");
|
|
const AX_TCP_BIN_DEST = path.resolve(BIN_DIR, "tcp/ax-service");
|
|
// tvOS control binaries. Both macOS-only, unix-socket only.
|
|
const TVOS_AX_BIN_SRC = path.resolve(BIN_SRC_ROOT, "darwin/tvos-ax-service");
|
|
const TVOS_HID_BIN_SRC = path.resolve(BIN_SRC_ROOT, "darwin/tvos-hid-daemon");
|
|
const TVOS_AX_BIN_DEST = path.resolve(BIN_DIR, "darwin/tvos-ax-service");
|
|
const TVOS_HID_BIN_DEST = path.resolve(BIN_DIR, "darwin/tvos-hid-daemon");
|
|
// Host platform keys (see hostPlatformKey() in @argent/native-devtools-ios):
|
|
// darwin is a universal binary; Linux ships one single-arch ELF per key;
|
|
// win32 ships a PE `.exe`.
|
|
const SUPPORTED_HOST_PLATFORMS = ["darwin", "linux", "linux-arm64", "win32"];
|
|
const simulatorServerFileName = (platform) =>
|
|
platform === "win32" ? "simulator-server.exe" : "simulator-server";
|
|
const BUNDLED_META_DEST = path.resolve(
|
|
WORKSPACE_ROOT,
|
|
"packages/native-devtools-android/src/bundled-meta.ts"
|
|
);
|
|
const PERFETTO_VERSION_FILE = path.resolve(
|
|
WORKSPACE_ROOT,
|
|
"packages/argent-private/packages/native-devtools-android/PERFETTO_VERSION"
|
|
);
|
|
const DYLIBS_SRC = path.resolve(WORKSPACE_ROOT, "packages/native-devtools-ios/dylibs");
|
|
const DYLIBS_DEST = path.resolve(__dirname, "../dylibs");
|
|
const SKILLS_SRC = path.resolve(WORKSPACE_ROOT, "packages/skills/skills");
|
|
const SKILLS_DEST = path.resolve(__dirname, "../skills");
|
|
const RULES_SRC = path.resolve(WORKSPACE_ROOT, "packages/skills/rules");
|
|
const RULES_DEST = path.resolve(__dirname, "../rules");
|
|
const AGENTS_SRC = path.resolve(WORKSPACE_ROOT, "packages/skills/agents");
|
|
const AGENTS_DEST = path.resolve(__dirname, "../agents");
|
|
const QUERIES_SRC = path.resolve(WORKSPACE_ROOT, "packages/native-devtools-android/assets/queries");
|
|
const QUERIES_DEST = path.resolve(__dirname, "../assets/queries");
|
|
const TRACE_PROCESSOR_SRC = path.resolve(
|
|
WORKSPACE_ROOT,
|
|
"packages/native-devtools-android/assets/trace-processor"
|
|
);
|
|
const TRACE_PROCESSOR_DEST = path.resolve(__dirname, "../assets/trace-processor");
|
|
const ANDROID_PKG_DIR = path.resolve(WORKSPACE_ROOT, "packages/native-devtools-android");
|
|
const ANDROID_MANIFEST_SRC = path.join(ANDROID_PKG_DIR, "assets/manifest.json");
|
|
const ANDROID_MANIFEST_DEST = path.resolve(__dirname, "../assets/manifest.json");
|
|
const ANDROID_APK_SRC_DIR = path.join(ANDROID_PKG_DIR, "bin");
|
|
const UI_SRC = path.resolve(WORKSPACE_ROOT, "packages/ui/index.html");
|
|
const UI_DEST = path.resolve(__dirname, "../dist/preview-ui/index.html");
|
|
const UI_THEME_SRC = path.resolve(WORKSPACE_ROOT, "packages/ui/theme.css");
|
|
const UI_THEME_DEST = path.resolve(__dirname, "../dist/preview-ui/theme.css");
|
|
const TRACE_TEMPLATE_SRC = path.resolve(
|
|
WORKSPACE_ROOT,
|
|
"packages/tool-server/src/utils/ios-profiler/Argent.tracetemplate"
|
|
);
|
|
const TRACE_TEMPLATE_DEST = path.resolve(__dirname, "../assets/Argent.tracetemplate");
|
|
const TRACECFG_SRC = path.resolve(
|
|
WORKSPACE_ROOT,
|
|
"packages/native-devtools-android/assets/argent.tracecfg.pbtxt"
|
|
);
|
|
const TRACECFG_DEST = path.resolve(__dirname, "../assets/argent.tracecfg.pbtxt");
|
|
|
|
// Declarative copy plan for copyAsset() below.
|
|
//
|
|
// kind "file" (copyFileSync) | "dir" (cpSync recursive)
|
|
// src/dest absolute source and destination paths
|
|
// mode optional chmod applied to the copied file
|
|
// required throw (true) vs warn (false) when src is missing
|
|
// copiedLabel text after "✓ Copied " on success (count is prefixed if set)
|
|
// missLabel subject of the "<X> not found at …" miss message
|
|
// countExt count src entries ending in this ext, shown before copiedLabel
|
|
// count custom counter (src) => number, takes precedence over countExt
|
|
// hint extra guidance appended to the throw message (required assets)
|
|
/**
|
|
* @typedef {Object} Asset
|
|
* @property {"file"|"dir"} kind
|
|
* @property {string} src
|
|
* @property {string} dest
|
|
* @property {boolean} required
|
|
* @property {string} copiedLabel
|
|
* @property {string} missLabel
|
|
* @property {number} [mode]
|
|
* @property {string} [countExt]
|
|
* @property {(src: string) => number} [count]
|
|
* @property {string} [hint]
|
|
*/
|
|
/** @type {Asset[]} */
|
|
const ASSETS = [
|
|
// iOS ax-service binary (macOS-only — runs inside an iOS Simulator).
|
|
{
|
|
kind: "file",
|
|
src: AX_BIN_SRC,
|
|
dest: AX_BIN_DEST,
|
|
mode: 0o755,
|
|
required: false,
|
|
copiedLabel: "ax-service binary",
|
|
missLabel: "ax-service binary",
|
|
},
|
|
// iOS ax-service TCP variant; only present when the TCP transport was built.
|
|
{
|
|
kind: "file",
|
|
src: AX_TCP_BIN_SRC,
|
|
dest: AX_TCP_BIN_DEST,
|
|
mode: 0o755,
|
|
required: false,
|
|
copiedLabel: "ax-service (tcp) binary",
|
|
missLabel: "ax-service (tcp) binary",
|
|
},
|
|
// tvOS AX reader — `simctl spawn`d inside an appletvsimulator to read the
|
|
// focus-engine accessibility tree.
|
|
{
|
|
kind: "file",
|
|
src: TVOS_AX_BIN_SRC,
|
|
dest: TVOS_AX_BIN_DEST,
|
|
mode: 0o755,
|
|
required: false,
|
|
copiedLabel: "tvos-ax-service binary",
|
|
missLabel: "tvos-ax-service binary",
|
|
},
|
|
// tvOS HID daemon — runs on the macOS host, injects Siri-remote HID events
|
|
// into the simulator via SimulatorKit.
|
|
{
|
|
kind: "file",
|
|
src: TVOS_HID_BIN_SRC,
|
|
dest: TVOS_HID_BIN_DEST,
|
|
mode: 0o755,
|
|
required: false,
|
|
copiedLabel: "tvos-hid-daemon binary",
|
|
missLabel: "tvos-hid-daemon binary",
|
|
},
|
|
// Android host-side Perfetto trace processor: the third-party WASM engine
|
|
// (trace_processor.wasm + emscripten glue + engine.mjs decoder + LICENSE).
|
|
// wasm-trace-processor.ts resolves them at `<pkg>/assets/trace-processor/`,
|
|
// i.e. this exact destination. Not committed; fetched + sha256-verified by
|
|
// scripts/download-trace-processor.sh.
|
|
{
|
|
kind: "dir",
|
|
src: TRACE_PROCESSOR_SRC,
|
|
dest: TRACE_PROCESSOR_DEST,
|
|
required: true,
|
|
copiedLabel: "trace-processor WASM asset(s)",
|
|
missLabel: "trace-processor WASM assets directory",
|
|
hint: "Run: bash scripts/download-trace-processor.sh (fetches from argent-private-releases)",
|
|
},
|
|
// iOS native devtools dylibs so the packaged tool-server can inject them at runtime.
|
|
{
|
|
kind: "dir",
|
|
src: DYLIBS_SRC,
|
|
dest: DYLIBS_DEST,
|
|
required: false,
|
|
copiedLabel: "native dylib(s)",
|
|
missLabel: "Native devtools dylibs",
|
|
countExt: ".dylib",
|
|
},
|
|
// Android helper manifest.json: helperManifest()/bundledHelperApkPath() read it
|
|
// at runtime, and the version-stamped APK filename comes from its versionName
|
|
// (see the APK block after the copy loop).
|
|
{
|
|
kind: "file",
|
|
src: ANDROID_MANIFEST_SRC,
|
|
dest: ANDROID_MANIFEST_DEST,
|
|
required: true,
|
|
copiedLabel: "Android manifest",
|
|
missLabel: "Android manifest",
|
|
hint: "Run: bash scripts/download-native-binaries.sh (fetches from argent-private-releases)",
|
|
},
|
|
// Preview UI (@argent/ui) next to the bundled tool-server, where the /preview/
|
|
// endpoint finds it via __dirname. The externalised theme.css must ship with
|
|
// index.html — a partial copy 404s /preview/theme.css and serves an unstyled UI.
|
|
{
|
|
kind: "file",
|
|
src: UI_SRC,
|
|
dest: UI_DEST,
|
|
required: false,
|
|
copiedLabel: "preview UI",
|
|
missLabel: "Preview UI",
|
|
},
|
|
{
|
|
kind: "file",
|
|
src: UI_THEME_SRC,
|
|
dest: UI_THEME_DEST,
|
|
required: false,
|
|
copiedLabel: "preview UI theme.css",
|
|
missLabel: "Preview UI theme.css",
|
|
},
|
|
// Argent.tracetemplate so native-profiler-start (iOS) can find it at runtime.
|
|
{
|
|
kind: "file",
|
|
src: TRACE_TEMPLATE_SRC,
|
|
dest: TRACE_TEMPLATE_DEST,
|
|
required: false,
|
|
copiedLabel: "Argent.tracetemplate",
|
|
missLabel: "Argent.tracetemplate",
|
|
},
|
|
// Android profiler capture reads this via @argent/native-devtools-android's
|
|
// `traceConfigPath()`, which resolves `<pkg>/assets/argent.tracecfg.pbtxt` —
|
|
// i.e. this exact destination.
|
|
{
|
|
kind: "file",
|
|
src: TRACECFG_SRC,
|
|
dest: TRACECFG_DEST,
|
|
required: true,
|
|
copiedLabel: "argent.tracecfg.pbtxt",
|
|
missLabel: "argent.tracecfg.pbtxt",
|
|
hint: "This file is required for Android native profiling.",
|
|
},
|
|
// Android profiler SQL queries. traceProcessorQueriesDir() resolves
|
|
// `<pkg>/assets/queries/`, i.e. this exact destination.
|
|
{
|
|
kind: "dir",
|
|
src: QUERIES_SRC,
|
|
dest: QUERIES_DEST,
|
|
required: true,
|
|
copiedLabel: "SQL queries",
|
|
missLabel: "Android profiler queries directory",
|
|
countExt: ".sql",
|
|
hint: "This directory is required for native-profiler-analyze on Android.",
|
|
},
|
|
// Skills shipped on npm; the full directory structure is mirrored.
|
|
{
|
|
kind: "dir",
|
|
src: SKILLS_SRC,
|
|
dest: SKILLS_DEST,
|
|
required: false,
|
|
copiedLabel: "skill(s)",
|
|
missLabel: "Skills source",
|
|
count: (src) =>
|
|
fs.readdirSync(src, { withFileTypes: true }).filter((e) => e.isDirectory()).length,
|
|
},
|
|
// Rules shipped on npm.
|
|
{
|
|
kind: "dir",
|
|
src: RULES_SRC,
|
|
dest: RULES_DEST,
|
|
required: false,
|
|
copiedLabel: "rule(s)",
|
|
missLabel: "Rules source",
|
|
countExt: ".md",
|
|
},
|
|
// Agents shipped on npm.
|
|
{
|
|
kind: "dir",
|
|
src: AGENTS_SRC,
|
|
dest: AGENTS_DEST,
|
|
required: false,
|
|
copiedLabel: "agent(s)",
|
|
missLabel: "Agents source",
|
|
count: (src) =>
|
|
fs
|
|
.readdirSync(src, { withFileTypes: true })
|
|
.filter((e) => e.isFile() && e.name.endsWith(".md")).length,
|
|
},
|
|
];
|
|
|
|
/**
|
|
* @param {{ entry: string, out: string, format: "cjs" | "esm", label: string, external?: string[] }} opts
|
|
*/
|
|
function buildBundle({ entry, out, format, label, external = [] }) {
|
|
esbuild.buildSync({
|
|
entryPoints: [entry],
|
|
bundle: true,
|
|
platform: "node",
|
|
target: "node22",
|
|
format,
|
|
outfile: out,
|
|
alias: ALIASES,
|
|
mainFields: MAIN_FIELDS,
|
|
define: TELEMETRY_DEFINE,
|
|
// ESM bundles need the require() shim and must keep node: builtins external;
|
|
// CJS bundles externalise only what the caller passes.
|
|
...(format === "esm"
|
|
? { banner: ESM_REQUIRE_BANNER, external: [...new Set(["node:*", ...external])] }
|
|
: external.length > 0
|
|
? { external }
|
|
: {}),
|
|
});
|
|
console.log(`✓ Bundled ${label} → ${path.relative(process.cwd(), out)}`);
|
|
}
|
|
|
|
/**
|
|
* Count shown in the success line, or null when the asset isn't counted.
|
|
* @param {Asset} a
|
|
* @returns {number | null}
|
|
*/
|
|
function assetCount(a) {
|
|
if (a.count) return a.count(a.src);
|
|
if (a.countExt) {
|
|
const ext = a.countExt;
|
|
return fs.readdirSync(a.src).filter((f) => f.endsWith(ext)).length;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Copy one ASSETS entry, throwing or warning per its `required` flag.
|
|
* @param {Asset} a
|
|
*/
|
|
function copyAsset(a) {
|
|
if (!fs.existsSync(a.src)) {
|
|
if (a.required) {
|
|
throw new Error(`${a.missLabel} not found at ${a.src}.` + (a.hint ? `\n${a.hint}` : ""));
|
|
}
|
|
console.warn(`⚠ ${a.missLabel} not found at ${a.src} — skipping copy`);
|
|
return;
|
|
}
|
|
|
|
if (a.kind === "dir") {
|
|
fs.cpSync(a.src, a.dest, { recursive: true });
|
|
} else {
|
|
fs.mkdirSync(path.dirname(a.dest), { recursive: true });
|
|
fs.copyFileSync(a.src, a.dest);
|
|
}
|
|
if (a.mode != null) fs.chmodSync(a.dest, a.mode);
|
|
|
|
const rel = path.relative(process.cwd(), a.dest);
|
|
const count = assetCount(a);
|
|
if (count != null) {
|
|
console.log(`✓ Copied ${count} ${a.copiedLabel} → ${rel}`);
|
|
} else {
|
|
console.log(`✓ Copied ${a.copiedLabel} → ${rel}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pinned Perfetto version from argent-private's PERFETTO_VERSION, falling back to
|
|
* the committed constant in bundled-meta.ts when the submodule isn't checked out.
|
|
*/
|
|
function readPerfettoVersion() {
|
|
try {
|
|
const v = fs.readFileSync(PERFETTO_VERSION_FILE, "utf8").trim();
|
|
if (v) return v;
|
|
} catch {
|
|
/* submodule absent — fall through to committed constant */
|
|
}
|
|
try {
|
|
const existing = fs.readFileSync(BUNDLED_META_DEST, "utf8");
|
|
const m = existing.match(/PERFETTO_VERSION\s*=\s*"([^"]+)"/);
|
|
if (m) return m[1];
|
|
} catch {
|
|
/* committed file missing — final fallback below */
|
|
}
|
|
return "v55.3";
|
|
}
|
|
|
|
/**
|
|
* Regenerate bundled-meta.ts so it pins the Perfetto version stamping the
|
|
* bundled trace_processor.wasm.
|
|
*/
|
|
function generateBundledMeta() {
|
|
const version = readPerfettoVersion();
|
|
const content = `// GENERATED by packages/argent/scripts/bundle-tools.cjs, which overwrites this
|
|
// file from argent-private's PERFETTO_VERSION; the committed value is a fallback
|
|
// so builds without that submodule still compile. Informational only — it stamps
|
|
// the bundled \`trace_processor.wasm\`, nothing resolves against it.
|
|
|
|
export const PERFETTO_VERSION = "${version}";
|
|
`;
|
|
fs.writeFileSync(BUNDLED_META_DEST, content);
|
|
console.log(`✓ Generated bundled-meta.ts (perfetto=${version})`);
|
|
}
|
|
|
|
// Must run before esbuild, so PERFETTO_VERSION is inlined into the bundles.
|
|
generateBundledMeta();
|
|
|
|
// Purge artifact directories so stale files don't survive across builds. Derived
|
|
// from the table so it can't drift.
|
|
const PURGE_DIRS = [BIN_DIR, ...ASSETS.filter((a) => a.kind === "dir").map((a) => a.dest)];
|
|
for (const dir of PURGE_DIRS) {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
|
|
// The `argent-simulator-server` dispatcher npm's `bin` field publishes; it picks
|
|
// the right per-platform binary at invocation time. Source lives in scripts/ so
|
|
// it isn't entangled with the gitignored bundle output under bin/.
|
|
const DISPATCHER_SRC = path.resolve(__dirname, "argent-simulator-server.cjs");
|
|
const DISPATCHER_DEST = path.resolve(BIN_DIR, "argent-simulator-server.cjs");
|
|
fs.copyFileSync(DISPATCHER_SRC, DISPATCHER_DEST);
|
|
fs.chmodSync(DISPATCHER_DEST, 0o755);
|
|
|
|
// The Android manifest is a single file under assets/, outside PURGE_DIRS, so
|
|
// remove it explicitly: a stale manifest would fool helperManifest() into
|
|
// pointing at an APK that's no longer present.
|
|
fs.rmSync(ANDROID_MANIFEST_DEST, { force: true });
|
|
|
|
fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
|
|
|
|
// tree-sitter / tree-sitter-typescript are native addons (.node) esbuild cannot
|
|
// inline; external so the bundle `require()`s them at runtime from the published
|
|
// package's own dependencies.
|
|
//
|
|
// `electron` MUST stay external. Inlined, electron's binary-path lookup runs
|
|
// with __dirname = <install>/dist (no path.txt there) and throws "Electron
|
|
// failed to install correctly" at eval time. External keeps the runtime
|
|
// `require("electron")` in preview-window.ts resolving against the installed
|
|
// node_modules/electron with an intact __dirname.
|
|
//
|
|
// `@fails-components/webtransport` and its http3-quiche transport ship native
|
|
// addons (quiche, prebuilt .node binaries) that can't be inlined either; they
|
|
// are declared on @swmansion/argent, so external resolves them from
|
|
// node_modules/ at runtime.
|
|
//
|
|
// `dtrace-provider` MUST stay external. It is an OPTIONAL dependency of bunyan
|
|
// (the tool-server event-log logger), which hides it from bundlers with
|
|
// `require('dtrace-provider' + '')` in a try/catch — a failed require just
|
|
// disables the unused DTrace USDT probes. esbuild constant-folds that string
|
|
// back to a literal and then chokes on dtrace-provider's own dynamic native
|
|
// binding require. External restores bunyan's intent: the published package
|
|
// never declares it, so the require misses and bunyan nulls it out.
|
|
buildBundle({
|
|
entry: TOOLS_ENTRY,
|
|
out: OUT_FILE,
|
|
format: "cjs",
|
|
label: "tools server",
|
|
external: [
|
|
"tree-sitter",
|
|
"tree-sitter-typescript",
|
|
"electron",
|
|
"@fails-components/webtransport",
|
|
"@fails-components/webtransport-transport-http3-quiche",
|
|
"dtrace-provider",
|
|
],
|
|
});
|
|
|
|
// The remaining bundles are ESM so that:
|
|
// - installer: import.meta.dirname works at runtime (the dispatcher
|
|
// lazy-imports it, so its workspace dep needn't resolve in the published pkg);
|
|
// - MCP server: the @modelcontextprotocol/sdk ESM export paths resolve.
|
|
const ESM_BUNDLES = [
|
|
{ entry: INSTALLER_ENTRY, out: INSTALLER_OUT_FILE, label: "installer" },
|
|
{ entry: MCP_ENTRY, out: MCP_OUT_FILE, label: "MCP server" },
|
|
// node-pty is a native addon `argent lens` loads at runtime (the agent PTY
|
|
// proxy); esbuild can't inline a .node. Absent install → loadNodePty() returns
|
|
// null → lens falls back to a new terminal window.
|
|
{ entry: CLI_ENTRY, out: CLI_OUT_FILE, label: "CLI commands", external: ["node-pty"] },
|
|
];
|
|
for (const b of ESM_BUNDLES) {
|
|
buildBundle({ ...b, format: "esm" });
|
|
}
|
|
|
|
for (const a of ASSETS) {
|
|
copyAsset(a);
|
|
}
|
|
|
|
// `electron` stays external here too — tool-server's preview-window.ts resolves
|
|
// the executable via `require("electron")`. The bundled main.cjs lands next to
|
|
// the tool-server bundle so that same helper finds it at
|
|
// `path.join(__dirname, "preview-window", "main.cjs")`, without needing
|
|
// @argent/preview-window to be a published sibling pkg.
|
|
fs.mkdirSync(path.dirname(PREVIEW_WINDOW_OUT_FILE), { recursive: true });
|
|
buildBundle({
|
|
entry: PREVIEW_WINDOW_ENTRY,
|
|
out: PREVIEW_WINDOW_OUT_FILE,
|
|
format: "cjs",
|
|
label: "preview-window main",
|
|
external: ["electron", "node:*"],
|
|
});
|
|
|
|
// Require the darwin binary only when bundling ON darwin (the publish pipeline):
|
|
// a Linux contributor running `npm run pack` can't produce the macOS binary, so
|
|
// don't block them on its absence.
|
|
for (const platform of SUPPORTED_HOST_PLATFORMS) {
|
|
const binaryFile = simulatorServerFileName(platform);
|
|
const src = path.join(BIN_SRC_ROOT, platform, binaryFile);
|
|
const destDir = path.join(BIN_DIR, platform);
|
|
const dest = path.join(destDir, binaryFile);
|
|
if (fs.existsSync(src)) {
|
|
fs.mkdirSync(destDir, { recursive: true });
|
|
fs.copyFileSync(src, dest);
|
|
fs.chmodSync(dest, 0o755);
|
|
console.log(`✓ Copied simulator-server (${platform}) → ${path.relative(process.cwd(), dest)}`);
|
|
// Screen-sharing agent resources (jar + per-ABI .so) the `android_device`
|
|
// controller pushes to a connected phone. simulator-server resolves them
|
|
// relative to its working directory, which the spawn sets to its own
|
|
// platform dir. Optional: physical-device support degrades without them.
|
|
const resourcesSrc = path.join(BIN_SRC_ROOT, platform, "resources");
|
|
if (fs.existsSync(resourcesSrc)) {
|
|
const resourcesDest = path.join(destDir, "resources");
|
|
fs.cpSync(resourcesSrc, resourcesDest, { recursive: true });
|
|
console.log(
|
|
`✓ Copied screen-sharing agent resources (${platform}) → ${path.relative(process.cwd(), resourcesDest)}`
|
|
);
|
|
}
|
|
} else if (platform === "darwin" && process.platform === "darwin") {
|
|
throw new Error(
|
|
`simulator-server binary not found at ${src}.\n` +
|
|
`Run: bash scripts/download-simulator-server.sh`
|
|
);
|
|
} else {
|
|
console.warn(`⚠ simulator-server (${platform}) not found at ${src} — skipping`);
|
|
}
|
|
}
|
|
|
|
// The Android helper APK is the only Android native artifact left (the trace
|
|
// processor ships as WASM under assets/trace-processor/). Its filename is
|
|
// version-stamped from manifest.json's versionName (see bundledHelperApkPath());
|
|
// it is required at runtime, so a missing one throws.
|
|
const manifest = JSON.parse(fs.readFileSync(ANDROID_MANIFEST_SRC, "utf8"));
|
|
const apkName = `argent-android-devtools-${manifest.versionName}.apk`;
|
|
const apkSrc = path.join(ANDROID_APK_SRC_DIR, apkName);
|
|
if (fs.existsSync(apkSrc)) {
|
|
fs.copyFileSync(apkSrc, path.join(BIN_DIR, apkName));
|
|
console.log(`✓ Copied Android helper APK → ${path.relative(process.cwd(), BIN_DIR)}/${apkName}`);
|
|
} else {
|
|
throw new Error(
|
|
`Android helper APK not found at ${apkSrc}.\n` +
|
|
`Run: bash scripts/download-native-binaries.sh (fetches from argent-private-releases)\n` +
|
|
`or: bash packages/native-devtools-android/scripts/build.sh`
|
|
);
|
|
}
|