You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor: auto-generate SDK from route tree, single entry point
Rework the typed SDK to auto-discover ALL commands from the Stricli
route tree with zero manual config:
- generate-sdk.ts walks the entire route tree recursively, discovers
44 commands, generates typed methods using CLI route names as-is
(sdk.org.list, sdk.dashboard.widget.add)
- Return types derived from __jsonSchema (PR #582) via
extractSchemaFields() — commands with schemas get typed returns,
others default to unknown
- Positional params derived from introspection placeholder strings
- createSentrySDK() is now the single public API (default export)
- sdk.run() escape hatch replaces the standalone sentry() function
- SentryError/SentryOptions extracted to sdk-types.ts to break
circular deps between index.ts and sdk-invoke.ts
* **Bundle uses esbuild with bun:sqlite polyfill plugin for Node.js compatibility**: \`script/bundle.ts\` uses esbuild to produce \`dist/index.cjs\`(the main bundle — library + CLI internals) from \`src/index.ts\`. A \`bunSqlitePlugin\` replaces \`bun:sqlite\` imports with a polyfill. Build defines \`SENTRY\_CLI\_VERSION\` and \`SENTRY\_CLIENT\_ID\_BUILD\`, externalizes \`node:\*\` builtins. A \`sentrySourcemapPlugin\` handles debug ID injection and sourcemap upload. After the main build, the script writes: (1) \`dist/bin.cjs\` — tiny ~300-byte CLI wrapper with shebang, Node version check, warning suppression, and \`require('./index.cjs').\_cli()\`, (2) \`dist/index.d.cts\` — type declarations auto-derived from \`src/sdk.generated.ts\` via\`extractSdkTypes()\`. The warning suppression banner lives only in the bin wrapper, not the library bundle — library consumers' \`process.emit\` is never patched. Debug IDs solve sourcemap deduplication between the npm bundle and bun compile builds.
897
+
* **Bundle uses esbuild with bun:sqlite polyfill plugin for Node.js compatibility**: \`script/bundle.ts\` uses esbuild to produce \`dist/index.cjs\` from \`src/index.ts\`. A \`bunSqlitePlugin\` replaces \`bun:sqlite\` imports with a polyfill. Build defines \`SENTRY\_CLI\_VERSION\` and \`SENTRY\_CLIENT\_ID\_BUILD\`, externalizes \`node:\*\` builtins. \`sentrySourcemapPlugin\` handles debug ID injection and sourcemap upload. After the main build, writes: (1) \`dist/bin.cjs\` — CLI wrapper with shebang/Node version check/warning suppression, (2) \`dist/index.d.cts\` — type declarations read from pre-built \`src/sdk.generated.d.cts\`. Both\`sdk.generated.\*\` files are gitignored and regenerated via \`generate:sdk\` script chained before \`bundle\` in \`package.json\`. Debug IDs solve sourcemap deduplication between npm bundle and bun compile builds.
* **CLI logic extracted from bin.ts into cli.ts for shared entry points**: \`src/cli.ts\` contains the full CLI runner extracted from \`bin.ts\`: \`runCompletion()\` (shell completion fast path), \`runCli()\` (full CLI with middleware — auto-auth, seer trial, unknown command telemetry), and \`startCli()\` (top-level dispatch). All functions are exported, no top-level execution. \`src/bin.ts\` is a thin ~30-line wrapper for bun compile that registers EPIPE/EIO stream error handlers and calls \`startCli()\`. The npm bin wrapper (\`dist/bin.cjs\`) is a ~300-byte generated script that \`require('./index.cjs').\_cli()\`. Both entry points share the same CLI logic via \`cli.ts\`.
* **getConfigDir and getAuthToken read global process.env directly**: \`src/lib/env.ts\` provides a module-level env registry: \`getEnv()\` defaults to \`process.env\`, \`setEnv(env)\` swaps it. Library entry (\`src/index.ts\`) calls \`setEnv({ ...process.env, ...overrides })\` before running commands, restores in \`finally\`. All ~14 files that previously read \`process.env.SENTRY\_\*\` directly now use \`getEnv().SENTRY\_\*\`. Key files ported: \`db/auth.ts\`, \`db/index.ts\`, \`db/schema.ts\`, \`constants.ts\`, \`resolve-target.ts\`, \`telemetry.ts\`, \`formatters/plain-detect.ts\`, \`sentry-url-parser.ts\` (which also WRITES to env), \`logger.ts\`, \`response-cache.ts\`, \`api/infrastructure.ts\`, \`dsn/env.ts\`, \`version-check.ts\`, \`oauth.ts\`. CLI mode never calls \`setEnv()\` so behavior is unchanged. Tests using \`useTestConfigDir()\` continue to work since \`getEnv()\` defaults to \`process.env\`.
* **Library API: variadic sentry() function with last-arg options detection**: The \`sentry()\` library export in \`src/index.ts\` uses variadic args with optional trailing options object: \`sentry("issue", "list", { token: "..." })\`. Last-arg detection: if final arg is an object (not string), it's \`SentryOptions\`. Options: \`token?\` (auth override, auto-fills from \`SENTRY\_AUTH\_TOKEN\`/\`SENTRY\_TOKEN\` env vars), \`text?\` (return human-readable string instead of parsed JSON), \`cwd?\` (working directory for DSN detection). Default behavior: JSON output via \`SENTRY\_OUTPUT\_FORMAT=json\` env var. Zero-copy return via \`captureObject\` duck-type on Writer — skips JSON.stringify/parse round-trip. Non-zero exit throws \`SentryError\` with \`.exitCode\` and \`.stderr\`. If \`capturedResult\` exists when an error is thrown (OutputError pattern), returns the data instead of throwing. Env isolation via \`setEnv({ ...process.env })\` — consumer's \`process.env\` never mutated. \`createSentrySDK(options?)\` provides typed namespace API that invokes commands directly via \`Command.loader()\`, bypassing Stricli's string dispatch.
906
+
* **Library API: variadic sentry() function with last-arg options detection**: \`createSentrySDK(options?)\` in \`src/index.ts\` is the sole public API. Returns a typed SDK object with methods for every CLI command plus \`run(...args)\` escape hatch. \`SentryError\` and \`SentryOptions\` live in \`src/lib/sdk-types.ts\` (shared module avoiding circular deps between \`index.ts\` ↔ \`sdk-invoke.ts\`), re-exported from \`index.ts\`. Env isolation via \`setEnv()\`, zero-copy \`captureObject\` return, \`OutputError\` → data recovery. \`buildInvoker()\` (typed methods) and \`buildRunner()\` (run escape hatch) in \`sdk-invoke.ts\`. Options: \`token?\`, \`text?\` (run-only), \`cwd?\`. Default JSON output via \`SENTRY\_OUTPUT\_FORMAT=json\` env var. Non-zero exit throws \`SentryError\` with \`.exitCode\` and \`.stderr\`.
* **Library mode telemetry strips all global-polluting Sentry integrations**: When \`initSentry(enabled, { libraryMode: true })\` is called, the Sentry SDK initializes without integrations that pollute the host process. \`LIBRARY\_EXCLUDED\_INTEGRATIONS\` extends the base set with: \`OnUncaughtException\`, \`OnUnhandledRejection\`, \`ProcessSession\` (process listeners), \`Http\`/\`NodeFetch\` (trace header injection), \`FunctionToString\` (wraps \`Function.prototype.toString\`), \`ChildProcess\`/\`NodeContext\`. Also disables \`enableLogs\` and \`sendClientReports\` (both use timers/\`beforeExit\`), and skips \`process.on('beforeExit')\` handler registration. Keeps pure integrations: \`eventFiltersIntegration\`, \`linkedErrorsIntegration\`. Library entry manually calls \`client.flush(3000)\` after command completion (both success and error paths via \`flushTelemetry()\` helper). Only unavoidable global: \`globalThis.\_\_SENTRY\_\_\[SDK\_VERSION]\`.
* **Typed SDK uses direct Command.loader() invocation bypassing Stricli dispatch**: \`createSentrySDK(options?)\` in \`src/index.ts\` builds a typed namespace API (\`sdk.organizations.list()\`, \`sdk.issues.get()\`) generated by \`script/generate-sdk.ts\`. At runtime, \`src/lib/sdk-invoke.ts\` resolves commands by walking the Stricli route tree via \`routes.getRoutingTargetForInput()\`, caches the \`Command\` objects, then calls \`command.loader()\` to get the wrapped func and invokes it with \`func.call(context, flags, ...args)\`. This bypasses Stricli's string scanning, route resolution, and flag parsing entirely — flags are passed as typed objects. The codegen script introspects flag definitions (kind, default, optional, variadic) at build time and generates TypeScript parameter interfaces. Return types use a manual type map (~20 entries) until schema registration on OutputConfig is implemented (#566).
912
+
* **Typed SDK uses direct Command.loader() invocation bypassing Stricli dispatch**: \`createSentrySDK(options?)\` in \`src/index.ts\` builds a typed namespace API (\`sdk.org.list()\`, \`sdk.issue.view()\`) generated by \`script/generate-sdk.ts\`. At runtime, \`src/lib/sdk-invoke.ts\` resolves commands via Stricli route tree, caches \`Command\` objects, and calls \`command.loader()\` directly — bypassing string dispatch and flag parsing. The standalone variadic \`sentry()\` function has been removed. Typed SDK methods are the primary path, with \`sdk.run()\` as an escape hatch for arbitrary CLI strings (interactive commands like \`auth login\`, raw \`api\` passthrough). The codegen auto-discovers ALL commands from the route tree with zero config, using CLI route names as-is (\`org.list\`, \`dashboard.widget.add\`). Return types are derived from \`\_\_jsonSchema\` when present, otherwise \`unknown\`. Positional patterns are derived from introspection placeholder strings. Hidden routes (plural aliases) are skipped.
* **OutputError propagates via throw instead of process.exit()**: The \`process.exit()\` call in \`command.ts\` (OutputError handler) is replaced with \`throw err\` to support library mode. \`OutputError\` is re-thrown through Stricli via \`exceptionWhileRunningCommand\` in \`app.ts\` (added before the \`AuthError\` check), so Stricli never writes an error message for it. In CLI mode (\`cli.ts\`), OutputError is caught and \`process.exitCode\` is set silently without writing to stderr (data was already rendered). In library mode (\`index.ts\`), the catch block checks if \`capturedResult\` has data (the OutputError's payload was rendered to stdout via \`captureObject\` before the throw) and returns it instead of throwing \`SentryError\`. This eliminates the only \`process.exit()\` outside of \`bin.ts\`.
* **SDK codegen moving to auto-generate all commands from route tree**: \`script/generate-sdk.ts\` walks the Stricli route tree via \`discoverCommands()\`, skipping hidden routes. For each command: extracts flags via introspection, derives positional params from placeholder strings, checks \`\_\_jsonSchema\` for typed return types. Naming uses CLI route path as-is: \`\["org", "list"]\` → \`sdk.org.list()\`. Generates TWO files: (1) \`src/sdk.generated.ts\` — runtime implementations, (2) \`src/sdk.generated.d.cts\` — type declarations for npm package. Both are gitignored and regenerated at build time. The \`generate:sdk\` script is chained before \`typecheck\`, \`dev\`, \`build\`, \`build:all\`, and \`bundle\` in \`package.json\`. CI check \`bun run check:skill\` validates SKILL.md stays in sync with command changes.
* **Target argument 4-mode parsing convention (project-search-first)**: \`parseOrgProjectArg()\` in \`src/lib/arg-parsing.ts\` returns a 4-mode discriminated union: \`auto-detect\` (empty), \`explicit\` (\`org/project\`), \`org-all\` (\`org/\` trailing slash), \`project-search\` (bare slug). Bare slugs are ALWAYS \`project-search\` first. The "is this an org?" check is secondary: list commands with \`orgSlugMatchBehavior\` pre-check cached orgs (\`redirect\` or \`error\` mode), and \`handleProjectSearch()\` has a safety net checking orgs after project search fails. Non-list commands (init, view) treat bare slugs purely as project search with no org pre-check. For \`init\`, unmatched bare slugs become new project names. Key files: \`src/lib/arg-parsing.ts\` (parsing), \`src/lib/org-list.ts\` (dispatch + org pre-check), \`src/lib/resolve-target.ts\` (resolution cascade).
* **Writer type is the minimal output interface for streams and mocks**: The \`Writer\` type in \`src/types/index.ts\` is \`{ write(data: string): void}\`. In library mode, the Writer is extended with a duck-typed \`captureObject(obj: unknown)\`method that \`emitJsonObject()\`in \`output.ts\` checks for via \`hasCaptureObject()\`. When present, the fully-transformed JSON object (post-\`jsonTransform\`, post-\`filterFields\`) is passed directly to the capture callback, skipping \`JSON.stringify\`. CLI mode is unaffected — \`process.stdout\` doesn't have \`captureObject\`, so \`emitJsonObject\`falls through to \`writeJson\`. The SDK invoke layer also uses this pattern via \`buildCaptureContext()\`.
931
+
* **Writer type is the minimal output interface for streams and mocks**: The \`Writer\` type in \`src/types/index.ts\` is \`{ write(data: string): void; captureObject?: (obj: unknown) => void }\`. The optional \`captureObject\` property replaces the previous duck-typing pattern (\`hasCaptureObject()\`with \`typeof\`check and \`Record\<string, unknown>\` cast). In library mode, the writer sets \`captureObject\` to capture the fully-transformed JSON object directly without serialization. In CLI mode, \`process.stdout\` lacks this property so it's \`undefined\` → falsy, and \`emitJsonObject()\` falls through to \`JSON.stringify\`. The check is now a simple truthiness test: \`if (stdout.captureObject)\`. Since \`captureObject\` is part of the \`Writer\`type, \`sdk-invoke.ts\` no longer needs \`Writer & { captureObject?: ... }\` intersection types — plain \`Writer\` suffices.
0 commit comments