+4
-12
| { | ||
| "name": "nostics", | ||
| "type": "module", | ||
| "version": "0.4.0", | ||
| "version": "0.5.0", | ||
| "description": "Structured diagnostic code library", | ||
@@ -26,4 +26,2 @@ "author": "Anthony Fu <anthonyfu117@hotmail.com>", | ||
| "./reporters/node": "./dist/reporters/node.mjs", | ||
| "./unplugin/dev-server-collector": "./dist/unplugin/dev-server-collector.mjs", | ||
| "./unplugin/strip-transform": "./dist/unplugin/strip-transform.mjs", | ||
| "./package.json": "./package.json" | ||
@@ -36,7 +34,2 @@ }, | ||
| ], | ||
| "dependencies": { | ||
| "magic-string": "^0.30.21", | ||
| "oxc-parser": "^0.132.0", | ||
| "unplugin": "^3.0.0" | ||
| }, | ||
| "devDependencies": { | ||
@@ -53,3 +46,2 @@ "@antfu/eslint-config": "^9.0.0", | ||
| "conventional-changelog-angular": "^8.3.1", | ||
| "esbuild": "^0.28.0", | ||
| "eslint": "^10.3.0", | ||
@@ -66,3 +58,3 @@ "lint-staged": "^17.0.4", | ||
| "vitest": "^4.1.6", | ||
| "nostics": "0.4.0" | ||
| "nostics": "0.5.0" | ||
| }, | ||
@@ -76,3 +68,3 @@ "simple-git-hooks": { | ||
| "scripts": { | ||
| "build": "tsdown", | ||
| "build": "tsdown && pnpm --filter \"./packages/*\" run build", | ||
| "dev": "vitest", | ||
@@ -85,4 +77,4 @@ "docs": "pnpm -C docs run dev", | ||
| "test": "pnpm run build && vitest run --coverage && pnpm -C demo-lib run build && pnpm -C playground run build && pnpm run typecheck && pnpm run lint", | ||
| "typecheck": "tsc" | ||
| "typecheck": "tsc && pnpm --filter \"./packages/*\" run typecheck" | ||
| } | ||
| } |
+8
-2
@@ -90,6 +90,12 @@ <p align="center"> | ||
| Build-time plugins live in the separate [`@nostics/unplugin`](./packages/unplugin) package: | ||
| ```bash | ||
| pnpm add -D @nostics/unplugin | ||
| ``` | ||
| For library builds, use the strip plugin: | ||
| ```ts | ||
| import { nosticsStrip } from 'nostics/unplugin/strip-transform' | ||
| import { nosticsStrip } from '@nostics/unplugin/strip-transform' | ||
@@ -104,3 +110,3 @@ export default defineConfig({ | ||
| ```ts | ||
| import { nosticsCollector } from 'nostics/unplugin/dev-server-collector' | ||
| import { nosticsCollector } from '@nostics/unplugin/dev-server-collector' | ||
@@ -107,0 +113,0 @@ export default defineConfig({ |
| --- | ||
| name: nostics | ||
| description: 'Structured diagnostic code library for JavaScript/TypeScript. Turns errors and other conditions into typed, machine-readable `Diagnostic` instances with stable codes, docs URLs, and actionable fields. Use this skill whenever the project imports `nostics`, or works with `defineDiagnostics`, the `Diagnostic` class, diagnostic code registries, or structured error handling. Also covers reporters (`createConsoleReporter`, `createFetchReporter` from nostics/reporters/fetch, `createFileReporter` from nostics/reporters/node, `createDevReporter` from nostics/reporters/dev), formatters (`formatDiagnostic`, `ansiFormatter`, `jsonFormatter`), and Vite plugins (`nosticsStrip` from nostics/unplugin/strip-transform, `nosticsCollector` from nostics/unplugin/dev-server-collector).' | ||
| description: 'Structured diagnostic code library for JavaScript/TypeScript. Turns errors and other conditions into typed, machine-readable `Diagnostic` instances with stable codes, docs URLs, and actionable fields. Use this skill whenever the project imports `nostics`, or works with `defineDiagnostics`, the `Diagnostic` class, diagnostic code registries, or structured error handling. Also covers reporters (`createConsoleReporter`, `createFetchReporter` from nostics/reporters/fetch, `createFileReporter` from nostics/reporters/node, `createDevReporter` from nostics/reporters/dev), formatters (`formatDiagnostic`, `ansiFormatter`, `jsonFormatter`), and Vite plugins (`nosticsStrip` from @nostics/unplugin/strip-transform, `nosticsCollector` from @nostics/unplugin/dev-server-collector).' | ||
| --- | ||
@@ -198,3 +198,3 @@ | ||
| Two unplugin-based plugins: `nostics/unplugin/strip-transform` (for library authors, build-time optimization) and `nostics/unplugin/dev-server-collector` (for app developers consuming a nostics-using library, dev-time diagnostic collection). | ||
| Two unplugin-based plugins shipped in the separate `@nostics/unplugin` package (install it as a dev dependency): `@nostics/unplugin/strip-transform` (for library authors, build-time optimization) and `@nostics/unplugin/dev-server-collector` (for app developers consuming a nostics-using library, dev-time diagnostic collection). | ||
@@ -206,3 +206,3 @@ ### `nosticsStrip`: Build-time AST transform | ||
| ```ts | ||
| import { nosticsStrip } from 'nostics/unplugin/strip-transform' | ||
| import { nosticsStrip } from '@nostics/unplugin/strip-transform' | ||
@@ -225,3 +225,3 @@ export default defineConfig({ | ||
| ```ts | ||
| import { nosticsCollector } from 'nostics/unplugin/dev-server-collector' | ||
| import { nosticsCollector } from '@nostics/unplugin/dev-server-collector' | ||
@@ -246,4 +246,4 @@ export default defineConfig({ | ||
| // vite.config.ts | ||
| import { nosticsStrip } from 'nostics/unplugin/strip-transform' | ||
| import { nosticsCollector } from 'nostics/unplugin/dev-server-collector' | ||
| import { nosticsCollector } from '@nostics/unplugin/dev-server-collector' | ||
| import { nosticsStrip } from '@nostics/unplugin/strip-transform' | ||
@@ -250,0 +250,0 @@ export default defineConfig({ |
| import { UnpluginInstance } from "unplugin"; | ||
| //#region src/unplugin/dev-server-collector.d.ts | ||
| interface NosticsCollectorOptions { | ||
| /** | ||
| * Path to the log file. | ||
| * @default '.nostics.log' | ||
| */ | ||
| logFile?: string; | ||
| /** | ||
| * Enable debug logging for the plugin. | ||
| * @default !!process.env.DEBUG | ||
| */ | ||
| debug?: boolean; | ||
| /** | ||
| * Stack frames matching ANY of these patterns are removed from each | ||
| * diagnostic's stack trace before it is written to the log file. | ||
| * Forwarded to {@link createFileReporter}. | ||
| */ | ||
| excludeStackFrames?: readonly RegExp[]; | ||
| } | ||
| /** | ||
| * Dev-server collector that forwards browser diagnostics to a log file. | ||
| * | ||
| * Listens on the Vite dev-server WebSocket for diagnostics emitted by | ||
| * `createDevReporter()` in the browser and appends them as NDJSON to a local | ||
| * log file via `createFileReporter`. | ||
| * | ||
| * ```ts | ||
| * // vite.config.ts | ||
| * import { nosticsCollector } from 'nostics/unplugin/dev-server-collector' | ||
| * export default defineConfig({ plugins: [nosticsCollector.vite()] }) | ||
| * ``` | ||
| * | ||
| * Note: Vite only. Other unplugin adapters are no-ops. | ||
| */ | ||
| declare const nosticsCollector: UnpluginInstance<NosticsCollectorOptions | undefined>; | ||
| //#endregion | ||
| export { NosticsCollectorOptions, nosticsCollector }; | ||
| //# sourceMappingURL=dev-server-collector.d.mts.map |
| import { createFileReporter } from "../reporters/node.mjs"; | ||
| import { createUnplugin } from "unplugin"; | ||
| import { existsSync, writeFileSync } from "node:fs"; | ||
| import { relative, resolve } from "node:path"; | ||
| //#region src/unplugin/dev-server-collector.ts | ||
| /** | ||
| * Dev-server collector that forwards browser diagnostics to a log file. | ||
| * | ||
| * Listens on the Vite dev-server WebSocket for diagnostics emitted by | ||
| * `createDevReporter()` in the browser and appends them as NDJSON to a local | ||
| * log file via `createFileReporter`. | ||
| * | ||
| * ```ts | ||
| * // vite.config.ts | ||
| * import { nosticsCollector } from 'nostics/unplugin/dev-server-collector' | ||
| * export default defineConfig({ plugins: [nosticsCollector.vite()] }) | ||
| * ``` | ||
| * | ||
| * Note: Vite only. Other unplugin adapters are no-ops. | ||
| */ | ||
| const nosticsCollector = createUnplugin((options) => { | ||
| const logFile = options?.logFile ?? ".nostics.log"; | ||
| const log = options?.debug ?? !!process.env.DEBUG ? (...args) => console.log("[nostics]", ...args) : () => {}; | ||
| const reporter = /* @__PURE__ */ createFileReporter({ | ||
| logFile, | ||
| excludeStackFrames: options?.excludeStackFrames | ||
| }); | ||
| return { | ||
| name: "nostics-collector", | ||
| enforce: "pre", | ||
| vite: { configureServer(server) { | ||
| const resolvedLogFile = resolve(server.config.root, logFile); | ||
| if (!existsSync(resolvedLogFile)) writeFileSync(resolvedLogFile, ""); | ||
| const displayPath = relative(process.cwd(), resolvedLogFile) || logFile; | ||
| server.config.logger.info(`📋 nostics ··· saving logs to ${displayPath}`); | ||
| log("listening for diagnostics on ws"); | ||
| server.ws.on("nostics:report", (data) => { | ||
| log("received diagnostic", data.name ?? data); | ||
| reporter(data, {}); | ||
| log("wrote diagnostic to", displayPath); | ||
| }); | ||
| } } | ||
| }; | ||
| }); | ||
| //#endregion | ||
| export { nosticsCollector }; | ||
| //# sourceMappingURL=dev-server-collector.mjs.map |
| {"version":3,"file":"dev-server-collector.mjs","names":[],"sources":["../../src/unplugin/dev-server-collector.ts"],"sourcesContent":["import type { UnpluginInstance } from 'unplugin'\nimport type { FileReporterOptions } from '../reporters/node'\nimport { existsSync, writeFileSync } from 'node:fs'\nimport { relative, resolve } from 'node:path'\nimport { createUnplugin } from 'unplugin'\nimport { createFileReporter } from '../reporters/node'\n\nexport interface NosticsCollectorOptions {\n /**\n * Path to the log file.\n * @default '.nostics.log'\n */\n logFile?: string\n\n /**\n * Enable debug logging for the plugin.\n * @default !!process.env.DEBUG\n */\n debug?: boolean\n\n /**\n * Stack frames matching ANY of these patterns are removed from each\n * diagnostic's stack trace before it is written to the log file.\n * Forwarded to {@link createFileReporter}.\n */\n excludeStackFrames?: readonly RegExp[]\n}\n\n/**\n * Dev-server collector that forwards browser diagnostics to a log file.\n *\n * Listens on the Vite dev-server WebSocket for diagnostics emitted by\n * `createDevReporter()` in the browser and appends them as NDJSON to a local\n * log file via `createFileReporter`.\n *\n * ```ts\n * // vite.config.ts\n * import { nosticsCollector } from 'nostics/unplugin/dev-server-collector'\n * export default defineConfig({ plugins: [nosticsCollector.vite()] })\n * ```\n *\n * Note: Vite only. Other unplugin adapters are no-ops.\n */\nexport const nosticsCollector: UnpluginInstance<NosticsCollectorOptions | undefined>\n = createUnplugin((options) => {\n const logFile = options?.logFile ?? '.nostics.log'\n const debug = options?.debug ?? !!process.env.DEBUG\n // eslint-disable-next-line no-console -- debug logging opt-in\n const log = debug ? (...args: unknown[]) => console.log('[nostics]', ...args) : () => {}\n const reporterOptions: FileReporterOptions = {\n logFile,\n excludeStackFrames: options?.excludeStackFrames,\n }\n const reporter = createFileReporter(reporterOptions)\n\n return {\n name: 'nostics-collector',\n enforce: 'pre',\n\n vite: {\n configureServer(server) {\n const resolvedLogFile = resolve(server.config.root, logFile)\n if (!existsSync(resolvedLogFile)) {\n writeFileSync(resolvedLogFile, '')\n }\n const displayPath = relative(process.cwd(), resolvedLogFile) || logFile\n server.config.logger.info(`📋 nostics ··· saving logs to ${displayPath}`)\n\n log('listening for diagnostics on ws')\n\n server.ws.on('nostics:report', (data) => {\n // TODO: validate data shape\n log('received diagnostic', data.name ?? data)\n reporter(data, {})\n log('wrote diagnostic to', displayPath)\n })\n },\n },\n }\n })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,mBACT,gBAAgB,YAAY;CAC5B,MAAM,UAAU,SAAS,WAAW;CAGpC,MAAM,MAFQ,SAAS,SAAS,CAAC,CAAC,QAAQ,IAAI,SAEzB,GAAG,SAAoB,QAAQ,IAAI,aAAa,GAAG,KAAK,SAAS;CAKtF,MAAM,WAAW,mCAAmB;EAHlC;EACA,oBAAoB,SAAS;EAEoB,CAAC;CAEpD,OAAO;EACL,MAAM;EACN,SAAS;EAET,MAAM,EACJ,gBAAgB,QAAQ;GACtB,MAAM,kBAAkB,QAAQ,OAAO,OAAO,MAAM,QAAQ;GAC5D,IAAI,CAAC,WAAW,gBAAgB,EAC9B,cAAc,iBAAiB,GAAG;GAEpC,MAAM,cAAc,SAAS,QAAQ,KAAK,EAAE,gBAAgB,IAAI;GAChE,OAAO,OAAO,OAAO,KAAK,iCAAiC,cAAc;GAEzE,IAAI,kCAAkC;GAEtC,OAAO,GAAG,GAAG,mBAAmB,SAAS;IAEvC,IAAI,uBAAuB,KAAK,QAAQ,KAAK;IAC7C,SAAS,MAAM,EAAE,CAAC;IAClB,IAAI,uBAAuB,YAAY;KACvC;KAEL;EACF;EACD"} |
| import { UnpluginInstance } from "unplugin"; | ||
| import MagicString from "magic-string"; | ||
| //#region src/unplugin/transform.d.ts | ||
| interface TransformOptions { | ||
| /** | ||
| * The package name to detect imports from. | ||
| * @default 'nostics' | ||
| */ | ||
| packageName?: string; | ||
| } | ||
| //#endregion | ||
| //#region src/unplugin/strip-transform.d.ts | ||
| type NosticsStripOptions = TransformOptions; | ||
| /** | ||
| * Build-time AST transform that strips diagnostics from production bundles. | ||
| * | ||
| * Marks `defineDiagnostics()` calls as `/*#__PURE__*\/` and wraps diagnostic | ||
| * call sites with a `NODE_ENV !== 'production'` guard so they tree-shake out | ||
| * of production builds. | ||
| * | ||
| * This is an [unplugin](https://github.com/unjs/unplugin) instance. Call the | ||
| * adapter for your bundler (`.vite()`, `.rolldown()`, `.rollup()`, `.webpack()`, | ||
| * `.rspack()`, `.esbuild()`, `.farm()`) to obtain the actual plugin: | ||
| * | ||
| * ```ts | ||
| * // vite.config.ts | ||
| * import { nosticsStrip } from 'nostics/unplugin/strip-transform' | ||
| * export default defineConfig({ plugins: [nosticsStrip.vite()] }) | ||
| * ``` | ||
| */ | ||
| declare const nosticsStrip: UnpluginInstance<NosticsStripOptions | undefined>; | ||
| //#endregion | ||
| export { NosticsStripOptions, nosticsStrip }; | ||
| //# sourceMappingURL=strip-transform.d.mts.map |
| import { createUnplugin } from "unplugin"; | ||
| import { existsSync, readFileSync } from "node:fs"; | ||
| import { dirname, join } from "node:path"; | ||
| import MagicString from "magic-string"; | ||
| import { parseSync } from "oxc-parser"; | ||
| //#region src/unplugin/transform.ts | ||
| const CONDITION = "process.env.NODE_ENV !== \"production\""; | ||
| /** | ||
| * Side-effect-free factory exports (from `nostics` or its subpaths): calling | ||
| * them only builds and returns a value. Marking their call sites pure lets the | ||
| * bundler drop them along with the `defineDiagnostics()` definition that holds | ||
| * them (e.g. `reporters: [createConsoleReporter(), createDevReporter()]`). | ||
| */ | ||
| const PURE_FACTORIES = new Set([ | ||
| "createConsoleReporter", | ||
| "createDevReporter", | ||
| "createFileReporter", | ||
| "createFetchReporter" | ||
| ]); | ||
| /** | ||
| * Transforms code that imports from `nostics`: | ||
| * - Adds `\/*#__PURE__*\/` to `defineDiagnostics()` call expressions | ||
| * - Adds `\/*#__PURE__*\/` to calls of known pure factories (e.g. `createConsoleReporter()`), | ||
| * whether imported from `nostics` or one of its subpaths | ||
| * - Prepends `process.env.NODE_ENV !== 'production' &&` to expression statements using diagnostics variables | ||
| * | ||
| * Also handles cross-file patterns: if a file imports a variable that was | ||
| * created from `defineDiagnostics()` in another file, the usage is tracked | ||
| * and wrapped. | ||
| */ | ||
| function transform(code, id, options, trackedExportsMap) { | ||
| const packageName = options?.packageName ?? "nostics"; | ||
| const ast = parseSync(id, code).program; | ||
| const defineDiagnosticsImports = /* @__PURE__ */ new Set(); | ||
| const pureFactoryImports = /* @__PURE__ */ new Set(); | ||
| for (const node of ast.body) { | ||
| if (node.type !== "ImportDeclaration") continue; | ||
| const source = node.source.value; | ||
| const isRoot = source === packageName; | ||
| const isSubpath = typeof source === "string" && source.startsWith(`${packageName}/`); | ||
| if (!isRoot && !isSubpath) continue; | ||
| for (const spec of node.specifiers) if (spec.type === "ImportSpecifier") { | ||
| const importedName = spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value; | ||
| if (isRoot && importedName === "defineDiagnostics") defineDiagnosticsImports.add(spec.local.name); | ||
| else if (PURE_FACTORIES.has(importedName)) pureFactoryImports.add(spec.local.name); | ||
| } | ||
| } | ||
| const crossFileTracked = /* @__PURE__ */ new Set(); | ||
| if (trackedExportsMap) { | ||
| for (const node of ast.body) if (node.type === "ImportDeclaration" && node.source.value !== packageName) { | ||
| const source = node.source.value; | ||
| if (!source.startsWith(".")) continue; | ||
| const resolvedPath = resolveModulePath(source, id); | ||
| if (!resolvedPath) continue; | ||
| if (!trackedExportsMap.has(resolvedPath)) analyzeModule(resolvedPath, packageName, trackedExportsMap); | ||
| const trackedNames = trackedExportsMap.get(resolvedPath); | ||
| if (trackedNames) { | ||
| for (const spec of node.specifiers) if (spec.type === "ImportSpecifier") { | ||
| const importedName = spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value; | ||
| if (trackedNames.has(importedName)) crossFileTracked.add(spec.local.name); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if (defineDiagnosticsImports.size === 0 && crossFileTracked.size === 0 && pureFactoryImports.size === 0) return; | ||
| const s = new MagicString(code); | ||
| const trackedVars = new Set(crossFileTracked); | ||
| trackTopLevelDefinitions(ast.body, s, defineDiagnosticsImports, trackedVars); | ||
| annotatePureFactoryCalls(ast, s, pureFactoryImports); | ||
| wrapTrackedExpressionStatements(ast, s, trackedVars, /* @__PURE__ */ new Set(), CONDITION); | ||
| if (!s.hasChanged()) return void 0; | ||
| if (trackedExportsMap) { | ||
| const exportedTracked = /* @__PURE__ */ new Set(); | ||
| for (const node of ast.body) if (node.type === "ExportNamedDeclaration" && node.declaration?.type === "VariableDeclaration") { | ||
| for (const decl of node.declaration.declarations) if (decl.id?.type === "Identifier" && trackedVars.has(decl.id.name)) exportedTracked.add(decl.id.name); | ||
| } | ||
| if (exportedTracked.size > 0) trackedExportsMap.set(id, exportedTracked); | ||
| } | ||
| return { | ||
| code: s.toString(), | ||
| map: s.generateMap({ hires: "boundary" }) | ||
| }; | ||
| } | ||
| /** | ||
| * Check if an expression has lower precedence than `&&` and needs inner parens | ||
| * when used as the right-hand side of `guard && expr`. | ||
| */ | ||
| function expressionNeedsParens(node) { | ||
| if (node.type === "ConditionalExpression") return true; | ||
| if (node.type === "LogicalExpression" && (node.operator === "||" || node.operator === "??")) return true; | ||
| if (node.type === "SequenceExpression") return true; | ||
| if (node.type === "AssignmentExpression") return true; | ||
| return false; | ||
| } | ||
| const EXTENSIONS = [ | ||
| ".ts", | ||
| ".tsx", | ||
| ".js", | ||
| ".jsx", | ||
| ".mts", | ||
| ".mjs" | ||
| ]; | ||
| function resolveModulePath(source, importer) { | ||
| const base = join(dirname(importer), source); | ||
| if (existsSync(base)) return base; | ||
| for (const ext of EXTENSIONS) { | ||
| const candidate = base + ext; | ||
| if (existsSync(candidate)) return candidate; | ||
| } | ||
| for (const ext of EXTENSIONS) { | ||
| const candidate = join(base, `index${ext}`); | ||
| if (existsSync(candidate)) return candidate; | ||
| } | ||
| } | ||
| /** | ||
| * Analyze a module to find exported variables derived from nostics calls. | ||
| * Results are cached in trackedExportsMap. | ||
| */ | ||
| function analyzeModule(filePath, packageName, trackedExportsMap) { | ||
| trackedExportsMap.set(filePath, /* @__PURE__ */ new Set()); | ||
| let source; | ||
| try { | ||
| source = readFileSync(filePath, "utf-8"); | ||
| } catch { | ||
| return; | ||
| } | ||
| if (!source.includes(packageName)) return; | ||
| const ast = parseSync(filePath, source).program; | ||
| const defineDiagnosticsImports = /* @__PURE__ */ new Set(); | ||
| for (const node of ast.body) if (node.type === "ImportDeclaration" && node.source.value === packageName) { | ||
| for (const spec of node.specifiers) if (spec.type === "ImportSpecifier") { | ||
| if ((spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value) === "defineDiagnostics") defineDiagnosticsImports.add(spec.local.name); | ||
| } | ||
| } | ||
| if (defineDiagnosticsImports.size === 0) return; | ||
| const trackedExports = /* @__PURE__ */ new Set(); | ||
| for (const node of ast.body) if (node.type === "ExportNamedDeclaration" && node.declaration?.type === "VariableDeclaration") { | ||
| for (const decl of node.declaration.declarations) if (decl.init?.type === "CallExpression" && decl.init.callee?.type === "Identifier" && defineDiagnosticsImports.has(decl.init.callee.name) && decl.id?.type === "Identifier") trackedExports.add(decl.id.name); | ||
| } | ||
| if (trackedExports.size > 0) trackedExportsMap.set(filePath, trackedExports); | ||
| } | ||
| function trackTopLevelDefinitions(body, s, defineDiagnosticsImports, trackedVars) { | ||
| for (const node of body) if (node.type === "VariableDeclaration") trackVariableDeclaration(node, s, defineDiagnosticsImports, trackedVars); | ||
| else if (node.type === "ExportNamedDeclaration" && node.declaration?.type === "VariableDeclaration") trackVariableDeclaration(node.declaration, s, defineDiagnosticsImports, trackedVars); | ||
| } | ||
| function trackVariableDeclaration(node, s, defineDiagnosticsImports, trackedVars) { | ||
| for (const decl of node.declarations) if (decl.init?.type === "CallExpression" && decl.init.callee?.type === "Identifier" && defineDiagnosticsImports.has(decl.init.callee.name) && decl.id?.type === "Identifier") { | ||
| trackedVars.add(decl.id.name); | ||
| s.appendLeft(decl.init.start, "/*#__PURE__*/ "); | ||
| } | ||
| } | ||
| /** | ||
| * Walks the AST and prepends `\/*#__PURE__*\/` to every call of an imported | ||
| * pure factory (its callee is an identifier in `pureFactoryImports`). The | ||
| * annotation only matters when the call's result is unused, so it is always | ||
| * safe: used results are kept by the bundler regardless. | ||
| */ | ||
| function annotatePureFactoryCalls(node, s, pureFactoryImports) { | ||
| if (!node || typeof node !== "object") return; | ||
| if (Array.isArray(node)) { | ||
| for (const child of node) annotatePureFactoryCalls(child, s, pureFactoryImports); | ||
| return; | ||
| } | ||
| if (node.type === "CallExpression" && node.callee?.type === "Identifier" && pureFactoryImports.has(node.callee.name)) s.appendLeft(node.start, "/*#__PURE__*/ "); | ||
| for (const key in node) { | ||
| if (key === "type" || key === "start" || key === "end") continue; | ||
| annotatePureFactoryCalls(node[key], s, pureFactoryImports); | ||
| } | ||
| } | ||
| function wrapTrackedExpressionStatements(node, s, trackedVars, shadowedVars, condition) { | ||
| if (!node || typeof node !== "object") return; | ||
| if (Array.isArray(node)) { | ||
| for (const child of node) wrapTrackedExpressionStatements(child, s, trackedVars, shadowedVars, condition); | ||
| return; | ||
| } | ||
| if (node.type === "Program") { | ||
| wrapTrackedExpressionStatements(node.body, s, trackedVars, shadowedVars, condition); | ||
| return; | ||
| } | ||
| if (node.type === "BlockStatement") { | ||
| const blockShadowedVars = new Set(shadowedVars); | ||
| for (const stmt of node.body) collectStatementBindingNames(stmt, blockShadowedVars); | ||
| wrapTrackedExpressionStatements(node.body, s, trackedVars, blockShadowedVars, condition); | ||
| return; | ||
| } | ||
| if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") { | ||
| if (node.body?.type === "BlockStatement") { | ||
| const functionShadowedVars = new Set(shadowedVars); | ||
| for (const param of node.params ?? []) collectPatternNames(param, functionShadowedVars); | ||
| wrapTrackedExpressionStatements(node.body, s, trackedVars, functionShadowedVars, condition); | ||
| } | ||
| return; | ||
| } | ||
| if (node.type === "CatchClause") { | ||
| const catchShadowedVars = new Set(shadowedVars); | ||
| collectPatternNames(node.param, catchShadowedVars); | ||
| wrapTrackedExpressionStatements(node.body, s, trackedVars, catchShadowedVars, condition); | ||
| return; | ||
| } | ||
| if (node.type === "ExpressionStatement" && expressionUsesTrackedVar(node.expression, trackedVars, shadowedVars)) { | ||
| if (expressionNeedsParens(node.expression)) { | ||
| s.appendLeft(node.expression.start, `${condition} && (`); | ||
| s.appendRight(node.expression.end, `)`); | ||
| } else s.appendLeft(node.expression.start, `${condition} && `); | ||
| return; | ||
| } | ||
| for (const key in node) { | ||
| if (key === "type" || key === "start" || key === "end") continue; | ||
| wrapTrackedExpressionStatements(node[key], s, trackedVars, shadowedVars, condition); | ||
| } | ||
| } | ||
| function collectStatementBindingNames(node, names) { | ||
| if (!node) return; | ||
| if (node.type === "VariableDeclaration") for (const decl of node.declarations) collectPatternNames(decl.id, names); | ||
| else if (node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") collectPatternNames(node.id, names); | ||
| } | ||
| function collectPatternNames(node, names) { | ||
| if (!node) return; | ||
| if (node.type === "Identifier") names.add(node.name); | ||
| else if (node.type === "AssignmentPattern") collectPatternNames(node.left, names); | ||
| else if (node.type === "RestElement") collectPatternNames(node.argument, names); | ||
| else if (node.type === "ArrayPattern") for (const element of node.elements) collectPatternNames(element, names); | ||
| else if (node.type === "ObjectPattern") for (const property of node.properties) collectPatternNames(property.value ?? property.argument, names); | ||
| } | ||
| /** | ||
| * Check if an expression references a tracked variable as the root of a member/call chain. | ||
| */ | ||
| function expressionUsesTrackedVar(node, trackedVars, shadowedVars) { | ||
| if (!node) return false; | ||
| if (node.type === "Identifier") return trackedVars.has(node.name) && !shadowedVars.has(node.name); | ||
| if (node.type === "MemberExpression") return expressionUsesTrackedVar(node.object, trackedVars, shadowedVars); | ||
| if (node.type === "CallExpression") return expressionUsesTrackedVar(node.callee, trackedVars, shadowedVars); | ||
| if (node.type === "LogicalExpression") return expressionUsesTrackedVar(node.left, trackedVars, shadowedVars) || expressionUsesTrackedVar(node.right, trackedVars, shadowedVars); | ||
| if (node.type === "ConditionalExpression") return expressionUsesTrackedVar(node.consequent, trackedVars, shadowedVars) || expressionUsesTrackedVar(node.alternate, trackedVars, shadowedVars); | ||
| if (node.type === "UnaryExpression") return expressionUsesTrackedVar(node.argument, trackedVars, shadowedVars); | ||
| if (node.type === "AwaitExpression") return expressionUsesTrackedVar(node.argument, trackedVars, shadowedVars); | ||
| if (node.type === "SequenceExpression") return node.expressions.some((expr) => expressionUsesTrackedVar(expr, trackedVars, shadowedVars)); | ||
| if (node.type === "ParenthesizedExpression") return expressionUsesTrackedVar(node.expression, trackedVars, shadowedVars); | ||
| return false; | ||
| } | ||
| //#endregion | ||
| //#region src/unplugin/strip-transform.ts | ||
| const JS_EXTENSIONS_RE = /\.[jt]sx?$/; | ||
| const NODE_MODULES_RE = /\/node_modules\//; | ||
| const unpluginFactory = (options) => { | ||
| const trackedExportsMap = /* @__PURE__ */ new Map(); | ||
| return { | ||
| name: "nostics-strip-transform", | ||
| transform: { | ||
| filter: { id: { | ||
| include: JS_EXTENSIONS_RE, | ||
| exclude: NODE_MODULES_RE | ||
| } }, | ||
| handler(code, id) { | ||
| const result = transform(code, id, options, trackedExportsMap); | ||
| if (!result) return; | ||
| return { | ||
| code: result.code, | ||
| map: result.map | ||
| }; | ||
| } | ||
| } | ||
| }; | ||
| }; | ||
| /** | ||
| * Build-time AST transform that strips diagnostics from production bundles. | ||
| * | ||
| * Marks `defineDiagnostics()` calls as `/*#__PURE__*\/` and wraps diagnostic | ||
| * call sites with a `NODE_ENV !== 'production'` guard so they tree-shake out | ||
| * of production builds. | ||
| * | ||
| * This is an [unplugin](https://github.com/unjs/unplugin) instance. Call the | ||
| * adapter for your bundler (`.vite()`, `.rolldown()`, `.rollup()`, `.webpack()`, | ||
| * `.rspack()`, `.esbuild()`, `.farm()`) to obtain the actual plugin: | ||
| * | ||
| * ```ts | ||
| * // vite.config.ts | ||
| * import { nosticsStrip } from 'nostics/unplugin/strip-transform' | ||
| * export default defineConfig({ plugins: [nosticsStrip.vite()] }) | ||
| * ``` | ||
| */ | ||
| const nosticsStrip = createUnplugin(unpluginFactory); | ||
| //#endregion | ||
| export { nosticsStrip }; | ||
| //# sourceMappingURL=strip-transform.mjs.map |
| {"version":3,"file":"strip-transform.mjs","names":[],"sources":["../../src/unplugin/transform.ts","../../src/unplugin/strip-transform.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport MagicString from 'magic-string'\nimport { parseSync } from 'oxc-parser'\n\nexport interface TransformResult {\n code: string\n map: ReturnType<MagicString['generateMap']>\n}\n\nexport interface TransformOptions {\n /**\n * The package name to detect imports from.\n * @default 'nostics'\n */\n packageName?: string\n}\n\n/**\n * Cross-file state: maps file paths to sets of exported variable names\n * that are derived from nostics function calls (e.g. `defineDiagnostics`)\n */\nexport type TrackedExportsMap = Map<string, Set<string>>\n\nconst CONDITION = 'process.env.NODE_ENV !== \"production\"'\n\n/**\n * Side-effect-free factory exports (from `nostics` or its subpaths): calling\n * them only builds and returns a value. Marking their call sites pure lets the\n * bundler drop them along with the `defineDiagnostics()` definition that holds\n * them (e.g. `reporters: [createConsoleReporter(), createDevReporter()]`).\n */\nconst PURE_FACTORIES = new Set([\n 'createConsoleReporter',\n 'createDevReporter',\n 'createFileReporter',\n 'createFetchReporter',\n])\n\n/**\n * Transforms code that imports from `nostics`:\n * - Adds `\\/*#__PURE__*\\/` to `defineDiagnostics()` call expressions\n * - Adds `\\/*#__PURE__*\\/` to calls of known pure factories (e.g. `createConsoleReporter()`),\n * whether imported from `nostics` or one of its subpaths\n * - Prepends `process.env.NODE_ENV !== 'production' &&` to expression statements using diagnostics variables\n *\n * Also handles cross-file patterns: if a file imports a variable that was\n * created from `defineDiagnostics()` in another file, the usage is tracked\n * and wrapped.\n */\nexport function transform(\n code: string,\n id: string,\n options?: TransformOptions,\n trackedExportsMap?: TrackedExportsMap,\n): TransformResult | undefined {\n const packageName = options?.packageName ?? 'nostics'\n\n const result = parseSync(id, code)\n const ast = result.program\n\n // Step 1: Find direct defineDiagnostics and pure-factory imports from the\n // package. `defineDiagnostics` lives at the root; pure factories may be\n // imported from the root or a subpath (e.g. `nostics/reporters/dev`).\n const defineDiagnosticsImports = new Set<string>()\n const pureFactoryImports = new Set<string>()\n for (const node of ast.body) {\n if (node.type !== 'ImportDeclaration')\n continue\n const source = node.source.value\n const isRoot = source === packageName\n const isSubpath = typeof source === 'string' && source.startsWith(`${packageName}/`)\n if (!isRoot && !isSubpath)\n continue\n for (const spec of node.specifiers) {\n if (spec.type === 'ImportSpecifier') {\n const importedName\n = spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value\n if (isRoot && importedName === 'defineDiagnostics') {\n defineDiagnosticsImports.add(spec.local.name)\n }\n else if (PURE_FACTORIES.has(importedName)) {\n pureFactoryImports.add(spec.local.name)\n }\n }\n }\n }\n\n // Step 2: Find cross-file tracked imports\n const crossFileTracked = new Set<string>()\n if (trackedExportsMap) {\n for (const node of ast.body) {\n if (node.type === 'ImportDeclaration' && node.source.value !== packageName) {\n const source = node.source.value as string\n // Only resolve relative imports\n if (!source.startsWith('.'))\n continue\n\n const resolvedPath = resolveModulePath(source, id)\n if (!resolvedPath)\n continue\n\n // Analyze the imported module if not already cached\n if (!trackedExportsMap.has(resolvedPath)) {\n analyzeModule(resolvedPath, packageName, trackedExportsMap)\n }\n\n const trackedNames = trackedExportsMap.get(resolvedPath)\n if (trackedNames) {\n for (const spec of node.specifiers) {\n if (spec.type === 'ImportSpecifier') {\n const importedName\n = spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value\n if (trackedNames.has(importedName)) {\n crossFileTracked.add(spec.local.name)\n }\n }\n }\n }\n }\n }\n }\n\n if (\n defineDiagnosticsImports.size === 0\n && crossFileTracked.size === 0\n && pureFactoryImports.size === 0\n ) {\n return undefined\n }\n\n const s = new MagicString(code)\n const trackedVars = new Set<string>(crossFileTracked)\n\n // Step 3a: track top-level variables derived from defineDiagnostics calls and\n // mark the calls as pure.\n trackTopLevelDefinitions(ast.body, s, defineDiagnosticsImports, trackedVars)\n\n // Step 3b: mark calls to known pure factories (e.g. `createConsoleReporter()`) as\n // pure so they can be dropped with the definition that holds them.\n annotatePureFactoryCalls(ast, s, pureFactoryImports)\n\n // Step 3c: wrap expression statements that use a tracked variable while\n // respecting lexical shadowing in nested scopes.\n wrapTrackedExpressionStatements(ast, s, trackedVars, new Set(), CONDITION)\n\n if (!s.hasChanged())\n return undefined\n\n // Step 4: Record exported tracked vars for cross-file tracking\n if (trackedExportsMap) {\n const exportedTracked = new Set<string>()\n for (const node of ast.body) {\n if (\n node.type === 'ExportNamedDeclaration'\n && node.declaration?.type === 'VariableDeclaration'\n ) {\n for (const decl of node.declaration.declarations) {\n if (decl.id?.type === 'Identifier' && trackedVars.has(decl.id.name)) {\n exportedTracked.add(decl.id.name)\n }\n }\n }\n }\n if (exportedTracked.size > 0) {\n trackedExportsMap.set(id, exportedTracked)\n }\n }\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: 'boundary' }),\n }\n}\n\n/**\n * Check if an expression has lower precedence than `&&` and needs inner parens\n * when used as the right-hand side of `guard && expr`.\n */\nfunction expressionNeedsParens(node: any): boolean {\n if (node.type === 'ConditionalExpression')\n return true\n if (node.type === 'LogicalExpression' && (node.operator === '||' || node.operator === '??'))\n return true\n if (node.type === 'SequenceExpression')\n return true\n if (node.type === 'AssignmentExpression')\n return true\n return false\n}\n\nconst EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mts', '.mjs']\n\nfunction resolveModulePath(source: string, importer: string): string | undefined {\n const dir = dirname(importer)\n const base = join(dir, source)\n\n // Try exact path (for imports with extension)\n if (existsSync(base))\n return base\n\n // Try with extensions\n for (const ext of EXTENSIONS) {\n const candidate = base + ext\n if (existsSync(candidate))\n return candidate\n }\n\n // Try index files\n for (const ext of EXTENSIONS) {\n const candidate = join(base, `index${ext}`)\n if (existsSync(candidate))\n return candidate\n }\n\n return undefined\n}\n\n/**\n * Analyze a module to find exported variables derived from nostics calls.\n * Results are cached in trackedExportsMap.\n */\nfunction analyzeModule(\n filePath: string,\n packageName: string,\n trackedExportsMap: TrackedExportsMap,\n): void {\n // Mark as analyzed (even if no tracked exports) to avoid re-analysis\n trackedExportsMap.set(filePath, new Set())\n\n let source: string\n try {\n source = readFileSync(filePath, 'utf-8')\n }\n catch {\n return\n }\n\n if (!source.includes(packageName))\n return\n\n const result = parseSync(filePath, source)\n const ast = result.program\n\n // Find defineDiagnostics imports from the package\n const defineDiagnosticsImports = new Set<string>()\n for (const node of ast.body) {\n if (node.type === 'ImportDeclaration' && node.source.value === packageName) {\n for (const spec of node.specifiers) {\n if (spec.type === 'ImportSpecifier') {\n const importedName\n = spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value\n if (importedName === 'defineDiagnostics') {\n defineDiagnosticsImports.add(spec.local.name)\n }\n }\n }\n }\n }\n\n if (defineDiagnosticsImports.size === 0)\n return\n\n // Find exported variables assigned from imported function calls\n const trackedExports = new Set<string>()\n for (const node of ast.body) {\n if (\n node.type === 'ExportNamedDeclaration'\n && node.declaration?.type === 'VariableDeclaration'\n ) {\n for (const decl of node.declaration.declarations) {\n if (\n decl.init?.type === 'CallExpression'\n && decl.init.callee?.type === 'Identifier'\n && defineDiagnosticsImports.has(decl.init.callee.name)\n && decl.id?.type === 'Identifier'\n ) {\n trackedExports.add(decl.id.name)\n }\n }\n }\n }\n\n if (trackedExports.size > 0) {\n trackedExportsMap.set(filePath, trackedExports)\n }\n}\n\nfunction trackTopLevelDefinitions(\n body: any[],\n s: MagicString,\n defineDiagnosticsImports: Set<string>,\n trackedVars: Set<string>,\n): void {\n for (const node of body) {\n if (node.type === 'VariableDeclaration') {\n trackVariableDeclaration(node, s, defineDiagnosticsImports, trackedVars)\n }\n else if (\n node.type === 'ExportNamedDeclaration'\n && node.declaration?.type === 'VariableDeclaration'\n ) {\n trackVariableDeclaration(node.declaration, s, defineDiagnosticsImports, trackedVars)\n }\n }\n}\n\nfunction trackVariableDeclaration(\n node: any,\n s: MagicString,\n defineDiagnosticsImports: Set<string>,\n trackedVars: Set<string>,\n): void {\n for (const decl of node.declarations) {\n if (\n decl.init?.type === 'CallExpression'\n && decl.init.callee?.type === 'Identifier'\n && defineDiagnosticsImports.has(decl.init.callee.name)\n && decl.id?.type === 'Identifier'\n ) {\n trackedVars.add(decl.id.name)\n s.appendLeft(decl.init.start, '/*#__PURE__*/ ')\n }\n }\n}\n\n/**\n * Walks the AST and prepends `\\/*#__PURE__*\\/` to every call of an imported\n * pure factory (its callee is an identifier in `pureFactoryImports`). The\n * annotation only matters when the call's result is unused, so it is always\n * safe: used results are kept by the bundler regardless.\n */\nfunction annotatePureFactoryCalls(\n node: any,\n s: MagicString,\n pureFactoryImports: Set<string>,\n): void {\n if (!node || typeof node !== 'object')\n return\n\n if (Array.isArray(node)) {\n for (const child of node) {\n annotatePureFactoryCalls(child, s, pureFactoryImports)\n }\n return\n }\n\n if (\n node.type === 'CallExpression'\n && node.callee?.type === 'Identifier'\n && pureFactoryImports.has(node.callee.name)\n ) {\n s.appendLeft(node.start, '/*#__PURE__*/ ')\n }\n\n for (const key in node) {\n if (key === 'type' || key === 'start' || key === 'end')\n continue\n annotatePureFactoryCalls(node[key], s, pureFactoryImports)\n }\n}\n\nfunction wrapTrackedExpressionStatements(\n node: any,\n s: MagicString,\n trackedVars: Set<string>,\n shadowedVars: Set<string>,\n condition: string,\n): void {\n if (!node || typeof node !== 'object')\n return\n\n if (Array.isArray(node)) {\n for (const child of node) {\n wrapTrackedExpressionStatements(child, s, trackedVars, shadowedVars, condition)\n }\n return\n }\n\n if (node.type === 'Program') {\n wrapTrackedExpressionStatements(node.body, s, trackedVars, shadowedVars, condition)\n return\n }\n\n if (node.type === 'BlockStatement') {\n const blockShadowedVars = new Set(shadowedVars)\n for (const stmt of node.body) {\n collectStatementBindingNames(stmt, blockShadowedVars)\n }\n wrapTrackedExpressionStatements(node.body, s, trackedVars, blockShadowedVars, condition)\n return\n }\n\n if (\n node.type === 'FunctionDeclaration'\n || node.type === 'FunctionExpression'\n || node.type === 'ArrowFunctionExpression'\n ) {\n if (node.body?.type === 'BlockStatement') {\n const functionShadowedVars = new Set(shadowedVars)\n for (const param of node.params ?? []) {\n collectPatternNames(param, functionShadowedVars)\n }\n wrapTrackedExpressionStatements(node.body, s, trackedVars, functionShadowedVars, condition)\n }\n return\n }\n\n if (node.type === 'CatchClause') {\n const catchShadowedVars = new Set(shadowedVars)\n collectPatternNames(node.param, catchShadowedVars)\n wrapTrackedExpressionStatements(node.body, s, trackedVars, catchShadowedVars, condition)\n return\n }\n\n if (\n node.type === 'ExpressionStatement'\n && expressionUsesTrackedVar(node.expression, trackedVars, shadowedVars)\n ) {\n if (expressionNeedsParens(node.expression)) {\n s.appendLeft(node.expression.start, `${condition} && (`)\n s.appendRight(node.expression.end, `)`)\n }\n else {\n s.appendLeft(node.expression.start, `${condition} && `)\n }\n return\n }\n\n for (const key in node) {\n if (key === 'type' || key === 'start' || key === 'end')\n continue\n wrapTrackedExpressionStatements(node[key], s, trackedVars, shadowedVars, condition)\n }\n}\n\nfunction collectStatementBindingNames(node: any, names: Set<string>): void {\n if (!node)\n return\n\n if (node.type === 'VariableDeclaration') {\n for (const decl of node.declarations) {\n collectPatternNames(decl.id, names)\n }\n }\n else if (node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') {\n collectPatternNames(node.id, names)\n }\n}\n\nfunction collectPatternNames(node: any, names: Set<string>): void {\n if (!node)\n return\n\n if (node.type === 'Identifier') {\n names.add(node.name)\n }\n else if (node.type === 'AssignmentPattern') {\n collectPatternNames(node.left, names)\n }\n else if (node.type === 'RestElement') {\n collectPatternNames(node.argument, names)\n }\n else if (node.type === 'ArrayPattern') {\n for (const element of node.elements) {\n collectPatternNames(element, names)\n }\n }\n else if (node.type === 'ObjectPattern') {\n for (const property of node.properties) {\n collectPatternNames(property.value ?? property.argument, names)\n }\n }\n}\n\n/**\n * Check if an expression references a tracked variable as the root of a member/call chain.\n */\nfunction expressionUsesTrackedVar(\n node: any,\n trackedVars: Set<string>,\n shadowedVars: Set<string>,\n): boolean {\n if (!node)\n return false\n\n // Direct identifier reference\n if (node.type === 'Identifier') {\n return trackedVars.has(node.name) && !shadowedVars.has(node.name)\n }\n\n // Member expression: check the object (root of the chain)\n if (node.type === 'MemberExpression') {\n return expressionUsesTrackedVar(node.object, trackedVars, shadowedVars)\n }\n\n // Call expression: check the callee\n if (node.type === 'CallExpression') {\n return expressionUsesTrackedVar(node.callee, trackedVars, shadowedVars)\n }\n\n // Logical expression: check either side\n if (node.type === 'LogicalExpression') {\n return (\n expressionUsesTrackedVar(node.left, trackedVars, shadowedVars)\n || expressionUsesTrackedVar(node.right, trackedVars, shadowedVars)\n )\n }\n\n // Conditional (ternary): check consequent or alternate\n if (node.type === 'ConditionalExpression') {\n return (\n expressionUsesTrackedVar(node.consequent, trackedVars, shadowedVars)\n || expressionUsesTrackedVar(node.alternate, trackedVars, shadowedVars)\n )\n }\n\n // Unary expression: check argument\n if (node.type === 'UnaryExpression') {\n return expressionUsesTrackedVar(node.argument, trackedVars, shadowedVars)\n }\n\n // Await expression: check argument\n if (node.type === 'AwaitExpression') {\n return expressionUsesTrackedVar(node.argument, trackedVars, shadowedVars)\n }\n\n // Sequence expression: check any element\n if (node.type === 'SequenceExpression') {\n return node.expressions.some((expr: any) =>\n expressionUsesTrackedVar(expr, trackedVars, shadowedVars),\n )\n }\n\n // Parenthesized expression: unwrap\n if (node.type === 'ParenthesizedExpression') {\n return expressionUsesTrackedVar(node.expression, trackedVars, shadowedVars)\n }\n\n return false\n}\n","import type { UnpluginFactory, UnpluginInstance } from 'unplugin'\nimport type { TrackedExportsMap, TransformOptions } from './transform'\nimport { createUnplugin } from 'unplugin'\nimport { transform } from './transform'\n\nexport type NosticsStripOptions = TransformOptions\n\nconst JS_EXTENSIONS_RE = /\\.[jt]sx?$/\nconst NODE_MODULES_RE = /\\/node_modules\\//\n\nconst unpluginFactory: UnpluginFactory<NosticsStripOptions | undefined> = (options) => {\n const trackedExportsMap: TrackedExportsMap = new Map()\n\n return {\n name: 'nostics-strip-transform',\n\n transform: {\n filter: {\n id: {\n include: JS_EXTENSIONS_RE,\n exclude: NODE_MODULES_RE,\n },\n },\n handler(code, id) {\n const result = transform(code, id, options, trackedExportsMap)\n if (!result)\n return\n return {\n code: result.code,\n map: result.map,\n }\n },\n },\n }\n}\n\n/**\n * Build-time AST transform that strips diagnostics from production bundles.\n *\n * Marks `defineDiagnostics()` calls as `/*#__PURE__*\\/` and wraps diagnostic\n * call sites with a `NODE_ENV !== 'production'` guard so they tree-shake out\n * of production builds.\n *\n * This is an [unplugin](https://github.com/unjs/unplugin) instance. Call the\n * adapter for your bundler (`.vite()`, `.rolldown()`, `.rollup()`, `.webpack()`,\n * `.rspack()`, `.esbuild()`, `.farm()`) to obtain the actual plugin:\n *\n * ```ts\n * // vite.config.ts\n * import { nosticsStrip } from 'nostics/unplugin/strip-transform'\n * export default defineConfig({ plugins: [nosticsStrip.vite()] })\n * ```\n */\nexport const nosticsStrip: UnpluginInstance<NosticsStripOptions | undefined>\n /* #__PURE__ */ = createUnplugin<NosticsStripOptions | undefined>(unpluginFactory)\n"],"mappings":"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"} |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
0
-100%22
-4.35%123
5.13%2
-60%78471
-37.35%25
-19.35%235
-58.41%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed