diff --git a/.lore.md b/.lore.md index e3f2bc576..edc7a93e3 100644 --- a/.lore.md +++ b/.lore.md @@ -10,12 +10,24 @@ * **Binary build pipeline: esbuild → fossilize → Node SEA (replacing Bun.build compile)**: (architecture) Binary build pipeline: \`src/bin.ts → \[esbuild CJS, node22 target] → dist-build/bin.js → \[fossilize --no-bundle] → Node SEA binary → \[binpunch] → gzip\`. Keep inputs in \`dist-build/\` (separate from fossilize's \`--out-dir dist-bin/\` — fossilize always \`rm -rf dist-bin/\` on start). Single fossilize invocation for all platforms via \`FOSSILIZE\_PLATFORMS\`. Post-process: rename \`sentry-win-x64.exe\`→\`sentry-windows-x64.exe\`. Platform matrix: darwin-arm64, darwin-x64, linux-x64, linux-arm64, win32-x64 (no musl). Ink sidecar: \`--assets dist-build/ink-app.js\`. \`NODE\_VERSION=22\` pinned. \`FOSSILIZE\_SIGN=y\` on push to main/release. esbuild: \`format:'cjs'\` + \`target:'node22'\` + inject \`import-meta-url.js\` shim. Build script: \`pnpm tsx script/build.ts\`. Gzip only when \`RELEASE\_BUILD=1\`. binpunch always runs. Sourcemap uploaded to Sentry, never shipped. + +* **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. + + +* **CLI mode never calls setEnv() — getEnv() returns process.env directly**: CLI mode NEVER calls \`setEnv()\` — \`getEnv()\` returns \`process.env\` directly. Only library mode calls \`setEnv()\` with a merged env copy to avoid mutating the consumer's \`process.env\`. This prevents unexpected side effects when the CLI is embedded as a library. + * **Consola chosen as CLI logger with Sentry createConsolaReporter integration**: Consola is the CLI logger with Sentry \`createConsolaReporter\` integration. Two reporters: FancyReporter (stderr) + Sentry structured logs. Level via \`SENTRY\_LOG\_LEVEL\`. \`buildCommand\` injects hidden \`--log-level\`/\`--verbose\` flags. \`withTag()\` creates independent instances; \`setLogLevel()\` propagates via registry. All user-facing output must use consola, not raw stderr. \`HandlerContext\` intentionally omits stderr. Telemetry opt-out priority: (1) \`SENTRY\_CLI\_NO\_TELEMETRY=1\`, (2) \`DO\_NOT\_TRACK=1\`, (3) \`metadata.defaults.telemetry\`, (4) default on. Shell completions set \`SENTRY\_CLI\_NO\_TELEMETRY=1\` in \`bin.ts\` before imports. Timing queued to \`completion\_telemetry\_queue\` SQLite table; normal runs drain via \`DELETE ... RETURNING\`. \`ENV\_VAR\_REGISTRY\` in \`src/lib/env-registry.ts\` is single source for all honored env vars; \`topLevel: true\` + \`briefDescription\` surfaces in \`--help\`. Add install-script-only vars with \`installOnly: true\`. + +* **Custom CA loading: priority, caching, TLS error detection, and SaaS warning**: Custom CA in \`src/lib/custom-ca.ts\`: Priority: (1) \`sentry cli defaults ca-cert\` (SQLite), (2) \`NODE\_EXTRA\_CA\_CERTS\`. Cached per-process via module-level vars (\`hasResolved\` flag). \`resolve()\` concatenates custom PEM with \`rootCertificates\` (additive — Bun replaces Mozilla bundle otherwise). \`tryReadPem()\` NEVER throws — missing CA file logs warn and returns \`undefined\`. \`injectIntoNodeTls()\` uses \`tls.setDefaultCACertificates()\` (Node 24+ only; no-op on Node 22). \`TLS\_ERROR\_PATTERNS\`: 5 patterns (local issuer, verify first cert, UNABLE\_TO\_VERIFY\_LEAF\_SIGNATURE, DEPTH\_ZERO\_SELF\_SIGNED\_CERT, SELF\_SIGNED\_CERT\_IN\_CHAIN) — explicitly excludes \`CERT\_HAS\_EXPIRED\` and \`ERR\_TLS\_CERT\_ALTNAME\_INVALID\`. \`getTlsCertErrorMessage()\` walks \`error.cause\` chain with cycle detection. SaaS target + env-sourced CA → one-time warning; stored default silences it. \`\_\_resetForTests()\` resets all cached state. + * **DSN cache invalidation uses two-level mtime tracking (sourceMtimes + dirMtimes)**: DSN cache invalidation — two-level mtime tracking: \`sourceMtimes\` (DSN-bearing files, catches in-place edits) + \`dirMtimes\` (every walked dir, catches new files) + root mtime fast-path + 24h TTL. Dropping either map is a correctness regression. Walker emits mtimes via \`onDirectoryVisit\` hook + \`recordMtimes\` option; DSN scanner uses \`grepFiles({pattern: DSN\_PATTERN, recordMtimes: true, onDirectoryVisit})\`. \`scanCodeForFirstDsn\` stays on direct walker loop (worker init ~20ms dominates). Invariants: \`processMatch\` must record mtime for EVERY file with host-validated DSN via \`fileHadValidDsn\` flag independent of \`seen.has(raw)\`. \`scanDirectory\` catch MUST return empty \`dirMtimes: {}\`, NOT partial map; \`ConfigError\` re-throws. + +* **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. + * **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\`. @@ -28,6 +40,9 @@ * **Node SEA ink sidecar: node:sea.getAsset() replaces Bun /$bunfs/ virtual FS**: (architecture) Node SEA ink sidecar: \`node:sea.getAsset()\` replaces Bun \`/$bunfs/\` virtual FS. Ink UI sidecar embedded via \`fossilize --assets dist-build/ink-app.js\`; asset key = raw CLI arg. At runtime: \`sea.getRawAsset('dist-build/ink-app.js')\`. Main bundle never calls \`import('ink')\` — sidecar pre-bundled by text-import-plugin. Dual-mode: detect SEA via \`createRequire(import.meta.url)('node:sea')\` with try/catch fallback. \`useSnapshot: true\` BROKEN. \`useCodeCache: true\` ~15% startup improvement but platform-specific V8 blob. Suppress \`ExperimentalWarning: SQLite\`: \`process.on('warning', ...)\` at very top of \`src/bin.ts\` BEFORE any imports. fossilize asset manifest key = \`basename(manifestPath)\`; entry keys = \`entry.file\`. \`new Worker(new URL(...))\` HANGS in SEA — use Blob+URL.createObjectURL. + +* **npm bundle (script/bundle.ts): esbuild CJS with bun:sqlite polyfill, external Ink, debug ID injection**: Entry: \`src/index.ts\` → \`dist/index.cjs\` (CJS, minified, Node 22). External: \`node:\*\`, \`ink\`, \`react\`, \`react-reconciler\`, \`yoga-layout\` (use top-level await, can't emit CJS). \`bunSqlitePlugin\` redirects \`bun:sqlite\` → \`globalThis.\_\_bun\_sqlite\_polyfill\` (injected by \`node-polyfills.ts\`). \`sentrySourcemapPlugin\` (onEnd): injects debug IDs, replaces \`PLACEHOLDER\_DEBUG\_ID\` with real UUID (same length → sourcemap positions valid), uploads only if \`SENTRY\_AUTH\_TOKEN\` set. \`textImportPlugin\` handles \`with { type: 'file' }\`. \`dist/bin.cjs\`: shebang, Node 22.12 floor check, warning suppression, calls \`require('./index.cjs').\_cli()\`. Build script: \`pnpm run bundle\`. Exits with code 1 if \`SENTRY\_CLIENT\_ID\` missing. + * **Response cache hit invisibility — synthetic Response carries no marker**: Response cache hit invisibility — synthetic Response from \`getCachedResponse()\` in \`src/lib/response-cache.ts\` is indistinguishable from network. Solved via module-level \`lastCacheHitAgeMs\`: set on hit, cleared at top of \`authenticatedFetch()\` per-call (single-process CLI = race-free). \`src/lib/cache-hint.ts\` provides \`formatCacheHint()\` (\`"cached · 3m ago · use -f to refresh"\`) and \`appendCacheHint(existingHint)\` (joins with \` | \`). Wired in \`buildCommand\` (\`src/lib/command.ts\`): \`appendCacheHint(returned?.hint)\` runs only when generator returns a \`CommandReturn\` — bare \`return;\` paths (e.g. \`--web\`) skip the hint. Same chokepoint can host future cross-cutting hint decorators. Test-only \`\_setLastCacheHitAgeForTesting(ms)\` exposes state. @@ -46,9 +61,15 @@ * **Sentry CLI resolve-target cascade has 5 priority levels with env var support**: Resolve-target cascade: (1) CLI flags, (2) SENTRY\_ORG/SENTRY\_PROJECT env vars, (3) SQLite defaults, (4) DSN auto-detection, (5) directory name inference. SENTRY\_PROJECT supports \`org/project\` combo — SENTRY\_ORG ignored if set. Schema v13 merged \`defaults\` table into \`metadata\` KV with keys \`defaults.{org,project,telemetry,url}\`; getters/setters in \`src/lib/db/defaults.ts\`. Prefer dedicated SQLite tables + migrations over \`metadata\` KV for non-trivial caches. Hidden global \`--org\`/\`--project\` flags: \`mergeGlobalFlags()\` in command.ts injects hidden flag shapes, \`applyOrgProjectFlags()\` writes to \`SENTRY\_ORG\`/\`SENTRY\_PROJECT\` before auth guard. No short aliases (\`-p\` conflicts). \`@sentry/api\` SDK: wrap types at \`src/lib/api/\*.ts\` with \`as unknown as SentryX\` casts; never leak to commands. \`unwrapResult\`/\`unwrapPaginatedResult\` must stay CLI-owned. \`apiRequestToRegion\` auto-sets JSON Content-Type; \`rawApiRequest\` preserves strings. + +* **sentry local command: Hono+Spotlight SDK server with SSE tail output**: \`sentry local\` (default: \`serve\`) and \`sentry local run\` — both \`auth: false\`. Default port 8969. Uses \`@spotlightjs/spotlight/sdk\` (\`createSpotlightBuffer\`/\`pushToSpotlightBuffer\`) for envelope buffering; custom Hono HTTP server for ingest. Endpoints: \`POST /stream\`, \`POST /api/:projectId/envelope\[/]\`, \`GET /stream\` (SSE), \`GET /health\`. CORS restricted to localhost origins only. Browser SDK \`sendBeacon\` workaround: overrides \`text/plain\` → \`application/x-sentry-envelope\` when \`sentry\_client\` query param starts with \`sentry.javascript.browser\`. \`sentry local run\` injects \`SENTRY\_SPOTLIGHT\`, \`NEXT\_PUBLIC\_SENTRY\_SPOTLIGHT\`, \`SENTRY\_TRACES\_SAMPLE\_RATE=1\` into child env. Attach mode: if server already running, connects as SSE consumer (manual SSE parser — no \`EventSource\`). Formatters in \`src/lib/formatters/local.ts\`: \`sanitize()\` strips ANSI/control/bidi chars; source inferred from \`sdk.name\` → \`\[SERVER]\`/\`\[BROWSER]\`/\`\[MOBILE]\`. + * **Sentry log IDs are UUIDv7 — enables deterministic retention checks**: Sentry log IDs are UUIDv7 — enables deterministic retention checks. \`decodeUuidV7Timestamp()\` and \`ageInDaysFromUuidV7()\` in \`src/lib/hex-id.ts\` return null for non-v7, safe to call unconditionally. \`RETENTION\_DAYS.log = 90\` in \`src/lib/retention.ts\`; traces/events are \`null\` (plan-dependent). \`LOG\_RETENTION\_PERIOD\` is DERIVED as \`\` \`${RETENTION\_DAYS.log}d\` \`\` — never hardcode \`'90d'\`. Shared hex primitives (\`HEX\_ID\_RE\`, \`SPAN\_ID\_RE\`, \`UUID\_DASH\_RE\`) live in \`hex-id.ts\`. Three Sentry span APIs: (1) \`/trace/{traceId}/\` — hierarchical tree with \`additional\_attributes\`. (2) \`/projects/{org}/{project}/trace-items/{itemId}/\` — single span with ALL attributes. (3) \`/events/?dataset=spans\` — list/search. \`meta.fields\` order is non-deterministic — derive column order from user's \`--field\` list via \`orderFieldNames()\` in \`explore.ts\`. + +* **src/cli.ts: middleware chain, completion optimization, sensitive argv redaction**: \`src/cli.ts\` exports \`startCli()\`, \`runCli()\`, \`runCompletion()\`. Middleware chain (innermost-first): \`\[seerTrialMiddleware, autoAuthMiddleware]\` — auth is outermost. \`autoAuthMiddleware\` uses \`isatty(0)\` not \`process.stdin.isTTY\` (Bun returns undefined). \`runCompletion()\` sets \`SENTRY\_CLI\_NO\_TELEMETRY=1\` to skip \`@sentry/node-core\` lazy-require (~280ms). \`redactArgv()\` handles \`--flag=value\` and \`--flag \\` forms; \`SENSITIVE\_ARGV\_FLAGS\` includes \`token\` and \`auth-token\`. \`reportUnknownCommand()\` wrapped in try/catch — telemetry must never crash CLI. \`preloadProjectContext()\` calls \`captureEnvTokenHost()\` BEFORE any env mutation. + * **Zod schema on OutputConfig enables self-documenting JSON fields in help and SKILL.md**: Zod schema on OutputConfig enables self-documenting JSON fields: List commands register \`schema?: ZodType\` on \`OutputConfig\\`. \`extractSchemaFields()\` produces \`SchemaFieldInfo\[]\` from Zod shapes. \`buildFieldsFlag()\` enriches \`--fields\` brief; \`enrichDocsWithSchema()\` appends fields to \`fullDescription\`. Schema exposed as \`\_\_jsonSchema\` on built commands — \`introspect.ts\` reads it into \`CommandInfo.jsonFields\`, \`help.ts\` and \`generate-skill.ts\` render it. For \`buildOrgListCommand\`/\`dispatchOrgScopedList\`, pass \`schema\` via \`OrgListConfig\`. @@ -89,8 +110,11 @@ * **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;\` + +* **useTestConfigDir afterEach: never delete CONFIG\_DIR\_ENV\_VAR — always restore previous value**: Trap: deleting \`process.env.SENTRY\_CONFIG\_DIR\` in \`afterEach\` looks like proper cleanup. But \`preload.ts\` always sets \`SENTRY\_CONFIG\_DIR\`, so \`savedConfigDir\` is always defined — deleting it causes subsequent test files' module-level code or \`beforeEach\` hooks to read \`undefined\`. Fix: always restore the previous value, never delete. The \`else { delete process.env\[CONFIG\_DIR\_ENV\_VAR] }\` branch is intentionally omitted in \`test/helpers.ts\` \`useTestConfigDir\`. Same principle applies in \`test/fixture.ts\` \`setAuthToken()\` finally block — the delete there is acceptable only because it's a scoped try/finally restore, not a test lifecycle hook. + -* **Vitest worker pool requires pool:forks + UV\_USE\_IO\_URING=0 on GitHub Actions**: Vitest/CI gotchas: (1) GitHub Actions io\_uring crashes Node.js workers (exit 134/SIGABRT) — fix: \`pool: 'forks'\` in \`vitest.config.ts\` AND \`UV\_USE\_IO\_URING=0\` in CI. (2) Vitest 4: options must be second arg: \`test(name, { timeout }, fn)\`. (3) \`http.createServer(async ...)\` — unhandled rejections crash test server; wrap body in try/catch. (4) \`dorny/paths-filter\` diffs against base — empty commits produce all-false outputs. (5) \`node:sqlite\` requires \`--experimental-sqlite\` on Node 22. (6) Lazy \`require()\` in test fixtures bypasses Vite's \`.js→.ts\` resolver — use top-level \`import\`. (7) \`spawn(process.execPath, \[workerScript.ts])\` fails under vitest/Node — use \`spawn('tsx', \[workerScript.ts])\`. Project uses vitest (migrated from bun:test); imports from 'vitest', mocks via \`vi.mock()\`. +* **Vitest worker pool requires pool:forks + UV\_USE\_IO\_URING=0 on GitHub Actions**: Vitest/CI gotchas: (1) GitHub Actions io\_uring crashes Node.js workers (exit 134/SIGABRT) — fix: \`pool: 'forks'\` in \`vitest.config.ts\` AND \`UV\_USE\_IO\_URING=0\` in CI. (2) Vitest 4: options must be second arg: \`test(name, { timeout }, fn)\`. (3) \`http.createServer(async ...)\` — unhandled rejections crash test server; wrap body in try/catch. (4) \`dorny/paths-filter\` diffs against base — empty commits produce all-false outputs. (5) \`node:sqlite\` requires \`--experimental-sqlite\` on Node 22. (6) Lazy \`require()\` in test fixtures bypasses Vite's \`.js→.ts\` resolver — use top-level \`import\`. (7) \`spawn(process.execPath, \[workerScript.ts])\` fails under vitest/Node — use \`spawn('tsx', \[workerScript.ts])\`. Project uses vitest (migrated from bun:test); imports from 'vitest', mocks via \`vi.mock()\`. \`vitest.config.ts\` at repo root. * **Whole-buffer matchAll slower than split+test when aggregated over many files**: Grep/scan traps in \`src/lib/scan/\`: (1) Whole-buffer \`regex.exec\` 12× faster per-file but ~1.6× SLOWER over 10k files — early-exit at \`maxResults\` via \`mapFilesConcurrent.onResult\` wins. (2) Literal prefilter is FILE-LEVEL gate (\`indexOf\`→skip); per-line verify breaks cross-newline patterns and Unicode length-changing \`toLowerCase\`. (3) Extractor \`hasTopLevelAlternation\`+\`skipGroup\` must call \`skipCharacterClass\`. (4) Wake-latch race: use latched \`pendingWake\` flag, not \`let notify=null; await new Promise(r=>notify=r)\`. (5) \`mapFilesConcurrent\` filters \`null\` but NOT \`\[]\` — return \`null\` for no-op files. (6) \`collectGlob\`/\`collectGrep\` must NOT forward \`maxResults\` to iterator; drain uncapped, set \`truncated=true\`. Worker pool: lazy singleton, size \`min(8, max(2, availableParallelism()))\`. Matches encoded as \`Uint32Array\` quads transferred via \`postMessage\` (~40% faster). \`new Worker(new URL(...))\` HANGS in SEA binaries — use Blob+URL.createObjectURL. FIFO \`pending\` queue per worker. \`ref()\`/\`unref()\` idempotent — only unref when \`inflight\` drops to 0. Disable via \`SENTRY\_SCAN\_DISABLE\_WORKERS=1\`. @@ -122,7 +146,7 @@ * **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. -* **Shared pagination infrastructure: buildPaginationContextKey and parseCursorFlag**: 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. \`hasPreviousPage\` checks \`page\_index > 0\`. \`paginationHint()\` builds nav strings. All list commands use this. Critical: \`resolveCursor()\` must be called inside \`org-all\` override closures, not before \`dispatchOrgScopedList\`. +* **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. * **Telemetry instrumentation pattern: withTracingSpan + captureException for handled errors**: For graceful-fallback operations, use \`withTracingSpan\` from \`src/lib/telemetry.ts\` for child spans and \`captureException\` from \`@sentry/bun\` (named import — Biome forbids namespace imports) with \`level: 'warning'\` for non-fatal errors. \`withTracingSpan\` uses \`onlyIfParent: true\` — no-op without active transaction. User-visible fallbacks use \`log.warn()\` not \`log.debug()\`. Several commands bypass telemetry by importing \`buildCommand\` from \`@stricli/core\` directly instead of \`../../lib/command.js\` (trace/list, trace/view, log/view, api.ts, help.ts). @@ -132,14 +156,83 @@ ### Preference + +* **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 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 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. + + +* **Always fix CI lint failures immediately before proceeding with other work**: When CI reports lint or typecheck failures, the user consistently treats them as blocking issues that must be resolved before continuing with feature work or merging. The user runs \`biome check --write\` (safe fixes first), then \`biome check --write --unsafe\` for remaining issues, and manually fixes any residual errors (e.g., hoisting regex to module level, reformatting long strings). Only after lint is clean does the user proceed with the next task. This applies across projects (getsentry/cli, loreai) and includes both formatter and linter rule violations. + + +* **Always get a PR up and monitor CI until all checks pass**: After completing implementation work, the user consistently requests that the assistant create a PR, then actively monitor CI and ensure all checks pass before considering the task done. This applies across different repos and task types. The user expects the assistant to: create the PR on the correct branch (not a wrong base), watch CI results, fix any failing checks, and only declare completion once all checks are green. The user sometimes also instructs the assistant to merge the PR once CI passes. In plan mode, the assistant should note it cannot execute these steps and call plan\_exit to signal readiness to proceed in build mode. + * **Always honor Retry-After header when present in LLM adapter**: (architecture) LLM adapter backoff in \`packages/gateway/src/llm-adapter.ts\`: Always honor Retry-After — \`backoffMs()\` returns \`Math.min(retryAfterMs, cap)\` where cap is \`RETRY\_AFTER\_CAP\_URGENT\_MS=8\_000\` or \`RETRY\_AFTER\_CAP\_BACKGROUND\_MS=120\_000\`. TRANSIENT\_CODES={429,500,502,503,529}; MAX\_RETRIES: rate-limit=3, server=3, urgent=2. Backoff (no Retry-After): 429 background=60s/120s/180s; urgent=min(1000×2^n,4000); 5xx background=min(1000×2^n,8000). Bearer tokens inject \`billingBlock\` as first system block; \`signBody()\` replaces \`cch=00000\` with xxHash64. System prompt caching uses \`cache\_control:{type:'ephemeral',ttl:'1h'}\`. \`opts.thinking\` NOT forwarded to bare API calls. Circuit breaker tripped on non-urgent 429s via \`tripCircuitBreaker()\`. Gateway auth (\`packages/gateway/src/auth.ts\`): \`AuthCredential\` (api-key|bearer). Two-level lookup: \`sessionAuth\` Map → \`lastSeenAuth\` global fallback via \`resolveAuth(sessionID?)\`. \`authFingerprint()\` = SHA-256 truncated to 16 hex chars. + +* **Always investigate and distinguish pre-existing issues from PR-specific failures before fixing**: When CI failures occur, the user consistently checks whether the failure exists on main (e.g., checking last successful main runs) before treating it as something to fix. If the failure is pre-existing or unrelated to current changes, the user explicitly notes it as such and moves on rather than fixing it. Apply this pattern: when a test failure or lint warning appears, first determine if it's on main or PR-specific. Only fix PR-specific regressions; document pre-existing issues as out of scope for the current change. + + +* **Always investigate bundle resolution issues by inspecting minified variable names and esbuild's static analysis limitations**: When debugging 'Cannot find module' errors in bundled output, the user consistently digs into the root cause at the esbuild level: checking whether require calls use bare \`require()\` vs renamed aliases like \`\_require\`, inspecting minified bundle output for renamed variable patterns, and verifying that esbuild only statically resolves bare \`require()\` calls. The user expects the assistant to check the actual bundle contents (grep for minified names, count createRequire occurrences, verify no relative requires remain unresolved) rather than assuming the fix worked. The fix pattern is always: use bare \`require\` (not \`\_require\` or other aliases) for local relative imports so esbuild can inline them at bundle time. + + +* **Always investigate root causes before accepting PR fixes at face value**: When reviewing PRs, the user consistently digs past the stated fix to verify whether the implementation actually solves the root cause. They examine bundle output, run smoke tests, check CI logs, and trace failure modes (e.g., esbuild variable renaming, wrong \`createRequire\` anchor, silent runtime failures). They expect the reviewer/assistant to identify not just surface bugs but also latent/silent failures introduced by the fix. Reviews should include: confirming the fix works end-to-end, identifying any new failure modes introduced, and flagging silent regressions (e.g., features that appear to work but silently fall back or skip logic). + + +* **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**: The user is actively migrating a CLI project from Bun to Node.js/pnpm. When encountering Bun-specific code, always replace it with Node.js equivalents: \`Bun.spawn\` → \`node:child\_process\` spawn, \`Bun.sleep\` → \`node:timers/promises\` setTimeout, \`bun:sqlite\` → \`node:sqlite\`, \`bun run\` → \`pnpm run\`/\`tsx\`, \`bun.lock\` → pnpm lockfile. All packages must be in \`devDependencies\` (never \`dependencies\`). Exception: \`script/build.ts\` uses \`Bun.build()\` and stays on Bun; the \`build-binary\` CI job retains \`setup-bun\`. After each migration phase, ensure lint and tests pass before committing. +* **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 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. + + +* **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. + + +* **Always pull from origin/main before starting any exploration or work in getsentry/cli**: Before beginning any exploration, investigation, or implementation work in the getsentry/cli repository, always run \`git pull origin/main\` first. If there are local changes (e.g., in \`.lore.md\`) that block the pull, stash them, complete the pull, resolve any conflicts by checking out the index version, then drop the stash. This is an explicit user directive that applies at the very start of every session involving this repo, regardless of what work is planned. + + +* **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, smoke testing, codesigning), they consistently push to move builds to native runners for each target platform. This applies to macOS targets moving from ubuntu-latest to macos-latest, and extends to investigating per-platform features like useCodeCache and useSnapshot in fossilize. When the assistant identifies a cross-compilation limitation, the user expects a concrete plan to restructure CI matrix jobs to use native runners, and will follow up to explore related optimizations (e.g., cloning upstream repos to check feasibility). Always check whether current CI runners match the target platform and propose native runner alternatives when they don't. + + +* **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 work from a plan file before making code changes**: When starting work on a GitHub issue or significant feature, the user expects a plan file to be created first (at \`.opencode/plans/\-\.md\`) before any edits to other files. The user enforces a 'plan mode' where only the plan file may be written. Only after the plan is established does implementation proceed. This applies to new features, bug fixes, and test additions alike. The assistant should create the plan file first, document the problem, proposed approach, and estimated impact, then await confirmation before touching source or CI files. + + +* **Always work from a structured plan file before executing multi-step tasks**: When tackling multi-step or multi-file changes, the user consistently creates a formal plan file (e.g., \`.opencode/plans/\-\.md\`) during a planning phase before any edits are made. The plan enumerates discrete numbered tasks with priorities and target files. Execution only begins after the user explicitly approves the plan. During execution, tasks are marked in\_progress and completed sequentially. The user expects this plan-then-execute workflow to be followed strictly — no file edits during planning, and tasks tracked against the approved plan. + + +* **Always write tests after implementing new modules or features**: After implementing a new module or integrating a feature, the user consistently adds corresponding tests — both a dedicated test file for the new module (e.g., \`semantic-display.test.ts\`) and additional tests in existing test files for integration points (e.g., new describe blocks or test cases in \`local.test.ts\`). The user also reads existing test files first to understand patterns before writing new tests. Tests are added as a required step in the todo list, not as an afterthought, and are followed by typecheck/test runs to verify correctness. * **Bot review triage: distinguish real bugs from SDK-mirroring false positives**: When Sentry Seer or Cursor Bugbot flags 'unusual' code that intentionally mirrors upstream SDK behavior (e.g., \`http\_proxy\` as last-resort fallback for HTTPS URLs — deliberate in \`@sentry/node-core\` \`applyNoProxyOption\`), decline with a written rationale referencing the SDK source rather than silently changing behavior. Removing the mirror creates a divergence where users get different proxy semantics from our transport vs. the SDK default. BYK's pattern: verify against \`node\_modules/@sentry/node-core/build/esm/transports/http.js\`, post a reply explaining the precedent, and resolve the thread. Real bugs (uppercase env var support, whitespace trimming, wildcard handling) get fixed; SDK-mirroring 'bugs' get explained and dismissed. + +* **Never merge a PR if CI is failing**: NEVER merge a PR if CI is failing unless the user explicitly says to ignore specific failures in that session. This is an absolute directive repeated across 20+ sessions. + -* **Prefers Bun-native APIs; use buildCommand from lib/command.js (never @stricli/core directly); use buildRouteMap from lib/route-map.js; silent catch blocks prohibited; every new src/lib/\*\*/\*.ts must start with module-level JSDoc; test isolation via useTestConfigDir(); prefer property-based and model-based tests over unit tests; DEFAULT\_NUM\_RUNS = 50; architecture tree documented; error exit code ranges: 1x=auth**: Project conventions (AGENTS.md): use \`pnpm run\`/\`pnpm install\`/\`pnpm add -D\` (NOT bun); use buildCommand from lib/command.js (never @stricli/core directly); use buildRouteMap from lib/route-map.js; silent catch blocks prohibited; every new src/lib/\*\*/\*.ts must start with module-level JSDoc; test isolation via useTestConfigDir(); prefer property-based and model-based tests over unit tests; DEFAULT\_NUM\_RUNS=50; error exit code ranges: 1x=auth, 2x=input/config, 3x=API/network, 4x=feature/billing, 5x=operations, 6x=command-specific. Testing: vitest + fast-check (migrated from bun:test). All packages in devDependencies (CI enforces via \`pnpm run check:deps\`). Always check package.json for latest scripts. +* **Prefers Bun-native APIs; use buildCommand from lib/command.js (never @stricli/core directly); use buildRouteMap from lib/route-map.js; silent catch blocks prohibited; every new src/lib/\*\*/\*.ts must start with module-level JSDoc; test isolation via useTestConfigDir(); prefer property-based and model-based tests over unit tests; DEFAULT\_NUM\_RUNS = 50; architecture tree documented; error exit code ranges: 1x=auth**: Project conventions (AGENTS.md): use \`pnpm run\`/\`pnpm install\`/\`pnpm add -D\` (NOT bun for package management); use buildCommand from lib/command.js (never @stricli/core directly); use buildRouteMap from lib/route-map.js; silent catch blocks prohibited; every new src/lib/\*\*/\*.ts must start with module-level JSDoc; test isolation via useTestConfigDir(); prefer property-based and model-based tests over unit tests; DEFAULT\_NUM\_RUNS=50; error exit code ranges: 1x=auth, 2x=input/config, 3x=API/network, 4x=feature/billing, 5x=operations, 6x=command-specific. Testing: vitest + fast-check. All packages in devDependencies (CI enforces via \`pnpm run check:deps\`). NEVER merge a PR if CI is failing unless explicitly told to ignore. Always use \`pnpm add -D \\` — never add to \`dependencies\`. + + +* **Review code before committing**: Behavioral pattern detected across 4 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). + + +* **Spend time on robust esbuild config — cover sourcemaps too**: When working on the build pipeline, spend time figuring out a robust esbuild config and stick to it. The config must also cover sourcemaps (debug ID injection, upload gating on SENTRY\_AUTH\_TOKEN). Don't iterate ad-hoc — design the full config once correctly. + + +* **Telemetry implementation invariants: handler cleanup, uid check, non-blocking, redaction**: Four absolute telemetry directives in \`src/lib/telemetry.ts\`: (1) \`initSentry()\` ALWAYS removes \`currentBeforeExitHandler\` via \`process.removeListener\` before registering new one — prevents duplicate handlers on re-init. (2) \`isOwnedByRoot()\` ALWAYS returns \`false\` immediately on \`process.platform === 'win32'\` — Windows \`fs.stat().uid\` always returns 0 regardless of actual ownership. (3) NEVER block CLI execution for telemetry emission — all telemetry drains are best-effort, wrapped in try/catch. (4) \`SENSITIVE\_ARGV\_FLAGS\` (includes \`token\`, \`auth-token\`) NEVER sent to telemetry — \`redactArgv()\` handles both \`--flag=value\` and \`--flag \\` forms. + + +* **tryReadPem must never throw — missing CA file is non-fatal**: Absolute directive: \`tryReadPem()\` in \`src/lib/custom-ca.ts\` NEVER throws. A missing or unreadable CA file must not crash the CLI — log a warning and return \`undefined\`. This is a design invariant for CA loading: failures are best-effort, not fatal. diff --git a/script/build.ts b/script/build.ts index 9db8e522c..71787c98e 100644 --- a/script/build.ts +++ b/script/build.ts @@ -47,6 +47,11 @@ const VERSION: string = pkg.version; /** Pin to Node 22 LTS for SEA binaries */ const NODE_VERSION = "22"; +/** Files that use _require() for lazy relative imports (circular dep breaking). */ +const REQUIRE_ALIAS_FILTER = + /(?:db[\\/](?:index|schema)|list-command|telemetry)\.ts$/; +const REQUIRE_ALIAS_RE = /\b_require\(/g; + /** Build-time constants injected into the binary */ const SENTRY_CLIENT_ID = process.env.SENTRY_CLIENT_ID ?? ""; @@ -140,7 +145,26 @@ async function bundleJs(): Promise { "process.env.NODE_ENV": JSON.stringify("production"), __SENTRY_DEBUG_ID__: JSON.stringify(PLACEHOLDER_DEBUG_ID), }, - plugins: [textImportPlugin], + plugins: [ + textImportPlugin, + // Transform _require() → require() so esbuild resolves lazy relative + // requires at bundle time. In tsx dev mode, _require is a file-local + // createRequire(import.meta.url) that resolves relative to the file. + // esbuild only statically resolves bare require() calls. + // Only targets the specific files that use _require with relative paths. + { + name: "require-alias", + setup(b) { + b.onLoad({ filter: REQUIRE_ALIAS_FILTER }, async (args) => { + const source = await readFile(args.path, "utf-8"); + return { + contents: source.replace(REQUIRE_ALIAS_RE, "require("), + loader: args.path.endsWith(".tsx") ? "tsx" : "ts", + }; + }); + }, + }, + ], }); const output = result.metafile?.outputs[BUNDLE_JS]; diff --git a/script/bundle.ts b/script/bundle.ts index e0fd8e8dd..7e0abb768 100644 --- a/script/bundle.ts +++ b/script/bundle.ts @@ -152,7 +152,30 @@ const sentrySourcemapPlugin: Plugin = { }; // Always inject debug IDs (even without auth token); upload is gated inside the plugin -const plugins: Plugin[] = [sentrySourcemapPlugin, textImportPlugin]; +/** Files that use _require() for lazy relative imports (circular dep breaking). */ +const REQUIRE_ALIAS_FILTER = + /(?:db[\\/](?:index|schema)|list-command|telemetry)\.ts$/; +const REQUIRE_ALIAS_RE = /\b_require\(/g; + +/** Transform _require() → require() so esbuild resolves lazy relative requires. */ +const requireAliasPlugin: Plugin = { + name: "require-alias", + setup(b) { + b.onLoad({ filter: REQUIRE_ALIAS_FILTER }, async (args) => { + const source = await readFile(args.path, "utf-8"); + return { + contents: source.replace(REQUIRE_ALIAS_RE, "require("), + loader: args.path.endsWith(".tsx") ? "tsx" : "ts", + }; + }); + }, +}; + +const plugins: Plugin[] = [ + sentrySourcemapPlugin, + textImportPlugin, + requireAliasPlugin, +]; if (process.env.SENTRY_AUTH_TOKEN) { console.log(" Sentry auth token found, source maps will be uploaded"); diff --git a/script/require-shim.mjs b/script/require-shim.mjs index 54ccb33ff..0d8420ce5 100644 --- a/script/require-shim.mjs +++ b/script/require-shim.mjs @@ -1,24 +1,14 @@ /** - * ESM preload shim that provides `require` in ESM modules and handles - * `with { type: "file" }` import attributes in tsx dev mode. + * ESM preload shim for tsx dev mode. * - * The source code uses bare `require()` for lazy loading (circular dependency - * breaking, optional features). This works natively in Bun and in the CJS - * bundle, but fails under tsx/Node ESM. This shim bridges the gap by making - * `require` globally available via `createRequire`. + * 1. Provides a global `require()` for ESM modules in `"type": "module"` + * packages. Anchored at the project root — works for `node:*` builtins + * and npm packages. Files that need relative `require()` must use a + * file-local `createRequire(import.meta.url)` instead. * - * The `require` function is anchored at the project root (package.json) so - * that `node:*` builtins and npm package requires resolve correctly. Note - * that relative `require("./foo.js")` calls resolve from the project root, - * not from the calling file. Files in `src/` that use lazy relative requires - * must use a file-local `createRequire(import.meta.url)` instead of relying - * on this global shim. - * - * `with { type: "file" }` import attributes are used to embed sidecar files - * (e.g. the Ink UI app). Bun supports this natively; esbuild's - * text-import-plugin handles it at build time. In tsx dev mode neither - * applies, so we register a loader hook that returns the file path as a - * string — matching Bun's native behaviour. + * 2. Handles `with { type: "file" }` import attributes that Node.js doesn't + * support natively. Registers a loader hook that returns the file path + * as a string — matching Bun's native behaviour. * * Usage: NODE_OPTIONS="--import ./script/require-shim.mjs" tsx script/... * Or in package.json scripts via the `pnpm tsx` alias. diff --git a/src/cli.ts b/src/cli.ts index 34ccaf1f6..2ca3e67f1 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,9 +11,7 @@ */ import { getEnv } from "./lib/env.js"; -import { captureEnvTokenHost } from "./lib/env-token-host.js"; import { CliError } from "./lib/errors.js"; -import { applySentryCliRcEnvShim } from "./lib/sentryclirc.js"; /** * Preload project context: walk up from `cwd` once, finding both the @@ -25,6 +23,8 @@ async function preloadProjectContext(cwd: string): Promise { // Snapshot env-token host BEFORE anything mutates env.SENTRY_HOST/URL // (the .sentryclirc shim or the default-URL fallback below). Pins the // env-token's trust scope to the user's shell, not a repo-local file. + // Dynamic import: env-token-host chains into db/auth → telemetry → @sentry/node-core + const { captureEnvTokenHost } = await import("./lib/env-token-host.js"); captureEnvTokenHost(); // Dynamic import keeps the heavy DSN/DB modules out of the completion fast-path @@ -42,6 +42,8 @@ async function preloadProjectContext(cwd: string): Promise { // Apply .sentryclirc env shim (token + URL). The URL trust check is // deferred to buildCommand's wrapper where commands can opt out via // skipRcUrlCheck (used by auth login/logout). + // Dynamic import: sentryclirc chains into db/index → sqlite, logger → consola + const { applySentryCliRcEnvShim } = await import("./lib/sentryclirc.js"); await applySentryCliRcEnvShim(cwd); // Apply persistent URL default (lower priority than env vars and .sentryclirc). diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index ba1f9f69a..0eba055cb 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -111,8 +111,7 @@ export function getDatabase(): Database { if (getEnv().SENTRY_CLI_NO_TELEMETRY === "1") { db = rawDb; } else { - // bare require so esbuild resolves this at bundle time (breaks circular dep) - const { createTracedDatabase } = require("../telemetry.js") as { + const { createTracedDatabase } = _require("../telemetry.js") as { createTracedDatabase: (d: Database) => Database; }; db = createTracedDatabase(rawDb); diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts index b5ea7a268..ecf9c4be6 100644 --- a/src/lib/db/schema.ts +++ b/src/lib/db/schema.ts @@ -674,8 +674,7 @@ export function tryRepairAndRetry( let repairSucceeded = false; try { // Dynamic imports to avoid circular dependencies with db/index.js - // bare require so esbuild resolves this at bundle time (breaks circular dep) - const { getRawDatabase } = require("./index.js") as { + const { getRawDatabase } = _require("./index.js") as { getRawDatabase: () => Database; }; diff --git a/src/lib/list-command.ts b/src/lib/list-command.ts index 257427696..43f170d36 100644 --- a/src/lib/list-command.ts +++ b/src/lib/list-command.ts @@ -418,8 +418,7 @@ function getSubcommandsForRoute(routeName: string): Set { _subcommandsByRoute = new Map(); try { - // bare require so esbuild resolves this at bundle time (breaks circular dep) - const { routes } = require("../app.js") as { + const { routes } = _require("../app.js") as { routes: { getAllEntries: () => readonly RouteEntry[] }; }; diff --git a/src/lib/telemetry.ts b/src/lib/telemetry.ts index eab827cac..ab5fb08ca 100644 --- a/src/lib/telemetry.ts +++ b/src/lib/telemetry.ts @@ -1021,8 +1021,7 @@ const noop = (): void => {}; /** Resolves the database path, falling back to a default if the import fails. */ function resolveDbPath(): string { try { - // bare require so esbuild resolves this at bundle time (breaks circular dep) - const { getDbPath } = require("./db/index.js") as { + const { getDbPath } = _require("./db/index.js") as { getDbPath: () => string; }; return getDbPath();