@@ -3,25 +3,36 @@ import { hideBin } from "yargs/helpers";
33import { checkDebugLogging , Logger , printBanner , usageText } from "./log.js" ;
44import { joinPaths , normalizePath , resolvePath } from "@typespec/compiler" ;
55import { addSpecFiles , checkoutCommit , cloneRepo , getRepoRoot , sparseCheckout } from "./git.js" ;
6- import { createTempDirectory , getEmitterFromRepoConfig , readTspLocation , removeDirectory } from "./fs.js" ;
6+ import {
7+ createTempDirectory ,
8+ getEmitterFromRepoConfig ,
9+ readTspLocation ,
10+ removeDirectory ,
11+ } from "./fs.js" ;
712import { cp , mkdir , readFile , rename , stat , unlink , writeFile } from "fs/promises" ;
813import { npmCommand , npxCommand } from "./npm.js" ;
914import { compileTsp , discoverMainFile , resolveTspConfigUrl , TspLocation } from "./typespec.js" ;
10- import { formatAdditionalDirectories , getAdditionalDirectoryName , getServiceDir , makeSparseSpecDir } from "./utils.js" ;
15+ import {
16+ formatAdditionalDirectories ,
17+ getAdditionalDirectoryName ,
18+ getServiceDir ,
19+ makeSparseSpecDir ,
20+ } from "./utils.js" ;
1121import { parse as parseYaml } from "yaml" ;
1222import { config as dotenvConfig } from "dotenv" ;
1323import { resolve } from "node:path" ;
1424import { doesFileExist } from "./network.js" ;
25+ import PromptSync from "prompt-sync" ;
1526
1627function commandPreamble ( argv : any ) {
17- checkDebugLogging ( argv ) ;
28+ checkDebugLogging ( argv ) ;
1829 printBanner ( ) ;
1930 yargs ( ) . showVersion ( ) ;
2031}
2132
2233async function initCommand ( argv : any ) {
34+ let outputDir = resolveOutputDir ( argv ) ;
2335 let tspConfig = argv [ "tsp-config" ] ;
24- let outputDir = argv [ "output-dir" ] ?? "." ;
2536 const skipSyncAndGenerate = argv [ "skip-sync-and-generate" ] ;
2637 const localSpecRepo = argv [ "local-spec-repo" ] ;
2738 const saveInputs = argv [ "save-inputs" ] ;
@@ -41,7 +52,18 @@ async function initCommand(argv: any) {
4152 throw new Error ( "Couldn't find emitter-package.json in the repo" ) ;
4253 }
4354
44- if ( isUrl ) {
55+ let isFolder = true ;
56+ if ( await doesFileExist ( tspConfig ) ) {
57+ isFolder = false ;
58+ }
59+ if ( ! isFolder ) {
60+ if ( ! commit || ! repo ) {
61+ Logger . error ( "--commit and --repo are required when --tsp-config is a local directory" ) ;
62+ process . exit ( 1 ) ;
63+ }
64+ }
65+
66+ if ( isFolder ) {
4567 // URL scenario
4668 const resolvedConfigUrl = resolveTspConfigUrl ( tspConfig ) ;
4769 const cloneDir = await makeSparseSpecDir ( repoRoot ) ;
@@ -124,7 +146,7 @@ async function initCommand(argv: any) {
124146 }
125147
126148 Logger . info ( `SDK initialized in ${ outputDir } ` ) ;
127-
149+
128150 if ( ! skipSyncAndGenerate ) {
129151 // FIXME: Call into the syncCommand and the generateCommand
130152 await syncCommand ( outputDir , localSpecRepo ) ;
@@ -137,11 +159,11 @@ async function initCommand(argv: any) {
137159}
138160
139161async function syncCommand ( argv : any ) {
140- let outputDir = argv [ "output-dir" ] ?? "." ;
162+ let outputDir = resolveOutputDir ( argv ) ;
141163 const localSpecRepo = argv [ "local-spec-repo" ] ;
142164
143165 commandPreamble ( argv ) ;
144-
166+
145167 const tempRoot = await createTempDirectory ( outputDir ) ;
146168 const repoRoot = await getRepoRoot ( outputDir ) ;
147169 Logger . debug ( `Repo root is ${ repoRoot } ` ) ;
@@ -227,8 +249,7 @@ async function syncCommand(argv: any) {
227249}
228250
229251async function generateCommand ( argv : any ) {
230-
231- let outputDir = argv [ "output-dir" ] ?? "." ;
252+ let outputDir = resolveOutputDir ( argv ) ;
232253 const emitterOptions = argv [ "emitter-options" ] ;
233254 const saveInputs = argv [ "save-inputs" ] ;
234255
@@ -286,8 +307,7 @@ async function generateCommand(argv: any) {
286307}
287308
288309async function updateCommand ( argv : any ) {
289-
290- const outputDir = argv [ "output-dir" ] ?? "." ;
310+ const outputDir = resolveOutputDir ( argv ) ;
291311 const repo = argv [ "repo" ] ;
292312 const commit = argv [ "commit" ] ;
293313 let tspConfig = argv [ "tsp-config" ] ;
@@ -331,10 +351,9 @@ async function updateCommand(argv: any) {
331351}
332352
333353async function convertCommand ( argv : any ) : Promise < void > {
334-
354+ const outputDir = resolveOutputDir ( argv ) ;
335355 const swaggerReadme = argv [ "swagger-readme" ] ;
336356 const arm = argv [ "arm" ] ;
337- const outputDir = argv [ "output-dir" ] ?? "." ;
338357 let rootUrl = resolvePath ( outputDir ) ;
339358
340359 commandPreamble ( argv ) ;
@@ -394,7 +413,7 @@ async function convertCommand(argv: any): Promise<void> {
394413}
395414
396415async function generateLockFileCommand ( argv : any ) {
397- const outputDir = argv [ "output-dir" ] ?? "." ;
416+ const outputDir = resolveOutputDir ( argv ) ;
398417 let rootUrl = resolvePath ( outputDir ) ;
399418 const repoRoot = await getRepoRoot ( rootUrl ) ;
400419
@@ -419,6 +438,33 @@ async function generateLockFileCommand(argv: any) {
419438 Logger . info ( `Lock file generated in ${ joinPaths ( repoRoot , "eng" , "emitter-package-lock.json" ) } ` ) ;
420439}
421440
441+ /** Ensure the output directory exists and allow interactive users to confirm or override the value. */
442+ function resolveOutputDir ( argv : any ) : string {
443+ let outputDir = resolvePath ( process . cwd ( ) , argv [ "output-dir" ] ) ;
444+ const noPrompt = argv [ "no-prompt" ] ;
445+
446+ let useOutputDir ;
447+ if ( process . stdin . isTTY && ! noPrompt ) {
448+ // Ask user is this is the correct output directory
449+ const prompt = PromptSync ( ) ;
450+ useOutputDir = prompt ( `Use output directory '${ outputDir } '? (y/n)` , "y" ) ;
451+ } else {
452+ // There is no user to ask, so assume yes
453+ useOutputDir = "y" ;
454+ }
455+
456+ if ( useOutputDir . toLowerCase ( ) === "n" ) {
457+ const newOutputDir = prompt ( "Enter output directory: " ) ;
458+ if ( ! newOutputDir ) {
459+ Logger . error ( "Output directory is required" ) ;
460+ process . exit ( 1 ) ;
461+ }
462+ outputDir = resolvePath ( normalizePath ( newOutputDir ) ) ;
463+ }
464+ Logger . info ( `Using output directory '${ outputDir } '` ) ;
465+ return outputDir ;
466+ }
467+
422468const parser = yargs ( hideBin ( process . argv ) )
423469 . scriptName ( "" )
424470 . usage ( usageText )
@@ -430,7 +476,14 @@ const parser = yargs(hideBin(process.argv))
430476 . option ( "output-dir" , {
431477 alias : "o" ,
432478 type : "string" ,
433- description : "Specify an alternate output directory for the generated files. Default is your current directory" ,
479+ description :
480+ "Specify an alternate output directory for the generated files. Default is your current directory." ,
481+ default : "." ,
482+ } )
483+ . option ( "no-prompt" , {
484+ alias : "y" ,
485+ type : "boolean" ,
486+ description : "Skip any interactive prompts." ,
434487 } )
435488 . command (
436489 "init" ,
@@ -466,11 +519,11 @@ const parser = yargs(hideBin(process.argv))
466519 . option ( "repo" , {
467520 type : "string" ,
468521 description : "Repository where the project is defined" ,
469- } )
522+ } ) ;
470523 } ,
471524 async ( argv ) => {
472525 await initCommand ( argv ) ;
473- }
526+ } ,
474527 )
475528 . command (
476529 "sync" ,
@@ -480,90 +533,97 @@ const parser = yargs(hideBin(process.argv))
480533 type : "string" ,
481534 description : "Path to local spec repo" ,
482535 } ) ;
483- } , async ( argv ) => {
484- await syncCommand ( argv ) ;
485- } )
486- . command ( "generate" , "Generate from a TypeSpec project" , ( yargs ) => {
487- return yargs
488- . options ( "emitter-options" , {
489- type : "string" ,
490- description : "The options to pass to the emitter" ,
491- } )
492- . options ( "save-inputs" , {
493- type : "boolean" ,
494- description : "Don't clean up the temp directory after generation" ,
495- } ) ;
496- } , async ( argv ) => {
497- await generateCommand ( argv ) ;
498- } )
499- . command ( "update" , "Sync and generate from a TypeSpec project" , ( yargs ) => {
500- return yargs
501- . option ( "repo" , {
502- type : "string" ,
503- description : "Repository where the project is defined" ,
504- } )
505- . option ( "commit" , {
506- type : "string" ,
507- description : "Commit hash to be used" ,
508- } )
509- . option ( "tsp-config" , {
510- type : "string" ,
511- description : "Path to tspconfig.yaml" ,
512- } )
513- . option ( "local-spec-repo" , {
514- type : "string" ,
515- description : "Path to local spec repo" ,
516- } )
517- . option ( "emitter-options" , {
518- type : "string" ,
519- description : "The options to pass to the emitter" ,
520- } )
521- . option ( "save-inputs" , {
522- type : "boolean" ,
523- description : "Don't clean up the temp directory after generation" ,
524- } )
525- } , async ( argv ) => {
526- await updateCommand ( argv ) ;
527- } )
528- . command ( "convert" , "Convert a swagger specification to TypeSpec" , ( yargs ) => {
529- return yargs . option ( "swagger-readme" , {
530- type : "string" ,
531- description : "Path to the swagger readme file" ,
532- demandOption : true ,
533- } )
534- . option ( "arm" , {
535- type : "boolean" ,
536- description : "Convert swagger to ARM TypeSpec" ,
537- } ) ;
538- } , async ( argv ) => {
539- await convertCommand ( argv ) ;
540- } )
541- . command ( "generate-lock-file" , "Generate a lock file under the eng/ directory from an existing emitter-package.json" , { } , async ( argv ) => {
542- await generateLockFileCommand ( argv ) ;
543- } )
536+ } ,
537+ async ( argv ) => {
538+ await syncCommand ( argv ) ;
539+ } ,
540+ )
541+ . command (
542+ "generate" ,
543+ "Generate from a TypeSpec project" ,
544+ ( yargs ) => {
545+ return yargs
546+ . options ( "emitter-options" , {
547+ type : "string" ,
548+ description : "The options to pass to the emitter" ,
549+ } )
550+ . options ( "save-inputs" , {
551+ type : "boolean" ,
552+ description : "Don't clean up the temp directory after generation" ,
553+ } ) ;
554+ } ,
555+ async ( argv ) => {
556+ await generateCommand ( argv ) ;
557+ } ,
558+ )
559+ . command (
560+ "update" ,
561+ "Sync and generate from a TypeSpec project" ,
562+ ( yargs ) => {
563+ return yargs
564+ . option ( "repo" , {
565+ type : "string" ,
566+ description : "Repository where the project is defined" ,
567+ } )
568+ . option ( "commit" , {
569+ type : "string" ,
570+ description : "Commit hash to be used" ,
571+ } )
572+ . option ( "tsp-config" , {
573+ type : "string" ,
574+ description : "Path to tspconfig.yaml" ,
575+ } )
576+ . option ( "local-spec-repo" , {
577+ type : "string" ,
578+ description : "Path to local spec repo" ,
579+ } )
580+ . option ( "emitter-options" , {
581+ type : "string" ,
582+ description : "The options to pass to the emitter" ,
583+ } )
584+ . option ( "save-inputs" , {
585+ type : "boolean" ,
586+ description : "Don't clean up the temp directory after generation" ,
587+ } ) ;
588+ } ,
589+ async ( argv ) => {
590+ await updateCommand ( argv ) ;
591+ } ,
592+ )
593+ . command (
594+ "convert" ,
595+ "Convert a swagger specification to TypeSpec" ,
596+ ( yargs ) => {
597+ return yargs
598+ . option ( "swagger-readme" , {
599+ type : "string" ,
600+ description : "Path to the swagger readme file" ,
601+ demandOption : true ,
602+ } )
603+ . option ( "arm" , {
604+ type : "boolean" ,
605+ description : "Convert swagger to ARM TypeSpec" ,
606+ } ) ;
607+ } ,
608+ async ( argv ) => {
609+ await convertCommand ( argv ) ;
610+ } ,
611+ )
612+ . command (
613+ "generate-lock-file" ,
614+ "Generate a lock file under the eng/ directory from an existing emitter-package.json" ,
615+ { } ,
616+ async ( argv ) => {
617+ await generateLockFileCommand ( argv ) ;
618+ } ,
619+ )
544620 . demandCommand ( 1 , "Please provide a command." )
545621 . help ( )
622+ . showHelpOnFail ( true ) ;
546623
547624try {
548625 await parser . parse ( ) ;
549- } catch ( err : any ) {
626+ } catch ( err : any ) {
550627 Logger . error ( err ) ;
551628 process . exit ( 1 ) ;
552629}
553-
554-
555- // Options:
556- // --arm Convert ARM swagger specification to TypeSpec [boolean]
557- // -c, --tsp-config The tspconfig.yaml file to use [string]
558- // --commit Commit to be used for project init or update [string]
559- // --emitter-options The options to pass to the emitter [string]
560- // --generate-lock-file Generate a lock file under the eng/ directory from
561- // an existing emitter-package.json [boolean]
562- // --no-prompt Skip prompting for output directory confirmation [boolean]
563- // --save-inputs Don't clean up the temp directory after generation [boolean]
564- // --skip-sync-and-generate Skip sync and generate during project init [boolean]
565- // --swagger-readme Path or url to swagger readme file [string]
566- // -o, --output-dir Specify an alternate output directory for the
567- // generated files. Default is your current directory [string]
568- // --repo Repository where the project is defined for init
569- // or update [string]
0 commit comments