@kanso-labs/unplugin-style-dictionary 0.10.4 → 0.10.6

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","names":[],"sources":["../src/patterns.ts"],"sourcesContent":["// The `source` and `include` patterns a configuration reads, the watch list\n// derived from them, and the paths a watcher is actually handed for it.\n//\n// Nothing here holds state. What a plugin instance owns — its root, its\n// logger, its `watch` option — is handed in on each call.\n\nimport type { Config } from 'style-dictionary'\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { glob } from 'tinyglobby'\n\nimport type { Log, ResolvedConfig } from './config.js'\nimport type { UnpluginStyleDictionaryOptions } from './types.js'\n\nimport { readConfigObject } from './config.js'\nimport { errorMessage } from './errors.js'\n\n// A pattern is a glob when any of these appear in it. Deliberately the set\n// picomatch and tinyglobby act on, since those two are what match and expand\n// here — a path containing one of these characters literally is not\n// distinguishable from a pattern, and would not be matchable either.\nconst GLOB_CHARACTERS = /[!*?[\\]{}]/\n\n// What a watcher is handed, and what a changed path is tested against, are\n// not the same list, and conflating them is why a glob source was watched by\n// nothing at all. Every watcher in play takes filenames rather than\n// patterns: Vite's chokidar and rollup's `FileWatcher` are both constructed\n// with `disableGlobbing: true`, Vite's `addWatchFile` drops anything that\n// fails `fs.existsSync`, and webpack never globs `fileDependencies`. So the\n// patterns stay for matching and the paths are expanded for registering.\nexport async function expandPatterns(\n patterns: string[],\n log: Log,\n): Promise<string[]> {\n const paths = new Set<string>()\n const globs: string[] = []\n\n for (const pattern of patterns) {\n if (GLOB_CHARACTERS.test(pattern)) {\n globs.push(pattern)\n\n // Watching the directory as well as its current contents. chokidar\n // reports a creation inside a watched directory, which is the only\n // way a token file added later is ever noticed.\n const parent = staticParentOf(pattern)\n if (parent && fs.existsSync(parent)) paths.add(parent)\n } else {\n paths.add(pattern)\n }\n }\n\n if (globs.length > 0) {\n try {\n // tinyglobby matches with picomatch, which is what\n // `matchesWatchedFile` tests with, so what is registered here and what\n // is accepted there cannot disagree.\n for (const match of await glob(globs, { absolute: true })) {\n paths.add(match.replace(/\\\\/g, '/'))\n }\n } catch (err) {\n log(`Failed to expand watch patterns: ${errorMessage(err)}`, 'error')\n }\n }\n\n return Array.from(paths)\n}\n\n// Which of a configuration's own `source`/`include` patterns match no file on\n// disk. Only for diagnosis: it is the emptiness of the resolved token set that\n// decides whether a build fails, because only that catches every route to an\n// empty set. This names the pattern at fault, which the token count cannot, and\n// it reports a mistyped pattern in a configuration whose others still match —\n// where nothing fails at all and one platform quietly loses its tokens.\nexport async function patternsMatchingNothing(\n patterns: string[],\n): Promise<string[]> {\n const barren: string[] = []\n\n for (const pattern of patterns) {\n // A literal path is a `stat`, not a glob: `tinyglobby` treats a path with\n // no magic characters as a literal anyway, and this keeps the common case\n // off the filesystem walk.\n if (!GLOB_CHARACTERS.test(pattern)) {\n if (!fs.existsSync(pattern)) barren.push(pattern)\n continue\n }\n\n try {\n const matched = await glob([pattern], { absolute: true })\n if (matched.length === 0) barren.push(pattern)\n } catch {\n // A pattern that cannot even be globbed is the build's problem to\n // report; saying it twice, in a diagnostic, helps nobody.\n }\n }\n\n return barren\n}\n\n// The patterns one configuration reads, resolved the way the build resolves\n// them. The same `source`/`include` walk `watchPatternsOf` does, for one\n// item rather than the whole set — against the working directory, because\n// that is where Style Dictionary's own `combineJSON` globs them.\nexport function sourcePatternsOf(configObj: Config): string[] {\n const patterns: string[] = []\n\n const add = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n patterns.push(\n (path.isAbsolute(pattern)\n ? pattern\n : path.resolve(process.cwd(), pattern)\n ).replace(/\\\\/g, '/'),\n )\n }\n }\n\n for (const value of [configObj.source, configObj.include]) {\n if (Array.isArray(value)) value.forEach(add)\n else add(value)\n }\n\n return patterns\n}\n\n// Parse token files to watch\n//\n// The patterns alone. Expanding them into the paths a watcher takes, and\n// remembering them for `watchChange` to filter against, is the caller's: the\n// second is state of one plugin instance, and this module holds none.\nexport async function watchPatternsOf(\n {\n log,\n root,\n watch,\n }: {\n log: Log\n root: string\n watch: UnpluginStyleDictionaryOptions['watch']\n },\n resolvedConfigs: ResolvedConfig[],\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 const configObj = await readConfigObject(item, log)\n\n if (configObj) {\n const addPattern = (pattern: unknown) => {\n if (typeof pattern === 'string') {\n // Against the working directory, because that is where Style\n // Dictionary resolves it: `combineJSON` globs each pattern with\n // no `cwd` of its own. Resolving against the configuration file's\n // directory instead is how the watch list came to name paths the\n // build never reads — a configuration in a subdirectory built\n // correctly and watched nothing at all.\n const absolutePattern = path.isAbsolute(pattern)\n ? pattern\n : path.resolve(process.cwd(), pattern)\n const normalized = absolutePattern.replace(/\\\\/g, '/')\n filesToWatch.add(normalized)\n }\n }\n\n if (configObj.source) {\n if (Array.isArray(configObj.source)) {\n configObj.source.forEach(addPattern)\n } else {\n addPattern(configObj.source)\n }\n }\n\n if (configObj.include) {\n if (Array.isArray(configObj.include)) {\n configObj.include.forEach(addPattern)\n } else {\n addPattern(configObj.include)\n }\n }\n }\n }\n\n // Add manually configured watch files\n if (watch) {\n const extraWatches = Array.isArray(watch) ? watch : [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// The leading run of a pattern that contains no glob character —\n// `/p/tokens` for `/p/tokens/**/*.json`. Registering it alongside the files\n// that match today is what makes a token file created tomorrow visible:\n// watching only the current matches can never see a path that did not exist\n// when the watcher was built.\nfunction staticParentOf(pattern: string): string {\n const segments = pattern.split('/')\n const firstGlob = segments.findIndex((segment) =>\n GLOB_CHARACTERS.test(segment),\n )\n\n return firstGlob === -1\n ? path.posix.dirname(pattern)\n : segments.slice(0, firstGlob).join('/')\n}\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { asError } from "./errors.js";
2
+ //#region src/scheduler.ts
3
+ function createScheduler({ debounceMs, isClosed, run }) {
4
+ let debounceTimer;
5
+ let pendingReason;
6
+ let inFlight;
7
+ let waiting = [];
8
+ const drain = async () => {
9
+ while (pendingReason !== void 0) {
10
+ const reason = pendingReason;
11
+ pendingReason = void 0;
12
+ const resolvers = waiting;
13
+ waiting = [];
14
+ let failure;
15
+ try {
16
+ await run(reason);
17
+ } catch (err) {
18
+ failure = { error: err };
19
+ }
20
+ for (const settle of resolvers) settle(failure);
21
+ }
22
+ };
23
+ return async (reason) => {
24
+ if (isClosed()) return;
25
+ pendingReason = reason;
26
+ const covered = new Promise((resolve, reject) => {
27
+ waiting.push((failure) => {
28
+ if (failure) reject(asError(failure.error));
29
+ else resolve();
30
+ });
31
+ });
32
+ if (debounceTimer) clearTimeout(debounceTimer);
33
+ debounceTimer = setTimeout(() => {
34
+ debounceTimer = void 0;
35
+ inFlight = (inFlight ?? Promise.resolve()).then(drain);
36
+ }, debounceMs);
37
+ debounceTimer.unref();
38
+ return covered;
39
+ };
40
+ }
41
+ //#endregion
42
+ export { createScheduler };
43
+
44
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","names":[],"sources":["../src/scheduler.ts"],"sourcesContent":["// One rebuild per burst of watcher events, and never two at once.\n//\n// Two things went wrong without this. A single token edit under Vite's dev\n// server reached both the `configureServer` listener and `watchChange` —\n// Vite 6, 7 and 8 all invoke plugin `watchChange` while serving — and each\n// started its own build, so one write produced two. And nothing serialised\n// them: a four-file change started one build per file, all overlapping.\n// `runBuilds` builds its configurations one after another precisely so two\n// instances never write the same destination at once, and concurrent calls\n// to it reintroduced that one level up.\n//\n// The trailing debounce collapses the burst; the in-flight chain means a\n// trigger arriving mid-build queues exactly one follow-up rather than\n// starting a second build beside it.\n//\n// **Every plugin instance gets a scheduler of its own**, so the state below\n// lives inside `createScheduler` rather than at module scope. One process\n// routinely holds several instances, and a debounce they shared would collapse\n// two instances' triggers into one rebuild — run by only one of them, with the\n// other's output left stale and its caller told it was covered.\n\nimport { asError } from './errors.js'\n\n// Returns `schedule`, which resolves once a rebuild covering its trigger has\n// finished, and rejects with what that rebuild threw.\n//\n// `run` is the rebuild itself, and a failure is whatever it throws. `isClosed`\n// is asked on every trigger, because the host closes its watcher after the\n// scheduler exists.\nexport function createScheduler({\n debounceMs,\n isClosed,\n run,\n}: {\n debounceMs: number\n isClosed: () => boolean\n run: (reason: string) => Promise<void>\n}): (reason: string) => Promise<void> {\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n let pendingReason: string | undefined\n let inFlight: Promise<void> | undefined\n let waiting: Array<(failure?: { error: unknown }) => void> = []\n\n const drain = async (): Promise<void> => {\n // A loop rather than a single pass: anything scheduled while the build\n // below is running is picked up here instead of starting a second one.\n while (pendingReason !== undefined) {\n const reason = pendingReason\n pendingReason = undefined\n\n // Captured before the await, so a trigger arriving mid-build waits for\n // the next pass rather than being told this one covered it.\n const resolvers = waiting\n waiting = []\n\n let failure: undefined | { error: unknown }\n\n try {\n await run(reason)\n } catch (err) {\n failure = { error: err }\n }\n\n // Handed on to whatever awaited this rebuild, which is `watchChange`\n // and so the host under a watching bundler. Vite's dev-server listener\n // has no build to fail and catches it.\n for (const settle of resolvers) settle(failure)\n }\n }\n\n // Resolves once a rebuild covering this trigger has finished.\n return async (reason: string): Promise<void> => {\n // Nothing consumes a rebuild once the host has closed its watcher. This is\n // where a close actually lands: `watchChange` reaches here only after\n // resolving configurations and deriving a watch list, so a `closeWatcher`\n // arriving mid-hook finds no timer armed yet and nothing else to stop it.\n //\n // Resolving rather than rejecting, because the trigger was handled — by\n // being declined — and the caller awaiting it is a host on its way out.\n if (isClosed()) return\n\n pendingReason = reason\n\n const covered = new Promise<void>((resolve, reject) => {\n waiting.push((failure) => {\n if (failure) reject(asError(failure.error))\n else resolve()\n })\n })\n\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounceTimer = undefined\n inFlight = (inFlight ?? Promise.resolve()).then(drain)\n }, debounceMs)\n\n // A pending rebuild must not be what keeps a process alive; whatever is\n // watching already is.\n debounceTimer.unref()\n\n return covered\n }\n}\n"],"mappings":";;AA6BA,SAAgB,gBAAgB,EAC9B,YACA,UACA,OAKoC;CACpC,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI,UAAyD,CAAC;CAE9D,MAAM,QAAQ,YAA2B;EAGvC,OAAO,kBAAkB,KAAA,GAAW;GAClC,MAAM,SAAS;GACf,gBAAgB,KAAA;GAIhB,MAAM,YAAY;GAClB,UAAU,CAAC;GAEX,IAAI;GAEJ,IAAI;IACF,MAAM,IAAI,MAAM;GAClB,SAAS,KAAK;IACZ,UAAU,EAAE,OAAO,IAAI;GACzB;GAKA,KAAK,MAAM,UAAU,WAAW,OAAO,OAAO;EAChD;CACF;CAGA,OAAO,OAAO,WAAkC;EAQ9C,IAAI,SAAS,GAAG;EAEhB,gBAAgB;EAEhB,MAAM,UAAU,IAAI,SAAe,SAAS,WAAW;GACrD,QAAQ,MAAM,YAAY;IACxB,IAAI,SAAS,OAAO,QAAQ,QAAQ,KAAK,CAAC;SACrC,QAAQ;GACf,CAAC;EACH,CAAC;EAED,IAAI,eAAe,aAAa,aAAa;EAC7C,gBAAgB,iBAAiB;GAC/B,gBAAgB,KAAA;GAChB,YAAY,YAAY,QAAQ,QAAQ,EAAA,CAAG,KAAK,KAAK;EACvD,GAAG,UAAU;EAIb,cAAc,MAAM;EAEpB,OAAO;CACT;AACF"}
@@ -0,0 +1,38 @@
1
+ import { paint } from "./colour.js";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import zlib from "node:zlib";
5
+ //#region src/size-report.ts
6
+ function reportSizes({ root, stdoutColour }, generatedFiles) {
7
+ const fileInfos = [];
8
+ for (const filePath of generatedFiles) if (fs.existsSync(filePath)) {
9
+ const displayPath = path.relative(root, filePath).replace(/\\/g, "/");
10
+ const dir = path.dirname(displayPath);
11
+ const base = path.basename(displayPath);
12
+ const coloredPath = dir === "." ? paint("32", base, stdoutColour) : paint("90", `${dir}/`, stdoutColour) + paint("32", base, stdoutColour);
13
+ try {
14
+ const sizeStr = `${(fs.statSync(filePath).size / 1024).toFixed(2)} kB`;
15
+ const content = fs.readFileSync(filePath);
16
+ const gzipSizeStr = `${(zlib.gzipSync(content).length / 1024).toFixed(2)} kB`;
17
+ fileInfos.push({
18
+ coloredPath,
19
+ gzipSizeStr,
20
+ relativeDisplayPath: displayPath,
21
+ sizeStr
22
+ });
23
+ } catch {}
24
+ }
25
+ if (fileInfos.length > 0) {
26
+ const longestPathLength = Math.max(...fileInfos.map((f) => f.relativeDisplayPath.length), 0);
27
+ const longestSizeLength = Math.max(...fileInfos.map((f) => f.sizeStr.length), 0);
28
+ for (const info of fileInfos) {
29
+ const pathPadding = " ".repeat(Math.max(2, longestPathLength - info.relativeDisplayPath.length + 2));
30
+ const sizePadded = info.sizeStr.padStart(longestSizeLength);
31
+ console.log(info.coloredPath + pathPadding + paint("90", `${sizePadded} │ gzip: ${info.gzipSizeStr}`, stdoutColour));
32
+ }
33
+ }
34
+ }
35
+ //#endregion
36
+ export { reportSizes };
37
+
38
+ //# sourceMappingURL=size-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"size-report.js","names":[],"sources":["../src/size-report.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport zlib from 'node:zlib'\n\nimport { paint } from './colour.js'\n\n// The size-and-gzip table, in a function of its own so that the compile\n// `try` in `runBuilds` can stop before it. Everything here is presentation\n// over files Style Dictionary has already finished writing, so a throw from\n// it is a reporting bug and nothing more.\n//\n// `root` and whether stdout takes colour belong to the plugin instance, so\n// they are handed in rather than held.\nexport function reportSizes(\n { root, stdoutColour }: { root: string; stdoutColour: boolean },\n generatedFiles: Set<string>,\n): void {\n const fileInfos: Array<{\n coloredPath: string\n gzipSizeStr: string\n relativeDisplayPath: string\n sizeStr: string\n }> = []\n\n for (const filePath of generatedFiles) {\n if (fs.existsSync(filePath)) {\n const displayPath = path.relative(root, filePath).replace(/\\\\/g, '/')\n const dir = path.dirname(displayPath)\n const base = path.basename(displayPath)\n // The table goes to stdout, so it follows stdout's decision — which\n // is not always stderr's, since the two are redirected separately.\n const coloredPath =\n dir === '.'\n ? paint('32', base, stdoutColour)\n : paint('90', `${dir}/`, stdoutColour) +\n paint('32', base, stdoutColour)\n\n try {\n const stats = fs.statSync(filePath)\n const bytes = stats.size\n const sizeStr = `${(bytes / 1024).toFixed(2)} kB`\n\n const content = fs.readFileSync(filePath)\n const gzipBytes = zlib.gzipSync(content).length\n const gzipSizeStr = `${(gzipBytes / 1024).toFixed(2)} kB`\n\n fileInfos.push({\n coloredPath,\n gzipSizeStr,\n relativeDisplayPath: displayPath,\n sizeStr,\n })\n } catch {\n // One unreadable destination costs its row rather than the table.\n // Deliberately narrower than the caller's `catch`: it covers the\n // three filesystem and gzip calls above and not the arithmetic\n // below, so a padding bug is reported rather than quietly printing\n // short.\n }\n }\n }\n\n if (fileInfos.length > 0) {\n const longestPathLength = Math.max(\n ...fileInfos.map((f) => f.relativeDisplayPath.length),\n 0,\n )\n const longestSizeLength = Math.max(\n ...fileInfos.map((f) => f.sizeStr.length),\n 0,\n )\n\n for (const info of fileInfos) {\n const pathPadding = ' '.repeat(\n Math.max(2, longestPathLength - info.relativeDisplayPath.length + 2),\n )\n const sizePadded = info.sizeStr.padStart(longestSizeLength)\n console.log(\n info.coloredPath +\n pathPadding +\n paint(\n '90',\n `${sizePadded} │ gzip: ${info.gzipSizeStr}`,\n stdoutColour,\n ),\n )\n }\n }\n}\n"],"mappings":";;;;;AAaA,SAAgB,YACd,EAAE,MAAM,gBACR,gBACM;CACN,MAAM,YAKD,CAAC;CAEN,KAAK,MAAM,YAAY,gBACrB,IAAI,GAAG,WAAW,QAAQ,GAAG;EAC3B,MAAM,cAAc,KAAK,SAAS,MAAM,QAAQ,CAAC,CAAC,QAAQ,OAAO,GAAG;EACpE,MAAM,MAAM,KAAK,QAAQ,WAAW;EACpC,MAAM,OAAO,KAAK,SAAS,WAAW;EAGtC,MAAM,cACJ,QAAQ,MACJ,MAAM,MAAM,MAAM,YAAY,IAC9B,MAAM,MAAM,GAAG,IAAI,IAAI,YAAY,IACnC,MAAM,MAAM,MAAM,YAAY;EAEpC,IAAI;GAGF,MAAM,UAAU,IAFF,GAAG,SAAS,QACR,CAAC,CAAC,OACQ,KAAA,CAAM,QAAQ,CAAC,EAAE;GAE7C,MAAM,UAAU,GAAG,aAAa,QAAQ;GAExC,MAAM,cAAc,IADF,KAAK,SAAS,OAAO,CAAC,CAAC,SACL,KAAA,CAAM,QAAQ,CAAC,EAAE;GAErD,UAAU,KAAK;IACb;IACA;IACA,qBAAqB;IACrB;GACF,CAAC;EACH,QAAQ,CAMR;CACF;CAGF,IAAI,UAAU,SAAS,GAAG;EACxB,MAAM,oBAAoB,KAAK,IAC7B,GAAG,UAAU,KAAK,MAAM,EAAE,oBAAoB,MAAM,GACpD,CACF;EACA,MAAM,oBAAoB,KAAK,IAC7B,GAAG,UAAU,KAAK,MAAM,EAAE,QAAQ,MAAM,GACxC,CACF;EAEA,KAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,cAAc,IAAI,OACtB,KAAK,IAAI,GAAG,oBAAoB,KAAK,oBAAoB,SAAS,CAAC,CACrE;GACA,MAAM,aAAa,KAAK,QAAQ,SAAS,iBAAiB;GAC1D,QAAQ,IACN,KAAK,cACH,cACA,MACE,MACA,GAAG,WAAW,WAAW,KAAK,eAC9B,YACF,CACJ;EACF;CACF;AACF"}
@@ -0,0 +1,61 @@
1
+ import { expandPatterns, sourcePatternsOf } from "./patterns.js";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ //#region src/up-to-date.ts
5
+ function configFingerprint(root, item) {
6
+ try {
7
+ return JSON.stringify([root, item.file ?? item.config], (_key, value) => typeof value === "function" ? `[fn]${String(value)}` : value);
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+ function declaredDestinations(configObj, only) {
13
+ const destinations = [];
14
+ const entries = Object.entries(configObj.platforms ?? {});
15
+ const selected = only ? entries.filter(([name]) => only.includes(name)) : entries;
16
+ for (const [, platform] of selected) for (const file of platform.files ?? []) if (file.destination) destinations.push(writtenDestination(platform.buildPath, file.destination));
17
+ return destinations;
18
+ }
19
+ async function isUpToDate({ compiledFingerprints, log, root, watch }, item, configObj, only) {
20
+ if (Object.values(configObj.platforms ?? {}).some((platform) => (platform.actions?.length ?? 0) > 0)) return false;
21
+ const destinations = declaredDestinations(configObj, only);
22
+ if (destinations.length === 0) return false;
23
+ const extraWatches = watch ? Array.isArray(watch) ? watch : [watch] : [];
24
+ const sources = await expandPatterns([...sourcePatternsOf(configObj), ...extraWatches.map((pattern) => (path.isAbsolute(pattern) ? pattern : path.resolve(root, pattern)).replace(/\\/g, "/"))], log);
25
+ if (item.file) sources.push(item.file.replace(/\\/g, "/"));
26
+ if (sources.length === 0) return false;
27
+ let newestSource = -Infinity;
28
+ let sawFile = false;
29
+ for (const source of sources) {
30
+ const stats = statOrNull(source);
31
+ if (!stats) return false;
32
+ if (stats.isDirectory()) continue;
33
+ sawFile = true;
34
+ newestSource = Math.max(newestSource, stats.mtimeMs);
35
+ }
36
+ if (!sawFile) return false;
37
+ let oldestDestination = Infinity;
38
+ for (const destination of destinations) {
39
+ const stats = statOrNull(destination);
40
+ if (!stats) return false;
41
+ oldestDestination = Math.min(oldestDestination, stats.mtimeMs);
42
+ }
43
+ if (oldestDestination <= newestSource) return false;
44
+ if (item.file) return true;
45
+ const fingerprint = configFingerprint(root, item);
46
+ return fingerprint !== null && compiledFingerprints.has(fingerprint);
47
+ }
48
+ function writtenDestination(buildPath, destination) {
49
+ return path.resolve(process.cwd(), buildPath ? path.join(buildPath, destination) : destination);
50
+ }
51
+ function statOrNull(file) {
52
+ try {
53
+ return fs.statSync(file);
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+ //#endregion
59
+ export { configFingerprint, declaredDestinations, isUpToDate, writtenDestination };
60
+
61
+ //# sourceMappingURL=up-to-date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"up-to-date.js","names":[],"sources":["../src/up-to-date.ts"],"sourcesContent":["// Whether a configuration's compile can be skipped, and the destinations that\n// question is asked about.\n//\n// Nothing here holds state — not even the fingerprints of what this process\n// has compiled, which are process-wide and live in `index.ts` beside\n// `compilesInFlight`. What a plugin instance owns is handed in on each call.\n\nimport type { Config } from 'style-dictionary'\n\nimport fs from 'node:fs'\nimport path from 'node:path'\n\nimport type { Log, ResolvedConfig } from './config.js'\nimport type { UnpluginStyleDictionaryOptions } from './types.js'\n\nimport { expandPatterns, sourcePatternsOf } from './patterns.js'\n\n// A stable identity for one resolved configuration, or `null` where it\n// cannot have one. Functions are serialised by source rather than dropped,\n// because an inline `format` or `transform` is exactly the edit a\n// fingerprint has to notice, and `JSON.stringify` omits a function outright.\nexport function configFingerprint(\n root: string,\n item: ResolvedConfig,\n): null | string {\n try {\n return JSON.stringify(\n [root, item.file ?? item.config],\n (_key, value: unknown) =>\n typeof value === 'function' ? `[fn]${String(value)}` : value,\n )\n } catch {\n // Circular, or holding a BigInt. It takes no identity rather than a\n // wrong one, so it compiles every time exactly as it did before.\n return null\n }\n}\n\n// Every absolute destination a configuration declares, read off the\n// configuration itself rather than off an extended Style Dictionary\n// instance. Reading it here is the whole point: constructing the instance\n// is what the skip exists to avoid.\n//\n// Each one is named by `writtenDestination`, which `runBuilds` asks as well,\n// so the up-to-date check and the record of what was built name the same\n// files — the ones Style Dictionary actually wrote.\n//\n// `only` narrows this to named platforms, and exactly one caller wants that:\n// the up-to-date check, which asks whether the work *this* compile would do\n// is already done. Everywhere else the answer has to cover every declared\n// platform, because a file an unselected platform wrote earlier is still the\n// plugin's own output and has to stay out of the watch list.\nexport function declaredDestinations(\n configObj: Config,\n only?: string[],\n): string[] {\n const destinations: string[] = []\n\n const entries = Object.entries(configObj.platforms ?? {})\n const selected = only\n ? entries.filter(([name]) => only.includes(name))\n : entries\n\n for (const [, platform] of selected) {\n for (const file of platform.files ?? []) {\n if (file.destination) {\n destinations.push(\n writtenDestination(platform.buildPath, file.destination),\n )\n }\n }\n }\n\n return destinations\n}\n\n// Whether every file a configuration declares is already newer than every\n// file it reads, so its compile can be skipped.\n//\n// Conservative in every direction it can be: anything it cannot establish —\n// a destination that is missing, a source it cannot stat, a configuration\n// declaring no destinations at all — is a reason to build rather than to\n// skip.\nexport async function isUpToDate(\n {\n compiledFingerprints,\n log,\n root,\n watch,\n }: {\n compiledFingerprints: ReadonlySet<string>\n log: Log\n root: string\n watch: UnpluginStyleDictionaryOptions['watch']\n },\n item: ResolvedConfig,\n configObj: Config,\n only?: string[],\n): Promise<boolean> {\n // An action writes what no `destination` names, so there is nothing for\n // the comparison below to check and skipping would leave its work undone.\n const hasActions = Object.values(configObj.platforms ?? {}).some(\n (platform) => (platform.actions?.length ?? 0) > 0,\n )\n if (hasActions) return false\n\n const destinations = declaredDestinations(configObj, only)\n if (destinations.length === 0) return false\n\n // `options.watch` belongs in here as much as `source` does. A consumer\n // names an extra file because something in the build reads it — a custom\n // format's own data file, most obviously — and leaving it out let a change\n // to it be skipped over while the watcher dutifully reported it.\n const extraWatches = watch ? (Array.isArray(watch) ? watch : [watch]) : []\n\n const sources = await expandPatterns(\n [\n ...sourcePatternsOf(configObj),\n ...extraWatches.map((pattern) =>\n (path.isAbsolute(pattern)\n ? pattern\n : path.resolve(root, pattern)\n ).replace(/\\\\/g, '/'),\n ),\n ],\n log,\n )\n if (item.file) sources.push(item.file.replace(/\\\\/g, '/'))\n\n if (sources.length === 0) return false\n\n let newestSource = -Infinity\n let sawFile = false\n\n for (const source of sources) {\n const stats = statOrNull(source)\n if (!stats) return false\n\n // Directories are in this list on purpose — `expandPatterns` registers\n // each pattern's static parent so a token file created later is\n // noticed — but their mtime cannot be read as an input signal here. A\n // directory's mtime moves whenever an entry is added or renamed inside\n // it, and the atomic write renames every generated file into place, so\n // a `buildPath` inside a watched directory made the build itself the\n // newest thing the comparison could see. Nothing was ever up to date.\n if (stats.isDirectory()) continue\n\n sawFile = true\n newestSource = Math.max(newestSource, stats.mtimeMs)\n }\n\n // Every pattern expanded to directories alone, so nothing was actually\n // read. Style Dictionary would build an empty dictionary from that, and a\n // skip would present the empty result as current.\n if (!sawFile) return false\n\n let oldestDestination = Infinity\n for (const destination of destinations) {\n const stats = statOrNull(destination)\n if (!stats) return false\n oldestDestination = Math.min(oldestDestination, stats.mtimeMs)\n }\n\n if (oldestDestination <= newestSource) return false\n\n // A configuration given as a path has its own file among the sources\n // above, so an edit to it has already been accounted for and the skip\n // holds across processes.\n if (item.file) return true\n\n // One given as an object or a function has not. Only this process knows\n // what it looked like when those destinations were written, so the skip\n // holds only against a fingerprint recorded here.\n const fingerprint = configFingerprint(root, item)\n\n return fingerprint !== null && compiledFingerprints.has(fingerprint)\n}\n\n// Where Style Dictionary writes one file, as an absolute path.\n//\n// It joins the destination onto the platform's `buildPath` when there is one\n// and writes the result relative to the working directory. **Joins, not\n// resolves**: an absolute destination lands under the build path rather than\n// replacing it. Measured with `buildPath: 'gen/'` and an absolute destination,\n// Style Dictionary wrote `gen/<that path>`, while the plugin had named the\n// absolute path on its own — a file that was never written.\n//\n// `root` has no say. It is where a relative `config` path is looked up and\n// nothing more, and reading `buildPath` against it named files that did not\n// exist whenever the two differed (#362). Style Dictionary joins with\n// `path-unified/posix`; the platform's own `join` names the same file, and\n// resolving the result is what normalises the separators either way.\nexport function writtenDestination(\n buildPath: string | undefined,\n destination: string,\n): string {\n return path.resolve(\n process.cwd(),\n buildPath ? path.join(buildPath, destination) : destination,\n )\n}\n\n// `fs.statSync` without the throw. A file that is missing, or that cannot be\n// read, is the same answer to every caller here: nothing to compare against.\nfunction statOrNull(file: string): fs.Stats | null {\n try {\n return fs.statSync(file)\n } catch {\n return null\n }\n}\n"],"mappings":";;;;AAqBA,SAAgB,kBACd,MACA,MACe;CACf,IAAI;EACF,OAAO,KAAK,UACV,CAAC,MAAM,KAAK,QAAQ,KAAK,MAAM,IAC9B,MAAM,UACL,OAAO,UAAU,aAAa,OAAO,OAAO,KAAK,MAAM,KAC3D;CACF,QAAQ;EAGN,OAAO;CACT;AACF;AAgBA,SAAgB,qBACd,WACA,MACU;CACV,MAAM,eAAyB,CAAC;CAEhC,MAAM,UAAU,OAAO,QAAQ,UAAU,aAAa,CAAC,CAAC;CACxD,MAAM,WAAW,OACb,QAAQ,QAAQ,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,IAC9C;CAEJ,KAAK,MAAM,GAAG,aAAa,UACzB,KAAK,MAAM,QAAQ,SAAS,SAAS,CAAC,GACpC,IAAI,KAAK,aACP,aAAa,KACX,mBAAmB,SAAS,WAAW,KAAK,WAAW,CACzD;CAKN,OAAO;AACT;AASA,eAAsB,WACpB,EACE,sBACA,KACA,MACA,SAOF,MACA,WACA,MACkB;CAMlB,IAHmB,OAAO,OAAO,UAAU,aAAa,CAAC,CAAC,CAAC,CAAC,MACzD,cAAc,SAAS,SAAS,UAAU,KAAK,CAErC,GAAG,OAAO;CAEvB,MAAM,eAAe,qBAAqB,WAAW,IAAI;CACzD,IAAI,aAAa,WAAW,GAAG,OAAO;CAMtC,MAAM,eAAe,QAAS,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,IAAK,CAAC;CAEzE,MAAM,UAAU,MAAM,eACpB,CACE,GAAG,iBAAiB,SAAS,GAC7B,GAAG,aAAa,KAAK,aAClB,KAAK,WAAW,OAAO,IACpB,UACA,KAAK,QAAQ,MAAM,OAAO,EAAA,CAC5B,QAAQ,OAAO,GAAG,CACtB,CACF,GACA,GACF;CACA,IAAI,KAAK,MAAM,QAAQ,KAAK,KAAK,KAAK,QAAQ,OAAO,GAAG,CAAC;CAEzD,IAAI,QAAQ,WAAW,GAAG,OAAO;CAEjC,IAAI,eAAe;CACnB,IAAI,UAAU;CAEd,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,QAAQ,WAAW,MAAM;EAC/B,IAAI,CAAC,OAAO,OAAO;EASnB,IAAI,MAAM,YAAY,GAAG;EAEzB,UAAU;EACV,eAAe,KAAK,IAAI,cAAc,MAAM,OAAO;CACrD;CAKA,IAAI,CAAC,SAAS,OAAO;CAErB,IAAI,oBAAoB;CACxB,KAAK,MAAM,eAAe,cAAc;EACtC,MAAM,QAAQ,WAAW,WAAW;EACpC,IAAI,CAAC,OAAO,OAAO;EACnB,oBAAoB,KAAK,IAAI,mBAAmB,MAAM,OAAO;CAC/D;CAEA,IAAI,qBAAqB,cAAc,OAAO;CAK9C,IAAI,KAAK,MAAM,OAAO;CAKtB,MAAM,cAAc,kBAAkB,MAAM,IAAI;CAEhD,OAAO,gBAAgB,QAAQ,qBAAqB,IAAI,WAAW;AACrE;AAgBA,SAAgB,mBACd,WACA,aACQ;CACR,OAAO,KAAK,QACV,QAAQ,IAAI,GACZ,YAAY,KAAK,KAAK,WAAW,WAAW,IAAI,WAClD;AACF;AAIA,SAAS,WAAW,MAA+B;CACjD,IAAI;EACF,OAAO,GAAG,SAAS,IAAI;CACzB,QAAQ;EACN,OAAO;CACT;AACF"}
@@ -1,5 +1,8 @@
1
1
  import picomatch from "picomatch";
2
2
  //#region src/watch-filter.ts
3
+ function isWatchedSource(file, patterns, generatedDestinations) {
4
+ return !generatedDestinations.has(file.replace(/\\/g, "/")) && matchesWatchedFile(file, patterns);
5
+ }
3
6
  function matchesWatchedFile(file, patterns) {
4
7
  const normalizedFile = file.replace(/\\/g, "/");
5
8
  return patterns.some((pattern) => {
@@ -8,6 +11,6 @@ function matchesWatchedFile(file, patterns) {
8
11
  });
9
12
  }
10
13
  //#endregion
11
- export { matchesWatchedFile };
14
+ export { isWatchedSource, matchesWatchedFile };
12
15
 
13
16
  //# sourceMappingURL=watch-filter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"watch-filter.js","names":[],"sources":["../src/watch-filter.ts"],"sourcesContent":["import picomatch from 'picomatch'\n\n// Whether `file` matches one of the resolved config/token watch patterns.\n// Shared by the Vite-specific `configureServer` watcher and the universal\n// `watchChange` hook — both need it, and both must skip files that don't\n// match: without this filter, `watchChange` reacts to *any* changed\n// module-graph file, including this plugin's own generated output (since\n// consuming code imports it). Every regenerate is itself a \"change\", which\n// without filtering re-triggers a rebuild forever.\n//\n// Matching a pattern is only half of that, and this function is only the half\n// it can answer. A `buildPath` inside a `source` directory is a supported\n// layout, and under any correct matcher its output matches the very glob that\n// produced it — so the caller also subtracts what the last build wrote. See\n// `generatedDestinations` and `isWatchedSource` in the factory below.\n//\n// The patterns are Style Dictionary's own `source` and `include` globs, so the\n// filter has to admit exactly what the build reads — which is why the matching\n// is a real globber's rather than hand-rolled. The version this replaces was\n// wrong in both directions at once: it stripped `/**` out of a pattern and\n// prefix-matched the remainder, so `tokens/**/*.json` matched nothing sitting\n// directly in `tokens/` and `tokens/**` matched a `tokens-backup/` sibling,\n// while its regex branch mapped every `*` to `.*` — crossing `/` — and tested\n// it unanchored, so generated output under a watched directory matched its own\n// source glob and rebuilt forever.\n//\n// picomatch rather than `path.matchesGlob`, which would need no dependency at\n// all: that function is documented experimental, and on Node 20 — the floor\n// `engines` declares — it prints `ExperimentalWarning: glob is an experimental\n// feature and might change at any time` into the consumer's build output. The\n// dependency is free in practice, since `unplugin` depends on the same\n// picomatch and is already installed wherever this plugin is. Its `dot: false`\n// default is deliberate: it is what glob, and so Style Dictionary, reads\n// sources with, so a dotfile is invisible to the filter and to the build alike.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some((pattern) => {\n const normalizedPattern = pattern.replace(/\\\\/g, '/')\n\n // A config file reaches this function as its own literal path, which is\n // both the common case and the one shape that is not a glob at all.\n return (\n normalizedPattern === normalizedFile ||\n picomatch.isMatch(normalizedFile, normalizedPattern)\n )\n })\n}\n"],"mappings":";;AAkCA,SAAgB,mBAAmB,MAAc,UAA6B;CAC5E,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,OAAO,SAAS,MAAM,YAAY;EAChC,MAAM,oBAAoB,QAAQ,QAAQ,OAAO,GAAG;EAIpD,OACE,sBAAsB,kBACtB,UAAU,QAAQ,gBAAgB,iBAAiB;CAEvD,CAAC;AACH"}
1
+ {"version":3,"file":"watch-filter.js","names":[],"sources":["../src/watch-filter.ts"],"sourcesContent":["import picomatch from 'picomatch'\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.\nexport function isWatchedSource(\n file: string,\n patterns: string[],\n generatedDestinations: ReadonlySet<string>,\n): boolean {\n return (\n !generatedDestinations.has(file.replace(/\\\\/g, '/')) &&\n matchesWatchedFile(file, patterns)\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.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some((pattern) => {\n const normalizedPattern = pattern.replace(/\\\\/g, '/')\n\n // A config file reaches this function as its own literal path, which is\n // both the common case and the one shape that is not a glob at all.\n return (\n normalizedPattern === normalizedFile ||\n picomatch.isMatch(normalizedFile, normalizedPattern)\n )\n })\n}\n"],"mappings":";;AASA,SAAgB,gBACd,MACA,UACA,uBACS;CACT,OACE,CAAC,sBAAsB,IAAI,KAAK,QAAQ,OAAO,GAAG,CAAC,KACnD,mBAAmB,MAAM,QAAQ;AAErC;AAkCA,SAAgB,mBAAmB,MAAc,UAA6B;CAC5E,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,OAAO,SAAS,MAAM,YAAY;EAChC,MAAM,oBAAoB,QAAQ,QAAQ,OAAO,GAAG;EAIpD,OACE,sBAAsB,kBACtB,UAAU,QAAQ,gBAAgB,iBAAiB;CAEvD,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanso-labs/unplugin-style-dictionary",
3
- "version": "0.10.4",
3
+ "version": "0.10.6",
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",
@@ -80,8 +80,8 @@
80
80
  "devDependencies": {
81
81
  "@arethetypeswrong/cli": "0.18.5",
82
82
  "@codecov/rollup-plugin": "2.0.1",
83
- "@commitlint/cli": "21.2.2",
84
- "@commitlint/config-conventional": "21.2.2",
83
+ "@commitlint/cli": "21.2.3",
84
+ "@commitlint/config-conventional": "21.2.3",
85
85
  "@eslint/js": "10.0.1",
86
86
  "@rspack/core": "2.2.6",
87
87
  "@types/node": "26.6.2",
@@ -102,7 +102,7 @@
102
102
  "rolldown": "1.2.9",
103
103
  "rollup": "4.63.4",
104
104
  "semver": "7.8.5",
105
- "style-dictionary": "5.5.4",
105
+ "style-dictionary": "5.5.5",
106
106
  "tsdown": "0.23.0",
107
107
  "typescript": "6.0.3",
108
108
  "typescript-eslint": "8.70.0",