2121 * UTF-8 is the default; binary files pass `"encoding": "base64"`.
2222 */
2323
24- import { readFileSync } from "node:fs" ;
24+ import { fstatSync , readFileSync } from "node:fs" ;
2525import { resolve , dirname , isAbsolute } from "node:path" ;
2626import {
2727 buildDeployResolveSummary ,
@@ -266,29 +266,113 @@ async function readStdin() {
266266 return Buffer . concat ( chunks ) . toString ( "utf-8" ) ;
267267}
268268
269+ function hasStdinSource ( ) {
270+ try {
271+ const stats = fstatSync ( 0 ) ;
272+ return stats . isFIFO ( ) || stats . isFile ( ) ;
273+ } catch {
274+ return false ;
275+ }
276+ }
277+
269278function makeStderrEventWriter ( quiet ) {
270279 if ( quiet ) return undefined ;
271280 return ( event ) => {
272281 console . error ( JSON . stringify ( event ) ) ;
273282 } ;
274283}
275284
276- async function applyCmd ( args ) {
285+ function parseApplyArgs ( args ) {
277286 const opts = { manifest : null , spec : null , project : null , quiet : false , allowWarnings : false } ;
287+ const allowedFlags = [ "--manifest" , "--spec" , "--project" , "--quiet" , "--allow-warnings" , "--help" , "-h" ] ;
288+
278289 for ( let i = 0 ; i < args . length ; i ++ ) {
279- if ( args [ i ] === "--help" || args [ i ] === "-h" ) { console . log ( APPLY_HELP ) ; process . exit ( 0 ) ; }
280- if ( args [ i ] === "--manifest" && args [ i + 1 ] ) { opts . manifest = args [ ++ i ] ; continue ; }
281- if ( args [ i ] === "--spec" && args [ i + 1 ] ) { opts . spec = args [ ++ i ] ; continue ; }
282- if ( args [ i ] === "--project" && args [ i + 1 ] ) { opts . project = args [ ++ i ] ; continue ; }
283- if ( args [ i ] === "--quiet" ) { opts . quiet = true ; continue ; }
284- if ( args [ i ] === "--allow-warnings" ) { opts . allowWarnings = true ; continue ; }
290+ const arg = args [ i ] ;
291+ if ( arg === "--help" || arg === "-h" ) {
292+ console . log ( APPLY_HELP ) ;
293+ process . exit ( 0 ) ;
294+ }
295+ if ( arg === "--manifest" || arg === "--spec" || arg === "--project" ) {
296+ const value = args [ i + 1 ] ;
297+ if ( value === undefined || ( typeof value === "string" && value . startsWith ( "--" ) ) ) {
298+ fail ( {
299+ code : "BAD_USAGE" ,
300+ message : `${ arg } requires a value` ,
301+ details : { flag : arg } ,
302+ } ) ;
303+ }
304+ if ( arg === "--manifest" ) {
305+ if ( opts . manifest !== null ) {
306+ fail ( {
307+ code : "BAD_USAGE" ,
308+ message : "--manifest may only be provided once" ,
309+ details : { flag : "--manifest" } ,
310+ } ) ;
311+ }
312+ opts . manifest = value ;
313+ } else if ( arg === "--spec" ) {
314+ if ( opts . spec !== null ) {
315+ fail ( {
316+ code : "BAD_USAGE" ,
317+ message : "--spec may only be provided once" ,
318+ details : { flag : "--spec" } ,
319+ } ) ;
320+ }
321+ opts . spec = value ;
322+ } else {
323+ opts . project = value ;
324+ }
325+ i += 1 ;
326+ continue ;
327+ }
328+ if ( arg === "--quiet" ) { opts . quiet = true ; continue ; }
329+ if ( arg === "--allow-warnings" ) { opts . allowWarnings = true ; continue ; }
330+ if ( typeof arg === "string" && arg . startsWith ( "-" ) ) {
331+ fail ( {
332+ code : "BAD_USAGE" ,
333+ message : `Unknown flag for deploy apply: ${ arg } ` ,
334+ details : { flag : arg , allowed_flags : allowedFlags } ,
335+ } ) ;
336+ }
337+ fail ( {
338+ code : "BAD_USAGE" ,
339+ message : `Unexpected argument for deploy apply: ${ arg } ` ,
340+ details : { argument : arg } ,
341+ } ) ;
285342 }
286343
344+ return opts ;
345+ }
346+
347+ function applySourceField ( opts ) {
348+ if ( opts . manifest !== null ) return "manifest" ;
349+ if ( opts . spec !== null ) return "spec" ;
350+ return "stdin" ;
351+ }
352+
353+ function validateApplySources ( opts ) {
354+ const sources = [ ] ;
355+ if ( opts . manifest !== null ) sources . push ( "--manifest" ) ;
356+ if ( opts . spec !== null ) sources . push ( "--spec" ) ;
357+ if ( hasStdinSource ( ) ) sources . push ( "stdin" ) ;
358+ if ( sources . length > 1 ) {
359+ fail ( {
360+ code : "BAD_USAGE" ,
361+ message : "Only one deploy manifest source may be provided: --spec, --manifest, or stdin." ,
362+ details : { sources } ,
363+ } ) ;
364+ }
365+ }
366+
367+ async function applyCmd ( args ) {
368+ const opts = parseApplyArgs ( args ) ;
369+ validateApplySources ( opts ) ;
370+
287371 let raw ;
288372 let manifestPath = null ;
289- if ( opts . spec ) {
373+ if ( opts . spec !== null ) {
290374 raw = opts . spec ;
291- } else if ( opts . manifest ) {
375+ } else if ( opts . manifest !== null ) {
292376 try {
293377 manifestPath = isAbsolute ( opts . manifest ) ? opts . manifest : resolve ( process . cwd ( ) , opts . manifest ) ;
294378 raw = readFileSync ( manifestPath , "utf-8" ) ;
@@ -310,11 +394,11 @@ async function applyCmd(args) {
310394 fail ( {
311395 code : "BAD_USAGE" ,
312396 message : `Manifest is not valid JSON: ${ err . message } ` ,
313- details : { source : opts . manifest ? "manifest" : opts . spec ? "spec" : "stdin" , parse_error : err . message } ,
397+ details : { source : applySourceField ( opts ) , parse_error : err . message } ,
314398 } ) ;
315399 }
316400 rejectLegacySecretManifest ( spec , {
317- source : opts . manifest ? "manifest" : opts . spec ? "spec" : "stdin" ,
401+ source : applySourceField ( opts ) ,
318402 ...( manifestPath ? { path : manifestPath } : { } ) ,
319403 } ) ;
320404
@@ -355,7 +439,7 @@ async function applyCmd(args) {
355439 message : `Manifest contains no deployable sections. Expected at least one of: ${ meaningful . join ( ", " ) } ` ,
356440 hint : "Did you mean to write a 'site.replace' or 'database.migrations' block? See https://run402.com/schemas/manifest.v1.json" ,
357441 details : {
358- field : opts . manifest ? "manifest" : opts . spec ? "spec" : "stdin" ,
442+ field : applySourceField ( opts ) ,
359443 ...( manifestPath ? { path : manifestPath } : { } ) ,
360444 meaningful_keys : meaningful ,
361445 } ,
0 commit comments