@optique/logtape 1.0.0-dev.908 → 1.0.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/index.cjs CHANGED
@@ -25,6 +25,8 @@ const __optique_core_valueparser = __toESM(require("@optique/core/valueparser"))
25
25
  const __optique_core_primitives = __toESM(require("@optique/core/primitives"));
26
26
  const __optique_core_modifiers = __toESM(require("@optique/core/modifiers"));
27
27
  const __optique_core_message = __toESM(require("@optique/core/message"));
28
+ const node_path = __toESM(require("node:path"));
29
+ const __optique_core_nonempty = __toESM(require("@optique/core/nonempty"));
28
30
  const __optique_core_constructs = __toESM(require("@optique/core/constructs"));
29
31
 
30
32
  //#region src/loglevel.ts
@@ -40,6 +42,17 @@ const LOG_LEVELS = [
40
42
  "fatal"
41
43
  ];
42
44
  /**
45
+ * Validates that the given value is a valid {@link LogLevel}.
46
+ *
47
+ * @param value The value to validate.
48
+ * @param paramName The parameter name for the error message.
49
+ * @throws {TypeError} If the value is not a valid log level.
50
+ * @since 1.0.0
51
+ */
52
+ function validateLogLevel(value, paramName) {
53
+ if (!LOG_LEVELS.includes(value)) throw new TypeError(`Invalid log level for ${paramName}: ${String(value)}. Expected ${LOG_LEVELS.map((l) => `"${l}"`).join(", ")}.`);
54
+ }
55
+ /**
43
56
  * Creates a {@link ValueParser} for LogTape log levels.
44
57
  *
45
58
  * This parser validates that the input is one of the valid LogTape severity
@@ -112,6 +125,7 @@ const WARNING_INDEX = 2;
112
125
  *
113
126
  * @param options Configuration options for the verbosity parser.
114
127
  * @returns A {@link Parser} that produces a {@link LogLevel}.
128
+ * @throws {TypeError} If `baseLevel` is not a valid log level.
115
129
  *
116
130
  * @example Basic usage
117
131
  * ```typescript
@@ -146,6 +160,7 @@ function verbosity(options = {}) {
146
160
  const short = options.short ?? "-v";
147
161
  const long = options.long ?? "--verbose";
148
162
  const baseLevel = options.baseLevel ?? "warning";
163
+ validateLogLevel(baseLevel, "baseLevel");
149
164
  const baseIndex = VERBOSITY_LEVELS.indexOf(baseLevel);
150
165
  const effectiveBaseIndex = baseIndex >= 0 ? baseIndex : WARNING_INDEX;
151
166
  const flagParser = (0, __optique_core_primitives.flag)(short, long, { description: options.description ?? __optique_core_message.message`Be more verbose. Can be repeated.` });
@@ -168,6 +183,8 @@ function verbosity(options = {}) {
168
183
  *
169
184
  * @param options Configuration options for the debug flag parser.
170
185
  * @returns A {@link Parser} that produces a {@link LogLevel}.
186
+ * @throws {TypeError} If `debugLevel` or `normalLevel` is not a valid log
187
+ * level.
171
188
  *
172
189
  * @example Basic usage
173
190
  * ```typescript
@@ -201,6 +218,8 @@ function debug(options = {}) {
201
218
  const long = options.long ?? "--debug";
202
219
  const debugLevel = options.debugLevel ?? "debug";
203
220
  const normalLevel = options.normalLevel ?? "info";
221
+ validateLogLevel(debugLevel, "debugLevel");
222
+ validateLogLevel(normalLevel, "normalLevel");
204
223
  const flagParser = (0, __optique_core_primitives.flag)(short, long, { description: options.description ?? __optique_core_message.message`Enable debug logging.` });
205
224
  return (0, __optique_core_modifiers.map)((0, __optique_core_modifiers.optional)(flagParser), (value) => {
206
225
  return value === true ? debugLevel : normalLevel;
@@ -218,11 +237,15 @@ function debug(options = {}) {
218
237
  *
219
238
  * @param options Configuration options for the parser.
220
239
  * @returns A {@link ValueParser} that produces a {@link LogOutput}.
240
+ * @throws {TypeError} If `options.metavar` is an empty string.
221
241
  */
222
242
  function logOutputValueParser(options = {}) {
243
+ const metavar = options.metavar ?? "FILE";
244
+ (0, __optique_core_nonempty.ensureNonEmptyString)(metavar);
223
245
  return {
224
- $mode: "sync",
225
- metavar: options.metavar ?? "FILE",
246
+ mode: "sync",
247
+ metavar,
248
+ placeholder: { type: "console" },
226
249
  parse(input) {
227
250
  if (input === "-") return {
228
251
  success: true,
@@ -251,7 +274,8 @@ function logOutputValueParser(options = {}) {
251
274
  yield {
252
275
  kind: "file",
253
276
  type: "file",
254
- pattern: prefix
277
+ pattern: prefix,
278
+ includeHidden: (0, node_path.basename)(prefix).startsWith(".") && (0, node_path.basename)(prefix) !== ".."
255
279
  };
256
280
  }
257
281
  };
@@ -264,6 +288,7 @@ function logOutputValueParser(options = {}) {
264
288
  *
265
289
  * @param options Configuration options for the log output parser.
266
290
  * @returns A {@link Parser} that produces a {@link LogOutput} or `undefined`.
291
+ * @throws {TypeError} If `options.metavar` is an empty string.
267
292
  *
268
293
  * @example Basic usage
269
294
  * ```typescript
@@ -299,6 +324,10 @@ function logOutput(options = {}) {
299
324
  *
300
325
  * @param options Configuration options for the console sink.
301
326
  * @returns A {@link Sink} function.
327
+ * @throws {TypeError} If `options.stream` is not `"stdout"` or `"stderr"`
328
+ * when `streamResolver` is not provided.
329
+ * @throws {TypeError} If `streamResolver` returns a value other than
330
+ * `"stdout"` or `"stderr"`.
302
331
  *
303
332
  * @example Static stream selection
304
333
  * ```typescript
@@ -320,10 +349,23 @@ function logOutput(options = {}) {
320
349
  * @since 0.8.0
321
350
  */
322
351
  function createConsoleSink(options = {}) {
323
- const defaultStream = options.stream ?? "stderr";
324
352
  const streamResolver = options.streamResolver;
353
+ const defaultStream = options.stream ?? "stderr";
354
+ const invalidStreamError = (value) => {
355
+ let repr;
356
+ if (typeof value === "string") repr = JSON.stringify(value);
357
+ else if (value === null || typeof value !== "object") repr = String(value);
358
+ else try {
359
+ repr = JSON.stringify(value) ?? String(value);
360
+ } catch {
361
+ repr = String(value);
362
+ }
363
+ return /* @__PURE__ */ new TypeError(`Invalid stream: expected "stdout" or "stderr", got ${repr}.`);
364
+ };
365
+ if (!streamResolver && defaultStream !== "stdout" && defaultStream !== "stderr") throw invalidStreamError(defaultStream);
325
366
  return (record) => {
326
367
  const stream = streamResolver ? streamResolver(record.level) : defaultStream;
368
+ if (stream !== "stdout" && stream !== "stderr") throw invalidStreamError(stream);
327
369
  const messageParts = [];
328
370
  for (let i = 0; i < record.message.length; i++) {
329
371
  const part = record.message[i];
@@ -331,7 +373,8 @@ function createConsoleSink(options = {}) {
331
373
  else messageParts.push(String(part));
332
374
  }
333
375
  const formattedMessage = messageParts.join("");
334
- const timestamp = record.timestamp ? new Date(record.timestamp).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
376
+ const ts = record.timestamp;
377
+ const timestamp = new Date(ts != null && !Number.isNaN(ts) ? ts : Date.now()).toISOString();
335
378
  const category = record.category.join(".");
336
379
  const level = record.level.toUpperCase().padEnd(7);
337
380
  const line = `${timestamp} [${level}] ${category}: ${formattedMessage}`;
@@ -348,7 +391,11 @@ function createConsoleSink(options = {}) {
348
391
  * @param output The log output destination.
349
392
  * @param consoleSinkOptions Options for console sink (only used when output is console).
350
393
  * @returns A promise that resolves to a {@link Sink}.
351
- * @throws {Error} If file output is requested but `@logtape/file` is not installed.
394
+ * @throws {Error} If file output is requested but `@logtape/file` is not
395
+ * installed.
396
+ * @throws If `@logtape/file` is installed but `getFileSink(output.path)` fails
397
+ * at runtime (e.g., the target directory does not exist), the original error
398
+ * propagates as-is.
352
399
  *
353
400
  * @example Console output
354
401
  * ```typescript
@@ -368,9 +415,9 @@ function createConsoleSink(options = {}) {
368
415
  */
369
416
  async function createSink(output, consoleSinkOptions = {}) {
370
417
  if (output.type === "console") return createConsoleSink(consoleSinkOptions);
418
+ let getFileSink;
371
419
  try {
372
- const { getFileSink } = await import("@logtape/file");
373
- return getFileSink(output.path);
420
+ ({getFileSink} = await import("@logtape/file"));
374
421
  } catch (e) {
375
422
  throw new Error(`File sink requires @logtape/file package. Install it with:
376
423
  npm install @logtape/file
@@ -379,6 +426,7 @@ async function createSink(output, consoleSinkOptions = {}) {
379
426
 
380
427
  Original error: ${e}`);
381
428
  }
429
+ return getFileSink(output.path);
382
430
  }
383
431
 
384
432
  //#endregion
@@ -430,6 +478,9 @@ Original error: ${e}`);
430
478
  * // --debug
431
479
  * ```
432
480
  *
481
+ * @throws {TypeError} If the `level` discriminant is not one of `"option"`,
482
+ * `"verbosity"`, or `"debug"`, or if a log level option (`default`,
483
+ * `debugLevel`, `normalLevel`, or `baseLevel`) is not a valid log level.
433
484
  * @since 0.8.0
434
485
  */
435
486
  function loggingOptions(config) {
@@ -442,6 +493,7 @@ function loggingOptions(config) {
442
493
  const long = config.long ?? "--log-level";
443
494
  const short = config.short ?? "-l";
444
495
  const defaultLevel = config.default ?? "info";
496
+ validateLogLevel(defaultLevel, "default");
445
497
  levelParser = (0, __optique_core_modifiers.withDefault)((0, __optique_core_primitives.option)(short, long, logLevel()), defaultLevel);
446
498
  break;
447
499
  }
@@ -464,16 +516,19 @@ function loggingOptions(config) {
464
516
  levelParser = debug(debugOptions);
465
517
  break;
466
518
  }
519
+ default: throw new TypeError(`Unsupported level configuration: ${String(config.level)}. Expected "option", "verbosity", or "debug".`);
467
520
  }
468
521
  const defaultOutput = { type: "console" };
469
522
  const outputParser = (0, __optique_core_modifiers.withDefault)(logOutput({ long: outputLong }), defaultOutput);
470
523
  if (!outputEnabled) {
471
524
  const constantOutputParser = {
472
- $mode: "sync",
525
+ mode: "sync",
473
526
  $valueType: [],
474
527
  $stateType: [],
475
528
  priority: 0,
476
529
  usage: [],
530
+ leadingNames: /* @__PURE__ */ new Set(),
531
+ acceptingAnyToken: false,
477
532
  initialState: void 0,
478
533
  parse: (context) => ({
479
534
  success: true,
package/dist/index.d.cts CHANGED
@@ -8,6 +8,15 @@ import { Parser } from "@optique/core/parser";
8
8
  * All valid log levels in order from lowest to highest severity.
9
9
  */
10
10
  declare const LOG_LEVELS: readonly LogLevel$1[];
11
+ /**
12
+ * Validates that the given value is a valid {@link LogLevel}.
13
+ *
14
+ * @param value The value to validate.
15
+ * @param paramName The parameter name for the error message.
16
+ * @throws {TypeError} If the value is not a valid log level.
17
+ * @since 1.0.0
18
+ */
19
+
11
20
  /**
12
21
  * Options for creating a log level value parser.
13
22
  * @since 0.8.0
@@ -105,6 +114,7 @@ interface VerbosityOptions {
105
114
  *
106
115
  * @param options Configuration options for the verbosity parser.
107
116
  * @returns A {@link Parser} that produces a {@link LogLevel}.
117
+ * @throws {TypeError} If `baseLevel` is not a valid log level.
108
118
  *
109
119
  * @example Basic usage
110
120
  * ```typescript
@@ -177,6 +187,8 @@ interface DebugOptions {
177
187
  *
178
188
  * @param options Configuration options for the debug flag parser.
179
189
  * @returns A {@link Parser} that produces a {@link LogLevel}.
190
+ * @throws {TypeError} If `debugLevel` or `normalLevel` is not a valid log
191
+ * level.
180
192
  *
181
193
  * @example Basic usage
182
194
  * ```typescript
@@ -228,9 +240,10 @@ type LogOutput = {
228
240
  interface ConsoleSinkOptions {
229
241
  /**
230
242
  * The stream to write to. Either `"stdout"` or `"stderr"`.
243
+ * If `null` or `undefined`, defaults to `"stderr"`.
231
244
  * @default `"stderr"`
232
245
  */
233
- readonly stream?: "stdout" | "stderr";
246
+ readonly stream?: "stdout" | "stderr" | null;
234
247
  /**
235
248
  * A function that determines which stream to use based on the log level.
236
249
  * If provided, this takes precedence over the `stream` option.
@@ -287,6 +300,7 @@ interface LogOutputOptions {
287
300
  *
288
301
  * @param options Configuration options for the log output parser.
289
302
  * @returns A {@link Parser} that produces a {@link LogOutput} or `undefined`.
303
+ * @throws {TypeError} If `options.metavar` is an empty string.
290
304
  *
291
305
  * @example Basic usage
292
306
  * ```typescript
@@ -313,6 +327,10 @@ declare function logOutput(options?: LogOutputOptions): Parser<"sync", LogOutput
313
327
  *
314
328
  * @param options Configuration options for the console sink.
315
329
  * @returns A {@link Sink} function.
330
+ * @throws {TypeError} If `options.stream` is not `"stdout"` or `"stderr"`
331
+ * when `streamResolver` is not provided.
332
+ * @throws {TypeError} If `streamResolver` returns a value other than
333
+ * `"stdout"` or `"stderr"`.
316
334
  *
317
335
  * @example Static stream selection
318
336
  * ```typescript
@@ -343,7 +361,11 @@ declare function createConsoleSink(options?: ConsoleSinkOptions): Sink$1;
343
361
  * @param output The log output destination.
344
362
  * @param consoleSinkOptions Options for console sink (only used when output is console).
345
363
  * @returns A promise that resolves to a {@link Sink}.
346
- * @throws {Error} If file output is requested but `@logtape/file` is not installed.
364
+ * @throws {Error} If file output is requested but `@logtape/file` is not
365
+ * installed.
366
+ * @throws If `@logtape/file` is installed but `getFileSink(output.path)` fails
367
+ * at runtime (e.g., the target directory does not exist), the original error
368
+ * propagates as-is.
347
369
  *
348
370
  * @example Console output
349
371
  * ```typescript
@@ -561,6 +583,9 @@ type LoggingOptionsConfig = LoggingOptionsWithLevel | LoggingOptionsWithVerbosit
561
583
  * // --debug
562
584
  * ```
563
585
  *
586
+ * @throws {TypeError} If the `level` discriminant is not one of `"option"`,
587
+ * `"verbosity"`, or `"debug"`, or if a log level option (`default`,
588
+ * `debugLevel`, `normalLevel`, or `baseLevel`) is not a valid log level.
564
589
  * @since 0.8.0
565
590
  */
566
591
  declare function loggingOptions(config: LoggingOptionsConfig): Parser<"sync", LoggingOptionsResult, unknown>;
package/dist/index.d.ts CHANGED
@@ -8,6 +8,15 @@ import { Parser } from "@optique/core/parser";
8
8
  * All valid log levels in order from lowest to highest severity.
9
9
  */
10
10
  declare const LOG_LEVELS: readonly LogLevel$1[];
11
+ /**
12
+ * Validates that the given value is a valid {@link LogLevel}.
13
+ *
14
+ * @param value The value to validate.
15
+ * @param paramName The parameter name for the error message.
16
+ * @throws {TypeError} If the value is not a valid log level.
17
+ * @since 1.0.0
18
+ */
19
+
11
20
  /**
12
21
  * Options for creating a log level value parser.
13
22
  * @since 0.8.0
@@ -105,6 +114,7 @@ interface VerbosityOptions {
105
114
  *
106
115
  * @param options Configuration options for the verbosity parser.
107
116
  * @returns A {@link Parser} that produces a {@link LogLevel}.
117
+ * @throws {TypeError} If `baseLevel` is not a valid log level.
108
118
  *
109
119
  * @example Basic usage
110
120
  * ```typescript
@@ -177,6 +187,8 @@ interface DebugOptions {
177
187
  *
178
188
  * @param options Configuration options for the debug flag parser.
179
189
  * @returns A {@link Parser} that produces a {@link LogLevel}.
190
+ * @throws {TypeError} If `debugLevel` or `normalLevel` is not a valid log
191
+ * level.
180
192
  *
181
193
  * @example Basic usage
182
194
  * ```typescript
@@ -228,9 +240,10 @@ type LogOutput = {
228
240
  interface ConsoleSinkOptions {
229
241
  /**
230
242
  * The stream to write to. Either `"stdout"` or `"stderr"`.
243
+ * If `null` or `undefined`, defaults to `"stderr"`.
231
244
  * @default `"stderr"`
232
245
  */
233
- readonly stream?: "stdout" | "stderr";
246
+ readonly stream?: "stdout" | "stderr" | null;
234
247
  /**
235
248
  * A function that determines which stream to use based on the log level.
236
249
  * If provided, this takes precedence over the `stream` option.
@@ -287,6 +300,7 @@ interface LogOutputOptions {
287
300
  *
288
301
  * @param options Configuration options for the log output parser.
289
302
  * @returns A {@link Parser} that produces a {@link LogOutput} or `undefined`.
303
+ * @throws {TypeError} If `options.metavar` is an empty string.
290
304
  *
291
305
  * @example Basic usage
292
306
  * ```typescript
@@ -313,6 +327,10 @@ declare function logOutput(options?: LogOutputOptions): Parser<"sync", LogOutput
313
327
  *
314
328
  * @param options Configuration options for the console sink.
315
329
  * @returns A {@link Sink} function.
330
+ * @throws {TypeError} If `options.stream` is not `"stdout"` or `"stderr"`
331
+ * when `streamResolver` is not provided.
332
+ * @throws {TypeError} If `streamResolver` returns a value other than
333
+ * `"stdout"` or `"stderr"`.
316
334
  *
317
335
  * @example Static stream selection
318
336
  * ```typescript
@@ -343,7 +361,11 @@ declare function createConsoleSink(options?: ConsoleSinkOptions): Sink$1;
343
361
  * @param output The log output destination.
344
362
  * @param consoleSinkOptions Options for console sink (only used when output is console).
345
363
  * @returns A promise that resolves to a {@link Sink}.
346
- * @throws {Error} If file output is requested but `@logtape/file` is not installed.
364
+ * @throws {Error} If file output is requested but `@logtape/file` is not
365
+ * installed.
366
+ * @throws If `@logtape/file` is installed but `getFileSink(output.path)` fails
367
+ * at runtime (e.g., the target directory does not exist), the original error
368
+ * propagates as-is.
347
369
  *
348
370
  * @example Console output
349
371
  * ```typescript
@@ -561,6 +583,9 @@ type LoggingOptionsConfig = LoggingOptionsWithLevel | LoggingOptionsWithVerbosit
561
583
  * // --debug
562
584
  * ```
563
585
  *
586
+ * @throws {TypeError} If the `level` discriminant is not one of `"option"`,
587
+ * `"verbosity"`, or `"debug"`, or if a log level option (`default`,
588
+ * `debugLevel`, `normalLevel`, or `baseLevel`) is not a valid log level.
564
589
  * @since 0.8.0
565
590
  */
566
591
  declare function loggingOptions(config: LoggingOptionsConfig): Parser<"sync", LoggingOptionsResult, unknown>;
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@ import { choice } from "@optique/core/valueparser";
2
2
  import { flag, option } from "@optique/core/primitives";
3
3
  import { map, multiple, optional, withDefault } from "@optique/core/modifiers";
4
4
  import { message } from "@optique/core/message";
5
+ import { basename } from "node:path";
6
+ import { ensureNonEmptyString } from "@optique/core/nonempty";
5
7
  import { group, object } from "@optique/core/constructs";
6
8
 
7
9
  //#region src/loglevel.ts
@@ -17,6 +19,17 @@ const LOG_LEVELS = [
17
19
  "fatal"
18
20
  ];
19
21
  /**
22
+ * Validates that the given value is a valid {@link LogLevel}.
23
+ *
24
+ * @param value The value to validate.
25
+ * @param paramName The parameter name for the error message.
26
+ * @throws {TypeError} If the value is not a valid log level.
27
+ * @since 1.0.0
28
+ */
29
+ function validateLogLevel(value, paramName) {
30
+ if (!LOG_LEVELS.includes(value)) throw new TypeError(`Invalid log level for ${paramName}: ${String(value)}. Expected ${LOG_LEVELS.map((l) => `"${l}"`).join(", ")}.`);
31
+ }
32
+ /**
20
33
  * Creates a {@link ValueParser} for LogTape log levels.
21
34
  *
22
35
  * This parser validates that the input is one of the valid LogTape severity
@@ -89,6 +102,7 @@ const WARNING_INDEX = 2;
89
102
  *
90
103
  * @param options Configuration options for the verbosity parser.
91
104
  * @returns A {@link Parser} that produces a {@link LogLevel}.
105
+ * @throws {TypeError} If `baseLevel` is not a valid log level.
92
106
  *
93
107
  * @example Basic usage
94
108
  * ```typescript
@@ -123,6 +137,7 @@ function verbosity(options = {}) {
123
137
  const short = options.short ?? "-v";
124
138
  const long = options.long ?? "--verbose";
125
139
  const baseLevel = options.baseLevel ?? "warning";
140
+ validateLogLevel(baseLevel, "baseLevel");
126
141
  const baseIndex = VERBOSITY_LEVELS.indexOf(baseLevel);
127
142
  const effectiveBaseIndex = baseIndex >= 0 ? baseIndex : WARNING_INDEX;
128
143
  const flagParser = flag(short, long, { description: options.description ?? message`Be more verbose. Can be repeated.` });
@@ -145,6 +160,8 @@ function verbosity(options = {}) {
145
160
  *
146
161
  * @param options Configuration options for the debug flag parser.
147
162
  * @returns A {@link Parser} that produces a {@link LogLevel}.
163
+ * @throws {TypeError} If `debugLevel` or `normalLevel` is not a valid log
164
+ * level.
148
165
  *
149
166
  * @example Basic usage
150
167
  * ```typescript
@@ -178,6 +195,8 @@ function debug(options = {}) {
178
195
  const long = options.long ?? "--debug";
179
196
  const debugLevel = options.debugLevel ?? "debug";
180
197
  const normalLevel = options.normalLevel ?? "info";
198
+ validateLogLevel(debugLevel, "debugLevel");
199
+ validateLogLevel(normalLevel, "normalLevel");
181
200
  const flagParser = flag(short, long, { description: options.description ?? message`Enable debug logging.` });
182
201
  return map(optional(flagParser), (value) => {
183
202
  return value === true ? debugLevel : normalLevel;
@@ -195,11 +214,15 @@ function debug(options = {}) {
195
214
  *
196
215
  * @param options Configuration options for the parser.
197
216
  * @returns A {@link ValueParser} that produces a {@link LogOutput}.
217
+ * @throws {TypeError} If `options.metavar` is an empty string.
198
218
  */
199
219
  function logOutputValueParser(options = {}) {
220
+ const metavar = options.metavar ?? "FILE";
221
+ ensureNonEmptyString(metavar);
200
222
  return {
201
- $mode: "sync",
202
- metavar: options.metavar ?? "FILE",
223
+ mode: "sync",
224
+ metavar,
225
+ placeholder: { type: "console" },
203
226
  parse(input) {
204
227
  if (input === "-") return {
205
228
  success: true,
@@ -228,7 +251,8 @@ function logOutputValueParser(options = {}) {
228
251
  yield {
229
252
  kind: "file",
230
253
  type: "file",
231
- pattern: prefix
254
+ pattern: prefix,
255
+ includeHidden: basename(prefix).startsWith(".") && basename(prefix) !== ".."
232
256
  };
233
257
  }
234
258
  };
@@ -241,6 +265,7 @@ function logOutputValueParser(options = {}) {
241
265
  *
242
266
  * @param options Configuration options for the log output parser.
243
267
  * @returns A {@link Parser} that produces a {@link LogOutput} or `undefined`.
268
+ * @throws {TypeError} If `options.metavar` is an empty string.
244
269
  *
245
270
  * @example Basic usage
246
271
  * ```typescript
@@ -276,6 +301,10 @@ function logOutput(options = {}) {
276
301
  *
277
302
  * @param options Configuration options for the console sink.
278
303
  * @returns A {@link Sink} function.
304
+ * @throws {TypeError} If `options.stream` is not `"stdout"` or `"stderr"`
305
+ * when `streamResolver` is not provided.
306
+ * @throws {TypeError} If `streamResolver` returns a value other than
307
+ * `"stdout"` or `"stderr"`.
279
308
  *
280
309
  * @example Static stream selection
281
310
  * ```typescript
@@ -297,10 +326,23 @@ function logOutput(options = {}) {
297
326
  * @since 0.8.0
298
327
  */
299
328
  function createConsoleSink(options = {}) {
300
- const defaultStream = options.stream ?? "stderr";
301
329
  const streamResolver = options.streamResolver;
330
+ const defaultStream = options.stream ?? "stderr";
331
+ const invalidStreamError = (value) => {
332
+ let repr;
333
+ if (typeof value === "string") repr = JSON.stringify(value);
334
+ else if (value === null || typeof value !== "object") repr = String(value);
335
+ else try {
336
+ repr = JSON.stringify(value) ?? String(value);
337
+ } catch {
338
+ repr = String(value);
339
+ }
340
+ return /* @__PURE__ */ new TypeError(`Invalid stream: expected "stdout" or "stderr", got ${repr}.`);
341
+ };
342
+ if (!streamResolver && defaultStream !== "stdout" && defaultStream !== "stderr") throw invalidStreamError(defaultStream);
302
343
  return (record) => {
303
344
  const stream = streamResolver ? streamResolver(record.level) : defaultStream;
345
+ if (stream !== "stdout" && stream !== "stderr") throw invalidStreamError(stream);
304
346
  const messageParts = [];
305
347
  for (let i = 0; i < record.message.length; i++) {
306
348
  const part = record.message[i];
@@ -308,7 +350,8 @@ function createConsoleSink(options = {}) {
308
350
  else messageParts.push(String(part));
309
351
  }
310
352
  const formattedMessage = messageParts.join("");
311
- const timestamp = record.timestamp ? new Date(record.timestamp).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
353
+ const ts = record.timestamp;
354
+ const timestamp = new Date(ts != null && !Number.isNaN(ts) ? ts : Date.now()).toISOString();
312
355
  const category = record.category.join(".");
313
356
  const level = record.level.toUpperCase().padEnd(7);
314
357
  const line = `${timestamp} [${level}] ${category}: ${formattedMessage}`;
@@ -325,7 +368,11 @@ function createConsoleSink(options = {}) {
325
368
  * @param output The log output destination.
326
369
  * @param consoleSinkOptions Options for console sink (only used when output is console).
327
370
  * @returns A promise that resolves to a {@link Sink}.
328
- * @throws {Error} If file output is requested but `@logtape/file` is not installed.
371
+ * @throws {Error} If file output is requested but `@logtape/file` is not
372
+ * installed.
373
+ * @throws If `@logtape/file` is installed but `getFileSink(output.path)` fails
374
+ * at runtime (e.g., the target directory does not exist), the original error
375
+ * propagates as-is.
329
376
  *
330
377
  * @example Console output
331
378
  * ```typescript
@@ -345,9 +392,9 @@ function createConsoleSink(options = {}) {
345
392
  */
346
393
  async function createSink(output, consoleSinkOptions = {}) {
347
394
  if (output.type === "console") return createConsoleSink(consoleSinkOptions);
395
+ let getFileSink;
348
396
  try {
349
- const { getFileSink } = await import("@logtape/file");
350
- return getFileSink(output.path);
397
+ ({getFileSink} = await import("@logtape/file"));
351
398
  } catch (e) {
352
399
  throw new Error(`File sink requires @logtape/file package. Install it with:
353
400
  npm install @logtape/file
@@ -356,6 +403,7 @@ async function createSink(output, consoleSinkOptions = {}) {
356
403
 
357
404
  Original error: ${e}`);
358
405
  }
406
+ return getFileSink(output.path);
359
407
  }
360
408
 
361
409
  //#endregion
@@ -407,6 +455,9 @@ Original error: ${e}`);
407
455
  * // --debug
408
456
  * ```
409
457
  *
458
+ * @throws {TypeError} If the `level` discriminant is not one of `"option"`,
459
+ * `"verbosity"`, or `"debug"`, or if a log level option (`default`,
460
+ * `debugLevel`, `normalLevel`, or `baseLevel`) is not a valid log level.
410
461
  * @since 0.8.0
411
462
  */
412
463
  function loggingOptions(config) {
@@ -419,6 +470,7 @@ function loggingOptions(config) {
419
470
  const long = config.long ?? "--log-level";
420
471
  const short = config.short ?? "-l";
421
472
  const defaultLevel = config.default ?? "info";
473
+ validateLogLevel(defaultLevel, "default");
422
474
  levelParser = withDefault(option(short, long, logLevel()), defaultLevel);
423
475
  break;
424
476
  }
@@ -441,16 +493,19 @@ function loggingOptions(config) {
441
493
  levelParser = debug(debugOptions);
442
494
  break;
443
495
  }
496
+ default: throw new TypeError(`Unsupported level configuration: ${String(config.level)}. Expected "option", "verbosity", or "debug".`);
444
497
  }
445
498
  const defaultOutput = { type: "console" };
446
499
  const outputParser = withDefault(logOutput({ long: outputLong }), defaultOutput);
447
500
  if (!outputEnabled) {
448
501
  const constantOutputParser = {
449
- $mode: "sync",
502
+ mode: "sync",
450
503
  $valueType: [],
451
504
  $stateType: [],
452
505
  priority: 0,
453
506
  usage: [],
507
+ leadingNames: /* @__PURE__ */ new Set(),
508
+ acceptingAnyToken: false,
454
509
  initialState: void 0,
455
510
  parse: (context) => ({
456
511
  success: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/logtape",
3
- "version": "1.0.0-dev.908+f60f037d",
3
+ "version": "1.0.0",
4
4
  "description": "LogTape logging integration for Optique CLI parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -54,7 +54,8 @@
54
54
  },
55
55
  "sideEffects": false,
56
56
  "peerDependencies": {
57
- "@logtape/logtape": "^1.2.2"
57
+ "@logtape/file": "^2.0.4",
58
+ "@logtape/logtape": "^2.0.4"
58
59
  },
59
60
  "peerDependenciesMeta": {
60
61
  "@logtape/file": {
@@ -62,10 +63,11 @@
62
63
  }
63
64
  },
64
65
  "dependencies": {
65
- "@optique/core": "1.0.0-dev.908+f60f037d"
66
+ "@optique/core": "1.0.0"
66
67
  },
67
68
  "devDependencies": {
68
- "@logtape/logtape": "^1.2.2",
69
+ "@logtape/file": "^2.0.4",
70
+ "@logtape/logtape": "^2.0.4",
69
71
  "@types/node": "^20.19.9",
70
72
  "tsdown": "^0.13.0",
71
73
  "typescript": "^5.8.3"