@wolfstar/http-framework 4.0.2 → 5.0.0-next-20260919094537

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.
@@ -1,4 +1,4 @@
1
- import { l as ResolvedImportsConfig } from "./resolve-CBovxOO3.js";
1
+ import { l as ResolvedImportsConfig } from "./resolve-Blb-IcFd.js";
2
2
  import unplugin from "unimport/unplugin";
3
3
  //#region src/auto-imports.d.ts
4
4
  export interface AutoImportsPluginOptions extends Pick<ResolvedImportsConfig, 'dirs' | 'presets' | 'exclude' | 'dts'> {
@@ -1,2 +1,2 @@
1
- import { A as StarsTypecheckConfig, B as LoadConfigFileOptions, C as StarsExperimentalConfig, D as StarsNitroConfig, E as StarsImportsConfig, F as loadStarsConfig, H as discoverConfigFile, I as ConfigError, L as ConfigErrorOptions, M as StarsViteConfig, N as defineConfig, O as StarsTsdownConfig, P as LoadStarsConfigOptions, R as CONFIG_EXTENSIONS, S as StarsDevConfig, T as StarsI18nCodegenConfig, U as loadConfigFile, V as LoadedConfigFile, _ as StarsBuildConfig, a as ResolvedDevConfig, b as StarsCompatibilityVersion, c as ResolvedI18nCodegenConfig, d as ResolvedStarsConfig, f as ResolvedTunnelConfig, g as resolveStarsConfig, h as readProjectEnvFiles, i as ResolvedCodegenConfig, j as StarsTypechecker, k as StarsTunnelConfig, l as ResolvedImportsConfig, m as displayPath, n as ResolveConfigOptions, o as ResolvedExperimentalConfig, p as ResolvedTypecheckConfig, r as ResolvedBuildConfig, s as ResolvedFutureConfig, t as PackageJsonLike, u as ResolvedNitroConfig, v as StarsBuildTool, w as StarsFutureConfig, x as StarsConfig, y as StarsCodegenConfig, z as CONFIG_FILE_NAMES } from "./resolve-CBovxOO3.js";
2
- export { CONFIG_EXTENSIONS, CONFIG_FILE_NAMES, ConfigError, type ConfigErrorOptions, type LoadConfigFileOptions, LoadStarsConfigOptions, type LoadedConfigFile, type PackageJsonLike, type ResolveConfigOptions, type ResolvedBuildConfig, type ResolvedCodegenConfig, type ResolvedDevConfig, type ResolvedExperimentalConfig, type ResolvedFutureConfig, type ResolvedI18nCodegenConfig, type ResolvedImportsConfig, type ResolvedNitroConfig, type ResolvedStarsConfig, type ResolvedTunnelConfig, type ResolvedTypecheckConfig, StarsBuildConfig, StarsBuildTool, StarsCodegenConfig, StarsCompatibilityVersion, StarsConfig, StarsDevConfig, StarsExperimentalConfig, StarsFutureConfig, StarsI18nCodegenConfig, StarsImportsConfig, StarsNitroConfig, StarsTsdownConfig, StarsTunnelConfig, StarsTypecheckConfig, StarsTypechecker, StarsViteConfig, defineConfig, discoverConfigFile, displayPath, loadConfigFile, loadStarsConfig, readProjectEnvFiles, resolveStarsConfig };
1
+ import { A as StarsTypecheckConfig, B as LoadConfigFileOptions, C as StarsExperimentalConfig, D as StarsNitroConfig, E as StarsImportsConfig, F as loadStarsConfig, H as discoverConfigFile, I as ConfigDiagnosticCode, L as configDiagnostics, M as StarsViteConfig, N as defineConfig, O as StarsTsdownConfig, P as LoadStarsConfigOptions, R as CONFIG_EXTENSIONS, S as StarsDevConfig, T as StarsI18nCodegenConfig, U as loadConfigFile, V as LoadedConfigFile, _ as StarsBuildConfig, a as ResolvedDevConfig, b as StarsCompatibilityVersion, c as ResolvedI18nCodegenConfig, d as ResolvedStarsConfig, f as ResolvedTunnelConfig, g as resolveStarsConfig, h as readProjectEnvFiles, i as ResolvedCodegenConfig, j as StarsTypechecker, k as StarsTunnelConfig, l as ResolvedImportsConfig, m as displayPath, n as ResolveConfigOptions, o as ResolvedExperimentalConfig, p as ResolvedTypecheckConfig, r as ResolvedBuildConfig, s as ResolvedFutureConfig, t as PackageJsonLike, u as ResolvedNitroConfig, v as StarsBuildTool, w as StarsFutureConfig, x as StarsConfig, y as StarsCodegenConfig, z as CONFIG_FILE_NAMES } from "./resolve-Blb-IcFd.js";
2
+ export { CONFIG_EXTENSIONS, CONFIG_FILE_NAMES, type ConfigDiagnosticCode, type LoadConfigFileOptions, LoadStarsConfigOptions, type LoadedConfigFile, type PackageJsonLike, type ResolveConfigOptions, type ResolvedBuildConfig, type ResolvedCodegenConfig, type ResolvedDevConfig, type ResolvedExperimentalConfig, type ResolvedFutureConfig, type ResolvedI18nCodegenConfig, type ResolvedImportsConfig, type ResolvedNitroConfig, type ResolvedStarsConfig, type ResolvedTunnelConfig, type ResolvedTypecheckConfig, StarsBuildConfig, StarsBuildTool, StarsCodegenConfig, StarsCompatibilityVersion, StarsConfig, StarsDevConfig, StarsExperimentalConfig, StarsFutureConfig, StarsI18nCodegenConfig, StarsImportsConfig, StarsNitroConfig, StarsTsdownConfig, StarsTunnelConfig, StarsTypecheckConfig, StarsTypechecker, StarsViteConfig, configDiagnostics, defineConfig, discoverConfigFile, displayPath, loadConfigFile, loadStarsConfig, readProjectEnvFiles, resolveStarsConfig };
@@ -1,29 +1,132 @@
1
- import { t as _defineProperty } from "./defineProperty-DeZQsruP.js";
2
1
  import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
3
2
  import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import { defineDiagnostics } from "nostics";
4
4
 
5
5
  //#region src/lib/config/errors.ts
6
6
  /**
7
- * A `stars.config.*` error: an invalid option, or a file that failed to load or parse.
7
+ * Structured, stable diagnostic codes for every way a `stars.config.*` can fail to load or validate.
8
8
  *
9
- * This is a plain data error (no exit code or terminal formatting) so it stays meaningful outside a CLI, e.g. for a
10
- * dashboard or test that calls {@link loadStarsConfig} directly. `@wolfstar/cli` maps it to exit code `2` and renders
11
- * `message`, `path`, `file` and `hint` for the terminal.
9
+ * This is a plain data error (no exit code or terminal formatting), so nothing here reports anywhere on its own
10
+ * `reporters` stays empty and each call only builds and returns a `Diagnostic`. That keeps it meaningful outside a
11
+ * CLI, e.g. for a dashboard or test that calls {@link loadStarsConfig} directly; `@wolfstar/cli` is what renders it
12
+ * and picks an exit code (`exitCodeOf`).
13
+ *
14
+ * The `sources` field (populated with the configuration file, when there is one) carries the "which file" grounding
15
+ * `ConfigError` used to expose as `.file`; the "which option" grounding it exposed as `.path` is folded directly into
16
+ * every `why`/`fix` message instead, the way every other diagnostic code already reads.
17
+ *
18
+ * `why` and `fix` are always given the same, fully-typed params object (even when one of them ignores part of it):
19
+ * a bare `() => value` loses nostics' param-type inference (a zero-arg function widens to `unknown` params), so every
20
+ * entry here spells out its shape on both sides instead.
12
21
  */
13
- var ConfigError = class extends Error {
14
- constructor(message, options) {
15
- super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
16
- _defineProperty(this, "code", void 0);
17
- _defineProperty(this, "hint", void 0);
18
- _defineProperty(this, "path", void 0);
19
- _defineProperty(this, "file", void 0);
20
- this.name = "ConfigError";
21
- this.code = options.code;
22
- this.hint = options.hint ?? null;
23
- this.path = options.path ?? null;
24
- this.file = options.file ?? null;
22
+ const configDiagnostics = defineDiagnostics({
23
+ docsBase: (code) => `https://stars-components.js.org/docs/config/errors#${code.toLowerCase()}`,
24
+ reporters: [],
25
+ codes: {
26
+ ROOT_NOT_FOUND: {
27
+ why: (p) => `The project root does not exist: ${p.root}`,
28
+ fix: (_p) => "Point `root` to an existing directory, relative to the configuration file."
29
+ },
30
+ PACKAGE_JSON_INVALID: {
31
+ why: (p) => `Failed to parse ${p.file}: ${p.message}`,
32
+ fix: (_p) => "Fix the JSON syntax of the package.json file."
33
+ },
34
+ ENTRY_NOT_FOUND: {
35
+ why: (p) => `The entry file does not exist: ${p.entry}`,
36
+ fix: (_p) => "Point `entry` to the file that starts the bot, relative to the project root."
37
+ },
38
+ ENTRY_DEFAULT_NOT_FOUND: {
39
+ why: (p) => `Could not find the entry file in ${p.root}`,
40
+ fix: (p) => `Set \`entry\` in the configuration, or create one of ${p.defaults}.`
41
+ },
42
+ INVALID_BUILD_TOOL: {
43
+ why: (p) => `Unknown build tool "${p.tool}"`,
44
+ fix: (_p) => "Use one of 'tsdown', 'tsc', 'vite', 'none' or 'auto'."
45
+ },
46
+ EXPERIMENTAL_BUILD_TOOL: {
47
+ why: (p) => `The '${p.tool}' build tool is experimental`,
48
+ fix: (p) => `Set \`${p.flag}\` to true to use it.`
49
+ },
50
+ BUILD_TOOL_REQUIRED: {
51
+ why: (p) => `The entry ${p.entry} is TypeScript but the build tool is 'none'`,
52
+ fix: (_p) => "Set `build.tool` to 'tsdown' or 'tsc', or point `entry` to a JavaScript file."
53
+ },
54
+ TSCONFIG_EXPLICIT_NOT_FOUND: {
55
+ why: (p) => `The tsconfig file does not exist: ${p.tsconfig}`,
56
+ fix: (p) => `Point \`${p.path}\` to an existing tsconfig.json, relative to the project root.`
57
+ },
58
+ TSCONFIG_NOT_FOUND: {
59
+ why: (p) => `Could not find a tsconfig.json in ${p.root}`,
60
+ fix: (p) => `Create src/tsconfig.json or tsconfig.json, or set \`${p.suggestion}\`.`
61
+ },
62
+ TSDOWN_OPTIONS_REQUIRE_TSDOWN: {
63
+ why: (_p) => "`tsdown` options need the `tsdown` build tool",
64
+ fix: (p) => `Set \`build.tool\` to 'tsdown', or remove \`tsdown\` (the build tool is '${p.tool}').`
65
+ },
66
+ VITE_OPTIONS_REQUIRE_VITE: {
67
+ why: (_p) => "`vite` options need the `vite` build tool",
68
+ fix: (p) => `Set \`build.tool\` to 'vite' with \`experimental.enableVite\`, or remove \`vite\` (the build tool is '${p.tool}').`
69
+ },
70
+ TSDOWN_CONFIG_FILE_UNSUPPORTED: {
71
+ why: (p) => `\`${p.file}\` is not used with compatibility version ${p.version}`,
72
+ fix: (p) => `Move its options into \`tsdown\` here, drop the ${p.file} configuration, or set \`future.compatibilityVersion\` to ${p.legacyVersion}.`
73
+ },
74
+ INVALID_TYPE: {
75
+ why: (p) => `\`${p.path}\` must be ${p.expected}, received ${describeValue(p.value)}`,
76
+ fix: (p) => p.fix
77
+ },
78
+ INVALID_COMPATIBILITY_VERSION: {
79
+ why: (p) => `Unknown compatibility version ${describeValue(p.value)}`,
80
+ fix: (p) => `Use ${p.legacyVersion} for the legacy build pipeline or ${p.latestVersion} for today's defaults.`
81
+ },
82
+ UNKNOWN_OPTION: {
83
+ why: (p) => `Unknown option \`${p.path}\``,
84
+ fix: (p) => `Known options${p.parent ? ` of \`${p.parent}\`` : ""}: ${p.known}.`
85
+ },
86
+ EXPERIMENT_REQUIRED: {
87
+ why: (p) => `\`${p.path}\` needs \`${p.requires}\``,
88
+ fix: (p) => `Set \`${p.requires}\` to true as well, or drop \`${p.drop}\`.`
89
+ },
90
+ IMPORTS_REQUIRE_TSDOWN: {
91
+ why: (_p) => "`imports` requires the `tsdown` build tool",
92
+ fix: (_p) => "Set `build.tool` to 'tsdown', or remove `imports`/set it to `false`."
93
+ },
94
+ LOCALES_NOT_FOUND: {
95
+ why: (p) => `The locales directory does not exist: ${p.locales}`,
96
+ fix: (_p) => "Point `codegen.i18n.locales` to the base locale directory, relative to the project root."
97
+ },
98
+ INVALID_URL: {
99
+ why: (p) => `Invalid URL "${p.url}"`,
100
+ fix: (p) => p.fix
101
+ },
102
+ TUNNEL_URL_NOT_HTTPS: {
103
+ why: (p) => `The tunnel URL must be https, received "${p.url}"`,
104
+ fix: (_p) => "Discord only accepts an https interactions endpoint."
105
+ },
106
+ INVALID_TYPECHECKER: {
107
+ why: (p) => `Unknown type checker "${p.checker}"`,
108
+ fix: (_p) => "Use one of 'tsc', 'golar', 'tsz' or 'auto'."
109
+ },
110
+ CONFIG_NOT_FOUND: {
111
+ why: (p) => `Configuration file not found: ${p.file}`,
112
+ fix: (p) => `Pass an existing file to --config, or create one of ${p.names} in ${p.cwd}.`
113
+ },
114
+ CONFIG_LOAD_FAILED: {
115
+ why: (p) => `Failed to load the configuration: ${p.message}`,
116
+ fix: (_p) => "The file must be valid TypeScript/JavaScript and export the configuration as its default export."
117
+ },
118
+ CONFIG_NOT_OBJECT: {
119
+ why: (_p) => "The configuration file must export an object as its default export.",
120
+ fix: (_p) => "Use `export default defineConfig({ ... })` from '@wolfstar/http-framework/config'."
121
+ }
25
122
  }
26
- };
123
+ });
124
+ function describeValue(value) {
125
+ if (value === null) return "null";
126
+ if (Array.isArray(value)) return "an array";
127
+ if (typeof value === "string") return `"${value}"`;
128
+ return typeof value === "object" ? "an object" : `${typeof value} ${String(value)}`;
129
+ }
27
130
 
28
131
  //#endregion
29
132
  //#region src/lib/config/load.ts
@@ -55,9 +158,10 @@ async function loadConfigFile(options) {
55
158
  let file;
56
159
  if (options.configFile) {
57
160
  file = resolve(cwd, options.configFile);
58
- if (!isFile$1(file)) throw new ConfigError(`Configuration file not found: ${file}`, {
59
- code: "CONFIG_NOT_FOUND",
60
- hint: `Pass an existing file to --config, or create one of ${CONFIG_FILE_NAMES.join(", ")} in ${cwd}.`
161
+ if (!isFile$1(file)) throw configDiagnostics.CONFIG_NOT_FOUND({
162
+ file,
163
+ cwd,
164
+ names: CONFIG_FILE_NAMES.join(", ")
61
165
  });
62
166
  } else {
63
167
  file = discoverConfigFile(cwd);
@@ -83,18 +187,13 @@ async function loadConfigFile(options) {
83
187
  loaded = layer ? layer.config : result.config;
84
188
  } catch (error) {
85
189
  const message = error instanceof Error ? error.message : String(error);
86
- throw new ConfigError(`Failed to load the configuration: ${message}`, {
87
- code: "CONFIG_LOAD_FAILED",
88
- file,
89
- hint: "The file must be valid TypeScript/JavaScript and export the configuration as its default export.",
90
- cause: error
190
+ throw configDiagnostics.CONFIG_LOAD_FAILED({
191
+ message,
192
+ cause: error,
193
+ sources: [file]
91
194
  });
92
195
  }
93
- if (loaded === null || typeof loaded !== "object" || Array.isArray(loaded)) throw new ConfigError("The configuration file must export an object as its default export.", {
94
- code: "CONFIG_NOT_OBJECT",
95
- file,
96
- hint: "Use `export default defineConfig({ ... })` from '@wolfstar/http-framework/config'."
97
- });
196
+ if (loaded === null || typeof loaded !== "object" || Array.isArray(loaded)) throw configDiagnostics.CONFIG_NOT_OBJECT({ sources: [file] });
98
197
  return {
99
198
  configFile: file,
100
199
  config: loaded
@@ -174,7 +273,7 @@ const TYPESCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
174
273
  /**
175
274
  * Applies defaults, validates every option and resolves all paths to absolute ones.
176
275
  *
177
- * @throws {ConfigError} with an actionable `hint` on the first invalid option.
276
+ * @throws {Diagnostic} (from `nostics`, via {@link configDiagnostics}) with an actionable `fix` on the first invalid option.
178
277
  */
179
278
  function resolveStarsConfig(options) {
180
279
  const cwd = resolve(options.cwd);
@@ -196,8 +295,8 @@ function resolveStarsConfig(options) {
196
295
  ]);
197
296
  const baseDirectory = file ? dirname(file) : cwd;
198
297
  const root = resolve(baseDirectory, validator.string(config.root, "root") ?? ".");
199
- if (!isDirectory(root)) throw validator.error(`The project root does not exist: ${root}`, "root", "ROOT_NOT_FOUND", "Point `root` to an existing directory, relative to the configuration file.");
200
- const packageJson = readPackageJson(root);
298
+ if (!isDirectory(root)) throw validator.error(configDiagnostics.ROOT_NOT_FOUND, { root });
299
+ const packageJson = readPackageJson(root, validator);
201
300
  const experimental = resolveExperimental(config.experimental ?? {}, validator);
202
301
  const future = resolveFuture(config.future ?? {}, validator);
203
302
  const entry = resolveEntry(root, validator.string(config.entry, "entry"), validator);
@@ -207,8 +306,8 @@ function resolveStarsConfig(options) {
207
306
  const dev = resolveDev(root, entry, packageJson, config.dev ?? {}, env, validator);
208
307
  const codegen = resolveCodegen(root, config.codegen ?? {}, validator);
209
308
  const imports = resolveImports(root, build.tool, future, config.imports, validator);
210
- if (Object.keys(tsdown).length > 0 && build.tool !== "tsdown") throw validator.error("`tsdown` options need the `tsdown` build tool", "tsdown", "TSDOWN_OPTIONS_REQUIRE_TSDOWN", `Set \`build.tool\` to 'tsdown', or remove \`tsdown\` (the build tool is '${build.tool}').`);
211
- if (Object.keys(vite).length > 0 && build.tool !== "vite") throw validator.error("`vite` options need the `vite` build tool", "vite", "VITE_OPTIONS_REQUIRE_VITE", `Set \`build.tool\` to 'vite' with \`experimental.enableVite\`, or remove \`vite\` (the build tool is '${build.tool}').`);
309
+ if (Object.keys(tsdown).length > 0 && build.tool !== "tsdown") throw validator.error(configDiagnostics.TSDOWN_OPTIONS_REQUIRE_TSDOWN, { tool: build.tool });
310
+ if (Object.keys(vite).length > 0 && build.tool !== "vite") throw validator.error(configDiagnostics.VITE_OPTIONS_REQUIRE_VITE, { tool: build.tool });
212
311
  return {
213
312
  configFile: file,
214
313
  cwd,
@@ -236,14 +335,17 @@ function displayPath(root, path) {
236
335
  function resolveEntry(root, configured, validator) {
237
336
  if (configured !== void 0) {
238
337
  const entry = resolve(root, configured);
239
- if (!isFile(entry)) throw validator.error(`The entry file does not exist: ${entry}`, "entry", "ENTRY_NOT_FOUND", "Point `entry` to the file that starts the bot, relative to the project root.");
338
+ if (!isFile(entry)) throw validator.error(configDiagnostics.ENTRY_NOT_FOUND, { entry });
240
339
  return entry;
241
340
  }
242
341
  for (const candidate of DEFAULT_ENTRIES) {
243
342
  const entry = join(root, candidate);
244
343
  if (isFile(entry)) return entry;
245
344
  }
246
- throw validator.error(`Could not find the entry file in ${root}`, "entry", "ENTRY_NOT_FOUND", `Set \`entry\` in the configuration, or create one of ${DEFAULT_ENTRIES.join(", ")}.`);
345
+ throw validator.error(configDiagnostics.ENTRY_DEFAULT_NOT_FOUND, {
346
+ root,
347
+ defaults: DEFAULT_ENTRIES.join(", ")
348
+ });
247
349
  }
248
350
  function resolveBuild(root, entry, packageJson, config, experimental, future, hasTsdownOptions, validator) {
249
351
  validator.knownKeys(config, "build", [
@@ -252,26 +354,39 @@ function resolveBuild(root, entry, packageJson, config, experimental, future, ha
252
354
  "tsconfig"
253
355
  ]);
254
356
  const requested = validator.string(config.tool, "build.tool") ?? "auto";
255
- if (!BUILD_TOOLS.has(requested)) throw validator.error(`Unknown build tool "${requested}"`, "build.tool", "INVALID_BUILD_TOOL", "Use one of 'tsdown', 'tsc', 'vite', 'none' or 'auto'.");
256
- if (requested === "vite" && !experimental.enableVite) throw validator.error("The 'vite' build tool is experimental", "build.tool", "EXPERIMENT_REQUIRED", "Set `experimental.enableVite` to true to use it.");
357
+ if (!BUILD_TOOLS.has(requested)) throw validator.error(configDiagnostics.INVALID_BUILD_TOOL, { tool: requested });
358
+ if (requested === "vite" && !experimental.enableVite) throw validator.error(configDiagnostics.EXPERIMENTAL_BUILD_TOOL, {
359
+ tool: "vite",
360
+ flag: "experimental.enableVite"
361
+ });
257
362
  const isTypeScriptEntry = TYPESCRIPT_EXTENSIONS.has(extname(entry));
258
363
  const tool = requested === "auto" ? detectBuildTool(root, packageJson, isTypeScriptEntry, experimental, future, hasTsdownOptions) : requested;
259
- if (tool === "none" && isTypeScriptEntry) throw validator.error(`The entry ${displayPath(root, entry)} is TypeScript but the build tool is 'none'`, "build.tool", "BUILD_TOOL_REQUIRED", "Set `build.tool` to 'tsdown' or 'tsc', or point `entry` to a JavaScript file.");
364
+ if (tool === "none" && isTypeScriptEntry) throw validator.error(configDiagnostics.BUILD_TOOL_REQUIRED, { entry: displayPath(root, entry) });
260
365
  const defaultOutDir = experimental.enableNitro ? ".output" : "dist";
261
366
  const outDir = resolve(root, validator.string(config.outDir, "build.outDir") ?? defaultOutDir);
262
367
  let tsconfig = null;
263
368
  const configuredTsconfig = validator.string(config.tsconfig, "build.tsconfig");
264
369
  if (configuredTsconfig !== void 0) {
265
370
  tsconfig = resolve(root, configuredTsconfig);
266
- if (!isFile(tsconfig)) throw validator.error(`The tsconfig file does not exist: ${tsconfig}`, "build.tsconfig", "TSCONFIG_NOT_FOUND", "Point `build.tsconfig` to an existing tsconfig.json, relative to the project root.");
371
+ if (!isFile(tsconfig)) throw validator.error(configDiagnostics.TSCONFIG_EXPLICIT_NOT_FOUND, {
372
+ tsconfig,
373
+ path: "build.tsconfig"
374
+ });
267
375
  } else if (tool === "tsc" || tool === "tsdown") {
268
376
  tsconfig = [join(root, "src", "tsconfig.json"), join(root, "tsconfig.json")].find((candidate) => isFile(candidate)) ?? null;
269
- if (!tsconfig && tool === "tsc") throw validator.error(`Could not find a tsconfig.json in ${root}`, "build.tsconfig", "TSCONFIG_NOT_FOUND", "Create src/tsconfig.json or tsconfig.json, or set `build.tsconfig`.");
377
+ if (!tsconfig && tool === "tsc") throw validator.error(configDiagnostics.TSCONFIG_NOT_FOUND, {
378
+ root,
379
+ suggestion: "build.tsconfig"
380
+ });
270
381
  }
271
382
  const output = experimental.enableNitro ? join(outDir, "server", "index.mjs") : tool === "none" ? entry : resolveBuildOutput(root, entry, outDir, packageJson);
272
383
  let configFile = findConfigFile(root, tool === "tsdown" ? TSDOWN_CONFIG_FILES : tool === "vite" ? VITE_CONFIG_FILES : []);
273
384
  if (tool === "tsdown" && configFile === null && packageJson?.tsdown !== void 0) configFile = join(root, "package.json");
274
- if (tool === "tsdown" && configFile !== null && future.compatibilityVersion >= 4) throw validator.error(`\`${displayPath(root, configFile)}\` is not used with compatibility version ${future.compatibilityVersion}`, "tsdown", "TSDOWN_CONFIG_FILE_UNSUPPORTED", `Move its options into \`tsdown\` here, drop the ${displayPath(root, configFile)} configuration, or set \`future.compatibilityVersion\` to ${3}.`);
385
+ if (tool === "tsdown" && configFile !== null && future.compatibilityVersion >= 4) throw validator.error(configDiagnostics.TSDOWN_CONFIG_FILE_UNSUPPORTED, {
386
+ file: displayPath(root, configFile),
387
+ version: future.compatibilityVersion,
388
+ legacyVersion: 3
389
+ });
275
390
  return {
276
391
  tool,
277
392
  outDir,
@@ -301,11 +416,15 @@ function detectBuildTool(root, packageJson, isTypeScriptEntry, experimental, fut
301
416
  * Resolves the Nuxt-style compatibility block. Version 4 is the default; version 3 remains an explicit legacy mode.
302
417
  */
303
418
  function resolveFuture(config, validator) {
304
- if (config === null || typeof config !== "object" || Array.isArray(config)) throw validator.error("`future` must be an object", "future", "INVALID_TYPE", "Use `{ compatibilityVersion }`.");
419
+ if (config === null || typeof config !== "object" || Array.isArray(config)) throw validator.typeError("future", "an object", config, "Use `{ compatibilityVersion }`.");
305
420
  validator.knownKeys(config, "future", ["compatibilityVersion"]);
306
421
  const version = config.compatibilityVersion;
307
422
  if (version === void 0) return { compatibilityVersion: 4 };
308
- if (typeof version !== "number" || !COMPATIBILITY_VERSIONS.has(version)) throw validator.error(`Unknown compatibility version ${describe(version)}`, "future.compatibilityVersion", "INVALID_COMPATIBILITY_VERSION", `Use ${3} for the legacy build pipeline or ${4} for today's defaults.`);
423
+ if (typeof version !== "number" || !COMPATIBILITY_VERSIONS.has(version)) throw validator.error(configDiagnostics.INVALID_COMPATIBILITY_VERSION, {
424
+ value: version,
425
+ legacyVersion: 3,
426
+ latestVersion: 4
427
+ });
309
428
  return { compatibilityVersion: version };
310
429
  }
311
430
  /**
@@ -314,7 +433,7 @@ function resolveFuture(config, validator) {
314
433
  * confusing runtime failure.
315
434
  */
316
435
  function resolveExperimental(config, validator) {
317
- if (config === null || typeof config !== "object" || Array.isArray(config)) throw validator.error("`experimental` must be an object", "experimental", "INVALID_TYPE", "Use `{ enableVite, enableExternalVite, enableNitro, nitro }`.");
436
+ if (config === null || typeof config !== "object" || Array.isArray(config)) throw validator.typeError("experimental", "an object", config, "Use `{ enableVite, enableExternalVite, enableNitro, nitro }`.");
318
437
  validator.knownKeys(config, "experimental", [
319
438
  "enableVite",
320
439
  "enableExternalVite",
@@ -324,11 +443,23 @@ function resolveExperimental(config, validator) {
324
443
  const enableVite = validator.boolean(config.enableVite, "experimental.enableVite") ?? false;
325
444
  const enableExternalVite = validator.boolean(config.enableExternalVite, "experimental.enableExternalVite") ?? false;
326
445
  const enableNitro = validator.boolean(config.enableNitro, "experimental.enableNitro") ?? false;
327
- if (enableExternalVite && !enableVite) throw validator.error("`experimental.enableExternalVite` needs `experimental.enableVite`", "experimental.enableExternalVite", "EXPERIMENT_REQUIRED", "Set `experimental.enableVite` to true as well, or drop `enableExternalVite`.");
328
- if (enableNitro && !enableVite) throw validator.error("`experimental.enableNitro` needs `experimental.enableVite`", "experimental.enableNitro", "EXPERIMENT_REQUIRED", "Set `experimental.enableVite` to true as well, or drop `enableNitro`.");
446
+ if (enableExternalVite && !enableVite) throw validator.error(configDiagnostics.EXPERIMENT_REQUIRED, {
447
+ path: "experimental.enableExternalVite",
448
+ requires: "experimental.enableVite",
449
+ drop: "enableExternalVite"
450
+ });
451
+ if (enableNitro && !enableVite) throw validator.error(configDiagnostics.EXPERIMENT_REQUIRED, {
452
+ path: "experimental.enableNitro",
453
+ requires: "experimental.enableVite",
454
+ drop: "enableNitro"
455
+ });
329
456
  const rawNitro = "nitro" in config ? config.nitro : void 0;
330
- if (rawNitro !== void 0 && !enableNitro) throw validator.error("`experimental.nitro` needs `experimental.enableNitro`", "experimental.nitro", "EXPERIMENT_REQUIRED", "Set `experimental.enableNitro` to true as well, or drop `nitro`.");
331
- if (rawNitro !== void 0 && (rawNitro === null || typeof rawNitro !== "object" || Array.isArray(rawNitro))) throw validator.error("`experimental.nitro` must be an object", "experimental.nitro", "INVALID_TYPE", "Use `{ preset }`.");
457
+ if (rawNitro !== void 0 && !enableNitro) throw validator.error(configDiagnostics.EXPERIMENT_REQUIRED, {
458
+ path: "experimental.nitro",
459
+ requires: "experimental.enableNitro",
460
+ drop: "nitro"
461
+ });
462
+ if (rawNitro !== void 0 && (rawNitro === null || typeof rawNitro !== "object" || Array.isArray(rawNitro))) throw validator.typeError("experimental.nitro", "an object", rawNitro, "Use `{ preset }`.");
332
463
  if (rawNitro) validator.knownKeys(rawNitro, "experimental.nitro", ["preset"]);
333
464
  return {
334
465
  enableVite,
@@ -412,7 +543,10 @@ function resolveDev(root, entry, packageJson, config, env, validator) {
412
543
  if (url !== null) try {
413
544
  new URL(url);
414
545
  } catch {
415
- throw validator.error(`Invalid URL "${url}"`, "dev.url", "INVALID_URL", "Use an absolute URL such as http://localhost:3000.");
546
+ throw validator.error(configDiagnostics.INVALID_URL, {
547
+ url,
548
+ fix: "Use an absolute URL such as http://localhost:3000."
549
+ });
416
550
  }
417
551
  else {
418
552
  const port = devEnv.HTTP_PORT ?? env.HTTP_PORT ?? readDevPortFromEnvFile(root, env.NODE_ENV ?? "development") ?? String(3e3);
@@ -451,16 +585,19 @@ function resolveTypecheck(root, packageJson, config, validator) {
451
585
  let configured;
452
586
  let requestedChecker = "auto";
453
587
  if (config !== true) {
454
- if (config === null || typeof config !== "object" || Array.isArray(config)) throw validator.error("`dev.typecheck` must be a boolean or an object", "dev.typecheck", "INVALID_TYPE", "Use `true` to type-check with the project tsconfig, `{ tsconfig }` to pick one, or `false` to disable it.");
588
+ if (config === null || typeof config !== "object" || Array.isArray(config)) throw validator.typeError("dev.typecheck", "a boolean or an object", config, "Use `true` to type-check with the project tsconfig, `{ tsconfig }` to pick one, or `false` to disable it.");
455
589
  validator.knownKeys(config, "dev.typecheck", ["tsconfig", "checker"]);
456
590
  configured = validator.string(config.tsconfig, "dev.typecheck.tsconfig");
457
591
  requestedChecker = validator.string(config.checker, "dev.typecheck.checker") ?? "auto";
458
- if (!TYPECHECKERS.has(requestedChecker)) throw validator.error(`Unknown type checker "${requestedChecker}"`, "dev.typecheck.checker", "INVALID_TYPECHECKER", "Use one of 'tsc', 'golar', 'tsz' or 'auto'.");
592
+ if (!TYPECHECKERS.has(requestedChecker)) throw validator.error(configDiagnostics.INVALID_TYPECHECKER, { checker: requestedChecker });
459
593
  }
460
594
  const checker = requestedChecker === "auto" ? detectTypechecker(packageJson) : requestedChecker;
461
595
  if (configured !== void 0) {
462
596
  const tsconfig = resolve(root, configured);
463
- if (!isFile(tsconfig)) throw validator.error(`The tsconfig file does not exist: ${tsconfig}`, "dev.typecheck.tsconfig", "TSCONFIG_NOT_FOUND", "Point `dev.typecheck.tsconfig` to an existing tsconfig.json, relative to the project root.");
597
+ if (!isFile(tsconfig)) throw validator.error(configDiagnostics.TSCONFIG_EXPLICIT_NOT_FOUND, {
598
+ tsconfig,
599
+ path: "dev.typecheck.tsconfig"
600
+ });
464
601
  return {
465
602
  enabled: true,
466
603
  tsconfig,
@@ -468,7 +605,10 @@ function resolveTypecheck(root, packageJson, config, validator) {
468
605
  };
469
606
  }
470
607
  const found = [join(root, "src", "tsconfig.json"), join(root, "tsconfig.json")].find((candidate) => isFile(candidate)) ?? null;
471
- if (!found) throw validator.error(`Could not find a tsconfig.json in ${root}`, "dev.typecheck", "TSCONFIG_NOT_FOUND", "Create src/tsconfig.json or tsconfig.json, or set `dev.typecheck.tsconfig`.");
608
+ if (!found) throw validator.error(configDiagnostics.TSCONFIG_NOT_FOUND, {
609
+ root,
610
+ suggestion: "dev.typecheck.tsconfig"
611
+ });
472
612
  return {
473
613
  enabled: true,
474
614
  tsconfig: found,
@@ -494,7 +634,7 @@ function resolveTunnel(config, validator) {
494
634
  let path = "/";
495
635
  if (typeof config === "string") url = config;
496
636
  else if (config !== true) {
497
- if (config === null || typeof config !== "object" || Array.isArray(config)) throw validator.error("`dev.tunnel` must be a boolean, an https URL or an object", "dev.tunnel", "INVALID_TYPE", "Use `true` for a cloudflared quick tunnel, an https URL you already serve, or `false` to disable it.");
637
+ if (config === null || typeof config !== "object" || Array.isArray(config)) throw validator.typeError("dev.tunnel", "a boolean, an https URL or an object", config, "Use `true` for a cloudflared quick tunnel, an https URL you already serve, or `false` to disable it.");
498
638
  validator.knownKeys(config, "dev.tunnel", [
499
639
  "url",
500
640
  "updateEndpoint",
@@ -513,9 +653,12 @@ function resolveTunnel(config, validator) {
513
653
  try {
514
654
  parsed = new URL(url);
515
655
  } catch {
516
- throw validator.error(`Invalid URL "${url}"`, "dev.tunnel", "INVALID_URL", "Use an absolute https URL such as https://bot.example.com.");
656
+ throw validator.error(configDiagnostics.INVALID_URL, {
657
+ url,
658
+ fix: "Use an absolute https URL such as https://bot.example.com."
659
+ });
517
660
  }
518
- if (parsed.protocol !== "https:") throw validator.error(`The tunnel URL must be https, received "${url}"`, "dev.tunnel", "INVALID_URL", "Discord only accepts an https interactions endpoint.");
661
+ if (parsed.protocol !== "https:") throw validator.error(configDiagnostics.TUNNEL_URL_NOT_HTTPS, { url });
519
662
  return {
520
663
  mode: "url",
521
664
  url,
@@ -533,10 +676,10 @@ function resolveCodegen(root, config, validator) {
533
676
  output: join(root, DEFAULT_I18N_OUTPUT)
534
677
  } : null };
535
678
  }
536
- if (config.i18n === null || typeof config.i18n !== "object") throw validator.error("`codegen.i18n` must be an object or `false`", "codegen.i18n", "INVALID_TYPE", "Use `{ locales, output }` to configure it or `false` to disable it.");
679
+ if (config.i18n === null || typeof config.i18n !== "object") throw validator.typeError("codegen.i18n", "an object or `false`", config.i18n, "Use `{ locales, output }` to configure it or `false` to disable it.");
537
680
  validator.knownKeys(config.i18n, "codegen.i18n", ["locales", "output"]);
538
681
  const locales = resolve(root, validator.string(config.i18n.locales, "codegen.i18n.locales") ?? "src/locales/en-US");
539
- if (!isDirectory(locales)) throw validator.error(`The locales directory does not exist: ${locales}`, "codegen.i18n.locales", "LOCALES_NOT_FOUND", "Point `codegen.i18n.locales` to the base locale directory, relative to the project root.");
682
+ if (!isDirectory(locales)) throw validator.error(configDiagnostics.LOCALES_NOT_FOUND, { locales });
540
683
  return { i18n: {
541
684
  locales,
542
685
  output: resolve(root, validator.string(config.i18n.output, "codegen.i18n.output") ?? "src/@types/i18next.d.ts")
@@ -564,7 +707,7 @@ function resolveImports(root, buildTool, future, config, validator) {
564
707
  };
565
708
  const forcedOn = config === true;
566
709
  const options = forcedOn || config === void 0 ? {} : config;
567
- if (typeof options !== "object" || options === null || Array.isArray(options)) throw validator.error("`imports` must be an object, `true` or `false`", "imports", "INVALID_TYPE", "Use `{ dirs, presets, exclude, dts }`, `true` to enable with defaults, or `false` to disable.");
710
+ if (typeof options !== "object" || options === null || Array.isArray(options)) throw validator.typeError("imports", "an object, `true` or `false`", options, "Use `{ dirs, presets, exclude, dts }`, `true` to enable with defaults, or `false` to disable.");
568
711
  validator.knownKeys(options, "imports", [
569
712
  "enabled",
570
713
  "dirs",
@@ -573,7 +716,7 @@ function resolveImports(root, buildTool, future, config, validator) {
573
716
  "dts"
574
717
  ]);
575
718
  const requestedOn = forcedOn || validator.boolean(options.enabled, "imports.enabled");
576
- if (requestedOn && buildTool !== "tsdown") throw validator.error("`imports` requires the `tsdown` build tool", "imports.enabled", "IMPORTS_REQUIRE_TSDOWN", "Set `build.tool` to 'tsdown', or remove `imports`/set it to `false`.");
719
+ if (requestedOn && buildTool !== "tsdown") throw validator.error(configDiagnostics.IMPORTS_REQUIRE_TSDOWN, {});
577
720
  const dirs = (validator.stringArray(options.dirs, "imports.dirs") ?? [...DEFAULT_IMPORTS_DIRS]).map((dir) => resolve(root, dir));
578
721
  const presets = validator.stringArray(options.presets, "imports.presets") ?? defaultPresets;
579
722
  const exclude = validator.stringArray(options.exclude, "imports.exclude") ?? [];
@@ -591,19 +734,24 @@ var Validator = class {
591
734
  constructor(file) {
592
735
  this.file = file;
593
736
  }
594
- error(message, path, code, hint) {
595
- return new ConfigError(message, {
596
- code,
597
- path,
598
- hint,
599
- file: this.file
737
+ get sources() {
738
+ return this.file ? [this.file] : void 0;
739
+ }
740
+ error(handle, params) {
741
+ return handle({
742
+ ...params,
743
+ sources: this.sources
600
744
  });
601
745
  }
602
746
  knownKeys(value, path, keys) {
603
747
  for (const key of Object.keys(value)) {
604
748
  if (keys.includes(key)) continue;
605
749
  const fullPath = path ? `${path}.${key}` : key;
606
- throw this.error(`Unknown option \`${fullPath}\``, fullPath, "UNKNOWN_OPTION", `Known options${path ? ` of \`${path}\`` : ""}: ${keys.join(", ")}.`);
750
+ throw this.error(configDiagnostics.UNKNOWN_OPTION, {
751
+ path: fullPath,
752
+ parent: path,
753
+ known: keys.join(", ")
754
+ });
607
755
  }
608
756
  }
609
757
  string(value, path) {
@@ -637,29 +785,29 @@ var Validator = class {
637
785
  if (value === null || typeof value !== "object" || Array.isArray(value) || !Object.values(value).every((item) => typeof item === "string")) throw this.typeError(path, "an object of string values", value);
638
786
  return value;
639
787
  }
640
- typeError(path, expected, value) {
641
- return this.error(`\`${path}\` must be ${expected}, received ${describe(value)}`, path, "INVALID_TYPE", `Set \`${path}\` to ${expected} or remove it to use the default.`);
788
+ /** A generic "wrong type" diagnostic. `fix` defaults to the standard "set it or remove it" wording. */
789
+ typeError(path, expected, value, fix) {
790
+ return this.error(configDiagnostics.INVALID_TYPE, {
791
+ path,
792
+ expected,
793
+ value,
794
+ fix: fix ?? `Set \`${path}\` to ${expected} or remove it to use the default.`
795
+ });
642
796
  }
643
797
  };
644
- function describe(value) {
645
- if (value === null) return "null";
646
- if (Array.isArray(value)) return "an array";
647
- if (typeof value === "string") return `"${value}"`;
648
- return typeof value === "object" ? "an object" : `${typeof value} ${String(value)}`;
649
- }
650
798
  function hasDependency(packageJson, name) {
651
799
  return Boolean(packageJson?.dependencies?.[name] ?? packageJson?.devDependencies?.[name]);
652
800
  }
653
- function readPackageJson(root) {
801
+ function readPackageJson(root, validator) {
654
802
  const file = join(root, "package.json");
655
803
  if (!isFile(file)) return null;
656
804
  try {
657
805
  const parsed = JSON.parse(readFileSync(file, "utf-8"));
658
806
  return parsed !== null && typeof parsed === "object" ? parsed : null;
659
807
  } catch (error) {
660
- throw new ConfigError(`Failed to parse ${file}: ${error instanceof Error ? error.message : String(error)}`, {
661
- code: "PACKAGE_JSON_INVALID",
662
- hint: "Fix the JSON syntax of the package.json file.",
808
+ throw validator.error(configDiagnostics.PACKAGE_JSON_INVALID, {
809
+ file,
810
+ message: error instanceof Error ? error.message : String(error),
663
811
  cause: error
664
812
  });
665
813
  }
@@ -684,7 +832,7 @@ function isDirectory(path) {
684
832
  /**
685
833
  * Loads, validates and resolves a project's `stars.config.*`.
686
834
  *
687
- * @throws {ConfigError} when the configuration file cannot be loaded or contains an invalid option.
835
+ * @throws {Diagnostic} (from `nostics`, via {@link configDiagnostics}) when the configuration file cannot be loaded or contains an invalid option.
688
836
  */
689
837
  async function loadStarsConfig(options = {}) {
690
838
  const cwd = options.cwd ?? process.cwd();
@@ -720,5 +868,5 @@ function defineConfig(config) {
720
868
  }
721
869
 
722
870
  //#endregion
723
- export { CONFIG_EXTENSIONS, CONFIG_FILE_NAMES, ConfigError, defineConfig, discoverConfigFile, displayPath, loadConfigFile, loadStarsConfig, readProjectEnvFiles, resolveStarsConfig };
871
+ export { CONFIG_EXTENSIONS, CONFIG_FILE_NAMES, configDiagnostics, defineConfig, discoverConfigFile, displayPath, loadConfigFile, loadStarsConfig, readProjectEnvFiles, resolveStarsConfig };
724
872
  //# sourceMappingURL=config.js.map