前端初始化

This commit is contained in:
“hsc”
2026-08-21 15:36:24 +08:00
commit e4d8ecde13
14411 changed files with 2275932 additions and 0 deletions
+295
View File
@@ -0,0 +1,295 @@
//#region src/utils.d.ts
/**
* A value of type T, or a function that resolves T from a single params object.
*
* @internal
*/
type ValueOrFn<T, P = any> = T | ((params: P) => T);
/**
* Extracts the param type from a single-arg function, or `never` for
* non-function inputs. Pairs with {@link ValueOrFn}.
*
* @internal
*/
type ExtractFnParam<T> = T extends ((params: infer P) => any) ? P : never;
/**
* Converts a union of types to their intersection.
*
* @internal
*/
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
/**
* `true` when `T` is the `any` type.
*
* @internal
*/
type IsAny<Type> = 0 extends 1 & Type ? true : false;
/**
* `true` when `T` is the `unknown` type (and not `any`).
*
* @internal
*/
type IsUnknown<Type> = IsAny<Type> extends true ? false : unknown extends Type ? true : false;
/**
* Expands a type to its property listing so editor hovers show the resolved
* shape instead of a chain of aliases / intersections.
*
* @internal
*/
type Prettify<Type> = { [Key in keyof Type]: Type[Key] };
//#endregion
//#region src/diagnostic.d.ts
/**
* Define-time shape of a diagnostic. Each field can be a static value or a
* function that resolves it from a shared `params` object passed at call
* time. Runtime-only fields (`cause`, `sources`) from {@link DiagnosticInit}
* are intentionally omitted: they're only meaningful at the call site.
*/
interface DiagnosticDefinition<P = any> {
/**
* The error message: why this failed. String, or a function of `params`.
*
* @example
* ```ts
* why: (p: { name: string }) => `module "${p.name}" failed to load`
* ```
*/
why: ValueOrFn<string, P>;
/**
* Actionable instructions on how to resolve the problem. String, or a
* function of `params`.
*
* @example
* ```ts
* fix: (p: { name: string }) => `run "npm install ${p.name}"`
* ```
*/
fix?: ValueOrFn<string, P>;
/**
* Per-code docs URL. A string overrides
* {@link DefineDiagnosticsOptions.docsBase} for this code; `false` opts this
* code out entirely, even when `docsBase` is set. When omitted, the URL is
* derived from `docsBase`.
*/
docs?: string | false;
}
/**
* Runtime-only fields that can be passed alongside the interpolation params
* at call time. Merged into the same object so callers pass everything in
* one place.
*/
interface DiagnosticCallParams {
/**
* Original error or exception that triggered this diagnostic. Pass it
* through when re-throwing so the original stack trace is preserved.
*/
cause?: unknown;
/**
* Locations in user code that contributed to this diagnostic, in
* `file:line:column` format. Useful for compilers and other tools where the
* JS stack trace doesn't reflect the user's source.
*/
sources?: string[];
}
/**
* Structured initializer for a {@link Diagnostic}. `why` is the only required
* field: it becomes the {@link Diagnostic.message}. The remaining fields are
* optional metadata that reporters and consumers can render or forward.
*/
interface DiagnosticInit extends DiagnosticCallParams {
/**
* The diagnostic code, e.g. `MATH_E001`. Appear as {@link Diagnostic.name}.
*/
code: string;
/**
* The actual error message: why this failed.
* Mirrored to `Error.message`.
*/
why: string;
/**
* Optional actionable instructions on how to resolve the problem.
*/
fix?: string;
/**
* URL to extended documentation for this diagnostic.
*/
docs?: string;
}
/**
* Represents how to report a diagnostic. Could call `console.log()`, send the
* diagnostic to a server, or something else. Reporters declare the shape of
* options they need via `ReporterOpts`; `defineDiagnostics` intersects every
* reporter's options into a single object passed at the call site.
*/
type DiagnosticReporter<ReporterOpts extends object = {}> = (diagnostic: Diagnostic, options: ReporterOpts) => void;
/**
* Permissive reporter constraint used internally so reporters with 1 arg,
* required options, or optional options all satisfy the array constraint.
*
* @internal
*/
type AnyDiagnosticReporter = (diagnostic: Diagnostic, options: any) => void;
/**
* The `console` methods a log reporter can route to.
*/
type ConsoleMethod = "log" | "error" | "warn";
/**
* Options for {@link createConsoleReporter}.
*/
interface ConsoleReporterOptions {
/**
* `console` method used to print the diagnostic. Defaults to `'warn'`. The
* returned reporter still accepts a per-call `{ method }` override through
* the call-site reporter options.
*/
method?: ConsoleMethod;
/**
* Renders the diagnostic into the string handed to `console`. Defaults to
* {@link formatDiagnostic}, the plain unicode-decorated formatter.
*/
formatter?: (diagnostic: Diagnostic) => string;
}
/**
* Creates a console reporter that renders each diagnostic with `formatter` and
* prints the result via `console[method]`. Both default sensibly (`'warn'` and
* {@link formatDiagnostic}); `method` can also be overridden per call through
* the reporter options.
*/
declare function createConsoleReporter({
method: defaultMethod,
formatter
}?: ConsoleReporterOptions): DiagnosticReporter<{
method?: ConsoleMethod;
}>;
/**
* Resolves the `params` type a code expects from the intersection of params
* across all function-typed fields, falling back to `{}` when every field is
* static. Merged with {@link DiagnosticCallParams} at the call site.
*
* @internal
*/
type InferCodeParams<Def> = [ExtractFnParam<Def[keyof Def]>] extends [never] ? {} : UnionToIntersection<ExtractFnParam<Def[keyof Def]>>;
/**
* Options for {@link defineDiagnostics}.
*/
interface DefineDiagnosticsOptions<Codes extends Record<string, DiagnosticDefinition>, Reporters extends readonly AnyDiagnosticReporter[]> {
/**
* Base URL or resolver for documentation links. When a string, the code is
* appended as a lowercase path segment (e.g. `"https://docs.example.com"` →
* `"https://docs.example.com/math_e001"`). When a function, receives the
* code and returns a URL or `undefined`.
*/
docsBase?: string | ((code: keyof Codes) => string | undefined);
/**
* Map of diagnostic codes to their definitions.
*/
codes: Codes;
/**
* Reporters called every time a diagnostic is produced. Can be used to
* integrate with custom logging.
*/
reporters?: Reporters;
}
/**
* The first positional argument of a {@link DiagnosticHandle} call:
* interpolation params merged with the runtime-only call-site fields
* (`cause`, `sources`).
*
* @internal
*/
type CallSiteParams<Params> = Params & DiagnosticCallParams;
/**
* Resolves the full argument tuple for a {@link DiagnosticHandle} call.
* Branches on whether params and reporter options each have required fields.
* Required positions become required tuple elements, all-optional ones
* become `?`, and when no reporter declares any options the parameter is
* omitted entirely.
*
* @internal
*/
type ActionArgs<Params, ReporterOpts> = keyof ReporterOpts extends never ? {} extends Params ? [params?: CallSiteParams<Params>] : [params: CallSiteParams<Params>] : {} extends ReporterOpts ? {} extends Params ? [params?: CallSiteParams<Params>, reporterOptions?: ReporterOpts] : [params: CallSiteParams<Params>, reporterOptions?: ReporterOpts] : {} extends Params ? [params: CallSiteParams<Params> | undefined, reporterOptions: ReporterOpts] : [params: CallSiteParams<Params>, reporterOptions: ReporterOpts];
/**
* Per-code handle exposed by {@link defineDiagnostics}. Each code is a
* callable: invoke it to build the diagnostic and run every reporter, or
* prefix the call with `throw` to raise it.
*
* @example
* ```ts
* diagnostics.MATH_E001({ name: 'x' }) // report
* throw diagnostics.MATH_E001({ name: 'x' }) // throw
* ```
*/
interface DiagnosticHandle<Params, ReporterOpts> {
/**
* Builds the diagnostic, runs every reporter, and returns the diagnostic
* instance. The returned diagnostic can be inspected, attached as `cause`,
* or thrown with `throw`.
*/
(...args: ActionArgs<Params, ReporterOpts>): Diagnostic;
}
/**
* Return type of {@link defineDiagnostics}.
*/
type Diagnostics<Codes extends Record<string, DiagnosticDefinition>, Reporters extends readonly AnyDiagnosticReporter[]> = { [Code in keyof Codes]: DiagnosticHandle<InferCodeParams<Codes[Code]>, Prettify<ExtractReportersOptions<Reporters>>> };
declare class Diagnostic extends Error {
name: string;
/**
* The diagnostic code, e.g. `MATH_E001`.
* Also appears as the `name` property.
*/
code: string;
/**
* URL to extended documentation for this diagnostic code.
* Auto-generated from {@link DefineDiagnosticsOptions.docsBase}.
*/
docs?: string;
/**
* Optional actionable instructions on how to resolve the problem.
*/
fix?: string;
/**
* Locations in user code that contributed to this diagnostic, in
* `file:line:column` format. Relevant when the stack trace doesn't reflect
* the user's source (e.g. compilers, bundlers), otherwise redundant with the
* stack and should be omitted.
*/
sources?: string[];
/**
* Alias for {@link Error.message}: the reason this diagnostic was raised.
*/
get why(): string;
/**
* @param init structured initializer; `why` is required
* @param captureFrom V8 stack-cutoff frame. Defaults to {@link Diagnostic}
* so the top of the trace is the `new Diagnostic(...)` call site.
* `defineDiagnostics` passes its action method to strip its own frames too.
* Ignored on engines without `Error.captureStackTrace`.
*/
constructor(init: DiagnosticInit, captureFrom?: Function);
/**
* Converts the diagnostic into a serializable structured object.
*/
toJSON(): object;
}
/**
* Creates a typed diagnostics object from a set of code definitions. Each
* code becomes a callable {@link DiagnosticHandle}: invoke to report, or
* `throw` the result to raise. No `new` required, no proxy.
*/
declare function defineDiagnostics<const Codes extends Record<string, DiagnosticDefinition>, const Reporters extends readonly AnyDiagnosticReporter[]>(options: DefineDiagnosticsOptions<Codes, Reporters>): Diagnostics<Codes, Reporters>;
/**
* Extracts the options object a reporter accepts as its 2nd argument. Returns
* `{}` when the reporter has no 2nd arg (so it contributes nothing to the
* merged shape).
*/
type ExtractSingleReporterOptions<Reporter> = Reporter extends ((diagnostic: Diagnostic, options: infer ReporterOpts) => any) ? IsUnknown<ReporterOpts> extends true ? {} : Exclude<ReporterOpts, undefined> : {};
/**
* Intersects every reporter's options shape into a single object. If any
* reporter has a required field, the merged shape has a required field, and
* {@link ActionArgs} flips `reporterOptions` from optional to required via
* `{} extends Merged`.
*/
type ExtractReportersOptions<Reporters extends readonly any[]> = Reporters extends readonly [infer First, ...infer Rest] ? ExtractSingleReporterOptions<First> & ExtractReportersOptions<Rest> : {};
//#endregion
export { Diagnostic as a, DiagnosticHandle as c, Diagnostics as d, createConsoleReporter as f, DefineDiagnosticsOptions as i, DiagnosticInit as l, ValueOrFn as m, ConsoleMethod as n, DiagnosticCallParams as o, defineDiagnostics as p, ConsoleReporterOptions as r, DiagnosticDefinition as s, AnyDiagnosticReporter as t, DiagnosticReporter as u };
//# sourceMappingURL=diagnostic-wduO7saY.d.mts.map
+15
View File
@@ -0,0 +1,15 @@
import { a as Diagnostic } from "../diagnostic-wduO7saY.mjs";
//#region src/formatters/ansi.d.ts
interface Colors {
red: (s: string) => string;
yellow: (s: string) => string;
cyan: (s: string) => string;
gray: (s: string) => string;
bold: (s: string) => string;
dim: (s: string) => string;
}
declare function ansiFormatter(colors: Colors): (d: Diagnostic) => string;
//#endregion
export { Colors, ansiFormatter };
//# sourceMappingURL=ansi.d.mts.map
+19
View File
@@ -0,0 +1,19 @@
//#region src/formatters/ansi.ts
/* @__NO_SIDE_EFFECTS__ */
function ansiFormatter(colors) {
return (d) => {
const header = `${colors.bold(colors.red(`[${d.name}]`))} ${d.message}`;
const details = [];
if (d.fix) details.push(`${colors.dim("fix:")} ${d.fix}`);
if (d.sources?.length) details.push(`${colors.dim("sources:")} ${d.sources.join(", ")}`);
if (d.docs) details.push(`${colors.dim("see:")} ${colors.cyan(d.docs)}`);
if (details.length === 0) return header;
return [header, ...details.map((detail, i) => {
return `${colors.dim(i < details.length - 1 ? "├▶" : "╰▶")} ${detail}`;
})].join("\n");
};
}
//#endregion
export { ansiFormatter };
//# sourceMappingURL=ansi.mjs.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ansi.mjs","names":[],"sources":["../../src/formatters/ansi.ts"],"sourcesContent":["import type { Diagnostic } from '../diagnostic'\n\nexport interface Colors {\n red: (s: string) => string\n yellow: (s: string) => string\n cyan: (s: string) => string\n gray: (s: string) => string\n bold: (s: string) => string\n dim: (s: string) => string\n}\n\n/* @__NO_SIDE_EFFECTS__ */\nexport function ansiFormatter(colors: Colors): (d: Diagnostic) => string {\n return (d) => {\n const tag = colors.bold(colors.red(`[${d.name}]`))\n const header = `${tag} ${d.message}`\n\n const details: string[] = []\n if (d.fix)\n details.push(`${colors.dim('fix:')} ${d.fix}`)\n if (d.sources?.length)\n details.push(`${colors.dim('sources:')} ${d.sources.join(', ')}`)\n if (d.docs)\n details.push(`${colors.dim('see:')} ${colors.cyan(d.docs)}`)\n\n if (details.length === 0)\n return header\n\n const lines = details.map((detail, i) => {\n const connector = colors.dim(i < details.length - 1 ? '├▶' : '╰▶')\n return `${connector} ${detail}`\n })\n return [header, ...lines].join('\\n')\n }\n}\n"],"mappings":";;AAYA,SAAgB,cAAc,QAA2C;CACvE,QAAQ,MAAM;EAEZ,MAAM,SAAS,GADH,OAAO,KAAK,OAAO,IAAI,IAAI,EAAE,KAAK,EAAE,CAC5B,EAAE,GAAG,EAAE;EAE3B,MAAM,UAAoB,CAAC;EAC3B,IAAI,EAAE,KACJ,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,EAAE,GAAG,EAAE,KAAK;EAC/C,IAAI,EAAE,SAAS,QACb,QAAQ,KAAK,GAAG,OAAO,IAAI,UAAU,EAAE,GAAG,EAAE,QAAQ,KAAK,IAAI,GAAG;EAClE,IAAI,EAAE,MACJ,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,EAAE,GAAG,OAAO,KAAK,EAAE,IAAI,GAAG;EAE7D,IAAI,QAAQ,WAAW,GACrB,OAAO;EAMT,OAAO,CAAC,QAAQ,GAJF,QAAQ,KAAK,QAAQ,MAAM;GAEvC,OAAO,GADW,OAAO,IAAI,IAAI,QAAQ,SAAS,IAAI,OAAO,IAC3C,EAAE,GAAG;EACzB,CACuB,CAAC,CAAC,CAAC,KAAK,IAAI;CACrC;AACF"}
+7
View File
@@ -0,0 +1,7 @@
import { a as Diagnostic } from "../diagnostic-wduO7saY.mjs";
//#region src/formatters/json.d.ts
declare const jsonFormatter: (d: Diagnostic) => string;
//#endregion
export { jsonFormatter };
//# sourceMappingURL=json.d.mts.map
+6
View File
@@ -0,0 +1,6 @@
//#region src/formatters/json.ts
const jsonFormatter = (d) => JSON.stringify(d);
//#endregion
export { jsonFormatter };
//# sourceMappingURL=json.mjs.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"json.mjs","names":[],"sources":["../../src/formatters/json.ts"],"sourcesContent":["import type { Diagnostic } from '../diagnostic'\n\nexport const jsonFormatter = (d: Diagnostic): string => JSON.stringify(d)\n"],"mappings":";AAEA,MAAa,iBAAiB,MAA0B,KAAK,UAAU,CAAC"}
+51
View File
@@ -0,0 +1,51 @@
import { a as Diagnostic, c as DiagnosticHandle, d as Diagnostics, f as createConsoleReporter, i as DefineDiagnosticsOptions, l as DiagnosticInit, m as ValueOrFn, n as ConsoleMethod, o as DiagnosticCallParams, p as defineDiagnostics, r as ConsoleReporterOptions, s as DiagnosticDefinition, t as AnyDiagnosticReporter, u as DiagnosticReporter } from "./diagnostic-wduO7saY.mjs";
//#region src/formatters/plain.d.ts
/**
* Renders a diagnostic into a multi-line, unicode-decorated string suitable
* for terminal output. The first line is `[<name>] <message>`; optional
* details (`fix`, `sources`, `docs`) follow with `├▶`/`╰▶` connectors.
*/
declare function formatDiagnostic(diagnostic: Diagnostic): string;
//#endregion
//#region src/prod-diagnostics.d.ts
/**
* Options for {@link defineProdDiagnostics}. A lean subset of
* {@link DefineDiagnosticsOptions}: no `codes` map (the proxy serves any code),
* only what is needed to keep behaviour correct in production.
*/
interface DefineProdDiagnosticsOptions<Reporters extends readonly AnyDiagnosticReporter[] = readonly AnyDiagnosticReporter[]> {
/**
* Base URL or resolver for documentation links, identical to
* {@link DefineDiagnosticsOptions.docsBase}. The docs URL is derived from the
* accessed code at call time, so links survive even without the catalog.
*/
docsBase?: string | ((code: string) => string | undefined);
/**
* Reporters called every time a diagnostic is produced. Omitted by default in
* production builds; the strip plugin can copy them into the prod branch when
* prod-time reporting (e.g. telemetry) is desired.
*/
reporters?: Reporters;
}
/**
* Production counterpart to {@link defineDiagnostics}. Returns a `Proxy` that
* builds a minimal {@link Diagnostic} for any accessed code: the code becomes
* the instance `name`, `docs` is derived from `docsBase`, and `why` points to
* the docs URL when one exists (empty otherwise, so the thrown header is just
* the code). It carries no catalog text, so it stays tiny in a bundle.
*
* The strip plugin (`@nostics/unplugin`) can rewrite a `defineDiagnostics()`
* call into a `process.env.NODE_ENV === 'production'` ternary that selects this
* factory in production, dropping every `why`/`fix` string from the bundle.
*
* @example
* ```ts
* const diagnostics = defineProdDiagnostics({ docsBase: 'https://docs.example.com' })
* throw diagnostics.NUXT_B2011() // NUXT_B2011: https://docs.example.com/nuxt_b2011
* ```
*/
declare function defineProdDiagnostics<const Codes extends Record<string, DiagnosticDefinition> = Record<string, DiagnosticDefinition>, const Reporters extends readonly AnyDiagnosticReporter[] = readonly AnyDiagnosticReporter[]>(options?: DefineProdDiagnosticsOptions<Reporters>): Diagnostics<Codes, Reporters>;
//#endregion
export { type AnyDiagnosticReporter, type ConsoleMethod, type ConsoleReporterOptions, type DefineDiagnosticsOptions, type DefineProdDiagnosticsOptions, Diagnostic, type DiagnosticCallParams, type DiagnosticDefinition, type DiagnosticHandle, type DiagnosticInit, type DiagnosticReporter, type Diagnostics, type ValueOrFn as _ValueOrFn, createConsoleReporter, defineDiagnostics, defineProdDiagnostics, formatDiagnostic };
//# sourceMappingURL=index.d.mts.map
+187
View File
@@ -0,0 +1,187 @@
//#region src/formatters/plain.ts
/**
* Renders a diagnostic into a multi-line, unicode-decorated string suitable
* for terminal output. The first line is `[<name>] <message>`; optional
* details (`fix`, `sources`, `docs`) follow with `├▶`/`╰▶` connectors.
*/
function formatDiagnostic(diagnostic) {
const header = `[${diagnostic.name}] ${diagnostic.message}`;
const details = [];
if (diagnostic.fix) details.push(`fix: ${diagnostic.fix}`);
if (diagnostic.sources?.length) details.push(`sources: ${diagnostic.sources.join(", ")}`);
if (diagnostic.docs) details.push(`see: ${diagnostic.docs}`);
if (details.length === 0) return header;
return [header, ...details.map((detail, i) => {
return `${i < details.length - 1 ? "├▶" : "╰▶"} ${detail}`;
})].join("\n");
}
//#endregion
//#region src/utils.ts
/**
* Transforms a value or a function that returns a value to a value.
*
* @param valFn either a value or a function that returns a value
* @param args arguments to pass to the function if `valFn` is a function
*
* @internal
*/
function toValueWithArgs(valFn, ...args) {
return typeof valFn === "function" ? valFn(...args) : valFn;
}
//#endregion
//#region src/diagnostic.ts
/**
* Creates a console reporter that renders each diagnostic with `formatter` and
* prints the result via `console[method]`. Both default sensibly (`'warn'` and
* {@link formatDiagnostic}); `method` can also be overridden per call through
* the reporter options.
*/
/* @__NO_SIDE_EFFECTS__ */
function createConsoleReporter({ method: defaultMethod = "warn", formatter = formatDiagnostic } = {}) {
return (diagnostic, { method = defaultMethod } = {}) => {
console[method](formatter(diagnostic));
};
}
const captureStackTrace = Error.captureStackTrace;
var Diagnostic = class Diagnostic extends Error {
name;
/**
* The diagnostic code, e.g. `MATH_E001`.
* Also appears as the `name` property.
*/
code;
/**
* URL to extended documentation for this diagnostic code.
* Auto-generated from {@link DefineDiagnosticsOptions.docsBase}.
*/
docs;
/**
* Optional actionable instructions on how to resolve the problem.
*/
fix;
/**
* Locations in user code that contributed to this diagnostic, in
* `file:line:column` format. Relevant when the stack trace doesn't reflect
* the user's source (e.g. compilers, bundlers), otherwise redundant with the
* stack and should be omitted.
*/
sources;
/**
* Alias for {@link Error.message}: the reason this diagnostic was raised.
*/
get why() {
return this.message;
}
/**
* @param init structured initializer; `why` is required
* @param captureFrom V8 stack-cutoff frame. Defaults to {@link Diagnostic}
* so the top of the trace is the `new Diagnostic(...)` call site.
* `defineDiagnostics` passes its action method to strip its own frames too.
* Ignored on engines without `Error.captureStackTrace`.
*/
constructor(init, captureFrom = Diagnostic) {
super(init.why, { cause: init.cause });
this.code = this.name = init.code;
this.fix = init.fix;
this.docs = init.docs;
this.sources = init.sources;
captureStackTrace?.(this, captureFrom);
}
/**
* Converts the diagnostic into a serializable structured object.
*/
toJSON() {
return {
name: this.name,
why: this.why,
fix: this.fix,
docs: this.docs,
sources: this.sources,
cause: this.cause,
stack: this.stack
};
}
};
/**
* Resolves the docs URL for a code from a `docsBase` (string template or
* resolver function). Shared by {@link defineDiagnostics} and
* {@link defineProdDiagnostics}. Per-code `docs` overrides are handled by the
* caller; this only covers the `docsBase`-derived case.
*
* @internal
*/
function deriveDocs(docsBase, code) {
return typeof docsBase === "string" ? `${docsBase}/${code.toLowerCase()}` : docsBase?.(code);
}
/**
* Creates a typed diagnostics object from a set of code definitions. Each
* code becomes a callable {@link DiagnosticHandle}: invoke to report, or
* `throw` the result to raise. No `new` required, no proxy.
*/
/* @__NO_SIDE_EFFECTS__ */
function defineDiagnostics(options) {
const reporters = options.reporters ?? [];
const result = {};
const { docsBase } = options;
for (const code of Object.keys(options.codes)) {
const def = options.codes[code];
const docs = def.docs === false ? void 0 : def.docs || deriveDocs(docsBase, code);
const handle = (params = {}, reporterOptions = {}) => {
const diagnostic = new Diagnostic({
code,
why: toValueWithArgs(def.why, params),
fix: toValueWithArgs(def.fix, params),
docs,
cause: params.cause,
sources: params.sources
}, handle);
for (const reporter of reporters) reporter(diagnostic, reporterOptions);
return diagnostic;
};
result[code] = handle;
}
return result;
}
//#endregion
//#region src/prod-diagnostics.ts
/**
* Production counterpart to {@link defineDiagnostics}. Returns a `Proxy` that
* builds a minimal {@link Diagnostic} for any accessed code: the code becomes
* the instance `name`, `docs` is derived from `docsBase`, and `why` points to
* the docs URL when one exists (empty otherwise, so the thrown header is just
* the code). It carries no catalog text, so it stays tiny in a bundle.
*
* The strip plugin (`@nostics/unplugin`) can rewrite a `defineDiagnostics()`
* call into a `process.env.NODE_ENV === 'production'` ternary that selects this
* factory in production, dropping every `why`/`fix` string from the bundle.
*
* @example
* ```ts
* const diagnostics = defineProdDiagnostics({ docsBase: 'https://docs.example.com' })
* throw diagnostics.NUXT_B2011() // NUXT_B2011: https://docs.example.com/nuxt_b2011
* ```
*/
/* @__NO_SIDE_EFFECTS__ */
function defineProdDiagnostics(options = {}) {
const { docsBase, reporters = [] } = options;
return new Proxy({}, { get(_target, code) {
if (typeof code !== "string") return void 0;
const handle = (params = {}, reporterOptions = {}) => {
const docs = deriveDocs(docsBase, code);
const diagnostic = new Diagnostic({
code,
why: docs ?? "",
docs,
cause: params.cause,
sources: params.sources
}, handle);
for (const reporter of reporters) reporter(diagnostic, reporterOptions);
return diagnostic;
};
return handle;
} });
}
//#endregion
export { Diagnostic, createConsoleReporter, defineDiagnostics, defineProdDiagnostics, formatDiagnostic };
//# sourceMappingURL=index.mjs.map
+1
View File
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
import { u as DiagnosticReporter } from "../diagnostic-wduO7saY.mjs";
//#region src/reporters/dev.d.ts
/**
* Creates a reporter for browser code under Vite dev: it forwards each
* diagnostic over `import.meta.hot.send('nostics:report', ...)` so the
* dev-server collector can file it. Outside Vite (`import.meta.hot` absent) it
* warns once and does nothing.
*/
declare function createDevReporter(): DiagnosticReporter;
//#endregion
export { createDevReporter };
//# sourceMappingURL=dev.d.mts.map
+18
View File
@@ -0,0 +1,18 @@
//#region src/reporters/dev.ts
/**
* Creates a reporter for browser code under Vite dev: it forwards each
* diagnostic over `import.meta.hot.send('nostics:report', ...)` so the
* dev-server collector can file it. Outside Vite (`import.meta.hot` absent) it
* warns once and does nothing.
*/
/* @__NO_SIDE_EFFECTS__ */
function createDevReporter() {
return (diagnostic) => {
if (import.meta.hot && typeof import.meta.hot.send === "function") import.meta.hot.send("nostics:report", diagnostic.toJSON());
else console.warn("[nostics]: import.meta.hot.send() is not available. This must be running on Vite.");
};
}
//#endregion
export { createDevReporter };
//# sourceMappingURL=dev.mjs.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"dev.mjs","names":[],"sources":["../../src/reporters/dev.ts"],"sourcesContent":["import type { DiagnosticReporter } from '../diagnostic'\n\n/**\n * Creates a reporter for browser code under Vite dev: it forwards each\n * diagnostic over `import.meta.hot.send('nostics:report', ...)` so the\n * dev-server collector can file it. Outside Vite (`import.meta.hot` absent) it\n * warns once and does nothing.\n */\n/* @__NO_SIDE_EFFECTS__ */\nexport function createDevReporter(): DiagnosticReporter {\n return (diagnostic) => {\n if (import.meta.hot && typeof import.meta.hot.send === 'function') {\n import.meta.hot.send('nostics:report', diagnostic.toJSON())\n }\n else {\n console.warn(\n '[nostics]: import.meta.hot.send() is not available. This must be running on Vite.',\n )\n }\n }\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,oBAAwC;CACtD,QAAQ,eAAe;EACrB,IAAI,OAAO,KAAK,OAAO,OAAO,OAAO,KAAK,IAAI,SAAS,YACrD,OAAO,KAAK,IAAI,KAAK,kBAAkB,WAAW,OAAO,CAAC;OAG1D,QAAQ,KACN,mFACF;CAEJ;AACF"}
+11
View File
@@ -0,0 +1,11 @@
import { u as DiagnosticReporter } from "../diagnostic-wduO7saY.mjs";
//#region src/reporters/fetch.d.ts
/**
* Creates a reporter that POSTs each diagnostic as JSON to the given URL.
* Errors are swallowed so reporting never throws into user code.
*/
declare function createFetchReporter(url: string): DiagnosticReporter;
//#endregion
export { createFetchReporter };
//# sourceMappingURL=fetch.d.mts.map
+19
View File
@@ -0,0 +1,19 @@
//#region src/reporters/fetch.ts
/**
* Creates a reporter that POSTs each diagnostic as JSON to the given URL.
* Errors are swallowed so reporting never throws into user code.
*/
/* @__NO_SIDE_EFFECTS__ */
function createFetchReporter(url) {
return (diagnostic) => {
fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(diagnostic)
}).catch(() => {});
};
}
//#endregion
export { createFetchReporter };
//# sourceMappingURL=fetch.mjs.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"fetch.mjs","names":[],"sources":["../../src/reporters/fetch.ts"],"sourcesContent":["import type { DiagnosticReporter } from '../diagnostic'\n\n/**\n * Creates a reporter that POSTs each diagnostic as JSON to the given URL.\n * Errors are swallowed so reporting never throws into user code.\n */\n/* @__NO_SIDE_EFFECTS__ */\nexport function createFetchReporter(url: string): DiagnosticReporter {\n return (diagnostic) => {\n fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(diagnostic),\n }).catch(() => {})\n }\n}\n"],"mappings":";;;;;;AAOA,SAAgB,oBAAoB,KAAiC;CACnE,QAAQ,eAAe;EACrB,MAAM,KAAK;GACT,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU,UAAU;EACjC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;CACnB;AACF"}
+43
View File
@@ -0,0 +1,43 @@
import { u as DiagnosticReporter } from "../diagnostic-wduO7saY.mjs";
//#region src/reporters/node.d.ts
interface FileReporterOptions {
/**
* Path to the log file.
* @default '.nostics.log'
*/
logFile?: string;
/**
* Stack frames matching ANY of these patterns are removed from
* `diagnostic.stack` before it is written to the log file. Useful to strip
* `node_modules` and Node internals. The `Error: ...` header line is
* always preserved.
*
* Pass an empty array to keep every frame.
* @default [/\/node_modules\//i]
*/
excludeStackFrames?: readonly RegExp[];
}
/**
* Creates a reporter that appends diagnostics as NDJSON to a local file.
* Each diagnostic is written as a single JSON line. The diagnostic's `stack`
* (if present) is included in the payload; noisy frames can be removed via
* {@link FileReporterOptions.excludeStackFrames}.
*
* @example
* ```ts
* import { defineDiagnostics } from 'nostics'
* import { createFileReporter } from 'nostics/reporters/node'
*
* const diagnostics = defineDiagnostics({
* codes: { ... },
* reporters: [createFileReporter({
* excludeStackFrames: [/\/node_modules\//, /\(node:/],
* })],
* })
* ```
*/
declare function createFileReporter(options?: FileReporterOptions): DiagnosticReporter;
//#endregion
export { FileReporterOptions, createFileReporter };
//# sourceMappingURL=node.d.mts.map
+45
View File
@@ -0,0 +1,45 @@
import { appendFileSync } from "node:fs";
//#region src/reporters/node.ts
const DEFAULT_EXCLUDE_STACK_FRAMES = [/\/node_modules\//i];
function applyExcludeStackFrames(raw, exclude) {
const [header, ...frames] = raw.split("\n");
return [header, ...frames.filter((frame) => !exclude.some((re) => re.test(frame)))].join("\n");
}
/**
* Creates a reporter that appends diagnostics as NDJSON to a local file.
* Each diagnostic is written as a single JSON line. The diagnostic's `stack`
* (if present) is included in the payload; noisy frames can be removed via
* {@link FileReporterOptions.excludeStackFrames}.
*
* @example
* ```ts
* import { defineDiagnostics } from 'nostics'
* import { createFileReporter } from 'nostics/reporters/node'
*
* const diagnostics = defineDiagnostics({
* codes: { ... },
* reporters: [createFileReporter({
* excludeStackFrames: [/\/node_modules\//, /\(node:/],
* })],
* })
* ```
*/
/* @__NO_SIDE_EFFECTS__ */
function createFileReporter(options) {
const logFile = options?.logFile ?? ".nostics.log";
const excludeStackFrames = options?.excludeStackFrames ?? DEFAULT_EXCLUDE_STACK_FRAMES;
return (diagnostic) => {
try {
const d = diagnostic;
const base = typeof d.toJSON === "function" ? d.toJSON() : { ...d };
if (d.stack) base.stack = excludeStackFrames?.length ? applyExcludeStackFrames(d.stack, excludeStackFrames) : d.stack;
appendFileSync(logFile, `${JSON.stringify(base)}\n`);
} catch (err) {
console.error(`[nostics]: Failed to write log to "${logFile}":`, err);
}
};
}
//#endregion
export { createFileReporter };
//# sourceMappingURL=node.mjs.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"node.mjs","names":[],"sources":["../../src/reporters/node.ts"],"sourcesContent":["import type { Diagnostic, DiagnosticReporter } from '../diagnostic'\nimport { appendFileSync } from 'node:fs'\n\nexport interface FileReporterOptions {\n /**\n * Path to the log file.\n * @default '.nostics.log'\n */\n logFile?: string\n\n /**\n * Stack frames matching ANY of these patterns are removed from\n * `diagnostic.stack` before it is written to the log file. Useful to strip\n * `node_modules` and Node internals. The `Error: ...` header line is\n * always preserved.\n *\n * Pass an empty array to keep every frame.\n * @default [/\\/node_modules\\//i]\n */\n excludeStackFrames?: readonly RegExp[]\n}\n\nconst DEFAULT_EXCLUDE_STACK_FRAMES: readonly RegExp[] = [/\\/node_modules\\//i]\n\nfunction applyExcludeStackFrames(raw: string, exclude: readonly RegExp[]): string {\n const [header, ...frames] = raw.split('\\n')\n return [header, ...frames.filter(frame => !exclude.some(re => re.test(frame)))].join('\\n')\n}\n\n/**\n * Creates a reporter that appends diagnostics as NDJSON to a local file.\n * Each diagnostic is written as a single JSON line. The diagnostic's `stack`\n * (if present) is included in the payload; noisy frames can be removed via\n * {@link FileReporterOptions.excludeStackFrames}.\n *\n * @example\n * ```ts\n * import { defineDiagnostics } from 'nostics'\n * import { createFileReporter } from 'nostics/reporters/node'\n *\n * const diagnostics = defineDiagnostics({\n * codes: { ... },\n * reporters: [createFileReporter({\n * excludeStackFrames: [/\\/node_modules\\//, /\\(node:/],\n * })],\n * })\n * ```\n */\n/* @__NO_SIDE_EFFECTS__ */\nexport function createFileReporter(options?: FileReporterOptions): DiagnosticReporter {\n const logFile = options?.logFile ?? '.nostics.log'\n const excludeStackFrames = options?.excludeStackFrames ?? DEFAULT_EXCLUDE_STACK_FRAMES\n return (diagnostic) => {\n try {\n const d = diagnostic as Diagnostic & Record<string, unknown>\n const base: Record<string, unknown>\n = typeof d.toJSON === 'function' ? (d.toJSON() as Record<string, unknown>) : { ...d }\n if (d.stack) {\n base.stack = excludeStackFrames?.length\n ? applyExcludeStackFrames(d.stack, excludeStackFrames)\n : d.stack\n }\n appendFileSync(logFile, `${JSON.stringify(base)}\\n`)\n }\n catch (err: unknown) {\n console.error(`[nostics]: Failed to write log to \"${logFile}\":`, err)\n }\n }\n}\n"],"mappings":";;AAsBA,MAAM,+BAAkD,CAAC,mBAAmB;AAE5E,SAAS,wBAAwB,KAAa,SAAoC;CAChF,MAAM,CAAC,QAAQ,GAAG,UAAU,IAAI,MAAM,IAAI;CAC1C,OAAO,CAAC,QAAQ,GAAG,OAAO,QAAO,UAAS,CAAC,QAAQ,MAAK,OAAM,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;AAC3F;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,mBAAmB,SAAmD;CACpF,MAAM,UAAU,SAAS,WAAW;CACpC,MAAM,qBAAqB,SAAS,sBAAsB;CAC1D,QAAQ,eAAe;EACrB,IAAI;GACF,MAAM,IAAI;GACV,MAAM,OACF,OAAO,EAAE,WAAW,aAAc,EAAE,OAAO,IAAgC,EAAE,GAAG,EAAE;GACtF,IAAI,EAAE,OACJ,KAAK,QAAQ,oBAAoB,SAC7B,wBAAwB,EAAE,OAAO,kBAAkB,IACnD,EAAE;GAER,eAAe,SAAS,GAAG,KAAK,UAAU,IAAI,EAAE,GAAG;EACrD,SACO,KAAc;GACnB,QAAQ,MAAM,sCAAsC,QAAQ,KAAK,GAAG;EACtE;CACF;AACF"}