@prisma/cli 3.0.0-beta.2 → 3.0.0-beta.21
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 +5 -16
- package/dist/adapters/git.js +8 -3
- package/dist/adapters/local-state.js +26 -7
- package/dist/adapters/mock-api.js +81 -2
- package/dist/adapters/token-storage.js +344 -25
- package/dist/cli.js +18 -3
- package/dist/cli2.js +12 -4
- package/dist/commands/agent/index.js +60 -0
- package/dist/commands/app/index.js +90 -61
- package/dist/commands/auth/index.js +55 -2
- package/dist/commands/branch/index.js +2 -27
- package/dist/commands/build/index.js +29 -0
- package/dist/commands/database/index.js +159 -0
- package/dist/commands/env.js +8 -4
- package/dist/commands/git/index.js +1 -1
- package/dist/commands/project/index.js +1 -1
- package/dist/controllers/agent.js +228 -0
- package/dist/controllers/app-env-api.js +55 -0
- package/dist/controllers/app-env-file.js +181 -0
- package/dist/controllers/app-env.js +286 -131
- package/dist/controllers/app.js +849 -309
- package/dist/controllers/auth.js +255 -11
- package/dist/controllers/branch.js +78 -48
- package/dist/controllers/build.js +88 -0
- package/dist/controllers/database.js +318 -0
- package/dist/controllers/project.js +156 -87
- package/dist/controllers/select-prompt-port.js +1 -0
- package/dist/lib/agent/cli-command.js +17 -0
- package/dist/lib/agent/constants.js +12 -0
- package/dist/lib/agent/package-manager.js +99 -0
- package/dist/lib/agent/setup-status.js +83 -0
- package/dist/lib/app/app-interaction.js +5 -0
- package/dist/lib/app/{preview-provider.js → app-provider.js} +220 -104
- package/dist/lib/app/branch-database-api.js +102 -0
- package/dist/lib/app/branch-database-deploy.js +326 -0
- package/dist/lib/app/branch-database.js +216 -0
- package/dist/lib/app/build-settings.js +93 -0
- package/dist/lib/app/build.js +82 -0
- package/dist/lib/app/bun-project.js +15 -9
- package/dist/lib/app/compute-config.js +145 -0
- package/dist/lib/app/deploy-plan.js +59 -0
- package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
- package/dist/lib/app/env-config.js +1 -1
- package/dist/lib/app/env-file.js +82 -0
- package/dist/lib/app/env-vars.js +28 -2
- package/dist/lib/app/local-dev.js +8 -49
- package/dist/lib/app/production-deploy-gate.js +162 -0
- package/dist/lib/app/read-branch.js +30 -0
- package/dist/lib/auth/auth-ops.js +38 -23
- package/dist/lib/auth/guard.js +6 -2
- package/dist/lib/auth/login.js +117 -15
- package/dist/lib/database/provider.js +167 -0
- package/dist/lib/diagnostics.js +15 -0
- package/dist/lib/fs/home-path.js +24 -0
- package/dist/lib/git/local-branch.js +53 -0
- package/dist/lib/git/local-status.js +57 -0
- package/dist/lib/project/interactive-setup.js +2 -1
- package/dist/lib/project/local-pin.js +183 -34
- package/dist/lib/project/resolution.js +206 -50
- package/dist/lib/project/setup.js +64 -18
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/agent.js +74 -0
- package/dist/presenters/app-env.js +149 -14
- package/dist/presenters/app.js +187 -26
- package/dist/presenters/auth.js +99 -2
- package/dist/presenters/branch.js +37 -102
- package/dist/presenters/database.js +274 -0
- package/dist/presenters/project.js +44 -26
- package/dist/presenters/verbose-context.js +64 -0
- package/dist/shell/cli-command.js +12 -0
- package/dist/shell/command-arguments.js +7 -1
- package/dist/shell/command-meta.js +243 -15
- package/dist/shell/command-runner.js +60 -21
- package/dist/shell/diagnostics-output.js +57 -0
- package/dist/shell/errors.js +61 -1
- package/dist/shell/help.js +31 -20
- package/dist/shell/output.js +10 -1
- package/dist/shell/prompt.js +7 -3
- package/dist/shell/runtime.js +10 -6
- package/dist/shell/ui.js +42 -3
- package/dist/shell/update-check.js +247 -0
- package/dist/use-cases/auth.js +68 -1
- package/dist/use-cases/branch.js +20 -68
- package/dist/use-cases/create-cli-gateways.js +2 -17
- package/package.json +27 -10
- package/dist/lib/app/preview-build.js +0 -284
- package/dist/lib/app/preview-interaction.js +0 -5
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
1
2
|
import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
3
|
import { renderSummaryLine } from "../shell/ui.js";
|
|
3
4
|
import { canPrompt } from "../shell/runtime.js";
|
|
4
|
-
import {
|
|
5
|
-
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
5
|
+
import { createAppProvider } from "../lib/app/app-provider.js";
|
|
6
6
|
import { readLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
7
|
-
import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
8
|
-
import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
7
|
+
import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
8
|
+
import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
9
|
+
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
9
10
|
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
10
|
-
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
11
11
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
12
12
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
13
13
|
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
14
14
|
import { createProjectUseCases } from "../use-cases/project.js";
|
|
15
|
+
import { matchError } from "better-result";
|
|
15
16
|
import open from "open";
|
|
16
17
|
//#region src/controllers/project.ts
|
|
17
18
|
const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
|
|
@@ -19,21 +20,34 @@ const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
|
|
|
19
20
|
function isRealMode(context) {
|
|
20
21
|
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
21
22
|
}
|
|
22
|
-
async function readProjectListLocalBinding(cwd, workspace, projects) {
|
|
23
|
-
const
|
|
23
|
+
async function readProjectListLocalBinding(cwd, workspace, projects, signal) {
|
|
24
|
+
const pinResult = await readLocalResolutionPin(cwd, signal);
|
|
25
|
+
if (pinResult.isErr()) return localPinReadErrorToInvalidLocalBinding(pinResult.error);
|
|
26
|
+
const pin = pinResult.value;
|
|
24
27
|
if (pin.kind === "present") return pin.pin.workspaceId === workspace.id && projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
|
|
25
|
-
if (pin.kind === "invalid") return { status: "invalid" };
|
|
26
28
|
return { status: "not-linked" };
|
|
27
29
|
}
|
|
30
|
+
function localPinReadErrorToInvalidLocalBinding(error) {
|
|
31
|
+
return matchError(error, {
|
|
32
|
+
LocalResolutionPinInvalidJsonError: () => ({ status: "invalid" }),
|
|
33
|
+
LocalResolutionPinInvalidShapeError: () => ({ status: "invalid" }),
|
|
34
|
+
LocalResolutionPinReadAbortedError: (error) => {
|
|
35
|
+
throw error;
|
|
36
|
+
},
|
|
37
|
+
UnhandledException: (error) => {
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
28
42
|
async function runProjectList(context) {
|
|
29
43
|
const authState = await requireAuthenticatedAuthState(context);
|
|
30
44
|
const workspace = authState.workspace;
|
|
31
45
|
if (!workspace) throw workspaceRequiredError();
|
|
32
46
|
if (isRealMode(context)) {
|
|
33
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
47
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
34
48
|
if (!client) throw authRequiredError();
|
|
35
|
-
const projects = sortProjects(await listRealWorkspaceProjects(client, workspace));
|
|
36
|
-
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects);
|
|
49
|
+
const projects = sortProjects(await listRealWorkspaceProjects(client, workspace, context.runtime.signal));
|
|
50
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects, context.runtime.signal);
|
|
37
51
|
const nextActions = buildProjectListNextActions(localBinding);
|
|
38
52
|
return {
|
|
39
53
|
command: "project.list",
|
|
@@ -48,7 +62,7 @@ async function runProjectList(context) {
|
|
|
48
62
|
};
|
|
49
63
|
}
|
|
50
64
|
const result = await createProjectUseCases(createCliUseCaseGateways(context)).list(authState);
|
|
51
|
-
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects);
|
|
65
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects, context.runtime.signal);
|
|
52
66
|
const nextActions = buildProjectListNextActions(localBinding);
|
|
53
67
|
return {
|
|
54
68
|
command: "project.list",
|
|
@@ -88,23 +102,28 @@ async function runProjectCreate(context, projectName) {
|
|
|
88
102
|
if (!workspace) throw workspaceRequiredError();
|
|
89
103
|
if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
|
|
90
104
|
if (!isRealMode(context)) throw featureUnavailableError("Project create is not available in fixture mode", "Creating Projects requires live platform integration.", "Rerun without fixture mode enabled to create a Project.", ["prisma-cli auth login"], "project");
|
|
91
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
105
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
92
106
|
if (!client) throw authRequiredError();
|
|
93
|
-
const provider =
|
|
107
|
+
const provider = createAppProvider(client);
|
|
94
108
|
const name = projectName.trim();
|
|
95
|
-
const created = await provider.createProject({
|
|
109
|
+
const created = await provider.createProject({
|
|
110
|
+
name,
|
|
111
|
+
signal: context.runtime.signal
|
|
112
|
+
}).catch((error) => {
|
|
96
113
|
throw projectCreateFailedError(error, name, workspace, {
|
|
97
114
|
nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"],
|
|
98
115
|
permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
|
|
99
116
|
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
100
117
|
});
|
|
101
118
|
});
|
|
119
|
+
const bindResult = await bindProjectToDirectory(context, workspace, {
|
|
120
|
+
id: created.id,
|
|
121
|
+
name: created.name
|
|
122
|
+
}, "created");
|
|
123
|
+
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
102
124
|
return {
|
|
103
125
|
command: "project.create",
|
|
104
|
-
result:
|
|
105
|
-
id: created.id,
|
|
106
|
-
name: created.name
|
|
107
|
-
}, "created"),
|
|
126
|
+
result: bindResult.value,
|
|
108
127
|
warnings: [],
|
|
109
128
|
nextSteps: ["prisma-cli app deploy"]
|
|
110
129
|
};
|
|
@@ -115,13 +134,13 @@ async function runProjectLink(context, projectRef) {
|
|
|
115
134
|
let provider = null;
|
|
116
135
|
let projects;
|
|
117
136
|
if (isRealMode(context)) {
|
|
118
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
137
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
119
138
|
if (!client) throw authRequiredError();
|
|
120
|
-
provider =
|
|
121
|
-
projects = await listRealWorkspaceProjects(client, workspace);
|
|
139
|
+
provider = createAppProvider(client);
|
|
140
|
+
projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
|
|
122
141
|
} else projects = listFixtureWorkspaceProjects(context, workspace);
|
|
123
142
|
let result;
|
|
124
|
-
if (projectRef?.trim()) result = await
|
|
143
|
+
if (projectRef?.trim()) result = await requireProjectDirectoryBinding(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
|
|
125
144
|
else if (canPrompt(context) && !context.flags.yes) result = await resolveInteractiveProjectLinkSetup(context, workspace, projects, provider);
|
|
126
145
|
else throw await projectLinkTargetRequiredError(context, projects);
|
|
127
146
|
return {
|
|
@@ -137,7 +156,7 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
|
|
|
137
156
|
projects,
|
|
138
157
|
createProject: (projectName) => {
|
|
139
158
|
if (!provider) throw featureUnavailableError("Project create is not available in fixture mode", "Creating Projects requires live platform integration.", "Rerun without fixture mode enabled to create a Project.", ["prisma-cli auth login"], "project");
|
|
140
|
-
return createProjectForLinkSetup(provider, projectName, workspace);
|
|
159
|
+
return createProjectForLinkSetup(provider, projectName, workspace, context.runtime.signal);
|
|
141
160
|
},
|
|
142
161
|
cancel: {
|
|
143
162
|
why: "Project link needs a Project before it can continue.",
|
|
@@ -145,10 +164,18 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
|
|
|
145
164
|
nextSteps: ["prisma-cli project link <id-or-name>", "prisma-cli project create <name>"]
|
|
146
165
|
}
|
|
147
166
|
});
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
async function
|
|
151
|
-
const
|
|
167
|
+
return requireProjectDirectoryBinding(context, workspace, setup.project, setup.action);
|
|
168
|
+
}
|
|
169
|
+
async function requireProjectDirectoryBinding(context, workspace, project, action) {
|
|
170
|
+
const bindResult = await bindProjectToDirectory(context, workspace, project, action);
|
|
171
|
+
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
172
|
+
return bindResult.value;
|
|
173
|
+
}
|
|
174
|
+
async function createProjectForLinkSetup(provider, projectName, workspace, signal) {
|
|
175
|
+
const created = await provider.createProject({
|
|
176
|
+
name: projectName,
|
|
177
|
+
signal
|
|
178
|
+
}).catch((error) => {
|
|
152
179
|
throw projectCreateFailedError(error, projectName, workspace, {
|
|
153
180
|
nextSteps: [
|
|
154
181
|
"prisma-cli project list",
|
|
@@ -166,7 +193,7 @@ async function createProjectForLinkSetup(provider, projectName, workspace) {
|
|
|
166
193
|
};
|
|
167
194
|
}
|
|
168
195
|
async function projectLinkTargetRequiredError(context, projects) {
|
|
169
|
-
const suggestedName = await inferTargetName(context.runtime.cwd);
|
|
196
|
+
const suggestedName = await inferTargetName(context.runtime.cwd, context.runtime.signal);
|
|
170
197
|
const createCommand = `prisma-cli project create ${formatCommandArgument(suggestedName.name)}`;
|
|
171
198
|
const recoveryCommands = ["prisma-cli project link <id-or-name>", createCommand];
|
|
172
199
|
return new CliError({
|
|
@@ -194,12 +221,12 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
194
221
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
195
222
|
if (!workspace) throw workspaceRequiredError();
|
|
196
223
|
if (isRealMode(context)) {
|
|
197
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
224
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
198
225
|
if (!client) throw authRequiredError();
|
|
199
226
|
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
|
|
200
227
|
const repository = await resolveRepositoryForConnect(context, gitUrl);
|
|
201
228
|
const api = client;
|
|
202
|
-
const existing = await readFirstSourceRepository(api, target.project.id);
|
|
229
|
+
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
203
230
|
if (existing) {
|
|
204
231
|
const existingConnection = toRepositoryConnection(existing);
|
|
205
232
|
if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
|
|
@@ -214,12 +241,15 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
214
241
|
throw repoAlreadyConnectedError(existingConnection.repository.fullName);
|
|
215
242
|
}
|
|
216
243
|
const resolvedRepository = await resolveInstalledRepository(context, api, workspace.id, repository);
|
|
217
|
-
const { data, error, response } = await api.POST("/v1/source-repositories", {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
244
|
+
const { data, error, response } = await api.POST("/v1/source-repositories", {
|
|
245
|
+
body: {
|
|
246
|
+
projectId: target.project.id,
|
|
247
|
+
provider: "github",
|
|
248
|
+
providerRepositoryId: resolvedRepository.repository.id,
|
|
249
|
+
installationId: resolvedRepository.installation.id
|
|
250
|
+
},
|
|
251
|
+
signal: context.runtime.signal
|
|
252
|
+
});
|
|
223
253
|
if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
|
|
224
254
|
return {
|
|
225
255
|
command: "git.connect",
|
|
@@ -262,13 +292,16 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
262
292
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
263
293
|
if (!workspace) throw workspaceRequiredError();
|
|
264
294
|
if (isRealMode(context)) {
|
|
265
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
295
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
266
296
|
if (!client) throw authRequiredError();
|
|
267
297
|
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
|
|
268
298
|
const api = client;
|
|
269
|
-
const existing = await readFirstSourceRepository(api, target.project.id);
|
|
299
|
+
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
270
300
|
if (!existing) throw repoNotConnectedError();
|
|
271
|
-
const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
|
|
301
|
+
const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
|
|
302
|
+
params: { path: { id: existing.id } },
|
|
303
|
+
signal: context.runtime.signal
|
|
304
|
+
});
|
|
272
305
|
if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
|
|
273
306
|
return {
|
|
274
307
|
command: "git.disconnect",
|
|
@@ -295,47 +328,55 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
295
328
|
};
|
|
296
329
|
}
|
|
297
330
|
async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
|
|
298
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
331
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
299
332
|
if (!client) throw authRequiredError();
|
|
300
|
-
|
|
333
|
+
const result = await inspectProjectBinding({
|
|
301
334
|
context,
|
|
302
335
|
workspace,
|
|
303
336
|
explicitProject,
|
|
304
|
-
listProjects: () => listRealWorkspaceProjects(client, workspace),
|
|
337
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
305
338
|
commandName: "project show"
|
|
306
339
|
});
|
|
340
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
341
|
+
return result.value;
|
|
307
342
|
}
|
|
308
343
|
async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
|
|
309
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
344
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
310
345
|
if (!client) throw authRequiredError();
|
|
311
|
-
|
|
346
|
+
const result = await resolveProjectTarget({
|
|
312
347
|
context,
|
|
313
348
|
workspace,
|
|
314
349
|
explicitProject,
|
|
315
|
-
listProjects: () => listRealWorkspaceProjects(client, workspace),
|
|
350
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
316
351
|
commandName
|
|
317
352
|
});
|
|
353
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
354
|
+
return result.value;
|
|
318
355
|
}
|
|
319
356
|
async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
|
|
320
|
-
|
|
357
|
+
const result = await inspectProjectBinding({
|
|
321
358
|
context,
|
|
322
359
|
workspace,
|
|
323
360
|
explicitProject,
|
|
324
361
|
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
325
362
|
commandName: "project show"
|
|
326
363
|
});
|
|
364
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
365
|
+
return result.value;
|
|
327
366
|
}
|
|
328
367
|
async function resolveRequiredProjectInFixtureMode(context, workspace, explicitProject, commandName) {
|
|
329
|
-
|
|
368
|
+
const result = await resolveProjectTarget({
|
|
330
369
|
context,
|
|
331
370
|
workspace,
|
|
332
371
|
explicitProject,
|
|
333
372
|
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
334
373
|
commandName
|
|
335
374
|
});
|
|
375
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
376
|
+
return result.value;
|
|
336
377
|
}
|
|
337
|
-
async function listRealWorkspaceProjects(client, workspace) {
|
|
338
|
-
const { data } = await client.GET("/v1/projects", {});
|
|
378
|
+
async function listRealWorkspaceProjects(client, workspace, signal) {
|
|
379
|
+
const { data } = await client.GET("/v1/projects", { signal });
|
|
339
380
|
return sortProjects((data?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
|
|
340
381
|
id: project.id,
|
|
341
382
|
name: project.name,
|
|
@@ -357,16 +398,16 @@ function listFixtureWorkspaceProjects(context, workspace) {
|
|
|
357
398
|
})));
|
|
358
399
|
}
|
|
359
400
|
async function resolveRepositoryForConnect(context, gitUrl) {
|
|
360
|
-
const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd);
|
|
401
|
+
const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
|
|
361
402
|
if (!remoteUrl) throw usageError("Repository connection requires a GitHub repository URL", "No git-url was provided and the local repo does not have an origin remote.", "Pass a GitHub repository URL, or add a GitHub origin remote and rerun prisma-cli git connect.", ["prisma-cli git connect git@github.com:prisma/prisma-cli.git"], "project");
|
|
362
403
|
const repository = parseGitHubRepositoryUrl(remoteUrl);
|
|
363
404
|
if (!repository) throw unsupportedRepositoryProviderError();
|
|
364
405
|
return repository;
|
|
365
406
|
}
|
|
366
407
|
async function resolveInstalledRepository(context, api, workspaceId, repository) {
|
|
367
|
-
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId), repository);
|
|
408
|
+
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
|
|
368
409
|
if (lookup.match) return lookup.match;
|
|
369
|
-
const installUrl = await createGitHubInstallIntent(api, workspaceId);
|
|
410
|
+
const installUrl = await createGitHubInstallIntent(api, workspaceId, context.runtime.signal);
|
|
370
411
|
const canWait = canPrompt(context);
|
|
371
412
|
const opened = await openInstallUrlIfInteractive(context, installUrl);
|
|
372
413
|
if (!canWait) {
|
|
@@ -379,11 +420,11 @@ async function resolveInstalledRepository(context, api, workspaceId, repository)
|
|
|
379
420
|
if (result.inspectableInstallationCount > 0) throw repoNotAccessibleError(repository, installUrl, opened);
|
|
380
421
|
throw repoInstallationRequiredError(repository, installUrl, opened);
|
|
381
422
|
}
|
|
382
|
-
async function findRepositoryInInstallations(api, installations, repository) {
|
|
423
|
+
async function findRepositoryInInstallations(api, installations, repository, signal) {
|
|
383
424
|
let inspectableInstallationCount = 0;
|
|
384
425
|
for (const installation of installations) {
|
|
385
426
|
if (installation.provider !== "github" || installation.suspended) continue;
|
|
386
|
-
const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository);
|
|
427
|
+
const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository, signal);
|
|
387
428
|
if (matchedRepository === "unavailable") continue;
|
|
388
429
|
inspectableInstallationCount += 1;
|
|
389
430
|
if (matchedRepository) return {
|
|
@@ -405,7 +446,8 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
|
|
|
405
446
|
const deadline = Date.now() + timeoutMs;
|
|
406
447
|
let inspectableInstallationCount = 0;
|
|
407
448
|
while (Date.now() <= deadline) {
|
|
408
|
-
|
|
449
|
+
context.runtime.signal.throwIfAborted();
|
|
450
|
+
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
|
|
409
451
|
inspectableInstallationCount = lookup.inspectableInstallationCount;
|
|
410
452
|
if (lookup.match) return {
|
|
411
453
|
match: lookup.match,
|
|
@@ -413,7 +455,7 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
|
|
|
413
455
|
};
|
|
414
456
|
const remainingMs = deadline - Date.now();
|
|
415
457
|
if (remainingMs <= 0) break;
|
|
416
|
-
await sleep(Math.min(intervalMs, remainingMs));
|
|
458
|
+
await sleep(Math.min(intervalMs, remainingMs), context.runtime.signal);
|
|
417
459
|
}
|
|
418
460
|
return {
|
|
419
461
|
match: null,
|
|
@@ -431,37 +473,54 @@ function writeInstallWaitStatus(context, opened, installUrl) {
|
|
|
431
473
|
if (!opened) lines.push(installUrl);
|
|
432
474
|
context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
433
475
|
}
|
|
434
|
-
function sleep(ms) {
|
|
435
|
-
|
|
476
|
+
function sleep(ms, signal) {
|
|
477
|
+
signal.throwIfAborted();
|
|
478
|
+
return new Promise((resolve, reject) => {
|
|
479
|
+
const onAbort = () => {
|
|
480
|
+
clearTimeout(timeout);
|
|
481
|
+
reject(signal.reason);
|
|
482
|
+
};
|
|
483
|
+
const timeout = setTimeout(() => {
|
|
484
|
+
signal.removeEventListener("abort", onAbort);
|
|
485
|
+
resolve();
|
|
486
|
+
}, ms);
|
|
487
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
488
|
+
});
|
|
436
489
|
}
|
|
437
|
-
async function listScmInstallations(api, workspaceId) {
|
|
490
|
+
async function listScmInstallations(api, workspaceId, signal) {
|
|
438
491
|
const installations = [];
|
|
439
492
|
let cursor;
|
|
440
493
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
441
494
|
do {
|
|
442
|
-
const { data, error, response } = await api.GET("/v1/scm-installations", {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
495
|
+
const { data, error, response } = await api.GET("/v1/scm-installations", {
|
|
496
|
+
params: { query: {
|
|
497
|
+
workspaceId,
|
|
498
|
+
limit: 100,
|
|
499
|
+
...cursor ? { cursor } : {}
|
|
500
|
+
} },
|
|
501
|
+
signal
|
|
502
|
+
});
|
|
447
503
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub App installations", response, error);
|
|
448
504
|
installations.push(...data.data);
|
|
449
505
|
cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub App installations", response);
|
|
450
506
|
} while (cursor);
|
|
451
507
|
return installations;
|
|
452
508
|
}
|
|
453
|
-
async function findRepositoryInInstallation(api, installationId, repository) {
|
|
509
|
+
async function findRepositoryInInstallation(api, installationId, repository, signal) {
|
|
454
510
|
const expectedFullName = repository.fullName.toLowerCase();
|
|
455
511
|
let cursor;
|
|
456
512
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
457
513
|
do {
|
|
458
|
-
const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
514
|
+
const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
|
|
515
|
+
params: {
|
|
516
|
+
path: { installationId },
|
|
517
|
+
query: {
|
|
518
|
+
limit: 100,
|
|
519
|
+
...cursor ? { cursor } : {}
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
signal
|
|
523
|
+
});
|
|
465
524
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repositories", response, error);
|
|
466
525
|
const matchedRepository = data.data.find((candidate) => candidate.fullName.toLowerCase() === expectedFullName);
|
|
467
526
|
if (matchedRepository) return matchedRepository;
|
|
@@ -476,10 +535,11 @@ function readNextPaginationCursor(pagination, seenCursors, summary, response) {
|
|
|
476
535
|
seenCursors.add(nextCursor);
|
|
477
536
|
return nextCursor;
|
|
478
537
|
}
|
|
479
|
-
async function findRepositoryInInstallationIfAvailable(api, installationId, repository) {
|
|
538
|
+
async function findRepositoryInInstallationIfAvailable(api, installationId, repository, signal) {
|
|
480
539
|
try {
|
|
481
|
-
return await findRepositoryInInstallation(api, installationId, repository);
|
|
540
|
+
return await findRepositoryInInstallation(api, installationId, repository, signal);
|
|
482
541
|
} catch (error) {
|
|
542
|
+
if (signal.aborted) throw error;
|
|
483
543
|
if (isUnavailableScmInstallationError(error)) return "unavailable";
|
|
484
544
|
throw error;
|
|
485
545
|
}
|
|
@@ -488,28 +548,37 @@ function isUnavailableScmInstallationError(error) {
|
|
|
488
548
|
if (!(error instanceof CliError) || error.code !== "REPO_CONNECTION_FAILED") return false;
|
|
489
549
|
return error.meta.status === 404 || error.meta.status === 422;
|
|
490
550
|
}
|
|
491
|
-
async function createGitHubInstallIntent(api, workspaceId) {
|
|
492
|
-
const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
551
|
+
async function createGitHubInstallIntent(api, workspaceId, signal) {
|
|
552
|
+
const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
|
|
553
|
+
body: {
|
|
554
|
+
provider: "github",
|
|
555
|
+
workspaceId
|
|
556
|
+
},
|
|
557
|
+
signal
|
|
558
|
+
});
|
|
496
559
|
if (error || !data) throw repoConnectionApiError("Failed to create GitHub App installation link", response, error);
|
|
497
560
|
return data.data.installUrl;
|
|
498
561
|
}
|
|
499
562
|
async function openInstallUrlIfInteractive(context, installUrl) {
|
|
500
563
|
if (!canPrompt(context)) return false;
|
|
501
564
|
try {
|
|
565
|
+
context.runtime.signal.throwIfAborted();
|
|
502
566
|
await open(installUrl);
|
|
567
|
+
context.runtime.signal.throwIfAborted();
|
|
503
568
|
return true;
|
|
504
|
-
} catch {
|
|
569
|
+
} catch (error) {
|
|
570
|
+
if (context.runtime.signal.aborted) throw error;
|
|
505
571
|
return false;
|
|
506
572
|
}
|
|
507
573
|
}
|
|
508
|
-
async function readFirstSourceRepository(api, projectId) {
|
|
509
|
-
const { data, error, response } = await api.GET("/v1/source-repositories", {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
574
|
+
async function readFirstSourceRepository(api, projectId, signal) {
|
|
575
|
+
const { data, error, response } = await api.GET("/v1/source-repositories", {
|
|
576
|
+
params: { query: {
|
|
577
|
+
projectId,
|
|
578
|
+
limit: 1
|
|
579
|
+
} },
|
|
580
|
+
signal
|
|
581
|
+
});
|
|
513
582
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
|
|
514
583
|
return data.data[0] ?? null;
|
|
515
584
|
}
|
|
@@ -659,4 +728,4 @@ function repoConnectionFixForStatus(status) {
|
|
|
659
728
|
return "Re-run with --trace for the underlying API response details.";
|
|
660
729
|
}
|
|
661
730
|
//#endregion
|
|
662
|
-
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|
|
731
|
+
export { listFixtureWorkspaceProjects, listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { formatPrismaCliCommand } from "../../shell/cli-command.js";
|
|
2
|
+
import { resolvePackageRunner, resolvePackageRunnerSync } from "./package-manager.js";
|
|
3
|
+
//#region src/lib/agent/cli-command.ts
|
|
4
|
+
async function resolvePrismaCliPackageCommandFormatter(options) {
|
|
5
|
+
return createPrismaCliPackageCommandFormatter(await resolvePackageRunner(options));
|
|
6
|
+
}
|
|
7
|
+
async function resolvePrismaCliPackageCommand(options) {
|
|
8
|
+
return (await resolvePrismaCliPackageCommandFormatter(options))(options.args);
|
|
9
|
+
}
|
|
10
|
+
function resolvePrismaCliPackageCommandFormatterSync(cwd) {
|
|
11
|
+
return createPrismaCliPackageCommandFormatter(resolvePackageRunnerSync(cwd));
|
|
12
|
+
}
|
|
13
|
+
function createPrismaCliPackageCommandFormatter(packageRunner) {
|
|
14
|
+
return (args) => formatPrismaCliCommand(args, { packageRunner });
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { resolvePrismaCliPackageCommand, resolvePrismaCliPackageCommandFormatterSync };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/lib/agent/constants.ts
|
|
2
|
+
const PRISMA_SKILLS_SOURCE = "prisma/skills";
|
|
3
|
+
const PRISMA_SKILLS_LOCK_FILENAME = "skills-lock.json";
|
|
4
|
+
const SKILLS_CLI_PACKAGE = "skills@latest";
|
|
5
|
+
const DEFAULT_PRISMA_AGENT_SKILLS = ["*"];
|
|
6
|
+
const PRISMA_COMPUTE_AGENT_SKILL = "prisma-compute";
|
|
7
|
+
const DEFAULT_PRISMA_AGENT_TARGETS = ["codex", "claude-code"];
|
|
8
|
+
const PRISMA_AGENT_INSTALL_ARGS = ["agent", "install"];
|
|
9
|
+
const PRISMA_AGENT_UPDATE_ARGS = ["agent", "update"];
|
|
10
|
+
const PRISMA_AGENT_STATUS_ARGS = ["agent", "status"];
|
|
11
|
+
//#endregion
|
|
12
|
+
export { DEFAULT_PRISMA_AGENT_SKILLS, DEFAULT_PRISMA_AGENT_TARGETS, PRISMA_AGENT_INSTALL_ARGS, PRISMA_AGENT_STATUS_ARGS, PRISMA_AGENT_UPDATE_ARGS, PRISMA_COMPUTE_AGENT_SKILL, PRISMA_SKILLS_LOCK_FILENAME, PRISMA_SKILLS_SOURCE, SKILLS_CLI_PACKAGE };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/lib/agent/package-manager.ts
|
|
4
|
+
const LOCKFILE_PACKAGE_MANAGERS = [
|
|
5
|
+
{
|
|
6
|
+
packageManager: "bun",
|
|
7
|
+
fileNames: ["bun.lock", "bun.lockb"]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
packageManager: "pnpm",
|
|
11
|
+
fileNames: ["pnpm-lock.yaml", "pnpm-workspace.yaml"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
packageManager: "yarn",
|
|
15
|
+
fileNames: ["yarn.lock"]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
packageManager: "npm",
|
|
19
|
+
fileNames: ["package-lock.json", "npm-shrinkwrap.json"]
|
|
20
|
+
}
|
|
21
|
+
];
|
|
22
|
+
async function resolveSkillsPackageRunner(options) {
|
|
23
|
+
return resolvePackageRunner(options);
|
|
24
|
+
}
|
|
25
|
+
async function resolvePackageRunner(options) {
|
|
26
|
+
options.signal.throwIfAborted();
|
|
27
|
+
const packageManager = detectPackageManagerSync(options.cwd, options.signal) ?? "npm";
|
|
28
|
+
options.signal.throwIfAborted();
|
|
29
|
+
return packageRunnerForPackageManager(packageManager);
|
|
30
|
+
}
|
|
31
|
+
function resolvePackageRunnerSync(cwd) {
|
|
32
|
+
return packageRunnerForPackageManager(detectPackageManagerSync(cwd) ?? "npm");
|
|
33
|
+
}
|
|
34
|
+
function detectPackageManagerSync(cwd, signal) {
|
|
35
|
+
let directory = path.resolve(cwd);
|
|
36
|
+
while (true) {
|
|
37
|
+
signal?.throwIfAborted();
|
|
38
|
+
const packageJsonManager = readPackageJsonPackageManager(directory);
|
|
39
|
+
if (packageJsonManager) return packageJsonManager;
|
|
40
|
+
const lockfileManager = readLockfilePackageManager(directory, signal);
|
|
41
|
+
if (lockfileManager) return lockfileManager;
|
|
42
|
+
const parent = path.dirname(directory);
|
|
43
|
+
if (parent === directory) return null;
|
|
44
|
+
directory = parent;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function readPackageJsonPackageManager(directory) {
|
|
48
|
+
const packageJsonPath = path.join(directory, "package.json");
|
|
49
|
+
let content;
|
|
50
|
+
try {
|
|
51
|
+
content = readFileSync(packageJsonPath, "utf8");
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (isMissingFileError(error)) return null;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return parsePackageManager(JSON.parse(content).packageManager);
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function readLockfilePackageManager(directory, signal) {
|
|
63
|
+
for (const candidate of LOCKFILE_PACKAGE_MANAGERS) for (const fileName of candidate.fileNames) {
|
|
64
|
+
signal?.throwIfAborted();
|
|
65
|
+
if (fileExists(path.join(directory, fileName))) return candidate.packageManager;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function fileExists(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
return statSync(filePath).isFile();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (isMissingFileError(error)) return false;
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function parsePackageManager(value) {
|
|
78
|
+
if (typeof value !== "string") return null;
|
|
79
|
+
const normalized = value.trim().toLowerCase();
|
|
80
|
+
if (normalized === "bun" || normalized.startsWith("bun@")) return "bun";
|
|
81
|
+
if (normalized === "pnpm" || normalized.startsWith("pnpm@")) return "pnpm";
|
|
82
|
+
if (normalized === "yarn" || normalized.startsWith("yarn@")) return "yarn";
|
|
83
|
+
if (normalized === "npm" || normalized.startsWith("npm@")) return "npm";
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function packageRunnerForPackageManager(packageManager) {
|
|
87
|
+
switch (packageManager) {
|
|
88
|
+
case "bun": return ["bunx"];
|
|
89
|
+
case "pnpm": return ["pnpm", "dlx"];
|
|
90
|
+
case "yarn": return ["yarn", "dlx"];
|
|
91
|
+
case "npm": return ["npx", "-y"];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function isMissingFileError(error) {
|
|
95
|
+
const code = error.code;
|
|
96
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { resolvePackageRunner, resolvePackageRunnerSync, resolveSkillsPackageRunner };
|