@prisma/cli 3.0.0-alpha.6 → 3.0.0-alpha.8
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/local-state.js +1 -1
- package/dist/commands/app/index.js +9 -1
- package/dist/controllers/app.js +690 -57
- package/dist/lib/app/bun-project.js +1 -1
- package/dist/lib/app/deploy-output.js +15 -0
- package/dist/lib/app/local-dev.js +1 -1
- package/dist/lib/app/preview-build.js +1 -1
- package/dist/lib/app/preview-interaction.js +2 -35
- package/dist/lib/app/preview-progress.js +43 -58
- package/dist/lib/app/preview-provider.js +110 -22
- package/dist/lib/auth/client.js +1 -1
- package/dist/lib/project/local-pin.js +51 -0
- package/dist/lib/project/resolution.js +71 -21
- package/dist/presenters/app.js +16 -37
- package/dist/presenters/project.js +3 -0
- package/dist/shell/command-meta.js +4 -4
- package/dist/shell/errors.js +2 -0
- package/dist/shell/output.js +11 -0
- package/dist/shell/prompt.js +12 -2
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { padDisplay } from "../../shell/ui.js";
|
|
2
|
+
//#region src/lib/app/deploy-output.ts
|
|
3
|
+
const DEPLOY_OUTPUT_MIN_LABEL_WIDTH = 9;
|
|
4
|
+
const DEPLOY_OUTPUT_MIN_VALUE_WIDTH = 9;
|
|
5
|
+
function renderDeployOutputRows(ui, rows) {
|
|
6
|
+
if (rows.length === 0) return [];
|
|
7
|
+
const labelWidth = Math.max(DEPLOY_OUTPUT_MIN_LABEL_WIDTH, ...rows.map((row) => row.label.length));
|
|
8
|
+
const valueWidth = Math.max(DEPLOY_OUTPUT_MIN_VALUE_WIDTH, ...rows.map((row) => row.value?.length ?? 0));
|
|
9
|
+
return rows.map((row) => {
|
|
10
|
+
if (!row.value) return ` ${row.label}`;
|
|
11
|
+
return ` ${padDisplay(row.label, labelWidth)} ${padDisplay(ui.strong(row.value), valueWidth)}${row.origin ? ` ${ui.dim(`· ${row.origin}`)}` : ""}`.trimEnd();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { renderDeployOutputRows };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readBunPackageEntrypoint, readBunPackageJson, resolveBunEntrypoint } from "./bun-project.js";
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
import { access } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
//#region src/lib/app/local-dev.ts
|
|
6
6
|
const NEXT_CONFIG_FILENAMES = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveBunEntrypoint } from "./bun-project.js";
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
import { cp, readdir, readlink, rm, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
4
|
import { AstroBuild, BunBuild, NextjsBuild, NuxtBuild, TanstackStartBuild } from "@prisma/compute-sdk";
|
|
5
5
|
//#region src/lib/app/preview-build.ts
|
|
6
6
|
const PREVIEW_BUILD_TYPES = [
|
|
@@ -1,38 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../../shell/prompt.js";
|
|
2
2
|
//#region src/lib/app/preview-interaction.ts
|
|
3
|
-
const CREATE_NEW_APP = "__create_new_app__";
|
|
4
3
|
const PREVIEW_DEFAULT_REGION = "eu-central-1";
|
|
5
|
-
function createPreviewDeployInteraction(context) {
|
|
6
|
-
return {
|
|
7
|
-
async selectService(services) {
|
|
8
|
-
const sorted = services.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
|
|
9
|
-
const selection = await selectPrompt({
|
|
10
|
-
input: context.runtime.stdin,
|
|
11
|
-
output: context.runtime.stderr,
|
|
12
|
-
message: "Select an app",
|
|
13
|
-
choices: [...sorted.map((service) => ({
|
|
14
|
-
label: service.name,
|
|
15
|
-
value: service.id
|
|
16
|
-
})), {
|
|
17
|
-
label: "Create a new app",
|
|
18
|
-
value: CREATE_NEW_APP
|
|
19
|
-
}]
|
|
20
|
-
});
|
|
21
|
-
return selection === CREATE_NEW_APP ? null : selection;
|
|
22
|
-
},
|
|
23
|
-
async provideServiceName() {
|
|
24
|
-
return textPrompt({
|
|
25
|
-
input: context.runtime.stdin,
|
|
26
|
-
output: context.runtime.stderr,
|
|
27
|
-
message: "App name",
|
|
28
|
-
placeholder: "hello-world",
|
|
29
|
-
validate: (value) => !value?.trim() ? "App name is required" : void 0
|
|
30
|
-
}).then((value) => value.trim());
|
|
31
|
-
},
|
|
32
|
-
async selectRegion(_regions) {
|
|
33
|
-
return PREVIEW_DEFAULT_REGION;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
4
|
//#endregion
|
|
38
|
-
export { PREVIEW_DEFAULT_REGION
|
|
5
|
+
export { PREVIEW_DEFAULT_REGION };
|
|
@@ -1,83 +1,68 @@
|
|
|
1
|
+
import { renderDeployOutputRows } from "./deploy-output.js";
|
|
1
2
|
//#region src/lib/app/preview-progress.ts
|
|
2
|
-
function
|
|
3
|
-
|
|
3
|
+
function createPreviewDeployProgressState() {
|
|
4
|
+
return {
|
|
5
|
+
buildStarted: false,
|
|
6
|
+
buildCompleted: false,
|
|
7
|
+
archiveReady: false,
|
|
8
|
+
uploadCompleted: false,
|
|
9
|
+
versionId: null,
|
|
10
|
+
startRequested: false,
|
|
11
|
+
containerLive: false,
|
|
12
|
+
deploymentUrl: null,
|
|
13
|
+
promotedUrl: null
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function createPreviewDeployProgress(output, ui, enabled, state = createPreviewDeployProgressState()) {
|
|
4
17
|
const write = (line) => {
|
|
18
|
+
if (!enabled) return;
|
|
5
19
|
output.write(`${line}\n`);
|
|
6
20
|
};
|
|
21
|
+
const writeRows = (rows) => {
|
|
22
|
+
for (const line of renderDeployOutputRows(ui, rows)) write(line);
|
|
23
|
+
};
|
|
7
24
|
return {
|
|
8
25
|
onBuildStart() {
|
|
9
|
-
|
|
26
|
+
state.buildStarted = true;
|
|
27
|
+
write("Building locally...");
|
|
10
28
|
},
|
|
11
29
|
onBuildComplete() {
|
|
12
|
-
|
|
13
|
-
},
|
|
14
|
-
onArchiveCreating() {
|
|
15
|
-
write("Creating deployment artifact...");
|
|
30
|
+
state.buildCompleted = true;
|
|
16
31
|
},
|
|
17
32
|
onArchiveReady(byteLength) {
|
|
18
|
-
|
|
33
|
+
state.archiveReady = true;
|
|
34
|
+
writeRows([{
|
|
35
|
+
label: "Built",
|
|
36
|
+
value: formatArtifactSize(byteLength)
|
|
37
|
+
}]);
|
|
38
|
+
},
|
|
39
|
+
onUploadStart() {
|
|
40
|
+
write("Uploading...");
|
|
19
41
|
},
|
|
20
42
|
onVersionCreated(versionId) {
|
|
21
|
-
|
|
43
|
+
state.versionId = versionId;
|
|
22
44
|
},
|
|
23
45
|
onUploadComplete() {
|
|
24
|
-
|
|
46
|
+
state.uploadCompleted = true;
|
|
47
|
+
writeRows([{ label: "Uploaded" }]);
|
|
25
48
|
},
|
|
26
49
|
onStartRequested() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
onStatusChange(status) {
|
|
30
|
-
write(`Status: ${status}`);
|
|
50
|
+
state.startRequested = true;
|
|
51
|
+
write("Deploying...");
|
|
31
52
|
},
|
|
32
53
|
onRunning(url) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
write("Deployment is running.");
|
|
38
|
-
},
|
|
39
|
-
onPromoteStart() {
|
|
40
|
-
write("Promoting deployment...");
|
|
54
|
+
state.containerLive = true;
|
|
55
|
+
state.deploymentUrl = url;
|
|
56
|
+
writeRows([{ label: "Deployed" }]);
|
|
41
57
|
},
|
|
42
58
|
onPromoted(url) {
|
|
43
|
-
|
|
44
|
-
write(`Promoted to ${url}.`);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
write("Promotion complete.");
|
|
48
|
-
},
|
|
49
|
-
onPromoteFailed(error) {
|
|
50
|
-
write(`Promotion failed${error?.message ? `: ${error.message}` : "."}`);
|
|
51
|
-
},
|
|
52
|
-
onOldVersionStopping(versionId) {
|
|
53
|
-
write(`Stopping previous deployment ${versionId}...`);
|
|
54
|
-
},
|
|
55
|
-
onOldVersionStopped(versionId) {
|
|
56
|
-
write(`Previous deployment ${versionId} stopped.`);
|
|
57
|
-
},
|
|
58
|
-
onOldVersionStopFailed(versionId) {
|
|
59
|
-
write(`Failed to stop previous deployment ${versionId} (non-fatal).`);
|
|
60
|
-
},
|
|
61
|
-
onOldVersionDeleting(versionId) {
|
|
62
|
-
write(`Deleting previous deployment ${versionId}...`);
|
|
63
|
-
},
|
|
64
|
-
onOldVersionDeleted(versionId) {
|
|
65
|
-
write(`Previous deployment ${versionId} deleted.`);
|
|
66
|
-
},
|
|
67
|
-
onOldVersionDeleteFailed(versionId) {
|
|
68
|
-
write(`Failed to delete previous deployment ${versionId} (non-fatal).`);
|
|
69
|
-
},
|
|
70
|
-
onCleanupDanglingVersion(versionId) {
|
|
71
|
-
write(`Cleaning up deployment ${versionId}...`);
|
|
72
|
-
},
|
|
73
|
-
onCleanupDanglingVersionComplete(versionId) {
|
|
74
|
-
write(`Deployment ${versionId} cleaned up.`);
|
|
75
|
-
},
|
|
76
|
-
onCleanupDanglingVersionFailed(versionId) {
|
|
77
|
-
write(`Failed to clean up deployment ${versionId}.`);
|
|
59
|
+
state.promotedUrl = url;
|
|
78
60
|
}
|
|
79
61
|
};
|
|
80
62
|
}
|
|
63
|
+
function formatArtifactSize(byteLength) {
|
|
64
|
+
return `${(byteLength / 1024 / 1024).toFixed(1)} MB`;
|
|
65
|
+
}
|
|
81
66
|
function createPreviewPromoteProgress(output, enabled) {
|
|
82
67
|
if (!enabled) return;
|
|
83
68
|
const write = (line) => {
|
|
@@ -136,4 +121,4 @@ function createPreviewUpdateEnvProgress(output, enabled) {
|
|
|
136
121
|
};
|
|
137
122
|
}
|
|
138
123
|
//#endregion
|
|
139
|
-
export { createPreviewDeployProgress, createPreviewPromoteProgress, createPreviewUpdateEnvProgress };
|
|
124
|
+
export { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress, createPreviewUpdateEnvProgress };
|
|
@@ -14,25 +14,11 @@ function createPreviewAppProvider(client, options) {
|
|
|
14
14
|
name: projectResult.value.name
|
|
15
15
|
};
|
|
16
16
|
},
|
|
17
|
-
async listApps(projectId) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return detailResult.isOk() ? detailResult.value : {
|
|
23
|
-
id: service.id,
|
|
24
|
-
name: service.name,
|
|
25
|
-
region: service.region,
|
|
26
|
-
latestVersionId: null,
|
|
27
|
-
serviceEndpointDomain: void 0
|
|
28
|
-
};
|
|
29
|
-
}))).map((service) => ({
|
|
30
|
-
id: service.id,
|
|
31
|
-
name: service.name,
|
|
32
|
-
region: service.region ?? null,
|
|
33
|
-
liveDeploymentId: service.latestVersionId ?? null,
|
|
34
|
-
liveUrl: toAbsoluteUrl(service.serviceEndpointDomain ?? null)
|
|
35
|
-
}));
|
|
17
|
+
async listApps(projectId, options) {
|
|
18
|
+
return listComputeServices(client, {
|
|
19
|
+
projectId,
|
|
20
|
+
branchGitName: options?.branchName
|
|
21
|
+
});
|
|
36
22
|
},
|
|
37
23
|
async removeApp(appId) {
|
|
38
24
|
const appResult = await sdk.showService({ serviceId: appId });
|
|
@@ -60,6 +46,20 @@ function createPreviewAppProvider(client, options) {
|
|
|
60
46
|
if (promoteResult.isErr()) throw new Error(promoteResult.error.message);
|
|
61
47
|
},
|
|
62
48
|
async deployApp(options) {
|
|
49
|
+
const resolvedApp = options.appId ? {
|
|
50
|
+
appId: options.appId,
|
|
51
|
+
appName: options.appName,
|
|
52
|
+
region: options.region
|
|
53
|
+
} : options.branchName && options.appName ? await createBranchApp(client, {
|
|
54
|
+
projectId: options.projectId,
|
|
55
|
+
branchName: options.branchName,
|
|
56
|
+
appName: options.appName,
|
|
57
|
+
region: options.region
|
|
58
|
+
}) : {
|
|
59
|
+
appId: void 0,
|
|
60
|
+
appName: options.appName,
|
|
61
|
+
region: options.region
|
|
62
|
+
};
|
|
63
63
|
const deployResult = await sdk.deploy({
|
|
64
64
|
strategy: new PreviewBuildStrategy({
|
|
65
65
|
appPath: path.resolve(options.cwd),
|
|
@@ -67,9 +67,9 @@ function createPreviewAppProvider(client, options) {
|
|
|
67
67
|
buildType: options.buildType
|
|
68
68
|
}),
|
|
69
69
|
projectId: options.projectId,
|
|
70
|
-
serviceId:
|
|
71
|
-
serviceName:
|
|
72
|
-
region:
|
|
70
|
+
serviceId: resolvedApp.appId,
|
|
71
|
+
serviceName: resolvedApp.appName,
|
|
72
|
+
region: resolvedApp.region,
|
|
73
73
|
portMapping: options.portMapping,
|
|
74
74
|
envVars: options.envVars,
|
|
75
75
|
timeoutSeconds: 120,
|
|
@@ -213,6 +213,94 @@ function createPreviewAppProvider(client, options) {
|
|
|
213
213
|
}
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
|
+
async function listBranches(client, options) {
|
|
217
|
+
const result = await client.GET("/v1/projects/{projectId}/branches", { params: {
|
|
218
|
+
path: { projectId: options.projectId },
|
|
219
|
+
query: { gitName: options.gitName }
|
|
220
|
+
} });
|
|
221
|
+
if (result.error || !result.data) throw apiCallError("Failed to list branches", result.response, result.error);
|
|
222
|
+
return result.data.data;
|
|
223
|
+
}
|
|
224
|
+
async function resolveOrCreateBranch(client, options) {
|
|
225
|
+
const existing = (await listBranches(client, options))[0];
|
|
226
|
+
if (existing) return existing;
|
|
227
|
+
const result = await client.POST("/v1/projects/{projectId}/branches", {
|
|
228
|
+
params: { path: { projectId: options.projectId } },
|
|
229
|
+
body: {
|
|
230
|
+
gitName: options.gitName,
|
|
231
|
+
isDefault: options.gitName === "main"
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
if (result.error || !result.data) {
|
|
235
|
+
if (result.response.status === 409) {
|
|
236
|
+
const raced = (await listBranches(client, options))[0];
|
|
237
|
+
if (raced) return raced;
|
|
238
|
+
}
|
|
239
|
+
throw apiCallError(`Failed to create branch "${options.gitName}"`, result.response, result.error);
|
|
240
|
+
}
|
|
241
|
+
return result.data.data;
|
|
242
|
+
}
|
|
243
|
+
async function listComputeServices(client, options) {
|
|
244
|
+
const services = [];
|
|
245
|
+
let cursor;
|
|
246
|
+
while (true) {
|
|
247
|
+
const result = await client.GET("/v1/compute-services", { params: { query: {
|
|
248
|
+
projectId: options.projectId,
|
|
249
|
+
branchGitName: options.branchGitName,
|
|
250
|
+
cursor
|
|
251
|
+
} } });
|
|
252
|
+
if (result.error || !result.data) throw apiCallError("Failed to list apps", result.response, result.error);
|
|
253
|
+
services.push(...result.data.data);
|
|
254
|
+
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
|
|
255
|
+
cursor = result.data.pagination.nextCursor;
|
|
256
|
+
}
|
|
257
|
+
return services.map((service) => ({
|
|
258
|
+
id: service.id,
|
|
259
|
+
name: service.name,
|
|
260
|
+
region: service.region.id ?? null,
|
|
261
|
+
branchId: service.branchId,
|
|
262
|
+
liveDeploymentId: service.latestVersionId ?? null,
|
|
263
|
+
liveUrl: toAbsoluteUrl(service.serviceEndpointDomain ?? null)
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
async function createBranchApp(client, options) {
|
|
267
|
+
const branch = await resolveOrCreateBranch(client, {
|
|
268
|
+
projectId: options.projectId,
|
|
269
|
+
gitName: options.branchName
|
|
270
|
+
});
|
|
271
|
+
const result = await client.POST("/v1/compute-services", { body: {
|
|
272
|
+
projectId: options.projectId,
|
|
273
|
+
branchId: branch.id,
|
|
274
|
+
displayName: options.appName,
|
|
275
|
+
...options.region ? { regionId: options.region } : {}
|
|
276
|
+
} });
|
|
277
|
+
if (result.error || !result.data) {
|
|
278
|
+
if (result.response.status === 409) {
|
|
279
|
+
const matched = (await listComputeServices(client, {
|
|
280
|
+
projectId: options.projectId,
|
|
281
|
+
branchGitName: options.branchName
|
|
282
|
+
})).find((app) => app.name === options.appName);
|
|
283
|
+
if (matched) return {
|
|
284
|
+
appId: matched.id,
|
|
285
|
+
appName: matched.name,
|
|
286
|
+
region: matched.region ?? options.region
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
throw apiCallError(`Failed to create app "${options.appName}"`, result.response, result.error);
|
|
290
|
+
}
|
|
291
|
+
const service = result.data.data;
|
|
292
|
+
return {
|
|
293
|
+
appId: service.id,
|
|
294
|
+
appName: service.name,
|
|
295
|
+
region: service.region.id ?? options.region
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function apiCallError(summary, response, error) {
|
|
299
|
+
if (response.status === 404) return /* @__PURE__ */ new Error("Resource Not Found");
|
|
300
|
+
const message = error.error?.message ?? `Management API returned HTTP ${response.status}.`;
|
|
301
|
+
const hint = error.error?.hint ? ` ${error.error.hint}` : "";
|
|
302
|
+
return /* @__PURE__ */ new Error(`${summary}: ${message}${hint}`);
|
|
303
|
+
}
|
|
216
304
|
async function findAppForDeployment(sdk, deploymentId) {
|
|
217
305
|
const projectsResult = await sdk.listProjects();
|
|
218
306
|
if (projectsResult.isErr()) throw new Error(projectsResult.error.message);
|
package/dist/lib/auth/client.js
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/lib/project/local-pin.ts
|
|
4
|
+
const LOCAL_RESOLUTION_PIN_RELATIVE_PATH = ".prisma/local.json";
|
|
5
|
+
async function readLocalResolutionPin(cwd) {
|
|
6
|
+
try {
|
|
7
|
+
const raw = await readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), "utf8");
|
|
8
|
+
const parsed = JSON.parse(raw);
|
|
9
|
+
if (!isLocalResolutionPin(parsed)) return { kind: "invalid" };
|
|
10
|
+
return {
|
|
11
|
+
kind: "present",
|
|
12
|
+
pin: parsed
|
|
13
|
+
};
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error.code === "ENOENT") return { kind: "missing" };
|
|
16
|
+
if (error instanceof SyntaxError) return { kind: "invalid" };
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function writeLocalResolutionPin(cwd, pin) {
|
|
21
|
+
const prismaDir = path.join(cwd, ".prisma");
|
|
22
|
+
await mkdir(prismaDir, { recursive: true });
|
|
23
|
+
const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
|
|
24
|
+
const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
|
|
25
|
+
await writeFile(tmpPath, `${JSON.stringify(pin, null, 2)}\n`, "utf8");
|
|
26
|
+
await rename(tmpPath, pinPath);
|
|
27
|
+
}
|
|
28
|
+
async function ensureLocalResolutionPinGitignore(cwd) {
|
|
29
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
30
|
+
let existing = null;
|
|
31
|
+
try {
|
|
32
|
+
existing = await readFile(gitignorePath, "utf8");
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error.code !== "ENOENT") throw error;
|
|
35
|
+
}
|
|
36
|
+
if (existing === null) {
|
|
37
|
+
await writeFile(gitignorePath, ".prisma/\n", "utf8");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return;
|
|
41
|
+
await writeFile(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, "utf8");
|
|
42
|
+
}
|
|
43
|
+
function isLocalResolutionPin(value) {
|
|
44
|
+
if (!value || typeof value !== "object") return false;
|
|
45
|
+
const keys = Object.keys(value);
|
|
46
|
+
if (keys.length !== 2 || !keys.includes("workspaceId") || !keys.includes("projectId")) return false;
|
|
47
|
+
const candidate = value;
|
|
48
|
+
return typeof candidate.workspaceId === "string" && candidate.workspaceId.trim().length > 0 && typeof candidate.projectId === "string" && candidate.projectId.trim().length > 0;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, readLocalResolutionPin, writeLocalResolutionPin };
|
|
@@ -1,33 +1,44 @@
|
|
|
1
1
|
import { CliError } from "../../shell/errors.js";
|
|
2
2
|
import { canPrompt } from "../../shell/runtime.js";
|
|
3
|
-
import path from "node:path";
|
|
4
3
|
import { readFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
5
|
//#region src/lib/project/resolution.ts
|
|
6
6
|
async function resolveProjectTarget(options) {
|
|
7
7
|
const projects = await options.listProjects();
|
|
8
|
-
|
|
8
|
+
const inferredName = await inferTargetName(options.context.runtime.cwd);
|
|
9
|
+
if (options.explicitProject) return rememberIfRequested(options, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
|
|
10
|
+
targetName: options.explicitProject,
|
|
11
|
+
targetNameSource: "explicit"
|
|
12
|
+
});
|
|
9
13
|
const platformMapping = await resolveDurablePlatformMapping();
|
|
10
14
|
if (platformMapping) return rememberIfRequested(options, platformMapping, "platform-mapping");
|
|
11
|
-
const remembered = await options.context.stateStore.readRememberedProject(options.workspace.id);
|
|
12
15
|
let staleRemembered = false;
|
|
13
|
-
if (
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
16
|
-
staleRemembered =
|
|
16
|
+
if (!options.allowCreate) {
|
|
17
|
+
const rememberedResult = await resolveRememberedProject(options, projects);
|
|
18
|
+
if (rememberedResult.target) return rememberedResult.target;
|
|
19
|
+
staleRemembered = rememberedResult.stale;
|
|
17
20
|
}
|
|
18
|
-
const packageName =
|
|
21
|
+
const packageName = inferredName.source === "package-name" ? inferredName.name : null;
|
|
19
22
|
if (packageName) {
|
|
20
23
|
const matches = projects.filter((project) => projectMatchesPackageName(project, packageName));
|
|
21
|
-
if (matches.length === 1) return rememberIfRequested(options, matches[0], "package-name"
|
|
22
|
-
|
|
24
|
+
if (matches.length === 1) return rememberIfRequested(options, matches[0], "package-name", {
|
|
25
|
+
targetName: packageName,
|
|
26
|
+
targetNameSource: "package-name"
|
|
27
|
+
});
|
|
28
|
+
if (matches.length > 1) return resolveAmbiguousProject(options, matches, packageName, "package-name");
|
|
23
29
|
}
|
|
24
30
|
if (options.allowCreate && options.createProject) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
if (inferredName.name) {
|
|
32
|
+
const existing = projects.filter((project) => projectMatchesPackageName(project, inferredName.name));
|
|
33
|
+
if (existing.length === 1) return rememberIfRequested(options, existing[0], inferredName.source, {
|
|
34
|
+
targetName: inferredName.name,
|
|
35
|
+
targetNameSource: inferredName.source
|
|
36
|
+
});
|
|
37
|
+
if (existing.length > 1) return resolveAmbiguousProject(options, existing, inferredName.name, inferredName.source);
|
|
38
|
+
return rememberIfRequested(options, await options.createProject(inferredName.name), "created", {
|
|
39
|
+
targetName: inferredName.name,
|
|
40
|
+
targetNameSource: inferredName.source
|
|
41
|
+
});
|
|
31
42
|
}
|
|
32
43
|
}
|
|
33
44
|
if (options.prompt && canPrompt(options.context) && projects.length > 0) return rememberIfRequested(options, await options.prompt.select({
|
|
@@ -40,6 +51,25 @@ async function resolveProjectTarget(options) {
|
|
|
40
51
|
if (staleRemembered && projects.length > 1) throw localStateStaleError();
|
|
41
52
|
throw projectUnresolvedError();
|
|
42
53
|
}
|
|
54
|
+
async function resolveRememberedProject(options, projects) {
|
|
55
|
+
const remembered = await options.context.stateStore.readRememberedProject(options.workspace.id);
|
|
56
|
+
if (!remembered) return {
|
|
57
|
+
target: null,
|
|
58
|
+
stale: false
|
|
59
|
+
};
|
|
60
|
+
const matched = projects.find((project) => project.id === remembered.id);
|
|
61
|
+
if (!matched) return {
|
|
62
|
+
target: null,
|
|
63
|
+
stale: true
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
target: await rememberIfRequested(options, matched, "remembered-local", {
|
|
67
|
+
targetName: remembered.name,
|
|
68
|
+
targetNameSource: "remembered-local"
|
|
69
|
+
}),
|
|
70
|
+
stale: false
|
|
71
|
+
};
|
|
72
|
+
}
|
|
43
73
|
function projectNotFoundError(projectRef, workspace) {
|
|
44
74
|
return new CliError({
|
|
45
75
|
code: "PROJECT_NOT_FOUND",
|
|
@@ -104,6 +134,20 @@ async function readPackageName(cwd) {
|
|
|
104
134
|
throw error;
|
|
105
135
|
}
|
|
106
136
|
}
|
|
137
|
+
async function inferTargetName(cwd) {
|
|
138
|
+
const packageName = await readPackageName(cwd);
|
|
139
|
+
if (packageName && isValidInferredTargetName(packageName)) return {
|
|
140
|
+
name: packageName,
|
|
141
|
+
source: "package-name"
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
name: path.basename(cwd),
|
|
145
|
+
source: "directory-name"
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function isValidInferredTargetName(value) {
|
|
149
|
+
return /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value);
|
|
150
|
+
}
|
|
107
151
|
function sortProjects(projects) {
|
|
108
152
|
return projects.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
|
|
109
153
|
}
|
|
@@ -113,14 +157,17 @@ function resolveExplicitProject(projectRef, projects, workspace) {
|
|
|
113
157
|
if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
|
|
114
158
|
throw projectNotFoundError(projectRef, workspace);
|
|
115
159
|
}
|
|
116
|
-
function resolveAmbiguousProject(options, matches, projectRef) {
|
|
160
|
+
function resolveAmbiguousProject(options, matches, projectRef, targetNameSource) {
|
|
117
161
|
if (options.prompt && canPrompt(options.context)) return options.prompt.select({
|
|
118
162
|
message: "Select a project",
|
|
119
163
|
choices: sortProjects(matches).map((project) => ({
|
|
120
164
|
label: `${project.name} (${project.id})`,
|
|
121
165
|
value: project
|
|
122
166
|
}))
|
|
123
|
-
}).then((selected) => rememberIfRequested(options, selected, "prompt"
|
|
167
|
+
}).then((selected) => rememberIfRequested(options, selected, "prompt", {
|
|
168
|
+
targetName: projectRef,
|
|
169
|
+
targetNameSource
|
|
170
|
+
}));
|
|
124
171
|
throw projectAmbiguousError(projectRef, matches);
|
|
125
172
|
}
|
|
126
173
|
function projectMatchesPackageName(project, packageName) {
|
|
@@ -129,7 +176,7 @@ function projectMatchesPackageName(project, packageName) {
|
|
|
129
176
|
async function resolveDurablePlatformMapping() {
|
|
130
177
|
return null;
|
|
131
178
|
}
|
|
132
|
-
async function rememberIfRequested(options, project, projectSource) {
|
|
179
|
+
async function rememberIfRequested(options, project, projectSource, resolutionDetails) {
|
|
133
180
|
if (options.remember) await options.context.stateStore.setRememberedProject({
|
|
134
181
|
id: project.id,
|
|
135
182
|
name: project.name,
|
|
@@ -138,7 +185,10 @@ async function rememberIfRequested(options, project, projectSource) {
|
|
|
138
185
|
return {
|
|
139
186
|
workspace: options.workspace,
|
|
140
187
|
project: toProjectSummary(project),
|
|
141
|
-
resolution: {
|
|
188
|
+
resolution: {
|
|
189
|
+
projectSource,
|
|
190
|
+
...resolutionDetails
|
|
191
|
+
}
|
|
142
192
|
};
|
|
143
193
|
}
|
|
144
194
|
function toProjectSummary(project) {
|
|
@@ -148,4 +198,4 @@ function toProjectSummary(project) {
|
|
|
148
198
|
};
|
|
149
199
|
}
|
|
150
200
|
//#endregion
|
|
151
|
-
export { resolveProjectTarget, sortProjects };
|
|
201
|
+
export { inferTargetName, projectNotFoundError, resolveProjectTarget, sortProjects };
|
package/dist/presenters/app.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { renderDeployOutputRows } from "../lib/app/deploy-output.js";
|
|
1
2
|
import { renderList, renderShow, serializeList } from "../output/patterns.js";
|
|
2
3
|
//#region src/presenters/app.ts
|
|
3
4
|
function renderAppBuild(context, descriptor, result) {
|
|
@@ -25,45 +26,23 @@ function serializeAppBuild(result) {
|
|
|
25
26
|
return result;
|
|
26
27
|
}
|
|
27
28
|
function renderAppDeploy(context, descriptor, result) {
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
key: "project",
|
|
38
|
-
value: result.project.name
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
key: "branch",
|
|
42
|
-
value: result.branch.name
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
key: "app",
|
|
46
|
-
value: result.app.name
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
key: "deployment",
|
|
50
|
-
value: result.deployment.id
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
key: "status",
|
|
54
|
-
value: result.deployment.status,
|
|
55
|
-
tone: toneForStatus(result.deployment.status)
|
|
56
|
-
},
|
|
57
|
-
...result.deployment.url ? [{
|
|
58
|
-
key: "url",
|
|
59
|
-
value: result.deployment.url,
|
|
60
|
-
tone: "link"
|
|
61
|
-
}] : []
|
|
62
|
-
]
|
|
63
|
-
}, context.ui);
|
|
29
|
+
return [
|
|
30
|
+
`Live in ${formatDuration(result.durationMs)}`,
|
|
31
|
+
...result.deployment.url ? [context.ui.link(result.deployment.url)] : [],
|
|
32
|
+
"",
|
|
33
|
+
...renderDeployOutputRows(context.ui, [{
|
|
34
|
+
label: "Logs",
|
|
35
|
+
value: "prisma-cli app logs"
|
|
36
|
+
}])
|
|
37
|
+
];
|
|
64
38
|
}
|
|
65
39
|
function serializeAppDeploy(result) {
|
|
66
|
-
|
|
40
|
+
const { localPin: _localPin, ...serialized } = result;
|
|
41
|
+
return serialized;
|
|
42
|
+
}
|
|
43
|
+
function formatDuration(durationMs) {
|
|
44
|
+
if (durationMs < 1e3) return `${durationMs}ms`;
|
|
45
|
+
return `${(durationMs / 1e3).toFixed(1)}s`;
|
|
67
46
|
}
|
|
68
47
|
function renderAppUpdateEnv(context, descriptor, result) {
|
|
69
48
|
return renderShow({
|
|
@@ -105,9 +105,12 @@ function renderGitDisconnect(context, descriptor, result) {
|
|
|
105
105
|
function formatProjectSource(source) {
|
|
106
106
|
switch (source) {
|
|
107
107
|
case "explicit": return "explicit";
|
|
108
|
+
case "env": return "environment";
|
|
109
|
+
case "local-pin": return "local pin";
|
|
108
110
|
case "platform-mapping": return "platform mapping";
|
|
109
111
|
case "remembered-local": return "remembered local context";
|
|
110
112
|
case "package-name": return "package name";
|
|
113
|
+
case "directory-name": return "directory name";
|
|
111
114
|
case "created": return "created";
|
|
112
115
|
case "prompt": return "prompt";
|
|
113
116
|
}
|