@prisma/cli 3.0.0-beta.8 → 3.0.0-dev.101.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 +0 -13
- package/dist/adapters/mock-api.js +75 -0
- package/dist/adapters/token-storage.js +311 -33
- package/dist/cli.js +7 -7
- package/dist/cli2.js +5 -3
- package/dist/commands/app/index.js +65 -52
- package/dist/commands/auth/index.js +55 -2
- package/dist/commands/database/index.js +159 -0
- package/dist/controllers/app-env.js +10 -7
- package/dist/controllers/app.js +550 -208
- package/dist/controllers/auth.js +211 -2
- package/dist/controllers/branch.js +5 -6
- package/dist/controllers/database.js +318 -0
- package/dist/controllers/project.js +49 -20
- package/dist/lib/app/app-interaction.js +5 -0
- package/dist/lib/app/{preview-provider.js → app-provider.js} +32 -28
- package/dist/lib/app/{preview-branch-database.js → branch-database-api.js} +1 -1
- package/dist/lib/app/branch-database-deploy.js +13 -60
- package/dist/lib/app/branch-database.js +1 -102
- package/dist/lib/app/build-settings.js +93 -0
- package/dist/lib/app/build.js +82 -0
- package/dist/lib/app/bun-project.js +2 -3
- package/dist/lib/app/compute-config.js +147 -0
- package/dist/lib/app/deploy-plan.js +58 -0
- package/dist/lib/app/{preview-progress.js → deploy-progress.js} +5 -5
- package/dist/lib/app/local-dev.js +3 -60
- package/dist/lib/app/production-deploy-gate.js +3 -3
- package/dist/lib/app/read-branch.js +30 -0
- package/dist/lib/auth/auth-ops.js +10 -4
- package/dist/lib/auth/guard.js +4 -1
- package/dist/lib/auth/login.js +32 -25
- package/dist/lib/database/provider.js +167 -0
- package/dist/lib/diagnostics.js +1 -1
- package/dist/lib/fs/home-path.js +24 -0
- package/dist/lib/git/local-branch.js +15 -3
- package/dist/lib/project/local-pin.js +170 -40
- package/dist/lib/project/resolution.js +166 -61
- package/dist/lib/project/setup.js +65 -19
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/app.js +44 -39
- package/dist/presenters/auth.js +98 -1
- package/dist/presenters/branch.js +1 -1
- package/dist/presenters/database.js +274 -0
- package/dist/presenters/project.js +3 -9
- package/dist/shell/command-meta.js +153 -2
- package/dist/shell/command-runner.js +39 -21
- package/dist/shell/diagnostics-output.js +2 -8
- package/dist/shell/errors.js +50 -1
- package/dist/shell/help.js +30 -20
- package/dist/shell/runtime.js +8 -4
- package/dist/shell/ui.js +19 -2
- package/dist/use-cases/auth.js +68 -1
- package/package.json +18 -3
- package/dist/lib/app/preview-build-settings.js +0 -385
- package/dist/lib/app/preview-build.js +0 -475
- package/dist/lib/app/preview-interaction.js +0 -5
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import "./build-settings.js";
|
|
2
|
+
import { FRAMEWORKS } from "@prisma/compute-sdk/config";
|
|
3
|
+
import "node:fs/promises";
|
|
4
|
+
import "node:path";
|
|
5
|
+
import { normalizeArtifactSymlinks, resolveBuildStrategy } from "@prisma/compute-sdk";
|
|
6
|
+
//#region src/lib/app/build.ts
|
|
7
|
+
const RESOLVED_APP_BUILD_TYPES = [...frameworkBuildTypesByLastOccurrence()];
|
|
8
|
+
const APP_BUILD_TYPES = ["auto", ...RESOLVED_APP_BUILD_TYPES];
|
|
9
|
+
const APP_BUILD_TYPE_LABELS = APP_BUILD_TYPES.join(", ");
|
|
10
|
+
function frameworkBuildTypesByLastOccurrence() {
|
|
11
|
+
const buildTypes = /* @__PURE__ */ new Set();
|
|
12
|
+
for (const framework of FRAMEWORKS) {
|
|
13
|
+
if (buildTypes.has(framework.buildType)) buildTypes.delete(framework.buildType);
|
|
14
|
+
buildTypes.add(framework.buildType);
|
|
15
|
+
}
|
|
16
|
+
return buildTypes;
|
|
17
|
+
}
|
|
18
|
+
var AppBuildStrategy = class {
|
|
19
|
+
#appPath;
|
|
20
|
+
#entrypoint;
|
|
21
|
+
#buildType;
|
|
22
|
+
#signal;
|
|
23
|
+
#buildSettings;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.#appPath = options.appPath;
|
|
26
|
+
this.#entrypoint = options.entrypoint;
|
|
27
|
+
this.#buildType = options.buildType ?? "auto";
|
|
28
|
+
this.#signal = options.signal;
|
|
29
|
+
this.#buildSettings = options.buildSettings;
|
|
30
|
+
}
|
|
31
|
+
async canBuild(signal = this.#signal) {
|
|
32
|
+
const { strategy } = await resolveAppBuildStrategy({
|
|
33
|
+
appPath: this.#appPath,
|
|
34
|
+
entrypoint: this.#entrypoint,
|
|
35
|
+
buildType: this.#buildType,
|
|
36
|
+
signal,
|
|
37
|
+
buildSettings: this.#buildSettings
|
|
38
|
+
});
|
|
39
|
+
return strategy.canBuild(signal);
|
|
40
|
+
}
|
|
41
|
+
async execute(signal = this.#signal) {
|
|
42
|
+
const { artifact } = await executeAppBuild({
|
|
43
|
+
appPath: this.#appPath,
|
|
44
|
+
entrypoint: this.#entrypoint,
|
|
45
|
+
buildType: this.#buildType,
|
|
46
|
+
signal,
|
|
47
|
+
buildSettings: this.#buildSettings
|
|
48
|
+
});
|
|
49
|
+
return artifact;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
async function executeAppBuild(options) {
|
|
53
|
+
const { strategy, buildType } = await resolveAppBuildStrategy({
|
|
54
|
+
appPath: options.appPath,
|
|
55
|
+
entrypoint: options.entrypoint,
|
|
56
|
+
buildType: options.buildType ?? "auto",
|
|
57
|
+
signal: options.signal,
|
|
58
|
+
buildSettings: options.buildSettings
|
|
59
|
+
});
|
|
60
|
+
const artifact = await strategy.execute(options.signal);
|
|
61
|
+
try {
|
|
62
|
+
await normalizeArtifactSymlinks(artifact.directory, options.appPath, options.signal);
|
|
63
|
+
return {
|
|
64
|
+
artifact,
|
|
65
|
+
buildType
|
|
66
|
+
};
|
|
67
|
+
} catch (error) {
|
|
68
|
+
await artifact.cleanup?.().catch(() => void 0);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function resolveAppBuildStrategy(options) {
|
|
73
|
+
return resolveBuildStrategy({
|
|
74
|
+
appPath: options.appPath,
|
|
75
|
+
buildType: options.buildType,
|
|
76
|
+
entrypoint: options.entrypoint,
|
|
77
|
+
buildSettings: options.buildSettings,
|
|
78
|
+
signal: options.signal
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { APP_BUILD_TYPES, APP_BUILD_TYPE_LABELS, AppBuildStrategy, RESOLVED_APP_BUILD_TYPES, executeAppBuild };
|
|
@@ -21,13 +21,12 @@ async function readBunPackageJson(appPath, signal) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
function readBunPackageEntrypoint(packageJson) {
|
|
24
|
-
|
|
25
|
-
if (typeof packageJson?.module === "string") return packageJson.module;
|
|
24
|
+
return typeof packageJson?.main === "string" ? packageJson.main : void 0;
|
|
26
25
|
}
|
|
27
26
|
async function resolveBunEntrypoint(appPath, explicitEntrypoint, signal) {
|
|
28
27
|
const packageJson = await readBunPackageJson(appPath, signal);
|
|
29
28
|
const candidate = explicitEntrypoint ?? readBunPackageEntrypoint(packageJson);
|
|
30
|
-
if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main
|
|
29
|
+
if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main.");
|
|
31
30
|
if (path.isAbsolute(candidate)) throw new Error("Entrypoint must be a relative path.");
|
|
32
31
|
const normalized = path.normalize(candidate);
|
|
33
32
|
if (normalized.startsWith("..") || path.isAbsolute(normalized) || normalized.includes(`${path.sep}..${path.sep}`)) throw new Error("Entrypoint must not escape the app directory.");
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { CliError } from "../../shell/errors.js";
|
|
2
|
+
import { COMPUTE_CONFIG_FILENAME, COMPUTE_CONFIG_FILENAME as COMPUTE_CONFIG_FILENAME$1, ComputeConfigTargetRequiredError, computeTargetAppDir, frameworkByKey, inferComputeTargetFromCwd, loadComputeConfig, selectComputeDeployTarget } from "@prisma/compute-sdk/config";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { matchError } from "better-result";
|
|
5
|
+
//#region src/lib/app/compute-config.ts
|
|
6
|
+
/**
|
|
7
|
+
* Loads the nearest compute config, searching from `cwd` up to the source
|
|
8
|
+
* root (repository or workspace boundary). Thin adapter over the SDK loader
|
|
9
|
+
* keeping the CLI's positional-signal call shape.
|
|
10
|
+
*/
|
|
11
|
+
async function loadComputeConfig$1(cwd, signal) {
|
|
12
|
+
return loadComputeConfig(cwd, { signal });
|
|
13
|
+
}
|
|
14
|
+
/** Local build/run strategy implied by a configured framework. */
|
|
15
|
+
function computeFrameworkToBuildType(framework) {
|
|
16
|
+
return frameworkByKey(framework).buildType;
|
|
17
|
+
}
|
|
18
|
+
function mergeComputeDeployInputs(options) {
|
|
19
|
+
const { cli, target, configFilename } = options;
|
|
20
|
+
const configAnnotation = `set by ${configFilename}`;
|
|
21
|
+
const framework = cli.framework ? {
|
|
22
|
+
value: cli.framework,
|
|
23
|
+
annotation: "set by --framework"
|
|
24
|
+
} : target?.framework ? {
|
|
25
|
+
value: target.framework,
|
|
26
|
+
annotation: configAnnotation
|
|
27
|
+
} : void 0;
|
|
28
|
+
const entrypoint = cli.entrypoint ? {
|
|
29
|
+
value: cli.entrypoint,
|
|
30
|
+
annotation: "set by --entry"
|
|
31
|
+
} : target?.entry ? {
|
|
32
|
+
value: target.entry,
|
|
33
|
+
annotation: configAnnotation
|
|
34
|
+
} : void 0;
|
|
35
|
+
const httpPort = cli.httpPort ? {
|
|
36
|
+
value: cli.httpPort,
|
|
37
|
+
annotation: "set by --http-port"
|
|
38
|
+
} : target?.httpPort ? {
|
|
39
|
+
value: String(target.httpPort),
|
|
40
|
+
annotation: configAnnotation
|
|
41
|
+
} : void 0;
|
|
42
|
+
const cliEnvInputs = cli.envInputs && cli.envInputs.length > 0 ? cli.envInputs : void 0;
|
|
43
|
+
const configEnvInputs = target && target.envInputs.length > 0 ? target.envInputs : void 0;
|
|
44
|
+
const envInputs = cliEnvInputs ?? configEnvInputs;
|
|
45
|
+
const configAppName = target?.name ? {
|
|
46
|
+
value: target.name,
|
|
47
|
+
annotation: configAnnotation
|
|
48
|
+
} : target?.key ? {
|
|
49
|
+
value: target.key,
|
|
50
|
+
annotation: configAnnotation
|
|
51
|
+
} : void 0;
|
|
52
|
+
return {
|
|
53
|
+
framework,
|
|
54
|
+
entrypoint,
|
|
55
|
+
httpPort,
|
|
56
|
+
envInputs,
|
|
57
|
+
envInputsFromConfig: !cliEnvInputs && configEnvInputs !== void 0,
|
|
58
|
+
configAppName,
|
|
59
|
+
appRoot: target?.root ?? void 0
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Merges CLI inputs for the local `app build` and `app run` commands with a
|
|
64
|
+
* selected config target. Explicit flags win; `--build-type auto` is the
|
|
65
|
+
* flag default and defers to the configured framework.
|
|
66
|
+
*/
|
|
67
|
+
function mergeComputeLocalInputs(options) {
|
|
68
|
+
const { cli, target } = options;
|
|
69
|
+
const cliBuildType = cli.buildType && cli.buildType !== "auto" ? cli.buildType : void 0;
|
|
70
|
+
const configBuildType = target?.framework ? computeFrameworkToBuildType(target.framework) : void 0;
|
|
71
|
+
return {
|
|
72
|
+
entrypoint: cli.entrypoint ?? target?.entry ?? void 0,
|
|
73
|
+
buildType: cliBuildType ?? configBuildType,
|
|
74
|
+
buildTypeFromConfig: !cliBuildType && configBuildType !== void 0,
|
|
75
|
+
port: cli.port ?? (target?.httpPort ? String(target.httpPort) : void 0),
|
|
76
|
+
appRoot: target?.root ?? void 0
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function computeConfigErrorToCliError(error, commandName = "deploy") {
|
|
80
|
+
const command = `prisma-cli app ${commandName}`;
|
|
81
|
+
return matchError(error, {
|
|
82
|
+
ComputeConfigAmbiguousError: (ambiguous) => new CliError({
|
|
83
|
+
code: "COMPUTE_CONFIG_INVALID",
|
|
84
|
+
domain: "app",
|
|
85
|
+
summary: "Multiple compute config files found",
|
|
86
|
+
why: ambiguous.message,
|
|
87
|
+
fix: `Keep exactly one compute config file, preferably ${COMPUTE_CONFIG_FILENAME}.`,
|
|
88
|
+
meta: { configPaths: ambiguous.configPaths },
|
|
89
|
+
exitCode: 2,
|
|
90
|
+
nextSteps: [command]
|
|
91
|
+
}),
|
|
92
|
+
ComputeConfigLoadError: (load) => new CliError({
|
|
93
|
+
code: "COMPUTE_CONFIG_INVALID",
|
|
94
|
+
domain: "app",
|
|
95
|
+
summary: `Could not load ${path.basename(load.configPath)}`,
|
|
96
|
+
why: load.message,
|
|
97
|
+
fix: `Fix the error in ${path.basename(load.configPath)} and rerun the command.`,
|
|
98
|
+
where: load.configPath,
|
|
99
|
+
meta: { configPath: load.configPath },
|
|
100
|
+
exitCode: 2,
|
|
101
|
+
nextSteps: [command]
|
|
102
|
+
}),
|
|
103
|
+
ComputeConfigInvalidError: (invalid) => new CliError({
|
|
104
|
+
code: "COMPUTE_CONFIG_INVALID",
|
|
105
|
+
domain: "app",
|
|
106
|
+
summary: `Invalid ${path.basename(invalid.configPath)}`,
|
|
107
|
+
why: invalid.issues.join(" "),
|
|
108
|
+
fix: `Edit ${path.basename(invalid.configPath)} so it default-exports defineComputeConfig({ app }) or defineComputeConfig({ apps }).`,
|
|
109
|
+
where: invalid.configPath,
|
|
110
|
+
meta: {
|
|
111
|
+
configPath: invalid.configPath,
|
|
112
|
+
issues: invalid.issues
|
|
113
|
+
},
|
|
114
|
+
exitCode: 2,
|
|
115
|
+
nextSteps: [command]
|
|
116
|
+
}),
|
|
117
|
+
ComputeConfigTargetRequiredError: (required) => new CliError({
|
|
118
|
+
code: "COMPUTE_CONFIG_TARGET_REQUIRED",
|
|
119
|
+
domain: "app",
|
|
120
|
+
summary: "App target required",
|
|
121
|
+
why: required.message,
|
|
122
|
+
fix: `Pass the app target, for example ${command} <target>.`,
|
|
123
|
+
meta: {
|
|
124
|
+
configPath: required.configPath,
|
|
125
|
+
availableTargets: required.availableTargets
|
|
126
|
+
},
|
|
127
|
+
exitCode: 2,
|
|
128
|
+
nextSteps: required.availableTargets.map((target) => `${command} ${target}`)
|
|
129
|
+
}),
|
|
130
|
+
ComputeConfigTargetUnknownError: (unknown) => new CliError({
|
|
131
|
+
code: "COMPUTE_CONFIG_TARGET_UNKNOWN",
|
|
132
|
+
domain: "app",
|
|
133
|
+
summary: `Unknown app target "${unknown.requestedTarget}"`,
|
|
134
|
+
why: unknown.message,
|
|
135
|
+
fix: unknown.availableTargets.length > 0 ? `Pass one of the configured targets: ${unknown.availableTargets.join(", ")}.` : "Remove the target argument; this config defines a single app.",
|
|
136
|
+
meta: {
|
|
137
|
+
configPath: unknown.configPath,
|
|
138
|
+
requestedTarget: unknown.requestedTarget,
|
|
139
|
+
availableTargets: unknown.availableTargets
|
|
140
|
+
},
|
|
141
|
+
exitCode: 2,
|
|
142
|
+
nextSteps: unknown.availableTargets.length > 0 ? unknown.availableTargets.map((target) => `${command} ${target}`) : [command]
|
|
143
|
+
})
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
export { COMPUTE_CONFIG_FILENAME$1 as COMPUTE_CONFIG_FILENAME, ComputeConfigTargetRequiredError, computeConfigErrorToCliError, computeFrameworkToBuildType, computeTargetAppDir, inferComputeTargetFromCwd, loadComputeConfig$1 as loadComputeConfig, mergeComputeDeployInputs, mergeComputeLocalInputs, selectComputeDeployTarget };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region src/lib/app/deploy-plan.ts
|
|
2
|
+
/**
|
|
3
|
+
* Decides whether a deploy covers one app or the whole system. A multi-app
|
|
4
|
+
* config with no target named or inferred deploys every target in declaration
|
|
5
|
+
* order; everything else (single-app config, an explicit/inferred target, or
|
|
6
|
+
* no config at all) is a single deploy.
|
|
7
|
+
*/
|
|
8
|
+
function planAppDeploy(options) {
|
|
9
|
+
const { config, requestedTarget, hasCreateProject } = options;
|
|
10
|
+
if (!(config !== null && config.kind === "multi" && !requestedTarget && config.targets.length > 1)) return { mode: "single" };
|
|
11
|
+
const total = config.targets.length;
|
|
12
|
+
return {
|
|
13
|
+
mode: "all",
|
|
14
|
+
targets: config.targets.map((target, index) => ({
|
|
15
|
+
targetKey: target.key,
|
|
16
|
+
index,
|
|
17
|
+
total,
|
|
18
|
+
bindsCreateProject: index === 0 && hasCreateProject
|
|
19
|
+
}))
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Names the per-app inputs present in a deploy-all invocation, in the order
|
|
24
|
+
* they should be reported. An empty list means the run is unambiguous; the
|
|
25
|
+
* caller renders the usage error when it is not.
|
|
26
|
+
*/
|
|
27
|
+
function perAppInputsForDeployAll(inputs) {
|
|
28
|
+
return [
|
|
29
|
+
["--app", inputs.appName],
|
|
30
|
+
["--framework", inputs.framework],
|
|
31
|
+
["--entry", inputs.entrypoint],
|
|
32
|
+
["--http-port", inputs.httpPort],
|
|
33
|
+
["--env", inputs.envAssignments?.length ? inputs.envAssignments : void 0],
|
|
34
|
+
[inputs.appIdEnvVar.name, inputs.appIdEnvVar.value]
|
|
35
|
+
].filter(([, value]) => value !== void 0).map(([flag]) => flag);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Describes a deploy-all run that stopped at the first failing target: which
|
|
39
|
+
* targets already went live and which were never attempted, so the caller can
|
|
40
|
+
* surface a converge path. Pure; the caller folds this into the CliError.
|
|
41
|
+
*/
|
|
42
|
+
function describeDeployAllFailure(options) {
|
|
43
|
+
const { targetKeys, failedIndex, completed } = options;
|
|
44
|
+
const failedTarget = targetKeys[failedIndex];
|
|
45
|
+
const notAttempted = targetKeys.slice(failedIndex + 1);
|
|
46
|
+
return {
|
|
47
|
+
failedTarget,
|
|
48
|
+
completed,
|
|
49
|
+
notAttempted,
|
|
50
|
+
contextLines: [
|
|
51
|
+
`Deploying all apps stopped at "${failedTarget}" (${failedIndex + 1}/${targetKeys.length}).`,
|
|
52
|
+
...completed.length > 0 ? [`Already live: ${completed.map((entry) => entry.target).join(", ")}.`] : [],
|
|
53
|
+
...notAttempted.length > 0 ? [`Not attempted: ${notAttempted.join(", ")}.`] : []
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { describeDeployAllFailure, perAppInputsForDeployAll, planAppDeploy };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { renderDeployOutputRows } from "./deploy-output.js";
|
|
2
|
-
//#region src/lib/app/
|
|
3
|
-
function
|
|
2
|
+
//#region src/lib/app/deploy-progress.ts
|
|
3
|
+
function createDeployProgressState() {
|
|
4
4
|
return {
|
|
5
5
|
buildStarted: false,
|
|
6
6
|
buildCompleted: false,
|
|
@@ -13,7 +13,7 @@ function createPreviewDeployProgressState() {
|
|
|
13
13
|
promotedUrl: null
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
function
|
|
16
|
+
function createDeployProgress(output, ui, enabled, state = createDeployProgressState()) {
|
|
17
17
|
const write = (line) => {
|
|
18
18
|
if (!enabled) return;
|
|
19
19
|
output.write(`${line}\n`);
|
|
@@ -63,7 +63,7 @@ function createPreviewDeployProgress(output, ui, enabled, state = createPreviewD
|
|
|
63
63
|
function formatArtifactSize(byteLength) {
|
|
64
64
|
return `${(byteLength / 1024 / 1024).toFixed(1)} MB`;
|
|
65
65
|
}
|
|
66
|
-
function
|
|
66
|
+
function createPromoteProgress(output, enabled) {
|
|
67
67
|
if (!enabled) return;
|
|
68
68
|
const write = (line) => {
|
|
69
69
|
output.write(`${line}\n`);
|
|
@@ -97,4 +97,4 @@ function createPreviewPromoteProgress(output, enabled) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
//#endregion
|
|
100
|
-
export {
|
|
100
|
+
export { createDeployProgress, createDeployProgressState, createPromoteProgress };
|
|
@@ -1,25 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { resolveBunEntrypoint } from "./bun-project.js";
|
|
2
|
+
import "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
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
6
|
const DEFAULT_LOCAL_DEV_PORT = 3e3;
|
|
13
|
-
async function resolveLocalBuildType(appPath, buildType, signal) {
|
|
14
|
-
if (buildType === "bun" || buildType === "nextjs") return buildType;
|
|
15
|
-
if (buildType !== "auto") return null;
|
|
16
|
-
return detectLocalBuildType(appPath, signal);
|
|
17
|
-
}
|
|
18
|
-
async function detectLocalBuildType(appPath, signal) {
|
|
19
|
-
if (await isNextProject(appPath, signal)) return "nextjs";
|
|
20
|
-
if (await isBunProject(appPath, signal)) return "bun";
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
7
|
async function runLocalApp(options) {
|
|
24
8
|
const spawnImpl = options.spawnImpl ?? spawn;
|
|
25
9
|
if (options.buildType === "nextjs") {
|
|
@@ -92,47 +76,6 @@ async function runLocalApp(options) {
|
|
|
92
76
|
signal: command.signal
|
|
93
77
|
};
|
|
94
78
|
}
|
|
95
|
-
async function isNextProject(appPath, signal) {
|
|
96
|
-
for (const fileName of NEXT_CONFIG_FILENAMES) {
|
|
97
|
-
signal?.throwIfAborted();
|
|
98
|
-
try {
|
|
99
|
-
await access(path.join(appPath, fileName));
|
|
100
|
-
signal?.throwIfAborted();
|
|
101
|
-
return true;
|
|
102
|
-
} catch (error) {
|
|
103
|
-
if (signal?.aborted) throw error;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return hasDependency(await readBunPackageJson(appPath, signal), "next");
|
|
107
|
-
}
|
|
108
|
-
async function isBunProject(appPath, signal) {
|
|
109
|
-
signal?.throwIfAborted();
|
|
110
|
-
try {
|
|
111
|
-
await access(path.join(appPath, "bun.lock"));
|
|
112
|
-
signal?.throwIfAborted();
|
|
113
|
-
return true;
|
|
114
|
-
} catch (error) {
|
|
115
|
-
if (signal?.aborted) throw error;
|
|
116
|
-
}
|
|
117
|
-
signal?.throwIfAborted();
|
|
118
|
-
try {
|
|
119
|
-
await access(path.join(appPath, "bun.lockb"));
|
|
120
|
-
signal?.throwIfAborted();
|
|
121
|
-
return true;
|
|
122
|
-
} catch (error) {
|
|
123
|
-
if (signal?.aborted) throw error;
|
|
124
|
-
}
|
|
125
|
-
const packageJson = await readBunPackageJson(appPath, signal);
|
|
126
|
-
if (!packageJson) return false;
|
|
127
|
-
const hasEntrypoint = typeof readBunPackageEntrypoint(packageJson) === "string";
|
|
128
|
-
const hasBunDependency = hasDependency(packageJson, "@types/bun") || hasDependency(packageJson, "bun");
|
|
129
|
-
const usesBunScripts = (typeof packageJson.scripts === "object" && packageJson.scripts !== null ? Object.values(packageJson.scripts) : []).some((value) => typeof value === "string" && /\bbun\b/.test(value));
|
|
130
|
-
return hasEntrypoint && (hasBunDependency || usesBunScripts);
|
|
131
|
-
}
|
|
132
|
-
function hasDependency(packageJson, dependencyName) {
|
|
133
|
-
if (!packageJson) return false;
|
|
134
|
-
return [packageJson.dependencies, packageJson.devDependencies].some((group) => typeof group === "object" && group !== null && dependencyName in group);
|
|
135
|
-
}
|
|
136
79
|
async function runWithFallback(candidates, options, spawnImpl, missingCommandMessage) {
|
|
137
80
|
for (const candidate of candidates) try {
|
|
138
81
|
const result = await spawnCommand(candidate, options, spawnImpl);
|
|
@@ -163,4 +106,4 @@ function spawnCommand(candidate, options, spawnImpl) {
|
|
|
163
106
|
});
|
|
164
107
|
}
|
|
165
108
|
//#endregion
|
|
166
|
-
export { DEFAULT_LOCAL_DEV_PORT,
|
|
109
|
+
export { DEFAULT_LOCAL_DEV_PORT, runLocalApp };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "../../shell/errors.js";
|
|
2
|
-
import { canPrompt } from "../../shell/runtime.js";
|
|
3
2
|
import { confirmPrompt } from "../../shell/prompt.js";
|
|
3
|
+
import { canPrompt } from "../../shell/runtime.js";
|
|
4
4
|
//#region src/lib/app/production-deploy-gate.ts
|
|
5
5
|
async function enforceProductionDeployGate(context, provider, options) {
|
|
6
6
|
if (options.branchKind !== "production") return { firstProductionDeploy: false };
|
|
@@ -104,12 +104,12 @@ function productionDeployRequiresFlagError() {
|
|
|
104
104
|
"",
|
|
105
105
|
"Production deploys require explicit intent. Re-run with:",
|
|
106
106
|
"",
|
|
107
|
-
" prisma app deploy --prod",
|
|
107
|
+
" prisma-cli app deploy --prod",
|
|
108
108
|
"",
|
|
109
109
|
"Or deploy a preview from a feature branch:",
|
|
110
110
|
"",
|
|
111
111
|
" git checkout -b <branch-name>",
|
|
112
|
-
" prisma app deploy"
|
|
112
|
+
" prisma-cli app deploy"
|
|
113
113
|
]
|
|
114
114
|
});
|
|
115
115
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/lib/app/read-branch.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the branch an app management command should read from, without ever
|
|
4
|
+
* creating one. Returns the branch whose `gitName` matches `branchName`, else
|
|
5
|
+
* the project's default branch, else null when the project has no branches.
|
|
6
|
+
*/
|
|
7
|
+
async function resolveReadBranch(client, options) {
|
|
8
|
+
const branches = [];
|
|
9
|
+
let cursor;
|
|
10
|
+
do {
|
|
11
|
+
const result = await client.GET("/v1/projects/{projectId}/branches", {
|
|
12
|
+
params: {
|
|
13
|
+
path: { projectId: options.projectId },
|
|
14
|
+
query: { cursor }
|
|
15
|
+
},
|
|
16
|
+
signal: options.signal
|
|
17
|
+
});
|
|
18
|
+
if (result.error || !result.data) throw new Error(`Failed to list branches for project ${options.projectId}: ${JSON.stringify(result.error)}`);
|
|
19
|
+
branches.push(...result.data.data);
|
|
20
|
+
cursor = result.data.pagination.hasMore ? result.data.pagination.nextCursor ?? void 0 : void 0;
|
|
21
|
+
} while (cursor);
|
|
22
|
+
const chosen = branches.find((branch) => branch.gitName === options.branchName) ?? branches.find((branch) => branch.isDefault) ?? null;
|
|
23
|
+
return chosen ? {
|
|
24
|
+
id: chosen.id,
|
|
25
|
+
name: chosen.gitName,
|
|
26
|
+
kind: chosen.role
|
|
27
|
+
} : null;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { resolveReadBranch };
|
|
@@ -26,7 +26,7 @@ function workspaceIdFromClaims(claims) {
|
|
|
26
26
|
}
|
|
27
27
|
async function performLogin(env, signal) {
|
|
28
28
|
await login({
|
|
29
|
-
tokenStorage: new FileTokenStorage(env, signal),
|
|
29
|
+
tokenStorage: new FileTokenStorage(env, signal, { activateOnSetTokens: true }),
|
|
30
30
|
env,
|
|
31
31
|
signal
|
|
32
32
|
});
|
|
@@ -38,7 +38,8 @@ async function readAuthState(env, signal) {
|
|
|
38
38
|
if (serviceToken.length === 0) throw new Error(`${SERVICE_TOKEN_ENV_VAR} is set but empty. Provide a valid token or unset the variable.`);
|
|
39
39
|
return readServiceTokenAuthState(serviceToken, env, signal);
|
|
40
40
|
}
|
|
41
|
-
const
|
|
41
|
+
const tokenStorage = new FileTokenStorage(env, signal);
|
|
42
|
+
const tokens = await tokenStorage.getTokens();
|
|
42
43
|
if (!tokens) return {
|
|
43
44
|
authenticated: false,
|
|
44
45
|
provider: null,
|
|
@@ -48,15 +49,20 @@ async function readAuthState(env, signal) {
|
|
|
48
49
|
};
|
|
49
50
|
const client = await requireComputeAuth(env, signal);
|
|
50
51
|
const currentPrincipal = await readCurrentPrincipalAuthState(client, signal);
|
|
51
|
-
if (currentPrincipal)
|
|
52
|
+
if (currentPrincipal) {
|
|
53
|
+
if (currentPrincipal.authenticated && currentPrincipal.workspace) await tokenStorage.rememberWorkspace?.(tokens.workspaceId, currentPrincipal.workspace);
|
|
54
|
+
return currentPrincipal;
|
|
55
|
+
}
|
|
52
56
|
const claims = decodeJwtPayload(tokens.accessToken);
|
|
53
|
-
|
|
57
|
+
const authState = await buildAuthState({
|
|
54
58
|
workspaceIdFromCredential: tokens.workspaceId,
|
|
55
59
|
claims,
|
|
56
60
|
env,
|
|
57
61
|
client,
|
|
58
62
|
signal
|
|
59
63
|
});
|
|
64
|
+
if (authState.authenticated && authState.workspace) await tokenStorage.rememberWorkspace?.(tokens.workspaceId, authState.workspace);
|
|
65
|
+
return authState;
|
|
60
66
|
}
|
|
61
67
|
async function readServiceTokenAuthState(token, env, signal) {
|
|
62
68
|
const client = await requireComputeAuth(env, signal);
|
package/dist/lib/auth/guard.js
CHANGED
|
@@ -22,7 +22,10 @@ async function requireComputeAuth(env = process.env, signal) {
|
|
|
22
22
|
token
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
-
const tokenStorage = new FileTokenStorage(env, signal
|
|
25
|
+
const tokenStorage = new FileTokenStorage(env, signal, {
|
|
26
|
+
activateOnSetTokens: false,
|
|
27
|
+
lockSetTokens: false
|
|
28
|
+
});
|
|
26
29
|
if (!await tokenStorage.getTokens()) return null;
|
|
27
30
|
return createManagementApiSdk({
|
|
28
31
|
clientId: CLIENT_ID,
|
package/dist/lib/auth/login.js
CHANGED
|
@@ -106,35 +106,42 @@ async function consumePastedCallback(options) {
|
|
|
106
106
|
});
|
|
107
107
|
try {
|
|
108
108
|
for (;;) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (error?.name === "AbortError") return;
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
const trimmed = answer.trim().replace(/^["']|["']$/g, "");
|
|
117
|
-
let url;
|
|
118
|
-
try {
|
|
119
|
-
if (!trimmed) throw new Error("empty input");
|
|
120
|
-
url = new URL(trimmed);
|
|
121
|
-
} catch {
|
|
122
|
-
options.output.write("That didn't look like a URL. Paste the full localhost callback URL and try again.\n");
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
try {
|
|
126
|
-
await options.complete(url);
|
|
127
|
-
return;
|
|
128
|
-
} catch (error) {
|
|
129
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
130
|
-
options.output.write(`Sign-in didn't complete (${message}). Paste the callback URL to try again.\n`);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
109
|
+
const url = await readPastedCallbackUrl(rl, options);
|
|
110
|
+
if (url === null) return;
|
|
111
|
+
if (url === void 0) continue;
|
|
112
|
+
if (await tryCompletePastedCallback(url, options)) return;
|
|
133
113
|
}
|
|
134
114
|
} finally {
|
|
135
115
|
rl.close();
|
|
136
116
|
}
|
|
137
117
|
}
|
|
118
|
+
async function readPastedCallbackUrl(rl, options) {
|
|
119
|
+
let answer;
|
|
120
|
+
try {
|
|
121
|
+
answer = await rl.question("Paste the callback URL here: ", { signal: options.signal });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (error?.name === "AbortError") return null;
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
const trimmed = answer.trim().replace(/^["']|["']$/g, "");
|
|
127
|
+
try {
|
|
128
|
+
if (!trimmed) throw new Error("empty input");
|
|
129
|
+
return new URL(trimmed);
|
|
130
|
+
} catch {
|
|
131
|
+
options.output.write("That didn't look like a URL. Paste the full localhost callback URL and try again.\n");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function tryCompletePastedCallback(url, options) {
|
|
136
|
+
try {
|
|
137
|
+
await options.complete(url);
|
|
138
|
+
return true;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
141
|
+
options.output.write(`Sign-in didn't complete (${message}). Paste the callback URL to try again.\n`);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
138
145
|
var LoginState = class {
|
|
139
146
|
latestVerifier;
|
|
140
147
|
latestState;
|
|
@@ -144,7 +151,7 @@ var LoginState = class {
|
|
|
144
151
|
output;
|
|
145
152
|
constructor(options) {
|
|
146
153
|
this.options = options;
|
|
147
|
-
this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env, options.signal);
|
|
154
|
+
this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env, options.signal, { activateOnSetTokens: true });
|
|
148
155
|
this.sdk = createManagementApiSdk({
|
|
149
156
|
clientId: options.clientId ?? "cmm3lndn701oo0uefvxzo0ivw",
|
|
150
157
|
redirectUri: `http://${options.hostname}:${options.port}/auth/callback`,
|