@@ -254,27 +254,24 @@ export async function runUpdate(
254254 throw localValidationError ( '--description must be at most 2000 characters' ) ;
255255 }
256256
257- // Resolve password
258- let password = opts . password ;
259- if ( password === undefined && opts . passwordFile !== undefined ) {
260- password = readFileSync ( opts . passwordFile , 'utf8' ) . trim ( ) ;
261- }
262-
263257 // P2-7: guard --url against localhost/RFC1918/non-http(s).
264258 if ( opts . targetUrl !== undefined ) {
265259 assertNotLocal ( opts . targetUrl ) ;
266260 }
267261
268- const mutableFields : Record < string , string | undefined > = {
269- name : opts . name ,
270- targetUrl : opts . targetUrl ,
271- username : opts . username ,
272- password,
273- description : opts . description ,
274- instruction : opts . instruction ,
262+ const passwordSupplied = opts . password !== undefined || opts . passwordFile !== undefined ;
263+ const mutableFields : Record < string , boolean > = {
264+ name : opts . name !== undefined ,
265+ targetUrl : opts . targetUrl !== undefined ,
266+ username : opts . username !== undefined ,
267+ password : passwordSupplied ,
268+ description : opts . description !== undefined ,
269+ instruction : opts . instruction !== undefined ,
275270 } ;
276- const presentFields = Object . entries ( mutableFields ) . filter ( ( [ , v ] ) => v !== undefined ) ;
277- if ( presentFields . length === 0 ) {
271+ const presentFieldNames = Object . entries ( mutableFields )
272+ . filter ( ( [ , present ] ) => present )
273+ . map ( ( [ field ] ) => field ) ;
274+ if ( presentFieldNames . length === 0 ) {
278275 throw localValidationError (
279276 'At least one mutable flag is required: --name, --url, --username, --password/--password-file, --description, or --instruction.' ,
280277 ) ;
@@ -290,19 +287,36 @@ export async function runUpdate(
290287 }
291288 const sample : CliUpdateProjectResponse = {
292289 id : opts . projectId ,
293- updatedFields : presentFields . map ( ( [ k ] ) => k ) ,
290+ updatedFields : presentFieldNames ,
294291 updatedAt : '2026-05-16T00:00:00.000Z' ,
295292 } ;
296293 out . print ( sample , data => renderUpdateText ( data as CliUpdateProjectResponse ) ) ;
297294 return sample ;
298295 }
299296
297+ // Resolve password only on the real path. Dry-run must not touch the
298+ // filesystem, even when --password-file is present.
299+ let password = opts . password ;
300+ if ( password === undefined && opts . passwordFile !== undefined ) {
301+ password = readFileSync ( opts . passwordFile , 'utf8' ) . trim ( ) ;
302+ }
303+
300304 const idempotencyKey = opts . idempotencyKey ?? `cli-proj-update-${ randomUUID ( ) } ` ;
301305 if ( opts . idempotencyKey === undefined && ( opts . output === 'json' || opts . verbose || opts . debug ) ) {
302306 stderr ( `idempotency-key: ${ idempotencyKey } ` ) ;
303307 }
304308
305- const body = Object . fromEntries ( presentFields ) as Record < string , string > ;
309+ const bodyFields : Record < string , string | undefined > = {
310+ name : opts . name ,
311+ targetUrl : opts . targetUrl ,
312+ username : opts . username ,
313+ password,
314+ description : opts . description ,
315+ instruction : opts . instruction ,
316+ } ;
317+ const body = Object . fromEntries (
318+ Object . entries ( bodyFields ) . filter ( ( [ , v ] ) => v !== undefined ) ,
319+ ) as Record < string , string > ;
306320 const client = makeClient ( opts , deps ) ;
307321 const updated = await client . patch < CliUpdateProjectResponse > (
308322 `/projects/${ encodeURIComponent ( opts . projectId ) } ` ,
0 commit comments