@prisma/cli 8.0.0-rc.2-dev.50 → 8.0.0-rc.2-dev.51

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.
Files changed (2) hide show
  1. package/dist/cli.js +115 -19
  2. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -24,6 +24,8 @@ import { promisify } from "node:util";
24
24
  import { ApiError, CancelledError, ComputeClient, normalizeArtifactSymlinks, resolveBuildStrategy, streamLogs } from "@prisma/compute-sdk";
25
25
  import { parse } from "dotenv";
26
26
  import { fileURLToPath } from "node:url";
27
+ import { Writable } from "node:stream";
28
+ import { pipeline } from "node:stream/promises";
27
29
  //#region src/cli-name.ts
28
30
  /**
29
31
  * The CLI's user-facing identity, in one place. The npm package is
@@ -15064,33 +15066,127 @@ const runPackageManager = async ({ file, args, cwd, signal, onOutput }) => {
15064
15066
  };
15065
15067
  //#endregion
15066
15068
  //#region src/spawn.ts
15069
+ /** How long after the child exits the relay keeps reading its pipes. A
15070
+ * grandchild that inherited them can hold EOF back forever; settlement
15071
+ * must not wait on it, so the pipes are destroyed after this grace. */
15072
+ const POST_EXIT_DRAIN_GRACE_MS = 5e3;
15067
15073
  /**
15068
- * The engine's spawn seam, adapted to node:child_process. Inherited
15069
- * stdio, no `detached`, no new console: the child stays in this
15070
- * process's group (POSIX) or console (Windows), so the terminal
15071
- * delivers Ctrl-C to it natively.
15074
+ * The engine's spawn seam, adapted to node:child_process. Human mode
15075
+ * inherits stdio; structured mode pipes both child output streams to
15076
+ * diagnostics. Neither mode detaches or opens a new console, so the child
15077
+ * stays in this process's group (POSIX) or console (Windows).
15078
+ *
15079
+ * The child's own status settles the run: `ended` resolves from the
15080
+ * process `exit` event, waits for the diagnostic relay only up to the
15081
+ * drain grace, and never rejects for a relay failure — rejection is
15082
+ * reserved for a child that could not be launched at all.
15072
15083
  */
15073
- const spawnChild = (request) => {
15074
- const child = spawn(request.command, [...request.args], {
15075
- cwd: request.cwd,
15076
- env: request.env,
15077
- stdio: "inherit"
15078
- });
15079
- return {
15080
- ended: new Promise((resolve, reject) => {
15084
+ function makeSpawnChild(diagnostics, options) {
15085
+ const drainGraceMs = options?.drainGraceMs ?? POST_EXIT_DRAIN_GRACE_MS;
15086
+ return (request) => {
15087
+ const structured = request.output === "diagnostic";
15088
+ const child = spawn(request.command, [...request.args], {
15089
+ cwd: request.cwd,
15090
+ env: request.env,
15091
+ stdio: structured ? [
15092
+ "inherit",
15093
+ "pipe",
15094
+ "pipe"
15095
+ ] : "inherit"
15096
+ });
15097
+ const processEnded = new Promise((resolve, reject) => {
15081
15098
  child.on("error", reject);
15082
- child.on("close", (exitCode, signal) => {
15099
+ child.on("exit", (exitCode, signal) => {
15083
15100
  resolve({
15084
15101
  exitCode,
15085
15102
  signal
15086
15103
  });
15087
15104
  });
15088
- }),
15089
- kill: (signal) => {
15090
- child.kill(signal);
15091
- }
15105
+ });
15106
+ if (!structured) return {
15107
+ ended: processEnded,
15108
+ kill: (signal) => {
15109
+ child.kill(signal);
15110
+ }
15111
+ };
15112
+ const forwarding = forwardStructuredOutput(child.stdout, child.stderr, diagnostics);
15113
+ return {
15114
+ ended: processEnded.then(async (result) => {
15115
+ const drainDeadline = setTimeout(() => {
15116
+ child.stdout?.destroy();
15117
+ child.stderr?.destroy();
15118
+ }, drainGraceMs);
15119
+ await forwarding;
15120
+ clearTimeout(drainDeadline);
15121
+ return result;
15122
+ }),
15123
+ kill: (signal) => {
15124
+ child.kill(signal);
15125
+ }
15126
+ };
15092
15127
  };
15093
- };
15128
+ }
15129
+ /** Best-effort relay: a forwarding failure never rejects, so the child's
15130
+ * real status still settles the run when the diagnostic sink dies. */
15131
+ function forwardStructuredOutput(stdout, stderr, diagnostics) {
15132
+ const sources = [stdout, stderr].filter((source) => source !== null);
15133
+ return Promise.all(sources.map((source) => forwardOutput(source, diagnostics))).then(() => void 0, () => void 0);
15134
+ }
15135
+ /** Decode each child stream continuously and stop reading while the
15136
+ * diagnostic destination applies backpressure. A destination that
15137
+ * errors or closes instead of draining fails the relay rather than
15138
+ * stalling it. */
15139
+ function forwardOutput(source, diagnostics) {
15140
+ let pendingDone;
15141
+ let pendingDrain;
15142
+ let failure;
15143
+ const fail = (cause) => {
15144
+ failure ??= cause;
15145
+ const done = pendingDone;
15146
+ pendingDone = void 0;
15147
+ done?.(cause);
15148
+ };
15149
+ const onSinkError = (cause) => {
15150
+ fail(toError(cause));
15151
+ };
15152
+ const onSinkClose = () => {
15153
+ fail(/* @__PURE__ */ new Error("the diagnostic stream closed during child output"));
15154
+ };
15155
+ diagnostics.once?.("error", onSinkError);
15156
+ diagnostics.once?.("close", onSinkClose);
15157
+ const destination = new Writable({
15158
+ decodeStrings: false,
15159
+ write: (text, _encoding, done) => {
15160
+ if (failure !== void 0) {
15161
+ done(failure);
15162
+ return;
15163
+ }
15164
+ try {
15165
+ if (diagnostics.write(text) === false && diagnostics.once !== void 0) {
15166
+ pendingDone = done;
15167
+ const onDrain = () => {
15168
+ pendingDrain = void 0;
15169
+ if (pendingDone !== done) return;
15170
+ pendingDone = void 0;
15171
+ done();
15172
+ };
15173
+ pendingDrain = onDrain;
15174
+ diagnostics.once("drain", onDrain);
15175
+ } else done();
15176
+ } catch (cause) {
15177
+ done(toError(cause));
15178
+ }
15179
+ }
15180
+ });
15181
+ return pipeline(source.setEncoding("utf8"), destination).finally(() => {
15182
+ diagnostics.off?.("error", onSinkError);
15183
+ diagnostics.off?.("close", onSinkClose);
15184
+ if (pendingDrain !== void 0) diagnostics.off?.("drain", pendingDrain);
15185
+ });
15186
+ }
15187
+ function toError(cause) {
15188
+ return cause instanceof Error ? cause : new Error(String(cause));
15189
+ }
15094
15190
  //#endregion
15095
15191
  //#region src/runtime.ts
15096
15192
  /** Dumb wiring: forwards process signals to the engine's subscribers.
@@ -15198,7 +15294,7 @@ async function assembleRuntime(proc) {
15198
15294
  apiBaseUrl,
15199
15295
  authBaseUrl
15200
15296
  },
15201
- spawn: spawnChild,
15297
+ spawn: makeSpawnChild(proc.stderr),
15202
15298
  /** The engine has already decided and composed; the bin only forks
15203
15299
  * the detached sender and hands the payload over. Every failure is
15204
15300
  * swallowed inside runTelemetry. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/cli",
3
- "version": "8.0.0-rc.2-dev.50",
3
+ "version": "8.0.0-rc.2-dev.51",
4
4
  "description": "Command-line interface for the Prisma Developer Platform.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -51,9 +51,9 @@
51
51
  "open": "^11.0.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@repo/cli-conformance": "8.0.0-rc.2-dev.50",
55
- "@repo/cli-telemetry": "8.0.0-rc.2-dev.50",
56
- "@repo/tsconfig": "8.0.0-rc.2-dev.50",
54
+ "@repo/cli-conformance": "8.0.0-rc.2-dev.51",
55
+ "@repo/cli-telemetry": "8.0.0-rc.2-dev.51",
56
+ "@repo/tsconfig": "8.0.0-rc.2-dev.51",
57
57
  "@types/node": "^22.19.19",
58
58
  "tsdown": "^0.21.10",
59
59
  "tsx": "^4.22.4",