@prisma/compute-cli 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AASA,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,WAAW,qDActB,CAAC;AAEH,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5C"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,WAAW,qDAgBtB,CAAC;AAEH,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5C"}
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import { deployCommand } from "./commands/deploy.js";
3
3
  import { envGroup } from "./commands/env.js";
4
4
  import { loginCommand } from "./commands/login.js";
5
5
  import { logoutCommand } from "./commands/logout.js";
6
+ import { logsCommand } from "./commands/logs.js";
6
7
  import { projectsGroup } from "./commands/projects.js";
7
8
  import { servicesGroup } from "./commands/services.js";
8
9
  import { versionsGroup } from "./commands/versions.js";
@@ -10,12 +11,13 @@ export { mergeEnvVars, parseEnvAssignments, parseEnvFromArgs, parseEnvVarsFromAr
10
11
  export const mainCommand = defineCommand({
11
12
  meta: {
12
13
  name: "compute",
13
- description: "Prisma Compute CLI",
14
+ description: "Prisma Compute CLI\n\nSet PRISMA_API_TOKEN to authenticate non-interactively (e.g. in CI). Otherwise, run `compute login` to start an interactive browser flow.",
14
15
  },
15
16
  subCommands: {
16
17
  login: loginCommand,
17
18
  logout: logoutCommand,
18
19
  deploy: deployCommand,
20
+ logs: logsCommand,
19
21
  env: envGroup,
20
22
  projects: projectsGroup,
21
23
  services: servicesGroup,
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;IACvC,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,oBAAoB;KAClC;IACD,WAAW,EAAE;QACX,KAAK,EAAE,YAAY;QACnB,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,aAAa;QACrB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,aAAa;QACvB,QAAQ,EAAE,aAAa;QACvB,QAAQ,EAAE,aAAa;KACxB;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,MAAM;IAC1B,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;AAC7B,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;IACvC,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,WAAW,EACT,iKAAiK;KACpK;IACD,WAAW,EAAE;QACX,KAAK,EAAE,YAAY;QACnB,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,aAAa;QACrB,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,aAAa;QACvB,QAAQ,EAAE,aAAa;QACvB,QAAQ,EAAE,aAAa;KACxB;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,MAAM;IAC1B,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;AAC7B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,YAAY,qDAcvB,CAAC"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,YAAY,qDAkBvB,CAAC"}
@@ -1,7 +1,10 @@
1
1
  import { defineCommand } from "citty";
2
2
  import { login as startLoginFlow } from "../lib/auth/login.js";
3
3
  export const loginCommand = defineCommand({
4
- meta: { name: "login", description: "Authenticate with Prisma" },
4
+ meta: {
5
+ name: "login",
6
+ description: "Authenticate with Prisma via interactive browser flow. Alternatively, set PRISMA_API_TOKEN for non-interactive auth (CI, scripts).",
7
+ },
5
8
  run: async () => {
6
9
  try {
7
10
  console.log("Opening browser for authentication...");
@@ -1 +1 @@
1
- {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE/D,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC;IACxC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,0BAA0B,EAAE;IAChE,GAAG,EAAE,KAAK,IAAI,EAAE;QACd,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;YACrD,MAAM,cAAc,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CACX,iBAAiB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC1E,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE/D,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC;IACxC,IAAI,EAAE;QACJ,IAAI,EAAE,OAAO;QACb,WAAW,EACT,oIAAoI;KACvI;IACD,GAAG,EAAE,KAAK,IAAI,EAAE;QACd,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;YACrD,MAAM,cAAc,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CACX,iBAAiB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC1E,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ export declare const logsCommand: import("citty").CommandDef<{
2
+ readonly id: {
3
+ readonly type: "positional";
4
+ readonly description: "Compute version ID";
5
+ readonly required: true;
6
+ };
7
+ readonly tail: {
8
+ readonly type: "string";
9
+ readonly description: "Number of recent lines to show (default: 100)";
10
+ };
11
+ readonly "from-start": {
12
+ readonly type: "boolean";
13
+ readonly default: false;
14
+ readonly description: "Start from the beginning of the log buffer instead of tailing";
15
+ };
16
+ readonly cursor: {
17
+ readonly type: "string";
18
+ readonly description: "Resume from a byte offset cursor (from a previous stream)";
19
+ };
20
+ readonly json: {
21
+ readonly type: "boolean";
22
+ readonly default: false;
23
+ readonly description: "Output each log/terminal record as a JSON line";
24
+ };
25
+ }>;
26
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;EA+FtB,CAAC"}
@@ -0,0 +1,141 @@
1
+ import { CancelledError, LogStreamError, streamLogs, } from "@prisma/compute-sdk";
2
+ import { defineCommand } from "citty";
3
+ import { requireAuthToken } from "../lib/auth/guard.js";
4
+ import { createCommandAbortSignal, handleError } from "./helpers.js";
5
+ export const logsCommand = defineCommand({
6
+ meta: {
7
+ name: "logs",
8
+ description: "Stream logs for a compute version",
9
+ },
10
+ args: {
11
+ id: {
12
+ type: "positional",
13
+ description: "Compute version ID",
14
+ required: true,
15
+ },
16
+ tail: {
17
+ type: "string",
18
+ description: "Number of recent lines to show (default: 100)",
19
+ },
20
+ "from-start": {
21
+ type: "boolean",
22
+ default: false,
23
+ description: "Start from the beginning of the log buffer instead of tailing",
24
+ },
25
+ cursor: {
26
+ type: "string",
27
+ description: "Resume from a byte offset cursor (from a previous stream)",
28
+ },
29
+ json: {
30
+ type: "boolean",
31
+ default: false,
32
+ description: "Output each log/terminal record as a JSON line",
33
+ },
34
+ },
35
+ run: async ({ args }) => {
36
+ const auth = await requireAuthToken();
37
+ if (!auth)
38
+ process.exit(1);
39
+ const { signal, cleanup } = createCommandAbortSignal();
40
+ try {
41
+ const versionId = args.id;
42
+ const tail = args.tail ? Number.parseInt(args.tail, 10) : undefined;
43
+ if (tail != null && (Number.isNaN(tail) || tail < 1)) {
44
+ console.error("--tail must be a positive integer");
45
+ process.exit(1);
46
+ }
47
+ const onRecord = args.json ? emitJson : emitText;
48
+ const result = await streamLogs({
49
+ baseUrl: auth.baseUrl,
50
+ token: auth.token,
51
+ versionId,
52
+ tail,
53
+ fromStart: args["from-start"],
54
+ cursor: args.cursor,
55
+ signal,
56
+ }, onRecord);
57
+ if (result.isErr()) {
58
+ const error = result.error;
59
+ if (CancelledError.is(error))
60
+ process.exit(130);
61
+ if (LogStreamError.is(error)) {
62
+ if (args.json) {
63
+ console.log(JSON.stringify({
64
+ ok: false,
65
+ error: {
66
+ statusCode: error.statusCode,
67
+ message: error.message,
68
+ },
69
+ }));
70
+ }
71
+ else {
72
+ console.error(error.message);
73
+ }
74
+ process.exit(1);
75
+ }
76
+ error;
77
+ process.exit(1);
78
+ }
79
+ const exitCode = handleStreamEnd(result.value, versionId, args.json);
80
+ if (exitCode !== 0)
81
+ process.exit(exitCode);
82
+ }
83
+ catch (error) {
84
+ if (signal.aborted)
85
+ process.exit(130);
86
+ process.exit(handleError(error));
87
+ }
88
+ finally {
89
+ cleanup();
90
+ }
91
+ },
92
+ });
93
+ function emitJson(record) {
94
+ console.log(JSON.stringify(record));
95
+ }
96
+ function emitText(record) {
97
+ if (record.type === "log") {
98
+ process.stdout.write(`${record.text}\n`);
99
+ }
100
+ }
101
+ function handleStreamEnd(result, versionId, json) {
102
+ const { terminal } = result;
103
+ if (!terminal) {
104
+ printResumeHint(result, versionId, json);
105
+ return 1;
106
+ }
107
+ if (!json) {
108
+ if (terminal.kind === "error") {
109
+ console.error(`Log stream ended: ${terminal.message} (${terminal.code})`);
110
+ }
111
+ else if (terminal.code === "superseded") {
112
+ console.error("Log stream superseded by another client.");
113
+ }
114
+ else if (terminal.code === "segment_time_limit") {
115
+ console.error("Log stream segment ended (10-minute limit).");
116
+ }
117
+ }
118
+ printResumeHint(result, versionId, json);
119
+ switch (terminal.code) {
120
+ case "vm_stopped":
121
+ case "superseded":
122
+ case "segment_time_limit":
123
+ return 0;
124
+ case "logs_unavailable":
125
+ case "upstream_error":
126
+ case "internal_error":
127
+ return 1;
128
+ default:
129
+ return terminal.kind === "error" ? 1 : 0;
130
+ }
131
+ }
132
+ function printResumeHint(result, versionId, json) {
133
+ if (json)
134
+ return;
135
+ const cursor = result.terminal?.cursor ??
136
+ (result.lastByteEnd != null ? String(result.lastByteEnd) : null);
137
+ if (!cursor)
138
+ return;
139
+ console.error(`\nTo resume: compute logs ${versionId} --cursor ${cursor}`);
140
+ }
141
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"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"}
@@ -9,4 +9,18 @@ import { type ManagementApiClient } from "@prisma/management-api-sdk";
9
9
  * Returns null if not authenticated (caller should print guidance and exit).
10
10
  */
11
11
  export declare function requireAuth(): Promise<ManagementApiClient | null>;
12
+ export type AuthToken = {
13
+ token: string;
14
+ baseUrl: string;
15
+ };
16
+ /**
17
+ * Resolve authentication and return a raw token + base URL.
18
+ *
19
+ * Reuses {@link requireAuth} for the OAuth path so token refresh logic
20
+ * isn't duplicated. For service tokens the SDK isn't needed.
21
+ *
22
+ * TODO: The management-api-sdk should expose a dedicated token refresh method
23
+ * so callers needing raw tokens don't have to trigger a dummy API call.
24
+ */
25
+ export declare function requireAuthToken(): Promise<AuthToken | null>;
12
26
  //# sourceMappingURL=guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAIpC;;;;;;;;GAQG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAiCvE"}
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAIpC;;;;;;;;GAQG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAiCvE;AAED,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CA6BlE"}
@@ -36,4 +36,40 @@ export async function requireAuth() {
36
36
  });
37
37
  return sdk.client;
38
38
  }
39
+ /**
40
+ * Resolve authentication and return a raw token + base URL.
41
+ *
42
+ * Reuses {@link requireAuth} for the OAuth path so token refresh logic
43
+ * isn't duplicated. For service tokens the SDK isn't needed.
44
+ *
45
+ * TODO: The management-api-sdk should expose a dedicated token refresh method
46
+ * so callers needing raw tokens don't have to trigger a dummy API call.
47
+ */
48
+ export async function requireAuthToken() {
49
+ const rawServiceToken = process.env[SERVICE_TOKEN_ENV_VAR];
50
+ if (rawServiceToken !== undefined) {
51
+ const serviceToken = rawServiceToken.trim();
52
+ if (serviceToken.length > 0) {
53
+ return { token: serviceToken, baseUrl: getApiBaseUrl() };
54
+ }
55
+ throw new Error(`${SERVICE_TOKEN_ENV_VAR} is set but empty. Provide a valid token or unset the variable.`);
56
+ }
57
+ const client = await requireAuth();
58
+ if (!client)
59
+ return null;
60
+ // Trigger a lightweight API call so the SDK can auto-refresh an expired
61
+ // token. On success the SDK writes refreshed tokens back into tokenStorage.
62
+ try {
63
+ await client.GET("/v1/projects", { params: { query: { limit: 1 } } });
64
+ }
65
+ catch {
66
+ // Refresh failed — fall through with the stored token and let the caller
67
+ // surface the auth error from the actual request.
68
+ }
69
+ const tokenStorage = new FileTokenStorage();
70
+ const tokens = await tokenStorage.getTokens();
71
+ if (!tokens)
72
+ return null;
73
+ return { token: tokens.accessToken, baseUrl: getApiBaseUrl() };
74
+ }
39
75
  //# sourceMappingURL=guard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../../src/lib/auth/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,sBAAsB,GAEvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC3D,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,OAAO,yBAAyB,CAAC;gBAC/B,OAAO,EAAE,aAAa,EAAE;gBACxB,KAAK,EAAE,YAAY;aACpB,CAAC,CAAC;QACL,CAAC;QACD,MAAM,IAAI,KAAK,CACb,GAAG,qBAAqB,iEAAiE,CAC1F,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,gBAAgB,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,SAAS,EAAE,CAAC;IAE9C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CACX,iEAAiE,CAClE,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,sBAAsB,CAAC;QACjC,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,kCAAkC;QAC/C,YAAY;QACZ,UAAU,EAAE,aAAa,EAAE;KAC5B,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC"}
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../../src/lib/auth/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,sBAAsB,GAEvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC3D,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,OAAO,yBAAyB,CAAC;gBAC/B,OAAO,EAAE,aAAa,EAAE;gBACxB,KAAK,EAAE,YAAY;aACpB,CAAC,CAAC;QACL,CAAC;QACD,MAAM,IAAI,KAAK,CACb,GAAG,qBAAqB,iEAAiE,CAC1F,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,gBAAgB,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,SAAS,EAAE,CAAC;IAE9C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CACX,iEAAiE,CAClE,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,sBAAsB,CAAC;QACjC,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,kCAAkC;QAC/C,YAAY;QACZ,UAAU,EAAE,aAAa,EAAE;KAC5B,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC;AAID;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC3D,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,CAAC;QAC3D,CAAC;QACD,MAAM,IAAI,KAAK,CACb,GAAG,qBAAqB,iEAAiE,CAC1F,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,WAAW,EAAE,CAAC;IACnC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,wEAAwE;IACxE,4EAA4E;IAC5E,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,kDAAkD;IACpD,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,gBAAgB,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,SAAS,EAAE,CAAC;IAC9C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,CAAC;AACjE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/compute-cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "repository": "prisma/project-compute",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@clack/prompts": "^1.1.0",
34
- "@prisma/compute-sdk": "0.12.0",
34
+ "@prisma/compute-sdk": "0.14.0",
35
35
  "@prisma/credentials-store": "^7.5.0",
36
36
  "@prisma/management-api-sdk": "^1.23.0",
37
37
  "citty": "^0.2.1",
package/src/cli.ts CHANGED
@@ -3,6 +3,7 @@ import { deployCommand } from "./commands/deploy.ts";
3
3
  import { envGroup } from "./commands/env.ts";
4
4
  import { loginCommand } from "./commands/login.ts";
5
5
  import { logoutCommand } from "./commands/logout.ts";
6
+ import { logsCommand } from "./commands/logs.ts";
6
7
  import { projectsGroup } from "./commands/projects.ts";
7
8
  import { servicesGroup } from "./commands/services.ts";
8
9
  import { versionsGroup } from "./commands/versions.ts";
@@ -19,12 +20,14 @@ export {
19
20
  export const mainCommand = defineCommand({
20
21
  meta: {
21
22
  name: "compute",
22
- description: "Prisma Compute CLI",
23
+ description:
24
+ "Prisma Compute CLI\n\nSet PRISMA_API_TOKEN to authenticate non-interactively (e.g. in CI). Otherwise, run `compute login` to start an interactive browser flow.",
23
25
  },
24
26
  subCommands: {
25
27
  login: loginCommand,
26
28
  logout: logoutCommand,
27
29
  deploy: deployCommand,
30
+ logs: logsCommand,
28
31
  env: envGroup,
29
32
  projects: projectsGroup,
30
33
  services: servicesGroup,
@@ -2,7 +2,11 @@ import { defineCommand } from "citty";
2
2
  import { login as startLoginFlow } from "../lib/auth/login.ts";
3
3
 
4
4
  export const loginCommand = defineCommand({
5
- meta: { name: "login", description: "Authenticate with Prisma" },
5
+ meta: {
6
+ name: "login",
7
+ description:
8
+ "Authenticate with Prisma via interactive browser flow. Alternatively, set PRISMA_API_TOKEN for non-interactive auth (CI, scripts).",
9
+ },
6
10
  run: async () => {
7
11
  try {
8
12
  console.log("Opening browser for authentication...");
@@ -0,0 +1,171 @@
1
+ import {
2
+ CancelledError,
3
+ LogStreamError,
4
+ type StreamRecord,
5
+ type StreamResult,
6
+ streamLogs,
7
+ } from "@prisma/compute-sdk";
8
+ import { defineCommand } from "citty";
9
+ import { requireAuthToken } from "../lib/auth/guard.ts";
10
+ import { createCommandAbortSignal, handleError } from "./helpers.ts";
11
+
12
+ export const logsCommand = defineCommand({
13
+ meta: {
14
+ name: "logs",
15
+ description: "Stream logs for a compute version",
16
+ },
17
+ args: {
18
+ id: {
19
+ type: "positional",
20
+ description: "Compute version ID",
21
+ required: true,
22
+ },
23
+ tail: {
24
+ type: "string",
25
+ description: "Number of recent lines to show (default: 100)",
26
+ },
27
+ "from-start": {
28
+ type: "boolean",
29
+ default: false,
30
+ description:
31
+ "Start from the beginning of the log buffer instead of tailing",
32
+ },
33
+ cursor: {
34
+ type: "string",
35
+ description: "Resume from a byte offset cursor (from a previous stream)",
36
+ },
37
+ json: {
38
+ type: "boolean",
39
+ default: false,
40
+ description: "Output each log/terminal record as a JSON line",
41
+ },
42
+ },
43
+ run: async ({ args }) => {
44
+ const auth = await requireAuthToken();
45
+ if (!auth) process.exit(1);
46
+
47
+ const { signal, cleanup } = createCommandAbortSignal();
48
+
49
+ try {
50
+ const versionId = args.id;
51
+
52
+ const tail = args.tail ? Number.parseInt(args.tail, 10) : undefined;
53
+ if (tail != null && (Number.isNaN(tail) || tail < 1)) {
54
+ console.error("--tail must be a positive integer");
55
+ process.exit(1);
56
+ }
57
+
58
+ const onRecord = args.json ? emitJson : emitText;
59
+
60
+ const result = await streamLogs(
61
+ {
62
+ baseUrl: auth.baseUrl,
63
+ token: auth.token,
64
+ versionId,
65
+ tail,
66
+ fromStart: args["from-start"],
67
+ cursor: args.cursor,
68
+ signal,
69
+ },
70
+ onRecord,
71
+ );
72
+
73
+ if (result.isErr()) {
74
+ const error = result.error;
75
+ if (CancelledError.is(error)) process.exit(130);
76
+
77
+ if (LogStreamError.is(error)) {
78
+ if (args.json) {
79
+ console.log(
80
+ JSON.stringify({
81
+ ok: false,
82
+ error: {
83
+ statusCode: error.statusCode,
84
+ message: error.message,
85
+ },
86
+ }),
87
+ );
88
+ } else {
89
+ console.error(error.message);
90
+ }
91
+ process.exit(1);
92
+ }
93
+
94
+ error satisfies never;
95
+ process.exit(1);
96
+ }
97
+
98
+ const exitCode = handleStreamEnd(result.value, versionId, args.json);
99
+ if (exitCode !== 0) process.exit(exitCode);
100
+ } catch (error) {
101
+ if (signal.aborted) process.exit(130);
102
+ process.exit(handleError(error));
103
+ } finally {
104
+ cleanup();
105
+ }
106
+ },
107
+ });
108
+
109
+ function emitJson(record: StreamRecord): void {
110
+ console.log(JSON.stringify(record));
111
+ }
112
+
113
+ function emitText(record: StreamRecord): void {
114
+ if (record.type === "log") {
115
+ process.stdout.write(`${record.text}\n`);
116
+ }
117
+ }
118
+
119
+ function handleStreamEnd(
120
+ result: StreamResult,
121
+ versionId: string,
122
+ json: boolean,
123
+ ): number {
124
+ const { terminal } = result;
125
+
126
+ if (!terminal) {
127
+ printResumeHint(result, versionId, json);
128
+ return 1;
129
+ }
130
+
131
+ if (!json) {
132
+ if (terminal.kind === "error") {
133
+ console.error(`Log stream ended: ${terminal.message} (${terminal.code})`);
134
+ } else if (terminal.code === "superseded") {
135
+ console.error("Log stream superseded by another client.");
136
+ } else if (terminal.code === "segment_time_limit") {
137
+ console.error("Log stream segment ended (10-minute limit).");
138
+ }
139
+ }
140
+
141
+ printResumeHint(result, versionId, json);
142
+
143
+ switch (terminal.code) {
144
+ case "vm_stopped":
145
+ case "superseded":
146
+ case "segment_time_limit":
147
+ return 0;
148
+ case "logs_unavailable":
149
+ case "upstream_error":
150
+ case "internal_error":
151
+ return 1;
152
+ default:
153
+ return terminal.kind === "error" ? 1 : 0;
154
+ }
155
+ }
156
+
157
+ function printResumeHint(
158
+ result: StreamResult,
159
+ versionId: string,
160
+ json: boolean,
161
+ ): void {
162
+ if (json) return;
163
+
164
+ const cursor =
165
+ result.terminal?.cursor ??
166
+ (result.lastByteEnd != null ? String(result.lastByteEnd) : null);
167
+
168
+ if (!cursor) return;
169
+
170
+ console.error(`\nTo resume: compute logs ${versionId} --cursor ${cursor}`);
171
+ }
@@ -49,3 +49,45 @@ export async function requireAuth(): Promise<ManagementApiClient | null> {
49
49
 
50
50
  return sdk.client;
51
51
  }
52
+
53
+ export type AuthToken = { token: string; baseUrl: string };
54
+
55
+ /**
56
+ * Resolve authentication and return a raw token + base URL.
57
+ *
58
+ * Reuses {@link requireAuth} for the OAuth path so token refresh logic
59
+ * isn't duplicated. For service tokens the SDK isn't needed.
60
+ *
61
+ * TODO: The management-api-sdk should expose a dedicated token refresh method
62
+ * so callers needing raw tokens don't have to trigger a dummy API call.
63
+ */
64
+ export async function requireAuthToken(): Promise<AuthToken | null> {
65
+ const rawServiceToken = process.env[SERVICE_TOKEN_ENV_VAR];
66
+ if (rawServiceToken !== undefined) {
67
+ const serviceToken = rawServiceToken.trim();
68
+ if (serviceToken.length > 0) {
69
+ return { token: serviceToken, baseUrl: getApiBaseUrl() };
70
+ }
71
+ throw new Error(
72
+ `${SERVICE_TOKEN_ENV_VAR} is set but empty. Provide a valid token or unset the variable.`,
73
+ );
74
+ }
75
+
76
+ const client = await requireAuth();
77
+ if (!client) return null;
78
+
79
+ // Trigger a lightweight API call so the SDK can auto-refresh an expired
80
+ // token. On success the SDK writes refreshed tokens back into tokenStorage.
81
+ try {
82
+ await client.GET("/v1/projects", { params: { query: { limit: 1 } } });
83
+ } catch {
84
+ // Refresh failed — fall through with the stored token and let the caller
85
+ // surface the auth error from the actual request.
86
+ }
87
+
88
+ const tokenStorage = new FileTokenStorage();
89
+ const tokens = await tokenStorage.getTokens();
90
+ if (!tokens) return null;
91
+
92
+ return { token: tokens.accessToken, baseUrl: getApiBaseUrl() };
93
+ }