first commit
This commit is contained in:
+19
@@ -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
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
//#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;
|
||||
/**
|
||||
* Structured information associated with this diagnostic code.
|
||||
*/
|
||||
data;
|
||||
/**
|
||||
* 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;
|
||||
this.data = init.data;
|
||||
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,
|
||||
data: this.data,
|
||||
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,
|
||||
data: toValueWithArgs(def.data, params)
|
||||
}, 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 or data, 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
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"name": "nostics",
|
||||
"type": "module",
|
||||
"version": "1.3.0",
|
||||
"description": "Structured diagnostic code library",
|
||||
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
||||
"contributors": [
|
||||
"Eduardo San Martin Morote <posva13@gmail.com>",
|
||||
"Daniel Roe <daniel@roe.dev>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/vercel-labs/nostics#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vercel-labs/nostics.git"
|
||||
},
|
||||
"bugs": "https://github.com/vercel-labs/nostics/issues",
|
||||
"keywords": [],
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": "./dist/index.mjs",
|
||||
"./formatters/ansi": "./dist/formatters/ansi.mjs",
|
||||
"./formatters/json": "./dist/formatters/json.mjs",
|
||||
"./reporters/dev": "./dist/reporters/dev.mjs",
|
||||
"./reporters/fetch": "./dist/reporters/fetch.mjs",
|
||||
"./reporters/node": "./dist/reporters/node.mjs",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"types": "./dist/index.d.mts",
|
||||
"files": [
|
||||
"dist",
|
||||
"skills"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@posva/prompts": "^2.4.4",
|
||||
"@size-limit/esbuild-why": "^14.0.0",
|
||||
"@size-limit/preset-small-lib": "^14.0.0",
|
||||
"@types/node": "^26.6.2",
|
||||
"@types/semver": "^7.8.0",
|
||||
"@vitest/coverage-v8": "5.0.1",
|
||||
"conventional-changelog": "^8.1.3",
|
||||
"conventional-changelog-angular": "^9.4.0",
|
||||
"lint-staged": "^17.5.1",
|
||||
"oxfmt": "^0.68.0",
|
||||
"oxlint": "^1.80.0",
|
||||
"oxlint-plugin-posva": "^0.0.2",
|
||||
"publint": "^0.3.24",
|
||||
"semver": "^7.8.5",
|
||||
"simple-git-hooks": "^2.14.0",
|
||||
"size-limit": "^14.0.0",
|
||||
"tsdown": "^0.23.0",
|
||||
"tsnapi": "^1.5.0",
|
||||
"typescript": "^6.0.0",
|
||||
"vite": "^8.3.0",
|
||||
"vitest": "^5.0.1"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && pnpm lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": [
|
||||
"oxfmt --no-error-on-unmatched-pattern",
|
||||
"oxlint --no-error-on-unmatched-pattern --fix"
|
||||
]
|
||||
},
|
||||
"size-limit": [
|
||||
{
|
||||
"name": "defineDiagnostics from source",
|
||||
"path": "src/index.ts",
|
||||
"import": "{ defineDiagnostics }"
|
||||
},
|
||||
{
|
||||
"name": "defineDiagnostics",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "{ defineDiagnostics }"
|
||||
},
|
||||
{
|
||||
"name": "defineProdDiagnostics",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "{ defineProdDiagnostics }"
|
||||
},
|
||||
{
|
||||
"name": "defineDiagnostics + createConsoleReporter",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "{ defineDiagnostics, createConsoleReporter }"
|
||||
},
|
||||
{
|
||||
"name": "defineProdDiagnostics + createConsoleReporter",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "{ defineProdDiagnostics, createConsoleReporter }"
|
||||
},
|
||||
{
|
||||
"name": "formatDiagnostic",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "{ formatDiagnostic }"
|
||||
},
|
||||
{
|
||||
"name": "full root",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "*"
|
||||
},
|
||||
{
|
||||
"name": "ansi formatter",
|
||||
"path": "dist/formatters/ansi.mjs",
|
||||
"import": "*"
|
||||
}
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsdown && pnpm --filter \"./packages/*\" run build",
|
||||
"dev": "vitest",
|
||||
"docs": "pnpm -C docs run dev",
|
||||
"docs:build": "pnpm -C docs run build",
|
||||
"play": "pnpm -C playground run dev",
|
||||
"lint": "oxlint ${CI:+--deny-warnings} . && oxfmt --check .",
|
||||
"lint:fix": "oxfmt && oxlint --fix .",
|
||||
"fmt": "oxfmt",
|
||||
"release": "node scripts/release.ts",
|
||||
"size": "size-limit",
|
||||
"test": "pnpm run build && pnpm run \"/^test:/\"",
|
||||
"test:unit": "vitest run --coverage",
|
||||
"test:types": "pnpm run typecheck",
|
||||
"test:lint": "pnpm run lint",
|
||||
"test:demo": "pnpm -C demo-lib run build && pnpm -C demo-lib run test && pnpm -C playground run build",
|
||||
"typecheck": "tsc && pnpm --filter \"./packages/*\" run typecheck"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user