@@ -66,6 +66,99 @@ export type LocalResolutionPinReadError =
6666 | LocalResolutionPinReadAbortedError
6767 | UnhandledException ;
6868
69+ export class LocalResolutionPinSerializationError extends TaggedError (
70+ "LocalResolutionPinSerializationError" ,
71+ ) < {
72+ message : string ;
73+ cause : unknown ;
74+ pinPath : string ;
75+ } > ( ) {
76+ constructor ( cause : unknown ) {
77+ super ( {
78+ message : `Could not serialize ${ LOCAL_RESOLUTION_PIN_RELATIVE_PATH } .` ,
79+ cause,
80+ pinPath : LOCAL_RESOLUTION_PIN_RELATIVE_PATH ,
81+ } ) ;
82+ }
83+ }
84+
85+ export class LocalResolutionPinWriteAbortedError extends TaggedError (
86+ "LocalResolutionPinWriteAbortedError" ,
87+ ) < {
88+ message : string ;
89+ cause : unknown ;
90+ pinPath : string ;
91+ } > ( ) {
92+ constructor ( cause : unknown ) {
93+ super ( {
94+ message : `Writing ${ LOCAL_RESOLUTION_PIN_RELATIVE_PATH } was aborted.` ,
95+ cause,
96+ pinPath : LOCAL_RESOLUTION_PIN_RELATIVE_PATH ,
97+ } ) ;
98+ }
99+ }
100+
101+ export class LocalResolutionPinWriteFailedError extends TaggedError (
102+ "LocalResolutionPinWriteFailedError" ,
103+ ) < {
104+ message : string ;
105+ cause : unknown ;
106+ operation : "create-directory" | "write-temp-file" | "rename-temp-file" ;
107+ pinPath : string ;
108+ } > ( ) {
109+ constructor ( operation : "create-directory" | "write-temp-file" | "rename-temp-file" , cause : unknown ) {
110+ super ( {
111+ message : `Could not write ${ LOCAL_RESOLUTION_PIN_RELATIVE_PATH } .` ,
112+ cause,
113+ operation,
114+ pinPath : LOCAL_RESOLUTION_PIN_RELATIVE_PATH ,
115+ } ) ;
116+ }
117+ }
118+
119+ export type LocalResolutionPinWriteError =
120+ | LocalResolutionPinSerializationError
121+ | LocalResolutionPinWriteAbortedError
122+ | LocalResolutionPinWriteFailedError ;
123+
124+ export class LocalResolutionPinGitignoreUpdateAbortedError extends TaggedError (
125+ "LocalResolutionPinGitignoreUpdateAbortedError" ,
126+ ) < {
127+ message : string ;
128+ cause : unknown ;
129+ gitignorePath : string ;
130+ } > ( ) {
131+ constructor ( cause : unknown ) {
132+ super ( {
133+ message : "Updating .gitignore for the local Project binding was aborted." ,
134+ cause,
135+ gitignorePath : ".gitignore" ,
136+ } ) ;
137+ }
138+ }
139+
140+ export class LocalResolutionPinGitignoreUpdateFailedError extends TaggedError (
141+ "LocalResolutionPinGitignoreUpdateFailedError" ,
142+ ) < {
143+ message : string ;
144+ cause : unknown ;
145+ operation : "read" | "write" ;
146+ gitignorePath : string ;
147+ } > ( ) {
148+ constructor ( operation : "read" | "write" , cause : unknown ) {
149+ super ( {
150+ message : "Could not update .gitignore for the local Project binding." ,
151+ cause,
152+ operation,
153+ gitignorePath : ".gitignore" ,
154+ } ) ;
155+ }
156+ }
157+
158+ export type LocalResolutionPinGitignoreUpdateError =
159+ | LocalResolutionPinGitignoreUpdateAbortedError
160+ | LocalResolutionPinGitignoreUpdateFailedError ;
161+
69162export async function readLocalResolutionPin (
70163 cwd : string ,
71164 signal ?: AbortSignal ,
@@ -138,58 +231,132 @@ export async function writeLocalResolutionPin(
138231 cwd : string ,
139232 pin : LocalResolutionPin ,
140233 signal ?: AbortSignal ,
141- ) : Promise < void > {
142- const prismaDir = path . join ( cwd , ".prisma" ) ;
143- signal ?. throwIfAborted ( ) ;
144- // mkdir does not accept AbortSignal; check before the filesystem boundary.
145- await mkdir ( prismaDir , { recursive : true } ) ;
146- const pinPath = path . join ( cwd , LOCAL_RESOLUTION_PIN_RELATIVE_PATH ) ;
147- const tmpPath = path . join (
148- prismaDir ,
149- `local.${ process . pid } .${ Date . now ( ) } .tmp` ,
150- ) ;
151- await writeFile ( tmpPath , `${ JSON . stringify ( pin , null , 2 ) } \n` , {
152- encoding : "utf8" ,
153- signal,
234+ ) : Promise < Result < void , LocalResolutionPinWriteError > > {
235+ return Result . gen ( async function * ( ) {
236+ const prismaDir = path . join ( cwd , ".prisma" ) ;
237+ yield * ensureLocalResolutionPinWriteNotAborted ( signal ) ;
238+ // mkdir does not accept AbortSignal; check before the filesystem boundary.
239+ yield * Result . await ( writeLocalResolutionPinBoundary (
240+ ( ) => mkdir ( prismaDir , { recursive : true } ) ,
241+ "create-directory" ,
242+ signal ,
243+ ) ) ;
244+ const pinPath = path . join ( cwd , LOCAL_RESOLUTION_PIN_RELATIVE_PATH ) ;
245+ const tmpPath = path . join (
246+ prismaDir ,
247+ `local.${ process . pid } .${ Date . now ( ) } .tmp` ,
248+ ) ;
249+ const serialized = yield * serializeLocalResolutionPin ( pin ) ;
250+ yield * Result . await ( writeLocalResolutionPinBoundary (
251+ ( ) => writeFile ( tmpPath , serialized , { encoding : "utf8" , signal } ) ,
252+ "write-temp-file" ,
253+ signal ,
254+ ) ) ;
255+ yield * ensureLocalResolutionPinWriteNotAborted ( signal ) ;
256+ // rename does not accept AbortSignal; check before the filesystem boundary.
257+ yield * Result . await ( writeLocalResolutionPinBoundary (
258+ ( ) => rename ( tmpPath , pinPath ) ,
259+ "rename-temp-file" ,
260+ signal ,
261+ ) ) ;
262+
263+ return Result . ok ( undefined ) ;
154264 } ) ;
155- signal ?. throwIfAborted ( ) ;
156- // rename does not accept AbortSignal; check before the filesystem boundary.
157- await rename ( tmpPath , pinPath ) ;
158265}
159266
160267export async function ensureLocalResolutionPinGitignore (
161268 cwd : string ,
162269 signal ?: AbortSignal ,
163- ) : Promise < void > {
270+ ) : Promise < Result < void , LocalResolutionPinGitignoreUpdateError > > {
164271 const gitignorePath = path . join ( cwd , ".gitignore" ) ;
165272 let existing : string | null = null ;
166273
167- signal ?. throwIfAborted ( ) ;
168- try {
169- existing = await readFile ( gitignorePath , { encoding : "utf8" , signal } ) ;
170- } catch ( error ) {
171- if ( ( error as NodeJS . ErrnoException ) . code !== "ENOENT" ) {
172- throw error ;
274+ const notAborted = ensureLocalResolutionPinGitignoreUpdateNotAborted ( signal ) ;
275+ if ( notAborted . isErr ( ) ) {
276+ return Result . err ( notAborted . error ) ;
277+ }
278+
279+ const existingResult = await Result . tryPromise ( {
280+ try : ( ) => readFile ( gitignorePath , { encoding : "utf8" , signal } ) ,
281+ catch : ( cause ) => signal ?. aborted
282+ ? new LocalResolutionPinGitignoreUpdateAbortedError ( cause )
283+ : new LocalResolutionPinGitignoreUpdateFailedError ( "read" , cause ) ,
284+ } ) ;
285+ if ( existingResult . isErr ( ) ) {
286+ if ( existingResult . error instanceof LocalResolutionPinGitignoreUpdateFailedError && ( existingResult . error . cause as NodeJS . ErrnoException ) . code === "ENOENT" ) {
287+ existing = null ;
288+ } else {
289+ return Result . err ( existingResult . error ) ;
173290 }
291+ } else {
292+ existing = existingResult . value ;
174293 }
175294
176295 if ( existing === null ) {
177- await writeFile ( gitignorePath , ".prisma/\n" , { encoding : "utf8" , signal } ) ;
178- return ;
296+ return writeLocalResolutionPinGitignore ( gitignorePath , ".prisma/\n" , signal ) ;
179297 }
180298
181299 const hasPrismaIgnore = existing
182300 . split ( / \r ? \n / )
183301 . map ( ( line ) => line . trim ( ) )
184302 . some ( ( line ) => line === ".prisma/" || line === ".prisma/local.json" ) ;
185303 if ( hasPrismaIgnore ) {
186- return ;
304+ return Result . ok ( undefined ) ;
187305 }
188306
189307 const next = existing . endsWith ( "\n" )
190308 ? `${ existing } .prisma/\n`
191309 : `${ existing } \n.prisma/\n` ;
192- await writeFile ( gitignorePath , next , { encoding : "utf8" , signal } ) ;
310+ return writeLocalResolutionPinGitignore ( gitignorePath , next , signal ) ;
311+ }
312+
313+ function ensureLocalResolutionPinWriteNotAborted ( signal : AbortSignal | undefined ) : Result < void , LocalResolutionPinWriteAbortedError > {
314+ return Result . try ( {
315+ try : ( ) => signal ?. throwIfAborted ( ) ,
316+ catch : ( cause ) => new LocalResolutionPinWriteAbortedError ( cause ) ,
317+ } ) ;
318+ }
319+
320+ function serializeLocalResolutionPin ( pin : LocalResolutionPin ) : Result < string , LocalResolutionPinSerializationError | LocalResolutionPinWriteAbortedError > {
321+ return Result . try ( {
322+ try : ( ) => `${ JSON . stringify ( pin , null , 2 ) } \n` ,
323+ catch : ( cause ) => new LocalResolutionPinSerializationError ( cause ) ,
324+ } ) ;
325+ }
326+
327+ function writeLocalResolutionPinBoundary (
328+ run : ( ) => Promise < unknown > ,
329+ operation : "create-directory" | "write-temp-file" | "rename-temp-file" ,
330+ signal : AbortSignal | undefined ,
331+ ) : Promise < Result < void , LocalResolutionPinWriteAbortedError | LocalResolutionPinWriteFailedError > > {
332+ return Result . tryPromise ( {
333+ try : async ( ) => {
334+ await run ( ) ;
335+ } ,
336+ catch : ( cause ) => signal ?. aborted
337+ ? new LocalResolutionPinWriteAbortedError ( cause )
338+ : new LocalResolutionPinWriteFailedError ( operation , cause ) ,
339+ } ) ;
340+ }
341+
342+ function ensureLocalResolutionPinGitignoreUpdateNotAborted ( signal : AbortSignal | undefined ) : Result < void , LocalResolutionPinGitignoreUpdateAbortedError > {
343+ return Result . try ( {
344+ try : ( ) => signal ?. throwIfAborted ( ) ,
345+ catch : ( cause ) => new LocalResolutionPinGitignoreUpdateAbortedError ( cause ) ,
346+ } ) ;
347+ }
348+
349+ function writeLocalResolutionPinGitignore (
350+ gitignorePath : string ,
351+ contents : string ,
352+ signal : AbortSignal | undefined ,
353+ ) : Promise < Result < void , LocalResolutionPinGitignoreUpdateAbortedError | LocalResolutionPinGitignoreUpdateFailedError > > {
354+ return Result . tryPromise ( {
355+ try : ( ) => writeFile ( gitignorePath , contents , { encoding : "utf8" , signal } ) ,
356+ catch : ( cause ) => signal ?. aborted
357+ ? new LocalResolutionPinGitignoreUpdateAbortedError ( cause )
358+ : new LocalResolutionPinGitignoreUpdateFailedError ( "write" , cause ) ,
359+ } ) ;
193360}
194361
195362function isLocalResolutionPin ( value : unknown ) : value is LocalResolutionPin {
0 commit comments