@driftime/sanity-plugin-icon 0.5.0 → 0.5.1

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.
@@ -1,7 +1,8 @@
1
1
  import { t as logger } from "./defaults-BEzCWZtH.js";
2
- import { l as isRecord } from "./nodes-Bn7fLVZO.js";
3
- import { a as writeIconNames, i as loadIconSet, n as detectLibraries, o as createRegistryModule, r as listRegistryDependencies, t as describeReport } from "./sets-B5arQvrZ.js";
4
- import { i as listPluginFiles } from "./adapters-Dw6yeA3A.js";
2
+ import { l as isRecord } from "./nodes-DdErxvpg.js";
3
+ import { n as getIconSetKey } from "./sets-BfqjcdEo.js";
4
+ import { a as writeIconNames, i as loadIconSet, n as detectLibraries, o as createRegistryModule, r as listRegistryDependencies, t as describeReport } from "./sets-FpS3h5A7.js";
5
+ import { i as listPluginFiles } from "./adapters-JowAC3d_.js";
5
6
  import path from "node:path";
6
7
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
7
8
  import { randomUUID } from "node:crypto";
@@ -52,7 +53,7 @@ async function generateRegistryOnce(resourcePath, options) {
52
53
  logger.info(describeReport(label, style, report));
53
54
  let libraryNames = names.get(library) ?? /* @__PURE__ */ new Set();
54
55
  for (let icon of set.icons) libraryNames.add(icon.name);
55
- names.set(library, libraryNames), files.set(`${library}/${style}`, file);
56
+ names.set(library, libraryNames), files.set(getIconSetKey(library, style), file);
56
57
  }
57
58
  await writeIconNames(new Map([...names].filter(([, libraryNames]) => libraryNames.size > 0)));
58
59
  let code = createRegistryModule(libraries, (key) => {
@@ -93,4 +94,4 @@ async function generateRegistry(resourcePath, options, attempts = 3) {
93
94
  }
94
95
  export { writeIconSets as n, generateRegistry as t };
95
96
 
96
- //# sourceMappingURL=loader-2g6inuO6.js.map
97
+ //# sourceMappingURL=loader-Mo7_FEBf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader-Mo7_FEBf.js","names":["randomUUID","mkdir","readFile","rename","writeFile","path","isRecord","writeIconNames","listPluginFiles","createRegistryModule","describeReport","detectLibraries","listRegistryDependencies","loadIconSet","logger","getIconSetKey","LoaderContext","resourcePath","getOptions","addDependency","file","addContextDependency","folder","writeIconSets","root","custom","Record","cacheDir","join","setsDir","recursive","libraries","converted","Promise","all","flatMap","library","styles","map","style","adapter","set","report","content","JSON","stringify","current","catch","temporary","generateRegistryOnce","options","process","cwd","folders","Object","fromEntries","entries","name","files","Map","names","Set","label","info","libraryNames","get","icon","icons","add","filter","size","code","key","relative","dirname","replaceAll","startsWith","dependencies","readManifest","generateRegistry","attempts","ReturnType","before","result","after","\"custom\""],"sources":["../src/build/loader.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { isRecord } from \"@repo/lib/utils\";\n\nimport { writeIconNames } from \"@/build/names\";\nimport { listPluginFiles } from \"@/build/packages\";\nimport { createRegistryModule } from \"@/build/registry\";\nimport { describeReport, detectLibraries, listRegistryDependencies, loadIconSet } from \"@/build/sets\";\nimport { logger } from \"@/config/defaults\";\nimport { getIconSetKey } from \"@/lib/registry\";\n\n/**\n * The parts of a webpack or Turbopack loader's context the build step uses.\n *\n * @public\n */\nexport interface LoaderContext {\n /** Path of the module being loaded. */\n resourcePath: string;\n /**\n * Reads the loader's options.\n *\n * @returns The options.\n */\n getOptions: () => unknown;\n /**\n * Tells the bundler to run the loader again when a file changes.\n *\n * @param file - The file.\n */\n addDependency?: (file: string) => void;\n /**\n * Tells the bundler to run the loader again when anything in a folder changes.\n *\n * @param folder - The folder.\n */\n addContextDependency?: (folder: string) => void;\n}\n\n/**\n * Converts every installed library and writes each set to a file the bundler can split into its own chunk.\n *\n * @param root - The project folder.\n * @param custom - Folders of the project's own icons, keyed by library identifier.\n * @returns The libraries found, and each set with the file it was written to and what happened converting it.\n */\nexport async function writeIconSets(root: string, custom: Record<string, string>) {\n const cacheDir = path.join(root, \"node_modules\", \".cache\");\n const setsDir = path.join(cacheDir, \"driftime-icon\", \"sets\");\n await mkdir(setsDir, { recursive: true });\n\n const libraries = await detectLibraries(root, custom);\n\n const converted = await Promise.all(\n libraries.flatMap((library) =>\n library.styles.map(async ({ style }) => {\n const { adapter } = library;\n const { set, report } = await loadIconSet(library, style, { root, cacheDir });\n const file = path.join(setsDir, `${adapter.library}-${style}.json`);\n const content = JSON.stringify(set);\n const current = await readFile(file, \"utf8\").catch(() => \"\");\n\n if (current !== content) {\n const temporary = `${file}.${randomUUID()}.tmp`;\n\n // The browser and server builds convert at once, even in one process, so each set is swapped in whole.\n await writeFile(temporary, content);\n await rename(temporary, file);\n }\n\n return { adapter, style, set, file, report };\n }),\n ),\n );\n\n return { libraries, converted };\n}\n\n/**\n * Converts every installed library, writes each set to a file the bundler can split into its own chunk, and returns\n * the registry that replaces the placeholder.\n *\n * @param resourcePath - Path of the placeholder being replaced.\n * @param options - The loader's options.\n * @returns The registry's code, and the files and folders it was generated from.\n */\nasync function generateRegistryOnce(resourcePath: string, options: unknown) {\n const root = process.cwd();\n const custom = isRecord(options) && isRecord(options[\"custom\"]) ? options[\"custom\"] : {};\n const folders = Object.fromEntries(\n Object.entries(custom).flatMap(([name, folder]) => (typeof folder === \"string\" ? [[name, folder]] : [])),\n );\n\n const { libraries, converted } = await writeIconSets(root, folders);\n const files = new Map<string, string>();\n const names = new Map<string, Set<string>>();\n\n for (const { adapter, style, set, file, report } of converted) {\n const { label, library } = adapter;\n logger.info(describeReport(label, style, report));\n\n const libraryNames = names.get(library) ?? new Set<string>();\n\n for (const icon of set.icons) libraryNames.add(icon.name);\n names.set(library, libraryNames);\n files.set(getIconSetKey(library, style), file);\n }\n\n await writeIconNames(new Map([...names].filter(([, libraryNames]) => libraryNames.size > 0)));\n\n const code = createRegistryModule(libraries, (key) => {\n const file = files.get(key) ?? \"\";\n const relative = path.relative(path.dirname(resourcePath), file).replaceAll(\"\\\\\", \"/\");\n\n return relative.startsWith(\".\") ? relative : `./${relative}`;\n });\n\n const dependencies = listRegistryDependencies(root, libraries);\n\n // The plugin's own files are included, so updating it runs the loader again even when `package.json` doesn't change.\n return { code, files: [...dependencies.files, ...(await listPluginFiles())], folders: dependencies.folders };\n}\n\n/**\n * Reads the project's `package.json` as text.\n *\n * @returns The contents, or an empty string when there's no file.\n */\nasync function readManifest() {\n try {\n return await readFile(path.join(process.cwd(), \"package.json\"), \"utf8\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * Generates the registry, running again when the project's `package.json` changes while it runs, so a package\n * installed during a run isn't missed.\n *\n * @param resourcePath - Path of the placeholder being replaced.\n * @param options - The loader's options.\n * @param attempts - Runs allowed, including this one.\n * @returns The registry's code, and the files and folders it was generated from.\n * @public\n */\nexport async function generateRegistry(\n resourcePath: string,\n options: unknown,\n attempts = 3,\n): ReturnType<typeof generateRegistryOnce> {\n const before = await readManifest();\n const result = await generateRegistryOnce(resourcePath, options);\n const after = await readManifest();\n\n return before === after || attempts <= 1 ? result : generateRegistry(resourcePath, options, attempts - 1);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"next-loader.d.ts","names":["_$__build_sets0","_$__adapters_types0","_$___lib_sets0","LoaderContext","writeIconSets","Record","DetectedLibrary","IconAdapter","IconSet","IconSetReport","Promise","generateRegistryOnce","generateRegistry","ReturnType","LoaderContext","iconSetsLoader","Promise","default","generateRegistry"],"sources":["../src/build/loader.ts","../src/next-loader.ts"],"mappings":"AAiBA;;;;;UAAiBG;;EAEf;;;;;;EAMA;;;;;;EAMA,iBAAiB;;;;;;EAMjB,wBAAwB;;;;;;;;;;iBAkDXQ,qBAAqB,sBAAsB,mBAAgBD;;;;;;;;;;;;;;;wBA4DpDE,iBACpB,sBACA,kBACA,oBACCC,kBAAkBF;;;;;;;;;;;iBCvHNI,eAAe,MAAMD,gBAAaE"}
1
+ {"version":3,"file":"next-loader.d.ts","names":["_$__build_sets0","_$__adapters_types0","_$___lib_sets0","LoaderContext","writeIconSets","Record","DetectedLibrary","IconAdapter","IconSet","IconSetReport","Promise","generateRegistryOnce","generateRegistry","ReturnType","LoaderContext","iconSetsLoader","Promise","default","generateRegistry"],"sources":["../src/build/loader.ts","../src/next-loader.ts"],"mappings":"AAkBA;;;;;UAAiBG;;EAEf;;;;;;EAMA;;;;;;EAMA,iBAAiB;;;;;;EAMjB,wBAAwB;;;;;;;;;;iBAkDXQ,qBAAqB,sBAAsB,mBAAgBD;;;;;;;;;;;;;;;wBA4DpDE,iBACpB,sBACA,kBACA,oBACCC,kBAAkBF;;;;;;;;;;;iBCxHNI,eAAe,MAAMD,gBAAaE"}
@@ -1,7 +1,7 @@
1
1
  import { t as logger } from "./defaults-BEzCWZtH.js";
2
- import { l as isRecord } from "./nodes-Bn7fLVZO.js";
3
- import { s as readPluginBuild } from "./adapters-Dw6yeA3A.js";
4
- import { t as generateRegistry } from "./loader-2g6inuO6.js";
2
+ import { l as isRecord } from "./nodes-DdErxvpg.js";
3
+ import { s as readPluginBuild } from "./adapters-JowAC3d_.js";
4
+ import { t as generateRegistry } from "./loader-Mo7_FEBf.js";
5
5
  /**
6
6
  * Checks whether an imported module is a fresh copy of this one.
7
7
  *
package/dist/next.js CHANGED
@@ -1,5 +1,5 @@
1
- import { c as registryModulePath, s as isRegistryModule } from "./sets-B5arQvrZ.js";
2
- import { n as writeIconSets } from "./loader-2g6inuO6.js";
1
+ import { c as registryModulePath, s as isRegistryModule } from "./sets-FpS3h5A7.js";
2
+ import { n as writeIconSets } from "./loader-Mo7_FEBf.js";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  /** Path of the loader, published next to this file. */
@@ -48,7 +48,12 @@ function isRecord(value) {
48
48
  * SVG elements a drawing may contain: everything that draws, paints, clips, masks, or filters. Anything that runs
49
49
  * code, embeds other content, links away, loads a file, animates, or styles the page around it is left out.
50
50
  */
51
- const iconElements = /* @__PURE__ */ new Set(/* @__PURE__ */ "circle.clipPath.defs.ellipse.feBlend.feColorMatrix.feComponentTransfer.feComposite.feConvolveMatrix.feDiffuseLighting.feDisplacementMap.feDistantLight.feDropShadow.feFlood.feFuncA.feFuncB.feFuncG.feFuncR.feGaussianBlur.feMerge.feMergeNode.feMorphology.feOffset.fePointLight.feSpecularLighting.feSpotLight.feTile.feTurbulence.filter.g.line.linearGradient.marker.mask.path.pattern.polygon.polyline.radialGradient.rect.stop.symbol.text.textPath.tspan.use".split(".")), linkAttributes = /* @__PURE__ */ new Set(["href", "xlinkHref"]), urlPattern = /url\((?<reference>[^)]*)\)/gu, iconSvgAttributes = {
51
+ const iconElements = /* @__PURE__ */ new Set(/* @__PURE__ */ "circle.clipPath.defs.ellipse.feBlend.feColorMatrix.feComponentTransfer.feComposite.feConvolveMatrix.feDiffuseLighting.feDisplacementMap.feDistantLight.feDropShadow.feFlood.feFuncA.feFuncB.feFuncG.feFuncR.feGaussianBlur.feMerge.feMergeNode.feMorphology.feOffset.fePointLight.feSpecularLighting.feSpotLight.feTile.feTurbulence.filter.g.line.linearGradient.marker.mask.path.pattern.polygon.polyline.radialGradient.rect.stop.symbol.text.textPath.tspan.use".split(".")), linkAttributes = /* @__PURE__ */ new Set(["href", "xlinkHref"]), reactProps = /* @__PURE__ */ new Set([
52
+ "children",
53
+ "dangerouslySetInnerHTML",
54
+ "key",
55
+ "ref"
56
+ ]), urlPattern = /url\((?<reference>[^)]*)\)/gu, iconSvgAttributes = {
52
57
  xmlns: "http://www.w3.org/2000/svg",
53
58
  width: 24,
54
59
  height: 24
@@ -83,6 +88,7 @@ function findAttributeProblem(attributes) {
83
88
  for (let [name, value] of Object.entries(attributes)) {
84
89
  if (/^on/iu.test(name)) return `has an event handler (\`${name}\`)`;
85
90
  if (!/^[a-zA-Z][a-zA-Z0-9]*$/u.test(name)) return `has an unknown attribute (\`${name}\`)`;
91
+ if (reactProps.has(name)) return `has an attribute React reserves (\`${name}\`)`;
86
92
  if (/javascript:/iu.test(value)) return "runs a script";
87
93
  if (linkAttributes.has(name) && !value.startsWith("#")) return "links outside the icon";
88
94
  if (value.includes("url(")) {
@@ -180,4 +186,4 @@ function serializeIconDrawing(drawing) {
180
186
  }
181
187
  export { resolveIconDrawing as a, isDefined as c, parseIconRoot as i, isRecord as l, iconSvgAttributes as n, serializeIconDrawing as o, parseIconNode as r, convertCase as s, findDrawingProblem as t };
182
188
 
183
- //# sourceMappingURL=nodes-Bn7fLVZO.js.map
189
+ //# sourceMappingURL=nodes-DdErxvpg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes-DdErxvpg.js","names":["Nullable","T","isDefined","value","undefined","trim","Array","isArray","some","element","prototype","Reflect","getPrototypeOf","Object","keys","length","convertCase","format","words","replaceAll","toLowerCase","_match","character","toUpperCase","isRecord","Record","readPath","path","current","segment","isDefined","isRecord","stegaClean","IconElement","element","attributes","Record","children","IconNode","IconRoot","IconDrawing","root","node","iconElements","ReadonlySet","Set","linkAttributes","reactProps","urlPattern","iconSvgAttributes","xmlns","width","height","const","isAttributes","value","Object","values","every","attribute","isIconElement","Array","isArray","length","parts","name","child","findAttributeProblem","entries","test","has","startsWith","includes","references","matchAll","outside","some","groups","trim","replace","split","undefined","findNodeProblem","problem","findDrawingProblem","parseIconNode","parseIconRoot","parseJson","JSON","parse","resolveIconDrawing","cleaned","stored","drawing","serializeIconDrawing","stringify","parts","\"reference\"","\"root\"","\"node\""],"sources":["../../@repo/lib/utils.ts","../src/lib/nodes.ts"],"sourcesContent":["/** A value that may be null or undefined. */\ntype Nullable<T> = T | null | undefined;\n\n/**\n * Checks whether a value is present. `false`, blank strings, and empty plain objects count as absent,\n * as does an array with no present elements.\n *\n * @param value - The value to check.\n * @returns True if the value is present.\n */\nexport function isDefined<T>(value: Nullable<T> | false): value is T {\n if (value === undefined || value === null || value === false) return false;\n if (typeof value === \"string\") return value.trim() !== \"\";\n if (Array.isArray(value)) return value.some((element) => isDefined(element));\n\n if (typeof value === \"object\") {\n const prototype = Reflect.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) return true;\n\n return Object.keys(value).length > 0;\n }\n\n return true;\n}\n\n/**\n * Converts a string to another case.\n *\n * @param value - The string to convert.\n * @param format - The case to convert to.\n * @returns The converted string.\n */\nexport function convertCase(value: string, format: \"kebab\" | \"snake\" | \"camel\" | \"pascal\" | \"title\" | \"sentence\") {\n const words = value\n .replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, \"$<lower> $<upper>\")\n .replaceAll(/[-_\\s]+/gu, \" \")\n .trim()\n .toLowerCase();\n\n switch (format) {\n case \"kebab\": {\n return words.replaceAll(/\\s+/gu, \"-\");\n }\n case \"snake\": {\n return words.replaceAll(/\\s+/gu, \"_\");\n }\n case \"camel\": {\n return words.replaceAll(/\\s+(?<character>.)/gu, (_match, character: string) => character.toUpperCase());\n }\n case \"pascal\": {\n return words.replaceAll(/(?:^|\\s+)(?<character>.)/gu, (_match, character: string) => character.toUpperCase());\n }\n case \"title\": {\n return words.replaceAll(/\\b\\w/gu, (character) => character.toUpperCase());\n }\n case \"sentence\": {\n return words.replaceAll(/^(?<character>.)/gu, (character) => character.toUpperCase());\n }\n default: {\n return value;\n }\n }\n}\n\n/**\n * Checks whether a value is an object whose keys can be read.\n *\n * @param value - The value to check.\n * @returns True if the value is an object.\n */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Reads a nested value by following a path of keys.\n *\n * @param value - The object to read from.\n * @param path - The keys to follow.\n * @returns The value, or undefined when the path doesn't exist.\n */\nexport function readPath(value: unknown, path: string[]) {\n let current: unknown = value;\n\n for (const segment of path) {\n if (!isRecord(current)) return undefined;\n\n current = current[segment];\n }\n\n return current;\n}\n","import { isDefined, isRecord } from \"@repo/lib/utils\";\nimport { stegaClean } from \"@sanity/client/stega\";\n\n/** One element in a drawing: its name, its attributes, and any children, which may include text. */\nexport type IconElement = [element: string, attributes: Record<string, string>, children?: (IconElement | string)[]];\n\n/** An icon's drawing, stored as a tree of elements rather than a component. */\nexport type IconNode = IconElement[];\n\n/** Attributes for the SVG around a drawing's elements. */\nexport type IconRoot = Record<string, string>;\n\n/** An icon's elements together with the SVG attributes they're drawn inside. */\nexport interface IconDrawing {\n /** Attributes for the SVG around the elements. */\n root: IconRoot;\n /** Elements the icon is drawn from. */\n node: IconNode;\n}\n\n/**\n * SVG elements a drawing may contain: everything that draws, paints, clips, masks, or filters. Anything that runs\n * code, embeds other content, links away, loads a file, animates, or styles the page around it is left out.\n */\nconst iconElements: ReadonlySet<string> = new Set([\n \"circle\",\n \"clipPath\",\n \"defs\",\n \"ellipse\",\n \"feBlend\",\n \"feColorMatrix\",\n \"feComponentTransfer\",\n \"feComposite\",\n \"feConvolveMatrix\",\n \"feDiffuseLighting\",\n \"feDisplacementMap\",\n \"feDistantLight\",\n \"feDropShadow\",\n \"feFlood\",\n \"feFuncA\",\n \"feFuncB\",\n \"feFuncG\",\n \"feFuncR\",\n \"feGaussianBlur\",\n \"feMerge\",\n \"feMergeNode\",\n \"feMorphology\",\n \"feOffset\",\n \"fePointLight\",\n \"feSpecularLighting\",\n \"feSpotLight\",\n \"feTile\",\n \"feTurbulence\",\n \"filter\",\n \"g\",\n \"line\",\n \"linearGradient\",\n \"marker\",\n \"mask\",\n \"path\",\n \"pattern\",\n \"polygon\",\n \"polyline\",\n \"radialGradient\",\n \"rect\",\n \"stop\",\n \"symbol\",\n \"text\",\n \"textPath\",\n \"tspan\",\n \"use\",\n]);\n\n/** Attributes that point at another element by URL, which must point inside the drawing. */\nconst linkAttributes = new Set([\"href\", \"xlinkHref\"]);\n\n/** Props React treats specially rather than passing to the element, where a stored string would break rendering. */\nconst reactProps = new Set([\"children\", \"dangerouslySetInnerHTML\", \"key\", \"ref\"]);\n\n/** A reference to another element in an attribute value, such as `url(#fade)`. */\nconst urlPattern = /url\\((?<reference>[^)]*)\\)/gu;\n\n/** Attributes every drawing's SVG carries, whichever library it came from. */\nexport const iconSvgAttributes = { xmlns: \"http://www.w3.org/2000/svg\", width: 24, height: 24 } as const;\n\n/**\n * Checks whether a value is a set of attributes, each with a text value.\n *\n * @param value - The value to check.\n * @returns True if the value is a set of attributes.\n */\nfunction isAttributes(value: unknown): value is Record<string, string> {\n return isRecord(value) && Object.values(value).every((attribute) => typeof attribute === \"string\");\n}\n\n/**\n * Checks whether a value is an element in a drawing, with text attributes and valid children.\n *\n * @param value - The value to check.\n * @returns True if the value is an element.\n */\nfunction isIconElement(value: unknown): value is IconElement {\n if (!Array.isArray(value) || value.length < 2 || value.length > 3) return false;\n\n const parts: unknown[] = value;\n const [name, attributes, children] = parts;\n if (typeof name !== \"string\" || !isAttributes(attributes)) return false;\n\n return (\n !isDefined(children) ||\n (Array.isArray(children) && children.every((child) => typeof child === \"string\" || isIconElement(child)))\n );\n}\n\n/**\n * Finds why an element's attributes can't be drawn safely.\n *\n * @param attributes - The attributes.\n * @returns The problem, or undefined when the attributes are safe.\n */\nfunction findAttributeProblem(attributes: Record<string, string>) {\n for (const [name, value] of Object.entries(attributes)) {\n if (/^on/iu.test(name)) return `has an event handler (\\`${name}\\`)`;\n if (!/^[a-zA-Z][a-zA-Z0-9]*$/u.test(name)) return `has an unknown attribute (\\`${name}\\`)`;\n if (reactProps.has(name)) return `has an attribute React reserves (\\`${name}\\`)`;\n if (/javascript:/iu.test(value)) return \"runs a script\";\n\n if (linkAttributes.has(name) && !value.startsWith(\"#\")) return \"links outside the icon\";\n\n if (value.includes(\"url(\")) {\n const references = [...value.matchAll(urlPattern)];\n const outside = references.some(\n ({ groups }) => !(groups?.[\"reference\"]?.trim().replace(/^[\"']/u, \"\").startsWith(\"#\") ?? false),\n );\n\n if (outside || value.split(\"url(\").length - 1 !== references.length) {\n return \"refers to something outside the icon\";\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Finds why part of a drawing can't be drawn safely.\n *\n * @param node - The elements to check.\n * @returns The problem, or undefined when every element is safe.\n */\nfunction findNodeProblem(node: (IconElement | string)[]): string | undefined {\n for (const child of node) {\n if (typeof child === \"string\") continue;\n\n const [name, attributes, children] = child;\n if (!iconElements.has(name)) return `uses the \\`<${name}>\\` element`;\n\n const problem = findAttributeProblem(attributes) ?? (isDefined(children) ? findNodeProblem(children) : undefined);\n if (isDefined(problem)) return problem;\n }\n\n return undefined;\n}\n\n/**\n * Finds why a drawing can't be drawn safely: an element or attribute that could run code, load something, or reach\n * outside the icon. The same check guards the build step's output and every stored value before it's drawn.\n *\n * @param drawing - The drawing to check.\n * @returns The problem, or undefined when the drawing is safe.\n */\nexport function findDrawingProblem({ root, node }: IconDrawing) {\n if (!isDefined(node)) return \"draws nothing\";\n\n return findAttributeProblem(root) ?? findNodeProblem(node);\n}\n\n/**\n * Reads a drawing's elements from an already parsed value, such as a converted set.\n *\n * @param value - The value to read.\n * @returns The elements, or undefined when the value isn't a list of them.\n */\nexport function parseIconNode(value: unknown) {\n return Array.isArray(value) && value.every((element) => isIconElement(element)) ? value : undefined;\n}\n\n/**\n * Reads an SVG root from an already parsed value.\n *\n * @param value - The value to read.\n * @returns The root, or undefined when the value isn't one.\n */\nexport function parseIconRoot(value: unknown) {\n return isAttributes(value) ? value : undefined;\n}\n\n/**\n * Parses stored JSON.\n *\n * @param value - The stored text.\n * @returns The parsed value, or undefined when the text isn't JSON.\n */\nfunction parseJson(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Reads a drawing back from its stored form, removing stega characters first.\n *\n * @param value - The stored drawing.\n * @returns The drawing, or undefined when nothing readable and safe was stored.\n */\nexport function resolveIconDrawing(value: string | undefined): IconDrawing | undefined {\n const cleaned = stegaClean(value);\n if (!isDefined(cleaned)) return undefined;\n\n const stored = parseJson(cleaned);\n if (!isRecord(stored)) return undefined;\n\n const root = parseIconRoot(stored[\"root\"]);\n const node = parseIconNode(stored[\"node\"]);\n if (!isDefined(root) || !isDefined(node)) return undefined;\n\n const drawing = { root, node };\n\n return isDefined(findDrawingProblem(drawing)) ? undefined : drawing;\n}\n\n/**\n * Converts a drawing to its stored form.\n *\n * @param drawing - The drawing.\n * @returns The drawing as a string.\n */\nexport function serializeIconDrawing(drawing: IconDrawing) {\n return JSON.stringify({ root: drawing.root, node: drawing.node });\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { c as isDefined, l as isRecord } from "./nodes-Bn7fLVZO.js";
2
- import { n as createCustomAdapter, t as iconAdapters } from "./adapters-Dw6yeA3A.js";
1
+ import { c as isDefined, l as isRecord } from "./nodes-DdErxvpg.js";
2
+ import { n as createCustomAdapter, t as iconAdapters } from "./adapters-JowAC3d_.js";
3
3
  import { parentPort, workerData } from "node:worker_threads";
4
4
  /**
5
5
  * Reads one library style in a worker thread, so every package is imported afresh, and posts the icons back.
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","names":["ComponentProps","SanityIcon","SanityIconProps","Omit","Icon","value","JSX","IconLibraryId","SanityIcon","SanityIconName","SanityIconStyle","SanityIconLookup","Library","getIcon","library","style","name","Promise"],"sources":["../src/components/icon.tsx","../src/lib/lookup.ts"],"mappings":";;;;;;;KAcYE,kBAAkBC,KAAKH;;EAEjC,OAAOC;;;;;;;;wBASOG,OAAOC,UAAU,SAASH,kCAAeI,IAAA;;;;;;KCb7CK,sBACT,WAAWJ;;EAEV,SAASK;;EAET,QAAQF,gBAAgBE;;EAExB,MAAMH,eAAeG;KAEvBL;;;;;;;;;wBAUoBM,UAAUC,SAASC,OAAOC,QAAQL,mBAAmBM,QAAQT"}
1
+ {"version":3,"file":"render.d.ts","names":["ComponentProps","SanityIcon","SanityIconProps","Omit","Icon","value","JSX","IconLibraryId","SanityIcon","SanityIconName","SanityIconStyle","SanityIconLookup","Library","getIcon","library","style","name","Promise"],"sources":["../src/components/icon.tsx","../src/lib/lookup.ts"],"mappings":";;;;;;;KAcYE,kBAAkBC,KAAKH;;EAEjC,OAAOC;;;;;;;;wBASOG,OAAOC,UAAU,SAASH,kCAAeI,IAAA;;;;;;KCd7CK,sBACT,WAAWJ;;EAEV,SAASK;;EAET,QAAQF,gBAAgBE;;EAExB,MAAMH,eAAeG;KAEvBL;;;;;;;;;wBAUoBM,UAAUC,SAASC,OAAOC,QAAQL,mBAAmBM,QAAQT"}
package/dist/render.js CHANGED
@@ -1,5 +1,5 @@
1
- import { a as resolveIconDrawing, c as isDefined, o as serializeIconDrawing } from "./nodes-Bn7fLVZO.js";
2
- import { o as Drawing, r as requestLibrary, t as iconTypeName } from "./types-CmTEhJK6.js";
1
+ import { a as resolveIconDrawing, c as isDefined, o as serializeIconDrawing } from "./nodes-DdErxvpg.js";
2
+ import { n as Drawing, t as iconTypeName } from "./types-Dl2HGUL5.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  /**
5
5
  * Draws a stored icon from the shapes saved with it, without loading an icon library.
@@ -22,7 +22,7 @@ function Icon({ value, ...props }) {
22
22
  * @public
23
23
  */
24
24
  async function getIcon({ library, style, name }) {
25
- let { icons } = await requestLibrary(library, style), icon = icons?.find((candidate) => candidate.name === name);
25
+ let { requestLibrary } = await import("./library-D07tGkaC.js"), { icons } = await requestLibrary(library, style), icon = icons?.find((candidate) => candidate.name === name);
26
26
  if (isDefined(icon)) return {
27
27
  _type: iconTypeName,
28
28
  library,
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","names":["ComponentProps","Drawing","resolveIconDrawing","SanityIcon","SanityIconProps","Omit","value","Icon","props","node","isDefined","requestLibrary","serializeIconDrawing","iconTypeName","IconLibraryId","SanityIcon","SanityIconName","SanityIconStyle","SanityIconLookup","library","Library","style","name","getIcon","Promise","icons","icon","find","candidate","undefined","_type","node","drawing"],"sources":["../src/components/icon.tsx","../src/lib/lookup.ts"],"sourcesContent":["// Consumers render this in server components, where the compiler's memo cache hook can't run.\n\"use no memo\";\n\nimport type { ComponentProps } from \"react\";\n\nimport { Drawing } from \"@/components/drawing\";\nimport { resolveIconDrawing } from \"@/lib/nodes\";\nimport type { SanityIcon } from \"@/types\";\n\n/**\n * Props for drawing a stored icon: an SVG element's props plus the stored value.\n *\n * @public\n */\nexport type SanityIconProps = Omit<ComponentProps<\"svg\">, \"children\"> & {\n /** Stored icon to draw. */\n value: SanityIcon | undefined;\n};\n\n/**\n * Draws a stored icon from the shapes saved with it, without loading an icon library.\n *\n * @returns The icon, or nothing when the value is missing or unreadable.\n * @public\n */\nexport function Icon({ value, ...props }: SanityIconProps) {\n return <Drawing drawing={resolveIconDrawing(value?.node)} {...props} />;\n}\n","import { isDefined } from \"@repo/lib/utils\";\n\nimport { requestLibrary } from \"@/lib/library\";\nimport { serializeIconDrawing } from \"@/lib/nodes\";\nimport { iconTypeName } from \"@/types\";\nimport type { IconLibraryId, SanityIcon, SanityIconName, SanityIconStyle } from \"@/types\";\n\n/**\n * An icon to look up by name, where the style and name are checked against the library.\n *\n * @public\n */\nexport type SanityIconLookup = {\n [Library in IconLibraryId]: {\n /** Identifier of the icon library, such as `phosphor`. */\n library: Library;\n /** Identifier of the library style, such as `bold`. Defaults to the library's first style. */\n style?: SanityIconStyle<Library>;\n /** Name of the icon in its library. */\n name: SanityIconName<Library>;\n };\n}[IconLibraryId];\n\n/**\n * Looks an icon up by name in an installed library, for an icon the site chooses rather than an author.\n *\n * @param lookup - The library, style, and name of the icon.\n * @returns The icon as the Studio stores it, or undefined when the build step hasn't run or the library, style, or\n * name isn't installed.\n * @public\n */\nexport async function getIcon({ library, style, name }: SanityIconLookup): Promise<SanityIcon | undefined> {\n const { icons } = await requestLibrary(library, style);\n const icon = icons?.find((candidate) => candidate.name === name);\n if (!isDefined(icon)) return undefined;\n\n return {\n _type: iconTypeName,\n library,\n ...(isDefined(style) && { style }),\n name,\n node: serializeIconDrawing(icon.drawing),\n };\n}\n"],"mappings":";;;;;;;;;AAyBA,SAAgBO,KAAK,EAAED,OAAO,GAAGE,SAA0B;CACzD,OAAO,oBAAC,SAAD;EAAS,SAASN,mBAAmBI,OAAOG,IAAI;EAAG,GAAID;CAAM,CAAA;AACtE;;;;;;;;;ACIA,eAAsBe,QAAQ,EAAEJ,SAASE,OAAOC,QAA2D;CACzG,IAAM,EAAEG,UAAU,MAAMd,eAAeQ,SAASE,KAAK,GAC/CK,OAAOD,OAAOE,MAAMC,cAAcA,UAAUN,SAASA,IAAI;CAC/D,IAAKZ,UAAUgB,IAAI,GAEnB,OAAO;EACLI,OAAOjB;EACPM;EACA,GAAIT,UAAUW,KAAK,KAAK,EAAEA,MAAM;EAChCC;EACAS,MAAMnB,qBAAqBc,KAAKM,OAAO;CACzC;AACF"}
1
+ {"version":3,"file":"render.js","names":["ComponentProps","Drawing","resolveIconDrawing","SanityIcon","SanityIconProps","Omit","value","Icon","props","node","isDefined","serializeIconDrawing","iconTypeName","IconLibraryId","SanityIcon","SanityIconName","SanityIconStyle","SanityIconLookup","library","Library","style","name","getIcon","Promise","requestLibrary","icons","icon","find","candidate","undefined","_type","node","drawing"],"sources":["../src/components/icon.tsx","../src/lib/lookup.ts"],"sourcesContent":["// Consumers render this in server components, where the compiler's memo cache hook can't run.\n\"use no memo\";\n\nimport type { ComponentProps } from \"react\";\n\nimport { Drawing } from \"@/components/drawing\";\nimport { resolveIconDrawing } from \"@/lib/nodes\";\nimport type { SanityIcon } from \"@/types\";\n\n/**\n * Props for drawing a stored icon: an SVG element's props plus the stored value.\n *\n * @public\n */\nexport type SanityIconProps = Omit<ComponentProps<\"svg\">, \"children\"> & {\n /** Stored icon to draw. */\n value: SanityIcon | undefined;\n};\n\n/**\n * Draws a stored icon from the shapes saved with it, without loading an icon library.\n *\n * @returns The icon, or nothing when the value is missing or unreadable.\n * @public\n */\nexport function Icon({ value, ...props }: SanityIconProps) {\n return <Drawing drawing={resolveIconDrawing(value?.node)} {...props} />;\n}\n","import { isDefined } from \"@repo/lib/utils\";\n\nimport { serializeIconDrawing } from \"@/lib/nodes\";\nimport { iconTypeName } from \"@/types\";\nimport type { IconLibraryId, SanityIcon, SanityIconName, SanityIconStyle } from \"@/types\";\n\n/**\n * An icon to look up by name, where the style and name are checked against the library.\n *\n * @public\n */\nexport type SanityIconLookup = {\n [Library in IconLibraryId]: {\n /** Identifier of the icon library, such as `phosphor`. */\n library: Library;\n /** Identifier of the library style, such as `bold`. Defaults to the library's first style. */\n style?: SanityIconStyle<Library>;\n /** Name of the icon in its library. */\n name: SanityIconName<Library>;\n };\n}[IconLibraryId];\n\n/**\n * Looks an icon up by name in an installed library, for an icon the site chooses rather than an author.\n *\n * @param lookup - The library, style, and name of the icon.\n * @returns The icon as the Studio stores it, or undefined when the build step hasn't run or the library, style, or\n * name isn't installed.\n * @public\n */\nexport async function getIcon({ library, style, name }: SanityIconLookup): Promise<SanityIcon | undefined> {\n // Imported here, so a site that only draws icons never downloads the library loader and the set registry.\n const { requestLibrary } = await import(\"@/lib/library\");\n const { icons } = await requestLibrary(library, style);\n const icon = icons?.find((candidate) => candidate.name === name);\n if (!isDefined(icon)) return undefined;\n\n return {\n _type: iconTypeName,\n library,\n ...(isDefined(style) && { style }),\n name,\n node: serializeIconDrawing(icon.drawing),\n };\n}\n"],"mappings":";;;;;;;;;AAyBA,SAAgBO,KAAK,EAAED,OAAO,GAAGE,SAA0B;CACzD,OAAO,oBAAC,SAAD;EAAS,SAASN,mBAAmBI,OAAOG,IAAI;EAAG,GAAID;CAAM,CAAA;AACtE;;;;;;;;;ACGA,eAAsBc,QAAQ,EAAEJ,SAASE,OAAOC,QAA2D;CAEzG,IAAM,EAAEG,mBAAmB,MAAM,OAAO,0BAClC,EAAEC,UAAU,MAAMD,eAAeN,SAASE,KAAK,GAC/CM,OAAOD,OAAOE,MAAMC,cAAcA,UAAUP,SAASA,IAAI;CAC/D,IAAKX,UAAUgB,IAAI,GAEnB,OAAO;EACLI,OAAOlB;EACPM;EACA,GAAIR,UAAUU,KAAK,KAAK,EAAEA,MAAM;EAChCC;EACAU,MAAMpB,qBAAqBe,KAAKM,OAAO;CACzC;AACF"}
@@ -1,4 +1,4 @@
1
- import { c as isDefined, i as parseIconRoot, l as isRecord, r as parseIconNode } from "./nodes-Bn7fLVZO.js";
1
+ import { c as isDefined, i as parseIconRoot, l as isRecord, r as parseIconNode } from "./nodes-DdErxvpg.js";
2
2
  /**
3
3
  * Builds the key a set is registered under.
4
4
  *
@@ -46,4 +46,4 @@ function parseIconSet(value) {
46
46
  }
47
47
  export { getIconSetKey as n, parseIconSet as t };
48
48
 
49
- //# sourceMappingURL=sets-DH5UbUV_.js.map
49
+ //# sourceMappingURL=sets-BfqjcdEo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sets-DH5UbUV_.js","names":["IconLibraryInfo","IconSetLoader","Promise","default","IconRegistry","ready","sets","Record","libraries","getIconSetKey","library","style","isDefined","isRecord","IconNode","IconRoot","parseIconNode","parseIconRoot","IconSetIcon","name","keywords","node","root","IconSet","library","style","icons","IconLibraryInfo","label","packageName","packagePrefix","styles","listed","installed","sitePackage","parseIconSetIcon","value","undefined","parsedNode","parsedRoot","Array","isArray","filter","keyword","parseIconSet","flatMap","icon","parsed"],"sources":["../src/lib/registry.ts","../src/lib/sets.ts"],"sourcesContent":["import type { IconLibraryInfo } from \"@/lib/sets\";\n\n/**\n * Loads one converted set, as a module whose default export is the set.\n *\n * @public\n */\nexport type IconSetLoader = () => Promise<{ default: unknown }>;\n\n/**\n * What the build step generates: a loader for every converted set, and every library it knows.\n *\n * @public\n */\nexport interface IconRegistry {\n /** Whether the build step generated the registry, rather than the placeholder being used. */\n ready: boolean;\n /** Loaders keyed by library and style, such as `phosphor/bold`. */\n sets: Record<string, IconSetLoader>;\n /** Libraries the build step knows, keyed by identifier. */\n libraries: Record<string, IconLibraryInfo>;\n}\n\n/**\n * Builds the key a set is registered under.\n *\n * @param library - Identifier of the library.\n * @param style - Identifier of the style.\n * @returns The key.\n */\nexport function getIconSetKey(library: string, style: string) {\n return `${library}/${style}`;\n}\n","import { isDefined, isRecord } from \"@repo/lib/utils\";\n\nimport type { IconNode, IconRoot } from \"@/lib/nodes\";\nimport { parseIconNode, parseIconRoot } from \"@/lib/nodes\";\n\n/** One icon in a converted set. */\nexport interface IconSetIcon {\n /** Name of the icon in its library. */\n name: string;\n /** Words describing the icon, from its library. */\n keywords: string[];\n /** Shapes the icon is drawn from. */\n node: IconNode;\n /** Root for this icon, or undefined when it uses the set's. */\n root?: IconRoot;\n}\n\n/** Icons of one library style, converted by the build step into the stored drawing format. */\nexport interface IconSet {\n /** Identifier of the library, such as `lucide`. */\n library: string;\n /** Identifier of the style, such as `bold`. */\n style: string;\n /** Root most icons in the set share. */\n root: IconRoot;\n /** Every icon in the set. */\n icons: IconSetIcon[];\n}\n\n/**\n * A library the build step knows how to convert, and whether the project has it installed.\n *\n * @public\n */\nexport interface IconLibraryInfo {\n /** Readable name of the library. */\n label: string;\n /** Package the library is read from, or an example of one for a family of packages. */\n packageName: string;\n /** Shared start of the package names of a family of packages, one per style, or undefined for a single package. */\n packagePrefix?: string;\n /** Styles the library offers, the first being the default. */\n styles: string[];\n /** Whether the project's `package.json` lists the package, installed or not. */\n listed: boolean;\n /** Whether the project has the package installed. */\n installed: boolean;\n /** Another package of the library the project lists, such as its React version, or undefined when there's none. */\n sitePackage?: string;\n}\n\n/**\n * Reads one icon of a converted set, skipping it when its data isn't usable.\n *\n * @param value - The value to read.\n * @returns The icon, or undefined when the value isn't one.\n */\nfunction parseIconSetIcon(value: unknown): IconSetIcon | undefined {\n if (!isRecord(value)) return undefined;\n\n const { name, keywords, node, root } = value;\n const parsedNode = parseIconNode(node);\n const parsedRoot = isDefined(root) ? parseIconRoot(root) : undefined;\n\n if (typeof name !== \"string\" || !Array.isArray(keywords) || !isDefined(parsedNode)) return undefined;\n if (isDefined(root) && !isDefined(parsedRoot)) return undefined;\n\n return {\n name,\n keywords: keywords.filter((keyword: unknown) => typeof keyword === \"string\"),\n node: parsedNode,\n ...(isDefined(parsedRoot) && { root: parsedRoot }),\n };\n}\n\n/**\n * Reads a converted set, keeping only the icons whose data is usable.\n *\n * @param value - The value to read.\n * @returns The set, or undefined when the value isn't one.\n */\nexport function parseIconSet(value: unknown): IconSet | undefined {\n if (!isRecord(value)) return undefined;\n\n const { library, style, root, icons } = value;\n const parsedRoot = parseIconRoot(root);\n\n if (typeof library !== \"string\" || typeof style !== \"string\" || !isDefined(parsedRoot) || !Array.isArray(icons)) {\n return undefined;\n }\n\n return {\n library,\n style,\n root: parsedRoot,\n icons: icons.flatMap((icon: unknown) => {\n const parsed = parseIconSetIcon(icon);\n\n return isDefined(parsed) ? [parsed] : [];\n }),\n };\n}\n"],"mappings":";;;;;;;;AA8BA,SAAgBS,cAAcC,SAAiBC,OAAe;CAC5D,OAAO,GAAGD,QAAO,GAAIC;AACvB;;;;;;;ACyBA,SAASwB,iBAAiBC,OAAyC;CACjE,IAAI,CAACvB,SAASuB,KAAK,GAAG;CAEtB,IAAM,EAAEjB,MAAMC,UAAUC,MAAMC,SAASc,OACjCE,aAAatB,cAAcK,IAAI,GAC/BkB,aAAa3B,UAAUU,IAAI,IAAIL,cAAcK,IAAI,IAAIe,KAAAA;CAE3D,IAAI,OAAOlB,QAAS,YAAaqB,MAAMC,QAAQrB,QAAQ,KAAMR,UAAU0B,UAAU,MAC7E1B,WAAUU,IAAI,KAAMV,UAAU2B,UAAU,IAE5C,OAAO;EACLpB;EACAC,UAAUA,SAASsB,QAAQC,YAAqB,OAAOA,WAAY,QAAQ;EAC3EtB,MAAMiB;EACN,GAAI1B,UAAU2B,UAAU,KAAK,EAAEjB,MAAMiB,WAAW;CAClD;AACF;;;;;;;AAQA,SAAgBK,aAAaR,OAAqC;CAChE,IAAI,CAACvB,SAASuB,KAAK,GAAG;CAEtB,IAAM,EAAEZ,SAASC,OAAOH,MAAMI,UAAUU,OAClCG,aAAatB,cAAcK,IAAI;CAErC,IAAI,OAAOE,WAAY,YAAY,OAAOC,SAAU,YAAab,UAAU2B,UAAU,KAAMC,MAAMC,QAAQf,KAAK,GAI9G,OAAO;EACLF;EACAC;EACAH,MAAMiB;EACNb,OAAOA,MAAMmB,SAASC,SAAkB;GACtC,IAAMC,SAASZ,iBAAiBW,IAAI;GAEpC,OAAOlC,UAAUmC,MAAM,IAAI,CAACA,MAAM,IAAI,CAAA;EACxC,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"sets-BfqjcdEo.js","names":["IconLibraryInfo","IconSetLoader","Promise","default","IconRegistry","ready","sets","Record","libraries","getIconSetKey","library","style","isDefined","isRecord","IconNode","IconRoot","parseIconNode","parseIconRoot","IconSetIcon","name","keywords","node","root","IconSet","library","style","icons","IconLibraryInfo","label","packageName","packagePrefix","styles","listed","installed","sitePackage","parseIconSetIcon","value","undefined","parsedNode","parsedRoot","Array","isArray","filter","keyword","parseIconSet","flatMap","icon","parsed"],"sources":["../src/lib/registry.ts","../src/lib/sets.ts"],"sourcesContent":["import type { IconLibraryInfo } from \"@/lib/sets\";\n\n/**\n * Loads one converted set, as a module whose default export is the set.\n *\n * @public\n */\nexport type IconSetLoader = () => Promise<{ default: unknown }>;\n\n/**\n * What the build step generates: a loader for every converted set, and every library it knows.\n *\n * @public\n */\nexport interface IconRegistry {\n /** Whether the build step generated the registry, rather than the placeholder being used. */\n ready: boolean;\n /** Loaders keyed by library and style, such as `phosphor/bold`. */\n sets: Record<string, IconSetLoader>;\n /** Libraries the build step knows, keyed by identifier. */\n libraries: Record<string, IconLibraryInfo>;\n}\n\n/**\n * Builds the key a set is registered under.\n *\n * @param library - Identifier of the library.\n * @param style - Identifier of the style.\n * @returns The key.\n */\nexport function getIconSetKey(library: string, style: string) {\n return `${library}/${style}`;\n}\n","import { isDefined, isRecord } from \"@repo/lib/utils\";\n\nimport type { IconNode, IconRoot } from \"@/lib/nodes\";\nimport { parseIconNode, parseIconRoot } from \"@/lib/nodes\";\n\n/** One icon in a converted set. */\nexport interface IconSetIcon {\n /** Name of the icon in its library. */\n name: string;\n /** Words describing the icon, from its library. */\n keywords: string[];\n /** Shapes the icon is drawn from. */\n node: IconNode;\n /** Root for this icon, or undefined when it uses the set's. */\n root?: IconRoot;\n}\n\n/** Icons of one library style, converted by the build step into the stored drawing format. */\nexport interface IconSet {\n /** Identifier of the library, such as `lucide`. */\n library: string;\n /** Identifier of the style, such as `bold`. */\n style: string;\n /** Root most icons in the set share. */\n root: IconRoot;\n /** Every icon in the set. */\n icons: IconSetIcon[];\n}\n\n/**\n * A library the build step knows how to convert, and whether the project has it installed.\n *\n * @public\n */\nexport interface IconLibraryInfo {\n /** Readable name of the library. */\n label: string;\n /** Package the library is read from, or an example of one for a family of packages. */\n packageName: string;\n /** Shared start of the package names of a family of packages, one per style, or undefined for a single package. */\n packagePrefix?: string;\n /** Styles the library offers, the first being the default. */\n styles: string[];\n /** Whether the project's `package.json` lists the package, installed or not. */\n listed: boolean;\n /** Whether the project has the package installed. */\n installed: boolean;\n /** Another package of the library the project lists, such as its React version, or undefined when there's none. */\n sitePackage?: string;\n}\n\n/**\n * Reads one icon of a converted set, skipping it when its data isn't usable.\n *\n * @param value - The value to read.\n * @returns The icon, or undefined when the value isn't one.\n */\nfunction parseIconSetIcon(value: unknown): IconSetIcon | undefined {\n if (!isRecord(value)) return undefined;\n\n const { name, keywords, node, root } = value;\n const parsedNode = parseIconNode(node);\n const parsedRoot = isDefined(root) ? parseIconRoot(root) : undefined;\n\n if (typeof name !== \"string\" || !Array.isArray(keywords) || !isDefined(parsedNode)) return undefined;\n if (isDefined(root) && !isDefined(parsedRoot)) return undefined;\n\n return {\n name,\n keywords: keywords.filter((keyword: unknown) => typeof keyword === \"string\"),\n node: parsedNode,\n ...(isDefined(parsedRoot) && { root: parsedRoot }),\n };\n}\n\n/**\n * Reads a converted set, keeping only the icons whose data is usable.\n *\n * @param value - The value to read.\n * @returns The set, or undefined when the value isn't one.\n */\nexport function parseIconSet(value: unknown): IconSet | undefined {\n if (!isRecord(value)) return undefined;\n\n const { library, style, root, icons } = value;\n const parsedRoot = parseIconRoot(root);\n\n if (typeof library !== \"string\" || typeof style !== \"string\" || !isDefined(parsedRoot) || !Array.isArray(icons)) {\n return undefined;\n }\n\n return {\n library,\n style,\n root: parsedRoot,\n icons: icons.flatMap((icon: unknown) => {\n const parsed = parseIconSetIcon(icon);\n\n return isDefined(parsed) ? [parsed] : [];\n }),\n };\n}\n"],"mappings":";;;;;;;;AA8BA,SAAgBS,cAAcC,SAAiBC,OAAe;CAC5D,OAAO,GAAGD,QAAO,GAAIC;AACvB;;;;;;;ACyBA,SAASwB,iBAAiBC,OAAyC;CACjE,IAAI,CAACvB,SAASuB,KAAK,GAAG;CAEtB,IAAM,EAAEjB,MAAMC,UAAUC,MAAMC,SAASc,OACjCE,aAAatB,cAAcK,IAAI,GAC/BkB,aAAa3B,UAAUU,IAAI,IAAIL,cAAcK,IAAI,IAAIe,KAAAA;CAE3D,IAAI,OAAOlB,QAAS,YAAaqB,MAAMC,QAAQrB,QAAQ,KAAMR,UAAU0B,UAAU,MAC7E1B,WAAUU,IAAI,KAAMV,UAAU2B,UAAU,IAE5C,OAAO;EACLpB;EACAC,UAAUA,SAASsB,QAAQC,YAAqB,OAAOA,WAAY,QAAQ;EAC3EtB,MAAMiB;EACN,GAAI1B,UAAU2B,UAAU,KAAK,EAAEjB,MAAMiB,WAAW;CAClD;AACF;;;;;;;AAQA,SAAgBK,aAAaR,OAAqC;CAChE,IAAI,CAACvB,SAASuB,KAAK,GAAG;CAEtB,IAAM,EAAEZ,SAASC,OAAOH,MAAMI,UAAUU,OAClCG,aAAatB,cAAcK,IAAI;CAErC,IAAI,OAAOE,WAAY,YAAY,OAAOC,SAAU,YAAab,UAAU2B,UAAU,KAAMC,MAAMC,QAAQf,KAAK,GAI9G,OAAO;EACLF;EACAC;EACAH,MAAMiB;EACNb,OAAOA,MAAMmB,SAASC,SAAkB;GACtC,IAAMC,SAASZ,iBAAiBW,IAAI;GAEpC,OAAOlC,UAAUmC,MAAM,IAAI,CAACA,MAAM,IAAI,CAAA;EACxC,CAAC;CACH;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { n as pluginName } from "./defaults-BEzCWZtH.js";
2
- import { c as isDefined, l as isRecord } from "./nodes-Bn7fLVZO.js";
3
- import { n as getIconSetKey, t as parseIconSet } from "./sets-DH5UbUV_.js";
4
- import { a as readDeclaredPackages, c as convertIcon, n as createCustomAdapter, o as readJson, r as findPackage, s as readPluginBuild, t as iconAdapters } from "./adapters-Dw6yeA3A.js";
2
+ import { c as isDefined, l as isRecord } from "./nodes-DdErxvpg.js";
3
+ import { n as getIconSetKey, t as parseIconSet } from "./sets-BfqjcdEo.js";
4
+ import { a as readDeclaredPackages, c as convertIcon, n as createCustomAdapter, o as readJson, r as findPackage, s as readPluginBuild, t as iconAdapters } from "./adapters-JowAC3d_.js";
5
5
  import path from "node:path";
6
6
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
7
7
  import { realpathSync } from "node:fs";
@@ -308,4 +308,4 @@ function describeReport(label, style, report) {
308
308
  }
309
309
  export { writeIconNames as a, registryModulePath as c, loadIconSet as i, detectLibraries as n, createRegistryModule as o, listRegistryDependencies as r, isRegistryModule as s, describeReport as t };
310
310
 
311
- //# sourceMappingURL=sets-B5arQvrZ.js.map
311
+ //# sourceMappingURL=sets-FpS3h5A7.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sets-B5arQvrZ.js","names":["realpathSync","fileURLToPath","isDefined","DetectedLibrary","getIconSetKey","IconLibraryInfo","resolveRealPath","file","registryModulePath","URL","import","meta","url","isRegistryModule","id","split","createRegistryModule","libraries","importFor","key","loaders","flatMap","adapter","styles","map","style","library","JSON","stringify","info","Record","Object","fromEntries","listed","sitePackage","label","packageName","packageExample","packagePrefix","installed","join","readFile","rename","writeFile","path","isDefined","registryModulePath","pluginName","namesFile","join","dirname","writeIconNames","names","Map","Set","entries","toSorted","first","second","localeCompare","map","library","icons","union","name","JSON","stringify","content","current","catch","temporary","process","pid","once","mkdir","writeFile","path","Worker","isDefined","isRecord","createCustomAdapter","iconAdapters","IconAdapter","SourceIcon","convertIcon","InstalledPackage","findPackage","readDeclaredPackages","readJson","readPluginBuild","IconSet","IconSetIcon","parseIconSet","IconSetReport","total","skipped","name","reason","duration","DetectedStyle","style","installed","DetectedLibrary","adapter","styles","listed","sitePackage","folder","IconSetContext","root","cacheDir","detectLibraries","custom","Record","Promise","declared","known","all","map","packageName","packagePrefix","packagePattern","names","filter","startsWith","toSorted","found","slice","length","flat","has","undefined","find","test","folders","Object","entries","library","directory","resolve","version","findCommonRoot","icons","NonNullable","counts","Map","count","key","JSON","stringify","set","get","common","values","first","second","isSourceIcon","value","keywords","svg","Array","isArray","every","keyword","readSources","candidate","worker","URL","import","meta","url","workerData","env","process","NODE_ENV","received","sources","source","terminate","convertIconSet","started","performance","now","report","converted","icon","problem","push","iconRoot","Math","round","parseReport","flatMap","entry","loadIconSet","context","cacheable","cacheFile","join","replaceAll","cached","dirname","recursive","listRegistryDependencies","libraries","files","describeReport","label","kept","toLocaleString","timing","summary","shown","hidden","more","\"total\"","\"skipped\"","\"name\"","\"reason\"","\"set\"","\"report\""],"sources":["../src/build/registry.ts","../src/build/names.ts","../src/build/sets.ts"],"sourcesContent":["import { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { isDefined } from \"@repo/lib/utils\";\n\nimport type { DetectedLibrary } from \"@/build/sets\";\nimport { getIconSetKey } from \"@/lib/registry\";\nimport type { IconLibraryInfo } from \"@/lib/sets\";\n\n/**\n * Resolves symbolic links in a path, so a package linked into `node_modules` matches its real location.\n *\n * @param file - The path.\n * @returns The real path, or the path unchanged when it doesn't exist.\n */\nfunction resolveRealPath(file: string) {\n try {\n return realpathSync(file);\n } catch {\n return file;\n }\n}\n\n/**\n * Path of the placeholder registry module the Studio imports, which the build step replaces. The build step's own\n * code is published next to it, so the path is found from this file's location.\n */\nexport const registryModulePath = resolveRealPath(fileURLToPath(new URL(\"sets.js\", import.meta.url)));\n\n/**\n * Checks whether a module the bundler is loading is the placeholder registry.\n *\n * @param id - The module's path, possibly followed by a query.\n * @returns Whether it's the placeholder.\n */\nexport function isRegistryModule(id: string) {\n const [file] = id.split(\"?\");\n\n return isDefined(file) && resolveRealPath(file) === registryModulePath;\n}\n\n/**\n * Writes the module that replaces the placeholder registry, with a loader for each installed library style, so each\n * set is only downloaded when a picker for it opens.\n *\n * @param libraries - Every library the build step knows.\n * @param importFor - The specifier the bundler imports a set from, given its key.\n * @returns The module's code.\n */\nexport function createRegistryModule(libraries: DetectedLibrary[], importFor: (key: string) => string) {\n const loaders = libraries.flatMap(({ adapter, styles }) =>\n styles.map(({ style }) => {\n const key = getIconSetKey(adapter.library, style);\n\n return `${JSON.stringify(key)}: () => import(${JSON.stringify(importFor(key))})`;\n }),\n );\n\n const info: Record<string, IconLibraryInfo> = Object.fromEntries(\n libraries.map(({ adapter, styles, listed, sitePackage }) => [\n adapter.library,\n {\n label: adapter.label,\n packageName: adapter.packageName ?? adapter.packageExample ?? adapter.library,\n ...(isDefined(adapter.packagePrefix) && { packagePrefix: adapter.packagePrefix }),\n styles: isDefined(styles) ? styles.map(({ style }) => style) : adapter.styles,\n listed,\n installed: isDefined(styles),\n ...(isDefined(sitePackage) && { sitePackage }),\n },\n ]),\n );\n\n return [\n `const sets = {\\n ${loaders.join(\",\\n \")}\\n};`,\n `export const iconRegistry = { ready: true, sets, libraries: ${JSON.stringify(info)} };`,\n ].join(\"\\n\");\n}\n","import { readFile, rename, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { isDefined } from \"@repo/lib/utils\";\n\nimport { registryModulePath } from \"@/build/registry\";\nimport { pluginName } from \"@/config/defaults\";\n\n/** Types file the plugin reads icon names from, published next to the placeholder registry. */\nconst namesFile = path.join(path.dirname(registryModulePath), \"names.d.ts\");\n\n/**\n * Writes the installed icon names into the plugin's own types, so options that list icons are checked against them.\n * The file is replaced rather than edited, because pnpm shares a package's files between projects.\n *\n * @param names - Icon names keyed by library.\n */\nexport async function writeIconNames(names: Map<string, Set<string>>) {\n const entries = [...names]\n .toSorted(([first], [second]) => first.localeCompare(second))\n .map(([library, icons]) => {\n const union = [...icons].toSorted().map((name) => JSON.stringify(name));\n\n return ` ${JSON.stringify(library)}: ${isDefined(union) ? union.join(\" | \") : \"never\"};`;\n });\n\n const content = [\n `// Written by ${pluginName}'s build step from the project's installed icon libraries.`,\n \"export type SanityIconNames = {\",\n ...entries,\n \"};\",\n \"\",\n ].join(\"\\n\");\n\n const current = await readFile(namesFile, \"utf8\").catch(() => \"\");\n if (current === content) return;\n\n const temporary = `${namesFile}.${process.pid}.tmp`;\n await writeFile(temporary, content);\n await rename(temporary, namesFile);\n}\n","import { once } from \"node:events\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Worker } from \"node:worker_threads\";\n\nimport { isDefined, isRecord } from \"@repo/lib/utils\";\n\nimport { createCustomAdapter } from \"@/build/adapters/custom\";\nimport { iconAdapters } from \"@/build/adapters/index\";\nimport type { IconAdapter } from \"@/build/adapters/types\";\nimport type { SourceIcon } from \"@/build/convert\";\nimport { convertIcon } from \"@/build/convert\";\nimport type { InstalledPackage } from \"@/build/packages\";\nimport { findPackage, readDeclaredPackages, readJson, readPluginBuild } from \"@/build/packages\";\nimport type { IconSet, IconSetIcon } from \"@/lib/sets\";\nimport { parseIconSet } from \"@/lib/sets\";\n\n/** What happened converting a set, for the build's log. */\nexport interface IconSetReport {\n /** Icons the library ships in the style. */\n total: number;\n /** Icons skipped, with why. */\n skipped: { name: string; reason: string }[];\n /** Time the conversion took, in milliseconds, or 0 when it came from the cache. */\n duration: number;\n}\n\n/** One installed style of a library, and the package it's read from. */\nexport interface DetectedStyle {\n /** Identifier of the style. */\n style: string;\n /** The package the style is read from. */\n installed: InstalledPackage;\n}\n\n/** A library found in the project, and where each of its styles is installed. */\nexport interface DetectedLibrary {\n /** How to read the library. */\n adapter: IconAdapter;\n /** The styles the project has installed, empty when it has none. */\n styles: DetectedStyle[];\n /** Whether the project's `package.json` lists the package the library is read from, installed or not. */\n listed: boolean;\n /**\n * Another package of the library the project lists, such as its React version, or undefined when there's none or the\n * library's own package is listed.\n */\n sitePackage?: string;\n /** Folder of the project's own icons, relative to the project, or undefined for a known library. */\n folder?: string;\n}\n\n/** Where the build step reads libraries from and caches conversions. */\nexport interface IconSetContext {\n /** The project folder. */\n root: string;\n /** Folder for cached conversions, or undefined to convert every time. */\n cacheDir: string | undefined;\n}\n\n/**\n * Finds every library the project can use: known libraries whose package it lists and has installed, and its own icon\n * folders. Only listed packages count, so a package that's only there because another one depends on it is ignored.\n *\n * @param root - The project folder.\n * @param custom - The project's own icon folders, keyed by identifier.\n * @returns Every library the build step knows, with where it's installed.\n */\nexport async function detectLibraries(root: string, custom: Record<string, string> = {}): Promise<DetectedLibrary[]> {\n const declared = await readDeclaredPackages(root);\n\n const known = await Promise.all(\n iconAdapters.map(async (adapter) => {\n const { packageName, packagePrefix, packagePattern } = adapter;\n\n if (isDefined(packagePrefix)) {\n const names = [...declared].filter((name) => name.startsWith(packagePrefix)).toSorted();\n const found = await Promise.all(\n names.map(async (name) => {\n const installed = await findPackage(root, name);\n\n return isDefined(installed) ? [{ style: name.slice(packagePrefix.length), installed }] : [];\n }),\n );\n\n return { adapter, styles: found.flat(), listed: isDefined(names) };\n }\n\n const listed = isDefined(packageName) && declared.has(packageName);\n const installed = listed ? await findPackage(root, packageName) : undefined;\n const styles = isDefined(installed) ? adapter.styles.map((style) => ({ style, installed })) : [];\n const sitePackage =\n listed || !isDefined(packagePattern)\n ? undefined\n : [...declared].toSorted().find((name) => packagePattern.test(name));\n\n return { adapter, styles, listed, ...(isDefined(sitePackage) && { sitePackage }) };\n }),\n );\n\n const folders = Object.entries(custom).map(([library, folder]) => ({\n adapter: createCustomAdapter(library, folder),\n listed: true,\n folder,\n styles: [\n { style: \"default\", installed: { name: library, directory: path.resolve(root, folder), version: \"custom\" } },\n ],\n }));\n\n return [...known, ...folders];\n}\n\n/**\n * Picks the root most icons in a set share, so only the others need their own.\n *\n * @param icons - The converted icons.\n * @returns The most common root.\n */\nfunction findCommonRoot(icons: (IconSetIcon & { root: NonNullable<IconSetIcon[\"root\"]> })[]) {\n const counts = new Map<string, { root: IconSet[\"root\"]; count: number }>();\n\n for (const { root } of icons) {\n const key = JSON.stringify(root);\n counts.set(key, { root, count: (counts.get(key)?.count ?? 0) + 1 });\n }\n\n const [common] = [...counts.values()].toSorted((first, second) => second.count - first.count);\n\n return common?.root ?? {};\n}\n\n/**\n * Checks whether a value is an icon as a worker read it from its library.\n *\n * @param value - The value to check.\n * @returns True if the value is a source icon.\n */\nfunction isSourceIcon(value: unknown): value is SourceIcon {\n if (!isRecord(value)) return false;\n\n const { name, keywords, svg } = value;\n\n return (\n typeof name === \"string\" &&\n Array.isArray(keywords) &&\n keywords.every((keyword) => typeof keyword === \"string\") &&\n typeof svg === \"string\"\n );\n}\n\n/**\n * Reads a library's icons in a fresh worker thread. A long-running bundler keeps every module it has imported, so an\n * icon package updated in place would otherwise be read from the old copy.\n *\n * @param library - The library and where it's installed.\n * @param style - The style to read.\n * @param root - The project folder.\n * @returns The icons as the library ships them.\n */\nasync function readSources(library: DetectedLibrary, style: string, root: string) {\n const { adapter, folder } = library;\n const installed = library.styles.find((candidate) => candidate.style === style)?.installed;\n if (!isDefined(installed)) return [];\n\n const worker = new Worker(new URL(\"read-worker.js\", import.meta.url), {\n workerData: { library: adapter.library, folder, style, root, directory: installed.directory },\n // Libraries' components render with React's production build, which writes the same markup without warnings.\n env: { ...process.env, NODE_ENV: \"production\" },\n });\n\n try {\n const received: unknown[] = await once(worker, \"message\");\n const [sources] = received;\n\n return Array.isArray(sources) ? sources.filter((source) => isSourceIcon(source)) : [];\n } finally {\n void worker.terminate();\n }\n}\n\n/**\n * Converts one style of a library into a set, reporting anything skipped.\n *\n * @param library - The library and where it's installed.\n * @param style - The style to convert.\n * @param root - The project folder.\n * @returns The set and its report.\n */\nasync function convertIconSet(library: DetectedLibrary, style: string, root: string) {\n const { adapter } = library;\n const started = performance.now();\n const sources = await readSources(library, style, root);\n\n const report: IconSetReport = { total: sources.length, skipped: [], duration: 0 };\n const converted: (IconSetIcon & { root: NonNullable<IconSetIcon[\"root\"]> })[] = [];\n\n for (const source of sources) {\n const { icon, problem } = convertIcon(source);\n\n if (isDefined(icon)) converted.push(icon);\n else if (isDefined(problem)) report.skipped.push({ name: source.name, reason: problem });\n }\n\n const common = findCommonRoot(converted);\n const set: IconSet = {\n library: adapter.library,\n style,\n root: common,\n icons: converted.map(({ root: iconRoot, ...icon }) =>\n JSON.stringify(iconRoot) === JSON.stringify(common) ? icon : { ...icon, root: iconRoot },\n ),\n };\n\n report.duration = Math.round(performance.now() - started);\n\n return { set, report };\n}\n\n/**\n * Reads a cached conversion's report.\n *\n * @param value - The cached report.\n * @returns The report, or undefined when the value isn't one.\n */\nfunction parseReport(value: unknown): IconSetReport | undefined {\n if (!isRecord(value) || typeof value[\"total\"] !== \"number\" || !Array.isArray(value[\"skipped\"])) return undefined;\n\n const skipped = value[\"skipped\"].flatMap((entry: unknown) =>\n isRecord(entry) && typeof entry[\"name\"] === \"string\" && typeof entry[\"reason\"] === \"string\"\n ? [{ name: entry[\"name\"], reason: entry[\"reason\"] }]\n : [],\n );\n\n return { total: value[\"total\"], skipped, duration: 0 };\n}\n\n/**\n * Loads a converted set, from the cache when the same package version was converted before.\n *\n * @param library - The library and where it's installed.\n * @param style - The style to load.\n * @param context - The project folder and cache folder.\n * @returns The set and its report.\n */\nexport async function loadIconSet(library: DetectedLibrary, style: string, context: IconSetContext) {\n const { adapter, folder } = library;\n const installed = library.styles.find((candidate) => candidate.style === style)?.installed;\n const cacheable = isDefined(context.cacheDir) && isDefined(installed) && !isDefined(folder);\n const cacheFile =\n cacheable && isDefined(context.cacheDir)\n ? path.join(\n context.cacheDir,\n \"driftime-icon\",\n `${adapter.library}-${style}-${installed.name.replaceAll(/[@/]/gu, \"_\")}-${installed.version}-${await readPluginBuild()}.json`,\n )\n : undefined;\n\n if (isDefined(cacheFile)) {\n const cached = await readJson(cacheFile);\n const set = isRecord(cached) ? parseIconSet(cached[\"set\"]) : undefined;\n const report = isRecord(cached) ? parseReport(cached[\"report\"]) : undefined;\n\n if (isDefined(set) && isDefined(report)) return { set, report };\n }\n\n const { set, report } = await convertIconSet(library, style, context.root);\n\n if (isDefined(cacheFile)) {\n await mkdir(path.dirname(cacheFile), { recursive: true });\n await writeFile(cacheFile, JSON.stringify({ set, report }));\n }\n\n return { set, report };\n}\n\n/**\n * Lists the files the registry is generated from, so the bundler regenerates it when a library is installed, removed,\n * or updated, instead of keeping a cached copy.\n *\n * @param root - The project folder.\n * @param libraries - Every library the build step knows.\n * @returns The files, and the folders of the project's own icons.\n */\nexport function listRegistryDependencies(root: string, libraries: DetectedLibrary[]) {\n const files = [path.join(root, \"package.json\")];\n const folders: string[] = [];\n\n for (const { styles, folder } of libraries) {\n for (const { installed } of styles) {\n if (isDefined(folder)) folders.push(installed.directory);\n else files.push(path.join(installed.directory, \"package.json\"));\n }\n }\n\n return { files, folders };\n}\n\n/**\n * Summarises a set's conversion for the build's log, listing the first few icons it skipped and why.\n *\n * @param label - Readable name of the library.\n * @param style - The style converted.\n * @param report - What happened.\n * @returns The summary, one line per skipped icon after the first.\n */\nexport function describeReport(label: string, style: string, report: IconSetReport) {\n const { total, skipped, duration } = report;\n const kept = total - skipped.length;\n const count = `${kept.toLocaleString(\"en-US\")} ${kept === 1 ? \"icon\" : \"icons\"}`;\n const timing = duration > 0 ? `converted in ${duration.toLocaleString(\"en-US\")} ms` : \"loaded from the cache\";\n const summary = `${label} (\\`${style}\\`): ${count} ${timing}`;\n if (!isDefined(skipped)) return `${summary}.`;\n\n const shown = skipped.slice(0, 5).map(({ name, reason }) => ` \\`${name}\\` ${reason}.`);\n const hidden = skipped.length - shown.length;\n const more = hidden > 0 ? [` …and ${hidden.toLocaleString(\"en-US\")} more.`] : [];\n\n return [`${summary}, ${skipped.length.toLocaleString(\"en-US\")} skipped:`, ...shown, ...more].join(\"\\n\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sets-FpS3h5A7.js","names":["realpathSync","fileURLToPath","isDefined","DetectedLibrary","getIconSetKey","IconLibraryInfo","resolveRealPath","file","registryModulePath","URL","import","meta","url","isRegistryModule","id","split","createRegistryModule","libraries","importFor","key","loaders","flatMap","adapter","styles","map","style","library","JSON","stringify","info","Record","Object","fromEntries","listed","sitePackage","label","packageName","packageExample","packagePrefix","installed","join","readFile","rename","writeFile","path","isDefined","registryModulePath","pluginName","namesFile","join","dirname","writeIconNames","names","Map","Set","entries","toSorted","first","second","localeCompare","map","library","icons","union","name","JSON","stringify","content","current","catch","temporary","process","pid","once","mkdir","writeFile","path","Worker","isDefined","isRecord","createCustomAdapter","iconAdapters","IconAdapter","SourceIcon","convertIcon","InstalledPackage","findPackage","readDeclaredPackages","readJson","readPluginBuild","IconSet","IconSetIcon","parseIconSet","IconSetReport","total","skipped","name","reason","duration","DetectedStyle","style","installed","DetectedLibrary","adapter","styles","listed","sitePackage","folder","IconSetContext","root","cacheDir","detectLibraries","custom","Record","Promise","declared","known","all","map","packageName","packagePrefix","packagePattern","names","filter","startsWith","toSorted","found","slice","length","flat","has","undefined","find","test","folders","Object","entries","library","directory","resolve","version","findCommonRoot","icons","NonNullable","counts","Map","count","key","JSON","stringify","set","get","common","values","first","second","isSourceIcon","value","keywords","svg","Array","isArray","every","keyword","readSources","candidate","worker","URL","import","meta","url","workerData","env","process","NODE_ENV","received","sources","source","terminate","convertIconSet","started","performance","now","report","converted","icon","problem","push","iconRoot","Math","round","parseReport","flatMap","entry","loadIconSet","context","cacheable","cacheFile","join","replaceAll","cached","dirname","recursive","listRegistryDependencies","libraries","files","describeReport","label","kept","toLocaleString","timing","summary","shown","hidden","more","\"total\"","\"skipped\"","\"name\"","\"reason\"","\"set\"","\"report\""],"sources":["../src/build/registry.ts","../src/build/names.ts","../src/build/sets.ts"],"sourcesContent":["import { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { isDefined } from \"@repo/lib/utils\";\n\nimport type { DetectedLibrary } from \"@/build/sets\";\nimport { getIconSetKey } from \"@/lib/registry\";\nimport type { IconLibraryInfo } from \"@/lib/sets\";\n\n/**\n * Resolves symbolic links in a path, so a package linked into `node_modules` matches its real location.\n *\n * @param file - The path.\n * @returns The real path, or the path unchanged when it doesn't exist.\n */\nfunction resolveRealPath(file: string) {\n try {\n return realpathSync(file);\n } catch {\n return file;\n }\n}\n\n/**\n * Path of the placeholder registry module the Studio imports, which the build step replaces. The build step's own\n * code is published next to it, so the path is found from this file's location.\n */\nexport const registryModulePath = resolveRealPath(fileURLToPath(new URL(\"sets.js\", import.meta.url)));\n\n/**\n * Checks whether a module the bundler is loading is the placeholder registry.\n *\n * @param id - The module's path, possibly followed by a query.\n * @returns Whether it's the placeholder.\n */\nexport function isRegistryModule(id: string) {\n const [file] = id.split(\"?\");\n\n return isDefined(file) && resolveRealPath(file) === registryModulePath;\n}\n\n/**\n * Writes the module that replaces the placeholder registry, with a loader for each installed library style, so each\n * set is only downloaded when a picker for it opens.\n *\n * @param libraries - Every library the build step knows.\n * @param importFor - The specifier the bundler imports a set from, given its key.\n * @returns The module's code.\n */\nexport function createRegistryModule(libraries: DetectedLibrary[], importFor: (key: string) => string) {\n const loaders = libraries.flatMap(({ adapter, styles }) =>\n styles.map(({ style }) => {\n const key = getIconSetKey(adapter.library, style);\n\n return `${JSON.stringify(key)}: () => import(${JSON.stringify(importFor(key))})`;\n }),\n );\n\n const info: Record<string, IconLibraryInfo> = Object.fromEntries(\n libraries.map(({ adapter, styles, listed, sitePackage }) => [\n adapter.library,\n {\n label: adapter.label,\n packageName: adapter.packageName ?? adapter.packageExample ?? adapter.library,\n ...(isDefined(adapter.packagePrefix) && { packagePrefix: adapter.packagePrefix }),\n styles: isDefined(styles) ? styles.map(({ style }) => style) : adapter.styles,\n listed,\n installed: isDefined(styles),\n ...(isDefined(sitePackage) && { sitePackage }),\n },\n ]),\n );\n\n return [\n `const sets = {\\n ${loaders.join(\",\\n \")}\\n};`,\n `export const iconRegistry = { ready: true, sets, libraries: ${JSON.stringify(info)} };`,\n ].join(\"\\n\");\n}\n","import { readFile, rename, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { isDefined } from \"@repo/lib/utils\";\n\nimport { registryModulePath } from \"@/build/registry\";\nimport { pluginName } from \"@/config/defaults\";\n\n/** Types file the plugin reads icon names from, published next to the placeholder registry. */\nconst namesFile = path.join(path.dirname(registryModulePath), \"names.d.ts\");\n\n/**\n * Writes the installed icon names into the plugin's own types, so options that list icons are checked against them.\n * The file is replaced rather than edited, because pnpm shares a package's files between projects.\n *\n * @param names - Icon names keyed by library.\n */\nexport async function writeIconNames(names: Map<string, Set<string>>) {\n const entries = [...names]\n .toSorted(([first], [second]) => first.localeCompare(second))\n .map(([library, icons]) => {\n const union = [...icons].toSorted().map((name) => JSON.stringify(name));\n\n return ` ${JSON.stringify(library)}: ${isDefined(union) ? union.join(\" | \") : \"never\"};`;\n });\n\n const content = [\n `// Written by ${pluginName}'s build step from the project's installed icon libraries.`,\n \"export type SanityIconNames = {\",\n ...entries,\n \"};\",\n \"\",\n ].join(\"\\n\");\n\n const current = await readFile(namesFile, \"utf8\").catch(() => \"\");\n if (current === content) return;\n\n const temporary = `${namesFile}.${process.pid}.tmp`;\n await writeFile(temporary, content);\n await rename(temporary, namesFile);\n}\n","import { once } from \"node:events\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Worker } from \"node:worker_threads\";\n\nimport { isDefined, isRecord } from \"@repo/lib/utils\";\n\nimport { createCustomAdapter } from \"@/build/adapters/custom\";\nimport { iconAdapters } from \"@/build/adapters/index\";\nimport type { IconAdapter } from \"@/build/adapters/types\";\nimport type { SourceIcon } from \"@/build/convert\";\nimport { convertIcon } from \"@/build/convert\";\nimport type { InstalledPackage } from \"@/build/packages\";\nimport { findPackage, readDeclaredPackages, readJson, readPluginBuild } from \"@/build/packages\";\nimport type { IconSet, IconSetIcon } from \"@/lib/sets\";\nimport { parseIconSet } from \"@/lib/sets\";\n\n/** What happened converting a set, for the build's log. */\nexport interface IconSetReport {\n /** Icons the library ships in the style. */\n total: number;\n /** Icons skipped, with why. */\n skipped: { name: string; reason: string }[];\n /** Time the conversion took, in milliseconds, or 0 when it came from the cache. */\n duration: number;\n}\n\n/** One installed style of a library, and the package it's read from. */\nexport interface DetectedStyle {\n /** Identifier of the style. */\n style: string;\n /** The package the style is read from. */\n installed: InstalledPackage;\n}\n\n/** A library found in the project, and where each of its styles is installed. */\nexport interface DetectedLibrary {\n /** How to read the library. */\n adapter: IconAdapter;\n /** The styles the project has installed, empty when it has none. */\n styles: DetectedStyle[];\n /** Whether the project's `package.json` lists the package the library is read from, installed or not. */\n listed: boolean;\n /**\n * Another package of the library the project lists, such as its React version, or undefined when there's none or the\n * library's own package is listed.\n */\n sitePackage?: string;\n /** Folder of the project's own icons, relative to the project, or undefined for a known library. */\n folder?: string;\n}\n\n/** Where the build step reads libraries from and caches conversions. */\nexport interface IconSetContext {\n /** The project folder. */\n root: string;\n /** Folder for cached conversions, or undefined to convert every time. */\n cacheDir: string | undefined;\n}\n\n/**\n * Finds every library the project can use: known libraries whose package it lists and has installed, and its own icon\n * folders. Only listed packages count, so a package that's only there because another one depends on it is ignored.\n *\n * @param root - The project folder.\n * @param custom - The project's own icon folders, keyed by identifier.\n * @returns Every library the build step knows, with where it's installed.\n */\nexport async function detectLibraries(root: string, custom: Record<string, string> = {}): Promise<DetectedLibrary[]> {\n const declared = await readDeclaredPackages(root);\n\n const known = await Promise.all(\n iconAdapters.map(async (adapter) => {\n const { packageName, packagePrefix, packagePattern } = adapter;\n\n if (isDefined(packagePrefix)) {\n const names = [...declared].filter((name) => name.startsWith(packagePrefix)).toSorted();\n const found = await Promise.all(\n names.map(async (name) => {\n const installed = await findPackage(root, name);\n\n return isDefined(installed) ? [{ style: name.slice(packagePrefix.length), installed }] : [];\n }),\n );\n\n return { adapter, styles: found.flat(), listed: isDefined(names) };\n }\n\n const listed = isDefined(packageName) && declared.has(packageName);\n const installed = listed ? await findPackage(root, packageName) : undefined;\n const styles = isDefined(installed) ? adapter.styles.map((style) => ({ style, installed })) : [];\n const sitePackage =\n listed || !isDefined(packagePattern)\n ? undefined\n : [...declared].toSorted().find((name) => packagePattern.test(name));\n\n return { adapter, styles, listed, ...(isDefined(sitePackage) && { sitePackage }) };\n }),\n );\n\n const folders = Object.entries(custom).map(([library, folder]) => ({\n adapter: createCustomAdapter(library, folder),\n listed: true,\n folder,\n styles: [\n { style: \"default\", installed: { name: library, directory: path.resolve(root, folder), version: \"custom\" } },\n ],\n }));\n\n return [...known, ...folders];\n}\n\n/**\n * Picks the root most icons in a set share, so only the others need their own.\n *\n * @param icons - The converted icons.\n * @returns The most common root.\n */\nfunction findCommonRoot(icons: (IconSetIcon & { root: NonNullable<IconSetIcon[\"root\"]> })[]) {\n const counts = new Map<string, { root: IconSet[\"root\"]; count: number }>();\n\n for (const { root } of icons) {\n const key = JSON.stringify(root);\n counts.set(key, { root, count: (counts.get(key)?.count ?? 0) + 1 });\n }\n\n const [common] = [...counts.values()].toSorted((first, second) => second.count - first.count);\n\n return common?.root ?? {};\n}\n\n/**\n * Checks whether a value is an icon as a worker read it from its library.\n *\n * @param value - The value to check.\n * @returns True if the value is a source icon.\n */\nfunction isSourceIcon(value: unknown): value is SourceIcon {\n if (!isRecord(value)) return false;\n\n const { name, keywords, svg } = value;\n\n return (\n typeof name === \"string\" &&\n Array.isArray(keywords) &&\n keywords.every((keyword) => typeof keyword === \"string\") &&\n typeof svg === \"string\"\n );\n}\n\n/**\n * Reads a library's icons in a fresh worker thread. A long-running bundler keeps every module it has imported, so an\n * icon package updated in place would otherwise be read from the old copy.\n *\n * @param library - The library and where it's installed.\n * @param style - The style to read.\n * @param root - The project folder.\n * @returns The icons as the library ships them.\n */\nasync function readSources(library: DetectedLibrary, style: string, root: string) {\n const { adapter, folder } = library;\n const installed = library.styles.find((candidate) => candidate.style === style)?.installed;\n if (!isDefined(installed)) return [];\n\n const worker = new Worker(new URL(\"read-worker.js\", import.meta.url), {\n workerData: { library: adapter.library, folder, style, root, directory: installed.directory },\n // Libraries' components render with React's production build, which writes the same markup without warnings.\n env: { ...process.env, NODE_ENV: \"production\" },\n });\n\n try {\n const received: unknown[] = await once(worker, \"message\");\n const [sources] = received;\n\n return Array.isArray(sources) ? sources.filter((source) => isSourceIcon(source)) : [];\n } finally {\n void worker.terminate();\n }\n}\n\n/**\n * Converts one style of a library into a set, reporting anything skipped.\n *\n * @param library - The library and where it's installed.\n * @param style - The style to convert.\n * @param root - The project folder.\n * @returns The set and its report.\n */\nasync function convertIconSet(library: DetectedLibrary, style: string, root: string) {\n const { adapter } = library;\n const started = performance.now();\n const sources = await readSources(library, style, root);\n\n const report: IconSetReport = { total: sources.length, skipped: [], duration: 0 };\n const converted: (IconSetIcon & { root: NonNullable<IconSetIcon[\"root\"]> })[] = [];\n\n for (const source of sources) {\n const { icon, problem } = convertIcon(source);\n\n if (isDefined(icon)) converted.push(icon);\n else if (isDefined(problem)) report.skipped.push({ name: source.name, reason: problem });\n }\n\n const common = findCommonRoot(converted);\n const set: IconSet = {\n library: adapter.library,\n style,\n root: common,\n icons: converted.map(({ root: iconRoot, ...icon }) =>\n JSON.stringify(iconRoot) === JSON.stringify(common) ? icon : { ...icon, root: iconRoot },\n ),\n };\n\n report.duration = Math.round(performance.now() - started);\n\n return { set, report };\n}\n\n/**\n * Reads a cached conversion's report.\n *\n * @param value - The cached report.\n * @returns The report, or undefined when the value isn't one.\n */\nfunction parseReport(value: unknown): IconSetReport | undefined {\n if (!isRecord(value) || typeof value[\"total\"] !== \"number\" || !Array.isArray(value[\"skipped\"])) return undefined;\n\n const skipped = value[\"skipped\"].flatMap((entry: unknown) =>\n isRecord(entry) && typeof entry[\"name\"] === \"string\" && typeof entry[\"reason\"] === \"string\"\n ? [{ name: entry[\"name\"], reason: entry[\"reason\"] }]\n : [],\n );\n\n return { total: value[\"total\"], skipped, duration: 0 };\n}\n\n/**\n * Loads a converted set, from the cache when the same package version was converted before.\n *\n * @param library - The library and where it's installed.\n * @param style - The style to load.\n * @param context - The project folder and cache folder.\n * @returns The set and its report.\n */\nexport async function loadIconSet(library: DetectedLibrary, style: string, context: IconSetContext) {\n const { adapter, folder } = library;\n const installed = library.styles.find((candidate) => candidate.style === style)?.installed;\n const cacheable = isDefined(context.cacheDir) && isDefined(installed) && !isDefined(folder);\n const cacheFile =\n cacheable && isDefined(context.cacheDir)\n ? path.join(\n context.cacheDir,\n \"driftime-icon\",\n `${adapter.library}-${style}-${installed.name.replaceAll(/[@/]/gu, \"_\")}-${installed.version}-${await readPluginBuild()}.json`,\n )\n : undefined;\n\n if (isDefined(cacheFile)) {\n const cached = await readJson(cacheFile);\n const set = isRecord(cached) ? parseIconSet(cached[\"set\"]) : undefined;\n const report = isRecord(cached) ? parseReport(cached[\"report\"]) : undefined;\n\n if (isDefined(set) && isDefined(report)) return { set, report };\n }\n\n const { set, report } = await convertIconSet(library, style, context.root);\n\n if (isDefined(cacheFile)) {\n await mkdir(path.dirname(cacheFile), { recursive: true });\n await writeFile(cacheFile, JSON.stringify({ set, report }));\n }\n\n return { set, report };\n}\n\n/**\n * Lists the files the registry is generated from, so the bundler regenerates it when a library is installed, removed,\n * or updated, instead of keeping a cached copy.\n *\n * @param root - The project folder.\n * @param libraries - Every library the build step knows.\n * @returns The files, and the folders of the project's own icons.\n */\nexport function listRegistryDependencies(root: string, libraries: DetectedLibrary[]) {\n const files = [path.join(root, \"package.json\")];\n const folders: string[] = [];\n\n for (const { styles, folder } of libraries) {\n for (const { installed } of styles) {\n if (isDefined(folder)) folders.push(installed.directory);\n else files.push(path.join(installed.directory, \"package.json\"));\n }\n }\n\n return { files, folders };\n}\n\n/**\n * Summarises a set's conversion for the build's log, listing the first few icons it skipped and why.\n *\n * @param label - Readable name of the library.\n * @param style - The style converted.\n * @param report - What happened.\n * @returns The summary, one line per skipped icon after the first.\n */\nexport function describeReport(label: string, style: string, report: IconSetReport) {\n const { total, skipped, duration } = report;\n const kept = total - skipped.length;\n const count = `${kept.toLocaleString(\"en-US\")} ${kept === 1 ? \"icon\" : \"icons\"}`;\n const timing = duration > 0 ? `converted in ${duration.toLocaleString(\"en-US\")} ms` : \"loaded from the cache\";\n const summary = `${label} (\\`${style}\\`): ${count} ${timing}`;\n if (!isDefined(skipped)) return `${summary}.`;\n\n const shown = skipped.slice(0, 5).map(({ name, reason }) => ` \\`${name}\\` ${reason}.`);\n const hidden = skipped.length - shown.length;\n const more = hidden > 0 ? [` …and ${hidden.toLocaleString(\"en-US\")} more.`] : [];\n\n return [`${summary}, ${skipped.length.toLocaleString(\"en-US\")} skipped:`, ...shown, ...more].join(\"\\n\");\n}\n"],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import { c as isDefined, n as iconSvgAttributes } from "./nodes-DdErxvpg.js";
2
+ import { createElement } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ /**
5
+ * Converts stored attributes to React props, turning a `style` attribute into the object React expects.
6
+ *
7
+ * @param attributes - The stored attributes.
8
+ * @returns The props.
9
+ */
10
+ function toProps({ style, ...attributes }) {
11
+ if (!isDefined(style)) return attributes;
12
+ let declarations = style.split(";").flatMap((declaration) => {
13
+ let [property = "", ...value] = declaration.split(":"), name = property.trim(), joined = value.join(":").trim();
14
+ return !isDefined(name) || !isDefined(joined) ? [] : [[name.startsWith("--") ? name : name.replace(/^-ms-/u, "ms-").replaceAll(/-(?<letter>[a-z])/gu, (_match, letter) => letter.toUpperCase()), joined]];
15
+ });
16
+ return {
17
+ ...attributes,
18
+ style: Object.fromEntries(declarations)
19
+ };
20
+ }
21
+ /**
22
+ * Draws one element of a stored drawing, with its children.
23
+ *
24
+ * @param child - The element, or text inside one.
25
+ * @param index - Its position among its siblings.
26
+ * @returns The element.
27
+ */
28
+ function renderChild(child, index) {
29
+ if (typeof child == "string") return child;
30
+ let [element, attributes, children] = child;
31
+ return createElement(element, {
32
+ ...toProps(attributes),
33
+ key: String(index)
34
+ }, children?.map((nested, position) => renderChild(nested, position)));
35
+ }
36
+ function Drawing({ drawing, ...props }) {
37
+ if (!isDefined(drawing)) return null;
38
+ let root = toProps(drawing.root);
39
+ return /* @__PURE__ */ jsx("svg", {
40
+ ...iconSvgAttributes,
41
+ ...root,
42
+ "aria-hidden": "true",
43
+ ...props,
44
+ children: drawing.node.map((child, index) => renderChild(child, index))
45
+ });
46
+ }
47
+ /** Schema type name of the icon object. */
48
+ const iconTypeName = "icon";
49
+ export { Drawing as n, iconTypeName as t };
50
+
51
+ //# sourceMappingURL=types-Dl2HGUL5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-Dl2HGUL5.js","names":["isDefined","ComponentProps","ReactElement","createElement","IconDrawing","IconElement","iconSvgAttributes","toProps","style","attributes","Record","declarations","split","flatMap","declaration","property","value","name","trim","joined","join","key","startsWith","replace","replaceAll","_match","letter","toUpperCase","Object","fromEntries","renderChild","child","index","element","children","String","map","nested","position","DrawingProps","Omit","drawing","Drawing","props","root","node","SanityIconNames","iconLibraryStyles","iconTypeName","SanityIcon","_type","library","style","name","node","IconLibraryStyles","SanityIconLibrary","SanityIconStyle","Library","SanityIconName","ListedLibraryId","NonNullable","Extract","IconLibraryId"],"sources":["../src/components/drawing.tsx","../src/types.ts"],"sourcesContent":["// Consumers render this in server components, where the compiler's memo cache hook can't run.\n\"use no memo\";\n\nimport { isDefined } from \"@repo/lib/utils\";\nimport type { ComponentProps, ReactElement } from \"react\";\nimport { createElement } from \"react\";\n\nimport type { IconDrawing, IconElement } from \"@/lib/nodes\";\nimport { iconSvgAttributes } from \"@/lib/nodes\";\n\n/**\n * Converts stored attributes to React props, turning a `style` attribute into the object React expects.\n *\n * @param attributes - The stored attributes.\n * @returns The props.\n */\nfunction toProps({ style, ...attributes }: Record<string, string>) {\n if (!isDefined(style)) return attributes;\n\n const declarations = style.split(\";\").flatMap((declaration): [string, string][] => {\n const [property = \"\", ...value] = declaration.split(\":\");\n const name = property.trim();\n const joined = value.join(\":\").trim();\n if (!isDefined(name) || !isDefined(joined)) return [];\n\n const key = name.startsWith(\"--\")\n ? name\n : name\n .replace(/^-ms-/u, \"ms-\")\n .replaceAll(/-(?<letter>[a-z])/gu, (_match, letter: string) => letter.toUpperCase());\n\n return [[key, joined]];\n });\n\n return { ...attributes, style: Object.fromEntries(declarations) };\n}\n\n/**\n * Draws one element of a stored drawing, with its children.\n *\n * @param child - The element, or text inside one.\n * @param index - Its position among its siblings.\n * @returns The element.\n */\nfunction renderChild(child: IconElement | string, index: number): ReactElement | string {\n if (typeof child === \"string\") return child;\n\n const [element, attributes, children] = child;\n\n return createElement(\n element,\n { ...toProps(attributes), key: String(index) },\n children?.map((nested, position) => renderChild(nested, position)),\n );\n}\n\nexport type DrawingProps = Omit<ComponentProps<\"svg\">, \"children\"> & { drawing: IconDrawing | undefined };\n\nexport function Drawing({ drawing, ...props }: DrawingProps) {\n if (!isDefined(drawing)) return null;\n\n // Stored roots only hold checked attribute names, but their values are text, not React's literal types.\n const root = toProps(drawing.root) as ComponentProps<\"svg\">;\n\n return (\n <svg {...iconSvgAttributes} {...root} aria-hidden=\"true\" {...props}>\n {drawing.node.map((child, index) => renderChild(child, index))}\n </svg>\n );\n}\n","import type { SanityIconNames } from \"@driftime/sanity-plugin-icon/names\";\n\nimport type { iconLibraryStyles } from \"@/lib/libraries\";\n\n/** Schema type name of the icon object. */\nexport const iconTypeName = \"icon\";\n\n/**\n * Stored icon: which library it came from, its name, and the shapes it's drawn from.\n *\n * @public\n */\nexport interface SanityIcon {\n _type: typeof iconTypeName;\n /** Identifier of the library the icon came from. */\n library?: string;\n /** Identifier of the library style the icon came from. */\n style?: string;\n /** Name of the icon in its library. */\n name?: string;\n /** Shapes the icon is drawn from, saved when it's chosen. */\n node?: string;\n}\n\n/** Styles of each supported library, from the plugin's own list. */\ntype IconLibraryStyles = typeof iconLibraryStyles;\n\n/**\n * Identifier of a library the plugin supports.\n *\n * @public\n */\nexport type SanityIconLibrary = keyof IconLibraryStyles;\n\n/**\n * Styles a library offers. The project's own icon folders have one style, `default`.\n *\n * @public\n */\nexport type SanityIconStyle<Library extends string> = Library extends SanityIconLibrary\n ? IconLibraryStyles[Library][number]\n : \"default\";\n\n/**\n * Names of a library's icons, once the build step has listed the installed version's, or any name before then.\n *\n * @public\n */\nexport type SanityIconName<Library extends string> = Library extends keyof SanityIconNames\n ? SanityIconNames[Library]\n : string;\n\n/** Libraries the build step has listed, or any identifier before it has, so the project's own folders can be named. */\nexport type ListedLibraryId = [keyof SanityIconNames] extends [never]\n ? string & NonNullable<unknown>\n : Extract<keyof SanityIconNames, string>;\n\n/** Libraries a field can name: the supported ones, plus the project's own folders. */\nexport type IconLibraryId = SanityIconLibrary | ListedLibraryId;\n"],"mappings":";;;;;;;;;AAgBA,SAASO,QAAQ,EAAEC,OAAO,GAAGC,cAAsC;CACjE,IAAI,CAACT,UAAUQ,KAAK,GAAG,OAAOC;CAE9B,IAAME,eAAeH,MAAMI,MAAM,GAAG,CAAC,CAACC,SAASC,gBAAoC;EACjF,IAAM,CAACC,WAAW,IAAI,GAAGC,SAASF,YAAYF,MAAM,GAAG,GACjDK,OAAOF,SAASG,KAAK,GACrBC,SAASH,MAAMI,KAAK,GAAG,CAAC,CAACF,KAAK;EASpC,OARI,CAAClB,UAAUiB,IAAI,KAAK,CAACjB,UAAUmB,MAAM,IAAU,CAAA,IAQ5C,CAAC,CANIF,KAAKK,WAAW,IAAI,IAC5BL,OACAA,KACGM,QAAQ,UAAU,KAAK,CAAC,CACxBC,WAAW,wBAAwBC,QAAQC,WAAmBA,OAAOC,YAAY,CAAC,GAE3ER,MAAM,CAAC;CACvB,CAAC;CAED,OAAO;EAAE,GAAGV;EAAYD,OAAOoB,OAAOC,YAAYlB,YAAY;CAAE;AAClE;;;;;;;;AASA,SAASmB,YAAYC,OAA6BC,OAAsC;CACtF,IAAI,OAAOD,SAAU,UAAU,OAAOA;CAEtC,IAAM,CAACE,SAASxB,YAAYyB,YAAYH;CAExC,OAAO5B,cACL8B,SACA;EAAE,GAAG1B,QAAQE,UAAU;EAAGY,KAAKc,OAAOH,KAAK;CAAE,GAC7CE,UAAUE,KAAKC,QAAQC,aAAaR,YAAYO,QAAQC,QAAQ,CAAC,CACnE;AACF;AAIA,SAAgBI,QAAQ,EAAED,SAAS,GAAGE,SAAuB;CAC3D,IAAI,CAAC3C,UAAUyC,OAAO,GAAG,OAAO;CAGhC,IAAMG,OAAOrC,QAAQkC,QAAQG,IAAI;CAEjC,OACE,oBAAC,OAAD;EAAK,GAAItC;EAAmB,GAAIsC;EAAM,eAAY;EAAO,GAAID;EAC1DF,UAAAA,QAAQI,KAAKT,KAAKL,OAAOC,UAAUF,YAAYC,OAAOC,KAAK,CAAC;CAC1D,CAAA;AAET;;AChEA,MAAagB,eAAe"}
package/dist/vite.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as pluginName, t as logger } from "./defaults-BEzCWZtH.js";
2
- import { c as isDefined } from "./nodes-Bn7fLVZO.js";
3
- import { a as writeIconNames, i as loadIconSet, n as detectLibraries, o as createRegistryModule, r as listRegistryDependencies, s as isRegistryModule, t as describeReport } from "./sets-B5arQvrZ.js";
2
+ import { c as isDefined } from "./nodes-DdErxvpg.js";
3
+ import { a as writeIconNames, i as loadIconSet, n as detectLibraries, o as createRegistryModule, r as listRegistryDependencies, s as isRegistryModule, t as describeReport } from "./sets-FpS3h5A7.js";
4
4
  import path from "node:path";
5
5
  /** Prefix of the module each set is loaded from, followed by its library and style. */
6
6
  const setPrefix = `virtual:${pluginName}/set/`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftime/sanity-plugin-icon",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Icons for Sanity Studio, managed as content by the authors who use them.",
5
5
  "keywords": [
6
6
  "central-icons",
@@ -1 +0,0 @@
1
- {"version":3,"file":"loader-2g6inuO6.js","names":["randomUUID","mkdir","readFile","rename","writeFile","path","isRecord","writeIconNames","listPluginFiles","createRegistryModule","describeReport","detectLibraries","listRegistryDependencies","loadIconSet","logger","LoaderContext","resourcePath","getOptions","addDependency","file","addContextDependency","folder","writeIconSets","root","custom","Record","cacheDir","join","setsDir","recursive","libraries","converted","Promise","all","flatMap","library","styles","map","style","adapter","set","report","content","JSON","stringify","current","catch","temporary","generateRegistryOnce","options","process","cwd","folders","Object","fromEntries","entries","name","files","Map","names","Set","label","info","libraryNames","get","icon","icons","add","filter","size","code","key","relative","dirname","replaceAll","startsWith","dependencies","readManifest","generateRegistry","attempts","ReturnType","before","result","after","\"custom\""],"sources":["../src/build/loader.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { isRecord } from \"@repo/lib/utils\";\n\nimport { writeIconNames } from \"@/build/names\";\nimport { listPluginFiles } from \"@/build/packages\";\nimport { createRegistryModule } from \"@/build/registry\";\nimport { describeReport, detectLibraries, listRegistryDependencies, loadIconSet } from \"@/build/sets\";\nimport { logger } from \"@/config/defaults\";\n\n/**\n * The parts of a webpack or Turbopack loader's context the build step uses.\n *\n * @public\n */\nexport interface LoaderContext {\n /** Path of the module being loaded. */\n resourcePath: string;\n /**\n * Reads the loader's options.\n *\n * @returns The options.\n */\n getOptions: () => unknown;\n /**\n * Tells the bundler to run the loader again when a file changes.\n *\n * @param file - The file.\n */\n addDependency?: (file: string) => void;\n /**\n * Tells the bundler to run the loader again when anything in a folder changes.\n *\n * @param folder - The folder.\n */\n addContextDependency?: (folder: string) => void;\n}\n\n/**\n * Converts every installed library and writes each set to a file the bundler can split into its own chunk.\n *\n * @param root - The project folder.\n * @param custom - Folders of the project's own icons, keyed by library identifier.\n * @returns The libraries found, and each set with the file it was written to and what happened converting it.\n */\nexport async function writeIconSets(root: string, custom: Record<string, string>) {\n const cacheDir = path.join(root, \"node_modules\", \".cache\");\n const setsDir = path.join(cacheDir, \"driftime-icon\", \"sets\");\n await mkdir(setsDir, { recursive: true });\n\n const libraries = await detectLibraries(root, custom);\n\n const converted = await Promise.all(\n libraries.flatMap((library) =>\n library.styles.map(async ({ style }) => {\n const { adapter } = library;\n const { set, report } = await loadIconSet(library, style, { root, cacheDir });\n const file = path.join(setsDir, `${adapter.library}-${style}.json`);\n const content = JSON.stringify(set);\n const current = await readFile(file, \"utf8\").catch(() => \"\");\n\n if (current !== content) {\n const temporary = `${file}.${randomUUID()}.tmp`;\n\n // The browser and server builds convert at once, even in one process, so each set is swapped in whole.\n await writeFile(temporary, content);\n await rename(temporary, file);\n }\n\n return { adapter, style, set, file, report };\n }),\n ),\n );\n\n return { libraries, converted };\n}\n\n/**\n * Converts every installed library, writes each set to a file the bundler can split into its own chunk, and returns\n * the registry that replaces the placeholder.\n *\n * @param resourcePath - Path of the placeholder being replaced.\n * @param options - The loader's options.\n * @returns The registry's code, and the files and folders it was generated from.\n */\nasync function generateRegistryOnce(resourcePath: string, options: unknown) {\n const root = process.cwd();\n const custom = isRecord(options) && isRecord(options[\"custom\"]) ? options[\"custom\"] : {};\n const folders = Object.fromEntries(\n Object.entries(custom).flatMap(([name, folder]) => (typeof folder === \"string\" ? [[name, folder]] : [])),\n );\n\n const { libraries, converted } = await writeIconSets(root, folders);\n const files = new Map<string, string>();\n const names = new Map<string, Set<string>>();\n\n for (const { adapter, style, set, file, report } of converted) {\n const { label, library } = adapter;\n logger.info(describeReport(label, style, report));\n\n const libraryNames = names.get(library) ?? new Set<string>();\n\n for (const icon of set.icons) libraryNames.add(icon.name);\n names.set(library, libraryNames);\n files.set(`${library}/${style}`, file);\n }\n\n await writeIconNames(new Map([...names].filter(([, libraryNames]) => libraryNames.size > 0)));\n\n const code = createRegistryModule(libraries, (key) => {\n const file = files.get(key) ?? \"\";\n const relative = path.relative(path.dirname(resourcePath), file).replaceAll(\"\\\\\", \"/\");\n\n return relative.startsWith(\".\") ? relative : `./${relative}`;\n });\n\n const dependencies = listRegistryDependencies(root, libraries);\n\n // The plugin's own files are included, so updating it runs the loader again even when `package.json` doesn't change.\n return { code, files: [...dependencies.files, ...(await listPluginFiles())], folders: dependencies.folders };\n}\n\n/**\n * Reads the project's `package.json` as text.\n *\n * @returns The contents, or an empty string when there's no file.\n */\nasync function readManifest() {\n try {\n return await readFile(path.join(process.cwd(), \"package.json\"), \"utf8\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * Generates the registry, running again when the project's `package.json` changes while it runs, so a package\n * installed during a run isn't missed.\n *\n * @param resourcePath - Path of the placeholder being replaced.\n * @param options - The loader's options.\n * @param attempts - Runs allowed, including this one.\n * @returns The registry's code, and the files and folders it was generated from.\n * @public\n */\nexport async function generateRegistry(\n resourcePath: string,\n options: unknown,\n attempts = 3,\n): ReturnType<typeof generateRegistryOnce> {\n const before = await readManifest();\n const result = await generateRegistryOnce(resourcePath, options);\n const after = await readManifest();\n\n return before === after || attempts <= 1 ? result : generateRegistry(resourcePath, options, attempts - 1);\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"nodes-Bn7fLVZO.js","names":["Nullable","T","isDefined","value","undefined","trim","Array","isArray","some","element","prototype","Reflect","getPrototypeOf","Object","keys","length","convertCase","format","words","replaceAll","toLowerCase","_match","character","toUpperCase","isRecord","Record","readPath","path","current","segment","isDefined","isRecord","stegaClean","IconElement","element","attributes","Record","children","IconNode","IconRoot","IconDrawing","root","node","iconElements","ReadonlySet","Set","linkAttributes","urlPattern","iconSvgAttributes","xmlns","width","height","const","isAttributes","value","Object","values","every","attribute","isIconElement","Array","isArray","length","parts","name","child","findAttributeProblem","entries","test","has","startsWith","includes","references","matchAll","outside","some","groups","trim","replace","split","undefined","findNodeProblem","problem","findDrawingProblem","parseIconNode","parseIconRoot","parseJson","JSON","parse","resolveIconDrawing","cleaned","stored","drawing","serializeIconDrawing","stringify","parts","\"reference\"","\"root\"","\"node\""],"sources":["../../@repo/lib/utils.ts","../src/lib/nodes.ts"],"sourcesContent":["/** A value that may be null or undefined. */\ntype Nullable<T> = T | null | undefined;\n\n/**\n * Checks whether a value is present. `false`, blank strings, and empty plain objects count as absent,\n * as does an array with no present elements.\n *\n * @param value - The value to check.\n * @returns True if the value is present.\n */\nexport function isDefined<T>(value: Nullable<T> | false): value is T {\n if (value === undefined || value === null || value === false) return false;\n if (typeof value === \"string\") return value.trim() !== \"\";\n if (Array.isArray(value)) return value.some((element) => isDefined(element));\n\n if (typeof value === \"object\") {\n const prototype = Reflect.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) return true;\n\n return Object.keys(value).length > 0;\n }\n\n return true;\n}\n\n/**\n * Converts a string to another case.\n *\n * @param value - The string to convert.\n * @param format - The case to convert to.\n * @returns The converted string.\n */\nexport function convertCase(value: string, format: \"kebab\" | \"snake\" | \"camel\" | \"pascal\" | \"title\" | \"sentence\") {\n const words = value\n .replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, \"$<lower> $<upper>\")\n .replaceAll(/[-_\\s]+/gu, \" \")\n .trim()\n .toLowerCase();\n\n switch (format) {\n case \"kebab\": {\n return words.replaceAll(/\\s+/gu, \"-\");\n }\n case \"snake\": {\n return words.replaceAll(/\\s+/gu, \"_\");\n }\n case \"camel\": {\n return words.replaceAll(/\\s+(?<character>.)/gu, (_match, character: string) => character.toUpperCase());\n }\n case \"pascal\": {\n return words.replaceAll(/(?:^|\\s+)(?<character>.)/gu, (_match, character: string) => character.toUpperCase());\n }\n case \"title\": {\n return words.replaceAll(/\\b\\w/gu, (character) => character.toUpperCase());\n }\n case \"sentence\": {\n return words.replaceAll(/^(?<character>.)/gu, (character) => character.toUpperCase());\n }\n default: {\n return value;\n }\n }\n}\n\n/**\n * Checks whether a value is an object whose keys can be read.\n *\n * @param value - The value to check.\n * @returns True if the value is an object.\n */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Reads a nested value by following a path of keys.\n *\n * @param value - The object to read from.\n * @param path - The keys to follow.\n * @returns The value, or undefined when the path doesn't exist.\n */\nexport function readPath(value: unknown, path: string[]) {\n let current: unknown = value;\n\n for (const segment of path) {\n if (!isRecord(current)) return undefined;\n\n current = current[segment];\n }\n\n return current;\n}\n","import { isDefined, isRecord } from \"@repo/lib/utils\";\nimport { stegaClean } from \"@sanity/client/stega\";\n\n/** One element in a drawing: its name, its attributes, and any children, which may include text. */\nexport type IconElement = [element: string, attributes: Record<string, string>, children?: (IconElement | string)[]];\n\n/** An icon's drawing, stored as a tree of elements rather than a component. */\nexport type IconNode = IconElement[];\n\n/** Attributes for the SVG around a drawing's elements. */\nexport type IconRoot = Record<string, string>;\n\n/** An icon's elements together with the SVG attributes they're drawn inside. */\nexport interface IconDrawing {\n /** Attributes for the SVG around the elements. */\n root: IconRoot;\n /** Elements the icon is drawn from. */\n node: IconNode;\n}\n\n/**\n * SVG elements a drawing may contain: everything that draws, paints, clips, masks, or filters. Anything that runs\n * code, embeds other content, links away, loads a file, animates, or styles the page around it is left out.\n */\nconst iconElements: ReadonlySet<string> = new Set([\n \"circle\",\n \"clipPath\",\n \"defs\",\n \"ellipse\",\n \"feBlend\",\n \"feColorMatrix\",\n \"feComponentTransfer\",\n \"feComposite\",\n \"feConvolveMatrix\",\n \"feDiffuseLighting\",\n \"feDisplacementMap\",\n \"feDistantLight\",\n \"feDropShadow\",\n \"feFlood\",\n \"feFuncA\",\n \"feFuncB\",\n \"feFuncG\",\n \"feFuncR\",\n \"feGaussianBlur\",\n \"feMerge\",\n \"feMergeNode\",\n \"feMorphology\",\n \"feOffset\",\n \"fePointLight\",\n \"feSpecularLighting\",\n \"feSpotLight\",\n \"feTile\",\n \"feTurbulence\",\n \"filter\",\n \"g\",\n \"line\",\n \"linearGradient\",\n \"marker\",\n \"mask\",\n \"path\",\n \"pattern\",\n \"polygon\",\n \"polyline\",\n \"radialGradient\",\n \"rect\",\n \"stop\",\n \"symbol\",\n \"text\",\n \"textPath\",\n \"tspan\",\n \"use\",\n]);\n\n/** Attributes that point at another element by URL, which must point inside the drawing. */\nconst linkAttributes = new Set([\"href\", \"xlinkHref\"]);\n\n/** A reference to another element in an attribute value, such as `url(#fade)`. */\nconst urlPattern = /url\\((?<reference>[^)]*)\\)/gu;\n\n/** Attributes every drawing's SVG carries, whichever library it came from. */\nexport const iconSvgAttributes = { xmlns: \"http://www.w3.org/2000/svg\", width: 24, height: 24 } as const;\n\n/**\n * Checks whether a value is a set of attributes, each with a text value.\n *\n * @param value - The value to check.\n * @returns True if the value is a set of attributes.\n */\nfunction isAttributes(value: unknown): value is Record<string, string> {\n return isRecord(value) && Object.values(value).every((attribute) => typeof attribute === \"string\");\n}\n\n/**\n * Checks whether a value is an element in a drawing, with text attributes and valid children.\n *\n * @param value - The value to check.\n * @returns True if the value is an element.\n */\nfunction isIconElement(value: unknown): value is IconElement {\n if (!Array.isArray(value) || value.length < 2 || value.length > 3) return false;\n\n const parts: unknown[] = value;\n const [name, attributes, children] = parts;\n if (typeof name !== \"string\" || !isAttributes(attributes)) return false;\n\n return (\n !isDefined(children) ||\n (Array.isArray(children) && children.every((child) => typeof child === \"string\" || isIconElement(child)))\n );\n}\n\n/**\n * Finds why an element's attributes can't be drawn safely.\n *\n * @param attributes - The attributes.\n * @returns The problem, or undefined when the attributes are safe.\n */\nfunction findAttributeProblem(attributes: Record<string, string>) {\n for (const [name, value] of Object.entries(attributes)) {\n if (/^on/iu.test(name)) return `has an event handler (\\`${name}\\`)`;\n if (!/^[a-zA-Z][a-zA-Z0-9]*$/u.test(name)) return `has an unknown attribute (\\`${name}\\`)`;\n if (/javascript:/iu.test(value)) return \"runs a script\";\n\n if (linkAttributes.has(name) && !value.startsWith(\"#\")) return \"links outside the icon\";\n\n if (value.includes(\"url(\")) {\n const references = [...value.matchAll(urlPattern)];\n const outside = references.some(\n ({ groups }) => !(groups?.[\"reference\"]?.trim().replace(/^[\"']/u, \"\").startsWith(\"#\") ?? false),\n );\n\n if (outside || value.split(\"url(\").length - 1 !== references.length) {\n return \"refers to something outside the icon\";\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Finds why part of a drawing can't be drawn safely.\n *\n * @param node - The elements to check.\n * @returns The problem, or undefined when every element is safe.\n */\nfunction findNodeProblem(node: (IconElement | string)[]): string | undefined {\n for (const child of node) {\n if (typeof child === \"string\") continue;\n\n const [name, attributes, children] = child;\n if (!iconElements.has(name)) return `uses the \\`<${name}>\\` element`;\n\n const problem = findAttributeProblem(attributes) ?? (isDefined(children) ? findNodeProblem(children) : undefined);\n if (isDefined(problem)) return problem;\n }\n\n return undefined;\n}\n\n/**\n * Finds why a drawing can't be drawn safely: an element or attribute that could run code, load something, or reach\n * outside the icon. The same check guards the build step's output and every stored value before it's drawn.\n *\n * @param drawing - The drawing to check.\n * @returns The problem, or undefined when the drawing is safe.\n */\nexport function findDrawingProblem({ root, node }: IconDrawing) {\n if (!isDefined(node)) return \"draws nothing\";\n\n return findAttributeProblem(root) ?? findNodeProblem(node);\n}\n\n/**\n * Reads a drawing's elements from an already parsed value, such as a converted set.\n *\n * @param value - The value to read.\n * @returns The elements, or undefined when the value isn't a list of them.\n */\nexport function parseIconNode(value: unknown) {\n return Array.isArray(value) && value.every((element) => isIconElement(element)) ? value : undefined;\n}\n\n/**\n * Reads an SVG root from an already parsed value.\n *\n * @param value - The value to read.\n * @returns The root, or undefined when the value isn't one.\n */\nexport function parseIconRoot(value: unknown) {\n return isAttributes(value) ? value : undefined;\n}\n\n/**\n * Parses stored JSON.\n *\n * @param value - The stored text.\n * @returns The parsed value, or undefined when the text isn't JSON.\n */\nfunction parseJson(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Reads a drawing back from its stored form, removing stega characters first.\n *\n * @param value - The stored drawing.\n * @returns The drawing, or undefined when nothing readable and safe was stored.\n */\nexport function resolveIconDrawing(value: string | undefined): IconDrawing | undefined {\n const cleaned = stegaClean(value);\n if (!isDefined(cleaned)) return undefined;\n\n const stored = parseJson(cleaned);\n if (!isRecord(stored)) return undefined;\n\n const root = parseIconRoot(stored[\"root\"]);\n const node = parseIconNode(stored[\"node\"]);\n if (!isDefined(root) || !isDefined(node)) return undefined;\n\n const drawing = { root, node };\n\n return isDefined(findDrawingProblem(drawing)) ? undefined : drawing;\n}\n\n/**\n * Converts a drawing to its stored form.\n *\n * @param drawing - The drawing.\n * @returns The drawing as a string.\n */\nexport function serializeIconDrawing(drawing: IconDrawing) {\n return JSON.stringify({ root: drawing.root, node: drawing.node });\n}\n"],"mappings":"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"}