diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9c37cefa..030329037 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -197,6 +197,7 @@ jobs: - run: pnpm run check:deps - run: pnpm run check:errors - run: pnpm run check:patches + - run: pnpm run check:stale-refs test-unit: name: Unit Tests diff --git a/.lore.md b/.lore.md index 38c524ec6..026a0b936 100644 --- a/.lore.md +++ b/.lore.md @@ -8,10 +8,16 @@ * **Auth token env var override pattern: SENTRY\_AUTH\_TOKEN > SENTRY\_TOKEN > SQLite**: Auth token precedence in \`src/lib/db/auth.ts\`: \`SENTRY\_AUTH\_TOKEN\` > \`SENTRY\_TOKEN\` > SQLite OAuth token. \`getEnvToken()\` trims env vars (empty/whitespace = unset). \`AuthSource\` tracks provenance. \`ENV\_SOURCE\_PREFIX = "env:"\` — use \`.length\` not hardcoded 4. Env tokens bypass refresh/expiry. \`isEnvTokenActive()\` guards auth commands. Logout must NOT clear stored auth when env token active. \`runInteractiveLogin\` catches OAuth flow errors internally and returns falsy on failure; login command sets \`process.exitCode = 1\` and returns normally (does NOT reject). Tests expecting \`rejects.toThrow()\` will fail — assert via fetch-call inspection instead. \`requestDeviceCode\` requires \`SENTRY\_CLIENT\_ID\` env var. -* **Binary build pipeline: esbuild → fossilize → Node SEA (replacing Bun.build compile)**: Binary build pipeline: \`src/bin.ts → \[esbuild CJS, node24 target] → dist-build/bin.js → \[fossilize --no-bundle] → Node SEA binary → \[binpunch ICU hole-punch] → gzip\`. Strip debug symbols handled INSIDE fossilize (as of fossilize #16) — fossilize strips the copied binary BEFORE postject injection. Strip MUST happen before injection — after SEA injection, \`strip\` fails ('section .text can't be allocated in segment 2'). macOS: \`strip -x\` on unsigned copy (fossilize unsigns before copy); cross-strip from Linux silently fails (caught). Windows: skipped (no debug symbols). NODE\_VERSION='lts'. ALL\_TARGETS: darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-x64. Post-process: rename \`sentry-win-x64.exe\`→\`sentry-windows-x64.exe\`. UPX RULED OUT — destroys ELF notes. \`FOSSILIZE\_SIGN=y\` on push to main/release. Gzip only when \`RELEASE\_BUILD=1\`. Sourcemap uploaded to Sentry, never shipped. +* **Binary build pipeline: esbuild → fossilize → Node SEA (replacing Bun.build compile)**: Binary build pipeline: \`src/bin.ts → \[esbuild CJS, node24 target] → dist-build/bin.js → \[fossilize --no-bundle] → Node SEA binary → \[binpunch ICU hole-punch] → gzip\`. Strip debug symbols handled INSIDE fossilize (as of fossilize 0.7.0) — fossilize strips the copied binary BEFORE postject injection. Strip MUST happen before injection — after SEA injection, \`strip\` fails ('section .text can't be allocated in segment 2'). macOS: \`strip -x\` on unsigned copy; cross-strip from Linux silently fails (caught). Windows: skipped (no debug symbols). NODE\_VERSION='lts'. ALL\_TARGETS: darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-x64 + musl variants. Post-process: rename \`sentry-win-x64.exe\`→\`sentry-windows-x64.exe\`. UPX RULED OUT — destroys ELF notes. \`FOSSILIZE\_SIGN=y\` on push to main/release. Gzip only when \`RELEASE\_BUILD=1\`. \`stripCachedNodeBinaries()\` removed from \`script/build.ts\` — superseded by fossilize 0.7.0. -* **Binary size breakdown: 94.5% is Node.js runtime — bundled code is ~6.3 MiB**: Binary composition (linux-x64, Node 24 LTS): Node.js runtime=121 MiB (ships with debug symbols). \`strip --strip-unneeded\` → 101 MiB (-17 MiB raw, -4 MiB compressed). Strip built into fossilize #16 — happens on the copied binary BEFORE postject injection. After strip+SEA+binpunch: ~108 MiB raw, ~30 MiB gzip (vs 125 MiB / 34 MiB unstripped). .rodata=52.5 MB: V8 snapshot ~12 MB, ICU full-icu data ~28 MB. UPX compresses to 25 MiB but DESTROYS ELF notes — ruled out. Slim Node build flags: \`--with-intl=small-icu\`, \`--without-inspector\`, \`--without-sqlite\`, \`--without-npm\`, \`--enable-lto\` (high CI cost, deferred). Cross-compilation from Linux to darwin not officially supported. Final vs Bun: download 30 MiB (Bun: 32 MiB), \`--version\` ~1.0s (Bun: ~1.9s), completions ~0.16s (Bun: ~0.42s). +* **Binary size breakdown: 94.5% is Node.js runtime — bundled code is ~6.3 MiB**: Binary composition (linux-x64, Node 24 LTS): Node.js runtime=121 MiB (ships with debug symbols). \`strip --strip-unneeded\` → 99 MiB (-17 MiB raw, -4 MiB compressed). Strip built into fossilize 0.7.0 — happens on the copied binary BEFORE postject injection. After strip+SEA+binpunch: ~108 MiB raw, ~30 MiB gzip (vs 125 MiB / 34 MiB unstripped). .rodata=52.5 MB: V8 snapshot ~12 MB, ICU full-icu data ~28 MB. UPX compresses to 25 MiB but DESTROYS ELF notes — ruled out. \`--with-intl=small-icu\` saves ~26-28 MiB (biggest win from custom build); \`--without-lief\` BREAKS SEA; \`--without-sqlite\` BREAKS CLI; \`--disable-single-executable-application\` BREAKS EVERYTHING. Custom build deferred — poor cost/benefit (~3.5h build vs 5min fossilize). Final vs Bun: download 30 MiB (Bun: 32 MiB), \`--version\` ~1.0s (Bun: ~1.9s), completions ~150ms (Bun: ~180ms). + + +* **check-fragments.ts: validates fragment files against actual route names**: \`script/check-fragments.ts\`: validates fragment files against actual route names (Check 1-4) AND validates subcommand coverage within fragments (Check 5). Check 5: for each route with >1 command, verifies fragment mentions each subcommand via a heading (outside fenced code blocks) or \`sentry \ \\` code reference. Default commands handled: if fragment contains bare \`sentry \\`, the default command is covered. Default commands detected from route map (\`defaultCommand\` field in route index files). Fenced code block content stripped before heading scan to avoid false positives from bash comments. Warnings by default; \`--strict\` makes them errors. Run via \`pnpm run check:fragments\`. CI \`check-generated\` job triggers when \`changes.outputs.skill == 'true'\`. + + +* **check:stale-refs: generic toolchain consistency scanner derived from package.json**: \`script/check-stale-references.ts\`: reads \`packageManager\` from \`package.json\` (e.g., \`pnpm@10.11.0\`), derives stale PMs dynamically, and scans dev-facing docs/scripts for stale \`\ run\`, \`\ remove\`, \`\ add -d\` commands and \`requires \\`/\`\ installed\` prerequisite prose. Excludes: user-facing install instructions (fenced code blocks with \`install -g\`/\`add -g\`), the check script itself, and \`node\_modules/\`. Added to CI lint job. \*\*Generic\*\*: if project migrates from pnpm to yarn, changing \`packageManager\` in \`package.json\` auto-flags all \`pnpm run\` references in dev docs — no manual pattern updates needed. Trap: script must exclude itself from scanning or its own JSDoc examples trigger false positives. * **CI build-binary matrix: PR=2 targets, main/release=7 targets**: \`ci.yml\` \`build-binary\` job: PRs build only \`linux-x64\` (can-test:true) + \`linux-x64-musl\` (can-test:false). Main/release/workflow\_call builds all 7: darwin-arm64, linux-x64, linux-x64-musl, windows-x64, darwin-x64, linux-arm64, linux-arm64-musl. Build command: \`bun run build --target ${{ matrix.target }}\`. \`test-e2e\` downloads \`sentry-linux-x64\` artifact and sets \`SENTRY\_CLI\_BINARY\`. \`build-npm\` matrix: Node 22+24; smoke test is only \`node dist/bin.cjs --help\`. \`SENTRY\_CLIENT\_ID\` defaults to \`ci-fork-pr-dummy\` for fork PRs (can't read repo vars). Gzip artifacts only on non-PR runs. \`FOSSILIZE\_SIGN=y\` on push to main/release. @@ -31,6 +37,18 @@ * **E2E test infrastructure: fixture.ts, helpers.ts, mocks/, and test:e2e script**: \`test/fixture.ts\`: \`getCliCommand()\` returns \`\[SENTRY\_CLI\_BINARY]\` or \`\[process.execPath, 'run', 'src/bin.ts']\`. \`createE2EContext(configDir, serverUrl)\` sets env \`SENTRY\_AUTH\_TOKEN: ''\`, \`SENTRY\_TOKEN: ''\`, \`SENTRY\_CLI\_NO\_TELEMETRY: '1'\`, \`SENTRY\_URL: serverUrl\`. \`setAuthToken(token)\` calls \`dbSetAuthToken(token, undefined, undefined, { host: serverUrl })\` then \`closeDatabase()\`, scoped to mock server URL for host-scoping fetch-layer guard. \`test/mocks/server.ts\`: \`createMockServer(routes, options?)\` uses Node \`http.createServer\` (migrated from \`Bun.serve\`). \`test/mocks/multiregion.ts\`: \`createMultiRegionMockServer()\` — US+EU regions + control silo; \`selfHostedMode\`, \`singleRegionMode\`. \`test:e2e\` runs WITHOUT \`--isolate --parallel\`. \`test:unit\` runs WITH \`--isolate --parallel\`. \`telemetry-exit.test.ts\` verifies \`@sentry/core\` patch adds \`.unref()\` to flush timers. Project uses vitest (migrated from bun:test); \`vitest.config.ts\` at repo root. + +* **generate-command-docs.ts: fragment system, markers, and gitignored output**: \`script/generate-command-docs.ts\` (463 lines): bootstraps stub \`src/generated/skill-content.ts\` if missing, then dynamically imports \`src/app.js\`, \`src/lib/introspect.js\`, \`src/lib/env-registry.js\`. Generates per-route pages to \`docs/src/content/docs/commands/{name}.md\` (gitignored). Each page = auto-generated content + \`\\` marker + optional fragment from \`docs/src/fragments/commands/{name}.md\` (committed). \`GLOBAL\_FLAG\_NAMES = \["json","fields","help","helpAll","log-level"]\` excluded from per-command docs. \`SKIP\_ROUTES = \["help"]\`. Also generates \`index.md\` (commands table) and \`configuration.md\` (env vars + \`docs/src/fragments/configuration.md\`). Cleans up legacy \`docs/src/content/docs/commands/cli/\` subdirectory on each run. + + +* **generate-docs-sections.ts: in-place marker injection into committed files**: \`script/generate-docs-sections.ts\` (555+ lines): injects auto-generated content into committed files between named marker pairs. Marker styles: HTML \`\\` (\`.md\`); MDX \`{/\* GENERATED:START name \*/}\` (\`.mdx\`). \`--check\` flag: dry-run, exits 1 if stale. 13 sections across 5 files: \`contributing.md\` (project-structure, dev-prereq, build-commands), \`DEVELOPMENT.md\` (oauth-scopes, dev-env-vars, dev-prereq, build-toolchain), \`self-hosted.md\` (oauth-scopes, self-hosted-env-vars), \`README.md\` (dev-prereq, library-prereq, dev-scripts), \`getting-started.mdx\` (platform-support). Version extractors (\`extractPnpmVersion\`, \`extractNodeVersion\`) \*\*throw on mismatch\*\* — no silent fallbacks. No Bun references remain. CI \`check-generated\` job runs with \`--check\` flag. + + +* **generate-docs-sections.ts: project-structure tree rendering invariant**: In \`generateProjectStructure()\` (line 178 comment): groups (route directories) always use \`├──\` prefix regardless of position — because standalones always follow groups. Standalone entries include \`help.ts\` (added manually before sort); last standalone uses \`└──\`, others use \`├──\`. Both groups and standalones sorted alphabetically within their sections. Output is a fenced code block with \`cli/\` tree. + + +* **generate:docs pipeline: 4-script sequence, prerequisites, and output ownership**: Master orchestrator: \`generate:docs\` runs 4 scripts in sequence: (1) \`generate:parser\` → \`script/generate-parser.ts\`, (2) \`generate:command-docs\` → \`script/generate-command-docs.ts\`, (3) \`generate:skill\` → \`script/generate-skill.ts\`, (4) \`generate:docs-sections\` → \`script/generate-docs-sections.ts\`. Prerequisite for: \`dev\`, \`build\`, \`build:all\`, \`bundle\`, \`typecheck\`, \`test:unit\`, \`test:changed\`, \`test:e2e\`. Output ownership: \`docs/src/content/docs/commands/\` and \`docs/src/content/docs/configuration.md\` are gitignored (fully generated). \`docs/src/fragments/\` files are committed source of truth (hand-written custom content). \`DEVELOPMENT.md\`, \`README.md\`, \`contributing.md\`, \`self-hosted.md\`, \`getting-started.mdx\` are committed but have in-place injected sections between named markers. + * **Host-scoped token model: auth.host column + three-layer enforcement**: Host-scoped token model (schema v16): every token bound to issuing host via \`auth.host\` column, lazy-migrated from boot-env. Trust established ONLY via \`sentry auth login --url\` or shell-exported \`SENTRY\_HOST\`/\`SENTRY\_URL\` at boot — \`.sentryclirc\` URL never a trust source. Three enforcement layers: (1) \`applySentryUrlContext\` throws on URL-arg mismatch; (2) \`applySentryCliRcEnvShim\` throws on rc-url mismatch (auth login/logout bypass via \`skipUrlTrustCheck\`); (3) fetch-layer \`isRequestOriginTrusted\`. Region trust: in-process Set in \`db/regions.ts\`, auto-synced by \`setOrgRegion(s)\`. \`clearTrustedHostState\` must NOT clear login anchor (breaks IAP re-auth). \`HostScopeError\` has overloads \`(message)\` and \`(source, destinationUrl, tokenHost)\`. Test helpers: \`resetHostScopingState()\` bundles \`resetEnvTokenHostForTesting\` + \`resetLoginTrustAnchorForTesting\` + \`resetTrustedRegionUrlsForTesting\`. E2E: pass \`--url ${ctx.serverUrl}\` to \`auth login --token\`; \`SENTRY\_URL\` alone doesn't anchor. Multi-region tests need \`registerTrustedRegionUrls\`. @@ -81,8 +99,11 @@ * **All view subcommands should use \ \ positional pattern**: All \`\* view\` subcommands use \`\ \\` positional pattern (Intent-First Correction UX): target is optional \`org/project\`. Use opportunistic arg swapping with \`log.warn()\` when args are wrong order — when intent is unambiguous, do what they meant. Normalize at command level, keep parsers pure. Model after \`gh\` CLI. Exception: \`auth\` uses \`defaultCommand: "status"\` (no viewable entity). Routes without defaults: \`cli\`, \`sourcemap\`, \`repo\`, \`team\`, \`trial\`, \`release\`, \`dashboard/widget\`. + +* **Migrated to node**: Migrated to Node.js (from Bun). Migration complete as of fossilize 0.7.0 update. Stack: pnpm, vitest, tsx, Node SEA via fossilize. PRs #1017, #1018, #1019 on getsentry/cli. Benchmarks vs v0.34.0 (Bun): download 32MB→30MB, startup ~1s both, shell completions 180ms→150ms. All macOS binaries signed+notarized. fossilize handles SEA builds with V8 code cache on linux+macOS, strips debug symbols automatically. \`bun.lock\` deleted, \`vitest.config.ts\` added, all test files migrated to vitest. \`script/build.ts\` uses fossilize (\`--no-bundle\`) with esbuild for bundling — does NOT use \`Bun.build({ compile: true })\`. + -* **Node.js slim build flags for SEA binary size reduction**: Node.js configure.py size-reduction flags relevant to SEA builds: \`--with-intl=small-icu\` (English-only ICU, no download needed; saves ~20 MB .rodata); \`--with-intl=none\` (disables Intl/String.normalize entirely); \`--without-inspector\` (removes V8 inspector protocol); \`--without-sqlite\` (removes SQLite + Web Storage API); \`--without-npm\`/\`--without-corepack\` (already excluded by default); \`--enable-lto\` (LTO, GCC 5.4.1+ or Clang 3.9.1+); \`--without-node-snapshot\`/\`--without-node-code-cache\` (removes V8 snapshot/code cache). AVOID: \`--disable-single-executable-application\` (removes SEA support), \`--v8-lite-mode\` (no JIT, much slower), \`--without-ssl\` (breaks crypto/https). Cross-compilation from Linux to darwin NOT officially supported — darwin builds require macOS runners with Xcode >= 16.4. +* **Node.js slim build flags for SEA binary size reduction**: Node.js configure.py size-reduction flags for SEA builds: \`--with-intl=small-icu\` (English-only ICU, saves ~26-28 MiB — biggest win; CLI uses hardcoded en-US/sv-SE locales, safe); \`--with-intl=none\` (saves ~28-30 MiB but breaks \`Intl.NumberFormat\`/\`String.normalize()\` — NOT safe for this CLI); \`--without-inspector\` saves ~2-4 MiB; \`--without-amaro\` saves ~0.5 MiB; \`--v8-disable-maglev\` saves ~1-2 MiB; \`--enable-lto\` saves ~3-5 MiB. AVOID: \`--without-ssl\` (breaks HTTPS), \`--without-lief\` (BREAKS SEA), \`--without-sqlite\` (BREAKS CLI — uses node:sqlite), \`--disable-single-executable-application\` (BREAKS EVERYTHING), \`--v8-lite-mode\` (10x slower). Custom build deferred indefinitely — requires 5 native CI runners, ~3.5h cold build vs 5min fossilize. Cross-compilation from Linux to darwin NOT officially supported. * **Raw markdown output for non-interactive terminals, rendered for TTY**: Markdown-first output pipeline: custom renderer in \`src/lib/formatters/markdown.ts\` walks \`marked\` tokens to produce ANSI-styled output. Commands build CommonMark using helpers (\`mdKvTable()\`, \`mdRow()\`, \`colorTag()\`, \`escapeMarkdownCell()\`, \`safeCodeSpan()\`) and pass through \`renderMarkdown()\`. \`isPlainOutput()\` precedence: \`SENTRY\_PLAIN\_OUTPUT\` > \`NO\_COLOR\` > \`FORCE\_COLOR\` > \`!isTTY\`. \`--json\` always outputs JSON. Colors defined in \`COLORS\` object in \`colors.ts\`. Tests run non-TTY so assertions match raw CommonMark; use \`stripAnsi()\` helper for rendered-mode assertions. @@ -104,6 +125,18 @@ * **Bun 1.3.11 tty.ReadStream leaks libuv handle — process.stdin.unref is undefined**: Bun 1.3.11 macOS TTY bug: \`process.stdin\` via kqueue \`EVFILT\_READ\` fails to deliver keystrokes when fd 0 is inherited via \`exec bin \ +* **check:fragments only validates file existence — not subcommand coverage depth**: RESOLVED in PR #1024. \`script/check-fragments.ts\` now has Check 5: for each route with >1 command, verifies the fragment mentions each subcommand via a heading (outside fenced code blocks) or \`sentry \ \\` code reference. Default commands (e.g., \`local serve\`) are handled — if the fragment contains \`sentry \\` bare, the default command is considered covered. Default commands detected from route map \`defaultCommand\` field. Fenced code block content stripped before heading scan to avoid bash-comment false positives. Warnings (not errors) by default; \`--strict\` flag makes them errors. + + +* **dashboard revisions/restore and issue events subcommands are undocumented in fragment files**: RESOLVED in PR #1024. \`docs/src/fragments/commands/dashboard.md\` now documents \`revisions\` and \`restore\`. \`docs/src/fragments/commands/issue.md\` now documents \`events\` and \`@latest\`/\`@most\_frequent\` selectors. \`docs/src/fragments/commands/cli.md\` now documents \`defaults\` and \`import\`. \`check:fragments\` (Check 5) now validates subcommand coverage within fragment files — not just file existence. When adding new subcommands, always update the corresponding fragment in \`docs/src/fragments/commands/\` AND run \`pnpm run check:fragments\` to verify coverage. + + +* **DEVELOPMENT.md hand-written prose is not covered by any staleness check**: RESOLVED in PR #1024. \`DEVELOPMENT.md\` hand-written prose is now wrapped in \`GENERATED:START/END\` markers: \`dev-prereq\` (lines 4-7) and \`build-toolchain\` (lines 91-97). These sections are now auto-generated from \`package.json\` by \`generate-docs-sections.ts\` and validated by \`check:docs-sections --check\`. The only remaining non-generated prose in \`DEVELOPMENT.md\` is the OAuth app setup instructions and architecture description — these don't reference toolchain versions. \`generate-docs-sections.ts\` no longer contains any Bun references; \`extractPnpmVersion()\` and \`extractNodeVersion()\` throw on mismatch. + + +* **generate-docs-sections.ts still references Bun — extractBunVersion() silently falls back to hardcoded '1.3'**: RESOLVED in PR #1024. \`generate-docs-sections.ts\` previously had \`BUN\_VERSION\_RE\` and \`extractBunVersion()\` that silently returned hardcoded \`'1.3'\` when \`packageManager\` was \`pnpm@10.11.0\`. Fixed: replaced with \`extractPnpmVersion()\` and \`extractNodeVersion()\` that \*\*throw on mismatch\*\* instead of silently falling back. \`generateDevPrereq()\`, \`generateDevPrereqContributing()\`, \`generateLibraryPrereq()\` now reference Node.js + pnpm. \`DEVELOPMENT.md\` lines 5 and 91 are now wrapped in \`GENERATED:START/END\` markers so they can't drift again. No Bun references remain in the script. + * **MastraClient has no dispose API — use AbortController for cleanup**: MastraClient has no \`close()\`/\`dispose()\` API — cleanup via \`ClientOptions.abortSignal\` (constructor) or per-prompt \`signal\`. Without explicit abort, Bun's fetch dispatcher keep-alive sockets hold the event loop alive past natural exit. Pattern in \`src/lib/init/wizard-runner.ts\`: create \`AbortController\` per \`runWizard\`, pass \`abortSignal: controller.signal\` to \`new MastraClient(...)\`, abort via \`using \_ = { \[Symbol.dispose]: () => controller.abort() }\`. Custom \`fetch\` wrapper must preserve \`init.signal\` via spread. Tests capture \`ClientOptions\` via \`spyOn(MastraClient.prototype, 'getWorkflow').mockImplementation(function() { capturedOpts.push(this.options); ... })\`. @@ -117,7 +150,7 @@ * **SQLite transaction() ROLLBACK can throw, discarding original error**: (gotcha) SQLite transaction ROLLBACK error-swallowing trap: In \`src/lib/db/sqlite.ts\`, \`transaction()\` catches errors and runs \`this.db.exec('ROLLBACK')\`. If ROLLBACK itself throws, the original error is lost. Fix: \`const origErr = e; try { this.db.exec('ROLLBACK'); } catch (rbErr) { log.debug(...); } throw origErr;\` -* **strip fails on Node SEA binaries — must strip BEFORE fossilize injection**: Strip debug symbols must happen BEFORE fossilize SEA injection. Trap: \`strip --strip-unneeded\` on a plain Node binary saves ~17 MiB and still runs — looks like it should work on the final SEA binary too. But after postject injects the SEA blob, \`strip\` fails: 'section .text can't be allocated in segment 2'. Fix: as of fossilize #16, stripping is built into fossilize itself — it strips the copied binary (already unsigned for macOS/Windows) BEFORE calling postject. Cross-strip from Linux to macOS silently fails (caught); native macOS runners strip correctly with \`strip -x\`. Windows skipped (no debug symbols). No need for \`stripCachedNodeBinaries\` in the CLI build script — fossilize handles it. +* **strip fails on Node SEA binaries — must strip BEFORE fossilize injection**: Strip debug symbols must happen BEFORE fossilize SEA injection. Trap: \`strip --strip-unneeded\` on a plain Node binary saves ~17 MiB and still runs — looks like it should work on the final SEA binary too. But after postject injects the SEA blob, \`strip\` fails: 'section .text can't be allocated in segment 2'. Fix: as of fossilize 0.7.0, stripping is built into fossilize itself — it strips the copied binary (already unsigned for macOS/Windows) BEFORE calling postject. Cross-strip from Linux to macOS silently fails (caught); native macOS runners strip correctly with \`strip -x\`. Windows skipped (no debug symbols). \`stripCachedNodeBinaries()\` was removed from \`script/build.ts\` in fossilize 0.7.0 update — fossilize handles it natively. * **UPX destroys ELF notes — incompatible with Node SEA binaries**: Trap: UPX compresses Node binaries from 99 MiB to 25 MiB and the compressed binary still runs — looks like a huge win. But UPX rewrites the entire ELF structure: original binary has 2 ELF notes (NT\_GNU\_BUILD\_ID + NT\_GNU\_ABI\_TAG), UPX'd binary has 0 notes and 0 sections. NODE\_SEA\_BLOB is stored as an ELF note — UPX destroys it. Fix: use \`strip --strip-unneeded\` instead, BUT only on the plain Node binary BEFORE fossilize SEA injection. After injection, \`strip\` fails with 'section .text can't be allocated in segment 2' — the SEA blob corrupts the ELF section-to-segment mapping. Strip the \`.node-cache/\` binaries before calling fossilize. Saves ~17 MB raw / ~4 MB compressed. Strip is idempotent — already-stripped binaries are unchanged. Recommended order: strip cached Node → fossilize (inject) → binpunch → gzip. @@ -157,6 +190,9 @@ * **Sentry SDK tree-shaking patches must be regenerated via bun patch workflow**: Sentry SDK tree-shaking via bun patch: \`patchedDependencies\` in \`package.json\` strips unused exports from \`@sentry/core\` and \`@sentry/node-core\`. Non-light root of \`@sentry/node-core\` pulls uninstalled \`@opentelemetry/instrumentation\` — \*\*always import from \`@sentry/node-core/light\`\*\* (subpaths: \`.\`, \`./light\`, \`./light/otlp\`, \`./init\`, \`./loader\`, \`./import\`). No supported import for \`HttpsProxyAgent\`. Bumping SDK: remove old patches, \`rm -rf ~/.bun/install/cache/@sentry\`, \`bun install\`, \`bun patch @sentry/core\`, edit, \`bun patch --commit\`; repeat for node-core. Preserved: \`\_INTERNAL\_safeUnref\`, \`\_INTERNAL\_safeDateNow\`, \`nodeRuntimeMetricsIntegration\`. Before stripping any core export, grep \`node-core/build/{cjs,esm}/light/sdk.js\` for runtime usage (e.g. \`spanStreamingIntegration\` when \`traceLifecycle === 'stream'\`). Remove \`.bun-tag-\*\` hunks from generated patches. Manual \`git diff\` patches fail. + +* **setup.ts bestEffort() wrapper: post-install steps must never crash setup**: \`src/commands/cli/setup.ts\` \`bestEffort(stepName, fn)\` wraps non-essential post-install steps (recording install info, shell completions, agent skills) in try/catch. On failure: calls \`warn(stepName, error)\` + \`captureException(error, { level: 'warning', tags: { 'setup.step': stepName } })\`. These steps must NEVER crash setup — enforced by \`bestEffort()\`. \`runConfigurationSteps()\` applies \`bestEffort()\` independently to all 4 steps. Install dir priority: (1) \`$SENTRY\_INSTALL\_DIR\`, (2) \`~/.local/bin\` if exists+in PATH, (3) \`~/bin\` if exists+in PATH, (4) \`~/.sentry/bin\` fallback. Welcome message only on fresh install (not upgrades). + * **Shared pagination infrastructure: buildPaginationContextKey and parseCursorFlag**: Pagination infrastructure + org flag injection: Bidirectional pagination via cursor stack in \`src/lib/db/pagination.ts\`. \`resolveCursor(flag, key, contextKey)\` maps keywords (next/prev/first/last) to \`{cursor, direction}\`. \`advancePaginationState\` manages stack — back-then-forward truncates stale entries. Critical: \`resolveCursor()\` must be called INSIDE \`org-all\` override closures, not before \`dispatchOrgScopedList\`. \`issue list --limit\` is global total: \`fetchWithBudget\` Phase 1 divides evenly, Phase 2 redistributes surplus. \`trimWithProjectGuarantee\` ensures ≥1 issue per project. Compound cursor (pipe-separated) enables \`-c last\` for multi-target pagination. JSON output wraps in \`{ data, hasMore }\` with optional \`errors\` array. @@ -171,9 +207,18 @@ * **Always check with user before taking irreversible or external actions**: When the user asks the assistant to perform actions that affect external systems (sending messages, merging PRs, deploying, etc.), they explicitly require confirmation before proceeding. The user states 'check with user before sending any messages' or similar directives. The assistant should always pause and present a plan or draft to the user for approval before executing any action that cannot be easily undone — such as sending communications, merging code, or triggering external workflows. This applies even when the user has asked the assistant to handle the task end-to-end. + +* **Always clarify that the repo uses plain git (not jj) when jj commands fail**: When a jj command fails with 'no jj repo in .', the user consistently clarifies that the repo is a plain git repo and that jj's 'never fails on conflict' behavior is being referenced conceptually — meaning conflicts should be recorded/resolved rather than aborting operations. The agent should: (1) fall back to git commands immediately without retrying jj, (2) handle merge conflicts by stashing, pulling, and resolving (e.g., \`git checkout --theirs\` for files like \`.lore.md\`), and (3) not attempt \`jj git init\` or any jj initialization. This pattern appears at the start of every build session. + * **Always commit and push after tests pass locally**: When a local test run completes successfully, the user consistently moves immediately to committing the changes and pushing to the remote branch. This pattern applies regardless of test suite size or duration. The assistant should proactively plan or execute a commit+push step as the natural next action after a passing test run, without waiting to be asked. Commit messages should follow conventional commit format (e.g., 'fix:', 'refactor:'). If warnings are present in the test output, they are noted but do not block the commit/push flow. + +* **Always compare PR branch against main before reviewing changes**: When reviewing a PR, the user consistently wants to understand exactly what changed in the PR branch versus main before diving into the content. This means fetching the remote branch if not available locally, running \`git log main..origin/\\` to see commits, and \`git diff\` (with stat) to understand the scope of changes. The user explicitly frames this as needing to know 'what changes were made vs what actually exists on main.' Always establish this baseline diff context first before analyzing or discussing PR content. + + +* **Always create a dedicated branch when updating fossilize versions**: When a new version of fossilize is released, always create a branch named \`chore/fossilize-{version}\` tracking origin/main, update the dependency, remove any functionality now handled natively by fossilize (e.g., \`stripCachedNodeBinaries()\` removed in 0.7.0), verify the build succeeds, then commit with \`chore: update fossilize to X.Y.Z\`. Follow this exact pattern: branch → update dep → remove superseded code → build verify → commit → PR. + * **Always explore e2e test infrastructure thoroughly before debugging or modifying tests**: When approaching e2e test work, always explore the full infrastructure before making changes: \`test/e2e/\` (14 files: api, auth, bundle, completion, delta-upgrade, event, issue, library, log, multiregion, project, skill-eval, telemetry-exit, trace), \`test/fixture.ts\` (getCliCommand, runCli, createE2EContext), \`test/helpers.ts\` (useTestConfigDir, useEnvSandbox, resetHostScopingState, mintSntrysToken, extractFetchUrl), \`test/mocks/\` (server.ts, routes.js, multiregion.ts), \`src/bin.ts\`, \`src/cli.ts\`. Key: \`getCliCommand()\` returns \`\[SENTRY\_CLI\_BINARY]\` if set, else \`\[process.execPath, 'run', 'src/bin.ts']\`. \`createE2EContext.run()\` sets \`SENTRY\_AUTH\_TOKEN: ''\`, \`SENTRY\_TOKEN: ''\`, \`SENTRY\_CLI\_NO\_TELEMETRY: '1'\`. \`test:e2e\` runs without \`--isolate --parallel\`. Map full infrastructure before proposing fixes. @@ -199,7 +244,7 @@ * **Always match user's casual, short, stream-of-consciousness messaging style**: When sending messages on the user's behalf (e.g., via Beeper or other chat tools), always read further back in the conversation history to understand existing relationships and context before composing. Messages must be short, casual, and match the user's natural style — not formal, long, or structured. Never reintroduce people who already know each other. If unsure about the relationship between recipients, check prior chat history first. The user will explicitly correct style mismatches and flag social context errors as serious mistakes. -* **Always migrate Bun-specific APIs and tooling to Node.js equivalents**: Migrating from Bun to Node.js/pnpm. Replace Bun-specific APIs: \`Bun.spawn\`→\`node:child\_process\`, \`Bun.sleep\`→\`node:timers/promises\`, \`bun:sqlite\`→\`node:sqlite\`, \`bun run\`→\`pnpm run\`/\`tsx\`, \`bun.lock\`→pnpm lockfile. All packages in \`devDependencies\` (never \`dependencies\`). Exception: \`script/build.ts\` uses \`Bun.build()\` and stays on Bun; \`build-binary\` CI job retains \`setup-bun\`. \`script/bundle.ts\` (npm bundle) uses esbuild via tsx and is Node-native. After each migration phase, ensure lint and tests pass before committing. Migration is largely complete as of main branch (bun.lock deleted, vitest.config.ts added, all test files migrated to vitest). +* **Always migrate Bun-specific APIs and tooling to Node.js equivalents**: Migrating from Bun to Node.js/pnpm. Replace Bun-specific APIs: \`Bun.spawn\`→\`node:child\_process\`, \`Bun.sleep\`→\`node:timers/promises\`, \`bun:sqlite\`→\`node:sqlite\`, \`bun run\`→\`pnpm run\`/\`tsx\`, \`bun.lock\`→pnpm lockfile. All packages in \`devDependencies\` (never \`dependencies\`). Exception: \`script/build.ts\` uses fossilize (not \`Bun.build\`) and stays on Bun for the build-binary CI job. \`script/bundle.ts\` (npm bundle) uses esbuild via tsx and is Node-native. \`packageManager\` field in package.json: \`pnpm@10.11.0\`. After each migration phase, ensure lint and tests pass before committing. Migration is complete as of main branch (bun.lock deleted, vitest.config.ts added, all test files migrated to vitest). * **Always monitor CI after push and fix all failures before considering work done**: After pushing code or merging PRs, the user expects the assistant to actively monitor CI results, wait for all checks (including bots like Sentry Seer and Cursor BugBot) to complete, fix any failing jobs, and address all unresolved comments (from both bots and humans). The cycle repeats until CI is fully green and no unresolved comments remain. Use \`gh run view --log-failed\` and \`gh pr checks\` to identify failures. Do not consider a task complete until this full cycle is done. @@ -207,6 +252,15 @@ * **Always pause current tasks to resolve architectural blockers before implementation**: When the user discovers that a foundational architectural assumption is wrong or a planned approach has a critical flaw (e.g., a security boundary that doesn't hold, a rejected integration pattern), they immediately reprioritize: they pause the current implementation task and require the architecture to be redesigned first before any further implementation proceeds. This applies even mid-task. The user explicitly calls out the rejected approach and states the new design direction, expecting the assistant to treat the architectural decision as a prerequisite gate before resuming the original work. + +* **Always perform thorough quality reviews of PRs distinguishing blocking vs non-blocking issues**: When reviewing PRs, the user expects a structured quality review that: (1) categorizes issues as BLOCKING vs non-blocking/low-priority, (2) verifies each claimed change against the actual codebase, (3) flags LLM-generated planning artifacts (e.g., DOCS-AUDIT.md) that violate repo conventions as blocking issues, (4) checks for missed/inconsistent changes across all affected files, and (5) confirms correct changes are working as intended. The user wants specific file paths and line numbers cited for each issue. Non-blocking issues should still be reported but clearly distinguished from blockers. + + +* **Always plan systemic fixes with structured multi-problem breakdowns before implementation**: When the user identifies documentation or tooling issues, they consistently organize them as numbered problems with precise file locations, line numbers, and root causes before any code is written. They expect the assistant to engage at the planning level first — proposing detection strategies, fix approaches, and tradeoffs — and to consolidate related problems (e.g., merging overlapping tasks) rather than treating each in isolation. Plans are written to files and iterated on. Implementation only follows after the plan is agreed upon. The user prefers systemic/automated fixes (e.g., derive patterns from package.json) over one-off patches. + + +* **Always prefer systemic/automated solutions over one-off fixes**: When the user identifies errors, gaps, or problems, they explicitly direct the assistant to create or fix systems that prevent the entire class of errors in the future, rather than applying isolated one-off fixes. This applies especially when evaluating code quality, reviewing PRs, or addressing bugs. The user wants automated checks (e.g., CI steps, lint rules, scripts) and general solutions that scale, not patches that only address the immediate symptom. When planning or executing fixes, always ask: 'Can this be automated or systematized?' and prefer that approach. + * **Always provide documentation/context dumps before requesting technical analysis**: The user consistently pastes large reference documents, source files, or full code listings into the conversation before asking for analysis or implementation work. This applies when exploring new APIs (e.g., Node.js SEA docs), auditing codebases, or planning migrations. The user expects the assistant to extract key insights, identify problems, and propose solutions directly from the pasted material — not from general knowledge alone. When responding, prioritize findings grounded in the specific pasted content, cite line numbers or section names where possible, and proactively surface implications the user may not have explicitly asked about. @@ -216,6 +270,9 @@ * **Always pursue native runner builds to enable platform-specific optimizations**: When the user discovers that cross-compilation from a non-native runner is blocking optimizations (e.g., code cache, codesigning, strip+resign), they consistently push to move builds to native runners for each target platform. macOS targets require macOS runners (Xcode >= 16.4 for Node.js builds; \`strip -x\` on Mach-O requires re-codesigning). Linux cross-compilation to darwin is NOT officially supported by Node.js. The user will switch to per-platform native builds if bytecode (\`useCodeCache\`) yields meaningful startup improvement. Always check whether current CI runners match the target platform and propose native runner alternatives when they don't. + +* **Always read and document full file details before proceeding with analysis or implementation**: When exploring a codebase, the user consistently reads files in full and records comprehensive structured details: exact line counts, all imports, every exported type/interface with their fields, all constants, all function signatures with their logic, and any notable comments or assertions. This applies to both source files and build/tooling scripts. The user expects the assistant to capture and reference these details precisely rather than summarizing loosely. When examining related files (e.g., a module and its consumers), the user reads each completely before drawing conclusions. This pattern applies during architecture exploration, feature planning, and documentation generation tasks. + * **Always reference external tools and prior art when exploring build/size optimization approaches**: When investigating build pipeline improvements or binary size reduction, the user consistently references specific external tools, repos, and contacts (e.g., Vercel's build-binary.mjs, binpunch, fossilize, Melkey's work) as starting points for evaluation. They expect the assistant to analyze whether each referenced approach actually applies to their specific setup before recommending it. The user wants a clear breakdown of what's relevant vs. irrelevant given their actual architecture (e.g., 'we already use esbuild full bundling, so node\_modules stripping doesn't apply'), followed by concrete alternative opportunities ranked by impact. @@ -225,12 +282,24 @@ * **Always research technical approaches thoroughly before implementation**: When facing a significant technical decision or migration, the user consistently requests deep research into multiple approaches before writing any code. This includes: fetching specific upstream documentation/source files (e.g., BUILDING.md, configure.py), identifying concrete flags/options, estimating build times, and evaluating cross-compilation feasibility. The user wants tradeoffs between paths laid out explicitly. Only after research is complete does implementation begin. When presenting research, include specific flags, URLs, estimated costs (time/size), and platform constraints. + +* **Always stage all modified files before committing, not just already-staged ones**: When preparing to commit, the user reviews git status and expects ALL modified files to be staged together — not just files already in the index. If unstaged modified files exist alongside staged ones, the user treats this as an incomplete commit state that needs to be resolved before proceeding. The user reviews the full list of changed files (staged + unstaged) as a checklist against completed tasks, and expects the commit to encompass all related changes from the session as a single coherent unit. + * **Always track migration progress with explicit completion criteria and remaining blockers**: The Bun→Node migration is complete only when \`Bun.build({ compile: true })\` is replaced by fossilize in \`script/build.ts\`. As of the current session, \`script/build.ts\` already uses fossilize (\`--no-bundle\`, \`--out-dir dist-bin\`, \`--node-version lts\`) with esbuild for bundling — the migration is complete. NODE\_VERSION='lts' in build.ts. The user expects the assistant to track this state across sessions and confirm the migration is done. When resuming sessions, verify \`script/build.ts\` does not contain \`Bun.build({ compile: true })\` before declaring migration complete. * **Always update dependencies promptly after releasing new versions**: When the user releases a new version of a tool they own (e.g., fossilize), they immediately update dependent projects to use that new version. This includes bumping the version in package files, creating a dedicated branch with a descriptive name (e.g., \`chore/tool-x.y.z\`), and opening a pull request. The commit message follows conventional commit format: \`chore: update \ to \ (\)\`. The assistant should proactively handle the full update workflow: fetch latest main, create the branch, update the dependency, commit, push, and open a PR. + +* **Always verify all tasks are complete before committing, then commit with descriptive conventional commit messages**: Before committing, the user reviews a task checklist to confirm all items are completed or in-progress. They stage all relevant files, then commit with a conventional commit message (e.g., 'docs: fix stale Bun references and add systemic doc checks') that summarizes the scope of changes. The commit message reflects the primary theme of the work session. The user expects the assistant to help verify task completion status, check git status, and confirm the commit succeeds with a summary of files changed and insertions/deletions. + + +* **Always verify code claims against actual file contents before accepting them as true**: When evaluating PRs, documentation, or assertions about code behavior, the user systematically cross-checks every claim against the actual source files at specific line numbers. They expect the assistant to read the real files, confirm exact line locations, quote the relevant code/comments, and flag discrepancies between what is claimed and what the code actually does. The user marks confirmed findings with 🟡 (verified) and actionable directives with 🔴 (user assertion/directive). Never accept a PR description or assertion at face value — always ground-truth it against the codebase with precise line references. + + +* **Always verify PR claims against actual codebase before accepting changes**: When reviewing a PR, the user consistently directs the assistant to check each stated claim against the real source files on the main branch rather than trusting the PR description or commit messages. This applies especially to documentation PRs: the user wants specific file paths, line numbers, and code excerpts cited as evidence. The user also cross-checks automated tooling (scripts, CI configs) against what they actually produce. When a PR introduces fixes, the user wants confirmation that the underlying problem genuinely existed and that the fix is correct — not just that the PR author says so. Always run the relevant check scripts and grep the codebase directly rather than reasoning from PR metadata alone. + * **Always work around the worktree conflict error when merging to main**: When merging PRs locally, the user consistently encounters \`fatal: 'main' is already used by worktree at ...\` and expects a workaround to be applied automatically rather than treating it as a blocking error. The merge is always completed successfully despite this error (e.g., using \`gh pr merge\` via CLI or other workaround). Never stop or report failure when this specific worktree conflict appears — proceed with the merge using an alternative method and confirm the PR was merged successfully. @@ -256,7 +325,7 @@ * **Respect explicitly rejected approaches**: Behavioral pattern detected across 3 sessions (action: rejected-approach). The user consistently demonstrates this behavior. -* **Review code before committing**: Behavioral pattern detected across 5 sessions (action: requested-review). The user consistently demonstrates this behavior. +* **Review code before committing**: Behavioral pattern detected across 6 sessions (action: requested-review). The user consistently demonstrates this behavior. * **Smoke tests must cover critical lazy-loaded paths, not just --help/--version**: Smoke tests that only run \`--help\` are insufficient — they never trigger lazy-loaded code paths. Critical paths: \`auth status\` (exits code 10, \`auth: false\`, exercises SQLite init/schema migrations/telemetry lazy import/CJS require chain, no network calls) and \`cli defaults\` (exits 0, \`auth: false\`, exercises \`getAllDefaults()\`/metadata KV). Both binary and npm bundle smoke tests must cover these paths. \`init --dry-run\` is NOT suitable as a smoke test — it lacks \`auth: false\`, so the auth guard runs first. CI currently only runs \`--help\` for all smoke tests (ci.yml lines 277-285, 683). diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index b5310089b..b13f13209 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -2,7 +2,10 @@ ## Prerequisites -- [Bun](https://bun.sh/) installed + +- [Node.js](https://nodejs.org/) v22.15+ installed +- [pnpm](https://pnpm.io/) v10.11+ installed + - A Sentry OAuth application (create one at https://sentry.io/settings/account/api/applications/) ## Setup @@ -88,11 +91,13 @@ The table below lists the most common development variables. For the complete re ## Building -Building the native binary still requires Bun: + +Build the native binary (uses esbuild for bundling and fossilize for Node SEA packaging): ```bash pnpm run build ``` + ## Architecture diff --git a/README.md b/README.md index 13ef12000..f45b29801 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Credentials are stored in `~/.sentry/` with restricted permissions (mode 600). ## Library Usage -Use Sentry CLI programmatically in Node.js (≥22.15) or Bun without spawning a subprocess: +Use Sentry CLI programmatically in Node.js (≥22.15) without spawning a subprocess: ```typescript @@ -118,7 +118,7 @@ Errors are thrown as `SentryError` with `.exitCode` and `.stderr`. ### Prerequisites -- [Bun](https://bun.sh) v1.3+ +- [Node.js](https://nodejs.org) v22.15+ and [pnpm](https://pnpm.io) v10.11+ ### Setup diff --git a/docs/src/content/docs/contributing.md b/docs/src/content/docs/contributing.md index f6fad5c42..c593e4b5d 100644 --- a/docs/src/content/docs/contributing.md +++ b/docs/src/content/docs/contributing.md @@ -10,7 +10,8 @@ We welcome contributions to the Sentry CLI! This guide will help you get started ### Prerequisites -- [Bun](https://bun.sh) runtime (v1.3 or later) +- [Node.js](https://nodejs.org) (v22.15 or later) +- [pnpm](https://pnpm.io) (v10.11 or later) - Git @@ -84,8 +85,9 @@ cli/ ## Building + ```bash -# Build for current platform (requires Bun for native binary compilation) +# Build for current platform (uses esbuild + fossilize for Node SEA packaging) pnpm run build # Build for all platforms @@ -94,6 +96,7 @@ pnpm run build:all # Create npm bundle pnpm run bundle ``` + ## Testing diff --git a/docs/src/content/docs/library-usage.md b/docs/src/content/docs/library-usage.md index cc61cab40..bee5978d7 100644 --- a/docs/src/content/docs/library-usage.md +++ b/docs/src/content/docs/library-usage.md @@ -1,6 +1,6 @@ --- title: Library Usage -description: Use the Sentry CLI programmatically in Node.js or Bun +description: Use the Sentry CLI programmatically in Node.js --- The Sentry CLI can be used as a JavaScript/TypeScript library, running commands @@ -216,7 +216,6 @@ Calls should be sequential (awaited one at a time). ## Requirements - **Node.js >= 22** (required for `node:sqlite`) -- Or **Bun** (any recent version) ## Streaming Commands diff --git a/docs/src/fragments/commands/cli.md b/docs/src/fragments/commands/cli.md index a0aa5bc21..652286708 100644 --- a/docs/src/fragments/commands/cli.md +++ b/docs/src/fragments/commands/cli.md @@ -61,6 +61,52 @@ The CLI detects how it was installed and uses the appropriate upgrade method: Nightly builds are only available as standalone binaries (via the curl install method). Switching to nightly from a package manager install will automatically migrate to a standalone binary. +### View and manage defaults + +```bash +# Show all current defaults +sentry cli defaults + +# Set default organization +sentry cli defaults org my-org + +# Set default project +sentry cli defaults project my-project + +# Set default Sentry URL (self-hosted) +sentry cli defaults url https://sentry.example.com + +# Disable telemetry +sentry cli defaults telemetry off + +# Clear a single default +sentry cli defaults org --clear + +# Clear all defaults +sentry cli defaults --clear +``` + +### Import legacy settings + +Import settings from `.sentryclirc` files used by the legacy `sentry-cli`: + +```bash +# Auto-detect and import .sentryclirc +sentry cli import + +# Preview what would be imported +sentry cli import --dry-run + +# Skip confirmation prompt +sentry cli import --yes + +# Explicitly trust a self-hosted URL +sentry cli import --url https://sentry.example.com + +# Skip API validation of the imported token +sentry cli import --skip-validation +``` + ### Send feedback ```bash diff --git a/docs/src/fragments/commands/dashboard.md b/docs/src/fragments/commands/dashboard.md index a532548a9..6977cc957 100644 --- a/docs/src/fragments/commands/dashboard.md +++ b/docs/src/fragments/commands/dashboard.md @@ -108,6 +108,36 @@ sentry dashboard widget delete 'My Dashboard' --title 'Error Count' sentry dashboard widget delete 12345 --index 2 ``` +### View revision history + +```bash +# List revisions by dashboard title +sentry dashboard revisions 'Frontend Performance' + +# List revisions by dashboard ID +sentry dashboard revisions 12345 + +# With explicit org +sentry dashboard revisions my-org 12345 +``` + +### Restore a previous revision + +```bash +# Restore by dashboard title and revision number +sentry dashboard restore 'Frontend Performance' --revision 3 + +# Restore by dashboard ID +sentry dashboard restore 12345 --revision 1 + +# With explicit org +sentry dashboard restore my-org 12345 --revision 1 +``` + +:::tip +Use `sentry dashboard revisions` to find the revision number before restoring. +::: + ## Query Shorthand The `--query` flag supports shorthand for aggregate functions: diff --git a/docs/src/fragments/commands/issue.md b/docs/src/fragments/commands/issue.md index db8b3a5fd..7ce163931 100644 --- a/docs/src/fragments/commands/issue.md +++ b/docs/src/fragments/commands/issue.md @@ -53,6 +53,40 @@ Sentry search uses **implicit AND** — space-separated terms are all required. Full syntax reference: [Sentry Search Docs](https://docs.sentry.io/concepts/search/) ::: +### Magic selectors + +Use `@latest` and `@most_frequent` to target issues without knowing their ID: + +```bash +# View the most recent issue +sentry issue view @latest + +# Explain the most frequently occurring issue +sentry issue explain @most_frequent + +# Generate a fix plan for the latest issue +sentry issue plan @latest +``` + +### List events for an issue + +```bash +# List recent events for an issue +sentry issue events FRONT-ABC + +# Filter events by search query +sentry issue events FRONT-ABC --query "browser:Chrome" + +# Show full event details +sentry issue events FRONT-ABC --full + +# Limit results and filter by time period +sentry issue events FRONT-ABC --limit 50 --period 24h + +# Paginate through results +sentry issue events FRONT-ABC -c next +``` + ### View an issue ```bash diff --git a/package.json b/package.json index 39e4b239d..4aebfa062 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,8 @@ "check:deps": "pnpm tsx script/check-no-deps.ts", "check:errors": "pnpm tsx script/check-error-patterns.ts", "check:patches": "pnpm tsx script/check-patches.ts", - "check:docs-sections": "pnpm tsx script/generate-docs-sections.ts --check" + "check:docs-sections": "pnpm tsx script/generate-docs-sections.ts --check", + "check:stale-refs": "pnpm tsx script/check-stale-references.ts" }, "type": "module", "types": "./dist/index.d.cts", diff --git a/plugins/README.md b/plugins/README.md index afa8d0ad4..ba62d5ed1 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -81,7 +81,7 @@ The SKILL.md file is **auto-generated** from the CLI's command definitions. Do n To regenerate after modifying commands: ```bash -bun run generate:docs +pnpm run generate:docs ``` CI will auto-commit updated skill files when they are stale. diff --git a/plugins/sentry-cli/skills/sentry-cli/references/cli.md b/plugins/sentry-cli/skills/sentry-cli/references/cli.md index 4ba56845d..6f0da60fb 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/cli.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/cli.md @@ -20,6 +20,31 @@ View and manage default settings - `-y, --yes - Skip confirmation prompt` - `-f, --force - Force the operation without confirmation` +**Examples:** + +```bash +# Show all current defaults +sentry cli defaults + +# Set default organization +sentry cli defaults org my-org + +# Set default project +sentry cli defaults project my-project + +# Set default Sentry URL (self-hosted) +sentry cli defaults url https://sentry.example.com + +# Disable telemetry +sentry cli defaults telemetry off + +# Clear a single default +sentry cli defaults org --clear + +# Clear all defaults +sentry cli defaults --clear +``` + ### `sentry cli feedback ` Send feedback about the CLI @@ -57,6 +82,25 @@ Import settings from legacy .sentryclirc files - `--url - Explicitly trust this URL (bypasses same-file trust check)` - `--skip-validation - Skip token validation against the Sentry API` +**Examples:** + +```bash +# Auto-detect and import .sentryclirc +sentry cli import + +# Preview what would be imported +sentry cli import --dry-run + +# Skip confirmation prompt +sentry cli import --yes + +# Explicitly trust a self-hosted URL +sentry cli import --url https://sentry.example.com + +# Skip API validation of the imported token +sentry cli import --skip-validation +``` + ### `sentry cli setup` Configure shell integration diff --git a/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md b/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md index 4a8df0d38..4464f607b 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md @@ -175,6 +175,19 @@ List dashboard revisions - `-n, --limit - Maximum number of revisions to list - (default: "25")` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` +**Examples:** + +```bash +# List revisions by dashboard title +sentry dashboard revisions 'Frontend Performance' + +# List revisions by dashboard ID +sentry dashboard revisions 12345 + +# With explicit org +sentry dashboard revisions my-org 12345 +``` + ### `sentry dashboard restore ` Restore a dashboard revision @@ -182,4 +195,17 @@ Restore a dashboard revision **Flags:** - `-r, --revision - Revision ID to restore` +**Examples:** + +```bash +# Restore by dashboard title and revision number +sentry dashboard restore 'Frontend Performance' --revision 3 + +# Restore by dashboard ID +sentry dashboard restore 12345 --revision 1 + +# With explicit org +sentry dashboard restore my-org 12345 --revision 1 +``` + All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags. diff --git a/plugins/sentry-cli/skills/sentry-cli/references/issue.md b/plugins/sentry-cli/skills/sentry-cli/references/issue.md index 448b6546b..d85970641 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/issue.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/issue.md @@ -111,6 +111,25 @@ List events for a specific issue | `crashFile` | string \| null | Crash file URL | | `metadata` | object \| null | Event metadata | +**Examples:** + +```bash +# List recent events for an issue +sentry issue events FRONT-ABC + +# Filter events by search query +sentry issue events FRONT-ABC --query "browser:Chrome" + +# Show full event details +sentry issue events FRONT-ABC --full + +# Limit results and filter by time period +sentry issue events FRONT-ABC --limit 50 --period 24h + +# Paginate through results +sentry issue events FRONT-ABC -c next +``` + ### `sentry issue explain ` Analyze an issue's root cause using Seer AI @@ -122,6 +141,15 @@ Analyze an issue's root cause using Seer AI **Examples:** ```bash +# View the most recent issue +sentry issue view @latest + +# Explain the most frequently occurring issue +sentry issue explain @most_frequent + +# Generate a fix plan for the latest issue +sentry issue plan @latest + # Analyze root cause (may take a few minutes for new issues) sentry issue explain 123456789 diff --git a/script/check-fragments.ts b/script/check-fragments.ts index a7e23d822..abe81e6f9 100644 --- a/script/check-fragments.ts +++ b/script/check-fragments.ts @@ -7,14 +7,16 @@ * 2. Every fragment file corresponds to an existing route (or is index.md) * 3. Fragment files don't accidentally contain frontmatter or the generated marker * 4. Top-level fragments (e.g., configuration.md) exist + * 5. Fragment content covers all subcommands in a route (warnings only) * * Usage: - * tsx script/check-fragments.ts + * tsx script/check-fragments.ts # Warnings for missing subcommands + * tsx script/check-fragments.ts --strict # Errors for missing subcommands */ import { mkdirSync, readdirSync } from "node:fs"; import { access, readFile, writeFile } from "node:fs/promises"; -import type { RouteMap } from "../src/lib/introspect.js"; +import type { RouteInfo, RouteMap } from "../src/lib/introspect.js"; // Ensure skill-content stub exists (see generate-command-docs.ts for rationale) const SKILL_CONTENT_PATH = "src/generated/skill-content.ts"; @@ -41,12 +43,13 @@ const SKIP_ROUTES = new Set(["help"]); const MD_EXTENSION_RE = /\.md$/; +const isStrict = process.argv.includes("--strict"); + const routeMap = routes as unknown as RouteMap; -const routeNames = new Set( - extractAllRoutes(routeMap) - .filter((r) => !SKIP_ROUTES.has(r.name)) - .map((r) => r.name) +const allRoutes = extractAllRoutes(routeMap).filter( + (r) => !SKIP_ROUTES.has(r.name) ); +const routeNames = new Set(allRoutes.map((r) => r.name)); // Expected fragment files: one per route + index const expectedFragments = new Set([...routeNames, "index"]); @@ -124,12 +127,177 @@ for (const name of REQUIRED_TOP_LEVEL_FRAGMENTS) { } } +// --------------------------------------------------------------------------- +// Check 5: Subcommand coverage in fragment content +// --------------------------------------------------------------------------- + +const warnings: string[] = []; + +const FENCED_CODE_BLOCK_RE = /^(`{3,}|~{3,}).*\n[\s\S]*?\n\1\s*$/gm; + +/** Strip fenced code blocks from markdown to avoid matching bash comments as headings. */ +function stripCodeBlocks(md: string): string { + return md.replace(FENCED_CODE_BLOCK_RE, ""); +} + +/** + * Check whether a fragment mentions a specific subcommand. + * + * Looks for either: + * 1. A full CLI reference: `sentry ` anywhere in the text + * 2. A markdown heading containing the leaf subcommand name (outside code blocks) + * + * For default commands (e.g., `sentry local serve` where `serve` is the + * default), also accepts `sentry ` without the subcommand — since + * users invoke the default command that way. + */ +function fragmentMentionsSubcommand( + content: string, + routeName: string, + command: { path: string }, + isDefaultCommand: boolean +): boolean { + // Extract the subcommand portion after "sentry " + const subPath = command.path.slice(`sentry ${routeName} `.length); + const leaf = subPath.split(" ").at(-1) ?? subPath; + const fullCliRef = command.path; // e.g. "sentry dashboard revisions" + + const lower = content.toLowerCase(); + + // Check 1: Full CLI reference anywhere (code blocks, backticks, prose) + if (lower.includes(fullCliRef.toLowerCase())) { + return true; + } + + // Check 2: For default commands, bare `sentry ` (not followed by a subcommand) counts. + // Use a regex with word boundary to avoid matching `sentry issue events` as `sentry issue`. + if (isDefaultCommand) { + const bareRouteRe = new RegExp( + `sentry\\s+${routeName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s*$|[^\\w-])`, + "im" + ); + if (bareRouteRe.test(lower)) { + return true; + } + } + + // Check 3: Heading that mentions the leaf name (outside code blocks) + const proseOnly = stripCodeBlocks(content); + const escapedLeaf = leaf.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const headingRe = new RegExp(`^#{1,4}\\s+.*\\b${escapedLeaf}\\b`, "im"); + if (headingRe.test(proseOnly)) { + return true; + } + + return false; +} + +/** + * Build a map of route name → RouteInfo for routes with multiple subcommands. + * Standalone commands (single command matching `sentry `) are skipped + * since there's nothing to check — the fragment covers the whole command. + */ +function getMultiCommandRoutes(): Map { + const result = new Map(); + for (const route of allRoutes) { + // Skip standalone commands (only one command, path = "sentry ") + if ( + route.commands.length <= 1 && + route.commands[0]?.path === `sentry ${route.name}` + ) { + continue; + } + result.set(route.name, route); + } + return result; +} + +/** + * Given a route map target, find the name of its default command (if any). + * Compares the default command object reference against all subcommand entries. + */ +function findDefaultInRouteMap(target: RouteMap): string | undefined { + if (!target.getDefaultCommand) { + return; + } + const defaultCmd = target.getDefaultCommand(); + if (!defaultCmd) { + return; + } + for (const sub of target.getAllEntries()) { + if (sub.target === defaultCmd) { + return sub.name.original; + } + } + return; +} + +/** + * Extract the default command name for a route by inspecting the Stricli route map. + * Returns the default command name (e.g., "serve" for `local`) or undefined. + */ +function getDefaultCommandName(routeName: string): string | undefined { + for (const entry of routeMap.getAllEntries()) { + if (entry.name.original !== routeName) { + continue; + } + return findDefaultInRouteMap(entry.target as RouteMap); + } + return; +} + +const multiCommandRoutes = getMultiCommandRoutes(); + +for (const [routeName, route] of multiCommandRoutes) { + const fragmentPath = `${FRAGMENTS_DIR}/${routeName}.md`; + let content: string; + try { + content = await readFile(fragmentPath, "utf-8"); + } catch { + // Fragment doesn't exist — already caught by Check 1 + continue; + } + + const defaultCmd = getDefaultCommandName(routeName); + + const missing: string[] = []; + for (const cmd of route.commands) { + const sub = cmd.path.slice(`sentry ${routeName} `.length); + const leaf = sub.split(" ").at(-1) ?? sub; + const isDefault = leaf === defaultCmd; + if (!fragmentMentionsSubcommand(content, routeName, cmd, isDefault)) { + missing.push(sub); + } + } + + if (missing.length > 0) { + const msg = + `Fragment missing subcommand coverage: ${fragmentPath}\n` + + ` Not documented: ${missing.join(", ")}\n` + + ` Hint: add a heading or code example for each (e.g., "sentry ${routeName} ${missing[0]}")`; + if (isStrict) { + errors.push(msg); + } else { + warnings.push(msg); + } + } +} + // --------------------------------------------------------------------------- // Results // --------------------------------------------------------------------------- +if (warnings.length > 0) { + console.warn( + `\n${warnings.length} subcommand coverage warning(s)${isStrict ? " (treated as errors with --strict)" : ""}:\n` + ); + for (const w of warnings) { + console.warn(` ⚠ ${w}`); + } +} + if (errors.length > 0) { - console.error(`Found ${errors.length} fragment validation error(s):\n`); + console.error(`\nFound ${errors.length} fragment validation error(s):\n`); for (const err of errors) { console.error(` ✗ ${err}`); } @@ -137,7 +305,7 @@ if (errors.length > 0) { } console.log( - `All ${actualFragments.size} command fragment files valid (${routeNames.size} routes + index)` + `\nAll ${actualFragments.size} command fragment files valid (${routeNames.size} routes + index)` ); console.log( `All ${REQUIRED_TOP_LEVEL_FRAGMENTS.length} top-level fragment(s) valid` diff --git a/script/check-no-deps.ts b/script/check-no-deps.ts index 5e357aaf7..4a2776aea 100644 --- a/script/check-no-deps.ts +++ b/script/check-no-deps.ts @@ -4,7 +4,7 @@ * * Ensures package.json has no `dependencies` field. All packages must be * listed under `devDependencies` and bundled at build time via esbuild - * (npm bundle) or Bun.build (standalone binary). This keeps the published + * (npm bundle) or fossilize (standalone binary). This keeps the published * package at zero install-time dependencies. * * Usage: @@ -38,7 +38,7 @@ console.error( "All packages must be in devDependencies and bundled at build time." ); console.error( - "Move these to devDependencies: bun remove && bun add -d " + "Move these to devDependencies: pnpm remove && pnpm add -D " ); process.exit(1); diff --git a/script/check-stale-references.ts b/script/check-stale-references.ts new file mode 100644 index 000000000..845064ffb --- /dev/null +++ b/script/check-stale-references.ts @@ -0,0 +1,191 @@ +#!/usr/bin/env tsx + +/** + * Check for Stale Toolchain References + * + * Scans dev-facing documentation and scripts for references to package + * managers or runtimes that are no longer used by this project. The check + * is generic: it reads `packageManager` from package.json to determine + * the current PM, then flags any dev-facing file that references a + * a different PM's commands (e.g., `bun run`, `yarn remove`). + * + * If the project migrates from pnpm to another PM, simply updating the + * `packageManager` field in package.json will automatically make this + * check flag every `pnpm run` / `pnpm add` reference in dev docs. + * + * Usage: + * tsx script/check-stale-references.ts + * + * Exit codes: + * 0 - No stale references found + * 1 - Stale references detected + */ + +import { readdirSync } from "node:fs"; +import { readFile } from "node:fs/promises"; + +const pkg = JSON.parse(await readFile("package.json", "utf-8")); +const currentPM: string = (pkg.packageManager ?? "").split("@")[0]; + +if (!currentPM) { + console.error("✗ Cannot determine package manager from package.json"); + process.exit(1); +} + +/** All known JS package managers whose dev commands should not appear when another PM is active. */ +const ALL_PACKAGE_MANAGERS = ["bun", "yarn", "pnpm", "npm"]; + +const stalePMs = ALL_PACKAGE_MANAGERS.filter((pm) => pm !== currentPM); + +if (stalePMs.length === 0) { + console.log("✓ No stale package manager patterns to check"); + process.exit(0); +} + +// --------------------------------------------------------------------------- +// Patterns (derived dynamically from the non-current package managers) +// --------------------------------------------------------------------------- + +const escaped = stalePMs.join("|"); + +type StalePattern = { + pattern: RegExp; + reason: string; +}; + +const PATTERNS: StalePattern[] = [ + { + // "bun run", "yarn remove", "bun add -d", etc. (dev commands) + pattern: new RegExp(`\\b(?:${escaped})\\s+(?:run|remove|add\\s+-[dD])\\b`), + reason: `Use '${currentPM}' commands instead`, + }, + { + // "requires Bun", "Yarn installed", "Bun runtime" (prerequisite prose) + pattern: new RegExp( + `\\b(?:requires\\s+(?:${escaped})|(?:${escaped})\\s+(?:installed|runtime))\\b`, + "i" + ), + reason: `Project uses ${currentPM}, not these runtimes`, + }, +]; + +// --------------------------------------------------------------------------- +// Files to scan (curated — dev-facing docs and scripts only) +// --------------------------------------------------------------------------- + +/** Dev-facing doc files that should not reference stale PMs. */ +const DOC_FILES = [ + "DEVELOPMENT.md", + "plugins/README.md", + "docs/src/content/docs/contributing.md", + "docs/src/content/docs/library-usage.md", +]; + +/** This script's own filename — excluded from scanning to avoid self-flagging. */ +const SELF = "script/check-stale-references.ts"; + +/** Script files whose error messages / JSDoc should not reference stale PMs. */ +function getScriptFiles(): string[] { + try { + return readdirSync("script") + .filter((f) => f.endsWith(".ts")) + .map((f) => `script/${f}`) + .filter((f) => f !== SELF); + } catch { + return []; + } +} + +const FILES_TO_SCAN = [...DOC_FILES, ...getScriptFiles()]; + +// --------------------------------------------------------------------------- +// Code block stripping (avoid false positives on user install examples) +// --------------------------------------------------------------------------- + +const FENCED_CODE_BLOCK_RE = /^(`{3,}|~{3,}).*\n[\s\S]*?\n\1\s*$/gm; + +/** Strip fenced code blocks from markdown content to avoid false positives. */ +function stripCodeBlocks(content: string): string { + return content.replace(FENCED_CODE_BLOCK_RE, ""); +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +type Finding = { + file: string; + line: number; + text: string; + reason: string; +}; + +const findings: Finding[] = []; + +for (const filePath of FILES_TO_SCAN) { + let content: string; + try { + content = await readFile(filePath, "utf-8"); + } catch { + // File doesn't exist — skip silently (e.g., optional doc files) + continue; + } + + // For markdown files, strip code blocks to avoid matching user install examples + const isMarkdown = filePath.endsWith(".md") || filePath.endsWith(".mdx"); + const scannable = isMarkdown ? stripCodeBlocks(content) : content; + + const lines = scannable.split("\n"); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + for (const { pattern, reason } of PATTERNS) { + if (pattern.test(line)) { + // Find the original line number in the un-stripped content + // For scripts (no stripping), i is the actual line number + const originalLine = isMarkdown + ? findOriginalLineNumber(content, line) + : i + 1; + findings.push({ + file: filePath, + line: originalLine, + text: line.trim(), + reason, + }); + } + } + } +} + +/** + * Find the 1-indexed line number of a text line in the original content. + * Falls back to 0 if not found (shouldn't happen in practice). + */ +function findOriginalLineNumber( + original: string, + strippedLine: string +): number { + const trimmed = strippedLine.trim(); + const lines = original.split("\n"); + for (let i = 0; i < lines.length; i++) { + if (lines[i].trim() === trimmed) { + return i + 1; + } + } + return 0; +} + +if (findings.length === 0) { + console.log( + `✓ No stale package manager references found (current: ${currentPM}, checked: ${FILES_TO_SCAN.length} files)` + ); + process.exit(0); +} + +console.error( + `✗ Found ${findings.length} stale package manager reference(s) (current PM: ${currentPM}):\n` +); +for (const f of findings) { + console.error(` ${f.file}:${f.line}: ${f.text}`); + console.error(` → ${f.reason}\n`); +} +process.exit(1); diff --git a/script/generate-docs-sections.ts b/script/generate-docs-sections.ts index 9d594fa27..6eb1a5b6d 100644 --- a/script/generate-docs-sections.ts +++ b/script/generate-docs-sections.ts @@ -261,39 +261,100 @@ function generateScopesInline(scopes: readonly string[]): string { // Section: Prerequisites (README.md, contributing.md) // --------------------------------------------------------------------------- -const BUN_VERSION_RE = /bun@(\d+\.\d+)/; +const PNPM_VERSION_RE = /pnpm@(\d+\.\d+)/; const SEMVER_RE = /(\d+\.\d+)/; /** - * Extract the Bun major.minor version from the `packageManager` field. - * `bun@1.3.13` → `1.3` + * Extract the pnpm major.minor version from the `packageManager` field. + * `pnpm@10.11.0` → `10.11` + * + * Throws if `packageManager` doesn't match the expected format — + * silent fallbacks caused stale Bun prerequisites after the Bun→Node migration. */ -function extractBunVersion(): string { +function extractPnpmVersion(): string { const pm: string = pkg.packageManager ?? ""; - const match = pm.match(BUN_VERSION_RE); - return match ? match[1] : "1.3"; + const match = pm.match(PNPM_VERSION_RE); + if (!match) { + throw new Error( + `Cannot extract pnpm version from packageManager: "${pm}". ` + + "Expected format: pnpm@X.Y.Z" + ); + } + return match[1]; } -/** Extract the Node.js minimum version from `engines.node` (e.g., `>=22.12` → `22.12`). */ +/** + * Extract the Node.js minimum version from `engines.node`. + * `>=22.15` → `22.15` + * + * Throws if `engines.node` is missing or doesn't contain a semver-like version. + */ function extractNodeVersion(): string { - const constraint: string = pkg.engines?.node ?? ">=22.12"; + const constraint: string | undefined = pkg.engines?.node; + if (!constraint) { + throw new Error("Missing engines.node in package.json"); + } const match = constraint.match(SEMVER_RE); - return match ? match[1] : "22.12"; + if (!match) { + throw new Error( + `Cannot extract Node.js version from engines.node: "${constraint}". ` + + "Expected a semver-like version (e.g., >=22.15)" + ); + } + return match[1]; } -/** Generate dev prerequisite line for README.md and contributing.md. */ +/** Generate dev prerequisite line for README.md. */ function generateDevPrereq(): string { - return `- [Bun](https://bun.sh) v${extractBunVersion()}+`; + return `- [Node.js](https://nodejs.org) v${extractNodeVersion()}+ and [pnpm](https://pnpm.io) v${extractPnpmVersion()}+`; } -/** Also used by contributing.md (same content, different phrasing). */ +/** Generate dev prerequisite lines for contributing.md. */ function generateDevPrereqContributing(): string { - return `- [Bun](https://bun.sh) runtime (v${extractBunVersion()} or later)`; + return [ + `- [Node.js](https://nodejs.org) (v${extractNodeVersion()} or later)`, + `- [pnpm](https://pnpm.io) (v${extractPnpmVersion()} or later)`, + ].join("\n"); } /** Generate the library-usage prerequisite line for README.md. */ function generateLibraryPrereq(): string { - return `Use Sentry CLI programmatically in Node.js (≥${extractNodeVersion()}) or Bun without spawning a subprocess:`; + return `Use Sentry CLI programmatically in Node.js (≥${extractNodeVersion()}) without spawning a subprocess:`; +} + +/** Generate dev prerequisite lines for DEVELOPMENT.md. */ +function generateDevPrereqDevelopment(): string { + return [ + `- [Node.js](https://nodejs.org/) v${extractNodeVersion()}+ installed`, + `- [pnpm](https://pnpm.io/) v${extractPnpmVersion()}+ installed`, + ].join("\n"); +} + +/** Generate build toolchain description for DEVELOPMENT.md. */ +function generateBuildToolchain(): string { + return [ + "Build the native binary (uses esbuild for bundling and fossilize for Node SEA packaging):", + "", + "```bash", + "pnpm run build", + "```", + ].join("\n"); +} + +/** Generate build commands section for contributing.md. */ +function generateBuildCommands(): string { + return [ + "```bash", + "# Build for current platform (uses esbuild + fossilize for Node SEA packaging)", + "pnpm run build", + "", + "# Build for all platforms", + "pnpm run build:all", + "", + "# Create npm bundle", + "pnpm run bundle", + "```", + ].join("\n"); } // --------------------------------------------------------------------------- @@ -492,6 +553,21 @@ const sections: SectionDef[] = [ sectionName: "dev-prereq", generate: generateDevPrereqContributing, }, + { + filePath: "DEVELOPMENT.md", + sectionName: "dev-prereq", + generate: generateDevPrereqDevelopment, + }, + { + filePath: "DEVELOPMENT.md", + sectionName: "build-toolchain", + generate: generateBuildToolchain, + }, + { + filePath: "docs/src/content/docs/contributing.md", + sectionName: "build-commands", + generate: generateBuildCommands, + }, // -- Dev scripts (README.md) -- { filePath: "README.md",