@prisma/cli 3.0.0-dev.69.1 → 3.0.0-dev.70.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/controllers/app.js
CHANGED
|
@@ -17,6 +17,7 @@ import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, re
|
|
|
17
17
|
import { bindProjectToDirectory, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
18
18
|
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
19
19
|
import { readLocalGitBranch } from "../lib/git/local-branch.js";
|
|
20
|
+
import { resolveOrCreatePreviewBuildSettings } from "../lib/app/preview-build-settings.js";
|
|
20
21
|
import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
|
|
21
22
|
import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
|
|
22
23
|
import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
|
|
@@ -170,6 +171,12 @@ async function runAppDeploy(context, appName, options) {
|
|
|
170
171
|
const buildType = framework.buildType;
|
|
171
172
|
assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
|
|
172
173
|
const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint, context.runtime.signal);
|
|
174
|
+
const buildSettingsResolution = await resolveOrCreatePreviewBuildSettings({
|
|
175
|
+
appPath: context.runtime.cwd,
|
|
176
|
+
buildType,
|
|
177
|
+
signal: context.runtime.signal
|
|
178
|
+
});
|
|
179
|
+
maybeRenderDeployBuildSettings(context, buildSettingsResolution);
|
|
173
180
|
const portMapping = parseDeployPortMapping(String(runtime.port));
|
|
174
181
|
const branchDatabaseSetup = await maybeSetupBranchDatabase(context, provider, projectId, toBranchDatabaseDeployBranch(target.branch), {
|
|
175
182
|
db: options?.db,
|
|
@@ -186,6 +193,7 @@ async function runAppDeploy(context, appName, options) {
|
|
|
186
193
|
region: selectedApp.region,
|
|
187
194
|
entrypoint,
|
|
188
195
|
buildType,
|
|
196
|
+
buildSettings: buildSettingsResolution.settings,
|
|
189
197
|
portMapping,
|
|
190
198
|
envVars,
|
|
191
199
|
interaction: void 0,
|
|
@@ -214,6 +222,18 @@ async function runAppDeploy(context, appName, options) {
|
|
|
214
222
|
},
|
|
215
223
|
deployment: deployResult.deployment,
|
|
216
224
|
deploySettings: {
|
|
225
|
+
config: {
|
|
226
|
+
path: buildSettingsResolution.relativeConfigPath,
|
|
227
|
+
status: buildSettingsResolution.status
|
|
228
|
+
},
|
|
229
|
+
buildCommand: {
|
|
230
|
+
value: buildSettingsResolution.settings.buildCommand,
|
|
231
|
+
source: buildSettingsResolution.settings.buildCommandSource
|
|
232
|
+
},
|
|
233
|
+
outputDirectory: {
|
|
234
|
+
value: buildSettingsResolution.settings.outputDirectory,
|
|
235
|
+
source: buildSettingsResolution.settings.outputDirectorySource
|
|
236
|
+
},
|
|
217
237
|
framework: {
|
|
218
238
|
key: framework.key,
|
|
219
239
|
buildType,
|
|
@@ -1603,7 +1623,6 @@ async function detectNextConfig(cwd, signal) {
|
|
|
1603
1623
|
for (const candidate of [
|
|
1604
1624
|
"next.config.js",
|
|
1605
1625
|
"next.config.mjs",
|
|
1606
|
-
"next.config.cjs",
|
|
1607
1626
|
"next.config.ts",
|
|
1608
1627
|
"next.config.mts"
|
|
1609
1628
|
]) {
|
|
@@ -1696,6 +1715,20 @@ async function maybeRenderDeploySetupBlock(context, details) {
|
|
|
1696
1715
|
const prefix = details.includeDirectory ? `Deploying ${directory} to` : "Deploying to";
|
|
1697
1716
|
context.output.stderr.write(`${prefix} ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
|
|
1698
1717
|
}
|
|
1718
|
+
function maybeRenderDeployBuildSettings(context, resolution) {
|
|
1719
|
+
if (context.flags.json || context.flags.quiet) return;
|
|
1720
|
+
const settings = resolution.settings;
|
|
1721
|
+
const title = resolution.status === "created" ? `Created ${resolution.relativeConfigPath}` : `Using ${resolution.relativeConfigPath}`;
|
|
1722
|
+
context.output.stderr.write(`${title}\n${renderDeployOutputRows(context.ui, [{
|
|
1723
|
+
label: "Build Command",
|
|
1724
|
+
value: settings.buildCommand ?? "none",
|
|
1725
|
+
origin: settings.buildCommandSource ?? void 0
|
|
1726
|
+
}, {
|
|
1727
|
+
label: "Output Directory",
|
|
1728
|
+
value: settings.outputDirectory,
|
|
1729
|
+
origin: settings.outputDirectorySource ?? void 0
|
|
1730
|
+
}]).join("\n")}\n\n`);
|
|
1731
|
+
}
|
|
1699
1732
|
function maybeRenderProjectLinked(context, directory, projectName, localPinPath) {
|
|
1700
1733
|
if (context.flags.json || context.flags.quiet) return;
|
|
1701
1734
|
context.output.stderr.write(`${context.ui.success("✔")} Linked "${directory}" to Project "${projectName}"\nSaved ${localPinPath}\n\n`);
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { CliError } from "../../shell/errors.js";
|
|
2
|
+
import { readBunPackageJson } from "./bun-project.js";
|
|
3
|
+
import { readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { exec } from "node:child_process";
|
|
6
|
+
import { parseModule } from "magicast";
|
|
7
|
+
//#region src/lib/app/preview-build-settings.ts
|
|
8
|
+
const PRISMA_APP_CONFIG_FILENAME = "prisma.app.json";
|
|
9
|
+
const PRISMA_APP_CONFIG_SCHEMA_URL = "https://pris.ly/schemas/prisma-app-config.v1.json";
|
|
10
|
+
async function resolveOrCreatePreviewBuildSettings(options) {
|
|
11
|
+
const configPath = path.join(options.appPath, PRISMA_APP_CONFIG_FILENAME);
|
|
12
|
+
const existing = await readPreviewBuildSettingsConfig(configPath, options.signal);
|
|
13
|
+
if (existing) return {
|
|
14
|
+
status: "used",
|
|
15
|
+
configPath,
|
|
16
|
+
relativeConfigPath: PRISMA_APP_CONFIG_FILENAME,
|
|
17
|
+
settings: {
|
|
18
|
+
buildCommand: existing.buildCommand,
|
|
19
|
+
buildCommandSource: null,
|
|
20
|
+
outputDirectory: existing.outputDirectory,
|
|
21
|
+
outputDirectorySource: null
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const settings = await resolvePreviewBuildSettings(options);
|
|
25
|
+
const config = {
|
|
26
|
+
$schema: PRISMA_APP_CONFIG_SCHEMA_URL,
|
|
27
|
+
buildCommand: settings.buildCommand,
|
|
28
|
+
outputDirectory: settings.outputDirectory
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, {
|
|
32
|
+
encoding: "utf8",
|
|
33
|
+
flag: "wx",
|
|
34
|
+
signal: options.signal
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error.code === "EEXIST") {
|
|
38
|
+
const raced = await readPreviewBuildSettingsConfig(configPath, options.signal);
|
|
39
|
+
if (raced) return {
|
|
40
|
+
status: "used",
|
|
41
|
+
configPath,
|
|
42
|
+
relativeConfigPath: PRISMA_APP_CONFIG_FILENAME,
|
|
43
|
+
settings: {
|
|
44
|
+
buildCommand: raced.buildCommand,
|
|
45
|
+
buildCommandSource: null,
|
|
46
|
+
outputDirectory: raced.outputDirectory,
|
|
47
|
+
outputDirectorySource: null
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
status: "created",
|
|
55
|
+
configPath,
|
|
56
|
+
relativeConfigPath: PRISMA_APP_CONFIG_FILENAME,
|
|
57
|
+
settings
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async function resolvePreviewBuildSettings(options) {
|
|
61
|
+
switch (options.buildType) {
|
|
62
|
+
case "nextjs": {
|
|
63
|
+
const packageJson = await readBunPackageJson(options.appPath, options.signal);
|
|
64
|
+
const buildCommand = await resolveFrameworkBuildCommand(options.appPath, packageJson, {
|
|
65
|
+
command: "next build",
|
|
66
|
+
source: "Next.js default",
|
|
67
|
+
signal: options.signal
|
|
68
|
+
});
|
|
69
|
+
const outputRoot = await resolveNextOutputRoot(options.appPath, options.signal);
|
|
70
|
+
return {
|
|
71
|
+
buildCommand: buildCommand.command,
|
|
72
|
+
buildCommandSource: buildCommand.source,
|
|
73
|
+
outputDirectory: joinPosix(outputRoot, "standalone"),
|
|
74
|
+
outputDirectorySource: outputRoot === ".next" ? "Next.js output" : "next.config distDir"
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
case "tanstack-start": {
|
|
78
|
+
const packageJson = await readBunPackageJson(options.appPath, options.signal);
|
|
79
|
+
const buildCommand = await resolveFrameworkBuildCommand(options.appPath, packageJson, {
|
|
80
|
+
command: "vite build",
|
|
81
|
+
source: "TanStack Start default",
|
|
82
|
+
signal: options.signal
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
buildCommand: buildCommand.command,
|
|
86
|
+
buildCommandSource: buildCommand.source,
|
|
87
|
+
outputDirectory: ".output",
|
|
88
|
+
outputDirectorySource: "TanStack Start output"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
case "bun": {
|
|
92
|
+
const packageJson = await readBunPackageJson(options.appPath, options.signal);
|
|
93
|
+
const buildCommand = await resolveFrameworkBuildCommand(options.appPath, packageJson, {
|
|
94
|
+
command: null,
|
|
95
|
+
source: null,
|
|
96
|
+
signal: options.signal
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
buildCommand: buildCommand.command,
|
|
100
|
+
buildCommandSource: buildCommand.source,
|
|
101
|
+
outputDirectory: ".",
|
|
102
|
+
outputDirectorySource: "app root"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function readPreviewBuildSettingsConfig(configPath, signal) {
|
|
108
|
+
let content;
|
|
109
|
+
try {
|
|
110
|
+
content = await readFile(configPath, {
|
|
111
|
+
encoding: "utf8",
|
|
112
|
+
signal
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error.code === "ENOENT") return null;
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
let parsed;
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(content);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw invalidPrismaAppConfigError(configPath, `The file is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
123
|
+
}
|
|
124
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw invalidPrismaAppConfigError(configPath, "The file must contain a JSON object.");
|
|
125
|
+
const raw = parsed;
|
|
126
|
+
if ("$schema" in raw && raw.$schema !== void 0 && typeof raw.$schema !== "string") throw invalidPrismaAppConfigError(configPath, "The $schema field must be a string when present.");
|
|
127
|
+
if (raw.buildCommand !== null && typeof raw.buildCommand !== "string") throw invalidPrismaAppConfigError(configPath, "The buildCommand field must be a string or null.");
|
|
128
|
+
let buildCommand = null;
|
|
129
|
+
if (typeof raw.buildCommand === "string") {
|
|
130
|
+
buildCommand = raw.buildCommand.trim();
|
|
131
|
+
if (buildCommand.length === 0) throw invalidPrismaAppConfigError(configPath, "The buildCommand field must not be an empty string. Use null to skip the build step.");
|
|
132
|
+
}
|
|
133
|
+
const outputDirectory = normalizeConfigOutputDirectory(configPath, raw.outputDirectory);
|
|
134
|
+
return {
|
|
135
|
+
buildCommand,
|
|
136
|
+
outputDirectory
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function normalizeConfigOutputDirectory(configPath, value) {
|
|
140
|
+
if (typeof value !== "string" || value.trim().length === 0) throw invalidPrismaAppConfigError(configPath, "The outputDirectory field must be a non-empty string.");
|
|
141
|
+
const normalized = normalizeRelativePath(value);
|
|
142
|
+
if (!normalized) throw invalidPrismaAppConfigError(configPath, "The outputDirectory field must be a relative path inside the app directory.");
|
|
143
|
+
return normalized;
|
|
144
|
+
}
|
|
145
|
+
function invalidPrismaAppConfigError(configPath, why) {
|
|
146
|
+
return new CliError({
|
|
147
|
+
code: "APP_CONFIG_INVALID",
|
|
148
|
+
domain: "app",
|
|
149
|
+
summary: `Invalid ${PRISMA_APP_CONFIG_FILENAME}`,
|
|
150
|
+
why,
|
|
151
|
+
fix: `Edit ${PRISMA_APP_CONFIG_FILENAME} so buildCommand is a string or null and outputDirectory is a relative path inside the app root. Delete the file and rerun prisma-cli app deploy to regenerate defaults.`,
|
|
152
|
+
where: configPath,
|
|
153
|
+
meta: { configPath },
|
|
154
|
+
exitCode: 2,
|
|
155
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async function hasRootFile(appPath, filenames, signal) {
|
|
159
|
+
let entries;
|
|
160
|
+
try {
|
|
161
|
+
signal?.throwIfAborted();
|
|
162
|
+
entries = await readdir(appPath);
|
|
163
|
+
signal?.throwIfAborted();
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (signal?.aborted) throw error;
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
return entries.some((entry) => filenames.includes(entry));
|
|
169
|
+
}
|
|
170
|
+
function hasPackageDependency(packageJson, dependencyName) {
|
|
171
|
+
return hasAnyPackageDependency(packageJson, [dependencyName]);
|
|
172
|
+
}
|
|
173
|
+
function hasAnyPackageDependency(packageJson, dependencyNames) {
|
|
174
|
+
if (!packageJson) return false;
|
|
175
|
+
return [packageJson.dependencies, packageJson.devDependencies].some((group) => {
|
|
176
|
+
if (!group || typeof group !== "object") return false;
|
|
177
|
+
return dependencyNames.some((dependencyName) => dependencyName in group);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async function resolveFrameworkBuildCommand(appPath, packageJson, fallback) {
|
|
181
|
+
const buildScript = readBuildScript(packageJson);
|
|
182
|
+
if (buildScript) {
|
|
183
|
+
const packageManager = await resolvePackageManager(appPath, packageJson, fallback.signal);
|
|
184
|
+
if (!packageManager) return {
|
|
185
|
+
command: buildScript,
|
|
186
|
+
source: "package.json scripts.build"
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
command: `${packageManager} run build`,
|
|
190
|
+
source: "package.json scripts.build"
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
command: fallback.command,
|
|
195
|
+
source: fallback.source
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function readBuildScript(packageJson) {
|
|
199
|
+
if (!packageJson?.scripts || typeof packageJson.scripts !== "object") return null;
|
|
200
|
+
const scripts = packageJson.scripts;
|
|
201
|
+
if (typeof scripts.build !== "string") return null;
|
|
202
|
+
const buildScript = scripts.build.trim();
|
|
203
|
+
return buildScript.length > 0 ? buildScript : null;
|
|
204
|
+
}
|
|
205
|
+
async function resolvePackageManager(appPath, packageJson, signal) {
|
|
206
|
+
const fromPackageManager = packageManagerFromPackageJson(packageJson?.packageManager);
|
|
207
|
+
if (fromPackageManager) return fromPackageManager;
|
|
208
|
+
if (await pathExists(path.join(appPath, "bun.lock"), signal) || await pathExists(path.join(appPath, "bun.lockb"), signal)) return "bun";
|
|
209
|
+
if (await pathExists(path.join(appPath, "pnpm-lock.yaml"), signal)) return "pnpm";
|
|
210
|
+
if (await pathExists(path.join(appPath, "yarn.lock"), signal)) return "yarn";
|
|
211
|
+
if (await pathExists(path.join(appPath, "package-lock.json"), signal)) return "npm";
|
|
212
|
+
}
|
|
213
|
+
function packageManagerFromPackageJson(value) {
|
|
214
|
+
if (typeof value !== "string") return null;
|
|
215
|
+
const name = value.split("@")[0];
|
|
216
|
+
return name === "bun" || name === "pnpm" || name === "yarn" || name === "npm" ? name : null;
|
|
217
|
+
}
|
|
218
|
+
async function runResolvedBuildCommand(appPath, settings, failurePrefix, signal) {
|
|
219
|
+
if (!settings.buildCommand) return;
|
|
220
|
+
await execBuildCommand(settings.buildCommand, appPath, failurePrefix, signal);
|
|
221
|
+
}
|
|
222
|
+
function execBuildCommand(command, cwd, failurePrefix, signal) {
|
|
223
|
+
return new Promise((resolve, reject) => {
|
|
224
|
+
const child = exec(command, {
|
|
225
|
+
cwd,
|
|
226
|
+
env: {
|
|
227
|
+
...process.env,
|
|
228
|
+
PATH: [path.join(cwd, "node_modules", ".bin"), process.env.PATH].filter(Boolean).join(path.delimiter)
|
|
229
|
+
},
|
|
230
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
231
|
+
signal
|
|
232
|
+
}, (error, stdout, stderr) => {
|
|
233
|
+
if (error) {
|
|
234
|
+
const output = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
|
|
235
|
+
reject(/* @__PURE__ */ new Error(`${failurePrefix} failed:\n${output || error.message}`));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
resolve();
|
|
239
|
+
});
|
|
240
|
+
if (signal?.aborted) child.kill();
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async function resolveNextOutputRoot(appPath, signal) {
|
|
244
|
+
return (await readNextConfig(appPath, signal)).distDir ?? ".next";
|
|
245
|
+
}
|
|
246
|
+
async function readNextConfig(appPath, signal) {
|
|
247
|
+
for (const fileName of NEXT_CONFIG_FILENAMES) {
|
|
248
|
+
const filePath = path.join(appPath, fileName);
|
|
249
|
+
let content;
|
|
250
|
+
try {
|
|
251
|
+
content = await readFile(filePath, {
|
|
252
|
+
encoding: "utf8",
|
|
253
|
+
signal
|
|
254
|
+
});
|
|
255
|
+
} catch (error) {
|
|
256
|
+
if (error.code === "ENOENT") continue;
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
return readStaticNextConfig(content);
|
|
260
|
+
}
|
|
261
|
+
return {};
|
|
262
|
+
}
|
|
263
|
+
const NEXT_CONFIG_FILENAMES = [
|
|
264
|
+
"next.config.js",
|
|
265
|
+
"next.config.mjs",
|
|
266
|
+
"next.config.ts",
|
|
267
|
+
"next.config.mts"
|
|
268
|
+
];
|
|
269
|
+
function readStaticNextConfig(content) {
|
|
270
|
+
try {
|
|
271
|
+
const program = asAstNode(parseModule(content).$ast);
|
|
272
|
+
const bindings = program ? collectStaticBindings(program) : /* @__PURE__ */ new Map();
|
|
273
|
+
const configObject = program ? findExportedConfigObject(program, bindings) : null;
|
|
274
|
+
if (!configObject) return {};
|
|
275
|
+
const rawDistDir = readStaticStringProperty(configObject, "distDir");
|
|
276
|
+
const output = readStaticStringProperty(configObject, "output");
|
|
277
|
+
return {
|
|
278
|
+
distDir: rawDistDir ? normalizeRelativePath(rawDistDir) : void 0,
|
|
279
|
+
output: output === "standalone" || output === "export" ? output : void 0
|
|
280
|
+
};
|
|
281
|
+
} catch {
|
|
282
|
+
return {};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function joinPosix(...parts) {
|
|
286
|
+
return parts.join("/").replace(/\/+/g, "/");
|
|
287
|
+
}
|
|
288
|
+
function nextOutputRootFromStandaloneDirectory(outputDirectory) {
|
|
289
|
+
const normalized = outputDirectory.replace(/\/+$/g, "");
|
|
290
|
+
if (normalized === "standalone") return ".";
|
|
291
|
+
if (normalized.endsWith("/standalone")) {
|
|
292
|
+
const outputRoot = normalized.slice(0, -11);
|
|
293
|
+
return outputRoot.length > 0 ? outputRoot : ".";
|
|
294
|
+
}
|
|
295
|
+
const dirname = path.posix.dirname(normalized);
|
|
296
|
+
return dirname === "." ? "." : dirname;
|
|
297
|
+
}
|
|
298
|
+
function asAstNode(value) {
|
|
299
|
+
if (!value || typeof value !== "object") return null;
|
|
300
|
+
return typeof value.type === "string" ? value : null;
|
|
301
|
+
}
|
|
302
|
+
function astNodes(value) {
|
|
303
|
+
if (!Array.isArray(value)) return [];
|
|
304
|
+
return value.map(asAstNode).filter((node) => Boolean(node));
|
|
305
|
+
}
|
|
306
|
+
function collectStaticBindings(program) {
|
|
307
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
308
|
+
for (const statement of astNodes(program.body)) {
|
|
309
|
+
if (statement.type !== "VariableDeclaration") continue;
|
|
310
|
+
for (const declaration of astNodes(statement.declarations)) {
|
|
311
|
+
const id = asAstNode(declaration.id);
|
|
312
|
+
const init = asAstNode(declaration.init);
|
|
313
|
+
if (id?.type === "Identifier" && typeof id.name === "string" && init) bindings.set(id.name, init);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return bindings;
|
|
317
|
+
}
|
|
318
|
+
function findExportedConfigObject(program, bindings) {
|
|
319
|
+
for (const statement of astNodes(program.body)) {
|
|
320
|
+
if (statement.type === "ExportDefaultDeclaration") return resolveConfigObject(statement.declaration, bindings);
|
|
321
|
+
if (statement.type !== "ExpressionStatement") continue;
|
|
322
|
+
const expression = asAstNode(statement.expression);
|
|
323
|
+
if (expression?.type !== "AssignmentExpression" || expression.operator !== "=") continue;
|
|
324
|
+
if (isModuleExports(expression.left)) return resolveConfigObject(expression.right, bindings);
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
function resolveConfigObject(value, bindings, depth = 0) {
|
|
329
|
+
if (depth > 4) return null;
|
|
330
|
+
const node = unwrapStaticExpression(asAstNode(value));
|
|
331
|
+
if (!node) return null;
|
|
332
|
+
if (node.type === "ObjectExpression") return node;
|
|
333
|
+
if (node.type === "Identifier" && typeof node.name === "string") return resolveConfigObject(bindings.get(node.name), bindings, depth + 1);
|
|
334
|
+
if (node.type === "CallExpression") return resolveConfigObject(astNodes(node.arguments)[0], bindings, depth + 1);
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
function unwrapStaticExpression(node) {
|
|
338
|
+
let current = node;
|
|
339
|
+
while (current?.type === "TSAsExpression" || current?.type === "TSSatisfiesExpression" || current?.type === "TSNonNullExpression") current = asAstNode(current.expression);
|
|
340
|
+
return current;
|
|
341
|
+
}
|
|
342
|
+
function isModuleExports(value) {
|
|
343
|
+
const node = asAstNode(value);
|
|
344
|
+
if (node?.type !== "MemberExpression" || node.computed === true) return false;
|
|
345
|
+
const object = asAstNode(node.object);
|
|
346
|
+
const property = asAstNode(node.property);
|
|
347
|
+
return object?.type === "Identifier" && object.name === "module" && property?.type === "Identifier" && property.name === "exports";
|
|
348
|
+
}
|
|
349
|
+
function readStaticStringProperty(objectExpression, propertyName) {
|
|
350
|
+
for (const property of astNodes(objectExpression.properties)) {
|
|
351
|
+
if (property.type !== "ObjectProperty" || property.computed === true) continue;
|
|
352
|
+
if (propertyKeyName(property.key) !== propertyName) continue;
|
|
353
|
+
const value = unwrapStaticExpression(asAstNode(property.value));
|
|
354
|
+
if (value?.type === "StringLiteral" && typeof value.value === "string") {
|
|
355
|
+
const trimmed = value.value.trim();
|
|
356
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function propertyKeyName(value) {
|
|
361
|
+
const key = asAstNode(value);
|
|
362
|
+
if (key?.type === "Identifier" && typeof key.name === "string") return key.name;
|
|
363
|
+
if (key?.type === "StringLiteral" && typeof key.value === "string") return key.value;
|
|
364
|
+
}
|
|
365
|
+
function normalizeRelativePath(value) {
|
|
366
|
+
const raw = value.trim().replace(/\\/g, "/");
|
|
367
|
+
if (raw.length === 0 || raw.split("/").includes("..")) return;
|
|
368
|
+
const normalized = path.posix.normalize(raw);
|
|
369
|
+
const segments = normalized.split("/");
|
|
370
|
+
if (path.win32.isAbsolute(value) || path.posix.isAbsolute(normalized) || segments.includes("..")) return;
|
|
371
|
+
return normalized === "." ? "." : normalized;
|
|
372
|
+
}
|
|
373
|
+
async function pathExists(targetPath, signal) {
|
|
374
|
+
try {
|
|
375
|
+
signal?.throwIfAborted();
|
|
376
|
+
await stat(targetPath);
|
|
377
|
+
signal?.throwIfAborted();
|
|
378
|
+
return true;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
if (signal?.aborted) throw error;
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
export { PRISMA_APP_CONFIG_FILENAME, PRISMA_APP_CONFIG_SCHEMA_URL, hasAnyPackageDependency, hasPackageDependency, hasRootFile, joinPosix, nextOutputRootFromStandaloneDirectory, resolveOrCreatePreviewBuildSettings, resolvePreviewBuildSettings, runResolvedBuildCommand };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { resolveBunEntrypoint } from "./bun-project.js";
|
|
2
|
-
import {
|
|
1
|
+
import { readBunPackageJson, resolveBunEntrypoint } from "./bun-project.js";
|
|
2
|
+
import { PRISMA_APP_CONFIG_FILENAME, hasAnyPackageDependency, hasPackageDependency, hasRootFile, joinPosix, nextOutputRootFromStandaloneDirectory, resolvePreviewBuildSettings, runResolvedBuildCommand } from "./preview-build-settings.js";
|
|
3
|
+
import { chmod, copyFile, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, rm, stat } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import { AstroBuild, BunBuild, NuxtBuild } from "@prisma/compute-sdk";
|
|
5
7
|
//#region src/lib/app/preview-build.ts
|
|
6
8
|
const PREVIEW_BUILD_TYPES = [
|
|
7
9
|
"auto",
|
|
@@ -17,18 +19,21 @@ var PreviewBuildStrategy = class {
|
|
|
17
19
|
#entrypoint;
|
|
18
20
|
#buildType;
|
|
19
21
|
#signal;
|
|
22
|
+
#buildSettings;
|
|
20
23
|
constructor(options) {
|
|
21
24
|
this.#appPath = options.appPath;
|
|
22
25
|
this.#entrypoint = options.entrypoint;
|
|
23
26
|
this.#buildType = options.buildType ?? "auto";
|
|
24
27
|
this.#signal = options.signal;
|
|
28
|
+
this.#buildSettings = options.buildSettings;
|
|
25
29
|
}
|
|
26
30
|
async canBuild(signal = this.#signal) {
|
|
27
31
|
const { strategy } = await resolvePreviewBuildStrategy({
|
|
28
32
|
appPath: this.#appPath,
|
|
29
33
|
entrypoint: this.#entrypoint,
|
|
30
34
|
buildType: this.#buildType,
|
|
31
|
-
signal
|
|
35
|
+
signal,
|
|
36
|
+
buildSettings: this.#buildSettings
|
|
32
37
|
});
|
|
33
38
|
return strategy.canBuild(signal);
|
|
34
39
|
}
|
|
@@ -37,7 +42,8 @@ var PreviewBuildStrategy = class {
|
|
|
37
42
|
appPath: this.#appPath,
|
|
38
43
|
entrypoint: this.#entrypoint,
|
|
39
44
|
buildType: this.#buildType,
|
|
40
|
-
signal
|
|
45
|
+
signal,
|
|
46
|
+
buildSettings: this.#buildSettings
|
|
41
47
|
});
|
|
42
48
|
return artifact;
|
|
43
49
|
}
|
|
@@ -47,11 +53,11 @@ async function executePreviewBuild(options) {
|
|
|
47
53
|
appPath: options.appPath,
|
|
48
54
|
entrypoint: options.entrypoint,
|
|
49
55
|
buildType: options.buildType ?? "auto",
|
|
50
|
-
signal: options.signal
|
|
56
|
+
signal: options.signal,
|
|
57
|
+
buildSettings: options.buildSettings
|
|
51
58
|
});
|
|
52
59
|
const artifact = await strategy.execute(options.signal);
|
|
53
60
|
try {
|
|
54
|
-
if (buildType === "nextjs") await restageNextjsArtifact(artifact, options.appPath, options.signal);
|
|
55
61
|
await normalizeArtifactSymlinks(artifact.directory, options.appPath, options.signal);
|
|
56
62
|
return {
|
|
57
63
|
artifact,
|
|
@@ -68,7 +74,8 @@ async function resolvePreviewBuildStrategy(options) {
|
|
|
68
74
|
appPath: options.appPath,
|
|
69
75
|
entrypoint: options.entrypoint,
|
|
70
76
|
buildType: options.buildType,
|
|
71
|
-
signal: options.signal
|
|
77
|
+
signal: options.signal,
|
|
78
|
+
buildSettings: options.buildSettings
|
|
72
79
|
});
|
|
73
80
|
return {
|
|
74
81
|
buildType: options.buildType,
|
|
@@ -81,7 +88,8 @@ async function resolvePreviewBuildStrategy(options) {
|
|
|
81
88
|
appPath: options.appPath,
|
|
82
89
|
entrypoint: options.entrypoint,
|
|
83
90
|
buildType,
|
|
84
|
-
signal: options.signal
|
|
91
|
+
signal: options.signal,
|
|
92
|
+
buildSettings: options.buildSettings
|
|
85
93
|
});
|
|
86
94
|
if (await strategy.canBuild(options.signal)) return {
|
|
87
95
|
buildType,
|
|
@@ -94,25 +102,170 @@ async function resolvePreviewBuildStrategy(options) {
|
|
|
94
102
|
appPath: options.appPath,
|
|
95
103
|
entrypoint: options.entrypoint,
|
|
96
104
|
buildType: "bun",
|
|
97
|
-
signal: options.signal
|
|
105
|
+
signal: options.signal,
|
|
106
|
+
buildSettings: options.buildSettings
|
|
98
107
|
})
|
|
99
108
|
};
|
|
100
109
|
}
|
|
101
110
|
async function createPreviewBuildStrategy(options) {
|
|
102
111
|
switch (options.buildType) {
|
|
103
|
-
case "nextjs": return new
|
|
112
|
+
case "nextjs": return new PreviewNextjsBuild({
|
|
113
|
+
appPath: options.appPath,
|
|
114
|
+
buildSettings: options.buildSettings
|
|
115
|
+
});
|
|
104
116
|
case "nuxt": return new NuxtBuild({ appPath: options.appPath });
|
|
105
117
|
case "astro": return new AstroBuild({ appPath: options.appPath });
|
|
106
|
-
case "tanstack-start": return new
|
|
118
|
+
case "tanstack-start": return new PreviewTanstackStartBuild({
|
|
119
|
+
appPath: options.appPath,
|
|
120
|
+
buildSettings: options.buildSettings
|
|
121
|
+
});
|
|
107
122
|
case "bun": {
|
|
108
123
|
const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint, options.signal);
|
|
109
|
-
return new
|
|
124
|
+
return new PreviewBunBuild({
|
|
110
125
|
appPath: options.appPath,
|
|
111
|
-
|
|
126
|
+
strategy: new BunBuild({
|
|
127
|
+
appPath: options.appPath,
|
|
128
|
+
entrypoint
|
|
129
|
+
}),
|
|
130
|
+
buildSettings: options.buildSettings
|
|
112
131
|
});
|
|
113
132
|
}
|
|
114
133
|
}
|
|
115
134
|
}
|
|
135
|
+
var PreviewNextjsBuild = class {
|
|
136
|
+
#appPath;
|
|
137
|
+
#buildSettings;
|
|
138
|
+
constructor(options) {
|
|
139
|
+
this.#appPath = options.appPath;
|
|
140
|
+
this.#buildSettings = options.buildSettings;
|
|
141
|
+
}
|
|
142
|
+
async canBuild(signal) {
|
|
143
|
+
const packageJson = await readBunPackageJson(this.#appPath, signal);
|
|
144
|
+
return await hasRootFile(this.#appPath, NEXT_CONFIG_FILENAMES, signal) || hasPackageDependency(packageJson, "next");
|
|
145
|
+
}
|
|
146
|
+
async execute(signal) {
|
|
147
|
+
const settings = this.#buildSettings ?? await resolvePreviewBuildSettings({
|
|
148
|
+
appPath: this.#appPath,
|
|
149
|
+
buildType: "nextjs",
|
|
150
|
+
signal
|
|
151
|
+
});
|
|
152
|
+
await runResolvedBuildCommand(this.#appPath, settings, "Next.js", signal);
|
|
153
|
+
const standaloneDir = path.join(this.#appPath, settings.outputDirectory);
|
|
154
|
+
if (!await directoryExists(standaloneDir, signal)) throw new Error(`Next.js build did not produce standalone output at ${settings.outputDirectory}. Add output: "standalone" to your next.config file, or update ${PRISMA_APP_CONFIG_FILENAME}.`);
|
|
155
|
+
const outDir = await unsupportedFilesystemBoundary(signal, () => mkdtemp(path.join(os.tmpdir(), "compute-build-")));
|
|
156
|
+
try {
|
|
157
|
+
const artifactDir = path.join(outDir, "app");
|
|
158
|
+
await stageNextjsStandaloneArtifact({
|
|
159
|
+
standaloneDir,
|
|
160
|
+
artifactDir,
|
|
161
|
+
appPath: this.#appPath,
|
|
162
|
+
signal
|
|
163
|
+
});
|
|
164
|
+
const entrypoint = await findNextStandaloneEntrypoint(artifactDir, signal);
|
|
165
|
+
await copyNextjsStaticAssets({
|
|
166
|
+
appPath: this.#appPath,
|
|
167
|
+
artifactDir,
|
|
168
|
+
outputRoot: nextOutputRootFromStandaloneDirectory(settings.outputDirectory),
|
|
169
|
+
entrypoint,
|
|
170
|
+
signal
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
directory: artifactDir,
|
|
174
|
+
entrypoint,
|
|
175
|
+
defaultPortMapping: { http: 3e3 },
|
|
176
|
+
cleanup: () => rm(outDir, {
|
|
177
|
+
recursive: true,
|
|
178
|
+
force: true
|
|
179
|
+
})
|
|
180
|
+
};
|
|
181
|
+
} catch (error) {
|
|
182
|
+
await rm(outDir, {
|
|
183
|
+
recursive: true,
|
|
184
|
+
force: true
|
|
185
|
+
});
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var PreviewTanstackStartBuild = class {
|
|
191
|
+
#appPath;
|
|
192
|
+
#buildSettings;
|
|
193
|
+
constructor(options) {
|
|
194
|
+
this.#appPath = options.appPath;
|
|
195
|
+
this.#buildSettings = options.buildSettings;
|
|
196
|
+
}
|
|
197
|
+
async canBuild(signal) {
|
|
198
|
+
return hasAnyPackageDependency(await readBunPackageJson(this.#appPath, signal), TANSTACK_START_PACKAGES);
|
|
199
|
+
}
|
|
200
|
+
async execute(signal) {
|
|
201
|
+
const settings = this.#buildSettings ?? await resolvePreviewBuildSettings({
|
|
202
|
+
appPath: this.#appPath,
|
|
203
|
+
buildType: "tanstack-start",
|
|
204
|
+
signal
|
|
205
|
+
});
|
|
206
|
+
await runResolvedBuildCommand(this.#appPath, settings, "TanStack Start", signal);
|
|
207
|
+
const outputDir = path.join(this.#appPath, settings.outputDirectory);
|
|
208
|
+
const entrypoint = "server/index.mjs";
|
|
209
|
+
const entryPath = path.join(outputDir, entrypoint);
|
|
210
|
+
if (!(await unsupportedFilesystemBoundary(signal, () => stat(entryPath).catch(() => null)))?.isFile()) throw new Error(`TanStack Start build did not produce a Nitro node server entrypoint at ${joinPosix(settings.outputDirectory, entrypoint)}. Ensure your vite.config includes the tanstackStart() and nitro() plugins with the default node preset, or update ${PRISMA_APP_CONFIG_FILENAME}.`);
|
|
211
|
+
const outDir = await unsupportedFilesystemBoundary(signal, () => mkdtemp(path.join(os.tmpdir(), "compute-build-")));
|
|
212
|
+
try {
|
|
213
|
+
const artifactDir = path.join(outDir, "app");
|
|
214
|
+
await unsupportedFilesystemBoundary(signal, () => cp(outputDir, artifactDir, {
|
|
215
|
+
recursive: true,
|
|
216
|
+
verbatimSymlinks: true
|
|
217
|
+
}));
|
|
218
|
+
return {
|
|
219
|
+
directory: artifactDir,
|
|
220
|
+
entrypoint,
|
|
221
|
+
defaultPortMapping: { http: 3e3 },
|
|
222
|
+
cleanup: () => rm(outDir, {
|
|
223
|
+
recursive: true,
|
|
224
|
+
force: true
|
|
225
|
+
})
|
|
226
|
+
};
|
|
227
|
+
} catch (error) {
|
|
228
|
+
await rm(outDir, {
|
|
229
|
+
recursive: true,
|
|
230
|
+
force: true
|
|
231
|
+
});
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
var PreviewBunBuild = class {
|
|
237
|
+
#appPath;
|
|
238
|
+
#strategy;
|
|
239
|
+
#buildSettings;
|
|
240
|
+
constructor(options) {
|
|
241
|
+
this.#appPath = options.appPath;
|
|
242
|
+
this.#strategy = options.strategy;
|
|
243
|
+
this.#buildSettings = options.buildSettings;
|
|
244
|
+
}
|
|
245
|
+
async canBuild(signal) {
|
|
246
|
+
return this.#strategy.canBuild(signal);
|
|
247
|
+
}
|
|
248
|
+
async execute(signal) {
|
|
249
|
+
const settings = this.#buildSettings ?? await resolvePreviewBuildSettings({
|
|
250
|
+
appPath: this.#appPath,
|
|
251
|
+
buildType: "bun",
|
|
252
|
+
signal
|
|
253
|
+
});
|
|
254
|
+
await runResolvedBuildCommand(this.#appPath, settings, "Bun", signal);
|
|
255
|
+
return this.#strategy.execute(signal);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const NEXT_CONFIG_FILENAMES = [
|
|
259
|
+
"next.config.js",
|
|
260
|
+
"next.config.mjs",
|
|
261
|
+
"next.config.ts",
|
|
262
|
+
"next.config.mts"
|
|
263
|
+
];
|
|
264
|
+
const TANSTACK_START_PACKAGES = [
|
|
265
|
+
"@tanstack/react-start",
|
|
266
|
+
"@tanstack/solid-start",
|
|
267
|
+
"@tanstack/start"
|
|
268
|
+
];
|
|
116
269
|
async function stageNextjsStandaloneArtifact(options) {
|
|
117
270
|
const standaloneRoot = path.resolve(options.standaloneDir);
|
|
118
271
|
const artifactRoot = path.resolve(options.artifactDir);
|
|
@@ -125,32 +278,42 @@ async function stageNextjsStandaloneArtifact(options) {
|
|
|
125
278
|
});
|
|
126
279
|
await hoistPnpmDependencies(path.join(artifactRoot, "node_modules"), options.signal);
|
|
127
280
|
}
|
|
128
|
-
async function
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
force: true
|
|
134
|
-
}));
|
|
135
|
-
await stageNextjsStandaloneArtifact({
|
|
136
|
-
standaloneDir,
|
|
137
|
-
artifactDir,
|
|
138
|
-
appPath,
|
|
139
|
-
signal
|
|
140
|
-
});
|
|
141
|
-
const serverSubpath = nextjsServerSubpath(artifact.entrypoint);
|
|
142
|
-
const serverDir = serverSubpath ? path.join(artifactDir, serverSubpath) : artifactDir;
|
|
143
|
-
const publicDir = path.join(appPath, "public");
|
|
144
|
-
if (await directoryExists(publicDir, signal)) await unsupportedFilesystemBoundary(signal, () => cp(publicDir, path.join(serverDir, "public"), {
|
|
281
|
+
async function copyNextjsStaticAssets(options) {
|
|
282
|
+
const serverSubpath = nextjsServerSubpath(options.entrypoint);
|
|
283
|
+
const serverDir = serverSubpath ? path.join(options.artifactDir, serverSubpath) : options.artifactDir;
|
|
284
|
+
const publicDir = path.join(options.appPath, "public");
|
|
285
|
+
if (await directoryExists(publicDir, options.signal)) await unsupportedFilesystemBoundary(options.signal, () => cp(publicDir, path.join(serverDir, "public"), {
|
|
145
286
|
recursive: true,
|
|
146
287
|
verbatimSymlinks: true
|
|
147
288
|
}));
|
|
148
|
-
const staticDir = path.join(appPath,
|
|
149
|
-
if (await directoryExists(staticDir, signal)) await unsupportedFilesystemBoundary(signal, () => cp(staticDir, path.join(serverDir,
|
|
289
|
+
const staticDir = path.join(options.appPath, options.outputRoot, "static");
|
|
290
|
+
if (await directoryExists(staticDir, options.signal)) await unsupportedFilesystemBoundary(options.signal, () => cp(staticDir, path.join(serverDir, options.outputRoot, "static"), {
|
|
150
291
|
recursive: true,
|
|
151
292
|
verbatimSymlinks: true
|
|
152
293
|
}));
|
|
153
294
|
}
|
|
295
|
+
async function findNextStandaloneEntrypoint(artifactDir, signal) {
|
|
296
|
+
const rootEntrypoint = path.join(artifactDir, "server.js");
|
|
297
|
+
if ((await unsupportedFilesystemBoundary(signal, () => stat(rootEntrypoint).catch(() => null)))?.isFile()) return "server.js";
|
|
298
|
+
const candidates = [];
|
|
299
|
+
await walk(artifactDir);
|
|
300
|
+
candidates.sort((left, right) => left.split("/").length - right.split("/").length || left.localeCompare(right));
|
|
301
|
+
const selected = candidates[0];
|
|
302
|
+
if (!selected) throw new Error(`Next.js standalone output did not contain server.js in ${artifactDir}`);
|
|
303
|
+
return selected;
|
|
304
|
+
async function walk(directory) {
|
|
305
|
+
const entries = await unsupportedFilesystemBoundary(signal, () => readdir(directory, { withFileTypes: true }));
|
|
306
|
+
for (const entry of entries) {
|
|
307
|
+
if (entry.name === "node_modules") continue;
|
|
308
|
+
const fullPath = path.join(directory, entry.name);
|
|
309
|
+
if (entry.isDirectory()) {
|
|
310
|
+
await walk(fullPath);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (entry.isFile() && entry.name === "server.js") candidates.push(path.relative(artifactDir, fullPath).split(path.sep).join("/"));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
154
317
|
function nextjsServerSubpath(entrypoint) {
|
|
155
318
|
const dir = path.posix.dirname(entrypoint);
|
|
156
319
|
return dir === "." ? "" : dir;
|
|
@@ -165,7 +165,8 @@ function createPreviewAppProvider(client, options) {
|
|
|
165
165
|
appPath: path.resolve(options.cwd),
|
|
166
166
|
entrypoint: options.entrypoint,
|
|
167
167
|
buildType: options.buildType,
|
|
168
|
-
signal: options.signal
|
|
168
|
+
signal: options.signal,
|
|
169
|
+
buildSettings: options.buildSettings
|
|
169
170
|
}),
|
|
170
171
|
projectId: options.projectId,
|
|
171
172
|
serviceId: resolvedApp.appId,
|
package/dist/presenters/app.js
CHANGED
|
@@ -43,11 +43,16 @@ function renderAppDeploy(context, descriptor, result) {
|
|
|
43
43
|
];
|
|
44
44
|
}
|
|
45
45
|
function serializeAppDeploy(result) {
|
|
46
|
-
const { deploySettings
|
|
46
|
+
const { deploySettings, localPin: _localPin, ...serialized } = result;
|
|
47
47
|
const { id: _branchId, ...branch } = serialized.branch;
|
|
48
48
|
return {
|
|
49
49
|
...serialized,
|
|
50
|
-
branch
|
|
50
|
+
branch,
|
|
51
|
+
deploySettings: {
|
|
52
|
+
config: deploySettings.config,
|
|
53
|
+
buildCommand: deploySettings.buildCommand,
|
|
54
|
+
outputDirectory: deploySettings.outputDirectory
|
|
55
|
+
}
|
|
51
56
|
};
|
|
52
57
|
}
|
|
53
58
|
function renderBranchDatabaseDeploySummary(context, result) {
|