@prisma/cli 3.0.0-alpha.0 → 3.0.0-alpha.10
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 +1 -16
- package/dist/adapters/git.js +49 -0
- package/dist/adapters/local-state.js +39 -1
- package/dist/cli2.js +60 -4
- package/dist/commands/app/index.js +43 -30
- package/dist/commands/auth/index.js +3 -2
- package/dist/commands/branch/index.js +2 -1
- package/dist/commands/env.js +87 -0
- package/dist/commands/git/index.js +36 -0
- package/dist/commands/project/index.js +12 -14
- package/dist/commands/version/index.js +18 -0
- package/dist/controllers/app-env.js +223 -0
- package/dist/controllers/app.js +1051 -173
- package/dist/controllers/auth.js +9 -9
- package/dist/controllers/branch.js +6 -6
- package/dist/controllers/project.js +451 -161
- package/dist/controllers/version.js +12 -0
- package/dist/lib/app/bun-project.js +1 -1
- package/dist/lib/app/deploy-output.js +15 -0
- package/dist/lib/app/env-config.js +57 -0
- package/dist/lib/app/env-vars.js +4 -4
- package/dist/lib/app/local-dev.js +2 -1
- package/dist/lib/app/preview-build.js +130 -144
- package/dist/lib/app/preview-interaction.js +2 -35
- package/dist/lib/app/preview-progress.js +43 -58
- package/dist/lib/app/preview-provider.js +125 -24
- package/dist/lib/auth/auth-ops.js +58 -13
- package/dist/lib/auth/client.js +1 -1
- package/dist/lib/auth/guard.js +1 -1
- package/dist/lib/auth/login.js +115 -4
- package/dist/lib/project/local-pin.js +51 -0
- package/dist/lib/project/resolution.js +201 -0
- package/dist/lib/version.js +55 -0
- package/dist/output/patterns.js +15 -18
- package/dist/presenters/app-env.js +129 -0
- package/dist/presenters/app.js +16 -29
- package/dist/presenters/auth.js +2 -2
- package/dist/presenters/branch.js +6 -6
- package/dist/presenters/project.js +87 -44
- package/dist/presenters/version.js +29 -0
- package/dist/shell/command-meta.js +150 -84
- package/dist/shell/command-runner.js +32 -2
- package/dist/shell/errors.js +8 -3
- package/dist/shell/global-flags.js +13 -1
- package/dist/shell/help.js +8 -7
- package/dist/shell/output.js +29 -12
- package/dist/shell/prompt.js +12 -2
- package/dist/shell/runtime.js +1 -1
- package/dist/shell/ui.js +19 -1
- package/dist/use-cases/auth.js +9 -12
- package/dist/use-cases/branch.js +20 -20
- package/dist/use-cases/create-cli-gateways.js +3 -13
- package/dist/use-cases/project.js +2 -48
- package/package.json +3 -3
- package/dist/adapters/config.js +0 -74
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { envVarNames } from "./env-vars.js";
|
|
2
2
|
import { PreviewBuildStrategy } from "./preview-build.js";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { ApiError, ComputeClient } from "@prisma/compute-sdk";
|
|
4
|
+
import { ApiError, CancelledError, ComputeClient, streamLogs } from "@prisma/compute-sdk";
|
|
5
5
|
//#region src/lib/app/preview-provider.ts
|
|
6
|
-
function createPreviewAppProvider(client) {
|
|
6
|
+
function createPreviewAppProvider(client, options) {
|
|
7
7
|
const sdk = new ComputeClient(client);
|
|
8
8
|
return {
|
|
9
9
|
async createProject(options) {
|
|
@@ -14,25 +14,11 @@ function createPreviewAppProvider(client) {
|
|
|
14
14
|
name: projectResult.value.name
|
|
15
15
|
};
|
|
16
16
|
},
|
|
17
|
-
async listApps(projectId) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return detailResult.isOk() ? detailResult.value : {
|
|
23
|
-
id: service.id,
|
|
24
|
-
name: service.name,
|
|
25
|
-
region: service.region,
|
|
26
|
-
latestVersionId: null,
|
|
27
|
-
serviceEndpointDomain: void 0
|
|
28
|
-
};
|
|
29
|
-
}))).map((service) => ({
|
|
30
|
-
id: service.id,
|
|
31
|
-
name: service.name,
|
|
32
|
-
region: service.region ?? null,
|
|
33
|
-
liveDeploymentId: service.latestVersionId ?? null,
|
|
34
|
-
liveUrl: toAbsoluteUrl(service.serviceEndpointDomain ?? null)
|
|
35
|
-
}));
|
|
17
|
+
async listApps(projectId, options) {
|
|
18
|
+
return listComputeServices(client, {
|
|
19
|
+
projectId,
|
|
20
|
+
branchGitName: options?.branchName
|
|
21
|
+
});
|
|
36
22
|
},
|
|
37
23
|
async removeApp(appId) {
|
|
38
24
|
const appResult = await sdk.showService({ serviceId: appId });
|
|
@@ -60,6 +46,20 @@ function createPreviewAppProvider(client) {
|
|
|
60
46
|
if (promoteResult.isErr()) throw new Error(promoteResult.error.message);
|
|
61
47
|
},
|
|
62
48
|
async deployApp(options) {
|
|
49
|
+
const resolvedApp = options.appId ? {
|
|
50
|
+
appId: options.appId,
|
|
51
|
+
appName: options.appName,
|
|
52
|
+
region: options.region
|
|
53
|
+
} : options.branchName && options.appName ? await createBranchApp(client, {
|
|
54
|
+
projectId: options.projectId,
|
|
55
|
+
branchName: options.branchName,
|
|
56
|
+
appName: options.appName,
|
|
57
|
+
region: options.region
|
|
58
|
+
}) : {
|
|
59
|
+
appId: void 0,
|
|
60
|
+
appName: options.appName,
|
|
61
|
+
region: options.region
|
|
62
|
+
};
|
|
63
63
|
const deployResult = await sdk.deploy({
|
|
64
64
|
strategy: new PreviewBuildStrategy({
|
|
65
65
|
appPath: path.resolve(options.cwd),
|
|
@@ -67,9 +67,9 @@ function createPreviewAppProvider(client) {
|
|
|
67
67
|
buildType: options.buildType
|
|
68
68
|
}),
|
|
69
69
|
projectId: options.projectId,
|
|
70
|
-
serviceId:
|
|
71
|
-
serviceName:
|
|
72
|
-
region:
|
|
70
|
+
serviceId: resolvedApp.appId,
|
|
71
|
+
serviceName: resolvedApp.appName,
|
|
72
|
+
region: resolvedApp.region,
|
|
73
73
|
portMapping: options.portMapping,
|
|
74
74
|
envVars: options.envVars,
|
|
75
75
|
timeoutSeconds: 120,
|
|
@@ -197,9 +197,110 @@ function createPreviewAppProvider(client) {
|
|
|
197
197
|
live: null
|
|
198
198
|
}
|
|
199
199
|
};
|
|
200
|
+
},
|
|
201
|
+
async streamDeploymentLogs(streamOptions) {
|
|
202
|
+
if (!options?.baseUrl || !options.getToken) throw new Error("Log streaming requires an authenticated API base URL and token.");
|
|
203
|
+
const result = await streamLogs({
|
|
204
|
+
baseUrl: options.baseUrl,
|
|
205
|
+
token: await options.getToken(),
|
|
206
|
+
versionId: streamOptions.deploymentId,
|
|
207
|
+
signal: streamOptions.signal
|
|
208
|
+
}, streamOptions.onRecord);
|
|
209
|
+
if (result.isErr()) {
|
|
210
|
+
if (CancelledError.is(result.error)) return;
|
|
211
|
+
throw result.error;
|
|
212
|
+
}
|
|
200
213
|
}
|
|
201
214
|
};
|
|
202
215
|
}
|
|
216
|
+
async function listBranches(client, options) {
|
|
217
|
+
const result = await client.GET("/v1/projects/{projectId}/branches", { params: {
|
|
218
|
+
path: { projectId: options.projectId },
|
|
219
|
+
query: { gitName: options.gitName }
|
|
220
|
+
} });
|
|
221
|
+
if (result.error || !result.data) throw apiCallError("Failed to list branches", result.response, result.error);
|
|
222
|
+
return result.data.data;
|
|
223
|
+
}
|
|
224
|
+
async function resolveOrCreateBranch(client, options) {
|
|
225
|
+
const existing = (await listBranches(client, options))[0];
|
|
226
|
+
if (existing) return existing;
|
|
227
|
+
const result = await client.POST("/v1/projects/{projectId}/branches", {
|
|
228
|
+
params: { path: { projectId: options.projectId } },
|
|
229
|
+
body: {
|
|
230
|
+
gitName: options.gitName,
|
|
231
|
+
isDefault: options.gitName === "main"
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
if (result.error || !result.data) {
|
|
235
|
+
if (result.response.status === 409) {
|
|
236
|
+
const raced = (await listBranches(client, options))[0];
|
|
237
|
+
if (raced) return raced;
|
|
238
|
+
}
|
|
239
|
+
throw apiCallError(`Failed to create branch "${options.gitName}"`, result.response, result.error);
|
|
240
|
+
}
|
|
241
|
+
return result.data.data;
|
|
242
|
+
}
|
|
243
|
+
async function listComputeServices(client, options) {
|
|
244
|
+
const services = [];
|
|
245
|
+
let cursor;
|
|
246
|
+
while (true) {
|
|
247
|
+
const result = await client.GET("/v1/compute-services", { params: { query: {
|
|
248
|
+
projectId: options.projectId,
|
|
249
|
+
branchGitName: options.branchGitName,
|
|
250
|
+
cursor
|
|
251
|
+
} } });
|
|
252
|
+
if (result.error || !result.data) throw apiCallError("Failed to list apps", result.response, result.error);
|
|
253
|
+
services.push(...result.data.data);
|
|
254
|
+
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
|
|
255
|
+
cursor = result.data.pagination.nextCursor;
|
|
256
|
+
}
|
|
257
|
+
return services.map((service) => ({
|
|
258
|
+
id: service.id,
|
|
259
|
+
name: service.name,
|
|
260
|
+
region: service.region.id ?? null,
|
|
261
|
+
branchId: service.branchId,
|
|
262
|
+
liveDeploymentId: service.latestVersionId ?? null,
|
|
263
|
+
liveUrl: toAbsoluteUrl(service.serviceEndpointDomain ?? null)
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
async function createBranchApp(client, options) {
|
|
267
|
+
const branch = await resolveOrCreateBranch(client, {
|
|
268
|
+
projectId: options.projectId,
|
|
269
|
+
gitName: options.branchName
|
|
270
|
+
});
|
|
271
|
+
const result = await client.POST("/v1/compute-services", { body: {
|
|
272
|
+
projectId: options.projectId,
|
|
273
|
+
branchId: branch.id,
|
|
274
|
+
displayName: options.appName,
|
|
275
|
+
...options.region ? { regionId: options.region } : {}
|
|
276
|
+
} });
|
|
277
|
+
if (result.error || !result.data) {
|
|
278
|
+
if (result.response.status === 409) {
|
|
279
|
+
const matched = (await listComputeServices(client, {
|
|
280
|
+
projectId: options.projectId,
|
|
281
|
+
branchGitName: options.branchName
|
|
282
|
+
})).find((app) => app.name === options.appName);
|
|
283
|
+
if (matched) return {
|
|
284
|
+
appId: matched.id,
|
|
285
|
+
appName: matched.name,
|
|
286
|
+
region: matched.region ?? options.region
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
throw apiCallError(`Failed to create app "${options.appName}"`, result.response, result.error);
|
|
290
|
+
}
|
|
291
|
+
const service = result.data.data;
|
|
292
|
+
return {
|
|
293
|
+
appId: service.id,
|
|
294
|
+
appName: service.name,
|
|
295
|
+
region: service.region.id ?? options.region
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function apiCallError(summary, response, error) {
|
|
299
|
+
if (response.status === 404) return /* @__PURE__ */ new Error("Resource Not Found");
|
|
300
|
+
const message = error.error?.message ?? `Management API returned HTTP ${response.status}.`;
|
|
301
|
+
const hint = error.error?.hint ? ` ${error.error.hint}` : "";
|
|
302
|
+
return /* @__PURE__ */ new Error(`${summary}: ${message}${hint}`);
|
|
303
|
+
}
|
|
203
304
|
async function findAppForDeployment(sdk, deploymentId) {
|
|
204
305
|
const projectsResult = await sdk.listProjects();
|
|
205
306
|
if (projectsResult.isErr()) throw new Error(projectsResult.error.message);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { SERVICE_TOKEN_ENV_VAR } from "./client.js";
|
|
1
2
|
import { FileTokenStorage } from "../../adapters/token-storage.js";
|
|
2
3
|
import { requireComputeAuth } from "./guard.js";
|
|
3
4
|
import { login } from "./login.js";
|
|
4
5
|
//#region src/lib/auth/auth-ops.ts
|
|
6
|
+
const WORKSPACE_SUB_PREFIX = "workspace:";
|
|
5
7
|
function decodeJwtPayload(token) {
|
|
6
8
|
try {
|
|
7
9
|
const payload = token.split(".")[1];
|
|
@@ -11,6 +13,17 @@ function decodeJwtPayload(token) {
|
|
|
11
13
|
return {};
|
|
12
14
|
}
|
|
13
15
|
}
|
|
16
|
+
function emailFromClaims(claims) {
|
|
17
|
+
const email = claims.email;
|
|
18
|
+
return typeof email === "string" && email.trim().length > 0 ? email.trim() : null;
|
|
19
|
+
}
|
|
20
|
+
function workspaceIdFromClaims(claims) {
|
|
21
|
+
const sub = claims.sub;
|
|
22
|
+
if (typeof sub !== "string") return null;
|
|
23
|
+
if (!sub.startsWith(WORKSPACE_SUB_PREFIX)) return null;
|
|
24
|
+
const id = sub.slice(10).trim();
|
|
25
|
+
return id.length > 0 ? id : null;
|
|
26
|
+
}
|
|
14
27
|
async function performLogin(env) {
|
|
15
28
|
await login({
|
|
16
29
|
tokenStorage: new FileTokenStorage(env),
|
|
@@ -18,36 +31,68 @@ async function performLogin(env) {
|
|
|
18
31
|
});
|
|
19
32
|
}
|
|
20
33
|
async function readAuthState(env) {
|
|
34
|
+
const rawServiceToken = env[SERVICE_TOKEN_ENV_VAR];
|
|
35
|
+
if (rawServiceToken !== void 0) {
|
|
36
|
+
const serviceToken = rawServiceToken.trim();
|
|
37
|
+
if (serviceToken.length === 0) throw new Error(`${SERVICE_TOKEN_ENV_VAR} is set but empty. Provide a valid token or unset the variable.`);
|
|
38
|
+
return readServiceTokenAuthState(serviceToken, env);
|
|
39
|
+
}
|
|
21
40
|
const tokens = await new FileTokenStorage(env).getTokens();
|
|
22
41
|
if (!tokens) return {
|
|
23
42
|
authenticated: false,
|
|
24
43
|
provider: null,
|
|
25
44
|
user: null,
|
|
26
|
-
workspace: null
|
|
27
|
-
linkedProjectId: null
|
|
45
|
+
workspace: null
|
|
28
46
|
};
|
|
29
47
|
const claims = decodeJwtPayload(tokens.accessToken);
|
|
48
|
+
return buildAuthState({
|
|
49
|
+
workspaceIdFromCredential: tokens.workspaceId,
|
|
50
|
+
claims,
|
|
51
|
+
env
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async function readServiceTokenAuthState(token, env) {
|
|
55
|
+
const claims = decodeJwtPayload(token);
|
|
56
|
+
const workspaceId = workspaceIdFromClaims(claims);
|
|
57
|
+
if (!workspaceId) return {
|
|
58
|
+
authenticated: false,
|
|
59
|
+
provider: null,
|
|
60
|
+
user: null,
|
|
61
|
+
workspace: null
|
|
62
|
+
};
|
|
63
|
+
return buildAuthState({
|
|
64
|
+
workspaceIdFromCredential: workspaceId,
|
|
65
|
+
claims,
|
|
66
|
+
env
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function buildAuthState({ workspaceIdFromCredential, claims, env }) {
|
|
70
|
+
let workspaceId = workspaceIdFromCredential;
|
|
71
|
+
let workspaceName = workspaceIdFromCredential;
|
|
30
72
|
const client = await requireComputeAuth(env);
|
|
31
|
-
let workspaceId = tokens.workspaceId;
|
|
32
|
-
let workspaceName = tokens.workspaceId;
|
|
33
73
|
if (client) try {
|
|
34
|
-
const { data } = await client.GET("/v1/workspaces/{id}", { params: { path: { id:
|
|
35
|
-
if (
|
|
74
|
+
const { data, response } = await client.GET("/v1/workspaces/{id}", { params: { path: { id: workspaceIdFromCredential } } });
|
|
75
|
+
if (response?.status === 401) return {
|
|
76
|
+
authenticated: false,
|
|
77
|
+
provider: null,
|
|
78
|
+
user: null,
|
|
79
|
+
workspace: null
|
|
80
|
+
};
|
|
81
|
+
if (data?.data?.id) {
|
|
82
|
+
workspaceId = data.data.id;
|
|
83
|
+
workspaceName = data.data.id;
|
|
84
|
+
}
|
|
36
85
|
if (data?.data?.name) workspaceName = data.data.name;
|
|
37
86
|
} catch {}
|
|
87
|
+
const email = emailFromClaims(claims);
|
|
38
88
|
return {
|
|
39
89
|
authenticated: true,
|
|
40
90
|
provider: null,
|
|
41
|
-
user: {
|
|
42
|
-
id: claims.sub ?? "",
|
|
43
|
-
name: claims.name ?? "",
|
|
44
|
-
email: claims.email ?? ""
|
|
45
|
-
},
|
|
91
|
+
user: email ? { email } : null,
|
|
46
92
|
workspace: {
|
|
47
93
|
id: workspaceId,
|
|
48
94
|
name: workspaceName
|
|
49
|
-
}
|
|
50
|
-
linkedProjectId: null
|
|
95
|
+
}
|
|
51
96
|
};
|
|
52
97
|
}
|
|
53
98
|
async function performLogout(env) {
|
package/dist/lib/auth/client.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
//#region src/lib/auth/client.ts
|
|
4
4
|
const CLIENT_ID = "cmm3lndn701oo0uefvxzo0ivw";
|
|
5
|
-
const SERVICE_TOKEN_ENV_VAR = "
|
|
5
|
+
const SERVICE_TOKEN_ENV_VAR = "PRISMA_SERVICE_TOKEN";
|
|
6
6
|
const AUTH_FILE_ENV_VAR = "PRISMA_COMPUTE_AUTH_FILE";
|
|
7
7
|
function getApiBaseUrl(env = process.env) {
|
|
8
8
|
return env.PRISMA_MANAGEMENT_API_URL?.trim() || "https://api.prisma.io";
|
package/dist/lib/auth/guard.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createManagementApiClient, createManagementApiSdk } from "@prisma/manag
|
|
|
6
6
|
* Resolve authentication and return a ManagementApiClient.
|
|
7
7
|
*
|
|
8
8
|
* Priority:
|
|
9
|
-
* 1.
|
|
9
|
+
* 1. PRISMA_SERVICE_TOKEN env var → service token (CI / headless)
|
|
10
10
|
* 2. Stored OAuth tokens → SDK with auto-refresh
|
|
11
11
|
*
|
|
12
12
|
* Returns null if not authenticated.
|
package/dist/lib/auth/login.js
CHANGED
|
@@ -47,8 +47,9 @@ async function login(options = {}) {
|
|
|
47
47
|
reject(error);
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
res.
|
|
50
|
+
const workspaceName = await state.resolveWorkspaceName();
|
|
51
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
52
|
+
res.end(renderSuccessPage(workspaceName));
|
|
52
53
|
resolve();
|
|
53
54
|
});
|
|
54
55
|
});
|
|
@@ -63,13 +64,14 @@ var LoginState = class {
|
|
|
63
64
|
latestState;
|
|
64
65
|
sdk;
|
|
65
66
|
openUrl;
|
|
67
|
+
tokenStorage;
|
|
66
68
|
constructor(options) {
|
|
67
69
|
this.options = options;
|
|
68
|
-
|
|
70
|
+
this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env);
|
|
69
71
|
this.sdk = createManagementApiSdk({
|
|
70
72
|
clientId: options.clientId ?? "cmm3lndn701oo0uefvxzo0ivw",
|
|
71
73
|
redirectUri: `http://${options.hostname}:${options.port}/auth/callback`,
|
|
72
|
-
tokenStorage,
|
|
74
|
+
tokenStorage: this.tokenStorage,
|
|
73
75
|
apiBaseUrl: options.apiBaseUrl ?? getApiBaseUrl(options.env),
|
|
74
76
|
authBaseUrl: options.authBaseUrl
|
|
75
77
|
});
|
|
@@ -109,9 +111,118 @@ var LoginState = class {
|
|
|
109
111
|
throw new AuthError$1(error instanceof Error ? error.message : "Unknown error during login");
|
|
110
112
|
}
|
|
111
113
|
}
|
|
114
|
+
async resolveWorkspaceName() {
|
|
115
|
+
try {
|
|
116
|
+
const tokens = await this.tokenStorage.getTokens();
|
|
117
|
+
if (!tokens?.workspaceId) return null;
|
|
118
|
+
const { data } = await this.sdk.client.GET("/v1/workspaces/{id}", { params: { path: { id: tokens.workspaceId } } });
|
|
119
|
+
const name = data?.data?.name;
|
|
120
|
+
return typeof name === "string" && name.trim().length > 0 ? name.trim() : null;
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
112
125
|
get host() {
|
|
113
126
|
return `${this.options.hostname}:${this.options.port}`;
|
|
114
127
|
}
|
|
115
128
|
};
|
|
129
|
+
function renderSuccessPage(workspaceName) {
|
|
130
|
+
return `<!doctype html>
|
|
131
|
+
<html lang="en">
|
|
132
|
+
<head>
|
|
133
|
+
<meta charset="utf-8">
|
|
134
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
135
|
+
<title>Prisma Developer Platform</title>
|
|
136
|
+
<style>
|
|
137
|
+
:root {
|
|
138
|
+
color-scheme: light dark;
|
|
139
|
+
--background: #ffffff;
|
|
140
|
+
--foreground: #1f2430;
|
|
141
|
+
--muted: #4f5665;
|
|
142
|
+
--mark-color: #050812;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@media (prefers-color-scheme: dark) {
|
|
146
|
+
:root {
|
|
147
|
+
--background: #050812;
|
|
148
|
+
--foreground: #f6f7fb;
|
|
149
|
+
--muted: #c5cad6;
|
|
150
|
+
--mark-color: #ffffff;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
* {
|
|
155
|
+
box-sizing: border-box;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
body {
|
|
159
|
+
min-height: 100vh;
|
|
160
|
+
margin: 0;
|
|
161
|
+
background: var(--background);
|
|
162
|
+
color: var(--foreground);
|
|
163
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
164
|
+
display: grid;
|
|
165
|
+
grid-template-rows: 128px 1fr;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.mark {
|
|
169
|
+
align-self: end;
|
|
170
|
+
justify-self: center;
|
|
171
|
+
width: 36px;
|
|
172
|
+
height: 36px;
|
|
173
|
+
color: var(--mark-color);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.mark path {
|
|
177
|
+
fill: currentColor !important;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
main {
|
|
181
|
+
align-self: center;
|
|
182
|
+
justify-self: center;
|
|
183
|
+
width: min(520px, calc(100vw - 48px));
|
|
184
|
+
margin-top: -128px;
|
|
185
|
+
text-align: center;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
h1 {
|
|
189
|
+
margin: 0 0 12px;
|
|
190
|
+
font-size: 26px;
|
|
191
|
+
line-height: 1.2;
|
|
192
|
+
font-weight: 700;
|
|
193
|
+
letter-spacing: 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
p {
|
|
197
|
+
margin: 0 auto;
|
|
198
|
+
max-width: 480px;
|
|
199
|
+
color: var(--muted);
|
|
200
|
+
font-size: 15px;
|
|
201
|
+
line-height: 1.55;
|
|
202
|
+
letter-spacing: 0;
|
|
203
|
+
}
|
|
204
|
+
</style>
|
|
205
|
+
</head>
|
|
206
|
+
<body>
|
|
207
|
+
<svg class="mark" width="36" height="36" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M25.21,24.21,12.739,27.928a.525.525,0,0,1-.667-.606L16.528,5.811a.43.43,0,0,1,.809-.094l8.249,17.661A.6.6,0,0,1,25.21,24.21Zm2.139-.878L17.8,2.883h0A1.531,1.531,0,0,0,16.491,2a1.513,1.513,0,0,0-1.4.729L4.736,19.648a1.592,1.592,0,0,0,.018,1.7l5.064,7.909a1.628,1.628,0,0,0,1.83.678l14.7-4.383a1.6,1.6,0,0,0,1-2.218Z" style="fill:#0c344b;fill-rule:evenodd"/></svg>
|
|
208
|
+
<main>
|
|
209
|
+
<h1>You're all set.</h1>
|
|
210
|
+
<p>${workspaceName ? `Your terminal is now connected to your ${escapeHtml(workspaceName)} workspace. Head back to your terminal to continue.` : "Your terminal is now connected to your Prisma workspace. Head back to your terminal to continue."}</p>
|
|
211
|
+
</main>
|
|
212
|
+
</body>
|
|
213
|
+
</html>`;
|
|
214
|
+
}
|
|
215
|
+
function escapeHtml(value) {
|
|
216
|
+
return value.replace(/[&<>"']/g, (char) => {
|
|
217
|
+
switch (char) {
|
|
218
|
+
case "&": return "&";
|
|
219
|
+
case "<": return "<";
|
|
220
|
+
case ">": return ">";
|
|
221
|
+
case "\"": return """;
|
|
222
|
+
case "'": return "'";
|
|
223
|
+
default: return char;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
116
227
|
//#endregion
|
|
117
228
|
export { login };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/lib/project/local-pin.ts
|
|
4
|
+
const LOCAL_RESOLUTION_PIN_RELATIVE_PATH = ".prisma/local.json";
|
|
5
|
+
async function readLocalResolutionPin(cwd) {
|
|
6
|
+
try {
|
|
7
|
+
const raw = await readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), "utf8");
|
|
8
|
+
const parsed = JSON.parse(raw);
|
|
9
|
+
if (!isLocalResolutionPin(parsed)) return { kind: "invalid" };
|
|
10
|
+
return {
|
|
11
|
+
kind: "present",
|
|
12
|
+
pin: parsed
|
|
13
|
+
};
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error.code === "ENOENT") return { kind: "missing" };
|
|
16
|
+
if (error instanceof SyntaxError) return { kind: "invalid" };
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function writeLocalResolutionPin(cwd, pin) {
|
|
21
|
+
const prismaDir = path.join(cwd, ".prisma");
|
|
22
|
+
await mkdir(prismaDir, { recursive: true });
|
|
23
|
+
const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
|
|
24
|
+
const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
|
|
25
|
+
await writeFile(tmpPath, `${JSON.stringify(pin, null, 2)}\n`, "utf8");
|
|
26
|
+
await rename(tmpPath, pinPath);
|
|
27
|
+
}
|
|
28
|
+
async function ensureLocalResolutionPinGitignore(cwd) {
|
|
29
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
30
|
+
let existing = null;
|
|
31
|
+
try {
|
|
32
|
+
existing = await readFile(gitignorePath, "utf8");
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error.code !== "ENOENT") throw error;
|
|
35
|
+
}
|
|
36
|
+
if (existing === null) {
|
|
37
|
+
await writeFile(gitignorePath, ".prisma/\n", "utf8");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return;
|
|
41
|
+
await writeFile(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, "utf8");
|
|
42
|
+
}
|
|
43
|
+
function isLocalResolutionPin(value) {
|
|
44
|
+
if (!value || typeof value !== "object") return false;
|
|
45
|
+
const keys = Object.keys(value);
|
|
46
|
+
if (keys.length !== 2 || !keys.includes("workspaceId") || !keys.includes("projectId")) return false;
|
|
47
|
+
const candidate = value;
|
|
48
|
+
return typeof candidate.workspaceId === "string" && candidate.workspaceId.trim().length > 0 && typeof candidate.projectId === "string" && candidate.projectId.trim().length > 0;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, readLocalResolutionPin, writeLocalResolutionPin };
|