@prisma/cli 3.0.0-dev.57.1 → 3.0.0-dev.60.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/README.md +2 -2
- package/dist/commands/app/index.js +21 -11
- package/dist/commands/branch/index.js +2 -27
- package/dist/controllers/app.js +25 -3
- package/dist/controllers/branch.js +73 -47
- package/dist/lib/app/branch-database-deploy.js +287 -0
- package/dist/lib/app/branch-database.js +184 -0
- package/dist/lib/app/preview-branch-database.js +102 -0
- package/dist/lib/app/preview-provider.js +19 -0
- package/dist/presenters/app.js +18 -0
- package/dist/presenters/branch.js +29 -101
- package/dist/shell/command-meta.js +5 -23
- package/dist/shell/ui.js +4 -1
- package/dist/use-cases/branch.js +20 -68
- package/dist/use-cases/create-cli-gateways.js +2 -17
- package/package.json +1 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { access, readFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
//#region src/lib/app/branch-database.ts
|
|
5
|
+
const SKIPPED_DIRECTORIES = new Set([
|
|
6
|
+
".git",
|
|
7
|
+
".next",
|
|
8
|
+
".nuxt",
|
|
9
|
+
".output",
|
|
10
|
+
".prisma",
|
|
11
|
+
".turbo",
|
|
12
|
+
".vercel",
|
|
13
|
+
"build",
|
|
14
|
+
"coverage",
|
|
15
|
+
"dist",
|
|
16
|
+
"node_modules",
|
|
17
|
+
"out"
|
|
18
|
+
]);
|
|
19
|
+
const DATABASE_URL_SCAN_EXTENSIONS = new Set([
|
|
20
|
+
".cjs",
|
|
21
|
+
".cts",
|
|
22
|
+
".env",
|
|
23
|
+
".js",
|
|
24
|
+
".json",
|
|
25
|
+
".jsx",
|
|
26
|
+
".mjs",
|
|
27
|
+
".mts",
|
|
28
|
+
".prisma",
|
|
29
|
+
".ts",
|
|
30
|
+
".tsx"
|
|
31
|
+
]);
|
|
32
|
+
const MAX_SCAN_DEPTH = 6;
|
|
33
|
+
const MAX_SCAN_FILES = 1e3;
|
|
34
|
+
const MAX_DATABASE_URL_REFERENCE_FILES = 10;
|
|
35
|
+
const MAX_TEXT_FILE_BYTES = 1024 * 1024;
|
|
36
|
+
async function inspectBranchDatabaseSignal(cwd, signal) {
|
|
37
|
+
const state = {
|
|
38
|
+
filesVisited: 0,
|
|
39
|
+
schemaCandidates: [],
|
|
40
|
+
databaseUrlReferences: []
|
|
41
|
+
};
|
|
42
|
+
await scanDirectory(cwd, cwd, 0, state, signal);
|
|
43
|
+
const schemaPath = selectSchemaPath(cwd, state.schemaCandidates);
|
|
44
|
+
const hasMigrations = schemaPath ? await hasMigrationsDirectory(path.dirname(schemaPath), signal) : false;
|
|
45
|
+
return {
|
|
46
|
+
schema: schemaPath ? {
|
|
47
|
+
path: schemaPath,
|
|
48
|
+
hasMigrations,
|
|
49
|
+
command: hasMigrations ? "migrate-deploy" : "db-push"
|
|
50
|
+
} : null,
|
|
51
|
+
databaseUrlReferences: state.databaseUrlReferences
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function hasBranchDatabaseSignal(signal) {
|
|
55
|
+
return Boolean(signal.schema || signal.databaseUrlReferences.length > 0);
|
|
56
|
+
}
|
|
57
|
+
async function runBranchDatabaseSchemaSetup(options) {
|
|
58
|
+
const schemaPath = path.relative(options.context.runtime.cwd, options.schema.path) || "schema.prisma";
|
|
59
|
+
const args = buildPrismaSchemaCommandArgs(options.schema.command, schemaPath);
|
|
60
|
+
await runPrismaCommand({
|
|
61
|
+
context: options.context,
|
|
62
|
+
args,
|
|
63
|
+
env: {
|
|
64
|
+
DATABASE_URL: options.databaseUrl,
|
|
65
|
+
...options.directUrl ? { DIRECT_URL: options.directUrl } : {}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
command: options.schema.command,
|
|
70
|
+
schemaPath
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function scanDirectory(cwd, directory, depth, state, signal) {
|
|
74
|
+
signal.throwIfAborted();
|
|
75
|
+
if (depth > MAX_SCAN_DEPTH || state.filesVisited >= MAX_SCAN_FILES) return;
|
|
76
|
+
let entries;
|
|
77
|
+
try {
|
|
78
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error.code === "ENOENT") return;
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
signal.throwIfAborted();
|
|
86
|
+
if (state.filesVisited >= MAX_SCAN_FILES) return;
|
|
87
|
+
const entryPath = path.join(directory, entry.name);
|
|
88
|
+
if (entry.isDirectory()) {
|
|
89
|
+
if (!SKIPPED_DIRECTORIES.has(entry.name)) await scanDirectory(cwd, entryPath, depth + 1, state, signal);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!entry.isFile()) continue;
|
|
93
|
+
state.filesVisited += 1;
|
|
94
|
+
if (entry.name === "schema.prisma") state.schemaCandidates.push(entryPath);
|
|
95
|
+
if (state.databaseUrlReferences.length < MAX_DATABASE_URL_REFERENCE_FILES && shouldScanForDatabaseUrl(entry.name) && await fileContainsDatabaseUrl(entryPath, signal)) state.databaseUrlReferences.push(path.relative(cwd, entryPath) || entry.name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function selectSchemaPath(cwd, candidates) {
|
|
99
|
+
return candidates.map((candidate) => ({
|
|
100
|
+
absolute: candidate,
|
|
101
|
+
relative: path.relative(cwd, candidate) || "schema.prisma"
|
|
102
|
+
})).sort((left, right) => {
|
|
103
|
+
if (left.relative === "schema.prisma") return -1;
|
|
104
|
+
if (right.relative === "schema.prisma") return 1;
|
|
105
|
+
return left.relative.length - right.relative.length || left.relative.localeCompare(right.relative);
|
|
106
|
+
})[0]?.absolute ?? null;
|
|
107
|
+
}
|
|
108
|
+
async function hasMigrationsDirectory(schemaDirectory, signal) {
|
|
109
|
+
signal.throwIfAborted();
|
|
110
|
+
const migrationsPath = path.join(schemaDirectory, "migrations");
|
|
111
|
+
try {
|
|
112
|
+
await access(migrationsPath);
|
|
113
|
+
return (await readdir(migrationsPath)).length > 0;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error.code === "ENOENT") return false;
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function shouldScanForDatabaseUrl(fileName) {
|
|
120
|
+
if (fileName === ".env" || fileName.startsWith(".env.")) return true;
|
|
121
|
+
return DATABASE_URL_SCAN_EXTENSIONS.has(path.extname(fileName));
|
|
122
|
+
}
|
|
123
|
+
async function fileContainsDatabaseUrl(filePath, signal) {
|
|
124
|
+
signal.throwIfAborted();
|
|
125
|
+
if ((await stat(filePath)).size > MAX_TEXT_FILE_BYTES) return false;
|
|
126
|
+
return (await readFile(filePath, {
|
|
127
|
+
encoding: "utf8",
|
|
128
|
+
signal
|
|
129
|
+
})).includes("DATABASE_URL");
|
|
130
|
+
}
|
|
131
|
+
function buildPrismaSchemaCommandArgs(command, schemaPath) {
|
|
132
|
+
if (command === "migrate-deploy") return [
|
|
133
|
+
"--no-install",
|
|
134
|
+
"prisma",
|
|
135
|
+
"migrate",
|
|
136
|
+
"deploy",
|
|
137
|
+
"--schema",
|
|
138
|
+
schemaPath
|
|
139
|
+
];
|
|
140
|
+
return [
|
|
141
|
+
"--no-install",
|
|
142
|
+
"prisma",
|
|
143
|
+
"db",
|
|
144
|
+
"push",
|
|
145
|
+
"--skip-generate",
|
|
146
|
+
"--schema",
|
|
147
|
+
schemaPath
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
async function runPrismaCommand(options) {
|
|
151
|
+
const shouldPipeOutput = !options.context.flags.json && !options.context.flags.quiet;
|
|
152
|
+
const child = spawn("npx", options.args, {
|
|
153
|
+
cwd: options.context.runtime.cwd,
|
|
154
|
+
env: {
|
|
155
|
+
...options.context.runtime.env,
|
|
156
|
+
...options.env
|
|
157
|
+
},
|
|
158
|
+
signal: options.context.runtime.signal,
|
|
159
|
+
stdio: shouldPipeOutput ? [
|
|
160
|
+
"ignore",
|
|
161
|
+
"pipe",
|
|
162
|
+
"pipe"
|
|
163
|
+
] : [
|
|
164
|
+
"ignore",
|
|
165
|
+
"ignore",
|
|
166
|
+
"ignore"
|
|
167
|
+
]
|
|
168
|
+
});
|
|
169
|
+
if (shouldPipeOutput) {
|
|
170
|
+
child.stdout?.pipe(options.context.output.stderr, { end: false });
|
|
171
|
+
child.stderr?.pipe(options.context.output.stderr, { end: false });
|
|
172
|
+
}
|
|
173
|
+
const exit = await new Promise((resolve, reject) => {
|
|
174
|
+
child.once("error", reject);
|
|
175
|
+
child.once("close", (code, signal) => resolve({
|
|
176
|
+
code,
|
|
177
|
+
signal
|
|
178
|
+
}));
|
|
179
|
+
});
|
|
180
|
+
if (exit.signal) throw new Error(`npx prisma was terminated by ${exit.signal}.`);
|
|
181
|
+
if (exit.code !== 0) throw new Error(`npx prisma exited with code ${exit.code ?? 1}.`);
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
export { hasBranchDatabaseSignal, inspectBranchDatabaseSignal, runBranchDatabaseSchemaSetup };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//#region src/lib/app/preview-branch-database.ts
|
|
2
|
+
async function createBranchDatabase(client, options) {
|
|
3
|
+
const result = await client.POST("/v1/databases", {
|
|
4
|
+
body: {
|
|
5
|
+
projectId: options.projectId,
|
|
6
|
+
branchId: options.branchId,
|
|
7
|
+
name: options.branchName,
|
|
8
|
+
source: { type: "empty" }
|
|
9
|
+
},
|
|
10
|
+
signal: options.signal
|
|
11
|
+
});
|
|
12
|
+
if (result.error || !result.data) throw apiCallError(`Failed to create database for branch "${options.branchName}"`, result.response, result.error);
|
|
13
|
+
return normalizeBranchDatabaseRecord(result.data.data);
|
|
14
|
+
}
|
|
15
|
+
async function listEnvironmentVariables(client, options) {
|
|
16
|
+
const variables = [];
|
|
17
|
+
let cursor;
|
|
18
|
+
while (true) {
|
|
19
|
+
const result = await client.GET("/v1/environment-variables", {
|
|
20
|
+
params: { query: {
|
|
21
|
+
projectId: options.projectId,
|
|
22
|
+
class: options.className,
|
|
23
|
+
key: options.key,
|
|
24
|
+
branchId: options.branchId,
|
|
25
|
+
cursor
|
|
26
|
+
} },
|
|
27
|
+
signal: options.signal
|
|
28
|
+
});
|
|
29
|
+
if (result.error || !result.data) throw apiCallError("Failed to list environment variables", result.response, result.error);
|
|
30
|
+
variables.push(...result.data.data);
|
|
31
|
+
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
|
|
32
|
+
cursor = result.data.pagination.nextCursor;
|
|
33
|
+
}
|
|
34
|
+
return variables.map((variable) => normalizeEnvironmentVariable(variable));
|
|
35
|
+
}
|
|
36
|
+
async function createEnvironmentVariable(client, options) {
|
|
37
|
+
const result = await client.POST("/v1/environment-variables", {
|
|
38
|
+
body: {
|
|
39
|
+
projectId: options.projectId,
|
|
40
|
+
class: options.className,
|
|
41
|
+
key: options.key,
|
|
42
|
+
value: options.value,
|
|
43
|
+
...options.branchId ? { branchId: options.branchId } : {}
|
|
44
|
+
},
|
|
45
|
+
signal: options.signal
|
|
46
|
+
});
|
|
47
|
+
if (result.error || !result.data) throw apiCallError(`Failed to add ${options.key}`, result.response, result.error);
|
|
48
|
+
return normalizeEnvironmentVariable(result.data.data);
|
|
49
|
+
}
|
|
50
|
+
async function deleteBranchDatabase(client, options) {
|
|
51
|
+
const result = await client.DELETE("/v1/databases/{databaseId}", {
|
|
52
|
+
params: { path: { databaseId: options.databaseId } },
|
|
53
|
+
signal: options.signal
|
|
54
|
+
});
|
|
55
|
+
if (result.error) throw apiCallError("Failed to delete branch database", result.response, result.error);
|
|
56
|
+
}
|
|
57
|
+
async function updateEnvironmentVariable(client, options) {
|
|
58
|
+
const result = await client.PATCH("/v1/environment-variables/{envVarId}", {
|
|
59
|
+
params: { path: { envVarId: options.envVarId } },
|
|
60
|
+
body: { value: options.value },
|
|
61
|
+
signal: options.signal
|
|
62
|
+
});
|
|
63
|
+
if (result.error || !result.data) throw apiCallError("Failed to update environment variable", result.response, result.error);
|
|
64
|
+
return normalizeEnvironmentVariable(result.data.data);
|
|
65
|
+
}
|
|
66
|
+
async function deleteEnvironmentVariable(client, options) {
|
|
67
|
+
const result = await client.DELETE("/v1/environment-variables/{envVarId}", {
|
|
68
|
+
params: { path: { envVarId: options.envVarId } },
|
|
69
|
+
signal: options.signal
|
|
70
|
+
});
|
|
71
|
+
if (result.error) throw apiCallError("Failed to delete environment variable", result.response, result.error);
|
|
72
|
+
}
|
|
73
|
+
function normalizeEnvironmentVariable(variable) {
|
|
74
|
+
return {
|
|
75
|
+
id: variable.id,
|
|
76
|
+
key: variable.key,
|
|
77
|
+
branchId: variable.branchId,
|
|
78
|
+
className: variable.class,
|
|
79
|
+
isManagedBySystem: variable.isManagedBySystem
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function normalizeBranchDatabaseRecord(database) {
|
|
83
|
+
const connection = database.connections?.[0];
|
|
84
|
+
const databaseUrl = connection?.endpoints?.pooled?.connectionString;
|
|
85
|
+
const directUrl = connection?.endpoints?.direct?.connectionString ?? null;
|
|
86
|
+
if (!databaseUrl) throw new Error("Created database did not return a pooled connection string.");
|
|
87
|
+
return {
|
|
88
|
+
id: database.id,
|
|
89
|
+
name: database.name,
|
|
90
|
+
branchId: database.branchId,
|
|
91
|
+
databaseUrl,
|
|
92
|
+
directUrl
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function apiCallError(summary, response, error) {
|
|
96
|
+
if (response.status === 404) return /* @__PURE__ */ new Error("Resource Not Found");
|
|
97
|
+
const message = error.error?.message ?? `Management API returned HTTP ${response.status}.`;
|
|
98
|
+
const hint = error.error?.hint ? ` ${error.error.hint}` : "";
|
|
99
|
+
return /* @__PURE__ */ new Error(`${summary}: ${message}${hint}`);
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { createBranchDatabase, createEnvironmentVariable, deleteBranchDatabase, deleteEnvironmentVariable, listEnvironmentVariables, updateEnvironmentVariable };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { envVarNames } from "./env-vars.js";
|
|
2
2
|
import { PreviewBuildStrategy } from "./preview-build.js";
|
|
3
|
+
import { createBranchDatabase, createEnvironmentVariable, deleteBranchDatabase, deleteEnvironmentVariable, listEnvironmentVariables, updateEnvironmentVariable } from "./preview-branch-database.js";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { ApiError, CancelledError, ComputeClient, streamLogs } from "@prisma/compute-sdk";
|
|
5
6
|
//#region src/lib/app/preview-provider.ts
|
|
@@ -48,6 +49,24 @@ function createPreviewAppProvider(client, options) {
|
|
|
48
49
|
role: branch.role
|
|
49
50
|
};
|
|
50
51
|
},
|
|
52
|
+
async createBranchDatabase(options) {
|
|
53
|
+
return createBranchDatabase(client, options);
|
|
54
|
+
},
|
|
55
|
+
async deleteBranchDatabase(options) {
|
|
56
|
+
return deleteBranchDatabase(client, options);
|
|
57
|
+
},
|
|
58
|
+
async listEnvironmentVariables(options) {
|
|
59
|
+
return listEnvironmentVariables(client, options);
|
|
60
|
+
},
|
|
61
|
+
async createEnvironmentVariable(options) {
|
|
62
|
+
return createEnvironmentVariable(client, options);
|
|
63
|
+
},
|
|
64
|
+
async updateEnvironmentVariable(options) {
|
|
65
|
+
return updateEnvironmentVariable(client, options);
|
|
66
|
+
},
|
|
67
|
+
async deleteEnvironmentVariable(options) {
|
|
68
|
+
return deleteEnvironmentVariable(client, options);
|
|
69
|
+
},
|
|
51
70
|
async removeApp(appId, options) {
|
|
52
71
|
const appResult = await sdk.showService({
|
|
53
72
|
serviceId: appId,
|
package/dist/presenters/app.js
CHANGED
|
@@ -30,6 +30,7 @@ function renderAppDeploy(context, descriptor, result) {
|
|
|
30
30
|
return [
|
|
31
31
|
`Live in ${formatDuration(result.durationMs)}`,
|
|
32
32
|
...result.deployment.url ? [context.ui.link(result.deployment.url)] : [],
|
|
33
|
+
...renderBranchDatabaseDeploySummary(context, result),
|
|
33
34
|
"",
|
|
34
35
|
...renderDeployOutputRows(context.ui, [{
|
|
35
36
|
label: "Logs",
|
|
@@ -41,6 +42,23 @@ function serializeAppDeploy(result) {
|
|
|
41
42
|
const { localPin: _localPin, ...serialized } = result;
|
|
42
43
|
return serialized;
|
|
43
44
|
}
|
|
45
|
+
function renderBranchDatabaseDeploySummary(context, result) {
|
|
46
|
+
if (!result.branchDatabase || result.branchDatabase.status !== "created") return [];
|
|
47
|
+
return ["", ...renderDeployOutputRows(context.ui, [
|
|
48
|
+
{
|
|
49
|
+
label: "Database",
|
|
50
|
+
value: result.branchDatabase.database?.name ?? "created"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
label: "Env",
|
|
54
|
+
value: result.branchDatabase.envVars.join(", ")
|
|
55
|
+
},
|
|
56
|
+
...result.branchDatabase.schema ? [{
|
|
57
|
+
label: "Schema",
|
|
58
|
+
value: result.branchDatabase.schema.command === "migrate-deploy" ? "prisma migrate deploy" : "prisma db push"
|
|
59
|
+
}] : []
|
|
60
|
+
])];
|
|
61
|
+
}
|
|
44
62
|
function formatDuration(durationMs) {
|
|
45
63
|
if (durationMs < 1e3) return `${durationMs}ms`;
|
|
46
64
|
return `${(durationMs / 1e3).toFixed(1)}s`;
|
|
@@ -1,111 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatColumns } from "../shell/ui.js";
|
|
2
|
+
import { formatDescriptorLabel } from "../shell/command-meta.js";
|
|
2
3
|
//#region src/presenters/branch.ts
|
|
3
4
|
function renderBranchList(context, descriptor, result) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
5
|
+
const ui = context.ui;
|
|
6
|
+
const lines = [`${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("→")} ${ui.dim("Listing branches for the resolved project.")}`, ""];
|
|
7
|
+
const rail = ui.dim("│");
|
|
8
|
+
lines.push(`${rail} ${ui.accent("project:")} ${result.projectName}`);
|
|
9
|
+
lines.push(rail);
|
|
10
|
+
if (result.branches.length === 0) {
|
|
11
|
+
lines.push(`${rail} ${ui.dim("No branches found.")}`);
|
|
12
|
+
return lines;
|
|
13
|
+
}
|
|
14
|
+
const widths = [
|
|
15
|
+
Math.max(4, ...result.branches.map((branch) => branch.name.length)),
|
|
16
|
+
Math.max(4, ...result.branches.map((branch) => branch.role.length)),
|
|
17
|
+
Math.max(7, ...result.branches.map((branch) => branch.envMap.length))
|
|
18
|
+
];
|
|
19
|
+
lines.push(`${rail} ${ui.accent(formatColumns([
|
|
20
|
+
"Name",
|
|
21
|
+
"Role",
|
|
22
|
+
"Env map"
|
|
23
|
+
], widths))}`);
|
|
24
|
+
for (const branch of result.branches) lines.push(`${rail} ${formatColumns([
|
|
25
|
+
branch.name,
|
|
26
|
+
branch.role,
|
|
27
|
+
branch.envMap
|
|
28
|
+
], widths)}`);
|
|
29
|
+
return lines;
|
|
19
30
|
}
|
|
20
31
|
function serializeBranchList(result) {
|
|
21
|
-
return serializeList({
|
|
22
|
-
context: { project: result.projectName ?? "not resolved" },
|
|
23
|
-
items: result.branches.map((branch) => ({
|
|
24
|
-
noun: "branch",
|
|
25
|
-
label: branch.name,
|
|
26
|
-
id: branch.id,
|
|
27
|
-
status: branch.active ? "active" : null
|
|
28
|
-
}))
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
function serializeBranchShow(result) {
|
|
32
32
|
return {
|
|
33
33
|
projectId: result.projectId,
|
|
34
34
|
projectName: result.projectName,
|
|
35
|
-
|
|
36
|
-
name: result.branch.name,
|
|
37
|
-
kind: result.branch.kind,
|
|
38
|
-
active: result.branch.active,
|
|
39
|
-
remoteState: result.branch.remoteState,
|
|
40
|
-
liveDeployment: result.branch.liveDeployment
|
|
41
|
-
}
|
|
35
|
+
branches: result.branches
|
|
42
36
|
};
|
|
43
37
|
}
|
|
44
|
-
function renderBranchShow(context, descriptor, result) {
|
|
45
|
-
const fields = [
|
|
46
|
-
{
|
|
47
|
-
key: "project",
|
|
48
|
-
value: result.projectName ?? "not resolved",
|
|
49
|
-
tone: result.projectName ? "default" : "dim"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
key: "branch",
|
|
53
|
-
value: result.branch.name,
|
|
54
|
-
tone: result.branch.active ? "success" : "default"
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
key: "kind",
|
|
58
|
-
value: result.branch.kind
|
|
59
|
-
}
|
|
60
|
-
];
|
|
61
|
-
if (result.branch.liveDeployment) {
|
|
62
|
-
fields.push({
|
|
63
|
-
key: "status",
|
|
64
|
-
value: result.branch.liveDeployment.status,
|
|
65
|
-
tone: toneForDeploymentStatus(result.branch.liveDeployment.status)
|
|
66
|
-
});
|
|
67
|
-
if (result.branch.liveDeployment.url) fields.push({
|
|
68
|
-
key: "url",
|
|
69
|
-
value: result.branch.liveDeployment.url,
|
|
70
|
-
tone: "link"
|
|
71
|
-
});
|
|
72
|
-
} else if (!result.branch.remoteState) fields.push({
|
|
73
|
-
key: "remote state",
|
|
74
|
-
value: "not created yet",
|
|
75
|
-
tone: "dim"
|
|
76
|
-
});
|
|
77
|
-
return renderShow({
|
|
78
|
-
title: "Showing the current active branch context.",
|
|
79
|
-
descriptor,
|
|
80
|
-
fields
|
|
81
|
-
}, context.ui);
|
|
82
|
-
}
|
|
83
|
-
function toneForDeploymentStatus(status) {
|
|
84
|
-
if (status === "ready" || status === "active" || status === "healthy") return "success";
|
|
85
|
-
if (status === "pending" || status === "building" || status === "starting") return "warning";
|
|
86
|
-
if (status === "failed" || status === "error") return "error";
|
|
87
|
-
return "default";
|
|
88
|
-
}
|
|
89
|
-
function renderBranchUse(context, descriptor, result) {
|
|
90
|
-
return renderMutate({
|
|
91
|
-
title: "Changing the local default branch context.",
|
|
92
|
-
descriptor,
|
|
93
|
-
context: [{
|
|
94
|
-
key: "project",
|
|
95
|
-
value: result.projectName ?? "not resolved",
|
|
96
|
-
tone: result.projectName ? "default" : "dim"
|
|
97
|
-
}, {
|
|
98
|
-
key: "branch",
|
|
99
|
-
value: result.branch.name
|
|
100
|
-
}],
|
|
101
|
-
operationDescription: "Applying active branch change",
|
|
102
|
-
operationCount: 1,
|
|
103
|
-
details: ["Active branch updated in local CLI state for this repo."],
|
|
104
|
-
alerts: result.branch.kind === "production" ? [{
|
|
105
|
-
tone: "warning",
|
|
106
|
-
text: "Production is protected and durable. Use with care"
|
|
107
|
-
}] : void 0
|
|
108
|
-
}, context.ui);
|
|
109
|
-
}
|
|
110
38
|
//#endregion
|
|
111
|
-
export { renderBranchList,
|
|
39
|
+
export { renderBranchList, serializeBranchList };
|
|
@@ -69,8 +69,8 @@ const DESCRIPTORS = [
|
|
|
69
69
|
{
|
|
70
70
|
id: "branch",
|
|
71
71
|
path: ["prisma", "branch"],
|
|
72
|
-
description: "View your
|
|
73
|
-
examples: ["prisma-cli branch list"
|
|
72
|
+
description: "View your Platform branches",
|
|
73
|
+
examples: ["prisma-cli branch list"]
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
id: "git",
|
|
@@ -153,29 +153,9 @@ const DESCRIPTORS = [
|
|
|
153
153
|
"branch",
|
|
154
154
|
"list"
|
|
155
155
|
],
|
|
156
|
-
description: "List
|
|
156
|
+
description: "List Platform branches for the resolved project",
|
|
157
157
|
examples: ["prisma-cli branch list", "prisma-cli branch list --json"]
|
|
158
158
|
},
|
|
159
|
-
{
|
|
160
|
-
id: "branch.show",
|
|
161
|
-
path: [
|
|
162
|
-
"prisma",
|
|
163
|
-
"branch",
|
|
164
|
-
"show"
|
|
165
|
-
],
|
|
166
|
-
description: "Show the Platform branch matching your current Git branch",
|
|
167
|
-
examples: ["prisma-cli branch show", "prisma-cli branch show --json"]
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
id: "branch.use",
|
|
171
|
-
path: [
|
|
172
|
-
"prisma",
|
|
173
|
-
"branch",
|
|
174
|
-
"use"
|
|
175
|
-
],
|
|
176
|
-
description: "Change the local default branch context.",
|
|
177
|
-
examples: ["prisma-cli branch use", "prisma-cli branch use production"]
|
|
178
|
-
},
|
|
179
159
|
{
|
|
180
160
|
id: "app.build",
|
|
181
161
|
path: [
|
|
@@ -214,6 +194,8 @@ const DESCRIPTORS = [
|
|
|
214
194
|
"prisma-cli app deploy --project proj_123",
|
|
215
195
|
"prisma-cli app deploy --create-project my-app --yes",
|
|
216
196
|
"prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example",
|
|
197
|
+
"prisma-cli app deploy --db",
|
|
198
|
+
"prisma-cli app deploy --db --yes",
|
|
217
199
|
"prisma-cli app deploy --app my-app --framework nextjs --http-port 3000",
|
|
218
200
|
"prisma-cli app deploy --branch feat-login --framework hono",
|
|
219
201
|
"prisma-cli app deploy --prod --yes",
|
package/dist/shell/ui.js
CHANGED
|
@@ -48,6 +48,9 @@ function renderNextSteps(steps) {
|
|
|
48
48
|
...steps.map((step) => `- ${step}`)
|
|
49
49
|
];
|
|
50
50
|
}
|
|
51
|
+
function formatColumns(columns, widths) {
|
|
52
|
+
return columns.map((value, index) => padDisplay(value, widths[index])).join(" ").trimEnd();
|
|
53
|
+
}
|
|
51
54
|
function wrapText(text, width, indent = "") {
|
|
52
55
|
return wrapAnsi(text, width, {
|
|
53
56
|
hard: false,
|
|
@@ -74,4 +77,4 @@ function formatHeaderValue(ui, row) {
|
|
|
74
77
|
return value;
|
|
75
78
|
}
|
|
76
79
|
//#endregion
|
|
77
|
-
export { createShellUi, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, wrapText };
|
|
80
|
+
export { createShellUi, formatColumns, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, wrapText };
|
package/dist/use-cases/branch.js
CHANGED
|
@@ -1,34 +1,19 @@
|
|
|
1
1
|
//#region src/use-cases/branch.ts
|
|
2
2
|
function createBranchUseCases(dependencies) {
|
|
3
|
-
return {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
projectId,
|
|
18
|
-
projectName: resolveProjectName(dependencies.projectGateway, projectId),
|
|
19
|
-
branch: buildBranchDetail(dependencies.branchGateway, projectId, activeBranch)
|
|
20
|
-
};
|
|
21
|
-
},
|
|
22
|
-
use: async (branchName) => {
|
|
23
|
-
await dependencies.branchStateGateway.writeActiveBranch(branchName);
|
|
24
|
-
const projectId = await dependencies.projectStateGateway.readRememberedProjectId();
|
|
25
|
-
return {
|
|
26
|
-
projectId,
|
|
27
|
-
projectName: resolveProjectName(dependencies.projectGateway, projectId),
|
|
28
|
-
branch: buildBranchDetail(dependencies.branchGateway, projectId, branchName)
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
};
|
|
3
|
+
return { list: async () => {
|
|
4
|
+
const projectId = await dependencies.projectStateGateway.readRememberedProjectId();
|
|
5
|
+
if (!projectId) return {
|
|
6
|
+
projectId: "",
|
|
7
|
+
projectName: "not resolved",
|
|
8
|
+
branches: []
|
|
9
|
+
};
|
|
10
|
+
const remoteBranches = await listRemoteBranches(dependencies.branchGateway, projectId);
|
|
11
|
+
return {
|
|
12
|
+
projectId,
|
|
13
|
+
projectName: resolveProjectName(dependencies.projectGateway, projectId) ?? "not resolved",
|
|
14
|
+
branches: buildBranchSummaries(remoteBranches)
|
|
15
|
+
};
|
|
16
|
+
} };
|
|
32
17
|
}
|
|
33
18
|
function resolveProjectName(projectGateway, projectId) {
|
|
34
19
|
if (!projectId) return null;
|
|
@@ -38,38 +23,13 @@ async function listRemoteBranches(branchGateway, projectId) {
|
|
|
38
23
|
if (!projectId) return [];
|
|
39
24
|
return branchGateway.listBranchesForProject(projectId);
|
|
40
25
|
}
|
|
41
|
-
function buildBranchSummaries(
|
|
42
|
-
|
|
43
|
-
for (const branch of remoteBranches) byName.set(branch.name, {
|
|
26
|
+
function buildBranchSummaries(remoteBranches) {
|
|
27
|
+
return sortBranches(remoteBranches.map((branch) => ({
|
|
44
28
|
id: branch.id,
|
|
45
29
|
name: branch.name,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
50
|
-
if (!byName.has(activeBranch)) byName.set(activeBranch, {
|
|
51
|
-
id: activeBranch,
|
|
52
|
-
name: activeBranch,
|
|
53
|
-
kind: toBranchKind(activeBranch),
|
|
54
|
-
active: true,
|
|
55
|
-
remoteState: false
|
|
56
|
-
});
|
|
57
|
-
return sortBranches([...byName.values()]);
|
|
58
|
-
}
|
|
59
|
-
function buildBranchDetail(branchGateway, projectId, branchName) {
|
|
60
|
-
const kind = toBranchKind(branchName);
|
|
61
|
-
const remoteBranch = projectId ? branchGateway.getBranchForProject(projectId, branchName) : void 0;
|
|
62
|
-
return {
|
|
63
|
-
name: branchName,
|
|
64
|
-
kind,
|
|
65
|
-
active: true,
|
|
66
|
-
remoteState: Boolean(remoteBranch),
|
|
67
|
-
liveDeployment: remoteBranch && remoteBranch.currentDeploymentId ? toLiveDeployment(branchGateway.getDeployment(remoteBranch.currentDeploymentId)) : null
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function toBranchKind(name) {
|
|
71
|
-
if (name === "production") return "production";
|
|
72
|
-
return "preview";
|
|
30
|
+
role: branch.role,
|
|
31
|
+
envMap: branch.role
|
|
32
|
+
})));
|
|
73
33
|
}
|
|
74
34
|
function sortBranches(branches) {
|
|
75
35
|
return branches.slice().sort((left, right) => {
|
|
@@ -80,16 +40,8 @@ function sortBranches(branches) {
|
|
|
80
40
|
});
|
|
81
41
|
}
|
|
82
42
|
function branchOrder(branch) {
|
|
83
|
-
if (branch.
|
|
43
|
+
if (branch.role === "production") return 0;
|
|
84
44
|
return 1;
|
|
85
45
|
}
|
|
86
|
-
function toLiveDeployment(deployment) {
|
|
87
|
-
if (!deployment) return null;
|
|
88
|
-
return {
|
|
89
|
-
id: deployment.id,
|
|
90
|
-
status: deployment.status,
|
|
91
|
-
url: deployment.url
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
46
|
//#endregion
|
|
95
47
|
export { createBranchUseCases };
|