@looma/prisma-cli 0.1.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.
Files changed (44) hide show
  1. package/README.md +39 -0
  2. package/dist/adapters/config.js +74 -0
  3. package/dist/adapters/local-state.js +98 -0
  4. package/dist/adapters/mock-api.js +57 -0
  5. package/dist/adapters/token-storage.js +43 -0
  6. package/dist/cli.js +9 -0
  7. package/dist/cli2.js +59 -0
  8. package/dist/commands/app/index.js +178 -0
  9. package/dist/commands/auth/index.js +42 -0
  10. package/dist/commands/env/index.js +51 -0
  11. package/dist/commands/project/index.js +45 -0
  12. package/dist/controllers/app.js +658 -0
  13. package/dist/controllers/auth.js +107 -0
  14. package/dist/controllers/env.js +73 -0
  15. package/dist/controllers/project.js +214 -0
  16. package/dist/controllers/select-prompt-port.js +12 -0
  17. package/dist/lib/app/local-dev.js +178 -0
  18. package/dist/lib/app/prototype-build.js +109 -0
  19. package/dist/lib/app/prototype-interaction.js +38 -0
  20. package/dist/lib/app/prototype-progress.js +115 -0
  21. package/dist/lib/app/prototype-provider.js +163 -0
  22. package/dist/lib/auth/auth-ops.js +57 -0
  23. package/dist/lib/auth/client.js +22 -0
  24. package/dist/lib/auth/guard.js +34 -0
  25. package/dist/lib/auth/login.js +117 -0
  26. package/dist/output/patterns.js +93 -0
  27. package/dist/presenters/app.js +333 -0
  28. package/dist/presenters/auth.js +73 -0
  29. package/dist/presenters/env.js +111 -0
  30. package/dist/presenters/project.js +84 -0
  31. package/dist/shell/command-meta.js +294 -0
  32. package/dist/shell/command-runner.js +33 -0
  33. package/dist/shell/errors.js +64 -0
  34. package/dist/shell/global-flags.js +25 -0
  35. package/dist/shell/help.js +78 -0
  36. package/dist/shell/output.js +48 -0
  37. package/dist/shell/prompt.js +31 -0
  38. package/dist/shell/runtime.js +51 -0
  39. package/dist/shell/ui.js +59 -0
  40. package/dist/use-cases/auth.js +70 -0
  41. package/dist/use-cases/create-cli-gateways.js +93 -0
  42. package/dist/use-cases/env.js +104 -0
  43. package/dist/use-cases/project.js +75 -0
  44. package/package.json +30 -0
@@ -0,0 +1,109 @@
1
+ import path from "node:path";
2
+ import { cp, readdir, readlink, rm, stat } from "node:fs/promises";
3
+ import { BunBuild, NextjsBuild } from "@prisma/compute-sdk";
4
+ //#region src/lib/app/prototype-build.ts
5
+ var PrototypeBuildStrategy = class {
6
+ #appPath;
7
+ #entrypoint;
8
+ #buildType;
9
+ constructor(options) {
10
+ this.#appPath = options.appPath;
11
+ this.#entrypoint = options.entrypoint;
12
+ this.#buildType = options.buildType ?? "auto";
13
+ }
14
+ async canBuild() {
15
+ const { strategy } = await resolvePrototypeBuildStrategy({
16
+ appPath: this.#appPath,
17
+ entrypoint: this.#entrypoint,
18
+ buildType: this.#buildType
19
+ });
20
+ return strategy.canBuild();
21
+ }
22
+ async execute() {
23
+ const { artifact } = await executePrototypeBuild({
24
+ appPath: this.#appPath,
25
+ entrypoint: this.#entrypoint,
26
+ buildType: this.#buildType
27
+ });
28
+ return artifact;
29
+ }
30
+ };
31
+ async function executePrototypeBuild(options) {
32
+ const { strategy, buildType } = await resolvePrototypeBuildStrategy({
33
+ appPath: options.appPath,
34
+ entrypoint: options.entrypoint,
35
+ buildType: options.buildType ?? "auto"
36
+ });
37
+ const artifact = await strategy.execute();
38
+ try {
39
+ await normalizeArtifactSymlinks(artifact.directory, options.appPath);
40
+ return {
41
+ artifact,
42
+ buildType
43
+ };
44
+ } catch (error) {
45
+ await artifact.cleanup?.().catch(() => void 0);
46
+ throw error;
47
+ }
48
+ }
49
+ async function resolvePrototypeBuildStrategy(options) {
50
+ if (options.buildType === "nextjs") return {
51
+ buildType: "nextjs",
52
+ strategy: new NextjsBuild({ appPath: options.appPath })
53
+ };
54
+ if (options.buildType === "bun") return {
55
+ buildType: "bun",
56
+ strategy: new BunBuild({
57
+ appPath: options.appPath,
58
+ entrypoint: options.entrypoint
59
+ })
60
+ };
61
+ const nextjsStrategy = new NextjsBuild({ appPath: options.appPath });
62
+ if (await nextjsStrategy.canBuild()) return {
63
+ buildType: "nextjs",
64
+ strategy: nextjsStrategy
65
+ };
66
+ return {
67
+ buildType: "bun",
68
+ strategy: new BunBuild({
69
+ appPath: options.appPath,
70
+ entrypoint: options.entrypoint
71
+ })
72
+ };
73
+ }
74
+ async function normalizeArtifactSymlinks(artifactDir, appPath) {
75
+ const normalizedArtifactDir = path.resolve(artifactDir);
76
+ const normalizedAppPath = path.resolve(appPath);
77
+ await walkDirectory(normalizedArtifactDir);
78
+ async function walkDirectory(directory) {
79
+ const entries = await readdir(directory, { withFileTypes: true });
80
+ for (const entry of entries) {
81
+ const fullPath = path.join(directory, entry.name);
82
+ if (entry.isDirectory()) {
83
+ await walkDirectory(fullPath);
84
+ continue;
85
+ }
86
+ if (!entry.isSymbolicLink()) continue;
87
+ const target = await readlink(fullPath);
88
+ const resolvedTarget = path.resolve(path.dirname(fullPath), target);
89
+ if (isPathWithin(normalizedArtifactDir, resolvedTarget)) continue;
90
+ if (!isPathWithin(normalizedAppPath, resolvedTarget)) throw new Error(`Build artifact symlink escapes the app directory: ${resolvedTarget}`);
91
+ const targetStat = await stat(resolvedTarget);
92
+ await rm(fullPath, {
93
+ force: true,
94
+ recursive: true
95
+ });
96
+ await cp(resolvedTarget, fullPath, {
97
+ recursive: targetStat.isDirectory(),
98
+ dereference: true
99
+ });
100
+ if (targetStat.isDirectory()) await walkDirectory(fullPath);
101
+ }
102
+ }
103
+ }
104
+ function isPathWithin(rootPath, candidatePath) {
105
+ const relativePath = path.relative(rootPath, candidatePath);
106
+ return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
107
+ }
108
+ //#endregion
109
+ export { PrototypeBuildStrategy, executePrototypeBuild };
@@ -0,0 +1,38 @@
1
+ import { selectPrompt, textPrompt } from "../../shell/prompt.js";
2
+ //#region src/lib/app/prototype-interaction.ts
3
+ const CREATE_NEW_APP = "__create_new_app__";
4
+ const PROTOTYPE_DEFAULT_REGION = "eu-central-1";
5
+ function createPrototypeDeployInteraction(context) {
6
+ return {
7
+ async selectService(services) {
8
+ const sorted = services.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
9
+ const selection = await selectPrompt({
10
+ input: context.runtime.stdin,
11
+ output: context.runtime.stderr,
12
+ message: "Select an app",
13
+ choices: [...sorted.map((service) => ({
14
+ label: service.name,
15
+ value: service.id
16
+ })), {
17
+ label: "Create a new app",
18
+ value: CREATE_NEW_APP
19
+ }]
20
+ });
21
+ return selection === CREATE_NEW_APP ? null : selection;
22
+ },
23
+ async provideServiceName() {
24
+ return textPrompt({
25
+ input: context.runtime.stdin,
26
+ output: context.runtime.stderr,
27
+ message: "App name",
28
+ placeholder: "hello-world",
29
+ validate: (value) => !value?.trim() ? "App name is required" : void 0
30
+ }).then((value) => value.trim());
31
+ },
32
+ async selectRegion(_regions) {
33
+ return PROTOTYPE_DEFAULT_REGION;
34
+ }
35
+ };
36
+ }
37
+ //#endregion
38
+ export { PROTOTYPE_DEFAULT_REGION, createPrototypeDeployInteraction };
@@ -0,0 +1,115 @@
1
+ //#region src/lib/app/prototype-progress.ts
2
+ function createPrototypeDeployProgress(output, enabled) {
3
+ if (!enabled) return;
4
+ const write = (line) => {
5
+ output.write(`${line}\n`);
6
+ };
7
+ return {
8
+ onBuildStart() {
9
+ write("Building application...");
10
+ },
11
+ onBuildComplete() {
12
+ write("Build complete.");
13
+ },
14
+ onArchiveCreating() {
15
+ write("Creating deployment artifact...");
16
+ },
17
+ onArchiveReady(byteLength) {
18
+ write(`Artifact ready (${(byteLength / 1024).toFixed(1)} KB).`);
19
+ },
20
+ onVersionCreated(versionId) {
21
+ write(`Deployment ${versionId} created.`);
22
+ },
23
+ onUploadComplete() {
24
+ write("Upload complete.");
25
+ },
26
+ onStartRequested() {
27
+ write("Starting deployment...");
28
+ },
29
+ onStatusChange(status) {
30
+ write(`Status: ${status}`);
31
+ },
32
+ onRunning(url) {
33
+ if (url) {
34
+ write(`Deployment is running at ${url}.`);
35
+ return;
36
+ }
37
+ write("Deployment is running.");
38
+ },
39
+ onPromoteStart() {
40
+ write("Promoting deployment...");
41
+ },
42
+ onPromoted(url) {
43
+ if (url) {
44
+ write(`Promoted to ${url}.`);
45
+ return;
46
+ }
47
+ write("Promotion complete.");
48
+ },
49
+ onPromoteFailed(error) {
50
+ write(`Promotion failed${error?.message ? `: ${error.message}` : "."}`);
51
+ },
52
+ onOldVersionStopping(versionId) {
53
+ write(`Stopping previous deployment ${versionId}...`);
54
+ },
55
+ onOldVersionStopped(versionId) {
56
+ write(`Previous deployment ${versionId} stopped.`);
57
+ },
58
+ onOldVersionStopFailed(versionId) {
59
+ write(`Failed to stop previous deployment ${versionId} (non-fatal).`);
60
+ },
61
+ onOldVersionDeleting(versionId) {
62
+ write(`Deleting previous deployment ${versionId}...`);
63
+ },
64
+ onOldVersionDeleted(versionId) {
65
+ write(`Previous deployment ${versionId} deleted.`);
66
+ },
67
+ onOldVersionDeleteFailed(versionId) {
68
+ write(`Failed to delete previous deployment ${versionId} (non-fatal).`);
69
+ },
70
+ onCleanupDanglingVersion(versionId) {
71
+ write(`Cleaning up deployment ${versionId}...`);
72
+ },
73
+ onCleanupDanglingVersionComplete(versionId) {
74
+ write(`Deployment ${versionId} cleaned up.`);
75
+ },
76
+ onCleanupDanglingVersionFailed(versionId) {
77
+ write(`Failed to clean up deployment ${versionId}.`);
78
+ }
79
+ };
80
+ }
81
+ function createPrototypePromoteProgress(output, enabled) {
82
+ if (!enabled) return;
83
+ const write = (line) => {
84
+ output.write(`${line}\n`);
85
+ };
86
+ return {
87
+ onVersionStarting(versionId) {
88
+ write(`Starting deployment ${versionId}...`);
89
+ },
90
+ onVersionStartRequested() {
91
+ write("Requesting deployment start...");
92
+ },
93
+ onStatusChange(status) {
94
+ write(`Status: ${status}`);
95
+ },
96
+ onVersionRunning() {
97
+ write("Deployment is running.");
98
+ },
99
+ onPromoteStart() {
100
+ write("Promoting deployment...");
101
+ },
102
+ onPromoted(url) {
103
+ if (url) {
104
+ write(`Promoted to ${url}.`);
105
+ return;
106
+ }
107
+ write("Promotion complete.");
108
+ },
109
+ onPromoteFailed(error) {
110
+ write(`Promotion failed${error?.message ? `: ${error.message}` : "."}`);
111
+ }
112
+ };
113
+ }
114
+ //#endregion
115
+ export { createPrototypeDeployProgress, createPrototypePromoteProgress };
@@ -0,0 +1,163 @@
1
+ import { PrototypeBuildStrategy } from "./prototype-build.js";
2
+ import path from "node:path";
3
+ import { ApiError, ComputeClient } from "@prisma/compute-sdk";
4
+ //#region src/lib/app/prototype-provider.ts
5
+ function createPrototypeAppProvider(client) {
6
+ const sdk = new ComputeClient(client);
7
+ return {
8
+ async createProject(options) {
9
+ const projectResult = await sdk.createProject({ name: options.name });
10
+ if (projectResult.isErr()) throw new Error(projectResult.error.message);
11
+ return {
12
+ id: projectResult.value.id,
13
+ name: projectResult.value.name
14
+ };
15
+ },
16
+ async listApps(projectId) {
17
+ const servicesResult = await sdk.listServices({ projectId });
18
+ if (servicesResult.isErr()) throw new Error(servicesResult.error.message);
19
+ return (await Promise.all(servicesResult.value.map(async (service) => {
20
+ const detailResult = await sdk.showService({ serviceId: service.id });
21
+ return detailResult.isOk() ? detailResult.value : {
22
+ id: service.id,
23
+ name: service.name,
24
+ region: service.region,
25
+ latestVersionId: null,
26
+ serviceEndpointDomain: void 0
27
+ };
28
+ }))).map((service) => ({
29
+ id: service.id,
30
+ name: service.name,
31
+ region: service.region ?? null,
32
+ liveDeploymentId: service.latestVersionId ?? null,
33
+ liveUrl: toAbsoluteUrl(service.serviceEndpointDomain ?? null)
34
+ }));
35
+ },
36
+ async removeApp(appId) {
37
+ const appResult = await sdk.showService({ serviceId: appId });
38
+ if (appResult.isErr()) throw new Error(appResult.error.message);
39
+ const destroyResult = await sdk.destroyService({
40
+ serviceId: appId,
41
+ keepService: false,
42
+ timeoutSeconds: 120,
43
+ pollIntervalMs: 2e3
44
+ });
45
+ if (destroyResult.isErr()) throw new Error(destroyResult.error.message);
46
+ return {
47
+ id: appResult.value.id,
48
+ name: appResult.value.name
49
+ };
50
+ },
51
+ async promoteDeployment(options) {
52
+ const promoteResult = await sdk.promote({
53
+ serviceId: options.appId,
54
+ versionId: options.deploymentId,
55
+ timeoutSeconds: 120,
56
+ pollIntervalMs: 2e3,
57
+ progress: options.progress
58
+ });
59
+ if (promoteResult.isErr()) throw new Error(promoteResult.error.message);
60
+ },
61
+ async deployApp(options) {
62
+ const deployResult = await sdk.deploy({
63
+ strategy: new PrototypeBuildStrategy({ appPath: path.resolve(options.cwd) }),
64
+ projectId: options.projectId,
65
+ serviceId: options.appId,
66
+ serviceName: options.appName,
67
+ region: options.region,
68
+ timeoutSeconds: 120,
69
+ pollIntervalMs: 2e3,
70
+ interaction: options.interaction,
71
+ progress: options.progress
72
+ });
73
+ if (deployResult.isErr()) throw new Error(deployResult.error.message);
74
+ const deployed = deployResult.value;
75
+ return {
76
+ projectId: deployed.projectId,
77
+ app: {
78
+ id: deployed.serviceId,
79
+ name: deployed.serviceName,
80
+ region: deployed.region ?? null,
81
+ liveDeploymentId: deployed.versionId,
82
+ liveUrl: toAbsoluteUrl(deployed.serviceEndpointDomain ?? null)
83
+ },
84
+ deployment: {
85
+ id: deployed.versionId,
86
+ status: "running",
87
+ url: toAbsoluteUrl(deployed.serviceEndpointDomain ?? deployed.versionEndpointDomain ?? null)
88
+ }
89
+ };
90
+ },
91
+ async listDeployments(appId) {
92
+ const [appResult, versionsResult] = await Promise.all([sdk.showService({ serviceId: appId }), sdk.listVersions({ serviceId: appId })]);
93
+ if (appResult.isErr()) throw new Error(appResult.error.message);
94
+ if (versionsResult.isErr()) throw new Error(versionsResult.error.message);
95
+ return {
96
+ app: {
97
+ id: appResult.value.id,
98
+ name: appResult.value.name,
99
+ region: appResult.value.region ?? null,
100
+ liveDeploymentId: appResult.value.latestVersionId ?? null,
101
+ liveUrl: toAbsoluteUrl(appResult.value.serviceEndpointDomain ?? null)
102
+ },
103
+ deployments: versionsResult.value.slice().sort((left, right) => {
104
+ const byDate = right.createdAt.localeCompare(left.createdAt);
105
+ return byDate !== 0 ? byDate : right.id.localeCompare(left.id);
106
+ }).map((deployment) => ({
107
+ id: deployment.id,
108
+ status: deployment.status,
109
+ createdAt: deployment.createdAt,
110
+ url: toAbsoluteUrl(deployment.previewDomain ?? null),
111
+ live: null
112
+ }))
113
+ };
114
+ },
115
+ async showDeployment(deploymentId) {
116
+ const deploymentResult = await sdk.showVersion({ versionId: deploymentId });
117
+ if (deploymentResult.isErr()) {
118
+ if (ApiError.is(deploymentResult.error) && deploymentResult.error.statusCode === 404) return null;
119
+ throw new Error(deploymentResult.error.message);
120
+ }
121
+ return {
122
+ app: await findAppForDeployment(sdk, deploymentId),
123
+ deployment: {
124
+ id: deploymentResult.value.id,
125
+ status: deploymentResult.value.status,
126
+ createdAt: deploymentResult.value.createdAt,
127
+ url: toAbsoluteUrl(deploymentResult.value.previewDomain ?? null),
128
+ live: null
129
+ }
130
+ };
131
+ }
132
+ };
133
+ }
134
+ async function findAppForDeployment(sdk, deploymentId) {
135
+ const projectsResult = await sdk.listProjects();
136
+ if (projectsResult.isErr()) throw new Error(projectsResult.error.message);
137
+ for (const project of projectsResult.value) {
138
+ const servicesResult = await sdk.listServices({ projectId: project.id });
139
+ if (servicesResult.isErr()) throw new Error(servicesResult.error.message);
140
+ for (const service of servicesResult.value) {
141
+ const detailResult = await sdk.showService({ serviceId: service.id });
142
+ if (detailResult.isErr()) throw new Error(detailResult.error.message);
143
+ const app = {
144
+ id: detailResult.value.id,
145
+ name: detailResult.value.name,
146
+ region: detailResult.value.region ?? null,
147
+ liveDeploymentId: detailResult.value.latestVersionId ?? null,
148
+ liveUrl: toAbsoluteUrl(detailResult.value.serviceEndpointDomain ?? null)
149
+ };
150
+ if (app.liveDeploymentId === deploymentId) return app;
151
+ const versionsResult = await sdk.listVersions({ serviceId: service.id });
152
+ if (versionsResult.isErr()) throw new Error(versionsResult.error.message);
153
+ if (versionsResult.value.some((version) => version.id === deploymentId)) return app;
154
+ }
155
+ }
156
+ return null;
157
+ }
158
+ function toAbsoluteUrl(url) {
159
+ if (!url) return null;
160
+ return url.startsWith("https://") || url.startsWith("http://") ? url : `https://${url}`;
161
+ }
162
+ //#endregion
163
+ export { createPrototypeAppProvider };
@@ -0,0 +1,57 @@
1
+ import { FileTokenStorage } from "../../adapters/token-storage.js";
2
+ import { requireComputeAuth } from "./guard.js";
3
+ import { login } from "./login.js";
4
+ //#region src/lib/auth/auth-ops.ts
5
+ function decodeJwtPayload(token) {
6
+ try {
7
+ const payload = token.split(".")[1];
8
+ if (!payload) return {};
9
+ return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
10
+ } catch {
11
+ return {};
12
+ }
13
+ }
14
+ async function performLogin(env) {
15
+ await login({
16
+ tokenStorage: new FileTokenStorage(env),
17
+ env
18
+ });
19
+ }
20
+ async function readAuthState(env) {
21
+ const tokens = await new FileTokenStorage(env).getTokens();
22
+ if (!tokens) return {
23
+ authenticated: false,
24
+ provider: null,
25
+ user: null,
26
+ workspace: null,
27
+ linkedProjectId: null
28
+ };
29
+ const claims = decodeJwtPayload(tokens.accessToken);
30
+ const client = await requireComputeAuth(env);
31
+ let workspaceId = tokens.workspaceId;
32
+ let workspaceName = tokens.workspaceId;
33
+ if (client) try {
34
+ const { data } = await client.GET("/v1/workspaces/{id}", { params: { path: { id: tokens.workspaceId } } });
35
+ if (data?.data?.id) workspaceId = data.data.id;
36
+ if (data?.data?.name) workspaceName = data.data.name;
37
+ } catch {}
38
+ return {
39
+ authenticated: true,
40
+ provider: null,
41
+ user: {
42
+ id: claims.sub ?? "",
43
+ name: claims.name ?? "",
44
+ email: claims.email ?? ""
45
+ },
46
+ workspace: {
47
+ id: workspaceId,
48
+ name: workspaceName
49
+ },
50
+ linkedProjectId: null
51
+ };
52
+ }
53
+ async function performLogout(env) {
54
+ await new FileTokenStorage(env).clearTokens();
55
+ }
56
+ //#endregion
57
+ export { performLogin, performLogout, readAuthState };
@@ -0,0 +1,22 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ //#region src/lib/auth/client.ts
4
+ const CLIENT_ID = "cmm3lndn701oo0uefvxzo0ivw";
5
+ const SERVICE_TOKEN_ENV_VAR = "PRISMA_API_TOKEN";
6
+ const AUTH_FILE_ENV_VAR = "PRISMA_COMPUTE_AUTH_FILE";
7
+ function getApiBaseUrl(env = process.env) {
8
+ return env.PRISMA_MANAGEMENT_API_URL?.trim() || "https://api.prisma.io";
9
+ }
10
+ function getAuthFilePath(env = process.env) {
11
+ const configured = env[AUTH_FILE_ENV_VAR];
12
+ if (configured?.trim()) return path.resolve(configured);
13
+ if (process.platform === "darwin") return path.join(os.homedir(), "Library", "Application Support", "prisma", "auth.json");
14
+ if (process.platform === "win32") {
15
+ const appData = env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
16
+ return path.join(appData, "prisma", "auth.json");
17
+ }
18
+ const xdgConfigHome = env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
19
+ return path.join(xdgConfigHome, "prisma", "auth.json");
20
+ }
21
+ //#endregion
22
+ export { CLIENT_ID, SERVICE_TOKEN_ENV_VAR, getApiBaseUrl, getAuthFilePath };
@@ -0,0 +1,34 @@
1
+ import { CLIENT_ID, SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "./client.js";
2
+ import { FileTokenStorage } from "../../adapters/token-storage.js";
3
+ import { createManagementApiClient, createManagementApiSdk } from "@prisma/management-api-sdk";
4
+ //#region src/lib/auth/guard.ts
5
+ /**
6
+ * Resolve authentication and return a ManagementApiClient.
7
+ *
8
+ * Priority:
9
+ * 1. PRISMA_API_TOKEN env var → service token (CI / headless)
10
+ * 2. Stored OAuth tokens → SDK with auto-refresh
11
+ *
12
+ * Returns null if not authenticated.
13
+ */
14
+ async function requireComputeAuth(env = process.env) {
15
+ const rawToken = env[SERVICE_TOKEN_ENV_VAR];
16
+ if (rawToken !== void 0) {
17
+ const token = rawToken.trim();
18
+ if (token.length === 0) throw new Error(`${SERVICE_TOKEN_ENV_VAR} is set but empty. Provide a valid token or unset the variable.`);
19
+ return createManagementApiClient({
20
+ baseUrl: getApiBaseUrl(env),
21
+ token
22
+ });
23
+ }
24
+ const tokenStorage = new FileTokenStorage(env);
25
+ if (!await tokenStorage.getTokens()) return null;
26
+ return createManagementApiSdk({
27
+ clientId: CLIENT_ID,
28
+ redirectUri: "http://localhost:0/auth/callback",
29
+ tokenStorage,
30
+ apiBaseUrl: getApiBaseUrl(env)
31
+ }).client;
32
+ }
33
+ //#endregion
34
+ export { requireComputeAuth };
@@ -0,0 +1,117 @@
1
+ import { getApiBaseUrl } from "./client.js";
2
+ import { FileTokenStorage } from "../../adapters/token-storage.js";
3
+ import open from "open";
4
+ import { AuthError, createManagementApiSdk } from "@prisma/management-api-sdk";
5
+ import events from "node:events";
6
+ import http from "node:http";
7
+ //#region src/lib/auth/login.ts
8
+ var AuthError$1 = class extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "AuthError";
12
+ }
13
+ };
14
+ async function login(options = {}) {
15
+ const hostname = options.hostname ?? "localhost";
16
+ const port = options.port ?? 0;
17
+ const server = http.createServer();
18
+ server.listen({
19
+ host: hostname,
20
+ port
21
+ });
22
+ try {
23
+ const state = new LoginState({
24
+ hostname,
25
+ port: (await events.once(server, "listening").then(() => server.address())).port,
26
+ tokenStorage: options.tokenStorage,
27
+ clientId: options.clientId,
28
+ apiBaseUrl: options.apiBaseUrl,
29
+ authBaseUrl: options.authBaseUrl,
30
+ openUrl: options.openUrl,
31
+ env: options.env
32
+ });
33
+ const authResult = new Promise((resolve, reject) => {
34
+ server.on("request", async (req, res) => {
35
+ const url = new URL(`http://${state.host}${req.url}`);
36
+ if (url.pathname !== "/auth/callback") {
37
+ res.statusCode = 404;
38
+ res.end("Not found");
39
+ return;
40
+ }
41
+ try {
42
+ await state.handleCallback(url);
43
+ } catch (error) {
44
+ res.statusCode = 400;
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ res.end(message);
47
+ reject(error);
48
+ return;
49
+ }
50
+ res.setHeader("Content-Type", "text/html");
51
+ res.end(`<html><body style="font-family:system-ui;max-width:400px;margin:80px auto;text-align:center"><h2>✓ Signed in</h2><p>You may now close this tab and return to the terminal.</p></body></html>`);
52
+ resolve();
53
+ });
54
+ });
55
+ await state.openLoginPage();
56
+ await authResult;
57
+ } finally {
58
+ if (server.listening) await new Promise((resolve) => server.close(() => resolve()));
59
+ }
60
+ }
61
+ var LoginState = class {
62
+ latestVerifier;
63
+ latestState;
64
+ sdk;
65
+ openUrl;
66
+ constructor(options) {
67
+ this.options = options;
68
+ const tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env);
69
+ this.sdk = createManagementApiSdk({
70
+ clientId: options.clientId ?? "cmm3lndn701oo0uefvxzo0ivw",
71
+ redirectUri: `http://${options.hostname}:${options.port}/auth/callback`,
72
+ tokenStorage,
73
+ apiBaseUrl: options.apiBaseUrl ?? getApiBaseUrl(options.env),
74
+ authBaseUrl: options.authBaseUrl
75
+ });
76
+ this.openUrl = options.openUrl ?? open;
77
+ }
78
+ async openLoginPage() {
79
+ const { url, state, verifier } = await this.sdk.getLoginUrl({
80
+ scope: "workspace:admin offline_access",
81
+ additionalParams: {
82
+ utm_source: "prisma-cli",
83
+ utm_medium: "command-login",
84
+ utm_campaign: "prisma-cli"
85
+ }
86
+ });
87
+ this.latestState = state;
88
+ this.latestVerifier = verifier;
89
+ await this.openUrl(url);
90
+ }
91
+ async handleCallback(url) {
92
+ if (url.pathname !== "/auth/callback") throw new AuthError$1("Not a callback URL");
93
+ const params = url.searchParams;
94
+ const error = params.get("error");
95
+ if (error) {
96
+ const desc = params.get("error_description");
97
+ throw new AuthError$1(desc ? `${error}: ${desc}` : error);
98
+ }
99
+ if (!this.latestVerifier) throw new AuthError$1("No verifier found");
100
+ if (!this.latestState) throw new AuthError$1("No state found");
101
+ try {
102
+ await this.sdk.handleCallback({
103
+ callbackUrl: url,
104
+ verifier: this.latestVerifier,
105
+ expectedState: this.latestState
106
+ });
107
+ } catch (error) {
108
+ if (error instanceof AuthError) throw new AuthError$1(error.message);
109
+ throw new AuthError$1(error instanceof Error ? error.message : "Unknown error during login");
110
+ }
111
+ }
112
+ get host() {
113
+ return `${this.options.hostname}:${this.options.port}`;
114
+ }
115
+ };
116
+ //#endregion
117
+ export { login };