@kanso-labs/unplugin-style-dictionary 0.10.14 → 0.10.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.d.ts CHANGED
@@ -154,9 +154,10 @@ export interface UnpluginStyleDictionaryOptions {
154
154
  * first one that *looks like a Style Dictionary configuration* wins — it has
155
155
  * to declare at least one of `platforms`, `source`, `include` or `tokens` —
156
156
  * and the path it picked is announced, so which file a build used is
157
- * answerable from the console. A candidate that fails that check is reported
158
- * and skipped rather than adopted, because `config.json` is an extremely
159
- * common name for something else entirely.
157
+ * answerable from the console. A candidate that fails that check is skipped
158
+ * rather than adopted, because `config.json` is an extremely common name for
159
+ * something else entirely. It is named in that announcement when a later
160
+ * candidate is adopted, and reported as an error when none is.
160
161
  *
161
162
  * **`false` turns discovery off.** Two of the four names are modules rather
162
163
  * than data, and reading a module means running it: a `sd.config.js` in the
@@ -209,6 +210,10 @@ export interface UnpluginStyleDictionaryOptions {
209
210
  *
210
211
  * Reporting happens either way, and is not suppressed by `silent`.
211
212
  *
213
+ * Any other value throws a `TypeError` when the plugin is created — the
214
+ * string `'true'` from an environment variable included, which used to
215
+ * behave as `false`.
216
+ *
212
217
  * @default 'build'
213
218
  */
214
219
  failOnError?: 'build' | 'serve' | boolean;
@@ -228,7 +233,9 @@ export interface UnpluginStyleDictionaryOptions {
228
233
  * about rather than pointing at its own `--verbose` flag.
229
234
  *
230
235
  * A compile that fails is reported at every level, so there is no
231
- * `'error'`: `'silent'` is the quietest and still reports a failure.
236
+ * `'error'`: `'silent'` is the quietest and still reports a failure. Any
237
+ * other value, `'error'` included, throws a `TypeError` when the plugin is
238
+ * created.
232
239
  *
233
240
  * This option governs what the plugin says, not where it goes. The messages
234
241
  * are handed to the host — Vite's `config.logger`, the rollup and rolldown
@@ -263,6 +270,10 @@ export interface UnpluginStyleDictionaryOptions {
263
270
  /**
264
271
  * Called when a build fails, with whatever was thrown.
265
272
  *
273
+ * A failure inside Style Dictionary arrives wrapped in an `Error` whose
274
+ * message names the configuration it came from, with Style Dictionary's own
275
+ * error as its `cause`. One the plugin raises itself already names it.
276
+ *
266
277
  * It fires whatever `failOnError` is set to, and before that option decides
267
278
  * whether to rethrow — the two answer different questions, and under a dev
268
279
  * server the default is not to throw at all.
@@ -319,6 +330,11 @@ export interface UnpluginStyleDictionaryOptions {
319
330
  * stale output for the rest. Scope the watch half rather than the build half
320
331
  * unless that is what you want.
321
332
  *
333
+ * Any other shape throws a `TypeError` when the plugin is created: a single
334
+ * string, or an object with a key other than `build` and `watch` — `serve`
335
+ * is the easy slip, since `failOnError` says `'serve'`. Both used to build
336
+ * every platform.
337
+ *
322
338
  * @default undefined, which builds every platform
323
339
  */
324
340
  platforms?: string[] | {
@@ -1,9 +1,14 @@
1
1
  import path from "node:path";
2
2
  import picomatch from "picomatch";
3
3
  //#region src/watch-filter.ts
4
- function isWatchedSource(file, { globs, literals }, generatedDestinations) {
4
+ function compileWatchPatterns({ globs, literals }) {
5
+ const named = /* @__PURE__ */ new Set([...globs, ...literals]);
6
+ const matchesGlob = globs.length > 0 ? picomatch(globs) : () => false;
7
+ return (normalizedFile) => named.has(normalizedFile) || matchesGlob(normalizedFile);
8
+ }
9
+ function isWatchedSource(file, matches, generatedDestinations) {
5
10
  const normalizedFile = file.replace(/\\/g, "/");
6
- return !generatedDestinations.has(normalizedFile) && (literals.includes(normalizedFile) || matchesWatchedFile(normalizedFile, globs));
11
+ return !generatedDestinations.has(normalizedFile) && matches(normalizedFile);
7
12
  }
8
13
  function matchesWatchedFile(file, patterns) {
9
14
  const normalizedFile = file.replace(/\\/g, "/");
@@ -19,6 +24,6 @@ function registeredSpellingOf(file, linkedPaths) {
19
24
  }
20
25
  }
21
26
  //#endregion
22
- export { isWatchedSource, matchesWatchedFile, registeredSpellingOf };
27
+ export { compileWatchPatterns, isWatchedSource, matchesWatchedFile, registeredSpellingOf };
23
28
 
24
29
  //# sourceMappingURL=watch-filter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"watch-filter.js","names":[],"sources":["../src/watch-filter.ts"],"sourcesContent":["import path from 'node:path'\nimport picomatch from 'picomatch'\n\nimport type { WatchPatterns } from './patterns.js'\n\n// Whether a changed file is a token or config source rather than something\n// this plugin just wrote. Both watch entry points ask through here, so\n// neither can react to its own output.\n//\n// `generatedDestinations` is the plugin instance's own record of what it\n// wrote, so it is handed in rather than held: one module serves every\n// instance in the process, and each one's output is its own.\n//\n// A literal is a path, so it is compared as one. A config file reaches here\n// that way, and so does a token file named without a glob, and neither may be\n// read as a pattern: the project's own directory can hold characters a glob\n// treats as syntax.\nexport function isWatchedSource(\n file: string,\n { globs, literals }: WatchPatterns,\n generatedDestinations: ReadonlySet<string>,\n): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return (\n !generatedDestinations.has(normalizedFile) &&\n (literals.includes(normalizedFile) ||\n matchesWatchedFile(normalizedFile, globs))\n )\n}\n\n// Whether `file` matches one of the resolved config/token watch patterns.\n// Shared by the Vite-specific `configureServer` watcher and the universal\n// `watchChange` hook — both need it, and both must skip files that don't\n// match: without this filter, `watchChange` reacts to *any* changed\n// module-graph file, including this plugin's own generated output (since\n// consuming code imports it). Every regenerate is itself a \"change\", which\n// without filtering re-triggers a rebuild forever.\n//\n// Matching a pattern is only half of that, and this function is only the half\n// it can answer. A `buildPath` inside a `source` directory is a supported\n// layout, and under any correct matcher its output matches the very glob that\n// produced it — so the caller also subtracts what the last build wrote. See\n// `isWatchedSource` above, and `generatedDestinations` in the factory.\n//\n// The patterns are Style Dictionary's own `source` and `include` globs, so the\n// filter has to admit exactly what the build reads — which is why the matching\n// is a real globber's rather than hand-rolled. The version this replaces was\n// wrong in both directions at once: it stripped `/**` out of a pattern and\n// prefix-matched the remainder, so `tokens/**/*.json` matched nothing sitting\n// directly in `tokens/` and `tokens/**` matched a `tokens-backup/` sibling,\n// while its regex branch mapped every `*` to `.*` — crossing `/` — and tested\n// it unanchored, so generated output under a watched directory matched its own\n// source glob and rebuilt forever.\n//\n// picomatch rather than `path.matchesGlob`, which would need no dependency at\n// all: that function is documented experimental, and on Node 20 — the floor\n// `engines` declares — it prints `ExperimentalWarning: glob is an experimental\n// feature and might change at any time` into the consumer's build output. The\n// dependency is free in practice, since `unplugin` depends on the same\n// picomatch and is already installed wherever this plugin is. Its `dot: false`\n// default is deliberate: it is what glob, and so Style Dictionary, reads\n// sources with, so a dotfile is invisible to the filter and to the build alike.\n//\n// **The patterns are used exactly as given.** A backslash in one is an escape:\n// the part of a pattern the plugin prefixed is escaped, and every producer\n// already writes separators as `/`. Rewriting backslashes to slashes here\n// turned each escape into a separator, so a project in `Dropbox (Personal)`\n// matched none of its own token files.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some(\n (pattern) =>\n // An absolute path a consumer wrote with a glob character in it is filed\n // as a glob, and still names itself.\n pattern === normalizedFile || picomatch.isMatch(normalizedFile, pattern),\n )\n}\n\n// The spelling the watch patterns use for `file`, which a host watching a\n// registered realpath reports by where a link leads rather than by the link.\n// `linkedPaths` maps each registered path that runs through a link from its\n// realpath back to the spelling it was registered under, and is the plugin\n// instance's, so it is handed in rather than held.\n//\n// The lookup walks up from `file` rather than asking about it alone, because\n// a pattern's static parent directory is registered so that a token file\n// created in it later is noticed — and that file arrives spelled under the\n// directory's realpath, which only the directory is a key for.\nexport function registeredSpellingOf(\n file: string,\n linkedPaths: ReadonlyMap<string, string>,\n): string {\n if (linkedPaths.size === 0) return file\n\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n for (let real = normalizedFile; ; real = path.posix.dirname(real)) {\n const registered = linkedPaths.get(real)\n if (registered !== undefined) {\n return registered + normalizedFile.slice(real.length)\n }\n\n if (path.posix.dirname(real) === real) return file\n }\n}\n"],"mappings":";;;AAiBA,SAAgB,gBACd,MACA,EAAE,OAAO,YACT,uBACS;CACT,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,OACE,CAAC,sBAAsB,IAAI,cAAc,MACxC,SAAS,SAAS,cAAc,KAC/B,mBAAmB,gBAAgB,KAAK;AAE9C;AAwCA,SAAgB,mBAAmB,MAAc,UAA6B;CAC5E,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,OAAO,SAAS,MACb,YAGC,YAAY,kBAAkB,UAAU,QAAQ,gBAAgB,OAAO,CAC3E;AACF;AAYA,SAAgB,qBACd,MACA,aACQ;CACR,IAAI,YAAY,SAAS,GAAG,OAAO;CAEnC,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,KAAK,IAAI,OAAO,iBAAkB,OAAO,KAAK,MAAM,QAAQ,IAAI,GAAG;EACjE,MAAM,aAAa,YAAY,IAAI,IAAI;EACvC,IAAI,eAAe,KAAA,GACjB,OAAO,aAAa,eAAe,MAAM,KAAK,MAAM;EAGtD,IAAI,KAAK,MAAM,QAAQ,IAAI,MAAM,MAAM,OAAO;CAChD;AACF"}
1
+ {"version":3,"file":"watch-filter.js","names":[],"sources":["../src/watch-filter.ts"],"sourcesContent":["import path from 'node:path'\nimport picomatch from 'picomatch'\n\nimport type { WatchPatterns } from './patterns.js'\n\n// Whether a forward-slashed path is one a watch list names.\nexport type WatchMatcher = (normalizedFile: string) => boolean\n\n// One watch list compiled into a matcher, once, where the list is derived.\n// picomatch compiles a pattern on every `isMatch`, with no cache, so asking\n// per event compiled every pattern again for every file a watcher reported —\n// under a dev server, each unrelated save went through the filter twice. The\n// matcher is the factory's to hold, beside the list it came from: one module\n// serves every plugin instance, so a cache here would be state in a module\n// that holds none.\n//\n// A literal is a path, so it is compared as one. A config file reaches here\n// that way, and so does a token file named without a glob, and neither may be\n// read as a pattern: the project's own directory can hold characters a glob\n// treats as syntax. The globs are compiled exactly as given — see\n// `matchesWatchedFile` below — and an absolute path a consumer wrote with a\n// glob character in it is filed as a glob and still names itself.\nexport function compileWatchPatterns({\n globs,\n literals,\n}: WatchPatterns): WatchMatcher {\n const named = new Set([...globs, ...literals])\n const matchesGlob = globs.length > 0 ? picomatch(globs) : () => false\n\n return (normalizedFile) =>\n named.has(normalizedFile) || matchesGlob(normalizedFile)\n}\n\n// Whether a changed file is a token or config source rather than something\n// this plugin just wrote. Both watch entry points ask through here, so\n// neither can react to its own output.\n//\n// `generatedDestinations` is the plugin instance's own record of what it\n// wrote, so it is handed in rather than held: one module serves every\n// instance in the process, and each one's output is its own.\n//\n// It takes the list compiled rather than the list: see `compileWatchPatterns`.\nexport function isWatchedSource(\n file: string,\n matches: WatchMatcher,\n generatedDestinations: ReadonlySet<string>,\n): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return !generatedDestinations.has(normalizedFile) && matches(normalizedFile)\n}\n\n// Whether `file` matches one of the resolved config/token watch patterns,\n// compiled for this one question. Every event goes through\n// `compileWatchPatterns` instead; this stays for a one-off answer.\n// Shared by the Vite-specific `configureServer` watcher and the universal\n// `watchChange` hook — both need it, and both must skip files that don't\n// match: without this filter, `watchChange` reacts to *any* changed\n// module-graph file, including this plugin's own generated output (since\n// consuming code imports it). Every regenerate is itself a \"change\", which\n// without filtering re-triggers a rebuild forever.\n//\n// Matching a pattern is only half of that, and this function is only the half\n// it can answer. A `buildPath` inside a `source` directory is a supported\n// layout, and under any correct matcher its output matches the very glob that\n// produced it — so the caller also subtracts what the last build wrote. See\n// `isWatchedSource` above, and `generatedDestinations` in the factory.\n//\n// The patterns are Style Dictionary's own `source` and `include` globs, so the\n// filter has to admit exactly what the build reads — which is why the matching\n// is a real globber's rather than hand-rolled. The version this replaces was\n// wrong in both directions at once: it stripped `/**` out of a pattern and\n// prefix-matched the remainder, so `tokens/**/*.json` matched nothing sitting\n// directly in `tokens/` and `tokens/**` matched a `tokens-backup/` sibling,\n// while its regex branch mapped every `*` to `.*` — crossing `/` — and tested\n// it unanchored, so generated output under a watched directory matched its own\n// source glob and rebuilt forever.\n//\n// picomatch rather than `path.matchesGlob`, which would need no dependency at\n// all: that function is documented experimental, and on Node 20 — the floor\n// `engines` declares — it prints `ExperimentalWarning: glob is an experimental\n// feature and might change at any time` into the consumer's build output. The\n// dependency is free in practice, since `unplugin` depends on the same\n// picomatch and is already installed wherever this plugin is. Its `dot: false`\n// default is deliberate: it is what glob, and so Style Dictionary, reads\n// sources with, so a dotfile is invisible to the filter and to the build alike.\n//\n// **The patterns are used exactly as given.** A backslash in one is an escape:\n// the part of a pattern the plugin prefixed is escaped, and every producer\n// already writes separators as `/`. Rewriting backslashes to slashes here\n// turned each escape into a separator, so a project in `Dropbox (Personal)`\n// matched none of its own token files.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some(\n (pattern) =>\n // An absolute path a consumer wrote with a glob character in it is filed\n // as a glob, and still names itself.\n pattern === normalizedFile || picomatch.isMatch(normalizedFile, pattern),\n )\n}\n\n// The spelling the watch patterns use for `file`, which a host watching a\n// registered realpath reports by where a link leads rather than by the link.\n// `linkedPaths` maps each registered path that runs through a link from its\n// realpath back to the spelling it was registered under, and is the plugin\n// instance's, so it is handed in rather than held.\n//\n// The lookup walks up from `file` rather than asking about it alone, because\n// a pattern's static parent directory is registered so that a token file\n// created in it later is noticed — and that file arrives spelled under the\n// directory's realpath, which only the directory is a key for.\nexport function registeredSpellingOf(\n file: string,\n linkedPaths: ReadonlyMap<string, string>,\n): string {\n if (linkedPaths.size === 0) return file\n\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n for (let real = normalizedFile; ; real = path.posix.dirname(real)) {\n const registered = linkedPaths.get(real)\n if (registered !== undefined) {\n return registered + normalizedFile.slice(real.length)\n }\n\n if (path.posix.dirname(real) === real) return file\n }\n}\n"],"mappings":";;;AAsBA,SAAgB,qBAAqB,EACnC,OACA,YAC8B;CAC9B,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,GAAG,QAAQ,CAAC;CAC7C,MAAM,cAAc,MAAM,SAAS,IAAI,UAAU,KAAK,UAAU;CAEhE,QAAQ,mBACN,MAAM,IAAI,cAAc,KAAK,YAAY,cAAc;AAC3D;AAWA,SAAgB,gBACd,MACA,SACA,uBACS;CACT,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,OAAO,CAAC,sBAAsB,IAAI,cAAc,KAAK,QAAQ,cAAc;AAC7E;AA0CA,SAAgB,mBAAmB,MAAc,UAA6B;CAC5E,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,OAAO,SAAS,MACb,YAGC,YAAY,kBAAkB,UAAU,QAAQ,gBAAgB,OAAO,CAC3E;AACF;AAYA,SAAgB,qBACd,MACA,aACQ;CACR,IAAI,YAAY,SAAS,GAAG,OAAO;CAEnC,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,KAAK,IAAI,OAAO,iBAAkB,OAAO,KAAK,MAAM,QAAQ,IAAI,GAAG;EACjE,MAAM,aAAa,YAAY,IAAI,IAAI;EACvC,IAAI,eAAe,KAAA,GACjB,OAAO,aAAa,eAAe,MAAM,KAAK,MAAM;EAGtD,IAAI,KAAK,MAAM,QAAQ,IAAI,MAAM,MAAM,OAAO;CAChD;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanso-labs/unplugin-style-dictionary",
3
- "version": "0.10.14",
3
+ "version": "0.10.15",
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",
@@ -83,19 +83,19 @@
83
83
  "@commitlint/cli": "21.2.3",
84
84
  "@commitlint/config-conventional": "21.2.3",
85
85
  "@eslint/js": "10.0.1",
86
- "@rspack/core": "2.2.7",
86
+ "@rspack/core": "2.2.8",
87
87
  "@types/node": "26.6.3",
88
88
  "@types/picomatch": "4.0.3",
89
89
  "@types/semver": "7.8.0",
90
90
  "@vitest/coverage-v8": "5.0.2",
91
91
  "eslint": "10.11.0",
92
- "eslint-plugin-oxlint": "1.85.0",
92
+ "eslint-plugin-oxlint": "1.86.0",
93
93
  "eslint-plugin-perfectionist": "5.12.1",
94
94
  "globals": "17.12.0",
95
95
  "husky": "9.1.7",
96
96
  "lint-staged": "17.6.0",
97
- "oxfmt": "0.70.0",
98
- "oxlint": "1.85.0",
97
+ "oxfmt": "0.71.0",
98
+ "oxlint": "1.86.0",
99
99
  "oxlint-tsgolint": "7.0.2003",
100
100
  "publint": "0.3.24",
101
101
  "rimraf": "6.1.3",
@@ -105,7 +105,7 @@
105
105
  "style-dictionary": "5.5.5",
106
106
  "tsdown": "0.23.0",
107
107
  "typescript": "6.0.3",
108
- "typescript-eslint": "8.70.1",
108
+ "typescript-eslint": "8.71.0",
109
109
  "vite": "8.3.1",
110
110
  "vitest": "5.0.2",
111
111
  "webpack": "5.111.1"