{"version":3,"file":"sender.mjs","names":[],"sources":["../src/payload.ts","../src/sender.ts"],"sourcesContent":["import { type } from 'arktype';\n\n/**\n * Wire-shape payload the parent IPC-sends to the forked child sender.\n * Mirrors only the fields the parent has naturally in hand at command\n * start: installation id, sanitised command + flags, CLI version, and\n * the project root the child uses to discover everything else. The\n * child probes its own process (runtime/os/arch, package manager, ts\n * version, agent) and reads the user's `prisma-next.config.*` via\n * c12 to derive `databaseTarget` and `extensions`.\n *\n * Loading c12 on the parent side would put a `loadConfig()` await on\n * the command's hot path between gate resolution and `fork()`,\n * opening a race against any CLI command that throws synchronously\n * before that await resolves (the parent exits before forking the\n * sender, and the telemetry event is lost). Moving the load into the\n * detached child eliminates that race; the trade is that the child\n * now evaluates user TS config code, so it's gated behind the same\n * privacy checks the parent already resolved before forking.\n *\n * `databaseTarget` is an optional parent-side override for the\n * c12-derived value: the first-`init` invocation supplies the\n * prompt-chosen target via this field because the config file does\n * not yet exist on disk at that moment. Every other invocation\n * leaves it unset (`undefined`) and the child's c12 load determines\n * the value — there is no third state, so the field's type is\n * `string | undefined`, not `string | null | undefined`.\n *\n * Both sides version-couple on this shape because the IPC carrier is\n * structured-cloned by Node and there's no on-wire compat to maintain.\n */\nexport interface ParentToSenderPayload {\n  readonly installationId: string;\n  readonly version: string;\n  readonly command: string;\n  readonly flags: readonly string[];\n  /**\n   * Absolute path of the user's project. The child reads\n   * `<projectRoot>/package.json` for `tsVersion` and loads\n   * `<projectRoot>/prisma-next.config.*` via c12 for `databaseTarget`\n   * + `extensions`.\n   */\n  readonly projectRoot: string;\n  /** Resolved endpoint URL (already includes the `/events` path). */\n  readonly endpoint: string;\n  /**\n   * Optional parent-side override for the c12-derived database target.\n   * Set by `fireTelemetryAfterInitConsent` (the first-`init` path,\n   * where the config file is about to be written but doesn't exist\n   * yet); left undefined by `fireTelemetryFromPreAction` (steady\n   * state, child resolves the value via c12). The wire-format\n   * `TelemetryEvent.databaseTarget: string | null` keeps `null` as\n   * the on-the-wire \"no target known\" marker, but the IPC override\n   * channel only needs two states so it's `string | undefined`.\n   */\n  readonly databaseTarget?: string;\n}\n\n/**\n * Runtime validator for {@link ParentToSenderPayload}. The child sender\n * uses this to gate `postEvent` so a payload missing a required field\n * cannot silently produce a degraded telemetry event downstream.\n *\n * Mirrors the backend's own arktype schema in spirit: required scalars\n * must be non-empty strings; the optional `databaseTarget` override is\n * `string` when present (no `null` — see the type's doc-block); the\n * string array is validated element-by-element. Size caps are enforced\n * by the backend, not here — IPC is structured-cloned and the\n * parent/child agree on the schema by version-coupling.\n */\nconst requiredString = type.string.moreThanLength(0);\nconst stringArray = type.string.array();\n\nexport const parentToSenderPayloadSchema = type({\n  installationId: requiredString,\n  version: requiredString,\n  command: requiredString,\n  flags: stringArray,\n  projectRoot: requiredString,\n  endpoint: requiredString,\n  'databaseTarget?': type.string,\n});\n\nexport function isParentToSenderPayload(value: unknown): value is ParentToSenderPayload {\n  return !(parentToSenderPayloadSchema(value) instanceof type.errors);\n}\n\n/**\n * The full event the child POSTs to the backend. Shape matches the\n * backend's arktype schema (`apps/telemetry-backend/src/schema.ts`).\n */\nexport interface TelemetryEvent {\n  readonly installationId: string;\n  readonly version: string;\n  readonly command: string;\n  readonly flags: readonly string[];\n  readonly runtimeName: string;\n  readonly runtimeVersion: string;\n  readonly os: string;\n  readonly arch: string;\n  readonly packageManager: string | null;\n  readonly databaseTarget: string | null;\n  readonly tsVersion: string | null;\n  readonly agent: string | null;\n  readonly extensions: readonly string[];\n}\n","/**\n * Sender script entry — forked into a detached child by the parent CLI via\n * `child_process.fork(senderPath, [], { detached: true, ... })`.\n *\n * Lifecycle:\n *   1. Wait for the parent's IPC `message` event carrying a\n *      `ParentToSenderPayload`.\n *   2. Enrich with the local-process probes (runtime, os, arch, agent,\n *      package manager, tsVersion).\n *   3. POST the event to the endpoint URL with a hard 1.5 s timeout.\n *   4. Exit 0 unconditionally — successful POST, network failure, server\n *      error, parse error of the response, anything else: same outcome.\n *\n * Every error is swallowed; the only escape valve for visibility is\n * `PRISMA_NEXT_DEBUG=1`, which routes diagnostics to stderr. In normal\n * operation no telemetry-originating output ever reaches the user — the\n * parent's stdio map ignores our streams anyway, but we also gate\n * stderr writes behind the debug flag so the same binary is safe to\n * invoke directly outside the spawn flow.\n */\nimport { buildTelemetryEventFromProcess } from './enrich';\nimport { isParentToSenderPayload, type ParentToSenderPayload } from './payload';\n\nconst REQUEST_TIMEOUT_MS = 1500;\n\nfunction debugLog(message: string, error?: unknown): void {\n  if (process.env['PRISMA_NEXT_DEBUG'] !== '1') return;\n  if (error !== undefined) {\n    process.stderr.write(`[cli-telemetry] ${message}: ${String(error)}\\n`);\n  } else {\n    process.stderr.write(`[cli-telemetry] ${message}\\n`);\n  }\n}\n\nasync function postEvent(payload: ParentToSenderPayload): Promise<void> {\n  const event = await buildTelemetryEventFromProcess(payload);\n  const controller = new AbortController();\n  const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n  try {\n    const response = await fetch(payload.endpoint, {\n      method: 'POST',\n      headers: { 'content-type': 'application/json' },\n      body: JSON.stringify(event),\n      signal: controller.signal,\n    });\n    debugLog(`sent event: status=${response.status}`);\n  } catch (err) {\n    debugLog('send failed', err);\n  } finally {\n    clearTimeout(timer);\n  }\n}\n\nfunction exitClean(): void {\n  // `process.disconnect()` lets the parent's `.disconnect()` complete\n  // without lingering IPC handles when the parent is fast.\n  try {\n    process.disconnect?.();\n  } catch {\n    // ignore\n  }\n  process.exit(0);\n}\n\nprocess.once('message', (message: unknown) => {\n  if (!isParentToSenderPayload(message)) {\n    debugLog('received malformed payload; exiting');\n    exitClean();\n    return;\n  }\n  postEvent(message)\n    .catch((err) => debugLog('post threw', err))\n    .finally(exitClean);\n});\n\n// Defensive: if the parent never sends a payload (or the IPC channel\n// closes before `message` arrives), exit after a generous grace period\n// so the child process is not stuck holding a handle.\nconst SENDER_IDLE_EXIT_MS = REQUEST_TIMEOUT_MS * 2;\nsetTimeout(exitClean, SENDER_IDLE_EXIT_MS).unref();\n"],"mappings":";;;;;;;;;;;;;;;AAsEA,MAAM,iBAAiB,KAAK,OAAO,eAAe,CAAC;AAGnD,MAAa,8BAA8B,KAAK;CAC9C,gBAAgB;CAChB,SAAS;CACT,SAAS;CACT,OANkB,KAAK,OAAO,MAMvB;CACP,aAAa;CACb,UAAU;CACV,mBAAmB,KAAK;AAC1B,CAAC;AAED,SAAgB,wBAAwB,OAAgD;CACtF,OAAO,EAAE,4BAA4B,KAAK,aAAa,KAAK;AAC9D;;;;;;;;;;;;;;;;;;;;;;;AC9DA,MAAM,qBAAqB;AAE3B,SAAS,SAAS,SAAiB,OAAuB;CACxD,IAAI,QAAQ,IAAI,yBAAyB,KAAK;CAC9C,IAAI,UAAU,KAAA,GACZ,QAAQ,OAAO,MAAM,mBAAmB,QAAQ,IAAI,OAAO,KAAK,EAAE,GAAG;MAErE,QAAQ,OAAO,MAAM,mBAAmB,QAAQ,GAAG;AAEvD;AAEA,eAAe,UAAU,SAA+C;CACtE,MAAM,QAAQ,MAAM,+BAA+B,OAAO;CAC1D,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,kBAAkB;CACrE,IAAI;EAOF,SAAS,uBAAsB,MANR,MAAM,QAAQ,UAAU;GAC7C,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU,KAAK;GAC1B,QAAQ,WAAW;EACrB,CAAC,EAAA,CACuC,QAAQ;CAClD,SAAS,KAAK;EACZ,SAAS,eAAe,GAAG;CAC7B,UAAU;EACR,aAAa,KAAK;CACpB;AACF;AAEA,SAAS,YAAkB;CAGzB,IAAI;EACF,QAAQ,aAAa;CACvB,QAAQ,CAER;CACA,QAAQ,KAAK,CAAC;AAChB;AAEA,QAAQ,KAAK,YAAY,YAAqB;CAC5C,IAAI,CAAC,wBAAwB,OAAO,GAAG;EACrC,SAAS,qCAAqC;EAC9C,UAAU;EACV;CACF;CACA,UAAU,OAAO,CAAC,CACf,OAAO,QAAQ,SAAS,cAAc,GAAG,CAAC,CAAC,CAC3C,QAAQ,SAAS;AACtB,CAAC;AAMD,WAAW,WADiB,qBAAqB,CACR,CAAC,CAAC,MAAM"}