@prisma/cli 3.0.0-dev.53.1 → 3.0.0-dev.56.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/adapters/git.js +8 -3
- package/dist/adapters/local-state.js +11 -3
- package/dist/adapters/mock-api.js +6 -2
- package/dist/adapters/token-storage.js +63 -22
- package/dist/cli.js +14 -3
- package/dist/cli2.js +1 -0
- package/dist/commands/app/index.js +4 -2
- package/dist/controllers/app-env.js +78 -46
- package/dist/controllers/app.js +136 -76
- package/dist/controllers/auth.js +8 -8
- package/dist/controllers/project.js +107 -67
- package/dist/lib/app/bun-project.js +12 -5
- package/dist/lib/app/local-dev.js +34 -18
- package/dist/lib/app/preview-build.js +90 -62
- package/dist/lib/app/preview-provider.js +143 -54
- package/dist/lib/app/production-deploy-gate.js +160 -0
- package/dist/lib/auth/auth-ops.js +30 -21
- package/dist/lib/auth/guard.js +3 -2
- package/dist/lib/auth/login.js +31 -17
- package/dist/lib/project/interactive-setup.js +1 -1
- package/dist/lib/project/local-pin.js +27 -8
- package/dist/lib/project/resolution.js +14 -8
- package/dist/lib/project/setup.js +2 -2
- package/dist/shell/command-meta.js +1 -0
- package/dist/shell/command-runner.js +9 -4
- package/dist/shell/errors.js +12 -1
- package/dist/shell/runtime.js +2 -2
- package/package.json +1 -1
package/dist/adapters/git.js
CHANGED
|
@@ -2,7 +2,7 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
//#region src/adapters/git.ts
|
|
4
4
|
const execFileAsync = promisify(execFile);
|
|
5
|
-
async function readGitOriginRemote(cwd) {
|
|
5
|
+
async function readGitOriginRemote(cwd, signal) {
|
|
6
6
|
try {
|
|
7
7
|
const { stdout } = await execFileAsync("git", [
|
|
8
8
|
"config",
|
|
@@ -10,14 +10,19 @@ async function readGitOriginRemote(cwd) {
|
|
|
10
10
|
"remote.origin.url"
|
|
11
11
|
], {
|
|
12
12
|
cwd,
|
|
13
|
-
timeout: 5e3
|
|
13
|
+
timeout: 5e3,
|
|
14
|
+
signal
|
|
14
15
|
});
|
|
15
16
|
const remote = stdout.trim();
|
|
16
17
|
return remote.length > 0 ? remote : null;
|
|
17
|
-
} catch {
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (signal?.aborted || isAbortError(error)) throw error;
|
|
18
20
|
return null;
|
|
19
21
|
}
|
|
20
22
|
}
|
|
23
|
+
function isAbortError(error) {
|
|
24
|
+
return error instanceof Error && error.name === "AbortError";
|
|
25
|
+
}
|
|
21
26
|
function parseGitHubRepositoryUrl(value) {
|
|
22
27
|
const input = value.trim();
|
|
23
28
|
const shorthand = input.match(/^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?$/);
|
|
@@ -20,12 +20,17 @@ function resolveLocalStateFilePath(stateDir) {
|
|
|
20
20
|
}
|
|
21
21
|
var LocalStateStore = class {
|
|
22
22
|
stateFilePath;
|
|
23
|
-
constructor(stateDir) {
|
|
23
|
+
constructor(stateDir, signal) {
|
|
24
|
+
this.signal = signal;
|
|
24
25
|
this.stateFilePath = resolveLocalStateFilePath(stateDir);
|
|
25
26
|
}
|
|
26
27
|
async read() {
|
|
28
|
+
this.signal?.throwIfAborted();
|
|
27
29
|
try {
|
|
28
|
-
const raw = await readFile(this.stateFilePath,
|
|
30
|
+
const raw = await readFile(this.stateFilePath, {
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
signal: this.signal
|
|
33
|
+
});
|
|
29
34
|
const parsed = JSON.parse(raw);
|
|
30
35
|
return {
|
|
31
36
|
auth: parsed.auth ?? structuredClone(DEFAULT_STATE.auth),
|
|
@@ -46,8 +51,11 @@ var LocalStateStore = class {
|
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
async write(state) {
|
|
54
|
+
this.signal?.throwIfAborted();
|
|
49
55
|
await mkdir(path.dirname(this.stateFilePath), { recursive: true });
|
|
50
|
-
|
|
56
|
+
this.signal?.throwIfAborted();
|
|
57
|
+
await writeFile(this.stateFilePath, `${JSON.stringify(state, null, 2)}\n`, { encoding: "utf8" });
|
|
58
|
+
this.signal?.throwIfAborted();
|
|
51
59
|
}
|
|
52
60
|
async setAuthSession(session) {
|
|
53
61
|
const state = await this.read();
|
|
@@ -5,8 +5,12 @@ var MockApi = class MockApi {
|
|
|
5
5
|
constructor(data) {
|
|
6
6
|
this.data = data;
|
|
7
7
|
}
|
|
8
|
-
static async load(fixturePath) {
|
|
9
|
-
|
|
8
|
+
static async load(fixturePath, signal) {
|
|
9
|
+
signal?.throwIfAborted();
|
|
10
|
+
const raw = await readFile(fixturePath, {
|
|
11
|
+
encoding: "utf8",
|
|
12
|
+
signal
|
|
13
|
+
});
|
|
10
14
|
return new MockApi(JSON.parse(raw));
|
|
11
15
|
}
|
|
12
16
|
listProviders() {
|
|
@@ -20,37 +20,60 @@ function findLatestValidTokens(allCredentials) {
|
|
|
20
20
|
function tokensEqual(a, b) {
|
|
21
21
|
return a?.workspaceId === b?.workspaceId && a?.accessToken === b?.accessToken && a?.refreshToken === b?.refreshToken;
|
|
22
22
|
}
|
|
23
|
-
function sleep(ms) {
|
|
24
|
-
|
|
23
|
+
function sleep(ms, signal) {
|
|
24
|
+
signal?.throwIfAborted();
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const onAbort = () => {
|
|
27
|
+
clearTimeout(timeout);
|
|
28
|
+
reject(signal?.reason);
|
|
29
|
+
};
|
|
30
|
+
const timeout = setTimeout(() => {
|
|
31
|
+
signal?.removeEventListener("abort", onAbort);
|
|
32
|
+
resolve();
|
|
33
|
+
}, ms);
|
|
34
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
35
|
+
});
|
|
25
36
|
}
|
|
26
37
|
var FileTokenStorage = class {
|
|
27
38
|
credentialsStore;
|
|
28
39
|
lockFilePath;
|
|
29
|
-
constructor(env = process.env) {
|
|
40
|
+
constructor(env = process.env, signal) {
|
|
41
|
+
this.signal = signal;
|
|
30
42
|
const authFilePath = getAuthFilePath(env);
|
|
31
43
|
this.credentialsStore = new CredentialsStore(authFilePath);
|
|
32
44
|
this.lockFilePath = `${authFilePath}.lock`;
|
|
33
45
|
}
|
|
34
46
|
async getTokens() {
|
|
47
|
+
this.signal?.throwIfAborted();
|
|
35
48
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
const all = await this.credentialsStore.getCredentials();
|
|
50
|
+
this.signal?.throwIfAborted();
|
|
51
|
+
return findLatestValidTokens(all);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (this.signal?.aborted) throw this.signal.reason;
|
|
38
54
|
return null;
|
|
39
55
|
}
|
|
40
56
|
}
|
|
41
57
|
async setTokens(tokens) {
|
|
58
|
+
this.signal?.throwIfAborted();
|
|
42
59
|
await this.credentialsStore.storeCredentials({
|
|
43
60
|
workspaceId: tokens.workspaceId,
|
|
44
61
|
token: tokens.accessToken,
|
|
45
62
|
refreshToken: tokens.refreshToken
|
|
46
63
|
});
|
|
64
|
+
this.signal?.throwIfAborted();
|
|
47
65
|
}
|
|
48
66
|
async clearTokens() {
|
|
49
|
-
|
|
67
|
+
this.signal?.throwIfAborted();
|
|
68
|
+
const all = await this.credentialsStore.getCredentials();
|
|
69
|
+
this.signal?.throwIfAborted();
|
|
70
|
+
const tokens = findLatestValidTokens(all);
|
|
50
71
|
if (!tokens) return;
|
|
51
72
|
await this.credentialsStore.deleteCredentials(tokens.workspaceId);
|
|
73
|
+
this.signal?.throwIfAborted();
|
|
52
74
|
}
|
|
53
75
|
async clearTokensIfCurrent(tokens) {
|
|
76
|
+
this.signal?.throwIfAborted();
|
|
54
77
|
if (!tokensEqual(await this.getTokens(), tokens)) return;
|
|
55
78
|
await this.clearTokens();
|
|
56
79
|
}
|
|
@@ -64,34 +87,52 @@ var FileTokenStorage = class {
|
|
|
64
87
|
}
|
|
65
88
|
async acquireRefreshLock() {
|
|
66
89
|
const lockId = randomUUID();
|
|
90
|
+
this.signal?.throwIfAborted();
|
|
67
91
|
await fs.mkdir(path.dirname(this.lockFilePath), { recursive: true });
|
|
68
|
-
while (true)
|
|
69
|
-
|
|
92
|
+
while (true) {
|
|
93
|
+
this.signal?.throwIfAborted();
|
|
94
|
+
let lockFileCreated = false;
|
|
70
95
|
try {
|
|
71
|
-
await
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
96
|
+
const handle = await fs.open(this.lockFilePath, "wx");
|
|
97
|
+
lockFileCreated = true;
|
|
98
|
+
try {
|
|
99
|
+
this.signal?.throwIfAborted();
|
|
100
|
+
await handle.writeFile(lockId, { encoding: "utf8" });
|
|
101
|
+
this.signal?.throwIfAborted();
|
|
102
|
+
} finally {
|
|
103
|
+
await handle.close();
|
|
104
|
+
}
|
|
105
|
+
return lockId;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (lockFileCreated) await fs.unlink(this.lockFilePath).catch(() => void 0);
|
|
108
|
+
if (error.code !== "EEXIST") throw error;
|
|
109
|
+
const staleLockId = await this.getStaleRefreshLockId();
|
|
110
|
+
if (staleLockId) {
|
|
111
|
+
await this.releaseRefreshLock(staleLockId);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
await sleep(100, this.signal);
|
|
82
115
|
}
|
|
83
|
-
await sleep(100);
|
|
84
116
|
}
|
|
85
117
|
}
|
|
86
118
|
async getStaleRefreshLockId() {
|
|
87
|
-
|
|
119
|
+
this.signal?.throwIfAborted();
|
|
120
|
+
const lockId = await fs.readFile(this.lockFilePath, {
|
|
121
|
+
encoding: "utf8",
|
|
122
|
+
signal: this.signal
|
|
123
|
+
}).catch((error) => {
|
|
124
|
+
if (this.signal?.aborted) throw error;
|
|
125
|
+
return null;
|
|
126
|
+
});
|
|
88
127
|
if (lockId === null) return null;
|
|
128
|
+
this.signal?.throwIfAborted();
|
|
89
129
|
const stats = await fs.stat(this.lockFilePath).catch(() => null);
|
|
130
|
+
this.signal?.throwIfAborted();
|
|
90
131
|
if (!stats) return null;
|
|
91
132
|
return Date.now() - stats.mtimeMs > 3e4 ? lockId : null;
|
|
92
133
|
}
|
|
93
134
|
async releaseRefreshLock(lockId) {
|
|
94
|
-
if (await fs.readFile(this.lockFilePath, "utf8").catch(() => null) !== lockId) return;
|
|
135
|
+
if (await fs.readFile(this.lockFilePath, { encoding: "utf8" }).catch(() => null) !== lockId) return;
|
|
95
136
|
await fs.unlink(this.lockFilePath).catch(() => {});
|
|
96
137
|
}
|
|
97
138
|
};
|
package/dist/cli.js
CHANGED
|
@@ -6,8 +6,19 @@ import process from "node:process";
|
|
|
6
6
|
if (process.env.PRISMA_CLI_RUN_UPDATE_CHECK_WORKER === "1") runUpdateDiscoveryWorker().then(() => {
|
|
7
7
|
process.exitCode = 0;
|
|
8
8
|
});
|
|
9
|
-
else
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
else {
|
|
10
|
+
const controller = new AbortController();
|
|
11
|
+
const abortCli = () => {
|
|
12
|
+
if (!controller.signal.aborted) controller.abort(new DOMException("Command canceled", "AbortError"));
|
|
13
|
+
};
|
|
14
|
+
process.once("SIGINT", abortCli);
|
|
15
|
+
process.once("SIGTERM", abortCli);
|
|
16
|
+
runCli({ signal: controller.signal }).then((exitCode) => {
|
|
17
|
+
process.exitCode = exitCode;
|
|
18
|
+
}).finally(() => {
|
|
19
|
+
process.off("SIGINT", abortCli);
|
|
20
|
+
process.off("SIGTERM", abortCli);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
12
23
|
//#endregion
|
|
13
24
|
export {};
|
package/dist/cli2.js
CHANGED
|
@@ -106,6 +106,7 @@ function resolveRuntime(options) {
|
|
|
106
106
|
argv: options.argv ?? process.argv.slice(2),
|
|
107
107
|
cwd: options.cwd ?? process.env.INIT_CWD ?? process.cwd(),
|
|
108
108
|
env: options.env ?? process.env,
|
|
109
|
+
signal: options.signal ?? new AbortController().signal,
|
|
109
110
|
stdin: options.stdin ?? process.stdin,
|
|
110
111
|
stdout: options.stdout ?? process.stdout,
|
|
111
112
|
stderr: options.stderr ?? process.stderr,
|
|
@@ -64,7 +64,7 @@ function createDeployCommand(runtime) {
|
|
|
64
64
|
"hono",
|
|
65
65
|
"tanstack-start",
|
|
66
66
|
"bun"
|
|
67
|
-
])).addOption(new Option("--entry <path>", "Entrypoint path for Bun deploys")).addOption(new Option("--http-port <port>", "HTTP port override for the deployed app")).addOption(new Option("--env <name=value>", "Environment variable").argParser(collectRepeatableValues));
|
|
67
|
+
])).addOption(new Option("--entry <path>", "Entrypoint path for Bun deploys")).addOption(new Option("--http-port <port>", "HTTP port override for the deployed app")).addOption(new Option("--env <name=value>", "Environment variable").argParser(collectRepeatableValues)).addOption(new Option("--prod", "Confirm intent to deploy to production"));
|
|
68
68
|
addGlobalFlags(command);
|
|
69
69
|
command.action(async (options) => {
|
|
70
70
|
const appName = options.app;
|
|
@@ -75,6 +75,7 @@ function createDeployCommand(runtime) {
|
|
|
75
75
|
const envAssignments = options.env;
|
|
76
76
|
const projectRef = options.project;
|
|
77
77
|
const createProjectName = options.createProject;
|
|
78
|
+
const prod = options.prod;
|
|
78
79
|
await runCommand(runtime, "app.deploy", options, (context) => runAppDeploy(context, appName, {
|
|
79
80
|
projectRef,
|
|
80
81
|
createProjectName,
|
|
@@ -82,7 +83,8 @@ function createDeployCommand(runtime) {
|
|
|
82
83
|
entrypoint: entry,
|
|
83
84
|
framework,
|
|
84
85
|
httpPort,
|
|
85
|
-
envAssignments
|
|
86
|
+
envAssignments,
|
|
87
|
+
prod: prod === true
|
|
86
88
|
}), {
|
|
87
89
|
renderHuman: (context, descriptor, result) => renderAppDeploy(context, descriptor, result),
|
|
88
90
|
renderJson: (result) => serializeAppDeploy(result)
|
|
@@ -19,8 +19,11 @@ async function runEnvAdd(context, rawAssignment, flags) {
|
|
|
19
19
|
});
|
|
20
20
|
if (!scope) throw usageError(`prisma-cli project env add requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma-cli project env add ${key}=${value} --role production`], "app");
|
|
21
21
|
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env add");
|
|
22
|
-
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
23
|
-
|
|
22
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
23
|
+
createBranchIfMissing: true,
|
|
24
|
+
signal: context.runtime.signal
|
|
25
|
+
});
|
|
26
|
+
if (await findVariableByNaturalKey(client, projectId, key, resolved, context.runtime.signal)) throw new CliError({
|
|
24
27
|
code: "ENV_VARIABLE_ALREADY_EXISTS",
|
|
25
28
|
domain: "app",
|
|
26
29
|
summary: `Variable "${key}" already exists in ${formatScopeLabel(scope)}`,
|
|
@@ -42,14 +45,17 @@ async function runEnvAdd(context, rawAssignment, flags) {
|
|
|
42
45
|
class: "preview",
|
|
43
46
|
branchId: null
|
|
44
47
|
}
|
|
45
|
-
}) ? [`Variable "${key}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`] : [];
|
|
46
|
-
const { data, error, response } = await client.POST("/v1/environment-variables", {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
}, context.runtime.signal) ? [`Variable "${key}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`] : [];
|
|
49
|
+
const { data, error, response } = await client.POST("/v1/environment-variables", {
|
|
50
|
+
body: {
|
|
51
|
+
projectId,
|
|
52
|
+
class: resolved.apiTarget.class,
|
|
53
|
+
...resolved.apiTarget.branchId !== null ? { branchId: resolved.apiTarget.branchId } : {},
|
|
54
|
+
key,
|
|
55
|
+
value
|
|
56
|
+
},
|
|
57
|
+
signal: context.runtime.signal
|
|
58
|
+
});
|
|
53
59
|
if (error || !data) throw apiCallError(`Failed to add ${key}`, response, error);
|
|
54
60
|
return {
|
|
55
61
|
command: "project.env.add",
|
|
@@ -70,8 +76,11 @@ async function runEnvUpdate(context, rawAssignment, flags) {
|
|
|
70
76
|
});
|
|
71
77
|
if (!scope) throw usageError(`prisma-cli project env update requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma-cli project env update ${key}=${value} --role production`], "app");
|
|
72
78
|
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env update");
|
|
73
|
-
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
74
|
-
|
|
79
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
80
|
+
createBranchIfMissing: false,
|
|
81
|
+
signal: context.runtime.signal
|
|
82
|
+
});
|
|
83
|
+
const existing = await findVariableByNaturalKey(client, projectId, key, resolved, context.runtime.signal);
|
|
75
84
|
if (!existing) throw new CliError({
|
|
76
85
|
code: "ENV_VARIABLE_NOT_FOUND",
|
|
77
86
|
domain: "app",
|
|
@@ -83,7 +92,8 @@ async function runEnvUpdate(context, rawAssignment, flags) {
|
|
|
83
92
|
});
|
|
84
93
|
const { data, error, response } = await client.PATCH("/v1/environment-variables/{envVarId}", {
|
|
85
94
|
params: { path: { envVarId: existing.id } },
|
|
86
|
-
body: { value }
|
|
95
|
+
body: { value },
|
|
96
|
+
signal: context.runtime.signal
|
|
87
97
|
});
|
|
88
98
|
if (error || !data) throw apiCallError(`Failed to update value for ${key}`, response, error);
|
|
89
99
|
return {
|
|
@@ -103,8 +113,11 @@ async function runEnvList(context, flags) {
|
|
|
103
113
|
command: "list"
|
|
104
114
|
}) ?? defaultRoleScope();
|
|
105
115
|
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env list");
|
|
106
|
-
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
107
|
-
|
|
116
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
117
|
+
createBranchIfMissing: false,
|
|
118
|
+
signal: context.runtime.signal
|
|
119
|
+
});
|
|
120
|
+
const variables = await listVariables(client, projectId, resolved, context.runtime.signal);
|
|
108
121
|
return {
|
|
109
122
|
command: "project.env.list",
|
|
110
123
|
result: {
|
|
@@ -124,8 +137,11 @@ async function runEnvRemove(context, key, flags) {
|
|
|
124
137
|
});
|
|
125
138
|
if (!scope) throw usageError("prisma-cli project env remove requires --role or --branch", "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma-cli project env remove ${key} --role production`], "app");
|
|
126
139
|
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env remove");
|
|
127
|
-
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
128
|
-
|
|
140
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
141
|
+
createBranchIfMissing: false,
|
|
142
|
+
signal: context.runtime.signal
|
|
143
|
+
});
|
|
144
|
+
const existing = await findVariableByNaturalKey(client, projectId, key, resolved, context.runtime.signal);
|
|
129
145
|
if (!existing) throw new CliError({
|
|
130
146
|
code: "ENV_VARIABLE_NOT_FOUND",
|
|
131
147
|
domain: "app",
|
|
@@ -135,7 +151,10 @@ async function runEnvRemove(context, key, flags) {
|
|
|
135
151
|
exitCode: 1,
|
|
136
152
|
nextSteps: [`prisma-cli project env list ${formatScopeFlag(scope)}`]
|
|
137
153
|
});
|
|
138
|
-
const { error, response } = await client.DELETE("/v1/environment-variables/{envVarId}", {
|
|
154
|
+
const { error, response } = await client.DELETE("/v1/environment-variables/{envVarId}", {
|
|
155
|
+
params: { path: { envVarId: existing.id } },
|
|
156
|
+
signal: context.runtime.signal
|
|
157
|
+
});
|
|
139
158
|
if (error) throw apiCallError(`Failed to remove ${key}`, response, error);
|
|
140
159
|
return {
|
|
141
160
|
command: "project.env.remove",
|
|
@@ -150,7 +169,7 @@ async function runEnvRemove(context, key, flags) {
|
|
|
150
169
|
}
|
|
151
170
|
async function requireClientAndProject(context, explicitProject, commandName) {
|
|
152
171
|
const authState = await requireAuthenticatedAuthState(context);
|
|
153
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
172
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
154
173
|
if (!client) throw authRequiredError(["prisma-cli auth login"]);
|
|
155
174
|
if (!authState.workspace) throw workspaceRequiredError();
|
|
156
175
|
return {
|
|
@@ -159,7 +178,7 @@ async function requireClientAndProject(context, explicitProject, commandName) {
|
|
|
159
178
|
context,
|
|
160
179
|
workspace: authState.workspace,
|
|
161
180
|
explicitProject,
|
|
162
|
-
listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
|
|
181
|
+
listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
|
|
163
182
|
commandName
|
|
164
183
|
})).project.id
|
|
165
184
|
};
|
|
@@ -176,7 +195,7 @@ async function resolveScopeToApi(client, projectId, scope, options) {
|
|
|
176
195
|
branchId: null
|
|
177
196
|
}
|
|
178
197
|
};
|
|
179
|
-
const branch = options.createBranchIfMissing ? await resolveOrCreateBranch(client, projectId, scope.branchName) : await resolveExistingBranch(client, projectId, scope.branchName);
|
|
198
|
+
const branch = options.createBranchIfMissing ? await resolveOrCreateBranch(client, projectId, scope.branchName, options.signal) : await resolveExistingBranch(client, projectId, scope.branchName, options.signal);
|
|
180
199
|
if (branch.isDefault) throw new CliError({
|
|
181
200
|
code: "ENV_BRANCH_SCOPE_IS_PRODUCTION",
|
|
182
201
|
domain: "app",
|
|
@@ -203,16 +222,19 @@ function formatScopeFlag(scope) {
|
|
|
203
222
|
if (scope.kind === "role") return `--role ${scope.role}`;
|
|
204
223
|
return `--branch ${scope.branchName}`;
|
|
205
224
|
}
|
|
206
|
-
async function listBranchesByName(client, projectId, branchName) {
|
|
207
|
-
const { data, error, response } = await client.GET("/v1/projects/{projectId}/branches", {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
225
|
+
async function listBranchesByName(client, projectId, branchName, signal) {
|
|
226
|
+
const { data, error, response } = await client.GET("/v1/projects/{projectId}/branches", {
|
|
227
|
+
params: {
|
|
228
|
+
path: { projectId },
|
|
229
|
+
query: { gitName: branchName }
|
|
230
|
+
},
|
|
231
|
+
signal
|
|
232
|
+
});
|
|
211
233
|
if (error || !data) throw apiCallError(`Failed to resolve branch "${branchName}"`, response, error);
|
|
212
234
|
return data.data;
|
|
213
235
|
}
|
|
214
|
-
async function resolveExistingBranch(client, projectId, branchName) {
|
|
215
|
-
const branch = (await listBranchesByName(client, projectId, branchName))[0];
|
|
236
|
+
async function resolveExistingBranch(client, projectId, branchName, signal) {
|
|
237
|
+
const branch = (await listBranchesByName(client, projectId, branchName, signal))[0];
|
|
216
238
|
if (!branch) throw new CliError({
|
|
217
239
|
code: "ENV_BRANCH_NOT_FOUND",
|
|
218
240
|
domain: "app",
|
|
@@ -224,10 +246,10 @@ async function resolveExistingBranch(client, projectId, branchName) {
|
|
|
224
246
|
});
|
|
225
247
|
return branch;
|
|
226
248
|
}
|
|
227
|
-
async function resolveOrCreateBranch(client, projectId, branchName) {
|
|
228
|
-
const existing = (await listBranchesByName(client, projectId, branchName))[0];
|
|
249
|
+
async function resolveOrCreateBranch(client, projectId, branchName, signal) {
|
|
250
|
+
const existing = (await listBranchesByName(client, projectId, branchName, signal))[0];
|
|
229
251
|
if (existing) return existing;
|
|
230
|
-
if (!await projectHasDefaultBranch(client, projectId)) throw new CliError({
|
|
252
|
+
if (!await projectHasDefaultBranch(client, projectId, signal)) throw new CliError({
|
|
231
253
|
code: "ENV_BRANCH_CREATE_REQUIRES_DEFAULT_BRANCH",
|
|
232
254
|
domain: "app",
|
|
233
255
|
summary: `Cannot create branch "${branchName}" from project env`,
|
|
@@ -241,42 +263,49 @@ async function resolveOrCreateBranch(client, projectId, branchName) {
|
|
|
241
263
|
body: {
|
|
242
264
|
gitName: branchName,
|
|
243
265
|
isDefault: false
|
|
244
|
-
}
|
|
266
|
+
},
|
|
267
|
+
signal
|
|
245
268
|
});
|
|
246
269
|
if (error || !data) {
|
|
247
270
|
if (response?.status === 409) {
|
|
248
|
-
const raced = (await listBranchesByName(client, projectId, branchName))[0];
|
|
271
|
+
const raced = (await listBranchesByName(client, projectId, branchName, signal))[0];
|
|
249
272
|
if (raced) return raced;
|
|
250
273
|
}
|
|
251
274
|
throw apiCallError(`Failed to create branch "${branchName}"`, response, error);
|
|
252
275
|
}
|
|
253
276
|
return data.data;
|
|
254
277
|
}
|
|
255
|
-
async function projectHasDefaultBranch(client, projectId) {
|
|
278
|
+
async function projectHasDefaultBranch(client, projectId, signal) {
|
|
256
279
|
let cursor;
|
|
257
280
|
while (true) {
|
|
258
281
|
const query = {};
|
|
259
282
|
if (cursor !== void 0) query.cursor = cursor;
|
|
260
|
-
const result = await client.GET("/v1/projects/{projectId}/branches", {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
283
|
+
const result = await client.GET("/v1/projects/{projectId}/branches", {
|
|
284
|
+
params: {
|
|
285
|
+
path: { projectId },
|
|
286
|
+
query
|
|
287
|
+
},
|
|
288
|
+
signal
|
|
289
|
+
});
|
|
264
290
|
if (result.error || !result.data) throw apiCallError("Failed to check project default branch", result.response, result.error);
|
|
265
291
|
if (result.data.data.some((branch) => branch.isDefault)) return true;
|
|
266
292
|
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) return false;
|
|
267
293
|
cursor = result.data.pagination.nextCursor;
|
|
268
294
|
}
|
|
269
295
|
}
|
|
270
|
-
async function findVariableByNaturalKey(client, projectId, key, resolved) {
|
|
271
|
-
const { data, error, response } = await client.GET("/v1/environment-variables", {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
296
|
+
async function findVariableByNaturalKey(client, projectId, key, resolved, signal) {
|
|
297
|
+
const { data, error, response } = await client.GET("/v1/environment-variables", {
|
|
298
|
+
params: { query: {
|
|
299
|
+
projectId,
|
|
300
|
+
class: resolved.apiTarget.class,
|
|
301
|
+
key
|
|
302
|
+
} },
|
|
303
|
+
signal
|
|
304
|
+
});
|
|
276
305
|
if (error || !data) throw apiCallError(`Failed to look up ${key}`, response, error);
|
|
277
306
|
return data.data.filter((row) => rowMatchesExactScope(row, resolved))[0] ?? null;
|
|
278
307
|
}
|
|
279
|
-
async function listVariables(client, projectId, resolved) {
|
|
308
|
+
async function listVariables(client, projectId, resolved, signal) {
|
|
280
309
|
const collected = [];
|
|
281
310
|
let cursor;
|
|
282
311
|
while (true) {
|
|
@@ -285,7 +314,10 @@ async function listVariables(client, projectId, resolved) {
|
|
|
285
314
|
class: resolved.apiTarget.class
|
|
286
315
|
};
|
|
287
316
|
if (cursor !== void 0) query.cursor = cursor;
|
|
288
|
-
const result = await client.GET("/v1/environment-variables", {
|
|
317
|
+
const result = await client.GET("/v1/environment-variables", {
|
|
318
|
+
params: { query },
|
|
319
|
+
signal
|
|
320
|
+
});
|
|
289
321
|
if (result.error || !result.data) throw apiCallError(`Failed to list environment variables`, result.response, result.error);
|
|
290
322
|
const page = result.data.data.filter((row) => rowMatchesScope(row, resolved));
|
|
291
323
|
collected.push(...page);
|