@kanso-labs/unplugin-style-dictionary 0.10.13 → 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/README.md +89 -34
- package/dist/compile.js +27 -14
- package/dist/compile.js.map +1 -1
- package/dist/config.js +65 -13
- package/dist/config.js.map +1 -1
- package/dist/index.js +308 -50
- package/dist/index.js.map +1 -1
- package/dist/options.js +40 -0
- package/dist/options.js.map +1 -0
- package/dist/patterns.js +8 -1
- package/dist/patterns.js.map +1 -1
- package/dist/size-report.js +6 -4
- package/dist/size-report.js.map +1 -1
- package/dist/types.d.ts +43 -14
- package/dist/watch-filter.js +18 -3
- package/dist/watch-filter.js.map +1 -1
- package/package.json +6 -6
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { errorMessage } from "./errors.js";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import StyleDictionary from "style-dictionary";
|
|
4
5
|
import JSON5 from "json5";
|
|
5
6
|
import { pathToFileURL } from "node:url";
|
|
6
7
|
//#region src/config.ts
|
|
@@ -11,26 +12,39 @@ const IMPORTED_CONFIG_EXTENSIONS = [
|
|
|
11
12
|
];
|
|
12
13
|
async function configForBuild(item) {
|
|
13
14
|
const { config } = item;
|
|
14
|
-
if (typeof config !== "string"
|
|
15
|
+
if (typeof config !== "string") return withTokenModuleParser(config);
|
|
15
16
|
const loaded = await readConfigObject(item);
|
|
16
17
|
if (!loaded) return item.config;
|
|
18
|
+
if (!isImportedConfig(config)) return withTokenModuleParser(loaded);
|
|
17
19
|
try {
|
|
18
|
-
return structuredClone(loaded);
|
|
20
|
+
return withTokenModuleParser(structuredClone(loaded));
|
|
19
21
|
} catch {
|
|
20
|
-
return loaded;
|
|
22
|
+
return withTokenModuleParser(loaded);
|
|
21
23
|
}
|
|
22
24
|
}
|
|
25
|
+
const TOKEN_MODULE_PARSER = "unplugin-style-dictionary/token-module";
|
|
26
|
+
var TokenModulePattern = class extends RegExp {
|
|
27
|
+
others;
|
|
28
|
+
constructor(others) {
|
|
29
|
+
super(String.raw`\.(?:js|mjs|ts)$`);
|
|
30
|
+
this.others = others;
|
|
31
|
+
}
|
|
32
|
+
[Symbol.match](value) {
|
|
33
|
+
if (this.others.some((other) => value.match(other) !== null)) return null;
|
|
34
|
+
return super[Symbol.match](value);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
23
37
|
function describeConfig(item, index) {
|
|
24
38
|
return item.file ? `The configuration ${item.file}` : `The configuration at position ${index + 1}`;
|
|
25
39
|
}
|
|
26
40
|
async function readConfigObject(item, report) {
|
|
27
41
|
if (typeof item.config !== "string") return item.config;
|
|
28
42
|
try {
|
|
29
|
-
const loaded = isImportedConfig(item.config) ? await importConfigModule(item.config) : JSON5.parse(fs.readFileSync(item.config, "utf-8"));
|
|
43
|
+
const loaded = isImportedConfig(item.config) ? await importConfigModule(item.config, item.watched) : JSON5.parse(fs.readFileSync(item.config, "utf-8"));
|
|
30
44
|
if (isConfig(loaded)) return loaded;
|
|
31
45
|
if (report) report(`Config file did not resolve to a configuration object: ${item.config}`, "error");
|
|
32
46
|
} catch (err) {
|
|
33
|
-
if (report) report(`Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}`, "error");
|
|
47
|
+
if (report) report(fs.existsSync(item.config) ? `Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}` : `Config file not found: ${item.config}`, "error");
|
|
34
48
|
}
|
|
35
49
|
return null;
|
|
36
50
|
}
|
|
@@ -57,12 +71,12 @@ async function resolveConfigOption({ config, configContext, discovery, log, root
|
|
|
57
71
|
}
|
|
58
72
|
if (!discovery.announced) {
|
|
59
73
|
discovery.announced = true;
|
|
60
|
-
log(`Using the configuration it found at ${fullPath}`, "info");
|
|
74
|
+
log(rejected.length > 0 ? `Using the configuration it found at ${fullPath}, after ignoring ${rejected.join(", ")}: nothing there declares platforms, source, include or tokens. Name ${file} with the config option to stop looking.` : `Using the configuration it found at ${fullPath}`, "info");
|
|
61
75
|
}
|
|
62
76
|
rawConfig = file;
|
|
63
77
|
break;
|
|
64
78
|
}
|
|
65
|
-
if (rejected.length > 0) log(`Ignored ${rejected.join(", ")} in ${root}: nothing there declares platforms, source, include or tokens, so it does not look like a Style Dictionary configuration. Name it with the config option if it is one, or set config to false to stop looking.`, "error");
|
|
79
|
+
if (!rawConfig && rejected.length > 0) log(`Ignored ${rejected.join(", ")} in ${root}: nothing there declares platforms, source, include or tokens, so it does not look like a Style Dictionary configuration. Name it with the config option if it is one, or set config to false to stop looking.`, "error");
|
|
66
80
|
}
|
|
67
81
|
if (!rawConfig) {
|
|
68
82
|
log("No configuration specified and no default config file found. Style Dictionary will not compile.", "error");
|
|
@@ -79,15 +93,20 @@ async function resolveConfigOption({ config, configContext, discovery, log, root
|
|
|
79
93
|
} else return { config: conf };
|
|
80
94
|
});
|
|
81
95
|
}
|
|
82
|
-
async function importConfigModule(file) {
|
|
83
|
-
|
|
96
|
+
async function importConfigModule(file, watched = []) {
|
|
97
|
+
const key = versionKey([file, ...watched]);
|
|
98
|
+
return unwrapDefault(await import(`${pathToFileURL(file).href}?t=${key}`));
|
|
99
|
+
}
|
|
100
|
+
async function importTokenModule(filePath) {
|
|
101
|
+
if (filePath === void 0) return {};
|
|
102
|
+
const module = await import(`${pathToFileURL(filePath).href}?t=${versionKey([filePath])}`);
|
|
103
|
+
const tokens = typeof module === "object" && module !== null && "default" in module ? module.default : void 0;
|
|
104
|
+
if (!isTokens(tokens)) return {};
|
|
84
105
|
try {
|
|
85
|
-
|
|
106
|
+
return structuredClone(tokens);
|
|
86
107
|
} catch {
|
|
87
|
-
|
|
108
|
+
return tokens;
|
|
88
109
|
}
|
|
89
|
-
const key = String(version).replace(".", "_");
|
|
90
|
-
return unwrapDefault(await import(`${pathToFileURL(file).href}?t=${key}`));
|
|
91
110
|
}
|
|
92
111
|
function isConfig(value) {
|
|
93
112
|
return typeof value === "object" && value !== null;
|
|
@@ -95,6 +114,9 @@ function isConfig(value) {
|
|
|
95
114
|
function isImportedConfig(file) {
|
|
96
115
|
return IMPORTED_CONFIG_EXTENSIONS.some((extension) => file.endsWith(extension));
|
|
97
116
|
}
|
|
117
|
+
function isTokens(value) {
|
|
118
|
+
return typeof value === "object" && value !== null;
|
|
119
|
+
}
|
|
98
120
|
function looksLikeConfig(value) {
|
|
99
121
|
if (typeof value !== "object" || value === null) return false;
|
|
100
122
|
return [
|
|
@@ -107,6 +129,36 @@ function looksLikeConfig(value) {
|
|
|
107
129
|
function unwrapDefault(value) {
|
|
108
130
|
return typeof value === "object" && value !== null && "default" in value ? value.default ?? value : value;
|
|
109
131
|
}
|
|
132
|
+
function versionKey(files) {
|
|
133
|
+
let newest = -1;
|
|
134
|
+
for (const file of files) try {
|
|
135
|
+
newest = Math.max(newest, fs.statSync(file).mtimeMs);
|
|
136
|
+
} catch {}
|
|
137
|
+
return String(newest < 0 ? Date.now() : newest).replace(".", "_");
|
|
138
|
+
}
|
|
139
|
+
function withTokenModuleParser(config) {
|
|
140
|
+
const applied = config.parsers ?? [];
|
|
141
|
+
const own = config.hooks?.parsers ?? {};
|
|
142
|
+
const registered = StyleDictionary.hooks.parsers;
|
|
143
|
+
const others = applied.flatMap((name) => {
|
|
144
|
+
const parser = own[name] ?? registered[name];
|
|
145
|
+
return parser ? [parser.pattern] : [];
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
...config,
|
|
149
|
+
hooks: {
|
|
150
|
+
...config.hooks,
|
|
151
|
+
parsers: {
|
|
152
|
+
...config.hooks?.parsers,
|
|
153
|
+
[TOKEN_MODULE_PARSER]: {
|
|
154
|
+
parser: async ({ filePath }) => importTokenModule(filePath),
|
|
155
|
+
pattern: new TokenModulePattern(others)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
parsers: [...applied, TOKEN_MODULE_PARSER]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
110
162
|
//#endregion
|
|
111
163
|
export { configForBuild, describeConfig, readConfigObject, resolveConfigOption };
|
|
112
164
|
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["// Where a configuration comes from and what it says: the `config` option\n// resolved into a list, each item read as an object, and the form Style\n// Dictionary is handed to build from.\n//\n// Nothing here holds state. One module serves every plugin instance in the\n// process, so what an instance owns — its root, its logger, whether it has\n// announced a discovery yet — is handed in on each call.\n\nimport type { Config } from 'style-dictionary'\n\nimport JSON5 from 'json5'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport type {\n StyleDictionaryConfigContext,\n UnpluginStyleDictionaryOptions,\n} from './types.js'\n\nimport { errorMessage } from './errors.js'\n\n// The plugin's logger, narrowed to the two levels anything here reports at. It\n// is handed on as a function rather than as the host it writes to, so it goes\n// on reading whichever host has claimed the plugin's messages by the time it\n// is called.\nexport type Log = (message: string, type: 'error' | 'info') => void\n\n// A configuration as `resolveConfigOption` hands it on: either the object the\n// consumer passed or the path it was read from, plus the directory relative\n// paths inside it resolve against.\nexport interface ResolvedConfig {\n config: Config | string\n file?: string\n}\n\n// The config extensions Style Dictionary loads with `import` rather than by\n// parsing the file — the `case` list in its own `loadFile`. They are the only\n// ones Node's permanent module cache applies to, and so the only ones this\n// plugin has to read on the build's behalf.\n//\n// Everything else Style Dictionary parses as JSON5, including `.json`, and\n// this list is what makes the plugin split the same way. Reading the two\n// halves apart is what silently unwatched a whole family of configurations:\n// a `.json5` or `.jsonc` file went down the import branch and failed there\n// while the build succeeded, and a `.json` file carrying a comment or a\n// trailing comma failed strict `JSON.parse` for the same reason.\nconst IMPORTED_CONFIG_EXTENSIONS = ['.js', '.mjs', '.ts']\n\n// What `new StyleDictionary` is handed for an item. Only a path in the JS\n// family becomes an object, because those are exactly the extensions Style\n// Dictionary's own `loadFile` reaches with `import` — the ones whose module\n// record Node then caches forever, and so the only ones a build could read\n// stale. The JSON5 family stays a path because there is nothing to gain:\n// those are read from disk on every pass either way, so a build can never\n// see one as it stood earlier in the process.\nexport async function configForBuild(\n item: ResolvedConfig,\n): Promise<Config | string> {\n const { config } = item\n\n if (typeof config !== 'string' || !isImportedConfig(config)) return config\n\n const loaded = await readConfigObject(item)\n\n // A config that could not be read falls back to the path, so the failure\n // stays Style Dictionary's to report — it knows more about why an import\n // failed than this does, a `.ts` config without type stripping especially.\n if (!loaded) return item.config\n\n // `loadFile` clones what it imports before handing it on, and passing an\n // object skips that. It matters more here than it does there: the module\n // record now outlives the build, and `extend` is called with\n // `mutateOriginal`. Cloning throws on a config carrying functions — an\n // inline transform — and Style Dictionary's own fallback in that case is\n // to use the original, so this one matches it.\n try {\n return structuredClone(loaded)\n } catch {\n return loaded\n }\n}\n\n// How to name a configuration in a message. A path is what a consumer\n// recognises; a configuration passed as an object or returned by a function has\n// no name, so it is identified by where it sits in the list rather than by a\n// stringified dump of itself.\nexport function describeConfig(item: ResolvedConfig, index: number): string {\n return item.file\n ? `The configuration ${item.file}`\n : `The configuration at position ${index + 1}`\n}\n\n// What a configuration item says, as an object. `report` is where a failure is\n// said, and leaving it out is what stops the two readers of this from saying\n// the same thing twice: a bad config has nowhere else to surface when the\n// watch list is being built, while a build falls back to handing Style\n// Dictionary the path and lets its message through instead.\nexport async function readConfigObject(\n item: ResolvedConfig,\n report?: Log,\n): Promise<Config | null> {\n if (typeof item.config !== 'string') return item.config\n\n try {\n // JSON5 rather than `JSON.parse`, because that is what Style Dictionary\n // reads these files with — it is a superset, so a plain `.json` config\n // parses identically and one carrying a comment stops being a config\n // the build understands and the watch list does not.\n const loaded: unknown = isImportedConfig(item.config)\n ? await importConfigModule(item.config)\n : JSON5.parse(fs.readFileSync(item.config, 'utf-8'))\n\n if (isConfig(loaded)) return loaded\n\n if (report) {\n report(\n `Config file did not resolve to a configuration object: ${item.config}`,\n 'error',\n )\n }\n } catch (err) {\n if (report) {\n report(\n `Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}`,\n 'error',\n )\n }\n }\n\n return null\n}\n\n// Resolve config file paths / objects\n//\n// Everything a plugin instance owns arrives as an argument, and none of it is\n// kept. `root` is read by the caller on each call because the host assigns it\n// after the factory has run, and `configContext` is a function rather than a\n// value for the same reason: it is asked only once a consumer's `config`\n// function is about to run.\nexport async function resolveConfigOption({\n config,\n configContext,\n discovery,\n log,\n root,\n}: {\n config: UnpluginStyleDictionaryOptions['config']\n configContext: () => StyleDictionaryConfigContext\n discovery: { announced: boolean }\n log: Log\n root: string\n}): Promise<ResolvedConfig[]> {\n let rawConfig = config\n\n // Checked ahead of the discovery below, and by identity rather than\n // truthiness: `false` is falsy, so the `!rawConfig` test that triggers\n // discovery would treat \"do not discover anything\" as \"go and look\".\n if (rawConfig === false) return []\n\n // If config is not defined, look for default configuration files\n if (!rawConfig) {\n const defaults = [\n 'sd.config.json',\n 'config.json',\n 'sd.config.js',\n 'sd.config.mjs',\n ]\n\n const rejected: string[] = []\n\n for (const file of defaults) {\n const fullPath = path.resolve(root, file)\n if (!fs.existsSync(fullPath)) continue\n\n // Read before adopting. For the two `.json` names this is a parse and\n // nothing more; for the two module names it is an import, and the\n // module has already run by the time there is anything to check —\n // which is what `config: false` exists for and why validation alone\n // does not cover them.\n const candidate = await readConfigObject({\n config: fullPath,\n file: fullPath,\n })\n\n if (!looksLikeConfig(candidate)) {\n rejected.push(file)\n continue\n }\n\n // Announced, because \"which configuration did it pick\" was not\n // answerable from the console at all, and discovery picks from four\n // generic names.\n if (!discovery.announced) {\n discovery.announced = true\n log(`Using the configuration it found at ${fullPath}`, 'info')\n }\n\n rawConfig = file\n break\n }\n\n // Said whether or not something usable turned up after them. A skipped\n // candidate is the interesting half of \"no configuration found\": the\n // file is right there, and the reason it was not used is not guessable.\n if (rejected.length > 0) {\n log(\n `Ignored ${rejected.join(', ')} in ${root}: nothing there declares platforms, source, include or tokens, so it does not look like a Style Dictionary configuration. Name it with the config option if it is one, or set config to false to stop looking.`,\n 'error',\n )\n }\n }\n\n if (!rawConfig) {\n log(\n 'No configuration specified and no default config file found. Style Dictionary will not compile.',\n 'error',\n )\n return []\n }\n\n // Evaluate function if provided\n if (typeof rawConfig === 'function') {\n rawConfig = await rawConfig(configContext())\n }\n\n const configs = Array.isArray(rawConfig) ? rawConfig : [rawConfig]\n\n return configs.map((conf) => {\n if (typeof conf === 'string') {\n const fullPath = path.resolve(root, conf)\n return { config: fullPath, file: fullPath }\n } else {\n return { config: conf }\n }\n })\n}\n\n// Imports a config module, re-evaluating it only when the file itself has\n// changed. The query string is what decides that, and it is not decoration:\n// Node's ESM cache is permanent and keyed on the specifier, so a config\n// imported without one is evaluated once and never read again — which is\n// how an edited `.mjs` config went on building the platform map the process\n// started with, for the rest of the session.\n//\n// `Date.now()` fixed that staleness and bought two problems. Every watcher\n// event registered another module record in a map nothing prunes, re-running\n// the config's own `registerFormat` side effects for a file nobody touched.\n// And its millisecond granularity meant an edit landing inside the same\n// millisecond as the previous import shared that import's key, and was\n// served the old module anyway. `mtimeMs` carries sub-millisecond\n// resolution and only moves when the file does.\nasync function importConfigModule(file: string): Promise<unknown> {\n let version: number\n try {\n version = fs.statSync(file).mtimeMs\n } catch {\n // A config that cannot be stat'd is about to fail its import too. The\n // old key is what keeps that failure the import's to report.\n version = Date.now()\n }\n\n // The dot goes, and that is not cosmetic. `mtimeMs` is fractional, so the\n // query it produces ends in something that reads as a file extension to\n // anything deriving a loader from the specifier without stripping the\n // query first — `sd.config.ts?t=1789565080284.6606` is then a `.6606`\n // file, and a TypeScript config gets parsed as JavaScript. Replacing the\n // one dot keeps every distinct mtime a distinct key.\n const key = String(version).replace('.', '_')\n\n // Sequential on purpose: a config module runs arbitrary code at import\n // time — `registerFormat` and friends — and Style Dictionary's registries\n // are global, so importing several at once would interleave those\n // registrations.\n return unwrapDefault(await import(`${pathToFileURL(file).href}?t=${key}`))\n}\n\n// 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// Whether a config path is one Style Dictionary imports rather than parses.\nfunction isImportedConfig(file: string): boolean {\n return IMPORTED_CONFIG_EXTENSIONS.some((extension) =>\n file.endsWith(extension),\n )\n}\n\n// Whether a discovered file looks like a Style Dictionary configuration at all.\n//\n// Only applied to a file the plugin went looking for, never to one a consumer\n// named: an explicit `config` is their choice and second-guessing it would\n// reject shapes Style Dictionary accepts and this does not know about.\n//\n// `config.json` is an extremely common name for something else entirely, and\n// the plugin used to adopt whatever it found under that name, add it to the\n// watch set, and report a successful compile over it.\nfunction looksLikeConfig(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false\n\n // The four keys any usable configuration has at least one of. `platforms`\n // alone is enough because a configuration can declare its tokens inline\n // under `tokens`, or read them through `source`/`include`.\n return ['include', 'platforms', 'source', 'tokens'].some(\n (key) => key in value,\n )\n}\n\n// 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"],"mappings":";;;;;;AA+CA,MAAM,6BAA6B;CAAC;CAAO;CAAQ;AAAK;AASxD,eAAsB,eACpB,MAC0B;CAC1B,MAAM,EAAE,WAAW;CAEnB,IAAI,OAAO,WAAW,YAAY,CAAC,iBAAiB,MAAM,GAAG,OAAO;CAEpE,MAAM,SAAS,MAAM,iBAAiB,IAAI;CAK1C,IAAI,CAAC,QAAQ,OAAO,KAAK;CAQzB,IAAI;EACF,OAAO,gBAAgB,MAAM;CAC/B,QAAQ;EACN,OAAO;CACT;AACF;AAMA,SAAgB,eAAe,MAAsB,OAAuB;CAC1E,OAAO,KAAK,OACR,qBAAqB,KAAK,SAC1B,iCAAiC,QAAQ;AAC/C;AAOA,eAAsB,iBACpB,MACA,QACwB;CACxB,IAAI,OAAO,KAAK,WAAW,UAAU,OAAO,KAAK;CAEjD,IAAI;EAKF,MAAM,SAAkB,iBAAiB,KAAK,MAAM,IAChD,MAAM,mBAAmB,KAAK,MAAM,IACpC,MAAM,MAAM,GAAG,aAAa,KAAK,QAAQ,OAAO,CAAC;EAErD,IAAI,SAAS,MAAM,GAAG,OAAO;EAE7B,IAAI,QACF,OACE,0DAA0D,KAAK,UAC/D,OACF;CAEJ,SAAS,KAAK;EACZ,IAAI,QACF,OACE,gCAAgC,KAAK,OAAO,WAAW,aAAa,GAAG,KACvE,OACF;CAEJ;CAEA,OAAO;AACT;AASA,eAAsB,oBAAoB,EACxC,QACA,eACA,WACA,KACA,QAO4B;CAC5B,IAAI,YAAY;CAKhB,IAAI,cAAc,OAAO,OAAO,CAAC;CAGjC,IAAI,CAAC,WAAW;EACd,MAAM,WAAW;GACf;GACA;GACA;GACA;EACF;EAEA,MAAM,WAAqB,CAAC;EAE5B,KAAK,MAAM,QAAQ,UAAU;GAC3B,MAAM,WAAW,KAAK,QAAQ,MAAM,IAAI;GACxC,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG;GAY9B,IAAI,CAAC,gBAAgB,MALG,iBAAiB;IACvC,QAAQ;IACR,MAAM;GACR,CAAC,CAE6B,GAAG;IAC/B,SAAS,KAAK,IAAI;IAClB;GACF;GAKA,IAAI,CAAC,UAAU,WAAW;IACxB,UAAU,YAAY;IACtB,IAAI,uCAAuC,YAAY,MAAM;GAC/D;GAEA,YAAY;GACZ;EACF;EAKA,IAAI,SAAS,SAAS,GACpB,IACE,WAAW,SAAS,KAAK,IAAI,EAAE,MAAM,KAAK,iNAC1C,OACF;CAEJ;CAEA,IAAI,CAAC,WAAW;EACd,IACE,mGACA,OACF;EACA,OAAO,CAAC;CACV;CAGA,IAAI,OAAO,cAAc,YACvB,YAAY,MAAM,UAAU,cAAc,CAAC;CAK7C,QAFgB,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,SAAS,EAAA,CAElD,KAAK,SAAS;EAC3B,IAAI,OAAO,SAAS,UAAU;GAC5B,MAAM,WAAW,KAAK,QAAQ,MAAM,IAAI;GACxC,OAAO;IAAE,QAAQ;IAAU,MAAM;GAAS;EAC5C,OACE,OAAO,EAAE,QAAQ,KAAK;CAE1B,CAAC;AACH;AAgBA,eAAe,mBAAmB,MAAgC;CAChE,IAAI;CACJ,IAAI;EACF,UAAU,GAAG,SAAS,IAAI,CAAC,CAAC;CAC9B,QAAQ;EAGN,UAAU,KAAK,IAAI;CACrB;CAQA,MAAM,MAAM,OAAO,OAAO,CAAC,CAAC,QAAQ,KAAK,GAAG;CAM5C,OAAO,cAAc,MAAM,OAAO,GAAG,cAAc,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AAC3E;AAOA,SAAS,SAAS,OAAiC;CACjD,OAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAGA,SAAS,iBAAiB,MAAuB;CAC/C,OAAO,2BAA2B,MAAM,cACtC,KAAK,SAAS,SAAS,CACzB;AACF;AAWA,SAAS,gBAAgB,OAAyB;CAChD,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CAKxD,OAAO;EAAC;EAAW;EAAa;EAAU;CAAQ,CAAC,CAAC,MACjD,QAAQ,OAAO,KAClB;AACF;AAKA,SAAS,cAAc,OAAyB;CAC9C,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,aAAa,QAC9D,MAAM,WAAW,QAClB;AACN"}
|
|
1
|
+
{"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["// Where a configuration comes from and what it says: the `config` option\n// resolved into a list, each item read as an object, and the form Style\n// Dictionary is handed to build from.\n//\n// Nothing here holds state. One module serves every plugin instance in the\n// process, so what an instance owns — its root, its logger, whether it has\n// announced a discovery yet — is handed in on each call.\n\nimport type { Config } from 'style-dictionary'\nimport type { DesignTokens } from 'style-dictionary/types'\n\nimport JSON5 from 'json5'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport StyleDictionary from 'style-dictionary'\n\nimport type {\n StyleDictionaryConfigContext,\n UnpluginStyleDictionaryOptions,\n} from './types.js'\n\nimport { errorMessage } from './errors.js'\n\n// The plugin's logger, narrowed to the two levels anything here reports at. It\n// is handed on as a function rather than as the host it writes to, so it goes\n// on reading whichever host has claimed the plugin's messages by the time it\n// is called.\nexport type Log = (message: string, type: 'error' | 'info') => void\n\n// A configuration as `resolveConfigOption` hands it on: either the object the\n// consumer passed or the path it was read from, plus the directory relative\n// paths inside it resolve against.\nexport interface ResolvedConfig {\n config: Config | string\n file?: string\n // The files the plugin's `watch` option names, expanded. A config module's\n // import is keyed on their mtimes beside its own, so a data file it reads\n // at load time — and that a consumer named in `watch` to trigger a rebuild\n // — re-evaluates it rather than only rebuilding with what it read before.\n watched?: string[]\n}\n\n// The config extensions Style Dictionary loads with `import` rather than by\n// parsing the file — the `case` list in its own `loadFile`. They are the only\n// ones Node's permanent module cache applies to, and so the only ones this\n// plugin has to read on the build's behalf.\n//\n// Everything else Style Dictionary parses as JSON5, including `.json`, and\n// this list is what makes the plugin split the same way. Reading the two\n// halves apart is what silently unwatched a whole family of configurations:\n// a `.json5` or `.jsonc` file went down the import branch and failed there\n// while the build succeeded, and a `.json` file carrying a comment or a\n// trailing comma failed strict `JSON.parse` for the same reason.\nconst IMPORTED_CONFIG_EXTENSIONS = ['.js', '.mjs', '.ts']\n\n// What `new StyleDictionary` is handed for an item: always an object, carrying\n// the plugin's own parser for token modules — see `withTokenModuleParser`.\n//\n// A path in the JS family is read here because those are exactly the\n// extensions Style Dictionary's own `loadFile` reaches with `import`, whose\n// module record Node then caches forever, so a build handed the path could\n// read the config stale. A JSON5-family path is read here too, so the parser\n// can ride along; Style Dictionary would have parsed the same file the same\n// way.\nexport async function configForBuild(\n item: ResolvedConfig,\n): Promise<Config | string> {\n const { config } = item\n\n if (typeof config !== 'string') return withTokenModuleParser(config)\n\n const loaded = await readConfigObject(item)\n\n // A config that could not be read falls back to the path, so the failure\n // stays Style Dictionary's to report — it knows more about why an import\n // failed than this does, a `.ts` config without type stripping especially.\n if (!loaded) return item.config\n\n // `loadFile` clones what it imports before handing it on, and passing an\n // object skips that. It matters more here than it does there: the module\n // record now outlives the build, and `extend` is called with\n // `mutateOriginal`. Cloning throws on a config carrying functions — an\n // inline transform — and Style Dictionary's own fallback in that case is\n // to use the original, so this one matches it.\n if (!isImportedConfig(config)) return withTokenModuleParser(loaded)\n\n try {\n return withTokenModuleParser(structuredClone(loaded))\n } catch {\n return withTokenModuleParser(loaded)\n }\n}\n\n// The name the plugin's token-module parser is registered and applied under.\nconst TOKEN_MODULE_PARSER = 'unplugin-style-dictionary/token-module'\n\n// Matches a `.js`, `.mjs` or `.ts` token source, unless another parser the\n// configuration applies matches it too. Style Dictionary runs every parser\n// whose pattern matches and keeps the last result, and a global parser comes\n// ahead of a configuration's own whatever the order in `parsers` — measured,\n// `['plugin-own', 'consumer-global']` ran in the order\n// `['consumer-global', 'plugin-own']`. So order cannot make the consumer's\n// win, and stepping aside is what does. A `RegExp` whose `Symbol.match` is\n// overridden, because `filePath.match(pattern)` is how Style Dictionary asks.\nclass TokenModulePattern extends RegExp {\n private readonly others: RegExp[]\n\n constructor(others: RegExp[]) {\n super(String.raw`\\.(?:js|mjs|ts)$`)\n this.others = others\n }\n\n override [Symbol.match](value: string): null | RegExpMatchArray {\n if (this.others.some((other) => value.match(other) !== null)) return null\n\n return super[Symbol.match](value)\n }\n}\n\n// How to name a configuration in a message. A path is what a consumer\n// recognises; a configuration passed as an object or returned by a function has\n// no name, so it is identified by where it sits in the list rather than by a\n// stringified dump of itself.\nexport function describeConfig(item: ResolvedConfig, index: number): string {\n return item.file\n ? `The configuration ${item.file}`\n : `The configuration at position ${index + 1}`\n}\n\n// What a configuration item says, as an object. `report` is where a failure is\n// said, and leaving it out is what stops the two readers of this from saying\n// the same thing twice: a bad config has nowhere else to surface when the\n// watch list is being built, while a build falls back to handing Style\n// Dictionary the path and lets its message through instead.\nexport async function readConfigObject(\n item: ResolvedConfig,\n report?: Log,\n): Promise<Config | null> {\n if (typeof item.config !== 'string') return item.config\n\n try {\n // JSON5 rather than `JSON.parse`, because that is what Style Dictionary\n // reads these files with — it is a superset, so a plain `.json` config\n // parses identically and one carrying a comment stops being a config\n // the build understands and the watch list does not.\n const loaded: unknown = isImportedConfig(item.config)\n ? await importConfigModule(item.config, item.watched)\n : JSON5.parse(fs.readFileSync(item.config, 'utf-8'))\n\n if (isConfig(loaded)) return loaded\n\n if (report) {\n report(\n `Config file did not resolve to a configuration object: ${item.config}`,\n 'error',\n )\n }\n } catch (err) {\n // Asked only once reading has failed, and of the file rather than the\n // error: a module config that imports something missing fails with the\n // same code as a config that is not there at all.\n if (report) {\n report(\n fs.existsSync(item.config)\n ? `Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}`\n : `Config file not found: ${item.config}`,\n 'error',\n )\n }\n }\n\n return null\n}\n\n// Resolve config file paths / objects\n//\n// Everything a plugin instance owns arrives as an argument, and none of it is\n// kept. `root` is read by the caller on each call because the host assigns it\n// after the factory has run, and `configContext` is a function rather than a\n// value for the same reason: it is asked only once a consumer's `config`\n// function is about to run.\nexport async function resolveConfigOption({\n config,\n configContext,\n discovery,\n log,\n root,\n}: {\n config: UnpluginStyleDictionaryOptions['config']\n configContext: () => StyleDictionaryConfigContext\n discovery: { announced: boolean }\n log: Log\n root: string\n}): Promise<ResolvedConfig[]> {\n let rawConfig = config\n\n // Checked ahead of the discovery below, and by identity rather than\n // truthiness: `false` is falsy, so the `!rawConfig` test that triggers\n // discovery would treat \"do not discover anything\" as \"go and look\".\n if (rawConfig === false) return []\n\n // If config is not defined, look for default configuration files\n if (!rawConfig) {\n const defaults = [\n 'sd.config.json',\n 'config.json',\n 'sd.config.js',\n 'sd.config.mjs',\n ]\n\n const rejected: string[] = []\n\n for (const file of defaults) {\n const fullPath = path.resolve(root, file)\n if (!fs.existsSync(fullPath)) continue\n\n // Read before adopting. For the two `.json` names this is a parse and\n // nothing more; for the two module names it is an import, and the\n // module has already run by the time there is anything to check —\n // which is what `config: false` exists for and why validation alone\n // does not cover them.\n const candidate = await readConfigObject({\n config: fullPath,\n file: fullPath,\n })\n\n if (!looksLikeConfig(candidate)) {\n rejected.push(file)\n continue\n }\n\n // Announced, because \"which configuration did it pick\" was not\n // answerable from the console at all, and discovery picks from four\n // generic names. A candidate skipped on the way is named here rather\n // than as an error: the build has a configuration, and the advice is\n // to name it, since `config: false` would stop discovery finding it.\n if (!discovery.announced) {\n discovery.announced = true\n log(\n rejected.length > 0\n ? `Using the configuration it found at ${fullPath}, after ignoring ${rejected.join(', ')}: nothing there declares platforms, source, include or tokens. Name ${file} with the config option to stop looking.`\n : `Using the configuration it found at ${fullPath}`,\n 'info',\n )\n }\n\n rawConfig = file\n break\n }\n\n // A skipped candidate is the interesting half of \"no configuration\n // found\": the file is right there, and the reason it was not used is not\n // guessable.\n if (!rawConfig && rejected.length > 0) {\n log(\n `Ignored ${rejected.join(', ')} in ${root}: nothing there declares platforms, source, include or tokens, so it does not look like a Style Dictionary configuration. Name it with the config option if it is one, or set config to false to stop looking.`,\n 'error',\n )\n }\n }\n\n if (!rawConfig) {\n log(\n 'No configuration specified and no default config file found. Style Dictionary will not compile.',\n 'error',\n )\n return []\n }\n\n // Evaluate function if provided\n if (typeof rawConfig === 'function') {\n rawConfig = await rawConfig(configContext())\n }\n\n const configs = Array.isArray(rawConfig) ? rawConfig : [rawConfig]\n\n return configs.map((conf) => {\n if (typeof conf === 'string') {\n const fullPath = path.resolve(root, conf)\n return { config: fullPath, file: fullPath }\n } else {\n return { config: conf }\n }\n })\n}\n\n// Imports a config module, re-evaluating it only when the file itself has\n// changed. The query string is what decides that, and it is not decoration:\n// Node's ESM cache is permanent and keyed on the specifier, so a config\n// imported without one is evaluated once and never read again — which is\n// how an edited `.mjs` config went on building the platform map the process\n// started with, for the rest of the session.\n//\n// `Date.now()` fixed that staleness and bought two problems. Every watcher\n// event registered another module record in a map nothing prunes, re-running\n// the config's own `registerFormat` side effects for a file nobody touched.\n// And its millisecond granularity meant an edit landing inside the same\n// millisecond as the previous import shared that import's key, and was\n// served the old module anyway. `mtimeMs` carries sub-millisecond\n// resolution and only moves when the file does.\n//\n// Keyed on the files the plugin's `watch` option names as well: a config that\n// reads a data file at load time went on building what that file said at\n// startup. A module the config itself imports is out of reach — a new query\n// on the config re-evaluates the config, not what it imports — so naming one\n// in `watch` buys a rebuild and not a re-read.\nasync function importConfigModule(\n file: string,\n watched: string[] = [],\n): Promise<unknown> {\n const key = versionKey([file, ...watched])\n\n // Sequential on purpose: a config module runs arbitrary code at import\n // time — `registerFormat` and friends — and Style Dictionary's registries\n // are global, so importing several at once would interleave those\n // registrations.\n return unwrapDefault(await import(`${pathToFileURL(file).href}?t=${key}`))\n}\n\n// A token module, read the way `importConfigModule` reads a config: under a\n// query keyed on its mtime, and cloned as Style Dictionary's own `loadFile`\n// clones it. Style Dictionary reads one with a bare `import`, and Node's\n// module cache keeps that first evaluation for the life of the process — so\n// under a long-lived watcher an edited `.mjs` token file rebuilt, reported\n// success, and wrote the values the process started with.\nasync function importTokenModule(\n filePath: string | undefined,\n): Promise<DesignTokens> {\n if (filePath === undefined) return {}\n\n const module: unknown = await import(\n `${pathToFileURL(filePath).href}?t=${versionKey([filePath])}`\n )\n\n // `.default`, exactly as `loadFile` reads it: a module without one carries\n // no tokens.\n const tokens: unknown =\n typeof module === 'object' && module !== null && 'default' in module\n ? module.default\n : undefined\n if (!isTokens(tokens)) return {}\n\n try {\n return structuredClone(tokens)\n } catch {\n return tokens\n }\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// Whether a config path is one Style Dictionary imports rather than parses.\nfunction isImportedConfig(file: string): boolean {\n return IMPORTED_CONFIG_EXTENSIONS.some((extension) =>\n file.endsWith(extension),\n )\n}\n\nfunction isTokens(value: unknown): value is DesignTokens {\n return typeof value === 'object' && value !== null\n}\n\n// Whether a discovered file looks like a Style Dictionary configuration at all.\n//\n// Only applied to a file the plugin went looking for, never to one a consumer\n// named: an explicit `config` is their choice and second-guessing it would\n// reject shapes Style Dictionary accepts and this does not know about.\n//\n// `config.json` is an extremely common name for something else entirely, and\n// the plugin used to adopt whatever it found under that name, add it to the\n// watch set, and report a successful compile over it.\nfunction looksLikeConfig(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false\n\n // The four keys any usable configuration has at least one of. `platforms`\n // alone is enough because a configuration can declare its tokens inline\n // under `tokens`, or read them through `source`/`include`.\n return ['include', 'platforms', 'source', 'tokens'].some(\n (key) => key in value,\n )\n}\n\n// 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// The cache key for a module read under `files`: the newest of their mtimes.\n//\n// The dot goes, and that is not cosmetic. `mtimeMs` is fractional, so the\n// query it produces ends in something that reads as a file extension to\n// anything deriving a loader from the specifier without stripping the query\n// first — `sd.config.ts?t=1789565080284.6606` is then a `.6606` file, and a\n// TypeScript config gets parsed as JavaScript. Replacing the one dot keeps\n// every distinct mtime a distinct key.\n//\n// A file that cannot be stat'd is about to fail its import too, and falls\n// back to the time now, which keeps that failure the import's to report.\nfunction versionKey(files: string[]): string {\n let newest = -1\n for (const file of files) {\n try {\n newest = Math.max(newest, fs.statSync(file).mtimeMs)\n } catch {\n // Named, and not there: a data file created later is fine to miss.\n }\n }\n\n return String(newest < 0 ? Date.now() : newest).replace('.', '_')\n}\n\n// `config` with the plugin's token-module parser registered in `hooks.parsers`\n// and named in `parsers` — Style Dictionary applies a parser only when both\n// are true. A copy, so the consumer's object is not changed.\nfunction withTokenModuleParser(config: Config): Config {\n const applied = config.parsers ?? []\n\n // The patterns of every other parser the configuration applies, from its\n // own hooks or the global registry, since either can be named.\n // Typed partial, because a name nothing registered indexes to nothing.\n const own: Partial<Record<string, { pattern: RegExp }>> =\n config.hooks?.parsers ?? {}\n const registered: Partial<Record<string, { pattern: RegExp }>> =\n StyleDictionary.hooks.parsers\n const others = applied.flatMap((name) => {\n const parser = own[name] ?? registered[name]\n return parser ? [parser.pattern] : []\n })\n\n return {\n ...config,\n hooks: {\n ...config.hooks,\n parsers: {\n ...config.hooks?.parsers,\n [TOKEN_MODULE_PARSER]: {\n parser: async ({ filePath }) => importTokenModule(filePath),\n pattern: new TokenModulePattern(others),\n },\n },\n },\n parsers: [...applied, TOKEN_MODULE_PARSER],\n }\n}\n"],"mappings":"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"}
|