@prisma/cli 3.0.0-alpha.4 → 3.0.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli2.js CHANGED
@@ -1,20 +1,27 @@
1
+ import { CliError } from "./shell/errors.js";
2
+ import { createShellUi } from "./shell/ui.js";
3
+ import { writeHumanError, writeJsonError, writeJsonSuccess } from "./shell/output.js";
1
4
  import { attachCommandDescriptor } from "./shell/command-meta.js";
2
5
  import { addCompactGlobalFlags } from "./shell/global-flags.js";
3
- import { configureRuntimeCommand } from "./shell/runtime.js";
6
+ import { configureRuntimeCommand, createCommandContext } from "./shell/runtime.js";
4
7
  import "./shell/prompt.js";
5
8
  import { createAppCommand } from "./commands/app/index.js";
6
9
  import { createAuthCommand } from "./commands/auth/index.js";
7
10
  import { createBranchCommand } from "./commands/branch/index.js";
8
11
  import { createGitCommand } from "./commands/git/index.js";
9
12
  import { createProjectCommand } from "./commands/project/index.js";
13
+ import { getCliName, getCliVersion } from "./lib/version.js";
14
+ import { runVersion } from "./controllers/version.js";
15
+ import { createVersionCommand } from "./commands/version/index.js";
10
16
  import process from "node:process";
11
- import { Command, CommanderError } from "commander";
17
+ import { Command, CommanderError, Option } from "commander";
12
18
  //#region src/cli.ts
13
19
  async function runCli(options = {}) {
14
20
  const runtime = resolveRuntime(options);
15
21
  const program = createProgram(runtime);
16
22
  process.exitCode = 0;
17
23
  try {
24
+ if (runtime.argv.includes("--version")) return await handleVersionFlag(runtime);
18
25
  const bareHelpCommand = resolveBareHelpCommand(program, runtime.argv);
19
26
  if (bareHelpCommand) {
20
27
  runtime.stderr.write(bareHelpCommand.helpInformation());
@@ -34,7 +41,9 @@ async function runCli(options = {}) {
34
41
  function createProgram(runtime) {
35
42
  const program = attachCommandDescriptor(configureRuntimeCommand(new Command(), runtime), "root");
36
43
  addCompactGlobalFlags(program);
44
+ program.addOption(new Option("--version", "Print the CLI version and exit."));
37
45
  program.name("prisma").showSuggestionAfterError();
46
+ program.addCommand(createVersionCommand(runtime));
38
47
  program.addCommand(createAuthCommand(runtime));
39
48
  program.addCommand(createProjectCommand(runtime));
40
49
  program.addCommand(createGitCommand(runtime));
@@ -42,10 +51,53 @@ function createProgram(runtime) {
42
51
  program.addCommand(createAppCommand(runtime));
43
52
  return program;
44
53
  }
54
+ async function handleVersionFlag(runtime) {
55
+ const wantsJson = runtime.argv.includes("--json");
56
+ const output = {
57
+ stdout: runtime.stdout,
58
+ stderr: runtime.stderr
59
+ };
60
+ try {
61
+ if (wantsJson) {
62
+ const success = await runVersion(await createCommandContext(runtime, buildVersionFlagFlags(runtime)));
63
+ writeJsonSuccess(output, {
64
+ command: success.command,
65
+ result: { version: success.result.cli.version },
66
+ warnings: success.warnings,
67
+ nextSteps: success.nextSteps
68
+ });
69
+ return 0;
70
+ }
71
+ const versionLine = `${getCliName()} ${getCliVersion()}`;
72
+ runtime.stdout.write(`${versionLine}\n`);
73
+ return 0;
74
+ } catch (error) {
75
+ if (error instanceof CliError) {
76
+ if (wantsJson) writeJsonError(output, "version", error);
77
+ else writeHumanError(output, createShellUi(runtime, buildVersionFlagFlags(runtime)), error, { trace: false });
78
+ return error.exitCode;
79
+ }
80
+ throw error;
81
+ }
82
+ }
83
+ function buildVersionFlagFlags(runtime) {
84
+ return {
85
+ json: runtime.argv.includes("--json"),
86
+ quiet: false,
87
+ verbose: false,
88
+ trace: false,
89
+ yes: false,
90
+ interactive: void 0,
91
+ color: void 0
92
+ };
93
+ }
45
94
  function resolveBareHelpCommand(program, argv) {
46
95
  if (argv.length === 0) return program;
47
96
  if (argv.length !== 1) return null;
48
- return program.commands.find((command) => command.name() === argv[0]) ?? null;
97
+ const candidate = program.commands.find((command) => command.name() === argv[0]) ?? null;
98
+ if (!candidate) return null;
99
+ if (candidate.commands.length === 0) return null;
100
+ return candidate;
49
101
  }
50
102
  function resolveRuntime(options) {
51
103
  return {
@@ -0,0 +1,18 @@
1
+ import { attachCommandDescriptor } from "../../shell/command-meta.js";
2
+ import { addGlobalFlags } from "../../shell/global-flags.js";
3
+ import { configureRuntimeCommand } from "../../shell/runtime.js";
4
+ import { runCommand } from "../../shell/command-runner.js";
5
+ import { runVersion } from "../../controllers/version.js";
6
+ import { renderVersionSuccess } from "../../presenters/version.js";
7
+ import { Command } from "commander";
8
+ //#region src/commands/version/index.ts
9
+ function createVersionCommand(runtime) {
10
+ const command = attachCommandDescriptor(configureRuntimeCommand(new Command("version"), runtime), "version");
11
+ addGlobalFlags(command);
12
+ command.action(async (options) => {
13
+ await runCommand(runtime, "version", options, (context) => runVersion(context), { renderHuman: (context, descriptor, result) => renderVersionSuccess(context, descriptor, result) });
14
+ });
15
+ return command;
16
+ }
17
+ //#endregion
18
+ export { createVersionCommand };
@@ -829,7 +829,11 @@ async function resolveProjectContext(context, client, provider, explicitProject,
829
829
  listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
830
830
  createProject: options?.allowCreate ? async (name) => {
831
831
  const project = await provider.createProject({ name }).catch((error) => {
832
- throw deployFailedError("Failed to create project for first deploy", error, ["prisma-cli app deploy"]);
832
+ throw createProjectOnFirstDeployError({
833
+ error,
834
+ inferredName: name,
835
+ workspaceName: authState.workspace.name
836
+ });
833
837
  });
834
838
  return {
835
839
  id: project.id,
@@ -907,6 +911,52 @@ function deployFailedError(summary, error, nextSteps) {
907
911
  nextSteps
908
912
  });
909
913
  }
914
+ /**
915
+ * `app deploy` falls into "create a new project on first deploy" when no
916
+ * existing project matches the package.json name (or the cwd basename as a
917
+ * fallback). When the create call fails the user often doesn't realise the
918
+ * CLI was attempting to create a project at all — they thought the deploy
919
+ * would find an existing project. Surface that context, and recommend the
920
+ * explicit `--project` flag as the unambiguous way out.
921
+ */
922
+ function createProjectOnFirstDeployError(options) {
923
+ const { error, inferredName, workspaceName } = options;
924
+ const status = extractHttpStatus(error);
925
+ const errorMessage = error instanceof Error ? error.message : String(error);
926
+ const inferredContext = `No existing project matched the package.json name \`${inferredName}\`, so the CLI attempted to create one.`;
927
+ const nextSteps = ["prisma-cli project list", "prisma-cli app deploy --project <id-or-name>"];
928
+ if (status === 401 || status === 403) return new CliError({
929
+ code: "AUTH_FORBIDDEN",
930
+ domain: "auth",
931
+ summary: "Could not create a new project for this deploy",
932
+ why: `${inferredContext} The platform rejected the create (HTTP ${status}).`,
933
+ fix: `Pass --project <id-or-name> to deploy into an existing project, or grant the service token project-create permission on workspace \`${workspaceName}\`.`,
934
+ debug: formatDebugDetails(error),
935
+ exitCode: 1,
936
+ nextSteps
937
+ });
938
+ return new CliError({
939
+ code: "DEPLOY_FAILED",
940
+ domain: "app",
941
+ summary: "Could not create a new project for this deploy",
942
+ why: `${inferredContext} ${errorMessage}`.trim(),
943
+ fix: "Pass --project <id-or-name> to deploy into an existing project, or retry after addressing the platform error above.",
944
+ debug: formatDebugDetails(error),
945
+ exitCode: 1,
946
+ nextSteps
947
+ });
948
+ }
949
+ function extractHttpStatus(error) {
950
+ if (!error || typeof error !== "object") return null;
951
+ const candidate = error;
952
+ if (typeof candidate.statusCode === "number") return candidate.statusCode;
953
+ if (typeof candidate.status === "number") return candidate.status;
954
+ if (typeof candidate.message === "string") {
955
+ const match = /\(HTTP (\d{3})\)/.exec(candidate.message);
956
+ if (match) return Number.parseInt(match[1], 10);
957
+ }
958
+ return null;
959
+ }
910
960
  function noDeploymentsError(summary, why) {
911
961
  return new CliError({
912
962
  code: "NO_DEPLOYMENTS",
@@ -0,0 +1,12 @@
1
+ import { buildVersionResult } from "../lib/version.js";
2
+ //#region src/controllers/version.ts
3
+ async function runVersion(context) {
4
+ return {
5
+ command: "version",
6
+ result: buildVersionResult(context.runtime.env, context.runtime.argv),
7
+ warnings: [],
8
+ nextSteps: []
9
+ };
10
+ }
11
+ //#endregion
12
+ export { runVersion };
@@ -1,7 +1,9 @@
1
+ import { SERVICE_TOKEN_ENV_VAR } from "./client.js";
1
2
  import { FileTokenStorage } from "../../adapters/token-storage.js";
2
3
  import { requireComputeAuth } from "./guard.js";
3
4
  import { login } from "./login.js";
4
5
  //#region src/lib/auth/auth-ops.ts
6
+ const WORKSPACE_SUB_PREFIX = "workspace:";
5
7
  function decodeJwtPayload(token) {
6
8
  try {
7
9
  const payload = token.split(".")[1];
@@ -15,6 +17,13 @@ function emailFromClaims(claims) {
15
17
  const email = claims.email;
16
18
  return typeof email === "string" && email.trim().length > 0 ? email.trim() : null;
17
19
  }
20
+ function workspaceIdFromClaims(claims) {
21
+ const sub = claims.sub;
22
+ if (typeof sub !== "string") return null;
23
+ if (!sub.startsWith(WORKSPACE_SUB_PREFIX)) return null;
24
+ const id = sub.slice(10).trim();
25
+ return id.length > 0 ? id : null;
26
+ }
18
27
  async function performLogin(env) {
19
28
  await login({
20
29
  tokenStorage: new FileTokenStorage(env),
@@ -22,6 +31,12 @@ async function performLogin(env) {
22
31
  });
23
32
  }
24
33
  async function readAuthState(env) {
34
+ const rawServiceToken = env[SERVICE_TOKEN_ENV_VAR];
35
+ if (rawServiceToken !== void 0) {
36
+ const serviceToken = rawServiceToken.trim();
37
+ if (serviceToken.length === 0) throw new Error(`${SERVICE_TOKEN_ENV_VAR} is set but empty. Provide a valid token or unset the variable.`);
38
+ return readServiceTokenAuthState(serviceToken, env);
39
+ }
25
40
  const tokens = await new FileTokenStorage(env).getTokens();
26
41
  if (!tokens) return {
27
42
  authenticated: false,
@@ -29,15 +44,47 @@ async function readAuthState(env) {
29
44
  user: null,
30
45
  workspace: null
31
46
  };
32
- const email = emailFromClaims(decodeJwtPayload(tokens.accessToken));
47
+ const claims = decodeJwtPayload(tokens.accessToken);
48
+ return buildAuthState({
49
+ workspaceIdFromCredential: tokens.workspaceId,
50
+ claims,
51
+ env
52
+ });
53
+ }
54
+ async function readServiceTokenAuthState(token, env) {
55
+ const claims = decodeJwtPayload(token);
56
+ const workspaceId = workspaceIdFromClaims(claims);
57
+ if (!workspaceId) return {
58
+ authenticated: false,
59
+ provider: null,
60
+ user: null,
61
+ workspace: null
62
+ };
63
+ return buildAuthState({
64
+ workspaceIdFromCredential: workspaceId,
65
+ claims,
66
+ env
67
+ });
68
+ }
69
+ async function buildAuthState({ workspaceIdFromCredential, claims, env }) {
70
+ let workspaceId = workspaceIdFromCredential;
71
+ let workspaceName = workspaceIdFromCredential;
33
72
  const client = await requireComputeAuth(env);
34
- let workspaceId = tokens.workspaceId;
35
- let workspaceName = tokens.workspaceId;
36
73
  if (client) try {
37
- const { data } = await client.GET("/v1/workspaces/{id}", { params: { path: { id: tokens.workspaceId } } });
38
- if (data?.data?.id) workspaceId = data.data.id;
74
+ const { data, response } = await client.GET("/v1/workspaces/{id}", { params: { path: { id: workspaceIdFromCredential } } });
75
+ if (response?.status === 401) return {
76
+ authenticated: false,
77
+ provider: null,
78
+ user: null,
79
+ workspace: null
80
+ };
81
+ if (data?.data?.id) {
82
+ workspaceId = data.data.id;
83
+ workspaceName = data.data.id;
84
+ }
39
85
  if (data?.data?.name) workspaceName = data.data.name;
40
86
  } catch {}
87
+ const email = emailFromClaims(claims);
41
88
  return {
42
89
  authenticated: true,
43
90
  provider: null,
@@ -2,7 +2,7 @@ import os from "node:os";
2
2
  import path from "node:path";
3
3
  //#region src/lib/auth/client.ts
4
4
  const CLIENT_ID = "cmm3lndn701oo0uefvxzo0ivw";
5
- const SERVICE_TOKEN_ENV_VAR = "PRISMA_API_TOKEN";
5
+ const SERVICE_TOKEN_ENV_VAR = "PRISMA_SERVICE_TOKEN";
6
6
  const AUTH_FILE_ENV_VAR = "PRISMA_COMPUTE_AUTH_FILE";
7
7
  function getApiBaseUrl(env = process.env) {
8
8
  return env.PRISMA_MANAGEMENT_API_URL?.trim() || "https://api.prisma.io";
@@ -6,7 +6,7 @@ import { createManagementApiClient, createManagementApiSdk } from "@prisma/manag
6
6
  * Resolve authentication and return a ManagementApiClient.
7
7
  *
8
8
  * Priority:
9
- * 1. PRISMA_API_TOKEN env var → service token (CI / headless)
9
+ * 1. PRISMA_SERVICE_TOKEN env var → service token (CI / headless)
10
10
  * 2. Stored OAuth tokens → SDK with auto-refresh
11
11
  *
12
12
  * Returns null if not authenticated.
@@ -52,6 +52,9 @@ function projectNotFoundError(projectRef, workspace) {
52
52
  });
53
53
  }
54
54
  function projectAmbiguousError(projectRef, matches) {
55
+ const firstMatch = matches[0];
56
+ const nextSteps = ["prisma-cli project list"];
57
+ if (firstMatch) nextSteps.push(`prisma-cli app deploy --project ${firstMatch.id}`);
55
58
  return new CliError({
56
59
  code: "PROJECT_AMBIGUOUS",
57
60
  domain: "project",
@@ -63,7 +66,7 @@ function projectAmbiguousError(projectRef, matches) {
63
66
  name: project.name
64
67
  })) },
65
68
  exitCode: 1,
66
- nextSteps: ["prisma-cli project list"]
69
+ nextSteps
67
70
  });
68
71
  }
69
72
  function projectUnresolvedError() {
@@ -0,0 +1,55 @@
1
+ import { CliError } from "../shell/errors.js";
2
+ import { createRequire } from "node:module";
3
+ import process from "node:process";
4
+ //#region src/lib/version.ts
5
+ const requireFromHere = createRequire(import.meta.url);
6
+ function readPackageMetadata() {
7
+ try {
8
+ return requireFromHere("../../package.json");
9
+ } catch {
10
+ return {};
11
+ }
12
+ }
13
+ function getCliVersion() {
14
+ const pkg = readPackageMetadata();
15
+ if (!pkg.version) throw new CliError({
16
+ code: "VERSION_UNAVAILABLE",
17
+ domain: "cli",
18
+ summary: "CLI version metadata is missing from the installed package",
19
+ why: "The bundled package.json could not be read or did not contain a version field.",
20
+ fix: "Reinstall the CLI from the npm registry, or check your install path is intact.",
21
+ exitCode: 1
22
+ });
23
+ return pkg.version;
24
+ }
25
+ function getCliName() {
26
+ return "prisma-cli";
27
+ }
28
+ function detectInvocation(env, argv) {
29
+ if (env.npm_config_user_agent?.startsWith("bun")) return "bunx";
30
+ const normalizedExecPath = env.npm_execpath?.replace(/\\/g, "/").toLowerCase();
31
+ const normalizedUserAgent = env.npm_config_user_agent?.toLowerCase();
32
+ if (env.npm_lifecycle_event === "npx" || normalizedExecPath?.includes("/_npx/") || normalizedUserAgent?.includes("npx")) return "npx";
33
+ const entry = (argv[1] ?? "").replace(/\\/g, "/").toLowerCase();
34
+ if (entry.endsWith(".ts") || entry.includes("/tsx/")) return "dev";
35
+ if (entry.includes("/_npx/")) return "npx";
36
+ if (entry.includes("/.bun/")) return "bunx";
37
+ if (entry.includes("/node_modules/.bin/") || /\/prisma-cli(\.cmd|\.exe)?$/.test(entry)) return "global";
38
+ return "unknown";
39
+ }
40
+ function buildVersionResult(env, argv) {
41
+ return {
42
+ cli: {
43
+ name: getCliName(),
44
+ version: getCliVersion()
45
+ },
46
+ node: { version: process.version },
47
+ os: {
48
+ platform: process.platform,
49
+ arch: process.arch
50
+ },
51
+ invocation: detectInvocation(env, argv)
52
+ };
53
+ }
54
+ //#endregion
55
+ export { buildVersionResult, getCliName, getCliVersion };
@@ -0,0 +1,29 @@
1
+ import { renderShow } from "../output/patterns.js";
2
+ //#region src/presenters/version.ts
3
+ function renderVersionSuccess(context, descriptor, result) {
4
+ return renderShow({
5
+ title: "Showing CLI build and environment.",
6
+ descriptor,
7
+ fields: [
8
+ {
9
+ key: result.cli.name,
10
+ value: result.cli.version
11
+ },
12
+ {
13
+ key: "node",
14
+ value: result.node.version
15
+ },
16
+ {
17
+ key: "os",
18
+ value: `${result.os.platform} ${result.os.arch}`
19
+ },
20
+ {
21
+ key: "invocation",
22
+ value: result.invocation,
23
+ tone: result.invocation === "unknown" ? "dim" : "default"
24
+ }
25
+ ]
26
+ }, context.ui);
27
+ }
28
+ //#endregion
29
+ export { renderVersionSuccess };
@@ -8,6 +8,12 @@ const DESCRIPTORS = [
8
8
  examples: ["prisma-cli auth login", "prisma-cli app deploy"],
9
9
  longDescription: "Deploy your app with isolated infrastructure for every branch"
10
10
  },
11
+ {
12
+ id: "version",
13
+ path: ["prisma", "version"],
14
+ description: "Show CLI build and environment",
15
+ examples: ["prisma-cli version", "prisma-cli version --json"]
16
+ },
11
17
  {
12
18
  id: "auth",
13
19
  path: ["prisma", "auth"],
@@ -6,7 +6,8 @@ const COMPACT_GLOBAL_OPTION_FLAGS = [
6
6
  "-v, --verbose",
7
7
  "--trace",
8
8
  "--no-interactive",
9
- "-y, --yes"
9
+ "-y, --yes",
10
+ "--version"
10
11
  ];
11
12
  function addGlobalFlags(command) {
12
13
  return command.option("--json", "Emit structured JSON output.").option("-q, --quiet", "Reduce human-oriented output.").option("-v, --verbose", "Increase human-oriented output detail.").option("--trace", "Show deeper diagnostics for failures.").option("-y, --yes", "Accept supported confirmation prompts.").addOption(new Option("--interactive", "Force interactive behavior when prompts are supported.")).addOption(new Option("--no-interactive", "Disable interactive behavior and prompts.")).addOption(new Option("--color", "Force color output in supported terminals.")).addOption(new Option("--no-color", "Disable color output."));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/cli",
3
- "version": "3.0.0-alpha.4",
3
+ "version": "3.0.0-alpha.6",
4
4
  "description": "Preview of the unified Prisma CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,7 +36,7 @@
36
36
  "license": "Apache-2.0",
37
37
  "dependencies": {
38
38
  "@clack/prompts": "^1.2.0",
39
- "@prisma/compute-sdk": "^0.17.0",
39
+ "@prisma/compute-sdk": "^0.18.0",
40
40
  "c12": "4.0.0-beta.4",
41
41
  "@prisma/credentials-store": "^7.7.0",
42
42
  "@prisma/management-api-sdk": "^1.27.0",