Skip to content

Commit b8af865

Browse files
committed
Add warning log for supersedes
1 parent 67da483 commit b8af865

2 files changed

Lines changed: 95 additions & 11 deletions

File tree

scripts/utils/CLI.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -218,25 +218,30 @@ class CLI<TConfig extends CLIConfig> {
218218
if (providedNamedArgs.has(name) && spec.supersedes) {
219219
for (const supersededArg of spec.supersedes) {
220220
supersededArgs.add(supersededArg);
221+
if (providedNamedArgs.has(supersededArg)) {
222+
console.warn(`⚠️ Warning: --${supersededArg} is superseded by --${name} and will be ignored.`);
223+
}
221224
}
222225
}
223226
}
224227

225228
// Validate that all required args are present, assign defaults where values are not parsed
226229
for (const [name, spec] of Object.entries(config.namedArgs ?? {})) {
227-
if (!(name in parsedNamedArgs)) {
230+
if (name in parsedNamedArgs) {
228231
if (supersededArgs.has(name)) {
229-
// This arg was superseded, so don't require it and don't assign a default
230-
continue;
231-
} else if (spec.default !== undefined) {
232-
parsedNamedArgs[name as keyof typeof parsedNamedArgs] = spec.default as ValueOf<typeof parsedNamedArgs>;
233-
} else if (spec.required === false) {
234-
// Explicitly marked as optional, leave undefined
235-
continue;
236-
} else {
237-
// Arguments without defaults are required by default (unless explicitly marked as optional)
238-
throw new Error(`Missing required named argument --${name}`);
232+
parsedNamedArgs[name as keyof typeof parsedNamedArgs] = undefined as ValueOf<typeof parsedNamedArgs>;
239233
}
234+
} else if (supersededArgs.has(name)) {
235+
// This arg was superseded, so don't require it and don't assign a default
236+
continue;
237+
} else if (spec.default !== undefined) {
238+
parsedNamedArgs[name as keyof typeof parsedNamedArgs] = spec.default as ValueOf<typeof parsedNamedArgs>;
239+
} else if (spec.required === false) {
240+
// Explicitly marked as optional, leave undefined
241+
continue;
242+
} else {
243+
// Arguments without defaults are required by default (unless explicitly marked as optional)
244+
throw new Error(`Missing required named argument --${name}`);
240245
}
241246
}
242247

tests/unit/CLITests.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ describe('CLI', () => {
99
let mockExit: jest.SpyInstance;
1010
let mockLog: jest.SpyInstance;
1111
let mockError: jest.SpyInstance;
12+
let mockWarn: jest.SpyInstance;
1213

1314
beforeEach(() => {
1415
process.argv = ['ts-node', 'script.ts'];
@@ -17,6 +18,7 @@ describe('CLI', () => {
1718
});
1819
mockLog = jest.spyOn(console, 'log').mockImplementation(() => {});
1920
mockError = jest.spyOn(console, 'error').mockImplementation(() => {});
21+
mockWarn = jest.spyOn(console, 'warn').mockImplementation(() => {});
2022
});
2123

2224
afterEach(() => {
@@ -322,4 +324,81 @@ describe('CLI', () => {
322324
).toThrow();
323325
expect(mockError).toHaveBeenCalledWith('Missing required named argument --compareRef');
324326
});
327+
328+
it('warns when superseded arg is provided alongside superseding arg', () => {
329+
process.argv.push('--paths', 'common.save', '--compare-ref', 'main');
330+
/* eslint-disable @typescript-eslint/naming-convention */
331+
const cli = new CLI({
332+
namedArgs: {
333+
'compare-ref': {
334+
description: 'Compare reference',
335+
default: 'main',
336+
},
337+
paths: {
338+
description: 'Specific paths to process',
339+
parse: (val) => val.split(','),
340+
supersedes: ['compare-ref'],
341+
required: false,
342+
},
343+
},
344+
});
345+
/* eslint-enable @typescript-eslint/naming-convention */
346+
347+
expect(mockWarn).toHaveBeenCalledWith('⚠️ Warning: --compare-ref is superseded by --paths and will be ignored.');
348+
expect(cli.namedArgs.paths).toEqual(['common.save']);
349+
expect(cli.namedArgs['compare-ref']).toBeUndefined();
350+
});
351+
352+
it('warns for multiple superseded args when provided', () => {
353+
process.argv.push('--priority', 'high', '--low-priority', 'disabled', '--medium-priority', 'enabled');
354+
/* eslint-disable @typescript-eslint/naming-convention */
355+
const cli = new CLI({
356+
namedArgs: {
357+
'low-priority': {
358+
description: 'Low priority mode',
359+
default: 'enabled',
360+
},
361+
'medium-priority': {
362+
description: 'Medium priority mode',
363+
default: 'enabled',
364+
},
365+
priority: {
366+
description: 'Priority level',
367+
supersedes: ['low-priority', 'medium-priority'],
368+
required: false,
369+
},
370+
},
371+
});
372+
/* eslint-enable @typescript-eslint/naming-convention */
373+
374+
expect(mockWarn).toHaveBeenCalledWith('⚠️ Warning: --low-priority is superseded by --priority and will be ignored.');
375+
expect(mockWarn).toHaveBeenCalledWith('⚠️ Warning: --medium-priority is superseded by --priority and will be ignored.');
376+
expect(cli.namedArgs.priority).toBe('high');
377+
expect(cli.namedArgs['low-priority']).toBeUndefined();
378+
expect(cli.namedArgs['medium-priority']).toBeUndefined();
379+
});
380+
381+
it('does not warn when only superseding arg is provided', () => {
382+
process.argv.push('--paths', 'common.save');
383+
/* eslint-disable @typescript-eslint/naming-convention */
384+
const cli = new CLI({
385+
namedArgs: {
386+
'compare-ref': {
387+
description: 'Compare reference',
388+
default: 'main',
389+
},
390+
paths: {
391+
description: 'Specific paths to process',
392+
parse: (val) => val.split(','),
393+
supersedes: ['compare-ref'],
394+
required: false,
395+
},
396+
},
397+
});
398+
/* eslint-enable @typescript-eslint/naming-convention */
399+
400+
expect(mockWarn).not.toHaveBeenCalled();
401+
expect(cli.namedArgs.paths).toEqual(['common.save']);
402+
expect(cli.namedArgs['compare-ref']).toBeUndefined();
403+
});
325404
});

0 commit comments

Comments
 (0)