@kanso-labs/unplugin-style-dictionary 0.9.0 → 0.10.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,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Kanso
3
+ Copyright (c) 2026 Kanso Labs
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
package/README.md CHANGED
@@ -10,16 +10,19 @@ compile **Style Dictionary** design tokens ahead of your bundler, with automatic
10
10
  watching, rebuilding, and hot reloading (HMR) under Vite's dev server.
11
11
 
12
12
  Built on unplugin, the same core plugin targets **Vite**, **Rolldown**,
13
- **Rollup**, and **Webpack** from a single implementation — useful when a project
14
- has more than one build surface (e.g. Storybook/Vitest on Vite, and a package
15
- build on Rolldown/tsdown) that both need tokens compiled ahead of them.
13
+ **Rollup**, **Rspack**, and **Webpack** from a single implementation — useful
14
+ when a project has more than one build surface (e.g. Storybook/Vitest on Vite,
15
+ and a package build on Rolldown/tsdown) that both need tokens compiled ahead of
16
+ them.
16
17
 
17
18
  ## Features
18
19
 
19
- - **Multi-bundler**: One implementation, four entry points — Vite, Rolldown,
20
- Rollup, and Webpack.
21
- - **Asynchronous builds**: Native support for Style Dictionary v4/v5 async
22
- compilation API.
20
+ - **Multi-bundler**: One implementation, five entry points — Vite, Rolldown,
21
+ Rollup, Rspack, and Webpack.
22
+ - **Asynchronous builds**: Native support for Style Dictionary v5's async
23
+ compilation API. The `style-dictionary` range in
24
+ [`package.json`](package.json)'s `peerDependencies` is the contract — this
25
+ bullet follows it rather than standing beside it.
23
26
  - **Automatic watching**: Reads the `source` and `include` patterns from your
24
27
  Style Dictionary configurations and watches the files they match, including a
25
28
  token package resolved through `node_modules` in a workspace. What a change
@@ -48,9 +51,19 @@ build on Rolldown/tsdown) that both need tokens compiled ahead of them.
48
51
  npm install @kanso-labs/unplugin-style-dictionary style-dictionary --save-dev
49
52
  ```
50
53
 
51
- _Note: `style-dictionary` and your bundler (`vite`, `rolldown`, `rollup`, or
52
- `webpack`) are peer dependencies, so you can manage their versions
53
- independently._
54
+ _Note: `style-dictionary` and your bundler (`vite`, `rolldown`, `rollup`,
55
+ `@rspack/core`, or `webpack`) are peer dependencies, so you can manage their
56
+ versions independently._
57
+
58
+ **`style-dictionary` is the one peer that is not optional**, and its range is
59
+ `^5.0.0`. An older major is refused at the install rather than failing later:
60
+
61
+ ```
62
+ npm error code ERESOLVE
63
+ npm error Found: style-dictionary@4.4.0
64
+ npm error Could not resolve dependency:
65
+ npm error peer style-dictionary@"^5.0.0" from @kanso-labs/unplugin-style-dictionary@0.9.0
66
+ ```
54
67
 
55
68
  **This package needs Node 22.12 or newer.** The floor is Style Dictionary v5's,
56
69
  not this plugin's: every 5.x release declares `engines.node >= 22.0.0`, and it
@@ -99,11 +112,12 @@ generated token files exist before the rest of the build consumes them.
99
112
  Under a real `rolldown.watch()`, do not rely on a token edit triggering a
100
113
  rebuild — see [Watching, per target](#watching-per-target).
101
114
 
102
- ### Rollup / Webpack
115
+ ### Rollup / Webpack / Rspack
103
116
 
104
117
  ```typescript
105
118
  import StyleDictionary from '@kanso-labs/unplugin-style-dictionary/rollup'
106
119
  // or: import StyleDictionary from '@kanso-labs/unplugin-style-dictionary/webpack'
120
+ // or: import StyleDictionary from '@kanso-labs/unplugin-style-dictionary/rspack'
107
121
  ```
108
122
 
109
123
  A `webpack.config.js` is often CommonJS rather than ESM. This package ships ESM
@@ -120,6 +134,14 @@ module.exports = {
120
134
  }
121
135
  ```
122
136
 
137
+ **Rspack is webpack's plugin API, and this package treats it as one.** Swap the
138
+ import for `…/rspack` and an `rspack.config.js` reads the same, `.default` hop
139
+ included. Everything below that mentions webpack — where a relative `config` is
140
+ resolved from, where a failed compile is reported, what a `config` function is
141
+ told about the build — holds there too, and `tests/webpack-api.test.ts` runs the
142
+ same cases against both. It installs as `@rspack/core`, which is the peer this
143
+ package declares.
144
+
123
145
  _Note: that path needs Node 20.19+ or 22.12+, the versions that can `require` an
124
146
  ES module. Every Node release still in support clears it. Importing from ESM has
125
147
  no such floor._
@@ -290,6 +312,23 @@ export default defineConfig({
290
312
  })
291
313
  ```
292
314
 
315
+ ## Platforms
316
+
317
+ Linux, macOS and Windows. All three run the suite in CI, and Windows is a
318
+ `Test on Windows` job rather than a claim — it was added after two defects
319
+ reached `main` because nothing ran there.
320
+
321
+ Both are fixed. The atomic write used to fail under the condition it exists for:
322
+ with a reader holding the destination open, Windows refuses the rename, so the
323
+ compile failed rather than the read being protected. The rename now backs off on
324
+ `EPERM` and `EBUSY` and retries, bounded, so a rename that genuinely cannot
325
+ succeed still fails rather than hanging a dev server.
326
+
327
+ And a token package resolved through `node_modules` used to build once and never
328
+ rebuild. Vite's watcher cannot be made to deliver those events on Windows — no
329
+ spelling of the ignore-list negation reaches them, and `watcher.add()` does not
330
+ either — so the plugin watches those directories itself, on every platform.
331
+
293
332
  ## Watching, per target
294
333
 
295
334
  Every target compiles tokens before the build that consumes them. What a later
@@ -301,6 +340,7 @@ on what the host bundler does with the watch list the plugin registers.
301
340
  | **Vite** | yes | yes, under the dev server | yes |
302
341
  | **Rollup** | yes | yes, under `rollup --watch` | yes |
303
342
  | **Webpack** | yes | yes, under `webpack --watch` | yes |
343
+ | **Rspack** | yes | yes, under `rspack --watch` | yes |
304
344
  | **Rolldown** | yes | platform-dependent — see below | yes |
305
345
 
306
346
  Patterns and literal paths behave the same way wherever rebuilds happen at all.
@@ -434,8 +474,8 @@ and does not cache anything for later.
434
474
  Two bases, and which one applies depends on whose path it is.
435
475
 
436
476
  **The `config` option is the plugin's**, so a relative path is looked up under
437
- the host's root: Vite's `root`, webpack's `context`, and the working directory
438
- for rollup and rolldown, which report none. `root` overrides that.
477
+ the host's root: Vite's `root`, webpack's or rspack's `context`, and the working
478
+ directory for rollup and rolldown, which report none. `root` overrides that.
439
479
 
440
480
  **Everything inside a Style Dictionary configuration is Style Dictionary's**, so
441
481
  `source`, `include` and `buildPath` are resolved against the working directory.
@@ -493,13 +533,20 @@ own way:
493
533
  | ------------------ | ------------------------ | ---------------------------------- |
494
534
  | Vite | `config.logger.info` | `config.logger.error` |
495
535
  | Rollup, Rolldown | the plugin context's log | the context's warning channel |
496
- | Webpack | the console | `compilation.warnings`, so `stats` |
536
+ | Webpack, Rspack | the console | `compilation.warnings`, so `stats` |
497
537
  | No host (one-shot) | the console | the console |
498
538
 
499
539
  That is what makes a `customLogger` and `clearScreen` work under Vite, and what
500
540
  puts a failed compile into `stats.toJson()` under webpack — where it reaches CI
501
541
  annotations and anything else reading the build's own output.
502
542
 
543
+ **Rspack reformats what it is given, and webpack does not.** The plugin hands
544
+ both the same plain text; rspack wraps every diagnostic in its own frame — a `⚠`
545
+ marker and a `│` gutter — and colours that frame whenever it thinks colour is
546
+ wanted, which setting `CI` is enough to do. So a warning read out of rspack's
547
+ `stats` can carry ANSI escapes that came from rspack rather than from here.
548
+ Strip them before feeding that text to anything that expects plain output.
549
+
503
550
  **A failure is reported as a warning, never on the host's error channel.**
504
551
  Rollup's `this.error` aborts the bundle, so reporting a failure through it would
505
552
  stop every build that reported one — taking the decision `failOnError` exists to
@@ -604,14 +651,14 @@ the host stops, the other is that a build went wrong.
604
651
 
605
652
  ## Public API
606
653
 
607
- Small on purpose. Four bundler entry points, one root entry, and two types.
654
+ Small on purpose. Five bundler entry points, one root entry, and two types.
608
655
 
609
- | Import | What it is |
610
- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
611
- | `…/vite`, `…/rolldown`, `…/rollup`, `…/webpack` | Default export: the plugin for that bundler. Call it with the options below. |
612
- | `…` (the root) | Default export, also named `unplugin`: the unplugin instance, carrying `.vite`, `.rolldown`, `.rollup` and `.webpack`. |
613
- | `UnpluginStyleDictionaryOptions` | The options type, exported from every entry above. |
614
- | `StyleDictionaryConfigContext` | What the function form of `config` is handed, exported from every entry above. |
656
+ | Import | What it is |
657
+ | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
658
+ | `…/vite`, `…/rolldown`, `…/rollup`, `…/rspack`, `…/webpack` | Default export: the plugin for that bundler. Call it with the options below. |
659
+ | `…` (the root) | Default export, also named `unplugin`: the unplugin instance, carrying `.vite`, `.rolldown`, `.rollup`, `.rspack` and `.webpack`. |
660
+ | `UnpluginStyleDictionaryOptions` | The options type, exported from every entry above. |
661
+ | `StyleDictionaryConfigContext` | What the function form of `config` is handed, exported from every entry above. |
615
662
 
616
663
  Anything not in that table is internal, whatever a build output happens to
617
664
  contain. In particular the watch filter and the raw unplugin factory are not
@@ -1026,6 +1073,68 @@ plugin. As of this unplugin-based rewrite:
1026
1073
  - Behavior under Vite is unchanged: the same `buildStart`-time compilation and
1027
1074
  dev-server watch/rebuild logic as before.
1028
1075
 
1076
+ ## Development
1077
+
1078
+ Fork, then clone the repository:
1079
+
1080
+ ```shell
1081
+ git clone https://github.com/your-username/unplugin-style-dictionary.git
1082
+ ```
1083
+
1084
+ Install with the Node version in [`.tool-versions`](.tool-versions). CI resolves
1085
+ it from that file, and an older npm rewrites `package-lock.json` as it installs.
1086
+ If `node --version` disagrees:
1087
+
1088
+ ```shell
1089
+ mise exec node@"$(awk '/^nodejs/{print $2}' .tool-versions)" -- npm install
1090
+ ```
1091
+
1092
+ ### The four commands
1093
+
1094
+ These are what CI runs, and between them they are the whole gate:
1095
+
1096
+ ```shell
1097
+ npm run lint # oxlint, then ESLint, then oxfmt --check
1098
+ npm run build # tsc -b, then tsdown into dist/
1099
+ npm test # vitest, one run, no watch
1100
+ npm run package:check # publint, attw, then scripts/check-package.mjs
1101
+ ```
1102
+
1103
+ `npm run package:check` reads `dist/`, so it needs a build first.
1104
+ `npm run peers:check` is the slower one that packs the tarball and builds it
1105
+ against both ends of every declared peer range; `Build` runs it, and it is worth
1106
+ running locally when you touch the exports map or the peer declarations.
1107
+
1108
+ **oxfmt formats this repository, not Prettier**, and it covers Markdown, JSON
1109
+ and YAML as well as TypeScript. `npm run lint -- --fix` will not reformat
1110
+ anything — reach for `npm run format`.
1111
+
1112
+ ### Tests
1113
+
1114
+ `tests/` drives real bundlers against real files in temporary directories rather
1115
+ than mocking Style Dictionary: a real Vite dev server, a real `rollup.watch()`,
1116
+ a real `webpack()` compile, a real `rspack()` compile, and all five targets
1117
+ through their own entry points. A new hook needs a matching caller in
1118
+ `tests/index.test.ts` — calling one bare leaves `this` undefined and the failure
1119
+ reads as a plugin bug.
1120
+
1121
+ The suite prints nothing. If you add a test that provokes the plugin's failure
1122
+ report, spy on `console.error`, **assert the message is there**, and restore in
1123
+ a `finally`. You can check the whole suite is quiet with:
1124
+
1125
+ ```shell
1126
+ npx vitest run --reporter=verbose 2>&1 | grep -E '^std(out|err) \|'
1127
+ ```
1128
+
1129
+ That should print nothing.
1130
+
1131
+ [`AGENTS.md`](AGENTS.md) carries the conventions, the traps, and the reasoning
1132
+ behind both. It is written for coding agents and is equally the fullest thing a
1133
+ human contributor can read.
1134
+
1135
+ Contribution guidelines for the organization are in
1136
+ [`kanso-labs/.github`](https://github.com/kanso-labs/.github).
1137
+
1029
1138
  ## License
1030
1139
 
1031
1140
  MIT
@@ -1037,7 +1146,7 @@ MIT
1037
1146
  [codecov]: https://codecov.io/gh/kanso-labs/unplugin-style-dictionary
1038
1147
  [coverage-shield]:
1039
1148
  https://img.shields.io/codecov/c/github/kanso-labs/unplugin-style-dictionary?label=Coverage
1040
- [license]: ./LICENSE
1149
+ [license]: ./LICENSE.md
1041
1150
  [license-shield]:
1042
1151
  https://img.shields.io/github/license/kanso-labs/unplugin-style-dictionary
1043
1152
  [npm]: https://www.npmjs.com/package/@kanso-labs/unplugin-style-dictionary
package/dist/index.js CHANGED
@@ -53,6 +53,15 @@ function nodeModulesNegations(paths) {
53
53
  }
54
54
  return Array.from(negations);
55
55
  }
56
+ function nodeModulesWatchDirectories(paths) {
57
+ const directories = /* @__PURE__ */ new Set();
58
+ for (const file of paths) {
59
+ const normalised = file.replace(/\\/g, "/");
60
+ if (!normalised.includes("/node_modules/")) continue;
61
+ directories.add(path.dirname(normalised));
62
+ }
63
+ return Array.from(directories);
64
+ }
56
65
  function paint(code, value, allowed) {
57
66
  return allowed ? `\u001B[${code}m${value}\u001B[0m` : value;
58
67
  }
@@ -92,6 +101,56 @@ function temporaryPathFor(destination) {
92
101
  const extension = path.extname(destination);
93
102
  return path.join(path.dirname(destination), `.${path.basename(destination, extension)}.${process.pid}.${temporaryFileCounter++}.tmp`);
94
103
  }
104
+ const RENAME_RETRY_CODES = /* @__PURE__ */ new Set(["EBUSY", "EPERM"]);
105
+ const RENAME_RETRY_DELAYS_MS = [
106
+ 1,
107
+ 2,
108
+ 4,
109
+ 8,
110
+ 16,
111
+ 32,
112
+ 64,
113
+ 128,
114
+ 256,
115
+ 512,
116
+ 1024
117
+ ];
118
+ const RENAME_RETRY_DELAYS_SYNC_MS = [
119
+ 1,
120
+ 2,
121
+ 4,
122
+ 8,
123
+ 16,
124
+ 32,
125
+ 64,
126
+ 128
127
+ ];
128
+ function isRetryableRenameError(error) {
129
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && RENAME_RETRY_CODES.has(error.code);
130
+ }
131
+ const sleepSync = (ms) => {
132
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
133
+ };
134
+ async function renameWithRetry(temporary, destination) {
135
+ for (const delay of RENAME_RETRY_DELAYS_MS) try {
136
+ await fs.promises.rename(temporary, destination);
137
+ return;
138
+ } catch (err) {
139
+ if (!isRetryableRenameError(err)) throw err;
140
+ await new Promise((resolve) => setTimeout(resolve, delay));
141
+ }
142
+ await fs.promises.rename(temporary, destination);
143
+ }
144
+ function renameWithRetrySync(temporary, destination) {
145
+ for (const delay of RENAME_RETRY_DELAYS_SYNC_MS) try {
146
+ fs.renameSync(temporary, destination);
147
+ return;
148
+ } catch (err) {
149
+ if (!isRetryableRenameError(err)) throw err;
150
+ sleepSync(delay);
151
+ }
152
+ fs.renameSync(temporary, destination);
153
+ }
95
154
  const writeFileAtomic = async (file, data, options) => {
96
155
  if (typeof file !== "string") return fs.promises.writeFile(file, data, options);
97
156
  const temporary = temporaryPathFor(file);
@@ -101,7 +160,7 @@ const writeFileAtomic = async (file, data, options) => {
101
160
  discardTemporaryFile(temporary);
102
161
  return;
103
162
  }
104
- await fs.promises.rename(temporary, file);
163
+ await renameWithRetry(temporary, file);
105
164
  } catch (err) {
106
165
  discardTemporaryFile(temporary);
107
166
  throw err;
@@ -119,7 +178,7 @@ const writeFileSyncAtomic = (file, data, options) => {
119
178
  discardTemporaryFile(temporary);
120
179
  return;
121
180
  }
122
- fs.renameSync(temporary, file);
181
+ renameWithRetrySync(temporary, file);
123
182
  } catch (err) {
124
183
  discardTemporaryFile(temporary);
125
184
  throw err;
@@ -172,7 +231,7 @@ function statOrNull(file) {
172
231
  }
173
232
  }
174
233
  const unpluginFactory = (options = {}, meta) => {
175
- const isWebpack = meta.framework === "webpack";
234
+ const isWebpack = meta.framework === "webpack" || meta.framework === "rspack";
176
235
  const { cache = true, errorOverlay = true, failOnError = "build", logLevel, onBuildEnd, onBuildError, onBuildStart, platforms: platformsOption, report = true, root: rootOption, silent = false } = options;
177
236
  const level = logLevel ?? (silent ? "silent" : void 0);
178
237
  const quiet = level === "silent" || level === "warn";
@@ -610,6 +669,33 @@ const unpluginFactory = (options = {}, meta) => {
610
669
  const closeWatcher = () => {
611
670
  hostClosed = true;
612
671
  };
672
+ const adoptCompiler = (compiler) => {
673
+ if (rootOption === void 0) root = compiler.options.context ?? process.cwd();
674
+ hostMode = compiler.options.mode;
675
+ const pending = [];
676
+ host = { error: (message) => {
677
+ pending.push(message);
678
+ } };
679
+ compiler.hooks.compilation.tap("unplugin-style-dictionary", (compilation) => {
680
+ for (const message of pending.splice(0)) {
681
+ const reported = new Error(message);
682
+ reported.name = "UnpluginStyleDictionaryWarning";
683
+ compilation.warnings.push(reported);
684
+ }
685
+ });
686
+ const drainToConsole = () => {
687
+ for (const message of pending.splice(0)) console.error(paint("31", message, stderrColour));
688
+ };
689
+ compiler.hooks.failed.tap("unplugin-style-dictionary", drainToConsole);
690
+ compiler.hooks.done.tap("unplugin-style-dictionary", drainToConsole);
691
+ compiler.hooks.beforeCompile.tapPromise("unplugin-style-dictionary", async () => {
692
+ isWatching = compiler.watchMode;
693
+ const resolved = await resolveConfigs();
694
+ if (resolved.length === 0) return;
695
+ await compileOnceAcrossInstances(resolved);
696
+ hasCompiled = true;
697
+ });
698
+ };
613
699
  return {
614
700
  async buildStart() {
615
701
  adoptHost(this);
@@ -631,6 +717,7 @@ const unpluginFactory = (options = {}, meta) => {
631
717
  name: "unplugin-style-dictionary",
632
718
  rolldown: { closeWatcher },
633
719
  rollup: { closeWatcher },
720
+ rspack: adoptCompiler,
634
721
  vite: {
635
722
  closeWatcher,
636
723
  async configResolved(config) {
@@ -670,9 +757,37 @@ const unpluginFactory = (options = {}, meta) => {
670
757
  if (resolved.length === 0) return;
671
758
  let targets = await getWatchTargets(resolved);
672
759
  server.watcher.add(targets.paths);
760
+ const ownWatchers = /* @__PURE__ */ new Map();
761
+ const watchNodeModules = (forPaths) => {
762
+ const wanted = new Set(nodeModulesWatchDirectories(forPaths));
763
+ for (const [directory, watcher] of ownWatchers) {
764
+ if (wanted.has(directory)) continue;
765
+ watcher.close();
766
+ ownWatchers.delete(directory);
767
+ }
768
+ for (const directory of wanted) {
769
+ if (ownWatchers.has(directory)) continue;
770
+ try {
771
+ const watcher = fs.watch(directory, (_event, filename) => {
772
+ if (filename === null) return;
773
+ const changed = path.posix.join(directory, filename);
774
+ if (!isWatchedSource(changed, targets.patterns)) return;
775
+ schedule(path.basename(changed)).catch(() => {});
776
+ });
777
+ watcher.unref();
778
+ ownWatchers.set(directory, watcher);
779
+ } catch {}
780
+ }
781
+ };
782
+ watchNodeModules(targets.paths);
783
+ server.httpServer?.once("close", () => {
784
+ for (const watcher of ownWatchers.values()) watcher.close();
785
+ ownWatchers.clear();
786
+ });
673
787
  refreshServerWatchList = async (rebuilt) => {
674
788
  targets = await getWatchTargets(rebuilt);
675
789
  server.watcher.add(targets.paths);
790
+ watchNodeModules(targets.paths);
676
791
  };
677
792
  if (errorOverlay) {
678
793
  let overlayShowing = false;
@@ -716,33 +831,7 @@ const unpluginFactory = (options = {}, meta) => {
716
831
  if (!isWebpack) await schedule(path.basename(id));
717
832
  for (const file of await expandPatterns(patterns)) this.addWatchFile(file);
718
833
  },
719
- webpack(compiler) {
720
- if (rootOption === void 0) root = compiler.options.context ?? process.cwd();
721
- hostMode = compiler.options.mode;
722
- const pending = [];
723
- host = { error: (message) => {
724
- pending.push(message);
725
- } };
726
- compiler.hooks.compilation.tap("unplugin-style-dictionary", (compilation) => {
727
- for (const message of pending.splice(0)) {
728
- const reported = new Error(message);
729
- reported.name = "UnpluginStyleDictionaryWarning";
730
- compilation.warnings.push(reported);
731
- }
732
- });
733
- const drainToConsole = () => {
734
- for (const message of pending.splice(0)) console.error(paint("31", message, stderrColour));
735
- };
736
- compiler.hooks.failed.tap("unplugin-style-dictionary", drainToConsole);
737
- compiler.hooks.done.tap("unplugin-style-dictionary", drainToConsole);
738
- compiler.hooks.beforeCompile.tapPromise("unplugin-style-dictionary", async () => {
739
- isWatching = compiler.watchMode;
740
- const resolved = await resolveConfigs();
741
- if (resolved.length === 0) return;
742
- await compileOnceAcrossInstances(resolved);
743
- hasCompiled = true;
744
- });
745
- }
834
+ webpack: adoptCompiler
746
835
  };
747
836
  };
748
837
  const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory);
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 JSON5 from 'json5'\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 { glob } from 'tinyglobby'\nimport { createUnplugin } from 'unplugin'\n\nimport type {\n StyleDictionaryConfigContext,\n UnpluginStyleDictionaryOptions,\n} from './types.js'\n\nimport { matchesWatchedFile } from './watch-filter.js'\n\nexport type * from './types.js'\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.\n// A rejected promise must carry an Error, and `catch` binds `unknown`. What\n// Style Dictionary throws is already one; anything else is wrapped rather than\n// handed on raw.\n// Where the plugin's own lines go when a host offers somewhere better than the\n// console: Vite's `config.logger`, rollup's and rolldown's plugin context, or\n// webpack's `compilation`.\n//\n// **There is no `error` channel that merely reports.** Rollup's `this.error`\n// aborts the bundle — measured: a `buildStart` calling it ends the run with\n// `THREW: [plugin err-probe] fatal?` — so routing a failure report through it\n// would stop every build that reported one and silently override `failOnError`,\n// whose entire job is deciding that. A failure is therefore reported on the\n// host's warning channel, and whether the build stops stays `failOnError`'s\n// decision alone.\ninterface HostMessenger {\n error: (message: string) => void\n\n // Optional because not every host has somewhere for a progress line to go.\n // webpack's `stats` carries warnings and errors and nothing else, and\n // `Compiling design tokens...` is neither — so there it stays on the\n // console rather than being dressed up as a warning.\n info?: (message: string) => void\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(errorMessage(error))\n}\n\n// Whether escapes may be written to this stream.\n//\n// **The three signals are ordered rather than combined into one conjunction**,\n// and that ordering is the whole of it. `FORCE_COLOR=1` on a non-TTY — a CI job\n// that wants colour in a log it will render itself — is the single job that\n// variable has, and\n// `!process.env.NO_COLOR && process.env.FORCE_COLOR !== '0' && stream.isTTY`\n// never honours it: the TTY check has the last word and answers `false`.\n//\n// `NO_COLOR` wins over `FORCE_COLOR` because the convention says so: any\n// non-empty value turns colour off, and nothing may turn it back on.\nfunction colourAllowed(stream: { isTTY?: boolean }): boolean {\n if (process.env.NO_COLOR) return false\n\n const forced = process.env.FORCE_COLOR\n if (forced === '0') return false\n if (forced !== undefined && forced !== '') return true\n\n // A terminal that has told us it cannot render escapes. Not one of the three\n // the issue named, but it is what `TERM=dumb` means and it costs a line.\n if (process.env.TERM === 'dumb') return false\n\n return stream.isTTY === true\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\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 host's message channel, narrowed by a predicate rather than asserted: what\n// a plugin context carries under `warn` is the host's business, and a cast\n// would only claim it is callable.\nfunction isMessageChannel(value: unknown): value is (message: string) => void {\n return typeof value === 'function'\n}\n\n// A hook is the consumer's code, and what it hands back is not this plugin's to\n// assume. A predicate rather than `instanceof Promise`, which answers `false`\n// for a thenable from another realm or from a promise library — exactly the\n// case where letting a rejection escape does the damage.\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function'\n )\n}\n\n// Whether a discovered file looks like a Style Dictionary configuration at all.\n//\n// Only applied to a file the plugin went looking for, never to one a consumer\n// named: an explicit `config` is their choice and second-guessing it would\n// reject shapes Style Dictionary accepts and this does not know about.\n//\n// `config.json` is an extremely common name for something else entirely, and\n// the plugin used to adopt whatever it found under that name, add it to the\n// watch set, and report a successful compile over it.\nfunction looksLikeConfig(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false\n\n // The four keys any usable configuration has at least one of. `platforms`\n // alone is enough because a configuration can declare its tokens inline\n // under `tokens`, or read them through `source`/`include`.\n return ['include', 'platforms', 'source', 'tokens'].some(\n (key) => key in value,\n )\n}\n\n// Vite builds its dev-server watcher with a fixed ignore list — `**/.git/**`,\n// `**/node_modules/**`, `**/test-results/**` and the cache directory — and\n// spreads the consumer's own `server.watch.ignored` entries in *after* them.\n// Entries are appended, never subtracted, so `server.watcher.add()` cannot\n// reach a path an earlier entry already covers.\n//\n// That makes a token package resolved through `node_modules` — the shape of\n// every workspace, `app/node_modules/@acme/tokens` symlinked to\n// `packages/tokens` — build correctly once and then never rebuild, with\n// nothing said about it. Measured on Vite 6.4.3, 7.3.6 and 8.3.0: zero watcher\n// events for an edit, while a token file outside the root but outside\n// `node_modules` rebuilt in the same run.\n//\n// A negation naming the file exactly is what un-ignores it, and is deliberately\n// the narrowest form that works. `!**/node_modules/**` would restore the whole\n// dependency tree to the watcher.\nfunction nodeModulesNegations(paths: string[]): string[] {\n const negations = new Set<string>()\n\n for (const file of paths) {\n const normalised = file.replace(/\\\\/g, '/')\n if (normalised.includes('/node_modules/')) negations.add(`!${normalised}`)\n }\n\n return Array.from(negations)\n}\n\nfunction paint(code: string, value: string, allowed: boolean): string {\n return allowed ? `\\u001B[${code}m${value}\\u001B[0m` : value\n}\n\n// Which of a configuration's own `source`/`include` patterns match no file on\n// disk. Only for diagnosis: it is the emptiness of the resolved token set that\n// decides whether a build fails, because only that catches every route to an\n// empty set. This names the pattern at fault, which the token count cannot, and\n// it reports a mistyped pattern in a configuration whose others still match —\n// where nothing fails at all and one platform quietly loses its tokens.\nasync function patternsMatchingNothing(patterns: string[]): Promise<string[]> {\n const barren: string[] = []\n\n for (const pattern of patterns) {\n // A literal path is a `stat`, not a glob: `tinyglobby` treats a path with\n // no magic characters as a literal anyway, and this keeps the common case\n // off the filesystem walk.\n if (!GLOB_CHARACTERS.test(pattern)) {\n if (!fs.existsSync(pattern)) barren.push(pattern)\n continue\n }\n\n try {\n const matched = await glob([pattern], { absolute: true })\n if (matched.length === 0) barren.push(pattern)\n } catch {\n // A pattern that cannot even be globbed is the build's problem to\n // report; saying it twice, in a diagnostic, helps nobody.\n }\n }\n\n return barren\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 config extensions Style Dictionary loads with `import` rather than by\n// parsing the file — the `case` list in its own `loadFile`. They are the only\n// ones Node's permanent module cache applies to, and so the only ones this\n// plugin has to read on the build's behalf.\n//\n// Everything else Style Dictionary parses as JSON5, including `.json`, and\n// this list is what makes the plugin split the same way. Reading the two\n// halves apart is what silently unwatched a whole family of configurations:\n// a `.json5` or `.jsonc` file went down the import branch and failed there\n// while the build succeeded, and a `.json` file carrying a comment or a\n// trailing comma failed strict `JSON.parse` for the same reason.\nconst IMPORTED_CONFIG_EXTENSIONS = ['.js', '.mjs', '.ts']\n\n// A configuration as `resolveConfigs` hands it on: either the object the\n// consumer passed or the path it was read from, plus the directory relative\n// paths inside it resolve against.\ninterface ResolvedConfig {\n config: Config | string\n file?: string\n}\n\n// How to name a configuration in a message. A path is what a consumer\n// recognises; a configuration passed as an object or returned by a function has\n// no name, so it is identified by where it sits in the list rather than by a\n// stringified dump of itself.\nfunction describeConfig(item: ResolvedConfig, index: number): string {\n return item.file\n ? `The configuration ${item.file}`\n : `The configuration at position ${index + 1}`\n}\n\n// Whether a config path is one Style Dictionary imports rather than parses.\nfunction isImportedConfig(file: string): boolean {\n return IMPORTED_CONFIG_EXTENSIONS.some((extension) =>\n file.endsWith(extension),\n )\n}\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\n// The fingerprints of configurations this process has compiled at least once.\n// It is what lets a configuration given as an object or a function be skipped\n// at all: such a configuration has no file to stat, so an edit to it inside\n// `vite.config.ts` moves no mtime and the filesystem cannot tell the two\n// apart. Having built it here, the plugin can — the fingerprint changes with\n// the configuration.\n//\n// Module scope rather than the factory's, for the same reason `compilesInFlight`\n// below is: the instances that would otherwise repeat the work are different\n// instances, so per-instance state cannot see them. A `vitest run` stands up\n// several, and a function configuration — the form the README recommends for\n// registering custom formats — would be the one form that never skipped.\n//\n// The fingerprint carries the root, so two projects in one process never share\n// one. A configuration given as a path needs none of this: its own file is one\n// of the sources the mtime comparison reads, so an edit to it is visible across\n// processes as well as within one.\nconst compiledFingerprints = new Set<string>()\n\n// A compile that is running right now, keyed by `buildKey`, so bundler\n// instances in one process wait on each other rather than each starting their\n// own.\n//\n// Generated token files are a side effect on the filesystem, not per-bundler\n// output, and one process routinely holds several instances of this plugin. A\n// single `vitest run` on a project with two test projects and browser mode\n// stands up five Vite servers — the root one, one per project, and one more\n// per project once its HTTP server listens — and every one of them runs\n// `buildStart`. `hasCompiled` cannot see any of that: it is closure state\n// inside the factory, so each instance has its own and each compiles.\n//\n// Module scope is the only place a shared answer can live, since the\n// instances know nothing about each other. It stays a claim about identical\n// work, never about identity: the key carries the root and the resolved\n// configurations, so one script building two packages shares nothing.\nconst compilesInFlight = new Map<string, Promise<void>>()\n\n// A stable identity for a set of resolved configurations, or `null` for one\n// that cannot have a stable identity at all.\n//\n// Functions are serialised by source rather than dropped, because a `format`\n// or `transform` written inline is exactly what distinguishes two otherwise\n// identical configurations — and `JSON.stringify` omits a function outright,\n// which would make two different builds look like one.\nfunction buildKey(root: string, resolved: ResolvedConfig[]): null | string {\n try {\n return JSON.stringify(\n [root, resolved.map((item) => item.file ?? item.config)],\n (_key, value: unknown) =>\n typeof value === 'function' ? `[fn]${String(value)}` : value,\n )\n } catch {\n // A configuration that will not serialise — a circular reference, a\n // BigInt — takes no shared identity rather than a wrong one, and compiles\n // exactly as it did before.\n return null\n }\n}\n\n// The patterns one configuration reads, resolved the way the build resolves\n// them. The same `source`/`include` walk `getWatchTargets` does, for one\n// item rather than the whole set — against the working directory, because\n// that is where Style Dictionary's own `combineJSON` globs them.\nfunction sourcePatternsOf(configObj: Config): string[] {\n const patterns: string[] = []\n\n const add = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n patterns.push(\n (path.isAbsolute(pattern)\n ? pattern\n : path.resolve(process.cwd(), pattern)\n ).replace(/\\\\/g, '/'),\n )\n }\n }\n\n for (const value of [configObj.source, configObj.include]) {\n if (Array.isArray(value)) value.forEach(add)\n else add(value)\n }\n\n return patterns\n}\n\n// `fs.statSync` without the throw. A file that is missing, or that cannot be\n// read, is the same answer to every caller here: nothing to compare against.\nfunction statOrNull(file: string): fs.Stats | null {\n try {\n return fs.statSync(file)\n } catch {\n return null\n }\n}\n\n// Not exported. It cannot be called in the form a reader would guess —\n// unplugin types the factory as `(options, meta)`, and `meta` is the\n// bundler-identifying `UnpluginContextMeta` a consumer would have to build by\n// hand — so publishing it offered a name that answered nothing. What a\n// consumer imports is the default export of the entry for their bundler.\nconst unpluginFactory: UnpluginFactory<\n undefined | UnpluginStyleDictionaryOptions,\n false\n> = (options = {}, meta) => {\n // webpack is the one target whose `buildStart` does not run before the\n // module graph is resolved: unplugin taps it on `make`, an\n // `AsyncParallelHook` that `EntryPlugin` taps too. The `webpack` key below\n // compiles on `beforeCompile` instead, which webpack awaits before the\n // compilation exists.\n const isWebpack = meta.framework === 'webpack'\n const {\n cache = true,\n errorOverlay = true,\n failOnError = 'build',\n logLevel,\n onBuildEnd,\n onBuildError,\n onBuildStart,\n platforms: platformsOption,\n report = true,\n root: rootOption,\n silent = false,\n } = options\n\n // `silent` predates `logLevel` and names its quietest level, so it is read\n // as one. `logLevel` wins when a consumer sets both.\n const level = logLevel ?? (silent ? 'silent' : undefined)\n\n // Whether the plugin keeps its own progress lines and size table to itself.\n // A failure is reported at every level, which is why this gate is not on the\n // error branch below.\n const quiet = level === 'silent' || level === 'warn'\n\n // What Style Dictionary is told, if anything. `undefined` is the point of\n // this: it leaves whatever the consumer's own `log.verbosity` asked for\n // standing, where the plugin used to overwrite it on every build. Style\n // Dictionary has three levels to this option's four, so `'warn'` and\n // `'info'` both map to its default — they differ in what the plugin itself\n // says, not in what Style Dictionary does.\n const verbosity =\n level === undefined\n ? undefined\n : level === 'verbose'\n ? 'verbose'\n : level === 'silent'\n ? 'silent'\n : 'default'\n\n // Which platforms this compile covers, or `undefined` for all of them.\n //\n // The array form applies to every build; the object form splits the first\n // compile from the watch rebuilds, and `context` is what tells them apart —\n // only the rebuild paths pass one. An absent key means every platform, so\n // `{ watch: ['css'] }` builds everything once and then only css.\n const platformsFor = (context: string | undefined): string[] | undefined => {\n if (platformsOption === undefined) return undefined\n if (Array.isArray(platformsOption)) return platformsOption\n\n return context === undefined ? platformsOption.build : platformsOption.watch\n }\n\n // Whether a failure in this compile should be thrown rather than only\n // reported. The two compiles are told apart by `runBuilds`'s `context`,\n // which only the rebuild paths pass.\n const failsTheBuild = (context: string | undefined): boolean =>\n failOnError === true ||\n (context === undefined ? failOnError === 'build' : failOnError === 'serve')\n // What the host is doing, for the function form of `config`. Populated where\n // each host knows the answer and read when that function is called — the\n // same shape as `root` and the message host above, and for the same reason:\n // `resolveConfigs` is reached from five places now, and threading a context\n // parameter through all five would make every caller restate what only the\n // host can say.\n let hostCommand: 'build' | 'serve' = 'build'\n let hostMode: string | undefined\n let isWatching = false\n\n // `mode` is derived rather than invented where a host has no notion of one.\n // rollup and rolldown report nothing, and following `command` is the answer\n // Vite itself would give: its default mode is `development` serving and\n // `production` building.\n const configContext = (): StyleDictionaryConfigContext => ({\n command: hostCommand,\n mode: hostMode ?? (hostCommand === 'serve' ? 'development' : 'production'),\n watch: isWatching,\n })\n\n // Whether the host will keep rebuilding, as the plugin context reports it.\n // Read from `meta.watchMode`, which rollup, rolldown and Vite all carry and\n // webpack does not — there it comes off the compiler instead.\n const adoptWatchMode = (context: object): void => {\n const hookMeta: unknown = 'meta' in context ? context.meta : undefined\n if (typeof hookMeta !== 'object' || hookMeta === null) return\n\n const watching: unknown =\n 'watchMode' in hookMeta ? hookMeta.watchMode : undefined\n if (typeof watching === 'boolean') isWatching = watching\n }\n\n // Where a relative `config` path is looked up. The host sets it below\n // unless the consumer named one, which is why an explicit option wins: a\n // layout the host cannot describe is exactly what it is for.\n let root = rootOption\n ? path.resolve(process.cwd(), rootOption)\n : 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 // The patterns the last `getWatchTargets` derived. `watchChange` tests a\n // changed path against these before it resolves anything, so a file the\n // plugin does not care about costs one glob match instead of a full config\n // resolution — which, when `config` is a function, is the consumer's own\n // code, and the place the README tells them to register custom formats.\n //\n // It is safe to filter on a list that may be one build out of date because\n // the list always contains the config files themselves: an edit that adds a\n // source matches as a config change, which re-resolves and re-derives. The\n // one thing it cannot see is a `config` function that starts returning\n // different sources with no file changing at all, and that was never\n // observable without a rebuild to observe it in.\n let cachedPatterns: string[] | undefined\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 // Whether the host has shut its watcher down. `await watcher.close()` is not\n // a promise that no build is in flight: rollup's `Watcher.close` clears the\n // pending build timeout, closes each task's file watcher and emits `close`,\n // and never awaits `run` — while `Task.run` checks `closed` only *after*\n // `rollupInternal` has resolved. So a build that has already entered\n // `rollupInternal` runs its `buildStart` hooks through to completion after\n // `close()` has returned to its caller, against a project that may be half\n // torn down by then. Measured on rollup 4.63.3 with no plugin of ours: a\n // `buildStart` reading a file 300ms after `close()` resolved gets ENOENT.\n //\n // `closeWatcher` is what makes that answerable. It runs synchronously inside\n // `close()`, and so before the in-flight hook resumes, which is the whole\n // reason a flag set there is worth setting. What it must not be is\n // `closeBundle`: that fires once per bundle — every `BUNDLE_END` a consumer\n // calls `result.close()` on — and would read as a shutdown on every rebuild.\n let hostClosed = 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 // Decided once, when the plugin is constructed, and held for its life. The\n // two streams are asked separately because they are redirected separately —\n // `build 2>err.log` leaves stdout a terminal and stderr a file.\n const stdoutColour = colourAllowed(process.stdout)\n const stderrColour = colourAllowed(process.stderr)\n\n // Where a message goes once a host has offered somewhere better than the\n // console. Set by `configResolved` under Vite, by the build hooks under\n // rollup and rolldown, and by the `webpack` block; left undefined when no\n // host has claimed it, which is every unit test binding its own context.\n let host: HostMessenger | undefined\n\n // Adopts a plugin context as the message host, if it has the channels — the\n // unit tests bind a context carrying `addWatchFile` and nothing else, and a\n // hook calling `this.warn` against that throws in a way that reads as a\n // plugin bug rather than as a missing stub.\n //\n // Only when nothing has claimed the host yet. Under Vite `configResolved`\n // has already installed the dev server's own logger, and `buildStart` runs\n // after it with a rollup-shaped context that would otherwise replace it.\n const adoptHost = (context: object): void => {\n if (host) return\n\n const warn: unknown = 'warn' in context ? context.warn : undefined\n if (!isMessageChannel(warn)) return\n\n const info: unknown = 'info' in context ? context.info : undefined\n\n host = {\n // `warn`, never `error`. Rollup's `this.error` aborts the bundle, so\n // reporting through it would stop every build that reported anything and\n // take the decision `failOnError` exists to make.\n error: (message) => {\n warn.call(context, message)\n },\n info: isMessageChannel(info)\n ? (message) => {\n info.call(context, message)\n }\n : undefined,\n }\n }\n\n // Helper to log at the configured level\n const log = (\n message: string,\n type: 'error' | 'info' | 'success' = 'info',\n ) => {\n const prefix = '[unplugin-style-dictionary]'\n\n // Ahead of the `silent` gate on purpose. `silent` is about the progress\n // lines and the size table; a compile that failed is not noise, and\n // hiding it left a broken token set shipping with nothing said at all.\n if (type === 'error') {\n // The host renders and colours its own output, so nothing painted here\n // is handed to one — an escape inside a webpack `stats` entry survives\n // into `stats.toJson()` and into whatever reads it.\n if (host) {\n host.error(`${prefix} ${message}`)\n return\n }\n\n console.error(paint('31', `${prefix} ${message}`, stderrColour))\n return\n }\n\n if (quiet) return\n\n if (host?.info) {\n host.info(`${prefix} ${message}`)\n return\n }\n\n console.log(\n paint(\n type === 'success' ? '32' : '36',\n `${prefix} ${message}`,\n stdoutColour,\n ),\n )\n }\n\n // Runs one of the consumer's `onBuild*` hooks without letting it decide the\n // fate of the build that called it.\n //\n // Two ways a hook can go wrong, and neither may propagate. A throw is caught\n // here, because a post-processing step that fails must not undo a compile the\n // plugin itself completed — the files are written and correct. A rejected\n // promise is the quieter one: the return value is deliberately not awaited,\n // so a rejection has nothing holding it and reaches the host as an unhandled\n // rejection, which under Node's default takes the process down — a dev server\n // killed from inside a hook that was only meant to reformat a file.\n //\n // Both are reported at `'error'`, so they are said at every level including\n // `silent`, and worded so neither can be read as the compile having failed.\n const callHook = <A extends unknown[]>(\n name: string,\n hook: (...args: A) => Promise<void> | void,\n ...args: A\n ): void => {\n let result: unknown\n\n try {\n // Captured rather than dropped, because the promise an `async` hook\n // returns is the thing the check below needs. Wrapping this call in a\n // block-bodied arrow — which is what the linter asks for when the return\n // type is plain `void` — discarded it, and the rejection escaped exactly\n // as it had before any of this existed.\n result = hook(...args)\n } catch (err) {\n log(`The ${name} hook threw: ${errorMessage(err)}`, 'error')\n return\n }\n\n if (!isThenable(result)) return\n\n void Promise.resolve(result).catch((err: unknown) => {\n log(`The ${name} hook rejected: ${errorMessage(err)}`, 'error')\n })\n }\n\n // Resolve config file paths / objects\n const resolveConfigs = async (): Promise<ResolvedConfig[]> => {\n let rawConfig = options.config\n\n // Checked ahead of the discovery below, and by identity rather than\n // truthiness: `false` is falsy, so the `!rawConfig` test that triggers\n // discovery would treat \"do not discover anything\" as \"go and look\".\n if (rawConfig === false) return []\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\n const rejected: string[] = []\n\n for (const file of defaults) {\n const fullPath = path.resolve(root, file)\n if (!fs.existsSync(fullPath)) continue\n\n // Read before adopting. For the two `.json` names this is a parse and\n // nothing more; for the two module names it is an import, and the\n // module has already run by the time there is anything to check —\n // which is what `config: false` exists for and why validation alone\n // does not cover them.\n const candidate = await readConfigObject(\n { config: fullPath, file: fullPath },\n false,\n )\n\n if (!looksLikeConfig(candidate)) {\n rejected.push(file)\n continue\n }\n\n // Announced, because \"which configuration did it pick\" was not\n // answerable from the console at all, and discovery picks from four\n // generic names.\n if (!announcedDiscovery) {\n announcedDiscovery = true\n log(`Using the configuration it found at ${fullPath}`, 'info')\n }\n\n rawConfig = file\n break\n }\n\n // Said whether or not something usable turned up after them. A skipped\n // candidate is the interesting half of \"no configuration found\": the\n // file is right there, and the reason it was not used is not guessable.\n if (rejected.length > 0) {\n log(\n `Ignored ${rejected.join(', ')} in ${root}: nothing there declares platforms, source, include or tokens, so it does not look like a Style Dictionary configuration. Name it with the config option if it is one, or set config to false to stop looking.`,\n 'error',\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(configContext())\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 { config: fullPath, file: fullPath }\n } else {\n return { config: conf }\n }\n })\n }\n\n // Imports a config module, re-evaluating it only when the file itself has\n // changed. The query string is what decides that, and it is not decoration:\n // Node's ESM cache is permanent and keyed on the specifier, so a config\n // imported without one is evaluated once and never read again — which is\n // how an edited `.mjs` config went on building the platform map the process\n // started with, for the rest of the session.\n //\n // `Date.now()` fixed that staleness and bought two problems. Every watcher\n // event registered another module record in a map nothing prunes, re-running\n // the config's own `registerFormat` side effects for a file nobody touched.\n // And its millisecond granularity meant an edit landing inside the same\n // millisecond as the previous import shared that import's key, and was\n // served the old module anyway. `mtimeMs` carries sub-millisecond\n // resolution and only moves when the file does.\n const importConfigModule = async (file: string): Promise<unknown> => {\n let version: number\n try {\n version = fs.statSync(file).mtimeMs\n } catch {\n // A config that cannot be stat'd is about to fail its import too. The\n // old key is what keeps that failure the import's to report.\n version = Date.now()\n }\n\n // The dot goes, and that is not cosmetic. `mtimeMs` is fractional, so the\n // query it produces ends in something that reads as a file extension to\n // anything deriving a loader from the specifier without stripping the\n // query first — `sd.config.ts?t=1789565080284.6606` is then a `.6606`\n // file, and a TypeScript config gets parsed as JavaScript. Replacing the\n // one dot keeps every distinct mtime a distinct key.\n const key = String(version).replace('.', '_')\n\n // Sequential on purpose: a config module runs arbitrary code at import\n // time — `registerFormat` and friends — and Style Dictionary's registries\n // are global, so importing several at once would interleave those\n // registrations.\n return unwrapDefault(await import(`${pathToFileURL(file).href}?t=${key}`))\n }\n\n // What a configuration item says, as an object. `report` is what stops the\n // two readers of this from saying the same thing twice: a bad config has\n // nowhere else to surface when the watch list is being built, while a build\n // falls back to handing Style Dictionary the path and lets its message\n // through instead.\n const readConfigObject = async (\n item: ResolvedConfig,\n reportErrors: boolean,\n ): Promise<Config | null> => {\n if (typeof item.config !== 'string') return item.config\n\n try {\n // JSON5 rather than `JSON.parse`, because that is what Style Dictionary\n // reads these files with — it is a superset, so a plain `.json` config\n // parses identically and one carrying a comment stops being a config\n // the build understands and the watch list does not.\n const loaded: unknown = isImportedConfig(item.config)\n ? await importConfigModule(item.config)\n : JSON5.parse(fs.readFileSync(item.config, 'utf-8'))\n\n if (isConfig(loaded)) return loaded\n\n if (reportErrors) {\n log(\n `Config file did not resolve to a configuration object: ${item.config}`,\n 'error',\n )\n }\n } catch (err) {\n if (reportErrors) {\n log(\n `Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}`,\n 'error',\n )\n }\n }\n\n return null\n }\n\n // Parse token files to watch\n const getWatchTargets = async (\n resolvedConfigs: ResolvedConfig[],\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 const configObj = await readConfigObject(item, true)\n\n if (configObj) {\n const addPattern = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n // Against the working directory, because that is where Style\n // Dictionary resolves it: `combineJSON` globs each pattern with\n // no `cwd` of its own. Resolving against the configuration file's\n // directory instead is how the watch list came to name paths the\n // build never reads — a configuration in a subdirectory built\n // correctly and watched nothing at all.\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(process.cwd(), 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 // Recorded here rather than at each call site, so every path that derives\n // a watch list refreshes the one `watchChange` filters against.\n cachedPatterns = patterns\n\n return { paths: await expandPatterns(patterns), patterns }\n }\n\n // What `new StyleDictionary` is handed for an item. Only a path in the JS\n // family becomes an object, because those are exactly the extensions Style\n // Dictionary's own `loadFile` reaches with `import` — the ones whose module\n // record Node then caches forever, and so the only ones a build could read\n // stale. The JSON5 family stays a path because there is nothing to gain:\n // those are read from disk on every pass either way, so a build can never\n // see one as it stood earlier in the process.\n const configForBuild = async (\n item: ResolvedConfig,\n ): Promise<Config | string> => {\n const { config } = item\n\n if (typeof config !== 'string' || !isImportedConfig(config)) return config\n\n const loaded = await readConfigObject(item, false)\n\n // A config that could not be read falls back to the path, so the failure\n // stays Style Dictionary's to report — it knows more about why an import\n // failed than this does, a `.ts` config without type stripping especially.\n if (!loaded) return item.config\n\n // `loadFile` clones what it imports before handing it on, and passing an\n // object skips that. It matters more here than it does there: the module\n // record now outlives the build, and `extend` is called with\n // `mutateOriginal`. Cloning throws on a config carrying functions — an\n // inline transform — and Style Dictionary's own fallback in that case is\n // to use the original, so this one matches it.\n try {\n return structuredClone(loaded)\n } catch {\n return loaded\n }\n }\n\n // Every absolute destination a configuration declares, read off the\n // configuration itself rather than off an extended Style Dictionary\n // instance. Reading it here is the whole point: constructing the instance\n // is what the skip exists to avoid.\n //\n // Resolved exactly as the build resolves it below, so the two name the same\n // files — a relative `buildPath` against `root`, and a `destination`\n // against that.\n // `only` narrows this to named platforms, and exactly one caller wants that:\n // the up-to-date check, which asks whether the work *this* compile would do\n // is already done. Everywhere else the answer has to cover every declared\n // platform, because a file an unselected platform wrote earlier is still the\n // plugin's own output and has to stay out of the watch list.\n const declaredDestinations = (\n configObj: Config,\n only?: string[],\n ): string[] => {\n const destinations: string[] = []\n\n const entries = Object.entries(configObj.platforms ?? {})\n const selected = only\n ? entries.filter(([name]) => only.includes(name))\n : entries\n\n for (const [, platform] of selected) {\n const buildPath = platform.buildPath ?? ''\n const absoluteBuildPath = path.isAbsolute(buildPath)\n ? buildPath\n : path.resolve(root, buildPath)\n\n for (const file of platform.files ?? []) {\n if (file.destination) {\n destinations.push(\n path.isAbsolute(file.destination)\n ? file.destination\n : path.resolve(absoluteBuildPath, file.destination),\n )\n }\n }\n }\n\n return destinations\n }\n\n // A stable identity for one resolved configuration, or `null` where it\n // cannot have one. Functions are serialised by source rather than dropped,\n // because an inline `format` or `transform` is exactly the edit a\n // fingerprint has to notice, and `JSON.stringify` omits a function outright.\n const configFingerprint = (item: ResolvedConfig): null | string => {\n try {\n return JSON.stringify(\n [root, item.file ?? item.config],\n (_key, value: unknown) =>\n typeof value === 'function' ? `[fn]${String(value)}` : value,\n )\n } catch {\n // Circular, or holding a BigInt. It takes no identity rather than a\n // wrong one, so it compiles every time exactly as it did before.\n return null\n }\n }\n\n // Whether every file a configuration declares is already newer than every\n // file it reads, so its compile can be skipped.\n //\n // Conservative in every direction it can be: anything it cannot establish —\n // a destination that is missing, a source it cannot stat, a configuration\n // declaring no destinations at all — is a reason to build rather than to\n // skip.\n const isUpToDate = async (\n item: ResolvedConfig,\n configObj: Config,\n only?: string[],\n ): Promise<boolean> => {\n // An action writes what no `destination` names, so there is nothing for\n // the comparison below to check and skipping would leave its work undone.\n const hasActions = Object.values(configObj.platforms ?? {}).some(\n (platform) => (platform.actions?.length ?? 0) > 0,\n )\n if (hasActions) return false\n\n const destinations = declaredDestinations(configObj, only)\n if (destinations.length === 0) return false\n\n // `options.watch` belongs in here as much as `source` does. A consumer\n // names an extra file because something in the build reads it — a custom\n // format's own data file, most obviously — and leaving it out let a change\n // to it be skipped over while the watcher dutifully reported it.\n const extraWatches = options.watch\n ? Array.isArray(options.watch)\n ? options.watch\n : [options.watch]\n : []\n\n const sources = await expandPatterns([\n ...sourcePatternsOf(configObj),\n ...extraWatches.map((pattern) =>\n (path.isAbsolute(pattern)\n ? pattern\n : path.resolve(root, pattern)\n ).replace(/\\\\/g, '/'),\n ),\n ])\n if (item.file) sources.push(item.file.replace(/\\\\/g, '/'))\n\n if (sources.length === 0) return false\n\n let newestSource = -Infinity\n let sawFile = false\n\n for (const source of sources) {\n const stats = statOrNull(source)\n if (!stats) return false\n\n // Directories are in this list on purpose — `expandPatterns` registers\n // each pattern's static parent so a token file created later is\n // noticed — but their mtime cannot be read as an input signal here. A\n // directory's mtime moves whenever an entry is added or renamed inside\n // it, and the atomic write renames every generated file into place, so\n // a `buildPath` inside a watched directory made the build itself the\n // newest thing the comparison could see. Nothing was ever up to date.\n if (stats.isDirectory()) continue\n\n sawFile = true\n newestSource = Math.max(newestSource, stats.mtimeMs)\n }\n\n // Every pattern expanded to directories alone, so nothing was actually\n // read. Style Dictionary would build an empty dictionary from that, and a\n // skip would present the empty result as current.\n if (!sawFile) return false\n\n let oldestDestination = Infinity\n for (const destination of destinations) {\n const stats = statOrNull(destination)\n if (!stats) return false\n oldestDestination = Math.min(oldestDestination, stats.mtimeMs)\n }\n\n if (oldestDestination <= newestSource) return false\n\n // A configuration given as a path has its own file among the sources\n // above, so an edit to it has already been accounted for and the skip\n // holds across processes.\n if (item.file) return true\n\n // One given as an object or a function has not. Only this process knows\n // what it looked like when those destinations were written, so the skip\n // holds only against a fingerprint recorded here.\n const fingerprint = configFingerprint(item)\n\n return fingerprint !== null && compiledFingerprints.has(fingerprint)\n }\n\n // The size-and-gzip table, in a function of its own so that the compile\n // `try` in `runBuilds` can stop before it. Everything here is presentation\n // over files Style Dictionary has already finished writing, so a throw from\n // it is a reporting bug and nothing more.\n const reportSizes = (generatedFiles: Set<string>) => {\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.relative(root, filePath).replace(/\\\\/g, '/')\n const dir = path.dirname(displayPath)\n const base = path.basename(displayPath)\n // The table goes to stdout, so it follows stdout's decision — which\n // is not always stderr's, since the two are redirected separately.\n const coloredPath =\n dir === '.'\n ? paint('32', base, stdoutColour)\n : paint('90', `${dir}/`, stdoutColour) +\n paint('32', base, stdoutColour)\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 // One unreadable destination costs its row rather than the table.\n // Deliberately narrower than the caller's `catch`: it covers the\n // three filesystem and gzip calls above and not the arithmetic\n // below, so a padding bug is reported rather than quietly printing\n // short.\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(2, longestPathLength - info.relativeDisplayPath.length + 2),\n )\n const sizePadded = info.sizeStr.padStart(longestSizeLength)\n console.log(\n info.coloredPath +\n pathPadding +\n paint(\n '90',\n `${sizePadded} │ gzip: ${info.gzipSizeStr}`,\n stdoutColour,\n ),\n )\n }\n }\n }\n\n // Compile design tokens\n const runBuilds = async (\n resolvedConfigs: ResolvedConfig[],\n context?: string,\n ) => {\n const startTime = Date.now()\n\n // Ahead of the `try` rather than inside it, because the reporting below\n // reads it and that reporting is deliberately outside.\n const generatedFiles = new Set<string>()\n\n // How many configurations were already up to date. Read by the reporting\n // below, which is why it sits out here with `generatedFiles`.\n let skipped = 0\n\n try {\n if (!context) {\n log('Compiling design tokens...', 'info')\n }\n\n // Before anything is resolved or built, and once per build — a watch\n // rebuild is a build, so this fires again for each one.\n if (onBuildStart) callHook('onBuildStart', onBuildStart)\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 [index, item] of resolvedConfigs.entries()) {\n // Read ahead of the instance, because avoiding the instance is the\n // point: construction plus `extend` is the 15-30% of a build that\n // parses the token sources, and `buildAllPlatforms` is the rest.\n //\n // `false` so a configuration that will not parse says nothing here —\n // the build below hands Style Dictionary the path and lets its own\n // message through, which is more specific than anything this could\n // say.\n const declared = cache ? await readConfigObject(item, false) : null\n const selectedPlatforms = platformsFor(context)\n\n if (declared && (await isUpToDate(item, declared, selectedPlatforms))) {\n // The destinations still have to be collected. They are what stops\n // the plugin's own output being treated as a watched source, so a\n // skipped configuration that contributed none would have its files\n // rebuild the moment a watcher noticed them.\n for (const destination of declaredDestinations(declared)) {\n generatedFiles.add(destination)\n }\n\n skipped++\n continue\n }\n\n // `{ init: false }` is the escape hatch Style Dictionary documents on\n // this constructor, and it is what makes a bad configuration\n // catchable. Left to itself the constructor ends in a call to\n // `init()` whose promise it neither stores nor returns, so a config\n // that fails to load rejects a promise nobody holds: the `catch`\n // below never runs, and the host dies with a raw stack or — where an\n // `unhandledRejection` handler suppresses it — hangs on a\n // `buildStart` that never settles. `await sd.hasInitialized` cannot\n // observe it either, since that promise is only ever resolved, at the\n // tail of a successful extend.\n //\n // It is handed the configuration as an object rather than as a path\n // for the same reason: Style Dictionary imports a path with no\n // cache-busting query of its own, so under a long-lived dev server\n // every rebuild after the first built the config the process started\n // with while the watch list followed the edit.\n const sd = new StyleDictionary(await configForBuild(item), {\n init: false,\n })\n\n // One initialisation rather than two. `init()` is `extend()` with\n // `mutateOriginal`, so the old pair loaded the configuration and\n // combined every source twice — running a custom parser or\n // preprocessor twice with it — and the first of the two ran at\n // default verbosity, which is how Style Dictionary's own warnings\n // escaped this plugin's `silent`. `config` defaults to the one the\n // constructor was handed.\n //\n // `verbosity` is `undefined` unless a consumer asked for a level, and\n // Style Dictionary falls through an unset one to the configuration's\n // own `log.verbosity`. Overwriting it here is what silenced the one\n // line explaining why a build wrote nothing. `log.warnings` is not\n // touched either way: a consumer's `warnings: 'error'` turning a\n // missing output file into a thrown build is their decision.\n await sd.extend(undefined, { mutateOriginal: true, verbosity })\n\n // **Before the build, and that is the whole of it.** A token set that\n // resolved to nothing is not an error anywhere in this stack: Style\n // Dictionary writes the file with no custom properties in it, prints\n // its usual `✔︎` line at any verbosity, and returns. So a token file\n // deleted mid-session took the generated output down with it and\n // reported `Rebuilt design tokens` while doing it, and a `source`\n // matching nothing shipped an empty stylesheet from a build that\n // exited 0.\n //\n // Checked here because `buildAllPlatforms` truncates and rewrites the\n // destination: one line later the previous good output is already gone\n // and an error would be accurate and useless.\n if (sd.allTokens.length === 0) {\n // The configuration as an object, so its own patterns can be named.\n // `false` because a configuration that will not parse never reaches\n // here — the `extend` above would have thrown first.\n const asObject = await readConfigObject(item, false)\n const barren = asObject\n ? await patternsMatchingNothing(sourcePatternsOf(asObject))\n : []\n\n // Thrown rather than reported, so it takes the path `failOnError`\n // already owns — the same decision, made in one place, rather than a\n // second way for a build to fail.\n throw new Error(\n [\n `${describeConfig(item, index)} resolved no tokens, so its output would be emptied.`,\n barren.length > 0\n ? `These patterns matched no files: ${barren.join(', ')}`\n : `It declares no source or include patterns that matched anything.`,\n `Nothing was written. Set failOnError to false to build anyway.`,\n ].join(' '),\n )\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 sd.volume = atomicVolume\n\n if (selectedPlatforms === undefined) {\n await sd.buildAllPlatforms()\n } else {\n // Named, so a typo is an error rather than a platform silently not\n // built — which is what Style Dictionary's own CLI means by \"Must be\n // defined in the config\".\n const defined = Object.keys(sd.platforms)\n const unknown = selectedPlatforms.filter(\n (name) => !defined.includes(name),\n )\n if (unknown.length > 0) {\n throw new Error(\n `${describeConfig(item, index)} does not define the platform(s) ${unknown.join(', ')}. It defines ${defined.join(', ')}.`,\n )\n }\n\n // One after another, matching the loop this sits inside: two\n // platforms may name the same destination, and `buildAllPlatforms`\n // fanning its own out with `Promise.all` is Style Dictionary's\n // choice over configurations it owns, not this plugin's over a\n // selection a consumer wrote.\n for (const name of selectedPlatforms) {\n await sd.buildPlatform(name)\n }\n }\n\n // Every declared platform, not only the ones this compile built. A\n // file an unselected platform wrote on an earlier build is still the\n // plugin's own output, and dropping it from this set would let a\n // watcher treat it as a token source and rebuild on it forever.\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(sd.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 // Recorded only now, so a configuration whose build threw is never\n // treated as one this process has compiled.\n const fingerprint = configFingerprint(item)\n if (fingerprint !== null) compiledFingerprints.add(fingerprint)\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 } catch (err) {\n const duration = Date.now() - startTime\n log(\n `Compilation failed after ${duration}ms: ${errorMessage(err)}`,\n 'error',\n )\n\n // Ahead of the throw decision on purpose, so the overlay sees a failure\n // whatever `failOnError` does with it. Under the dev server's default\n // the line below does not throw, and reading the outcome from a caller's\n // `catch` would see a rebuild that looked like it succeeded.\n notifyBuildOutcome?.(asError(err))\n\n // Ahead of the throw decision for the same reason as the line above: a\n // rebuild under the dev server's default does not throw, and a hook that\n // only fired when something else was about to fail would be silent on\n // exactly the builds a consumer is watching.\n if (onBuildError) callHook('onBuildError', onBuildError, err)\n\n // Reported, and then rethrown so the host stops. Swallowing it left\n // every target exiting 0 with the previous run's tokens still on disk\n // and in the bundle — a green build shipping stale values.\n if (failsTheBuild(context)) throw err\n\n // Explicit, now that the reporting below sits outside the `try`. This\n // `catch` used to end the function by falling off the end of it; a\n // failure that is not rethrown would otherwise carry on to announce a\n // compile that did not happen.\n return\n }\n\n // The compile is what the overlay reflects, so this is said here rather\n // than at the end: everything below is reporting, it returns early in\n // three places, and a size table that throws must not leave a successful\n // build looking unfinished.\n notifyBuildOutcome?.(null)\n\n // One measurement, read by the hook below and by the reporting under it.\n const duration = Date.now() - startTime\n\n // Beside the overlay notification, and for the same reason it sits here\n // rather than at the end of the function: the reporting below returns\n // early in three places, and a build that finished has finished whether or\n // not a size table gets printed for it.\n //\n // Sorted, so two runs of one configuration hand back the same order —\n // `generatedFiles` is a set in platform-then-file order, which is stable\n // in practice and guaranteed by nothing. The paths stay platform-native:\n // this is a list a consumer is going to open files with, not one the\n // watcher compares against.\n if (onBuildEnd) {\n // `toSorted` is what the linter asks for and what this cannot use:\n // `lib` is ES2022 here and `toSorted` is ES2023, so it types as an error\n // even though every Node this package supports has it. The rule guards\n // against mutating an array someone else holds, and this one was built\n // from the set on the line it appears on.\n // oxlint-disable-next-line unicorn/no-array-sort\n const files = Array.from(generatedFiles).sort((left, right) =>\n left.localeCompare(right),\n )\n callHook('onBuildEnd', onBuildEnd, files, duration)\n }\n\n // The `try` ends above, and everything from here down is reporting. Style\n // Dictionary has finished writing by now and `generatedDestinations` is\n // already replaced, so nothing below can put a file on disk in doubt —\n // which is why a throw from it must not be caught as a compile failure.\n // It used to be: a fault in the padding arithmetic printed `Compilation\n // failed after 19ms` over a build whose every token file was correct, and\n // with `failOnError` defaulting to `'build'` that stopped the bundler.\n\n // Every configuration was already current, so nothing was written. Said\n // rather than left implied: a build that prints its opening line and then\n // finishes in two milliseconds reads as one that silently did nothing.\n const everythingSkipped = skipped === resolvedConfigs.length\n\n if (context) {\n log(\n everythingSkipped\n ? `Design tokens already up to date after change in ${context} (${duration}ms)`\n : `Rebuilt design tokens due to change in ${context} (${duration}ms)`,\n 'success',\n )\n return\n }\n\n // The table is skipped when nothing was written, on top of `report` and\n // `quiet`. It reads every generated file in full and gzips it, and\n // reprinting the sizes of files this build did not touch is the one case\n // where that cost buys nothing at all.\n if (report && !quiet && !everythingSkipped && generatedFiles.size > 0) {\n try {\n reportSizes(generatedFiles)\n } catch (err) {\n // At `'error'`, so it is said at every level including `silent`,\n // exactly as a compile failure is — and worded so it cannot be read\n // as one. Not rethrown: the build succeeded.\n log(\n `Failed to report generated file sizes: ${errorMessage(err)}`,\n 'error',\n )\n }\n }\n\n if (everythingSkipped) {\n log(`Design tokens are already up to date (${duration}ms)`, 'success')\n return\n }\n\n log(\n skipped > 0\n ? `Compiled successfully! (${duration}ms, ${skipped} already up to date)`\n : `Compiled successfully! (${duration}ms)`,\n 'success',\n )\n }\n\n // `runBuilds` for the first build of a process, with the compile shared\n // between every plugin instance that wants the same one.\n //\n // An instance arriving while a compile for the same key is running waits on\n // that compile instead of starting a second. It is the concurrent half that\n // needs this: an up-to-date check compares what is on disk against the\n // sources, and two instances that start together have nothing on disk to\n // compare against yet, so only a shared promise can tell them apart from\n // two genuinely separate builds.\n //\n // The entry is dropped as soon as the compile settles, so this coalesces\n // rather than caches — a later `buildStart` still compiles. Skipping one\n // whose output is already current is #212's up-to-date check, and belongs\n // with it rather than as a second mechanism here.\n //\n // A rejection reaches every waiter, which is the point: an instance that\n // waited on a failed compile must not carry on as though the tokens were\n // written. Whether that rejection is thrown at all is `failOnError`'s\n // decision, already made inside `runBuilds`.\n const compileOnceAcrossInstances = async (\n resolvedConfigs: ResolvedConfig[],\n ): Promise<void> => {\n const key = buildKey(root, resolvedConfigs)\n if (key === null) {\n await runBuilds(resolvedConfigs)\n return\n }\n\n const running = compilesInFlight.get(key)\n if (running) {\n await running\n return\n }\n\n const compile = runBuilds(resolvedConfigs)\n compilesInFlight.set(key, compile)\n\n try {\n await compile\n } finally {\n compilesInFlight.delete(key)\n }\n }\n\n // One rebuild per burst of watcher events, and never two at once.\n //\n // Two things went wrong without this. A single token edit under Vite's dev\n // server reached both the `configureServer` listener and `watchChange` —\n // Vite 6, 7 and 8 all invoke plugin `watchChange` while serving — and each\n // started its own build, so one write produced two. And nothing serialised\n // them: a four-file change started one build per file, all overlapping.\n // `runBuilds` builds its configurations one after another precisely so two\n // instances never write the same destination at once, and concurrent calls\n // to it reintroduced that one level up.\n //\n // The trailing debounce collapses the burst; the in-flight chain means a\n // trigger arriving mid-build queues exactly one follow-up rather than\n // starting a second build beside it.\n const REBUILD_DEBOUNCE_MS = 50\n\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n let pendingReason: string | undefined\n let inFlight: Promise<void> | undefined\n let waiting: Array<(failure?: { error: unknown }) => void> = []\n\n // Set by `configureServer`. A dev server's watcher is long-lived, so its\n // list has to follow a configuration that changes; every other target\n // re-registers on each build through `addWatchFile` instead.\n let refreshServerWatchList:\n | ((resolved: ResolvedConfig[]) => Promise<void>)\n | undefined\n\n // Whether the discovered path has been announced. Once per plugin instance:\n // `resolveConfigs` runs on every build and rebuild, and a dev server would\n // otherwise repeat the line for the rest of the session.\n let announcedDiscovery = false\n\n // Resolved by `configResolved` so it can amend the watcher's ignore list, and\n // handed to `configureServer` rather than resolved again — one start-up, one\n // call of the consumer's `config` function.\n let startupResolved: ResolvedConfig[] | undefined\n\n // Also set by `configureServer`, and left undefined everywhere else: this is\n // how a compile outcome reaches Vite's error overlay. It is deliberately not\n // the same path as `failOnError`.\n //\n // `failOnError` decides whether the host stops; this decides whether the\n // browser is told. Under a dev server the default is not to stop, so the\n // failure is reported and swallowed — and that is exactly the case where the\n // page is left rendering the last good file with nothing to say it is stale.\n // Reading the outcome off whether `runBuilds` threw would therefore see\n // nothing at all on the only configuration that matters.\n let notifyBuildOutcome: ((error: Error | null) => void) | undefined\n\n const drain = async (): Promise<void> => {\n // A loop rather than a single pass: anything scheduled while the build\n // below is running is picked up here instead of starting a second one.\n while (pendingReason !== undefined) {\n const reason = pendingReason\n pendingReason = undefined\n\n // Captured before the await, so a trigger arriving mid-build waits for\n // the next pass rather than being told this one covered it.\n const resolvers = waiting\n waiting = []\n\n let failure: undefined | { error: unknown }\n let compiling = false\n\n try {\n const resolved = await resolveConfigs()\n if (resolved.length > 0) {\n compiling = true\n await runBuilds(resolved, reason)\n compiling = false\n hasCompiled = true\n await refreshServerWatchList?.(resolved)\n }\n } catch (err) {\n failure = { error: err }\n\n // `runBuilds` reports its own failure before rethrowing, so only the\n // other things that can throw here — a `config` function of the\n // consumer's that raises, a watch list that cannot be rebuilt — need\n // reporting. They reach the overlay for the same reason: from the\n // page's point of view the rebuild failed, whichever half of it did.\n if (!compiling) {\n log(`Rebuild failed: ${errorMessage(err)}`, 'error')\n notifyBuildOutcome?.(asError(err))\n }\n }\n\n // Handed on to whatever awaited this rebuild, which is `watchChange`\n // and so the host under a watching bundler. Vite's dev-server listener\n // has no build to fail and catches it.\n for (const settle of resolvers) settle(failure)\n }\n }\n\n // Resolves once a rebuild covering this trigger has finished.\n const schedule = async (reason: string): Promise<void> => {\n // Nothing consumes a rebuild once the host has closed its watcher. This is\n // where a close actually lands: `watchChange` reaches here only after\n // resolving configurations and deriving a watch list, so a `closeWatcher`\n // arriving mid-hook finds no timer armed yet and nothing else to stop it.\n //\n // Resolving rather than rejecting, because the trigger was handled — by\n // being declined — and the caller awaiting it is a host on its way out.\n if (hostClosed) return\n\n pendingReason = reason\n\n const covered = new Promise<void>((resolve, reject) => {\n waiting.push((failure) => {\n if (failure) reject(asError(failure.error))\n else resolve()\n })\n })\n\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounceTimer = undefined\n inFlight = (inFlight ?? Promise.resolve()).then(drain)\n }, REBUILD_DEBOUNCE_MS)\n\n // A pending rebuild must not be what keeps a process alive; whatever is\n // watching already is.\n debounceTimer.unref()\n\n return covered\n }\n\n // Every host that runs a rollup-shaped watcher calls this on shutdown, and\n // all three get the same handler below. There is deliberately no webpack\n // equivalent here: it has no `closeWatcher`, its nearest thing is\n // `compiler.hooks.watchClose`, and nothing measured shows it exposed.\n //\n // It raises the flag and nothing else. A debounce timer armed before the\n // close is deliberately left to fire: the rebuild it runs is one the host\n // asked for while the project was still whole, and `drain` reports its own\n // failures. Cancelling it would be a guard no test could fail on, since a\n // trigger arriving after the close is declined by `schedule` instead.\n const closeWatcher = (): void => {\n hostClosed = true\n }\n\n return {\n async buildStart() {\n adoptHost(this)\n adoptWatchMode(this)\n\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 — not because\n // `watchChange` is missing there, which it is not on any Vite this\n // package supports, but because the declared peer range is wider than\n // what has been measured and the scheduler above makes a duplicate\n // trigger free.\n //\n // Skipped outright once the host has closed, because rollup discards the\n // result: with the task closed, `Task.run` returns before\n // `updateWatchedFiles`, so every path registered here goes nowhere. What\n // deriving it does still do is read each config file — with\n // `reportErrors: true` — and report an ENOENT for a project the host is\n // in the middle of tearing down. That report was the one thing this\n // block contributed after a close.\n if (!hostClosed) {\n const { paths } = await getWatchTargets(resolved)\n for (const file of paths) {\n this.addWatchFile(file)\n }\n }\n\n // Registering the watch list is all this hook does on webpack, and it\n // has to happen here rather than beside the compile: `addWatchFile`\n // reaches `compilation.fileDependencies`, and `beforeCompile` runs\n // before there is a compilation to add to. Compiling here as well would\n // put the race back, and run every webpack build twice.\n if (isWebpack) return\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 compileOnceAcrossInstances(resolved)\n hasCompiled = true\n },\n\n name: 'unplugin-style-dictionary',\n\n // `closeWatcher` is a rollup-shaped hook and `UnpluginOptions` declares no\n // top-level equivalent, so it is registered per target instead: rolldown\n // lists it among its input plugin hooks, and Vite's plugin type is\n // rollup's, which is what carries it to `vite build --watch`. Vite's dev\n // server runs no rollup watcher, so there it simply never fires.\n rolldown: { closeWatcher },\n\n rollup: { closeWatcher },\n\n vite: {\n closeWatcher,\n\n async configResolved(config) {\n if (rootOption === undefined) root = config.root || process.cwd()\n\n // The only host that has both. `command` is what makes `'serve'`\n // reachable at all, since nothing else here serves.\n hostCommand = config.command\n hostMode = config.mode\n\n // Vite's own logger, so the plugin's lines obey `customLogger` and\n // `clearScreen` like every other line the dev server prints. It\n // colours and prefixes its own output, which is why nothing painted\n // reaches it.\n //\n // Ahead of the early return below, because `vite build` needs the\n // logger just as much and takes that return.\n host = {\n error: (message) => {\n config.logger.error(message)\n },\n info: (message) => {\n config.logger.info(message)\n },\n }\n\n // Nothing below concerns a build: only the dev server has a watcher,\n // and only its ignore list needs amending.\n if (config.command !== 'serve') return\n\n // Ahead of the resolution below, so the `config` function a consumer\n // wrote is told `watch: true` on this call as well as on every later\n // one. Setting it in `configureServer` alone was correct until this\n // hook started resolving configurations too.\n isWatching = true\n\n // **This is the last hook that can reach the ignore list.** Vite\n // builds the watcher from the resolved config, and `configureServer`\n // runs after it exists — `server.watcher` is a parameter there — so a\n // negation added then changes nothing. Measured on Vite 6.4.3, 7.3.6\n // and 8.3.0: amending it here reaches the watcher on all three, and\n // amending it in `configureServer` does not.\n //\n // The resolution is kept for `configureServer` to reuse rather than\n // discarded, because resolving is how a `config` function gets called\n // and doing it twice in one start-up would call the consumer's code an\n // extra time for nothing.\n try {\n startupResolved = await resolveConfigs()\n if (startupResolved.length === 0) return\n\n const { paths } = await getWatchTargets(startupResolved)\n const negations = nodeModulesNegations(paths)\n if (negations.length === 0) return\n\n // Appended to whatever the consumer asked for, not replacing it.\n const existing = config.server.watch?.ignored\n config.server.watch = {\n ...config.server.watch,\n ignored: [\n ...(Array.isArray(existing)\n ? existing\n : existing === undefined\n ? []\n : [existing]),\n ...negations,\n ],\n }\n } catch (err) {\n // A configuration that cannot be resolved is the build's problem to\n // report, and it will: `buildStart` resolves again and fails there\n // with the host watching. Throwing here would fail the dev server\n // before it started, for the sake of a watch-list refinement.\n log(\n `Could not read the configuration while preparing the watch list: ${errorMessage(err)}`,\n 'error',\n )\n startupResolved = undefined\n }\n },\n\n async configureServer(server: ViteDevServer) {\n // A dev server watches, by definition. Said here rather than left to\n // `adoptWatchMode` because this hook runs *before* `buildStart` —\n // `createServer` calls it, and `buildStart` waits for the plugin\n // container — so the first `config` function of the process would\n // otherwise be told `watch: false` while a dev server started up\n // around it.\n isWatching = true\n\n // `configResolved` has already resolved these, on its way to amending\n // the watcher's ignore list. Taken rather than copied, so a later\n // rebuild re-resolves as it always did.\n const resolved = startupResolved ?? (await resolveConfigs())\n startupResolved = undefined\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 // Runs once per rebuild rather than once per event, which is why it\n // is handed to the scheduler rather than done in the listener.\n refreshServerWatchList = async (rebuilt) => {\n targets = await getWatchTargets(rebuilt)\n server.watcher.add(targets.paths)\n }\n\n if (errorOverlay) {\n // Whether the page is currently showing an overlay this plugin put\n // there. Only the clearing frame reads it: a success that follows a\n // success has no overlay to take down, and sending an update frame\n // for it would be traffic for nothing — and would spend the client's\n // one-time `isFirstUpdate`, which Vite uses to decide that an\n // overlay standing at the first update means a full reload.\n let overlayShowing = false\n\n notifyBuildOutcome = (error) => {\n if (error) {\n // Sent on every failure rather than only on the transition into\n // one. Vite's client replaces the overlay wholesale, so a repeat\n // is idempotent — and two different failures in a row must not\n // leave the first one's message on screen describing the second.\n overlayShowing = true\n server.hot.send({\n err: {\n message: error.message,\n plugin: 'unplugin-style-dictionary',\n stack: error.stack ?? '',\n },\n type: 'error',\n })\n return\n }\n\n if (!overlayShowing) return\n overlayShowing = false\n\n // Vite's protocol has no frame for \"take the overlay down\". The\n // client clears it when an update arrives, so an update carrying\n // nothing is the clear: it dismisses the overlay and then iterates\n // an empty list, reloading no page and touching no stylesheet.\n server.hot.send({ type: 'update', updates: [] })\n }\n }\n\n // chokidar types its listener as returning void and does not await\n // what it is handed, so an async listener left every rejection\n // floating. `schedule` owns the whole rebuild including its errors,\n // so there is nothing here left to reject.\n server.watcher.on('all', (_event, file) => {\n if (!isWatchedSource(file, targets.patterns)) return\n\n // A dev server has no build to fail, so a rebuild that throws is\n // reported by the scheduler and the server keeps serving.\n void schedule(path.basename(file)).catch(() => {})\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 adoptHost(this)\n adoptWatchMode(this)\n\n // Ahead of everything, including the flag below: a change reported\n // after the watcher closed earns no rebuild, so there is no re-entry\n // into `buildStart` for a flag to describe.\n if (hostClosed) return\n\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 // The cheap half of the decision, taken before anything is resolved.\n // Under Vite the scope this hook sees is the whole project root rather\n // than the module graph, so most of what arrives here has nothing to do\n // with tokens, and resolving every configuration only to discard the\n // answer ran a consumer's `config` function once per unrelated file.\n // Skipped until a build has derived a list to filter against.\n if (cachedPatterns && !isWatchedSource(id, cachedPatterns)) return\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Derived again rather than trusted from the cache, because the cache\n // is what decided this path was worth resolving and not what decides a\n // rebuild. A config edit reaches here through its own filename and can\n // have dropped the very source the cached list matched.\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 // Same division as `buildStart`: on webpack the compile belongs to\n // `beforeCompile`, which has already run for this compilation, so all\n // that is left is to re-register the watch list below.\n if (!isWebpack) await schedule(path.basename(id))\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 // unplugin calls this inside `apply(compiler)`, one line before it taps\n // `make`, so the root is in place before the first compile. Without it a\n // webpack build whose `context` is not the working directory looked for\n // the configuration in the wrong place and reported ENOENT.\n webpack(compiler) {\n if (rootOption === undefined) {\n root = compiler.options.context ?? process.cwd()\n }\n\n // webpack's `buildStart` context carries no `meta`, so neither half of\n // the build context can come from there. `mode` is a webpack option, and\n // `watchMode` is only true once `watch()` has been called — which is\n // after this runs, so it is read per compile below rather than here.\n hostMode = compiler.options.mode\n\n // The compile happens in `beforeCompile`, which webpack awaits *before*\n // the compilation exists — so a message from it has nothing to attach to\n // yet and is held until one appears.\n //\n // Only failures are routed. `stats` carries warnings and errors and\n // nothing else, so the progress lines stay on the console rather than\n // being reported as warnings they are not.\n //\n // A warning rather than an error, for the same reason as on rollup: this\n // is the report, and `failOnError` decides separately whether the build\n // stops. Pushing to `compilation.errors` would fail a webpack build that\n // asked not to be failed.\n const pending: string[] = []\n host = {\n error: (message) => {\n pending.push(message)\n },\n }\n\n compiler.hooks.compilation.tap(\n 'unplugin-style-dictionary',\n (compilation) => {\n for (const message of pending.splice(0)) {\n const reported = new Error(message)\n reported.name = 'UnpluginStyleDictionaryWarning'\n compilation.warnings.push(reported)\n }\n },\n )\n\n // A `beforeCompile` that throws ends the run without ever creating a\n // compilation, and that is exactly the case that produced the message.\n // Left to the buffer it would be reported nowhere at all, so whatever is\n // still held when the run ends goes to the console after all.\n const drainToConsole = () => {\n for (const message of pending.splice(0)) {\n console.error(paint('31', message, stderrColour))\n }\n }\n compiler.hooks.failed.tap('unplugin-style-dictionary', drainToConsole)\n compiler.hooks.done.tap('unplugin-style-dictionary', drainToConsole)\n\n // `beforeCompile` is awaited before the compilation exists, so the\n // tokens are on disk before webpack resolves the module that imports\n // them. Tapped on every compilation rather than only the first: a watch\n // rebuild needs the same guarantee, and a compile that renders what is\n // already there skips its own write.\n compiler.hooks.beforeCompile.tapPromise(\n 'unplugin-style-dictionary',\n async () => {\n isWatching = compiler.watchMode\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n await compileOnceAcrossInstances(resolved)\n hasCompiled = true\n },\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 JSON5 from 'json5'\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 { glob } from 'tinyglobby'\nimport { createUnplugin } from 'unplugin'\n\nimport type {\n StyleDictionaryConfigContext,\n UnpluginStyleDictionaryOptions,\n} from './types.js'\n\nimport { matchesWatchedFile } from './watch-filter.js'\n\nexport type * from './types.js'\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.\n// A rejected promise must carry an Error, and `catch` binds `unknown`. What\n// Style Dictionary throws is already one; anything else is wrapped rather than\n// handed on raw.\n// Where the plugin's own lines go when a host offers somewhere better than the\n// console: Vite's `config.logger`, rollup's and rolldown's plugin context, or\n// webpack's `compilation`.\n//\n// **There is no `error` channel that merely reports.** Rollup's `this.error`\n// aborts the bundle — measured: a `buildStart` calling it ends the run with\n// `THREW: [plugin err-probe] fatal?` — so routing a failure report through it\n// would stop every build that reported one and silently override `failOnError`,\n// whose entire job is deciding that. A failure is therefore reported on the\n// host's warning channel, and whether the build stops stays `failOnError`'s\n// decision alone.\ninterface HostMessenger {\n error: (message: string) => void\n\n // Optional because not every host has somewhere for a progress line to go.\n // webpack's `stats` carries warnings and errors and nothing else, and\n // `Compiling design tokens...` is neither — so there it stays on the\n // console rather than being dressed up as a warning.\n info?: (message: string) => void\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(errorMessage(error))\n}\n\n// Whether escapes may be written to this stream.\n//\n// **The three signals are ordered rather than combined into one conjunction**,\n// and that ordering is the whole of it. `FORCE_COLOR=1` on a non-TTY — a CI job\n// that wants colour in a log it will render itself — is the single job that\n// variable has, and\n// `!process.env.NO_COLOR && process.env.FORCE_COLOR !== '0' && stream.isTTY`\n// never honours it: the TTY check has the last word and answers `false`.\n//\n// `NO_COLOR` wins over `FORCE_COLOR` because the convention says so: any\n// non-empty value turns colour off, and nothing may turn it back on.\nfunction colourAllowed(stream: { isTTY?: boolean }): boolean {\n if (process.env.NO_COLOR) return false\n\n const forced = process.env.FORCE_COLOR\n if (forced === '0') return false\n if (forced !== undefined && forced !== '') return true\n\n // A terminal that has told us it cannot render escapes. Not one of the three\n // the issue named, but it is what `TERM=dumb` means and it costs a line.\n if (process.env.TERM === 'dumb') return false\n\n return stream.isTTY === true\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\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 host's message channel, narrowed by a predicate rather than asserted: what\n// a plugin context carries under `warn` is the host's business, and a cast\n// would only claim it is callable.\nfunction isMessageChannel(value: unknown): value is (message: string) => void {\n return typeof value === 'function'\n}\n\n// A hook is the consumer's code, and what it hands back is not this plugin's to\n// assume. A predicate rather than `instanceof Promise`, which answers `false`\n// for a thenable from another realm or from a promise library — exactly the\n// case where letting a rejection escape does the damage.\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function'\n )\n}\n\n// Whether a discovered file looks like a Style Dictionary configuration at all.\n//\n// Only applied to a file the plugin went looking for, never to one a consumer\n// named: an explicit `config` is their choice and second-guessing it would\n// reject shapes Style Dictionary accepts and this does not know about.\n//\n// `config.json` is an extremely common name for something else entirely, and\n// the plugin used to adopt whatever it found under that name, add it to the\n// watch set, and report a successful compile over it.\nfunction looksLikeConfig(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false\n\n // The four keys any usable configuration has at least one of. `platforms`\n // alone is enough because a configuration can declare its tokens inline\n // under `tokens`, or read them through `source`/`include`.\n return ['include', 'platforms', 'source', 'tokens'].some(\n (key) => key in value,\n )\n}\n\n// Vite builds its dev-server watcher with a fixed ignore list — `**/.git/**`,\n// `**/node_modules/**`, `**/test-results/**` and the cache directory — and\n// spreads the consumer's own `server.watch.ignored` entries in *after* them.\n// Entries are appended, never subtracted, so `server.watcher.add()` cannot\n// reach a path an earlier entry already covers.\n//\n// That makes a token package resolved through `node_modules` — the shape of\n// every workspace, `app/node_modules/@acme/tokens` symlinked to\n// `packages/tokens` — build correctly once and then never rebuild, with\n// nothing said about it. Measured on Vite 6.4.3, 7.3.6 and 8.3.0: zero watcher\n// events for an edit, while a token file outside the root but outside\n// `node_modules` rebuilt in the same run.\n//\n// A negation naming the file exactly is what un-ignores it, and is deliberately\n// the narrowest form that works. `!**/node_modules/**` would restore the whole\n// dependency tree to the watcher.\nfunction nodeModulesNegations(paths: string[]): string[] {\n const negations = new Set<string>()\n\n for (const file of paths) {\n const normalised = file.replace(/\\\\/g, '/')\n if (normalised.includes('/node_modules/')) negations.add(`!${normalised}`)\n }\n\n return Array.from(negations)\n}\n\n// The directories holding token files that resolve through `node_modules`.\n//\n// Vite's ignore list cannot be argued with on Windows. The negation below is\n// honoured on Linux and macOS, and there a token inside `node_modules` rebuilds\n// through the dev-server watcher like any other. On Windows it is not, and no\n// spelling of the negation changes that — measured on a `windows-latest`\n// runner: the file path, every ancestor directory, and the package subtree as\n// a globstar all leave the edit reaching no rebuild, while the same fixture\n// outside `node_modules` rebuilds. `server.watcher.add()` does not reach it\n// either, which is the same limit AGENTS.md already records for a path an\n// earlier ignore entry covers.\n//\n// A symlink is *not* what distinguishes them, which is worth stating because it\n// is the obvious suspect: a real directory inside `node_modules` fails exactly\n// as the symlinked one does, and a symlink outside it succeeds.\n//\n// So these directories get a watcher of the plugin's own, which Vite's ignore\n// list has no say over. It runs on every platform rather than behind a\n// `process.platform` check: one path that is exercised everywhere beats a\n// Windows-only branch that nothing else executes, and the scheduler already\n// collapses the duplicate trigger this produces where the negation also works.\nfunction nodeModulesWatchDirectories(paths: string[]): string[] {\n const directories = new Set<string>()\n\n for (const file of paths) {\n const normalised = file.replace(/\\\\/g, '/')\n if (!normalised.includes('/node_modules/')) continue\n\n // The directory rather than the file: `fs.watch` on a file stops reporting\n // once an editor replaces it by rename, which is what an atomic save does.\n directories.add(path.dirname(normalised))\n }\n\n return Array.from(directories)\n}\n\nfunction paint(code: string, value: string, allowed: boolean): string {\n return allowed ? `\\u001B[${code}m${value}\\u001B[0m` : value\n}\n\n// Which of a configuration's own `source`/`include` patterns match no file on\n// disk. Only for diagnosis: it is the emptiness of the resolved token set that\n// decides whether a build fails, because only that catches every route to an\n// empty set. This names the pattern at fault, which the token count cannot, and\n// it reports a mistyped pattern in a configuration whose others still match —\n// where nothing fails at all and one platform quietly loses its tokens.\nasync function patternsMatchingNothing(patterns: string[]): Promise<string[]> {\n const barren: string[] = []\n\n for (const pattern of patterns) {\n // A literal path is a `stat`, not a glob: `tinyglobby` treats a path with\n // no magic characters as a literal anyway, and this keeps the common case\n // off the filesystem walk.\n if (!GLOB_CHARACTERS.test(pattern)) {\n if (!fs.existsSync(pattern)) barren.push(pattern)\n continue\n }\n\n try {\n const matched = await glob([pattern], { absolute: true })\n if (matched.length === 0) barren.push(pattern)\n } catch {\n // A pattern that cannot even be globbed is the build's problem to\n // report; saying it twice, in a diagnostic, helps nobody.\n }\n }\n\n return barren\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\n// Windows refuses a rename over a destination another process holds open, and\n// that is exactly the case the atomic write exists to serve: measured on a\n// `windows-latest` runner, the suite's own concurrent-reader case fails with\n// `EPERM: operation not permitted, rename`. So the feature inverts — the\n// compile fails rather than the read being protected.\n//\n// This **refutes** the reasoning that put the case in doubt. It was argued that\n// libuv opens files with `FILE_SHARE_DELETE`, so a concurrent reader would most\n// likely not block the rename. It blocks it.\n//\n// The blocking handle is transient — a reader, an indexer, a virus scanner —\n// so a short bounded backoff clears it. The bound matters as much as the retry:\n// a rename that genuinely cannot succeed has to fail rather than hang a dev\n// server, and the existing failure path already reports and lets `failOnError`\n// decide.\n//\n// Unreachable on Linux and macOS, where a rename over an open file succeeds.\nconst RENAME_RETRY_CODES = new Set(['EBUSY', 'EPERM'])\n\n// Doubling rather than a fixed interval, so the common case — a handle already\n// gone by the first retry — costs a millisecond rather than the whole budget.\n//\n// **The total is sized by what a dev server can tolerate waiting, not by how\n// long a handle usually persists.** An earlier version stopped at about 255ms,\n// which clears a transient hold and is not the situation that matters: a\n// consumer polling the generated file holds it for a large fraction of wall\n// time, and then no budget wins every race. What that produced was a rebuild\n// that failed roughly once in a hundred renames — which the suite's own\n// concurrent-reader case turns into a failure about once a run, because it\n// performs twenty of them. Intermittent, and on a platform where the whole\n// point of the atomic write is that a reader never sees a partial file.\n//\n// About two seconds is therefore the bound. A rebuild that takes a second is\n// something a dev server absorbs; one that fails is not. `write-file-atomic`\n// and `graceful-fs` both take this approach, the latter retrying for up to a\n// minute, so this is still the conservative end.\n//\n// The bound stays a bound: a rename that genuinely cannot succeed has to fail\n// rather than hang, and the unguarded attempt after the loop is what makes it.\nconst RENAME_RETRY_DELAYS_MS = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]\n\n// **The synchronous path gets a shorter budget, and the asymmetry is the\n// point.** `renameWithRetry` waits on a timer, so the event loop keeps serving\n// while it does; `renameWithRetrySync` waits on `Atomics.wait`, which blocks\n// everything — a dev server holding its main thread for two seconds is worse\n// than the failed rebuild the wait is trying to avoid.\n//\n// It is reached only through a Style Dictionary custom action's own\n// `vol.writeFileSync`, which is rare, and it keeps roughly the budget the async\n// path had before this change.\nconst RENAME_RETRY_DELAYS_SYNC_MS = [1, 2, 4, 8, 16, 32, 64, 128]\n\nfunction isRetryableRenameError(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n typeof error.code === 'string' &&\n RENAME_RETRY_CODES.has(error.code)\n )\n}\n\n// `Atomics.wait` rather than a spin on `Date.now()`, because the sync path has\n// no event loop to yield to and a busy loop would hold the CPU for the whole\n// backoff — on the one platform where the handle it is waiting for belongs to\n// another process.\nconst sleepSync = (ms: number): void => {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)\n}\n\nasync function renameWithRetry(\n temporary: string,\n destination: string,\n): Promise<void> {\n for (const delay of RENAME_RETRY_DELAYS_MS) {\n try {\n await fs.promises.rename(temporary, destination)\n return\n } catch (err) {\n if (!isRetryableRenameError(err)) throw err\n await new Promise((resolve) => setTimeout(resolve, delay))\n }\n }\n\n // The last attempt is deliberately outside the loop and unguarded: the bound\n // is a bound, so whatever it throws here is what the caller sees.\n await fs.promises.rename(temporary, destination)\n}\n\nfunction renameWithRetrySync(temporary: string, destination: string): void {\n for (const delay of RENAME_RETRY_DELAYS_SYNC_MS) {\n try {\n fs.renameSync(temporary, destination)\n return\n } catch (err) {\n if (!isRetryableRenameError(err)) throw err\n sleepSync(delay)\n }\n }\n\n fs.renameSync(temporary, destination)\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 renameWithRetry(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 renameWithRetrySync(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 config extensions Style Dictionary loads with `import` rather than by\n// parsing the file — the `case` list in its own `loadFile`. They are the only\n// ones Node's permanent module cache applies to, and so the only ones this\n// plugin has to read on the build's behalf.\n//\n// Everything else Style Dictionary parses as JSON5, including `.json`, and\n// this list is what makes the plugin split the same way. Reading the two\n// halves apart is what silently unwatched a whole family of configurations:\n// a `.json5` or `.jsonc` file went down the import branch and failed there\n// while the build succeeded, and a `.json` file carrying a comment or a\n// trailing comma failed strict `JSON.parse` for the same reason.\nconst IMPORTED_CONFIG_EXTENSIONS = ['.js', '.mjs', '.ts']\n\n// A configuration as `resolveConfigs` hands it on: either the object the\n// consumer passed or the path it was read from, plus the directory relative\n// paths inside it resolve against.\ninterface ResolvedConfig {\n config: Config | string\n file?: string\n}\n\n// How to name a configuration in a message. A path is what a consumer\n// recognises; a configuration passed as an object or returned by a function has\n// no name, so it is identified by where it sits in the list rather than by a\n// stringified dump of itself.\nfunction describeConfig(item: ResolvedConfig, index: number): string {\n return item.file\n ? `The configuration ${item.file}`\n : `The configuration at position ${index + 1}`\n}\n\n// Whether a config path is one Style Dictionary imports rather than parses.\nfunction isImportedConfig(file: string): boolean {\n return IMPORTED_CONFIG_EXTENSIONS.some((extension) =>\n file.endsWith(extension),\n )\n}\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\n// The fingerprints of configurations this process has compiled at least once.\n// It is what lets a configuration given as an object or a function be skipped\n// at all: such a configuration has no file to stat, so an edit to it inside\n// `vite.config.ts` moves no mtime and the filesystem cannot tell the two\n// apart. Having built it here, the plugin can — the fingerprint changes with\n// the configuration.\n//\n// Module scope rather than the factory's, for the same reason `compilesInFlight`\n// below is: the instances that would otherwise repeat the work are different\n// instances, so per-instance state cannot see them. A `vitest run` stands up\n// several, and a function configuration — the form the README recommends for\n// registering custom formats — would be the one form that never skipped.\n//\n// The fingerprint carries the root, so two projects in one process never share\n// one. A configuration given as a path needs none of this: its own file is one\n// of the sources the mtime comparison reads, so an edit to it is visible across\n// processes as well as within one.\nconst compiledFingerprints = new Set<string>()\n\n// A compile that is running right now, keyed by `buildKey`, so bundler\n// instances in one process wait on each other rather than each starting their\n// own.\n//\n// Generated token files are a side effect on the filesystem, not per-bundler\n// output, and one process routinely holds several instances of this plugin. A\n// single `vitest run` on a project with two test projects and browser mode\n// stands up five Vite servers — the root one, one per project, and one more\n// per project once its HTTP server listens — and every one of them runs\n// `buildStart`. `hasCompiled` cannot see any of that: it is closure state\n// inside the factory, so each instance has its own and each compiles.\n//\n// Module scope is the only place a shared answer can live, since the\n// instances know nothing about each other. It stays a claim about identical\n// work, never about identity: the key carries the root and the resolved\n// configurations, so one script building two packages shares nothing.\nconst compilesInFlight = new Map<string, Promise<void>>()\n\n// The slice of a webpack-shaped compiler this plugin touches, named rather\n// than imported. webpack and rspack each ship their own `Compiler` type, and\n// neither is assignable to the other, so a hook written against one cannot be\n// handed to the other's key. Both satisfy this structurally, which is what\n// makes `adoptCompiler` one function instead of two copies drifting apart.\ninterface BundlerCompiler {\n hooks: {\n beforeCompile: {\n tapPromise: (name: string, handler: () => Promise<void>) => void\n }\n compilation: {\n tap: (name: string, handler: (compilation: Compilation) => void) => void\n }\n done: { tap: (name: string, handler: () => void) => void }\n failed: { tap: (name: string, handler: () => void) => void }\n }\n options: { context?: string | undefined; mode?: string | undefined }\n watchMode: boolean\n}\n\n// Only the one array a report is pushed onto. webpack types it `WebpackError[]`\n// and rspack `Error[]`; both are arrays of something extending `Error`, so a\n// plain one is what this pushes on either.\ninterface Compilation {\n warnings: Error[]\n}\n\n// A stable identity for a set of resolved configurations, or `null` for one\n// that cannot have a stable identity at all.\n//\n// Functions are serialised by source rather than dropped, because a `format`\n// or `transform` written inline is exactly what distinguishes two otherwise\n// identical configurations — and `JSON.stringify` omits a function outright,\n// which would make two different builds look like one.\nfunction buildKey(root: string, resolved: ResolvedConfig[]): null | string {\n try {\n return JSON.stringify(\n [root, resolved.map((item) => item.file ?? item.config)],\n (_key, value: unknown) =>\n typeof value === 'function' ? `[fn]${String(value)}` : value,\n )\n } catch {\n // A configuration that will not serialise — a circular reference, a\n // BigInt — takes no shared identity rather than a wrong one, and compiles\n // exactly as it did before.\n return null\n }\n}\n\n// The patterns one configuration reads, resolved the way the build resolves\n// them. The same `source`/`include` walk `getWatchTargets` does, for one\n// item rather than the whole set — against the working directory, because\n// that is where Style Dictionary's own `combineJSON` globs them.\nfunction sourcePatternsOf(configObj: Config): string[] {\n const patterns: string[] = []\n\n const add = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n patterns.push(\n (path.isAbsolute(pattern)\n ? pattern\n : path.resolve(process.cwd(), pattern)\n ).replace(/\\\\/g, '/'),\n )\n }\n }\n\n for (const value of [configObj.source, configObj.include]) {\n if (Array.isArray(value)) value.forEach(add)\n else add(value)\n }\n\n return patterns\n}\n\n// `fs.statSync` without the throw. A file that is missing, or that cannot be\n// read, is the same answer to every caller here: nothing to compare against.\nfunction statOrNull(file: string): fs.Stats | null {\n try {\n return fs.statSync(file)\n } catch {\n return null\n }\n}\n\n// Not exported. It cannot be called in the form a reader would guess —\n// unplugin types the factory as `(options, meta)`, and `meta` is the\n// bundler-identifying `UnpluginContextMeta` a consumer would have to build by\n// hand — so publishing it offered a name that answered nothing. What a\n// consumer imports is the default export of the entry for their bundler.\nconst unpluginFactory: UnpluginFactory<\n undefined | UnpluginStyleDictionaryOptions,\n false\n> = (options = {}, meta) => {\n // webpack and rspack are the targets whose `buildStart` does not run before\n // the module graph is resolved: unplugin taps it on `make`, an\n // `AsyncParallelHook` that `EntryPlugin` taps too. The `webpack` and\n // `rspack` keys below compile on `beforeCompile` instead, which both await\n // before the compilation exists. rspack reimplements webpack's plugin API,\n // so everything this plugin does with a compiler is the same on either —\n // but unplugin dispatches them by separate keys, so the flag names both.\n const isWebpack = meta.framework === 'webpack' || meta.framework === 'rspack'\n const {\n cache = true,\n errorOverlay = true,\n failOnError = 'build',\n logLevel,\n onBuildEnd,\n onBuildError,\n onBuildStart,\n platforms: platformsOption,\n report = true,\n root: rootOption,\n silent = false,\n } = options\n\n // `silent` predates `logLevel` and names its quietest level, so it is read\n // as one. `logLevel` wins when a consumer sets both.\n const level = logLevel ?? (silent ? 'silent' : undefined)\n\n // Whether the plugin keeps its own progress lines and size table to itself.\n // A failure is reported at every level, which is why this gate is not on the\n // error branch below.\n const quiet = level === 'silent' || level === 'warn'\n\n // What Style Dictionary is told, if anything. `undefined` is the point of\n // this: it leaves whatever the consumer's own `log.verbosity` asked for\n // standing, where the plugin used to overwrite it on every build. Style\n // Dictionary has three levels to this option's four, so `'warn'` and\n // `'info'` both map to its default — they differ in what the plugin itself\n // says, not in what Style Dictionary does.\n const verbosity =\n level === undefined\n ? undefined\n : level === 'verbose'\n ? 'verbose'\n : level === 'silent'\n ? 'silent'\n : 'default'\n\n // Which platforms this compile covers, or `undefined` for all of them.\n //\n // The array form applies to every build; the object form splits the first\n // compile from the watch rebuilds, and `context` is what tells them apart —\n // only the rebuild paths pass one. An absent key means every platform, so\n // `{ watch: ['css'] }` builds everything once and then only css.\n const platformsFor = (context: string | undefined): string[] | undefined => {\n if (platformsOption === undefined) return undefined\n if (Array.isArray(platformsOption)) return platformsOption\n\n return context === undefined ? platformsOption.build : platformsOption.watch\n }\n\n // Whether a failure in this compile should be thrown rather than only\n // reported. The two compiles are told apart by `runBuilds`'s `context`,\n // which only the rebuild paths pass.\n const failsTheBuild = (context: string | undefined): boolean =>\n failOnError === true ||\n (context === undefined ? failOnError === 'build' : failOnError === 'serve')\n // What the host is doing, for the function form of `config`. Populated where\n // each host knows the answer and read when that function is called — the\n // same shape as `root` and the message host above, and for the same reason:\n // `resolveConfigs` is reached from five places now, and threading a context\n // parameter through all five would make every caller restate what only the\n // host can say.\n let hostCommand: 'build' | 'serve' = 'build'\n let hostMode: string | undefined\n let isWatching = false\n\n // `mode` is derived rather than invented where a host has no notion of one.\n // rollup and rolldown report nothing, and following `command` is the answer\n // Vite itself would give: its default mode is `development` serving and\n // `production` building.\n const configContext = (): StyleDictionaryConfigContext => ({\n command: hostCommand,\n mode: hostMode ?? (hostCommand === 'serve' ? 'development' : 'production'),\n watch: isWatching,\n })\n\n // Whether the host will keep rebuilding, as the plugin context reports it.\n // Read from `meta.watchMode`, which rollup, rolldown and Vite all carry and\n // webpack does not — there it comes off the compiler instead.\n const adoptWatchMode = (context: object): void => {\n const hookMeta: unknown = 'meta' in context ? context.meta : undefined\n if (typeof hookMeta !== 'object' || hookMeta === null) return\n\n const watching: unknown =\n 'watchMode' in hookMeta ? hookMeta.watchMode : undefined\n if (typeof watching === 'boolean') isWatching = watching\n }\n\n // Where a relative `config` path is looked up. The host sets it below\n // unless the consumer named one, which is why an explicit option wins: a\n // layout the host cannot describe is exactly what it is for.\n let root = rootOption\n ? path.resolve(process.cwd(), rootOption)\n : 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 // The patterns the last `getWatchTargets` derived. `watchChange` tests a\n // changed path against these before it resolves anything, so a file the\n // plugin does not care about costs one glob match instead of a full config\n // resolution — which, when `config` is a function, is the consumer's own\n // code, and the place the README tells them to register custom formats.\n //\n // It is safe to filter on a list that may be one build out of date because\n // the list always contains the config files themselves: an edit that adds a\n // source matches as a config change, which re-resolves and re-derives. The\n // one thing it cannot see is a `config` function that starts returning\n // different sources with no file changing at all, and that was never\n // observable without a rebuild to observe it in.\n let cachedPatterns: string[] | undefined\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 // Whether the host has shut its watcher down. `await watcher.close()` is not\n // a promise that no build is in flight: rollup's `Watcher.close` clears the\n // pending build timeout, closes each task's file watcher and emits `close`,\n // and never awaits `run` — while `Task.run` checks `closed` only *after*\n // `rollupInternal` has resolved. So a build that has already entered\n // `rollupInternal` runs its `buildStart` hooks through to completion after\n // `close()` has returned to its caller, against a project that may be half\n // torn down by then. Measured on rollup 4.63.3 with no plugin of ours: a\n // `buildStart` reading a file 300ms after `close()` resolved gets ENOENT.\n //\n // `closeWatcher` is what makes that answerable. It runs synchronously inside\n // `close()`, and so before the in-flight hook resumes, which is the whole\n // reason a flag set there is worth setting. What it must not be is\n // `closeBundle`: that fires once per bundle — every `BUNDLE_END` a consumer\n // calls `result.close()` on — and would read as a shutdown on every rebuild.\n let hostClosed = 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 // Decided once, when the plugin is constructed, and held for its life. The\n // two streams are asked separately because they are redirected separately —\n // `build 2>err.log` leaves stdout a terminal and stderr a file.\n const stdoutColour = colourAllowed(process.stdout)\n const stderrColour = colourAllowed(process.stderr)\n\n // Where a message goes once a host has offered somewhere better than the\n // console. Set by `configResolved` under Vite, by the build hooks under\n // rollup and rolldown, and by the `webpack` block; left undefined when no\n // host has claimed it, which is every unit test binding its own context.\n let host: HostMessenger | undefined\n\n // Adopts a plugin context as the message host, if it has the channels — the\n // unit tests bind a context carrying `addWatchFile` and nothing else, and a\n // hook calling `this.warn` against that throws in a way that reads as a\n // plugin bug rather than as a missing stub.\n //\n // Only when nothing has claimed the host yet. Under Vite `configResolved`\n // has already installed the dev server's own logger, and `buildStart` runs\n // after it with a rollup-shaped context that would otherwise replace it.\n const adoptHost = (context: object): void => {\n if (host) return\n\n const warn: unknown = 'warn' in context ? context.warn : undefined\n if (!isMessageChannel(warn)) return\n\n const info: unknown = 'info' in context ? context.info : undefined\n\n host = {\n // `warn`, never `error`. Rollup's `this.error` aborts the bundle, so\n // reporting through it would stop every build that reported anything and\n // take the decision `failOnError` exists to make.\n error: (message) => {\n warn.call(context, message)\n },\n info: isMessageChannel(info)\n ? (message) => {\n info.call(context, message)\n }\n : undefined,\n }\n }\n\n // Helper to log at the configured level\n const log = (\n message: string,\n type: 'error' | 'info' | 'success' = 'info',\n ) => {\n const prefix = '[unplugin-style-dictionary]'\n\n // Ahead of the `silent` gate on purpose. `silent` is about the progress\n // lines and the size table; a compile that failed is not noise, and\n // hiding it left a broken token set shipping with nothing said at all.\n if (type === 'error') {\n // The host renders and colours its own output, so nothing painted here\n // is handed to one — an escape inside a webpack `stats` entry survives\n // into `stats.toJson()` and into whatever reads it.\n if (host) {\n host.error(`${prefix} ${message}`)\n return\n }\n\n console.error(paint('31', `${prefix} ${message}`, stderrColour))\n return\n }\n\n if (quiet) return\n\n if (host?.info) {\n host.info(`${prefix} ${message}`)\n return\n }\n\n console.log(\n paint(\n type === 'success' ? '32' : '36',\n `${prefix} ${message}`,\n stdoutColour,\n ),\n )\n }\n\n // Runs one of the consumer's `onBuild*` hooks without letting it decide the\n // fate of the build that called it.\n //\n // Two ways a hook can go wrong, and neither may propagate. A throw is caught\n // here, because a post-processing step that fails must not undo a compile the\n // plugin itself completed — the files are written and correct. A rejected\n // promise is the quieter one: the return value is deliberately not awaited,\n // so a rejection has nothing holding it and reaches the host as an unhandled\n // rejection, which under Node's default takes the process down — a dev server\n // killed from inside a hook that was only meant to reformat a file.\n //\n // Both are reported at `'error'`, so they are said at every level including\n // `silent`, and worded so neither can be read as the compile having failed.\n const callHook = <A extends unknown[]>(\n name: string,\n hook: (...args: A) => Promise<void> | void,\n ...args: A\n ): void => {\n let result: unknown\n\n try {\n // Captured rather than dropped, because the promise an `async` hook\n // returns is the thing the check below needs. Wrapping this call in a\n // block-bodied arrow — which is what the linter asks for when the return\n // type is plain `void` — discarded it, and the rejection escaped exactly\n // as it had before any of this existed.\n result = hook(...args)\n } catch (err) {\n log(`The ${name} hook threw: ${errorMessage(err)}`, 'error')\n return\n }\n\n if (!isThenable(result)) return\n\n void Promise.resolve(result).catch((err: unknown) => {\n log(`The ${name} hook rejected: ${errorMessage(err)}`, 'error')\n })\n }\n\n // Resolve config file paths / objects\n const resolveConfigs = async (): Promise<ResolvedConfig[]> => {\n let rawConfig = options.config\n\n // Checked ahead of the discovery below, and by identity rather than\n // truthiness: `false` is falsy, so the `!rawConfig` test that triggers\n // discovery would treat \"do not discover anything\" as \"go and look\".\n if (rawConfig === false) return []\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\n const rejected: string[] = []\n\n for (const file of defaults) {\n const fullPath = path.resolve(root, file)\n if (!fs.existsSync(fullPath)) continue\n\n // Read before adopting. For the two `.json` names this is a parse and\n // nothing more; for the two module names it is an import, and the\n // module has already run by the time there is anything to check —\n // which is what `config: false` exists for and why validation alone\n // does not cover them.\n const candidate = await readConfigObject(\n { config: fullPath, file: fullPath },\n false,\n )\n\n if (!looksLikeConfig(candidate)) {\n rejected.push(file)\n continue\n }\n\n // Announced, because \"which configuration did it pick\" was not\n // answerable from the console at all, and discovery picks from four\n // generic names.\n if (!announcedDiscovery) {\n announcedDiscovery = true\n log(`Using the configuration it found at ${fullPath}`, 'info')\n }\n\n rawConfig = file\n break\n }\n\n // Said whether or not something usable turned up after them. A skipped\n // candidate is the interesting half of \"no configuration found\": the\n // file is right there, and the reason it was not used is not guessable.\n if (rejected.length > 0) {\n log(\n `Ignored ${rejected.join(', ')} in ${root}: nothing there declares platforms, source, include or tokens, so it does not look like a Style Dictionary configuration. Name it with the config option if it is one, or set config to false to stop looking.`,\n 'error',\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(configContext())\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 { config: fullPath, file: fullPath }\n } else {\n return { config: conf }\n }\n })\n }\n\n // Imports a config module, re-evaluating it only when the file itself has\n // changed. The query string is what decides that, and it is not decoration:\n // Node's ESM cache is permanent and keyed on the specifier, so a config\n // imported without one is evaluated once and never read again — which is\n // how an edited `.mjs` config went on building the platform map the process\n // started with, for the rest of the session.\n //\n // `Date.now()` fixed that staleness and bought two problems. Every watcher\n // event registered another module record in a map nothing prunes, re-running\n // the config's own `registerFormat` side effects for a file nobody touched.\n // And its millisecond granularity meant an edit landing inside the same\n // millisecond as the previous import shared that import's key, and was\n // served the old module anyway. `mtimeMs` carries sub-millisecond\n // resolution and only moves when the file does.\n const importConfigModule = async (file: string): Promise<unknown> => {\n let version: number\n try {\n version = fs.statSync(file).mtimeMs\n } catch {\n // A config that cannot be stat'd is about to fail its import too. The\n // old key is what keeps that failure the import's to report.\n version = Date.now()\n }\n\n // The dot goes, and that is not cosmetic. `mtimeMs` is fractional, so the\n // query it produces ends in something that reads as a file extension to\n // anything deriving a loader from the specifier without stripping the\n // query first — `sd.config.ts?t=1789565080284.6606` is then a `.6606`\n // file, and a TypeScript config gets parsed as JavaScript. Replacing the\n // one dot keeps every distinct mtime a distinct key.\n const key = String(version).replace('.', '_')\n\n // Sequential on purpose: a config module runs arbitrary code at import\n // time — `registerFormat` and friends — and Style Dictionary's registries\n // are global, so importing several at once would interleave those\n // registrations.\n return unwrapDefault(await import(`${pathToFileURL(file).href}?t=${key}`))\n }\n\n // What a configuration item says, as an object. `report` is what stops the\n // two readers of this from saying the same thing twice: a bad config has\n // nowhere else to surface when the watch list is being built, while a build\n // falls back to handing Style Dictionary the path and lets its message\n // through instead.\n const readConfigObject = async (\n item: ResolvedConfig,\n reportErrors: boolean,\n ): Promise<Config | null> => {\n if (typeof item.config !== 'string') return item.config\n\n try {\n // JSON5 rather than `JSON.parse`, because that is what Style Dictionary\n // reads these files with — it is a superset, so a plain `.json` config\n // parses identically and one carrying a comment stops being a config\n // the build understands and the watch list does not.\n const loaded: unknown = isImportedConfig(item.config)\n ? await importConfigModule(item.config)\n : JSON5.parse(fs.readFileSync(item.config, 'utf-8'))\n\n if (isConfig(loaded)) return loaded\n\n if (reportErrors) {\n log(\n `Config file did not resolve to a configuration object: ${item.config}`,\n 'error',\n )\n }\n } catch (err) {\n if (reportErrors) {\n log(\n `Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}`,\n 'error',\n )\n }\n }\n\n return null\n }\n\n // Parse token files to watch\n const getWatchTargets = async (\n resolvedConfigs: ResolvedConfig[],\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 const configObj = await readConfigObject(item, true)\n\n if (configObj) {\n const addPattern = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n // Against the working directory, because that is where Style\n // Dictionary resolves it: `combineJSON` globs each pattern with\n // no `cwd` of its own. Resolving against the configuration file's\n // directory instead is how the watch list came to name paths the\n // build never reads — a configuration in a subdirectory built\n // correctly and watched nothing at all.\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(process.cwd(), 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 // Recorded here rather than at each call site, so every path that derives\n // a watch list refreshes the one `watchChange` filters against.\n cachedPatterns = patterns\n\n return { paths: await expandPatterns(patterns), patterns }\n }\n\n // What `new StyleDictionary` is handed for an item. Only a path in the JS\n // family becomes an object, because those are exactly the extensions Style\n // Dictionary's own `loadFile` reaches with `import` — the ones whose module\n // record Node then caches forever, and so the only ones a build could read\n // stale. The JSON5 family stays a path because there is nothing to gain:\n // those are read from disk on every pass either way, so a build can never\n // see one as it stood earlier in the process.\n const configForBuild = async (\n item: ResolvedConfig,\n ): Promise<Config | string> => {\n const { config } = item\n\n if (typeof config !== 'string' || !isImportedConfig(config)) return config\n\n const loaded = await readConfigObject(item, false)\n\n // A config that could not be read falls back to the path, so the failure\n // stays Style Dictionary's to report — it knows more about why an import\n // failed than this does, a `.ts` config without type stripping especially.\n if (!loaded) return item.config\n\n // `loadFile` clones what it imports before handing it on, and passing an\n // object skips that. It matters more here than it does there: the module\n // record now outlives the build, and `extend` is called with\n // `mutateOriginal`. Cloning throws on a config carrying functions — an\n // inline transform — and Style Dictionary's own fallback in that case is\n // to use the original, so this one matches it.\n try {\n return structuredClone(loaded)\n } catch {\n return loaded\n }\n }\n\n // Every absolute destination a configuration declares, read off the\n // configuration itself rather than off an extended Style Dictionary\n // instance. Reading it here is the whole point: constructing the instance\n // is what the skip exists to avoid.\n //\n // Resolved exactly as the build resolves it below, so the two name the same\n // files — a relative `buildPath` against `root`, and a `destination`\n // against that.\n // `only` narrows this to named platforms, and exactly one caller wants that:\n // the up-to-date check, which asks whether the work *this* compile would do\n // is already done. Everywhere else the answer has to cover every declared\n // platform, because a file an unselected platform wrote earlier is still the\n // plugin's own output and has to stay out of the watch list.\n const declaredDestinations = (\n configObj: Config,\n only?: string[],\n ): string[] => {\n const destinations: string[] = []\n\n const entries = Object.entries(configObj.platforms ?? {})\n const selected = only\n ? entries.filter(([name]) => only.includes(name))\n : entries\n\n for (const [, platform] of selected) {\n const buildPath = platform.buildPath ?? ''\n const absoluteBuildPath = path.isAbsolute(buildPath)\n ? buildPath\n : path.resolve(root, buildPath)\n\n for (const file of platform.files ?? []) {\n if (file.destination) {\n destinations.push(\n path.isAbsolute(file.destination)\n ? file.destination\n : path.resolve(absoluteBuildPath, file.destination),\n )\n }\n }\n }\n\n return destinations\n }\n\n // A stable identity for one resolved configuration, or `null` where it\n // cannot have one. Functions are serialised by source rather than dropped,\n // because an inline `format` or `transform` is exactly the edit a\n // fingerprint has to notice, and `JSON.stringify` omits a function outright.\n const configFingerprint = (item: ResolvedConfig): null | string => {\n try {\n return JSON.stringify(\n [root, item.file ?? item.config],\n (_key, value: unknown) =>\n typeof value === 'function' ? `[fn]${String(value)}` : value,\n )\n } catch {\n // Circular, or holding a BigInt. It takes no identity rather than a\n // wrong one, so it compiles every time exactly as it did before.\n return null\n }\n }\n\n // Whether every file a configuration declares is already newer than every\n // file it reads, so its compile can be skipped.\n //\n // Conservative in every direction it can be: anything it cannot establish —\n // a destination that is missing, a source it cannot stat, a configuration\n // declaring no destinations at all — is a reason to build rather than to\n // skip.\n const isUpToDate = async (\n item: ResolvedConfig,\n configObj: Config,\n only?: string[],\n ): Promise<boolean> => {\n // An action writes what no `destination` names, so there is nothing for\n // the comparison below to check and skipping would leave its work undone.\n const hasActions = Object.values(configObj.platforms ?? {}).some(\n (platform) => (platform.actions?.length ?? 0) > 0,\n )\n if (hasActions) return false\n\n const destinations = declaredDestinations(configObj, only)\n if (destinations.length === 0) return false\n\n // `options.watch` belongs in here as much as `source` does. A consumer\n // names an extra file because something in the build reads it — a custom\n // format's own data file, most obviously — and leaving it out let a change\n // to it be skipped over while the watcher dutifully reported it.\n const extraWatches = options.watch\n ? Array.isArray(options.watch)\n ? options.watch\n : [options.watch]\n : []\n\n const sources = await expandPatterns([\n ...sourcePatternsOf(configObj),\n ...extraWatches.map((pattern) =>\n (path.isAbsolute(pattern)\n ? pattern\n : path.resolve(root, pattern)\n ).replace(/\\\\/g, '/'),\n ),\n ])\n if (item.file) sources.push(item.file.replace(/\\\\/g, '/'))\n\n if (sources.length === 0) return false\n\n let newestSource = -Infinity\n let sawFile = false\n\n for (const source of sources) {\n const stats = statOrNull(source)\n if (!stats) return false\n\n // Directories are in this list on purpose — `expandPatterns` registers\n // each pattern's static parent so a token file created later is\n // noticed — but their mtime cannot be read as an input signal here. A\n // directory's mtime moves whenever an entry is added or renamed inside\n // it, and the atomic write renames every generated file into place, so\n // a `buildPath` inside a watched directory made the build itself the\n // newest thing the comparison could see. Nothing was ever up to date.\n if (stats.isDirectory()) continue\n\n sawFile = true\n newestSource = Math.max(newestSource, stats.mtimeMs)\n }\n\n // Every pattern expanded to directories alone, so nothing was actually\n // read. Style Dictionary would build an empty dictionary from that, and a\n // skip would present the empty result as current.\n if (!sawFile) return false\n\n let oldestDestination = Infinity\n for (const destination of destinations) {\n const stats = statOrNull(destination)\n if (!stats) return false\n oldestDestination = Math.min(oldestDestination, stats.mtimeMs)\n }\n\n if (oldestDestination <= newestSource) return false\n\n // A configuration given as a path has its own file among the sources\n // above, so an edit to it has already been accounted for and the skip\n // holds across processes.\n if (item.file) return true\n\n // One given as an object or a function has not. Only this process knows\n // what it looked like when those destinations were written, so the skip\n // holds only against a fingerprint recorded here.\n const fingerprint = configFingerprint(item)\n\n return fingerprint !== null && compiledFingerprints.has(fingerprint)\n }\n\n // The size-and-gzip table, in a function of its own so that the compile\n // `try` in `runBuilds` can stop before it. Everything here is presentation\n // over files Style Dictionary has already finished writing, so a throw from\n // it is a reporting bug and nothing more.\n const reportSizes = (generatedFiles: Set<string>) => {\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.relative(root, filePath).replace(/\\\\/g, '/')\n const dir = path.dirname(displayPath)\n const base = path.basename(displayPath)\n // The table goes to stdout, so it follows stdout's decision — which\n // is not always stderr's, since the two are redirected separately.\n const coloredPath =\n dir === '.'\n ? paint('32', base, stdoutColour)\n : paint('90', `${dir}/`, stdoutColour) +\n paint('32', base, stdoutColour)\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 // One unreadable destination costs its row rather than the table.\n // Deliberately narrower than the caller's `catch`: it covers the\n // three filesystem and gzip calls above and not the arithmetic\n // below, so a padding bug is reported rather than quietly printing\n // short.\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(2, longestPathLength - info.relativeDisplayPath.length + 2),\n )\n const sizePadded = info.sizeStr.padStart(longestSizeLength)\n console.log(\n info.coloredPath +\n pathPadding +\n paint(\n '90',\n `${sizePadded} │ gzip: ${info.gzipSizeStr}`,\n stdoutColour,\n ),\n )\n }\n }\n }\n\n // Compile design tokens\n const runBuilds = async (\n resolvedConfigs: ResolvedConfig[],\n context?: string,\n ) => {\n const startTime = Date.now()\n\n // Ahead of the `try` rather than inside it, because the reporting below\n // reads it and that reporting is deliberately outside.\n const generatedFiles = new Set<string>()\n\n // How many configurations were already up to date. Read by the reporting\n // below, which is why it sits out here with `generatedFiles`.\n let skipped = 0\n\n try {\n if (!context) {\n log('Compiling design tokens...', 'info')\n }\n\n // Before anything is resolved or built, and once per build — a watch\n // rebuild is a build, so this fires again for each one.\n if (onBuildStart) callHook('onBuildStart', onBuildStart)\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 [index, item] of resolvedConfigs.entries()) {\n // Read ahead of the instance, because avoiding the instance is the\n // point: construction plus `extend` is the 15-30% of a build that\n // parses the token sources, and `buildAllPlatforms` is the rest.\n //\n // `false` so a configuration that will not parse says nothing here —\n // the build below hands Style Dictionary the path and lets its own\n // message through, which is more specific than anything this could\n // say.\n const declared = cache ? await readConfigObject(item, false) : null\n const selectedPlatforms = platformsFor(context)\n\n if (declared && (await isUpToDate(item, declared, selectedPlatforms))) {\n // The destinations still have to be collected. They are what stops\n // the plugin's own output being treated as a watched source, so a\n // skipped configuration that contributed none would have its files\n // rebuild the moment a watcher noticed them.\n for (const destination of declaredDestinations(declared)) {\n generatedFiles.add(destination)\n }\n\n skipped++\n continue\n }\n\n // `{ init: false }` is the escape hatch Style Dictionary documents on\n // this constructor, and it is what makes a bad configuration\n // catchable. Left to itself the constructor ends in a call to\n // `init()` whose promise it neither stores nor returns, so a config\n // that fails to load rejects a promise nobody holds: the `catch`\n // below never runs, and the host dies with a raw stack or — where an\n // `unhandledRejection` handler suppresses it — hangs on a\n // `buildStart` that never settles. `await sd.hasInitialized` cannot\n // observe it either, since that promise is only ever resolved, at the\n // tail of a successful extend.\n //\n // It is handed the configuration as an object rather than as a path\n // for the same reason: Style Dictionary imports a path with no\n // cache-busting query of its own, so under a long-lived dev server\n // every rebuild after the first built the config the process started\n // with while the watch list followed the edit.\n const sd = new StyleDictionary(await configForBuild(item), {\n init: false,\n })\n\n // One initialisation rather than two. `init()` is `extend()` with\n // `mutateOriginal`, so the old pair loaded the configuration and\n // combined every source twice — running a custom parser or\n // preprocessor twice with it — and the first of the two ran at\n // default verbosity, which is how Style Dictionary's own warnings\n // escaped this plugin's `silent`. `config` defaults to the one the\n // constructor was handed.\n //\n // `verbosity` is `undefined` unless a consumer asked for a level, and\n // Style Dictionary falls through an unset one to the configuration's\n // own `log.verbosity`. Overwriting it here is what silenced the one\n // line explaining why a build wrote nothing. `log.warnings` is not\n // touched either way: a consumer's `warnings: 'error'` turning a\n // missing output file into a thrown build is their decision.\n await sd.extend(undefined, { mutateOriginal: true, verbosity })\n\n // **Before the build, and that is the whole of it.** A token set that\n // resolved to nothing is not an error anywhere in this stack: Style\n // Dictionary writes the file with no custom properties in it, prints\n // its usual `✔︎` line at any verbosity, and returns. So a token file\n // deleted mid-session took the generated output down with it and\n // reported `Rebuilt design tokens` while doing it, and a `source`\n // matching nothing shipped an empty stylesheet from a build that\n // exited 0.\n //\n // Checked here because `buildAllPlatforms` truncates and rewrites the\n // destination: one line later the previous good output is already gone\n // and an error would be accurate and useless.\n if (sd.allTokens.length === 0) {\n // The configuration as an object, so its own patterns can be named.\n // `false` because a configuration that will not parse never reaches\n // here — the `extend` above would have thrown first.\n const asObject = await readConfigObject(item, false)\n const barren = asObject\n ? await patternsMatchingNothing(sourcePatternsOf(asObject))\n : []\n\n // Thrown rather than reported, so it takes the path `failOnError`\n // already owns — the same decision, made in one place, rather than a\n // second way for a build to fail.\n throw new Error(\n [\n `${describeConfig(item, index)} resolved no tokens, so its output would be emptied.`,\n barren.length > 0\n ? `These patterns matched no files: ${barren.join(', ')}`\n : `It declares no source or include patterns that matched anything.`,\n `Nothing was written. Set failOnError to false to build anyway.`,\n ].join(' '),\n )\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 sd.volume = atomicVolume\n\n if (selectedPlatforms === undefined) {\n await sd.buildAllPlatforms()\n } else {\n // Named, so a typo is an error rather than a platform silently not\n // built — which is what Style Dictionary's own CLI means by \"Must be\n // defined in the config\".\n const defined = Object.keys(sd.platforms)\n const unknown = selectedPlatforms.filter(\n (name) => !defined.includes(name),\n )\n if (unknown.length > 0) {\n throw new Error(\n `${describeConfig(item, index)} does not define the platform(s) ${unknown.join(', ')}. It defines ${defined.join(', ')}.`,\n )\n }\n\n // One after another, matching the loop this sits inside: two\n // platforms may name the same destination, and `buildAllPlatforms`\n // fanning its own out with `Promise.all` is Style Dictionary's\n // choice over configurations it owns, not this plugin's over a\n // selection a consumer wrote.\n for (const name of selectedPlatforms) {\n await sd.buildPlatform(name)\n }\n }\n\n // Every declared platform, not only the ones this compile built. A\n // file an unselected platform wrote on an earlier build is still the\n // plugin's own output, and dropping it from this set would let a\n // watcher treat it as a token source and rebuild on it forever.\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(sd.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 // Recorded only now, so a configuration whose build threw is never\n // treated as one this process has compiled.\n const fingerprint = configFingerprint(item)\n if (fingerprint !== null) compiledFingerprints.add(fingerprint)\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 } catch (err) {\n const duration = Date.now() - startTime\n log(\n `Compilation failed after ${duration}ms: ${errorMessage(err)}`,\n 'error',\n )\n\n // Ahead of the throw decision on purpose, so the overlay sees a failure\n // whatever `failOnError` does with it. Under the dev server's default\n // the line below does not throw, and reading the outcome from a caller's\n // `catch` would see a rebuild that looked like it succeeded.\n notifyBuildOutcome?.(asError(err))\n\n // Ahead of the throw decision for the same reason as the line above: a\n // rebuild under the dev server's default does not throw, and a hook that\n // only fired when something else was about to fail would be silent on\n // exactly the builds a consumer is watching.\n if (onBuildError) callHook('onBuildError', onBuildError, err)\n\n // Reported, and then rethrown so the host stops. Swallowing it left\n // every target exiting 0 with the previous run's tokens still on disk\n // and in the bundle — a green build shipping stale values.\n if (failsTheBuild(context)) throw err\n\n // Explicit, now that the reporting below sits outside the `try`. This\n // `catch` used to end the function by falling off the end of it; a\n // failure that is not rethrown would otherwise carry on to announce a\n // compile that did not happen.\n return\n }\n\n // The compile is what the overlay reflects, so this is said here rather\n // than at the end: everything below is reporting, it returns early in\n // three places, and a size table that throws must not leave a successful\n // build looking unfinished.\n notifyBuildOutcome?.(null)\n\n // One measurement, read by the hook below and by the reporting under it.\n const duration = Date.now() - startTime\n\n // Beside the overlay notification, and for the same reason it sits here\n // rather than at the end of the function: the reporting below returns\n // early in three places, and a build that finished has finished whether or\n // not a size table gets printed for it.\n //\n // Sorted, so two runs of one configuration hand back the same order —\n // `generatedFiles` is a set in platform-then-file order, which is stable\n // in practice and guaranteed by nothing. The paths stay platform-native:\n // this is a list a consumer is going to open files with, not one the\n // watcher compares against.\n if (onBuildEnd) {\n // `toSorted` is what the linter asks for and what this cannot use:\n // `lib` is ES2022 here and `toSorted` is ES2023, so it types as an error\n // even though every Node this package supports has it. The rule guards\n // against mutating an array someone else holds, and this one was built\n // from the set on the line it appears on.\n // oxlint-disable-next-line unicorn/no-array-sort\n const files = Array.from(generatedFiles).sort((left, right) =>\n left.localeCompare(right),\n )\n callHook('onBuildEnd', onBuildEnd, files, duration)\n }\n\n // The `try` ends above, and everything from here down is reporting. Style\n // Dictionary has finished writing by now and `generatedDestinations` is\n // already replaced, so nothing below can put a file on disk in doubt —\n // which is why a throw from it must not be caught as a compile failure.\n // It used to be: a fault in the padding arithmetic printed `Compilation\n // failed after 19ms` over a build whose every token file was correct, and\n // with `failOnError` defaulting to `'build'` that stopped the bundler.\n\n // Every configuration was already current, so nothing was written. Said\n // rather than left implied: a build that prints its opening line and then\n // finishes in two milliseconds reads as one that silently did nothing.\n const everythingSkipped = skipped === resolvedConfigs.length\n\n if (context) {\n log(\n everythingSkipped\n ? `Design tokens already up to date after change in ${context} (${duration}ms)`\n : `Rebuilt design tokens due to change in ${context} (${duration}ms)`,\n 'success',\n )\n return\n }\n\n // The table is skipped when nothing was written, on top of `report` and\n // `quiet`. It reads every generated file in full and gzips it, and\n // reprinting the sizes of files this build did not touch is the one case\n // where that cost buys nothing at all.\n if (report && !quiet && !everythingSkipped && generatedFiles.size > 0) {\n try {\n reportSizes(generatedFiles)\n } catch (err) {\n // At `'error'`, so it is said at every level including `silent`,\n // exactly as a compile failure is — and worded so it cannot be read\n // as one. Not rethrown: the build succeeded.\n log(\n `Failed to report generated file sizes: ${errorMessage(err)}`,\n 'error',\n )\n }\n }\n\n if (everythingSkipped) {\n log(`Design tokens are already up to date (${duration}ms)`, 'success')\n return\n }\n\n log(\n skipped > 0\n ? `Compiled successfully! (${duration}ms, ${skipped} already up to date)`\n : `Compiled successfully! (${duration}ms)`,\n 'success',\n )\n }\n\n // `runBuilds` for the first build of a process, with the compile shared\n // between every plugin instance that wants the same one.\n //\n // An instance arriving while a compile for the same key is running waits on\n // that compile instead of starting a second. It is the concurrent half that\n // needs this: an up-to-date check compares what is on disk against the\n // sources, and two instances that start together have nothing on disk to\n // compare against yet, so only a shared promise can tell them apart from\n // two genuinely separate builds.\n //\n // The entry is dropped as soon as the compile settles, so this coalesces\n // rather than caches — a later `buildStart` still compiles. Skipping one\n // whose output is already current is #212's up-to-date check, and belongs\n // with it rather than as a second mechanism here.\n //\n // A rejection reaches every waiter, which is the point: an instance that\n // waited on a failed compile must not carry on as though the tokens were\n // written. Whether that rejection is thrown at all is `failOnError`'s\n // decision, already made inside `runBuilds`.\n const compileOnceAcrossInstances = async (\n resolvedConfigs: ResolvedConfig[],\n ): Promise<void> => {\n const key = buildKey(root, resolvedConfigs)\n if (key === null) {\n await runBuilds(resolvedConfigs)\n return\n }\n\n const running = compilesInFlight.get(key)\n if (running) {\n await running\n return\n }\n\n const compile = runBuilds(resolvedConfigs)\n compilesInFlight.set(key, compile)\n\n try {\n await compile\n } finally {\n compilesInFlight.delete(key)\n }\n }\n\n // One rebuild per burst of watcher events, and never two at once.\n //\n // Two things went wrong without this. A single token edit under Vite's dev\n // server reached both the `configureServer` listener and `watchChange` —\n // Vite 6, 7 and 8 all invoke plugin `watchChange` while serving — and each\n // started its own build, so one write produced two. And nothing serialised\n // them: a four-file change started one build per file, all overlapping.\n // `runBuilds` builds its configurations one after another precisely so two\n // instances never write the same destination at once, and concurrent calls\n // to it reintroduced that one level up.\n //\n // The trailing debounce collapses the burst; the in-flight chain means a\n // trigger arriving mid-build queues exactly one follow-up rather than\n // starting a second build beside it.\n const REBUILD_DEBOUNCE_MS = 50\n\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n let pendingReason: string | undefined\n let inFlight: Promise<void> | undefined\n let waiting: Array<(failure?: { error: unknown }) => void> = []\n\n // Set by `configureServer`. A dev server's watcher is long-lived, so its\n // list has to follow a configuration that changes; every other target\n // re-registers on each build through `addWatchFile` instead.\n let refreshServerWatchList:\n | ((resolved: ResolvedConfig[]) => Promise<void>)\n | undefined\n\n // Whether the discovered path has been announced. Once per plugin instance:\n // `resolveConfigs` runs on every build and rebuild, and a dev server would\n // otherwise repeat the line for the rest of the session.\n let announcedDiscovery = false\n\n // Resolved by `configResolved` so it can amend the watcher's ignore list, and\n // handed to `configureServer` rather than resolved again — one start-up, one\n // call of the consumer's `config` function.\n let startupResolved: ResolvedConfig[] | undefined\n\n // Also set by `configureServer`, and left undefined everywhere else: this is\n // how a compile outcome reaches Vite's error overlay. It is deliberately not\n // the same path as `failOnError`.\n //\n // `failOnError` decides whether the host stops; this decides whether the\n // browser is told. Under a dev server the default is not to stop, so the\n // failure is reported and swallowed — and that is exactly the case where the\n // page is left rendering the last good file with nothing to say it is stale.\n // Reading the outcome off whether `runBuilds` threw would therefore see\n // nothing at all on the only configuration that matters.\n let notifyBuildOutcome: ((error: Error | null) => void) | undefined\n\n const drain = async (): Promise<void> => {\n // A loop rather than a single pass: anything scheduled while the build\n // below is running is picked up here instead of starting a second one.\n while (pendingReason !== undefined) {\n const reason = pendingReason\n pendingReason = undefined\n\n // Captured before the await, so a trigger arriving mid-build waits for\n // the next pass rather than being told this one covered it.\n const resolvers = waiting\n waiting = []\n\n let failure: undefined | { error: unknown }\n let compiling = false\n\n try {\n const resolved = await resolveConfigs()\n if (resolved.length > 0) {\n compiling = true\n await runBuilds(resolved, reason)\n compiling = false\n hasCompiled = true\n await refreshServerWatchList?.(resolved)\n }\n } catch (err) {\n failure = { error: err }\n\n // `runBuilds` reports its own failure before rethrowing, so only the\n // other things that can throw here — a `config` function of the\n // consumer's that raises, a watch list that cannot be rebuilt — need\n // reporting. They reach the overlay for the same reason: from the\n // page's point of view the rebuild failed, whichever half of it did.\n if (!compiling) {\n log(`Rebuild failed: ${errorMessage(err)}`, 'error')\n notifyBuildOutcome?.(asError(err))\n }\n }\n\n // Handed on to whatever awaited this rebuild, which is `watchChange`\n // and so the host under a watching bundler. Vite's dev-server listener\n // has no build to fail and catches it.\n for (const settle of resolvers) settle(failure)\n }\n }\n\n // Resolves once a rebuild covering this trigger has finished.\n const schedule = async (reason: string): Promise<void> => {\n // Nothing consumes a rebuild once the host has closed its watcher. This is\n // where a close actually lands: `watchChange` reaches here only after\n // resolving configurations and deriving a watch list, so a `closeWatcher`\n // arriving mid-hook finds no timer armed yet and nothing else to stop it.\n //\n // Resolving rather than rejecting, because the trigger was handled — by\n // being declined — and the caller awaiting it is a host on its way out.\n if (hostClosed) return\n\n pendingReason = reason\n\n const covered = new Promise<void>((resolve, reject) => {\n waiting.push((failure) => {\n if (failure) reject(asError(failure.error))\n else resolve()\n })\n })\n\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounceTimer = undefined\n inFlight = (inFlight ?? Promise.resolve()).then(drain)\n }, REBUILD_DEBOUNCE_MS)\n\n // A pending rebuild must not be what keeps a process alive; whatever is\n // watching already is.\n debounceTimer.unref()\n\n return covered\n }\n\n // Every host that runs a rollup-shaped watcher calls this on shutdown, and\n // all three get the same handler below. There is deliberately no webpack\n // equivalent here: it has no `closeWatcher`, its nearest thing is\n // `compiler.hooks.watchClose`, and nothing measured shows it exposed.\n //\n // It raises the flag and nothing else. A debounce timer armed before the\n // close is deliberately left to fire: the rebuild it runs is one the host\n // asked for while the project was still whole, and `drain` reports its own\n // failures. Cancelling it would be a guard no test could fail on, since a\n // trigger arriving after the close is declined by `schedule` instead.\n const closeWatcher = (): void => {\n hostClosed = true\n }\n\n // What both webpack-shaped hosts do with a compiler, written once.\n // rspack reimplements webpack's plugin API hook for hook, but ships its\n // own `Compiler` type and unplugin dispatches the two through separate\n // keys — so a function typed against either one rejects the other. Naming\n // the surface actually used is what lets one implementation serve both.\n //\n // unplugin calls this inside `apply(compiler)`, one line before it taps\n // `make`, so the root is in place before the first compile. Without it a\n // webpack build whose `context` is not the working directory looked for\n // the configuration in the wrong place and reported ENOENT.\n const adoptCompiler = (compiler: BundlerCompiler): void => {\n if (rootOption === undefined) {\n root = compiler.options.context ?? process.cwd()\n }\n\n // webpack's `buildStart` context carries no `meta`, so neither half of\n // the build context can come from there. `mode` is a webpack option, and\n // `watchMode` is only true once `watch()` has been called — which is\n // after this runs, so it is read per compile below rather than here.\n hostMode = compiler.options.mode\n\n // The compile happens in `beforeCompile`, which webpack awaits *before*\n // the compilation exists — so a message from it has nothing to attach to\n // yet and is held until one appears.\n //\n // Only failures are routed. `stats` carries warnings and errors and\n // nothing else, so the progress lines stay on the console rather than\n // being reported as warnings they are not.\n //\n // A warning rather than an error, for the same reason as on rollup: this\n // is the report, and `failOnError` decides separately whether the build\n // stops. Pushing to `compilation.errors` would fail a webpack build that\n // asked not to be failed.\n const pending: string[] = []\n host = {\n error: (message) => {\n pending.push(message)\n },\n }\n\n compiler.hooks.compilation.tap(\n 'unplugin-style-dictionary',\n (compilation) => {\n for (const message of pending.splice(0)) {\n const reported = new Error(message)\n reported.name = 'UnpluginStyleDictionaryWarning'\n compilation.warnings.push(reported)\n }\n },\n )\n\n // A `beforeCompile` that throws ends the run without ever creating a\n // compilation, and that is exactly the case that produced the message.\n // Left to the buffer it would be reported nowhere at all, so whatever is\n // still held when the run ends goes to the console after all.\n const drainToConsole = () => {\n for (const message of pending.splice(0)) {\n console.error(paint('31', message, stderrColour))\n }\n }\n compiler.hooks.failed.tap('unplugin-style-dictionary', drainToConsole)\n compiler.hooks.done.tap('unplugin-style-dictionary', drainToConsole)\n\n // `beforeCompile` is awaited before the compilation exists, so the\n // tokens are on disk before webpack resolves the module that imports\n // them. Tapped on every compilation rather than only the first: a watch\n // rebuild needs the same guarantee, and a compile that renders what is\n // already there skips its own write.\n compiler.hooks.beforeCompile.tapPromise(\n 'unplugin-style-dictionary',\n async () => {\n isWatching = compiler.watchMode\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n await compileOnceAcrossInstances(resolved)\n hasCompiled = true\n },\n )\n }\n\n return {\n async buildStart() {\n adoptHost(this)\n adoptWatchMode(this)\n\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 — not because\n // `watchChange` is missing there, which it is not on any Vite this\n // package supports, but because the declared peer range is wider than\n // what has been measured and the scheduler above makes a duplicate\n // trigger free.\n //\n // Skipped outright once the host has closed, because rollup discards the\n // result: with the task closed, `Task.run` returns before\n // `updateWatchedFiles`, so every path registered here goes nowhere. What\n // deriving it does still do is read each config file — with\n // `reportErrors: true` — and report an ENOENT for a project the host is\n // in the middle of tearing down. That report was the one thing this\n // block contributed after a close.\n if (!hostClosed) {\n const { paths } = await getWatchTargets(resolved)\n for (const file of paths) {\n this.addWatchFile(file)\n }\n }\n\n // Registering the watch list is all this hook does on webpack, and it\n // has to happen here rather than beside the compile: `addWatchFile`\n // reaches `compilation.fileDependencies`, and `beforeCompile` runs\n // before there is a compilation to add to. Compiling here as well would\n // put the race back, and run every webpack build twice.\n if (isWebpack) return\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 compileOnceAcrossInstances(resolved)\n hasCompiled = true\n },\n\n name: 'unplugin-style-dictionary',\n\n // `closeWatcher` is a rollup-shaped hook and `UnpluginOptions` declares no\n // top-level equivalent, so it is registered per target instead: rolldown\n // lists it among its input plugin hooks, and Vite's plugin type is\n // rollup's, which is what carries it to `vite build --watch`. Vite's dev\n // server runs no rollup watcher, so there it simply never fires.\n rolldown: { closeWatcher },\n\n rollup: { closeWatcher },\n\n // unplugin calls the matching key from inside `apply(compiler)` and\n // never both, so the two share one implementation rather than one\n // delegating to the other.\n rspack: adoptCompiler,\n\n vite: {\n closeWatcher,\n\n async configResolved(config) {\n if (rootOption === undefined) root = config.root || process.cwd()\n\n // The only host that has both. `command` is what makes `'serve'`\n // reachable at all, since nothing else here serves.\n hostCommand = config.command\n hostMode = config.mode\n\n // Vite's own logger, so the plugin's lines obey `customLogger` and\n // `clearScreen` like every other line the dev server prints. It\n // colours and prefixes its own output, which is why nothing painted\n // reaches it.\n //\n // Ahead of the early return below, because `vite build` needs the\n // logger just as much and takes that return.\n host = {\n error: (message) => {\n config.logger.error(message)\n },\n info: (message) => {\n config.logger.info(message)\n },\n }\n\n // Nothing below concerns a build: only the dev server has a watcher,\n // and only its ignore list needs amending.\n if (config.command !== 'serve') return\n\n // Ahead of the resolution below, so the `config` function a consumer\n // wrote is told `watch: true` on this call as well as on every later\n // one. Setting it in `configureServer` alone was correct until this\n // hook started resolving configurations too.\n isWatching = true\n\n // **This is the last hook that can reach the ignore list.** Vite\n // builds the watcher from the resolved config, and `configureServer`\n // runs after it exists — `server.watcher` is a parameter there — so a\n // negation added then changes nothing. Measured on Vite 6.4.3, 7.3.6\n // and 8.3.0: amending it here reaches the watcher on all three, and\n // amending it in `configureServer` does not.\n //\n // The resolution is kept for `configureServer` to reuse rather than\n // discarded, because resolving is how a `config` function gets called\n // and doing it twice in one start-up would call the consumer's code an\n // extra time for nothing.\n try {\n startupResolved = await resolveConfigs()\n if (startupResolved.length === 0) return\n\n const { paths } = await getWatchTargets(startupResolved)\n const negations = nodeModulesNegations(paths)\n if (negations.length === 0) return\n\n // Appended to whatever the consumer asked for, not replacing it.\n const existing = config.server.watch?.ignored\n config.server.watch = {\n ...config.server.watch,\n ignored: [\n ...(Array.isArray(existing)\n ? existing\n : existing === undefined\n ? []\n : [existing]),\n ...negations,\n ],\n }\n } catch (err) {\n // A configuration that cannot be resolved is the build's problem to\n // report, and it will: `buildStart` resolves again and fails there\n // with the host watching. Throwing here would fail the dev server\n // before it started, for the sake of a watch-list refinement.\n log(\n `Could not read the configuration while preparing the watch list: ${errorMessage(err)}`,\n 'error',\n )\n startupResolved = undefined\n }\n },\n\n async configureServer(server: ViteDevServer) {\n // A dev server watches, by definition. Said here rather than left to\n // `adoptWatchMode` because this hook runs *before* `buildStart` —\n // `createServer` calls it, and `buildStart` waits for the plugin\n // container — so the first `config` function of the process would\n // otherwise be told `watch: false` while a dev server started up\n // around it.\n isWatching = true\n\n // `configResolved` has already resolved these, on its way to amending\n // the watcher's ignore list. Taken rather than copied, so a later\n // rebuild re-resolves as it always did.\n const resolved = startupResolved ?? (await resolveConfigs())\n startupResolved = undefined\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 // The `node_modules` half, which Vite's watcher cannot be made to\n // deliver on Windows — see `nodeModulesWatchDirectories`. Keyed by\n // directory so a configuration that changes can close the ones it no\n // longer needs rather than accumulating watchers for the session.\n const ownWatchers = new Map<string, fs.FSWatcher>()\n\n const watchNodeModules = (forPaths: string[]) => {\n const wanted = new Set(nodeModulesWatchDirectories(forPaths))\n\n for (const [directory, watcher] of ownWatchers) {\n if (wanted.has(directory)) continue\n watcher.close()\n ownWatchers.delete(directory)\n }\n\n for (const directory of wanted) {\n if (ownWatchers.has(directory)) continue\n\n try {\n const watcher = fs.watch(directory, (_event, filename) => {\n if (filename === null) return\n\n const changed = path.posix.join(directory, filename)\n if (!isWatchedSource(changed, targets.patterns)) return\n\n void schedule(path.basename(changed)).catch(() => {})\n })\n\n // A watcher of ours must not be what keeps a process alive; the\n // dev server already is.\n watcher.unref()\n ownWatchers.set(directory, watcher)\n } catch {\n // A directory that cannot be watched is not a reason to fail a\n // dev server. Where the negation works — Linux, macOS — Vite's\n // own watcher is still delivering these events.\n }\n }\n }\n\n watchNodeModules(targets.paths)\n server.httpServer?.once('close', () => {\n for (const watcher of ownWatchers.values()) watcher.close()\n ownWatchers.clear()\n })\n\n // Runs once per rebuild rather than once per event, which is why it\n // is handed to the scheduler rather than done in the listener.\n refreshServerWatchList = async (rebuilt) => {\n targets = await getWatchTargets(rebuilt)\n server.watcher.add(targets.paths)\n watchNodeModules(targets.paths)\n }\n\n if (errorOverlay) {\n // Whether the page is currently showing an overlay this plugin put\n // there. Only the clearing frame reads it: a success that follows a\n // success has no overlay to take down, and sending an update frame\n // for it would be traffic for nothing — and would spend the client's\n // one-time `isFirstUpdate`, which Vite uses to decide that an\n // overlay standing at the first update means a full reload.\n let overlayShowing = false\n\n notifyBuildOutcome = (error) => {\n if (error) {\n // Sent on every failure rather than only on the transition into\n // one. Vite's client replaces the overlay wholesale, so a repeat\n // is idempotent — and two different failures in a row must not\n // leave the first one's message on screen describing the second.\n overlayShowing = true\n server.hot.send({\n err: {\n message: error.message,\n plugin: 'unplugin-style-dictionary',\n stack: error.stack ?? '',\n },\n type: 'error',\n })\n return\n }\n\n if (!overlayShowing) return\n overlayShowing = false\n\n // Vite's protocol has no frame for \"take the overlay down\". The\n // client clears it when an update arrives, so an update carrying\n // nothing is the clear: it dismisses the overlay and then iterates\n // an empty list, reloading no page and touching no stylesheet.\n server.hot.send({ type: 'update', updates: [] })\n }\n }\n\n // chokidar types its listener as returning void and does not await\n // what it is handed, so an async listener left every rejection\n // floating. `schedule` owns the whole rebuild including its errors,\n // so there is nothing here left to reject.\n server.watcher.on('all', (_event, file) => {\n if (!isWatchedSource(file, targets.patterns)) return\n\n // A dev server has no build to fail, so a rebuild that throws is\n // reported by the scheduler and the server keeps serving.\n void schedule(path.basename(file)).catch(() => {})\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 adoptHost(this)\n adoptWatchMode(this)\n\n // Ahead of everything, including the flag below: a change reported\n // after the watcher closed earns no rebuild, so there is no re-entry\n // into `buildStart` for a flag to describe.\n if (hostClosed) return\n\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 // The cheap half of the decision, taken before anything is resolved.\n // Under Vite the scope this hook sees is the whole project root rather\n // than the module graph, so most of what arrives here has nothing to do\n // with tokens, and resolving every configuration only to discard the\n // answer ran a consumer's `config` function once per unrelated file.\n // Skipped until a build has derived a list to filter against.\n if (cachedPatterns && !isWatchedSource(id, cachedPatterns)) return\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Derived again rather than trusted from the cache, because the cache\n // is what decided this path was worth resolving and not what decides a\n // rebuild. A config edit reaches here through its own filename and can\n // have dropped the very source the cached list matched.\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 // Same division as `buildStart`: on webpack the compile belongs to\n // `beforeCompile`, which has already run for this compilation, so all\n // that is left is to re-register the watch list below.\n if (!isWebpack) await schedule(path.basename(id))\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 webpack: adoptCompiler,\n }\n}\n\nexport const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory)\n\nexport default unplugin\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { StyleDictionaryConfigContext, UnpluginStyleDictionaryOptions } from "./types.js";
2
+ //#region src/rspack.d.ts
3
+ declare const _default: (options?: UnpluginStyleDictionaryOptions | undefined) => import("unplugin").RspackPluginInstance;
4
+ //#endregion
5
+ export { type StyleDictionaryConfigContext, type UnpluginStyleDictionaryOptions, _default as default };
6
+ //# sourceMappingURL=rspack.d.ts.map
package/dist/rspack.js ADDED
@@ -0,0 +1,7 @@
1
+ import unplugin from "./index.js";
2
+ //#region src/rspack.ts
3
+ var rspack_default = unplugin.rspack;
4
+ //#endregion
5
+ export { rspack_default as default };
6
+
7
+ //# sourceMappingURL=rspack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rspack.js","names":[],"sources":["../src/rspack.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\n// Re-exported here as well as from the root, so a consumer who imports the\n// entry point for their bundler can name the options type from the same\n// specifier. Without it the built `.d.ts` imports the type for its own\n// signature and exports nothing but the plugin, and\n// `import type { UnpluginStyleDictionaryOptions } from '.../rspack'` is a\n// TS2614.\nexport type * from './types.js'\n\nexport default unplugin.rspack\n"],"mappings":";;AAUA,IAAA,iBAAe,SAAS"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanso-labs/unplugin-style-dictionary",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
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",
@@ -36,6 +36,10 @@
36
36
  "types": "./dist/rollup.d.ts",
37
37
  "default": "./dist/rollup.js"
38
38
  },
39
+ "./rspack": {
40
+ "types": "./dist/rspack.d.ts",
41
+ "default": "./dist/rspack.js"
42
+ },
39
43
  "./webpack": {
40
44
  "types": "./dist/webpack.d.ts",
41
45
  "default": "./dist/webpack.js"
@@ -60,11 +64,13 @@
60
64
  "format": "oxfmt",
61
65
  "format:check": "oxfmt --check",
62
66
  "lint": "oxlint --type-aware . && eslint . && npm run format:check",
63
- "package:check": "publint && node scripts/check-package.mjs",
64
- "prepare": "husky",
67
+ "package:check": "publint && attw --pack . --profile esm-only && node scripts/check-package.mjs",
68
+ "peers:check": "node scripts/check-peers.mjs",
69
+ "prepare": "husky || true",
65
70
  "test": "vitest run",
66
71
  "test:coverage": "vitest run --coverage"
67
72
  },
73
+ "author": "Kanso Labs <kanso.labs.org@gmail.com>",
68
74
  "dependencies": {
69
75
  "json5": "2.2.3",
70
76
  "picomatch": "4.0.7",
@@ -72,10 +78,12 @@
72
78
  "unplugin": "3.4.0"
73
79
  },
74
80
  "devDependencies": {
81
+ "@arethetypeswrong/cli": "0.18.5",
75
82
  "@codecov/rollup-plugin": "2.0.1",
76
83
  "@commitlint/cli": "21.2.2",
77
84
  "@commitlint/config-conventional": "21.2.2",
78
85
  "@eslint/js": "10.0.1",
86
+ "@rspack/core": "2.2.6",
79
87
  "@types/node": "26.6.1",
80
88
  "@types/picomatch": "4.0.3",
81
89
  "@types/semver": "7.8.0",
@@ -103,6 +111,7 @@
103
111
  "webpack": "5.111.0"
104
112
  },
105
113
  "peerDependencies": {
114
+ "@rspack/core": "*",
106
115
  "rolldown": "*",
107
116
  "rollup": "*",
108
117
  "style-dictionary": "^5.0.0",
@@ -110,6 +119,9 @@
110
119
  "webpack": "*"
111
120
  },
112
121
  "peerDependenciesMeta": {
122
+ "@rspack/core": {
123
+ "optional": true
124
+ },
113
125
  "rolldown": {
114
126
  "optional": true
115
127
  },