@@ -270,27 +270,24 @@ export async function runUpdate(
270270 throw localValidationError ( '--description must be at most 2000 characters' ) ;
271271 }
272272
273- // Resolve password
274- let password = opts . password ;
275- if ( password === undefined && opts . passwordFile !== undefined ) {
276- password = readFileSync ( opts . passwordFile , 'utf8' ) . trim ( ) ;
277- }
278-
279273 // P2-7: guard --url against localhost/RFC1918/non-http(s).
280274 if ( opts . targetUrl !== undefined ) {
281275 assertNotLocal ( opts . targetUrl ) ;
282276 }
283277
284- const mutableFields : Record < string , string | undefined > = {
285- name : opts . name ,
286- targetUrl : opts . targetUrl ,
287- username : opts . username ,
288- password,
289- description : opts . description ,
290- instruction : opts . instruction ,
278+ const passwordSupplied = opts . password !== undefined || opts . passwordFile !== undefined ;
279+ const mutableFields : Record < string , boolean > = {
280+ name : opts . name !== undefined ,
281+ targetUrl : opts . targetUrl !== undefined ,
282+ username : opts . username !== undefined ,
283+ password : passwordSupplied ,
284+ description : opts . description !== undefined ,
285+ instruction : opts . instruction !== undefined ,
291286 } ;
292- const presentFields = Object . entries ( mutableFields ) . filter ( ( [ , v ] ) => v !== undefined ) ;
293- if ( presentFields . length === 0 ) {
287+ const presentFieldNames = Object . entries ( mutableFields )
288+ . filter ( ( [ , present ] ) => present )
289+ . map ( ( [ field ] ) => field ) ;
290+ if ( presentFieldNames . length === 0 ) {
294291 throw localValidationError (
295292 'At least one mutable flag is required: --name, --url, --username, --password/--password-file, --description, or --instruction.' ,
296293 ) ;
@@ -308,19 +305,36 @@ export async function runUpdate(
308305 }
309306 const sample : CliUpdateProjectResponse = {
310307 id : opts . projectId ,
311- updatedFields : presentFields . map ( ( [ k ] ) => k ) ,
308+ updatedFields : presentFieldNames ,
312309 updatedAt : '2026-05-16T00:00:00.000Z' ,
313310 } ;
314311 out . print ( sample , data => renderUpdateText ( data as CliUpdateProjectResponse ) ) ;
315312 return sample ;
316313 }
317314
315+ // Resolve password only on the real path. Dry-run must not touch the
316+ // filesystem, even when --password-file is present.
317+ let password = opts . password ;
318+ if ( password === undefined && opts . passwordFile !== undefined ) {
319+ password = readFileSync ( opts . passwordFile , 'utf8' ) . trim ( ) ;
320+ }
321+
318322 const idempotencyKey = opts . idempotencyKey ?? `cli-proj-update-${ randomUUID ( ) } ` ;
319323 if ( opts . idempotencyKey === undefined && ( opts . output === 'json' || opts . verbose || opts . debug ) ) {
320324 stderr ( `idempotency-key: ${ idempotencyKey } ` ) ;
321325 }
322326
323- const body = Object . fromEntries ( presentFields ) as Record < string , string > ;
327+ const bodyFields : Record < string , string | undefined > = {
328+ name : opts . name ,
329+ targetUrl : opts . targetUrl ,
330+ username : opts . username ,
331+ password,
332+ description : opts . description ,
333+ instruction : opts . instruction ,
334+ } ;
335+ const body = Object . fromEntries (
336+ Object . entries ( bodyFields ) . filter ( ( [ , v ] ) => v !== undefined ) ,
337+ ) as Record < string , string > ;
324338 const client = makeClient ( opts , deps ) ;
325339 const updated = await client . patch < CliUpdateProjectResponse > (
326340 `/projects/${ encodeURIComponent ( opts . projectId ) } ` ,
0 commit comments