@prisma-next/cli-telemetry 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/payload.ts CHANGED
@@ -2,11 +2,29 @@ import { type } from 'arktype';
2
2
 
3
3
  /**
4
4
  * Wire-shape payload the parent IPC-sends to the forked child sender.
5
- * Mirrors the fields the parent has naturally in hand at command start
6
- * (installation id, sanitised command + flags, CLI version, db target,
7
- * extension-pack ids, project root for TS-version lookup). The child
8
- * fills in the rest (runtime/os/arch, package manager, ts version,
9
- * agent) on its side.
5
+ * Mirrors only the fields the parent has naturally in hand at command
6
+ * start: installation id, sanitised command + flags, CLI version, and
7
+ * the project root the child uses to discover everything else. The
8
+ * child probes its own process (runtime/os/arch, package manager, ts
9
+ * version, agent) and reads the user's `prisma-next.config.*` via
10
+ * c12 to derive `databaseTarget` and `extensions`.
11
+ *
12
+ * Loading c12 on the parent side would put a `loadConfig()` await on
13
+ * the command's hot path between gate resolution and `fork()`,
14
+ * opening a race against any CLI command that throws synchronously
15
+ * before that await resolves (the parent exits before forking the
16
+ * sender, and the telemetry event is lost). Moving the load into the
17
+ * detached child eliminates that race; the trade is that the child
18
+ * now evaluates user TS config code, so it's gated behind the same
19
+ * privacy checks the parent already resolved before forking.
20
+ *
21
+ * `databaseTarget` is an optional parent-side override for the
22
+ * c12-derived value: the first-`init` invocation supplies the
23
+ * prompt-chosen target via this field because the config file does
24
+ * not yet exist on disk at that moment. Every other invocation
25
+ * leaves it unset (`undefined`) and the child's c12 load determines
26
+ * the value — there is no third state, so the field's type is
27
+ * `string | undefined`, not `string | null | undefined`.
10
28
  *
11
29
  * Both sides version-couple on this shape because the IPC carrier is
12
30
  * structured-cloned by Node and there's no on-wire compat to maintain.
@@ -16,12 +34,26 @@ export interface ParentToSenderPayload {
16
34
  readonly version: string;
17
35
  readonly command: string;
18
36
  readonly flags: readonly string[];
19
- readonly databaseTarget: string | null;
20
- readonly extensions: readonly string[];
21
- /** Absolute path of the user's project. The child reads `<projectRoot>/package.json` for `tsVersion`. */
37
+ /**
38
+ * Absolute path of the user's project. The child reads
39
+ * `<projectRoot>/package.json` for `tsVersion` and loads
40
+ * `<projectRoot>/prisma-next.config.*` via c12 for `databaseTarget`
41
+ * + `extensions`.
42
+ */
22
43
  readonly projectRoot: string;
23
44
  /** Resolved endpoint URL (already includes the `/events` path). */
24
45
  readonly endpoint: string;
46
+ /**
47
+ * Optional parent-side override for the c12-derived database target.
48
+ * Set by `fireTelemetryAfterInitConsent` (the first-`init` path,
49
+ * where the config file is about to be written but doesn't exist
50
+ * yet); left undefined by `fireTelemetryFromPreAction` (steady
51
+ * state, child resolves the value via c12). The wire-format
52
+ * `TelemetryEvent.databaseTarget: string | null` keeps `null` as
53
+ * the on-the-wire "no target known" marker, but the IPC override
54
+ * channel only needs two states so it's `string | undefined`.
55
+ */
56
+ readonly databaseTarget?: string;
25
57
  }
26
58
 
27
59
  /**
@@ -30,10 +62,11 @@ export interface ParentToSenderPayload {
30
62
  * cannot silently produce a degraded telemetry event downstream.
31
63
  *
32
64
  * Mirrors the backend's own arktype schema in spirit: required scalars
33
- * must be non-empty strings; `databaseTarget` is `string | null`; the
34
- * two string arrays are validated element-by-element. Size caps are
35
- * enforced by the backend, not here IPC is structured-cloned and
36
- * the parent/child agree on the schema by version-coupling.
65
+ * must be non-empty strings; the optional `databaseTarget` override is
66
+ * `string` when present (no `null` see the type's doc-block); the
67
+ * string array is validated element-by-element. Size caps are enforced
68
+ * by the backend, not here IPC is structured-cloned and the
69
+ * parent/child agree on the schema by version-coupling.
37
70
  */
38
71
  const requiredString = type.string.moreThanLength(0);
39
72
  const stringArray = type.string.array();
@@ -43,10 +76,9 @@ export const parentToSenderPayloadSchema = type({
43
76
  version: requiredString,
44
77
  command: requiredString,
45
78
  flags: stringArray,
46
- databaseTarget: type.string.or('null'),
47
- extensions: stringArray,
48
79
  projectRoot: requiredString,
49
80
  endpoint: requiredString,
81
+ 'databaseTarget?': type.string,
50
82
  });
51
83
 
52
84
  export function isParentToSenderPayload(value: unknown): value is ParentToSenderPayload {
package/src/sender.ts CHANGED
@@ -33,7 +33,7 @@ function debugLog(message: string, error?: unknown): void {
33
33
  }
34
34
 
35
35
  async function postEvent(payload: ParentToSenderPayload): Promise<void> {
36
- const event = buildTelemetryEventFromProcess(payload);
36
+ const event = await buildTelemetryEventFromProcess(payload);
37
37
  const controller = new AbortController();
38
38
  const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
39
39
  try {
package/src/spawn.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { fork } from 'node:child_process';
2
2
  import { fileURLToPath } from 'node:url';
3
+ import { ifDefined } from '@prisma-next/utils/defined';
3
4
  import { resolveTelemetryEndpoint } from './endpoint';
4
5
  import { resolveGating } from './gating';
5
6
  import type { ParentToSenderPayload } from './payload';
@@ -8,22 +9,33 @@ import { readUserConfig, type UserConfig } from './user-config';
8
9
 
9
10
  /**
10
11
  * Inputs the CLI entry point hands the telemetry layer at command
11
- * start. The CLI is responsible for stitching commander's result, the
12
- * loaded config, and the project root together; the telemetry module
13
- * does no I/O of its own except for the user-config read (skipped when
14
- * `userConfig` is provided).
12
+ * start. The CLI is responsible for stitching commander's result and
13
+ * the project root together; the telemetry module does no I/O of its
14
+ * own except for the user-config read (skipped when `userConfig` is
15
+ * provided). `extensions` is deliberately absent: the detached child
16
+ * loads `prisma-next.config.*` via c12 itself and derives the
17
+ * extension-pack ids from the validated config — see the rationale
18
+ * on `ParentToSenderPayload` for why c12 lives in the child rather
19
+ * than on the parent's hot path.
20
+ *
21
+ * `databaseTarget` is an optional parent-side override forwarded to
22
+ * the child. Set by `fireTelemetryAfterInitConsent` (where the
23
+ * config file does not yet exist on disk); left unset by the
24
+ * preAction-hook path so the child's c12 load supplies the value.
15
25
  */
16
26
  export interface RunTelemetryInputs {
17
27
  /** Sanitised commander snapshot — see `CommanderResultShape`. */
18
28
  readonly command: CommanderResultShape;
19
29
  /** This CLI's own version (from its `package.json`). */
20
30
  readonly version: string;
21
- /** Resolved `config.target.targetId`, or `null` when the config could not be loaded. */
22
- readonly databaseTarget: string | null;
23
- /** Declared extension-pack IDs, in any deterministic order. */
24
- readonly extensions: readonly string[];
25
31
  /** Absolute path of the project root (typically `process.cwd()`). */
26
32
  readonly projectRoot: string;
33
+ /**
34
+ * Optional parent-side override for the c12-derived database target,
35
+ * forwarded verbatim to the child sender. Wins over the child's
36
+ * c12-derived value when present; `undefined` means "no override".
37
+ */
38
+ readonly databaseTarget?: string;
27
39
  /**
28
40
  * Path to the sender entry compiled into this package's `dist/`.
29
41
  * Resolved by the caller because the compiled sender lives at
@@ -85,10 +97,9 @@ export function runTelemetry(inputs: RunTelemetryInputs): TelemetryRunOutcome {
85
97
  version: inputs.version,
86
98
  command: sanitised.command,
87
99
  flags: sanitised.flags,
88
- databaseTarget: inputs.databaseTarget,
89
- extensions: inputs.extensions,
90
100
  projectRoot: inputs.projectRoot,
91
101
  endpoint: resolveTelemetryEndpoint(env),
102
+ ...ifDefined('databaseTarget', inputs.databaseTarget),
92
103
  };
93
104
 
94
105
  try {