asset-doctor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 STkangyh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # asset-doctor ๐Ÿฉบ
2
+
3
+ [![CI](https://github.com/STkangyh/asset-doctor/actions/workflows/ci.yml/badge.svg)](https://github.com/STkangyh/asset-doctor/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/asset-doctor.svg)](https://www.npmjs.com/package/asset-doctor)
5
+
6
+ **Health checks for your `public/` folder โ€” catch runtime 404s before your users do.**
7
+
8
+ Your bundler verifies every `import`. **Nobody verifies your string asset paths.**
9
+ A typo in `"/img/hero.png"` builds fine, deploys fine, and 404s in production.
10
+ Meanwhile dead files pile up in `public/` and a 20 MB SVG ships unnoticed.
11
+
12
+ ```bash
13
+ npx asset-doctor check --max-size 500
14
+ ```
15
+
16
+ ```
17
+ public/ 101 assets ยท scanned 237 files ยท 97 referenced
18
+ โœ– missing 4 reference(s) point to files that don't exist
19
+ /img/hero@2x.png (src/components/Hero.tsx:12)
20
+ /lottie/loading.json (src/hooks/useAnimations.ts:8)
21
+ โœ– oversize 2 file(s) exceed 500 KB
22
+ /img/background.svg (15.0 MB)
23
+ โš  orphans 4 file(s) (15.6 MB) never referenced โ€” review before deleting
24
+ โ„น dynamic 14 template-string reference(s) couldn't be verified
25
+ ```
26
+
27
+ Exits `1` on missing or oversize files โ€” drop it straight into CI.
28
+
29
+ ## Why
30
+
31
+ Real result from the first project this ran on: **10 broken asset references**
32
+ (code fetching `.json` files that had been replaced by `.lottie` versions โ€”
33
+ silent runtime 404s), a **20 MB SVG** shipping to every visitor, and 15 MB of
34
+ orphaned files. All invisible to the bundler, the type checker, and code review.
35
+
36
+ ## What it checks
37
+
38
+ | Check | What it catches |
39
+ | --- | --- |
40
+ | โœ– **missing** | Root-absolute references (`"/img/x.png"`) to files that don't exist โ€” runtime 404s. Found in JS/TS/JSX, CSS, HTML, Markdown, and even `site.webmanifest` icons. |
41
+ | โœ– **oversize** | Public files over `--max-size` KB โ€” your asset weight budget, enforced in CI. |
42
+ | โš  **orphans** | Files in `public/` that no scanned file references. Conventional files (`favicon*`, `robots.txt`, `sitemap*.xml`, `*.webmanifest`, `.well-known/**`, โ€ฆ) are exempt by default. |
43
+
44
+ ## Typed asset manifest
45
+
46
+ Stop hand-maintaining an `assets.ts` full of string paths. Generate one:
47
+
48
+ ```bash
49
+ npx asset-doctor manifest -o src/assets.gen.ts
50
+ ```
51
+
52
+ ```ts
53
+ // AUTO-GENERATED by asset-doctor โ€” do not edit.
54
+ export const assets = {
55
+ img: {
56
+ logo: "/img/logo.png",
57
+ heroImage2x: "/img/hero-image_2x.png",
58
+ },
59
+ } as const;
60
+
61
+ export type AssetPath = (typeof assetList)[number];
62
+ ```
63
+
64
+ Now `assets.img.logo` autocompletes, and a renamed file becomes a **compile
65
+ error** instead of a production 404. (Generated files are automatically
66
+ excluded from reference scanning, so the manifest never masks real orphans.)
67
+
68
+ ## Versus existing tools
69
+
70
+ | | asset-doctor | knip / deadfile | assetdrain |
71
+ | --- | :---: | :---: | :---: |
72
+ | Broken references (404s) | โœ… | โŒ | โŒ |
73
+ | Orphaned public assets | โœ… | โŒ module graph only โ€” string paths are invisible to it | โœ… |
74
+ | Size budget for CI | โœ… | โŒ | โŒ |
75
+ | Typed manifest generator | โœ… | โŒ | โŒ |
76
+ | Runtime dependencies | **0** | โ€” | โ€” |
77
+
78
+ Module-graph tools (knip, deadfile) are great at unused *code*, but `public/`
79
+ assets referenced by URL string never enter the module graph โ€” they can't see
80
+ them. assetdrain cleans unused assets but doesn't catch broken references,
81
+ which is where the actual bugs are.
82
+
83
+ ## Usage
84
+
85
+ ```bash
86
+ # Full health check (CI-friendly: exits 1 on missing/oversize)
87
+ asset-doctor check --max-size 500
88
+
89
+ # Treat orphans as errors too
90
+ asset-doctor check --fail-orphans
91
+
92
+ # Custom public dir, extra orphan exemptions, JSON output
93
+ asset-doctor check --public static --exempt "og/**" --json
94
+
95
+ # Generate the typed manifest
96
+ asset-doctor manifest -o src/assets.gen.ts
97
+ ```
98
+
99
+ ### GitHub Action
100
+
101
+ ```yaml
102
+ - uses: STkangyh/asset-doctor@v1
103
+ with:
104
+ max-size: 500
105
+ ```
106
+
107
+ ### Library
108
+
109
+ ```ts
110
+ import { scan, generateManifest, collectAssets } from "asset-doctor";
111
+
112
+ const report = scan({ root: process.cwd(), maxSizeKb: 500 });
113
+ if (report.missing.length > 0) process.exit(1);
114
+ ```
115
+
116
+ ## Honest limitations
117
+
118
+ - Matching is **exact string matching** (after stripping `?query`/`#hash`).
119
+ Paths built with template interpolation can't be verified โ€” they're counted
120
+ and reported as `dynamic`, never guessed at.
121
+ - Because of dynamic paths, **orphans are warnings by default**. Review before
122
+ deleting; use `--fail-orphans` once you trust the result.
123
+ - A root-absolute string with an asset extension (`"/foo/bar.json"`) is assumed
124
+ to target `public/`. If it's actually an API route, add it to `--exempt`.
125
+
126
+ ## Sister project
127
+
128
+ ๐Ÿฉบ [lottie-doctor](https://github.com/STkangyh/lottie-doctor) โ€” diagnose and
129
+ repair broken Lottie files (garbled text, size bloat).
130
+
131
+ ## License
132
+
133
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import { parseArgs } from "node:util";
5
+ import { generateManifest } from "./manifest.js";
6
+ import { collectAssets, scan } from "./scan.js";
7
+ import { GENERATED_MARKER } from "./util.js";
8
+ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
9
+ const paint = (code) => (s) => useColor ? `\x1b[${code}m${s}\x1b[0m` : s;
10
+ const c = {
11
+ red: paint("31"),
12
+ green: paint("32"),
13
+ yellow: paint("33"),
14
+ blue: paint("36"),
15
+ dim: paint("2"),
16
+ bold: paint("1"),
17
+ };
18
+ function fmtBytes(n) {
19
+ if (n >= 1024 * 1024)
20
+ return `${(n / 1024 / 1024).toFixed(1)} MB`;
21
+ if (n >= 1024)
22
+ return `${(n / 1024).toFixed(1)} KB`;
23
+ return `${n} B`;
24
+ }
25
+ const HELP = `asset-doctor โ€” health checks for your public/ assets
26
+
27
+ Usage:
28
+ asset-doctor check [--root <dir>] [--public <dir>] [--max-size <KB>]
29
+ [--fail-orphans] [--exempt <glob>]... [--json]
30
+ asset-doctor manifest [-o <file>] [--root <dir>] [--public <dir>]
31
+
32
+ check Cross-reference public/ against your source code.
33
+ โœ– missing references to files that don't exist (runtime 404s)
34
+ โš  orphans files in public/ that nothing references
35
+ โœ– oversize files exceeding --max-size
36
+ Exits 1 on missing refs or oversize files (CI-friendly).
37
+ manifest Generate a typed TS manifest of every public asset, so a typo in
38
+ an asset path becomes a compile error.
39
+
40
+ Options:
41
+ --root <dir> Project root (default: current directory)
42
+ --public <dir> Public directory name (default: public)
43
+ --max-size <KB> Flag public files larger than this
44
+ --fail-orphans Exit 1 when orphans are found (default: warn only)
45
+ --exempt <glob> Extra orphan exemptions (repeatable)
46
+ --json Machine-readable output
47
+ -o, --output <file> Manifest output path (default: src/assets.gen.ts)
48
+ -h, --help Show this help
49
+ `;
50
+ function cmdCheck(o) {
51
+ const r = scan({
52
+ root: o.root,
53
+ publicDir: o.publicDir,
54
+ maxSizeKb: o.maxSize,
55
+ exemptOrphans: o.exempt,
56
+ });
57
+ const failed = r.missing.length > 0 ||
58
+ r.oversize.length > 0 ||
59
+ (o.failOrphans && r.orphans.length > 0);
60
+ if (o.json) {
61
+ console.log(JSON.stringify(r, null, 2));
62
+ return failed ? 1 : 0;
63
+ }
64
+ console.log(`${c.bold(o.publicDir + "/")} ${r.publicFiles} assets ยท scanned ${r.readableFiles} files ยท ${r.referencedCount} referenced`);
65
+ if (r.missing.length > 0) {
66
+ console.log(` ${c.red("โœ– missing")} ${r.missing.length} reference(s) point to files that don't exist`);
67
+ for (const m of r.missing.slice(0, 8)) {
68
+ console.log(` ${m.ref} ${c.dim(`(${m.file}:${m.line})`)}`);
69
+ }
70
+ if (r.missing.length > 8)
71
+ console.log(c.dim(` โ€ฆ and ${r.missing.length - 8} more (--json for all)`));
72
+ }
73
+ if (r.oversize.length > 0) {
74
+ console.log(` ${c.red("โœ– oversize")} ${r.oversize.length} file(s) exceed ${o.maxSize} KB`);
75
+ for (const f of r.oversize.slice(0, 8)) {
76
+ console.log(` ${f.path} ${c.dim(`(${fmtBytes(f.sizeBytes)})`)}`);
77
+ }
78
+ }
79
+ if (r.orphans.length > 0) {
80
+ console.log(` ${c.yellow("โš  orphans")} ${r.orphans.length} file(s) (${fmtBytes(r.orphanBytes)}) never referenced โ€” review before deleting`);
81
+ for (const f of r.orphans.slice(0, 8)) {
82
+ console.log(` ${f.path} ${c.dim(`(${fmtBytes(f.sizeBytes)})`)}`);
83
+ }
84
+ if (r.orphans.length > 8)
85
+ console.log(c.dim(` โ€ฆ and ${r.orphans.length - 8} more (--json for all)`));
86
+ }
87
+ if (r.dynamicRefs > 0) {
88
+ console.log(` ${c.blue("โ„น dynamic")} ${r.dynamicRefs} template-string reference(s) couldn't be verified`);
89
+ }
90
+ if (!failed && r.orphans.length === 0) {
91
+ console.log(` ${c.green("โœ” healthy")}`);
92
+ }
93
+ return failed ? 1 : 0;
94
+ }
95
+ function cmdManifest(o) {
96
+ const assets = collectAssets(o.root, o.publicDir);
97
+ if (assets.size === 0) {
98
+ console.error(c.red(`error: no assets found under ${o.publicDir}/ (root: ${o.root})`));
99
+ return 2;
100
+ }
101
+ if (existsSync(o.output)) {
102
+ const head = readFileSync(o.output, "utf8").slice(0, 300);
103
+ if (!head.includes(GENERATED_MARKER)) {
104
+ console.error(c.red(`error: ${o.output} exists and was not generated by asset-doctor โ€” refusing to overwrite`));
105
+ return 2;
106
+ }
107
+ }
108
+ mkdirSync(dirname(o.output), { recursive: true });
109
+ writeFileSync(o.output, generateManifest(assets.keys()));
110
+ console.log(`${c.green("โœ”")} wrote ${c.bold(o.output)} (${assets.size} assets)`);
111
+ return 0;
112
+ }
113
+ function main() {
114
+ const { values, positionals } = parseArgs({
115
+ allowPositionals: true,
116
+ options: {
117
+ root: { type: "string" },
118
+ public: { type: "string" },
119
+ "max-size": { type: "string" },
120
+ "fail-orphans": { type: "boolean", default: false },
121
+ exempt: { type: "string", multiple: true },
122
+ json: { type: "boolean", default: false },
123
+ output: { type: "string", short: "o" },
124
+ help: { type: "boolean", short: "h", default: false },
125
+ },
126
+ });
127
+ if (values.help || positionals.length === 0) {
128
+ console.log(HELP);
129
+ return values.help ? 0 : 1;
130
+ }
131
+ const root = values.root ?? process.cwd();
132
+ const publicDir = values.public ?? "public";
133
+ const command = positionals[0];
134
+ switch (command) {
135
+ case "check":
136
+ return cmdCheck({
137
+ root,
138
+ publicDir,
139
+ maxSize: values["max-size"] != null ? Number(values["max-size"]) : null,
140
+ failOrphans: values["fail-orphans"],
141
+ exempt: values.exempt ?? [],
142
+ json: values.json,
143
+ });
144
+ case "manifest":
145
+ return cmdManifest({
146
+ root,
147
+ publicDir,
148
+ output: values.output ?? "src/assets.gen.ts",
149
+ });
150
+ default:
151
+ console.error(c.red(`unknown command: ${command}`));
152
+ console.log(HELP);
153
+ return 2;
154
+ }
155
+ }
156
+ process.exit(main());
157
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { scan, collectAssets } from "./scan.js";
2
+ export { generateManifest } from "./manifest.js";
3
+ export type { MissingRef, OrphanAsset, OversizeAsset, ScanOptions, ScanReport, } from "./types.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,YAAY,EACV,UAAU,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { scan, collectAssets } from "./scan.js";
2
+ export { generateManifest } from "./manifest.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Generate a typed TypeScript manifest module from a list of public web paths
3
+ * (as produced by {@link collectAssets}). The result is a nested `assets`
4
+ * object mirroring the directory structure, plus a flat `assetList` and an
5
+ * `AssetPath` union type โ€” so a typo in an asset path is a compile error.
6
+ */
7
+ export declare function generateManifest(webPaths: Iterable<string>): string;
8
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AA4BA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CA+BnE"}
@@ -0,0 +1,62 @@
1
+ import { GENERATED_MARKER, IDENT_RE, camelCase } from "./util.js";
2
+ function keyFor(node, fileName) {
3
+ const stem = fileName.replace(/\.[^.]+$/, "");
4
+ let key = camelCase(stem) || stem;
5
+ if (!(key in node))
6
+ return key;
7
+ key = camelCase(fileName) || fileName; // disambiguate by extension
8
+ let i = 2;
9
+ let candidate = key;
10
+ while (candidate in node)
11
+ candidate = key + i++;
12
+ return candidate;
13
+ }
14
+ function serialize(node, depth) {
15
+ const pad = " ".repeat(depth);
16
+ const keys = Object.keys(node).sort();
17
+ const lines = keys.map((k) => {
18
+ const v = node[k];
19
+ const key = IDENT_RE.test(k) ? k : JSON.stringify(k);
20
+ return typeof v === "string"
21
+ ? `${pad}${key}: ${JSON.stringify(v)},`
22
+ : `${pad}${key}: ${serialize(v, depth + 1)},`;
23
+ });
24
+ return "{\n" + lines.join("\n") + "\n" + " ".repeat(depth - 1) + "}";
25
+ }
26
+ /**
27
+ * Generate a typed TypeScript manifest module from a list of public web paths
28
+ * (as produced by {@link collectAssets}). The result is a nested `assets`
29
+ * object mirroring the directory structure, plus a flat `assetList` and an
30
+ * `AssetPath` union type โ€” so a typo in an asset path is a compile error.
31
+ */
32
+ export function generateManifest(webPaths) {
33
+ const sorted = [...webPaths].sort();
34
+ const root = {};
35
+ for (const p of sorted) {
36
+ const segs = p.slice(1).split("/");
37
+ const file = segs.pop();
38
+ let node = root;
39
+ for (const dir of segs) {
40
+ let key = camelCase(dir) || dir;
41
+ if (typeof node[key] === "string")
42
+ key += "Dir";
43
+ node = (node[key] ??= {});
44
+ }
45
+ node[keyFor(node, file)] = p;
46
+ }
47
+ const list = sorted.map((p) => ` ${JSON.stringify(p)},`).join("\n");
48
+ return [
49
+ `// ${GENERATED_MARKER} โ€” do not edit.`,
50
+ `// Regenerate with: npx asset-doctor manifest`,
51
+ ``,
52
+ `export const assets = ${serialize(root, 1)} as const;`,
53
+ ``,
54
+ `export const assetList = [`,
55
+ list,
56
+ `] as const;`,
57
+ ``,
58
+ `export type AssetPath = (typeof assetList)[number];`,
59
+ ``,
60
+ ].join("\n");
61
+ }
62
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAIlE,SAAS,MAAM,CAAC,IAAU,EAAE,QAAgB;IAC1C,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC9C,IAAI,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAClC,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IAC/B,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,4BAA4B;IACnE,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,SAAS,GAAG,GAAG,CAAC;IACpB,OAAO,SAAS,IAAI,IAAI;QAAE,SAAS,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC;IAChD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,SAAS,CAAC,IAAU,EAAE,KAAa;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACnB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACrD,OAAO,OAAO,CAAC,KAAK,QAAQ;YAC1B,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG;YACvC,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC;IAClD,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AACxE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAA0B;IACzD,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,MAAM,IAAI,GAAS,EAAE,CAAC;IAEtB,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAG,CAAC;QACzB,IAAI,IAAI,GAAG,IAAI,CAAC;QAChB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;YAChC,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,QAAQ;gBAAE,GAAG,IAAI,KAAK,CAAC;YAChD,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAS,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAErE,OAAO;QACL,MAAM,gBAAgB,iBAAiB;QACvC,+CAA+C;QAC/C,EAAE;QACF,yBAAyB,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,YAAY;QACvD,EAAE;QACF,4BAA4B;QAC5B,IAAI;QACJ,aAAa;QACb,EAAE;QACF,qDAAqD;QACrD,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
package/dist/scan.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { ScanOptions, ScanReport } from "./types.js";
2
+ /** List public assets as web paths (`/img/logo.png`) with their sizes. */
3
+ export declare function collectAssets(root: string, publicDir?: string): Map<string, number>;
4
+ /**
5
+ * Cross-check the public directory against every reference in the codebase.
6
+ *
7
+ * Finds: references to assets that don't exist (runtime 404s), assets nothing
8
+ * references (orphans), and assets over the size budget. String matching is
9
+ * exact after stripping `?query`/`#hash`; root-absolute references built with
10
+ * template interpolation are counted as `dynamicRefs` and never flagged.
11
+ */
12
+ export declare function scan(options?: ScanOptions): ScanReport;
13
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAIV,WAAW,EACX,UAAU,EACX,MAAM,YAAY,CAAC;AAmDpB,0EAA0E;AAC1E,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,SAAW,GACnB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAYrB;AAED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,UAAU,CAwG1D"}
package/dist/scan.js ADDED
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /** Options for {@link scan}. */
2
+ export interface ScanOptions {
3
+ /** Project root to scan. Default: `process.cwd()`. */
4
+ root?: string;
5
+ /** Static-assets directory name, relative to root. Default: `"public"`. */
6
+ publicDir?: string;
7
+ /** Flag public files larger than this many KB. `null` disables. Default: `null`. */
8
+ maxSizeKb?: number | null;
9
+ /**
10
+ * Extra glob patterns (matched against the public-relative path and the
11
+ * basename) exempted from the orphan check, on top of the built-in
12
+ * conventions (favicon*, robots.txt, sitemap*.xml, *.webmanifest, โ€ฆ).
13
+ */
14
+ exemptOrphans?: string[];
15
+ }
16
+ /** A reference in source code to a public asset that does not exist. */
17
+ export interface MissingRef {
18
+ /** The referenced web path, e.g. `/img/hero.png`. */
19
+ ref: string;
20
+ /** Source file containing the reference, relative to root. */
21
+ file: string;
22
+ /** 1-based line number of the reference. */
23
+ line: number;
24
+ }
25
+ /** A public asset that no scanned file references. */
26
+ export interface OrphanAsset {
27
+ /** Web path of the asset, e.g. `/img/old-banner.png`. */
28
+ path: string;
29
+ sizeBytes: number;
30
+ }
31
+ /** A public asset exceeding the size budget. */
32
+ export interface OversizeAsset {
33
+ path: string;
34
+ sizeBytes: number;
35
+ }
36
+ /** The result of {@link scan}. */
37
+ export interface ScanReport {
38
+ /** Number of files found under the public directory. */
39
+ publicFiles: number;
40
+ /** Number of source/text files whose contents were scanned for references. */
41
+ readableFiles: number;
42
+ /** Number of distinct public assets referenced somewhere. */
43
+ referencedCount: number;
44
+ /** Root-absolute references built with template interpolation โ€” unverifiable. */
45
+ dynamicRefs: number;
46
+ missing: MissingRef[];
47
+ orphans: OrphanAsset[];
48
+ oversize: OversizeAsset[];
49
+ /** Total size of all orphaned assets, in bytes. */
50
+ orphanBytes: number;
51
+ }
52
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,wEAAwE;AACxE,MAAM,WAAW,UAAU;IACzB,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,gDAAgD;AAChD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,kCAAkC;AAClC,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;CACrB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/dist/util.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /** First-line marker that identifies files generated by asset-doctor. */
2
+ export declare const GENERATED_MARKER = "AUTO-GENERATED by asset-doctor";
3
+ /**
4
+ * Orphan-check exemptions for files browsers and crawlers fetch by
5
+ * convention, without any reference appearing in source code.
6
+ */
7
+ export declare const DEFAULT_ORPHAN_EXEMPT: string[];
8
+ /** Recursively list all regular files under `dir` (absolute paths). */
9
+ export declare function walk(dir: string): string[];
10
+ /** Minimal glob โ†’ RegExp: `**` crosses slashes, `*` does not, `?` is one char. */
11
+ export declare function globToRegExp(glob: string): RegExp;
12
+ /** Valid bare JS identifier (no quoting needed as an object key). */
13
+ export declare const IDENT_RE: RegExp;
14
+ /** `"hero_image-2x"` โ†’ `"heroImage2x"`. */
15
+ export declare function camelCase(s: string): string;
16
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAGA,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,mCAAmC,CAAC;AAcjE;;;GAGG;AACH,eAAO,MAAM,qBAAqB,UAWjC,CAAC;AAEF,uEAAuE;AACvE,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAoB1C;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBjD;AAED,qEAAqE;AACrE,eAAO,MAAM,QAAQ,QAA+B,CAAC;AAErD,2CAA2C;AAC3C,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAS3C"}
package/dist/util.js ADDED
@@ -0,0 +1,95 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /** First-line marker that identifies files generated by asset-doctor. */
4
+ export const GENERATED_MARKER = "AUTO-GENERATED by asset-doctor";
5
+ /** Directories never worth scanning. */
6
+ const SKIP_DIRS = new Set([
7
+ "node_modules",
8
+ "dist",
9
+ "build",
10
+ "out",
11
+ "coverage",
12
+ "vendor",
13
+ ]);
14
+ const JUNK_FILES = new Set([".DS_Store", "Thumbs.db", "desktop.ini", ".gitkeep"]);
15
+ /**
16
+ * Orphan-check exemptions for files browsers and crawlers fetch by
17
+ * convention, without any reference appearing in source code.
18
+ */
19
+ export const DEFAULT_ORPHAN_EXEMPT = [
20
+ "robots.txt",
21
+ "sitemap*.xml",
22
+ "favicon*",
23
+ "apple-touch-icon*",
24
+ "*.webmanifest",
25
+ "manifest.json",
26
+ "browserconfig.xml",
27
+ "humans.txt",
28
+ "ads.txt",
29
+ ".well-known/**",
30
+ ];
31
+ /** Recursively list all regular files under `dir` (absolute paths). */
32
+ export function walk(dir) {
33
+ const out = [];
34
+ let entries;
35
+ try {
36
+ entries = readdirSync(dir, { withFileTypes: true });
37
+ }
38
+ catch {
39
+ return out;
40
+ }
41
+ for (const e of entries) {
42
+ if (e.isDirectory()) {
43
+ if (SKIP_DIRS.has(e.name))
44
+ continue;
45
+ // skip dot-directories (.git, .next, .vercel, โ€ฆ) except .well-known,
46
+ // which legitimately lives inside public/
47
+ if (e.name.startsWith(".") && e.name !== ".well-known")
48
+ continue;
49
+ out.push(...walk(join(dir, e.name)));
50
+ }
51
+ else if (e.isFile()) {
52
+ if (!JUNK_FILES.has(e.name))
53
+ out.push(join(dir, e.name));
54
+ }
55
+ }
56
+ return out;
57
+ }
58
+ /** Minimal glob โ†’ RegExp: `**` crosses slashes, `*` does not, `?` is one char. */
59
+ export function globToRegExp(glob) {
60
+ const g = glob.replace(/^\//, "");
61
+ let re = "";
62
+ for (let i = 0; i < g.length; i++) {
63
+ const ch = g[i];
64
+ if (ch === "*") {
65
+ if (g[i + 1] === "*") {
66
+ re += ".*";
67
+ i++;
68
+ if (g[i + 1] === "/")
69
+ i++;
70
+ }
71
+ else {
72
+ re += "[^/]*";
73
+ }
74
+ }
75
+ else if (ch === "?") {
76
+ re += "[^/]";
77
+ }
78
+ else {
79
+ re += ch.replace(/[.+^${}()|[\]\\]/, "\\$&");
80
+ }
81
+ }
82
+ return new RegExp("^" + re + "$");
83
+ }
84
+ /** Valid bare JS identifier (no quoting needed as an object key). */
85
+ export const IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
86
+ /** `"hero_image-2x"` โ†’ `"heroImage2x"`. */
87
+ export function camelCase(s) {
88
+ const parts = s.split(/[^A-Za-z0-9]+/).filter(Boolean);
89
+ return parts
90
+ .map((p, i) => i === 0
91
+ ? p.charAt(0).toLowerCase() + p.slice(1)
92
+ : p.charAt(0).toUpperCase() + p.slice(1))
93
+ .join("");
94
+ }
95
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "asset-doctor",
3
+ "version": "0.1.0",
4
+ "description": "Health checks for your public/ assets: find broken references (runtime 404s), orphaned files, and oversized assets โ€” and generate a typed asset manifest. Zero dependencies.",
5
+ "type": "module",
6
+ "bin": {
7
+ "asset-doctor": "dist/cli.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "typecheck": "tsc --noEmit",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "assets",
32
+ "public",
33
+ "unused",
34
+ "orphan",
35
+ "missing",
36
+ "404",
37
+ "broken-links",
38
+ "nextjs",
39
+ "vite",
40
+ "cli",
41
+ "linter",
42
+ "manifest",
43
+ "typescript"
44
+ ],
45
+ "license": "MIT",
46
+ "author": "STkangyh",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/STkangyh/asset-doctor.git"
50
+ },
51
+ "homepage": "https://github.com/STkangyh/asset-doctor#readme",
52
+ "bugs": "https://github.com/STkangyh/asset-doctor/issues",
53
+ "devDependencies": {
54
+ "@types/node": "^22",
55
+ "typescript": "^5",
56
+ "vitest": "^3"
57
+ }
58
+ }