@gajae-code/utils 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/cli.d.ts +8 -0
- package/dist/types/tab-spacing.d.ts +3 -0
- package/package.json +2 -2
- package/src/cli.ts +51 -13
- package/src/tab-spacing.ts +16 -1
package/dist/types/cli.d.ts
CHANGED
|
@@ -39,6 +39,14 @@ export declare const Flags: {
|
|
|
39
39
|
export declare const Args: {
|
|
40
40
|
string<T extends ArgInput>(opts?: T): ArgDescriptor & T;
|
|
41
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Thrown when CLI argument/flag parsing or validation fails (unknown flag,
|
|
44
|
+
* bad option value, missing required arg, etc.). `run()` catches this to print
|
|
45
|
+
* the message and render usage instead of crashing as an uncaught exception.
|
|
46
|
+
*/
|
|
47
|
+
export declare class CliParseError extends Error {
|
|
48
|
+
constructor(message: string);
|
|
49
|
+
}
|
|
42
50
|
type FlagValue<D extends FlagDescriptor> = D["kind"] extends "boolean" ? D extends {
|
|
43
51
|
default: boolean;
|
|
44
52
|
} ? boolean : boolean | undefined : D["kind"] extends "integer" ? D extends {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
export declare const MIN_TAB_WIDTH = 1;
|
|
2
2
|
export declare const MAX_TAB_WIDTH = 16;
|
|
3
3
|
export declare const DEFAULT_TAB_WIDTH = 3;
|
|
4
|
+
type TabWidthChangeListener = (width: number) => void;
|
|
5
|
+
export declare function onDefaultTabWidthChange(listener: TabWidthChangeListener): () => void;
|
|
4
6
|
export declare function getDefaultTabWidth(): number;
|
|
5
7
|
export declare function setDefaultTabWidth(width: number): void;
|
|
6
8
|
/**
|
|
7
9
|
* Visible tab width in columns for `file` (from `.editorconfig` + default), or the default when `file` is omitted.
|
|
8
10
|
*/
|
|
9
11
|
export declare function getIndentation(file?: string | null, projectDir?: string | null): number;
|
|
12
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/utils",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"fmt": "biome format --write ."
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@gajae-code/natives": "0.
|
|
34
|
+
"@gajae-code/natives": "0.9.0",
|
|
35
35
|
"beautiful-mermaid": "^1.1.3",
|
|
36
36
|
"handlebars": "^4.7.9",
|
|
37
37
|
"winston": "^3.19.0",
|
package/src/cli.ts
CHANGED
|
@@ -68,6 +68,18 @@ export const Args = {
|
|
|
68
68
|
},
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Thrown when CLI argument/flag parsing or validation fails (unknown flag,
|
|
73
|
+
* bad option value, missing required arg, etc.). `run()` catches this to print
|
|
74
|
+
* the message and render usage instead of crashing as an uncaught exception.
|
|
75
|
+
*/
|
|
76
|
+
export class CliParseError extends Error {
|
|
77
|
+
constructor(message: string) {
|
|
78
|
+
super(message);
|
|
79
|
+
this.name = "CliParseError";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
71
83
|
// ---------------------------------------------------------------------------
|
|
72
84
|
// Parse result types — mirrors oclif's typed output from this.parse()
|
|
73
85
|
// ---------------------------------------------------------------------------
|
|
@@ -174,12 +186,22 @@ export abstract class Command {
|
|
|
174
186
|
|
|
175
187
|
// strict=false when command declares args (positionals must pass through)
|
|
176
188
|
// or when the command itself opts out
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
let rawValues: Record<string, string | boolean | Array<string | boolean> | undefined>;
|
|
190
|
+
let positionals: string[];
|
|
191
|
+
try {
|
|
192
|
+
const parsed = nodeParseArgs({
|
|
193
|
+
args: this.argv,
|
|
194
|
+
options,
|
|
195
|
+
allowPositionals: true,
|
|
196
|
+
strict,
|
|
197
|
+
});
|
|
198
|
+
rawValues = parsed.values;
|
|
199
|
+
positionals = parsed.positionals;
|
|
200
|
+
} catch (err) {
|
|
201
|
+
// node:util parseArgs throws on unknown flags / malformed input — surface
|
|
202
|
+
// it as a CliParseError so run() renders usage instead of crashing.
|
|
203
|
+
throw new CliParseError(err instanceof Error ? err.message : String(err));
|
|
204
|
+
}
|
|
183
205
|
|
|
184
206
|
// Convert raw values to proper types and validate
|
|
185
207
|
const flags: Record<string, unknown> = {};
|
|
@@ -191,7 +213,7 @@ export abstract class Command {
|
|
|
191
213
|
} else {
|
|
192
214
|
const n = Number.parseInt(raw as string, 10);
|
|
193
215
|
if (Number.isNaN(n)) {
|
|
194
|
-
throw new
|
|
216
|
+
throw new CliParseError(`Expected integer for --${name}, got "${raw}"`);
|
|
195
217
|
}
|
|
196
218
|
flags[name] = n;
|
|
197
219
|
}
|
|
@@ -204,14 +226,16 @@ export abstract class Command {
|
|
|
204
226
|
// Validate options constraint
|
|
205
227
|
if (val !== undefined && desc.options && !Array.isArray(val)) {
|
|
206
228
|
if (!desc.options.includes(val as string)) {
|
|
207
|
-
throw new
|
|
229
|
+
throw new CliParseError(
|
|
230
|
+
`Expected --${name} to be one of: ${[...desc.options].join(", ")}; got "${val}"`,
|
|
231
|
+
);
|
|
208
232
|
}
|
|
209
233
|
}
|
|
210
234
|
flags[name] = val;
|
|
211
235
|
}
|
|
212
236
|
// Validate required
|
|
213
237
|
if (desc.required && flags[name] === undefined) {
|
|
214
|
-
throw new
|
|
238
|
+
throw new CliParseError(`Missing required flag: --${name}`);
|
|
215
239
|
}
|
|
216
240
|
}
|
|
217
241
|
|
|
@@ -230,13 +254,15 @@ export abstract class Command {
|
|
|
230
254
|
}
|
|
231
255
|
// Validate required
|
|
232
256
|
if (desc.required && args[argName] === undefined) {
|
|
233
|
-
throw new
|
|
257
|
+
throw new CliParseError(`Missing required argument: ${argName}`);
|
|
234
258
|
}
|
|
235
259
|
// Validate options constraint
|
|
236
260
|
const argVal = args[argName];
|
|
237
261
|
if (argVal !== undefined && desc.options && typeof argVal === "string") {
|
|
238
262
|
if (!desc.options.includes(argVal)) {
|
|
239
|
-
throw new
|
|
263
|
+
throw new CliParseError(
|
|
264
|
+
`Expected ${argName} to be one of: ${[...desc.options].join(", ")}; got "${argVal}"`,
|
|
265
|
+
);
|
|
240
266
|
}
|
|
241
267
|
}
|
|
242
268
|
}
|
|
@@ -407,7 +433,7 @@ export async function run(opts: RunOptions): Promise<void> {
|
|
|
407
433
|
const instance = new Cmd(commandArgv, config);
|
|
408
434
|
await instance.run();
|
|
409
435
|
} else {
|
|
410
|
-
const config =
|
|
436
|
+
const config: CliConfig = { bin, version, commands: new Map([[entry.name, Cmd]]) };
|
|
411
437
|
renderCommandHelp(bin, entry.name, config.commands.get(entry.name) ?? Cmd);
|
|
412
438
|
}
|
|
413
439
|
return;
|
|
@@ -425,7 +451,19 @@ export async function run(opts: RunOptions): Promise<void> {
|
|
|
425
451
|
const Cmd = await entry.load();
|
|
426
452
|
const config: CliConfig = { bin, version, commands: new Map([[entry.name, Cmd]]) };
|
|
427
453
|
const instance = new Cmd(commandArgv, config);
|
|
428
|
-
|
|
454
|
+
try {
|
|
455
|
+
await instance.run();
|
|
456
|
+
} catch (err) {
|
|
457
|
+
if (err instanceof CliParseError) {
|
|
458
|
+
// Invalid args/flags for a real command: print the problem + usage and
|
|
459
|
+
// exit with a usage error, instead of crashing as an uncaught exception.
|
|
460
|
+
process.stderr.write(`${err.message}\n\n`);
|
|
461
|
+
renderCommandHelp(bin, entry.name, Cmd);
|
|
462
|
+
process.exitCode = 2;
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
throw err;
|
|
466
|
+
}
|
|
429
467
|
}
|
|
430
468
|
|
|
431
469
|
/** Resolve all command loaders for help/alias display. */
|
package/src/tab-spacing.ts
CHANGED
|
@@ -13,6 +13,9 @@ export const DEFAULT_TAB_WIDTH = 3;
|
|
|
13
13
|
const EDITORCONFIG_NAME = ".editorconfig";
|
|
14
14
|
|
|
15
15
|
let defaultTabWidth = DEFAULT_TAB_WIDTH;
|
|
16
|
+
type TabWidthChangeListener = (width: number) => void;
|
|
17
|
+
|
|
18
|
+
const tabWidthChangeListeners = new Set<TabWidthChangeListener>();
|
|
16
19
|
|
|
17
20
|
const editorConfigCache = new Map<string, ParsedEditorConfig>();
|
|
18
21
|
const editorConfigChainCache = new Map<string, ChainEntry[]>();
|
|
@@ -278,13 +281,25 @@ function resolveEditorConfigTabWidth(match: EditorConfigMatch | undefined, fallb
|
|
|
278
281
|
|
|
279
282
|
return undefined;
|
|
280
283
|
}
|
|
284
|
+
export function onDefaultTabWidthChange(listener: TabWidthChangeListener): () => void {
|
|
285
|
+
tabWidthChangeListeners.add(listener);
|
|
286
|
+
return () => {
|
|
287
|
+
tabWidthChangeListeners.delete(listener);
|
|
288
|
+
};
|
|
289
|
+
}
|
|
281
290
|
|
|
282
291
|
export function getDefaultTabWidth(): number {
|
|
283
292
|
return defaultTabWidth;
|
|
284
293
|
}
|
|
285
294
|
|
|
286
295
|
export function setDefaultTabWidth(width: number): void {
|
|
287
|
-
|
|
296
|
+
const next = clampTabWidth(width);
|
|
297
|
+
if (defaultTabWidth === next) return;
|
|
298
|
+
defaultTabWidth = next;
|
|
299
|
+
indentationCache.clear();
|
|
300
|
+
for (const listener of tabWidthChangeListeners) {
|
|
301
|
+
listener(next);
|
|
302
|
+
}
|
|
288
303
|
}
|
|
289
304
|
|
|
290
305
|
/**
|