@prisma/cli 3.0.0-beta.8 → 3.0.0-dev.101.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 (56) hide show
  1. package/README.md +0 -13
  2. package/dist/adapters/mock-api.js +75 -0
  3. package/dist/adapters/token-storage.js +311 -33
  4. package/dist/cli.js +7 -7
  5. package/dist/cli2.js +5 -3
  6. package/dist/commands/app/index.js +65 -52
  7. package/dist/commands/auth/index.js +55 -2
  8. package/dist/commands/database/index.js +159 -0
  9. package/dist/controllers/app-env.js +10 -7
  10. package/dist/controllers/app.js +550 -208
  11. package/dist/controllers/auth.js +211 -2
  12. package/dist/controllers/branch.js +5 -6
  13. package/dist/controllers/database.js +318 -0
  14. package/dist/controllers/project.js +49 -20
  15. package/dist/lib/app/app-interaction.js +5 -0
  16. package/dist/lib/app/{preview-provider.js → app-provider.js} +32 -28
  17. package/dist/lib/app/{preview-branch-database.js → branch-database-api.js} +1 -1
  18. package/dist/lib/app/branch-database-deploy.js +13 -60
  19. package/dist/lib/app/branch-database.js +1 -102
  20. package/dist/lib/app/build-settings.js +93 -0
  21. package/dist/lib/app/build.js +82 -0
  22. package/dist/lib/app/bun-project.js +2 -3
  23. package/dist/lib/app/compute-config.js +147 -0
  24. package/dist/lib/app/deploy-plan.js +58 -0
  25. package/dist/lib/app/{preview-progress.js → deploy-progress.js} +5 -5
  26. package/dist/lib/app/local-dev.js +3 -60
  27. package/dist/lib/app/production-deploy-gate.js +3 -3
  28. package/dist/lib/app/read-branch.js +30 -0
  29. package/dist/lib/auth/auth-ops.js +10 -4
  30. package/dist/lib/auth/guard.js +4 -1
  31. package/dist/lib/auth/login.js +32 -25
  32. package/dist/lib/database/provider.js +167 -0
  33. package/dist/lib/diagnostics.js +1 -1
  34. package/dist/lib/fs/home-path.js +24 -0
  35. package/dist/lib/git/local-branch.js +15 -3
  36. package/dist/lib/project/local-pin.js +170 -40
  37. package/dist/lib/project/resolution.js +166 -61
  38. package/dist/lib/project/setup.js +65 -19
  39. package/dist/output/patterns.js +1 -1
  40. package/dist/presenters/app.js +44 -39
  41. package/dist/presenters/auth.js +98 -1
  42. package/dist/presenters/branch.js +1 -1
  43. package/dist/presenters/database.js +274 -0
  44. package/dist/presenters/project.js +3 -9
  45. package/dist/shell/command-meta.js +153 -2
  46. package/dist/shell/command-runner.js +39 -21
  47. package/dist/shell/diagnostics-output.js +2 -8
  48. package/dist/shell/errors.js +50 -1
  49. package/dist/shell/help.js +30 -20
  50. package/dist/shell/runtime.js +8 -4
  51. package/dist/shell/ui.js +19 -2
  52. package/dist/use-cases/auth.js +68 -1
  53. package/package.json +18 -3
  54. package/dist/lib/app/preview-build-settings.js +0 -385
  55. package/dist/lib/app/preview-build.js +0 -475
  56. package/dist/lib/app/preview-interaction.js +0 -5
@@ -0,0 +1,167 @@
1
+ import { CliError } from "../../shell/errors.js";
2
+ //#region src/lib/database/provider.ts
3
+ function createManagementDatabaseProvider(client) {
4
+ return {
5
+ async listDatabases(options) {
6
+ const databases = [];
7
+ let cursor;
8
+ while (true) {
9
+ const result = await client.GET("/v1/databases", {
10
+ params: { query: {
11
+ projectId: options.projectId,
12
+ branchGitName: options.branchName,
13
+ cursor
14
+ } },
15
+ signal: options.signal
16
+ });
17
+ if (result.error || !result.data) throw databaseApiError("Failed to list databases", result.response, result.error);
18
+ databases.push(...result.data.data);
19
+ if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
20
+ cursor = result.data.pagination.nextCursor;
21
+ }
22
+ return databases.map((database) => normalizeDatabase(database, options.projectId));
23
+ },
24
+ async showDatabase(databaseId, options) {
25
+ const result = await client.GET("/v1/databases/{databaseId}", {
26
+ params: { path: { databaseId } },
27
+ signal: options?.signal
28
+ });
29
+ if (result.response?.status === 404) return null;
30
+ if (result.error || !result.data) throw databaseApiError("Failed to show database", result.response, result.error);
31
+ const database = result.data.data;
32
+ return normalizeDatabase(database, requireDatabaseProjectId(database, options?.projectId));
33
+ },
34
+ async createDatabase(options) {
35
+ const result = await client.POST("/v1/databases", {
36
+ body: {
37
+ projectId: options.projectId,
38
+ name: options.name,
39
+ source: { type: "empty" },
40
+ ...options.branchName ? { branchGitName: options.branchName } : {},
41
+ ...options.region ? { region: options.region } : {}
42
+ },
43
+ signal: options.signal
44
+ });
45
+ if (result.error || !result.data) throw databaseApiError("Failed to create database", result.response, result.error);
46
+ return normalizeCreatedDatabase(result.data.data, options.projectId);
47
+ },
48
+ async removeDatabase(databaseId, options) {
49
+ const result = await client.DELETE("/v1/databases/{databaseId}", {
50
+ params: { path: { databaseId } },
51
+ signal: options?.signal
52
+ });
53
+ if (result.error) throw databaseApiError("Failed to remove database", result.response, result.error);
54
+ },
55
+ async listConnections(databaseId, options) {
56
+ const result = await client.GET("/v1/databases/{databaseId}/connections", {
57
+ params: { path: { databaseId } },
58
+ signal: options?.signal
59
+ });
60
+ if (result.error || !result.data) throw databaseApiError("Failed to list database connections", result.response, result.error);
61
+ return result.data.data.map((connection) => normalizeConnection(connection, databaseId));
62
+ },
63
+ async createConnection(options) {
64
+ const result = await client.POST("/v1/databases/{databaseId}/connections", {
65
+ params: { path: { databaseId: options.databaseId } },
66
+ body: { name: options.name },
67
+ signal: options.signal
68
+ });
69
+ if (result.error || !result.data) throw databaseApiError("Failed to create database connection", result.response, result.error);
70
+ return normalizeCreatedConnection(result.data.data, options.databaseId);
71
+ },
72
+ async removeConnection(connectionId, options) {
73
+ const result = await client.DELETE("/v1/connections/{id}", {
74
+ params: { path: { id: connectionId } },
75
+ signal: options?.signal
76
+ });
77
+ if (result.error) throw databaseApiError("Failed to remove database connection", result.response, result.error);
78
+ }
79
+ };
80
+ }
81
+ function normalizeDatabase(database, fallbackProjectId) {
82
+ return {
83
+ id: database.id,
84
+ name: database.name,
85
+ projectId: database.projectId ?? fallbackProjectId,
86
+ branchId: database.branchId ?? database.branch?.id ?? null,
87
+ branchName: database.branchGitName ?? database.branchName ?? database.branch?.gitName ?? database.branch?.name ?? null,
88
+ region: normalizeRegion(database),
89
+ status: database.status ?? null,
90
+ isDefault: database.isDefault ?? null,
91
+ createdAt: database.createdAt ?? null
92
+ };
93
+ }
94
+ function normalizeConnection(connection, fallbackDatabaseId) {
95
+ return {
96
+ id: connection.id,
97
+ name: connection.name ?? connection.id,
98
+ databaseId: connection.databaseId ?? fallbackDatabaseId,
99
+ createdAt: connection.createdAt ?? null
100
+ };
101
+ }
102
+ function normalizeCreatedDatabase(database, fallbackProjectId) {
103
+ const rawConnection = database.connections?.[0];
104
+ if (!rawConnection) throw new CliError({
105
+ code: "DATABASE_CONNECTION_MISSING",
106
+ domain: "database",
107
+ summary: "Created database did not return a connection string",
108
+ why: "The Management API created the database but did not include the one-time connection payload.",
109
+ fix: "Create a connection explicitly with prisma-cli database connection create <database>.",
110
+ exitCode: 1,
111
+ nextSteps: [`prisma-cli database connection create ${database.id}`]
112
+ });
113
+ return {
114
+ database: normalizeDatabase(database, fallbackProjectId),
115
+ ...normalizeCreatedConnection(rawConnection, database.id)
116
+ };
117
+ }
118
+ function normalizeCreatedConnection(connection, fallbackDatabaseId) {
119
+ const connectionString = extractConnectionString(connection);
120
+ if (!connectionString) throw new CliError({
121
+ code: "DATABASE_CONNECTION_STRING_MISSING",
122
+ domain: "database",
123
+ summary: "Created connection did not return a connection string",
124
+ why: "Database connection strings are one-time-view secrets, but the Management API did not include one in this create response.",
125
+ fix: "Create another database connection and store the returned URL immediately.",
126
+ exitCode: 1,
127
+ nextSteps: [`prisma-cli database connection create ${fallbackDatabaseId}`]
128
+ });
129
+ return {
130
+ connection: normalizeConnection(connection, fallbackDatabaseId),
131
+ connectionString
132
+ };
133
+ }
134
+ function normalizeRegion(database) {
135
+ if (typeof database.region === "string") return database.region;
136
+ return database.region?.id ?? database.regionId ?? null;
137
+ }
138
+ function requireDatabaseProjectId(database, fallbackProjectId) {
139
+ const projectId = database.projectId ?? fallbackProjectId;
140
+ if (projectId) return projectId;
141
+ throw new CliError({
142
+ code: "DATABASE_API_ERROR",
143
+ domain: "database",
144
+ summary: "Database response did not include a project id",
145
+ why: "The Management API returned database metadata without project context.",
146
+ fix: "Re-run with --trace for the underlying API response details.",
147
+ exitCode: 1,
148
+ nextSteps: []
149
+ });
150
+ }
151
+ function extractConnectionString(connection) {
152
+ return connection.endpoints?.pooled?.connectionString ?? connection.connectionString ?? connection.endpoints?.direct?.connectionString ?? connection.endpoints?.accelerate?.connectionString ?? null;
153
+ }
154
+ function databaseApiError(summary, response, error) {
155
+ const status = response?.status ?? 0;
156
+ return new CliError({
157
+ code: error?.error?.code ?? "DATABASE_API_ERROR",
158
+ domain: "database",
159
+ summary,
160
+ why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
161
+ fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
162
+ exitCode: 1,
163
+ nextSteps: []
164
+ });
165
+ }
166
+ //#endregion
167
+ export { createManagementDatabaseProvider, normalizeConnection, normalizeDatabase };
@@ -3,7 +3,7 @@ import { resolveStateDir } from "../shell/runtime.js";
3
3
  import { readLocalGitState } from "./git/local-status.js";
4
4
  //#region src/lib/diagnostics.ts
5
5
  async function collectCommandDiagnostics(context, options = {}) {
6
- const stateDir = resolveStateDir(context.runtime);
6
+ const stateDir = await resolveStateDir(context.runtime);
7
7
  return {
8
8
  cwd: context.runtime.cwd,
9
9
  stateFilePath: resolveLocalStateFilePath(stateDir),
@@ -0,0 +1,24 @@
1
+ import path from "node:path";
2
+ //#region src/lib/fs/home-path.ts
3
+ /**
4
+ * Shortens a path under the user's home directory to `~/...` for display,
5
+ * posix-style on every platform. Falls back to the Windows home variables
6
+ * when `HOME` is unset (native cmd/PowerShell sessions).
7
+ */
8
+ function shortenHomePath(value, env) {
9
+ const resolved = path.resolve(value);
10
+ const home = resolveHomeDirectory(env);
11
+ if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
12
+ const relative = path.relative(home, resolved).split(path.sep).join("/");
13
+ return relative ? `~/${relative}` : "~";
14
+ }
15
+ return resolved;
16
+ }
17
+ function resolveHomeDirectory(env) {
18
+ if (env.HOME) return path.resolve(env.HOME);
19
+ if (env.USERPROFILE) return path.resolve(env.USERPROFILE);
20
+ if (env.HOMEDRIVE && env.HOMEPATH) return path.resolve(`${env.HOMEDRIVE}${env.HOMEPATH}`);
21
+ return null;
22
+ }
23
+ //#endregion
24
+ export { shortenHomePath };
@@ -1,9 +1,22 @@
1
1
  import { access, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  //#region src/lib/git/local-branch.ts
4
+ /**
5
+ * Resolves the checked-out branch the way git does: the nearest `.git`
6
+ * (directory or worktree file) from `cwd` upward owns the answer, so
7
+ * monorepo commands run from inside a package see the repository branch.
8
+ * Returns null for detached HEAD or when no repository contains `cwd`.
9
+ */
4
10
  async function readLocalGitBranch(cwd, signal) {
5
- const headPath = await resolveGitHeadPath(path.join(cwd, ".git"), signal);
6
- if (!headPath) return null;
11
+ for (let directory = path.resolve(cwd);;) {
12
+ const headPath = await resolveGitHeadPath(path.join(directory, ".git"), signal);
13
+ if (headPath) return readBranchFromHead(headPath, signal);
14
+ const parent = path.dirname(directory);
15
+ if (parent === directory) return null;
16
+ directory = parent;
17
+ }
18
+ }
19
+ async function readBranchFromHead(headPath, signal) {
7
20
  try {
8
21
  const head = (await readFile(headPath, {
9
22
  encoding: "utf8",
@@ -12,7 +25,6 @@ async function readLocalGitBranch(cwd, signal) {
12
25
  if (head.startsWith("ref: refs/heads/")) return head.slice(16);
13
26
  } catch (error) {
14
27
  if (signal.aborted) throw error;
15
- return null;
16
28
  }
17
29
  return null;
18
30
  }
@@ -1,62 +1,192 @@
1
1
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { Result, TaggedError, UnhandledException } from "better-result";
3
4
  //#region src/lib/project/local-pin.ts
4
5
  const LOCAL_RESOLUTION_PIN_RELATIVE_PATH = ".prisma/local.json";
5
- async function readLocalResolutionPin(cwd, signal) {
6
- signal?.throwIfAborted();
7
- try {
8
- const raw = await readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), {
9
- encoding: "utf8",
10
- signal
6
+ var LocalResolutionPinInvalidJsonError = class extends TaggedError("LocalResolutionPinInvalidJsonError")() {
7
+ constructor(cause) {
8
+ super({
9
+ message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} contains invalid JSON.`,
10
+ cause,
11
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
12
+ });
13
+ }
14
+ };
15
+ var LocalResolutionPinInvalidShapeError = class extends TaggedError("LocalResolutionPinInvalidShapeError")() {
16
+ constructor() {
17
+ super({
18
+ message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} must contain workspaceId and projectId string fields only.`,
19
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
20
+ });
21
+ }
22
+ };
23
+ var LocalResolutionPinReadAbortedError = class extends TaggedError("LocalResolutionPinReadAbortedError")() {
24
+ constructor(cause) {
25
+ super({
26
+ message: `Reading ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} was aborted.`,
27
+ cause,
28
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
29
+ });
30
+ }
31
+ };
32
+ var LocalResolutionPinSerializationError = class extends TaggedError("LocalResolutionPinSerializationError")() {
33
+ constructor(cause) {
34
+ super({
35
+ message: `Could not serialize ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
36
+ cause,
37
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
11
38
  });
12
- const parsed = JSON.parse(raw);
13
- if (!isLocalResolutionPin(parsed)) return { kind: "invalid" };
14
- return {
39
+ }
40
+ };
41
+ var LocalResolutionPinWriteAbortedError = class extends TaggedError("LocalResolutionPinWriteAbortedError")() {
42
+ constructor(cause) {
43
+ super({
44
+ message: `Writing ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} was aborted.`,
45
+ cause,
46
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
47
+ });
48
+ }
49
+ };
50
+ var LocalResolutionPinWriteFailedError = class extends TaggedError("LocalResolutionPinWriteFailedError")() {
51
+ constructor(operation, cause) {
52
+ super({
53
+ message: `Could not write ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
54
+ cause,
55
+ operation,
56
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
57
+ });
58
+ }
59
+ };
60
+ var LocalResolutionPinGitignoreUpdateAbortedError = class extends TaggedError("LocalResolutionPinGitignoreUpdateAbortedError")() {
61
+ constructor(cause) {
62
+ super({
63
+ message: "Updating .gitignore for the local Project binding was aborted.",
64
+ cause,
65
+ gitignorePath: ".gitignore"
66
+ });
67
+ }
68
+ };
69
+ var LocalResolutionPinGitignoreUpdateFailedError = class extends TaggedError("LocalResolutionPinGitignoreUpdateFailedError")() {
70
+ constructor(operation, cause) {
71
+ super({
72
+ message: "Could not update .gitignore for the local Project binding.",
73
+ cause,
74
+ operation,
75
+ gitignorePath: ".gitignore"
76
+ });
77
+ }
78
+ };
79
+ async function readLocalResolutionPin(cwd, signal) {
80
+ return Result.gen(async function* () {
81
+ yield* ensureLocalResolutionPinReadNotAborted(signal);
82
+ const file = yield* Result.await(readLocalResolutionPinFile(cwd, signal));
83
+ if (file.kind === "missing") return Result.ok({ kind: "missing" });
84
+ const parsed = yield* parseLocalResolutionPin(file.raw);
85
+ if (!isLocalResolutionPin(parsed)) return Result.err(new LocalResolutionPinInvalidShapeError());
86
+ return Result.ok({
15
87
  kind: "present",
16
88
  pin: parsed
17
- };
18
- } catch (error) {
19
- if (error.code === "ENOENT") return { kind: "missing" };
20
- if (error instanceof SyntaxError) return { kind: "invalid" };
21
- throw error;
89
+ });
90
+ });
91
+ }
92
+ function ensureLocalResolutionPinReadNotAborted(signal) {
93
+ return Result.try({
94
+ try: () => signal?.throwIfAborted(),
95
+ catch: (cause) => new LocalResolutionPinReadAbortedError(cause)
96
+ });
97
+ }
98
+ async function readLocalResolutionPinFile(cwd, signal) {
99
+ const readResult = await Result.tryPromise({
100
+ try: () => readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), {
101
+ encoding: "utf8",
102
+ signal
103
+ }),
104
+ catch: (cause) => signal?.aborted ? new LocalResolutionPinReadAbortedError(cause) : new UnhandledException({ cause })
105
+ });
106
+ if (readResult.isErr()) {
107
+ if (readResult.error instanceof UnhandledException && readResult.error.cause.code === "ENOENT") return Result.ok({ kind: "missing" });
108
+ return Result.err(readResult.error);
22
109
  }
110
+ return Result.ok({
111
+ kind: "present",
112
+ raw: readResult.value
113
+ });
114
+ }
115
+ function parseLocalResolutionPin(raw) {
116
+ return Result.try({
117
+ try: () => JSON.parse(raw),
118
+ catch: (cause) => cause instanceof SyntaxError ? new LocalResolutionPinInvalidJsonError(cause) : new UnhandledException({ cause })
119
+ });
23
120
  }
24
121
  async function writeLocalResolutionPin(cwd, pin, signal) {
25
- const prismaDir = path.join(cwd, ".prisma");
26
- signal?.throwIfAborted();
27
- await mkdir(prismaDir, { recursive: true });
28
- const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
29
- const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
30
- await writeFile(tmpPath, `${JSON.stringify(pin, null, 2)}\n`, {
31
- encoding: "utf8",
32
- signal
122
+ return Result.gen(async function* () {
123
+ const prismaDir = path.join(cwd, ".prisma");
124
+ yield* ensureLocalResolutionPinWriteNotAborted(signal);
125
+ yield* Result.await(writeLocalResolutionPinBoundary(() => mkdir(prismaDir, { recursive: true }), "create-directory", signal));
126
+ const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
127
+ const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
128
+ const serialized = yield* serializeLocalResolutionPin(pin);
129
+ yield* Result.await(writeLocalResolutionPinBoundary(() => writeFile(tmpPath, serialized, {
130
+ encoding: "utf8",
131
+ signal
132
+ }), "write-temp-file", signal));
133
+ yield* ensureLocalResolutionPinWriteNotAborted(signal);
134
+ yield* Result.await(writeLocalResolutionPinBoundary(() => rename(tmpPath, pinPath), "rename-temp-file", signal));
135
+ return Result.ok(void 0);
33
136
  });
34
- signal?.throwIfAborted();
35
- await rename(tmpPath, pinPath);
36
137
  }
37
138
  async function ensureLocalResolutionPinGitignore(cwd, signal) {
38
139
  const gitignorePath = path.join(cwd, ".gitignore");
39
140
  let existing = null;
40
- signal?.throwIfAborted();
41
- try {
42
- existing = await readFile(gitignorePath, {
141
+ const notAborted = ensureLocalResolutionPinGitignoreUpdateNotAborted(signal);
142
+ if (notAborted.isErr()) return Result.err(notAborted.error);
143
+ const existingResult = await Result.tryPromise({
144
+ try: () => readFile(gitignorePath, {
43
145
  encoding: "utf8",
44
146
  signal
45
- });
46
- } catch (error) {
47
- if (error.code !== "ENOENT") throw error;
48
- }
49
- if (existing === null) {
50
- await writeFile(gitignorePath, ".prisma/\n", {
147
+ }),
148
+ catch: (cause) => signal?.aborted ? new LocalResolutionPinGitignoreUpdateAbortedError(cause) : new LocalResolutionPinGitignoreUpdateFailedError("read", cause)
149
+ });
150
+ if (existingResult.isErr()) if (existingResult.error instanceof LocalResolutionPinGitignoreUpdateFailedError && existingResult.error.cause.code === "ENOENT") existing = null;
151
+ else return Result.err(existingResult.error);
152
+ else existing = existingResult.value;
153
+ if (existing === null) return writeLocalResolutionPinGitignore(gitignorePath, ".prisma/\n", signal);
154
+ if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return Result.ok(void 0);
155
+ return writeLocalResolutionPinGitignore(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, signal);
156
+ }
157
+ function ensureLocalResolutionPinWriteNotAborted(signal) {
158
+ return Result.try({
159
+ try: () => signal?.throwIfAborted(),
160
+ catch: (cause) => new LocalResolutionPinWriteAbortedError(cause)
161
+ });
162
+ }
163
+ function serializeLocalResolutionPin(pin) {
164
+ return Result.try({
165
+ try: () => `${JSON.stringify(pin, null, 2)}\n`,
166
+ catch: (cause) => new LocalResolutionPinSerializationError(cause)
167
+ });
168
+ }
169
+ function writeLocalResolutionPinBoundary(run, operation, signal) {
170
+ return Result.tryPromise({
171
+ try: async () => {
172
+ await run();
173
+ },
174
+ catch: (cause) => signal?.aborted ? new LocalResolutionPinWriteAbortedError(cause) : new LocalResolutionPinWriteFailedError(operation, cause)
175
+ });
176
+ }
177
+ function ensureLocalResolutionPinGitignoreUpdateNotAborted(signal) {
178
+ return Result.try({
179
+ try: () => signal?.throwIfAborted(),
180
+ catch: (cause) => new LocalResolutionPinGitignoreUpdateAbortedError(cause)
181
+ });
182
+ }
183
+ function writeLocalResolutionPinGitignore(gitignorePath, contents, signal) {
184
+ return Result.tryPromise({
185
+ try: () => writeFile(gitignorePath, contents, {
51
186
  encoding: "utf8",
52
187
  signal
53
- });
54
- return;
55
- }
56
- if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return;
57
- await writeFile(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, {
58
- encoding: "utf8",
59
- signal
188
+ }),
189
+ catch: (cause) => signal?.aborted ? new LocalResolutionPinGitignoreUpdateAbortedError(cause) : new LocalResolutionPinGitignoreUpdateFailedError("write", cause)
60
190
  });
61
191
  }
62
192
  function isLocalResolutionPin(value) {