//#region src/endpoint.d.ts /** * Production endpoint pinned to the deployed Prisma Compute backend. * Compiled as a build-time constant; not user-configurable. */ declare const TELEMETRY_BACKEND_URL = "https://cmpbfbsdp09hr3jf7pojjs5qs.ewr.prisma.build"; /** * Path within the backend that accepts telemetry POSTs. */ declare const TELEMETRY_ENDPOINT_PATH = "/events"; /** * Resolve the full POST URL the sender targets. The * `PRISMA_NEXT_TELEMETRY_ENDPOINT` env var is an integration-testing * affordance only — it lets the test suite spin up a mock HTTP server * on an ephemeral port and point the spawned sender at it. The override * is intentionally undocumented in user-facing material. * * Fail-open: a malformed override (typo in a dev shell, bad CI config) * silently falls back to the production backend rather than throwing, * matching the telemetry layer's broader silent-on-failure contract. */ declare function resolveTelemetryEndpoint(env?: Readonly>): string; //#endregion //#region src/payload.d.ts /** * Wire-shape payload the parent IPC-sends to the forked child sender. * Mirrors only the fields the parent has naturally in hand at command * start: installation id, sanitised command + flags, CLI version, and * the project root the child uses to discover everything else. The * child probes its own process (runtime/os/arch, package manager, ts * version, agent) and reads the user's `prisma-next.config.*` via * c12 to derive `databaseTarget` and `extensions`. * * Loading c12 on the parent side would put a `loadConfig()` await on * the command's hot path between gate resolution and `fork()`, * opening a race against any CLI command that throws synchronously * before that await resolves (the parent exits before forking the * sender, and the telemetry event is lost). Moving the load into the * detached child eliminates that race; the trade is that the child * now evaluates user TS config code, so it's gated behind the same * privacy checks the parent already resolved before forking. * * `databaseTarget` is an optional parent-side override for the * c12-derived value: the first-`init` invocation supplies the * prompt-chosen target via this field because the config file does * not yet exist on disk at that moment. Every other invocation * leaves it unset (`undefined`) and the child's c12 load determines * the value — there is no third state, so the field's type is * `string | undefined`, not `string | null | undefined`. * * Both sides version-couple on this shape because the IPC carrier is * structured-cloned by Node and there's no on-wire compat to maintain. */ interface ParentToSenderPayload { readonly installationId: string; readonly version: string; readonly command: string; readonly flags: readonly string[]; /** * Absolute path of the user's project. The child reads * `/package.json` for `tsVersion` and loads * `/prisma-next.config.*` via c12 for `databaseTarget` * + `extensions`. */ readonly projectRoot: string; /** Resolved endpoint URL (already includes the `/events` path). */ readonly endpoint: string; /** * Optional parent-side override for the c12-derived database target. * Set by `fireTelemetryAfterInitConsent` (the first-`init` path, * where the config file is about to be written but doesn't exist * yet); left undefined by `fireTelemetryFromPreAction` (steady * state, child resolves the value via c12). The wire-format * `TelemetryEvent.databaseTarget: string | null` keeps `null` as * the on-the-wire "no target known" marker, but the IPC override * channel only needs two states so it's `string | undefined`. */ readonly databaseTarget?: string; } /** * The full event the child POSTs to the backend. Shape matches the * backend's arktype schema (`apps/telemetry-backend/src/schema.ts`). */ interface TelemetryEvent { readonly installationId: string; readonly version: string; readonly command: string; readonly flags: readonly string[]; readonly runtimeName: string; readonly runtimeVersion: string; readonly os: string; readonly arch: string; readonly packageManager: string | null; readonly databaseTarget: string | null; readonly tsVersion: string | null; readonly agent: string | null; readonly extensions: readonly string[]; } //#endregion //#region src/enrich.d.ts /** * Subset of the user's `prisma-next.config.*` the telemetry event * surfaces. Loaded inside the detached child via {@link loadProjectConfig} * — see the design rationale on {@link ParentToSenderPayload} for why * this side runs c12 instead of the parent CLI. */ interface ProjectConfigFields { readonly databaseTarget: string | null; readonly extensions: readonly string[]; } /** * Best-effort load of `prisma-next.config.*` from `projectRoot`, * validated against the canonical `@prisma-next/config` schema. * Returns `{ databaseTarget: null, extensions: [] }` on any failure * mode — missing config file (e.g. before `prisma-next init`), c12 * throws while evaluating user TS, validator rejects a malformed * shape, etc. Telemetry is non-blocking and best-effort; an empty * result is the only downside of an unloadable or invalid config. * * Both `c12` and `@prisma-next/config/config-validation` are imported * lazily so the detached sender's cold-start cost is paid only when * telemetry actually fires, not on every fork even when gates * short-circuit before reaching this code path. */ declare function loadProjectConfig(projectRoot: string): Promise; //#endregion //#region src/user-config.d.ts /** * The user-level config file. Persists the telemetry flag and the * installation UUID. Under the opt-out model the flag stays `undefined` * until the user makes an explicit choice (default-on first run mints * only the id via {@link ensureInstallationId}), and an env-var opt-out * never mutates disk. Once the id exists it survives any * on → off → on cycle, keeping the same UUID (correct for MAU continuity). * * Readers tolerate unknown fields for forward compat; writers merge * partials into the existing object so unknown fields are preserved. */ interface UserConfig { readonly enableTelemetry?: boolean; readonly installationId?: string; readonly [key: string]: unknown; } /** * Path to the user-level config file. Resolved per call so test * harnesses can mutate `$XDG_CONFIG_HOME` between cases. */ declare function userConfigPath(): string; /** * Reads the user-level config. File-missing, unreadable, or malformed → * `{}` (the absence of consent is the same answer in every error mode). * Unknown fields from a future client are passed through verbatim. */ declare function readUserConfig(): UserConfig; /** * Merges `partial` into the current config and writes the result * atomically (temp file + rename) so a crash mid-write never leaves a * half-baked file readable on disk. Unknown fields already on disk are * preserved. * * When `partial.enableTelemetry === true` and no `installationId` is * stored yet, generates a v4 random UUID and persists both fields in * the same write. An existing `installationId` is never rotated. This is * the *explicit-consent* mint path: a `false` answer * (`writeUserConfig({ enableTelemetry: false })`) writes no id, and a bare * `writeUserConfig({ installationId })` mints nothing extra. The default-on * first-send path mints its id separately via {@link ensureInstallationId}, * which records no consent answer. */ declare function writeUserConfig(partial: Partial): void; /** * Returns the stored `installationId`, minting and persisting a fresh v4 * UUID when none exists yet. Crucially, this persists *only* the id — * `enableTelemetry` is left untouched (stays `undefined` on a default-on * first run), so the interactive `init` consent prompt is not wrongly * suppressed and no explicit consent the user never gave is recorded. * * Used by the default-on first-run fire path: the gate has already * resolved enabled, so this only ever runs when telemetry is on. */ declare function ensureInstallationId(): string; //#endregion //#region src/gating.d.ts /** * Why telemetry was disabled. Useful for debug-mode logging in the * parent; never surfaces to users. */ type GatingDisabledReason = 'env-override' | 'stored-opt-out'; type GatingResolution = { readonly enabled: true; } | { readonly enabled: false; readonly reason: GatingDisabledReason; }; interface GatingInputs { /** * Environment-variable lookups the resolver consults. Tests pass a * literal record; production passes `process.env`. The two opt-out * signals are `PRISMA_NEXT_DISABLE_TELEMETRY` (Prisma-specific) and * `DO_NOT_TRACK` (community convention). */ readonly env: Readonly>; /** Result of `readUserConfig()` — file-missing tolerated as `{}`. */ readonly config: UserConfig; } /** * Pure-function resolution of the gating decision. Same input → same * output; no I/O. The caller is responsible for reading the env and the * user config. * * Decision order: * 1. Env-var override (`PRISMA_NEXT_DISABLE_TELEMETRY` truthy, or * `DO_NOT_TRACK=1`) → disabled. The env check runs first, so an * opt-out env var wins over any stored or unset preference. * 2. Stored `enableTelemetry === false` → disabled (`stored-opt-out`). * 3. Stored `enableTelemetry === true` → enabled. * 4. Stored `enableTelemetry === undefined` (file missing, or field * not set) → ENABLED. This is the opt-out default: absence of an * explicit choice means telemetry is on. This is the load-bearing, * counter-intuitive branch — do not "fix" it to default-off. * * Telemetry is disabled only when an env override is active or * `enableTelemetry` is explicitly `false`. */ declare function resolveGating(inputs: GatingInputs): GatingResolution; //#endregion //#region src/sanitize.d.ts interface CommanderOptionShape { /** Commander's option attribute name, e.g. `dryRun` for `--dry-run`. */ readonly attributeName: string; /** Commander's long, user-facing flag spelling, e.g. `--dry-run` or `--no-install`. */ readonly longName: string | null; /** Commander's value source for this option. Only `cli` is user-supplied. */ readonly source: string | null; } /** * Input shape: a thin projection of commander's parsed-result surface. * The parent extracts the command path, positional args, and per-option * metadata from the leaf command. The sanitiser never consumes raw * argv, never reads `process.argv`, and never sees flag values. */ interface CommanderResultShape { /** * The full command path from the root program to the leaf, including * the root program name as the first element (the sanitiser drops it). * Example: `['prisma-next', 'migration', 'new']`. */ readonly commandPath: readonly string[]; /** * Positional arguments commander parsed for the leaf command. * **Intentionally never read.** Accepted so the call site doesn't have * to think about whether to pass it; the sanitiser's contract is that * positionals never leave the parent process. */ readonly positionalArgs: readonly string[]; /** * Per-option Commander metadata. The sanitiser emits only options whose * source is `cli`, and uses `longName` so telemetry sees user-facing * names (`dry-run`, `connection-string`, `no-install`) rather than * Commander's internal camelCase attribute names or defaulted options. */ readonly options: readonly CommanderOptionShape[]; } /** * Output shape: the sanitised projection that flows into the telemetry * payload. Two fields only — command name (space-delimited subcommand * path) and flag names (in commander's option declaration order). */ interface SanitisedCommand { readonly command: string; readonly flags: readonly string[]; } /** * Project commander's parsed result into the wire-shape command and * flag-name list. Pure; the only allowed inputs are the fields of * `CommanderResultShape`. * * Sanitiser contract — no flag values, no positionals, no raw argv: * - Drop the root program name (`commandPath[0]`); the wire ships * `migration new`, not `prisma-next migration new`. * - Emit only options whose Commander source is `cli`. * - Emit the long user-facing flag spelling without the `--` prefix; * never emit Commander's camelCase attribute names. * - `positionalArgs` is accepted but never consumed; the field exists * in the input type to make it obvious at the call site that * positionals were deliberately excluded. */ declare function sanitizeCommanderResult(input: CommanderResultShape): SanitisedCommand; //#endregion //#region src/spawn.d.ts /** * Inputs the CLI entry point hands the telemetry layer at command * start. The CLI is responsible for stitching commander's result and * the project root together; the telemetry module does no I/O of its * own except for the user-config read (skipped when `userConfig` is * provided). `extensions` is deliberately absent: the detached child * loads `prisma-next.config.*` via c12 itself and derives the * extension-pack ids from the validated config — see the rationale * on `ParentToSenderPayload` for why c12 lives in the child rather * than on the parent's hot path. * * `databaseTarget` is an optional parent-side override forwarded to * the child. Set by `fireTelemetryAfterInitConsent` (where the * config file does not yet exist on disk); left unset by the * preAction-hook path so the child's c12 load supplies the value. */ interface RunTelemetryInputs { /** Sanitised commander snapshot — see `CommanderResultShape`. */ readonly command: CommanderResultShape; /** This CLI's own version (from its `package.json`). */ readonly version: string; /** Absolute path of the project root (typically `process.cwd()`). */ readonly projectRoot: string; /** * Optional parent-side override for the c12-derived database target, * forwarded verbatim to the child sender. Wins over the child's * c12-derived value when present; `undefined` means "no override". */ readonly databaseTarget?: string; /** * Path to the sender entry compiled into this package's `dist/`. * Resolved by the caller because the compiled sender lives at * `/dist/sender.mjs` and only the consumer knows its own * `import.meta.url`. */ readonly senderPath: string; /** * `isCI()` result from the consumer. Telemetry is suppressed when * `true` regardless of the stored consent answer — CI environments * never emit (matches the colour-output convention's CI suppression). */ readonly isCI: boolean; /** Process env to read for opt-out signals. Defaults to `process.env`. */ readonly env?: Readonly>; /** Cached user config when the caller already read it to resolve gates before other work. */ readonly userConfig?: UserConfig; } /** * Best-effort telemetry spawn at command start. Returns synchronously — * the fork runs in the background and never blocks the parent. Every * failure mode is swallowed; the parent's stdout/stderr is untouched in * normal operation, the only escape valve being * `PRISMA_NEXT_DEBUG=1` which routes diagnostics to stderr. * * Returns the spawn outcome so debug-mode logging and the test-harness * probe (which verifies test runs short-circuit the fork) can inspect * the decision without scraping stderr. */ type TelemetryRunOutcome = { readonly spawned: true; } | { readonly spawned: false; readonly reason: 'gated-off' | 'ci' | 'fork-failed'; }; declare function runTelemetry(inputs: RunTelemetryInputs): TelemetryRunOutcome; /** * Resolve the path to the compiled sender entry relative to a consumer * that has captured its own `import.meta.url`. The CLI's * `tsdown`-emitted entry sits at `/dist/sender.mjs`; the * consumer asks `senderModuleUrl()` and forwards the result to * `runTelemetry({ senderPath })`. */ declare function senderModuleUrl(importMetaUrl: string): string; //#endregion export { type CommanderOptionShape, type CommanderResultShape, type GatingDisabledReason, type GatingInputs, type GatingResolution, type ParentToSenderPayload, type ProjectConfigFields, type RunTelemetryInputs, type SanitisedCommand, TELEMETRY_BACKEND_URL, TELEMETRY_ENDPOINT_PATH, type TelemetryEvent, type TelemetryRunOutcome, type UserConfig, ensureInstallationId, loadProjectConfig, readUserConfig, resolveGating, resolveTelemetryEndpoint, runTelemetry, sanitizeCommanderResult, senderModuleUrl, userConfigPath, writeUserConfig }; //# sourceMappingURL=index.d.mts.map