@kanso-labs/unplugin-style-dictionary 0.2.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,35 @@
1
1
  # unplugin-style-dictionary
2
2
 
3
- A lightweight, robust [unplugin](https://unplugin.unjs.io/)-based plugin to compile **Style Dictionary** design tokens ahead of your bundler, with automatic watching, rebuilding, and hot reloading (HMR) under Vite's dev server.
3
+ A lightweight, robust [unplugin](https://unplugin.unjs.io/)-based plugin to
4
+ compile **Style Dictionary** design tokens ahead of your bundler, with automatic
5
+ watching, rebuilding, and hot reloading (HMR) under Vite's dev server.
4
6
 
5
- Built on unplugin, the same core plugin targets **Vite**, **Rolldown**, **Rollup**, and **Webpack** from a single implementation — useful when a project has more than one build surface (e.g. Storybook/Vitest on Vite, and a package build on Rolldown/tsdown) that both need tokens compiled ahead of them.
7
+ Built on unplugin, the same core plugin targets **Vite**, **Rolldown**,
8
+ **Rollup**, and **Webpack** from a single implementation — useful when a project
9
+ has more than one build surface (e.g. Storybook/Vitest on Vite, and a package
10
+ build on Rolldown/tsdown) that both need tokens compiled ahead of them.
6
11
 
7
12
  ## Features
8
13
 
9
- - **Multi-bundler**: One implementation, four entry points — Vite, Rolldown, Rollup, and Webpack.
10
- - **Asynchronous builds**: Native support for Style Dictionary v4/v5 async compilation API.
11
- - **Automatic watching**: Reads the `source` and `include` patterns from your Style Dictionary configurations and automatically watches them. Live rebuild-on-change is fully supported under Vite's dev server; other targets rebuild on change wherever the host bundler itself runs a persistent watch mode.
12
- - **Config flexibility**: Supports file paths (JSON, JS, MJS, CJS, TS), configuration objects, or functions — including registering custom formats at config-resolution time.
13
- - **Atomic writes**: Every generated file is written to a temporary sibling and renamed into place, so code importing a token file while it is being rebuilt never reads a half-written file.
14
- - **Multi-configuration**: Can run multiple Style Dictionary configurations in parallel (useful for multi-brand or multi-theme projects).
15
- - **TypeScript Support**: Fully written in TypeScript and exports complete type definitions.
14
+ - **Multi-bundler**: One implementation, four entry points — Vite, Rolldown,
15
+ Rollup, and Webpack.
16
+ - **Asynchronous builds**: Native support for Style Dictionary v4/v5 async
17
+ compilation API.
18
+ - **Automatic watching**: Reads the `source` and `include` patterns from your
19
+ Style Dictionary configurations and automatically watches them. Live
20
+ rebuild-on-change is fully supported under Vite's dev server; other targets
21
+ rebuild on change wherever the host bundler itself runs a persistent watch
22
+ mode.
23
+ - **Config flexibility**: Supports file paths (JSON, JS, MJS, CJS, TS),
24
+ configuration objects, or functions — including registering custom formats at
25
+ config-resolution time.
26
+ - **Atomic writes**: Every generated file is written to a temporary sibling and
27
+ renamed into place, so code importing a token file while it is being rebuilt
28
+ never reads a half-written file.
29
+ - **Multi-configuration**: Can run multiple Style Dictionary configurations in
30
+ parallel (useful for multi-brand or multi-theme projects).
31
+ - **TypeScript Support**: Fully written in TypeScript and exports complete type
32
+ definitions.
16
33
 
17
34
  ## Installation
18
35
 
@@ -20,7 +37,9 @@ Built on unplugin, the same core plugin targets **Vite**, **Rolldown**, **Rollup
20
37
  npm install @kanso-labs/unplugin-style-dictionary style-dictionary --save-dev
21
38
  ```
22
39
 
23
- *Note: `style-dictionary` and your bundler (`vite`, `rolldown`, `rollup`, or `webpack`) are peer dependencies, so you can manage their versions independently.*
40
+ _Note: `style-dictionary` and your bundler (`vite`, `rolldown`, `rollup`, or
41
+ `webpack`) are peer dependencies, so you can manage their versions
42
+ independently._
24
43
 
25
44
  ## Usage
26
45
 
@@ -56,7 +75,11 @@ export default {
56
75
  }
57
76
  ```
58
77
 
59
- Rolldown (and tools built on it, like [tsdown](https://tsdown.dev/)) is typically run as a one-shot build rather than a long-lived dev server, so under this target the plugin compiles tokens once in `buildStart` rather than watching for changes. That's enough to guarantee generated token files exist before the rest of the build consumes them.
78
+ Rolldown (and tools built on it, like [tsdown](https://tsdown.dev/)) is
79
+ typically run as a one-shot build rather than a long-lived dev server, so under
80
+ this target the plugin compiles tokens once in `buildStart` rather than watching
81
+ for changes. That's enough to guarantee generated token files exist before the
82
+ rest of the build consumes them.
60
83
 
61
84
  ### Rollup / Webpack
62
85
 
@@ -65,16 +88,35 @@ import StyleDictionary from '@kanso-labs/unplugin-style-dictionary/rollup'
65
88
  // or: import StyleDictionary from '@kanso-labs/unplugin-style-dictionary/webpack'
66
89
  ```
67
90
 
91
+ A `webpack.config.js` is often CommonJS rather than ESM. This package ships ESM
92
+ only, and Node serves a `require` of it through `require(esm)`, which hands back
93
+ the module namespace — so reach for `.default`:
94
+
95
+ ```javascript
96
+ const {
97
+ default: StyleDictionary,
98
+ } = require('@kanso-labs/unplugin-style-dictionary/webpack')
99
+
100
+ module.exports = {
101
+ plugins: [StyleDictionary({ config: 'sd.config.json' })],
102
+ }
103
+ ```
104
+
105
+ _Note: that path needs Node 20.19+ or 22.12+, the versions that can `require` an
106
+ ES module. Every Node release still in support clears it. Importing from ESM has
107
+ no such floor._
108
+
68
109
  ### Multiple Configurations
69
110
 
70
- If you have multiple themes or sub-brands, pass an array of config paths or objects:
111
+ If you have multiple themes or sub-brands, pass an array of config paths or
112
+ objects:
71
113
 
72
114
  ```typescript
73
115
  StyleDictionary({
74
116
  config: [
75
117
  'tokens/config/sd-base.config.json',
76
- 'tokens/config/sd-theme.config.json'
77
- ]
118
+ 'tokens/config/sd-theme.config.json',
119
+ ],
78
120
  })
79
121
  ```
80
122
 
@@ -90,16 +132,22 @@ StyleDictionary({
90
132
  css: {
91
133
  transformGroup: 'css',
92
134
  buildPath: 'dist/css/',
93
- files: [{ destination: 'variables.css', format: 'css/variables' }]
94
- }
95
- }
96
- }
135
+ files: [{ destination: 'variables.css', format: 'css/variables' }],
136
+ },
137
+ },
138
+ },
97
139
  })
98
140
  ```
99
141
 
100
142
  ### Custom Formats
101
143
 
102
- `config` also accepts a function, which is the pattern to use when you need to register a custom Style Dictionary format (via `StyleDictionary.registerFormat()`, or any other `register*` call) before it's referenced by name in the returned config. The function re-runs on every build — including watch-triggered rebuilds under Vite — so the format is always registered before it's needed; re-registering the same format name on every rebuild is safe (Style Dictionary silently replaces the existing one).
144
+ `config` also accepts a function, which is the pattern to use when you need to
145
+ register a custom Style Dictionary format (via
146
+ `StyleDictionary.registerFormat()`, or any other `register*` call) before it's
147
+ referenced by name in the returned config. The function re-runs on every build —
148
+ including watch-triggered rebuilds under Vite — so the format is always
149
+ registered before it's needed; re-registering the same format name on every
150
+ rebuild is safe (Style Dictionary silently replaces the existing one).
103
151
 
104
152
  ```typescript
105
153
  // Named `styleDictionaryPlugin` here to avoid colliding with the `StyleDictionary`
@@ -125,7 +173,9 @@ export default defineConfig({
125
173
  custom: {
126
174
  transformGroup: 'css',
127
175
  buildPath: 'dist/',
128
- files: [{ destination: 'tokens.txt', format: 'custom/my-format' }],
176
+ files: [
177
+ { destination: 'tokens.txt', format: 'custom/my-format' },
178
+ ],
129
179
  },
130
180
  },
131
181
  }
@@ -176,12 +226,20 @@ export interface UnpluginStyleDictionaryOptions {
176
226
 
177
227
  ## Migrating from `vite-plugin-style-dictionary`
178
228
 
179
- This package was previously published as `@kanso-labs/vite-plugin-style-dictionary`, implemented directly as a Vite plugin. As of this unplugin-based rewrite:
229
+ This package was previously published as
230
+ `@kanso-labs/vite-plugin-style-dictionary`, implemented directly as a Vite
231
+ plugin. As of this unplugin-based rewrite:
180
232
 
181
233
  - The package is renamed to `@kanso-labs/unplugin-style-dictionary`.
182
- - The root import no longer resolves to a ready-to-use Vite plugin. Import the bundler-specific entry point instead: `@kanso-labs/unplugin-style-dictionary/vite` (a drop-in replacement for the old default export), `/rolldown`, `/rollup`, or `/webpack`.
183
- - The exported options type is renamed from `VitePluginStyleDictionaryOptions` to `UnpluginStyleDictionaryOptions`. The shape of the options (`config`, `watch`, `silent`) is unchanged.
184
- - Behavior under Vite is unchanged: the same `buildStart`-time compilation and dev-server watch/rebuild logic as before.
234
+ - The root import no longer resolves to a ready-to-use Vite plugin. Import the
235
+ bundler-specific entry point instead:
236
+ `@kanso-labs/unplugin-style-dictionary/vite` (a drop-in replacement for the
237
+ old default export), `/rolldown`, `/rollup`, or `/webpack`.
238
+ - The exported options type is renamed from `VitePluginStyleDictionaryOptions`
239
+ to `UnpluginStyleDictionaryOptions`. The shape of the options (`config`,
240
+ `watch`, `silent`) is unchanged.
241
+ - Behavior under Vite is unchanged: the same `buildStart`-time compilation and
242
+ dev-server watch/rebuild logic as before.
185
243
 
186
244
  ## License
187
245
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- import { UnpluginFactory } from 'unplugin';
2
- import { UnpluginStyleDictionaryOptions } from './types.js';
3
- export * from './types.js';
4
- export declare function matchesWatchedFile(file: string, patterns: string[]): boolean;
5
- export declare const unpluginFactory: UnpluginFactory<undefined | UnpluginStyleDictionaryOptions, false>;
6
- export declare const unplugin: import('unplugin').UnpluginInstance<UnpluginStyleDictionaryOptions | undefined, false>;
7
- export default unplugin;
1
+ import { UnpluginStyleDictionaryOptions } from "./types.js";
2
+ import { UnpluginFactory } from "unplugin";
3
+ //#region src/index.d.ts
4
+ declare function matchesWatchedFile(file: string, patterns: string[]): boolean;
5
+ declare const unpluginFactory: UnpluginFactory<undefined | UnpluginStyleDictionaryOptions, false>;
6
+ declare const unplugin: import("unplugin").UnpluginInstance<UnpluginStyleDictionaryOptions | undefined, false>;
7
+ //#endregion
8
+ export { type UnpluginStyleDictionaryOptions, unplugin as default, unplugin, matchesWatchedFile, unpluginFactory };
9
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,204 +1,256 @@
1
- import e from "node:fs";
2
- import t from "node:path";
3
- import { pathToFileURL as n } from "node:url";
4
- import r from "node:zlib";
5
- import i from "style-dictionary";
6
- import { createUnplugin as a } from "unplugin";
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import zlib from "node:zlib";
5
+ import StyleDictionary from "style-dictionary";
6
+ import { createUnplugin } from "unplugin";
7
7
  //#region src/index.ts
8
- function o(e, t) {
9
- let n = e.replace(/\\/g, "/");
10
- return t.some((e) => e === n || n.startsWith(e.replace(/\/\*\*/g, "")) || e.includes("*") && new RegExp(e.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")).test(n));
8
+ function matchesWatchedFile(file, patterns) {
9
+ const normalizedFile = file.replace(/\\/g, "/");
10
+ return patterns.some((pattern) => {
11
+ if (pattern === normalizedFile) return true;
12
+ return normalizedFile.startsWith(pattern.replace(/\/\*\*/g, "")) || pattern.includes("*") && new RegExp(pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")).test(normalizedFile);
13
+ });
11
14
  }
12
- function s(t) {
15
+ function discardTemporaryFile(temporary) {
13
16
  try {
14
- e.rmSync(t, { force: !0 });
17
+ fs.rmSync(temporary, { force: true });
15
18
  } catch {}
16
19
  }
17
- var c = 0;
18
- function l(e) {
19
- let n = t.extname(e);
20
- return t.join(t.dirname(e), `.${t.basename(e, n)}.${process.pid}.${c++}.tmp`);
20
+ function errorMessage(error) {
21
+ return error instanceof Error ? error.message : String(error);
21
22
  }
22
- var u = Object.create(e, {
23
- promises: { value: Object.create(e.promises, { writeFile: { value: async (t, n, r) => {
24
- if (typeof t != "string") return e.promises.writeFile(t, n, r);
25
- let i = l(t);
26
- try {
27
- await e.promises.writeFile(i, n, r), await e.promises.rename(i, t);
28
- } catch (e) {
29
- throw s(i), e;
30
- }
31
- } } }) },
32
- writeFileSync: { value: (t, n, r) => {
33
- if (typeof t != "string") {
34
- e.writeFileSync(t, n, r);
35
- return;
36
- }
37
- let i = l(t);
38
- try {
39
- e.writeFileSync(i, n, r), e.renameSync(i, t);
40
- } catch (e) {
41
- throw s(i), e;
42
- }
43
- } }
44
- }), d = (a = {}) => {
45
- let { silent: s = !1 } = a, c = process.cwd(), l = (e, t = "info") => {
46
- if (s) return;
47
- let n = "[unplugin-style-dictionary]";
48
- t === "error" ? console.error(`\x1b[31m${n} ${e}\x1b[0m`) : console.log(t === "success" ? `\x1b[32m${n} ${e}\x1b[0m` : `\x1b[36m${n} ${e}\x1b[0m`);
49
- }, d = async () => {
50
- let n = a.config;
51
- if (!n) for (let r of [
23
+ function isConfig(value) {
24
+ return typeof value === "object" && value !== null;
25
+ }
26
+ function unwrapDefault(value) {
27
+ return typeof value === "object" && value !== null && "default" in value ? value.default ?? value : value;
28
+ }
29
+ let temporaryFileCounter = 0;
30
+ function temporaryPathFor(destination) {
31
+ const extension = path.extname(destination);
32
+ return path.join(path.dirname(destination), `.${path.basename(destination, extension)}.${process.pid}.${temporaryFileCounter++}.tmp`);
33
+ }
34
+ const writeFileAtomic = async (file, data, options) => {
35
+ if (typeof file !== "string") return fs.promises.writeFile(file, data, options);
36
+ const temporary = temporaryPathFor(file);
37
+ try {
38
+ await fs.promises.writeFile(temporary, data, options);
39
+ await fs.promises.rename(temporary, file);
40
+ } catch (err) {
41
+ discardTemporaryFile(temporary);
42
+ throw err;
43
+ }
44
+ };
45
+ const writeFileSyncAtomic = (file, data, options) => {
46
+ if (typeof file !== "string") {
47
+ fs.writeFileSync(file, data, options);
48
+ return;
49
+ }
50
+ const temporary = temporaryPathFor(file);
51
+ try {
52
+ fs.writeFileSync(temporary, data, options);
53
+ fs.renameSync(temporary, file);
54
+ } catch (err) {
55
+ discardTemporaryFile(temporary);
56
+ throw err;
57
+ }
58
+ };
59
+ const atomicVolume = Object.create(fs, {
60
+ promises: { value: Object.create(fs.promises, { writeFile: { value: writeFileAtomic } }) },
61
+ writeFileSync: { value: writeFileSyncAtomic }
62
+ });
63
+ const unpluginFactory = (options = {}) => {
64
+ const { silent = false } = options;
65
+ let root = process.cwd();
66
+ const log = (message, type = "info") => {
67
+ if (silent) return;
68
+ const prefix = "[unplugin-style-dictionary]";
69
+ if (type === "error") console.error(`\x1b[31m${prefix} ${message}\x1b[0m`);
70
+ else if (type === "success") console.log(`\x1b[32m${prefix} ${message}\x1b[0m`);
71
+ else console.log(`\x1b[36m${prefix} ${message}\x1b[0m`);
72
+ };
73
+ const resolveConfigs = async () => {
74
+ let rawConfig = options.config;
75
+ if (!rawConfig) for (const file of [
52
76
  "sd.config.json",
53
77
  "config.json",
54
78
  "sd.config.js",
55
79
  "sd.config.mjs"
56
80
  ]) {
57
- let i = t.resolve(c, r);
58
- if (e.existsSync(i)) {
59
- n = r;
81
+ const fullPath = path.resolve(root, file);
82
+ if (fs.existsSync(fullPath)) {
83
+ rawConfig = file;
60
84
  break;
61
85
  }
62
86
  }
63
- return n ? (typeof n == "function" && (n = await n()), (Array.isArray(n) ? n : [n]).map((e) => {
64
- if (typeof e == "string") {
65
- let n = t.resolve(c, e);
87
+ if (!rawConfig) {
88
+ log("No configuration specified and no default config file found. Style Dictionary will not compile.", "error");
89
+ return [];
90
+ }
91
+ if (typeof rawConfig === "function") rawConfig = await rawConfig();
92
+ return (Array.isArray(rawConfig) ? rawConfig : [rawConfig]).map((conf) => {
93
+ if (typeof conf === "string") {
94
+ const fullPath = path.resolve(root, conf);
66
95
  return {
67
- config: n,
68
- dir: t.dirname(n),
69
- file: n
96
+ config: fullPath,
97
+ dir: path.dirname(fullPath),
98
+ file: fullPath
70
99
  };
71
- }
72
- return {
73
- config: e,
74
- dir: c
100
+ } else return {
101
+ config: conf,
102
+ dir: root
75
103
  };
76
- })) : (l("No configuration specified and no default config file found. Style Dictionary will not compile.", "error"), []);
77
- }, f = async (r) => {
78
- let i = /* @__PURE__ */ new Set();
79
- for (let a of r) {
80
- a.file && i.add(a.file.replace(/\\/g, "/"));
81
- let r = null;
82
- if (typeof a.config == "string") try {
83
- if (a.config.endsWith(".json")) {
84
- let t = e.readFileSync(a.config, "utf-8");
85
- r = JSON.parse(t);
86
- } else {
87
- let e = await import(`${n(a.config).href}?t=${Date.now()}`);
88
- r = e.default || e;
104
+ });
105
+ };
106
+ const getWatchFiles = async (resolvedConfigs) => {
107
+ const filesToWatch = /* @__PURE__ */ new Set();
108
+ for (const item of resolvedConfigs) {
109
+ if (item.file) filesToWatch.add(item.file.replace(/\\/g, "/"));
110
+ let configObj = null;
111
+ if (typeof item.config === "string") try {
112
+ let loaded;
113
+ if (item.config.endsWith(".json")) loaded = JSON.parse(fs.readFileSync(item.config, "utf-8"));
114
+ else {
115
+ const fileUrl = pathToFileURL(item.config).href;
116
+ loaded = unwrapDefault(await import(`${fileUrl}?t=${Date.now()}`));
89
117
  }
90
- } catch (e) {
91
- l(`Failed to parse config file: ${a.config}. Error: ${e.message}`, "error");
118
+ if (isConfig(loaded)) configObj = loaded;
119
+ else log(`Config file did not resolve to a configuration object: ${item.config}`, "error");
120
+ } catch (err) {
121
+ log(`Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}`, "error");
92
122
  }
93
- else r = a.config;
94
- if (r) {
95
- let e = (e) => {
96
- if (typeof e == "string") {
97
- let n = (t.isAbsolute(e) ? e : t.resolve(a.dir, e)).replace(/\\/g, "/");
98
- i.add(n);
123
+ else configObj = item.config;
124
+ if (configObj) {
125
+ const addPattern = (pattern) => {
126
+ if (typeof pattern === "string") {
127
+ const normalized = (path.isAbsolute(pattern) ? pattern : path.resolve(item.dir, pattern)).replace(/\\/g, "/");
128
+ filesToWatch.add(normalized);
99
129
  }
100
130
  };
101
- r.source && (Array.isArray(r.source) ? r.source.forEach(e) : e(r.source)), r.include && (Array.isArray(r.include) ? r.include.forEach(e) : e(r.include));
131
+ if (configObj.source) {
132
+ if (Array.isArray(configObj.source)) configObj.source.forEach(addPattern);
133
+ else addPattern(configObj.source);
134
+ }
135
+ if (configObj.include) {
136
+ if (Array.isArray(configObj.include)) configObj.include.forEach(addPattern);
137
+ else addPattern(configObj.include);
138
+ }
102
139
  }
103
140
  }
104
- if (a.watch) {
105
- let e = Array.isArray(a.watch) ? a.watch : [a.watch];
106
- for (let n of e) {
107
- let e = t.isAbsolute(n) ? n : t.resolve(c, n);
108
- i.add(e.replace(/\\/g, "/"));
141
+ if (options.watch) {
142
+ const extraWatches = Array.isArray(options.watch) ? options.watch : [options.watch];
143
+ for (const pattern of extraWatches) {
144
+ const absolutePattern = path.isAbsolute(pattern) ? pattern : path.resolve(root, pattern);
145
+ filesToWatch.add(absolutePattern.replace(/\\/g, "/"));
109
146
  }
110
147
  }
111
- return Array.from(i);
112
- }, p = async (n, a) => {
113
- let o = Date.now();
148
+ return Array.from(filesToWatch);
149
+ };
150
+ const runBuilds = async (resolvedConfigs, context) => {
151
+ const startTime = Date.now();
114
152
  try {
115
- a || l("Compiling design tokens...", "info");
116
- let d = /* @__PURE__ */ new Set();
117
- for (let e of n) {
118
- let n = new i(e.config);
119
- await n.hasInitialized;
120
- let r = await n.extend({ log: { verbosity: "silent" } });
121
- if (r.volume = u, await r.buildAllPlatforms(), !a && !s && r.platforms) for (let e of Object.keys(r.platforms)) {
122
- let n = r.platforms[e];
123
- if (n && n.files) {
124
- let e = n.buildPath || "";
125
- for (let r of n.files) if (r && r.destination) {
126
- let n = t.isAbsolute(e) ? e : t.resolve(c, e), i = t.isAbsolute(r.destination) ? r.destination : t.resolve(n, r.destination);
127
- d.add(i);
128
- }
153
+ if (!context) log("Compiling design tokens...", "info");
154
+ const generatedFiles = /* @__PURE__ */ new Set();
155
+ for (const item of resolvedConfigs) {
156
+ const sd = new StyleDictionary(item.config);
157
+ await sd.hasInitialized;
158
+ const silentSD = await sd.extend({ log: { verbosity: "silent" } });
159
+ silentSD.volume = atomicVolume;
160
+ await silentSD.buildAllPlatforms();
161
+ if (!context && !silent) for (const platform of Object.values(silentSD.platforms)) {
162
+ const buildPath = platform.buildPath ?? "";
163
+ for (const file of platform.files ?? []) if (file.destination) {
164
+ const absoluteBuildPath = path.isAbsolute(buildPath) ? buildPath : path.resolve(root, buildPath);
165
+ const absoluteDestination = path.isAbsolute(file.destination) ? file.destination : path.resolve(absoluteBuildPath, file.destination);
166
+ generatedFiles.add(absoluteDestination);
129
167
  }
130
168
  }
131
169
  }
132
- let f = Date.now() - o;
133
- if (a) l(`Rebuilt design tokens due to change in ${a} (${f}ms)`, "success");
170
+ const duration = Date.now() - startTime;
171
+ if (context) log(`Rebuilt design tokens due to change in ${context} (${duration}ms)`, "success");
134
172
  else {
135
- if (!s && d.size > 0) {
136
- let n = [];
137
- for (let i of d) if (e.existsSync(i)) {
138
- let a = t.relative(c, i).replace(/\\/g, "/"), o = t.dirname(a), s = t.basename(a), l = o === "." ? `\x1b[32m${s}\x1b[0m` : `\x1b[90m${o}/\x1b[0m\x1b[32m${s}\x1b[0m`;
173
+ if (!silent && generatedFiles.size > 0) {
174
+ const fileInfos = [];
175
+ for (const filePath of generatedFiles) if (fs.existsSync(filePath)) {
176
+ const displayPath = path.relative(root, filePath).replace(/\\/g, "/");
177
+ const dir = path.dirname(displayPath);
178
+ const base = path.basename(displayPath);
179
+ const coloredPath = dir === "." ? `\x1b[32m${base}\x1b[0m` : `\x1b[90m${dir}/\x1b[0m\x1b[32m${base}\x1b[0m`;
139
180
  try {
140
- let t = `${(e.statSync(i).size / 1024).toFixed(2)} kB`, o = e.readFileSync(i), s = `${(r.gzipSync(o).length / 1024).toFixed(2)} kB`;
141
- n.push({
142
- coloredPath: l,
143
- gzipSizeStr: s,
144
- relativeDisplayPath: a,
145
- sizeStr: t
181
+ const sizeStr = `${(fs.statSync(filePath).size / 1024).toFixed(2)} kB`;
182
+ const content = fs.readFileSync(filePath);
183
+ const gzipSizeStr = `${(zlib.gzipSync(content).length / 1024).toFixed(2)} kB`;
184
+ fileInfos.push({
185
+ coloredPath,
186
+ gzipSizeStr,
187
+ relativeDisplayPath: displayPath,
188
+ sizeStr
146
189
  });
147
190
  } catch {}
148
191
  }
149
- if (n.length > 0) {
150
- let e = Math.max(...n.map((e) => e.relativeDisplayPath.length), 0), t = Math.max(...n.map((e) => e.sizeStr.length), 0);
151
- for (let r of n) {
152
- let n = " ".repeat(Math.max(2, e - r.relativeDisplayPath.length + 2)), i = r.sizeStr.padStart(t);
153
- console.log(`${r.coloredPath}${n}\x1b[90m${i} gzip: ${r.gzipSizeStr}\x1b[0m`);
192
+ if (fileInfos.length > 0) {
193
+ const longestPathLength = Math.max(...fileInfos.map((f) => f.relativeDisplayPath.length), 0);
194
+ const longestSizeLength = Math.max(...fileInfos.map((f) => f.sizeStr.length), 0);
195
+ for (const info of fileInfos) {
196
+ const pathPadding = " ".repeat(Math.max(2, longestPathLength - info.relativeDisplayPath.length + 2));
197
+ const sizePadded = info.sizeStr.padStart(longestSizeLength);
198
+ console.log(`${info.coloredPath}${pathPadding}\x1b[90m${sizePadded} │ gzip: ${info.gzipSizeStr}\x1b[0m`);
154
199
  }
155
200
  }
156
201
  }
157
- l(`Compiled successfully! (${f}ms)`, "success");
202
+ log(`Compiled successfully! (${duration}ms)`, "success");
158
203
  }
159
- } catch (e) {
160
- let t = Date.now() - o;
161
- l(`Compilation failed after ${t}ms: ${e.message}`, "error");
204
+ } catch (err) {
205
+ const duration = Date.now() - startTime;
206
+ log(`Compilation failed after ${duration}ms: ${errorMessage(err)}`, "error");
162
207
  }
163
208
  };
164
209
  return {
165
210
  async buildStart() {
166
- let e = await d();
167
- if (e.length === 0) return;
168
- let t = await f(e);
169
- for (let e of t) this.addWatchFile(e);
170
- await p(e);
211
+ const resolved = await resolveConfigs();
212
+ if (resolved.length === 0) return;
213
+ const watchFiles = await getWatchFiles(resolved);
214
+ for (const file of watchFiles) this.addWatchFile(file);
215
+ await runBuilds(resolved);
171
216
  },
172
217
  name: "unplugin-style-dictionary",
173
218
  vite: {
174
- configResolved(e) {
175
- c = e.root || process.cwd();
219
+ configResolved(config) {
220
+ root = config.root || process.cwd();
176
221
  },
177
- async configureServer(e) {
178
- let n = await d();
179
- if (n.length === 0) return;
180
- let r = await f(n);
181
- e.watcher.add(r), e.watcher.on("all", async (n, i) => {
182
- if (!o(i, r)) return;
183
- let a = await d();
184
- await p(a, t.basename(i));
185
- let s = await f(a);
186
- e.watcher.add(s);
222
+ async configureServer(server) {
223
+ const resolved = await resolveConfigs();
224
+ if (resolved.length === 0) return;
225
+ const filesToWatch = await getWatchFiles(resolved);
226
+ server.watcher.add(filesToWatch);
227
+ server.watcher.on("all", (_event, file) => {
228
+ if (!matchesWatchedFile(file, filesToWatch)) return;
229
+ (async () => {
230
+ try {
231
+ const currentResolved = await resolveConfigs();
232
+ await runBuilds(currentResolved, path.basename(file));
233
+ const newWatches = await getWatchFiles(currentResolved);
234
+ server.watcher.add(newWatches);
235
+ } catch (err) {
236
+ log(`Rebuild failed: ${errorMessage(err)}`, "error");
237
+ }
238
+ })();
187
239
  });
188
240
  }
189
241
  },
190
- async watchChange(e) {
191
- let n = await d();
192
- if (n.length === 0) return;
193
- let r = await f(n);
194
- if (o(e, r)) {
195
- await p(n, t.basename(e));
196
- for (let e of r) this.addWatchFile(e);
197
- }
242
+ async watchChange(id) {
243
+ const resolved = await resolveConfigs();
244
+ if (resolved.length === 0) return;
245
+ const watchFiles = await getWatchFiles(resolved);
246
+ if (!matchesWatchedFile(id, watchFiles)) return;
247
+ await runBuilds(resolved, path.basename(id));
248
+ for (const file of watchFiles) this.addWatchFile(file);
198
249
  }
199
250
  };
200
- }, f = /* #__PURE__ */ a(d);
251
+ };
252
+ const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory);
201
253
  //#endregion
202
- export { f as default, f as unplugin, o as matchesWatchedFile, d as unpluginFactory };
254
+ export { unplugin as default, unplugin, matchesWatchedFile, unpluginFactory };
203
255
 
204
256
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'style-dictionary'\nimport type { UnpluginFactory } from 'unplugin'\nimport type { ViteDevServer } from 'vite'\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport zlib from 'node:zlib'\nimport StyleDictionary from 'style-dictionary'\nimport { createUnplugin } from 'unplugin'\n\nimport type { UnpluginStyleDictionaryOptions } from './types.js'\n\nexport * from './types.js'\n\n// Whether `file` matches one of the resolved config/token watch patterns.\n// Shared by the Vite-specific `configureServer` watcher and the universal\n// `watchChange` hook — both need it, and both must skip files that don't\n// match: without this filter, `watchChange` reacts to *any* changed\n// module-graph file, including this plugin's own generated output (since\n// consuming code imports it). Every regenerate is itself a \"change\", which\n// without filtering re-triggers a rebuild forever.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some((pattern) => {\n // If the pattern is an exact file path\n if (pattern === normalizedFile) return true\n\n // If the pattern is a glob, we check if the file matches it.\n // Note: A simple string match or simple glob matcher can be used here.\n // For simplicity and correctness, since token source paths are usually globs,\n // we can match based on path directory containment or general matching.\n // Let's implement a robust matchesGlob check or check if it's one of the token files.\n // Chokidar triggers on actual files, so we want to check if the changed file matches\n // any of the config files or token files/globs.\n return (\n normalizedFile.startsWith(pattern.replace(/\\/\\*\\*/g, '')) ||\n (pattern.includes('*') &&\n new RegExp(\n pattern.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*'),\n ).test(normalizedFile))\n )\n })\n}\n\n// 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// Best-effort cleanup of a temporary file whose write or rename failed. The\n// original failure is what the caller reports, so nothing here may throw.\nfunction discardTemporaryFile(temporary: string): void {\n try {\n fs.rmSync(temporary, { force: true })\n } catch {\n // Ignore: a leftover temporary file is not worth masking the real error.\n }\n}\n\n// Temporary path for an atomic write of `destination`.\n//\n// It has to be a sibling of the destination, because `rename` is only atomic\n// within one filesystem and the system temp directory is often a different\n// mount. The final extension is dropped rather than kept, so the temporary\n// file cannot match a pattern written for the generated file's own extension —\n// `matchesWatchedFile` tests its globs unanchored, and a leftover\n// `vars.css.tmp` would match a `*.css` watch. The pid and counter make the\n// name unique, so two writes of the same destination — parallel platforms in\n// one build, or two builds overlapping — never share a temporary file.\nlet temporaryFileCounter = 0\n\nfunction temporaryPathFor(destination: string): string {\n const extension = path.extname(destination)\n\n return path.join(\n path.dirname(destination),\n `.${path.basename(destination, extension)}.${process.pid}.${temporaryFileCounter++}.tmp`,\n )\n}\n\nconst writeFileAtomic: typeof fs.promises.writeFile = async (\n file,\n data,\n options,\n) => {\n // A file handle or descriptor is already-open state that a rename cannot\n // stand in for, so only a path is written atomically.\n if (typeof file !== 'string') {\n return fs.promises.writeFile(file, data, options)\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n await fs.promises.writeFile(temporary, data, options)\n await fs.promises.rename(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\nconst writeFileSyncAtomic: typeof fs.writeFileSync = (file, data, options) => {\n if (typeof file !== 'string') {\n fs.writeFileSync(file, data, options)\n return\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n fs.writeFileSync(temporary, data, options)\n fs.renameSync(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\n// `node:fs` with both write entry points swapped for their atomic\n// equivalents, handed to Style Dictionary as the volume it builds through.\n// Everything else — reads, `mkdir`, `access`, the `promises` namespace — is\n// inherited from `node:fs` unchanged, so only the moment a file becomes\n// visible to readers changes. Custom actions receive this volume too, so\n// whatever they emit is written the same way.\n//\n// It is assigned onto the instance rather than passed as the `volume`\n// constructor option on purpose: that option marks the volume as a custom\n// filesystem shim, which switches Style Dictionary's path resolution off for\n// every read as well.\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\nexport const unpluginFactory: UnpluginFactory<\n undefined | UnpluginStyleDictionaryOptions,\n false\n> = (options = {}) => {\n const { silent = false } = options\n let root = process.cwd()\n\n // Helper to log if not silent\n const log = (\n message: string,\n type: 'error' | 'info' | 'success' = 'info',\n ) => {\n if (silent) return\n const prefix = '[unplugin-style-dictionary]'\n if (type === 'error') {\n console.error(`\\x1b[31m${prefix} ${message}\\x1b[0m`)\n } else if (type === 'success') {\n console.log(`\\x1b[32m${prefix} ${message}\\x1b[0m`)\n } else {\n console.log(`\\x1b[36m${prefix} ${message}\\x1b[0m`)\n }\n }\n\n // Resolve config file paths / objects\n const resolveConfigs = async (): Promise<\n Array<{ config: Config | string; dir: string; file?: string }>\n > => {\n let rawConfig = options.config\n\n // If config is not defined, look for default configuration files\n if (!rawConfig) {\n const defaults = [\n 'sd.config.json',\n 'config.json',\n 'sd.config.js',\n 'sd.config.mjs',\n ]\n for (const file of defaults) {\n const fullPath = path.resolve(root, file)\n if (fs.existsSync(fullPath)) {\n rawConfig = file\n break\n }\n }\n }\n\n if (!rawConfig) {\n log(\n 'No configuration specified and no default config file found. Style Dictionary will not compile.',\n 'error',\n )\n return []\n }\n\n // Evaluate function if provided\n if (typeof rawConfig === 'function') {\n rawConfig = await rawConfig()\n }\n\n const configs = Array.isArray(rawConfig) ? rawConfig : [rawConfig]\n\n return configs.map((conf) => {\n if (typeof conf === 'string') {\n const fullPath = path.resolve(root, conf)\n return {\n config: fullPath,\n dir: path.dirname(fullPath),\n file: fullPath,\n }\n } else {\n return {\n config: conf,\n dir: root,\n }\n }\n })\n }\n\n // Parse token files to watch\n const getWatchFiles = async (\n resolvedConfigs: Array<{\n config: Config | string\n dir: string\n file?: string\n }>,\n ): Promise<string[]> => {\n const filesToWatch = new Set<string>()\n\n for (const item of resolvedConfigs) {\n if (item.file) {\n filesToWatch.add(item.file.replace(/\\\\/g, '/'))\n }\n\n let configObj: Config | null = null\n\n if (typeof item.config === 'string') {\n try {\n if (item.config.endsWith('.json')) {\n const content = fs.readFileSync(item.config, 'utf-8')\n configObj = JSON.parse(content)\n } else {\n const fileUrl = pathToFileURL(item.config).href\n const module = await import(`${fileUrl}?t=${Date.now()}`)\n configObj = module.default || module\n }\n } catch (err) {\n log(\n `Failed to parse config file: ${item.config}. Error: ${(err as Error).message}`,\n 'error',\n )\n }\n } else {\n configObj = item.config\n }\n\n if (configObj) {\n const addPattern = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(item.dir, pattern)\n const normalized = absolutePattern.replace(/\\\\/g, '/')\n filesToWatch.add(normalized)\n }\n }\n\n if (configObj.source) {\n if (Array.isArray(configObj.source)) {\n configObj.source.forEach(addPattern)\n } else {\n addPattern(configObj.source)\n }\n }\n\n if (configObj.include) {\n if (Array.isArray(configObj.include)) {\n configObj.include.forEach(addPattern)\n } else {\n addPattern(configObj.include)\n }\n }\n }\n }\n\n // Add manually configured watch files\n if (options.watch) {\n const extraWatches = Array.isArray(options.watch)\n ? options.watch\n : [options.watch]\n for (const pattern of extraWatches) {\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(root, pattern)\n filesToWatch.add(absolutePattern.replace(/\\\\/g, '/'))\n }\n }\n\n return Array.from(filesToWatch)\n }\n\n // Compile design tokens\n const runBuilds = async (\n resolvedConfigs: Array<{ config: Config | string; dir: string }>,\n context?: string,\n ) => {\n const startTime = Date.now()\n try {\n if (!context) {\n log('Compiling design tokens...', 'info')\n }\n\n const generatedFiles = new Set<string>()\n\n for (const item of resolvedConfigs) {\n const sd = new StyleDictionary(item.config)\n await sd.hasInitialized\n const silentSD = await sd.extend({\n log: {\n verbosity: 'silent',\n },\n })\n // Swap in the atomic volume only now that the instance has finished\n // reading its configs and token sources, so every write below lands\n // through `rename` while the read path stays exactly as it was.\n silentSD.volume = atomicVolume\n await silentSD.buildAllPlatforms()\n\n if (!context && !silent && silentSD.platforms) {\n for (const platformName of Object.keys(silentSD.platforms)) {\n const platform = silentSD.platforms[platformName]\n if (platform && platform.files) {\n const buildPath = platform.buildPath || ''\n for (const file of platform.files) {\n if (file && file.destination) {\n const absoluteBuildPath = path.isAbsolute(buildPath)\n ? buildPath\n : path.resolve(root, buildPath)\n const absoluteDestination = path.isAbsolute(file.destination)\n ? file.destination\n : path.resolve(absoluteBuildPath, file.destination)\n generatedFiles.add(absoluteDestination)\n }\n }\n }\n }\n }\n }\n\n const duration = Date.now() - startTime\n\n if (context) {\n log(\n `Rebuilt design tokens due to change in ${context} (${duration}ms)`,\n 'success',\n )\n } else {\n if (!silent && generatedFiles.size > 0) {\n const fileInfos: Array<{\n coloredPath: string\n gzipSizeStr: string\n relativeDisplayPath: string\n sizeStr: string\n }> = []\n\n for (const filePath of generatedFiles) {\n if (fs.existsSync(filePath)) {\n const displayPath = path\n .relative(root, filePath)\n .replace(/\\\\/g, '/')\n const dir = path.dirname(displayPath)\n const base = path.basename(displayPath)\n const coloredPath =\n dir === '.'\n ? `\\x1b[32m${base}\\x1b[0m`\n : `\\x1b[90m${dir}/\\x1b[0m\\x1b[32m${base}\\x1b[0m`\n\n try {\n const stats = fs.statSync(filePath)\n const bytes = stats.size\n const sizeStr = `${(bytes / 1024).toFixed(2)} kB`\n\n const content = fs.readFileSync(filePath)\n const gzipBytes = zlib.gzipSync(content).length\n const gzipSizeStr = `${(gzipBytes / 1024).toFixed(2)} kB`\n\n fileInfos.push({\n coloredPath,\n gzipSizeStr,\n relativeDisplayPath: displayPath,\n sizeStr,\n })\n } catch {\n // Ignore errors reading individual files\n }\n }\n }\n\n if (fileInfos.length > 0) {\n const longestPathLength = Math.max(\n ...fileInfos.map((f) => f.relativeDisplayPath.length),\n 0,\n )\n const longestSizeLength = Math.max(\n ...fileInfos.map((f) => f.sizeStr.length),\n 0,\n )\n\n for (const info of fileInfos) {\n const pathPadding = ' '.repeat(\n Math.max(\n 2,\n longestPathLength - info.relativeDisplayPath.length + 2,\n ),\n )\n const sizePadded = info.sizeStr.padStart(longestSizeLength)\n console.log(\n `${info.coloredPath}${pathPadding}\\x1b[90m${sizePadded} │ gzip: ${info.gzipSizeStr}\\x1b[0m`,\n )\n }\n }\n }\n\n log(`Compiled successfully! (${duration}ms)`, 'success')\n }\n } catch (err) {\n const duration = Date.now() - startTime\n log(\n `Compilation failed after ${duration}ms: ${(err as Error).message}`,\n 'error',\n )\n }\n }\n\n return {\n async buildStart() {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Register token/config files with the host bundler's watch mode.\n // Works out of the box wherever the host runs a persistent watcher\n // (e.g. `rollup --watch`); Vite's dev server is additionally handled\n // below via the `vite.configureServer` escape hatch, since token\n // sources are plain JSON/JS files outside the module graph and\n // `watchChange` is not reliably invoked by Vite while serving.\n const watchFiles = await getWatchFiles(resolved)\n for (const file of watchFiles) {\n this.addWatchFile(file)\n }\n\n await runBuilds(resolved)\n },\n\n name: 'unplugin-style-dictionary',\n\n vite: {\n configResolved(config) {\n root = config.root || process.cwd()\n },\n\n async configureServer(server: ViteDevServer) {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n const filesToWatch = await getWatchFiles(resolved)\n\n // Watch configuration files and token files\n server.watcher.add(filesToWatch)\n\n server.watcher.on('all', async (_event, file) => {\n if (!matchesWatchedFile(file, filesToWatch)) return\n\n // Re-resolve configs to handle added/removed configs or changes to config itself\n const currentResolved = await resolveConfigs()\n await runBuilds(currentResolved, path.basename(file))\n\n // Dynamically update the watch list in case the configurations changed\n const newWatches = await getWatchFiles(currentResolved)\n server.watcher.add(newWatches)\n })\n },\n },\n\n async watchChange(id) {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n const watchFiles = await getWatchFiles(resolved)\n // Without this check, watchChange fires for *any* changed file in the\n // host bundler's module graph — including our own generated output,\n // since consuming code imports it. Every regenerate is itself a\n // \"change\", so skipping non-matching files here is what keeps this\n // from rebuilding forever.\n if (!matchesWatchedFile(id, watchFiles)) return\n\n await runBuilds(resolved, path.basename(id))\n\n for (const file of watchFiles) {\n this.addWatchFile(file)\n }\n },\n }\n}\n\nexport const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory)\n\nexport default unplugin\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'style-dictionary'\nimport type { UnpluginFactory } from 'unplugin'\nimport type { ViteDevServer } from 'vite'\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport zlib from 'node:zlib'\nimport StyleDictionary from 'style-dictionary'\nimport { createUnplugin } from 'unplugin'\n\nimport type { UnpluginStyleDictionaryOptions } from './types.js'\n\nexport type * from './types.js'\n\n// Whether `file` matches one of the resolved config/token watch patterns.\n// Shared by the Vite-specific `configureServer` watcher and the universal\n// `watchChange` hook — both need it, and both must skip files that don't\n// match: without this filter, `watchChange` reacts to *any* changed\n// module-graph file, including this plugin's own generated output (since\n// consuming code imports it). Every regenerate is itself a \"change\", which\n// without filtering re-triggers a rebuild forever.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some((pattern) => {\n // If the pattern is an exact file path\n if (pattern === normalizedFile) return true\n\n // If the pattern is a glob, we check if the file matches it.\n // Note: A simple string match or simple glob matcher can be used here.\n // For simplicity and correctness, since token source paths are usually globs,\n // we can match based on path directory containment or general matching.\n // Let's implement a robust matchesGlob check or check if it's one of the token files.\n // Chokidar triggers on actual files, so we want to check if the changed file matches\n // any of the config files or token files/globs.\n return (\n normalizedFile.startsWith(pattern.replace(/\\/\\*\\*/g, '')) ||\n (pattern.includes('*') &&\n new RegExp(\n pattern.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*'),\n ).test(normalizedFile))\n )\n })\n}\n\n// Best-effort cleanup of a temporary file whose write or rename failed. The\n// original failure is what the caller reports, so nothing here may throw.\nfunction discardTemporaryFile(temporary: string): void {\n try {\n fs.rmSync(temporary, { force: true })\n } catch {\n // Ignore: a leftover temporary file is not worth masking the real error.\n }\n}\n\n// `catch` binds `unknown`, and a thrown non-Error — a string, a rejected\n// value out of a config module — carries no `.message`. The `as Error` casts\n// this replaces claimed otherwise and printed `undefined` for exactly those\n// cases, which is the least useful thing a failure log can say.\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n// A config file is an untyped boundary: `JSON.parse` and a dynamic `import`\n// both hand back `any`, and an `any` assigned to `configObj` spreads through\n// every read of it downstream. These two narrow that boundary once, here.\n// They are type predicates rather than assertions on purpose — a predicate is\n// a check the compiler verifies, where a cast is only a claim.\nfunction isConfig(value: unknown): value is Config {\n return typeof value === 'object' && value !== null\n}\n\n// A config module may expose its config as a `default` export or as the\n// namespace itself. `'default' in value` is what lets the compiler reach\n// `.default` without a cast.\nfunction unwrapDefault(value: unknown): unknown {\n return typeof value === 'object' && value !== null && 'default' in value\n ? (value.default ?? value)\n : value\n}\n\n// Style Dictionary writes every generated file with a plain `writeFile` on the\n// volume it was handed, which truncates the destination and then streams the\n// new contents into it. Anything reading that file inside the window sees a\n// partial file: a consuming test run whose tokens are rebuilt mid-suite, or a\n// dev-server request landing on a rebuild, gets a truncated module and fails\n// to parse it. Writing a sibling temporary file and renaming it over the\n// destination closes the window — `rename` is atomic within a filesystem, so a\n// concurrent reader sees either the whole old file or the whole new one.\n\n// Temporary path for an atomic write of `destination`.\n//\n// It has to be a sibling of the destination, because `rename` is only atomic\n// within one filesystem and the system temp directory is often a different\n// mount. The final extension is dropped rather than kept, so the temporary\n// file cannot match a pattern written for the generated file's own extension —\n// `matchesWatchedFile` tests its globs unanchored, and a leftover\n// `vars.css.tmp` would match a `*.css` watch. The pid and counter make the\n// name unique, so two writes of the same destination — parallel platforms in\n// one build, or two builds overlapping — never share a temporary file.\nlet temporaryFileCounter = 0\n\nfunction temporaryPathFor(destination: string): string {\n const extension = path.extname(destination)\n\n return path.join(\n path.dirname(destination),\n `.${path.basename(destination, extension)}.${process.pid}.${temporaryFileCounter++}.tmp`,\n )\n}\n\nconst writeFileAtomic: typeof fs.promises.writeFile = async (\n file,\n data,\n options,\n) => {\n // A file handle or descriptor is already-open state that a rename cannot\n // stand in for, so only a path is written atomically.\n if (typeof file !== 'string') {\n return fs.promises.writeFile(file, data, options)\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n await fs.promises.writeFile(temporary, data, options)\n await fs.promises.rename(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\nconst writeFileSyncAtomic: typeof fs.writeFileSync = (file, data, options) => {\n if (typeof file !== 'string') {\n fs.writeFileSync(file, data, options)\n return\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n fs.writeFileSync(temporary, data, options)\n fs.renameSync(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\n// `node:fs` with both write entry points swapped for their atomic\n// equivalents, handed to Style Dictionary as the volume it builds through.\n// Everything else — reads, `mkdir`, `access`, the `promises` namespace — is\n// inherited from `node:fs` unchanged, so only the moment a file becomes\n// visible to readers changes. Custom actions receive this volume too, so\n// whatever they emit is written the same way.\n//\n// It is assigned onto the instance rather than passed as the `volume`\n// constructor option on purpose: that option marks the volume as a custom\n// filesystem shim, which switches Style Dictionary's path resolution off for\n// every read as well.\n// `Object.create` is declared as returning `any`, so pinning the result to\n// `typeof fs` is a claim no type guard can replace. The prototype link is the\n// whole point — see the note above — so rebuilding this with a spread, which\n// copies own properties and drops the chain, is not a substitute.\n/* oxlint-disable typescript/no-unsafe-type-assertion */\nconst atomicVolume = Object.create(fs, {\n promises: {\n value: Object.create(fs.promises, {\n writeFile: { value: writeFileAtomic },\n }) as typeof fs.promises,\n },\n writeFileSync: { value: writeFileSyncAtomic },\n}) as typeof fs\n/* oxlint-enable typescript/no-unsafe-type-assertion */\n\nexport const unpluginFactory: UnpluginFactory<\n undefined | UnpluginStyleDictionaryOptions,\n false\n> = (options = {}) => {\n const { silent = false } = options\n let root = process.cwd()\n\n // Helper to log if not silent\n const log = (\n message: string,\n type: 'error' | 'info' | 'success' = 'info',\n ) => {\n if (silent) return\n const prefix = '[unplugin-style-dictionary]'\n if (type === 'error') {\n console.error(`\\x1b[31m${prefix} ${message}\\x1b[0m`)\n } else if (type === 'success') {\n console.log(`\\x1b[32m${prefix} ${message}\\x1b[0m`)\n } else {\n console.log(`\\x1b[36m${prefix} ${message}\\x1b[0m`)\n }\n }\n\n // Resolve config file paths / objects\n const resolveConfigs = async (): Promise<\n Array<{ config: Config | string; dir: string; file?: string }>\n > => {\n let rawConfig = options.config\n\n // If config is not defined, look for default configuration files\n if (!rawConfig) {\n const defaults = [\n 'sd.config.json',\n 'config.json',\n 'sd.config.js',\n 'sd.config.mjs',\n ]\n for (const file of defaults) {\n const fullPath = path.resolve(root, file)\n if (fs.existsSync(fullPath)) {\n rawConfig = file\n break\n }\n }\n }\n\n if (!rawConfig) {\n log(\n 'No configuration specified and no default config file found. Style Dictionary will not compile.',\n 'error',\n )\n return []\n }\n\n // Evaluate function if provided\n if (typeof rawConfig === 'function') {\n rawConfig = await rawConfig()\n }\n\n const configs = Array.isArray(rawConfig) ? rawConfig : [rawConfig]\n\n return configs.map((conf) => {\n if (typeof conf === 'string') {\n const fullPath = path.resolve(root, conf)\n return {\n config: fullPath,\n dir: path.dirname(fullPath),\n file: fullPath,\n }\n } else {\n return {\n config: conf,\n dir: root,\n }\n }\n })\n }\n\n // Parse token files to watch\n const getWatchFiles = async (\n resolvedConfigs: Array<{\n config: Config | string\n dir: string\n file?: string\n }>,\n ): Promise<string[]> => {\n const filesToWatch = new Set<string>()\n\n for (const item of resolvedConfigs) {\n if (item.file) {\n filesToWatch.add(item.file.replace(/\\\\/g, '/'))\n }\n\n let configObj: Config | null = null\n\n if (typeof item.config === 'string') {\n try {\n let loaded: unknown\n\n if (item.config.endsWith('.json')) {\n loaded = JSON.parse(fs.readFileSync(item.config, 'utf-8'))\n } else {\n const fileUrl = pathToFileURL(item.config).href\n // Sequential on purpose: a config module runs arbitrary code at\n // import time — `registerFormat` and friends — and Style\n // Dictionary's registries are global, so importing several at\n // once would interleave those registrations.\n loaded = unwrapDefault(await import(`${fileUrl}?t=${Date.now()}`))\n }\n\n if (isConfig(loaded)) {\n configObj = loaded\n } else {\n log(\n `Config file did not resolve to a configuration object: ${item.config}`,\n 'error',\n )\n }\n } catch (err) {\n log(\n `Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}`,\n 'error',\n )\n }\n } else {\n configObj = item.config\n }\n\n if (configObj) {\n const addPattern = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(item.dir, pattern)\n const normalized = absolutePattern.replace(/\\\\/g, '/')\n filesToWatch.add(normalized)\n }\n }\n\n if (configObj.source) {\n if (Array.isArray(configObj.source)) {\n configObj.source.forEach(addPattern)\n } else {\n addPattern(configObj.source)\n }\n }\n\n if (configObj.include) {\n if (Array.isArray(configObj.include)) {\n configObj.include.forEach(addPattern)\n } else {\n addPattern(configObj.include)\n }\n }\n }\n }\n\n // Add manually configured watch files\n if (options.watch) {\n const extraWatches = Array.isArray(options.watch)\n ? options.watch\n : [options.watch]\n for (const pattern of extraWatches) {\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(root, pattern)\n filesToWatch.add(absolutePattern.replace(/\\\\/g, '/'))\n }\n }\n\n return Array.from(filesToWatch)\n }\n\n // Compile design tokens\n const runBuilds = async (\n resolvedConfigs: Array<{ config: Config | string; dir: string }>,\n context?: string,\n ) => {\n const startTime = Date.now()\n try {\n if (!context) {\n log('Compiling design tokens...', 'info')\n }\n\n const generatedFiles = new Set<string>()\n\n // Configurations are built one after another rather than with\n // `Promise.all`, and that is load-bearing. Two configurations may name\n // the same destination file, and each instance gets the atomic volume\n // swapped onto it below — overlapping builds would interleave those\n // writes and hand a reader a file assembled from both.\n for (const item of resolvedConfigs) {\n const sd = new StyleDictionary(item.config)\n await sd.hasInitialized\n const silentSD = await sd.extend({\n log: {\n verbosity: 'silent',\n },\n })\n // Swap in the atomic volume only now that the instance has finished\n // reading its configs and token sources, so every write below lands\n // through `rename` while the read path stays exactly as it was.\n silentSD.volume = atomicVolume\n await silentSD.buildAllPlatforms()\n\n if (!context && !silent) {\n for (const platform of Object.values(silentSD.platforms)) {\n const buildPath = platform.buildPath ?? ''\n for (const file of platform.files ?? []) {\n if (file.destination) {\n const absoluteBuildPath = path.isAbsolute(buildPath)\n ? buildPath\n : path.resolve(root, buildPath)\n const absoluteDestination = path.isAbsolute(file.destination)\n ? file.destination\n : path.resolve(absoluteBuildPath, file.destination)\n generatedFiles.add(absoluteDestination)\n }\n }\n }\n }\n }\n\n const duration = Date.now() - startTime\n\n if (context) {\n log(\n `Rebuilt design tokens due to change in ${context} (${duration}ms)`,\n 'success',\n )\n } else {\n if (!silent && generatedFiles.size > 0) {\n const fileInfos: Array<{\n coloredPath: string\n gzipSizeStr: string\n relativeDisplayPath: string\n sizeStr: string\n }> = []\n\n for (const filePath of generatedFiles) {\n if (fs.existsSync(filePath)) {\n const displayPath = path\n .relative(root, filePath)\n .replace(/\\\\/g, '/')\n const dir = path.dirname(displayPath)\n const base = path.basename(displayPath)\n const coloredPath =\n dir === '.'\n ? `\\x1b[32m${base}\\x1b[0m`\n : `\\x1b[90m${dir}/\\x1b[0m\\x1b[32m${base}\\x1b[0m`\n\n try {\n const stats = fs.statSync(filePath)\n const bytes = stats.size\n const sizeStr = `${(bytes / 1024).toFixed(2)} kB`\n\n const content = fs.readFileSync(filePath)\n const gzipBytes = zlib.gzipSync(content).length\n const gzipSizeStr = `${(gzipBytes / 1024).toFixed(2)} kB`\n\n fileInfos.push({\n coloredPath,\n gzipSizeStr,\n relativeDisplayPath: displayPath,\n sizeStr,\n })\n } catch {\n // Ignore errors reading individual files\n }\n }\n }\n\n if (fileInfos.length > 0) {\n const longestPathLength = Math.max(\n ...fileInfos.map((f) => f.relativeDisplayPath.length),\n 0,\n )\n const longestSizeLength = Math.max(\n ...fileInfos.map((f) => f.sizeStr.length),\n 0,\n )\n\n for (const info of fileInfos) {\n const pathPadding = ' '.repeat(\n Math.max(\n 2,\n longestPathLength - info.relativeDisplayPath.length + 2,\n ),\n )\n const sizePadded = info.sizeStr.padStart(longestSizeLength)\n console.log(\n `${info.coloredPath}${pathPadding}\\x1b[90m${sizePadded} │ gzip: ${info.gzipSizeStr}\\x1b[0m`,\n )\n }\n }\n }\n\n log(`Compiled successfully! (${duration}ms)`, 'success')\n }\n } catch (err) {\n const duration = Date.now() - startTime\n log(\n `Compilation failed after ${duration}ms: ${errorMessage(err)}`,\n 'error',\n )\n }\n }\n\n return {\n async buildStart() {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Register token/config files with the host bundler's watch mode.\n // Works out of the box wherever the host runs a persistent watcher\n // (e.g. `rollup --watch`); Vite's dev server is additionally handled\n // below via the `vite.configureServer` escape hatch, since token\n // sources are plain JSON/JS files outside the module graph and\n // `watchChange` is not reliably invoked by Vite while serving.\n const watchFiles = await getWatchFiles(resolved)\n for (const file of watchFiles) {\n this.addWatchFile(file)\n }\n\n await runBuilds(resolved)\n },\n\n name: 'unplugin-style-dictionary',\n\n vite: {\n configResolved(config) {\n root = config.root || process.cwd()\n },\n\n async configureServer(server: ViteDevServer) {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n const filesToWatch = await getWatchFiles(resolved)\n\n // Watch configuration files and token files\n server.watcher.add(filesToWatch)\n\n // chokidar types its listener as returning void and does not await\n // what it is handed, so handing it an async function left every\n // rejection floating — `runBuilds` catches its own, but\n // `resolveConfigs` and `getWatchFiles` do not. Launching the work\n // explicitly and catching here is what keeps a bad config on disk\n // from surfacing as an unhandled rejection that kills the dev server.\n server.watcher.on('all', (_event, file) => {\n if (!matchesWatchedFile(file, filesToWatch)) return\n\n void (async () => {\n try {\n // Re-resolve configs to handle added/removed configs or changes\n // to config itself\n const currentResolved = await resolveConfigs()\n await runBuilds(currentResolved, path.basename(file))\n\n // Dynamically update the watch list in case the configurations\n // changed\n const newWatches = await getWatchFiles(currentResolved)\n server.watcher.add(newWatches)\n } catch (err) {\n log(`Rebuild failed: ${errorMessage(err)}`, 'error')\n }\n })()\n })\n },\n },\n\n // Rollup types `watchChange` as returning void, yet awaits it as a\n // sequential hook — and the work here is inherently asynchronous. The\n // signature is the thing that is wrong, so the rule is silenced rather\n // than the hook made to lie about finishing.\n // oxlint-disable-next-line typescript/no-misused-promises\n async watchChange(id) {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n const watchFiles = await getWatchFiles(resolved)\n // Without this check, watchChange fires for *any* changed file in the\n // host bundler's module graph — including our own generated output,\n // since consuming code imports it. Every regenerate is itself a\n // \"change\", so skipping non-matching files here is what keeps this\n // from rebuilding forever.\n if (!matchesWatchedFile(id, watchFiles)) return\n\n await runBuilds(resolved, path.basename(id))\n\n for (const file of watchFiles) {\n this.addWatchFile(file)\n }\n },\n }\n}\n\nexport const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory)\n\nexport default unplugin\n"],"mappings":"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"}
@@ -1,2 +1,6 @@
1
- declare const _default: (options?: import('./types.js').UnpluginStyleDictionaryOptions | undefined) => import('rolldown').Plugin<any>;
2
- export default _default;
1
+ import { UnpluginStyleDictionaryOptions } from "./types.js";
2
+ //#region src/rolldown.d.ts
3
+ declare const _default: (options?: UnpluginStyleDictionaryOptions | undefined) => import("rolldown").Plugin<any>;
4
+ //#endregion
5
+ export { _default as default };
6
+ //# sourceMappingURL=rolldown.d.ts.map
package/dist/rolldown.js CHANGED
@@ -1,7 +1,7 @@
1
- import e from "./index.js";
1
+ import unplugin from "./index.js";
2
2
  //#region src/rolldown.ts
3
- var t = e.rolldown;
3
+ var rolldown_default = unplugin.rolldown;
4
4
  //#endregion
5
- export { t as default };
5
+ export { rolldown_default as default };
6
6
 
7
7
  //# sourceMappingURL=rolldown.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rolldown.js","names":[],"sources":["../src/rolldown.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.rolldown\n"],"mappings":";;AAEA,IAAA,IAAe,EAAS"}
1
+ {"version":3,"file":"rolldown.js","names":[],"sources":["../src/rolldown.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.rolldown\n"],"mappings":";;AAEA,IAAA,mBAAe,SAAS"}
package/dist/rollup.d.ts CHANGED
@@ -1,2 +1,6 @@
1
- declare const _default: (options?: import('./types.js').UnpluginStyleDictionaryOptions | undefined) => import('rollup').Plugin<any>;
2
- export default _default;
1
+ import { UnpluginStyleDictionaryOptions } from "./types.js";
2
+ //#region src/rollup.d.ts
3
+ declare const _default: (options?: UnpluginStyleDictionaryOptions | undefined) => import("rollup").Plugin<any>;
4
+ //#endregion
5
+ export { _default as default };
6
+ //# sourceMappingURL=rollup.d.ts.map
package/dist/rollup.js CHANGED
@@ -1,7 +1,7 @@
1
- import e from "./index.js";
1
+ import unplugin from "./index.js";
2
2
  //#region src/rollup.ts
3
- var t = e.rollup;
3
+ var rollup_default = unplugin.rollup;
4
4
  //#endregion
5
- export { t as default };
5
+ export { rollup_default as default };
6
6
 
7
7
  //# sourceMappingURL=rollup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rollup.js","names":[],"sources":["../src/rollup.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.rollup\n"],"mappings":";;AAEA,IAAA,IAAe,EAAS"}
1
+ {"version":3,"file":"rollup.js","names":[],"sources":["../src/rollup.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.rollup\n"],"mappings":";;AAEA,IAAA,iBAAe,SAAS"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Config } from 'style-dictionary';
1
+ import { Config } from "style-dictionary";
2
+ //#region src/types.d.ts
2
3
  /**
3
4
  * Options for the Style Dictionary unplugin factory, shared across all bundler
4
5
  * targets (Vite, Rolldown, Rollup, Webpack).
@@ -10,30 +11,33 @@ import { Config } from 'style-dictionary';
10
11
  * graph. A one-shot build (e.g. `tsdown`/`rolldown build` without `--watch`)
11
12
  * only builds once, in `buildStart`.
12
13
  */
13
- export interface UnpluginStyleDictionaryOptions {
14
- /**
15
- * Style Dictionary configuration(s).
16
- * Can be:
17
- * - A file path string (e.g. 'sd.config.json')
18
- * - An array of file path strings
19
- * - A Style Dictionary configuration object
20
- * - An array of Style Dictionary configuration objects
21
- * - A function that returns a config or array of configs (or resolves to them).
22
- * Useful for calling `StyleDictionary.registerFormat()` (or other `register*`
23
- * methods) before returning a config that references the custom format by name.
24
- *
25
- * If not provided, it will look for 'sd.config.json' or 'config.json' in the root directory.
26
- */
27
- config?: (() => Config | Config[] | Promise<Config | Config[]>) | Config | Config[] | string | string[];
28
- /**
29
- * Disable console logging.
30
- * @default false
31
- */
32
- silent?: boolean;
33
- /**
34
- * Additional files or glob patterns to watch.
35
- * If config files are paths, those paths are watched automatically.
36
- * By default, the plugin also parses 'source' and 'include' properties in configurations and watches them.
37
- */
38
- watch?: string | string[];
14
+ interface UnpluginStyleDictionaryOptions {
15
+ /**
16
+ * Style Dictionary configuration(s).
17
+ * Can be:
18
+ * - A file path string (e.g. 'sd.config.json')
19
+ * - An array of file path strings
20
+ * - A Style Dictionary configuration object
21
+ * - An array of Style Dictionary configuration objects
22
+ * - A function that returns a config or array of configs (or resolves to them).
23
+ * Useful for calling `StyleDictionary.registerFormat()` (or other `register*`
24
+ * methods) before returning a config that references the custom format by name.
25
+ *
26
+ * If not provided, it will look for 'sd.config.json' or 'config.json' in the root directory.
27
+ */
28
+ config?: (() => Config | Config[] | Promise<Config | Config[]>) | Config | Config[] | string | string[];
29
+ /**
30
+ * Disable console logging.
31
+ * @default false
32
+ */
33
+ silent?: boolean;
34
+ /**
35
+ * Additional files or glob patterns to watch.
36
+ * If config files are paths, those paths are watched automatically.
37
+ * By default, the plugin also parses 'source' and 'include' properties in configurations and watches them.
38
+ */
39
+ watch?: string | string[];
39
40
  }
41
+ //#endregion
42
+ export { UnpluginStyleDictionaryOptions };
43
+ //# sourceMappingURL=types.d.ts.map
package/dist/vite.d.ts CHANGED
@@ -1,2 +1,6 @@
1
- declare const _default: (options?: import('./types.js').UnpluginStyleDictionaryOptions | undefined) => import('vite').Plugin<any>;
2
- export default _default;
1
+ import { UnpluginStyleDictionaryOptions } from "./types.js";
2
+ //#region src/vite.d.ts
3
+ declare const _default: (options?: UnpluginStyleDictionaryOptions | undefined) => import("vite").Plugin<any>;
4
+ //#endregion
5
+ export { _default as default };
6
+ //# sourceMappingURL=vite.d.ts.map
package/dist/vite.js CHANGED
@@ -1,7 +1,7 @@
1
- import e from "./index.js";
1
+ import unplugin from "./index.js";
2
2
  //#region src/vite.ts
3
- var t = e.vite;
3
+ var vite_default = unplugin.vite;
4
4
  //#endregion
5
- export { t as default };
5
+ export { vite_default as default };
6
6
 
7
7
  //# sourceMappingURL=vite.js.map
package/dist/vite.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.vite\n"],"mappings":";;AAEA,IAAA,IAAe,EAAS"}
1
+ {"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.vite\n"],"mappings":";;AAEA,IAAA,eAAe,SAAS"}
package/dist/webpack.d.ts CHANGED
@@ -1,2 +1,6 @@
1
- declare const _default: (options?: import('./types.js').UnpluginStyleDictionaryOptions | undefined) => import('webpack').WebpackPluginInstance;
2
- export default _default;
1
+ import { UnpluginStyleDictionaryOptions } from "./types.js";
2
+ //#region src/webpack.d.ts
3
+ declare const _default: (options?: UnpluginStyleDictionaryOptions | undefined) => import("webpack").WebpackPluginInstance;
4
+ //#endregion
5
+ export { _default as default };
6
+ //# sourceMappingURL=webpack.d.ts.map
package/dist/webpack.js CHANGED
@@ -1,7 +1,7 @@
1
- import e from "./index.js";
1
+ import unplugin from "./index.js";
2
2
  //#region src/webpack.ts
3
- var t = e.webpack;
3
+ var webpack_default = unplugin.webpack;
4
4
  //#endregion
5
- export { t as default };
5
+ export { webpack_default as default };
6
6
 
7
7
  //# sourceMappingURL=webpack.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webpack.js","names":[],"sources":["../src/webpack.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.webpack\n"],"mappings":";;AAEA,IAAA,IAAe,EAAS"}
1
+ {"version":3,"file":"webpack.js","names":[],"sources":["../src/webpack.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.webpack\n"],"mappings":";;AAEA,IAAA,kBAAe,SAAS"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanso-labs/unplugin-style-dictionary",
3
- "version": "0.2.6",
3
+ "version": "0.4.0",
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",
@@ -22,45 +22,45 @@
22
22
  "exports": {
23
23
  ".": {
24
24
  "types": "./dist/index.d.ts",
25
- "import": "./dist/index.js",
26
- "require": "./dist/index.cjs"
25
+ "default": "./dist/index.js"
27
26
  },
28
27
  "./vite": {
29
28
  "types": "./dist/vite.d.ts",
30
- "import": "./dist/vite.js",
31
- "require": "./dist/vite.cjs"
29
+ "default": "./dist/vite.js"
32
30
  },
33
31
  "./rolldown": {
34
32
  "types": "./dist/rolldown.d.ts",
35
- "import": "./dist/rolldown.js",
36
- "require": "./dist/rolldown.cjs"
33
+ "default": "./dist/rolldown.js"
37
34
  },
38
35
  "./rollup": {
39
36
  "types": "./dist/rollup.d.ts",
40
- "import": "./dist/rollup.js",
41
- "require": "./dist/rollup.cjs"
37
+ "default": "./dist/rollup.js"
42
38
  },
43
39
  "./webpack": {
44
40
  "types": "./dist/webpack.d.ts",
45
- "import": "./dist/webpack.js",
46
- "require": "./dist/webpack.cjs"
41
+ "default": "./dist/webpack.js"
47
42
  },
48
43
  "./package.json": "./package.json"
49
44
  },
50
- "main": "./dist/index.cjs",
45
+ "main": "./dist/index.js",
51
46
  "module": "./dist/index.js",
52
47
  "types": "./dist/index.d.ts",
53
48
  "type": "module",
49
+ "engines": {
50
+ "node": "^20.19.0 || >=22.12.0"
51
+ },
54
52
  "repository": {
55
53
  "type": "git",
56
54
  "url": "git+https://github.com/kanso-labs/unplugin-style-dictionary.git"
57
55
  },
58
56
  "scripts": {
59
- "build": "tsc -b && vite build",
57
+ "build": "tsc -b && tsdown",
60
58
  "clean": "rimraf dist",
61
- "lint": "eslint .",
62
- "test": "vitest run",
63
- "prepare": "husky"
59
+ "format": "oxfmt",
60
+ "format:check": "oxfmt --check",
61
+ "lint": "oxlint --type-aware . && eslint . && npm run format:check",
62
+ "prepare": "husky",
63
+ "test": "vitest run"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "rolldown": "*",
@@ -89,19 +89,21 @@
89
89
  "@eslint/js": "10.0.1",
90
90
  "@types/node": "26.2.0",
91
91
  "eslint": "10.9.0",
92
- "eslint-config-prettier": "10.1.8",
92
+ "eslint-plugin-oxlint": "1.78.0",
93
93
  "eslint-plugin-perfectionist": "5.10.1",
94
- "eslint-plugin-prettier": "5.5.6",
95
94
  "globals": "17.11.0",
96
95
  "husky": "9.1.7",
97
- "prettier": "3.9.6",
96
+ "lint-staged": "17.3.0",
97
+ "oxfmt": "0.64.0",
98
+ "oxlint": "1.78.0",
99
+ "oxlint-tsgolint": "7.0.2001",
98
100
  "rimraf": "6.1.3",
99
101
  "rollup": "4.62.5",
100
102
  "style-dictionary": "5.5.2",
103
+ "tsdown": "0.22.14",
101
104
  "typescript": "6.0.3",
102
105
  "typescript-eslint": "8.67.0",
103
106
  "vite": "8.2.2",
104
- "vite-plugin-dts": "5.0.3",
105
107
  "vitest": "4.1.11",
106
108
  "webpack": "5.109.2"
107
109
  },
package/dist/index.cjs DELETED
@@ -1,2 +0,0 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("node:fs");c=s(c,1);let l=require("node:path");l=s(l,1);let u=require("node:url"),d=require("node:zlib");d=s(d,1);let f=require("style-dictionary");f=s(f,1);let p=require("unplugin");function m(e,t){let n=e.replace(/\\/g,`/`);return t.some(e=>e===n||n.startsWith(e.replace(/\/\*\*/g,``))||e.includes(`*`)&&new RegExp(e.replace(/[.+^${}()|[\]\\]/g,`\\$&`).replace(/\*/g,`.*`)).test(n))}function h(e){try{c.default.rmSync(e,{force:!0})}catch{}}var g=0;function _(e){let t=l.default.extname(e);return l.default.join(l.default.dirname(e),`.${l.default.basename(e,t)}.${process.pid}.${g++}.tmp`)}var v=Object.create(c.default,{promises:{value:Object.create(c.default.promises,{writeFile:{value:async(e,t,n)=>{if(typeof e!=`string`)return c.default.promises.writeFile(e,t,n);let r=_(e);try{await c.default.promises.writeFile(r,t,n),await c.default.promises.rename(r,e)}catch(e){throw h(r),e}}}})},writeFileSync:{value:(e,t,n)=>{if(typeof e!=`string`){c.default.writeFileSync(e,t,n);return}let r=_(e);try{c.default.writeFileSync(r,t,n),c.default.renameSync(r,e)}catch(e){throw h(r),e}}}}),y=(e={})=>{let{silent:t=!1}=e,n=process.cwd(),r=(e,n=`info`)=>{if(t)return;let r=`[unplugin-style-dictionary]`;n===`error`?console.error(`\x1b[31m${r} ${e}\x1b[0m`):console.log(n===`success`?`\x1b[32m${r} ${e}\x1b[0m`:`\x1b[36m${r} ${e}\x1b[0m`)},i=async()=>{let t=e.config;if(!t)for(let e of[`sd.config.json`,`config.json`,`sd.config.js`,`sd.config.mjs`]){let r=l.default.resolve(n,e);if(c.default.existsSync(r)){t=e;break}}return t?(typeof t==`function`&&(t=await t()),(Array.isArray(t)?t:[t]).map(e=>{if(typeof e==`string`){let t=l.default.resolve(n,e);return{config:t,dir:l.default.dirname(t),file:t}}return{config:e,dir:n}})):(r(`No configuration specified and no default config file found. Style Dictionary will not compile.`,`error`),[])},a=async t=>{let i=new Set;for(let e of t){e.file&&i.add(e.file.replace(/\\/g,`/`));let t=null;if(typeof e.config==`string`)try{if(e.config.endsWith(`.json`)){let n=c.default.readFileSync(e.config,`utf-8`);t=JSON.parse(n)}else{let n=await import(`${(0,u.pathToFileURL)(e.config).href}?t=${Date.now()}`);t=n.default||n}}catch(t){r(`Failed to parse config file: ${e.config}. Error: ${t.message}`,`error`)}else t=e.config;if(t){let n=t=>{if(typeof t==`string`){let n=(l.default.isAbsolute(t)?t:l.default.resolve(e.dir,t)).replace(/\\/g,`/`);i.add(n)}};t.source&&(Array.isArray(t.source)?t.source.forEach(n):n(t.source)),t.include&&(Array.isArray(t.include)?t.include.forEach(n):n(t.include))}}if(e.watch){let t=Array.isArray(e.watch)?e.watch:[e.watch];for(let e of t){let t=l.default.isAbsolute(e)?e:l.default.resolve(n,e);i.add(t.replace(/\\/g,`/`))}}return Array.from(i)},o=async(e,i)=>{let a=Date.now();try{i||r(`Compiling design tokens...`,`info`);let o=new Set;for(let r of e){let e=new f.default(r.config);await e.hasInitialized;let a=await e.extend({log:{verbosity:`silent`}});if(a.volume=v,await a.buildAllPlatforms(),!i&&!t&&a.platforms)for(let e of Object.keys(a.platforms)){let t=a.platforms[e];if(t&&t.files){let e=t.buildPath||``;for(let r of t.files)if(r&&r.destination){let t=l.default.isAbsolute(e)?e:l.default.resolve(n,e),i=l.default.isAbsolute(r.destination)?r.destination:l.default.resolve(t,r.destination);o.add(i)}}}}let s=Date.now()-a;if(i)r(`Rebuilt design tokens due to change in ${i} (${s}ms)`,`success`);else{if(!t&&o.size>0){let e=[];for(let t of o)if(c.default.existsSync(t)){let r=l.default.relative(n,t).replace(/\\/g,`/`),i=l.default.dirname(r),a=l.default.basename(r),o=i===`.`?`\x1b[32m${a}\x1b[0m`:`\x1b[90m${i}/\x1b[0m\x1b[32m${a}\x1b[0m`;try{let n=`${(c.default.statSync(t).size/1024).toFixed(2)} kB`,i=c.default.readFileSync(t),a=`${(d.default.gzipSync(i).length/1024).toFixed(2)} kB`;e.push({coloredPath:o,gzipSizeStr:a,relativeDisplayPath:r,sizeStr:n})}catch{}}if(e.length>0){let t=Math.max(...e.map(e=>e.relativeDisplayPath.length),0),n=Math.max(...e.map(e=>e.sizeStr.length),0);for(let r of e){let e=` `.repeat(Math.max(2,t-r.relativeDisplayPath.length+2)),i=r.sizeStr.padStart(n);console.log(`${r.coloredPath}${e}\x1b[90m${i} │ gzip: ${r.gzipSizeStr}\x1b[0m`)}}}r(`Compiled successfully! (${s}ms)`,`success`)}}catch(e){let t=Date.now()-a;r(`Compilation failed after ${t}ms: ${e.message}`,`error`)}};return{async buildStart(){let e=await i();if(e.length===0)return;let t=await a(e);for(let e of t)this.addWatchFile(e);await o(e)},name:`unplugin-style-dictionary`,vite:{configResolved(e){n=e.root||process.cwd()},async configureServer(e){let t=await i();if(t.length===0)return;let n=await a(t);e.watcher.add(n),e.watcher.on(`all`,async(t,r)=>{if(!m(r,n))return;let s=await i();await o(s,l.default.basename(r));let c=await a(s);e.watcher.add(c)})}},async watchChange(e){let t=await i();if(t.length===0)return;let n=await a(t);if(m(e,n)){await o(t,l.default.basename(e));for(let e of n)this.addWatchFile(e)}}}},b=(0,p.createUnplugin)(y);exports.default=b,exports.unplugin=b,exports.matchesWatchedFile=m,exports.unpluginFactory=y;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'style-dictionary'\nimport type { UnpluginFactory } from 'unplugin'\nimport type { ViteDevServer } from 'vite'\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport zlib from 'node:zlib'\nimport StyleDictionary from 'style-dictionary'\nimport { createUnplugin } from 'unplugin'\n\nimport type { UnpluginStyleDictionaryOptions } from './types.js'\n\nexport * from './types.js'\n\n// Whether `file` matches one of the resolved config/token watch patterns.\n// Shared by the Vite-specific `configureServer` watcher and the universal\n// `watchChange` hook — both need it, and both must skip files that don't\n// match: without this filter, `watchChange` reacts to *any* changed\n// module-graph file, including this plugin's own generated output (since\n// consuming code imports it). Every regenerate is itself a \"change\", which\n// without filtering re-triggers a rebuild forever.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some((pattern) => {\n // If the pattern is an exact file path\n if (pattern === normalizedFile) return true\n\n // If the pattern is a glob, we check if the file matches it.\n // Note: A simple string match or simple glob matcher can be used here.\n // For simplicity and correctness, since token source paths are usually globs,\n // we can match based on path directory containment or general matching.\n // Let's implement a robust matchesGlob check or check if it's one of the token files.\n // Chokidar triggers on actual files, so we want to check if the changed file matches\n // any of the config files or token files/globs.\n return (\n normalizedFile.startsWith(pattern.replace(/\\/\\*\\*/g, '')) ||\n (pattern.includes('*') &&\n new RegExp(\n pattern.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*'),\n ).test(normalizedFile))\n )\n })\n}\n\n// 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// Best-effort cleanup of a temporary file whose write or rename failed. The\n// original failure is what the caller reports, so nothing here may throw.\nfunction discardTemporaryFile(temporary: string): void {\n try {\n fs.rmSync(temporary, { force: true })\n } catch {\n // Ignore: a leftover temporary file is not worth masking the real error.\n }\n}\n\n// Temporary path for an atomic write of `destination`.\n//\n// It has to be a sibling of the destination, because `rename` is only atomic\n// within one filesystem and the system temp directory is often a different\n// mount. The final extension is dropped rather than kept, so the temporary\n// file cannot match a pattern written for the generated file's own extension —\n// `matchesWatchedFile` tests its globs unanchored, and a leftover\n// `vars.css.tmp` would match a `*.css` watch. The pid and counter make the\n// name unique, so two writes of the same destination — parallel platforms in\n// one build, or two builds overlapping — never share a temporary file.\nlet temporaryFileCounter = 0\n\nfunction temporaryPathFor(destination: string): string {\n const extension = path.extname(destination)\n\n return path.join(\n path.dirname(destination),\n `.${path.basename(destination, extension)}.${process.pid}.${temporaryFileCounter++}.tmp`,\n )\n}\n\nconst writeFileAtomic: typeof fs.promises.writeFile = async (\n file,\n data,\n options,\n) => {\n // A file handle or descriptor is already-open state that a rename cannot\n // stand in for, so only a path is written atomically.\n if (typeof file !== 'string') {\n return fs.promises.writeFile(file, data, options)\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n await fs.promises.writeFile(temporary, data, options)\n await fs.promises.rename(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\nconst writeFileSyncAtomic: typeof fs.writeFileSync = (file, data, options) => {\n if (typeof file !== 'string') {\n fs.writeFileSync(file, data, options)\n return\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n fs.writeFileSync(temporary, data, options)\n fs.renameSync(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\n// `node:fs` with both write entry points swapped for their atomic\n// equivalents, handed to Style Dictionary as the volume it builds through.\n// Everything else — reads, `mkdir`, `access`, the `promises` namespace — is\n// inherited from `node:fs` unchanged, so only the moment a file becomes\n// visible to readers changes. Custom actions receive this volume too, so\n// whatever they emit is written the same way.\n//\n// It is assigned onto the instance rather than passed as the `volume`\n// constructor option on purpose: that option marks the volume as a custom\n// filesystem shim, which switches Style Dictionary's path resolution off for\n// every read as well.\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\nexport const unpluginFactory: UnpluginFactory<\n undefined | UnpluginStyleDictionaryOptions,\n false\n> = (options = {}) => {\n const { silent = false } = options\n let root = process.cwd()\n\n // Helper to log if not silent\n const log = (\n message: string,\n type: 'error' | 'info' | 'success' = 'info',\n ) => {\n if (silent) return\n const prefix = '[unplugin-style-dictionary]'\n if (type === 'error') {\n console.error(`\\x1b[31m${prefix} ${message}\\x1b[0m`)\n } else if (type === 'success') {\n console.log(`\\x1b[32m${prefix} ${message}\\x1b[0m`)\n } else {\n console.log(`\\x1b[36m${prefix} ${message}\\x1b[0m`)\n }\n }\n\n // Resolve config file paths / objects\n const resolveConfigs = async (): Promise<\n Array<{ config: Config | string; dir: string; file?: string }>\n > => {\n let rawConfig = options.config\n\n // If config is not defined, look for default configuration files\n if (!rawConfig) {\n const defaults = [\n 'sd.config.json',\n 'config.json',\n 'sd.config.js',\n 'sd.config.mjs',\n ]\n for (const file of defaults) {\n const fullPath = path.resolve(root, file)\n if (fs.existsSync(fullPath)) {\n rawConfig = file\n break\n }\n }\n }\n\n if (!rawConfig) {\n log(\n 'No configuration specified and no default config file found. Style Dictionary will not compile.',\n 'error',\n )\n return []\n }\n\n // Evaluate function if provided\n if (typeof rawConfig === 'function') {\n rawConfig = await rawConfig()\n }\n\n const configs = Array.isArray(rawConfig) ? rawConfig : [rawConfig]\n\n return configs.map((conf) => {\n if (typeof conf === 'string') {\n const fullPath = path.resolve(root, conf)\n return {\n config: fullPath,\n dir: path.dirname(fullPath),\n file: fullPath,\n }\n } else {\n return {\n config: conf,\n dir: root,\n }\n }\n })\n }\n\n // Parse token files to watch\n const getWatchFiles = async (\n resolvedConfigs: Array<{\n config: Config | string\n dir: string\n file?: string\n }>,\n ): Promise<string[]> => {\n const filesToWatch = new Set<string>()\n\n for (const item of resolvedConfigs) {\n if (item.file) {\n filesToWatch.add(item.file.replace(/\\\\/g, '/'))\n }\n\n let configObj: Config | null = null\n\n if (typeof item.config === 'string') {\n try {\n if (item.config.endsWith('.json')) {\n const content = fs.readFileSync(item.config, 'utf-8')\n configObj = JSON.parse(content)\n } else {\n const fileUrl = pathToFileURL(item.config).href\n const module = await import(`${fileUrl}?t=${Date.now()}`)\n configObj = module.default || module\n }\n } catch (err) {\n log(\n `Failed to parse config file: ${item.config}. Error: ${(err as Error).message}`,\n 'error',\n )\n }\n } else {\n configObj = item.config\n }\n\n if (configObj) {\n const addPattern = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(item.dir, pattern)\n const normalized = absolutePattern.replace(/\\\\/g, '/')\n filesToWatch.add(normalized)\n }\n }\n\n if (configObj.source) {\n if (Array.isArray(configObj.source)) {\n configObj.source.forEach(addPattern)\n } else {\n addPattern(configObj.source)\n }\n }\n\n if (configObj.include) {\n if (Array.isArray(configObj.include)) {\n configObj.include.forEach(addPattern)\n } else {\n addPattern(configObj.include)\n }\n }\n }\n }\n\n // Add manually configured watch files\n if (options.watch) {\n const extraWatches = Array.isArray(options.watch)\n ? options.watch\n : [options.watch]\n for (const pattern of extraWatches) {\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(root, pattern)\n filesToWatch.add(absolutePattern.replace(/\\\\/g, '/'))\n }\n }\n\n return Array.from(filesToWatch)\n }\n\n // Compile design tokens\n const runBuilds = async (\n resolvedConfigs: Array<{ config: Config | string; dir: string }>,\n context?: string,\n ) => {\n const startTime = Date.now()\n try {\n if (!context) {\n log('Compiling design tokens...', 'info')\n }\n\n const generatedFiles = new Set<string>()\n\n for (const item of resolvedConfigs) {\n const sd = new StyleDictionary(item.config)\n await sd.hasInitialized\n const silentSD = await sd.extend({\n log: {\n verbosity: 'silent',\n },\n })\n // Swap in the atomic volume only now that the instance has finished\n // reading its configs and token sources, so every write below lands\n // through `rename` while the read path stays exactly as it was.\n silentSD.volume = atomicVolume\n await silentSD.buildAllPlatforms()\n\n if (!context && !silent && silentSD.platforms) {\n for (const platformName of Object.keys(silentSD.platforms)) {\n const platform = silentSD.platforms[platformName]\n if (platform && platform.files) {\n const buildPath = platform.buildPath || ''\n for (const file of platform.files) {\n if (file && file.destination) {\n const absoluteBuildPath = path.isAbsolute(buildPath)\n ? buildPath\n : path.resolve(root, buildPath)\n const absoluteDestination = path.isAbsolute(file.destination)\n ? file.destination\n : path.resolve(absoluteBuildPath, file.destination)\n generatedFiles.add(absoluteDestination)\n }\n }\n }\n }\n }\n }\n\n const duration = Date.now() - startTime\n\n if (context) {\n log(\n `Rebuilt design tokens due to change in ${context} (${duration}ms)`,\n 'success',\n )\n } else {\n if (!silent && generatedFiles.size > 0) {\n const fileInfos: Array<{\n coloredPath: string\n gzipSizeStr: string\n relativeDisplayPath: string\n sizeStr: string\n }> = []\n\n for (const filePath of generatedFiles) {\n if (fs.existsSync(filePath)) {\n const displayPath = path\n .relative(root, filePath)\n .replace(/\\\\/g, '/')\n const dir = path.dirname(displayPath)\n const base = path.basename(displayPath)\n const coloredPath =\n dir === '.'\n ? `\\x1b[32m${base}\\x1b[0m`\n : `\\x1b[90m${dir}/\\x1b[0m\\x1b[32m${base}\\x1b[0m`\n\n try {\n const stats = fs.statSync(filePath)\n const bytes = stats.size\n const sizeStr = `${(bytes / 1024).toFixed(2)} kB`\n\n const content = fs.readFileSync(filePath)\n const gzipBytes = zlib.gzipSync(content).length\n const gzipSizeStr = `${(gzipBytes / 1024).toFixed(2)} kB`\n\n fileInfos.push({\n coloredPath,\n gzipSizeStr,\n relativeDisplayPath: displayPath,\n sizeStr,\n })\n } catch {\n // Ignore errors reading individual files\n }\n }\n }\n\n if (fileInfos.length > 0) {\n const longestPathLength = Math.max(\n ...fileInfos.map((f) => f.relativeDisplayPath.length),\n 0,\n )\n const longestSizeLength = Math.max(\n ...fileInfos.map((f) => f.sizeStr.length),\n 0,\n )\n\n for (const info of fileInfos) {\n const pathPadding = ' '.repeat(\n Math.max(\n 2,\n longestPathLength - info.relativeDisplayPath.length + 2,\n ),\n )\n const sizePadded = info.sizeStr.padStart(longestSizeLength)\n console.log(\n `${info.coloredPath}${pathPadding}\\x1b[90m${sizePadded} │ gzip: ${info.gzipSizeStr}\\x1b[0m`,\n )\n }\n }\n }\n\n log(`Compiled successfully! (${duration}ms)`, 'success')\n }\n } catch (err) {\n const duration = Date.now() - startTime\n log(\n `Compilation failed after ${duration}ms: ${(err as Error).message}`,\n 'error',\n )\n }\n }\n\n return {\n async buildStart() {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Register token/config files with the host bundler's watch mode.\n // Works out of the box wherever the host runs a persistent watcher\n // (e.g. `rollup --watch`); Vite's dev server is additionally handled\n // below via the `vite.configureServer` escape hatch, since token\n // sources are plain JSON/JS files outside the module graph and\n // `watchChange` is not reliably invoked by Vite while serving.\n const watchFiles = await getWatchFiles(resolved)\n for (const file of watchFiles) {\n this.addWatchFile(file)\n }\n\n await runBuilds(resolved)\n },\n\n name: 'unplugin-style-dictionary',\n\n vite: {\n configResolved(config) {\n root = config.root || process.cwd()\n },\n\n async configureServer(server: ViteDevServer) {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n const filesToWatch = await getWatchFiles(resolved)\n\n // Watch configuration files and token files\n server.watcher.add(filesToWatch)\n\n server.watcher.on('all', async (_event, file) => {\n if (!matchesWatchedFile(file, filesToWatch)) return\n\n // Re-resolve configs to handle added/removed configs or changes to config itself\n const currentResolved = await resolveConfigs()\n await runBuilds(currentResolved, path.basename(file))\n\n // Dynamically update the watch list in case the configurations changed\n const newWatches = await getWatchFiles(currentResolved)\n server.watcher.add(newWatches)\n })\n },\n },\n\n async watchChange(id) {\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n const watchFiles = await getWatchFiles(resolved)\n // Without this check, watchChange fires for *any* changed file in the\n // host bundler's module graph — including our own generated output,\n // since consuming code imports it. Every regenerate is itself a\n // \"change\", so skipping non-matching files here is what keeps this\n // from rebuilding forever.\n if (!matchesWatchedFile(id, watchFiles)) return\n\n await runBuilds(resolved, path.basename(id))\n\n for (const file of watchFiles) {\n this.addWatchFile(file)\n }\n },\n }\n}\n\nexport const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory)\n\nexport default unplugin\n"],"mappings":"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"}
package/dist/rolldown.cjs DELETED
@@ -1,2 +0,0 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=require("./index.cjs").default.rolldown;exports.default=e;
2
- //# sourceMappingURL=rolldown.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rolldown.cjs","names":[],"sources":["../src/rolldown.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.rolldown\n"],"mappings":"+FAEA,IAAA,uBAAe,CAAA,CAAA,QAAS"}
package/dist/rollup.cjs DELETED
@@ -1,2 +0,0 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=require("./index.cjs").default.rollup;exports.default=e;
2
- //# sourceMappingURL=rollup.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rollup.cjs","names":[],"sources":["../src/rollup.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.rollup\n"],"mappings":"+FAEA,IAAA,uBAAe,CAAA,CAAA,QAAS"}
package/dist/vite.cjs DELETED
@@ -1,2 +0,0 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=require("./index.cjs").default.vite;exports.default=e;
2
- //# sourceMappingURL=vite.cjs.map
package/dist/vite.cjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"vite.cjs","names":[],"sources":["../src/vite.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.vite\n"],"mappings":"+FAEA,IAAA,uBAAe,CAAA,CAAA,QAAS"}
package/dist/webpack.cjs DELETED
@@ -1,2 +0,0 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=require("./index.cjs").default.webpack;exports.default=e;
2
- //# sourceMappingURL=webpack.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webpack.cjs","names":[],"sources":["../src/webpack.ts"],"sourcesContent":["import { unplugin } from './index.js'\n\nexport default unplugin.webpack\n"],"mappings":"+FAEA,IAAA,uBAAe,CAAA,CAAA,QAAS"}