@kanso-labs/unplugin-style-dictionary 0.4.28 → 0.4.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,14 +2,16 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import zlib from "node:zlib";
5
+ import picomatch from "picomatch";
5
6
  import StyleDictionary from "style-dictionary";
7
+ import { glob } from "tinyglobby";
6
8
  import { createUnplugin } from "unplugin";
7
9
  //#region src/index.ts
8
10
  function matchesWatchedFile(file, patterns) {
9
11
  const normalizedFile = file.replace(/\\/g, "/");
10
12
  return patterns.some((pattern) => {
11
- if (pattern === normalizedFile) return true;
12
- return normalizedFile.startsWith(pattern.replace(/\/\*\*/g, "")) || pattern.includes("*") && new RegExp(pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")).test(normalizedFile);
13
+ const normalizedPattern = pattern.replace(/\\/g, "/");
14
+ return normalizedPattern === normalizedFile || picomatch.isMatch(normalizedFile, normalizedPattern);
13
15
  });
14
16
  }
15
17
  function discardTemporaryFile(temporary) {
@@ -27,6 +29,21 @@ function unwrapDefault(value) {
27
29
  return typeof value === "object" && value !== null && "default" in value ? value.default ?? value : value;
28
30
  }
29
31
  let temporaryFileCounter = 0;
32
+ async function rendersWhatIsAlreadyThere(temporary, destination) {
33
+ try {
34
+ const [existing, rendered] = await Promise.all([fs.promises.readFile(destination), fs.promises.readFile(temporary)]);
35
+ return existing.equals(rendered);
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+ function rendersWhatIsAlreadyThereSync(temporary, destination) {
41
+ try {
42
+ return fs.readFileSync(destination).equals(fs.readFileSync(temporary));
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
30
47
  function temporaryPathFor(destination) {
31
48
  const extension = path.extname(destination);
32
49
  return path.join(path.dirname(destination), `.${path.basename(destination, extension)}.${process.pid}.${temporaryFileCounter++}.tmp`);
@@ -36,6 +53,10 @@ const writeFileAtomic = async (file, data, options) => {
36
53
  const temporary = temporaryPathFor(file);
37
54
  try {
38
55
  await fs.promises.writeFile(temporary, data, options);
56
+ if (await rendersWhatIsAlreadyThere(temporary, file)) {
57
+ discardTemporaryFile(temporary);
58
+ return;
59
+ }
39
60
  await fs.promises.rename(temporary, file);
40
61
  } catch (err) {
41
62
  discardTemporaryFile(temporary);
@@ -50,6 +71,10 @@ const writeFileSyncAtomic = (file, data, options) => {
50
71
  const temporary = temporaryPathFor(file);
51
72
  try {
52
73
  fs.writeFileSync(temporary, data, options);
74
+ if (rendersWhatIsAlreadyThereSync(temporary, file)) {
75
+ discardTemporaryFile(temporary);
76
+ return;
77
+ }
53
78
  fs.renameSync(temporary, file);
54
79
  } catch (err) {
55
80
  discardTemporaryFile(temporary);
@@ -60,9 +85,34 @@ const atomicVolume = Object.create(fs, {
60
85
  promises: { value: Object.create(fs.promises, { writeFile: { value: writeFileAtomic } }) },
61
86
  writeFileSync: { value: writeFileSyncAtomic }
62
87
  });
88
+ const GLOB_CHARACTERS = /[!*?[\]{}]/;
89
+ function staticParentOf(pattern) {
90
+ const segments = pattern.split("/");
91
+ const firstGlob = segments.findIndex((segment) => GLOB_CHARACTERS.test(segment));
92
+ return firstGlob === -1 ? path.posix.dirname(pattern) : segments.slice(0, firstGlob).join("/");
93
+ }
63
94
  const unpluginFactory = (options = {}) => {
64
95
  const { silent = false } = options;
65
96
  let root = process.cwd();
97
+ const generatedDestinations = /* @__PURE__ */ new Set();
98
+ let watchRebuild = false;
99
+ let hasCompiled = false;
100
+ const expandPatterns = async (patterns) => {
101
+ const paths = /* @__PURE__ */ new Set();
102
+ const globs = [];
103
+ for (const pattern of patterns) if (GLOB_CHARACTERS.test(pattern)) {
104
+ globs.push(pattern);
105
+ const parent = staticParentOf(pattern);
106
+ if (parent && fs.existsSync(parent)) paths.add(parent);
107
+ } else paths.add(pattern);
108
+ if (globs.length > 0) try {
109
+ for (const match of await glob(globs, { absolute: true })) paths.add(match.replace(/\\/g, "/"));
110
+ } catch (err) {
111
+ log(`Failed to expand watch patterns: ${errorMessage(err)}`, "error");
112
+ }
113
+ return Array.from(paths);
114
+ };
115
+ const isWatchedSource = (file, patterns) => !generatedDestinations.has(file.replace(/\\/g, "/")) && matchesWatchedFile(file, patterns);
66
116
  const log = (message, type = "info") => {
67
117
  if (silent) return;
68
118
  const prefix = "[unplugin-style-dictionary]";
@@ -103,7 +153,7 @@ const unpluginFactory = (options = {}) => {
103
153
  };
104
154
  });
105
155
  };
106
- const getWatchFiles = async (resolvedConfigs) => {
156
+ const getWatchTargets = async (resolvedConfigs) => {
107
157
  const filesToWatch = /* @__PURE__ */ new Set();
108
158
  for (const item of resolvedConfigs) {
109
159
  if (item.file) filesToWatch.add(item.file.replace(/\\/g, "/"));
@@ -145,7 +195,11 @@ const unpluginFactory = (options = {}) => {
145
195
  filesToWatch.add(absolutePattern.replace(/\\/g, "/"));
146
196
  }
147
197
  }
148
- return Array.from(filesToWatch);
198
+ const patterns = Array.from(filesToWatch);
199
+ return {
200
+ paths: await expandPatterns(patterns),
201
+ patterns
202
+ };
149
203
  };
150
204
  const runBuilds = async (resolvedConfigs, context) => {
151
205
  const startTime = Date.now();
@@ -158,7 +212,7 @@ const unpluginFactory = (options = {}) => {
158
212
  const silentSD = await sd.extend({ log: { verbosity: "silent" } });
159
213
  silentSD.volume = atomicVolume;
160
214
  await silentSD.buildAllPlatforms();
161
- if (!context && !silent) for (const platform of Object.values(silentSD.platforms)) {
215
+ for (const platform of Object.values(silentSD.platforms)) {
162
216
  const buildPath = platform.buildPath ?? "";
163
217
  for (const file of platform.files ?? []) if (file.destination) {
164
218
  const absoluteBuildPath = path.isAbsolute(buildPath) ? buildPath : path.resolve(root, buildPath);
@@ -167,6 +221,8 @@ const unpluginFactory = (options = {}) => {
167
221
  }
168
222
  }
169
223
  }
224
+ generatedDestinations.clear();
225
+ for (const destination of generatedFiles) generatedDestinations.add(destination.replace(/\\/g, "/"));
170
226
  const duration = Date.now() - startTime;
171
227
  if (context) log(`Rebuilt design tokens due to change in ${context} (${duration}ms)`, "success");
172
228
  else {
@@ -210,9 +266,14 @@ const unpluginFactory = (options = {}) => {
210
266
  async buildStart() {
211
267
  const resolved = await resolveConfigs();
212
268
  if (resolved.length === 0) return;
213
- const watchFiles = await getWatchFiles(resolved);
214
- for (const file of watchFiles) this.addWatchFile(file);
269
+ const { paths } = await getWatchTargets(resolved);
270
+ for (const file of paths) this.addWatchFile(file);
271
+ if (watchRebuild && hasCompiled) {
272
+ watchRebuild = false;
273
+ return;
274
+ }
215
275
  await runBuilds(resolved);
276
+ hasCompiled = true;
216
277
  },
217
278
  name: "unplugin-style-dictionary",
218
279
  vite: {
@@ -222,16 +283,16 @@ const unpluginFactory = (options = {}) => {
222
283
  async configureServer(server) {
223
284
  const resolved = await resolveConfigs();
224
285
  if (resolved.length === 0) return;
225
- const filesToWatch = await getWatchFiles(resolved);
226
- server.watcher.add(filesToWatch);
286
+ let targets = await getWatchTargets(resolved);
287
+ server.watcher.add(targets.paths);
227
288
  server.watcher.on("all", (_event, file) => {
228
- if (!matchesWatchedFile(file, filesToWatch)) return;
289
+ if (!isWatchedSource(file, targets.patterns)) return;
229
290
  (async () => {
230
291
  try {
231
292
  const currentResolved = await resolveConfigs();
232
293
  await runBuilds(currentResolved, path.basename(file));
233
- const newWatches = await getWatchFiles(currentResolved);
234
- server.watcher.add(newWatches);
294
+ targets = await getWatchTargets(currentResolved);
295
+ server.watcher.add(targets.paths);
235
296
  } catch (err) {
236
297
  log(`Rebuild failed: ${errorMessage(err)}`, "error");
237
298
  }
@@ -240,12 +301,14 @@ const unpluginFactory = (options = {}) => {
240
301
  }
241
302
  },
242
303
  async watchChange(id) {
304
+ watchRebuild = true;
243
305
  const resolved = await resolveConfigs();
244
306
  if (resolved.length === 0) return;
245
- const watchFiles = await getWatchFiles(resolved);
246
- if (!matchesWatchedFile(id, watchFiles)) return;
307
+ const { patterns } = await getWatchTargets(resolved);
308
+ if (!isWatchedSource(id, patterns)) return;
247
309
  await runBuilds(resolved, path.basename(id));
248
- for (const file of watchFiles) this.addWatchFile(file);
310
+ hasCompiled = true;
311
+ for (const file of await expandPatterns(patterns)) this.addWatchFile(file);
249
312
  }
250
313
  };
251
314
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'style-dictionary'\nimport type { UnpluginFactory } from 'unplugin'\nimport type { ViteDevServer } from 'vite'\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport zlib from 'node:zlib'\nimport StyleDictionary from 'style-dictionary'\nimport { createUnplugin } from 'unplugin'\n\nimport type { UnpluginStyleDictionaryOptions } from './types.js'\n\nexport type * from './types.js'\n\n// Whether `file` matches one of the resolved config/token watch patterns.\n// Shared by the Vite-specific `configureServer` watcher and the universal\n// `watchChange` hook — both need it, and both must skip files that don't\n// match: without this filter, `watchChange` reacts to *any* changed\n// module-graph file, including this plugin's own generated output (since\n// consuming code imports it). Every regenerate is itself a \"change\", which\n// without filtering re-triggers a rebuild forever.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some((pattern) => {\n // If the pattern is an exact file path\n if (pattern === normalizedFile) return true\n\n // If the pattern is a glob, we check if the file matches it.\n // Note: A simple string match or simple glob matcher can be used here.\n // For simplicity and correctness, since token source paths are usually globs,\n // we can match based on path directory containment or general matching.\n // Let's implement a robust matchesGlob check or check if it's one of the token files.\n // Chokidar triggers on actual files, so we want to check if the changed file matches\n // any of the config files or token files/globs.\n return (\n normalizedFile.startsWith(pattern.replace(/\\/\\*\\*/g, '')) ||\n (pattern.includes('*') &&\n new RegExp(\n pattern.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*'),\n ).test(normalizedFile))\n )\n })\n}\n\n// Best-effort cleanup of a temporary file whose write or rename failed. The\n// original failure is what the caller reports, so nothing here may throw.\nfunction discardTemporaryFile(temporary: string): void {\n try {\n fs.rmSync(temporary, { force: true })\n } catch {\n // Ignore: a leftover temporary file is not worth masking the real error.\n }\n}\n\n// `catch` binds `unknown`, and a thrown non-Error — a string, a rejected\n// value out of a config module — carries no `.message`. The `as Error` casts\n// this replaces claimed otherwise and printed `undefined` for exactly those\n// cases, which is the least useful thing a failure log can say.\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n// A config file is an untyped boundary: `JSON.parse` and a dynamic `import`\n// both hand back `any`, and an `any` assigned to `configObj` spreads through\n// every read of it downstream. These two narrow that boundary once, here.\n// They are type predicates rather than assertions on purpose — a predicate is\n// a check the compiler verifies, where a cast is only a claim.\nfunction isConfig(value: unknown): value is Config {\n return typeof value === 'object' && value !== null\n}\n\n// A config module may expose its config as a `default` export or as the\n// namespace itself. `'default' in value` is what lets the compiler reach\n// `.default` without a cast.\nfunction unwrapDefault(value: unknown): unknown {\n return typeof value === 'object' && value !== null && 'default' in value\n ? (value.default ?? value)\n : value\n}\n\n// Style Dictionary writes every generated file with a plain `writeFile` on the\n// volume it was handed, which truncates the destination and then streams the\n// new contents into it. Anything reading that file inside the window sees a\n// partial file: a consuming test run whose tokens are rebuilt mid-suite, or a\n// dev-server request landing on a rebuild, gets a truncated module and fails\n// to parse it. Writing a sibling temporary file and renaming it over the\n// destination closes the window — `rename` is atomic within a filesystem, so a\n// concurrent reader sees either the whole old file or the whole new one.\n\n// Temporary path for an atomic write of `destination`.\n//\n// It has to be a sibling of the destination, because `rename` is only atomic\n// within one filesystem and the system temp directory is often a different\n// mount. The final extension is dropped rather than kept, so the temporary\n// file cannot match a pattern written for the generated file's own extension —\n// `matchesWatchedFile` tests its globs unanchored, and a leftover\n// `vars.css.tmp` would match a `*.css` watch. The pid and counter make the\n// name unique, so two writes of the same destination — parallel platforms in\n// one build, or two builds overlapping — never share a temporary file.\nlet temporaryFileCounter = 0\n\nfunction temporaryPathFor(destination: string): string {\n const extension = path.extname(destination)\n\n return path.join(\n path.dirname(destination),\n `.${path.basename(destination, extension)}.${process.pid}.${temporaryFileCounter++}.tmp`,\n )\n}\n\nconst writeFileAtomic: typeof fs.promises.writeFile = async (\n file,\n data,\n options,\n) => {\n // A file handle or descriptor is already-open state that a rename cannot\n // stand in for, so only a path is written atomically.\n if (typeof file !== 'string') {\n return fs.promises.writeFile(file, data, options)\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n await fs.promises.writeFile(temporary, data, options)\n await fs.promises.rename(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\nconst writeFileSyncAtomic: typeof fs.writeFileSync = (file, data, options) => {\n if (typeof file !== 'string') {\n fs.writeFileSync(file, data, options)\n return\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n fs.writeFileSync(temporary, data, options)\n fs.renameSync(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\n// `node:fs` with both write entry points swapped for their atomic\n// equivalents, handed to Style Dictionary as the volume it builds through.\n// Everything else — reads, `mkdir`, `access`, the `promises` namespace — is\n// inherited from `node:fs` unchanged, so only the moment a file becomes\n// visible to readers changes. Custom actions receive this volume too, so\n// whatever they emit is written the same way.\n//\n// It is assigned onto the instance rather than passed as the `volume`\n// constructor option on purpose: that option marks the volume as a custom\n// filesystem shim, which switches Style Dictionary's path resolution off for\n// every read as well.\n// `Object.create` is declared as returning `any`, so pinning the result to\n// `typeof fs` is a claim no type guard can replace. The prototype link is the\n// whole point — see the note above — so rebuilding this with a spread, which\n// copies own properties and drops the chain, is not a substitute.\n/* oxlint-disable typescript/no-unsafe-type-assertion */\nconst atomicVolume = Object.create(fs, {\n promises: {\n value: Object.create(fs.promises, {\n writeFile: { value: writeFileAtomic },\n }) as typeof fs.promises,\n },\n writeFileSync: { value: writeFileSyncAtomic },\n}) as typeof fs\n/* oxlint-enable typescript/no-unsafe-type-assertion */\n\nexport const unpluginFactory: UnpluginFactory<\n undefined | UnpluginStyleDictionaryOptions,\n false\n> = (options = {}) => {\n const { silent = false } = options\n let root = process.cwd()\n\n // Helper to log if not silent\n const log = (\n message: string,\n type: 'error' | 'info' | 'success' = 'info',\n ) => {\n if (silent) return\n const prefix = '[unplugin-style-dictionary]'\n if (type === 'error') {\n console.error(`\\x1b[31m${prefix} ${message}\\x1b[0m`)\n } else if (type === 'success') {\n console.log(`\\x1b[32m${prefix} ${message}\\x1b[0m`)\n } else {\n console.log(`\\x1b[36m${prefix} ${message}\\x1b[0m`)\n }\n }\n\n // Resolve config file paths / objects\n const resolveConfigs = async (): Promise<\n Array<{ config: Config | string; dir: string; file?: string }>\n > => {\n let rawConfig = options.config\n\n // If config is not defined, look for default configuration files\n if (!rawConfig) {\n const defaults = [\n 'sd.config.json',\n 'config.json',\n 'sd.config.js',\n 'sd.config.mjs',\n ]\n for (const file of defaults) {\n const fullPath = path.resolve(root, file)\n if (fs.existsSync(fullPath)) {\n rawConfig = file\n break\n }\n }\n }\n\n if (!rawConfig) {\n log(\n 'No configuration specified and no default config file found. Style Dictionary will not compile.',\n 'error',\n )\n return []\n }\n\n // Evaluate function if provided\n if (typeof rawConfig === 'function') {\n rawConfig = await rawConfig()\n }\n\n const configs = Array.isArray(rawConfig) ? rawConfig : [rawConfig]\n\n return configs.map((conf) => {\n if (typeof conf === 'string') {\n const fullPath = path.resolve(root, conf)\n return {\n config: fullPath,\n dir: path.dirname(fullPath),\n file: fullPath,\n }\n } else {\n return {\n config: conf,\n dir: root,\n }\n }\n })\n }\n\n // Parse token files to watch\n const getWatchFiles = async (\n resolvedConfigs: Array<{\n config: Config | string\n dir: string\n file?: string\n }>,\n ): Promise<string[]> => {\n const filesToWatch = new Set<string>()\n\n for (const item of resolvedConfigs) {\n if (item.file) {\n filesToWatch.add(item.file.replace(/\\\\/g, '/'))\n }\n\n let configObj: Config | null = null\n\n if (typeof item.config === 'string') {\n try {\n let loaded: unknown\n\n if (item.config.endsWith('.json')) {\n loaded = JSON.parse(fs.readFileSync(item.config, 'utf-8'))\n } else {\n const fileUrl = pathToFileURL(item.config).href\n // Sequential on purpose: a config module runs arbitrary code at\n // import time — `registerFormat` and friends — and Style\n // Dictionary's registries are global, so importing several at\n // once would interleave those registrations.\n loaded = unwrapDefault(await import(`${fileUrl}?t=${Date.now()}`))\n }\n\n if (isConfig(loaded)) {\n configObj = loaded\n } else {\n log(\n `Config file did not resolve to a configuration object: ${item.config}`,\n 'error',\n )\n }\n } catch (err) {\n log(\n `Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}`,\n 'error',\n )\n }\n } else {\n configObj = item.config\n }\n\n if (configObj) {\n const addPattern = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(item.dir, pattern)\n const normalized = absolutePattern.replace(/\\\\/g, '/')\n filesToWatch.add(normalized)\n }\n }\n\n if (configObj.source) {\n if (Array.isArray(configObj.source)) {\n configObj.source.forEach(addPattern)\n } else {\n addPattern(configObj.source)\n }\n }\n\n if (configObj.include) {\n if (Array.isArray(configObj.include)) {\n configObj.include.forEach(addPattern)\n } else {\n addPattern(configObj.include)\n }\n }\n }\n }\n\n // Add manually configured watch files\n if (options.watch) {\n const extraWatches = Array.isArray(options.watch)\n ? options.watch\n : [options.watch]\n for (const pattern of extraWatches) {\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(root, pattern)\n filesToWatch.add(absolutePattern.replace(/\\\\/g, '/'))\n }\n }\n\n return Array.from(filesToWatch)\n }\n\n // Compile design tokens\n const runBuilds = async (\n resolvedConfigs: Array<{ config: Config | string; dir: string }>,\n context?: string,\n ) => {\n const startTime = Date.now()\n try {\n if (!context) {\n log('Compiling design tokens...', 'info')\n }\n\n const generatedFiles = new Set<string>()\n\n // Configurations are built one after another rather than with\n // `Promise.all`, and that is load-bearing. Two configurations may name\n // the same destination file, and each instance gets the atomic volume\n // swapped onto it below — overlapping builds would interleave those\n // writes and hand a reader a file assembled from both.\n for (const item of resolvedConfigs) {\n const sd = new StyleDictionary(item.config)\n await sd.hasInitialized\n const silentSD = await sd.extend({\n log: {\n verbosity: 'silent',\n },\n })\n // Swap in the atomic volume only now that the instance has finished\n // reading its configs and token sources, so every write below lands\n // through `rename` while the read path stays exactly as it was.\n silentSD.volume = atomicVolume\n await silentSD.buildAllPlatforms()\n\n if (!context && !silent) {\n for (const platform of Object.values(silentSD.platforms)) {\n const buildPath = platform.buildPath ?? ''\n for (const file of platform.files ?? []) {\n if (file.destination) {\n const absoluteBuildPath = path.isAbsolute(buildPath)\n ? buildPath\n : path.resolve(root, buildPath)\n const absoluteDestination = path.isAbsolute(file.destination)\n ? file.destination\n : path.resolve(absoluteBuildPath, file.destination)\n generatedFiles.add(absoluteDestination)\n }\n }\n }\n }\n }\n\n const duration = Date.now() - startTime\n\n if (context) {\n log(\n `Rebuilt design tokens due to change in ${context} (${duration}ms)`,\n 'success',\n )\n } else {\n if (!silent && generatedFiles.size > 0) {\n const fileInfos: Array<{\n coloredPath: string\n gzipSizeStr: string\n relativeDisplayPath: string\n sizeStr: string\n }> = []\n\n for (const filePath of generatedFiles) {\n if (fs.existsSync(filePath)) {\n const displayPath = path\n .relative(root, filePath)\n .replace(/\\\\/g, '/')\n const dir = path.dirname(displayPath)\n const base = path.basename(displayPath)\n const coloredPath =\n dir === '.'\n ? `\\x1b[32m${base}\\x1b[0m`\n : `\\x1b[90m${dir}/\\x1b[0m\\x1b[32m${base}\\x1b[0m`\n\n try {\n const stats = fs.statSync(filePath)\n const bytes = stats.size\n const sizeStr = `${(bytes / 1024).toFixed(2)} kB`\n\n const content = fs.readFileSync(filePath)\n const gzipBytes = zlib.gzipSync(content).length\n const gzipSizeStr = `${(gzipBytes / 1024).toFixed(2)} kB`\n\n fileInfos.push({\n coloredPath,\n gzipSizeStr,\n relativeDisplayPath: displayPath,\n sizeStr,\n })\n } catch {\n // Ignore errors reading individual files\n }\n }\n }\n\n if (fileInfos.length > 0) {\n const longestPathLength = Math.max(\n ...fileInfos.map((f) => f.relativeDisplayPath.length),\n 0,\n )\n const longestSizeLength = Math.max(\n ...fileInfos.map((f) => f.sizeStr.length),\n 0,\n )\n\n for (const info of fileInfos) {\n const pathPadding = ' '.repeat(\n Math.max(\n 2,\n longestPathLength - info.relativeDisplayPath.length + 2,\n ),\n )\n const sizePadded = info.sizeStr.padStart(longestSizeLength)\n console.log(\n `${info.coloredPath}${pathPadding}\\x1b[90m${sizePadded} │ gzip: ${info.gzipSizeStr}\\x1b[0m`,\n )\n }\n }\n }\n\n log(`Compiled successfully! (${duration}ms)`, 'success')\n }\n } catch (err) {\n const duration = Date.now() - startTime\n log(\n `Compilation failed after ${duration}ms: ${errorMessage(err)}`,\n 'error',\n )\n }\n }\n\n return {\n async buildStart() {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Register token/config files with the host bundler's watch mode.\n // Works out of the box wherever the host runs a persistent watcher\n // (e.g. `rollup --watch`); Vite's dev server is additionally handled\n // below via the `vite.configureServer` escape hatch, since token\n // sources are plain JSON/JS files outside the module graph and\n // `watchChange` is not reliably invoked by Vite while serving.\n const watchFiles = await getWatchFiles(resolved)\n for (const file of watchFiles) {\n this.addWatchFile(file)\n }\n\n await runBuilds(resolved)\n },\n\n name: 'unplugin-style-dictionary',\n\n vite: {\n configResolved(config) {\n root = config.root || process.cwd()\n },\n\n async configureServer(server: ViteDevServer) {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n const filesToWatch = await getWatchFiles(resolved)\n\n // Watch configuration files and token files\n server.watcher.add(filesToWatch)\n\n // chokidar types its listener as returning void and does not await\n // what it is handed, so handing it an async function left every\n // rejection floating — `runBuilds` catches its own, but\n // `resolveConfigs` and `getWatchFiles` do not. Launching the work\n // explicitly and catching here is what keeps a bad config on disk\n // from surfacing as an unhandled rejection that kills the dev server.\n server.watcher.on('all', (_event, file) => {\n if (!matchesWatchedFile(file, filesToWatch)) return\n\n void (async () => {\n try {\n // Re-resolve configs to handle added/removed configs or changes\n // to config itself\n const currentResolved = await resolveConfigs()\n await runBuilds(currentResolved, path.basename(file))\n\n // Dynamically update the watch list in case the configurations\n // changed\n const newWatches = await getWatchFiles(currentResolved)\n server.watcher.add(newWatches)\n } catch (err) {\n log(`Rebuild failed: ${errorMessage(err)}`, 'error')\n }\n })()\n })\n },\n },\n\n // Rollup types `watchChange` as returning void, yet awaits it as a\n // sequential hook — and the work here is inherently asynchronous. The\n // signature is the thing that is wrong, so the rule is silenced rather\n // than the hook made to lie about finishing.\n // oxlint-disable-next-line typescript/no-misused-promises\n async watchChange(id) {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n const watchFiles = await getWatchFiles(resolved)\n // Without this check, watchChange fires for *any* changed file in the\n // host bundler's module graph — including our own generated output,\n // since consuming code imports it. Every regenerate is itself a\n // \"change\", so skipping non-matching files here is what keeps this\n // from rebuilding forever.\n if (!matchesWatchedFile(id, watchFiles)) return\n\n await runBuilds(resolved, path.basename(id))\n\n for (const file of watchFiles) {\n this.addWatchFile(file)\n }\n },\n }\n}\n\nexport const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory)\n\nexport default unplugin\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'style-dictionary'\nimport type { UnpluginFactory } from 'unplugin'\nimport type { ViteDevServer } from 'vite'\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport zlib from 'node:zlib'\nimport picomatch from 'picomatch'\nimport StyleDictionary from 'style-dictionary'\nimport { glob } from 'tinyglobby'\nimport { createUnplugin } from 'unplugin'\n\nimport type { UnpluginStyleDictionaryOptions } from './types.js'\n\nexport type * from './types.js'\n\n// Whether `file` matches one of the resolved config/token watch patterns.\n// Shared by the Vite-specific `configureServer` watcher and the universal\n// `watchChange` hook — both need it, and both must skip files that don't\n// match: without this filter, `watchChange` reacts to *any* changed\n// module-graph file, including this plugin's own generated output (since\n// consuming code imports it). Every regenerate is itself a \"change\", which\n// without filtering re-triggers a rebuild forever.\n//\n// Matching a pattern is only half of that, and this function is only the half\n// it can answer. A `buildPath` inside a `source` directory is a supported\n// layout, and under any correct matcher its output matches the very glob that\n// produced it — so the caller also subtracts what the last build wrote. See\n// `generatedDestinations` and `isWatchedSource` in the factory below.\n//\n// The patterns are Style Dictionary's own `source` and `include` globs, so the\n// filter has to admit exactly what the build reads — which is why the matching\n// is a real globber's rather than hand-rolled. The version this replaces was\n// wrong in both directions at once: it stripped `/**` out of a pattern and\n// prefix-matched the remainder, so `tokens/**/*.json` matched nothing sitting\n// directly in `tokens/` and `tokens/**` matched a `tokens-backup/` sibling,\n// while its regex branch mapped every `*` to `.*` — crossing `/` — and tested\n// it unanchored, so generated output under a watched directory matched its own\n// source glob and rebuilt forever.\n//\n// picomatch rather than `path.matchesGlob`, which would need no dependency at\n// all: that function is documented experimental, and on Node 20 — the floor\n// `engines` declares — it prints `ExperimentalWarning: glob is an experimental\n// feature and might change at any time` into the consumer's build output. The\n// dependency is free in practice, since `unplugin` depends on the same\n// picomatch and is already installed wherever this plugin is. Its `dot: false`\n// default is deliberate: it is what glob, and so Style Dictionary, reads\n// sources with, so a dotfile is invisible to the filter and to the build alike.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some((pattern) => {\n const normalizedPattern = pattern.replace(/\\\\/g, '/')\n\n // A config file reaches this function as its own literal path, which is\n // both the common case and the one shape that is not a glob at all.\n return (\n normalizedPattern === normalizedFile ||\n picomatch.isMatch(normalizedFile, normalizedPattern)\n )\n })\n}\n\n// Best-effort cleanup of a temporary file whose write or rename failed. The\n// original failure is what the caller reports, so nothing here may throw.\nfunction discardTemporaryFile(temporary: string): void {\n try {\n fs.rmSync(temporary, { force: true })\n } catch {\n // Ignore: a leftover temporary file is not worth masking the real error.\n }\n}\n\n// `catch` binds `unknown`, and a thrown non-Error — a string, a rejected\n// value out of a config module — carries no `.message`. The `as Error` casts\n// this replaces claimed otherwise and printed `undefined` for exactly those\n// cases, which is the least useful thing a failure log can say.\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n// A config file is an untyped boundary: `JSON.parse` and a dynamic `import`\n// both hand back `any`, and an `any` assigned to `configObj` spreads through\n// every read of it downstream. These two narrow that boundary once, here.\n// They are type predicates rather than assertions on purpose — a predicate is\n// a check the compiler verifies, where a cast is only a claim.\nfunction isConfig(value: unknown): value is Config {\n return typeof value === 'object' && value !== null\n}\n\n// A config module may expose its config as a `default` export or as the\n// namespace itself. `'default' in value` is what lets the compiler reach\n// `.default` without a cast.\nfunction unwrapDefault(value: unknown): unknown {\n return typeof value === 'object' && value !== null && 'default' in value\n ? (value.default ?? value)\n : value\n}\n\n// Style Dictionary writes every generated file with a plain `writeFile` on the\n// volume it was handed, which truncates the destination and then streams the\n// new contents into it. Anything reading that file inside the window sees a\n// partial file: a consuming test run whose tokens are rebuilt mid-suite, or a\n// dev-server request landing on a rebuild, gets a truncated module and fails\n// to parse it. Writing a sibling temporary file and renaming it over the\n// destination closes the window — `rename` is atomic within a filesystem, so a\n// concurrent reader sees either the whole old file or the whole new one.\n\n// Temporary path for an atomic write of `destination`.\n//\n// It has to be a sibling of the destination, because `rename` is only atomic\n// within one filesystem and the system temp directory is often a different\n// mount. The final extension is dropped rather than kept, so the temporary\n// file cannot match a pattern written for the generated file's own extension.\n// That was load-bearing while `matchesWatchedFile` tested its globs\n// unanchored, where a leftover `vars.css.tmp` matched a `*.css` watch; it is\n// belt-and-braces now that the matcher anchors and, like the globber Style\n// Dictionary reads sources with, does not match the leading dot this name\n// already starts with. Both stay, because a temporary file only outlives its\n// rename when a write failed, and hiding one costs a string. The pid and\n// counter make the name unique, so two writes of the same destination —\n// parallel platforms in one build, or two builds overlapping — never share a\n// temporary file.\nlet temporaryFileCounter = 0\n\n// Whether the freshly rendered `temporary` holds exactly what `destination`\n// already holds. A rebuild whose inputs did not change renders byte-identical\n// output, and renaming that over the destination is a filesystem event the\n// host bundler reacts to — which is the whole of the rebuild loop, since\n// consuming code imports the generated file and every regenerate is therefore\n// a module-graph change. Comparing the two files rather than the `data`\n// argument keeps this indifferent to whether the caller passed a string, a\n// buffer or a stream, and to the encoding it passed with it.\n//\n// A destination that cannot be read is not identical, which covers the\n// ordinary case of it not existing yet.\nasync function rendersWhatIsAlreadyThere(\n temporary: string,\n destination: string,\n): Promise<boolean> {\n try {\n const [existing, rendered] = await Promise.all([\n fs.promises.readFile(destination),\n fs.promises.readFile(temporary),\n ])\n\n return existing.equals(rendered)\n } catch {\n return false\n }\n}\n\nfunction rendersWhatIsAlreadyThereSync(\n temporary: string,\n destination: string,\n): boolean {\n try {\n return fs.readFileSync(destination).equals(fs.readFileSync(temporary))\n } catch {\n return false\n }\n}\n\nfunction temporaryPathFor(destination: string): string {\n const extension = path.extname(destination)\n\n return path.join(\n path.dirname(destination),\n `.${path.basename(destination, extension)}.${process.pid}.${temporaryFileCounter++}.tmp`,\n )\n}\n\nconst writeFileAtomic: typeof fs.promises.writeFile = async (\n file,\n data,\n options,\n) => {\n // A file handle or descriptor is already-open state that a rename cannot\n // stand in for, so only a path is written atomically.\n if (typeof file !== 'string') {\n return fs.promises.writeFile(file, data, options)\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n await fs.promises.writeFile(temporary, data, options)\n\n // The check sits in front of the rename rather than in place of it: the\n // temporary file is still written, so a destination that does need\n // replacing is still replaced in one atomic step and a concurrent reader\n // still never sees a partial file.\n if (await rendersWhatIsAlreadyThere(temporary, file)) {\n discardTemporaryFile(temporary)\n return\n }\n\n await fs.promises.rename(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\nconst writeFileSyncAtomic: typeof fs.writeFileSync = (file, data, options) => {\n if (typeof file !== 'string') {\n fs.writeFileSync(file, data, options)\n return\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n fs.writeFileSync(temporary, data, options)\n\n if (rendersWhatIsAlreadyThereSync(temporary, file)) {\n discardTemporaryFile(temporary)\n return\n }\n\n fs.renameSync(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\n// `node:fs` with both write entry points swapped for their atomic\n// equivalents, handed to Style Dictionary as the volume it builds through.\n// Everything else — reads, `mkdir`, `access`, the `promises` namespace — is\n// inherited from `node:fs` unchanged, so only the moment a file becomes\n// visible to readers changes. Custom actions receive this volume too, so\n// whatever they emit is written the same way.\n//\n// It is assigned onto the instance rather than passed as the `volume`\n// constructor option on purpose: that option marks the volume as a custom\n// filesystem shim, which switches Style Dictionary's path resolution off for\n// every read as well.\n// `Object.create` is declared as returning `any`, so pinning the result to\n// `typeof fs` is a claim no type guard can replace. The prototype link is the\n// whole point — see the note above — so rebuilding this with a spread, which\n// copies own properties and drops the chain, is not a substitute.\n/* oxlint-disable typescript/no-unsafe-type-assertion */\nconst atomicVolume = Object.create(fs, {\n promises: {\n value: Object.create(fs.promises, {\n writeFile: { value: writeFileAtomic },\n }) as typeof fs.promises,\n },\n writeFileSync: { value: writeFileSyncAtomic },\n}) as typeof fs\n/* oxlint-enable typescript/no-unsafe-type-assertion */\n\n// A pattern is a glob when any of these appear in it. Deliberately the set\n// picomatch and tinyglobby act on, since those two are what match and expand\n// here — a path containing one of these characters literally is not\n// distinguishable from a pattern, and would not be matchable either.\nconst GLOB_CHARACTERS = /[!*?[\\]{}]/\n\n// The leading run of a pattern that contains no glob character —\n// `/p/tokens` for `/p/tokens/**/*.json`. Registering it alongside the files\n// that match today is what makes a token file created tomorrow visible:\n// watching only the current matches can never see a path that did not exist\n// when the watcher was built.\nfunction staticParentOf(pattern: string): string {\n const segments = pattern.split('/')\n const firstGlob = segments.findIndex((segment) =>\n GLOB_CHARACTERS.test(segment),\n )\n\n return firstGlob === -1\n ? path.posix.dirname(pattern)\n : segments.slice(0, firstGlob).join('/')\n}\n\nexport const unpluginFactory: UnpluginFactory<\n undefined | UnpluginStyleDictionaryOptions,\n false\n> = (options = {}) => {\n const { silent = false } = options\n let root = process.cwd()\n\n // Every absolute destination the last completed build wrote, spelled with\n // forward slashes so it compares against a normalised watcher path. This is\n // the half of the rebuild-loop guard that pattern matching cannot supply:\n // output written under a watched directory matches the source glob that\n // produced it, so without subtracting this set a supported layout rebuilds\n // on its own writes for as long as the dev server runs.\n const generatedDestinations = new Set<string>()\n\n // Whether `watchChange` has fired since the last `buildStart`, and whether\n // anything has been compiled yet. Rollup, rolldown and webpack all run\n // `watchChange` for every changed file and only then re-enter `buildStart`\n // — unplugin's webpack adapter awaits both in one `make` tap — so a flag\n // raised in the first is still standing in the second, and is what tells it\n // this is a watch rebuild rather than the first build of the process.\n let watchRebuild = false\n let hasCompiled = false\n\n // What a watcher is handed, and what a changed path is tested against, are\n // not the same list, and conflating them is why a glob source was watched by\n // nothing at all. Every watcher in play takes filenames rather than\n // patterns: Vite's chokidar and rollup's `FileWatcher` are both constructed\n // with `disableGlobbing: true`, Vite's `addWatchFile` drops anything that\n // fails `fs.existsSync`, and webpack never globs `fileDependencies`. So the\n // patterns stay for matching and the paths are expanded for registering.\n const expandPatterns = async (patterns: string[]): Promise<string[]> => {\n const paths = new Set<string>()\n const globs: string[] = []\n\n for (const pattern of patterns) {\n if (GLOB_CHARACTERS.test(pattern)) {\n globs.push(pattern)\n\n // Watching the directory as well as its current contents. chokidar\n // reports a creation inside a watched directory, which is the only\n // way a token file added later is ever noticed.\n const parent = staticParentOf(pattern)\n if (parent && fs.existsSync(parent)) paths.add(parent)\n } else {\n paths.add(pattern)\n }\n }\n\n if (globs.length > 0) {\n try {\n // tinyglobby matches with picomatch, which is what\n // `matchesWatchedFile` tests with, so what is registered here and what\n // is accepted there cannot disagree.\n for (const match of await glob(globs, { absolute: true })) {\n paths.add(match.replace(/\\\\/g, '/'))\n }\n } catch (err) {\n log(`Failed to expand watch patterns: ${errorMessage(err)}`, 'error')\n }\n }\n\n return Array.from(paths)\n }\n\n // Whether a changed file is a token or config source rather than something\n // this plugin just wrote. Both watch entry points ask through here, so\n // neither can react to its own output.\n const isWatchedSource = (file: string, patterns: string[]): boolean =>\n !generatedDestinations.has(file.replace(/\\\\/g, '/')) &&\n matchesWatchedFile(file, patterns)\n\n // Helper to log if not silent\n const log = (\n message: string,\n type: 'error' | 'info' | 'success' = 'info',\n ) => {\n if (silent) return\n const prefix = '[unplugin-style-dictionary]'\n if (type === 'error') {\n console.error(`\\x1b[31m${prefix} ${message}\\x1b[0m`)\n } else if (type === 'success') {\n console.log(`\\x1b[32m${prefix} ${message}\\x1b[0m`)\n } else {\n console.log(`\\x1b[36m${prefix} ${message}\\x1b[0m`)\n }\n }\n\n // Resolve config file paths / objects\n const resolveConfigs = async (): Promise<\n Array<{ config: Config | string; dir: string; file?: string }>\n > => {\n let rawConfig = options.config\n\n // If config is not defined, look for default configuration files\n if (!rawConfig) {\n const defaults = [\n 'sd.config.json',\n 'config.json',\n 'sd.config.js',\n 'sd.config.mjs',\n ]\n for (const file of defaults) {\n const fullPath = path.resolve(root, file)\n if (fs.existsSync(fullPath)) {\n rawConfig = file\n break\n }\n }\n }\n\n if (!rawConfig) {\n log(\n 'No configuration specified and no default config file found. Style Dictionary will not compile.',\n 'error',\n )\n return []\n }\n\n // Evaluate function if provided\n if (typeof rawConfig === 'function') {\n rawConfig = await rawConfig()\n }\n\n const configs = Array.isArray(rawConfig) ? rawConfig : [rawConfig]\n\n return configs.map((conf) => {\n if (typeof conf === 'string') {\n const fullPath = path.resolve(root, conf)\n return {\n config: fullPath,\n dir: path.dirname(fullPath),\n file: fullPath,\n }\n } else {\n return {\n config: conf,\n dir: root,\n }\n }\n })\n }\n\n // Parse token files to watch\n const getWatchTargets = async (\n resolvedConfigs: Array<{\n config: Config | string\n dir: string\n file?: string\n }>,\n ): Promise<{ paths: string[]; patterns: string[] }> => {\n const filesToWatch = new Set<string>()\n\n for (const item of resolvedConfigs) {\n if (item.file) {\n filesToWatch.add(item.file.replace(/\\\\/g, '/'))\n }\n\n let configObj: Config | null = null\n\n if (typeof item.config === 'string') {\n try {\n let loaded: unknown\n\n if (item.config.endsWith('.json')) {\n loaded = JSON.parse(fs.readFileSync(item.config, 'utf-8'))\n } else {\n const fileUrl = pathToFileURL(item.config).href\n // Sequential on purpose: a config module runs arbitrary code at\n // import time — `registerFormat` and friends — and Style\n // Dictionary's registries are global, so importing several at\n // once would interleave those registrations.\n loaded = unwrapDefault(await import(`${fileUrl}?t=${Date.now()}`))\n }\n\n if (isConfig(loaded)) {\n configObj = loaded\n } else {\n log(\n `Config file did not resolve to a configuration object: ${item.config}`,\n 'error',\n )\n }\n } catch (err) {\n log(\n `Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}`,\n 'error',\n )\n }\n } else {\n configObj = item.config\n }\n\n if (configObj) {\n const addPattern = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(item.dir, pattern)\n const normalized = absolutePattern.replace(/\\\\/g, '/')\n filesToWatch.add(normalized)\n }\n }\n\n if (configObj.source) {\n if (Array.isArray(configObj.source)) {\n configObj.source.forEach(addPattern)\n } else {\n addPattern(configObj.source)\n }\n }\n\n if (configObj.include) {\n if (Array.isArray(configObj.include)) {\n configObj.include.forEach(addPattern)\n } else {\n addPattern(configObj.include)\n }\n }\n }\n }\n\n // Add manually configured watch files\n if (options.watch) {\n const extraWatches = Array.isArray(options.watch)\n ? options.watch\n : [options.watch]\n for (const pattern of extraWatches) {\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(root, pattern)\n filesToWatch.add(absolutePattern.replace(/\\\\/g, '/'))\n }\n }\n\n const patterns = Array.from(filesToWatch)\n\n return { paths: await expandPatterns(patterns), patterns }\n }\n\n // Compile design tokens\n const runBuilds = async (\n resolvedConfigs: Array<{ config: Config | string; dir: string }>,\n context?: string,\n ) => {\n const startTime = Date.now()\n try {\n if (!context) {\n log('Compiling design tokens...', 'info')\n }\n\n const generatedFiles = new Set<string>()\n\n // Configurations are built one after another rather than with\n // `Promise.all`, and that is load-bearing. Two configurations may name\n // the same destination file, and each instance gets the atomic volume\n // swapped onto it below — overlapping builds would interleave those\n // writes and hand a reader a file assembled from both.\n for (const item of resolvedConfigs) {\n const sd = new StyleDictionary(item.config)\n await sd.hasInitialized\n const silentSD = await sd.extend({\n log: {\n verbosity: 'silent',\n },\n })\n // Swap in the atomic volume only now that the instance has finished\n // reading its configs and token sources, so every write below lands\n // through `rename` while the read path stays exactly as it was.\n silentSD.volume = atomicVolume\n await silentSD.buildAllPlatforms()\n\n // Collected on every build rather than only on the ones whose size\n // report prints it below. The set is also what keeps a rebuild from\n // being triggered by the write it just made, and a rebuild passes a\n // `context` — so gating the collection on `!context` left it empty on\n // exactly the builds a watcher is live for.\n for (const platform of Object.values(silentSD.platforms)) {\n const buildPath = platform.buildPath ?? ''\n for (const file of platform.files ?? []) {\n if (file.destination) {\n const absoluteBuildPath = path.isAbsolute(buildPath)\n ? buildPath\n : path.resolve(root, buildPath)\n const absoluteDestination = path.isAbsolute(file.destination)\n ? file.destination\n : path.resolve(absoluteBuildPath, file.destination)\n generatedFiles.add(absoluteDestination)\n }\n }\n }\n }\n\n // Replaced wholesale rather than added to, so a destination dropped from\n // a configuration stops being treated as ours and becomes watchable\n // again. A build that throws never reaches this and leaves the previous\n // set standing, which is the safe direction: the files it wrote before\n // failing are still ours.\n generatedDestinations.clear()\n for (const destination of generatedFiles) {\n generatedDestinations.add(destination.replace(/\\\\/g, '/'))\n }\n\n const duration = Date.now() - startTime\n\n if (context) {\n log(\n `Rebuilt design tokens due to change in ${context} (${duration}ms)`,\n 'success',\n )\n } else {\n if (!silent && generatedFiles.size > 0) {\n const fileInfos: Array<{\n coloredPath: string\n gzipSizeStr: string\n relativeDisplayPath: string\n sizeStr: string\n }> = []\n\n for (const filePath of generatedFiles) {\n if (fs.existsSync(filePath)) {\n const displayPath = path\n .relative(root, filePath)\n .replace(/\\\\/g, '/')\n const dir = path.dirname(displayPath)\n const base = path.basename(displayPath)\n const coloredPath =\n dir === '.'\n ? `\\x1b[32m${base}\\x1b[0m`\n : `\\x1b[90m${dir}/\\x1b[0m\\x1b[32m${base}\\x1b[0m`\n\n try {\n const stats = fs.statSync(filePath)\n const bytes = stats.size\n const sizeStr = `${(bytes / 1024).toFixed(2)} kB`\n\n const content = fs.readFileSync(filePath)\n const gzipBytes = zlib.gzipSync(content).length\n const gzipSizeStr = `${(gzipBytes / 1024).toFixed(2)} kB`\n\n fileInfos.push({\n coloredPath,\n gzipSizeStr,\n relativeDisplayPath: displayPath,\n sizeStr,\n })\n } catch {\n // Ignore errors reading individual files\n }\n }\n }\n\n if (fileInfos.length > 0) {\n const longestPathLength = Math.max(\n ...fileInfos.map((f) => f.relativeDisplayPath.length),\n 0,\n )\n const longestSizeLength = Math.max(\n ...fileInfos.map((f) => f.sizeStr.length),\n 0,\n )\n\n for (const info of fileInfos) {\n const pathPadding = ' '.repeat(\n Math.max(\n 2,\n longestPathLength - info.relativeDisplayPath.length + 2,\n ),\n )\n const sizePadded = info.sizeStr.padStart(longestSizeLength)\n console.log(\n `${info.coloredPath}${pathPadding}\\x1b[90m${sizePadded} │ gzip: ${info.gzipSizeStr}\\x1b[0m`,\n )\n }\n }\n }\n\n log(`Compiled successfully! (${duration}ms)`, 'success')\n }\n } catch (err) {\n const duration = Date.now() - startTime\n log(\n `Compilation failed after ${duration}ms: ${errorMessage(err)}`,\n 'error',\n )\n }\n }\n\n return {\n async buildStart() {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Register token/config files with the host bundler's watch mode.\n // Works out of the box wherever the host runs a persistent watcher\n // (e.g. `rollup --watch`); Vite's dev server is additionally handled\n // below via the `vite.configureServer` escape hatch, since token\n // sources are plain JSON/JS files outside the module graph and\n // `watchChange` is not reliably invoked by Vite while serving.\n const { paths } = await getWatchTargets(resolved)\n for (const file of paths) {\n this.addWatchFile(file)\n }\n\n // Every watch rebuild re-enters this hook, and compiling here as well as\n // in `watchChange` is what closed the loop: consuming code imports the\n // generated file, so writing it is itself a module-graph change, which\n // re-enters `buildStart`, which writes it again. `watchChange` has\n // already run for every file in this cycle and rebuilt if any of them\n // was a source, so the only thing left for a re-entry to do is the\n // re-registration above.\n //\n // `hasCompiled` is the floor under that: a host that fires\n // `watchChange` without ever re-entering here would otherwise leave the\n // flag standing, and no first compile of a process may ever be skipped —\n // the tokens have to exist before the build that consumes them.\n if (watchRebuild && hasCompiled) {\n watchRebuild = false\n return\n }\n\n await runBuilds(resolved)\n hasCompiled = true\n },\n\n name: 'unplugin-style-dictionary',\n\n vite: {\n configResolved(config) {\n root = config.root || process.cwd()\n },\n\n async configureServer(server: ViteDevServer) {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Reassigned after every rebuild below, so a configuration that gains\n // a source is matched against its new patterns rather than the ones\n // read at start-up.\n let targets = await getWatchTargets(resolved)\n\n // Watch configuration files and token files\n server.watcher.add(targets.paths)\n\n // chokidar types its listener as returning void and does not await\n // what it is handed, so handing it an async function left every\n // rejection floating — `runBuilds` catches its own, but\n // `resolveConfigs` and `getWatchTargets` do not. Launching the work\n // explicitly and catching here is what keeps a bad config on disk\n // from surfacing as an unhandled rejection that kills the dev server.\n server.watcher.on('all', (_event, file) => {\n if (!isWatchedSource(file, targets.patterns)) return\n\n void (async () => {\n try {\n // Re-resolve configs to handle added/removed configs or changes\n // to config itself\n const currentResolved = await resolveConfigs()\n await runBuilds(currentResolved, path.basename(file))\n\n // Dynamically update the watch list in case the configurations\n // changed\n targets = await getWatchTargets(currentResolved)\n server.watcher.add(targets.paths)\n } catch (err) {\n log(`Rebuild failed: ${errorMessage(err)}`, 'error')\n }\n })()\n })\n },\n },\n\n // Rollup types `watchChange` as returning void, yet awaits it as a\n // sequential hook — and the work here is inherently asynchronous. The\n // signature is the thing that is wrong, so the rule is silenced rather\n // than the hook made to lie about finishing.\n // oxlint-disable-next-line typescript/no-misused-promises\n async watchChange(id) {\n // Raised before any decision about `id`, because whatever this change\n // was, the host is now on its way back into `buildStart`.\n watchRebuild = true\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n const { patterns } = await getWatchTargets(resolved)\n // Without this check, watchChange fires for *any* changed file in the\n // host bundler's module graph — including our own generated output,\n // since consuming code imports it. Every regenerate is itself a\n // \"change\", so skipping what is not a source here is what keeps this\n // from rebuilding forever — both the files that match no pattern and\n // the ones that match only because this plugin wrote them.\n if (!isWatchedSource(id, patterns)) return\n\n await runBuilds(resolved, path.basename(id))\n hasCompiled = true\n\n // Expanded again after the build rather than reusing the list from\n // before it, so a token file the build itself produced is registered.\n for (const file of await expandPatterns(patterns)) {\n this.addWatchFile(file)\n }\n },\n }\n}\n\nexport const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory)\n\nexport default unplugin\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanso-labs/unplugin-style-dictionary",
3
- "version": "0.4.28",
3
+ "version": "0.4.29",
4
4
  "description": "Compile Style Dictionary design tokens ahead of your bundler (Vite, Rolldown, Rollup, or Webpack) from a single unplugin-based plugin, with automatic watching and rebuilding under Vite",
5
5
  "keywords": [
6
6
  "unplugin",
@@ -66,6 +66,8 @@
66
66
  "test:coverage": "vitest run --coverage"
67
67
  },
68
68
  "dependencies": {
69
+ "picomatch": "4.0.7",
70
+ "tinyglobby": "0.2.17",
69
71
  "unplugin": "3.3.0"
70
72
  },
71
73
  "devDependencies": {
@@ -73,10 +75,11 @@
73
75
  "@commitlint/config-conventional": "21.2.2",
74
76
  "@eslint/js": "10.0.1",
75
77
  "@types/node": "26.5.1",
76
- "@vitest/coverage-v8": "5.0.0",
78
+ "@types/picomatch": "4.0.3",
79
+ "@vitest/coverage-v8": "5.0.1",
77
80
  "eslint": "10.10.0",
78
81
  "eslint-plugin-oxlint": "1.83.0",
79
- "eslint-plugin-perfectionist": "5.11.0",
82
+ "eslint-plugin-perfectionist": "5.11.1",
80
83
  "globals": "17.12.0",
81
84
  "husky": "9.1.7",
82
85
  "lint-staged": "17.5.1",
@@ -92,7 +95,7 @@
92
95
  "typescript": "6.0.3",
93
96
  "typescript-eslint": "8.70.0",
94
97
  "vite": "8.3.0",
95
- "vitest": "5.0.0",
98
+ "vitest": "5.0.1",
96
99
  "webpack": "5.111.0"
97
100
  },
98
101
  "peerDependencies": {