@prisma/cli 3.0.0-beta.0 → 3.0.0-beta.2
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 +13 -0
- package/dist/commands/app/index.js +6 -5
- package/dist/commands/project/index.js +28 -2
- package/dist/controllers/app-env.js +6 -8
- package/dist/controllers/app.js +283 -261
- package/dist/controllers/project.js +179 -21
- package/dist/lib/app/deploy-output.js +10 -1
- package/dist/lib/auth/auth-ops.js +49 -8
- package/dist/lib/project/interactive-setup.js +56 -0
- package/dist/lib/project/resolution.js +126 -97
- package/dist/lib/project/setup.js +89 -0
- package/dist/presenters/auth.js +19 -6
- package/dist/presenters/project.js +66 -37
- package/dist/shell/command-arguments.js +6 -0
- package/dist/shell/command-meta.js +36 -3
- package/dist/shell/command-runner.js +4 -2
- package/dist/shell/errors.js +2 -0
- package/dist/shell/output.js +3 -1
- package/dist/use-cases/auth.js +15 -4
- package/dist/use-cases/project.js +2 -1
- package/package.json +2 -2
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
1
|
+
import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
2
|
import { renderSummaryLine } from "../shell/ui.js";
|
|
3
3
|
import { canPrompt } from "../shell/runtime.js";
|
|
4
4
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
5
|
-
import {
|
|
5
|
+
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
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";
|
|
9
|
+
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
10
|
+
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
6
11
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
7
12
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
8
13
|
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
@@ -14,6 +19,12 @@ const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
|
|
|
14
19
|
function isRealMode(context) {
|
|
15
20
|
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
16
21
|
}
|
|
22
|
+
async function readProjectListLocalBinding(cwd, workspace, projects) {
|
|
23
|
+
const pin = await readLocalResolutionPin(cwd);
|
|
24
|
+
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
|
+
return { status: "not-linked" };
|
|
27
|
+
}
|
|
17
28
|
async function runProjectList(context) {
|
|
18
29
|
const authState = await requireAuthenticatedAuthState(context);
|
|
19
30
|
const workspace = authState.workspace;
|
|
@@ -21,40 +32,171 @@ async function runProjectList(context) {
|
|
|
21
32
|
if (isRealMode(context)) {
|
|
22
33
|
const client = await requireComputeAuth(context.runtime.env);
|
|
23
34
|
if (!client) throw authRequiredError();
|
|
35
|
+
const projects = sortProjects(await listRealWorkspaceProjects(client, workspace));
|
|
36
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects);
|
|
37
|
+
const nextActions = buildProjectListNextActions(localBinding);
|
|
24
38
|
return {
|
|
25
39
|
command: "project.list",
|
|
26
40
|
result: {
|
|
27
41
|
workspace,
|
|
28
|
-
projects:
|
|
42
|
+
projects: projects.map(toProjectSummary),
|
|
43
|
+
localBinding
|
|
29
44
|
},
|
|
30
45
|
warnings: [],
|
|
31
|
-
nextSteps: []
|
|
46
|
+
nextSteps: [],
|
|
47
|
+
nextActions
|
|
32
48
|
};
|
|
33
49
|
}
|
|
50
|
+
const result = await createProjectUseCases(createCliUseCaseGateways(context)).list(authState);
|
|
51
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects);
|
|
52
|
+
const nextActions = buildProjectListNextActions(localBinding);
|
|
34
53
|
return {
|
|
35
54
|
command: "project.list",
|
|
36
|
-
result:
|
|
55
|
+
result: {
|
|
56
|
+
...result,
|
|
57
|
+
localBinding
|
|
58
|
+
},
|
|
37
59
|
warnings: [],
|
|
38
|
-
nextSteps: []
|
|
60
|
+
nextSteps: [],
|
|
61
|
+
nextActions
|
|
39
62
|
};
|
|
40
63
|
}
|
|
64
|
+
function buildProjectListNextActions(localBinding) {
|
|
65
|
+
return localBinding?.status === "linked" ? [] : buildProjectSetupNextActions({
|
|
66
|
+
createCommand: "prisma-cli project create <name>",
|
|
67
|
+
reason: localBinding?.status === "invalid" ? "This directory has an invalid local Project binding. Ask the user which Prisma Project to link before running Project-scoped commands." : "This directory is not linked to a Prisma Project. Project list shows available Projects, but none is selected for this directory."
|
|
68
|
+
});
|
|
69
|
+
}
|
|
41
70
|
async function runProjectShow(context, explicitProject) {
|
|
42
71
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
43
72
|
if (!workspace) throw workspaceRequiredError();
|
|
73
|
+
const result = isRealMode(context) ? await resolveProjectShowInRealMode(context, workspace, explicitProject) : await resolveProjectShowInFixtureMode(context, workspace, explicitProject);
|
|
44
74
|
return {
|
|
45
75
|
command: "project.show",
|
|
46
|
-
result
|
|
76
|
+
result,
|
|
47
77
|
warnings: [],
|
|
48
|
-
nextSteps: []
|
|
78
|
+
nextSteps: [],
|
|
79
|
+
nextActions: result.project === null ? buildProjectSetupNextActions({
|
|
80
|
+
commandName: "project show",
|
|
81
|
+
suggestedProjectName: result.suggestedProjectName,
|
|
82
|
+
reason: "This directory is not linked to a Prisma Project. Package and directory names can suggest setup defaults, but they do not select a Project."
|
|
83
|
+
}) : []
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async function runProjectCreate(context, projectName) {
|
|
87
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
88
|
+
if (!workspace) throw workspaceRequiredError();
|
|
89
|
+
if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
|
|
90
|
+
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);
|
|
92
|
+
if (!client) throw authRequiredError();
|
|
93
|
+
const provider = createPreviewAppProvider(client);
|
|
94
|
+
const name = projectName.trim();
|
|
95
|
+
const created = await provider.createProject({ name }).catch((error) => {
|
|
96
|
+
throw projectCreateFailedError(error, name, workspace, {
|
|
97
|
+
nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"],
|
|
98
|
+
permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
|
|
99
|
+
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
command: "project.create",
|
|
104
|
+
result: await bindProjectToDirectory(context, workspace, {
|
|
105
|
+
id: created.id,
|
|
106
|
+
name: created.name
|
|
107
|
+
}, "created"),
|
|
108
|
+
warnings: [],
|
|
109
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function runProjectLink(context, projectRef) {
|
|
113
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
114
|
+
if (!workspace) throw workspaceRequiredError();
|
|
115
|
+
let provider = null;
|
|
116
|
+
let projects;
|
|
117
|
+
if (isRealMode(context)) {
|
|
118
|
+
const client = await requireComputeAuth(context.runtime.env);
|
|
119
|
+
if (!client) throw authRequiredError();
|
|
120
|
+
provider = createPreviewAppProvider(client);
|
|
121
|
+
projects = await listRealWorkspaceProjects(client, workspace);
|
|
122
|
+
} else projects = listFixtureWorkspaceProjects(context, workspace);
|
|
123
|
+
let result;
|
|
124
|
+
if (projectRef?.trim()) result = await bindProjectToDirectory(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
|
|
125
|
+
else if (canPrompt(context) && !context.flags.yes) result = await resolveInteractiveProjectLinkSetup(context, workspace, projects, provider);
|
|
126
|
+
else throw await projectLinkTargetRequiredError(context, projects);
|
|
127
|
+
return {
|
|
128
|
+
command: "project.link",
|
|
129
|
+
result,
|
|
130
|
+
warnings: [],
|
|
131
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function resolveInteractiveProjectLinkSetup(context, workspace, projects, provider) {
|
|
135
|
+
const setup = await promptForProjectSetupChoice({
|
|
136
|
+
context,
|
|
137
|
+
projects,
|
|
138
|
+
createProject: (projectName) => {
|
|
139
|
+
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);
|
|
141
|
+
},
|
|
142
|
+
cancel: {
|
|
143
|
+
why: "Project link needs a Project before it can continue.",
|
|
144
|
+
fix: "Choose an existing Project or create a new one, then rerun project link.",
|
|
145
|
+
nextSteps: ["prisma-cli project link <id-or-name>", "prisma-cli project create <name>"]
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return bindProjectToDirectory(context, workspace, setup.project, setup.action);
|
|
149
|
+
}
|
|
150
|
+
async function createProjectForLinkSetup(provider, projectName, workspace) {
|
|
151
|
+
const created = await provider.createProject({ name: projectName }).catch((error) => {
|
|
152
|
+
throw projectCreateFailedError(error, projectName, workspace, {
|
|
153
|
+
nextSteps: [
|
|
154
|
+
"prisma-cli project list",
|
|
155
|
+
"prisma-cli project link <id-or-name>",
|
|
156
|
+
`prisma-cli project create ${formatCommandArgument(projectName)}`
|
|
157
|
+
],
|
|
158
|
+
permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
|
|
159
|
+
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
id: created.id,
|
|
164
|
+
name: created.name,
|
|
165
|
+
workspace
|
|
49
166
|
};
|
|
50
167
|
}
|
|
168
|
+
async function projectLinkTargetRequiredError(context, projects) {
|
|
169
|
+
const suggestedName = await inferTargetName(context.runtime.cwd);
|
|
170
|
+
const createCommand = `prisma-cli project create ${formatCommandArgument(suggestedName.name)}`;
|
|
171
|
+
const recoveryCommands = ["prisma-cli project link <id-or-name>", createCommand];
|
|
172
|
+
return new CliError({
|
|
173
|
+
code: "PROJECT_LINK_TARGET_REQUIRED",
|
|
174
|
+
domain: "project",
|
|
175
|
+
summary: "Choose a Project to link this directory",
|
|
176
|
+
why: "This directory is not linked to a Prisma Project. Existing Projects are candidates until the user chooses one, and package or directory names are suggestions only.",
|
|
177
|
+
fix: "Run prisma-cli project link in a TTY to choose from the setup list, pass a Project id or name, or create a new Project.",
|
|
178
|
+
meta: {
|
|
179
|
+
suggestedProjectName: suggestedName.name,
|
|
180
|
+
suggestedProjectNameSource: suggestedName.source,
|
|
181
|
+
candidates: sortProjects(projects).map(toProjectSummary),
|
|
182
|
+
recoveryCommands
|
|
183
|
+
},
|
|
184
|
+
exitCode: 2,
|
|
185
|
+
nextSteps: ["prisma-cli project list", ...recoveryCommands],
|
|
186
|
+
nextActions: buildProjectSetupNextActions({
|
|
187
|
+
suggestedProjectName: suggestedName.name,
|
|
188
|
+
createCommand,
|
|
189
|
+
reason: "Project link needs the user to choose an existing Project or create a new one. Existing Projects, package names, and directory names are candidates only, not selections."
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
}
|
|
51
193
|
async function runGitConnect(context, gitUrl, options = {}) {
|
|
52
194
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
53
195
|
if (!workspace) throw workspaceRequiredError();
|
|
54
196
|
if (isRealMode(context)) {
|
|
55
197
|
const client = await requireComputeAuth(context.runtime.env);
|
|
56
198
|
if (!client) throw authRequiredError();
|
|
57
|
-
const target = await
|
|
199
|
+
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
|
|
58
200
|
const repository = await resolveRepositoryForConnect(context, gitUrl);
|
|
59
201
|
const api = client;
|
|
60
202
|
const existing = await readFirstSourceRepository(api, target.project.id);
|
|
@@ -89,7 +231,7 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
89
231
|
nextSteps: []
|
|
90
232
|
};
|
|
91
233
|
}
|
|
92
|
-
const target = await
|
|
234
|
+
const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git connect");
|
|
93
235
|
const repository = await resolveRepositoryForConnect(context, gitUrl);
|
|
94
236
|
const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
|
|
95
237
|
if (existingConnection) {
|
|
@@ -122,7 +264,7 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
122
264
|
if (isRealMode(context)) {
|
|
123
265
|
const client = await requireComputeAuth(context.runtime.env);
|
|
124
266
|
if (!client) throw authRequiredError();
|
|
125
|
-
const target = await
|
|
267
|
+
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
|
|
126
268
|
const api = client;
|
|
127
269
|
const existing = await readFirstSourceRepository(api, target.project.id);
|
|
128
270
|
if (!existing) throw repoNotConnectedError();
|
|
@@ -138,7 +280,7 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
138
280
|
nextSteps: []
|
|
139
281
|
};
|
|
140
282
|
}
|
|
141
|
-
const target = await
|
|
283
|
+
const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git disconnect");
|
|
142
284
|
const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
|
|
143
285
|
if (!existingConnection) throw repoNotConnectedError();
|
|
144
286
|
await context.stateStore.clearRepositoryConnection(target.project.id);
|
|
@@ -153,6 +295,17 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
153
295
|
};
|
|
154
296
|
}
|
|
155
297
|
async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
|
|
298
|
+
const client = await requireComputeAuth(context.runtime.env);
|
|
299
|
+
if (!client) throw authRequiredError();
|
|
300
|
+
return inspectProjectBinding({
|
|
301
|
+
context,
|
|
302
|
+
workspace,
|
|
303
|
+
explicitProject,
|
|
304
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace),
|
|
305
|
+
commandName: "project show"
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
|
|
156
309
|
const client = await requireComputeAuth(context.runtime.env);
|
|
157
310
|
if (!client) throw authRequiredError();
|
|
158
311
|
return resolveProjectTarget({
|
|
@@ -160,16 +313,25 @@ async function resolveProjectShowInRealMode(context, workspace, explicitProject)
|
|
|
160
313
|
workspace,
|
|
161
314
|
explicitProject,
|
|
162
315
|
listProjects: () => listRealWorkspaceProjects(client, workspace),
|
|
163
|
-
|
|
316
|
+
commandName
|
|
164
317
|
});
|
|
165
318
|
}
|
|
166
319
|
async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
|
|
320
|
+
return inspectProjectBinding({
|
|
321
|
+
context,
|
|
322
|
+
workspace,
|
|
323
|
+
explicitProject,
|
|
324
|
+
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
325
|
+
commandName: "project show"
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
async function resolveRequiredProjectInFixtureMode(context, workspace, explicitProject, commandName) {
|
|
167
329
|
return resolveProjectTarget({
|
|
168
330
|
context,
|
|
169
331
|
workspace,
|
|
170
332
|
explicitProject,
|
|
171
333
|
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
172
|
-
|
|
334
|
+
commandName
|
|
173
335
|
});
|
|
174
336
|
}
|
|
175
337
|
async function listRealWorkspaceProjects(client, workspace) {
|
|
@@ -177,6 +339,7 @@ async function listRealWorkspaceProjects(client, workspace) {
|
|
|
177
339
|
return sortProjects((data?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
|
|
178
340
|
id: project.id,
|
|
179
341
|
name: project.name,
|
|
342
|
+
..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
|
|
180
343
|
slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
|
|
181
344
|
workspace: {
|
|
182
345
|
id: project.workspace.id,
|
|
@@ -188,6 +351,7 @@ function listFixtureWorkspaceProjects(context, workspace) {
|
|
|
188
351
|
return sortProjects(context.api.listProjectsForWorkspace(workspace.id).map((project) => ({
|
|
189
352
|
id: project.id,
|
|
190
353
|
name: project.name,
|
|
354
|
+
...project.url ? { url: project.url } : {},
|
|
191
355
|
slug: project.slug,
|
|
192
356
|
workspace
|
|
193
357
|
})));
|
|
@@ -494,11 +658,5 @@ function repoConnectionFixForStatus(status) {
|
|
|
494
658
|
if (status === 422) return "Make sure the GitHub App installation has access to this repository.";
|
|
495
659
|
return "Re-run with --trace for the underlying API response details.";
|
|
496
660
|
}
|
|
497
|
-
function toProjectSummary(project) {
|
|
498
|
-
return {
|
|
499
|
-
id: project.id,
|
|
500
|
-
name: project.name
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
661
|
//#endregion
|
|
504
|
-
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectList, runProjectShow };
|
|
662
|
+
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|
|
@@ -2,6 +2,7 @@ import { padDisplay } from "../../shell/ui.js";
|
|
|
2
2
|
//#region src/lib/app/deploy-output.ts
|
|
3
3
|
const DEPLOY_OUTPUT_MIN_LABEL_WIDTH = 9;
|
|
4
4
|
const DEPLOY_OUTPUT_MIN_VALUE_WIDTH = 9;
|
|
5
|
+
const DEPLOY_SETTINGS_MIN_KEY_WIDTH = 10;
|
|
5
6
|
function renderDeployOutputRows(ui, rows) {
|
|
6
7
|
if (rows.length === 0) return [];
|
|
7
8
|
const labelWidth = Math.max(DEPLOY_OUTPUT_MIN_LABEL_WIDTH, ...rows.map((row) => row.label.length));
|
|
@@ -11,5 +12,13 @@ function renderDeployOutputRows(ui, rows) {
|
|
|
11
12
|
return ` ${padDisplay(row.label, labelWidth)} ${padDisplay(ui.strong(row.value), valueWidth)}${row.origin ? ` ${ui.dim(`· ${row.origin}`)}` : ""}`.trimEnd();
|
|
12
13
|
});
|
|
13
14
|
}
|
|
15
|
+
function renderDeploySettingsPreview(ui, rows) {
|
|
16
|
+
if (rows.length === 0) return [];
|
|
17
|
+
const keyWidth = Math.max(DEPLOY_SETTINGS_MIN_KEY_WIDTH, ...rows.map((row) => `${row.key}:`.length));
|
|
18
|
+
const rail = ui.dim("│");
|
|
19
|
+
return rows.map((row) => {
|
|
20
|
+
return `${rail} ${ui.accent(padDisplay(`${row.key}:`, keyWidth))} ${ui.strong(row.value)}`;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
14
23
|
//#endregion
|
|
15
|
-
export { renderDeployOutputRows };
|
|
24
|
+
export { renderDeployOutputRows, renderDeploySettingsPreview };
|
|
@@ -42,41 +42,52 @@ async function readAuthState(env) {
|
|
|
42
42
|
authenticated: false,
|
|
43
43
|
provider: null,
|
|
44
44
|
user: null,
|
|
45
|
-
workspace: null
|
|
45
|
+
workspace: null,
|
|
46
|
+
credential: null
|
|
46
47
|
};
|
|
48
|
+
const client = await requireComputeAuth(env);
|
|
49
|
+
const currentPrincipal = await readCurrentPrincipalAuthState(client);
|
|
50
|
+
if (currentPrincipal) return currentPrincipal;
|
|
47
51
|
const claims = decodeJwtPayload(tokens.accessToken);
|
|
48
52
|
return buildAuthState({
|
|
49
53
|
workspaceIdFromCredential: tokens.workspaceId,
|
|
50
54
|
claims,
|
|
51
|
-
env
|
|
55
|
+
env,
|
|
56
|
+
client
|
|
52
57
|
});
|
|
53
58
|
}
|
|
54
59
|
async function readServiceTokenAuthState(token, env) {
|
|
60
|
+
const client = await requireComputeAuth(env);
|
|
61
|
+
const currentPrincipal = await readCurrentPrincipalAuthState(client);
|
|
62
|
+
if (currentPrincipal) return currentPrincipal;
|
|
55
63
|
const claims = decodeJwtPayload(token);
|
|
56
64
|
const workspaceId = workspaceIdFromClaims(claims);
|
|
57
65
|
if (!workspaceId) return {
|
|
58
66
|
authenticated: false,
|
|
59
67
|
provider: null,
|
|
60
68
|
user: null,
|
|
61
|
-
workspace: null
|
|
69
|
+
workspace: null,
|
|
70
|
+
credential: null
|
|
62
71
|
};
|
|
63
72
|
return buildAuthState({
|
|
64
73
|
workspaceIdFromCredential: workspaceId,
|
|
65
74
|
claims,
|
|
66
|
-
env
|
|
75
|
+
env,
|
|
76
|
+
client
|
|
67
77
|
});
|
|
68
78
|
}
|
|
69
|
-
async function buildAuthState({ workspaceIdFromCredential, claims, env }) {
|
|
79
|
+
async function buildAuthState({ workspaceIdFromCredential, claims, env, client }) {
|
|
70
80
|
let workspaceId = workspaceIdFromCredential;
|
|
71
81
|
let workspaceName = workspaceIdFromCredential;
|
|
72
|
-
|
|
82
|
+
client ??= await requireComputeAuth(env);
|
|
73
83
|
if (client) try {
|
|
74
84
|
const { data, response } = await client.GET("/v1/workspaces/{id}", { params: { path: { id: workspaceIdFromCredential } } });
|
|
75
85
|
if (response?.status === 401) return {
|
|
76
86
|
authenticated: false,
|
|
77
87
|
provider: null,
|
|
78
88
|
user: null,
|
|
79
|
-
workspace: null
|
|
89
|
+
workspace: null,
|
|
90
|
+
credential: null
|
|
80
91
|
};
|
|
81
92
|
if (data?.data?.id) {
|
|
82
93
|
workspaceId = data.data.id;
|
|
@@ -92,9 +103,39 @@ async function buildAuthState({ workspaceIdFromCredential, claims, env }) {
|
|
|
92
103
|
workspace: {
|
|
93
104
|
id: workspaceId,
|
|
94
105
|
name: workspaceName
|
|
95
|
-
}
|
|
106
|
+
},
|
|
107
|
+
credential: null
|
|
96
108
|
};
|
|
97
109
|
}
|
|
110
|
+
async function readCurrentPrincipalAuthState(client) {
|
|
111
|
+
if (!client) return null;
|
|
112
|
+
try {
|
|
113
|
+
const { data, response } = await client.GET("/v1/me");
|
|
114
|
+
if (response?.status === 401) return {
|
|
115
|
+
authenticated: false,
|
|
116
|
+
provider: null,
|
|
117
|
+
user: null,
|
|
118
|
+
workspace: null,
|
|
119
|
+
credential: null
|
|
120
|
+
};
|
|
121
|
+
const principal = data?.data;
|
|
122
|
+
if (!principal) return null;
|
|
123
|
+
if (!principal.credential) return null;
|
|
124
|
+
return {
|
|
125
|
+
authenticated: true,
|
|
126
|
+
provider: null,
|
|
127
|
+
user: principal.user ? {
|
|
128
|
+
id: principal.user.id,
|
|
129
|
+
email: principal.user.email,
|
|
130
|
+
name: principal.user.name
|
|
131
|
+
} : null,
|
|
132
|
+
workspace: principal.workspace,
|
|
133
|
+
credential: principal.credential
|
|
134
|
+
};
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
98
139
|
async function performLogout(env) {
|
|
99
140
|
await new FileTokenStorage(env).clearTokens();
|
|
100
141
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { usageError } from "../../shell/errors.js";
|
|
2
|
+
import { selectPrompt, textPrompt } from "../../shell/prompt.js";
|
|
3
|
+
import { inferTargetName, sortProjects } from "./resolution.js";
|
|
4
|
+
import { toProjectSummary, validateProjectSetupNameText } from "./setup.js";
|
|
5
|
+
//#region src/lib/project/interactive-setup.ts
|
|
6
|
+
async function promptForProjectSetupChoice(options) {
|
|
7
|
+
const sortedProjects = sortProjects(options.projects);
|
|
8
|
+
const projectNames = sortedProjects.map((project) => project.name);
|
|
9
|
+
const duplicateNames = new Set(projectNames.filter((name, index) => projectNames.indexOf(name) !== index));
|
|
10
|
+
const choice = await selectPrompt({
|
|
11
|
+
input: options.context.runtime.stdin,
|
|
12
|
+
output: options.context.runtime.stderr,
|
|
13
|
+
message: "Which Project should this directory use?",
|
|
14
|
+
choices: [
|
|
15
|
+
...sortedProjects.map((project) => ({
|
|
16
|
+
label: duplicateNames.has(project.name) ? `${project.name} (${project.id})` : project.name,
|
|
17
|
+
value: {
|
|
18
|
+
kind: "project",
|
|
19
|
+
project
|
|
20
|
+
}
|
|
21
|
+
})),
|
|
22
|
+
{
|
|
23
|
+
label: "Create a new Project",
|
|
24
|
+
value: { kind: "create" }
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
label: "Cancel",
|
|
28
|
+
value: { kind: "cancel" }
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
if (choice.kind === "cancel") throw usageError("Project setup canceled", options.cancel.why, options.cancel.fix, options.cancel.nextSteps, "project");
|
|
33
|
+
if (choice.kind === "project") return {
|
|
34
|
+
project: toProjectSummary(choice.project),
|
|
35
|
+
action: "linked",
|
|
36
|
+
targetName: choice.project.name,
|
|
37
|
+
targetNameSource: "prompt"
|
|
38
|
+
};
|
|
39
|
+
const suggestedName = await inferTargetName(options.context.runtime.cwd);
|
|
40
|
+
const rawName = await textPrompt({
|
|
41
|
+
input: options.context.runtime.stdin,
|
|
42
|
+
output: options.context.runtime.stderr,
|
|
43
|
+
message: "Project name",
|
|
44
|
+
placeholder: suggestedName.name,
|
|
45
|
+
validate: (value) => validateProjectSetupNameText(value, suggestedName.name)
|
|
46
|
+
});
|
|
47
|
+
const projectName = rawName.trim() || suggestedName.name;
|
|
48
|
+
return {
|
|
49
|
+
project: toProjectSummary(await options.createProject(projectName)),
|
|
50
|
+
action: "created",
|
|
51
|
+
targetName: projectName,
|
|
52
|
+
targetNameSource: rawName.trim() ? "prompt" : suggestedName.source
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { promptForProjectSetupChoice };
|