@vzn/vx 0.0.400 → 0.0.401

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.400",
3
+ "version": "0.0.401",
4
4
  "description": "The fastest task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -21,10 +21,10 @@
21
21
  "node": ">=18"
22
22
  },
23
23
  "optionalDependencies": {
24
- "@vzn/vx-linux-x64": "0.0.400",
25
- "@vzn/vx-linux-arm64": "0.0.400",
26
- "@vzn/vx-darwin-x64": "0.0.400",
27
- "@vzn/vx-darwin-arm64": "0.0.400"
24
+ "@vzn/vx-linux-x64": "0.0.401",
25
+ "@vzn/vx-linux-arm64": "0.0.401",
26
+ "@vzn/vx-darwin-x64": "0.0.401",
27
+ "@vzn/vx-darwin-arm64": "0.0.401"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
package/src/index.ts CHANGED
@@ -149,6 +149,7 @@ export type {
149
149
  VxPlugin,
150
150
  PluginHooks,
151
151
  PluginOrigin,
152
+ PluginOptionKinds,
152
153
  CacheContext,
153
154
  ExecutorContext,
154
155
  CommandContext,
@@ -87,6 +87,7 @@ export {
87
87
  type VxPlugin,
88
88
  definePlugin,
89
89
  refuseUnknownOptions,
90
+ type PluginOptionKinds,
90
91
  type PluginHooks,
91
92
  type PluginOrigin,
92
93
  } from './plugin.js'
@@ -483,29 +483,69 @@ export function definePlugin(origin: PluginOrigin, hooks: PluginHooks): VxPlugin
483
483
  return { ...hooks, name, [PLUGIN_PACKAGE]: name } as VxPlugin
484
484
  }
485
485
 
486
+ /** What `refuseUnknownOptions` holds an option's value to. */
487
+ type OptionKind<V> = [V] extends [string]
488
+ ? 'string'
489
+ : [V] extends [number]
490
+ ? 'number'
491
+ : [V] extends [boolean]
492
+ ? 'boolean'
493
+ : [V] extends [(...args: never[]) => unknown]
494
+ ? 'function'
495
+ : [V] extends [object]
496
+ ? 'object'
497
+ : 'any'
498
+
499
+ /**
500
+ * Every option a factory takes, each with the one kind its type allows
501
+ * (`'any'` for a union of kinds, such as `false | { … }`). Derived from
502
+ * the options interface, so the type checker refuses a missing option, an
503
+ * extra one or a wrong kind.
504
+ */
505
+ export type PluginOptionKinds<T> = {
506
+ readonly [K in keyof Required<T>]-?: OptionKind<Required<T>[K]>
507
+ }
508
+
486
509
  /**
487
- * Refuse an option a plugin factory does not take, as core refuses an
488
- * unknown config field. Bun strips a config's types, so a misspelt option
489
- * (`reapi({ endpont })`) reached the factory, which read it as unset and
490
- * quietly declined: the run went local with no word. `factory` names the
491
- * call in the message (`reapi()`), `known` is the options the factory reads.
510
+ * Refuse an option a plugin factory does not take, or a value of the wrong
511
+ * kind, as core refuses an unknown config field. Bun strips a config's
512
+ * types, so a misspelt option (`reapi({ endpont })`) reached the factory,
513
+ * which read it as unset and quietly declined, and a string where a number
514
+ * or a boolean belongs (`process.env.X`) was misread or threw a bare
515
+ * TypeError. `factory` names the call in the message (`reapi()`).
492
516
  */
493
- export function refuseUnknownOptions(
517
+ export function refuseUnknownOptions<T>(
494
518
  factory: string,
495
519
  options: unknown,
496
- known: readonly string[],
520
+ kinds: PluginOptionKinds<T>,
497
521
  ): void {
498
522
  if (options === undefined) return
499
523
  if (options === null || typeof options !== 'object' || Array.isArray(options)) {
500
524
  throw new UserError(`${factory}: options must be an object`)
501
525
  }
502
- for (const key of Object.keys(options)) {
503
- if (known.includes(key)) continue
504
- const near = nearest(key, known)
505
- throw new UserError(
506
- `${factory} has unknown option "${key}" (allowed: ${[...known].sort().join(', ')})` +
507
- (near === undefined ? '' : ` — did you mean ${near}?`),
508
- )
526
+ const known = kinds as Readonly<Record<string, string>>
527
+ const names = Object.keys(known)
528
+ for (const [key, value] of Object.entries(options)) {
529
+ const kind = known[key]
530
+ if (kind === undefined) {
531
+ const near = nearest(key, names)
532
+ throw new UserError(
533
+ `${factory} has unknown option "${key}" (allowed: ${[...names].sort().join(', ')})` +
534
+ (near === undefined ? '' : ` — did you mean ${near}?`),
535
+ )
536
+ }
537
+ if (value === undefined || kind === 'any') continue
538
+ const ok =
539
+ kind === 'object'
540
+ ? value !== null && typeof value === 'object' && !Array.isArray(value)
541
+ : typeof value === kind
542
+ if (!ok) {
543
+ const got =
544
+ typeof value === 'function' ? 'a function' : (JSON.stringify(value) ?? String(value))
545
+ throw new UserError(
546
+ `${factory} option "${key}" must be ${kind === 'object' ? 'an' : 'a'} ${kind}, got ${got}`,
547
+ )
548
+ }
509
549
  }
510
550
  }
511
551