From 1406d24fc00633d2e6aa4157b9a9ff4ec1420fdd Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Tue, 7 Jul 2026 21:48:01 +0530 Subject: [PATCH 1/8] feat(cli): add JSON compute config format to init init gains --format . The json format writes a dependency-free prisma.compute.json through the SDK's serializeComputeConfigJson with a $schema-first document, pins the same resolved values as the TypeScript path, and keeps the wx no-overwrite semantics. Because the format exists to be dependency-free, the types install step never runs with it and --install becomes a usage error; the custom framework is refused since strict JSON cannot carry the commented build stub init relies on. An explicit --format ts with an existing prisma.compute.json is the graduation path: the JSON config is validated, rewritten as prisma.compute.ts wrapping the same object in defineComputeConfig, the JSON file is deleted in the same operation, and the types install step runs. The reverse direction fails with INIT_CONVERT_UNSUPPORTED because TypeScript configs may contain logic a static file cannot express. Plain init still refuses every existing config with INIT_CONFIG_EXISTS. The result envelope now reports format and converted alongside configPath. The product decision asked for a --json boolean; that name is the global JSON-output flag on every command, so the config format rides a dedicated --format option instead. --- docs/product/command-spec.md | 13 +- docs/product/error-conventions.md | 2 + packages/cli/src/commands/init/index.ts | 10 +- packages/cli/src/controllers/init.ts | 314 ++++++++++++++++++++- packages/cli/src/presenters/init.ts | 10 +- packages/cli/src/shell/command-meta.ts | 3 +- packages/cli/src/types/init.ts | 12 +- packages/cli/tests/init.test.ts | 357 ++++++++++++++++++++++++ 8 files changed, 699 insertions(+), 22 deletions(-) diff --git a/docs/product/command-spec.md b/docs/product/command-spec.md index 2cb0705..8daba52 100644 --- a/docs/product/command-spec.md +++ b/docs/product/command-spec.md @@ -384,17 +384,22 @@ prisma-cli --version --json `prisma-cli version` is the richer environment report; `prisma-cli --version` is the terse one-liner. Both report the same `cli.version`. Use the flag for quick checks, the subcommand for support tickets and bug reports. -## `prisma-cli init --framework --entry --http-port --region --name --link --no-link --project --install --no-install` +## `prisma-cli init --framework --entry --http-port --region --name --link --no-link --project --install --no-install --format ` Purpose: -- write a committed `prisma.compute.ts` for the app in this directory +- write a committed `prisma.compute.ts` (or, with `--format json`, a dependency-free `prisma.compute.json`) for the app in this directory Behavior: - init is the config formalizer: `app deploy` works with zero config, and init writes down what deploy would infer so the setup is committed, reviewable, and stable for teammates and CI - writing the config requires no auth and no network; linking is the only remote step - fails with `INIT_CONFIG_EXISTS` when a compute config already exists in the invocation directory or any ancestor up to the repository or workspace root; the error names the existing file, and init never overwrites or merges — editing a committed config is the user's editor's job +- `--format ` selects the config serialization; `ts` (alias `typescript`) is the default and writes `prisma.compute.ts`, `json` writes `prisma.compute.json` via the shared SDK serializer with a `$schema`-first document that validates in editors without any local dependency; both formats pin exactly the same resolved values, and the CLI's global `--json` output flag is unrelated to the config format +- with `--format json`, the types install step never runs: the JSON format exists to be dependency-free, so `--install` is a usage error and the result reports the step as skipped with no install hint +- with `--format json` and the custom framework, init fails with a usage error: custom needs `build.outputDirectory` and `build.entrypoint`, which init does not collect, and strict JSON cannot carry the commented build stub the TypeScript format uses; the fix is the TypeScript format or a hand-written `build` object +- graduation path: an explicit `--format ts` with an existing `prisma.compute.json` converts it in place; the JSON config is loaded and validated, the equivalent `prisma.compute.ts` wrapping the same object in `defineComputeConfig` is written next to it, the JSON file is deleted in the same operation, and the types install step runs as usual (skippable with `--no-install`); values are transported, never re-resolved, and the conversion is reported with `converted: true` +- conversion is one-way and explicit: plain `init` with any existing config still fails with `INIT_CONFIG_EXISTS`, and `--format json` with an existing TypeScript config fails with `INIT_CONVERT_UNSUPPORTED` because TypeScript configs may contain logic that a static JSON file cannot express; a fully static config can be rewritten by hand - detects the framework from the same registry and signals `app deploy` uses; explicit `--framework` wins over detection - `--entry` sets the source entrypoint for entrypoint frameworks (Bun, Hono); `--http-port` overrides the framework default port; `--name` overrides the app name inferred from `package.json#name` or the directory name - previews the resolved values with per-value source annotations (`detected`, `framework default`, `package.json`, `flag`) before writing; in interactive mode the preview is followed by an optional adjust step for framework and HTTP port, and `--yes` accepts the preview as shown @@ -415,7 +420,7 @@ Behavior: - link failures and cancellations after the config is written downgrade to warnings and `nextSteps`; the config write stands and init exits 0 - `nextSteps` includes the deploy command, plus the project link command when the directory is still unlinked - user-facing command hints in init output (next steps, link hints, error recovery commands) use the package runner detected from the project, such as `pnpm dlx @prisma/cli@latest project link` or `npx -y @prisma/cli@latest app deploy`, matching the `agent` group's convention -- in `--json`, `result` includes `configPath`, the written `app` values, per-value `settings` sources, and `link` state; `--json` never prompts +- in `--json`, `result` includes `configPath`, `format` (`typescript` or `json`), `converted` (true only for the `--format ts` conversion path), the written `app` values (null when a conversion transported a config that does not pin a single fully-resolved app), per-value `settings` sources, and `link` state; `--json` never prompts Examples: @@ -424,6 +429,8 @@ prisma-cli init prisma-cli init --framework hono --entry src/index.ts prisma-cli init --name api --http-port 8080 --no-link prisma-cli init --project proj_123 +prisma-cli init --format json +prisma-cli init --format ts prisma-cli init --json ``` diff --git a/docs/product/error-conventions.md b/docs/product/error-conventions.md index ef9fa9f..54a94fe 100644 --- a/docs/product/error-conventions.md +++ b/docs/product/error-conventions.md @@ -188,6 +188,7 @@ These codes are the minimum stable set for the MVP: - `BUILD_SETTINGS_UNSUPPORTED` - `FRAMEWORK_NOT_DETECTED` - `INIT_CONFIG_EXISTS` +- `INIT_CONVERT_UNSUPPORTED` - `INIT_DETECTION_FAILED` - `DEPLOYMENT_NOT_FOUND` - `NO_DEPLOYMENTS` @@ -261,6 +262,7 @@ Recommended meanings: - `BUILD_SETTINGS_UNSUPPORTED`: a compute config `build` block targets a framework whose SDK strategy does not consume committed build settings - `FRAMEWORK_NOT_DETECTED`: app deploy could not detect a supported Beta framework and no explicit framework/build type was provided - `INIT_CONFIG_EXISTS`: a compute config already exists in this directory or an ancestor; init never overwrites or merges +- `INIT_CONVERT_UNSUPPORTED`: `init --format json` found an existing TypeScript config; TypeScript configs may contain logic, so converting them to JSON automatically would be lossy and the rewrite is manual - `INIT_DETECTION_FAILED`: no supported framework detected and no --framework passed; `meta.frameworks` lists the valid values - `DEPLOYMENT_NOT_FOUND`: requested deployment id does not exist - `NO_DEPLOYMENTS`: command resolved a branch or app but found no deployments diff --git a/packages/cli/src/commands/init/index.ts b/packages/cli/src/commands/init/index.ts index 5e5a342..647229f 100644 --- a/packages/cli/src/commands/init/index.ts +++ b/packages/cli/src/commands/init/index.ts @@ -43,7 +43,13 @@ export function createInitCommand(runtime: CliRuntime): Command { .addOption( new Option("--install", "Install @prisma/compute-sdk for config types"), ) - .addOption(new Option("--no-install", "Skip the types install step")); + .addOption(new Option("--no-install", "Skip the types install step")) + .addOption( + new Option( + "--format ", + "Config file format: ts (default) writes prisma.compute.ts, json writes dependency-free prisma.compute.json; --format ts converts an existing prisma.compute.json", + ), + ); addGlobalFlags(command); command.action(async (options) => { @@ -56,6 +62,7 @@ export function createInitCommand(runtime: CliRuntime): Command { link?: boolean; project?: string; install?: boolean; + format?: string; }; await runCommand( @@ -72,6 +79,7 @@ export function createInitCommand(runtime: CliRuntime): Command { link: flags.link, project: flags.project, install: flags.install, + format: flags.format, }), { renderHuman: (context, descriptor, result) => diff --git a/packages/cli/src/controllers/init.ts b/packages/cli/src/controllers/init.ts index b492bac..262021f 100644 --- a/packages/cli/src/controllers/init.ts +++ b/packages/cli/src/controllers/init.ts @@ -1,7 +1,8 @@ -import { writeFile } from "node:fs/promises"; +import { readFile, rm, writeFile } from "node:fs/promises"; import path from "node:path"; import { COMPUTE_CONFIG_FILENAME, + COMPUTE_CONFIG_JSON_FILENAME, COMPUTE_REGIONS, type ComputeConfig, type ComputeFramework, @@ -12,7 +13,10 @@ import { findComputeConfigDir, frameworkByKey, frameworkFromAlias, + type LoadedComputeConfig, + normalizeComputeConfig, serializeComputeConfig, + serializeComputeConfigJson, } from "@prisma/compute-sdk/config"; import { execa } from "execa"; @@ -39,6 +43,7 @@ import { } from "../shell/prompt"; import { type CommandContext, canPrompt } from "../shell/runtime"; import type { + InitConfigFormat, InitLinkState, InitResult, InitSettingRow, @@ -56,6 +61,7 @@ export interface InitFlags { link?: boolean; project?: string; install?: boolean; + format?: string; } interface ResolvedInitFramework { @@ -74,7 +80,36 @@ export async function runInit( // bunx, npx -y), matching the agent group's convention. const formatCommand = resolvePrismaCliPackageCommandFormatterSync(cwd); - await requireNoExistingComputeConfig(cwd, signal); + const format = parseInitFormat(flags.format, formatCommand); + if (format.value === "json" && flags.install === true) { + throw usageError( + "--install does not apply to the JSON config format", + `${COMPUTE_CONFIG_JSON_FILENAME} is a dependency-free static config; the ${COMPUTE_SDK_PACKAGE} devDependency exists only for ${COMPUTE_CONFIG_FILENAME} editor types.`, + "Drop --install, or use the TypeScript format.", + [formatCommand(["init", "--format", "json"])], + "app", + ); + } + + const existingConfig = await findExistingComputeConfig(cwd, signal); + if (existingConfig) { + const solePath = + existingConfig.candidates.length === 1 + ? existingConfig.candidates[0] + : undefined; + const soleIsJson = + solePath !== undefined && path.extname(solePath) === ".json"; + // Conversion must be explicit: plain init refuses every existing config. + if (soleIsJson && format.value === "typescript" && format.explicit) { + return runInitConversion(context, flags, solePath, formatCommand); + } + if (solePath && !soleIsJson && format.value === "json") { + throw initConvertUnsupportedError(solePath); + } + throw initConfigExistsError( + existingConfig.candidates[0] ?? existingConfig.directory, + ); + } const region = parseInitRegion(flags.region, formatCommand); let framework = await resolveInitFramework(context, flags, formatCommand); @@ -89,6 +124,20 @@ export async function runInit( framework = adjusted.framework; httpPort = adjusted.httpPort; + // The custom framework needs build.outputDirectory and build.entrypoint, + // which init does not collect. The TypeScript format carries a commented + // build stub to fill in; strict JSON cannot hold comments, so refuse here + // instead of writing a config that deploy would reject. + if (format.value === "json" && framework.key === "custom") { + throw usageError( + "Custom framework requires the TypeScript config format", + "The custom framework needs build.outputDirectory and build.entrypoint, which init does not collect; the TypeScript format includes a commented build stub to complete, and strict JSON cannot carry it.", + `Rerun without --format json and fill in the build stub, or write ${COMPUTE_CONFIG_JSON_FILENAME} by hand with a build object.`, + [formatCommand(["init", "--framework", "custom"])], + "app", + ); + } + // Entry resolves against the FINAL framework so an interactive framework // switch cannot leave a stale (or missing) entry in the written config. const entry = await resolveInitEntry(cwd, framework, flags.entry, signal); @@ -123,10 +172,19 @@ export async function runInit( }, }; - const configPath = path.join(cwd, COMPUTE_CONFIG_FILENAME); - let source = serializeComputeConfig(config); - if (framework.key === "custom") { - source += CUSTOM_BUILD_STUB; + const configFilename = + format.value === "json" + ? COMPUTE_CONFIG_JSON_FILENAME + : COMPUTE_CONFIG_FILENAME; + const configPath = path.join(cwd, configFilename); + let source: string; + if (format.value === "json") { + source = serializeComputeConfigJson(config); + } else { + source = serializeComputeConfig(config); + if (framework.key === "custom") { + source += CUSTOM_BUILD_STUB; + } } signal.throwIfAborted(); @@ -141,9 +199,18 @@ export async function runInit( } const warnings: string[] = []; - const types = await resolveInitTypes(context, flags, { - onWarning: (message) => warnings.push(message), - }); + // The JSON format exists to be dependency-free, so the types install step + // never runs for it; prisma.compute.json validates through $schema instead. + const types: InitTypesState = + format.value === "json" + ? { + status: "skipped", + package: COMPUTE_SDK_PACKAGE, + installCommand: null, + } + : await resolveInitTypes(context, flags, { + onWarning: (message) => warnings.push(message), + }); const link = await resolveInitLink(context, flags, { onWarning: (message) => warnings.push(message), formatCommand, @@ -155,7 +222,9 @@ export async function runInit( return { command: "init", result: { - configPath: COMPUTE_CONFIG_FILENAME, + configPath: configFilename, + format: format.value, + converted: false, directory: formatInitDirectory(cwd), app: { name: name.value, @@ -347,17 +416,232 @@ const CUSTOM_BUILD_STUB = ` // }, `; -async function requireNoExistingComputeConfig( +/** + * Nearest existing compute config, searching from `cwd` up to the source + * root. Init routes on this: refuse, convert, or proceed fresh. + */ +async function findExistingComputeConfig( cwd: string, signal: AbortSignal, -): Promise { +): Promise<{ directory: string; candidates: string[] } | null> { const configDir = await findComputeConfigDir(cwd, signal); if (!configDir) { - return; + return null; + } + + return { + directory: configDir, + candidates: await findComputeConfigCandidates(configDir, signal), + }; +} + +function parseInitFormat( + value: string | undefined, + formatCommand: PrismaCliPackageCommandFormatter, +): { value: InitConfigFormat; explicit: boolean } { + if (value === undefined) { + return { value: "typescript", explicit: false }; + } + + const normalized = value.trim().toLowerCase(); + if (normalized === "ts" || normalized === "typescript") { + return { value: "typescript", explicit: true }; + } + if (normalized === "json") { + return { value: "json", explicit: true }; + } + + throw usageError( + "Unknown config format", + `"${value}" is not a supported config format.`, + "Pass --format ts or --format json.", + [formatCommand(["init", "--format", "json"])], + "app", + ); +} + +function initConvertUnsupportedError(existingPath: string): CliError { + return new CliError({ + code: "INIT_CONVERT_UNSUPPORTED", + domain: "app", + summary: "TypeScript configs do not convert to JSON", + why: `${existingPath} may contain imports, expressions, or comments that the static ${COMPUTE_CONFIG_JSON_FILENAME} format cannot express, so an automatic conversion would be lossy.`, + fix: `If the config is fully static, rewrite it by hand as ${COMPUTE_CONFIG_JSON_FILENAME} and delete ${path.basename(existingPath)}.`, + exitCode: 1, + nextSteps: [], + meta: { existingConfigPath: existingPath }, + }); +} + +/** + * The graduation path: an explicit `--format ts` with an existing + * `prisma.compute.json` rewrites the same config as `prisma.compute.ts` and + * deletes the JSON file, so a static config can grow into a programmatic one. + * The values are transported, never re-resolved. + */ +async function runInitConversion( + context: CommandContext, + flags: InitFlags, + jsonConfigPath: string, + formatCommand: PrismaCliPackageCommandFormatter, +): Promise> { + const cwd = context.runtime.cwd; + const signal = context.runtime.signal; + + let parsed: unknown; + try { + parsed = JSON.parse(await readFile(jsonConfigPath, "utf8")); + } catch (error) { + if (signal.aborted) { + throw error; + } + throw initConvertInvalidError(jsonConfigPath, [ + error instanceof Error + ? (error.message.split("\n")[0] as string) + : String(error), + ]); + } + + // "$schema" is editor tooling metadata, not config; the TypeScript format + // carries types through its import instead. + const config = stripJsonSchemaKey(parsed); + const normalized = normalizeComputeConfig(config, jsonConfigPath); + if (normalized.isErr()) { + throw initConvertInvalidError(jsonConfigPath, normalized.error.issues); + } + const loaded = normalized.value; + + const tsConfigPath = path.join(loaded.configDir, COMPUTE_CONFIG_FILENAME); + const source = serializeComputeConfig(config as ComputeConfig); + + signal.throwIfAborted(); + try { + // wx: fail instead of clobbering a config that appeared since discovery. + await writeFile(tsConfigPath, source, { encoding: "utf8", flag: "wx" }); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "EEXIST") { + throw initConfigExistsError(tsConfigPath); + } + throw error; + } + try { + await rm(jsonConfigPath); + } catch (error) { + // Two coexisting config files are a hard loader error, so a failed + // delete rolls the write back and leaves the JSON config untouched. + await rm(tsConfigPath, { force: true }); + throw error; } - const candidates = await findComputeConfigCandidates(configDir, signal); - throw initConfigExistsError(candidates[0] ?? configDir); + const settings = conversionSettings(loaded); + renderInitSettingsPreview(context, settings); + + const warnings: string[] = []; + const types = await resolveInitTypes(context, flags, { + onWarning: (message) => warnings.push(message), + }); + // Conversion changes the config's serialization, not the directory's + // project binding, so it reports the link state without prompting. + const pin = await readLocalResolutionPin(cwd, signal); + const link: InitLinkState = + pin.isOk() && pin.value.kind === "present" + ? { status: "already-linked", project: null } + : { status: "skipped", project: null }; + + const unlinked = link.status !== "already-linked"; + const typesMissing = + types.status !== "installed" && types.status !== "already-installed"; + return { + command: "init", + result: { + configPath: path.relative(cwd, tsConfigPath) || COMPUTE_CONFIG_FILENAME, + format: "typescript", + converted: true, + directory: formatInitDirectory(loaded.configDir), + app: conversionApp(loaded), + settings, + types, + link, + }, + warnings, + nextSteps: [ + ...(typesMissing && types.installCommand ? [types.installCommand] : []), + formatCommand(["app", "deploy"]), + ...(unlinked ? [formatCommand(["project", "link"])] : []), + ], + }; +} + +function stripJsonSchemaKey(parsed: unknown): unknown { + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + return parsed; + } + const { $schema: _schema, ...config } = parsed as Record; + return config; +} + +function initConvertInvalidError( + jsonConfigPath: string, + issues: string[], +): CliError { + return new CliError({ + code: "COMPUTE_CONFIG_INVALID", + domain: "app", + summary: `Invalid ${path.basename(jsonConfigPath)}`, + why: issues.join(" "), + fix: `Fix ${path.basename(jsonConfigPath)} and rerun the conversion.`, + where: jsonConfigPath, + meta: { configPath: jsonConfigPath, issues }, + exitCode: 2, + nextSteps: [], + }); +} + +/** Preview rows for a conversion; every value is sourced from the JSON file. */ +function conversionSettings(loaded: LoadedComputeConfig): InitSettingRow[] { + const target = loaded.kind === "single" ? loaded.targets[0] : undefined; + if (!target) { + return []; + } + + const source = COMPUTE_CONFIG_JSON_FILENAME; + return [ + ...(target.name ? [{ key: "app", value: target.name, source }] : []), + ...(target.framework + ? [ + { + key: "framework", + value: frameworkByKey(target.framework).displayName, + source, + }, + ] + : []), + ...(target.entry ? [{ key: "entry", value: target.entry, source }] : []), + ...(target.httpPort !== null + ? [{ key: "http port", value: String(target.httpPort), source }] + : []), + ...(target.region ? [{ key: "region", value: target.region, source }] : []), + ]; +} + +/** + * App identity for the conversion result. Configs written by init pin all of + * name, framework, and httpPort; hand-written configs that omit any of them + * (or define multiple apps) report null instead of a partial identity. + */ +function conversionApp(loaded: LoadedComputeConfig): InitResult["app"] { + const target = loaded.kind === "single" ? loaded.targets[0] : undefined; + if (!target?.name || !target.framework || target.httpPort === null) { + return null; + } + + return { + name: target.name, + framework: target.framework, + httpPort: target.httpPort, + ...(target.entry ? { entry: target.entry } : {}), + ...(target.region ? { region: target.region } : {}), + }; } function initConfigExistsError(existingPath: string): CliError { diff --git a/packages/cli/src/presenters/init.ts b/packages/cli/src/presenters/init.ts index 492f34c..037b10f 100644 --- a/packages/cli/src/presenters/init.ts +++ b/packages/cli/src/presenters/init.ts @@ -1,3 +1,5 @@ +import { COMPUTE_CONFIG_JSON_FILENAME } from "@prisma/compute-sdk/config"; + import { resolvePrismaCliPackageCommandFormatterSync } from "../lib/agent/cli-command"; import type { CommandDescriptor } from "../shell/command-meta"; import type { CommandContext } from "../shell/runtime"; @@ -14,7 +16,13 @@ export function renderInit( context.runtime.cwd, ); const lines = [ - renderSummaryLine(ui, "success", `Wrote ${result.configPath}`), + renderSummaryLine( + ui, + "success", + result.converted + ? `Converted ${COMPUTE_CONFIG_JSON_FILENAME} to ${result.configPath}` + : `Wrote ${result.configPath}`, + ), ]; // Failed steps surface through the runner's success-warning rendering, so diff --git a/packages/cli/src/shell/command-meta.ts b/packages/cli/src/shell/command-meta.ts index 1c0f50a..069f213 100644 --- a/packages/cli/src/shell/command-meta.ts +++ b/packages/cli/src/shell/command-meta.ts @@ -47,11 +47,12 @@ const DESCRIPTORS: CommandDescriptor[] = [ { id: "init", path: ["prisma", "init"], - description: "Write a committed prisma.compute.ts for this app", + description: "Write a committed compute config for this app", examples: (runtime) => agentCommandExamples(runtime, [ ["init"], ["init", "--framework", "hono", "--entry", "src/index.ts"], + ["init", "--format", "json"], ["init", "--no-link"], ]), }, diff --git a/packages/cli/src/types/init.ts b/packages/cli/src/types/init.ts index 8ffe741..dad5e80 100644 --- a/packages/cli/src/types/init.ts +++ b/packages/cli/src/types/init.ts @@ -1,3 +1,6 @@ +/** Serialization the compute config was written in. */ +export type InitConfigFormat = "typescript" | "json"; + export interface InitSettingRow { key: string; value: string; @@ -35,14 +38,21 @@ export interface InitTypesState { export interface InitResult { configPath: string; + format: InitConfigFormat; + /** True when init converted an existing prisma.compute.json to TypeScript. */ + converted: boolean; directory: string; + /** + * App identity pinned by the written config. Null when a conversion + * transported a config that does not pin a single fully-resolved app. + */ app: { name: string; framework: string; httpPort: number; entry?: string; region?: string; - }; + } | null; settings: InitSettingRow[]; types: InitTypesState; link: InitLinkState; diff --git a/packages/cli/tests/init.test.ts b/packages/cli/tests/init.test.ts index d53a709..8157219 100644 --- a/packages/cli/tests/init.test.ts +++ b/packages/cli/tests/init.test.ts @@ -1,5 +1,9 @@ import { mkdir, readFile, writeFile } from "node:fs/promises"; import path from "node:path"; +import { + COMPUTE_CONFIG_JSON_SCHEMA_URL, + loadComputeConfig, +} from "@prisma/compute-sdk/config"; import stripAnsi from "strip-ansi"; import { describe, expect, it } from "vitest"; @@ -31,6 +35,10 @@ async function readConfig(cwd: string): Promise { return readFile(path.join(cwd, "prisma.compute.ts"), "utf8"); } +async function readJsonConfig(cwd: string): Promise { + return readFile(path.join(cwd, "prisma.compute.json"), "utf8"); +} + describe("init", () => { it("writes a config for an explicit framework without auth or prompts", async () => { const cwd = await createTempCwd(); @@ -412,6 +420,355 @@ describe("init types install", () => { }); }); +describe("init config format", () => { + it("writes a $schema-first prisma.compute.json with --format json that round-trips through the loader", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writePackageJson(cwd, { name: "billing-api" }); + + const result = await executeCli({ + argv: [ + "init", + "--framework", + "hono", + "--entry", + "src/index.ts", + "--no-link", + "--format", + "json", + "--json", + ], + cwd, + stateDir, + fixturePath, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(0); + expect(payload).toMatchObject({ + ok: true, + command: "init", + result: { + configPath: "prisma.compute.json", + format: "json", + converted: false, + app: { + name: "billing-api", + framework: "hono", + entry: "src/index.ts", + }, + // The JSON format is dependency-free by design, so the types install + // step never runs and no install hint is emitted. + types: { + status: "skipped", + package: "@prisma/compute-sdk", + installCommand: null, + }, + }, + }); + expect(payload.nextSteps).toEqual([ + "npx -y @prisma/cli@latest app deploy", + "npx -y @prisma/cli@latest project link", + ]); + + const written = JSON.parse(await readJsonConfig(cwd)); + expect(Object.keys(written)[0]).toBe("$schema"); + expect(written.$schema).toBe(COMPUTE_CONFIG_JSON_SCHEMA_URL); + expect(written.app).toMatchObject({ + name: "billing-api", + framework: "hono", + entry: "src/index.ts", + }); + + const loaded = await loadComputeConfig(cwd); + expect(loaded.isOk()).toBe(true); + expect(loaded.isOk() && loaded.value?.targets[0]).toMatchObject({ + name: "billing-api", + framework: "hono", + entry: "src/index.ts", + httpPort: payload.result.app.httpPort, + }); + await expect(readConfig(cwd)).rejects.toMatchObject({ code: "ENOENT" }); + }); + + it("writes prisma.compute.ts with an explicit --format ts", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writePackageJson(cwd, { name: "api" }); + + const result = await executeCli({ + argv: [ + "init", + "--framework", + "hono", + "--no-link", + "--format", + "ts", + "--json", + ], + cwd, + stateDir, + fixturePath, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(0); + expect(payload.result).toMatchObject({ + configPath: "prisma.compute.ts", + format: "typescript", + converted: false, + }); + await expect(readConfig(cwd)).resolves.toContain("defineComputeConfig"); + }); + + it("rejects --install with --format json as a usage error", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writePackageJson(cwd, { name: "api" }); + + const result = await executeCli({ + argv: [ + "init", + "--framework", + "hono", + "--install", + "--format", + "json", + "--json", + ], + cwd, + stateDir, + fixturePath, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(2); + expect(payload.error.code).toBe("USAGE_ERROR"); + await expect(readJsonConfig(cwd)).rejects.toMatchObject({ + code: "ENOENT", + }); + }); + + it("rejects unknown --format values as a usage error", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + + const result = await executeCli({ + argv: ["init", "--framework", "hono", "--format", "yaml", "--json"], + cwd, + stateDir, + fixturePath, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(2); + expect(payload.error.code).toBe("USAGE_ERROR"); + }); + + it("rejects --format json for the custom framework and writes nothing", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + + const result = await executeCli({ + argv: [ + "init", + "--framework", + "custom", + "--no-link", + "--format", + "json", + "--json", + ], + cwd, + stateDir, + fixturePath, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(2); + expect(payload.error.code).toBe("USAGE_ERROR"); + expect(payload.error.summary).toContain("TypeScript config format"); + await expect(readJsonConfig(cwd)).rejects.toMatchObject({ + code: "ENOENT", + }); + }); + + it("fails with INIT_CONVERT_UNSUPPORTED when a TypeScript config exists and --format json is passed", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writeFile( + path.join(cwd, "prisma.compute.ts"), + 'export default { app: { framework: "hono" } };\n', + ); + + const result = await executeCli({ + argv: ["init", "--framework", "hono", "--format", "json", "--json"], + cwd, + stateDir, + fixturePath, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(1); + expect(payload.error.code).toBe("INIT_CONVERT_UNSUPPORTED"); + expect(payload.error.fix).toContain("rewrite it by hand"); + expect(payload.error.meta.existingConfigPath).toContain( + "prisma.compute.ts", + ); + await expect(readJsonConfig(cwd)).rejects.toMatchObject({ + code: "ENOENT", + }); + }); + + it("refuses plain init when prisma.compute.json exists", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writeFile( + path.join(cwd, "prisma.compute.json"), + `${JSON.stringify({ app: { framework: "hono" } })}\n`, + ); + + // Conversion must be explicit via --format ts; plain init and a repeated + // --format json both refuse. + for (const argv of [ + ["init", "--framework", "hono", "--json"], + ["init", "--framework", "hono", "--format", "json", "--json"], + ]) { + const result = await executeCli({ argv, cwd, stateDir, fixturePath }); + const payload = JSON.parse(result.stdout); + expect(result.exitCode).toBe(1); + expect(payload.error.code).toBe("INIT_CONFIG_EXISTS"); + expect(payload.error.meta.existingConfigPath).toContain( + "prisma.compute.json", + ); + } + }); + + it("converts prisma.compute.json to prisma.compute.ts with --format ts", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writeFile( + path.join(cwd, "prisma.compute.json"), + `${JSON.stringify( + { + $schema: COMPUTE_CONFIG_JSON_SCHEMA_URL, + app: { + name: "billing-api", + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + region: "us-east-1", + }, + }, + null, + 2, + )}\n`, + ); + + const result = await executeCli({ + argv: ["init", "--format", "ts", "--json"], + cwd, + stateDir, + fixturePath, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(0); + expect(payload.result).toMatchObject({ + configPath: "prisma.compute.ts", + format: "typescript", + converted: true, + app: { + name: "billing-api", + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + region: "us-east-1", + }, + }); + expect(payload.result.settings).toContainEqual({ + key: "framework", + value: "Hono", + source: "prisma.compute.json", + }); + + const config = await readConfig(cwd); + expect(config).toContain("defineComputeConfig"); + expect(config).toContain('name: "billing-api"'); + expect(config).toContain('framework: "hono"'); + expect(config).toContain('entry: "src/index.ts"'); + expect(config).toContain("httpPort: 8080"); + expect(config).toContain('region: "us-east-1"'); + expect(config).not.toContain("$schema"); + await expect(readJsonConfig(cwd)).rejects.toMatchObject({ + code: "ENOENT", + }); + + const loaded = await loadComputeConfig(cwd); + expect(loaded.isOk()).toBe(true); + expect(loaded.isOk() && loaded.value?.targets[0]).toMatchObject({ + name: "billing-api", + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + region: "us-east-1", + }); + }); + + it("runs the types install step when converting with --install", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writePackageJson(cwd, { name: "api" }); + await writeFile( + path.join(cwd, "prisma.compute.json"), + `${JSON.stringify({ app: { framework: "hono", httpPort: 8080 } })}\n`, + ); + + const result = await executeCli({ + argv: ["init", "--format", "ts", "--install", "--json"], + cwd, + stateDir, + fixturePath, + env: { + PRISMA_CLI_INIT_INSTALL_COMMAND: JSON.stringify([ + "node", + "-e", + "process.exit(0)", + ]), + }, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(0); + expect(payload.result.converted).toBe(true); + // A hand-written config without a pinned name reports no app identity. + expect(payload.result.app).toBeNull(); + expect(payload.result.types.status).toBe("installed"); + }); + + it("prints the human conversion summary", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writeFile( + path.join(cwd, "prisma.compute.json"), + `${JSON.stringify({ app: { framework: "hono", httpPort: 8080 } })}\n`, + ); + + const result = await executeCli({ + argv: ["init", "--format", "ts", "--no-install"], + cwd, + stateDir, + fixturePath, + }); + const stderr = stripAnsi(result.stderr); + + expect(result.exitCode).toBe(0); + expect(stderr).toContain( + "✔ Converted prisma.compute.json to prisma.compute.ts", + ); + }); +}); + describe("init edge cases", () => { it("rejects --entry for frameworks that derive their entrypoint", async () => { const cwd = await createTempCwd(); From 43c3f0ffcd33a4f3440249b0982952401a1f6e1d Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Tue, 7 Jul 2026 21:48:12 +0530 Subject: [PATCH 2/8] chore: link local compute-sdk pending 0.33 release (drop before merge) --- pnpm-lock.yaml | 505 +------------------------------------------- pnpm-workspace.yaml | 5 + 2 files changed, 12 insertions(+), 498 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66aba11..d73fb5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@prisma/compute-sdk': link:../project-compute/sdk + importers: .: @@ -24,8 +27,8 @@ importers: specifier: ^1.5.0 version: 1.5.0 '@prisma/compute-sdk': - specifier: 0.31.0 - version: 0.31.0(@prisma/management-api-sdk@1.46.0)(rollup@4.62.2) + specifier: link:../../../project-compute/sdk + version: link:../../../project-compute/sdk '@prisma/credentials-store': specifier: ^7.8.0 version: 7.8.0 @@ -520,10 +523,6 @@ packages: cpu: [x64] os: [win32] - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -537,11 +536,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@mapbox/node-pre-gyp@2.0.3': - resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} - engines: {node: '>=18'} - hasBin: true - '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -551,12 +545,6 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} - '@prisma/compute-sdk@0.31.0': - resolution: {integrity: sha512-V2av8c6qVrWqYug6qL82qBnyep+mFq0FP3MANSGalISoSUHwiQbXIWVoT7HnM6kaXYa6d1aU4Sb+1iR8GnQ6DA==} - engines: {node: '>=18.0.0'} - peerDependencies: - '@prisma/management-api-sdk': ^1.44.0 - '@prisma/credentials-store@7.8.0': resolution: {integrity: sha512-T9yp5uYSowV2ZRkBeCZrqWFP4REUlxd5WEYgOFJDjZBRRV+zx3VFCBf0zJI7Z3/PYFF9o3+ZzLwokQ9nY5EbqA==} @@ -664,15 +652,6 @@ packages: '@rolldown/pluginutils@1.0.0-rc.17': resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} - '@rollup/pluginutils@5.4.0': - resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - '@rollup/rollup-android-arm-eabi@4.62.2': resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} cpu: [arm] @@ -839,11 +818,6 @@ packages: '@types/node@22.19.19': resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} - '@vercel/nft@1.10.2': - resolution: {integrity: sha512-w+WyX5Ulmj4dtTZrxaulqrjaLZHSbnPzx75SJsTNYmotKsqn1JlLnDJa+lz5hn90HJofhl/2MAtw0mCrgM3qYw==} - engines: {node: '>=20'} - hasBin: true - '@vitest/expect@4.1.8': resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} @@ -873,24 +847,6 @@ packages: '@vitest/utils@4.1.8': resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} - abbrev@3.0.1: - resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} - engines: {node: ^18.17.0 || >=20.5.0} - - acorn-import-attributes@1.9.5: - resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} - peerDependencies: - acorn: ^8 - - acorn@8.17.0: - resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} - engines: {node: '>=0.4.0'} - hasBin: true - - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} @@ -911,75 +867,12 @@ packages: resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} engines: {node: '>=20.19.0'} - async-sema@3.1.1: - resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} - - b4a@1.8.1: - resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} - peerDependencies: - react-native-b4a: '*' - peerDependenciesMeta: - react-native-b4a: - optional: true - - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - bare-events@2.9.1: - resolution: {integrity: sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==} - peerDependencies: - bare-abort-controller: '*' - peerDependenciesMeta: - bare-abort-controller: - optional: true - - bare-fs@4.7.2: - resolution: {integrity: sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==} - engines: {bare: '>=1.16.0'} - peerDependencies: - bare-buffer: '*' - peerDependenciesMeta: - bare-buffer: - optional: true - - bare-os@3.9.1: - resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==} - engines: {bare: '>=1.14.0'} - - bare-path@3.0.1: - resolution: {integrity: sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==} - - bare-stream@2.13.3: - resolution: {integrity: sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==} - peerDependencies: - bare-abort-controller: '*' - bare-buffer: '*' - bare-events: '*' - peerDependenciesMeta: - bare-abort-controller: - optional: true - bare-buffer: - optional: true - bare-events: - optional: true - - bare-url@2.4.5: - resolution: {integrity: sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==} - better-result@2.9.2: resolution: {integrity: sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==} - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} - brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} - engines: {node: 18 || 20 || >=22} - bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -992,10 +885,6 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -1003,10 +892,6 @@ packages: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1014,15 +899,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - default-browser-id@5.0.1: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} @@ -1038,10 +914,6 @@ packages: defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - dotenv@17.4.2: resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} @@ -1072,15 +944,9 @@ packages: engines: {node: '>=18'} hasBin: true - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - events-universal@1.0.1: - resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} - execa@9.6.1: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} @@ -1089,9 +955,6 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - fast-string-truncated-width@3.0.3: resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} @@ -1114,9 +977,6 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1133,20 +993,9 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - hookable@6.1.1: resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - human-signals@8.0.1: resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} engines: {node: '>=18.18.0'} @@ -1197,54 +1046,17 @@ packages: engines: {node: '>=6'} hasBin: true - lru-cache@11.5.1: - resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} - engines: {node: 20 || >=22} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} magicast@0.5.3: resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@3.1.0: - resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} - engines: {node: '>= 18'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.15: resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-gyp-build@4.8.4: - resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} - hasBin: true - - nopt@8.1.0: - resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} - engines: {node: ^18.17.0 || >=20.5.0} - hasBin: true - npm-run-path@6.0.0: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} @@ -1278,10 +1090,6 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1311,10 +1119,6 @@ packages: quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -1356,11 +1160,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} - engines: {node: '>=10'} - hasBin: true - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1389,9 +1188,6 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} - streamx@2.28.0: - resolution: {integrity: sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==} - string-width@8.2.1: resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} @@ -1404,22 +1200,6 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - tar-stream@3.2.0: - resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} - - tar@7.5.16: - resolution: {integrity: sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==} - engines: {node: '>=18'} - - teex@1.0.1: - resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} - - text-decoder@1.2.7: - resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} - - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1435,9 +1215,6 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -1584,12 +1361,6 @@ packages: jsdom: optional: true - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1604,18 +1375,6 @@ packages: resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} engines: {node: '>=20'} - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -1628,10 +1387,6 @@ packages: resolution: {integrity: sha512-xrcqhWDvtZ7WLmt8G4f3hHy37iK7D2idtosRgkeiSPZEPmBShp0VfmRBLWAPC6zLF48APJ21yfea+RfQMF4/Aw==} engines: {node: '>= 4.0'} - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - yaml@2.9.0: resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} @@ -1897,10 +1652,6 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.3 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1915,19 +1666,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@mapbox/node-pre-gyp@2.0.3': - dependencies: - consola: 3.4.2 - detect-libc: 2.1.2 - https-proxy-agent: 7.0.6 - node-fetch: 2.7.0 - nopt: 8.1.0 - semver: 7.8.5 - tar: 7.5.16 - transitivePeerDependencies: - - encoding - - supports-color - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -1937,26 +1675,6 @@ snapshots: '@oxc-project/types@0.127.0': {} - '@prisma/compute-sdk@0.31.0(@prisma/management-api-sdk@1.46.0)(rollup@4.62.2)': - dependencies: - '@prisma/management-api-sdk': 1.46.0 - '@vercel/nft': 1.10.2(rollup@4.62.2) - better-result: 2.9.2 - jiti: 2.7.0 - magicast: 0.5.3 - tar-stream: 3.2.0 - tiny-invariant: 1.3.3 - ws: 8.21.0 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - bufferutil - - encoding - - react-native-b4a - - rollup - - supports-color - - utf-8-validate - '@prisma/credentials-store@7.8.0': dependencies: xdg-app-paths: 8.3.0 @@ -2020,14 +1738,6 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.17': {} - '@rollup/pluginutils@5.4.0(rollup@4.62.2)': - dependencies: - '@types/estree': 1.0.9 - estree-walker: 2.0.2 - picomatch: 4.0.4 - optionalDependencies: - rollup: 4.62.2 - '@rollup/rollup-android-arm-eabi@4.62.2': optional: true @@ -2129,25 +1839,6 @@ snapshots: dependencies: undici-types: 6.21.0 - '@vercel/nft@1.10.2(rollup@4.62.2)': - dependencies: - '@mapbox/node-pre-gyp': 2.0.3 - '@rollup/pluginutils': 5.4.0(rollup@4.62.2) - acorn: 8.17.0 - acorn-import-attributes: 1.9.5(acorn@8.17.0) - async-sema: 3.1.1 - bindings: 1.5.0 - estree-walker: 2.0.2 - glob: 13.0.6 - graceful-fs: 4.2.11 - node-gyp-build: 4.8.4 - picomatch: 4.0.4 - resolve-from: 5.0.0 - transitivePeerDependencies: - - encoding - - rollup - - supports-color - '@vitest/expect@4.1.8': dependencies: '@standard-schema/spec': 1.1.0 @@ -2189,16 +1880,6 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - abbrev@3.0.1: {} - - acorn-import-attributes@1.9.5(acorn@8.17.0): - dependencies: - acorn: 8.17.0 - - acorn@8.17.0: {} - - agent-base@7.1.4: {} - ansi-regex@6.2.2: {} ansi-styles@6.2.3: {} @@ -2213,57 +1894,10 @@ snapshots: estree-walker: 3.0.3 pathe: 2.0.3 - async-sema@3.1.1: {} - - b4a@1.8.1: {} - - balanced-match@4.0.4: {} - - bare-events@2.9.1: {} - - bare-fs@4.7.2: - dependencies: - bare-events: 2.9.1 - bare-path: 3.0.1 - bare-stream: 2.13.3(bare-events@2.9.1) - bare-url: 2.4.5 - fast-fifo: 1.3.2 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - bare-os@3.9.1: {} - - bare-path@3.0.1: - dependencies: - bare-os: 3.9.1 - - bare-stream@2.13.3(bare-events@2.9.1): - dependencies: - b4a: 1.8.1 - streamx: 2.28.0 - teex: 1.0.1 - optionalDependencies: - bare-events: 2.9.1 - transitivePeerDependencies: - - react-native-b4a - - bare-url@2.4.5: - dependencies: - bare-path: 3.0.1 - better-result@2.9.2: {} - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - birpc@4.0.0: {} - brace-expansion@5.0.6: - dependencies: - balanced-match: 4.0.4 - bundle-name@4.1.0: dependencies: run-applescript: 7.1.0 @@ -2272,14 +1906,10 @@ snapshots: chai@6.2.2: {} - chownr@3.0.0: {} - colorette@2.0.20: {} commander@14.0.3: {} - consola@3.4.2: {} - convert-source-map@2.0.0: {} cross-spawn@7.0.6: @@ -2288,10 +1918,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.4.3: - dependencies: - ms: 2.1.3 - default-browser-id@5.0.1: {} default-browser@5.5.0: @@ -2303,8 +1929,6 @@ snapshots: defu@6.1.7: {} - detect-libc@2.1.2: {} - dotenv@17.4.2: {} dts-resolver@2.1.3: {} @@ -2371,18 +1995,10 @@ snapshots: '@esbuild/win32-ia32': 0.28.0 '@esbuild/win32-x64': 0.28.0 - estree-walker@2.0.2: {} - estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 - events-universal@1.0.1: - dependencies: - bare-events: 2.9.1 - transitivePeerDependencies: - - bare-abort-controller - execa@9.6.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -2400,8 +2016,6 @@ snapshots: expect-type@1.3.0: {} - fast-fifo@1.3.2: {} - fast-string-truncated-width@3.0.3: {} fast-string-width@3.0.2: @@ -2420,8 +2034,6 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 - file-uri-to-path@1.0.0: {} - fsevents@2.3.3: optional: true @@ -2436,23 +2048,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - glob@13.0.6: - dependencies: - minimatch: 10.2.5 - minipass: 7.1.3 - path-scurry: 2.0.2 - - graceful-fs@4.2.11: {} - hookable@6.1.1: {} - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - human-signals@8.0.1: {} import-without-cache@0.3.3: {} @@ -2477,12 +2074,11 @@ snapshots: isexe@2.0.0: {} - jiti@2.7.0: {} + jiti@2.7.0: + optional: true jsesc@3.1.0: {} - lru-cache@11.5.1: {} - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2493,30 +2089,8 @@ snapshots: '@babel/types': 7.29.7 source-map-js: 1.2.1 - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.6 - - minipass@7.1.3: {} - - minizlib@3.1.0: - dependencies: - minipass: 7.1.3 - - ms@2.1.3: {} - nanoid@3.3.15: {} - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - node-gyp-build@4.8.4: {} - - nopt@8.1.0: - dependencies: - abbrev: 3.0.1 - npm-run-path@6.0.0: dependencies: path-key: 4.0.0 @@ -2549,11 +2123,6 @@ snapshots: path-key@4.0.0: {} - path-scurry@2.0.2: - dependencies: - lru-cache: 11.5.1 - minipass: 7.1.3 - pathe@2.0.3: {} picocolors@1.1.1: {} @@ -2576,8 +2145,6 @@ snapshots: quansync@1.0.0: {} - resolve-from@5.0.0: {} - resolve-pkg-maps@1.0.0: {} rolldown-plugin-dts@0.23.2(rolldown@1.0.0-rc.17)(typescript@6.0.3): @@ -2654,8 +2221,6 @@ snapshots: semver@7.8.1: {} - semver@7.8.5: {} - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -2674,15 +2239,6 @@ snapshots: std-env@4.1.0: {} - streamx@2.28.0: - dependencies: - events-universal: 1.0.1 - fast-fifo: 1.3.2 - text-decoder: 1.2.7 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - string-width@8.2.1: dependencies: get-east-asian-width: 1.6.0 @@ -2694,40 +2250,6 @@ snapshots: strip-final-newline@4.0.0: {} - tar-stream@3.2.0: - dependencies: - b4a: 1.8.1 - bare-fs: 4.7.2 - fast-fifo: 1.3.2 - streamx: 2.28.0 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a - - tar@7.5.16: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.3 - minizlib: 3.1.0 - yallist: 5.0.0 - - teex@1.0.1: - dependencies: - streamx: 2.28.0 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - text-decoder@1.2.7: - dependencies: - b4a: 1.8.1 - transitivePeerDependencies: - - react-native-b4a - - tiny-invariant@1.3.3: {} - tinybench@2.9.0: {} tinyexec@1.2.4: {} @@ -2739,8 +2261,6 @@ snapshots: tinyrainbow@3.1.0: {} - tr46@0.0.3: {} - tree-kill@1.2.2: {} tsdown@0.21.10(typescript@6.0.3): @@ -2836,13 +2356,6 @@ snapshots: transitivePeerDependencies: - msw - webidl-conversions@3.0.1: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -2858,8 +2371,6 @@ snapshots: string-width: 8.2.1 strip-ansi: 7.2.0 - ws@8.21.0: {} - wsl-utils@0.3.1: dependencies: is-wsl: 3.1.1 @@ -2877,8 +2388,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - yallist@5.0.0: {} - yaml@2.9.0: optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index df42253..d869377 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,3 +12,8 @@ allowBuilds: minimumReleaseAgeExclude: - '@prisma/compute-sdk' - '@prisma/management-api-sdk' + +# Local development link pending the @prisma/compute-sdk 0.33 release; drop +# before merge. +overrides: + '@prisma/compute-sdk': link:../project-compute/sdk From f011d2c80e2dea615bc1e6f58a96a95022a76867 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Tue, 7 Jul 2026 23:13:15 +0530 Subject: [PATCH 3/8] fix(cli): reject resolution flags during conversion; handle rollback double-fault Review findings: init --format ts with an existing prisma.compute.json silently ignored --framework/--entry/--name/--http-port/--region; they now fail as a usage error since conversion transports values and never re-resolves them. If the post-write JSON delete fails AND the rollback delete also fails, the raw fs error escaped while two config files were left coexisting; that now surfaces as structured INIT_CONVERT_INCOMPLETE naming both files. Adds conversion tests for full field preservation (env, build, root, project region) and multi-app configs including a null build command. --- docs/product/error-conventions.md | 2 + packages/cli/src/controllers/init.ts | 50 ++++++++- packages/cli/tests/init.test.ts | 145 +++++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 1 deletion(-) diff --git a/docs/product/error-conventions.md b/docs/product/error-conventions.md index 54a94fe..7939fd3 100644 --- a/docs/product/error-conventions.md +++ b/docs/product/error-conventions.md @@ -189,6 +189,7 @@ These codes are the minimum stable set for the MVP: - `FRAMEWORK_NOT_DETECTED` - `INIT_CONFIG_EXISTS` - `INIT_CONVERT_UNSUPPORTED` +- `INIT_CONVERT_INCOMPLETE` - `INIT_DETECTION_FAILED` - `DEPLOYMENT_NOT_FOUND` - `NO_DEPLOYMENTS` @@ -263,6 +264,7 @@ Recommended meanings: - `FRAMEWORK_NOT_DETECTED`: app deploy could not detect a supported Beta framework and no explicit framework/build type was provided - `INIT_CONFIG_EXISTS`: a compute config already exists in this directory or an ancestor; init never overwrites or merges - `INIT_CONVERT_UNSUPPORTED`: `init --format json` found an existing TypeScript config; TypeScript configs may contain logic, so converting them to JSON automatically would be lossy and the rewrite is manual +- `INIT_CONVERT_INCOMPLETE`: a conversion wrote prisma.compute.ts but could not delete prisma.compute.json, and rolling back the write also failed; both files exist and one must be deleted by hand - `INIT_DETECTION_FAILED`: no supported framework detected and no --framework passed; `meta.frameworks` lists the valid values - `DEPLOYMENT_NOT_FOUND`: requested deployment id does not exist - `NO_DEPLOYMENTS`: command resolved a branch or app but found no deployments diff --git a/packages/cli/src/controllers/init.ts b/packages/cli/src/controllers/init.ts index 262021f..c92b293 100644 --- a/packages/cli/src/controllers/init.ts +++ b/packages/cli/src/controllers/init.ts @@ -101,6 +101,7 @@ export async function runInit( solePath !== undefined && path.extname(solePath) === ".json"; // Conversion must be explicit: plain init refuses every existing config. if (soleIsJson && format.value === "typescript" && format.explicit) { + rejectConversionResolutionFlags(flags, formatCommand); return runInitConversion(context, flags, solePath, formatCommand); } if (solePath && !soleIsJson && format.value === "json") { @@ -460,6 +461,49 @@ function parseInitFormat( ); } +/** + * Conversion transports the existing config's values; it never re-resolves + * settings. Refusing resolution flags beats silently ignoring them. + */ +function rejectConversionResolutionFlags( + flags: InitFlags, + formatCommand: PrismaCliPackageCommandFormatter, +): void { + const passed = [ + flags.framework !== undefined ? "--framework" : null, + flags.entry !== undefined ? "--entry" : null, + flags.httpPort !== undefined ? "--http-port" : null, + flags.name !== undefined ? "--name" : null, + flags.region !== undefined ? "--region" : null, + ].filter((flag): flag is string => flag !== null); + if (passed.length === 0) { + return; + } + throw usageError( + `${passed.join(", ")} ${passed.length === 1 ? "does" : "do"} not apply when converting an existing config`, + `--format ts with an existing ${COMPUTE_CONFIG_JSON_FILENAME} converts it as-is; settings are transported, never re-resolved.`, + `Convert first, then edit ${COMPUTE_CONFIG_FILENAME} directly.`, + [formatCommand(["init", "--format", "ts"])], + "app", + ); +} + +function initConvertIncompleteError( + jsonConfigPath: string, + tsConfigPath: string, +): CliError { + return new CliError({ + code: "INIT_CONVERT_INCOMPLETE", + domain: "app", + summary: "Conversion left two config files behind", + why: `${path.basename(tsConfigPath)} was written but ${path.basename(jsonConfigPath)} could not be deleted, and rolling back the write also failed. Commands refuse to load a directory with two config files.`, + fix: `Delete one file by hand: keep ${path.basename(tsConfigPath)} to finish the conversion, or keep ${path.basename(jsonConfigPath)} to undo it.`, + exitCode: 1, + nextSteps: [], + meta: { jsonConfigPath, tsConfigPath }, + }); +} + function initConvertUnsupportedError(existingPath: string): CliError { return new CliError({ code: "INIT_CONVERT_UNSUPPORTED", @@ -529,7 +573,11 @@ async function runInitConversion( } catch (error) { // Two coexisting config files are a hard loader error, so a failed // delete rolls the write back and leaves the JSON config untouched. - await rm(tsConfigPath, { force: true }); + try { + await rm(tsConfigPath, { force: true }); + } catch { + throw initConvertIncompleteError(jsonConfigPath, tsConfigPath); + } throw error; } diff --git a/packages/cli/tests/init.test.ts b/packages/cli/tests/init.test.ts index 8157219..c59ea7b 100644 --- a/packages/cli/tests/init.test.ts +++ b/packages/cli/tests/init.test.ts @@ -715,6 +715,151 @@ describe("init config format", () => { }); }); + it("preserves env, build, root, and the project region through conversion", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writeFile( + path.join(cwd, "prisma.compute.json"), + `${JSON.stringify({ + region: "eu-central-1", + app: { + name: "web", + framework: "nextjs", + root: "apps/web", + httpPort: 3000, + env: { file: ".env.production", vars: { NODE_ENV: "production" } }, + build: { command: "pnpm build", outputDirectory: ".next" }, + }, + })}\n`, + ); + + const result = await executeCli({ + argv: ["init", "--format", "ts", "--json"], + cwd, + stateDir, + fixturePath, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(0); + expect(payload.result.converted).toBe(true); + + const config = await readConfig(cwd); + expect(config).toContain('region: "eu-central-1"'); + expect(config).toContain('root: "apps/web"'); + expect(config).toContain('file: ".env.production"'); + expect(config).toContain('NODE_ENV: "production"'); + expect(config).toContain('command: "pnpm build"'); + expect(config).toContain('outputDirectory: ".next"'); + + const loaded = await loadComputeConfig(cwd); + expect(loaded.isOk() && loaded.value?.targets[0]).toMatchObject({ + name: "web", + framework: "nextjs", + root: "apps/web", + region: "eu-central-1", + envInputs: [".env.production", "NODE_ENV=production"], + build: { + command: "pnpm build", + outputDirectory: ".next", + entrypoint: undefined, + }, + }); + }); + + it("converts a multi-app config, including a null build command", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writeFile( + path.join(cwd, "prisma.compute.json"), + `${JSON.stringify({ + apps: { + web: { + framework: "nextjs", + root: "apps/web", + build: { command: null }, + }, + api: { + framework: "hono", + root: "apps/api", + entry: "src/index.ts", + httpPort: 8080, + }, + }, + })}\n`, + ); + + const result = await executeCli({ + argv: ["init", "--format", "ts", "--json"], + cwd, + stateDir, + fixturePath, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(0); + expect(payload.result.converted).toBe(true); + // Multi-app configs carry no single app identity. + expect(payload.result.app).toBeNull(); + + const config = await readConfig(cwd); + expect(config).toContain("command: null"); + await expect(readJsonConfig(cwd)).rejects.toMatchObject({ + code: "ENOENT", + }); + + const loaded = await loadComputeConfig(cwd); + expect(loaded.isOk() && loaded.value?.kind).toBe("multi"); + expect(loaded.isOk() && loaded.value?.targets).toEqual([ + expect.objectContaining({ + key: "web", + framework: "nextjs", + build: expect.objectContaining({ command: null }), + }), + expect.objectContaining({ + key: "api", + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + }), + ]); + }); + + it("rejects resolution flags during conversion instead of ignoring them", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + const jsonSource = `${JSON.stringify({ + app: { name: "api", framework: "hono", httpPort: 8080 }, + })}\n`; + await writeFile(path.join(cwd, "prisma.compute.json"), jsonSource); + + const result = await executeCli({ + argv: [ + "init", + "--format", + "ts", + "--framework", + "nextjs", + "--http-port", + "3000", + "--json", + ], + cwd, + stateDir, + fixturePath, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(2); + expect(payload.error.code).toBe("USAGE_ERROR"); + expect(payload.error.summary).toContain("--framework"); + expect(payload.error.summary).toContain("--http-port"); + + // Nothing on disk changed: no TS config, JSON untouched. + await expect(readConfig(cwd)).rejects.toMatchObject({ code: "ENOENT" }); + expect(await readJsonConfig(cwd)).toBe(jsonSource); + }); + it("runs the types install step when converting with --install", async () => { const cwd = await createTempCwd(); const stateDir = path.join(cwd, ".state"); From 19cd6f310c5b95117729b8f0e25f2c74518cdae4 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Wed, 8 Jul 2026 13:04:23 +0530 Subject: [PATCH 4/8] fix(cli): stop emitting $schema in generated prisma.compute.json Follows the SDK default flip: the schema URL does not resolve yet, so generated configs no longer reference it. The loader still strips a hand-added $schema, and emission returns as opt-in once the schema is hosted. --- docs/product/command-spec.md | 2 +- packages/cli/src/controllers/init.ts | 2 +- packages/cli/tests/init.test.ts | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/product/command-spec.md b/docs/product/command-spec.md index 8daba52..7770ee0 100644 --- a/docs/product/command-spec.md +++ b/docs/product/command-spec.md @@ -395,7 +395,7 @@ Behavior: - init is the config formalizer: `app deploy` works with zero config, and init writes down what deploy would infer so the setup is committed, reviewable, and stable for teammates and CI - writing the config requires no auth and no network; linking is the only remote step - fails with `INIT_CONFIG_EXISTS` when a compute config already exists in the invocation directory or any ancestor up to the repository or workspace root; the error names the existing file, and init never overwrites or merges — editing a committed config is the user's editor's job -- `--format ` selects the config serialization; `ts` (alias `typescript`) is the default and writes `prisma.compute.ts`, `json` writes `prisma.compute.json` via the shared SDK serializer with a `$schema`-first document that validates in editors without any local dependency; both formats pin exactly the same resolved values, and the CLI's global `--json` output flag is unrelated to the config format +- `--format ` selects the config serialization; `ts` (alias `typescript`) is the default and writes `prisma.compute.ts`, `json` writes `prisma.compute.json` via the shared SDK serializer, a dependency-free static document (a `$schema` reference will be emitted once the schema is hosted; the loader already accepts and strips one); both formats pin exactly the same resolved values, and the CLI's global `--json` output flag is unrelated to the config format - with `--format json`, the types install step never runs: the JSON format exists to be dependency-free, so `--install` is a usage error and the result reports the step as skipped with no install hint - with `--format json` and the custom framework, init fails with a usage error: custom needs `build.outputDirectory` and `build.entrypoint`, which init does not collect, and strict JSON cannot carry the commented build stub the TypeScript format uses; the fix is the TypeScript format or a hand-written `build` object - graduation path: an explicit `--format ts` with an existing `prisma.compute.json` converts it in place; the JSON config is loaded and validated, the equivalent `prisma.compute.ts` wrapping the same object in `defineComputeConfig` is written next to it, the JSON file is deleted in the same operation, and the types install step runs as usual (skippable with `--no-install`); values are transported, never re-resolved, and the conversion is reported with `converted: true` diff --git a/packages/cli/src/controllers/init.ts b/packages/cli/src/controllers/init.ts index c92b293..9e1328a 100644 --- a/packages/cli/src/controllers/init.ts +++ b/packages/cli/src/controllers/init.ts @@ -201,7 +201,7 @@ export async function runInit( const warnings: string[] = []; // The JSON format exists to be dependency-free, so the types install step - // never runs for it; prisma.compute.json validates through $schema instead. + // never runs for it; validation happens when commands load the config. const types: InitTypesState = format.value === "json" ? { diff --git a/packages/cli/tests/init.test.ts b/packages/cli/tests/init.test.ts index c59ea7b..4b739d2 100644 --- a/packages/cli/tests/init.test.ts +++ b/packages/cli/tests/init.test.ts @@ -421,7 +421,7 @@ describe("init types install", () => { }); describe("init config format", () => { - it("writes a $schema-first prisma.compute.json with --format json that round-trips through the loader", async () => { + it("writes prisma.compute.json with --format json that round-trips through the loader", async () => { const cwd = await createTempCwd(); const stateDir = path.join(cwd, ".state"); await writePackageJson(cwd, { name: "billing-api" }); @@ -472,8 +472,9 @@ describe("init config format", () => { ]); const written = JSON.parse(await readJsonConfig(cwd)); - expect(Object.keys(written)[0]).toBe("$schema"); - expect(written.$schema).toBe(COMPUTE_CONFIG_JSON_SCHEMA_URL); + // No $schema until the schema URL actually resolves; the loader accepts + // it either way, so hand-added references keep working. + expect(written).not.toHaveProperty("$schema"); expect(written.app).toMatchObject({ name: "billing-api", framework: "hono", From 0c1dbf0af85cf29f2f6ded11647dd3fe6ca16074 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Wed, 8 Jul 2026 13:45:13 +0530 Subject: [PATCH 5/8] chore(cli): consume @prisma/compute-sdk 0.33.0 Drops the local link override now that 0.33.0 is published with the JSON config contract. --- packages/cli/package.json | 2 +- pnpm-lock.yaml | 498 +++++++++++++++++++++++++++++++++++++- pnpm-workspace.yaml | 5 - 3 files changed, 492 insertions(+), 13 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 3623423..4a84bbf 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -44,7 +44,7 @@ }, "dependencies": { "@clack/prompts": "^1.5.0", - "@prisma/compute-sdk": "0.31.0", + "@prisma/compute-sdk": "0.33.0", "@prisma/credentials-store": "^7.8.0", "@prisma/management-api-sdk": "^1.46.0", "better-result": "^2.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d73fb5a..c85a1d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,9 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - '@prisma/compute-sdk': link:../project-compute/sdk - importers: .: @@ -27,8 +24,8 @@ importers: specifier: ^1.5.0 version: 1.5.0 '@prisma/compute-sdk': - specifier: link:../../../project-compute/sdk - version: link:../../../project-compute/sdk + specifier: 0.33.0 + version: 0.33.0(@prisma/management-api-sdk@1.46.0)(rollup@4.62.2) '@prisma/credentials-store': specifier: ^7.8.0 version: 7.8.0 @@ -523,6 +520,10 @@ packages: cpu: [x64] os: [win32] + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -536,6 +537,11 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mapbox/node-pre-gyp@2.0.3': + resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} + engines: {node: '>=18'} + hasBin: true + '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -545,6 +551,12 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@prisma/compute-sdk@0.33.0': + resolution: {integrity: sha512-lk+WOfh0km/HbqcOsFtGYUTkQjTzESZcR8q7FPX1xtuyp7ccoFNTGKhDqM5VJw6GLeQgCeZlkdAZXBtHRfN7Vw==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@prisma/management-api-sdk': ^1.44.0 + '@prisma/credentials-store@7.8.0': resolution: {integrity: sha512-T9yp5uYSowV2ZRkBeCZrqWFP4REUlxd5WEYgOFJDjZBRRV+zx3VFCBf0zJI7Z3/PYFF9o3+ZzLwokQ9nY5EbqA==} @@ -652,6 +664,15 @@ packages: '@rolldown/pluginutils@1.0.0-rc.17': resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/rollup-android-arm-eabi@4.62.2': resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} cpu: [arm] @@ -818,6 +839,11 @@ packages: '@types/node@22.19.19': resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + '@vercel/nft@1.10.2': + resolution: {integrity: sha512-w+WyX5Ulmj4dtTZrxaulqrjaLZHSbnPzx75SJsTNYmotKsqn1JlLnDJa+lz5hn90HJofhl/2MAtw0mCrgM3qYw==} + engines: {node: '>=20'} + hasBin: true + '@vitest/expect@4.1.8': resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} @@ -847,6 +873,24 @@ packages: '@vitest/utils@4.1.8': resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + abbrev@3.0.1: + resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} + engines: {node: ^18.17.0 || >=20.5.0} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} @@ -867,12 +911,75 @@ packages: resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} engines: {node: '>=20.19.0'} + async-sema@3.1.1: + resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + bare-events@2.9.1: + resolution: {integrity: sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.7.3: + resolution: {integrity: sha512-xRgplks8SvcKkdlv2M6Z2LZmRsmqd+x0nXXGXeMEjwdibj1HSDrlnqBRLeYdMvsgCox7Bq0e+DHwfczOfsn6IA==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.9.3: + resolution: {integrity: sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.1: + resolution: {integrity: sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==} + + bare-stream@2.13.3: + resolution: {integrity: sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.4.5: + resolution: {integrity: sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==} + better-result@2.9.2: resolution: {integrity: sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==} + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + brace-expansion@5.0.7: + resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} + engines: {node: 18 || 20 || >=22} + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -885,6 +992,10 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -892,6 +1003,10 @@ packages: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -899,6 +1014,15 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + default-browser-id@5.0.1: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} @@ -914,6 +1038,10 @@ packages: defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + dotenv@17.4.2: resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} @@ -944,9 +1072,15 @@ packages: engines: {node: '>=18'} hasBin: true + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + execa@9.6.1: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} @@ -955,6 +1089,9 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-string-truncated-width@3.0.3: resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} @@ -977,6 +1114,9 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -993,9 +1133,20 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + hookable@6.1.1: resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-signals@8.0.1: resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} engines: {node: '>=18.18.0'} @@ -1046,17 +1197,54 @@ packages: engines: {node: '>=6'} hasBin: true + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} magicast@0.5.3: resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.15: resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + npm-run-path@6.0.0: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} @@ -1090,6 +1278,10 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1119,6 +1311,10 @@ packages: quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -1188,6 +1384,9 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + streamx@2.28.0: + resolution: {integrity: sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==} + string-width@8.2.1: resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} @@ -1200,6 +1399,22 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + tar-stream@3.2.0: + resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} + + tar@7.5.19: + resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==} + engines: {node: '>=18'} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1215,6 +1430,9 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -1361,6 +1579,12 @@ packages: jsdom: optional: true + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1375,6 +1599,18 @@ packages: resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} engines: {node: '>=20'} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -1387,6 +1623,10 @@ packages: resolution: {integrity: sha512-xrcqhWDvtZ7WLmt8G4f3hHy37iK7D2idtosRgkeiSPZEPmBShp0VfmRBLWAPC6zLF48APJ21yfea+RfQMF4/Aw==} engines: {node: '>= 4.0'} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml@2.9.0: resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} @@ -1652,6 +1892,10 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.3 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1666,6 +1910,19 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mapbox/node-pre-gyp@2.0.3': + dependencies: + consola: 3.4.2 + detect-libc: 2.1.2 + https-proxy-agent: 7.0.6 + node-fetch: 2.7.0 + nopt: 8.1.0 + semver: 7.8.1 + tar: 7.5.19 + transitivePeerDependencies: + - encoding + - supports-color + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -1675,6 +1932,26 @@ snapshots: '@oxc-project/types@0.127.0': {} + '@prisma/compute-sdk@0.33.0(@prisma/management-api-sdk@1.46.0)(rollup@4.62.2)': + dependencies: + '@prisma/management-api-sdk': 1.46.0 + '@vercel/nft': 1.10.2(rollup@4.62.2) + better-result: 2.9.2 + jiti: 2.7.0 + magicast: 0.5.3 + tar-stream: 3.2.0 + tiny-invariant: 1.3.3 + ws: 8.21.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - encoding + - react-native-b4a + - rollup + - supports-color + - utf-8-validate + '@prisma/credentials-store@7.8.0': dependencies: xdg-app-paths: 8.3.0 @@ -1738,6 +2015,14 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.17': {} + '@rollup/pluginutils@5.4.0(rollup@4.62.2)': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.62.2 + '@rollup/rollup-android-arm-eabi@4.62.2': optional: true @@ -1839,6 +2124,25 @@ snapshots: dependencies: undici-types: 6.21.0 + '@vercel/nft@1.10.2(rollup@4.62.2)': + dependencies: + '@mapbox/node-pre-gyp': 2.0.3 + '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + acorn: 8.17.0 + acorn-import-attributes: 1.9.5(acorn@8.17.0) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 13.0.6 + graceful-fs: 4.2.11 + node-gyp-build: 4.8.4 + picomatch: 4.0.4 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + '@vitest/expect@4.1.8': dependencies: '@standard-schema/spec': 1.1.0 @@ -1880,6 +2184,16 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + abbrev@3.0.1: {} + + acorn-import-attributes@1.9.5(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + + agent-base@7.1.4: {} + ansi-regex@6.2.2: {} ansi-styles@6.2.3: {} @@ -1894,10 +2208,57 @@ snapshots: estree-walker: 3.0.3 pathe: 2.0.3 + async-sema@3.1.1: {} + + b4a@1.8.1: {} + + balanced-match@4.0.4: {} + + bare-events@2.9.1: {} + + bare-fs@4.7.3: + dependencies: + bare-events: 2.9.1 + bare-path: 3.0.1 + bare-stream: 2.13.3(bare-events@2.9.1) + bare-url: 2.4.5 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-os@3.9.3: {} + + bare-path@3.0.1: + dependencies: + bare-os: 3.9.3 + + bare-stream@2.13.3(bare-events@2.9.1): + dependencies: + b4a: 1.8.1 + streamx: 2.28.0 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.9.1 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.4.5: + dependencies: + bare-path: 3.0.1 + better-result@2.9.2: {} + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + birpc@4.0.0: {} + brace-expansion@5.0.7: + dependencies: + balanced-match: 4.0.4 + bundle-name@4.1.0: dependencies: run-applescript: 7.1.0 @@ -1906,10 +2267,14 @@ snapshots: chai@6.2.2: {} + chownr@3.0.0: {} + colorette@2.0.20: {} commander@14.0.3: {} + consola@3.4.2: {} + convert-source-map@2.0.0: {} cross-spawn@7.0.6: @@ -1918,6 +2283,10 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + debug@4.4.3: + dependencies: + ms: 2.1.3 + default-browser-id@5.0.1: {} default-browser@5.5.0: @@ -1929,6 +2298,8 @@ snapshots: defu@6.1.7: {} + detect-libc@2.1.2: {} + dotenv@17.4.2: {} dts-resolver@2.1.3: {} @@ -1995,10 +2366,18 @@ snapshots: '@esbuild/win32-ia32': 0.28.0 '@esbuild/win32-x64': 0.28.0 + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 + events-universal@1.0.1: + dependencies: + bare-events: 2.9.1 + transitivePeerDependencies: + - bare-abort-controller + execa@9.6.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -2016,6 +2395,8 @@ snapshots: expect-type@1.3.0: {} + fast-fifo@1.3.2: {} + fast-string-truncated-width@3.0.3: {} fast-string-width@3.0.2: @@ -2034,6 +2415,8 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 + file-uri-to-path@1.0.0: {} + fsevents@2.3.3: optional: true @@ -2048,8 +2431,23 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + + graceful-fs@4.2.11: {} + hookable@6.1.1: {} + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + human-signals@8.0.1: {} import-without-cache@0.3.3: {} @@ -2074,11 +2472,12 @@ snapshots: isexe@2.0.0: {} - jiti@2.7.0: - optional: true + jiti@2.7.0: {} jsesc@3.1.0: {} + lru-cache@11.5.1: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2089,8 +2488,30 @@ snapshots: '@babel/types': 7.29.7 source-map-js: 1.2.1 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.7 + + minipass@7.1.3: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + + ms@2.1.3: {} + nanoid@3.3.15: {} + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-gyp-build@4.8.4: {} + + nopt@8.1.0: + dependencies: + abbrev: 3.0.1 + npm-run-path@6.0.0: dependencies: path-key: 4.0.0 @@ -2123,6 +2544,11 @@ snapshots: path-key@4.0.0: {} + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -2145,6 +2571,8 @@ snapshots: quansync@1.0.0: {} + resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} rolldown-plugin-dts@0.23.2(rolldown@1.0.0-rc.17)(typescript@6.0.3): @@ -2239,6 +2667,15 @@ snapshots: std-env@4.1.0: {} + streamx@2.28.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + string-width@8.2.1: dependencies: get-east-asian-width: 1.6.0 @@ -2250,6 +2687,40 @@ snapshots: strip-final-newline@4.0.0: {} + tar-stream@3.2.0: + dependencies: + b4a: 1.8.1 + bare-fs: 4.7.3 + fast-fifo: 1.3.2 + streamx: 2.28.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar@7.5.19: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + + teex@1.0.1: + dependencies: + streamx: 2.28.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.1 + transitivePeerDependencies: + - react-native-b4a + + tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} tinyexec@1.2.4: {} @@ -2261,6 +2732,8 @@ snapshots: tinyrainbow@3.1.0: {} + tr46@0.0.3: {} + tree-kill@1.2.2: {} tsdown@0.21.10(typescript@6.0.3): @@ -2356,6 +2829,13 @@ snapshots: transitivePeerDependencies: - msw + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -2371,6 +2851,8 @@ snapshots: string-width: 8.2.1 strip-ansi: 7.2.0 + ws@8.21.0: {} + wsl-utils@0.3.1: dependencies: is-wsl: 3.1.1 @@ -2388,6 +2870,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + yallist@5.0.0: {} + yaml@2.9.0: optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d869377..df42253 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,8 +12,3 @@ allowBuilds: minimumReleaseAgeExclude: - '@prisma/compute-sdk' - '@prisma/management-api-sdk' - -# Local development link pending the @prisma/compute-sdk 0.33 release; drop -# before merge. -overrides: - '@prisma/compute-sdk': link:../project-compute/sdk From e1b3ee177c0f6b9b1fc94b2b63a18839d26e8996 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Wed, 8 Jul 2026 14:01:47 +0530 Subject: [PATCH 6/8] fix(cli): honor link flags during conversion instead of ignoring them Review follow-up: conversion already ran the install step like fresh init but silently ignored --link/--no-link/--project. The side-effect steps now behave identically to fresh init (resolveInitLink handles the already-linked short-circuit and non-interactive skip), while the value-resolution flags stay rejected since conversion transports values. Spec updated to state both rules. --- docs/product/command-spec.md | 2 +- packages/cli/src/controllers/init.ts | 19 ++++++----- packages/cli/tests/init.test.ts | 49 ++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/docs/product/command-spec.md b/docs/product/command-spec.md index 7770ee0..692dda3 100644 --- a/docs/product/command-spec.md +++ b/docs/product/command-spec.md @@ -398,7 +398,7 @@ Behavior: - `--format ` selects the config serialization; `ts` (alias `typescript`) is the default and writes `prisma.compute.ts`, `json` writes `prisma.compute.json` via the shared SDK serializer, a dependency-free static document (a `$schema` reference will be emitted once the schema is hosted; the loader already accepts and strips one); both formats pin exactly the same resolved values, and the CLI's global `--json` output flag is unrelated to the config format - with `--format json`, the types install step never runs: the JSON format exists to be dependency-free, so `--install` is a usage error and the result reports the step as skipped with no install hint - with `--format json` and the custom framework, init fails with a usage error: custom needs `build.outputDirectory` and `build.entrypoint`, which init does not collect, and strict JSON cannot carry the commented build stub the TypeScript format uses; the fix is the TypeScript format or a hand-written `build` object -- graduation path: an explicit `--format ts` with an existing `prisma.compute.json` converts it in place; the JSON config is loaded and validated, the equivalent `prisma.compute.ts` wrapping the same object in `defineComputeConfig` is written next to it, the JSON file is deleted in the same operation, and the types install step runs as usual (skippable with `--no-install`); values are transported, never re-resolved, and the conversion is reported with `converted: true` +- graduation path: an explicit `--format ts` with an existing `prisma.compute.json` converts it in place; the JSON config is loaded and validated, the equivalent `prisma.compute.ts` wrapping the same object in `defineComputeConfig` is written next to it, the JSON file is deleted in the same operation, and the types install and link steps run as usual (`--install`/`--no-install`, `--link`/`--no-link`/`--project`); config values are transported, never re-resolved, so the value-resolution flags (`--framework`, `--entry`, `--http-port`, `--name`, `--region`) are usage errors during conversion, and the conversion is reported with `converted: true` - conversion is one-way and explicit: plain `init` with any existing config still fails with `INIT_CONFIG_EXISTS`, and `--format json` with an existing TypeScript config fails with `INIT_CONVERT_UNSUPPORTED` because TypeScript configs may contain logic that a static JSON file cannot express; a fully static config can be rewritten by hand - detects the framework from the same registry and signals `app deploy` uses; explicit `--framework` wins over detection - `--entry` sets the source entrypoint for entrypoint frameworks (Bun, Hono); `--http-port` overrides the framework default port; `--name` overrides the app name inferred from `package.json#name` or the directory name diff --git a/packages/cli/src/controllers/init.ts b/packages/cli/src/controllers/init.ts index 9e1328a..60239bb 100644 --- a/packages/cli/src/controllers/init.ts +++ b/packages/cli/src/controllers/init.ts @@ -588,15 +588,16 @@ async function runInitConversion( const types = await resolveInitTypes(context, flags, { onWarning: (message) => warnings.push(message), }); - // Conversion changes the config's serialization, not the directory's - // project binding, so it reports the link state without prompting. - const pin = await readLocalResolutionPin(cwd, signal); - const link: InitLinkState = - pin.isOk() && pin.value.kind === "present" - ? { status: "already-linked", project: null } - : { status: "skipped", project: null }; - - const unlinked = link.status !== "already-linked"; + // Conversion transports the config's values but its side-effect steps + // behave exactly like fresh init: install flags feed resolveInitTypes + // above, and link flags (--link/--no-link/--project) resolve here + // instead of being silently ignored. + const link = await resolveInitLink(context, flags, { + onWarning: (message) => warnings.push(message), + formatCommand, + }); + + const unlinked = link.status !== "already-linked" && link.status !== "linked"; const typesMissing = types.status !== "installed" && types.status !== "already-installed"; return { diff --git a/packages/cli/tests/init.test.ts b/packages/cli/tests/init.test.ts index 4b739d2..acd06e6 100644 --- a/packages/cli/tests/init.test.ts +++ b/packages/cli/tests/init.test.ts @@ -892,6 +892,55 @@ describe("init config format", () => { expect(payload.result.types.status).toBe("installed"); }); + it("honors link flags when converting, like fresh init", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await writePackageJson(cwd, { name: "api" }); + await login(cwd, stateDir); + const jsonConfig = `${JSON.stringify({ app: { framework: "hono", httpPort: 8080 } })}\n`; + await writeFile(path.join(cwd, "prisma.compute.json"), jsonConfig); + + const linked = await executeCli({ + argv: [ + "init", + "--format", + "ts", + "--project", + "proj_123", + "--no-install", + "--json", + ], + cwd, + stateDir, + fixturePath, + }); + const linkedPayload = JSON.parse(linked.stdout); + + expect(linked.exitCode).toBe(0); + expect(linkedPayload.result.converted).toBe(true); + expect(linkedPayload.result.link).toMatchObject({ + status: "linked", + project: { id: "proj_123" }, + }); + // Linked conversions do not suggest linking again. + expect(linkedPayload.nextSteps).not.toContainEqual( + expect.stringContaining("project link"), + ); + + // --no-link stays an explicit skip. + const cwd2 = await createTempCwd(); + await writeFile(path.join(cwd2, "prisma.compute.json"), jsonConfig); + const skipped = await executeCli({ + argv: ["init", "--format", "ts", "--no-link", "--no-install", "--json"], + cwd: cwd2, + stateDir, + fixturePath, + }); + const skippedPayload = JSON.parse(skipped.stdout); + expect(skipped.exitCode).toBe(0); + expect(skippedPayload.result.link.status).toBe("skipped"); + }); + it("prints the human conversion summary", async () => { const cwd = await createTempCwd(); const stateDir = path.join(cwd, ".state"); From 23b796df32a96365697bab5bf24764584f25fcff Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Thu, 9 Jul 2026 14:24:28 +0530 Subject: [PATCH 7/8] fix(cli): run conversion side effects in the config directory Review finding (Luan): conversion can discover prisma.compute.json in an ancestor directory, but the types install and project link still acted on the invocation directory, so --install could target the wrong package.json and --project could pin .prisma/local.json in a nested app dir while the config lives at the root. The side-effect steps now run against the discovered config directory via a context whose runtime cwd is the config's home; fresh init is unaffected since it writes at cwd. The reported configPath stays relative to the invocation directory. --- docs/product/command-spec.md | 2 +- packages/cli/src/controllers/init.ts | 24 +++++++--- packages/cli/tests/init.test.ts | 70 ++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/docs/product/command-spec.md b/docs/product/command-spec.md index 692dda3..5da9bfb 100644 --- a/docs/product/command-spec.md +++ b/docs/product/command-spec.md @@ -398,7 +398,7 @@ Behavior: - `--format ` selects the config serialization; `ts` (alias `typescript`) is the default and writes `prisma.compute.ts`, `json` writes `prisma.compute.json` via the shared SDK serializer, a dependency-free static document (a `$schema` reference will be emitted once the schema is hosted; the loader already accepts and strips one); both formats pin exactly the same resolved values, and the CLI's global `--json` output flag is unrelated to the config format - with `--format json`, the types install step never runs: the JSON format exists to be dependency-free, so `--install` is a usage error and the result reports the step as skipped with no install hint - with `--format json` and the custom framework, init fails with a usage error: custom needs `build.outputDirectory` and `build.entrypoint`, which init does not collect, and strict JSON cannot carry the commented build stub the TypeScript format uses; the fix is the TypeScript format or a hand-written `build` object -- graduation path: an explicit `--format ts` with an existing `prisma.compute.json` converts it in place; the JSON config is loaded and validated, the equivalent `prisma.compute.ts` wrapping the same object in `defineComputeConfig` is written next to it, the JSON file is deleted in the same operation, and the types install and link steps run as usual (`--install`/`--no-install`, `--link`/`--no-link`/`--project`); config values are transported, never re-resolved, so the value-resolution flags (`--framework`, `--entry`, `--http-port`, `--name`, `--region`) are usage errors during conversion, and the conversion is reported with `converted: true` +- graduation path: an explicit `--format ts` with an existing `prisma.compute.json` converts it in place; the JSON config is loaded and validated, the equivalent `prisma.compute.ts` wrapping the same object in `defineComputeConfig` is written next to it, the JSON file is deleted in the same operation, and the types install and link steps run as usual (`--install`/`--no-install`, `--link`/`--no-link`/`--project`) and act on the discovered config directory, not the invocation directory, so a conversion run from a nested app dir installs types and writes the project pin where the config lives; config values are transported, never re-resolved, so the value-resolution flags (`--framework`, `--entry`, `--http-port`, `--name`, `--region`) are usage errors during conversion, and the conversion is reported with `converted: true` - conversion is one-way and explicit: plain `init` with any existing config still fails with `INIT_CONFIG_EXISTS`, and `--format json` with an existing TypeScript config fails with `INIT_CONVERT_UNSUPPORTED` because TypeScript configs may contain logic that a static JSON file cannot express; a fully static config can be rewritten by hand - detects the framework from the same registry and signals `app deploy` uses; explicit `--framework` wins over detection - `--entry` sets the source entrypoint for entrypoint frameworks (Bun, Hono); `--http-port` overrides the framework default port; `--name` overrides the app name inferred from `package.json#name` or the directory name diff --git a/packages/cli/src/controllers/init.ts b/packages/cli/src/controllers/init.ts index 60239bb..80c4498 100644 --- a/packages/cli/src/controllers/init.ts +++ b/packages/cli/src/controllers/init.ts @@ -585,14 +585,18 @@ async function runInitConversion( renderInitSettingsPreview(context, settings); const warnings: string[] = []; - const types = await resolveInitTypes(context, flags, { + // Conversion transports the config's values but its side-effect steps + // behave exactly like fresh init: install flags feed resolveInitTypes, + // and link flags (--link/--no-link/--project) resolve via resolveInitLink + // instead of being silently ignored. Both act on the config's home, not + // the invocation directory: discovery may have found the config in an + // ancestor, and the types dependency and project pin belong where the + // config lives (fresh init has no such split; it writes at cwd). + const stepContext = withRuntimeCwd(context, loaded.configDir); + const types = await resolveInitTypes(stepContext, flags, { onWarning: (message) => warnings.push(message), }); - // Conversion transports the config's values but its side-effect steps - // behave exactly like fresh init: install flags feed resolveInitTypes - // above, and link flags (--link/--no-link/--project) resolve here - // instead of being silently ignored. - const link = await resolveInitLink(context, flags, { + const link = await resolveInitLink(stepContext, flags, { onWarning: (message) => warnings.push(message), formatCommand, }); @@ -621,6 +625,14 @@ async function runInitConversion( }; } +/** The same command context, acting from `cwd` instead of the invocation directory. */ +function withRuntimeCwd(context: CommandContext, cwd: string): CommandContext { + if (path.resolve(context.runtime.cwd) === path.resolve(cwd)) { + return context; + } + return { ...context, runtime: { ...context.runtime, cwd } }; +} + function stripJsonSchemaKey(parsed: unknown): unknown { if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { return parsed; diff --git a/packages/cli/tests/init.test.ts b/packages/cli/tests/init.test.ts index acd06e6..8e3725c 100644 --- a/packages/cli/tests/init.test.ts +++ b/packages/cli/tests/init.test.ts @@ -941,6 +941,76 @@ describe("init config format", () => { expect(skippedPayload.result.link.status).toBe("skipped"); }); + it("runs conversion side effects in the config directory, not the invocation directory", async () => { + const cwd = await createTempCwd(); + const stateDir = path.join(cwd, ".state"); + await mkdir(path.join(cwd, ".git"), { recursive: true }); + await writePackageJson(cwd, { name: "root-app" }); + await writeFile( + path.join(cwd, "prisma.compute.json"), + `${JSON.stringify({ app: { framework: "hono", httpPort: 8080 } })}\n`, + ); + const nested = path.join(cwd, "apps", "api"); + await mkdir(nested, { recursive: true }); + await writePackageJson(nested, { name: "api" }); + await login(cwd, stateDir); + + const result = await executeCli({ + argv: [ + "init", + "--format", + "ts", + "--install", + "--project", + "proj_123", + "--json", + ], + cwd: nested, + stateDir, + fixturePath, + env: { + // The fake installer records its working directory on disk. + PRISMA_CLI_INIT_INSTALL_COMMAND: JSON.stringify([ + "node", + "-e", + "require('fs').writeFileSync('install-cwd.txt','ok')", + ]), + }, + }); + const payload = JSON.parse(result.stdout); + + expect(result.exitCode).toBe(0); + expect(payload.result.configPath).toBe( + path.join("..", "..", "prisma.compute.ts"), + ); + expect(payload.result.types.status).toBe("installed"); + expect(payload.result.link).toMatchObject({ + status: "linked", + project: { id: "proj_123" }, + }); + + // The install ran in the config's directory, and the project pin was + // written there too; the nested invocation directory got neither. + await expect( + readFile(path.join(cwd, "install-cwd.txt"), "utf8"), + ).resolves.toBe("ok"); + await expect( + readFile(path.join(nested, "install-cwd.txt"), "utf8"), + ).rejects.toMatchObject({ code: "ENOENT" }); + await expect( + readFile(path.join(cwd, ".prisma/local.json"), "utf8"), + ).resolves.toContain("proj_123"); + await expect( + readFile(path.join(nested, ".prisma/local.json"), "utf8"), + ).rejects.toMatchObject({ code: "ENOENT" }); + + // The conversion itself happened at the config's home. + await expect(readConfig(cwd)).resolves.toContain("defineComputeConfig"); + await expect(readJsonConfig(cwd)).rejects.toMatchObject({ + code: "ENOENT", + }); + }); + it("prints the human conversion summary", async () => { const cwd = await createTempCwd(); const stateDir = path.join(cwd, ".state"); From 92637ce89feadc0ba20117d6a01d0e9594074c55 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Thu, 9 Jul 2026 15:53:11 +0530 Subject: [PATCH 8/8] docs(cli): clarify install usage-error vs skipped report; state conversion delete semantics Review nits: the --format json sentence conflated the --install usage error with the skipped-step report, and "deleted in the same operation" overstated atomicity the INIT_CONVERT_INCOMPLETE error exists to handle. --- docs/product/command-spec.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/product/command-spec.md b/docs/product/command-spec.md index 5da9bfb..5c04fef 100644 --- a/docs/product/command-spec.md +++ b/docs/product/command-spec.md @@ -396,9 +396,9 @@ Behavior: - writing the config requires no auth and no network; linking is the only remote step - fails with `INIT_CONFIG_EXISTS` when a compute config already exists in the invocation directory or any ancestor up to the repository or workspace root; the error names the existing file, and init never overwrites or merges — editing a committed config is the user's editor's job - `--format ` selects the config serialization; `ts` (alias `typescript`) is the default and writes `prisma.compute.ts`, `json` writes `prisma.compute.json` via the shared SDK serializer, a dependency-free static document (a `$schema` reference will be emitted once the schema is hosted; the loader already accepts and strips one); both formats pin exactly the same resolved values, and the CLI's global `--json` output flag is unrelated to the config format -- with `--format json`, the types install step never runs: the JSON format exists to be dependency-free, so `--install` is a usage error and the result reports the step as skipped with no install hint +- with `--format json`, the types install step never runs: the JSON format exists to be dependency-free, so `--install` is a usage error; without `--install`, the result reports the step as skipped with no install hint - with `--format json` and the custom framework, init fails with a usage error: custom needs `build.outputDirectory` and `build.entrypoint`, which init does not collect, and strict JSON cannot carry the commented build stub the TypeScript format uses; the fix is the TypeScript format or a hand-written `build` object -- graduation path: an explicit `--format ts` with an existing `prisma.compute.json` converts it in place; the JSON config is loaded and validated, the equivalent `prisma.compute.ts` wrapping the same object in `defineComputeConfig` is written next to it, the JSON file is deleted in the same operation, and the types install and link steps run as usual (`--install`/`--no-install`, `--link`/`--no-link`/`--project`) and act on the discovered config directory, not the invocation directory, so a conversion run from a nested app dir installs types and writes the project pin where the config lives; config values are transported, never re-resolved, so the value-resolution flags (`--framework`, `--entry`, `--http-port`, `--name`, `--region`) are usage errors during conversion, and the conversion is reported with `converted: true` +- graduation path: an explicit `--format ts` with an existing `prisma.compute.json` converts it in place; the JSON config is loaded and validated, the equivalent `prisma.compute.ts` wrapping the same object in `defineComputeConfig` is written next to it, the JSON file is then deleted (a failed delete rolls the write back, and a failed rollback fails with `INIT_CONVERT_INCOMPLETE` so two config files never coexist silently), and the types install and link steps run as usual (`--install`/`--no-install`, `--link`/`--no-link`/`--project`) and act on the discovered config directory, not the invocation directory, so a conversion run from a nested app dir installs types and writes the project pin where the config lives; config values are transported, never re-resolved, so the value-resolution flags (`--framework`, `--entry`, `--http-port`, `--name`, `--region`) are usage errors during conversion, and the conversion is reported with `converted: true` - conversion is one-way and explicit: plain `init` with any existing config still fails with `INIT_CONFIG_EXISTS`, and `--format json` with an existing TypeScript config fails with `INIT_CONVERT_UNSUPPORTED` because TypeScript configs may contain logic that a static JSON file cannot express; a fully static config can be rewritten by hand - detects the framework from the same registry and signals `app deploy` uses; explicit `--framework` wins over detection - `--entry` sets the source entrypoint for entrypoint frameworks (Bun, Hono); `--http-port` overrides the framework default port; `--name` overrides the app name inferred from `package.json#name` or the directory name