@prisma/cli 2.20.0 → 3.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +158 -0
- package/README.md +29 -28
- package/dist/adapters/config.js +74 -0
- package/dist/adapters/local-state.js +98 -0
- package/dist/adapters/mock-api.js +57 -0
- package/dist/adapters/token-storage.js +43 -0
- package/dist/cli.js +9 -0
- package/dist/cli2.js +59 -0
- package/dist/commands/app/index.js +223 -0
- package/dist/commands/auth/index.js +42 -0
- package/dist/commands/branch/index.js +51 -0
- package/dist/commands/project/index.js +45 -0
- package/dist/controllers/app.js +813 -0
- package/dist/controllers/auth.js +107 -0
- package/dist/controllers/branch.js +73 -0
- package/dist/controllers/project.js +214 -0
- package/dist/controllers/select-prompt-port.js +12 -0
- package/dist/lib/app/bun-project.js +39 -0
- package/dist/lib/app/env-vars.js +24 -0
- package/dist/lib/app/local-dev.js +149 -0
- package/dist/lib/app/preview-build.js +283 -0
- package/dist/lib/app/preview-interaction.js +38 -0
- package/dist/lib/app/preview-progress.js +139 -0
- package/dist/lib/app/preview-provider.js +232 -0
- package/dist/lib/auth/auth-ops.js +57 -0
- package/dist/lib/auth/client.js +22 -0
- package/dist/lib/auth/guard.js +34 -0
- package/dist/lib/auth/login.js +117 -0
- package/dist/output/patterns.js +93 -0
- package/dist/presenters/app.js +405 -0
- package/dist/presenters/auth.js +73 -0
- package/dist/presenters/branch.js +111 -0
- package/dist/presenters/project.js +84 -0
- package/dist/shell/command-meta.js +320 -0
- package/dist/shell/command-runner.js +33 -0
- package/dist/shell/errors.js +66 -0
- package/dist/shell/global-flags.js +25 -0
- package/dist/shell/help.js +78 -0
- package/dist/shell/output.js +54 -0
- package/dist/shell/prompt.js +31 -0
- package/dist/shell/runtime.js +51 -0
- package/dist/shell/ui.js +59 -0
- package/dist/use-cases/auth.js +70 -0
- package/dist/use-cases/branch.js +95 -0
- package/dist/use-cases/create-cli-gateways.js +93 -0
- package/dist/use-cases/project.js +75 -0
- package/package.json +49 -137
- package/build/child.js +0 -4110
- package/build/index.js +0 -104447
- package/build/public/demo.html +0 -17
- package/build/public/demoChunk.js +0 -2
- package/build/public/electron-darwin.html +0 -19
- package/build/public/electron-linux.html +0 -18
- package/build/public/electron-mac.html +0 -18
- package/build/public/electron-win.html +0 -17
- package/build/public/electron-win32.html +0 -18
- package/build/public/electron.html +0 -17
- package/build/public/electronBus.js +0 -2
- package/build/public/electronChunk.js +0 -2
- package/build/public/favicon/apple-touch-icon.png +0 -0
- package/build/public/favicon/favicon-16x16.png +0 -0
- package/build/public/favicon/favicon-32x32.png +0 -0
- package/build/public/favicon/prisma.png +0 -0
- package/build/public/fonts/Inter.ttf +0 -0
- package/build/public/fonts/RobotoMono.ttf +0 -0
- package/build/public/icons/.DS_Store +0 -0
- package/build/public/icons/alert.svg +0 -5
- package/build/public/icons/array.svg +0 -4
- package/build/public/icons/bin.svg +0 -37
- package/build/public/icons/boolean.svg +0 -4
- package/build/public/icons/check.svg +0 -3
- package/build/public/icons/chevron-down.svg +0 -3
- package/build/public/icons/code.svg +0 -4
- package/build/public/icons/cross.svg +0 -11
- package/build/public/icons/data-tool.svg +0 -5
- package/build/public/icons/database.svg +0 -4
- package/build/public/icons/datetime.svg +0 -4
- package/build/public/icons/double-arrow-right.svg +0 -4
- package/build/public/icons/download.svg +0 -4
- package/build/public/icons/ellipsis.svg +0 -10
- package/build/public/icons/enum.svg +0 -6
- package/build/public/icons/expand.svg +0 -4
- package/build/public/icons/eye.svg +0 -20
- package/build/public/icons/filters.svg +0 -5
- package/build/public/icons/folder.svg +0 -6
- package/build/public/icons/hamburger.svg +0 -4
- package/build/public/icons/icon.svg +0 -199
- package/build/public/icons/logo.svg +0 -199
- package/build/public/icons/logotype.svg +0 -4
- package/build/public/icons/number.svg +0 -7
- package/build/public/icons/object.svg +0 -5
- package/build/public/icons/play.svg +0 -6
- package/build/public/icons/plus.svg +0 -4
- package/build/public/icons/refresh.svg +0 -4
- package/build/public/icons/search.svg +0 -7
- package/build/public/icons/settings.svg +0 -8
- package/build/public/icons/string.svg +0 -4
- package/build/public/icons/tick-indeterminate.svg +0 -3
- package/build/public/icons/tick.svg +0 -4
- package/build/public/illustrations/.DS_Store +0 -0
- package/build/public/illustrations/empty.svg +0 -1
- package/build/public/illustrations/read.svg +0 -1
- package/build/public/illustrations/searching.svg +0 -1
- package/build/public/images/.DS_Store +0 -0
- package/build/public/images/icon-1024.png +0 -0
- package/build/public/index.html +0 -49
- package/build/public/main.31f688a6254e294d9128.css +0 -118
- package/build/public/main.31f688a6254e294d9128.css.map +0 -1
- package/build/public/main.364c712f32662ff0e43d.css +0 -116
- package/build/public/main.364c712f32662ff0e43d.css.map +0 -1
- package/build/public/main.42ef3fe7e97347765c91.css +0 -118
- package/build/public/main.42ef3fe7e97347765c91.css.map +0 -1
- package/build/public/main.503446defafe7aeca2f3.css +0 -116
- package/build/public/main.503446defafe7aeca2f3.css.map +0 -1
- package/build/public/main.c50a3b5980fe26f78b65.css +0 -118
- package/build/public/main.c50a3b5980fe26f78b65.css.map +0 -1
- package/build/public/main.cda25e5813776c2af295.css +0 -116
- package/build/public/main.cda25e5813776c2af295.css.map +0 -1
- package/build/public/main.dbac3b290f78404ff579.css +0 -116
- package/build/public/main.dbac3b290f78404ff579.css.map +0 -1
- package/build/public/main.f8ce44e122e5e2b8f778.css +0 -118
- package/build/public/main.f8ce44e122e5e2b8f778.css.map +0 -1
- package/build/public/main.fc9ea7521a4aecce1a77.css +0 -118
- package/build/public/main.fc9ea7521a4aecce1a77.css.map +0 -1
- package/build/public/main.js +0 -403
- package/build/public/main.js.map +0 -1
- package/build/public/mainChunk.0a84f3ba4d2ab481e877.css +0 -116
- package/build/public/mainChunk.1b327d58afbddc917bce.css +0 -118
- package/build/public/mainChunk.2cdf583b2bd51aa67587.css +0 -118
- package/build/public/mainChunk.544af00f7e9ffcbe782c.css +0 -118
- package/build/public/mainChunk.56f96a13868b2b4a53be.css +0 -118
- package/build/public/mainChunk.5cee429bfbf06e7ecb39.css +0 -118
- package/build/public/mainChunk.a862474ed4cdb421ffa2.css +0 -118
- package/build/public/mainChunk.ada34153c4911b5ac22b.css +0 -118
- package/build/public/mainChunk.c16a8d01b2c49b2f2751.css +0 -118
- package/build/public/mainChunk.ca4a878d5478b9320be8.css +0 -116
- package/build/public/mainChunk.e3adc8758b4395546cef.css +0 -116
- package/build/public/mainChunk.f2c59fcbfc455d8b9de5.css +0 -118
- package/build/public/mainChunk.js +0 -396
- package/build/public/preview.html +0 -16
- package/build/public/previewBus.js +0 -2
- package/build/public/previewChunk.js +0 -2
- package/build/public/projects.html +0 -86
- package/build/public/server.html +0 -16
- package/build/public/serverBus.js +0 -2
- package/build/public/serverChunk.js +0 -2
- package/build/public/splash.html +0 -17
- package/build/public/studioBundle.828f34b1781061528841.css +0 -118
- package/build/public/studioBundle.bfe9138b2e0293fcb7da.css +0 -118
- package/build/public/studioBundle.c5b256eede880e502aac.css +0 -118
- package/build/public/studioBundle.e43df511c8e2e368900a.css +0 -118
- package/build/public/studioBundle.e69382554c2a2942ae32.css +0 -118
- package/build/public/studioBundle.ee21aa56a3999a2b380b.css +0 -118
- package/build/public/studioBundle.js +0 -396
- package/build/public/vercel.html +0 -16
- package/build/public/vercelChunk.js +0 -2
- package/build/xdg-open +0 -1066
- package/install/index.js +0 -5
- package/preinstall/index.js +0 -71
- package/prisma-client/README.md +0 -27
- package/prisma-client/generator-build/.DS_Store +0 -0
- package/prisma-client/generator-build/index.js +0 -79697
- package/prisma-client/index-browser.js +0 -3
- package/prisma-client/index.d.ts +0 -1
- package/prisma-client/index.js +0 -11
- package/prisma-client/package.json +0 -138
- package/prisma-client/runtime/index-browser.d.ts +0 -267
- package/prisma-client/runtime/index-browser.js +0 -2273
- package/prisma-client/runtime/index.d.ts +0 -1018
- package/prisma-client/runtime/index.js +0 -35028
- package/prisma-client/scripts/backup-index-browser.js +0 -3
- package/prisma-client/scripts/backup-index.d.ts +0 -1
- package/prisma-client/scripts/backup-index.js +0 -11
- package/prisma-client/scripts/colors.js +0 -180
- package/prisma-client/scripts/default-index-browser.js +0 -12
- package/prisma-client/scripts/default-index.d.ts +0 -47
- package/prisma-client/scripts/default-index.js +0 -12
- package/prisma-client/scripts/get-packed-client.js +0 -12
- package/prisma-client/scripts/mock-fs.js +0 -14
- package/prisma-client/scripts/postinstall.d.ts +0 -5
- package/prisma-client/scripts/postinstall.js +0 -398
- package/scripts/install-entry.js +0 -8
- package/scripts/preinstall-entry.js +0 -9
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { authRequiredError, usageError } from "../shell/errors.js";
|
|
2
|
+
import { canPrompt } from "../shell/runtime.js";
|
|
3
|
+
import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
4
|
+
import { createAuthUseCases } from "../use-cases/auth.js";
|
|
5
|
+
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
6
|
+
import { performLogin, performLogout, readAuthState } from "../lib/auth/auth-ops.js";
|
|
7
|
+
//#region src/controllers/auth.ts
|
|
8
|
+
function isRealMode(context) {
|
|
9
|
+
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
10
|
+
}
|
|
11
|
+
async function runAuthLogin(context, options) {
|
|
12
|
+
let result;
|
|
13
|
+
if (isRealMode(context)) {
|
|
14
|
+
await performLogin(context.runtime.env);
|
|
15
|
+
result = await readAuthState(context.runtime.env);
|
|
16
|
+
} else result = await loginWithSelectionFlow(context, createAuthUseCases(createCliUseCaseGateways(context)), options);
|
|
17
|
+
return createAuthSuccess("auth.login", result, ["prisma auth whoami", "prisma project list"]);
|
|
18
|
+
}
|
|
19
|
+
async function runAuthLogout(context) {
|
|
20
|
+
let result;
|
|
21
|
+
if (isRealMode(context)) {
|
|
22
|
+
await performLogout(context.runtime.env);
|
|
23
|
+
result = await readAuthState(context.runtime.env);
|
|
24
|
+
} else result = await createAuthUseCases(createCliUseCaseGateways(context)).logout();
|
|
25
|
+
return createAuthSuccess("auth.logout", result, ["prisma auth login"]);
|
|
26
|
+
}
|
|
27
|
+
async function runAuthWhoAmI(context) {
|
|
28
|
+
let result;
|
|
29
|
+
if (isRealMode(context)) result = await readAuthState(context.runtime.env);
|
|
30
|
+
else result = await createAuthUseCases(createCliUseCaseGateways(context)).whoami();
|
|
31
|
+
return createAuthSuccess("auth.whoami", result, result.authenticated ? [] : ["prisma auth login"]);
|
|
32
|
+
}
|
|
33
|
+
async function requireAuthenticatedAuthState(context) {
|
|
34
|
+
if (isRealMode(context)) {
|
|
35
|
+
const current = await readAuthState(context.runtime.env);
|
|
36
|
+
if (current.authenticated) return current;
|
|
37
|
+
if (!canPrompt(context)) throw authRequiredError();
|
|
38
|
+
await performLogin(context.runtime.env);
|
|
39
|
+
return readAuthState(context.runtime.env);
|
|
40
|
+
}
|
|
41
|
+
const useCases = createAuthUseCases(createCliUseCaseGateways(context));
|
|
42
|
+
const current = await useCases.whoami();
|
|
43
|
+
if (current.authenticated) return current;
|
|
44
|
+
if (!canPrompt(context)) throw authRequiredError();
|
|
45
|
+
return loginWithSelectionFlow(context, useCases, {});
|
|
46
|
+
}
|
|
47
|
+
async function loginWithSelectionFlow(context, useCases, options) {
|
|
48
|
+
const selection = await resolveLoginSelection(useCases, canPrompt(context) ? createSelectPromptPort(context) : null, options);
|
|
49
|
+
return useCases.login(selection);
|
|
50
|
+
}
|
|
51
|
+
async function resolveLoginSelection(useCases, prompt, options) {
|
|
52
|
+
const provider = options.provider ? (await useCases.resolveProvider(options.provider)).id : (await selectProvider(useCases, prompt)).id;
|
|
53
|
+
const user = options.user ? await useCases.resolveUserForProvider(provider, options.user) : await selectUser(useCases, prompt, provider);
|
|
54
|
+
const workspace = options.workspace ? await useCases.resolveWorkspaceForUser(user.id, options.workspace) : await selectWorkspace(useCases, prompt, user.id);
|
|
55
|
+
return {
|
|
56
|
+
provider,
|
|
57
|
+
userId: user.id,
|
|
58
|
+
workspaceId: workspace.id
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function selectProvider(useCases, prompt) {
|
|
62
|
+
if (!prompt) throw nonInteractiveLoginError("Re-run prisma auth login in a TTY, or provide --provider and --user, and --workspace when required.");
|
|
63
|
+
const providers = await useCases.listProviders();
|
|
64
|
+
return prompt.select({
|
|
65
|
+
message: "Select a provider",
|
|
66
|
+
choices: providers.map((provider) => ({
|
|
67
|
+
label: provider.name,
|
|
68
|
+
value: provider
|
|
69
|
+
}))
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function selectUser(useCases, prompt, provider) {
|
|
73
|
+
const users = await useCases.listUsersForProvider(provider);
|
|
74
|
+
if (!prompt) throw nonInteractiveLoginError("Re-run prisma auth login in a TTY, or provide --provider and --user, and --workspace when required.");
|
|
75
|
+
return prompt.select({
|
|
76
|
+
message: "Select a user",
|
|
77
|
+
choices: users.map((user) => ({
|
|
78
|
+
label: `${user.name} <${user.email}>`,
|
|
79
|
+
value: user
|
|
80
|
+
}))
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async function selectWorkspace(useCases, prompt, userId) {
|
|
84
|
+
const workspaces = await useCases.listWorkspacesForUser(userId);
|
|
85
|
+
if (workspaces.length === 1) return workspaces[0];
|
|
86
|
+
if (!prompt) throw usageError("Login requires explicit selectors in non-interactive mode", "The selected mock user belongs to more than one workspace and the shell cannot prompt in the current mode.", "Re-run prisma auth login in a TTY, or provide --workspace.", ["prisma auth login"], "auth");
|
|
87
|
+
return prompt.select({
|
|
88
|
+
message: "Select a workspace",
|
|
89
|
+
choices: workspaces.map((workspace) => ({
|
|
90
|
+
label: `${workspace.name} (${workspace.id})`,
|
|
91
|
+
value: workspace
|
|
92
|
+
}))
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function nonInteractiveLoginError(fix) {
|
|
96
|
+
return usageError("Login requires explicit selectors in non-interactive mode", "The fixture mode cannot prompt in the current mode.", fix, ["prisma auth login"], "auth");
|
|
97
|
+
}
|
|
98
|
+
function createAuthSuccess(command, result, nextSteps) {
|
|
99
|
+
return {
|
|
100
|
+
command,
|
|
101
|
+
result,
|
|
102
|
+
warnings: [],
|
|
103
|
+
nextSteps
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { requireAuthenticatedAuthState, runAuthLogin, runAuthLogout, runAuthWhoAmI };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { featureUnavailableError, usageError } from "../shell/errors.js";
|
|
2
|
+
import { canPrompt } from "../shell/runtime.js";
|
|
3
|
+
import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
4
|
+
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
5
|
+
import { createBranchUseCases } from "../use-cases/branch.js";
|
|
6
|
+
//#region src/controllers/branch.ts
|
|
7
|
+
const PREVIEW_BRANCH_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
8
|
+
function isRealMode(context) {
|
|
9
|
+
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
10
|
+
}
|
|
11
|
+
async function runBranchList(context) {
|
|
12
|
+
if (isRealMode(context)) throw branchCommandsUnavailableError();
|
|
13
|
+
return {
|
|
14
|
+
command: "branch.list",
|
|
15
|
+
result: await createBranchUseCases(createCliUseCaseGateways(context)).list(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
nextSteps: []
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function runBranchShow(context) {
|
|
21
|
+
if (isRealMode(context)) throw branchCommandsUnavailableError();
|
|
22
|
+
const result = await createBranchUseCases(createCliUseCaseGateways(context)).show();
|
|
23
|
+
return {
|
|
24
|
+
command: "branch.show",
|
|
25
|
+
result,
|
|
26
|
+
warnings: [],
|
|
27
|
+
nextSteps: result.branch.kind === "preview" && !result.branch.remoteState ? ["prisma app deploy"] : []
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function runBranchUse(context, branchName) {
|
|
31
|
+
if (isRealMode(context)) throw branchCommandsUnavailableError();
|
|
32
|
+
const useCases = createBranchUseCases(createCliUseCaseGateways(context));
|
|
33
|
+
const resolvedBranchName = await resolveBranchNameForUse(context, useCases, branchName);
|
|
34
|
+
validateBranchName(resolvedBranchName);
|
|
35
|
+
const result = await useCases.use(resolvedBranchName);
|
|
36
|
+
return {
|
|
37
|
+
command: "branch.use",
|
|
38
|
+
result,
|
|
39
|
+
warnings: result.branch.kind === "production" ? ["Production is protected and durable. Use with care."] : [],
|
|
40
|
+
nextSteps: result.branch.kind === "preview" && !result.branch.remoteState ? ["prisma branch show", "prisma app deploy"] : ["prisma branch show"]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function resolveBranchNameForUse(context, useCases, branchName) {
|
|
44
|
+
if (branchName) return branchName;
|
|
45
|
+
if (!canPrompt(context)) throw branchSelectionRequiredError();
|
|
46
|
+
const result = await useCases.list();
|
|
47
|
+
return createSelectPromptPort(context).select({
|
|
48
|
+
message: "Select a branch",
|
|
49
|
+
choices: result.branches.map((branch) => ({
|
|
50
|
+
label: renderBranchChoiceLabel(branch),
|
|
51
|
+
value: branch.name
|
|
52
|
+
}))
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function renderBranchChoiceLabel(branch) {
|
|
56
|
+
const markers = [];
|
|
57
|
+
if (branch.active) markers.push("active");
|
|
58
|
+
if (!branch.remoteState) markers.push("not created yet");
|
|
59
|
+
return markers.length > 0 ? `${branch.name} (${markers.join(", ")})` : branch.name;
|
|
60
|
+
}
|
|
61
|
+
function validateBranchName(branchName) {
|
|
62
|
+
if (branchName === "production") return;
|
|
63
|
+
if (PREVIEW_BRANCH_PATTERN.test(branchName)) return;
|
|
64
|
+
throw usageError("Branch name must use the documented form", "Branch names must be production or a lowercase preview slug such as preview or feat-auth.", "Use production or a lowercase preview branch name with letters, numbers, and hyphens.", ["prisma branch list"], "branch");
|
|
65
|
+
}
|
|
66
|
+
function branchSelectionRequiredError() {
|
|
67
|
+
return usageError("Branch use requires a target in non-interactive mode", "This command cannot prompt for branch selection in the current mode.", "Re-run prisma branch use in a TTY, or pass a branch name explicitly.", ["prisma branch list"], "branch");
|
|
68
|
+
}
|
|
69
|
+
function branchCommandsUnavailableError() {
|
|
70
|
+
return featureUnavailableError("Branch commands are not available in this preview", "The current preview cannot resolve or change remote branch context yet.", "Use prisma app deploy for preview app deployment workflows.", ["prisma app deploy --app <name>"], "branch");
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { runBranchList, runBranchShow, runBranchUse };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { UnsafeConfigWriteError, readLinkedProjectId, writeLinkedProjectId } from "../adapters/config.js";
|
|
2
|
+
import { CliError, authRequiredError, usageError } from "../shell/errors.js";
|
|
3
|
+
import { canPrompt } from "../shell/runtime.js";
|
|
4
|
+
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
5
|
+
import { createProjectUseCases, projectNotFoundError } from "../use-cases/project.js";
|
|
6
|
+
import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
7
|
+
import { createAuthUseCases } from "../use-cases/auth.js";
|
|
8
|
+
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
9
|
+
import { readAuthState } from "../lib/auth/auth-ops.js";
|
|
10
|
+
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
11
|
+
//#region src/controllers/project.ts
|
|
12
|
+
function isRealMode(context) {
|
|
13
|
+
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
14
|
+
}
|
|
15
|
+
async function runProjectList(context) {
|
|
16
|
+
if (isRealMode(context)) {
|
|
17
|
+
const authState = await requireAuthenticatedAuthState(context);
|
|
18
|
+
const client = await requireComputeAuth(context.runtime.env);
|
|
19
|
+
const workspace = authState.workspace;
|
|
20
|
+
if (!client || !workspace) throw authRequiredError();
|
|
21
|
+
const { data: projectsData } = await client.GET("/v1/projects", {});
|
|
22
|
+
return {
|
|
23
|
+
command: "project.list",
|
|
24
|
+
result: {
|
|
25
|
+
workspace,
|
|
26
|
+
linkedProjectId: await readLinkedProjectId(context.runtime.cwd),
|
|
27
|
+
projects: (projectsData?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
|
|
28
|
+
id: project.id,
|
|
29
|
+
name: project.name
|
|
30
|
+
})).sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id))
|
|
31
|
+
},
|
|
32
|
+
warnings: [],
|
|
33
|
+
nextSteps: ["prisma project link"]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const authState = await requireAuthenticatedAuthState(context);
|
|
37
|
+
return {
|
|
38
|
+
command: "project.list",
|
|
39
|
+
result: await createProjectUseCases(createCliUseCaseGateways(context)).list(authState),
|
|
40
|
+
warnings: [],
|
|
41
|
+
nextSteps: ["prisma project link"]
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function runProjectShow(context) {
|
|
45
|
+
if (isRealMode(context)) {
|
|
46
|
+
const linkedProjectId = await readLinkedProjectId(context.runtime.cwd);
|
|
47
|
+
if (!linkedProjectId) return {
|
|
48
|
+
command: "project.show",
|
|
49
|
+
result: {
|
|
50
|
+
linkedProjectId: null,
|
|
51
|
+
workspace: null,
|
|
52
|
+
project: null
|
|
53
|
+
},
|
|
54
|
+
warnings: [],
|
|
55
|
+
nextSteps: ["prisma project link"]
|
|
56
|
+
};
|
|
57
|
+
const authState = await readAuthState(context.runtime.env);
|
|
58
|
+
if (!authState.authenticated || !authState.workspace) return {
|
|
59
|
+
command: "project.show",
|
|
60
|
+
result: {
|
|
61
|
+
linkedProjectId,
|
|
62
|
+
workspace: null,
|
|
63
|
+
project: null
|
|
64
|
+
},
|
|
65
|
+
warnings: [],
|
|
66
|
+
nextSteps: ["prisma auth login"]
|
|
67
|
+
};
|
|
68
|
+
const client = await requireComputeAuth(context.runtime.env);
|
|
69
|
+
if (!client) return {
|
|
70
|
+
command: "project.show",
|
|
71
|
+
result: {
|
|
72
|
+
linkedProjectId,
|
|
73
|
+
workspace: null,
|
|
74
|
+
project: null
|
|
75
|
+
},
|
|
76
|
+
warnings: [],
|
|
77
|
+
nextSteps: ["prisma auth login"]
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
const { data } = await client.GET("/v1/projects/{id}", { params: { path: { id: linkedProjectId } } });
|
|
81
|
+
const project = data?.data;
|
|
82
|
+
if (!project || project.workspace.id !== authState.workspace.id) return {
|
|
83
|
+
command: "project.show",
|
|
84
|
+
result: {
|
|
85
|
+
linkedProjectId,
|
|
86
|
+
workspace: null,
|
|
87
|
+
project: null
|
|
88
|
+
},
|
|
89
|
+
warnings: [],
|
|
90
|
+
nextSteps: []
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
command: "project.show",
|
|
94
|
+
result: {
|
|
95
|
+
linkedProjectId,
|
|
96
|
+
workspace: {
|
|
97
|
+
id: project.workspace.id,
|
|
98
|
+
name: project.workspace.name
|
|
99
|
+
},
|
|
100
|
+
project: {
|
|
101
|
+
id: project.id,
|
|
102
|
+
name: project.name
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
warnings: [],
|
|
106
|
+
nextSteps: []
|
|
107
|
+
};
|
|
108
|
+
} catch {
|
|
109
|
+
return {
|
|
110
|
+
command: "project.show",
|
|
111
|
+
result: {
|
|
112
|
+
linkedProjectId,
|
|
113
|
+
workspace: null,
|
|
114
|
+
project: null
|
|
115
|
+
},
|
|
116
|
+
warnings: [],
|
|
117
|
+
nextSteps: []
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const gateways = createCliUseCaseGateways(context);
|
|
122
|
+
const authUseCases = createAuthUseCases(gateways);
|
|
123
|
+
const projectUseCases = createProjectUseCases(gateways);
|
|
124
|
+
const authState = await authUseCases.whoami();
|
|
125
|
+
const result = await projectUseCases.show(authState);
|
|
126
|
+
return {
|
|
127
|
+
command: "project.show",
|
|
128
|
+
result,
|
|
129
|
+
warnings: [],
|
|
130
|
+
nextSteps: result.linkedProjectId ? authState.authenticated ? [] : ["prisma auth login"] : ["prisma project link"]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function runProjectLink(context, projectId) {
|
|
134
|
+
if (!projectId && !canPrompt(context)) throw projectSelectionRequiredError();
|
|
135
|
+
if (isRealMode(context)) {
|
|
136
|
+
const authState = await requireAuthenticatedAuthState(context);
|
|
137
|
+
const client = await requireComputeAuth(context.runtime.env);
|
|
138
|
+
const workspace = authState.workspace;
|
|
139
|
+
if (!client || !workspace) throw authRequiredError();
|
|
140
|
+
let selectedProject;
|
|
141
|
+
if (projectId) try {
|
|
142
|
+
const { data } = await client.GET("/v1/projects/{id}", { params: { path: { id: projectId } } });
|
|
143
|
+
if (!data?.data || data.data.workspace.id !== workspace.id) throw projectNotFoundError(`The project "${projectId}" does not exist in workspace "${workspace.name}".`, "Run prisma project list and choose a project id from the active workspace.");
|
|
144
|
+
selectedProject = data.data;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (error instanceof CliError) throw error;
|
|
147
|
+
throw projectNotFoundError(`The project "${projectId}" does not exist in workspace "${workspace.name}".`, "Run prisma project list and choose a project id from the active workspace.");
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const { data: projectsData } = await client.GET("/v1/projects", {});
|
|
151
|
+
const projects = (projectsData?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
|
|
152
|
+
id: project.id,
|
|
153
|
+
name: project.name,
|
|
154
|
+
workspace: project.workspace
|
|
155
|
+
})).sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
|
|
156
|
+
if (projects.length === 0) throw projectNotFoundError(`No projects are available in workspace "${workspace.name}".`, "Use prisma app deploy to create project context, or switch workspaces and try again.", []);
|
|
157
|
+
selectedProject = await createSelectPromptPort(context).select({
|
|
158
|
+
message: "Select a project",
|
|
159
|
+
choices: projects.map((project) => ({
|
|
160
|
+
label: `${project.name} (${project.id})`,
|
|
161
|
+
value: project
|
|
162
|
+
}))
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
await writeLinkedProjectId(context.runtime.cwd, selectedProject.id);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (error instanceof UnsafeConfigWriteError) throw usageError("Project link requires a writable Prisma config", error.message, "Update prisma.config.ts to use a recognizable project field, or remove it and rerun prisma project link.", ["prisma project link proj_123"], "project");
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
command: "project.link",
|
|
173
|
+
result: {
|
|
174
|
+
linkedProjectId: selectedProject.id,
|
|
175
|
+
workspace: {
|
|
176
|
+
id: selectedProject.workspace.id,
|
|
177
|
+
name: selectedProject.workspace.name
|
|
178
|
+
},
|
|
179
|
+
project: {
|
|
180
|
+
id: selectedProject.id,
|
|
181
|
+
name: selectedProject.name
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
warnings: [],
|
|
185
|
+
nextSteps: ["prisma project show", "prisma app deploy"]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const projectUseCases = createProjectUseCases(createCliUseCaseGateways(context));
|
|
189
|
+
const authState = await requireAuthenticatedAuthState(context);
|
|
190
|
+
const resolvedProjectId = projectId ?? await resolveProjectIdForLink(context, authState, projectUseCases);
|
|
191
|
+
return {
|
|
192
|
+
command: "project.link",
|
|
193
|
+
result: await projectUseCases.link(authState, resolvedProjectId),
|
|
194
|
+
warnings: [],
|
|
195
|
+
nextSteps: ["prisma project show", "prisma app deploy"]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
async function resolveProjectIdForLink(context, authState, projectUseCases) {
|
|
199
|
+
if (!authState.workspace) throw projectSelectionRequiredError();
|
|
200
|
+
const projects = await projectUseCases.listProjectsForWorkspace(authState.workspace.id);
|
|
201
|
+
if (projects.length === 0) throw projectNotFoundError(`No projects are available in workspace "${authState.workspace.name}".`, "Use prisma app deploy to create project context, or switch workspaces and try again.", []);
|
|
202
|
+
return (await createSelectPromptPort(context).select({
|
|
203
|
+
message: "Select a project",
|
|
204
|
+
choices: projects.map((project) => ({
|
|
205
|
+
label: `${project.name} (${project.id})`,
|
|
206
|
+
value: project
|
|
207
|
+
}))
|
|
208
|
+
})).id;
|
|
209
|
+
}
|
|
210
|
+
function projectSelectionRequiredError() {
|
|
211
|
+
return usageError("Project link requires a project target in non-interactive mode", "This command cannot prompt for project selection in the current mode.", "Re-run prisma project link in a TTY, or pass a project id explicitly.", ["prisma project list"], "project");
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
export { runProjectLink, runProjectList, runProjectShow };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { selectPrompt } from "../shell/prompt.js";
|
|
2
|
+
//#region src/controllers/select-prompt-port.ts
|
|
3
|
+
function createSelectPromptPort(context) {
|
|
4
|
+
return { select: ({ message, choices }) => selectPrompt({
|
|
5
|
+
input: context.runtime.stdin,
|
|
6
|
+
output: context.runtime.stderr,
|
|
7
|
+
message,
|
|
8
|
+
choices
|
|
9
|
+
}) };
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { createSelectPromptPort };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { access, readFile } from "node:fs/promises";
|
|
3
|
+
//#region src/lib/app/bun-project.ts
|
|
4
|
+
async function readBunPackageJson(appPath) {
|
|
5
|
+
const packageJsonPath = path.join(appPath, "package.json");
|
|
6
|
+
let content;
|
|
7
|
+
try {
|
|
8
|
+
content = await readFile(packageJsonPath, "utf8");
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (error.code === "ENOENT") return null;
|
|
11
|
+
throw new Error(`Failed to read ${packageJsonPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(content);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
throw new Error(`Failed to parse ${packageJsonPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function readBunPackageEntrypoint(packageJson) {
|
|
20
|
+
if (typeof packageJson?.main === "string") return packageJson.main;
|
|
21
|
+
if (typeof packageJson?.module === "string") return packageJson.module;
|
|
22
|
+
}
|
|
23
|
+
async function resolveBunEntrypoint(appPath, explicitEntrypoint) {
|
|
24
|
+
const packageJson = await readBunPackageJson(appPath);
|
|
25
|
+
const candidate = explicitEntrypoint ?? readBunPackageEntrypoint(packageJson);
|
|
26
|
+
if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main or module.");
|
|
27
|
+
if (path.isAbsolute(candidate)) throw new Error("Entrypoint must be a relative path.");
|
|
28
|
+
const normalized = path.normalize(candidate);
|
|
29
|
+
if (normalized.startsWith("..") || path.isAbsolute(normalized) || normalized.includes(`${path.sep}..${path.sep}`)) throw new Error("Entrypoint must not escape the app directory.");
|
|
30
|
+
const entrypointPath = path.join(appPath, normalized);
|
|
31
|
+
try {
|
|
32
|
+
await access(entrypointPath);
|
|
33
|
+
} catch {
|
|
34
|
+
throw new Error(`Entrypoint file does not exist: ${entrypointPath}`);
|
|
35
|
+
}
|
|
36
|
+
return normalized.split(path.sep).join("/");
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { readBunPackageEntrypoint, readBunPackageJson, resolveBunEntrypoint };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { usageError } from "../../shell/errors.js";
|
|
2
|
+
//#region src/lib/app/env-vars.ts
|
|
3
|
+
function parseEnvAssignments(assignments, options) {
|
|
4
|
+
const values = assignments ?? [];
|
|
5
|
+
if (options.requireAtLeastOne && values.length === 0) throw usageError("At least one environment variable is required", `prisma app ${options.commandName} needs at least one --env NAME=VALUE flag in the current mode.`, `Pass one or more --env NAME=VALUE flags, for example prisma app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
|
|
6
|
+
const parsed = {};
|
|
7
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8
|
+
for (const assignment of values) {
|
|
9
|
+
const separatorIndex = assignment.indexOf("=");
|
|
10
|
+
if (separatorIndex === -1) throw usageError("Environment variable assignment must use NAME=VALUE", "A provided --env flag is missing the = separator.", `Pass repeated --env NAME=VALUE flags, for example prisma app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
|
|
11
|
+
const name = assignment.slice(0, separatorIndex);
|
|
12
|
+
if (name.length === 0) throw usageError("Environment variable name is required", "A provided --env flag has an empty variable name.", `Pass repeated --env NAME=VALUE flags, for example prisma app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
|
|
13
|
+
if (seen.has(name)) throw usageError(`Environment variable "${name}" was provided more than once`, "Each environment variable name may be set only once per command invocation.", `Remove the duplicate "${name}" assignment and rerun prisma app ${options.commandName}.`, [`prisma app ${options.commandName} --env ${name}=value`], "app");
|
|
14
|
+
seen.add(name);
|
|
15
|
+
parsed[name] = assignment.slice(separatorIndex + 1);
|
|
16
|
+
}
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
function envVarNames(envVars) {
|
|
20
|
+
if (!envVars) return [];
|
|
21
|
+
return Object.entries(envVars).filter(([, value]) => value !== null).map(([name]) => name).sort((left, right) => left.localeCompare(right));
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { envVarNames, parseEnvAssignments };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { readBunPackageEntrypoint, readBunPackageJson, resolveBunEntrypoint } from "./bun-project.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { access } from "node:fs/promises";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
//#region src/lib/app/local-dev.ts
|
|
6
|
+
const NEXT_CONFIG_FILENAMES = [
|
|
7
|
+
"next.config.js",
|
|
8
|
+
"next.config.mjs",
|
|
9
|
+
"next.config.ts",
|
|
10
|
+
"next.config.mts"
|
|
11
|
+
];
|
|
12
|
+
const DEFAULT_LOCAL_DEV_PORT = 3e3;
|
|
13
|
+
async function resolveLocalBuildType(appPath, buildType) {
|
|
14
|
+
if (buildType === "bun" || buildType === "nextjs") return buildType;
|
|
15
|
+
return detectLocalBuildType(appPath);
|
|
16
|
+
}
|
|
17
|
+
async function detectLocalBuildType(appPath) {
|
|
18
|
+
if (await isNextProject(appPath)) return "nextjs";
|
|
19
|
+
if (await isBunProject(appPath)) return "bun";
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
async function runLocalApp(options) {
|
|
23
|
+
const spawnImpl = options.spawnImpl ?? spawn;
|
|
24
|
+
if (options.buildType === "nextjs") {
|
|
25
|
+
const command = await runWithFallback([
|
|
26
|
+
{
|
|
27
|
+
command: path.join(options.appPath, "node_modules", ".bin", process.platform === "win32" ? "next.cmd" : "next"),
|
|
28
|
+
args: [
|
|
29
|
+
"dev",
|
|
30
|
+
"--port",
|
|
31
|
+
String(options.port)
|
|
32
|
+
],
|
|
33
|
+
display: `next dev --port ${options.port}`
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
command: "npx",
|
|
37
|
+
args: [
|
|
38
|
+
"next",
|
|
39
|
+
"dev",
|
|
40
|
+
"--port",
|
|
41
|
+
String(options.port)
|
|
42
|
+
],
|
|
43
|
+
display: `next dev --port ${options.port}`
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
command: "bunx",
|
|
47
|
+
args: [
|
|
48
|
+
"next",
|
|
49
|
+
"dev",
|
|
50
|
+
"--port",
|
|
51
|
+
String(options.port)
|
|
52
|
+
],
|
|
53
|
+
display: `next dev --port ${options.port}`
|
|
54
|
+
}
|
|
55
|
+
], {
|
|
56
|
+
cwd: options.appPath,
|
|
57
|
+
env: {
|
|
58
|
+
...options.env,
|
|
59
|
+
PORT: String(options.port)
|
|
60
|
+
}
|
|
61
|
+
}, spawnImpl, "Could not find the Next.js CLI. Install it with `npm install next` or ensure npx/bunx is available.");
|
|
62
|
+
return {
|
|
63
|
+
framework: "nextjs",
|
|
64
|
+
entrypoint: null,
|
|
65
|
+
port: options.port,
|
|
66
|
+
command: command.display,
|
|
67
|
+
exitCode: command.exitCode,
|
|
68
|
+
signal: command.signal
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint);
|
|
72
|
+
const command = await runWithFallback([{
|
|
73
|
+
command: "bun",
|
|
74
|
+
args: ["--watch", entrypoint],
|
|
75
|
+
display: `bun --watch ${entrypoint}`
|
|
76
|
+
}], {
|
|
77
|
+
cwd: options.appPath,
|
|
78
|
+
env: {
|
|
79
|
+
...options.env,
|
|
80
|
+
PORT: String(options.port)
|
|
81
|
+
}
|
|
82
|
+
}, spawnImpl, "Bun is required to run this app locally. Install it from https://bun.sh.");
|
|
83
|
+
return {
|
|
84
|
+
framework: "bun",
|
|
85
|
+
entrypoint,
|
|
86
|
+
port: options.port,
|
|
87
|
+
command: command.display,
|
|
88
|
+
exitCode: command.exitCode,
|
|
89
|
+
signal: command.signal
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function isNextProject(appPath) {
|
|
93
|
+
for (const fileName of NEXT_CONFIG_FILENAMES) try {
|
|
94
|
+
await access(path.join(appPath, fileName));
|
|
95
|
+
return true;
|
|
96
|
+
} catch {}
|
|
97
|
+
return hasDependency(await readBunPackageJson(appPath), "next");
|
|
98
|
+
}
|
|
99
|
+
async function isBunProject(appPath) {
|
|
100
|
+
try {
|
|
101
|
+
await access(path.join(appPath, "bun.lock"));
|
|
102
|
+
return true;
|
|
103
|
+
} catch {}
|
|
104
|
+
try {
|
|
105
|
+
await access(path.join(appPath, "bun.lockb"));
|
|
106
|
+
return true;
|
|
107
|
+
} catch {}
|
|
108
|
+
const packageJson = await readBunPackageJson(appPath);
|
|
109
|
+
if (!packageJson) return false;
|
|
110
|
+
const hasEntrypoint = typeof readBunPackageEntrypoint(packageJson) === "string";
|
|
111
|
+
const hasBunDependency = hasDependency(packageJson, "@types/bun") || hasDependency(packageJson, "bun");
|
|
112
|
+
const usesBunScripts = (typeof packageJson.scripts === "object" && packageJson.scripts !== null ? Object.values(packageJson.scripts) : []).some((value) => typeof value === "string" && /\bbun\b/.test(value));
|
|
113
|
+
return hasEntrypoint && (hasBunDependency || usesBunScripts);
|
|
114
|
+
}
|
|
115
|
+
function hasDependency(packageJson, dependencyName) {
|
|
116
|
+
if (!packageJson) return false;
|
|
117
|
+
return [packageJson.dependencies, packageJson.devDependencies].some((group) => typeof group === "object" && group !== null && dependencyName in group);
|
|
118
|
+
}
|
|
119
|
+
async function runWithFallback(candidates, options, spawnImpl, missingCommandMessage) {
|
|
120
|
+
for (const candidate of candidates) try {
|
|
121
|
+
const result = await spawnCommand(candidate, options, spawnImpl);
|
|
122
|
+
return {
|
|
123
|
+
display: candidate.display,
|
|
124
|
+
exitCode: result.exitCode,
|
|
125
|
+
signal: result.signal
|
|
126
|
+
};
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error.code === "ENOENT") continue;
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
throw new Error(missingCommandMessage);
|
|
132
|
+
}
|
|
133
|
+
function spawnCommand(candidate, options, spawnImpl) {
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const child = spawnImpl(candidate.command, candidate.args, {
|
|
136
|
+
...options,
|
|
137
|
+
stdio: "inherit"
|
|
138
|
+
});
|
|
139
|
+
child.once("error", reject);
|
|
140
|
+
child.once("exit", (code, signal) => {
|
|
141
|
+
resolve({
|
|
142
|
+
exitCode: code ?? 1,
|
|
143
|
+
signal
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
export { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp };
|