diff --git a/packages/cli-engine/package.json b/packages/cli-engine/package.json index 46d0b700..ac725cf5 100644 --- a/packages/cli-engine/package.json +++ b/packages/cli-engine/package.json @@ -1,6 +1,6 @@ { "name": "@prisma/cli-engine", - "version": "0.6.1", + "version": "0.6.2", "description": "The execution engine of the unified Prisma CLI.", "type": "module", "exports": { diff --git a/packages/cli-engine/src/execution/bin-name.ts b/packages/cli-engine/src/execution/bin-name.ts new file mode 100644 index 00000000..f6569e6a --- /dev/null +++ b/packages/cli-engine/src/execution/bin-name.ts @@ -0,0 +1,118 @@ +import { + type Block, + PRESENTED, + type PresentedResult, + type Span, + type Text, +} from "../presentation"; +import type { Diagnostic, NextAction } from "../protocol"; +import { substituteBinName } from "./stricli-adapter"; + +function isRecord(value: unknown): boolean { + return typeof value === "object" && value !== null; +} + +function inText(text: Text, cliName: string): Text { + if (!Array.isArray(text)) { + return substituteBinName(text, cliName); + } + return text.map((span: Span) => + isRecord(span) + ? { ...span, text: substituteBinName(span.text, cliName) } + : span, + ); +} + +function inBlock(block: Block, cliName: string): Block { + switch (block.kind) { + case "summary": + return { ...block, text: inText(block.text, cliName) }; + case "list": + return { + ...block, + items: block.items.map((item) => inText(item, cliName)), + }; + case "fields": + case "table": + case "tree": + case "drawing": + return block; + } +} + +function nextActionWithBinName( + action: NextAction, + cliName: string, +): NextAction { + if (!isRecord(action)) { + return action; + } + return { + ...action, + label: substituteBinName(action.label, cliName), + ...(action.reason === undefined + ? {} + : { reason: substituteBinName(action.reason, cliName) }), + ...(action.command === undefined + ? {} + : { command: substituteBinName(action.command, cliName) }), + ...(Array.isArray(action.commands) + ? { + commands: action.commands.map((command: string) => + substituteBinName(command, cliName), + ), + } + : {}), + }; +} + +export function nextActionsWithBinName( + actions: readonly NextAction[], + cliName: string, +): readonly NextAction[] { + return Array.isArray(actions) + ? actions.map((action: NextAction) => + nextActionWithBinName(action, cliName), + ) + : actions; +} + +export function diagnosticWithBinName( + diagnostic: Diagnostic, + cliName: string, +): Diagnostic { + if (!isRecord(diagnostic)) { + return diagnostic; + } + return { + ...diagnostic, + summary: substituteBinName(diagnostic.summary, cliName), + ...(diagnostic.why === undefined + ? {} + : { why: substituteBinName(diagnostic.why, cliName) }), + nextActions: nextActionsWithBinName(diagnostic.nextActions, cliName), + }; +} + +/** Only summary and list blocks are rewritten. Every other block, the + * data, the json result and the stdout lines can hold user data and + * pass through unchanged. */ +export function presentedWithBinName( + presented: PresentedResult, + cliName: string, +): PresentedResult { + return { + ...presented, + [PRESENTED]: true, + diagnostics: presented.diagnostics.map((diagnostic) => + diagnosticWithBinName(diagnostic, cliName), + ), + presentation: { + ...presented.presentation, + human: presented.presentation.human.map((block) => + inBlock(block, cliName), + ), + next: nextActionsWithBinName(presented.presentation.next, cliName), + }, + }; +} diff --git a/packages/cli-engine/src/execution/engine.ts b/packages/cli-engine/src/execution/engine.ts index 00091426..637795fc 100644 --- a/packages/cli-engine/src/execution/engine.ts +++ b/packages/cli-engine/src/execution/engine.ts @@ -198,6 +198,8 @@ export interface RunState { } export interface Invocation { + /** The name of the binary the user ran, substituted for `{bin}`. */ + readonly cliName: string; readonly runtime: Runtime; readonly hooks: RunHooks; readonly now: () => Date; @@ -376,6 +378,7 @@ export class EngineImpl implements Engine { }; const unsubscribe = runtime.onSignal(deliverSignal); const invocation: Invocation = { + cliName: this.spec.name, runtime, hooks: { ...hooks }, now: this.now, diff --git a/packages/cli-engine/src/execution/markdown.ts b/packages/cli-engine/src/execution/markdown.ts index edb7c96c..34c475b6 100644 --- a/packages/cli-engine/src/execution/markdown.ts +++ b/packages/cli-engine/src/execution/markdown.ts @@ -9,6 +9,7 @@ import { commentaryLine, MASK, PLACEHOLDER, + renderableNextActions, sentenceCase, withDocsUrl, } from "./rendering"; @@ -28,7 +29,8 @@ function longestBacktickRun(text: string): number { /** An inline code span whose delimiter is one backtick longer than any * run inside it, padded when the content starts or ends with one. */ -export function codeSpan(text: string): string { +export function codeSpan(value: string): string { + const text = String(value); const delimiter = "`".repeat(longestBacktickRun(text) + 1); const padded = text.startsWith("`") || text.endsWith("`") ? ` ${text} ` : text; @@ -116,7 +118,7 @@ function renderTreeNode(node: TreeNode, depth: number): string[] { export function renderNextActionMarkdown(action: NextAction): string[] { const target = action.command ?? action.url; - if (target === undefined && action.commands !== undefined) { + if (target === undefined && Array.isArray(action.commands)) { return [ `- ${action.label}`, ...action.commands.map((command) => ` - ${codeSpan(command)}`), @@ -163,7 +165,7 @@ export function renderDiagnosticMarkdown(diagnostic: Diagnostic): string[] { if (where !== undefined) { lines.push(where); } - for (const action of diagnostic.nextActions) { + for (const action of renderableNextActions(diagnostic.nextActions)) { lines.push(...renderNextActionMarkdown(action)); } if (diagnostic.docsUrl !== undefined) { diff --git a/packages/cli-engine/src/execution/needs.ts b/packages/cli-engine/src/execution/needs.ts index 058acb77..e4cf2642 100644 --- a/packages/cli-engine/src/execution/needs.ts +++ b/packages/cli-engine/src/execution/needs.ts @@ -13,6 +13,7 @@ import { } from "../package-manager"; import { CliStructuredError, type Diagnostic } from "../protocol"; import type { LoadedConfig, LoadedConfigFile } from "../runtime"; +import { diagnosticWithBinName } from "./bin-name"; import type { Invocation } from "./engine"; import { renderWarningsMarkdown } from "./markdown"; import { makePaint } from "./palette"; @@ -413,7 +414,9 @@ function writeSectionWarnings( (diagnostic) => SEVERITY_RANK[diagnostic.severity] <= SEVERITY_RANK[state.logLevel], ) - .map((diagnostic) => withDocsUrl(state, diagnostic)); + .map((diagnostic) => + withDocsUrl(state, diagnosticWithBinName(diagnostic, invocation.cliName)), + ); if (state.format === "markdown") { renderWarningsMarkdown(invocation, shown); return; diff --git a/packages/cli-engine/src/execution/rendering.ts b/packages/cli-engine/src/execution/rendering.ts index 730aa81e..dbf98e8d 100644 --- a/packages/cli-engine/src/execution/rendering.ts +++ b/packages/cli-engine/src/execution/rendering.ts @@ -310,7 +310,7 @@ export function writeDiagnostic( if (diagnostic.why !== undefined) { stream.write(` ${paint("muted", `why: ${diagnostic.why}`)}\n`); } - for (const action of diagnostic.nextActions) { + for (const action of renderableNextActions(diagnostic.nextActions)) { stream.write(`${renderNextAction(action, paint)}\n`); } if (diagnostic.docsUrl !== undefined) { @@ -320,6 +320,15 @@ export function writeDiagnostic( } } +/** A diagnostic reaches the renderers unvalidated: a list that is + * missing renders as no next actions, and an entry that is not an + * object is skipped. */ +export function renderableNextActions(actions: unknown): NextAction[] { + return Array.isArray(actions) + ? actions.filter((action) => typeof action === "object" && action !== null) + : []; +} + /** `label` is required, so a mapper building an action out of a bare * command string — a legacy error's follow-up step, with no prose * beside it — has nothing to put in the label but the command itself. diff --git a/packages/cli-engine/src/execution/settlement.ts b/packages/cli-engine/src/execution/settlement.ts index 2ae47a6f..45cfacbb 100644 --- a/packages/cli-engine/src/execution/settlement.ts +++ b/packages/cli-engine/src/execution/settlement.ts @@ -12,6 +12,11 @@ import { type NextAction, } from "../protocol"; import { type ChildStatusSettlement, childExitCode } from "../spawn"; +import { + diagnosticWithBinName, + nextActionsWithBinName, + presentedWithBinName, +} from "./bin-name"; import type { EngineSpec, Invocation } from "./engine"; import { renderChildNextActionsMarkdown, @@ -50,12 +55,12 @@ function undocumentedExitCode( export function settleCompleted( invocation: Invocation, def: AnyCommand, - presented: PresentedResult, + returned: PresentedResult, ): void { if ( - typeof presented !== "object" || - presented === null || - (presented as unknown as Record)[PRESENTED] !== true + typeof returned !== "object" || + returned === null || + (returned as unknown as Record)[PRESENTED] !== true ) { settleBug( invocation, @@ -65,11 +70,12 @@ export function settleCompleted( ); return; } - const violation = undocumentedExitCode(def, presented.exitCode); + const violation = undocumentedExitCode(def, returned.exitCode); if (violation !== undefined) { settleBug(invocation, violation); return; } + const presented = presentedWithBinName(returned, invocation.cliName); const state = invocation.state; invocation.hooks.onPresented?.(presented); const exitCode = runExitCode(invocation, presented.exitCode); @@ -127,9 +133,11 @@ export function settleErrored( emitErrored(invocation, { ok: false, commandId: state.commandId, - error: diagnosticOf(error), - diagnostics: accompanyingFindings(diagnostics), - nextActions: error.nextActions, + error: diagnosticWithBinName(diagnosticOf(error), invocation.cliName), + diagnostics: accompanyingFindings(diagnostics).map((diagnostic) => + diagnosticWithBinName(diagnostic, invocation.cliName), + ), + nextActions: nextActionsWithBinName(error.nextActions, invocation.cliName), }); } @@ -244,12 +252,16 @@ export function settleChildStatus( ); } const exitCode = childExitCode(child); + const nextActions = nextActionsWithBinName( + settlement.nextActions, + invocation.cliName, + ); if (invocation.state.format === "json") { - settleStructuredChildStatus(invocation, settlement, child, exitCode); + settleStructuredChildStatus(invocation, nextActions, child, exitCode); return; } if (child.signal === null) { - renderChildNextActions(invocation, settlement.nextActions); + renderChildNextActions(invocation, nextActions); } settleVerbatimExitCode(invocation, exitCode); } @@ -270,12 +282,12 @@ function renderChildNextActions( function settleStructuredChildStatus( invocation: Invocation, - settlement: ChildStatusSettlement, + actions: readonly NextAction[], child: { readonly exitCode: number | null; readonly signal: string | null }, exitCode: number, ): void { settleVerbatimExitCode(invocation, exitCode); - const nextActions = child.signal === null ? settlement.nextActions : []; + const nextActions = child.signal === null ? actions : []; if (exitCode === 0) { const envelope: CompletedEnvelope = { ok: true, diff --git a/packages/cli-engine/src/execution/stricli-adapter.ts b/packages/cli-engine/src/execution/stricli-adapter.ts index 17572b07..8fb440d1 100644 --- a/packages/cli-engine/src/execution/stricli-adapter.ts +++ b/packages/cli-engine/src/execution/stricli-adapter.ts @@ -177,10 +177,18 @@ function commandParameters(def: AnyCommand): Record { * written the same way and rendered by the same rule. */ export function resolveExample(example: string, cliName: string): string { return example.includes("{bin}") - ? example.replaceAll("{bin}", cliName) + ? substituteBinName(example, cliName) : `${cliName} ${example}`; } +/** Settlement hands this values nothing has validated, so anything that + * is not a string is returned as it came. */ +export function substituteBinName(text: T, cliName: string): T { + return typeof text === "string" + ? (text.replaceAll("{bin}", cliName) as T) + : text; +} + function commandDocs( def: AnyCommand, cliName: string, diff --git a/packages/cli-engine/tests/bin-placeholder-unvalidated.test.ts b/packages/cli-engine/tests/bin-placeholder-unvalidated.test.ts new file mode 100644 index 00000000..c252c09c --- /dev/null +++ b/packages/cli-engine/tests/bin-placeholder-unvalidated.test.ts @@ -0,0 +1,131 @@ +/** + * Settlement receives errors nothing has validated: one built by another + * copy of the engine, or a handler's notOk failure. Substituting `{bin}` + * must never be the reason such a run fails. + */ +import { defineCommand, type StreamEvent } from "@prisma/cli-engine"; +import { + type CliStructuredError, + type Diagnostic, + notOk, +} from "@prisma/cli-engine/protocol"; +import { createTestCli } from "@prisma/cli-engine/testing"; +import { describe, expect, test } from "vitest"; + +function foreignError(fields: Record): CliStructuredError { + return { + name: "CliStructuredError", + code: "FOREIGN.FAILED", + message: "Run `{bin} status`.", + toEnvelope: () => envelope({ nextActions: fields.nextActions ?? [] }), + ...fields, + } as unknown as CliStructuredError; +} + +function envelope(fields: Record) { + return { + ok: false, + code: "FOREIGN.FAILED", + severity: "error", + summary: "Run `{bin} status`.", + ...fields, + }; +} + +const CASES: Record CliStructuredError> = { + "an error without nextActions": () => + foreignError({ nextActions: undefined }), + "an envelope without nextActions": () => + foreignError({ nextActions: [], toEnvelope: () => envelope({}) }), + "an accompanying finding without nextActions": () => + foreignError({ + nextActions: [], + diagnostics: [ + { code: "FOREIGN.FINDING", severity: "warn", summary: "{bin}" }, + ] as unknown as Diagnostic[], + }), + "an accompanying finding whose why is not a string": () => + foreignError({ + nextActions: [], + diagnostics: [ + { + code: "FOREIGN.FINDING", + severity: "warn", + summary: "{bin}", + why: 42, + nextActions: [], + }, + ] as unknown as Diagnostic[], + }), + "a next action that is null": () => + foreignError({ nextActions: [null, { kind: "done", label: "{bin}" }] }), + "a next action with fields of the wrong type": () => + foreignError({ + nextActions: [ + { kind: "run-command", label: 42, reason: 42, command: 42 }, + { kind: "run-command", label: "{bin}", commands: "{bin} status" }, + { kind: "run-command", label: "{bin}", commands: [42, null] }, + ], + }), +}; + +function cli(build: () => CliStructuredError) { + return createTestCli({ + commands: { + thrown: defineCommand({ + help: { summary: "Throw" }, + args: {}, + handler: async () => { + throw build(); + }, + }), + returned: defineCommand({ + help: { summary: "Return" }, + args: {}, + handler: async () => notOk(build()), + }), + }, + }); +} + +function errorOf(frames: readonly StreamEvent[]) { + const last = frames[frames.length - 1]; + if (last?.kind !== "result" || last.envelope.ok) { + throw new Error("expected an errored result frame"); + } + return last.envelope.error; +} + +describe.each(Object.entries(CASES))("%s", (_name, build) => { + describe.each(["thrown", "returned"])("%s by the handler", (command) => { + test("json settles with the original error", async () => { + const result = await cli(build).run([command, "--json"]); + + expect(result.exitCode).toBe(2); + expect(errorOf(result.json)).toMatchObject({ + code: "FOREIGN.FAILED", + summary: "Run `prisma-test status`.", + }); + }); + + test("human settles with the original error", async () => { + const result = await cli(build).run([command], { + isTty: { stdout: true }, + }); + + expect(result.exitCode).toBe(2); + expect(result.stderr).toContain( + "[FOREIGN.FAILED] Run `prisma-test status`.", + ); + }); + + test("markdown settles with the original error", async () => { + const result = await cli(build).run([command, "--format", "markdown"]); + + expect(result.exitCode).toBe(2); + expect(result.stdout).toContain( + "FOREIGN.FAILED: Run `prisma-test status`.", + ); + }); + }); +}); diff --git a/packages/cli-engine/tests/bin-placeholder.test.ts b/packages/cli-engine/tests/bin-placeholder.test.ts new file mode 100644 index 00000000..315aced6 --- /dev/null +++ b/packages/cli-engine/tests/bin-placeholder.test.ts @@ -0,0 +1,260 @@ +/** + * A command family never names the binary: it writes `{bin}`, and the + * engine substitutes the name of the CLI the user ran in everything it + * prints or serializes from the command. + */ +import { defineCommand, type StreamEvent } from "@prisma/cli-engine"; +import { + CliStructuredError, + type Diagnostic, + ok, +} from "@prisma/cli-engine/protocol"; +import { createTestCli } from "@prisma/cli-engine/testing"; +import { describe, expect, test } from "vitest"; + +const FINDING: Diagnostic = { + code: "MIGRATION.DRIFT", + severity: "warn", + summary: "Run `{bin} migration status` to see the drift.", + why: "`{bin} db migrate` was interrupted.", + nextActions: [ + { kind: "run-command", label: "Inspect", command: "{bin} db inspect" }, + ], +}; + +const DATA = { note: "the row says {bin}" }; + +const plan = defineCommand({ + help: { summary: "Plan a migration" }, + args: {}, + handler: async (_args, ctx) => + ok( + ctx.present( + { data: DATA, diagnostics: [FINDING] }, + { + human: () => [ + { + kind: "summary", + status: "info", + text: "Run `{bin} migration plan --name ` to author one.", + }, + { + kind: "list", + items: [[{ text: "{bin} migration graph", tone: "identifier" }]], + }, + { + kind: "table", + columns: ["name"], + rows: [["{bin}"]], + }, + { + kind: "fields", + rows: [{ label: "value", value: "{bin}" }], + }, + ], + stdout: () => ["{bin}"], + json: () => DATA, + next: () => [ + { + kind: "run-command", + label: "Apply the migration", + command: "{bin} db migrate", + }, + { + kind: "run-command", + label: "Or step by step", + commands: ["{bin} db migrate --dry-run", "{bin} db migrate"], + }, + { kind: "run-command", label: "No placeholder", command: "ls" }, + { + kind: "open-url", + label: "Read about `{bin} db migrate`", + url: "https://example.com/{bin}", + reason: "`{bin} db migrate` changes the database.", + }, + ], + }, + ), + ), +}); + +const failing = defineCommand({ + help: { summary: "Fail" }, + args: {}, + handler: async () => { + throw new CliStructuredError( + "MIGRATION.NOT_FOUND", + "Run `{bin} migration plan` first.", + { + why: "`{bin} db migrate` found no migration.", + nextActions: [ + { + kind: "run-command", + label: "Plan one", + command: "{bin} migration plan", + }, + ], + diagnostics: [FINDING], + meta: { echoed: "{bin}" }, + }, + ); + }, +}); + +const HUMAN = { isTty: { stdout: true } }; + +const cli = () => createTestCli({ commands: { plan, failing } }); + +function lastEnvelope(frames: readonly StreamEvent[]) { + const last = frames[frames.length - 1]; + if (last.kind !== "result") { + throw new Error("expected a result frame"); + } + return last.envelope; +} + +describe("a completed command", () => { + test("human output names the binary and leaves table cells and field values alone", async () => { + const result = await cli().run(["plan"], HUMAN); + + expect(result.stderr).toBe( + "ℹ Run `prisma-test migration plan --name ` to author one.\n" + + "\n" + + "- prisma-test migration graph\n" + + "\n" + + "Name\n" + + "{bin}\n" + + "\n" + + "value: {bin}\n" + + "\n" + + "→ Apply the migration: prisma-test db migrate\n" + + "→ Or step by step\n" + + "→ No placeholder: ls\n" + + "→ Read about `prisma-test db migrate`: https://example.com/{bin}\n" + + "\n" + + "⚠ [MIGRATION.DRIFT] Run `prisma-test migration status` to see the drift.\n" + + " why: `prisma-test db migrate` was interrupted.\n" + + "→ Inspect: prisma-test db inspect\n", + ); + }); + + test("the machine lines on stdout are left as the command wrote them", async () => { + const result = await cli().run(["plan"], HUMAN); + + expect(result.stdout).toBe("{bin}\n"); + }); + + test("markdown output names the binary and leaves table cells and field values alone", async () => { + const result = await cli().run(["plan", "--format", "markdown"]); + + expect(result.stdout).toBe( + "[info] Run `prisma-test migration plan --name ` to author one.\n" + + "\n" + + "- prisma-test migration graph\n" + + "\n" + + "| Name |\n" + + "| --- |\n" + + "| {bin} |\n" + + "\n" + + "value: {bin}\n" + + "\n" + + "### Next\n" + + "- Apply the migration: `prisma-test db migrate`\n" + + "- Or step by step\n" + + " - `prisma-test db migrate --dry-run`\n" + + " - `prisma-test db migrate`\n" + + "- No placeholder: `ls`\n" + + "- Read about `prisma-test db migrate`: https://example.com/{bin}\n" + + "\n" + + "### Diagnostics\n" + + "[warn] MIGRATION.DRIFT: Run `prisma-test migration status` to see the drift.\n" + + "why: `prisma-test db migrate` was interrupted.\n" + + "- Inspect: `prisma-test db inspect`\n", + ); + }); + + test("the json envelope names the binary and leaves the result alone", async () => { + const result = await cli().run(["plan", "--json"]); + + expect(lastEnvelope(result.json)).toMatchObject({ + ok: true, + result: { note: "the row says {bin}" }, + nextActions: [ + { label: "Apply the migration", command: "prisma-test db migrate" }, + { + label: "Or step by step", + commands: [ + "prisma-test db migrate --dry-run", + "prisma-test db migrate", + ], + }, + { label: "No placeholder", command: "ls" }, + { + label: "Read about `prisma-test db migrate`", + url: "https://example.com/{bin}", + reason: "`prisma-test db migrate` changes the database.", + }, + ], + diagnostics: [ + { + summary: "Run `prisma-test migration status` to see the drift.", + why: "`prisma-test db migrate` was interrupted.", + nextActions: [{ command: "prisma-test db inspect" }], + }, + ], + }); + }); +}); + +describe("a failed command", () => { + test("human output names the binary", async () => { + const result = await cli().run(["failing"], HUMAN); + + expect(result.stderr).toBe( + "✘ [MIGRATION.NOT_FOUND] Run `prisma-test migration plan` first.\n" + + " why: `prisma-test db migrate` found no migration.\n" + + "→ Plan one: prisma-test migration plan\n" + + "\n" + + "⚠ [MIGRATION.DRIFT] Run `prisma-test migration status` to see the drift.\n" + + " why: `prisma-test db migrate` was interrupted.\n" + + "→ Inspect: prisma-test db inspect\n", + ); + }); + + test("markdown output names the binary", async () => { + const result = await cli().run(["failing", "--format", "markdown"]); + + expect(result.stdout).not.toContain("{bin}"); + expect(result.stdout).toContain( + "[error] MIGRATION.NOT_FOUND: Run `prisma-test migration plan` first.\n" + + "why: `prisma-test db migrate` found no migration.\n" + + "- Plan one: `prisma-test migration plan`\n", + ); + }); + + test("the json envelope names the binary and leaves meta alone", async () => { + const result = await cli().run(["failing", "--json"]); + + const planOne = { + label: "Plan one", + command: "prisma-test migration plan", + }; + expect(lastEnvelope(result.json)).toMatchObject({ + ok: false, + error: { + summary: "Run `prisma-test migration plan` first.", + why: "`prisma-test db migrate` found no migration.", + nextActions: [planOne], + meta: { echoed: "{bin}" }, + }, + diagnostics: [ + { + summary: "Run `prisma-test migration status` to see the drift.", + why: "`prisma-test db migrate` was interrupted.", + nextActions: [{ command: "prisma-test db inspect" }], + }, + ], + nextActions: [planOne], + }); + }); +}); diff --git a/packages/cli/package.json b/packages/cli/package.json index 5e0e59cd..c772e015 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -49,7 +49,7 @@ }, "dependencies": { "@manypkg/tools": "^2.1.2", - "@prisma/cli-engine": "workspace:0.6.1", + "@prisma/cli-engine": "workspace:0.6.2", "@prisma/composer-cli": "0.23.0", "@prisma/compute-sdk": "0.43.0", "@prisma/management-api-sdk": "1.79.0", diff --git a/packages/cli/scripts/conformance.ts b/packages/cli/scripts/conformance.ts index 3403120d..0aad3038 100644 --- a/packages/cli/scripts/conformance.ts +++ b/packages/cli/scripts/conformance.ts @@ -109,7 +109,24 @@ async function tarball(): Promise { shellPackage: "@prisma/cli", enginePackage: "@prisma/cli-engine", familyPackages: ["@prisma/composer-cli", "@prisma/orm-toolchain"], - exceptions: [], + exceptions: [ + { + familyPackage: "@prisma/composer-cli", + familyPin: "0.6.1", + shellPin: "0.6.2", + reason: "engine 0.6.2 must publish before composer-cli can peer it", + removeWhen: + "composer-cli releases peering 0.6.2 and the follow-up bump PR pins that release", + }, + { + familyPackage: "@prisma/orm-toolchain", + familyPin: "0.6.1", + shellPin: "0.6.2", + reason: "engine 0.6.2 must publish before orm-toolchain can peer it", + removeWhen: + "orm-toolchain releases peering 0.6.2 and the follow-up bump PR pins that release", + }, + ], channel: CHANNEL, sandboxDir: join(WORK_DIR, "sandbox"), }, diff --git a/packages/prisma/package.json b/packages/prisma/package.json index e2da8e57..756ba9c9 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -50,7 +50,7 @@ }, "dependencies": { "@manypkg/tools": "^2.1.2", - "@prisma/cli-engine": "workspace:0.6.1", + "@prisma/cli-engine": "workspace:0.6.2", "@prisma/composer-cli": "0.23.0", "@prisma/compute-sdk": "0.43.0", "@prisma/management-api-sdk": "1.79.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa18215b..56c7cd9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,7 +27,7 @@ importers: specifier: ^2.1.2 version: 2.1.2 '@prisma/cli-engine': - specifier: workspace:0.6.1 + specifier: workspace:0.6.2 version: link:../cli-engine '@prisma/composer-cli': specifier: 0.23.0 @@ -210,7 +210,7 @@ importers: specifier: ^2.1.2 version: 2.1.2 '@prisma/cli-engine': - specifier: workspace:0.6.1 + specifier: workspace:0.6.2 version: link:../cli-engine '@prisma/composer-cli': specifier: 0.23.0