@prisma/cli 3.0.0-dev.53.1 → 3.0.0-dev.56.1
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/adapters/git.js +8 -3
- package/dist/adapters/local-state.js +11 -3
- package/dist/adapters/mock-api.js +6 -2
- package/dist/adapters/token-storage.js +63 -22
- package/dist/cli.js +14 -3
- package/dist/cli2.js +1 -0
- package/dist/commands/app/index.js +4 -2
- package/dist/controllers/app-env.js +78 -46
- package/dist/controllers/app.js +136 -76
- package/dist/controllers/auth.js +8 -8
- package/dist/controllers/project.js +107 -67
- package/dist/lib/app/bun-project.js +12 -5
- package/dist/lib/app/local-dev.js +34 -18
- package/dist/lib/app/preview-build.js +90 -62
- package/dist/lib/app/preview-provider.js +143 -54
- package/dist/lib/app/production-deploy-gate.js +160 -0
- package/dist/lib/auth/auth-ops.js +30 -21
- package/dist/lib/auth/guard.js +3 -2
- package/dist/lib/auth/login.js +31 -17
- package/dist/lib/project/interactive-setup.js +1 -1
- package/dist/lib/project/local-pin.js +27 -8
- package/dist/lib/project/resolution.js +14 -8
- package/dist/lib/project/setup.js +2 -2
- package/dist/shell/command-meta.js +1 -0
- package/dist/shell/command-runner.js +9 -4
- package/dist/shell/errors.js +12 -1
- package/dist/shell/runtime.js +2 -2
- package/package.json +1 -1
|
@@ -19,7 +19,10 @@ function createPreviewAppProvider(client, options) {
|
|
|
19
19
|
const sdk = new ComputeClient(client);
|
|
20
20
|
return {
|
|
21
21
|
async createProject(options) {
|
|
22
|
-
const projectResult = await sdk.createProject({
|
|
22
|
+
const projectResult = await sdk.createProject({
|
|
23
|
+
name: options.name,
|
|
24
|
+
signal: options.signal
|
|
25
|
+
});
|
|
23
26
|
if (projectResult.isErr()) throw new Error(projectResult.error.message);
|
|
24
27
|
return {
|
|
25
28
|
id: projectResult.value.id,
|
|
@@ -29,17 +32,34 @@ function createPreviewAppProvider(client, options) {
|
|
|
29
32
|
async listApps(projectId, options) {
|
|
30
33
|
return listComputeServices(client, {
|
|
31
34
|
projectId,
|
|
32
|
-
branchGitName: options?.branchName
|
|
35
|
+
branchGitName: options?.branchName,
|
|
36
|
+
signal: options?.signal
|
|
33
37
|
});
|
|
34
38
|
},
|
|
35
|
-
async
|
|
36
|
-
const
|
|
39
|
+
async resolveBranch(projectId, options) {
|
|
40
|
+
const branch = await resolveOrCreateBranch(client, {
|
|
41
|
+
projectId,
|
|
42
|
+
gitName: options.branchName,
|
|
43
|
+
signal: options.signal
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
id: branch.id,
|
|
47
|
+
name: branch.gitName,
|
|
48
|
+
role: branch.role
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
async removeApp(appId, options) {
|
|
52
|
+
const appResult = await sdk.showService({
|
|
53
|
+
serviceId: appId,
|
|
54
|
+
signal: options?.signal
|
|
55
|
+
});
|
|
37
56
|
if (appResult.isErr()) throw new Error(appResult.error.message);
|
|
38
57
|
const destroyResult = await sdk.destroyService({
|
|
39
58
|
serviceId: appId,
|
|
40
59
|
keepService: false,
|
|
41
60
|
timeoutSeconds: 120,
|
|
42
|
-
pollIntervalMs: 2e3
|
|
61
|
+
pollIntervalMs: 2e3,
|
|
62
|
+
signal: options?.signal
|
|
43
63
|
});
|
|
44
64
|
if (destroyResult.isErr()) throw new Error(destroyResult.error.message);
|
|
45
65
|
return {
|
|
@@ -47,17 +67,18 @@ function createPreviewAppProvider(client, options) {
|
|
|
47
67
|
name: appResult.value.name
|
|
48
68
|
};
|
|
49
69
|
},
|
|
50
|
-
async listDomains(appId) {
|
|
51
|
-
return listComputeServiceDomains(client, appId);
|
|
70
|
+
async listDomains(appId, options) {
|
|
71
|
+
return listComputeServiceDomains(client, appId, options?.signal);
|
|
52
72
|
},
|
|
53
73
|
async addDomain(options) {
|
|
54
74
|
const result = await client.POST("/v1/compute-services/{computeServiceId}/domains", {
|
|
55
75
|
params: { path: { computeServiceId: options.appId } },
|
|
56
|
-
body: { hostname: options.hostname }
|
|
76
|
+
body: { hostname: options.hostname },
|
|
77
|
+
signal: options.signal
|
|
57
78
|
});
|
|
58
79
|
if (result.error || !result.data) {
|
|
59
80
|
if (result.response.status === 409) {
|
|
60
|
-
const existing = (await listComputeServiceDomains(client, options.appId)).find((domain) => sameHostname(domain.hostname, options.hostname));
|
|
81
|
+
const existing = (await listComputeServiceDomains(client, options.appId, options.signal)).find((domain) => sameHostname(domain.hostname, options.hostname));
|
|
61
82
|
if (existing) return {
|
|
62
83
|
domain: existing,
|
|
63
84
|
existing: true
|
|
@@ -70,17 +91,26 @@ function createPreviewAppProvider(client, options) {
|
|
|
70
91
|
existing: false
|
|
71
92
|
};
|
|
72
93
|
},
|
|
73
|
-
async showDomain(domainId) {
|
|
74
|
-
const result = await client.GET("/v1/domains/{domainId}", {
|
|
94
|
+
async showDomain(domainId, options) {
|
|
95
|
+
const result = await client.GET("/v1/domains/{domainId}", {
|
|
96
|
+
params: { path: { domainId } },
|
|
97
|
+
signal: options?.signal
|
|
98
|
+
});
|
|
75
99
|
if (result.error || !result.data) throw domainApiCallError("Failed to show custom domain", result.response, result.error);
|
|
76
100
|
return normalizeDomainRecord(result.data.data);
|
|
77
101
|
},
|
|
78
|
-
async removeDomain(domainId) {
|
|
79
|
-
const result = await client.DELETE("/v1/domains/{domainId}", {
|
|
102
|
+
async removeDomain(domainId, options) {
|
|
103
|
+
const result = await client.DELETE("/v1/domains/{domainId}", {
|
|
104
|
+
params: { path: { domainId } },
|
|
105
|
+
signal: options?.signal
|
|
106
|
+
});
|
|
80
107
|
if (result.error) throw domainApiCallError("Failed to remove custom domain", result.response, result.error);
|
|
81
108
|
},
|
|
82
|
-
async retryDomain(domainId) {
|
|
83
|
-
const result = await client.POST("/v1/domains/{domainId}/retry", {
|
|
109
|
+
async retryDomain(domainId, options) {
|
|
110
|
+
const result = await client.POST("/v1/domains/{domainId}/retry", {
|
|
111
|
+
params: { path: { domainId } },
|
|
112
|
+
signal: options?.signal
|
|
113
|
+
});
|
|
84
114
|
if (result.error || !result.data) throw domainApiCallError("Failed to retry custom domain", result.response, result.error);
|
|
85
115
|
return normalizeDomainRecord(result.data.data);
|
|
86
116
|
},
|
|
@@ -90,6 +120,7 @@ function createPreviewAppProvider(client, options) {
|
|
|
90
120
|
versionId: options.deploymentId,
|
|
91
121
|
timeoutSeconds: 120,
|
|
92
122
|
pollIntervalMs: 2e3,
|
|
123
|
+
signal: options.signal,
|
|
93
124
|
progress: options.progress
|
|
94
125
|
});
|
|
95
126
|
if (promoteResult.isErr()) throw new Error(promoteResult.error.message);
|
|
@@ -103,7 +134,8 @@ function createPreviewAppProvider(client, options) {
|
|
|
103
134
|
projectId: options.projectId,
|
|
104
135
|
branchName: options.branchName,
|
|
105
136
|
appName: options.appName,
|
|
106
|
-
region: options.region
|
|
137
|
+
region: options.region,
|
|
138
|
+
signal: options.signal
|
|
107
139
|
}) : {
|
|
108
140
|
appId: void 0,
|
|
109
141
|
appName: options.appName,
|
|
@@ -113,7 +145,8 @@ function createPreviewAppProvider(client, options) {
|
|
|
113
145
|
strategy: new PreviewBuildStrategy({
|
|
114
146
|
appPath: path.resolve(options.cwd),
|
|
115
147
|
entrypoint: options.entrypoint,
|
|
116
|
-
buildType: options.buildType
|
|
148
|
+
buildType: options.buildType,
|
|
149
|
+
signal: options.signal
|
|
117
150
|
}),
|
|
118
151
|
projectId: options.projectId,
|
|
119
152
|
serviceId: resolvedApp.appId,
|
|
@@ -124,6 +157,7 @@ function createPreviewAppProvider(client, options) {
|
|
|
124
157
|
timeoutSeconds: 120,
|
|
125
158
|
pollIntervalMs: 2e3,
|
|
126
159
|
interaction: options.interaction,
|
|
160
|
+
signal: options.signal,
|
|
127
161
|
progress: options.progress
|
|
128
162
|
});
|
|
129
163
|
if (deployResult.isErr()) throw new Error(deployResult.error.message);
|
|
@@ -150,6 +184,7 @@ function createPreviewAppProvider(client, options) {
|
|
|
150
184
|
envVars: options.envVars,
|
|
151
185
|
timeoutSeconds: 120,
|
|
152
186
|
pollIntervalMs: 2e3,
|
|
187
|
+
signal: options.signal,
|
|
153
188
|
progress: options.progress
|
|
154
189
|
});
|
|
155
190
|
if (updateResult.isErr()) throw new Error(updateResult.error.message);
|
|
@@ -158,10 +193,17 @@ function createPreviewAppProvider(client, options) {
|
|
|
158
193
|
versionId: updateResult.value.versionId,
|
|
159
194
|
timeoutSeconds: 120,
|
|
160
195
|
pollIntervalMs: 2e3,
|
|
196
|
+
signal: options.signal,
|
|
161
197
|
progress: options.promoteProgress
|
|
162
198
|
});
|
|
163
199
|
if (promoteResult.isErr()) throw new Error(promoteResult.error.message);
|
|
164
|
-
const [serviceResult, versionResult] = await Promise.all([sdk.showService({
|
|
200
|
+
const [serviceResult, versionResult] = await Promise.all([sdk.showService({
|
|
201
|
+
serviceId: options.appId,
|
|
202
|
+
signal: options.signal
|
|
203
|
+
}), sdk.showVersion({
|
|
204
|
+
versionId: updateResult.value.versionId,
|
|
205
|
+
signal: options.signal
|
|
206
|
+
})]);
|
|
165
207
|
if (serviceResult.isErr()) throw new Error(serviceResult.error.message);
|
|
166
208
|
if (versionResult.isErr()) throw new Error(versionResult.error.message);
|
|
167
209
|
return {
|
|
@@ -184,7 +226,13 @@ function createPreviewAppProvider(client, options) {
|
|
|
184
226
|
};
|
|
185
227
|
},
|
|
186
228
|
async listAppEnvNames(options) {
|
|
187
|
-
const [serviceResult, versionResult] = await Promise.all([sdk.showService({
|
|
229
|
+
const [serviceResult, versionResult] = await Promise.all([sdk.showService({
|
|
230
|
+
serviceId: options.appId,
|
|
231
|
+
signal: options.signal
|
|
232
|
+
}), sdk.showVersion({
|
|
233
|
+
versionId: options.deploymentId,
|
|
234
|
+
signal: options.signal
|
|
235
|
+
})]);
|
|
188
236
|
if (serviceResult.isErr()) throw new Error(serviceResult.error.message);
|
|
189
237
|
if (versionResult.isErr()) throw new Error(versionResult.error.message);
|
|
190
238
|
return {
|
|
@@ -206,8 +254,14 @@ function createPreviewAppProvider(client, options) {
|
|
|
206
254
|
variables: envVarNames(versionResult.value.envVars)
|
|
207
255
|
};
|
|
208
256
|
},
|
|
209
|
-
async listDeployments(appId) {
|
|
210
|
-
const [appResult, versionsResult] = await Promise.all([sdk.showService({
|
|
257
|
+
async listDeployments(appId, options) {
|
|
258
|
+
const [appResult, versionsResult] = await Promise.all([sdk.showService({
|
|
259
|
+
serviceId: appId,
|
|
260
|
+
signal: options?.signal
|
|
261
|
+
}), sdk.listVersions({
|
|
262
|
+
serviceId: appId,
|
|
263
|
+
signal: options?.signal
|
|
264
|
+
})]);
|
|
211
265
|
if (appResult.isErr()) throw new Error(appResult.error.message);
|
|
212
266
|
if (versionsResult.isErr()) throw new Error(versionsResult.error.message);
|
|
213
267
|
return {
|
|
@@ -230,14 +284,17 @@ function createPreviewAppProvider(client, options) {
|
|
|
230
284
|
}))
|
|
231
285
|
};
|
|
232
286
|
},
|
|
233
|
-
async showDeployment(deploymentId) {
|
|
234
|
-
const deploymentResult = await sdk.showVersion({
|
|
287
|
+
async showDeployment(deploymentId, options) {
|
|
288
|
+
const deploymentResult = await sdk.showVersion({
|
|
289
|
+
versionId: deploymentId,
|
|
290
|
+
signal: options?.signal
|
|
291
|
+
});
|
|
235
292
|
if (deploymentResult.isErr()) {
|
|
236
293
|
if (ApiError.is(deploymentResult.error) && deploymentResult.error.statusCode === 404) return null;
|
|
237
294
|
throw new Error(deploymentResult.error.message);
|
|
238
295
|
}
|
|
239
296
|
return {
|
|
240
|
-
app: await findAppForDeployment(sdk, deploymentId),
|
|
297
|
+
app: await findAppForDeployment(sdk, deploymentId, options?.signal),
|
|
241
298
|
deployment: {
|
|
242
299
|
id: deploymentResult.value.id,
|
|
243
300
|
status: deploymentResult.value.status,
|
|
@@ -263,22 +320,28 @@ function createPreviewAppProvider(client, options) {
|
|
|
263
320
|
};
|
|
264
321
|
}
|
|
265
322
|
async function listBranches(client, options) {
|
|
266
|
-
const result = await client.GET("/v1/projects/{projectId}/branches", {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
323
|
+
const result = await client.GET("/v1/projects/{projectId}/branches", {
|
|
324
|
+
params: {
|
|
325
|
+
path: { projectId: options.projectId },
|
|
326
|
+
query: { gitName: options.gitName }
|
|
327
|
+
},
|
|
328
|
+
signal: options.signal
|
|
329
|
+
});
|
|
270
330
|
if (result.error || !result.data) throw apiCallError("Failed to list branches", result.response, result.error);
|
|
271
|
-
return result.data.data
|
|
331
|
+
return result.data.data.map((branch) => ({
|
|
332
|
+
id: branch.id,
|
|
333
|
+
gitName: branch.gitName,
|
|
334
|
+
isDefault: branch.isDefault,
|
|
335
|
+
role: branch.role
|
|
336
|
+
}));
|
|
272
337
|
}
|
|
273
338
|
async function resolveOrCreateBranch(client, options) {
|
|
274
339
|
const existing = (await listBranches(client, options))[0];
|
|
275
340
|
if (existing) return existing;
|
|
276
341
|
const result = await client.POST("/v1/projects/{projectId}/branches", {
|
|
277
342
|
params: { path: { projectId: options.projectId } },
|
|
278
|
-
body: {
|
|
279
|
-
|
|
280
|
-
isDefault: options.gitName === "main"
|
|
281
|
-
}
|
|
343
|
+
body: { gitName: options.gitName },
|
|
344
|
+
signal: options.signal
|
|
282
345
|
});
|
|
283
346
|
if (result.error || !result.data) {
|
|
284
347
|
if (result.response.status === 409) {
|
|
@@ -287,17 +350,26 @@ async function resolveOrCreateBranch(client, options) {
|
|
|
287
350
|
}
|
|
288
351
|
throw apiCallError(`Failed to create branch "${options.gitName}"`, result.response, result.error);
|
|
289
352
|
}
|
|
290
|
-
|
|
353
|
+
const branch = result.data.data;
|
|
354
|
+
return {
|
|
355
|
+
id: branch.id,
|
|
356
|
+
gitName: branch.gitName,
|
|
357
|
+
isDefault: branch.isDefault,
|
|
358
|
+
role: branch.role
|
|
359
|
+
};
|
|
291
360
|
}
|
|
292
361
|
async function listComputeServices(client, options) {
|
|
293
362
|
const services = [];
|
|
294
363
|
let cursor;
|
|
295
364
|
while (true) {
|
|
296
|
-
const result = await client.GET("/v1/compute-services", {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
365
|
+
const result = await client.GET("/v1/compute-services", {
|
|
366
|
+
params: { query: {
|
|
367
|
+
projectId: options.projectId,
|
|
368
|
+
branchGitName: options.branchGitName,
|
|
369
|
+
cursor
|
|
370
|
+
} },
|
|
371
|
+
signal: options.signal
|
|
372
|
+
});
|
|
301
373
|
if (result.error || !result.data) throw apiCallError("Failed to list apps", result.response, result.error);
|
|
302
374
|
services.push(...result.data.data);
|
|
303
375
|
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
|
|
@@ -312,8 +384,11 @@ async function listComputeServices(client, options) {
|
|
|
312
384
|
liveUrl: toAbsoluteUrl(service.serviceEndpointDomain ?? null)
|
|
313
385
|
}));
|
|
314
386
|
}
|
|
315
|
-
async function listComputeServiceDomains(client, computeServiceId) {
|
|
316
|
-
const result = await client.GET("/v1/compute-services/{computeServiceId}/domains", {
|
|
387
|
+
async function listComputeServiceDomains(client, computeServiceId, signal) {
|
|
388
|
+
const result = await client.GET("/v1/compute-services/{computeServiceId}/domains", {
|
|
389
|
+
params: { path: { computeServiceId } },
|
|
390
|
+
signal
|
|
391
|
+
});
|
|
317
392
|
if (result.error || !result.data) throw domainApiCallError("Failed to list custom domains", result.response, result.error);
|
|
318
393
|
return result.data.data.map((domain) => normalizeDomainRecord(domain));
|
|
319
394
|
}
|
|
@@ -355,19 +430,24 @@ function normalizeHostnameForComparison(hostname) {
|
|
|
355
430
|
async function createBranchApp(client, options) {
|
|
356
431
|
const branch = await resolveOrCreateBranch(client, {
|
|
357
432
|
projectId: options.projectId,
|
|
358
|
-
gitName: options.branchName
|
|
433
|
+
gitName: options.branchName,
|
|
434
|
+
signal: options.signal
|
|
435
|
+
});
|
|
436
|
+
const result = await client.POST("/v1/compute-services", {
|
|
437
|
+
body: {
|
|
438
|
+
projectId: options.projectId,
|
|
439
|
+
branchId: branch.id,
|
|
440
|
+
displayName: options.appName,
|
|
441
|
+
...options.region ? { regionId: options.region } : {}
|
|
442
|
+
},
|
|
443
|
+
signal: options.signal
|
|
359
444
|
});
|
|
360
|
-
const result = await client.POST("/v1/compute-services", { body: {
|
|
361
|
-
projectId: options.projectId,
|
|
362
|
-
branchId: branch.id,
|
|
363
|
-
displayName: options.appName,
|
|
364
|
-
...options.region ? { regionId: options.region } : {}
|
|
365
|
-
} });
|
|
366
445
|
if (result.error || !result.data) {
|
|
367
446
|
if (result.response.status === 409) {
|
|
368
447
|
const matched = (await listComputeServices(client, {
|
|
369
448
|
projectId: options.projectId,
|
|
370
|
-
branchGitName: options.branchName
|
|
449
|
+
branchGitName: options.branchName,
|
|
450
|
+
signal: options.signal
|
|
371
451
|
})).find((app) => app.name === options.appName);
|
|
372
452
|
if (matched) return {
|
|
373
453
|
appId: matched.id,
|
|
@@ -399,14 +479,20 @@ function domainApiCallError(summary, response, error) {
|
|
|
399
479
|
hint: error.error?.hint ?? null
|
|
400
480
|
});
|
|
401
481
|
}
|
|
402
|
-
async function findAppForDeployment(sdk, deploymentId) {
|
|
403
|
-
const projectsResult = await sdk.listProjects();
|
|
482
|
+
async function findAppForDeployment(sdk, deploymentId, signal) {
|
|
483
|
+
const projectsResult = await sdk.listProjects({ signal });
|
|
404
484
|
if (projectsResult.isErr()) throw new Error(projectsResult.error.message);
|
|
405
485
|
for (const project of projectsResult.value) {
|
|
406
|
-
const servicesResult = await sdk.listServices({
|
|
486
|
+
const servicesResult = await sdk.listServices({
|
|
487
|
+
projectId: project.id,
|
|
488
|
+
signal
|
|
489
|
+
});
|
|
407
490
|
if (servicesResult.isErr()) throw new Error(servicesResult.error.message);
|
|
408
491
|
for (const service of servicesResult.value) {
|
|
409
|
-
const detailResult = await sdk.showService({
|
|
492
|
+
const detailResult = await sdk.showService({
|
|
493
|
+
serviceId: service.id,
|
|
494
|
+
signal
|
|
495
|
+
});
|
|
410
496
|
if (detailResult.isErr()) throw new Error(detailResult.error.message);
|
|
411
497
|
const app = {
|
|
412
498
|
id: detailResult.value.id,
|
|
@@ -416,7 +502,10 @@ async function findAppForDeployment(sdk, deploymentId) {
|
|
|
416
502
|
liveUrl: toAbsoluteUrl(detailResult.value.serviceEndpointDomain ?? null)
|
|
417
503
|
};
|
|
418
504
|
if (app.liveDeploymentId === deploymentId) return app;
|
|
419
|
-
const versionsResult = await sdk.listVersions({
|
|
505
|
+
const versionsResult = await sdk.listVersions({
|
|
506
|
+
serviceId: service.id,
|
|
507
|
+
signal
|
|
508
|
+
});
|
|
420
509
|
if (versionsResult.isErr()) throw new Error(versionsResult.error.message);
|
|
421
510
|
if (versionsResult.value.some((version) => version.id === deploymentId)) return app;
|
|
422
511
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { CliError } from "../../shell/errors.js";
|
|
2
|
+
import { canPrompt } from "../../shell/runtime.js";
|
|
3
|
+
import { confirmPrompt } from "../../shell/prompt.js";
|
|
4
|
+
//#region src/lib/app/production-deploy-gate.ts
|
|
5
|
+
async function enforceProductionDeployGate(context, provider, options) {
|
|
6
|
+
if (options.branchKind !== "production") return;
|
|
7
|
+
if (!options.appId) {
|
|
8
|
+
renderFirstProductionDeployLine(context, options.appName);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const currentLiveDeployment = resolveCurrentProductionDeployment(await provider.listDeployments(options.appId).catch((error) => {
|
|
12
|
+
throw productionDeployInspectionFailedError(error);
|
|
13
|
+
}));
|
|
14
|
+
if (!currentLiveDeployment) {
|
|
15
|
+
renderFirstProductionDeployLine(context, options.appName);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!options.prod) throw productionDeployRequiresFlagError();
|
|
19
|
+
if (context.flags.yes) {
|
|
20
|
+
renderProductionDeployYesLine(context);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!canPrompt(context)) throw productionDeployConfirmationRequiredError(options.appName);
|
|
24
|
+
renderProductionDeployConfirmation(context, currentLiveDeployment);
|
|
25
|
+
if (!await confirmPrompt({
|
|
26
|
+
input: context.runtime.stdin,
|
|
27
|
+
output: context.output.stderr,
|
|
28
|
+
message: "Deploy to production?",
|
|
29
|
+
initialValue: false
|
|
30
|
+
})) throw productionDeployCancelledError();
|
|
31
|
+
}
|
|
32
|
+
function resolveCurrentProductionDeployment(result) {
|
|
33
|
+
if (result.deployments.length === 0) return null;
|
|
34
|
+
if (result.app.liveDeploymentId) {
|
|
35
|
+
const live = result.deployments.find((deployment) => deployment.id === result.app.liveDeploymentId);
|
|
36
|
+
if (live) return live;
|
|
37
|
+
}
|
|
38
|
+
return result.deployments.find((deployment) => deployment.live === true) ?? result.deployments[0] ?? null;
|
|
39
|
+
}
|
|
40
|
+
function renderFirstProductionDeployLine(context, appName) {
|
|
41
|
+
if (context.flags.json || context.flags.quiet) return;
|
|
42
|
+
context.output.stderr.write(`First deploy of "${appName}" -- promoting to production.\n\n`);
|
|
43
|
+
}
|
|
44
|
+
function renderProductionDeployYesLine(context) {
|
|
45
|
+
if (context.flags.json || context.flags.quiet) return;
|
|
46
|
+
context.output.stderr.write("Deploying to production (--prod --yes).\n\n");
|
|
47
|
+
}
|
|
48
|
+
function renderProductionDeployConfirmation(context, currentLiveDeployment) {
|
|
49
|
+
if (context.flags.json || context.flags.quiet) return;
|
|
50
|
+
const lines = [
|
|
51
|
+
"This will deploy to production and replace the live deployment.",
|
|
52
|
+
"",
|
|
53
|
+
` Current live: ${currentLiveDeployment.id} deployed ${formatDeploymentAge(currentLiveDeployment.createdAt)}`,
|
|
54
|
+
" New deploy: will be built from your local code",
|
|
55
|
+
""
|
|
56
|
+
];
|
|
57
|
+
context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
58
|
+
}
|
|
59
|
+
function formatDeploymentAge(createdAt) {
|
|
60
|
+
const createdAtMs = Date.parse(createdAt);
|
|
61
|
+
if (!Number.isFinite(createdAtMs)) return createdAt;
|
|
62
|
+
const elapsedMs = Math.max(0, Date.now() - createdAtMs);
|
|
63
|
+
for (const unit of [
|
|
64
|
+
{
|
|
65
|
+
label: "day",
|
|
66
|
+
ms: 1440 * 60 * 1e3
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
label: "hour",
|
|
70
|
+
ms: 3600 * 1e3
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
label: "minute",
|
|
74
|
+
ms: 60 * 1e3
|
|
75
|
+
}
|
|
76
|
+
]) if (elapsedMs >= unit.ms) {
|
|
77
|
+
const value = Math.floor(elapsedMs / unit.ms);
|
|
78
|
+
return `${value} ${unit.label}${value === 1 ? "" : "s"} ago`;
|
|
79
|
+
}
|
|
80
|
+
return "less than a minute ago";
|
|
81
|
+
}
|
|
82
|
+
function productionDeployRequiresFlagError() {
|
|
83
|
+
return new CliError({
|
|
84
|
+
code: "PROD_DEPLOY_REQUIRES_FLAG",
|
|
85
|
+
domain: "app",
|
|
86
|
+
summary: "Production deploy requires --prod",
|
|
87
|
+
why: "The resolved Branch is production and this App already has a production deployment.",
|
|
88
|
+
fix: "Re-run with --prod, or deploy from a preview Branch.",
|
|
89
|
+
exitCode: 2,
|
|
90
|
+
nextActions: [{
|
|
91
|
+
kind: "run-command",
|
|
92
|
+
journey: "deploy-app",
|
|
93
|
+
label: "Deploy to production",
|
|
94
|
+
command: "prisma-cli app deploy --prod"
|
|
95
|
+
}, {
|
|
96
|
+
kind: "run-command",
|
|
97
|
+
journey: "deploy-app",
|
|
98
|
+
label: "Create a preview branch",
|
|
99
|
+
commands: ["git checkout -b <branch-name>", "prisma-cli app deploy"]
|
|
100
|
+
}],
|
|
101
|
+
humanLines: [
|
|
102
|
+
"This would deploy to production.",
|
|
103
|
+
"",
|
|
104
|
+
"Production deploys require explicit intent. Re-run with:",
|
|
105
|
+
"",
|
|
106
|
+
" prisma app deploy --prod",
|
|
107
|
+
"",
|
|
108
|
+
"Or deploy a preview from a feature branch:",
|
|
109
|
+
"",
|
|
110
|
+
" git checkout -b <branch-name>",
|
|
111
|
+
" prisma app deploy"
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function productionDeployConfirmationRequiredError(appName) {
|
|
116
|
+
return new CliError({
|
|
117
|
+
code: "CONFIRMATION_REQUIRED",
|
|
118
|
+
domain: "app",
|
|
119
|
+
summary: "Production deploy requires confirmation in the current mode",
|
|
120
|
+
why: "This command cannot prompt for production deploy confirmation in the current mode.",
|
|
121
|
+
fix: `Pass --prod --yes to confirm deployment of "${appName}" to production.`,
|
|
122
|
+
exitCode: 1,
|
|
123
|
+
nextSteps: ["prisma-cli app deploy --prod --yes"],
|
|
124
|
+
nextActions: [{
|
|
125
|
+
kind: "run-command",
|
|
126
|
+
journey: "deploy-app",
|
|
127
|
+
label: "Deploy to production non-interactively",
|
|
128
|
+
command: "prisma-cli app deploy --prod --yes"
|
|
129
|
+
}]
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function productionDeployCancelledError() {
|
|
133
|
+
return new CliError({
|
|
134
|
+
code: "CONFIRMATION_REQUIRED",
|
|
135
|
+
domain: "app",
|
|
136
|
+
summary: "Production deploy cancelled",
|
|
137
|
+
why: null,
|
|
138
|
+
fix: null,
|
|
139
|
+
exitCode: 0,
|
|
140
|
+
humanLines: ["Cancelled."]
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function productionDeployInspectionFailedError(error) {
|
|
144
|
+
return new CliError({
|
|
145
|
+
code: "DEPLOY_FAILED",
|
|
146
|
+
domain: "app",
|
|
147
|
+
summary: "Failed to inspect production deployments",
|
|
148
|
+
why: error instanceof Error ? error.message : String(error),
|
|
149
|
+
fix: "Retry the command, or rerun with --trace for more detailed diagnostics.",
|
|
150
|
+
debug: formatDebugDetails(error),
|
|
151
|
+
exitCode: 1,
|
|
152
|
+
nextSteps: ["prisma-cli app list-deploys"]
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function formatDebugDetails(error) {
|
|
156
|
+
if (error instanceof Error) return error.stack ?? error.message;
|
|
157
|
+
return typeof error === "string" ? error : null;
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
export { enforceProductionDeployGate };
|
|
@@ -24,20 +24,21 @@ function workspaceIdFromClaims(claims) {
|
|
|
24
24
|
const id = sub.slice(10).trim();
|
|
25
25
|
return id.length > 0 ? id : null;
|
|
26
26
|
}
|
|
27
|
-
async function performLogin(env) {
|
|
27
|
+
async function performLogin(env, signal) {
|
|
28
28
|
await login({
|
|
29
|
-
tokenStorage: new FileTokenStorage(env),
|
|
30
|
-
env
|
|
29
|
+
tokenStorage: new FileTokenStorage(env, signal),
|
|
30
|
+
env,
|
|
31
|
+
signal
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
|
-
async function readAuthState(env) {
|
|
34
|
+
async function readAuthState(env, signal) {
|
|
34
35
|
const rawServiceToken = env[SERVICE_TOKEN_ENV_VAR];
|
|
35
36
|
if (rawServiceToken !== void 0) {
|
|
36
37
|
const serviceToken = rawServiceToken.trim();
|
|
37
38
|
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
|
+
return readServiceTokenAuthState(serviceToken, env, signal);
|
|
39
40
|
}
|
|
40
|
-
const tokens = await new FileTokenStorage(env).getTokens();
|
|
41
|
+
const tokens = await new FileTokenStorage(env, signal).getTokens();
|
|
41
42
|
if (!tokens) return {
|
|
42
43
|
authenticated: false,
|
|
43
44
|
provider: null,
|
|
@@ -45,20 +46,21 @@ async function readAuthState(env) {
|
|
|
45
46
|
workspace: null,
|
|
46
47
|
credential: null
|
|
47
48
|
};
|
|
48
|
-
const client = await requireComputeAuth(env);
|
|
49
|
-
const currentPrincipal = await readCurrentPrincipalAuthState(client);
|
|
49
|
+
const client = await requireComputeAuth(env, signal);
|
|
50
|
+
const currentPrincipal = await readCurrentPrincipalAuthState(client, signal);
|
|
50
51
|
if (currentPrincipal) return currentPrincipal;
|
|
51
52
|
const claims = decodeJwtPayload(tokens.accessToken);
|
|
52
53
|
return buildAuthState({
|
|
53
54
|
workspaceIdFromCredential: tokens.workspaceId,
|
|
54
55
|
claims,
|
|
55
56
|
env,
|
|
56
|
-
client
|
|
57
|
+
client,
|
|
58
|
+
signal
|
|
57
59
|
});
|
|
58
60
|
}
|
|
59
|
-
async function readServiceTokenAuthState(token, env) {
|
|
60
|
-
const client = await requireComputeAuth(env);
|
|
61
|
-
const currentPrincipal = await readCurrentPrincipalAuthState(client);
|
|
61
|
+
async function readServiceTokenAuthState(token, env, signal) {
|
|
62
|
+
const client = await requireComputeAuth(env, signal);
|
|
63
|
+
const currentPrincipal = await readCurrentPrincipalAuthState(client, signal);
|
|
62
64
|
if (currentPrincipal) return currentPrincipal;
|
|
63
65
|
const claims = decodeJwtPayload(token);
|
|
64
66
|
const workspaceId = workspaceIdFromClaims(claims);
|
|
@@ -73,15 +75,19 @@ async function readServiceTokenAuthState(token, env) {
|
|
|
73
75
|
workspaceIdFromCredential: workspaceId,
|
|
74
76
|
claims,
|
|
75
77
|
env,
|
|
76
|
-
client
|
|
78
|
+
client,
|
|
79
|
+
signal
|
|
77
80
|
});
|
|
78
81
|
}
|
|
79
|
-
async function buildAuthState({ workspaceIdFromCredential, claims, env, client }) {
|
|
82
|
+
async function buildAuthState({ workspaceIdFromCredential, claims, env, client, signal }) {
|
|
80
83
|
let workspaceId = workspaceIdFromCredential;
|
|
81
84
|
let workspaceName = workspaceIdFromCredential;
|
|
82
|
-
client ??= await requireComputeAuth(env);
|
|
85
|
+
client ??= await requireComputeAuth(env, signal);
|
|
83
86
|
if (client) try {
|
|
84
|
-
const { data, response } = await client.GET("/v1/workspaces/{id}", {
|
|
87
|
+
const { data, response } = await client.GET("/v1/workspaces/{id}", {
|
|
88
|
+
params: { path: { id: workspaceIdFromCredential } },
|
|
89
|
+
signal
|
|
90
|
+
});
|
|
85
91
|
if (response?.status === 401) return {
|
|
86
92
|
authenticated: false,
|
|
87
93
|
provider: null,
|
|
@@ -94,7 +100,9 @@ async function buildAuthState({ workspaceIdFromCredential, claims, env, client }
|
|
|
94
100
|
workspaceName = data.data.id;
|
|
95
101
|
}
|
|
96
102
|
if (data?.data?.name) workspaceName = data.data.name;
|
|
97
|
-
} catch {
|
|
103
|
+
} catch {
|
|
104
|
+
signal?.throwIfAborted();
|
|
105
|
+
}
|
|
98
106
|
const email = emailFromClaims(claims);
|
|
99
107
|
return {
|
|
100
108
|
authenticated: true,
|
|
@@ -107,10 +115,10 @@ async function buildAuthState({ workspaceIdFromCredential, claims, env, client }
|
|
|
107
115
|
credential: null
|
|
108
116
|
};
|
|
109
117
|
}
|
|
110
|
-
async function readCurrentPrincipalAuthState(client) {
|
|
118
|
+
async function readCurrentPrincipalAuthState(client, signal) {
|
|
111
119
|
if (!client) return null;
|
|
112
120
|
try {
|
|
113
|
-
const { data, response } = await client.GET("/v1/me");
|
|
121
|
+
const { data, response } = await client.GET("/v1/me", { signal });
|
|
114
122
|
if (response?.status === 401) return {
|
|
115
123
|
authenticated: false,
|
|
116
124
|
provider: null,
|
|
@@ -133,11 +141,12 @@ async function readCurrentPrincipalAuthState(client) {
|
|
|
133
141
|
credential: principal.credential
|
|
134
142
|
};
|
|
135
143
|
} catch {
|
|
144
|
+
signal?.throwIfAborted();
|
|
136
145
|
return null;
|
|
137
146
|
}
|
|
138
147
|
}
|
|
139
|
-
async function performLogout(env) {
|
|
140
|
-
await new FileTokenStorage(env).clearTokens();
|
|
148
|
+
async function performLogout(env, signal) {
|
|
149
|
+
await new FileTokenStorage(env, signal).clearTokens();
|
|
141
150
|
}
|
|
142
151
|
//#endregion
|
|
143
152
|
export { performLogin, performLogout, readAuthState };
|
package/dist/lib/auth/guard.js
CHANGED
|
@@ -11,7 +11,8 @@ import { createManagementApiClient, createManagementApiSdk } from "@prisma/manag
|
|
|
11
11
|
*
|
|
12
12
|
* Returns null if not authenticated.
|
|
13
13
|
*/
|
|
14
|
-
async function requireComputeAuth(env = process.env) {
|
|
14
|
+
async function requireComputeAuth(env = process.env, signal) {
|
|
15
|
+
signal?.throwIfAborted();
|
|
15
16
|
const rawToken = env[SERVICE_TOKEN_ENV_VAR];
|
|
16
17
|
if (rawToken !== void 0) {
|
|
17
18
|
const token = rawToken.trim();
|
|
@@ -21,7 +22,7 @@ async function requireComputeAuth(env = process.env) {
|
|
|
21
22
|
token
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
|
-
const tokenStorage = new FileTokenStorage(env);
|
|
25
|
+
const tokenStorage = new FileTokenStorage(env, signal);
|
|
25
26
|
if (!await tokenStorage.getTokens()) return null;
|
|
26
27
|
return createManagementApiSdk({
|
|
27
28
|
clientId: CLIENT_ID,
|