@pro-laico/payload-icons 0.0.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.
Files changed (119) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +9 -0
  3. package/dist/cache/index.d.ts +9 -0
  4. package/dist/cache/index.d.ts.map +1 -0
  5. package/dist/cache/index.js +96 -0
  6. package/dist/cache/index.js.map +1 -0
  7. package/dist/collections/Icon.d.ts +14 -0
  8. package/dist/collections/Icon.d.ts.map +1 -0
  9. package/dist/collections/Icon.js +96 -0
  10. package/dist/collections/Icon.js.map +1 -0
  11. package/dist/collections/IconRequest.d.ts +30 -0
  12. package/dist/collections/IconRequest.d.ts.map +1 -0
  13. package/dist/collections/IconRequest.js +82 -0
  14. package/dist/collections/IconRequest.js.map +1 -0
  15. package/dist/collections/IconSet.d.ts +43 -0
  16. package/dist/collections/IconSet.d.ts.map +1 -0
  17. package/dist/collections/IconSet.js +168 -0
  18. package/dist/collections/IconSet.js.map +1 -0
  19. package/dist/components/Icon.d.ts +35 -0
  20. package/dist/components/Icon.d.ts.map +1 -0
  21. package/dist/components/Icon.js +56 -0
  22. package/dist/components/Icon.js.map +1 -0
  23. package/dist/components/admin/clearIconRequests.d.ts +14 -0
  24. package/dist/components/admin/clearIconRequests.d.ts.map +1 -0
  25. package/dist/components/admin/clearIconRequests.js +52 -0
  26. package/dist/components/admin/clearIconRequests.js.map +1 -0
  27. package/dist/components/admin/iconPreview.d.ts +7 -0
  28. package/dist/components/admin/iconPreview.d.ts.map +1 -0
  29. package/dist/components/admin/iconPreview.js +49 -0
  30. package/dist/components/admin/iconPreview.js.map +1 -0
  31. package/dist/components/admin/iconRowLabel.d.ts +8 -0
  32. package/dist/components/admin/iconRowLabel.d.ts.map +1 -0
  33. package/dist/components/admin/iconRowLabel.js +28 -0
  34. package/dist/components/admin/iconRowLabel.js.map +1 -0
  35. package/dist/components/admin/iconUsagePanel.client.d.ts +26 -0
  36. package/dist/components/admin/iconUsagePanel.client.d.ts.map +1 -0
  37. package/dist/components/admin/iconUsagePanel.client.js +290 -0
  38. package/dist/components/admin/iconUsagePanel.client.js.map +1 -0
  39. package/dist/components/admin/iconUsagePanel.d.ts +12 -0
  40. package/dist/components/admin/iconUsagePanel.d.ts.map +1 -0
  41. package/dist/components/admin/iconUsagePanel.js +68 -0
  42. package/dist/components/admin/iconUsagePanel.js.map +1 -0
  43. package/dist/hooks/formatSVG.d.ts +22 -0
  44. package/dist/hooks/formatSVG.d.ts.map +1 -0
  45. package/dist/hooks/formatSVG.js +270 -0
  46. package/dist/hooks/formatSVG.js.map +1 -0
  47. package/dist/index.d.ts +7 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +10 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/lib/activeField.d.ts +24 -0
  52. package/dist/lib/activeField.d.ts.map +1 -0
  53. package/dist/lib/activeField.js +71 -0
  54. package/dist/lib/activeField.js.map +1 -0
  55. package/dist/lib/authenticated.d.ts +5 -0
  56. package/dist/lib/authenticated.d.ts.map +1 -0
  57. package/dist/lib/authenticated.js +4 -0
  58. package/dist/lib/authenticated.js.map +1 -0
  59. package/dist/lib/defaultAccess.d.ts +8 -0
  60. package/dist/lib/defaultAccess.d.ts.map +1 -0
  61. package/dist/lib/defaultAccess.js +6 -0
  62. package/dist/lib/defaultAccess.js.map +1 -0
  63. package/dist/lib/extractSVG.d.ts +3 -0
  64. package/dist/lib/extractSVG.d.ts.map +1 -0
  65. package/dist/lib/extractSVG.js +27 -0
  66. package/dist/lib/extractSVG.js.map +1 -0
  67. package/dist/lib/getPayloadClient.d.ts +22 -0
  68. package/dist/lib/getPayloadClient.d.ts.map +1 -0
  69. package/dist/lib/getPayloadClient.js +39 -0
  70. package/dist/lib/getPayloadClient.js.map +1 -0
  71. package/dist/lib/mergeHooks.d.ts +8 -0
  72. package/dist/lib/mergeHooks.d.ts.map +1 -0
  73. package/dist/lib/mergeHooks.js +19 -0
  74. package/dist/lib/mergeHooks.js.map +1 -0
  75. package/dist/lib/titleCase.d.ts +7 -0
  76. package/dist/lib/titleCase.d.ts.map +1 -0
  77. package/dist/lib/titleCase.js +17 -0
  78. package/dist/lib/titleCase.js.map +1 -0
  79. package/dist/plugin.d.ts +91 -0
  80. package/dist/plugin.d.ts.map +1 -0
  81. package/dist/plugin.js +72 -0
  82. package/dist/plugin.js.map +1 -0
  83. package/dist/scan/cli.d.ts +14 -0
  84. package/dist/scan/cli.d.ts.map +1 -0
  85. package/dist/scan/cli.js +114 -0
  86. package/dist/scan/cli.js.map +1 -0
  87. package/dist/scan/extract.d.ts +58 -0
  88. package/dist/scan/extract.d.ts.map +1 -0
  89. package/dist/scan/extract.js +286 -0
  90. package/dist/scan/extract.js.map +1 -0
  91. package/dist/scan/index.d.ts +81 -0
  92. package/dist/scan/index.d.ts.map +1 -0
  93. package/dist/scan/index.js +133 -0
  94. package/dist/scan/index.js.map +1 -0
  95. package/dist/scan/live.d.ts +20 -0
  96. package/dist/scan/live.d.ts.map +1 -0
  97. package/dist/scan/live.js +109 -0
  98. package/dist/scan/live.js.map +1 -0
  99. package/dist/scan/load.d.ts +28 -0
  100. package/dist/scan/load.d.ts.map +1 -0
  101. package/dist/scan/load.js +37 -0
  102. package/dist/scan/load.js.map +1 -0
  103. package/dist/scan/types.d.ts +29 -0
  104. package/dist/scan/types.d.ts.map +1 -0
  105. package/dist/scan/types.js +8 -0
  106. package/dist/scan/types.js.map +1 -0
  107. package/dist/types.d.ts +38 -0
  108. package/dist/types.d.ts.map +1 -0
  109. package/dist/types.js +3 -0
  110. package/dist/types.js.map +1 -0
  111. package/dist/usage/recordMiss.d.ts +17 -0
  112. package/dist/usage/recordMiss.d.ts.map +1 -0
  113. package/dist/usage/recordMiss.js +60 -0
  114. package/dist/usage/recordMiss.js.map +1 -0
  115. package/dist/usage/trackIconMiss.d.ts +16 -0
  116. package/dist/usage/trackIconMiss.d.ts.map +1 -0
  117. package/dist/usage/trackIconMiss.js +43 -0
  118. package/dist/usage/trackIconMiss.js.map +1 -0
  119. package/package.json +106 -0
@@ -0,0 +1,109 @@
1
+ import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ import { extractIconUsages } from "./extract.js";
4
+ /**
5
+ * In-process icon-usage scan — the bundler-safe counterpart of the CLI's
6
+ * {@link file://./index.ts | scanIconUsages}. Same fs walk + parser, but with
7
+ * EXTENSIONLESS relative imports (`./extract`, not `./extract.js`) so it bundles
8
+ * cleanly into the Next-rendered admin panel; `./index.ts` keeps `.js`
9
+ * specifiers for the raw-Node CLI, which a bundler can't map back to `.ts`.
10
+ *
11
+ * This is what lets the "Requested icons" panel scan source **live in dev** — so
12
+ * seeing what the code needs requires no build step, CLI, or manifest. (In
13
+ * production the source isn't on disk, so the panel reads the CLI-written
14
+ * manifest instead.)
15
+ */ const DEFAULT_EXTENSIONS = [
16
+ 'tsx',
17
+ 'jsx',
18
+ 'ts',
19
+ 'js',
20
+ 'mdx'
21
+ ];
22
+ const DEFAULT_IGNORE = [
23
+ 'node_modules',
24
+ '.next',
25
+ '.git',
26
+ 'dist',
27
+ 'build',
28
+ 'coverage',
29
+ '.turbo'
30
+ ];
31
+ const DEFAULT_ROOTS = [
32
+ 'src',
33
+ 'app'
34
+ ];
35
+ const toPosix = (p)=>p.split(sep).join('/');
36
+ /** Recursively collect scannable file paths under `dir`. */ const walk = (dir, exts, ignore, out)=>{
37
+ let entries;
38
+ try {
39
+ entries = readdirSync(dir, {
40
+ withFileTypes: true
41
+ });
42
+ } catch {
43
+ return; // unreadable/missing — skip
44
+ }
45
+ for (const entry of entries){
46
+ const full = join(dir, entry.name);
47
+ if (entry.isDirectory()) {
48
+ if (!ignore.has(entry.name)) walk(full, exts, ignore, out);
49
+ } else if (entry.isFile()) {
50
+ const dot = entry.name.lastIndexOf('.');
51
+ if (dot !== -1 && exts.has(entry.name.slice(dot + 1))) out.push(full);
52
+ }
53
+ }
54
+ };
55
+ /**
56
+ * Scan the source tree and return a manifest — the same shape the CLI writes,
57
+ * built in memory. Returns an empty manifest (no throw) when the roots don't
58
+ * exist, so it's safe to call at admin-render time.
59
+ */ export const scanIconUsagesLive = (options = {})=>{
60
+ const cwd = options.cwd ?? process.cwd();
61
+ const roots = options.roots?.length ? options.roots : DEFAULT_ROOTS;
62
+ const exts = new Set((options.extensions?.length ? options.extensions : DEFAULT_EXTENSIONS).map((e)=>e.replace(/^\./, '')));
63
+ const ignore = new Set(options.ignore?.length ? options.ignore : DEFAULT_IGNORE);
64
+ const files = [];
65
+ for (const root of roots){
66
+ const abs = isAbsolute(root) ? root : resolve(cwd, root);
67
+ let stat = null;
68
+ try {
69
+ stat = statSync(abs);
70
+ } catch {
71
+ continue; // missing root — skip
72
+ }
73
+ if (stat.isDirectory()) walk(abs, exts, ignore, files);
74
+ else if (stat.isFile()) files.push(abs);
75
+ }
76
+ const usages = [];
77
+ for (const file of files){
78
+ let source;
79
+ try {
80
+ source = readFileSync(file, 'utf8');
81
+ } catch {
82
+ continue;
83
+ }
84
+ const rel = toPosix(relative(cwd, file));
85
+ for (const u of extractIconUsages(source, {
86
+ components: options.components
87
+ })){
88
+ usages.push({
89
+ name: u.name,
90
+ file: rel,
91
+ line: u.line,
92
+ column: u.column
93
+ });
94
+ }
95
+ }
96
+ // Deterministic ordering, matching the CLI manifest.
97
+ usages.sort((a, b)=>a.name.localeCompare(b.name) || a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column);
98
+ const names = [
99
+ ...new Set(usages.map((u)=>u.name))
100
+ ].sort((a, b)=>a.localeCompare(b));
101
+ return {
102
+ version: 1,
103
+ generatedAt: new Date().toISOString(),
104
+ names,
105
+ usages
106
+ };
107
+ };
108
+
109
+ //# sourceMappingURL=live.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/scan/live.ts"],"sourcesContent":["import { type Dirent, readdirSync, readFileSync, statSync } from 'node:fs'\nimport { isAbsolute, join, relative, resolve, sep } from 'node:path'\n\nimport { extractIconUsages } from './extract'\nimport type { IconUsage, IconUsageManifest } from './types'\n\n/**\n * In-process icon-usage scan — the bundler-safe counterpart of the CLI's\n * {@link file://./index.ts | scanIconUsages}. Same fs walk + parser, but with\n * EXTENSIONLESS relative imports (`./extract`, not `./extract.js`) so it bundles\n * cleanly into the Next-rendered admin panel; `./index.ts` keeps `.js`\n * specifiers for the raw-Node CLI, which a bundler can't map back to `.ts`.\n *\n * This is what lets the \"Requested icons\" panel scan source **live in dev** — so\n * seeing what the code needs requires no build step, CLI, or manifest. (In\n * production the source isn't on disk, so the panel reads the CLI-written\n * manifest instead.)\n */\n\nconst DEFAULT_EXTENSIONS = ['tsx', 'jsx', 'ts', 'js', 'mdx']\nconst DEFAULT_IGNORE = ['node_modules', '.next', '.git', 'dist', 'build', 'coverage', '.turbo']\nconst DEFAULT_ROOTS = ['src', 'app']\n\nexport interface LiveScanOptions {\n /** Directories/files to scan, relative to {@link cwd} or absolute. @default ['src', 'app'] */\n roots?: string[]\n /** Working directory. @default process.cwd() */\n cwd?: string\n /** JSX tag names treated as icon usages. @default ['Icon'] */\n components?: string[]\n /** File extensions (without the dot) to read. @default ['tsx', 'jsx', 'ts', 'js', 'mdx'] */\n extensions?: string[]\n /** Directory names to skip. @default ['node_modules', '.next', '.git', 'dist', 'build', 'coverage', '.turbo'] */\n ignore?: string[]\n}\n\nconst toPosix = (p: string): string => p.split(sep).join('/')\n\n/** Recursively collect scannable file paths under `dir`. */\nconst walk = (dir: string, exts: Set<string>, ignore: Set<string>, out: string[]): void => {\n let entries: Dirent[]\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return // unreadable/missing — skip\n }\n for (const entry of entries) {\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n if (!ignore.has(entry.name)) walk(full, exts, ignore, out)\n } else if (entry.isFile()) {\n const dot = entry.name.lastIndexOf('.')\n if (dot !== -1 && exts.has(entry.name.slice(dot + 1))) out.push(full)\n }\n }\n}\n\n/**\n * Scan the source tree and return a manifest — the same shape the CLI writes,\n * built in memory. Returns an empty manifest (no throw) when the roots don't\n * exist, so it's safe to call at admin-render time.\n */\nexport const scanIconUsagesLive = (options: LiveScanOptions = {}): IconUsageManifest => {\n const cwd = options.cwd ?? process.cwd()\n const roots = options.roots?.length ? options.roots : DEFAULT_ROOTS\n const exts = new Set((options.extensions?.length ? options.extensions : DEFAULT_EXTENSIONS).map((e) => e.replace(/^\\./, '')))\n const ignore = new Set(options.ignore?.length ? options.ignore : DEFAULT_IGNORE)\n\n const files: string[] = []\n for (const root of roots) {\n const abs = isAbsolute(root) ? root : resolve(cwd, root)\n let stat: ReturnType<typeof statSync> | null = null\n try {\n stat = statSync(abs)\n } catch {\n continue // missing root — skip\n }\n if (stat.isDirectory()) walk(abs, exts, ignore, files)\n else if (stat.isFile()) files.push(abs)\n }\n\n const usages: IconUsage[] = []\n for (const file of files) {\n let source: string\n try {\n source = readFileSync(file, 'utf8')\n } catch {\n continue\n }\n const rel = toPosix(relative(cwd, file))\n for (const u of extractIconUsages(source, { components: options.components })) {\n usages.push({ name: u.name, file: rel, line: u.line, column: u.column })\n }\n }\n\n // Deterministic ordering, matching the CLI manifest.\n usages.sort((a, b) => a.name.localeCompare(b.name) || a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column)\n const names = [...new Set(usages.map((u) => u.name))].sort((a, b) => a.localeCompare(b))\n return { version: 1, generatedAt: new Date().toISOString(), names, usages }\n}\n"],"names":["readdirSync","readFileSync","statSync","isAbsolute","join","relative","resolve","sep","extractIconUsages","DEFAULT_EXTENSIONS","DEFAULT_IGNORE","DEFAULT_ROOTS","toPosix","p","split","walk","dir","exts","ignore","out","entries","withFileTypes","entry","full","name","isDirectory","has","isFile","dot","lastIndexOf","slice","push","scanIconUsagesLive","options","cwd","process","roots","length","Set","extensions","map","e","replace","files","root","abs","stat","usages","file","source","rel","u","components","line","column","sort","a","b","localeCompare","names","version","generatedAt","Date","toISOString"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Manifest path resolution + reading — the ONLY part of the scan feature the
3
+ * admin "requested icons" panel needs at runtime. Kept deliberately separate
4
+ * from {@link file://./index.ts} (the build/CLI aggregator) so the admin server
5
+ * component can import it without dragging in the parser/CLI chain.
6
+ *
7
+ * Why that matters: `index.ts` and `extract.ts` are consumed by the raw-Node
8
+ * CLI (`dist/scan/cli.js`), which requires explicit `.js` import specifiers —
9
+ * but a bundler (Next/webpack) resolving the admin component can't map a `.js`
10
+ * specifier back to a `.ts` source, so importing the index chain from the
11
+ * component breaks `next build`. This module sidesteps that entirely: its only
12
+ * relative import is type-only (erased at compile time), so it has NO runtime
13
+ * relative imports and resolves cleanly under both a bundler and raw Node.
14
+ */
15
+ import type { IconUsageManifest } from './types';
16
+ /** Default manifest filename, resolved relative to the scan's working directory. */
17
+ export declare const DEFAULT_MANIFEST_FILENAME = "icon-usage-manifest.json";
18
+ /** Env var the admin panel and scanner both honor to override the manifest path. */
19
+ export declare const MANIFEST_PATH_ENV = "ICON_USAGE_MANIFEST";
20
+ /** Resolves the manifest path: explicit arg → `ICON_USAGE_MANIFEST` env → default filename under `cwd`. */
21
+ export declare const resolveManifestPath: (explicit?: string, cwd?: string) => string;
22
+ /**
23
+ * Reads and parses a manifest from disk. Returns `null` when the file is
24
+ * absent or unparseable, so callers (the admin panel) can render a graceful
25
+ * "run the scan" empty state rather than throwing.
26
+ */
27
+ export declare const loadIconUsageManifest: (path?: string, cwd?: string) => IconUsageManifest | null;
28
+ //# sourceMappingURL=load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/scan/load.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAEhD,oFAAoF;AACpF,eAAO,MAAM,yBAAyB,6BAA6B,CAAA;AAEnE,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,wBAAwB,CAAA;AAEtD,2GAA2G;AAC3G,eAAO,MAAM,mBAAmB,GAAI,WAAW,MAAM,EAAE,MAAK,MAAsB,KAAG,MAGpF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,MAAM,EAAE,MAAM,MAAM,KAAG,iBAAiB,GAAG,IASvF,CAAA"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Manifest path resolution + reading — the ONLY part of the scan feature the
3
+ * admin "requested icons" panel needs at runtime. Kept deliberately separate
4
+ * from {@link file://./index.ts} (the build/CLI aggregator) so the admin server
5
+ * component can import it without dragging in the parser/CLI chain.
6
+ *
7
+ * Why that matters: `index.ts` and `extract.ts` are consumed by the raw-Node
8
+ * CLI (`dist/scan/cli.js`), which requires explicit `.js` import specifiers —
9
+ * but a bundler (Next/webpack) resolving the admin component can't map a `.js`
10
+ * specifier back to a `.ts` source, so importing the index chain from the
11
+ * component breaks `next build`. This module sidesteps that entirely: its only
12
+ * relative import is type-only (erased at compile time), so it has NO runtime
13
+ * relative imports and resolves cleanly under both a bundler and raw Node.
14
+ */ import { readFileSync } from "node:fs";
15
+ import { isAbsolute, resolve } from "node:path";
16
+ /** Default manifest filename, resolved relative to the scan's working directory. */ export const DEFAULT_MANIFEST_FILENAME = 'icon-usage-manifest.json';
17
+ /** Env var the admin panel and scanner both honor to override the manifest path. */ export const MANIFEST_PATH_ENV = 'ICON_USAGE_MANIFEST';
18
+ /** Resolves the manifest path: explicit arg → `ICON_USAGE_MANIFEST` env → default filename under `cwd`. */ export const resolveManifestPath = (explicit, cwd = process.cwd())=>{
19
+ const chosen = explicit ?? process.env[MANIFEST_PATH_ENV] ?? DEFAULT_MANIFEST_FILENAME;
20
+ return isAbsolute(chosen) ? chosen : resolve(cwd, chosen);
21
+ };
22
+ /**
23
+ * Reads and parses a manifest from disk. Returns `null` when the file is
24
+ * absent or unparseable, so callers (the admin panel) can render a graceful
25
+ * "run the scan" empty state rather than throwing.
26
+ */ export const loadIconUsageManifest = (path, cwd)=>{
27
+ const abs = resolveManifestPath(path, cwd);
28
+ try {
29
+ const parsed = JSON.parse(readFileSync(abs, 'utf8'));
30
+ if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.names)) return null;
31
+ return parsed;
32
+ } catch {
33
+ return null;
34
+ }
35
+ };
36
+
37
+ //# sourceMappingURL=load.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/scan/load.ts"],"sourcesContent":["/**\n * Manifest path resolution + reading — the ONLY part of the scan feature the\n * admin \"requested icons\" panel needs at runtime. Kept deliberately separate\n * from {@link file://./index.ts} (the build/CLI aggregator) so the admin server\n * component can import it without dragging in the parser/CLI chain.\n *\n * Why that matters: `index.ts` and `extract.ts` are consumed by the raw-Node\n * CLI (`dist/scan/cli.js`), which requires explicit `.js` import specifiers —\n * but a bundler (Next/webpack) resolving the admin component can't map a `.js`\n * specifier back to a `.ts` source, so importing the index chain from the\n * component breaks `next build`. This module sidesteps that entirely: its only\n * relative import is type-only (erased at compile time), so it has NO runtime\n * relative imports and resolves cleanly under both a bundler and raw Node.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { isAbsolute, resolve } from 'node:path'\n\nimport type { IconUsageManifest } from './types'\n\n/** Default manifest filename, resolved relative to the scan's working directory. */\nexport const DEFAULT_MANIFEST_FILENAME = 'icon-usage-manifest.json'\n\n/** Env var the admin panel and scanner both honor to override the manifest path. */\nexport const MANIFEST_PATH_ENV = 'ICON_USAGE_MANIFEST'\n\n/** Resolves the manifest path: explicit arg → `ICON_USAGE_MANIFEST` env → default filename under `cwd`. */\nexport const resolveManifestPath = (explicit?: string, cwd: string = process.cwd()): string => {\n const chosen = explicit ?? process.env[MANIFEST_PATH_ENV] ?? DEFAULT_MANIFEST_FILENAME\n return isAbsolute(chosen) ? chosen : resolve(cwd, chosen)\n}\n\n/**\n * Reads and parses a manifest from disk. Returns `null` when the file is\n * absent or unparseable, so callers (the admin panel) can render a graceful\n * \"run the scan\" empty state rather than throwing.\n */\nexport const loadIconUsageManifest = (path?: string, cwd?: string): IconUsageManifest | null => {\n const abs = resolveManifestPath(path, cwd)\n try {\n const parsed = JSON.parse(readFileSync(abs, 'utf8')) as IconUsageManifest\n if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.names)) return null\n return parsed\n } catch {\n return null\n }\n}\n"],"names":["readFileSync","isAbsolute","resolve","DEFAULT_MANIFEST_FILENAME","MANIFEST_PATH_ENV","resolveManifestPath","explicit","cwd","process","chosen","env","loadIconUsageManifest","path","abs","parsed","JSON","parse","Array","isArray","names"],"mappings":"AAAA;;;;;;;;;;;;;CAaC,GAED,SAASA,YAAY,QAAQ,UAAS;AACtC,SAASC,UAAU,EAAEC,OAAO,QAAQ,YAAW;AAI/C,kFAAkF,GAClF,OAAO,MAAMC,4BAA4B,2BAA0B;AAEnE,kFAAkF,GAClF,OAAO,MAAMC,oBAAoB,sBAAqB;AAEtD,yGAAyG,GACzG,OAAO,MAAMC,sBAAsB,CAACC,UAAmBC,MAAcC,QAAQD,GAAG,EAAE;IAChF,MAAME,SAASH,YAAYE,QAAQE,GAAG,CAACN,kBAAkB,IAAID;IAC7D,OAAOF,WAAWQ,UAAUA,SAASP,QAAQK,KAAKE;AACpD,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAME,wBAAwB,CAACC,MAAeL;IACnD,MAAMM,MAAMR,oBAAoBO,MAAML;IACtC,IAAI;QACF,MAAMO,SAASC,KAAKC,KAAK,CAAChB,aAAaa,KAAK;QAC5C,IAAI,CAACC,UAAU,OAAOA,WAAW,YAAY,CAACG,MAAMC,OAAO,CAACJ,OAAOK,KAAK,GAAG,OAAO;QAClF,OAAOL;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF,EAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared shape of the icon-usage manifest — the JSON the build-time scan writes
3
+ * and the admin "requested icons" panel reads. Kept in its own dependency-free
4
+ * module so both the Node CLI and the React admin component can import it
5
+ * without dragging in `fs` or JSX.
6
+ */
7
+ /** One literal `<Icon name="…">` occurrence and where it was found. */
8
+ export interface IconUsage {
9
+ /** The resolved literal icon name. */
10
+ name: string;
11
+ /** Repo-relative POSIX path of the file the usage was found in. */
12
+ file: string;
13
+ /** 1-based line of the `name` value. */
14
+ line: number;
15
+ /** 1-based column of the `name` value. */
16
+ column: number;
17
+ }
18
+ /** The manifest written by the scan and consumed by the admin panel. */
19
+ export interface IconUsageManifest {
20
+ /** Schema version, so a future format change can be detected/migrated. */
21
+ version: 1;
22
+ /** ISO timestamp of when the scan ran. */
23
+ generatedAt: string;
24
+ /** Unique, sorted list of every literal icon name requested in the repo. */
25
+ names: string[];
26
+ /** Every individual occurrence, in deterministic (name, file, line) order. */
27
+ usages: IconUsage[];
28
+ }
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scan/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACxB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAA;IACZ,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,OAAO,EAAE,CAAC,CAAA;IACV,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAA;IACnB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,8EAA8E;IAC9E,MAAM,EAAE,SAAS,EAAE,CAAA;CACpB"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared shape of the icon-usage manifest — the JSON the build-time scan writes
3
+ * and the admin "requested icons" panel reads. Kept in its own dependency-free
4
+ * module so both the Node CLI and the React admin component can import it
5
+ * without dragging in `fs` or JSX.
6
+ */ /** One literal `<Icon name="…">` occurrence and where it was found. */ /** The manifest written by the scan and consumed by the admin panel. */ export { };
7
+
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/scan/types.ts"],"sourcesContent":["/**\n * Shared shape of the icon-usage manifest — the JSON the build-time scan writes\n * and the admin \"requested icons\" panel reads. Kept in its own dependency-free\n * module so both the Node CLI and the React admin component can import it\n * without dragging in `fs` or JSX.\n */\n\n/** One literal `<Icon name=\"…\">` occurrence and where it was found. */\nexport interface IconUsage {\n /** The resolved literal icon name. */\n name: string\n /** Repo-relative POSIX path of the file the usage was found in. */\n file: string\n /** 1-based line of the `name` value. */\n line: number\n /** 1-based column of the `name` value. */\n column: number\n}\n\n/** The manifest written by the scan and consumed by the admin panel. */\nexport interface IconUsageManifest {\n /** Schema version, so a future format change can be detected/migrated. */\n version: 1\n /** ISO timestamp of when the scan ran. */\n generatedAt: string\n /** Unique, sorted list of every literal icon name requested in the repo. */\n names: string[]\n /** Every individual occurrence, in deterministic (name, file, line) order. */\n usages: IconUsage[]\n}\n"],"names":[],"mappings":"AAAA;;;;;CAKC,GAED,qEAAqE,GAYrE,sEAAsE,GACtE,WASC"}
@@ -0,0 +1,38 @@
1
+ import type { Access, CollectionConfig, Field } from 'payload';
2
+ /** Access gates for the Icon collection. Any operation you omit falls back to the plugin's
3
+ * defaults: public `read` (icons are frontend assets) and logged-in-admin writes. */
4
+ export interface IconAccess {
5
+ read?: Access;
6
+ create?: Access;
7
+ update?: Access;
8
+ delete?: Access;
9
+ }
10
+ /** Overrides for the `icon` upload collection — replaces collection-level config (slug, adminGroup,
11
+ * access, upload) and extends `fields` / `hooks`. Every field is optional. */
12
+ export interface IconCollectionOverrides {
13
+ /** Slug for the icon collection. @default 'icon' */
14
+ slug?: string;
15
+ /** Admin sidebar group the collection appears under. @default 'Assets' */
16
+ adminGroup?: string;
17
+ /** Override per-operation access. Omitted operations keep the defaults (public read,
18
+ * logged-in-admin writes). */
19
+ access?: IconAccess;
20
+ /** Extra fields appended after the built-in `optimized` + `svgString` fields. */
21
+ fields?: Field[];
22
+ /** Additional collection hooks, APPENDED to the built-ins — your `beforeChange` hooks
23
+ * always see the already-optimized SVG. */
24
+ hooks?: CollectionConfig['hooks'];
25
+ /** Extra `upload` config shallow-merged onto the default `{ mimeTypes: ['image/svg+xml'] }`
26
+ * (e.g. a `staticDir`). */
27
+ upload?: Exclude<CollectionConfig['upload'], boolean>;
28
+ }
29
+ /** Minimal shape of a stored icon doc — the upload fields plus the optimizer's output. */
30
+ export interface IconDoc {
31
+ id: string | number;
32
+ filename?: string | null;
33
+ /** The optimized, sanitized `<svg>…</svg>` string, inlined by the `<Icon>` component. */
34
+ svgString?: string | null;
35
+ /** Human-readable optimization report (e.g. "SVG optimized: 1234 to 567 bytes (54.1% reduction)"). */
36
+ optimized?: string | null;
37
+ }
38
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE9D;sFACsF;AACtF,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;+EAC+E;AAC/E,MAAM,WAAW,uBAAuB;IACtC,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;mCAC+B;IAC/B,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,iFAAiF;IACjF,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAChB;gDAC4C;IAC5C,KAAK,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACjC;gCAC4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAA;CACtD;AAED,0FAA0F;AAC1F,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Minimal shape of a stored icon doc — the upload fields plus the optimizer's output. */ export { };
2
+
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { Access, CollectionConfig, Field } from 'payload'\n\n/** Access gates for the Icon collection. Any operation you omit falls back to the plugin's\n * defaults: public `read` (icons are frontend assets) and logged-in-admin writes. */\nexport interface IconAccess {\n read?: Access\n create?: Access\n update?: Access\n delete?: Access\n}\n\n/** Overrides for the `icon` upload collection — replaces collection-level config (slug, adminGroup,\n * access, upload) and extends `fields` / `hooks`. Every field is optional. */\nexport interface IconCollectionOverrides {\n /** Slug for the icon collection. @default 'icon' */\n slug?: string\n /** Admin sidebar group the collection appears under. @default 'Assets' */\n adminGroup?: string\n /** Override per-operation access. Omitted operations keep the defaults (public read,\n * logged-in-admin writes). */\n access?: IconAccess\n /** Extra fields appended after the built-in `optimized` + `svgString` fields. */\n fields?: Field[]\n /** Additional collection hooks, APPENDED to the built-ins — your `beforeChange` hooks\n * always see the already-optimized SVG. */\n hooks?: CollectionConfig['hooks']\n /** Extra `upload` config shallow-merged onto the default `{ mimeTypes: ['image/svg+xml'] }`\n * (e.g. a `staticDir`). */\n upload?: Exclude<CollectionConfig['upload'], boolean>\n}\n\n/** Minimal shape of a stored icon doc — the upload fields plus the optimizer's output. */\nexport interface IconDoc {\n id: string | number\n filename?: string | null\n /** The optimized, sanitized `<svg>…</svg>` string, inlined by the `<Icon>` component. */\n svgString?: string | null\n /** Human-readable optimization report (e.g. \"SVG optimized: 1234 to 567 bytes (54.1% reduction)\"). */\n optimized?: string | null\n}\n"],"names":[],"mappings":"AA+BA,wFAAwF,GACxF,WAOC"}
@@ -0,0 +1,17 @@
1
+ import type { Payload } from 'payload';
2
+ /**
3
+ * Upserts one unresolved icon request into the `iconRequest` collection:
4
+ * increments `count` + bumps `lastRequestedAt` for an existing name, or creates
5
+ * the row. Self-gating — a no-op unless the collection is registered (via
6
+ * `iconsPlugin({ trackRequests: true })`), so it's always safe to call.
7
+ *
8
+ * Deliberately free of `next/server` / `server-only` imports so it can be unit-
9
+ * tested directly against a booted Payload; the request-scoped, throttled,
10
+ * fire-and-forget wrapper lives in {@link file://./trackIconMiss.ts}.
11
+ *
12
+ * Best-effort by contract: callers should not await it on the render path, and
13
+ * any concurrent-create race (two instances, same new name) simply loses one
14
+ * increment.
15
+ */
16
+ export declare const recordIconMiss: (payload: Payload, name: string) => Promise<void>;
17
+ //# sourceMappingURL=recordMiss.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordMiss.d.ts","sourceRoot":"","sources":["../../src/usage/recordMiss.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,OAAO,EAAE,MAAM,SAAS,CAAA;AAItD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GAAU,SAAS,OAAO,EAAE,MAAM,MAAM,KAAG,OAAO,CAAC,IAAI,CA2BjF,CAAA"}
@@ -0,0 +1,60 @@
1
+ import { ICON_REQUEST_SLUG } from "../collections/IconRequest.js";
2
+ /**
3
+ * Upserts one unresolved icon request into the `iconRequest` collection:
4
+ * increments `count` + bumps `lastRequestedAt` for an existing name, or creates
5
+ * the row. Self-gating — a no-op unless the collection is registered (via
6
+ * `iconsPlugin({ trackRequests: true })`), so it's always safe to call.
7
+ *
8
+ * Deliberately free of `next/server` / `server-only` imports so it can be unit-
9
+ * tested directly against a booted Payload; the request-scoped, throttled,
10
+ * fire-and-forget wrapper lives in {@link file://./trackIconMiss.ts}.
11
+ *
12
+ * Best-effort by contract: callers should not await it on the render path, and
13
+ * any concurrent-create race (two instances, same new name) simply loses one
14
+ * increment.
15
+ */ export const recordIconMiss = async (payload, name)=>{
16
+ if (!name) return;
17
+ const slug = ICON_REQUEST_SLUG;
18
+ // No-op when tracking isn't enabled (collection absent).
19
+ if (!payload.collections?.[ICON_REQUEST_SLUG]) return;
20
+ const now = new Date().toISOString();
21
+ const existing = await payload.find({
22
+ collection: slug,
23
+ where: {
24
+ name: {
25
+ equals: name
26
+ }
27
+ },
28
+ limit: 1,
29
+ depth: 0,
30
+ overrideAccess: true
31
+ });
32
+ const doc = existing.docs[0];
33
+ // Args are cast because `iconRequest` may not exist in a consumer's GENERATED
34
+ // payload types (they need not regenerate to use tracking), so the strict
35
+ // per-collection `data` union would otherwise reject these fields.
36
+ if (doc) {
37
+ await payload.update({
38
+ collection: slug,
39
+ id: doc.id,
40
+ data: {
41
+ count: (doc.count ?? 0) + 1,
42
+ lastRequestedAt: now
43
+ },
44
+ overrideAccess: true
45
+ });
46
+ } else {
47
+ await payload.create({
48
+ collection: slug,
49
+ data: {
50
+ name,
51
+ count: 1,
52
+ firstRequestedAt: now,
53
+ lastRequestedAt: now
54
+ },
55
+ overrideAccess: true
56
+ });
57
+ }
58
+ };
59
+
60
+ //# sourceMappingURL=recordMiss.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/usage/recordMiss.ts"],"sourcesContent":["import type { CollectionSlug, Payload } from 'payload'\n\nimport { ICON_REQUEST_SLUG } from '../collections/IconRequest'\n\n/**\n * Upserts one unresolved icon request into the `iconRequest` collection:\n * increments `count` + bumps `lastRequestedAt` for an existing name, or creates\n * the row. Self-gating — a no-op unless the collection is registered (via\n * `iconsPlugin({ trackRequests: true })`), so it's always safe to call.\n *\n * Deliberately free of `next/server` / `server-only` imports so it can be unit-\n * tested directly against a booted Payload; the request-scoped, throttled,\n * fire-and-forget wrapper lives in {@link file://./trackIconMiss.ts}.\n *\n * Best-effort by contract: callers should not await it on the render path, and\n * any concurrent-create race (two instances, same new name) simply loses one\n * increment.\n */\nexport const recordIconMiss = async (payload: Payload, name: string): Promise<void> => {\n if (!name) return\n const slug = ICON_REQUEST_SLUG as CollectionSlug\n // No-op when tracking isn't enabled (collection absent).\n if (!(payload.collections as Record<string, unknown>)?.[ICON_REQUEST_SLUG]) return\n\n const now = new Date().toISOString()\n const existing = await payload.find({ collection: slug, where: { name: { equals: name } }, limit: 1, depth: 0, overrideAccess: true })\n const doc = existing.docs[0] as { id: string | number; count?: number | null } | undefined\n\n // Args are cast because `iconRequest` may not exist in a consumer's GENERATED\n // payload types (they need not regenerate to use tracking), so the strict\n // per-collection `data` union would otherwise reject these fields.\n if (doc) {\n await payload.update({\n collection: slug,\n id: doc.id,\n data: { count: (doc.count ?? 0) + 1, lastRequestedAt: now },\n overrideAccess: true,\n } as Parameters<typeof payload.update>[0])\n } else {\n await payload.create({\n collection: slug,\n data: { name, count: 1, firstRequestedAt: now, lastRequestedAt: now },\n overrideAccess: true,\n } as Parameters<typeof payload.create>[0])\n }\n}\n"],"names":["ICON_REQUEST_SLUG","recordIconMiss","payload","name","slug","collections","now","Date","toISOString","existing","find","collection","where","equals","limit","depth","overrideAccess","doc","docs","update","id","data","count","lastRequestedAt","create","firstRequestedAt"],"mappings":"AAEA,SAASA,iBAAiB,QAAQ,gCAA4B;AAE9D;;;;;;;;;;;;;CAaC,GACD,OAAO,MAAMC,iBAAiB,OAAOC,SAAkBC;IACrD,IAAI,CAACA,MAAM;IACX,MAAMC,OAAOJ;IACb,yDAAyD;IACzD,IAAI,CAAEE,QAAQG,WAAW,EAA8B,CAACL,kBAAkB,EAAE;IAE5E,MAAMM,MAAM,IAAIC,OAAOC,WAAW;IAClC,MAAMC,WAAW,MAAMP,QAAQQ,IAAI,CAAC;QAAEC,YAAYP;QAAMQ,OAAO;YAAET,MAAM;gBAAEU,QAAQV;YAAK;QAAE;QAAGW,OAAO;QAAGC,OAAO;QAAGC,gBAAgB;IAAK;IACpI,MAAMC,MAAMR,SAASS,IAAI,CAAC,EAAE;IAE5B,8EAA8E;IAC9E,0EAA0E;IAC1E,mEAAmE;IACnE,IAAID,KAAK;QACP,MAAMf,QAAQiB,MAAM,CAAC;YACnBR,YAAYP;YACZgB,IAAIH,IAAIG,EAAE;YACVC,MAAM;gBAAEC,OAAO,AAACL,CAAAA,IAAIK,KAAK,IAAI,CAAA,IAAK;gBAAGC,iBAAiBjB;YAAI;YAC1DU,gBAAgB;QAClB;IACF,OAAO;QACL,MAAMd,QAAQsB,MAAM,CAAC;YACnBb,YAAYP;YACZiB,MAAM;gBAAElB;gBAAMmB,OAAO;gBAAGG,kBAAkBnB;gBAAKiB,iBAAiBjB;YAAI;YACpEU,gBAAgB;QAClB;IACF;AACF,EAAC"}
@@ -0,0 +1,16 @@
1
+ import 'server-only';
2
+ /**
3
+ * Fire-and-forget runtime recorder for an unresolved icon name. Called by the
4
+ * `<Icon>` server component when a name doesn't resolve. NEVER blocks rendering
5
+ * or throws:
6
+ *
7
+ * - Throttled per name per process (so a hot page doesn't hammer the DB).
8
+ * - Deferred via Next's `after()` so the write runs AFTER the response streams
9
+ * (and the serverless function stays alive to finish it).
10
+ * - Best-effort: every failure is swallowed, and it's a no-op unless the
11
+ * `iconRequest` collection is registered (see {@link recordIconMiss}).
12
+ *
13
+ * Force-disable with `ICON_USAGE_TRACKING=false`.
14
+ */
15
+ export declare const trackIconMiss: (name: string) => void;
16
+ //# sourceMappingURL=trackIconMiss.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trackIconMiss.d.ts","sourceRoot":"","sources":["../../src/usage/trackIconMiss.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAapB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,IAuB5C,CAAA"}
@@ -0,0 +1,43 @@
1
+ import "server-only";
2
+ import { after } from "next/server";
3
+ import { getPayloadClient } from "../lib/getPayloadClient.js";
4
+ import { recordIconMiss } from "./recordMiss.js";
5
+ /** Don't write the same name more than once per this window per process. */ const THROTTLE_MS = 60_000;
6
+ /** Evict stale throttle entries once the map exceeds this — dynamic names would otherwise grow it forever. */ const MAX_TRACKED = 500;
7
+ const lastRecorded = new Map();
8
+ /**
9
+ * Fire-and-forget runtime recorder for an unresolved icon name. Called by the
10
+ * `<Icon>` server component when a name doesn't resolve. NEVER blocks rendering
11
+ * or throws:
12
+ *
13
+ * - Throttled per name per process (so a hot page doesn't hammer the DB).
14
+ * - Deferred via Next's `after()` so the write runs AFTER the response streams
15
+ * (and the serverless function stays alive to finish it).
16
+ * - Best-effort: every failure is swallowed, and it's a no-op unless the
17
+ * `iconRequest` collection is registered (see {@link recordIconMiss}).
18
+ *
19
+ * Force-disable with `ICON_USAGE_TRACKING=false`.
20
+ */ export const trackIconMiss = (name)=>{
21
+ if (!name || process.env.ICON_USAGE_TRACKING === 'false') return;
22
+ const now = Date.now();
23
+ const last = lastRecorded.get(name);
24
+ if (last !== undefined && now - last < THROTTLE_MS) return;
25
+ if (lastRecorded.size >= MAX_TRACKED) {
26
+ for (const [key, at] of lastRecorded)if (now - at >= THROTTLE_MS) lastRecorded.delete(key);
27
+ }
28
+ lastRecorded.set(name, now);
29
+ try {
30
+ after(async ()=>{
31
+ try {
32
+ const payload = await getPayloadClient();
33
+ await recordIconMiss(payload, name);
34
+ } catch {
35
+ // Telemetry is best-effort — never surface its failures.
36
+ }
37
+ });
38
+ } catch {
39
+ // `after()` was called outside a request scope (e.g. static prerender) — skip.
40
+ }
41
+ };
42
+
43
+ //# sourceMappingURL=trackIconMiss.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/usage/trackIconMiss.ts"],"sourcesContent":["import 'server-only'\n\nimport { after } from 'next/server'\n\nimport { getPayloadClient } from '../lib/getPayloadClient'\nimport { recordIconMiss } from './recordMiss'\n\n/** Don't write the same name more than once per this window per process. */\nconst THROTTLE_MS = 60_000\n/** Evict stale throttle entries once the map exceeds this — dynamic names would otherwise grow it forever. */\nconst MAX_TRACKED = 500\nconst lastRecorded = new Map<string, number>()\n\n/**\n * Fire-and-forget runtime recorder for an unresolved icon name. Called by the\n * `<Icon>` server component when a name doesn't resolve. NEVER blocks rendering\n * or throws:\n *\n * - Throttled per name per process (so a hot page doesn't hammer the DB).\n * - Deferred via Next's `after()` so the write runs AFTER the response streams\n * (and the serverless function stays alive to finish it).\n * - Best-effort: every failure is swallowed, and it's a no-op unless the\n * `iconRequest` collection is registered (see {@link recordIconMiss}).\n *\n * Force-disable with `ICON_USAGE_TRACKING=false`.\n */\nexport const trackIconMiss = (name: string): void => {\n if (!name || process.env.ICON_USAGE_TRACKING === 'false') return\n\n const now = Date.now()\n const last = lastRecorded.get(name)\n if (last !== undefined && now - last < THROTTLE_MS) return\n if (lastRecorded.size >= MAX_TRACKED) {\n for (const [key, at] of lastRecorded) if (now - at >= THROTTLE_MS) lastRecorded.delete(key)\n }\n lastRecorded.set(name, now)\n\n try {\n after(async () => {\n try {\n const payload = await getPayloadClient()\n await recordIconMiss(payload, name)\n } catch {\n // Telemetry is best-effort — never surface its failures.\n }\n })\n } catch {\n // `after()` was called outside a request scope (e.g. static prerender) — skip.\n }\n}\n"],"names":["after","getPayloadClient","recordIconMiss","THROTTLE_MS","MAX_TRACKED","lastRecorded","Map","trackIconMiss","name","process","env","ICON_USAGE_TRACKING","now","Date","last","get","undefined","size","key","at","delete","set","payload"],"mappings":"AAAA,OAAO,cAAa;AAEpB,SAASA,KAAK,QAAQ,cAAa;AAEnC,SAASC,gBAAgB,QAAQ,6BAAyB;AAC1D,SAASC,cAAc,QAAQ,kBAAc;AAE7C,0EAA0E,GAC1E,MAAMC,cAAc;AACpB,4GAA4G,GAC5G,MAAMC,cAAc;AACpB,MAAMC,eAAe,IAAIC;AAEzB;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMC,gBAAgB,CAACC;IAC5B,IAAI,CAACA,QAAQC,QAAQC,GAAG,CAACC,mBAAmB,KAAK,SAAS;IAE1D,MAAMC,MAAMC,KAAKD,GAAG;IACpB,MAAME,OAAOT,aAAaU,GAAG,CAACP;IAC9B,IAAIM,SAASE,aAAaJ,MAAME,OAAOX,aAAa;IACpD,IAAIE,aAAaY,IAAI,IAAIb,aAAa;QACpC,KAAK,MAAM,CAACc,KAAKC,GAAG,IAAId,aAAc,IAAIO,MAAMO,MAAMhB,aAAaE,aAAae,MAAM,CAACF;IACzF;IACAb,aAAagB,GAAG,CAACb,MAAMI;IAEvB,IAAI;QACFZ,MAAM;YACJ,IAAI;gBACF,MAAMsB,UAAU,MAAMrB;gBACtB,MAAMC,eAAeoB,SAASd;YAChC,EAAE,OAAM;YACN,yDAAyD;YAC3D;QACF;IACF,EAAE,OAAM;IACN,+EAA+E;IACjF;AACF,EAAC"}
package/package.json ADDED
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "@pro-laico/payload-icons",
3
+ "version": "0.0.0",
4
+ "description": "SVG icons for Payload CMS: an Icon upload collection that optimizes and sanitizes SVGs on save (svgo + viewBox tightening + currentColor theming), a drop-in <Icon name> server component that fetches and inlines the SVG, and one-line declarative seeding via @pro-laico/payload-seed.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/pro-laico/payload-plugins.git",
9
+ "directory": "packages/payload-icons"
10
+ },
11
+ "keywords": [
12
+ "payload",
13
+ "payloadcms",
14
+ "plugin",
15
+ "icons",
16
+ "svg",
17
+ "svgo",
18
+ "upload"
19
+ ],
20
+ "sideEffects": false,
21
+ "type": "module",
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "default": "./dist/index.js"
32
+ },
33
+ "./components/Icon": {
34
+ "types": "./dist/components/Icon.d.ts",
35
+ "import": "./dist/components/Icon.js",
36
+ "default": "./dist/components/Icon.js"
37
+ },
38
+ "./cache": {
39
+ "types": "./dist/cache/index.d.ts",
40
+ "import": "./dist/cache/index.js",
41
+ "default": "./dist/cache/index.js"
42
+ },
43
+ "./admin/iconRowLabel": {
44
+ "types": "./dist/components/admin/iconRowLabel.d.ts",
45
+ "import": "./dist/components/admin/iconRowLabel.js",
46
+ "default": "./dist/components/admin/iconRowLabel.js"
47
+ },
48
+ "./admin/iconUsagePanel": {
49
+ "types": "./dist/components/admin/iconUsagePanel.d.ts",
50
+ "import": "./dist/components/admin/iconUsagePanel.js",
51
+ "default": "./dist/components/admin/iconUsagePanel.js"
52
+ },
53
+ "./admin/iconPreview": {
54
+ "types": "./dist/components/admin/iconPreview.d.ts",
55
+ "import": "./dist/components/admin/iconPreview.js",
56
+ "default": "./dist/components/admin/iconPreview.js"
57
+ }
58
+ },
59
+ "files": [
60
+ "dist"
61
+ ],
62
+ "dependencies": {
63
+ "server-only": "^0.0.1",
64
+ "svg-path-bbox": "^2.1.0",
65
+ "svgo": "^4.0.1"
66
+ },
67
+ "peerDependencies": {
68
+ "@payloadcms/ui": "^3.0.0",
69
+ "next": ">=15.0.0",
70
+ "payload": "^3.0.0",
71
+ "react": "^19.0.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "@payloadcms/ui": {
75
+ "optional": true
76
+ },
77
+ "next": {
78
+ "optional": true
79
+ }
80
+ },
81
+ "devDependencies": {
82
+ "@payloadcms/ui": "^3.85.1",
83
+ "@types/node": "^26.0.1",
84
+ "@types/react": "^19.2.17",
85
+ "next": "^16.2.9",
86
+ "payload": "^3.85.1",
87
+ "react": "^19.2.7",
88
+ "typescript": "^6.0.3",
89
+ "vitest": "^4.1.9"
90
+ },
91
+ "engines": {
92
+ "node": ">=18"
93
+ },
94
+ "scripts": {
95
+ "build": "pnpm clean && pnpm build:types && pnpm build:swc",
96
+ "clean": "rimraf dist tsconfig.tsbuildinfo",
97
+ "typecheck": "tsc -p tsconfig.json --noEmit",
98
+ "test": "vitest run",
99
+ "test:watch": "vitest",
100
+ "build:swc": "swc ./src -d ./dist --config-file ../../.swcrc --strip-leading-paths",
101
+ "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly --noCheck"
102
+ },
103
+ "bin": {
104
+ "payload-icons-scan": "./dist/scan/cli.js"
105
+ }
106
+ }