@@ -6,9 +6,9 @@ import chalk from 'chalk';
66import { normalizeOptions } from '../config/options.js' ;
77import { discoverEnvFiles } from '../services/envDiscovery.js' ;
88import { pairWithExample } from '../services/envPairing.js' ;
9- import { ensureFilesOrPrompt } from '../commands/init .js' ;
9+ import { ensureFilesOrPrompt } from '../services/ensureFilesOrPrompt .js' ;
1010import { compareMany } from '../commands/compare.js' ;
11- import type { CompareJsonEntry } from '../config/types.js' ;
11+ import { type CompareJsonEntry , type Options } from '../config/types.js' ;
1212import { scanUsage } from '../commands/scanUsage.js' ;
1313
1414/**
@@ -17,134 +17,189 @@ import { scanUsage } from '../commands/scanUsage.js';
1717 */
1818export async function run ( program : Command ) {
1919 program . parse ( process . argv ) ;
20- const raw = program . opts ( ) ;
21- const opts = normalizeOptions ( raw ) ;
20+ const opts = normalizeOptions ( program . opts ( ) ) ;
2221
22+ setupGlobalConfig ( opts ) ;
23+
24+ // Route to appropriate command
25+ if ( opts . compare ) {
26+ await runCompareMode ( opts ) ;
27+ } else {
28+ await runScanMode ( opts ) ;
29+ }
30+ }
31+
32+ /**
33+ * Setup global configuration
34+ */
35+ function setupGlobalConfig ( opts : Options ) {
2336 if ( opts . noColor ) {
2437 chalk . level = 0 ; // disable colors globally
2538 }
39+ }
2640
27- // DEFAULT: scan-usage unless --compare is set
28- if ( ! opts . compare ) {
29- const envPath =
30- opts . envFlag || ( fs . existsSync ( '.env' ) ? '.env' : undefined ) ;
41+ /**
42+ * Run scan-usage mode (default behavior)
43+ */
44+ async function runScanMode ( opts : Options ) {
45+ const envPath = opts . envFlag || ( fs . existsSync ( '.env' ) ? '.env' : undefined ) ;
3146
32- const { exitWithError } = await scanUsage ( {
33- cwd : opts . cwd ,
34- include : opts . includeFiles ,
35- exclude : opts . excludeFiles ,
36- ignore : opts . ignore ,
37- ignoreRegex : opts . ignoreRegex ,
38- examplePath : opts . exampleFlag ,
39- envPath,
40- fix : opts . fix ,
41- json : opts . json ,
42- showUnused : opts . showUnused ,
43- showStats : opts . showStats ,
44- isCiMode : opts . isCiMode ,
45- secrets : opts . secrets ,
46- strict : opts . strict ?? false ,
47- ...( opts . files ? { files : opts . files } : { } ) ,
48- } ) ;
47+ const { exitWithError } = await scanUsage ( {
48+ cwd : opts . cwd ,
49+ include : opts . includeFiles ,
50+ exclude : opts . excludeFiles ,
51+ ignore : opts . ignore ,
52+ ignoreRegex : opts . ignoreRegex ,
53+ examplePath : opts . exampleFlag ,
54+ envPath,
55+ fix : opts . fix ,
56+ json : opts . json ,
57+ showUnused : opts . showUnused ,
58+ showStats : opts . showStats ,
59+ isCiMode : opts . isCiMode ,
60+ secrets : opts . secrets ,
61+ strict : opts . strict ?? false ,
62+ ...( opts . files ? { files : opts . files } : { } ) ,
63+ } ) ;
4964
50- process . exit ( exitWithError ? 1 : 0 ) ;
51- }
65+ process . exit ( exitWithError ? 1 : 0 ) ;
66+ }
5267
53- // Special-case: both flags → direct comparison of exactly those two files
68+ /**
69+ * Run compare mode
70+ */
71+ async function runCompareMode ( opts : Options ) {
72+ // Handle direct file comparison (both --env and --example specified)
5473 if ( opts . envFlag && opts . exampleFlag ) {
55- const envExists = fs . existsSync ( opts . envFlag ) ;
56- const exExists = fs . existsSync ( opts . exampleFlag ) ;
57-
58- // Handle missing files with prompting (unless in CI mode)
59- if ( ! envExists || ! exExists ) {
60- // Check if we should prompt for file creation
61- if ( ! opts . isCiMode ) {
62- const res = await ensureFilesOrPrompt ( {
63- cwd : opts . cwd ,
64- primaryEnv : opts . envFlag ,
65- primaryExample : opts . exampleFlag ,
66- alreadyWarnedMissingEnv : false ,
67- isYesMode : opts . isYesMode ,
68- isCiMode : opts . isCiMode ,
69- } ) ;
70-
71- if ( res . shouldExit ) {
72- if ( opts . json ) console . log ( JSON . stringify ( [ ] , null , 2 ) ) ;
73- process . exit ( res . exitCode ) ;
74- }
75- } else {
76- // In CI mode, we just show errors and exit
77- if ( ! envExists ) {
78- console . error (
79- chalk . red (
80- `❌ Error: --env file not found: ${ path . basename ( opts . envFlag ) } ` ,
81- ) ,
82- ) ;
83- }
84- if ( ! exExists ) {
85- console . error (
86- chalk . red (
87- `❌ Error: --example file not found: ${ path . basename ( opts . exampleFlag ) } ` ,
88- ) ,
89- ) ;
90- }
91- process . exit ( 1 ) ;
92- }
93- }
74+ await runDirectFileComparison ( opts ) ;
75+ return ;
76+ }
77+
78+ // Handle auto-discovery comparison
79+ await runAutoDiscoveryComparison ( opts ) ;
80+ }
9481
95- const report : CompareJsonEntry [ ] = [ ] ;
96- const { exitWithError } = await compareMany (
97- [
98- {
99- envName : path . basename ( opts . envFlag ) ,
100- envPath : opts . envFlag ,
101- examplePath : opts . exampleFlag ,
102- } ,
103- ] ,
82+ /**
83+ * Compare two specific files directly
84+ */
85+ async function runDirectFileComparison ( opts : Options ) {
86+ const envExists = fs . existsSync ( opts . envFlag ! ) ;
87+ const exExists = fs . existsSync ( opts . exampleFlag ! ) ;
88+
89+ // Handle missing files
90+ if ( ! envExists || ! exExists ) {
91+ const shouldExit = await handleMissingFiles (
92+ opts ,
93+ opts . envFlag ! ,
94+ opts . exampleFlag ! ,
95+ ) ;
96+ if ( shouldExit ) return ;
97+ }
98+
99+ // Perform comparison
100+ const report : CompareJsonEntry [ ] = [ ] ;
101+ const { exitWithError } = await compareMany (
102+ [
104103 {
105- checkValues : opts . checkValues ,
106- cwd : opts . cwd ,
107- allowDuplicates : opts . allowDuplicates ,
108- json : opts . json ,
109- ignore : opts . ignore ,
110- ignoreRegex : opts . ignoreRegex ,
111- showStats : opts . showStats ,
112- collect : ( e ) => report . push ( e ) ,
113- ...( opts . only ? { only : opts . only } : { } ) ,
104+ envName : path . basename ( opts . envFlag ! ) ,
105+ envPath : opts . envFlag ! ,
106+ examplePath : opts . exampleFlag ! ,
114107 } ,
115- ) ;
108+ ] ,
109+ buildCompareOptions ( opts , report ) ,
110+ ) ;
116111
117- if ( opts . json ) {
118- console . log ( JSON . stringify ( report , null , 2 ) ) ;
119- }
120- process . exit ( exitWithError ? 1 : 0 ) ;
121- }
112+ outputResults ( report , opts , exitWithError ) ;
113+ }
122114
123- // Auto-discovery flow
124- const d = discoverEnvFiles ( {
115+ /**
116+ * Compare using auto-discovery
117+ */
118+ async function runAutoDiscoveryComparison ( opts : Options ) {
119+ // Discover available env files
120+ const discovery = discoverEnvFiles ( {
125121 cwd : opts . cwd ,
126122 envFlag : opts . envFlag ?? null ,
127123 exampleFlag : opts . exampleFlag ?? null ,
128124 } ) ;
129125
130- // Init cases (may create files or early-exit)
131- const res = await ensureFilesOrPrompt ( {
132- cwd : d . cwd ,
133- primaryEnv : d . primaryEnv ,
134- primaryExample : d . primaryExample ,
135- alreadyWarnedMissingEnv : d . alreadyWarnedMissingEnv ,
126+ // Ensure required files exist or prompt to create them
127+ const initResult = await ensureFilesOrPrompt ( {
128+ cwd : discovery . cwd ,
129+ primaryEnv : discovery . primaryEnv ,
130+ primaryExample : discovery . primaryExample ,
131+ alreadyWarnedMissingEnv : discovery . alreadyWarnedMissingEnv ,
136132 isYesMode : opts . isYesMode ,
137133 isCiMode : opts . isCiMode ,
138134 } ) ;
139- if ( res . shouldExit ) {
140- if ( opts . json ) console . log ( JSON . stringify ( [ ] , null , 2 ) ) ;
141- process . exit ( res . exitCode ) ;
135+
136+ if ( initResult . shouldExit ) {
137+ outputResults ( [ ] , opts , initResult . exitCode !== 0 ) ;
138+ return ;
142139 }
143140
144141 // Compare all discovered pairs
145- const pairs = pairWithExample ( d ) ;
142+ const pairs = pairWithExample ( discovery ) ;
146143 const report : CompareJsonEntry [ ] = [ ] ;
147- const { exitWithError } = await compareMany ( pairs , {
144+ const { exitWithError } = await compareMany (
145+ pairs ,
146+ buildCompareOptions ( opts , report ) ,
147+ ) ;
148+
149+ outputResults ( report , opts , exitWithError ) ;
150+ }
151+
152+ /**
153+ * Handle missing files in CI vs interactive mode
154+ */
155+ async function handleMissingFiles (
156+ opts : Options ,
157+ envFlag : string ,
158+ exampleFlag : string ,
159+ ) : Promise < boolean > {
160+ const envExists = fs . existsSync ( envFlag ) ;
161+ const exExists = fs . existsSync ( exampleFlag ) ;
162+
163+ if ( opts . isCiMode ) {
164+ // In CI mode, just show errors and exit
165+ if ( ! envExists ) {
166+ console . error (
167+ chalk . red ( `❌ Error: --env file not found: ${ path . basename ( envFlag ) } ` ) ,
168+ ) ;
169+ }
170+ if ( ! exExists ) {
171+ console . error (
172+ chalk . red (
173+ `❌ Error: --example file not found: ${ path . basename ( exampleFlag ) } ` ,
174+ ) ,
175+ ) ;
176+ }
177+ process . exit ( 1 ) ;
178+ } else {
179+ // Interactive mode - try to prompt for file creation
180+ const result = await ensureFilesOrPrompt ( {
181+ cwd : opts . cwd ,
182+ primaryEnv : envFlag ,
183+ primaryExample : exampleFlag ,
184+ alreadyWarnedMissingEnv : false ,
185+ isYesMode : opts . isYesMode ,
186+ isCiMode : opts . isCiMode ,
187+ } ) ;
188+
189+ if ( result . shouldExit ) {
190+ outputResults ( [ ] , opts , result . exitCode !== 0 ) ;
191+ return true ;
192+ }
193+ }
194+
195+ return false ;
196+ }
197+
198+ /**
199+ * Build options object for compareMany function
200+ */
201+ function buildCompareOptions ( opts : Options , report : CompareJsonEntry [ ] ) {
202+ return {
148203 checkValues : opts . checkValues ,
149204 cwd : opts . cwd ,
150205 allowDuplicates : opts . allowDuplicates ,
@@ -153,13 +208,21 @@ export async function run(program: Command) {
153208 ignore : opts . ignore ,
154209 ignoreRegex : opts . ignoreRegex ,
155210 showStats : opts . showStats ,
156- collect : ( e ) => report . push ( e ) ,
211+ collect : ( e : CompareJsonEntry ) => report . push ( e ) ,
157212 ...( opts . only ? { only : opts . only } : { } ) ,
158- } ) ;
213+ } ;
214+ }
159215
216+ /**
217+ * Output results and exit with appropriate code
218+ */
219+ function outputResults (
220+ report : CompareJsonEntry [ ] ,
221+ opts : Options ,
222+ exitWithError : boolean ,
223+ ) {
160224 if ( opts . json ) {
161225 console . log ( JSON . stringify ( report , null , 2 ) ) ;
162226 }
163-
164227 process . exit ( exitWithError ? 1 : 0 ) ;
165228}
0 commit comments