@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/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { UnpluginFactory } from 'unplugin'\nimport type { ViteDevServer } from 'vite'\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { createUnplugin } from 'unplugin'\n\nimport type { PluginInstance } from './compile.js'\nimport type { ResolvedConfig } from './config.js'\nimport type { WatchPatterns } from './patterns.js'\nimport type {\n StyleDictionaryConfigContext,\n UnpluginStyleDictionaryOptions,\n} from './types.js'\nimport type { DestinationRecord } from './up-to-date.js'\n\nimport { colourAllowed, paint } from './colour.js'\nimport { runBuilds } from './compile.js'\nimport { resolveConfigOption } from './config.js'\nimport { asError, errorMessage } from './errors.js'\nimport { expandPatterns, watchPatternsOf } from './patterns.js'\nimport { createScheduler } from './scheduler.js'\nimport { isWatchedSource } from './watch-filter.js'\n\nexport type * from './types.js'\n\n// Where the plugin's own lines go when a host offers somewhere better than the\n// console: Vite's `config.logger`, rollup's and rolldown's plugin context, or\n// webpack's `compilation`.\n//\n// **There is no `error` channel that merely reports.** Rollup's `this.error`\n// aborts the bundle — measured: a `buildStart` calling it ends the run with\n// `THREW: [plugin err-probe] fatal?` — so routing a failure report through it\n// would stop every build that reported one and silently override `failOnError`,\n// whose entire job is deciding that. A failure is therefore reported on the\n// host's warning channel, and whether the build stops stays `failOnError`'s\n// decision alone.\ninterface HostMessenger {\n error: (message: string) => void\n\n // Optional because not every host has somewhere for a progress line to go.\n // webpack's `stats` carries warnings and errors and nothing else, and\n // `Compiling design tokens...` is neither — so there it stays on the\n // console rather than being dressed up as a warning.\n info?: (message: string) => void\n}\n\n// Appends `negations` to a Vite build watcher's `chokidar.ignored`, after\n// whatever the consumer put there, exactly as the dev server's list is amended.\n//\n// Reached structurally, because the types disagree about where it lives. Vite 6\n// and 7 type `build.watch` as rollup's options, which carry `chokidar`; Vite 8\n// types it as rolldown's, which do not, and this package compiles against 8.\n// On 8 the write reaches nothing — Vite forwards only its polling settings to\n// rolldown's watcher — and is harmless there.\nfunction appendChokidarIgnored(watch: object, negations: string[]): void {\n if (negations.length === 0) return\n\n const chokidar: unknown = Reflect.get(watch, 'chokidar')\n const options: object =\n typeof chokidar === 'object' && chokidar !== null ? chokidar : {}\n const existing: unknown = Reflect.get(options, 'ignored')\n const previous: unknown[] = Array.isArray(existing)\n ? existing\n : existing === undefined\n ? []\n : [existing]\n\n Reflect.set(watch, 'chokidar', {\n ...options,\n ignored: [...previous, ...negations],\n })\n}\n\n// Best-effort cleanup of a temporary file whose write or rename failed. The\n// original failure is what the caller reports, so nothing here may throw.\nfunction discardTemporaryFile(temporary: string): void {\n try {\n fs.rmSync(temporary, { force: true })\n } catch {\n // Ignore: a leftover temporary file is not worth masking the real error.\n }\n}\n\n// A host's message channel, narrowed by a predicate rather than asserted: what\n// a plugin context carries under `warn` is the host's business, and a cast\n// would only claim it is callable.\nfunction isMessageChannel(value: unknown): value is (message: string) => void {\n return typeof value === 'function'\n}\n\n// Vite builds its dev-server watcher with a fixed ignore list — `**/.git/**`,\n// `**/node_modules/**`, `**/test-results/**` and the cache directory — and\n// spreads the consumer's own `server.watch.ignored` entries in *after* them.\n// Entries are appended, never subtracted, so `server.watcher.add()` cannot\n// reach a path an earlier entry already covers.\n//\n// That makes a token package resolved through `node_modules` — the shape of\n// every workspace, `app/node_modules/@acme/tokens` symlinked to\n// `packages/tokens` — build correctly once and then never rebuild, with\n// nothing said about it. Measured on Vite 6.4.3, 7.3.6 and 8.3.0: zero watcher\n// events for an edit, while a token file outside the root but outside\n// `node_modules` rebuilt in the same run.\n//\n// A negation naming the file exactly is what un-ignores it, and is deliberately\n// the narrowest form that works. `!**/node_modules/**` would restore the whole\n// dependency tree to the watcher.\nfunction nodeModulesNegations(paths: string[]): string[] {\n const negations = new Set<string>()\n\n for (const file of paths) {\n const normalised = file.replace(/\\\\/g, '/')\n if (normalised.includes('/node_modules/')) negations.add(`!${normalised}`)\n }\n\n return Array.from(negations)\n}\n\n// The directories holding token files that resolve through `node_modules`.\n//\n// Vite's ignore list cannot be argued with on Windows. The negation below is\n// honoured on Linux and macOS, and there a token inside `node_modules` rebuilds\n// through the dev-server watcher like any other. On Windows it is not, and no\n// spelling of the negation changes that — measured on a `windows-latest`\n// runner: the file path, every ancestor directory, and the package subtree as\n// a globstar all leave the edit reaching no rebuild, while the same fixture\n// outside `node_modules` rebuilds. `server.watcher.add()` does not reach it\n// either, which is the same limit AGENTS.md already records for a path an\n// earlier ignore entry covers.\n//\n// A symlink is *not* what distinguishes them, which is worth stating because it\n// is the obvious suspect: a real directory inside `node_modules` fails exactly\n// as the symlinked one does, and a symlink outside it succeeds.\n//\n// So these directories get a watcher of the plugin's own, which Vite's ignore\n// list has no say over. It runs on every platform rather than behind a\n// `process.platform` check: one path that is exercised everywhere beats a\n// Windows-only branch that nothing else executes, and the scheduler already\n// collapses the duplicate trigger this produces where the negation also works.\nfunction nodeModulesWatchDirectories(paths: string[]): string[] {\n const directories = new Set<string>()\n\n for (const file of paths) {\n const normalised = file.replace(/\\\\/g, '/')\n if (!normalised.includes('/node_modules/')) continue\n\n // The directory rather than the file: `fs.watch` on a file stops reporting\n // once an editor replaces it by rename, which is what an atomic save does.\n directories.add(path.dirname(normalised))\n }\n\n return Array.from(directories)\n}\n\n// Style Dictionary writes every generated file with a plain `writeFile` on the\n// volume it was handed, which truncates the destination and then streams the\n// new contents into it. Anything reading that file inside the window sees a\n// partial file: a consuming test run whose tokens are rebuilt mid-suite, or a\n// dev-server request landing on a rebuild, gets a truncated module and fails\n// to parse it. Writing a sibling temporary file and renaming it over the\n// destination closes the window — `rename` is atomic within a filesystem, so a\n// concurrent reader sees either the whole old file or the whole new one.\n\n// Temporary path for an atomic write of `destination`.\n//\n// It has to be a sibling of the destination, because `rename` is only atomic\n// within one filesystem and the system temp directory is often a different\n// mount. The final extension is dropped rather than kept, so the temporary\n// file cannot match a pattern written for the generated file's own extension.\n// That was load-bearing while `matchesWatchedFile` tested its globs\n// unanchored, where a leftover `vars.css.tmp` matched a `*.css` watch; it is\n// belt-and-braces now that the matcher anchors and, like the globber Style\n// Dictionary reads sources with, does not match the leading dot this name\n// already starts with. Both stay, because a temporary file only outlives its\n// rename when a write failed, and hiding one costs a string. The pid and\n// counter make the name unique, so two writes of the same destination —\n// parallel platforms in one build, or two builds overlapping — never share a\n// temporary file.\nlet temporaryFileCounter = 0\n\n// Whether the freshly rendered `temporary` holds exactly what `destination`\n// already holds. A rebuild whose inputs did not change renders byte-identical\n// output, and renaming that over the destination is a filesystem event the\n// host bundler reacts to — which is the whole of the rebuild loop, since\n// consuming code imports the generated file and every regenerate is therefore\n// a module-graph change. Comparing the two files rather than the `data`\n// argument keeps this indifferent to whether the caller passed a string, a\n// buffer or a stream, and to the encoding it passed with it.\n//\n// A destination that cannot be read is not identical, which covers the\n// ordinary case of it not existing yet.\nasync function rendersWhatIsAlreadyThere(\n temporary: string,\n destination: string,\n): Promise<boolean> {\n try {\n const [existing, rendered] = await Promise.all([\n fs.promises.readFile(destination),\n fs.promises.readFile(temporary),\n ])\n\n return existing.equals(rendered)\n } catch {\n return false\n }\n}\n\nfunction rendersWhatIsAlreadyThereSync(\n temporary: string,\n destination: string,\n): boolean {\n try {\n return fs.readFileSync(destination).equals(fs.readFileSync(temporary))\n } catch {\n return false\n }\n}\n\nfunction temporaryPathFor(destination: string): string {\n const extension = path.extname(destination)\n\n return path.join(\n path.dirname(destination),\n `.${path.basename(destination, extension)}.${process.pid}.${temporaryFileCounter++}.tmp`,\n )\n}\n\n// Windows refuses a rename over a destination another process holds open, and\n// that is exactly the case the atomic write exists to serve: measured on a\n// `windows-latest` runner, the suite's own concurrent-reader case fails with\n// `EPERM: operation not permitted, rename`. So the feature inverts — the\n// compile fails rather than the read being protected.\n//\n// This **refutes** the reasoning that put the case in doubt. It was argued that\n// libuv opens files with `FILE_SHARE_DELETE`, so a concurrent reader would most\n// likely not block the rename. It blocks it.\n//\n// The blocking handle is transient — a reader, an indexer, a virus scanner —\n// so a short bounded backoff clears it. The bound matters as much as the retry:\n// a rename that genuinely cannot succeed has to fail rather than hang a dev\n// server, and the existing failure path already reports and lets `failOnError`\n// decide.\n//\n// Unreachable on Linux and macOS, where a rename over an open file succeeds.\nconst RENAME_RETRY_CODES = new Set(['EBUSY', 'EPERM'])\n\n// Doubling rather than a fixed interval, so the common case — a handle already\n// gone by the first retry — costs a millisecond rather than the whole budget.\n//\n// **The total is sized by what a dev server can tolerate waiting, not by how\n// long a handle usually persists.** An earlier version stopped at about 255ms,\n// which clears a transient hold and is not the situation that matters: a\n// consumer polling the generated file holds it for a large fraction of wall\n// time, and then no budget wins every race. What that produced was a rebuild\n// that failed roughly once in a hundred renames — which the suite's own\n// concurrent-reader case turns into a failure about once a run, because it\n// performs twenty of them. Intermittent, and on a platform where the whole\n// point of the atomic write is that a reader never sees a partial file.\n//\n// About two seconds is therefore the bound. A rebuild that takes a second is\n// something a dev server absorbs; one that fails is not. `write-file-atomic`\n// and `graceful-fs` both take this approach, the latter retrying for up to a\n// minute, so this is still the conservative end.\n//\n// The bound stays a bound: a rename that genuinely cannot succeed has to fail\n// rather than hang, and the unguarded attempt after the loop is what makes it.\nconst RENAME_RETRY_DELAYS_MS = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]\n\n// **The synchronous path gets a shorter budget, and the asymmetry is the\n// point.** `renameWithRetry` waits on a timer, so the event loop keeps serving\n// while it does; `renameWithRetrySync` waits on `Atomics.wait`, which blocks\n// everything — a dev server holding its main thread for two seconds is worse\n// than the failed rebuild the wait is trying to avoid.\n//\n// It is reached only through a Style Dictionary custom action's own\n// `vol.writeFileSync`, which is rare, and it keeps roughly the budget the async\n// path had before this change.\nconst RENAME_RETRY_DELAYS_SYNC_MS = [1, 2, 4, 8, 16, 32, 64, 128]\n\nfunction isRetryableRenameError(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n typeof error.code === 'string' &&\n RENAME_RETRY_CODES.has(error.code)\n )\n}\n\n// `Atomics.wait` rather than a spin on `Date.now()`, because the sync path has\n// no event loop to yield to and a busy loop would hold the CPU for the whole\n// backoff — on the one platform where the handle it is waiting for belongs to\n// another process.\nconst sleepSync = (ms: number): void => {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)\n}\n\nasync function renameWithRetry(\n temporary: string,\n destination: string,\n): Promise<void> {\n for (const delay of RENAME_RETRY_DELAYS_MS) {\n try {\n await fs.promises.rename(temporary, destination)\n return\n } catch (err) {\n if (!isRetryableRenameError(err)) throw err\n await new Promise((resolve) => setTimeout(resolve, delay))\n }\n }\n\n // The last attempt is deliberately outside the loop and unguarded: the bound\n // is a bound, so whatever it throws here is what the caller sees.\n await fs.promises.rename(temporary, destination)\n}\n\nfunction renameWithRetrySync(temporary: string, destination: string): void {\n for (const delay of RENAME_RETRY_DELAYS_SYNC_MS) {\n try {\n fs.renameSync(temporary, destination)\n return\n } catch (err) {\n if (!isRetryableRenameError(err)) throw err\n sleepSync(delay)\n }\n }\n\n fs.renameSync(temporary, destination)\n}\n\nconst writeFileAtomic: typeof fs.promises.writeFile = async (\n file,\n data,\n options,\n) => {\n // A file handle or descriptor is already-open state that a rename cannot\n // stand in for, so only a path is written atomically.\n if (typeof file !== 'string') {\n return fs.promises.writeFile(file, data, options)\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n await fs.promises.writeFile(temporary, data, options)\n\n // The check sits in front of the rename rather than in place of it: the\n // temporary file is still written, so a destination that does need\n // replacing is still replaced in one atomic step and a concurrent reader\n // still never sees a partial file.\n if (await rendersWhatIsAlreadyThere(temporary, file)) {\n discardTemporaryFile(temporary)\n return\n }\n\n await renameWithRetry(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\nconst writeFileSyncAtomic: typeof fs.writeFileSync = (file, data, options) => {\n if (typeof file !== 'string') {\n fs.writeFileSync(file, data, options)\n return\n }\n\n const temporary = temporaryPathFor(file)\n\n try {\n fs.writeFileSync(temporary, data, options)\n\n if (rendersWhatIsAlreadyThereSync(temporary, file)) {\n discardTemporaryFile(temporary)\n return\n }\n\n renameWithRetrySync(temporary, file)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\n// `node:fs` with both write entry points swapped for their atomic\n// equivalents, handed to Style Dictionary as the volume it builds through.\n// Everything else — reads, `mkdir`, `access`, the `promises` namespace — is\n// inherited from `node:fs` unchanged, so only the moment a file becomes\n// visible to readers changes. Custom actions receive this volume too, so\n// whatever they emit is written the same way.\n//\n// It is assigned onto the instance rather than passed as the `volume`\n// constructor option on purpose: that option marks the volume as a custom\n// filesystem shim, which switches Style Dictionary's path resolution off for\n// every read as well.\n// `Object.create` is declared as returning `any`, so pinning the result to\n// `typeof fs` is a claim no type guard can replace. The prototype link is the\n// whole point — see the note above — so rebuilding this with a spread, which\n// copies own properties and drops the chain, is not a substitute.\n/* oxlint-disable typescript/no-unsafe-type-assertion */\nconst atomicVolume = Object.create(fs, {\n promises: {\n value: Object.create(fs.promises, {\n writeFile: { value: writeFileAtomic },\n }) as typeof fs.promises,\n },\n writeFileSync: { value: writeFileSyncAtomic },\n}) as typeof fs\n/* oxlint-enable typescript/no-unsafe-type-assertion */\n\n// Which configuration last wrote each destination, as the fingerprint of that\n// configuration keyed by the file. It is what lets a configuration given as an\n// object or a function be skipped at all: such a configuration has no file to\n// stat, so an edit to it inside `vite.config.ts` moves no mtime and the\n// filesystem cannot tell the two apart. Having built it here, the plugin can —\n// the fingerprint changes with the configuration.\n//\n// Beside the fingerprint sits the newest source that compile saw before it read\n// anything. That, rather than the output's own mtime, is what the next check\n// measures \"changed since\" against, so a save landing mid-compile is not\n// mistaken for one the compile read — see `isUpToDate`.\n//\n// Keyed by destination rather than kept as the set of everything ever built,\n// because the question is what the file on disk holds now. A set answered\n// \"was this built at some point\", which an edit that was then undone, or a\n// build that threw halfway through its platforms, both answer wrongly: the\n// file holds the edit either way. It also grew with every distinct\n// configuration, where this is bounded by the number of destinations.\n//\n// Module scope rather than the factory's, for the same reason `compilesInFlight`\n// below is: the instances that would otherwise repeat the work are different\n// instances, so per-instance state cannot see them. A `vitest run` stands up\n// several, and a function configuration — the form the README recommends for\n// registering custom formats — would be the one form that never skipped. Every\n// compile forgets the files it is about to write, whether or not `cache` is on:\n// an instance that rewrote a file without that would leave another instance\n// vouching for output no longer its own. Only a compile with `cache` on records\n// them again, since only it has read the configuration a fingerprint is taken\n// from.\n//\n// The fingerprint carries the root, so two projects in one process never share\n// one. A configuration given as a path needs no fingerprint to be skipped: its\n// own file is one of the sources, so an edit to it is visible across processes\n// as well as within one.\n//\n// It outlives the process through a copy persisted beside each project's other\n// build caches, loaded the first time a compile runs for that root. Without it\n// a process's first compile could only compare the sources against the output,\n// and after one edit left any destination byte-identical — so its mtime stayed\n// behind — that comparison never skipped again. Where nothing is recorded, in\n// memory or on disk, it is still what the check falls back on.\nconst destinationRecords = new Map<string, DestinationRecord>()\n\n// The file each root's records persist to, once this process has loaded it.\n// Being in here is what says the load has happened.\nconst persistedRecordFiles = new Map<string, string>()\n\n// A compile that is running right now, keyed by `buildKey`, so bundler\n// instances in one process wait on each other rather than each starting their\n// own.\n//\n// Generated token files are a side effect on the filesystem, not per-bundler\n// output, and one process routinely holds several instances of this plugin. A\n// single `vitest run` on a project with two test projects and browser mode\n// stands up five Vite servers — the root one, one per project, and one more\n// per project once its HTTP server listens — and every one of them runs\n// `buildStart`. `hasCompiled` cannot see any of that: it is closure state\n// inside the factory, so each instance has its own and each compiles.\n//\n// Module scope is the only place a shared answer can live, since the\n// instances know nothing about each other. It stays a claim about identical\n// work, never about identity: the key carries the root and the resolved\n// configurations, so one script building two packages shares nothing.\nconst compilesInFlight = new Map<string, Promise<void>>()\n\n// The slice of a webpack-shaped compiler this plugin touches, named rather\n// than imported. webpack and rspack each ship their own `Compiler` type, and\n// neither is assignable to the other, so a hook written against one cannot be\n// handed to the other's key. Both satisfy this structurally, which is what\n// makes `adoptCompiler` one function instead of two copies drifting apart.\ninterface BundlerCompiler {\n hooks: {\n beforeCompile: {\n tapPromise: (name: string, handler: () => Promise<void>) => void\n }\n compilation: {\n tap: (name: string, handler: (compilation: Compilation) => void) => void\n }\n done: { tap: (name: string, handler: () => void) => void }\n failed: { tap: (name: string, handler: () => void) => void }\n }\n options: { context?: string | undefined; mode?: string | undefined }\n watchMode: boolean\n}\n\n// Only the one array a report is pushed onto. webpack types it `WebpackError[]`\n// and rspack `Error[]`; both are arrays of something extending `Error`, so a\n// plain one is what this pushes on either.\ninterface Compilation {\n warnings: Error[]\n}\n\n// A stable identity for a set of resolved configurations, or `null` for one\n// that cannot have a stable identity at all.\n//\n// Functions are serialised by source rather than dropped, because a `format`\n// or `transform` written inline is exactly what distinguishes two otherwise\n// identical configurations — and `JSON.stringify` omits a function outright,\n// which would make two different builds look like one.\nfunction buildKey(root: string, resolved: ResolvedConfig[]): null | string {\n try {\n return JSON.stringify(\n [root, resolved.map((item) => item.file ?? item.config)],\n (_key, value: unknown) =>\n typeof value === 'function' ? `[fn]${String(value)}` : value,\n )\n } catch {\n // A configuration that will not serialise — a circular reference, a\n // BigInt — takes no shared identity rather than a wrong one, and compiles\n // exactly as it did before.\n return null\n }\n}\n\n// Not exported. It cannot be called in the form a reader would guess —\n// unplugin types the factory as `(options, meta)`, and `meta` is the\n// bundler-identifying `UnpluginContextMeta` a consumer would have to build by\n// hand — so publishing it offered a name that answered nothing. What a\n// consumer imports is the default export of the entry for their bundler.\nconst unpluginFactory: UnpluginFactory<\n undefined | UnpluginStyleDictionaryOptions,\n false\n> = (options = {}, meta) => {\n // webpack and rspack are the targets whose `buildStart` does not run before\n // the module graph is resolved: unplugin taps it on `make`, an\n // `AsyncParallelHook` that `EntryPlugin` taps too. The `webpack` and\n // `rspack` keys below compile on `beforeCompile` instead, which both await\n // before the compilation exists. rspack reimplements webpack's plugin API,\n // so everything this plugin does with a compiler is the same on either —\n // but unplugin dispatches them by separate keys, so the flag names both.\n const isWebpack = meta.framework === 'webpack' || meta.framework === 'rspack'\n const {\n cache = true,\n errorOverlay = true,\n failOnError = 'build',\n logLevel,\n onBuildEnd,\n onBuildError,\n onBuildStart,\n platforms: platformsOption,\n report = true,\n root: rootOption,\n silent = false,\n } = options\n\n // `silent` predates `logLevel` and names its quietest level, so it is read\n // as one. `logLevel` wins when a consumer sets both.\n const level = logLevel ?? (silent ? 'silent' : undefined)\n\n // Whether the plugin keeps its own progress lines and size table to itself.\n // A failure is reported at every level, which is why this gate is not on the\n // error branch below.\n const quiet = level === 'silent' || level === 'warn'\n\n // What Style Dictionary is told, if anything. `undefined` is the point of\n // this: it leaves whatever the consumer's own `log.verbosity` asked for\n // standing, where the plugin used to overwrite it on every build. Style\n // Dictionary has three levels to this option's four, so `'warn'` and\n // `'info'` both map to its default — they differ in what the plugin itself\n // says, not in what Style Dictionary does.\n const verbosity =\n level === undefined\n ? undefined\n : level === 'verbose'\n ? 'verbose'\n : level === 'silent'\n ? 'silent'\n : 'default'\n\n // What the host is doing, for the function form of `config`. Populated where\n // each host knows the answer and read when that function is called — the\n // same shape as `root` and the message host above, and for the same reason:\n // `resolveConfigs` is reached from every hook that builds or watches, and\n // threading a context parameter through each of them would make every caller\n // restate what only the host can say.\n let hostCommand: 'build' | 'serve' = 'build'\n let hostMode: string | undefined\n let isWatching = false\n\n // `mode` is derived rather than invented where a host has no notion of one.\n // rollup and rolldown report nothing, and following `command` is the answer\n // Vite itself would give: its default mode is `development` serving and\n // `production` building.\n const configContext = (): StyleDictionaryConfigContext => ({\n command: hostCommand,\n mode: hostMode ?? (hostCommand === 'serve' ? 'development' : 'production'),\n watch: isWatching,\n })\n\n // Whether the host will keep rebuilding, as the plugin context reports it.\n // Read from `meta.watchMode`, which rollup, rolldown and Vite all carry and\n // webpack does not — there it comes off the compiler instead.\n const adoptWatchMode = (context: object): void => {\n const hookMeta: unknown = 'meta' in context ? context.meta : undefined\n if (typeof hookMeta !== 'object' || hookMeta === null) return\n\n const watching: unknown =\n 'watchMode' in hookMeta ? hookMeta.watchMode : undefined\n if (typeof watching === 'boolean') isWatching = watching\n }\n\n // Where a relative `config` path is looked up. The host sets it below\n // unless the consumer named one, which is why an explicit option wins: a\n // layout the host cannot describe is exactly what it is for.\n let root = rootOption\n ? path.resolve(process.cwd(), rootOption)\n : process.cwd()\n\n // Every absolute destination the last completed build wrote, spelled with\n // forward slashes so it compares against a normalised watcher path. This is\n // the half of the rebuild-loop guard that pattern matching cannot supply:\n // output written under a watched directory matches the source glob that\n // produced it, so without subtracting this set a supported layout rebuilds\n // on its own writes for as long as the dev server runs.\n const generatedDestinations = new Set<string>()\n\n // The patterns the last `getWatchTargets` derived. `watchChange` tests a\n // changed path against these before it resolves anything, so a file the\n // plugin does not care about costs one glob match instead of a full config\n // resolution — which, when `config` is a function, is the consumer's own\n // code, and the place the README tells them to register custom formats.\n //\n // It is safe to filter on a list that may be one build out of date because\n // the list always contains the config files themselves: an edit that adds a\n // source matches as a config change, which re-resolves and re-derives. The\n // one thing it cannot see is a `config` function that starts returning\n // different sources with no file changing at all, and that was never\n // observable without a rebuild to observe it in.\n let cachedPatterns: undefined | WatchPatterns\n\n // Whether `watchChange` has fired since the last `buildStart`, and whether\n // anything has been compiled yet. Rollup, rolldown and webpack all run\n // `watchChange` for every changed file and only then re-enter `buildStart`\n // — unplugin's webpack adapter awaits both in one `make` tap — so a flag\n // raised in the first is still standing in the second, and is what tells it\n // this is a watch rebuild rather than the first build of the process. Vite's\n // dev server runs `watchChange` too and never re-enters, which is why\n // `buildStart` ignores the flag there.\n let watchRebuild = false\n let hasCompiled = false\n\n // Whether the host has shut its watcher down. `await watcher.close()` is not\n // a promise that no build is in flight: rollup's `Watcher.close` clears the\n // pending build timeout, closes each task's file watcher and emits `close`,\n // and never awaits `run` — while `Task.run` checks `closed` only *after*\n // `rollupInternal` has resolved. So a build that has already entered\n // `rollupInternal` runs its `buildStart` hooks through to completion after\n // `close()` has returned to its caller, against a project that may be half\n // torn down by then. Measured on rollup 4.63.3 with no plugin of ours: a\n // `buildStart` reading a file 300ms after `close()` resolved gets ENOENT.\n //\n // `closeWatcher` is what makes that answerable. It runs synchronously inside\n // `close()`, and so before the in-flight hook resumes, which is the whole\n // reason a flag set there is worth setting. What it must not be is\n // `closeBundle`: that fires once per bundle — every `BUNDLE_END` a consumer\n // calls `result.close()` on — and would read as a shutdown on every rebuild.\n let hostClosed = false\n\n // Decided once, when the plugin is constructed, and held for its life. The\n // two streams are asked separately because they are redirected separately —\n // `build 2>err.log` leaves stdout a terminal and stderr a file.\n const stdoutColour = colourAllowed(process.stdout)\n const stderrColour = colourAllowed(process.stderr)\n\n // Where a message goes once a host has offered somewhere better than the\n // console. Set by `configResolved` under Vite, by the build hooks under\n // rollup and rolldown, and by the `webpack` block; left undefined when no\n // host has claimed it, which is every unit test binding its own context.\n let host: HostMessenger | undefined\n\n // Adopts a plugin context as the message host, if it has the channels — the\n // unit tests bind a context carrying `addWatchFile` and nothing else, and a\n // hook calling `this.warn` against that throws in a way that reads as a\n // plugin bug rather than as a missing stub.\n //\n // Only when nothing has claimed the host yet. Under Vite `configResolved`\n // has already installed the dev server's own logger, and `buildStart` runs\n // after it with a rollup-shaped context that would otherwise replace it.\n const adoptHost = (context: object): void => {\n if (host) return\n\n const warn: unknown = 'warn' in context ? context.warn : undefined\n if (!isMessageChannel(warn)) return\n\n const info: unknown = 'info' in context ? context.info : undefined\n\n host = {\n // `warn`, never `error`. Rollup's `this.error` aborts the bundle, so\n // reporting through it would stop every build that reported anything and\n // take the decision `failOnError` exists to make.\n error: (message) => {\n warn.call(context, message)\n },\n info: isMessageChannel(info)\n ? (message) => {\n info.call(context, message)\n }\n : undefined,\n }\n }\n\n // Helper to log at the configured level\n const log = (\n message: string,\n type: 'error' | 'info' | 'success' = 'info',\n ) => {\n const prefix = '[unplugin-style-dictionary]'\n\n // Ahead of the `silent` gate on purpose. `silent` is about the progress\n // lines and the size table; a compile that failed is not noise, and\n // hiding it left a broken token set shipping with nothing said at all.\n if (type === 'error') {\n // The host renders and colours its own output, so nothing painted here\n // is handed to one — an escape inside a webpack `stats` entry survives\n // into `stats.toJson()` and into whatever reads it.\n if (host) {\n host.error(`${prefix} ${message}`)\n return\n }\n\n console.error(paint('31', `${prefix} ${message}`, stderrColour))\n return\n }\n\n if (quiet) return\n\n if (host?.info) {\n host.info(`${prefix} ${message}`)\n return\n }\n\n console.log(\n paint(\n type === 'success' ? '32' : '36',\n `${prefix} ${message}`,\n stdoutColour,\n ),\n )\n }\n\n // Resolve config file paths / objects. The work is `resolveConfigOption`'s;\n // what this adds is the instance it runs for, read at the moment of the call.\n // `root` is assigned by the host after the factory has run, so it is passed\n // as it stands now rather than as it stood when this was built.\n const resolveConfigs = async (): Promise<ResolvedConfig[]> =>\n resolveConfigOption({\n config: options.config,\n configContext,\n discovery,\n log,\n root,\n })\n\n // Parse token files to watch\n const getWatchTargets = async (\n resolvedConfigs: ResolvedConfig[],\n ): Promise<{ paths: string[]; patterns: WatchPatterns }> => {\n const patterns = await watchPatternsOf(\n { log, root, watch: options.watch },\n resolvedConfigs,\n )\n\n // Recorded here rather than at each call site, so every path that derives\n // a watch list refreshes the one `watchChange` filters against.\n cachedPatterns = patterns\n\n return { paths: await expandPatterns(patterns, log), patterns }\n }\n\n // What `runBuilds` reads from this plugin instance, built once. `root` and\n // the overlay callback go in as functions rather than values, because both\n // are assigned after this runs — see `PluginInstance`.\n const instance: PluginInstance = {\n cache,\n destinationRecords,\n failOnError,\n generatedDestinations,\n log,\n notifyBuildOutcome: (error) => {\n notifyBuildOutcome?.(error)\n },\n onBuildEnd,\n onBuildError,\n onBuildStart,\n persistedRecordFiles,\n platformsOption,\n quiet,\n report,\n root: () => root,\n stdoutColour,\n verbosity,\n volume: atomicVolume,\n watch: options.watch,\n }\n\n // `runBuilds` for the first build of a process, with the compile shared\n // between every plugin instance that wants the same one.\n //\n // An instance arriving while a compile for the same key is running waits on\n // that compile instead of starting a second. It is the concurrent half that\n // needs this: an up-to-date check compares what is on disk against the\n // sources, and two instances that start together have nothing on disk to\n // compare against yet, so only a shared promise can tell them apart from\n // two genuinely separate builds.\n //\n // The entry is dropped as soon as the compile settles, so this coalesces\n // rather than caches — a later `buildStart` still compiles. Skipping one\n // whose output is already current is #212's up-to-date check, and belongs\n // with it rather than as a second mechanism here.\n //\n // A rejection reaches every waiter, which is the point: an instance that\n // waited on a failed compile must not carry on as though the tokens were\n // written. Whether that rejection is thrown at all is `failOnError`'s\n // decision, already made inside `runBuilds`.\n const compileOnceAcrossInstances = async (\n resolvedConfigs: ResolvedConfig[],\n ): Promise<void> => {\n const key = buildKey(root, resolvedConfigs)\n if (key === null) {\n await runBuilds(instance, resolvedConfigs)\n return\n }\n\n const running = compilesInFlight.get(key)\n if (running) {\n await running\n return\n }\n\n const compile = runBuilds(instance, resolvedConfigs)\n compilesInFlight.set(key, compile)\n\n try {\n await compile\n } finally {\n compilesInFlight.delete(key)\n }\n }\n\n // Set by `configureServer`. A dev server's watcher is long-lived, so its\n // list has to follow a configuration that changes; every other target\n // re-registers on each build through `addWatchFile` instead.\n let refreshServerWatchList:\n | ((resolved: ResolvedConfig[]) => Promise<void>)\n | undefined\n\n // Also set by `configureServer`: closes the `node_modules` watchers it\n // opened. Called from `buildEnd` rather than registered on the server's own\n // shutdown, because nothing on the server fires in every mode — see the\n // `vite` block's `buildEnd`.\n let closeOwnWatchers: (() => void) | undefined\n\n // Whether the discovered path has been announced. Once per plugin instance:\n // `resolveConfigs` runs on every build and rebuild, and a dev server would\n // otherwise repeat the line for the rest of the session.\n //\n // An object rather than a flag, because it is handed to\n // `resolveConfigOption` to set: that function serves every instance in the\n // process, so the record of what this one has said has to stay here.\n const discovery = { announced: false }\n\n // Resolved by `configResolved` so it can amend the watcher's ignore list, and\n // handed to `configureServer` rather than resolved again — one start-up, one\n // call of the consumer's `config` function.\n let startupResolved: ResolvedConfig[] | undefined\n\n // Also set by `configureServer`, and left undefined everywhere else: this is\n // how a compile outcome reaches Vite's error overlay. It is deliberately not\n // the same path as `failOnError`.\n //\n // `failOnError` decides whether the host stops; this decides whether the\n // browser is told. Under a dev server the default is not to stop, so the\n // failure is reported and swallowed — and that is exactly the case where the\n // page is left rendering the last good file with nothing to say it is stale.\n // Reading the outcome off whether `runBuilds` threw would therefore see\n // nothing at all on the only configuration that matters.\n let notifyBuildOutcome: ((error: Error | null) => void) | undefined\n\n // A rebuild, as the scheduler runs it: resolve, compile, and bring the dev\n // server's watch list up to date. A failure is rethrown once it has been\n // reported, so the scheduler can hand it to every trigger this covered.\n const rebuild = async (reason: string): Promise<void> => {\n let compiling = false\n\n try {\n const resolved = await resolveConfigs()\n if (resolved.length > 0) {\n compiling = true\n await runBuilds(instance, resolved, reason)\n compiling = false\n hasCompiled = true\n await refreshServerWatchList?.(resolved)\n }\n } catch (err) {\n // `runBuilds` reports its own failure before rethrowing, so only the\n // other things that can throw here — a `config` function of the\n // consumer's that raises, a watch list that cannot be rebuilt — need\n // reporting. They reach the overlay for the same reason: from the\n // page's point of view the rebuild failed, whichever half of it did.\n if (!compiling) {\n log(`Rebuild failed: ${errorMessage(err)}`, 'error')\n notifyBuildOutcome?.(asError(err))\n }\n\n throw err\n }\n }\n\n // One rebuild per burst of watcher events, and never two at once — see\n // `createScheduler`. Created here, once per plugin instance: a debounce two\n // instances shared would run one instance's rebuild and drop the other's.\n const schedule = createScheduler({\n debounceMs: 50,\n isClosed: () => hostClosed,\n run: rebuild,\n })\n\n // Every host that runs a rollup-shaped watcher calls this on shutdown, and\n // all three get the same handler below. There is deliberately no webpack\n // equivalent here: it has no `closeWatcher`, its nearest thing is\n // `compiler.hooks.watchClose`, and nothing measured shows it exposed.\n //\n // It raises the flag and nothing else. A debounce timer armed before the\n // close is deliberately left to fire: the rebuild it runs is one the host\n // asked for while the project was still whole, and `rebuild` reports its\n // own failures. Cancelling it would be a guard no test could fail on, since a\n // trigger arriving after the close is declined by `schedule` instead.\n const closeWatcher = (): void => {\n hostClosed = true\n }\n\n // What both webpack-shaped hosts do with a compiler, written once.\n // rspack reimplements webpack's plugin API hook for hook, but ships its\n // own `Compiler` type and unplugin dispatches the two through separate\n // keys — so a function typed against either one rejects the other. Naming\n // the surface actually used is what lets one implementation serve both.\n //\n // unplugin calls this inside `apply(compiler)`, one line before it taps\n // `make`, so the root is in place before the first compile. Without it a\n // webpack build whose `context` is not the working directory looked for\n // the configuration in the wrong place and reported ENOENT.\n const adoptCompiler = (compiler: BundlerCompiler): void => {\n if (rootOption === undefined) {\n root = compiler.options.context ?? process.cwd()\n }\n\n // webpack's `buildStart` context carries no `meta`, so neither half of\n // the build context can come from there. `mode` is a webpack option, and\n // `watchMode` is only true once `watch()` has been called — which is\n // after this runs, so it is read per compile below rather than here.\n hostMode = compiler.options.mode\n\n // The compile happens in `beforeCompile`, which webpack awaits *before*\n // the compilation exists — so a message from it has nothing to attach to\n // yet and is held until one appears.\n //\n // Only failures are routed. `stats` carries warnings and errors and\n // nothing else, so the progress lines stay on the console rather than\n // being reported as warnings they are not.\n //\n // A warning rather than an error, for the same reason as on rollup: this\n // is the report, and `failOnError` decides separately whether the build\n // stops. Pushing to `compilation.errors` would fail a webpack build that\n // asked not to be failed.\n const pending: string[] = []\n host = {\n error: (message) => {\n pending.push(message)\n },\n }\n\n compiler.hooks.compilation.tap(\n 'unplugin-style-dictionary',\n (compilation) => {\n for (const message of pending.splice(0)) {\n const reported = new Error(message)\n reported.name = 'UnpluginStyleDictionaryWarning'\n compilation.warnings.push(reported)\n }\n },\n )\n\n // A `beforeCompile` that throws ends the run without ever creating a\n // compilation, and that is exactly the case that produced the message.\n // Left to the buffer it would be reported nowhere at all, so whatever is\n // still held when the run ends goes to the console after all.\n const drainToConsole = () => {\n for (const message of pending.splice(0)) {\n console.error(paint('31', message, stderrColour))\n }\n }\n compiler.hooks.failed.tap('unplugin-style-dictionary', drainToConsole)\n compiler.hooks.done.tap('unplugin-style-dictionary', drainToConsole)\n\n // `beforeCompile` is awaited before the compilation exists, so the\n // tokens are on disk before webpack resolves the module that imports\n // them. Tapped on every compilation rather than only the first: a watch\n // rebuild needs the same guarantee, and a compile that renders what is\n // already there skips its own write.\n compiler.hooks.beforeCompile.tapPromise(\n 'unplugin-style-dictionary',\n async () => {\n isWatching = compiler.watchMode\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n await compileOnceAcrossInstances(resolved)\n hasCompiled = true\n },\n )\n }\n\n return {\n async buildStart() {\n adoptHost(this)\n adoptWatchMode(this)\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Register token/config files with the host bundler's watch mode.\n // Works out of the box wherever the host runs a persistent watcher\n // (e.g. `rollup --watch`). Vite's dev server is additionally handled\n // below via the `vite.configureServer` escape hatch — not because\n // `watchChange` is missing there, which it is not on any Vite this\n // package supports, but because the declared peer range is wider than\n // what has been measured and the scheduler above makes a duplicate\n // trigger free.\n //\n // Skipped outright once the host has closed, because rollup discards the\n // result: with the task closed, `Task.run` returns before\n // `updateWatchedFiles`, so every path registered here goes nowhere. What\n // deriving it does still do is read each config file — with\n // its errors reported — and report an ENOENT for a project the host is\n // in the middle of tearing down. That report was the one thing this\n // block contributed after a close.\n if (!hostClosed) {\n const { paths } = await getWatchTargets(resolved)\n for (const file of paths) {\n this.addWatchFile(file)\n }\n }\n\n // Registering the watch list is all this hook does on webpack, and it\n // has to happen here rather than beside the compile: `addWatchFile`\n // reaches `compilation.fileDependencies`, and `beforeCompile` runs\n // before there is a compilation to add to. Compiling here as well would\n // put the race back, and run every webpack build twice.\n if (isWebpack) return\n\n // Every watch rebuild re-enters this hook, and compiling here as well as\n // in `watchChange` is what closed the loop: consuming code imports the\n // generated file, so writing it is itself a module-graph change, which\n // re-enters `buildStart`, which writes it again. `watchChange` has\n // already run for every file in this cycle and rebuilt if any of them\n // was a source, so the only thing left for a re-entry to do is the\n // re-registration above.\n //\n // `hasCompiled` is the floor under that: a host that fires\n // `watchChange` without ever re-entering here would otherwise leave the\n // flag standing, and no first compile of a process may ever be skipped —\n // the tokens have to exist before the build that consumes them.\n //\n // Vite's dev server is that host, and more than a first compile is at\n // stake there. It calls this hook once per server, at start and on each\n // restart, and never after a change — so a flag some earlier file event\n // raised says nothing about this call. Honouring it made a restart after\n // any event in the session build nothing, including the restart Vite\n // performs when `vite.config.ts` changes. Cleared either way, so the\n // next call starts from what happens after this one.\n const reentry = watchRebuild && hasCompiled && hostCommand !== 'serve'\n watchRebuild = false\n if (reentry) return\n\n await compileOnceAcrossInstances(resolved)\n hasCompiled = true\n },\n\n name: 'unplugin-style-dictionary',\n\n // `closeWatcher` is a rollup-shaped hook and `UnpluginOptions` declares no\n // top-level equivalent, so it is registered per target instead: rolldown\n // lists it among its input plugin hooks, and Vite's plugin type is\n // rollup's, which is what carries it to `vite build --watch`. Vite's dev\n // server runs no rollup watcher, so there it simply never fires.\n rolldown: { closeWatcher },\n\n rollup: { closeWatcher },\n\n // unplugin calls the matching key from inside `apply(compiler)` and\n // never both, so the two share one implementation rather than one\n // delegating to the other.\n rspack: adoptCompiler,\n\n vite: {\n // **`buildEnd` is the hook a dev server's close reliably reaches.** It\n // replaced `server.httpServer?.once('close', …)`, which middleware mode\n // — Express, Koa, most SSR — has no `httpServer` for, so the optional\n // chain registered nothing and every restart leaked the watchers.\n //\n // Measured on Vite 6.4.3, 7.3.6 and 8.3.0, in middleware mode and\n // listening alike: while a server runs only `buildStart` fires, and on\n // `server.close()` it is `buildEnd` once and `closeBundle` twice.\n // **`closeWatcher` fires in neither mode on any of them**, which is why\n // this is not the `closeWatcher` handler registered below.\n //\n // `buildEnd` also fires at the end of `vite build`, and on every rebuild\n // of `vite build --watch`. Neither runs `configureServer`, so there is\n // nothing to close there and this is a no-op.\n buildEnd() {\n closeOwnWatchers?.()\n },\n\n closeWatcher,\n\n async configResolved(config) {\n if (rootOption === undefined) root = config.root || process.cwd()\n\n // The only host that has both. `command` is what makes `'serve'`\n // reachable at all, since nothing else here serves.\n hostCommand = config.command\n hostMode = config.mode\n\n // Vite's own logger, so the plugin's lines obey `customLogger` and\n // `clearScreen` like every other line the dev server prints. It\n // colours and prefixes its own output, which is why nothing painted\n // reaches it.\n //\n // Ahead of the early return below, because `vite build` needs the\n // logger just as much and takes that return.\n host = {\n error: (message) => {\n config.logger.error(message)\n },\n info: (message) => {\n config.logger.info(message)\n },\n }\n\n // A build has a watcher too under `vite build --watch`. On Vite 6 and 7\n // it is rollup's, whose chokidar ignore list always starts with\n // `**/node_modules/**`, so a token package resolved through\n // `node_modules` built once and never rebuilt — the dev server's fix\n // below never reached this mode. Everything after this block is the\n // dev server's.\n if (config.command !== 'serve') {\n if (config.build.watch) {\n // Before the resolution, so the `config` function it calls is told\n // it is being watched, as every later call will be.\n isWatching = true\n\n try {\n const resolved = await resolveConfigs()\n if (resolved.length > 0) {\n const { paths } = await getWatchTargets(resolved)\n appendChokidarIgnored(\n config.build.watch,\n nodeModulesNegations(paths),\n )\n }\n } catch (err) {\n // The build reports it itself when `buildStart` resolves again,\n // as the dev server's branch below explains.\n log(\n `Could not read the configuration while preparing the watch list: ${errorMessage(err)}`,\n 'error',\n )\n }\n }\n return\n }\n\n // Ahead of the resolution below, so the `config` function a consumer\n // wrote is told `watch: true` on this call as well as on every later\n // one. Setting it in `configureServer` alone was correct until this\n // hook started resolving configurations too.\n isWatching = true\n\n // **This is the last hook that can reach the ignore list.** Vite\n // builds the watcher from the resolved config, and `configureServer`\n // runs after it exists — `server.watcher` is a parameter there — so a\n // negation added then changes nothing. Measured on Vite 6.4.3, 7.3.6\n // and 8.3.0: amending it here reaches the watcher on all three, and\n // amending it in `configureServer` does not.\n //\n // The resolution is kept for `configureServer` to reuse rather than\n // discarded, because resolving is how a `config` function gets called\n // and doing it twice in one start-up would call the consumer's code an\n // extra time for nothing.\n try {\n startupResolved = await resolveConfigs()\n if (startupResolved.length === 0) return\n\n // A watcher the consumer turned off stays off. Vite builds a real one\n // for any value but a strict `null`, and appending the negation\n // below spread that `null` into an object — so the setting stopped\n // working exactly when a token came from a workspace package. By\n // identity, not truthiness: an unset `server.watch` resolves to\n // `undefined`, and almost nobody sets it, so a falsy check would drop\n // the negation for nearly every consumer.\n //\n // Here rather than above the `command` return: `server.watch` is\n // the dev server's, and says nothing about `vite build --watch`.\n if (config.server.watch === null) return\n\n const { paths } = await getWatchTargets(startupResolved)\n const negations = nodeModulesNegations(paths)\n if (negations.length === 0) return\n\n // Appended to whatever the consumer asked for, not replacing it.\n const existing = config.server.watch?.ignored\n config.server.watch = {\n ...config.server.watch,\n ignored: [\n ...(Array.isArray(existing)\n ? existing\n : existing === undefined\n ? []\n : [existing]),\n ...negations,\n ],\n }\n } catch (err) {\n // A configuration that cannot be resolved is the build's problem to\n // report, and it will: `buildStart` resolves again and fails there\n // with the host watching. Throwing here would fail the dev server\n // before it started, for the sake of a watch-list refinement.\n log(\n `Could not read the configuration while preparing the watch list: ${errorMessage(err)}`,\n 'error',\n )\n startupResolved = undefined\n }\n },\n\n async configureServer(server: ViteDevServer) {\n // A dev server watches, by definition. Said here rather than left to\n // `adoptWatchMode` because this hook runs *before* `buildStart` —\n // `createServer` calls it, and `buildStart` waits for the plugin\n // container — so the first `config` function of the process would\n // otherwise be told `watch: false` while a dev server started up\n // around it.\n isWatching = true\n\n // `configResolved` has already resolved these, on its way to amending\n // the watcher's ignore list. Taken rather than copied, so a later\n // rebuild re-resolves as it always did.\n const resolved = startupResolved ?? (await resolveConfigs())\n startupResolved = undefined\n if (resolved.length === 0) return\n\n // Reassigned after every rebuild below, so a configuration that gains\n // a source is matched against its new patterns rather than the ones\n // read at start-up.\n let targets = await getWatchTargets(resolved)\n\n // Watch configuration files and token files\n server.watcher.add(targets.paths)\n\n // The `node_modules` half, which Vite's watcher cannot be made to\n // deliver on Windows — see `nodeModulesWatchDirectories`. Keyed by\n // directory so a configuration that changes can close the ones it no\n // longer needs rather than accumulating watchers for the session.\n const ownWatchers = new Map<string, fs.FSWatcher>()\n\n // None of these either when the consumer turned Vite's watcher off.\n // Kept running, they rebuilt a token resolved through `node_modules`\n // while one in the root did not rebuild at all, which is not what a\n // disabled watcher means.\n const watcherDisabled = server.config.server.watch === null\n\n const watchNodeModules = (forPaths: string[]) => {\n if (watcherDisabled) return\n\n const wanted = new Set(nodeModulesWatchDirectories(forPaths))\n\n for (const [directory, watcher] of ownWatchers) {\n if (wanted.has(directory)) continue\n watcher.close()\n ownWatchers.delete(directory)\n }\n\n for (const directory of wanted) {\n if (ownWatchers.has(directory)) continue\n\n try {\n const watcher = fs.watch(directory, (_event, filename) => {\n if (filename === null) return\n\n const changed = path.posix.join(directory, filename)\n if (\n !isWatchedSource(\n changed,\n targets.patterns,\n generatedDestinations,\n )\n )\n return\n\n void schedule(path.basename(changed)).catch(() => {})\n })\n\n // A watcher of ours must not be what keeps a process alive; the\n // dev server already is.\n watcher.unref()\n ownWatchers.set(directory, watcher)\n } catch {\n // A directory that cannot be watched is not a reason to fail a\n // dev server. Where the negation works — Linux, macOS — Vite's\n // own watcher is still delivering these events.\n }\n }\n }\n\n watchNodeModules(targets.paths)\n\n // Clearing the map is what makes this safe to call more than once.\n closeOwnWatchers = () => {\n for (const watcher of ownWatchers.values()) watcher.close()\n ownWatchers.clear()\n }\n\n // Runs once per rebuild rather than once per event, which is why it\n // is handed to the scheduler rather than done in the listener.\n refreshServerWatchList = async (rebuilt) => {\n targets = await getWatchTargets(rebuilt)\n server.watcher.add(targets.paths)\n watchNodeModules(targets.paths)\n }\n\n if (errorOverlay) {\n // Whether the page is currently showing an overlay this plugin put\n // there. Only the clearing frame reads it: a success that follows a\n // success has no overlay to take down, and sending an update frame\n // for it would be traffic for nothing — and would spend the client's\n // one-time `isFirstUpdate`, which Vite uses to decide that an\n // overlay standing at the first update means a full reload.\n let overlayShowing = false\n\n notifyBuildOutcome = (error) => {\n if (error) {\n // Sent on every failure rather than only on the transition into\n // one. Vite's client replaces the overlay wholesale, so a repeat\n // is idempotent — and two different failures in a row must not\n // leave the first one's message on screen describing the second.\n overlayShowing = true\n server.hot.send({\n err: {\n message: error.message,\n plugin: 'unplugin-style-dictionary',\n stack: error.stack ?? '',\n },\n type: 'error',\n })\n return\n }\n\n if (!overlayShowing) return\n overlayShowing = false\n\n // Vite's protocol has no frame for \"take the overlay down\". The\n // client clears it when an update arrives, so an update carrying\n // nothing is the clear: it dismisses the overlay and then iterates\n // an empty list, reloading no page and touching no stylesheet.\n server.hot.send({ type: 'update', updates: [] })\n }\n }\n\n // chokidar types its listener as returning void and does not await\n // what it is handed, so an async listener left every rejection\n // floating. `schedule` owns the whole rebuild including its errors,\n // so there is nothing here left to reject.\n server.watcher.on('all', (_event, file) => {\n if (!isWatchedSource(file, targets.patterns, generatedDestinations))\n return\n\n // A dev server has no build to fail, so a rebuild that throws is\n // reported by the scheduler and the server keeps serving.\n void schedule(path.basename(file)).catch(() => {})\n })\n },\n },\n\n // Rollup types `watchChange` as returning void, yet awaits it as a\n // sequential hook — and the work here is inherently asynchronous. The\n // signature is the thing that is wrong, so the rule is silenced rather\n // than the hook made to lie about finishing.\n // oxlint-disable-next-line typescript/no-misused-promises\n async watchChange(id) {\n adoptHost(this)\n adoptWatchMode(this)\n\n // Ahead of everything, including the flag below: a change reported\n // after the watcher closed earns no rebuild, so there is no re-entry\n // into `buildStart` for a flag to describe.\n if (hostClosed) return\n\n // Raised before any decision about `id`, because whatever this change\n // was, the host is now on its way back into `buildStart`.\n watchRebuild = true\n\n // The cheap half of the decision, taken before anything is resolved.\n // Under Vite the scope this hook sees is the whole project root rather\n // than the module graph, so most of what arrives here has nothing to do\n // with tokens, and resolving every configuration only to discard the\n // answer ran a consumer's `config` function once per unrelated file.\n // Skipped until a build has derived a list to filter against.\n if (\n cachedPatterns &&\n !isWatchedSource(id, cachedPatterns, generatedDestinations)\n )\n return\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Derived again rather than trusted from the cache, because the cache\n // is what decided this path was worth resolving and not what decides a\n // rebuild. A config edit reaches here through its own filename and can\n // have dropped the very source the cached list matched.\n const { patterns } = await getWatchTargets(resolved)\n // Without this check, watchChange fires for *any* changed file in the\n // host bundler's module graph — including our own generated output,\n // since consuming code imports it. Every regenerate is itself a\n // \"change\", so skipping what is not a source here is what keeps this\n // from rebuilding forever — both the files that match no pattern and\n // the ones that match only because this plugin wrote them.\n if (!isWatchedSource(id, patterns, generatedDestinations)) return\n\n // Same division as `buildStart`: on webpack the compile belongs to\n // `beforeCompile`, which has already run for this compilation, so all\n // that is left is to re-register the watch list below.\n if (!isWebpack) await schedule(path.basename(id))\n\n // Expanded again after the build rather than reusing the list from\n // before it, so a token file the build itself produced is registered.\n for (const file of await expandPatterns(patterns, log)) {\n this.addWatchFile(file)\n }\n },\n\n webpack: adoptCompiler,\n }\n}\n\nexport const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory)\n\nexport default unplugin\n"],"mappings":";;;;;;;;;;;AAuDA,SAAS,sBAAsB,OAAe,WAA2B;CACvE,IAAI,UAAU,WAAW,GAAG;CAE5B,MAAM,WAAoB,QAAQ,IAAI,OAAO,UAAU;CACvD,MAAM,UACJ,OAAO,aAAa,YAAY,aAAa,OAAO,WAAW,CAAC;CAClE,MAAM,WAAoB,QAAQ,IAAI,SAAS,SAAS;CACxD,MAAM,WAAsB,MAAM,QAAQ,QAAQ,IAC9C,WACA,aAAa,KAAA,IACX,CAAC,IACD,CAAC,QAAQ;CAEf,QAAQ,IAAI,OAAO,YAAY;EAC7B,GAAG;EACH,SAAS,CAAC,GAAG,UAAU,GAAG,SAAS;CACrC,CAAC;AACH;AAIA,SAAS,qBAAqB,WAAyB;CACrD,IAAI;EACF,GAAG,OAAO,WAAW,EAAE,OAAO,KAAK,CAAC;CACtC,QAAQ,CAER;AACF;AAKA,SAAS,iBAAiB,OAAoD;CAC5E,OAAO,OAAO,UAAU;AAC1B;AAkBA,SAAS,qBAAqB,OAA2B;CACvD,MAAM,4BAAY,IAAI,IAAY;CAElC,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,aAAa,KAAK,QAAQ,OAAO,GAAG;EAC1C,IAAI,WAAW,SAAS,gBAAgB,GAAG,UAAU,IAAI,IAAI,YAAY;CAC3E;CAEA,OAAO,MAAM,KAAK,SAAS;AAC7B;AAuBA,SAAS,4BAA4B,OAA2B;CAC9D,MAAM,8BAAc,IAAI,IAAY;CAEpC,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,aAAa,KAAK,QAAQ,OAAO,GAAG;EAC1C,IAAI,CAAC,WAAW,SAAS,gBAAgB,GAAG;EAI5C,YAAY,IAAI,KAAK,QAAQ,UAAU,CAAC;CAC1C;CAEA,OAAO,MAAM,KAAK,WAAW;AAC/B;AA0BA,IAAI,uBAAuB;AAa3B,eAAe,0BACb,WACA,aACkB;CAClB,IAAI;EACF,MAAM,CAAC,UAAU,YAAY,MAAM,QAAQ,IAAI,CAC7C,GAAG,SAAS,SAAS,WAAW,GAChC,GAAG,SAAS,SAAS,SAAS,CAChC,CAAC;EAED,OAAO,SAAS,OAAO,QAAQ;CACjC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,8BACP,WACA,aACS;CACT,IAAI;EACF,OAAO,GAAG,aAAa,WAAW,CAAC,CAAC,OAAO,GAAG,aAAa,SAAS,CAAC;CACvE,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,iBAAiB,aAA6B;CACrD,MAAM,YAAY,KAAK,QAAQ,WAAW;CAE1C,OAAO,KAAK,KACV,KAAK,QAAQ,WAAW,GACxB,IAAI,KAAK,SAAS,aAAa,SAAS,EAAE,GAAG,QAAQ,IAAI,GAAG,uBAAuB,KACrF;AACF;AAmBA,MAAM,qCAAqB,IAAI,IAAI,CAAC,SAAS,OAAO,CAAC;AAsBrD,MAAM,yBAAyB;CAAC;CAAG;CAAG;CAAG;CAAG;CAAI;CAAI;CAAI;CAAK;CAAK;CAAK;AAAI;AAW3E,MAAM,8BAA8B;CAAC;CAAG;CAAG;CAAG;CAAG;CAAI;CAAI;CAAI;AAAG;AAEhE,SAAS,uBAAuB,OAAyB;CACvD,OACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,OAAO,MAAM,SAAS,YACtB,mBAAmB,IAAI,MAAM,IAAI;AAErC;AAMA,MAAM,aAAa,OAAqB;CACtC,QAAQ,KAAK,IAAI,WAAW,IAAI,kBAAkB,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE;AACjE;AAEA,eAAe,gBACb,WACA,aACe;CACf,KAAK,MAAM,SAAS,wBAClB,IAAI;EACF,MAAM,GAAG,SAAS,OAAO,WAAW,WAAW;EAC/C;CACF,SAAS,KAAK;EACZ,IAAI,CAAC,uBAAuB,GAAG,GAAG,MAAM;EACxC,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,KAAK,CAAC;CAC3D;CAKF,MAAM,GAAG,SAAS,OAAO,WAAW,WAAW;AACjD;AAEA,SAAS,oBAAoB,WAAmB,aAA2B;CACzE,KAAK,MAAM,SAAS,6BAClB,IAAI;EACF,GAAG,WAAW,WAAW,WAAW;EACpC;CACF,SAAS,KAAK;EACZ,IAAI,CAAC,uBAAuB,GAAG,GAAG,MAAM;EACxC,UAAU,KAAK;CACjB;CAGF,GAAG,WAAW,WAAW,WAAW;AACtC;AAEA,MAAM,kBAAgD,OACpD,MACA,MACA,YACG;CAGH,IAAI,OAAO,SAAS,UAClB,OAAO,GAAG,SAAS,UAAU,MAAM,MAAM,OAAO;CAGlD,MAAM,YAAY,iBAAiB,IAAI;CAEvC,IAAI;EACF,MAAM,GAAG,SAAS,UAAU,WAAW,MAAM,OAAO;EAMpD,IAAI,MAAM,0BAA0B,WAAW,IAAI,GAAG;GACpD,qBAAqB,SAAS;GAC9B;EACF;EAEA,MAAM,gBAAgB,WAAW,IAAI;CACvC,SAAS,KAAK;EACZ,qBAAqB,SAAS;EAC9B,MAAM;CACR;AACF;AAEA,MAAM,uBAAgD,MAAM,MAAM,YAAY;CAC5E,IAAI,OAAO,SAAS,UAAU;EAC5B,GAAG,cAAc,MAAM,MAAM,OAAO;EACpC;CACF;CAEA,MAAM,YAAY,iBAAiB,IAAI;CAEvC,IAAI;EACF,GAAG,cAAc,WAAW,MAAM,OAAO;EAEzC,IAAI,8BAA8B,WAAW,IAAI,GAAG;GAClD,qBAAqB,SAAS;GAC9B;EACF;EAEA,oBAAoB,WAAW,IAAI;CACrC,SAAS,KAAK;EACZ,qBAAqB,SAAS;EAC9B,MAAM;CACR;AACF;AAkBA,MAAM,eAAe,OAAO,OAAO,IAAI;CACrC,UAAU,EACR,OAAO,OAAO,OAAO,GAAG,UAAU,EAChC,WAAW,EAAE,OAAO,gBAAgB,EACtC,CAAC,EACH;CACA,eAAe,EAAE,OAAO,oBAAoB;AAC9C,CAAC;AA4CD,MAAM,qCAAqB,IAAI,IAA+B;AAI9D,MAAM,uCAAuB,IAAI,IAAoB;AAkBrD,MAAM,mCAAmB,IAAI,IAA2B;AAoCxD,SAAS,SAAS,MAAc,UAA2C;CACzE,IAAI;EACF,OAAO,KAAK,UACV,CAAC,MAAM,SAAS,KAAK,SAAS,KAAK,QAAQ,KAAK,MAAM,CAAC,IACtD,MAAM,UACL,OAAO,UAAU,aAAa,OAAO,OAAO,KAAK,MAAM,KAC3D;CACF,QAAQ;EAIN,OAAO;CACT;AACF;AAOA,MAAM,mBAGD,UAAU,CAAC,GAAG,SAAS;CAQ1B,MAAM,YAAY,KAAK,cAAc,aAAa,KAAK,cAAc;CACrE,MAAM,EACJ,QAAQ,MACR,eAAe,MACf,cAAc,SACd,UACA,YACA,cACA,cACA,WAAW,iBACX,SAAS,MACT,MAAM,YACN,SAAS,UACP;CAIJ,MAAM,QAAQ,aAAa,SAAS,WAAW,KAAA;CAK/C,MAAM,QAAQ,UAAU,YAAY,UAAU;CAQ9C,MAAM,YACJ,UAAU,KAAA,IACN,KAAA,IACA,UAAU,YACR,YACA,UAAU,WACR,WACA;CAQV,IAAI,cAAiC;CACrC,IAAI;CACJ,IAAI,aAAa;CAMjB,MAAM,uBAAqD;EACzD,SAAS;EACT,MAAM,aAAa,gBAAgB,UAAU,gBAAgB;EAC7D,OAAO;CACT;CAKA,MAAM,kBAAkB,YAA0B;EAChD,MAAM,WAAoB,UAAU,UAAU,QAAQ,OAAO,KAAA;EAC7D,IAAI,OAAO,aAAa,YAAY,aAAa,MAAM;EAEvD,MAAM,WACJ,eAAe,WAAW,SAAS,YAAY,KAAA;EACjD,IAAI,OAAO,aAAa,WAAW,aAAa;CAClD;CAKA,IAAI,OAAO,aACP,KAAK,QAAQ,QAAQ,IAAI,GAAG,UAAU,IACtC,QAAQ,IAAI;CAQhB,MAAM,wCAAwB,IAAI,IAAY;CAc9C,IAAI;CAUJ,IAAI,eAAe;CACnB,IAAI,cAAc;CAiBlB,IAAI,aAAa;CAKjB,MAAM,eAAe,cAAc,QAAQ,MAAM;CACjD,MAAM,eAAe,cAAc,QAAQ,MAAM;CAMjD,IAAI;CAUJ,MAAM,aAAa,YAA0B;EAC3C,IAAI,MAAM;EAEV,MAAM,OAAgB,UAAU,UAAU,QAAQ,OAAO,KAAA;EACzD,IAAI,CAAC,iBAAiB,IAAI,GAAG;EAE7B,MAAM,OAAgB,UAAU,UAAU,QAAQ,OAAO,KAAA;EAEzD,OAAO;GAIL,QAAQ,YAAY;IAClB,KAAK,KAAK,SAAS,OAAO;GAC5B;GACA,MAAM,iBAAiB,IAAI,KACtB,YAAY;IACX,KAAK,KAAK,SAAS,OAAO;GAC5B,IACA,KAAA;EACN;CACF;CAGA,MAAM,OACJ,SACA,OAAqC,WAClC;EACH,MAAM,SAAS;EAKf,IAAI,SAAS,SAAS;GAIpB,IAAI,MAAM;IACR,KAAK,MAAM,GAAG,OAAO,GAAG,SAAS;IACjC;GACF;GAEA,QAAQ,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,WAAW,YAAY,CAAC;GAC/D;EACF;EAEA,IAAI,OAAO;EAEX,IAAI,MAAM,MAAM;GACd,KAAK,KAAK,GAAG,OAAO,GAAG,SAAS;GAChC;EACF;EAEA,QAAQ,IACN,MACE,SAAS,YAAY,OAAO,MAC5B,GAAG,OAAO,GAAG,WACb,YACF,CACF;CACF;CAMA,MAAM,iBAAiB,YACrB,oBAAoB;EAClB,QAAQ,QAAQ;EAChB;EACA;EACA;EACA;CACF,CAAC;CAGH,MAAM,kBAAkB,OACtB,oBAC0D;EAC1D,MAAM,WAAW,MAAM,gBACrB;GAAE;GAAK;GAAM,OAAO,QAAQ;EAAM,GAClC,eACF;EAIA,iBAAiB;EAEjB,OAAO;GAAE,OAAO,MAAM,eAAe,UAAU,GAAG;GAAG;EAAS;CAChE;CAKA,MAAM,WAA2B;EAC/B;EACA;EACA;EACA;EACA;EACA,qBAAqB,UAAU;GAC7B,qBAAqB,KAAK;EAC5B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAAY;EACZ;EACA;EACA,QAAQ;EACR,OAAO,QAAQ;CACjB;CAqBA,MAAM,6BAA6B,OACjC,oBACkB;EAClB,MAAM,MAAM,SAAS,MAAM,eAAe;EAC1C,IAAI,QAAQ,MAAM;GAChB,MAAM,UAAU,UAAU,eAAe;GACzC;EACF;EAEA,MAAM,UAAU,iBAAiB,IAAI,GAAG;EACxC,IAAI,SAAS;GACX,MAAM;GACN;EACF;EAEA,MAAM,UAAU,UAAU,UAAU,eAAe;EACnD,iBAAiB,IAAI,KAAK,OAAO;EAEjC,IAAI;GACF,MAAM;EACR,UAAU;GACR,iBAAiB,OAAO,GAAG;EAC7B;CACF;CAKA,IAAI;CAQJ,IAAI;CASJ,MAAM,YAAY,EAAE,WAAW,MAAM;CAKrC,IAAI;CAYJ,IAAI;CAKJ,MAAM,UAAU,OAAO,WAAkC;EACvD,IAAI,YAAY;EAEhB,IAAI;GACF,MAAM,WAAW,MAAM,eAAe;GACtC,IAAI,SAAS,SAAS,GAAG;IACvB,YAAY;IACZ,MAAM,UAAU,UAAU,UAAU,MAAM;IAC1C,YAAY;IACZ,cAAc;IACd,MAAM,yBAAyB,QAAQ;GACzC;EACF,SAAS,KAAK;GAMZ,IAAI,CAAC,WAAW;IACd,IAAI,mBAAmB,aAAa,GAAG,KAAK,OAAO;IACnD,qBAAqB,QAAQ,GAAG,CAAC;GACnC;GAEA,MAAM;EACR;CACF;CAKA,MAAM,WAAW,gBAAgB;EAC/B,YAAY;EACZ,gBAAgB;EAChB,KAAK;CACP,CAAC;CAYD,MAAM,qBAA2B;EAC/B,aAAa;CACf;CAYA,MAAM,iBAAiB,aAAoC;EACzD,IAAI,eAAe,KAAA,GACjB,OAAO,SAAS,QAAQ,WAAW,QAAQ,IAAI;EAOjD,WAAW,SAAS,QAAQ;EAc5B,MAAM,UAAoB,CAAC;EAC3B,OAAO,EACL,QAAQ,YAAY;GAClB,QAAQ,KAAK,OAAO;EACtB,EACF;EAEA,SAAS,MAAM,YAAY,IACzB,8BACC,gBAAgB;GACf,KAAK,MAAM,WAAW,QAAQ,OAAO,CAAC,GAAG;IACvC,MAAM,WAAW,IAAI,MAAM,OAAO;IAClC,SAAS,OAAO;IAChB,YAAY,SAAS,KAAK,QAAQ;GACpC;EACF,CACF;EAMA,MAAM,uBAAuB;GAC3B,KAAK,MAAM,WAAW,QAAQ,OAAO,CAAC,GACpC,QAAQ,MAAM,MAAM,MAAM,SAAS,YAAY,CAAC;EAEpD;EACA,SAAS,MAAM,OAAO,IAAI,6BAA6B,cAAc;EACrE,SAAS,MAAM,KAAK,IAAI,6BAA6B,cAAc;EAOnE,SAAS,MAAM,cAAc,WAC3B,6BACA,YAAY;GACV,aAAa,SAAS;GAEtB,MAAM,WAAW,MAAM,eAAe;GACtC,IAAI,SAAS,WAAW,GAAG;GAE3B,MAAM,2BAA2B,QAAQ;GACzC,cAAc;EAChB,CACF;CACF;CAEA,OAAO;EACL,MAAM,aAAa;GACjB,UAAU,IAAI;GACd,eAAe,IAAI;GAEnB,MAAM,WAAW,MAAM,eAAe;GACtC,IAAI,SAAS,WAAW,GAAG;GAkB3B,IAAI,CAAC,YAAY;IACf,MAAM,EAAE,UAAU,MAAM,gBAAgB,QAAQ;IAChD,KAAK,MAAM,QAAQ,OACjB,KAAK,aAAa,IAAI;GAE1B;GAOA,IAAI,WAAW;GAsBf,MAAM,UAAU,gBAAgB,eAAe,gBAAgB;GAC/D,eAAe;GACf,IAAI,SAAS;GAEb,MAAM,2BAA2B,QAAQ;GACzC,cAAc;EAChB;EAEA,MAAM;EAON,UAAU,EAAE,aAAa;EAEzB,QAAQ,EAAE,aAAa;EAKvB,QAAQ;EAER,MAAM;GAeJ,WAAW;IACT,mBAAmB;GACrB;GAEA;GAEA,MAAM,eAAe,QAAQ;IAC3B,IAAI,eAAe,KAAA,GAAW,OAAO,OAAO,QAAQ,QAAQ,IAAI;IAIhE,cAAc,OAAO;IACrB,WAAW,OAAO;IASlB,OAAO;KACL,QAAQ,YAAY;MAClB,OAAO,OAAO,MAAM,OAAO;KAC7B;KACA,OAAO,YAAY;MACjB,OAAO,OAAO,KAAK,OAAO;KAC5B;IACF;IAQA,IAAI,OAAO,YAAY,SAAS;KAC9B,IAAI,OAAO,MAAM,OAAO;MAGtB,aAAa;MAEb,IAAI;OACF,MAAM,WAAW,MAAM,eAAe;OACtC,IAAI,SAAS,SAAS,GAAG;QACvB,MAAM,EAAE,UAAU,MAAM,gBAAgB,QAAQ;QAChD,sBACE,OAAO,MAAM,OACb,qBAAqB,KAAK,CAC5B;OACF;MACF,SAAS,KAAK;OAGZ,IACE,oEAAoE,aAAa,GAAG,KACpF,OACF;MACF;KACF;KACA;IACF;IAMA,aAAa;IAab,IAAI;KACF,kBAAkB,MAAM,eAAe;KACvC,IAAI,gBAAgB,WAAW,GAAG;KAYlC,IAAI,OAAO,OAAO,UAAU,MAAM;KAElC,MAAM,EAAE,UAAU,MAAM,gBAAgB,eAAe;KACvD,MAAM,YAAY,qBAAqB,KAAK;KAC5C,IAAI,UAAU,WAAW,GAAG;KAG5B,MAAM,WAAW,OAAO,OAAO,OAAO;KACtC,OAAO,OAAO,QAAQ;MACpB,GAAG,OAAO,OAAO;MACjB,SAAS,CACP,GAAI,MAAM,QAAQ,QAAQ,IACtB,WACA,aAAa,KAAA,IACX,CAAC,IACD,CAAC,QAAQ,GACf,GAAG,SACL;KACF;IACF,SAAS,KAAK;KAKZ,IACE,oEAAoE,aAAa,GAAG,KACpF,OACF;KACA,kBAAkB,KAAA;IACpB;GACF;GAEA,MAAM,gBAAgB,QAAuB;IAO3C,aAAa;IAKb,MAAM,WAAW,mBAAoB,MAAM,eAAe;IAC1D,kBAAkB,KAAA;IAClB,IAAI,SAAS,WAAW,GAAG;IAK3B,IAAI,UAAU,MAAM,gBAAgB,QAAQ;IAG5C,OAAO,QAAQ,IAAI,QAAQ,KAAK;IAMhC,MAAM,8BAAc,IAAI,IAA0B;IAMlD,MAAM,kBAAkB,OAAO,OAAO,OAAO,UAAU;IAEvD,MAAM,oBAAoB,aAAuB;KAC/C,IAAI,iBAAiB;KAErB,MAAM,SAAS,IAAI,IAAI,4BAA4B,QAAQ,CAAC;KAE5D,KAAK,MAAM,CAAC,WAAW,YAAY,aAAa;MAC9C,IAAI,OAAO,IAAI,SAAS,GAAG;MAC3B,QAAQ,MAAM;MACd,YAAY,OAAO,SAAS;KAC9B;KAEA,KAAK,MAAM,aAAa,QAAQ;MAC9B,IAAI,YAAY,IAAI,SAAS,GAAG;MAEhC,IAAI;OACF,MAAM,UAAU,GAAG,MAAM,YAAY,QAAQ,aAAa;QACxD,IAAI,aAAa,MAAM;QAEvB,MAAM,UAAU,KAAK,MAAM,KAAK,WAAW,QAAQ;QACnD,IACE,CAAC,gBACC,SACA,QAAQ,UACR,qBACF,GAEA;QAEF,SAAc,KAAK,SAAS,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;OACtD,CAAC;OAID,QAAQ,MAAM;OACd,YAAY,IAAI,WAAW,OAAO;MACpC,QAAQ,CAIR;KACF;IACF;IAEA,iBAAiB,QAAQ,KAAK;IAG9B,yBAAyB;KACvB,KAAK,MAAM,WAAW,YAAY,OAAO,GAAG,QAAQ,MAAM;KAC1D,YAAY,MAAM;IACpB;IAIA,yBAAyB,OAAO,YAAY;KAC1C,UAAU,MAAM,gBAAgB,OAAO;KACvC,OAAO,QAAQ,IAAI,QAAQ,KAAK;KAChC,iBAAiB,QAAQ,KAAK;IAChC;IAEA,IAAI,cAAc;KAOhB,IAAI,iBAAiB;KAErB,sBAAsB,UAAU;MAC9B,IAAI,OAAO;OAKT,iBAAiB;OACjB,OAAO,IAAI,KAAK;QACd,KAAK;SACH,SAAS,MAAM;SACf,QAAQ;SACR,OAAO,MAAM,SAAS;QACxB;QACA,MAAM;OACR,CAAC;OACD;MACF;MAEA,IAAI,CAAC,gBAAgB;MACrB,iBAAiB;MAMjB,OAAO,IAAI,KAAK;OAAE,MAAM;OAAU,SAAS,CAAC;MAAE,CAAC;KACjD;IACF;IAMA,OAAO,QAAQ,GAAG,QAAQ,QAAQ,SAAS;KACzC,IAAI,CAAC,gBAAgB,MAAM,QAAQ,UAAU,qBAAqB,GAChE;KAIF,SAAc,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACnD,CAAC;GACH;EACF;EAOA,MAAM,YAAY,IAAI;GACpB,UAAU,IAAI;GACd,eAAe,IAAI;GAKnB,IAAI,YAAY;GAIhB,eAAe;GAQf,IACE,kBACA,CAAC,gBAAgB,IAAI,gBAAgB,qBAAqB,GAE1D;GAEF,MAAM,WAAW,MAAM,eAAe;GACtC,IAAI,SAAS,WAAW,GAAG;GAM3B,MAAM,EAAE,aAAa,MAAM,gBAAgB,QAAQ;GAOnD,IAAI,CAAC,gBAAgB,IAAI,UAAU,qBAAqB,GAAG;GAK3D,IAAI,CAAC,WAAW,MAAM,SAAS,KAAK,SAAS,EAAE,CAAC;GAIhD,KAAK,MAAM,QAAQ,MAAM,eAAe,UAAU,GAAG,GACnD,KAAK,aAAa,IAAI;EAE1B;EAEA,SAAS;CACX;AACF;AAEA,MAAa,WAA2B,+BAAe,eAAe"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { UnpluginFactory } from 'unplugin'\nimport type { ViteDevServer } from 'vite'\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { createUnplugin } from 'unplugin'\n\nimport type { PluginInstance } from './compile.js'\nimport type { Log, ResolvedConfig } from './config.js'\nimport type { WatchPatterns } from './patterns.js'\nimport type {\n StyleDictionaryConfigContext,\n UnpluginStyleDictionaryOptions,\n} from './types.js'\nimport type { DestinationRecord } from './up-to-date.js'\nimport type { WatchMatcher } from './watch-filter.js'\n\nimport { colourAllowed, paint } from './colour.js'\nimport { runBuilds } from './compile.js'\nimport { resolveConfigOption } from './config.js'\nimport { asError, errorMessage } from './errors.js'\nimport { checkOptions } from './options.js'\nimport {\n expandPatterns,\n realpathOf,\n watchOptionPatterns,\n watchPatternsOf,\n} from './patterns.js'\nimport { createScheduler } from './scheduler.js'\nimport {\n compileWatchPatterns,\n isWatchedSource,\n matchesWatchedFile,\n registeredSpellingOf,\n} from './watch-filter.js'\n\nexport type * from './types.js'\n\n// Where the plugin's own lines go when a host offers somewhere better than the\n// console: Vite's `config.logger`, rollup's and rolldown's plugin context, or\n// webpack's `compilation`.\n//\n// **There is no `error` channel that merely reports.** Rollup's `this.error`\n// aborts the bundle — measured: a `buildStart` calling it ends the run with\n// `THREW: [plugin err-probe] fatal?` — so routing a failure report through it\n// would stop every build that reported one and silently override `failOnError`,\n// whose entire job is deciding that. A failure is therefore reported on the\n// host's warning channel, and whether the build stops stays `failOnError`'s\n// decision alone.\ninterface HostMessenger {\n error: (message: string) => void\n\n // Optional because not every host has somewhere for a progress line to go.\n // webpack's `stats` carries warnings and errors and nothing else, and\n // `Compiling design tokens...` is neither — so there it stays on the\n // console rather than being dressed up as a warning.\n info?: (message: string) => void\n}\n\n// Appends `negations` to a Vite build watcher's `chokidar.ignored`, after\n// whatever the consumer put there, exactly as the dev server's list is amended.\n//\n// Reached structurally, because the types disagree about where it lives. Vite 6\n// and 7 type `build.watch` as rollup's options, which carry `chokidar`; Vite 8\n// types it as rolldown's, which do not, and this package compiles against 8.\n// On 8 the write reaches nothing — Vite forwards only its polling settings to\n// rolldown's watcher — and is harmless there.\nfunction appendChokidarIgnored(watch: object, negations: string[]): void {\n if (negations.length === 0) return\n\n const chokidar: unknown = Reflect.get(watch, 'chokidar')\n const options: object =\n typeof chokidar === 'object' && chokidar !== null ? chokidar : {}\n const existing: unknown = Reflect.get(options, 'ignored')\n const previous: unknown[] = Array.isArray(existing)\n ? existing\n : existing === undefined\n ? []\n : [existing]\n\n Reflect.set(watch, 'chokidar', {\n ...options,\n ignored: [...previous, ...negations],\n })\n}\n\n// Best-effort cleanup of a temporary file whose write or rename failed. The\n// original failure is what the caller reports, so nothing here may throw.\nfunction discardTemporaryFile(temporary: string): void {\n try {\n fs.rmSync(temporary, { force: true })\n } catch {\n // Ignore: a leftover temporary file is not worth masking the real error.\n }\n}\n\n// A host's message channel, narrowed by a predicate rather than asserted: what\n// a plugin context carries under `warn` is the host's business, and a cast\n// would only claim it is callable.\nfunction isMessageChannel(value: unknown): value is (message: string) => void {\n return typeof value === 'function'\n}\n\n// Vite builds its dev-server watcher with a fixed ignore list — `**/.git/**`,\n// `**/node_modules/**`, `**/test-results/**` and the cache directory — and\n// spreads the consumer's own `server.watch.ignored` entries in *after* them.\n// Entries are appended, never subtracted, so `server.watcher.add()` cannot\n// reach a path an earlier entry already covers.\n//\n// That makes a token package resolved through `node_modules` — the shape of\n// every workspace, `app/node_modules/@acme/tokens` symlinked to\n// `packages/tokens` — build correctly once and then never rebuild, with\n// nothing said about it. Measured on Vite 6.4.3, 7.3.6 and 8.3.0: zero watcher\n// events for an edit, while a token file outside the root but outside\n// `node_modules` rebuilt in the same run.\n//\n// A negation of the pattern that names the files is what un-ignores them: one\n// `!<pattern>` for each source pattern that itself reaches into\n// `node_modules`, a literal being its own pattern. It un-ignores exactly the\n// files Style Dictionary reads, which is why it is not the broadening that\n// must not be done — `!**/node_modules/**`, or the package directory, would\n// restore thousands of files no build reads to the watcher.\n//\n// It used to be one negation per matched file and per static parent\n// directory. chokidar tests every path it considers against the whole list,\n// so start-up and every later event grew with the token count: measured on\n// Vite 8.3.0 with 2,000 token files, 1,373ms of ignore-matching at start-up\n// and 116µs per path, against 14ms and 1.4µs for the one pattern. The\n// directory needs no entry, because the pattern alone delivers the edit, and\n// a file created there later reaches the plugin's own watcher from\n// `nodeModulesWatchDirectories`.\n//\n// A file a pattern outside `node_modules` reached — a `**` from above — is\n// still negated on its own: negating that pattern would say nothing about\n// `node_modules`, and Vite's `**/node_modules/**` is ahead of it in the list.\nfunction nodeModulesNegations(\n patterns: WatchPatterns,\n paths: string[],\n): string[] {\n const reaching = [...patterns.globs, ...patterns.literals].filter((pattern) =>\n pattern.includes('/node_modules/'),\n )\n const negations = new Set(reaching.map((pattern) => `!${pattern}`))\n\n for (const file of paths) {\n const normalised = file.replace(/\\\\/g, '/')\n if (!normalised.includes('/node_modules/') || isDirectory(file)) continue\n if (matchesWatchedFile(normalised, reaching)) continue\n\n negations.add(`!${normalised}`)\n }\n\n return Array.from(negations)\n}\n\n// The directories holding token files that resolve through `node_modules`.\n//\n// Vite's ignore list cannot be argued with on Windows. The negation below is\n// honoured on Linux and macOS, and there a token inside `node_modules` rebuilds\n// through the dev-server watcher like any other. On Windows it is not, and no\n// spelling of the negation changes that — measured on a `windows-latest`\n// runner: the file path, every ancestor directory, and the package subtree as\n// a globstar all leave the edit reaching no rebuild, while the same fixture\n// outside `node_modules` rebuilds. `server.watcher.add()` does not reach it\n// either, which is the same limit AGENTS.md already records for a path an\n// earlier ignore entry covers.\n//\n// A symlink is *not* what distinguishes them, which is worth stating because it\n// is the obvious suspect: a real directory inside `node_modules` fails exactly\n// as the symlinked one does, and a symlink outside it succeeds.\n//\n// So these directories get a watcher of the plugin's own, which Vite's ignore\n// list has no say over. It runs on every platform rather than behind a\n// `process.platform` check: one path that is exercised everywhere beats a\n// Windows-only branch that nothing else executes, and the scheduler already\n// collapses the duplicate trigger this produces where the negation also works.\nfunction nodeModulesWatchDirectories(paths: string[]): string[] {\n const directories = new Set<string>()\n\n for (const file of paths) {\n const normalised = file.replace(/\\\\/g, '/')\n if (!normalised.includes('/node_modules/')) continue\n\n // The directory rather than the file: `fs.watch` on a file stops reporting\n // once an editor replaces it by rename, which is what an atomic save does.\n directories.add(path.dirname(normalised))\n }\n\n return Array.from(directories)\n}\n\n// Style Dictionary writes every generated file with a plain `writeFile` on the\n// volume it was handed, which truncates the destination and then streams the\n// new contents into it. Anything reading that file inside the window sees a\n// partial file: a consuming test run whose tokens are rebuilt mid-suite, or a\n// dev-server request landing on a rebuild, gets a truncated module and fails\n// to parse it. Writing a sibling temporary file and renaming it over the\n// destination closes the window — `rename` is atomic within a filesystem, so a\n// concurrent reader sees either the whole old file or the whole new one.\n\n// Temporary path for an atomic write of `destination`.\n//\n// It has to be a sibling of the destination, because `rename` is only atomic\n// within one filesystem and the system temp directory is often a different\n// mount. The final extension is dropped rather than kept, so the temporary\n// file cannot match a pattern written for the generated file's own extension.\n// That was load-bearing while `matchesWatchedFile` tested its globs\n// unanchored, where a leftover `vars.css.tmp` matched a `*.css` watch; it is\n// belt-and-braces now that the matcher anchors and, like the globber Style\n// Dictionary reads sources with, does not match the leading dot this name\n// already starts with. Both stay, because a temporary file does outlive its\n// rename — when a write fails, and when the process is killed between the two:\n// `SIGINT` under Node's default handler, `SIGKILL`, an OOM kill — and hiding\n// one costs a string. The pid and counter make the name unique, so two writes\n// of the same destination — parallel platforms in one build, or two builds\n// overlapping — never share a temporary file.\nlet temporaryFileCounter = 0\n\n// The destinations this process has swept beside, as the prefix their\n// temporary names share. Module scope, beside the counter, because the volume\n// is module-level too.\nconst sweptTemporaryPrefixes = new Set<string>()\n\n// How old a temporary file has to be before its writer's pid stops vouching\n// for it. A write takes milliseconds, so an hour is generous — and a pid can\n// outlive the process that wrote the file, once the system hands it to an\n// unrelated one.\nconst ABANDONED_TEMPORARY_AGE_MS = 60 * 60 * 1000\n\n// Whether `destination` already holds exactly `rendered`. A rebuild whose\n// inputs did not change renders byte-identical output, and writing that over\n// the destination is a filesystem event the host bundler reacts to — which is\n// the whole of the rebuild loop, since consuming code imports the generated\n// file and every regenerate is therefore a module-graph change.\n//\n// A destination that cannot be read does not, which covers the ordinary case\n// of it not existing yet.\nasync function alreadyHolds(\n destination: string,\n rendered: Buffer,\n): Promise<boolean> {\n try {\n return (await fs.promises.readFile(destination)).equals(rendered)\n } catch {\n return false\n }\n}\n\nfunction alreadyHoldsSync(destination: string, rendered: Buffer): boolean {\n try {\n return fs.readFileSync(destination).equals(rendered)\n } catch {\n return false\n }\n}\n\n// The file a write of `destination` should replace. A rename onto a symbolic\n// link replaces the link itself with a regular file, so the file it pointed at\n// was never updated again — where Style Dictionary's own in-place write goes\n// through the link. The real target is what gets the temporary sibling and the\n// rename, since a link and its target may sit on different filesystems and a\n// rename is only atomic within one. A dangling link is followed to the target\n// it names, which the write then creates; anything else that cannot be\n// resolved — the ordinary case of a file not written yet — is kept as given.\nfunction atomicTargetOf(destination: string): string {\n try {\n return fs.realpathSync(destination)\n } catch {\n try {\n return path.resolve(\n path.dirname(destination),\n fs.readlinkSync(destination),\n )\n } catch {\n return destination\n }\n }\n}\n\n// The bytes a write of `data` leaves on disk, wherever they can be known\n// before anything is written: a string encoded the way `writeFile` encodes\n// it, with the encoding `options` names or utf8, and a buffer as it stands.\n// Style Dictionary only ever hands its volume a string. A stream or an\n// iterable cannot be read without being consumed, so it has none here, and\n// the writer compares what it wrote instead.\nfunction bytesToWrite(\n data: Parameters<typeof fs.promises.writeFile>[1],\n options: Parameters<typeof fs.promises.writeFile>[2],\n): Buffer | undefined {\n if (ArrayBuffer.isView(data)) {\n return Buffer.from(data.buffer, data.byteOffset, data.byteLength)\n }\n\n if (typeof data !== 'string') return undefined\n\n const encoding = typeof options === 'string' ? options : options?.encoding\n\n return Buffer.from(data, encoding ?? 'utf8')\n}\n\nfunction isOlderThan(file: string, ageMs: number): boolean {\n try {\n return Date.now() - fs.statSync(file).mtimeMs > ageMs\n } catch {\n return false\n }\n}\n\n// Signal 0 tests for a process without touching it. Only `ESRCH` says it is\n// gone: `EPERM` is a live process another user owns.\nfunction isRunning(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n } catch (err) {\n return !(\n typeof err === 'object' &&\n err !== null &&\n 'code' in err &&\n err.code === 'ESRCH'\n )\n }\n}\n\n// Gives the temporary file the mode of the file it is about to replace, and\n// its owner when running as root, as `write-file-atomic` does. An in-place\n// write keeps both, and a restricted destination used to come back at the\n// umask default: 0640 became 0644. A destination that does not exist yet has\n// nothing to keep. A hard link to the destination is still replaced rather\n// than updated, because keeping it would mean writing in place, which is the\n// partial-read window the rename exists to close.\nfunction keepExistingMode(temporary: string, target: string): void {\n let existing: fs.Stats\n try {\n existing = fs.statSync(target)\n } catch {\n return\n }\n\n fs.chmodSync(temporary, existing.mode & 0o7777)\n if (process.getuid?.() === 0) {\n fs.chownSync(temporary, existing.uid, existing.gid)\n }\n}\n\n// Removes the temporary files a killed process left beside `destination`, on\n// this process's first write for it. Nothing else ever would: each name\n// carries its writer's pid, so no later run reuses one, and a build directory\n// that is published — a package whose `files` includes it — shipped them.\n//\n// Called ahead of the byte comparison, so it runs whether or not the write\n// changes anything: the killed builds that were measured leaving files were\n// rendering what was already there, and every later write for that\n// destination returns at the comparison.\n//\n// Only the files of writers that are gone, and this process is not one of\n// them: another instance or platform in it, or in another process, may be\n// between its write and its rename, and removing its file would make that\n// rename fail. That is also why no signal handler cleans up instead: a\n// listener on `SIGINT` removes Node's default exit and takes over the host's\n// shutdown, and `SIGKILL` reaches none.\nfunction sweepAbandonedTemporaries(destination: string): void {\n const directory = path.dirname(destination)\n const stem = path.basename(destination, path.extname(destination))\n const prefix = path.join(directory, `.${stem}.`)\n if (sweptTemporaryPrefixes.has(prefix)) return\n sweptTemporaryPrefixes.add(prefix)\n\n let names: string[]\n try {\n names = fs.readdirSync(directory)\n } catch {\n return\n }\n\n for (const name of names) {\n const writer = temporaryWriterOf(name, stem)\n if (writer === undefined) continue\n\n const file = path.join(directory, name)\n if (isRunning(writer) && !isOlderThan(file, ABANDONED_TEMPORARY_AGE_MS)) {\n continue\n }\n\n discardTemporaryFile(file)\n }\n}\n\nfunction temporaryPathFor(destination: string): string {\n const extension = path.extname(destination)\n\n return path.join(\n path.dirname(destination),\n `.${path.basename(destination, extension)}.${process.pid}.${temporaryFileCounter++}.tmp`,\n )\n}\n\n// The pid in `name` when it is a temporary file for a destination named\n// `stem`, as `temporaryPathFor` spells one: `.<stem>.<pid>.<counter>.tmp`.\nfunction temporaryWriterOf(name: string, stem: string): number | undefined {\n if (!name.startsWith(`.${stem}.`) || !name.endsWith('.tmp')) return undefined\n\n const numbers = /^(\\d+)\\.\\d+$/.exec(name.slice(stem.length + 2, -4))\n\n return numbers ? Number(numbers[1]) : undefined\n}\n\n// Windows refuses a rename over a destination another process holds open, and\n// that is exactly the case the atomic write exists to serve: measured on a\n// `windows-latest` runner, the suite's own concurrent-reader case fails with\n// `EPERM: operation not permitted, rename`. So the feature inverts — the\n// compile fails rather than the read being protected.\n//\n// This **refutes** the reasoning that put the case in doubt. It was argued that\n// libuv opens files with `FILE_SHARE_DELETE`, so a concurrent reader would most\n// likely not block the rename. It blocks it.\n//\n// The blocking handle is transient — a reader, an indexer, a virus scanner —\n// so a short bounded backoff clears it. The bound matters as much as the retry:\n// a rename that genuinely cannot succeed has to fail rather than hang a dev\n// server, and the existing failure path already reports and lets `failOnError`\n// decide.\n//\n// Unreachable on Linux and macOS, where a rename over an open file succeeds.\nconst RENAME_RETRY_CODES = new Set(['EBUSY', 'EPERM'])\n\n// Doubling rather than a fixed interval, so the common case — a handle already\n// gone by the first retry — costs a millisecond rather than the whole budget.\n//\n// **The total is sized by what a dev server can tolerate waiting, not by how\n// long a handle usually persists.** An earlier version stopped at about 255ms,\n// which clears a transient hold and is not the situation that matters: a\n// consumer polling the generated file holds it for a large fraction of wall\n// time, and then no budget wins every race. What that produced was a rebuild\n// that failed roughly once in a hundred renames — which the suite's own\n// concurrent-reader case turns into a failure about once a run, because it\n// performs twenty of them. Intermittent, and on a platform where the whole\n// point of the atomic write is that a reader never sees a partial file.\n//\n// About two seconds is therefore the bound. A rebuild that takes a second is\n// something a dev server absorbs; one that fails is not. `write-file-atomic`\n// and `graceful-fs` both take this approach, the latter retrying for up to a\n// minute, so this is still the conservative end.\n//\n// The bound stays a bound: a rename that genuinely cannot succeed has to fail\n// rather than hang, and the unguarded attempt after the loop is what makes it.\nconst RENAME_RETRY_DELAYS_MS = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]\n\n// **The synchronous path gets a shorter budget, and the asymmetry is the\n// point.** `renameWithRetry` waits on a timer, so the event loop keeps serving\n// while it does; `renameWithRetrySync` waits on `Atomics.wait`, which blocks\n// everything — a dev server holding its main thread for two seconds is worse\n// than the failed rebuild the wait is trying to avoid.\n//\n// It is reached only through a Style Dictionary custom action's own\n// `vol.writeFileSync`, which is rare, and it keeps roughly the budget the async\n// path had before this change.\nconst RENAME_RETRY_DELAYS_SYNC_MS = [1, 2, 4, 8, 16, 32, 64, 128]\n\nfunction isRetryableRenameError(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n typeof error.code === 'string' &&\n RENAME_RETRY_CODES.has(error.code)\n )\n}\n\n// `Atomics.wait` rather than a spin on `Date.now()`, because the sync path has\n// no event loop to yield to and a busy loop would hold the CPU for the whole\n// backoff — on the one platform where the handle it is waiting for belongs to\n// another process.\nconst sleepSync = (ms: number): void => {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)\n}\n\nasync function renameWithRetry(\n temporary: string,\n destination: string,\n): Promise<void> {\n for (const delay of RENAME_RETRY_DELAYS_MS) {\n try {\n await fs.promises.rename(temporary, destination)\n return\n } catch (err) {\n if (!isRetryableRenameError(err)) throw err\n await new Promise((resolve) => setTimeout(resolve, delay))\n }\n }\n\n // The last attempt is deliberately outside the loop and unguarded: the bound\n // is a bound, so whatever it throws here is what the caller sees.\n await fs.promises.rename(temporary, destination)\n}\n\nfunction renameWithRetrySync(temporary: string, destination: string): void {\n for (const delay of RENAME_RETRY_DELAYS_SYNC_MS) {\n try {\n fs.renameSync(temporary, destination)\n return\n } catch (err) {\n if (!isRetryableRenameError(err)) throw err\n sleepSync(delay)\n }\n }\n\n fs.renameSync(temporary, destination)\n}\n\nconst writeFileAtomic: typeof fs.promises.writeFile = async (\n file,\n data,\n options,\n) => {\n // A file handle or descriptor is already-open state that a rename cannot\n // stand in for, so only a path is written atomically.\n if (typeof file !== 'string') {\n return fs.promises.writeFile(file, data, options)\n }\n\n // Compared before anything is created, so a rebuild that renders what is\n // already there leaves nothing for a watcher to see — not even a temporary\n // file created and removed again, which a raw directory watcher such as\n // `node --watch-path` reports. A write that does change the destination\n // still goes through a temporary file and a rename, so a concurrent reader\n // still never sees a partial file.\n //\n // Every step works on the destination's real target rather than the path as\n // given — see `atomicTargetOf`.\n const target = atomicTargetOf(file)\n sweepAbandonedTemporaries(target)\n const bytes = bytesToWrite(data, options)\n if (bytes && (await alreadyHolds(target, bytes))) return\n\n const temporary = temporaryPathFor(target)\n\n try {\n await fs.promises.writeFile(temporary, data, options)\n\n // A stream or an iterable is only known once it is on disk, so it is the\n // one write that still creates a temporary file for a destination it\n // leaves unchanged.\n if (\n !bytes &&\n (await alreadyHolds(target, await fs.promises.readFile(temporary)))\n ) {\n discardTemporaryFile(temporary)\n return\n }\n\n keepExistingMode(temporary, target)\n await renameWithRetry(temporary, target)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\nconst writeFileSyncAtomic: typeof fs.writeFileSync = (file, data, options) => {\n if (typeof file !== 'string') {\n fs.writeFileSync(file, data, options)\n return\n }\n\n // `writeFileSync` takes only a string or a buffer, so every write it is\n // given can be compared before anything is created.\n const target = atomicTargetOf(file)\n sweepAbandonedTemporaries(target)\n const bytes = bytesToWrite(data, options)\n if (bytes && alreadyHoldsSync(target, bytes)) return\n\n const temporary = temporaryPathFor(target)\n\n try {\n fs.writeFileSync(temporary, data, options)\n keepExistingMode(temporary, target)\n renameWithRetrySync(temporary, target)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\n// Copies go the same way as writes. Two of Style Dictionary's own actions copy\n// rather than format — `copy_assets` through `promises.cp` and\n// `android/copyImages` through `promises.copyFile` — and both methods used to\n// be inherited from `node:fs`, which copies in place: measured, a reader\n// polling a copied 2 MB asset across 20 rebuilds read a partial file 1352\n// times in 3752. A configuration with actions is never skipped as up to date,\n// so every trigger copied every asset again, and an identical copy is now\n// compared first for the same reason an identical write is.\n//\n// `COPYFILE_EXCL` asks for a failure when the destination exists, which a\n// rename over it would quietly overwrite, so such a copy keeps the inherited\n// behaviour.\nconst copyFileAtomic: typeof fs.promises.copyFile = async (\n source,\n destination,\n mode,\n) => {\n if (typeof destination !== 'string' || excludesExisting(mode)) {\n return fs.promises.copyFile(source, destination, mode)\n }\n\n // A copy takes its source's mode, as `node:fs`'s own does, so only the\n // target is resolved here.\n const target = atomicTargetOf(destination)\n sweepAbandonedTemporaries(target)\n if (await alreadyHolds(target, await fs.promises.readFile(source))) {\n return\n }\n\n const temporary = temporaryPathFor(target)\n\n try {\n await fs.promises.copyFile(source, temporary, mode)\n await renameWithRetry(temporary, target)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\nconst copyFileSyncAtomic: typeof fs.copyFileSync = (\n source,\n destination,\n mode,\n) => {\n if (typeof destination !== 'string' || excludesExisting(mode)) {\n fs.copyFileSync(source, destination, mode)\n return\n }\n\n const target = atomicTargetOf(destination)\n sweepAbandonedTemporaries(target)\n if (alreadyHoldsSync(target, fs.readFileSync(source))) return\n\n const temporary = temporaryPathFor(target)\n\n try {\n fs.copyFileSync(source, temporary, mode)\n renameWithRetrySync(temporary, target)\n } catch (err) {\n discardTemporaryFile(temporary)\n throw err\n }\n}\n\nfunction excludesExisting(mode: number | undefined): boolean {\n return mode !== undefined && (mode & fs.constants.COPYFILE_EXCL) !== 0\n}\n\n// `promises.cp` as a walk over the source that sends each regular file through\n// `copyFileAtomic`. The walk models the call `copy_assets` makes — a directory\n// copied recursively over whatever is there — and hands anything else to the\n// inherited `cp`, which copies or refuses exactly as it always has: an option\n// the walk does not honour, such as `filter` or `dereference`, a destination\n// inside its own source, and, entry by entry, anything that is neither a file\n// nor a directory, such as a symbolic link.\nconst cpAtomic: typeof fs.promises.cp = async (\n source,\n destination,\n options,\n) => {\n if (\n typeof source !== 'string' ||\n typeof destination !== 'string' ||\n !walksCopy(options) ||\n isWithin(destination, source)\n ) {\n return fs.promises.cp(source, destination, options)\n }\n\n // `cp` creates a missing parent, and the walk creates only what it copies.\n await fs.promises.mkdir(path.dirname(path.resolve(destination)), {\n recursive: true,\n })\n await copyTreeAtomic(source, destination, options)\n}\n\nasync function copyTreeAtomic(\n source: string,\n destination: string,\n options: fs.CopyOptions | undefined,\n): Promise<void> {\n const stat = await fs.promises.lstat(source)\n const existing = await fs.promises.lstat(destination).catch(() => undefined)\n\n if (stat.isFile() && !existing?.isDirectory()) {\n await copyFileAtomic(source, destination, options?.mode)\n return\n }\n\n if (\n !stat.isDirectory() ||\n !options?.recursive ||\n (existing && !existing.isDirectory())\n ) {\n await fs.promises.cp(source, destination, options)\n return\n }\n\n // Sequential, as `cp` is, and a directory the walk creates takes its\n // source's mode once its contents are in, as `cp` does it.\n const created = await fs.promises.mkdir(destination, { recursive: true })\n for (const entry of await fs.promises.readdir(source)) {\n await copyTreeAtomic(\n path.join(source, entry),\n path.join(destination, entry),\n options,\n )\n }\n if (created !== undefined) await fs.promises.chmod(destination, stat.mode)\n}\n\nfunction isWithin(candidate: string, directory: string): boolean {\n const relative = path.relative(\n path.resolve(directory),\n path.resolve(candidate),\n )\n\n return !(\n relative === '..' ||\n relative.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relative)\n )\n}\n\n// The options the walk honours: `recursive`, `mode`, and `force` at its\n// default of overwriting.\nfunction walksCopy(options: fs.CopyOptions | undefined): boolean {\n return Object.entries(options ?? {}).every(\n ([key, value]) =>\n value === undefined ||\n key === 'mode' ||\n key === 'recursive' ||\n (key === 'force' && value === true),\n )\n}\n\n// `node:fs` with its write and copy entry points swapped for atomic\n// equivalents, handed to Style Dictionary as the volume it builds through:\n// `promises.writeFile`, `writeFileSync`, `promises.copyFile`, `copyFileSync`\n// and `promises.cp`. Everything else — reads, `mkdir`, `access`, the rest of\n// the `promises` namespace — is inherited from `node:fs` unchanged, so only the\n// moment a file becomes visible to readers changes. Custom actions receive this\n// volume too, so a file they emit through one of those five is written the\n// same way. `appendFile`, `createWriteStream`, `cpSync` and the callback forms\n// of `writeFile`, `copyFile` and `cp` are not among them, and write in place.\n//\n// It is assigned onto the instance rather than passed as the `volume`\n// constructor option on purpose: that option marks the volume as a custom\n// filesystem shim, which switches Style Dictionary's path resolution off for\n// every read as well.\n// `Object.create` is declared as returning `any`, so pinning the result to\n// `typeof fs` is a claim no type guard can replace. The prototype link is the\n// whole point — see the note above — so rebuilding this with a spread, which\n// copies own properties and drops the chain, is not a substitute.\n/* oxlint-disable typescript/no-unsafe-type-assertion */\nconst atomicVolume = Object.create(fs, {\n copyFileSync: { value: copyFileSyncAtomic },\n promises: {\n value: Object.create(fs.promises, {\n copyFile: { value: copyFileAtomic },\n cp: { value: cpAtomic },\n writeFile: { value: writeFileAtomic },\n }) as typeof fs.promises,\n },\n writeFileSync: { value: writeFileSyncAtomic },\n}) as typeof fs\n/* oxlint-enable typescript/no-unsafe-type-assertion */\n\n// Which configuration last wrote each destination, as the fingerprint of that\n// configuration keyed by the file. It is what lets a configuration given as an\n// object or a function be skipped at all: such a configuration has no file to\n// stat, so an edit to it inside `vite.config.ts` moves no mtime and the\n// filesystem cannot tell the two apart. Having built it here, the plugin can —\n// the fingerprint changes with the configuration.\n//\n// Beside the fingerprint sits the newest source that compile saw before it read\n// anything. That, rather than the output's own mtime, is what the next check\n// measures \"changed since\" against, so a save landing mid-compile is not\n// mistaken for one the compile read — see `isUpToDate`.\n//\n// Keyed by destination rather than kept as the set of everything ever built,\n// because the question is what the file on disk holds now. A set answered\n// \"was this built at some point\", which an edit that was then undone, or a\n// build that threw halfway through its platforms, both answer wrongly: the\n// file holds the edit either way. It also grew with every distinct\n// configuration, where this is bounded by the number of destinations.\n//\n// Module scope rather than the factory's, for the same reason `compilesInFlight`\n// below is: the instances that would otherwise repeat the work are different\n// instances, so per-instance state cannot see them. A `vitest run` stands up\n// several, and a function configuration — the form the README recommends for\n// registering custom formats — would be the one form that never skipped. Every\n// compile forgets the files it is about to write, whether or not `cache` is on:\n// an instance that rewrote a file without that would leave another instance\n// vouching for output no longer its own. Only a compile with `cache` on records\n// them again, since only it has read the configuration a fingerprint is taken\n// from.\n//\n// The fingerprint carries the root, so two projects in one process never share\n// one. A configuration given as a path needs no fingerprint to be skipped: its\n// own file is one of the sources, so an edit to it is visible across processes\n// as well as within one.\n//\n// It outlives the process through a copy persisted beside each project's other\n// build caches, loaded the first time a compile runs for that root. Without it\n// a process's first compile could only compare the sources against the output,\n// and after one edit left any destination byte-identical — so its mtime stayed\n// behind — that comparison never skipped again. Where nothing is recorded, in\n// memory or on disk, it is still what the check falls back on.\nconst destinationRecords = new Map<string, DestinationRecord>()\n\n// The file each root's records persist to, once this process has loaded it.\n// Being in here is what says the load has happened.\nconst persistedRecordFiles = new Map<string, string>()\n\n// A compile that is running right now, keyed by `buildKey`, so bundler\n// instances in one process wait on each other rather than each starting their\n// own.\n//\n// Generated token files are a side effect on the filesystem, not per-bundler\n// output, and one process routinely holds several instances of this plugin. A\n// single `vitest run` on a project with two test projects and browser mode\n// stands up five Vite servers — the root one, one per project, and one more\n// per project once its HTTP server listens — and every one of them runs\n// `buildStart`. `hasCompiled` cannot see any of that: it is closure state\n// inside the factory, so each instance has its own and each compiles.\n//\n// Module scope is the only place a shared answer can live, since the\n// instances know nothing about each other. It stays a claim about identical\n// work, never about identity: the key carries the root, the resolved\n// configurations, and every setting of the instance that changes what the\n// compile writes or whether it fails — `platforms`, `cache`, `failOnError`,\n// `watch` and the three `onBuild*` hooks. So one script building two\n// packages shares nothing, and neither do two instances on one configuration\n// asking for different builds: a waiter returns without running `runBuilds`,\n// so its own selection, its hooks and its decision to fail used to be dropped\n// in favour of the first instance's. What only changes what is printed —\n// `logLevel`, `report`, `silent` — stays out, since a waiter printing nothing\n// is part of sharing.\nconst compilesInFlight = new Map<string, Promise<void>>()\n\n// The slice of a webpack-shaped compiler this plugin touches, named rather\n// than imported. webpack and rspack each ship their own `Compiler` type, and\n// neither is assignable to the other, so a hook written against one cannot be\n// handed to the other's key. Both satisfy this structurally, which is what\n// makes `adoptCompiler` one function instead of two copies drifting apart.\ninterface BundlerCompiler {\n hooks: {\n compilation: {\n tap: (name: string, handler: (compilation: Compilation) => void) => void\n }\n done: { tap: (name: string, handler: () => void) => void }\n failed: { tap: (name: string, handler: () => void) => void }\n run: { tapPromise: (name: string, handler: () => Promise<void>) => void }\n watchRun: {\n tapPromise: (name: string, handler: () => Promise<void>) => void\n }\n }\n // Set by a watcher before `watchRun`, and left unset for its first compile.\n modifiedFiles?: ReadonlySet<string> | undefined\n options: { context?: string | undefined; mode?: string | undefined }\n removedFiles?: ReadonlySet<string> | undefined\n watchMode: boolean\n}\n\n// Only the one array a report is pushed onto. webpack types it `WebpackError[]`\n// and rspack `Error[]`; both are arrays of something extending `Error`, so a\n// plain one is what this pushes on either.\ninterface Compilation {\n errors: Error[]\n warnings: Error[]\n}\n\n// What a hook's plugin context offers for registering watched paths. Every\n// host's has `addWatchFile`; webpack's and rspack's also reach the compilation\n// through `getNativeBuildContext`, and the unit tests bind neither.\ninterface WatchContext {\n addWatchFile: (id: string) => void\n getNativeBuildContext?: () => unknown\n}\n\n// A stable identity for a compile — a set of resolved configurations and the\n// settings it runs with — or `null` for one that cannot have a stable\n// identity at all.\n//\n// Functions are serialised by source rather than dropped, because a `format`\n// or `transform` written inline is exactly what distinguishes two otherwise\n// identical configurations — and `JSON.stringify` omits a function outright,\n// which would make two different builds look like one. The `onBuild*` hooks\n// among the settings go the same way.\nfunction buildKey(\n root: string,\n resolved: ResolvedConfig[],\n settings: Record<string, unknown>,\n): null | string {\n try {\n return JSON.stringify(\n [root, resolved.map((item) => item.file ?? item.config), settings],\n (_key, value: unknown) =>\n typeof value === 'function' ? `[fn]${String(value)}` : value,\n )\n } catch {\n // A configuration that will not serialise — a circular reference, a\n // BigInt — takes no shared identity rather than a wrong one, and compiles\n // exactly as it did before.\n return null\n }\n}\n\n// The `contextDependencies` of the compilation a webpack or rspack hook is\n// running for, reached through the native context unplugin hands it — the\n// one set there that reports a file created in a watched directory.\nfunction contextDependenciesOf(\n context: WatchContext,\n): undefined | { add: (path: string) => void } {\n const native = context.getNativeBuildContext?.()\n const compilation: unknown =\n typeof native === 'object' && native !== null && 'compilation' in native\n ? native.compilation\n : undefined\n const dependencies: unknown =\n typeof compilation === 'object' &&\n compilation !== null &&\n 'contextDependencies' in compilation\n ? compilation.contextDependencies\n : undefined\n\n if (\n typeof dependencies !== 'object' ||\n dependencies === null ||\n !('add' in dependencies)\n ) {\n return undefined\n }\n\n const add: unknown = dependencies.add\n if (typeof add !== 'function') return undefined\n\n return {\n add: (entry: string) => {\n Reflect.apply(add, dependencies, [entry])\n },\n }\n}\n\nfunction isDirectory(file: string): boolean {\n try {\n return fs.statSync(file).isDirectory()\n } catch {\n return false\n }\n}\n\n// Not exported. It cannot be called in the form a reader would guess —\n// unplugin types the factory as `(options, meta)`, and `meta` is the\n// bundler-identifying `UnpluginContextMeta` a consumer would have to build by\n// hand — so publishing it offered a name that answered nothing. What a\n// consumer imports is the default export of the entry for their bundler.\nconst unpluginFactory: UnpluginFactory<\n undefined | UnpluginStyleDictionaryOptions,\n false\n> = (options = {}, meta) => {\n // webpack and rspack are the targets whose `buildStart` does not run before\n // the module graph is resolved: unplugin taps it on `make`, an\n // `AsyncParallelHook` that `EntryPlugin` taps too. The `webpack` and\n // `rspack` keys below compile on `run` and `watchRun` instead, which both\n // await before the compilation exists. rspack reimplements webpack's plugin API,\n // so everything this plugin does with a compiler is the same on either —\n // but unplugin dispatches them by separate keys, so the flag names both.\n const isWebpack = meta.framework === 'webpack' || meta.framework === 'rspack'\n\n // Before anything reads them. unplugin calls this as soon as a Vite, rollup\n // or rolldown config calls the plugin, and from `apply(compiler)` under\n // webpack and rspack, so a wrong value throws before any build starts.\n checkOptions(options)\n\n const {\n cache = true,\n errorOverlay = true,\n failOnError = 'build',\n logLevel,\n onBuildEnd,\n onBuildError,\n onBuildStart,\n platforms: platformsOption,\n report = true,\n root: rootOption,\n silent = false,\n } = options\n\n // `silent` predates `logLevel` and names its quietest level, so it is read\n // as one. `logLevel` wins when a consumer sets both.\n const level = logLevel ?? (silent ? 'silent' : undefined)\n\n // Whether the plugin keeps its own progress lines and size table to itself.\n // A failure is reported at every level, which is why this gate is not on the\n // error branch below.\n const quiet = level === 'silent' || level === 'warn'\n\n // What Style Dictionary is told, if anything. `undefined` is the point of\n // this: it leaves whatever the consumer's own `log.verbosity` asked for\n // standing, where the plugin used to overwrite it on every build. Style\n // Dictionary has three levels to this option's four, so `'warn'` and\n // `'info'` both map to its default — they differ in what the plugin itself\n // says, not in what Style Dictionary does.\n const verbosity =\n level === undefined\n ? undefined\n : level === 'verbose'\n ? 'verbose'\n : level === 'silent'\n ? 'silent'\n : 'default'\n\n // What the host is doing, for the function form of `config`. Populated where\n // each host knows the answer and read when that function is called — the\n // same shape as `root` and the message host above, and for the same reason:\n // `resolveConfigs` is reached from every hook that builds or watches, and\n // threading a context parameter through each of them would make every caller\n // restate what only the host can say.\n let hostCommand: 'build' | 'serve' = 'build'\n let hostMode: string | undefined\n let isWatching = false\n\n // `mode` is derived rather than invented where a host has no notion of one.\n // rollup and rolldown report nothing, and following `command` is the answer\n // Vite itself would give: its default mode is `development` serving and\n // `production` building.\n const configContext = (): StyleDictionaryConfigContext => ({\n command: hostCommand,\n mode: hostMode ?? (hostCommand === 'serve' ? 'development' : 'production'),\n watch: isWatching,\n })\n\n // Whether the host will keep rebuilding, as the plugin context reports it.\n // Read from `meta.watchMode`, which rollup, rolldown and Vite all carry and\n // webpack does not — there it comes off the compiler instead.\n const adoptWatchMode = (context: object): void => {\n const hookMeta: unknown = 'meta' in context ? context.meta : undefined\n if (typeof hookMeta !== 'object' || hookMeta === null) return\n\n const watching: unknown =\n 'watchMode' in hookMeta ? hookMeta.watchMode : undefined\n if (typeof watching === 'boolean') isWatching = watching\n }\n\n // Where a relative `config` path is looked up. The host sets it below\n // unless the consumer named one, which is why an explicit option wins: a\n // layout the host cannot describe is exactly what it is for.\n let root = rootOption\n ? path.resolve(process.cwd(), rootOption)\n : process.cwd()\n\n // Every absolute destination the last completed build wrote, spelled with\n // forward slashes so it compares against a normalised watcher path. This is\n // the half of the rebuild-loop guard that pattern matching cannot supply:\n // output written under a watched directory matches the source glob that\n // produced it, so without subtracting this set a supported layout rebuilds\n // on its own writes for as long as the dev server runs.\n const generatedDestinations = new Set<string>()\n\n // The patterns the last `getWatchTargets` derived, compiled. `watchChange`\n // tests a changed path against these before it resolves anything, so a file\n // the plugin does not care about costs one match against an already compiled\n // matcher instead of a full config resolution — which, when `config` is a\n // function, is the consumer's own code, and the place the README tells them\n // to register custom formats.\n //\n // It is safe to filter on a list that may be one build out of date because\n // the list always contains the config files themselves: an edit that adds a\n // source matches as a config change, which re-resolves and re-derives. The\n // one thing it cannot see is a `config` function that starts returning\n // different sources with no file changing at all, and that was never\n // observable without a rebuild to observe it in.\n let cachedMatcher: undefined | WatchMatcher\n\n // Every registered path that runs through a symbolic link, keyed by where\n // the link leads. On macOS rolldown's watcher drops the events for a path\n // registered through a link — a workspace package linked into\n // `node_modules`, or any project under `/var` or `/tmp` — so a token edit\n // there built once and never again, and said nothing. Registering the\n // realpath beside the path is what reaches that watcher, and the event then\n // arrives spelled by the realpath, which neither the patterns nor\n // `generatedDestinations` use. `watchChange` turns it back through this map\n // before asking either.\n const linkedPaths = new Map<string, string>()\n\n // Hands `paths` to the host's watcher, and beside each one reached through a\n // link, the path the link leads to. Where the link already delivers events,\n // as under rollup, both spellings report one edit and `schedule` collapses\n // the pair. Only the `addWatchFile` sites come through here: the list\n // `getWatchTargets` returns also feeds the dev server's watcher and its\n // negations, which see through a link as they are.\n //\n // On webpack and rspack a directory goes to `contextDependencies` instead,\n // and is remembered in `watchedDirectories`. unplugin turns `addWatchFile`\n // into `fileDependencies.add`, and a directory registered as a file\n // dependency reports no entry created inside it — so a token file added\n // under a glob `source` was never picked up there, though the static parent\n // is registered for exactly that.\n const addWatchFiles = (context: WatchContext, paths: string[]): void => {\n const contextDependencies = isWebpack\n ? contextDependenciesOf(context)\n : undefined\n\n const register = (file: string) => {\n if (contextDependencies && isDirectory(file)) {\n contextDependencies.add(path.resolve(file))\n watchedDirectories.add(file)\n return\n }\n\n context.addWatchFile(file)\n }\n\n for (const file of paths) {\n register(file)\n\n const real = realpathOf(file)\n if (real === undefined || real === file) continue\n\n linkedPaths.set(real, file)\n register(real)\n }\n }\n\n // The directories registered as context dependencies, spelled as\n // registered. webpack's watcher reports a change inside one as a change to\n // the directory rather than to the entry, so the `watchRun` filter counts a\n // match here as a token source changing.\n const watchedDirectories = new Set<string>()\n\n // Whether `watchChange` has fired since the last `buildStart`, and whether\n // anything has been compiled yet. Rollup, rolldown and webpack all run\n // `watchChange` for every changed file and only then re-enter `buildStart`\n // — unplugin's webpack adapter awaits both in one `make` tap — so a flag\n // raised in the first is still standing in the second, and is what tells it\n // this is a watch rebuild rather than the first build of the process. Vite's\n // dev server runs `watchChange` too and never re-enters, which is why\n // `buildStart` ignores the flag there.\n let watchRebuild = false\n let hasCompiled = false\n\n // Whether the host has shut its watcher down. `await watcher.close()` is not\n // a promise that no build is in flight: rollup's `Watcher.close` clears the\n // pending build timeout, closes each task's file watcher and emits `close`,\n // and never awaits `run` — while `Task.run` checks `closed` only *after*\n // `rollupInternal` has resolved. So a build that has already entered\n // `rollupInternal` runs its `buildStart` hooks through to completion after\n // `close()` has returned to its caller, against a project that may be half\n // torn down by then. Measured on rollup 4.63.3 with no plugin of ours: a\n // `buildStart` reading a file 300ms after `close()` resolved gets ENOENT.\n //\n // `closeWatcher` is what makes that answerable. It runs synchronously inside\n // `close()`, and so before the in-flight hook resumes, which is the whole\n // reason a flag set there is worth setting. What it must not be is\n // `closeBundle`: that fires once per bundle — every `BUNDLE_END` a consumer\n // calls `result.close()` on — and would read as a shutdown on every rebuild.\n let hostClosed = false\n\n // Decided once, when the plugin is constructed, and held for its life. The\n // two streams are asked separately because they are redirected separately —\n // `build 2>err.log` leaves stdout a terminal and stderr a file.\n const stdoutColour = colourAllowed(process.stdout)\n const stderrColour = colourAllowed(process.stderr)\n\n // Where a message goes once a host has offered somewhere better than the\n // console. Set by `configResolved` under Vite, by the build hooks under\n // rollup and rolldown, and by the `webpack` block; left undefined when no\n // host has claimed it, which is every unit test binding its own context.\n let host: HostMessenger | undefined\n\n // Adopts a plugin context as the message host, if it has the channels — the\n // unit tests bind a context carrying `addWatchFile` and nothing else, and a\n // hook calling `this.warn` against that throws in a way that reads as a\n // plugin bug rather than as a missing stub.\n //\n // Only when nothing has claimed the host yet. Under Vite `configResolved`\n // has already installed the dev server's own logger, and `buildStart` runs\n // after it with a rollup-shaped context that would otherwise replace it.\n const adoptHost = (context: object): void => {\n if (host) return\n\n const warn: unknown = 'warn' in context ? context.warn : undefined\n if (!isMessageChannel(warn)) return\n\n const info: unknown = 'info' in context ? context.info : undefined\n\n host = {\n // `warn`, never `error`. Rollup's `this.error` aborts the bundle, so\n // reporting through it would stop every build that reported anything and\n // take the decision `failOnError` exists to make.\n error: (message) => {\n warn.call(context, message)\n },\n info: isMessageChannel(info)\n ? (message) => {\n info.call(context, message)\n }\n : undefined,\n }\n }\n\n // Helper to log at the configured level\n const log = (\n message: string,\n type: 'error' | 'info' | 'success' = 'info',\n ) => {\n const prefix = '[unplugin-style-dictionary]'\n\n // Ahead of the `silent` gate on purpose. `silent` is about the progress\n // lines and the size table; a compile that failed is not noise, and\n // hiding it left a broken token set shipping with nothing said at all.\n if (type === 'error') {\n // The host renders and colours its own output, so nothing painted here\n // is handed to one — an escape inside a webpack `stats` entry survives\n // into `stats.toJson()` and into whatever reads it.\n if (host) {\n host.error(`${prefix} ${message}`)\n return\n }\n\n console.error(paint('31', `${prefix} ${message}`, stderrColour))\n return\n }\n\n if (quiet) return\n\n if (host?.info) {\n host.info(`${prefix} ${message}`)\n return\n }\n\n console.log(\n paint(\n type === 'success' ? '32' : '36',\n `${prefix} ${message}`,\n stdoutColour,\n ),\n )\n }\n\n // The size table, by the route `log` gives an `'info'` line and without its\n // prefix, which would push every row out of line with the next. It takes a\n // renderer rather than finished lines because only this end knows where they\n // go, and so whether to paint them: nothing painted is handed to a host.\n //\n // Under webpack and rspack there is no `info` channel, so the table goes to\n // the console beside the progress lines, as README documents.\n const logTable = (render: (colour: boolean) => string[]): void => {\n if (host?.info) {\n for (const line of render(false)) host.info(line)\n return\n }\n\n for (const line of render(stdoutColour)) console.log(line)\n }\n\n // What this instance has said about its configuration. A problem with one\n // is found on every resolution, and a watching host resolves on every\n // rebuild — three times during a Vite start-up alone — so without this each\n // one repeats at `'error'`, which `'silent'` prints and webpack files as a\n // warning. Cleared when a compile succeeds, so a problem that is fixed and\n // comes back is said again.\n //\n // Kept here, beside `discovery`, because `config.ts` serves every instance\n // in the process and holds nothing.\n const saidAboutConfig = new Set<string>()\n const logConfig: Log = (message, type) => {\n if (saidAboutConfig.has(message)) return\n saidAboutConfig.add(message)\n log(message, type)\n }\n\n // Resolve config file paths / objects. The work is `resolveConfigOption`'s;\n // what this adds is the instance it runs for, read at the moment of the call.\n // `root` is assigned by the host after the factory has run, so it is passed\n // as it stands now rather than as it stood when this was built.\n //\n // A configuration read from a file also carries the files `watch` names,\n // which key a config module's import — see `ResolvedConfig`. Directories\n // are left out, because a directory's mtime moves whenever an entry is\n // renamed inside it and so says nothing about what the config would read.\n const resolveConfigs = async (): Promise<ResolvedConfig[]> => {\n const resolved = await resolveConfigOption({\n config: options.config,\n configContext,\n discovery,\n log: logConfig,\n root,\n })\n if (!options.watch) return resolved\n\n const watched = (\n await expandPatterns(watchOptionPatterns(root, options.watch), log)\n ).filter((file) => !isDirectory(file))\n\n // Each call resolves fresh items, so they are this call's to annotate.\n for (const item of resolved) {\n if (item.file) item.watched = watched\n }\n\n return resolved\n }\n\n // The watch list's patterns, compiled, without walking the filesystem for\n // what they match today — which is all a decision about one changed path\n // needs, and the walk is the expensive half on a large token set.\n const deriveWatchPatterns = async (\n resolvedConfigs: ResolvedConfig[],\n ): Promise<{ matcher: WatchMatcher; patterns: WatchPatterns }> => {\n const patterns = await watchPatternsOf(\n { log: logConfig, root, watch: options.watch },\n resolvedConfigs,\n )\n\n // Compiled once per derived list rather than on every event, and recorded\n // here rather than at each call site, so every path that derives a watch\n // list refreshes the one `watchChange` filters against.\n const matcher = compileWatchPatterns(patterns)\n cachedMatcher = matcher\n\n return { matcher, patterns }\n }\n\n // Parse token files to watch\n const getWatchTargets = async (\n resolvedConfigs: ResolvedConfig[],\n ): Promise<{\n matcher: WatchMatcher\n paths: string[]\n patterns: WatchPatterns\n }> => {\n const { matcher, patterns } = await deriveWatchPatterns(resolvedConfigs)\n\n return { matcher, paths: await expandPatterns(patterns, log), patterns }\n }\n\n // What `runBuilds` reads from this plugin instance, built once. `root` and\n // the overlay callback go in as functions rather than values, because both\n // are assigned after this runs — see `PluginInstance`.\n const instance: PluginInstance = {\n cache,\n destinationRecords,\n failOnError,\n generatedDestinations,\n log,\n logTable,\n notifyBuildOutcome: (error) => {\n if (!error) saidAboutConfig.clear()\n notifyBuildOutcome?.(error)\n },\n onBuildEnd,\n onBuildError,\n onBuildStart,\n persistedRecordFiles,\n platformsOption,\n quiet,\n report,\n root: () => root,\n verbosity,\n volume: atomicVolume,\n watch: options.watch,\n }\n\n // `runBuilds` for the first build of a process, with the compile shared\n // between every plugin instance that wants the same one.\n //\n // An instance arriving while a compile for the same key is running waits on\n // that compile instead of starting a second. It is the concurrent half that\n // needs this: an up-to-date check compares what is on disk against the\n // sources, and two instances that start together have nothing on disk to\n // compare against yet, so only a shared promise can tell them apart from\n // two genuinely separate builds.\n //\n // The entry is dropped as soon as the compile settles, so this coalesces\n // rather than caches — a later `buildStart` still compiles. Skipping one\n // whose output is already current is #212's up-to-date check, and belongs\n // with it rather than as a second mechanism here.\n //\n // A rejection reaches every waiter, which is the point: an instance that\n // waited on a failed compile must not carry on as though the tokens were\n // written. Whether that rejection is thrown at all is `failOnError`'s\n // decision, already made inside `runBuilds`.\n const compileOnceAcrossInstances = async (\n resolvedConfigs: ResolvedConfig[],\n ): Promise<void> => {\n const key = buildKey(root, resolvedConfigs, {\n cache,\n failOnError,\n onBuildEnd,\n onBuildError,\n onBuildStart,\n platforms: platformsOption,\n watch: options.watch,\n })\n if (key === null) {\n await runBuilds(instance, resolvedConfigs)\n return\n }\n\n const running = compilesInFlight.get(key)\n if (running) {\n await running\n return\n }\n\n const compile = runBuilds(instance, resolvedConfigs)\n compilesInFlight.set(key, compile)\n\n try {\n await compile\n } finally {\n compilesInFlight.delete(key)\n }\n }\n\n // Set by `configureServer`. A dev server's watcher is long-lived, so its\n // list has to follow a configuration that changes; every other target\n // re-registers on each build through `addWatchFile` instead.\n let refreshServerWatchList:\n | ((resolved: ResolvedConfig[]) => Promise<void>)\n | undefined\n\n // Also set by `configureServer`: closes the `node_modules` watchers it\n // opened. Called from `buildEnd` rather than registered on the server's own\n // shutdown, because nothing on the server fires in every mode — see the\n // `vite` block's `buildEnd`.\n let closeOwnWatchers: (() => void) | undefined\n\n // Whether the discovered path has been announced. Once per plugin instance:\n // `resolveConfigs` runs on every build and rebuild, and a dev server would\n // otherwise repeat the line for the rest of the session.\n //\n // An object rather than a flag, because it is handed to\n // `resolveConfigOption` to set: that function serves every instance in the\n // process, so the record of what this one has said has to stay here.\n const discovery = { announced: false }\n\n // Resolved by `configResolved` so it can amend the watcher's ignore list, and\n // handed to `configureServer` rather than resolved again — one start-up, one\n // call of the consumer's `config` function.\n let startupResolved: ResolvedConfig[] | undefined\n\n // Also set by `configureServer`, and left undefined everywhere else: this is\n // how a compile outcome reaches Vite's error overlay. It is deliberately not\n // the same path as `failOnError`.\n //\n // `failOnError` decides whether the host stops; this decides whether the\n // browser is told. Under a dev server the default is not to stop, so the\n // failure is reported and swallowed — and that is exactly the case where the\n // page is left rendering the last good file with nothing to say it is stale.\n // Reading the outcome off whether `runBuilds` threw would therefore see\n // nothing at all on the only configuration that matters.\n let notifyBuildOutcome: ((error: Error | null) => void) | undefined\n\n // A rebuild, as the scheduler runs it: resolve, compile, and bring the dev\n // server's watch list up to date. A failure is rethrown once it has been\n // reported, so the scheduler can hand it to every trigger this covered.\n const rebuild = async (reason: string): Promise<void> => {\n let compiling = false\n\n try {\n const resolved = await resolveConfigs()\n if (resolved.length > 0) {\n compiling = true\n await runBuilds(instance, resolved, reason)\n compiling = false\n hasCompiled = true\n await refreshServerWatchList?.(resolved)\n }\n } catch (err) {\n // `runBuilds` reports its own failure before rethrowing, so only the\n // other things that can throw here — a `config` function of the\n // consumer's that raises, a watch list that cannot be rebuilt — need\n // reporting. They reach the overlay for the same reason: from the\n // page's point of view the rebuild failed, whichever half of it did.\n if (!compiling) {\n log(`Rebuild failed: ${errorMessage(err)}`, 'error')\n notifyBuildOutcome?.(asError(err))\n }\n\n throw err\n }\n }\n\n // One rebuild per burst of watcher events, and never two at once — see\n // `createScheduler`. Created here, once per plugin instance: a debounce two\n // instances shared would run one instance's rebuild and drop the other's.\n const schedule = createScheduler({\n debounceMs: 50,\n isClosed: () => hostClosed,\n run: rebuild,\n })\n\n // Every host that runs a rollup-shaped watcher calls this on shutdown, and\n // all three get the same handler below. There is deliberately no webpack\n // equivalent here: it has no `closeWatcher`, its nearest thing is\n // `compiler.hooks.watchClose`, and nothing measured shows it exposed.\n //\n // It raises the flag and nothing else. A debounce timer armed before the\n // close is deliberately left to fire: the rebuild it runs is one the host\n // asked for while the project was still whole, and `rebuild` reports its\n // own failures. Cancelling it would be a guard no test could fail on, since a\n // trigger arriving after the close is declined by `schedule` instead.\n const closeWatcher = (): void => {\n hostClosed = true\n }\n\n // What both webpack-shaped hosts do with a compiler, written once.\n // rspack reimplements webpack's plugin API hook for hook, but ships its\n // own `Compiler` type and unplugin dispatches the two through separate\n // keys — so a function typed against either one rejects the other. Naming\n // the surface actually used is what lets one implementation serve both.\n //\n // unplugin calls this inside `apply(compiler)`, one line before it taps\n // `make`, so the root is in place before the first compile. Without it a\n // webpack build whose `context` is not the working directory looked for\n // the configuration in the wrong place and reported ENOENT.\n const adoptCompiler = (compiler: BundlerCompiler): void => {\n if (rootOption === undefined) {\n root = compiler.options.context ?? process.cwd()\n }\n\n // webpack's `buildStart` context carries no `meta`, so neither half of\n // the build context can come from there. `mode` is a webpack option, and\n // `watchMode` is only true once `watch()` has been called — which is\n // after this runs, so it is read per compile below rather than here.\n hostMode = compiler.options.mode\n\n // The compile happens in `run` and `watchRun`, which webpack awaits\n // *before* the compilation exists — so a message from it has nothing to\n // attach to yet and is held until one appears.\n //\n // Only failures are routed. `stats` carries warnings and errors and\n // nothing else, so the progress lines stay on the console rather than\n // being reported as warnings they are not.\n //\n // A warning rather than an error, for the same reason as on rollup: this\n // is the report, and `failOnError` decides separately whether the build\n // stops. Pushing to `compilation.errors` would fail a webpack build that\n // asked not to be failed.\n const pending: string[] = []\n host = {\n error: (message) => {\n pending.push(message)\n },\n }\n\n // A watch recompile that `failOnError` says should fail, held for the\n // compilation it is about to create. Thrown from `watchRun`, it reaches\n // the same `_done` a first build's failure does, which never re-arms the\n // watcher — so a broken token edit would end the watch session, and no\n // later edit of any kind would rebuild. As an entry in\n // `compilation.errors` it fails that recompile in `stats` and leaves the\n // watcher running.\n let heldFailure: Error | undefined\n\n compiler.hooks.compilation.tap(\n 'unplugin-style-dictionary',\n (compilation) => {\n for (const message of pending.splice(0)) {\n const reported = new Error(message)\n reported.name = 'UnpluginStyleDictionaryWarning'\n compilation.warnings.push(reported)\n }\n\n if (heldFailure) {\n compilation.errors.push(heldFailure)\n heldFailure = undefined\n }\n },\n )\n\n // A `run` or `watchRun` that throws ends the run without ever creating a\n // compilation, and that is exactly the case that produced the message.\n // Left to the buffer it would be reported nowhere at all, so whatever is\n // still held when the run ends goes to the console after all.\n const drainToConsole = () => {\n for (const message of pending.splice(0)) {\n console.error(paint('31', message, stderrColour))\n }\n }\n compiler.hooks.failed.tap('unplugin-style-dictionary', drainToConsole)\n compiler.hooks.done.tap('unplugin-style-dictionary', drainToConsole)\n\n // `run` and `watchRun` are awaited before `compile()`, so the tokens are\n // on disk before webpack resolves the module that imports them, and\n // `watchRun` fires for every compile a watcher runs, the first included —\n // a watch rebuild needs the same guarantee, and a compile that renders\n // what is already there skips its own write.\n //\n // Not `beforeCompile`, which is where this used to be: a child compiler\n // inherits every parent tap but `make`, `compile`, `emit`, `afterEmit`,\n // `invalid`, `done` and `thisCompilation`, so html-webpack-plugin's child\n // ran the whole pipeline a second time per build — the consumer's\n // `config` function, both hooks, and a second compile under\n // `cache: false`. `runAsChild` calls `compile()` directly and reaches\n // neither of these. Returning early on `compiler.isChild()` cannot do the\n // same job: the child runs the parent's tap, which closes over the\n // parent's `compiler`, so it answers `false` either way.\n const compileTokens = async () => {\n isWatching = compiler.watchMode\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n await compileOnceAcrossInstances(resolved)\n hasCompiled = true\n }\n // The token source a watch recompile is for, spelled the way the patterns\n // spell it, or `undefined` when none changed — an edit to application\n // code, or this plugin's own output. Before a build has derived a watch\n // list there is nothing to filter against, so any change counts.\n const changedSource = (): string | undefined => {\n const changed = [\n ...(compiler.modifiedFiles ?? []),\n ...(compiler.removedFiles ?? []),\n ]\n\n for (const file of changed) {\n const id = registeredSpellingOf(file, linkedPaths)\n if (\n !cachedMatcher ||\n watchedDirectories.has(id.replace(/\\\\/g, '/')) ||\n isWatchedSource(id, cachedMatcher, generatedDestinations)\n ) {\n return id\n }\n }\n\n return undefined\n }\n\n // A watcher's later compiles are rebuilds, and every one of them used to\n // reach `runBuilds` without a `context`, which reads as a first build: the\n // default `failOnError: 'build'` threw on a broken token edit and ended the\n // watch session, `platforms.watch` was never used, `failOnError: 'serve'`\n // failed nothing, and an edit to application code called the consumer's\n // `config` function and fired both hooks. Its first compile has no\n // `modifiedFiles`, and is a first build like any other.\n const rebuildTokens = async () => {\n if (!hasCompiled || compiler.modifiedFiles === undefined) {\n await compileTokens()\n return\n }\n\n isWatching = compiler.watchMode\n\n const changed = changedSource()\n if (changed === undefined) return\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n try {\n await runBuilds(instance, resolved, path.basename(changed))\n } catch (err) {\n heldFailure = asError(err)\n }\n }\n\n compiler.hooks.run.tapPromise('unplugin-style-dictionary', compileTokens)\n compiler.hooks.watchRun.tapPromise(\n 'unplugin-style-dictionary',\n rebuildTokens,\n )\n }\n\n return {\n async buildStart() {\n adoptHost(this)\n adoptWatchMode(this)\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Register token/config files with the host bundler's watch mode.\n // Works out of the box wherever the host runs a persistent watcher\n // (e.g. `rollup --watch`). Vite's dev server is additionally handled\n // below via the `vite.configureServer` escape hatch — not because\n // `watchChange` is missing there, which it is not on any Vite this\n // package supports, but because the declared peer range is wider than\n // what has been measured and the scheduler above makes a duplicate\n // trigger free.\n //\n // Skipped outright once the host has closed, because rollup discards the\n // result: with the task closed, `Task.run` returns before\n // `updateWatchedFiles`, so every path registered here goes nowhere. What\n // deriving it does still do is read each config file — with\n // its errors reported — and report an ENOENT for a project the host is\n // in the middle of tearing down. That report was the one thing this\n // block contributed after a close.\n if (!hostClosed) {\n const { paths } = await getWatchTargets(resolved)\n addWatchFiles(this, paths)\n }\n\n // Registering the watch list is all this hook does on webpack, and it\n // has to happen here rather than beside the compile: `addWatchFile`\n // reaches `compilation.fileDependencies`, and `run` and `watchRun` run\n // before there is a compilation to add to. Compiling here as well would\n // put the race back, and run every webpack build twice.\n if (isWebpack) return\n\n // Every watch rebuild re-enters this hook, and compiling here as well as\n // in `watchChange` is what closed the loop: consuming code imports the\n // generated file, so writing it is itself a module-graph change, which\n // re-enters `buildStart`, which writes it again. `watchChange` has\n // already run for every file in this cycle and rebuilt if any of them\n // was a source, so the only thing left for a re-entry to do is the\n // re-registration above.\n //\n // `hasCompiled` is the floor under that: a host that fires\n // `watchChange` without ever re-entering here would otherwise leave the\n // flag standing, and no first compile of a process may ever be skipped —\n // the tokens have to exist before the build that consumes them.\n //\n // Vite's dev server is that host, and more than a first compile is at\n // stake there. It calls this hook once per server, at start and on each\n // restart, and never after a change — so a flag some earlier file event\n // raised says nothing about this call. Honouring it made a restart after\n // any event in the session build nothing, including the restart Vite\n // performs when `vite.config.ts` changes. Cleared either way, so the\n // next call starts from what happens after this one.\n const reentry = watchRebuild && hasCompiled && hostCommand !== 'serve'\n watchRebuild = false\n if (reentry) return\n\n await compileOnceAcrossInstances(resolved)\n hasCompiled = true\n },\n\n name: 'unplugin-style-dictionary',\n\n // `closeWatcher` is a rollup-shaped hook and `UnpluginOptions` declares no\n // top-level equivalent, so it is registered per target instead: rolldown\n // lists it among its input plugin hooks, and Vite's plugin type is\n // rollup's, which is what carries it to `vite build --watch`. Vite's dev\n // server runs no rollup watcher, so there it simply never fires.\n rolldown: { closeWatcher },\n\n rollup: { closeWatcher },\n\n // unplugin calls the matching key from inside `apply(compiler)` and\n // never both, so the two share one implementation rather than one\n // delegating to the other.\n rspack: adoptCompiler,\n\n vite: {\n // **`buildEnd` is the hook a dev server's close reliably reaches.** It\n // replaced `server.httpServer?.once('close', …)`, which middleware mode\n // — Express, Koa, most SSR — has no `httpServer` for, so the optional\n // chain registered nothing and every restart leaked the watchers.\n //\n // Measured on Vite 6.4.3, 7.3.6 and 8.3.0, in middleware mode and\n // listening alike: while a server runs only `buildStart` fires, and on\n // `server.close()` it is `buildEnd` once and `closeBundle` twice.\n // **`closeWatcher` fires in neither mode on any of them**, which is why\n // this is not the `closeWatcher` handler registered below.\n //\n // `buildEnd` also fires at the end of `vite build`, and on every rebuild\n // of `vite build --watch`. Neither runs `configureServer`, so there is\n // nothing to close there and this is a no-op.\n buildEnd() {\n closeOwnWatchers?.()\n },\n\n closeWatcher,\n\n async configResolved(config) {\n if (rootOption === undefined) root = config.root || process.cwd()\n\n // The only host that has both. `command` is what makes `'serve'`\n // reachable at all, since nothing else here serves.\n hostCommand = config.command\n hostMode = config.mode\n\n // Vite's own logger, so the plugin's lines obey `customLogger` and\n // `clearScreen` like every other line the dev server prints. It\n // colours and prefixes its own output, which is why nothing painted\n // reaches it.\n //\n // Ahead of the early return below, because `vite build` needs the\n // logger just as much and takes that return.\n host = {\n error: (message) => {\n config.logger.error(message)\n },\n info: (message) => {\n config.logger.info(message)\n },\n }\n\n // A build has a watcher too under `vite build --watch`. On Vite 6 and 7\n // it is rollup's, whose chokidar ignore list always starts with\n // `**/node_modules/**`, so a token package resolved through\n // `node_modules` built once and never rebuilt — the dev server's fix\n // below never reached this mode. Everything after this block is the\n // dev server's.\n if (config.command !== 'serve') {\n if (config.build.watch) {\n // Before the resolution, so the `config` function it calls is told\n // it is being watched, as every later call will be.\n isWatching = true\n\n try {\n const resolved = await resolveConfigs()\n if (resolved.length > 0) {\n const { paths, patterns } = await getWatchTargets(resolved)\n appendChokidarIgnored(\n config.build.watch,\n nodeModulesNegations(patterns, paths),\n )\n }\n } catch (err) {\n // The build reports it itself when `buildStart` resolves again,\n // as the dev server's branch below explains.\n log(\n `Could not read the configuration while preparing the watch list: ${errorMessage(err)}`,\n 'error',\n )\n }\n }\n return\n }\n\n // Ahead of the resolution below, so the `config` function a consumer\n // wrote is told `watch: true` on this call as well as on every later\n // one. Setting it in `configureServer` alone was correct until this\n // hook started resolving configurations too.\n isWatching = true\n\n // **This is the last hook that can reach the ignore list.** Vite\n // builds the watcher from the resolved config, and `configureServer`\n // runs after it exists — `server.watcher` is a parameter there — so a\n // negation added then changes nothing. Measured on Vite 6.4.3, 7.3.6\n // and 8.3.0: amending it here reaches the watcher on all three, and\n // amending it in `configureServer` does not.\n //\n // The resolution is kept for `configureServer` to reuse rather than\n // discarded, because resolving is how a `config` function gets called\n // and doing it twice in one start-up would call the consumer's code an\n // extra time for nothing.\n try {\n startupResolved = await resolveConfigs()\n if (startupResolved.length === 0) return\n\n // A watcher the consumer turned off stays off. Vite builds a real one\n // for any value but a strict `null`, and appending the negation\n // below spread that `null` into an object — so the setting stopped\n // working exactly when a token came from a workspace package. By\n // identity, not truthiness: an unset `server.watch` resolves to\n // `undefined`, and almost nobody sets it, so a falsy check would drop\n // the negation for nearly every consumer.\n //\n // Here rather than above the `command` return: `server.watch` is\n // the dev server's, and says nothing about `vite build --watch`.\n if (config.server.watch === null) return\n\n const { paths, patterns } = await getWatchTargets(startupResolved)\n const negations = nodeModulesNegations(patterns, paths)\n if (negations.length === 0) return\n\n // Appended to whatever the consumer asked for, not replacing it.\n const existing = config.server.watch?.ignored\n config.server.watch = {\n ...config.server.watch,\n ignored: [\n ...(Array.isArray(existing)\n ? existing\n : existing === undefined\n ? []\n : [existing]),\n ...negations,\n ],\n }\n } catch (err) {\n // A configuration that cannot be resolved is the build's problem to\n // report, and it will: `buildStart` resolves again and fails there\n // with the host watching. Throwing here would fail the dev server\n // before it started, for the sake of a watch-list refinement.\n log(\n `Could not read the configuration while preparing the watch list: ${errorMessage(err)}`,\n 'error',\n )\n startupResolved = undefined\n }\n },\n\n async configureServer(server: ViteDevServer) {\n // A dev server watches, by definition. Said here rather than left to\n // `adoptWatchMode` because this hook runs *before* `buildStart` —\n // `createServer` calls it, and `buildStart` waits for the plugin\n // container — so the first `config` function of the process would\n // otherwise be told `watch: false` while a dev server started up\n // around it.\n isWatching = true\n\n // `configResolved` has already resolved these, on its way to amending\n // the watcher's ignore list. Taken rather than copied, so a later\n // rebuild re-resolves as it always did.\n const resolved = startupResolved ?? (await resolveConfigs())\n startupResolved = undefined\n if (resolved.length === 0) return\n\n // Reassigned after every rebuild below, so a configuration that gains\n // a source is matched against its new patterns rather than the ones\n // read at start-up.\n let targets = await getWatchTargets(resolved)\n\n // Watch configuration files and token files\n server.watcher.add(targets.paths)\n\n // The `node_modules` half, which Vite's watcher cannot be made to\n // deliver on Windows — see `nodeModulesWatchDirectories`. Keyed by\n // directory so a configuration that changes can close the ones it no\n // longer needs rather than accumulating watchers for the session.\n const ownWatchers = new Map<string, fs.FSWatcher>()\n\n // None of these either when the consumer turned Vite's watcher off.\n // Kept running, they rebuilt a token resolved through `node_modules`\n // while one in the root did not rebuild at all, which is not what a\n // disabled watcher means.\n const watcherDisabled = server.config.server.watch === null\n\n const watchNodeModules = (forPaths: string[]) => {\n if (watcherDisabled) return\n\n const wanted = new Set(nodeModulesWatchDirectories(forPaths))\n\n for (const [directory, watcher] of ownWatchers) {\n if (wanted.has(directory)) continue\n watcher.close()\n ownWatchers.delete(directory)\n }\n\n for (const directory of wanted) {\n if (ownWatchers.has(directory)) continue\n\n try {\n const watcher = fs.watch(directory, (_event, filename) => {\n if (filename === null) return\n\n const changed = path.posix.join(directory, filename)\n if (\n !isWatchedSource(\n changed,\n targets.matcher,\n generatedDestinations,\n )\n )\n return\n\n void schedule(path.basename(changed)).catch(() => {})\n })\n\n // A watcher of ours must not be what keeps a process alive; the\n // dev server already is.\n watcher.unref()\n ownWatchers.set(directory, watcher)\n } catch {\n // A directory that cannot be watched is not a reason to fail a\n // dev server. Where the negation works — Linux, macOS — Vite's\n // own watcher is still delivering these events.\n }\n }\n }\n\n watchNodeModules(targets.paths)\n\n // Clearing the map is what makes this safe to call more than once.\n closeOwnWatchers = () => {\n for (const watcher of ownWatchers.values()) watcher.close()\n ownWatchers.clear()\n }\n\n // Runs once per rebuild rather than once per event, which is why it\n // is handed to the scheduler rather than done in the listener.\n //\n // The watcher is handed only what it does not have: a path new since\n // the last list, and one that no longer exists. Re-adding the whole\n // unchanged list cost chokidar tens of milliseconds a save on a large\n // token set. The missing half is not waste — on chokidar's\n // non-fsevents path, which Linux takes, adding a missing path is what\n // watches its parent for that name, so a deleted literal source is\n // seen when it comes back. Diffing against the last list alone would\n // never add it again.\n refreshServerWatchList = async (rebuilt) => {\n const previous = new Set(targets.paths)\n targets = await getWatchTargets(rebuilt)\n\n const adding = targets.paths.filter(\n (file) => !previous.has(file) || !fs.existsSync(file),\n )\n if (adding.length > 0) server.watcher.add(adding)\n\n watchNodeModules(targets.paths)\n }\n\n if (errorOverlay) {\n // Whether the page is currently showing an overlay this plugin put\n // there. Only the clearing frame reads it: a success that follows a\n // success has no overlay to take down, and sending an update frame\n // for it would be traffic for nothing — and would spend the client's\n // one-time `isFirstUpdate`, which Vite uses to decide that an\n // overlay standing at the first update means a full reload.\n let overlayShowing = false\n\n notifyBuildOutcome = (error) => {\n if (error) {\n // Sent on every failure rather than only on the transition into\n // one. Vite's client replaces the overlay wholesale, so a repeat\n // is idempotent — and two different failures in a row must not\n // leave the first one's message on screen describing the second.\n overlayShowing = true\n server.hot.send({\n err: {\n message: error.message,\n plugin: 'unplugin-style-dictionary',\n stack: error.stack ?? '',\n },\n type: 'error',\n })\n return\n }\n\n if (!overlayShowing) return\n overlayShowing = false\n\n // Vite's protocol has no frame for \"take the overlay down\". The\n // client clears it when an update arrives, so an update carrying\n // nothing is the clear: it dismisses the overlay and then iterates\n // an empty list, reloading no page and touching no stylesheet.\n server.hot.send({ type: 'update', updates: [] })\n }\n }\n\n // chokidar types its listener as returning void and does not await\n // what it is handed, so an async listener left every rejection\n // floating. `schedule` owns the whole rebuild including its errors,\n // so there is nothing here left to reject.\n server.watcher.on('all', (_event, file) => {\n if (!isWatchedSource(file, targets.matcher, generatedDestinations))\n return\n\n // A dev server has no build to fail, so a rebuild that throws is\n // reported by the scheduler and the server keeps serving.\n void schedule(path.basename(file)).catch(() => {})\n })\n },\n },\n\n // Rollup types `watchChange` as returning void, yet awaits it as a\n // sequential hook — and the work here is inherently asynchronous. The\n // signature is the thing that is wrong, so the rule is silenced rather\n // than the hook made to lie about finishing.\n // oxlint-disable-next-line typescript/no-misused-promises\n async watchChange(changed) {\n adoptHost(this)\n adoptWatchMode(this)\n\n // Ahead of everything, including the flag below: a change reported\n // after the watcher closed earns no rebuild, so there is no re-entry\n // into `buildStart` for a flag to describe.\n if (hostClosed) return\n\n // Raised before any decision about the change, because whatever it\n // was, the host is now on its way back into `buildStart`.\n watchRebuild = true\n\n // Spelled the way the patterns and `generatedDestinations` spell it,\n // which a change reported through a registered realpath is not.\n const id = registeredSpellingOf(changed, linkedPaths)\n\n // The cheap half of the decision, taken before anything is resolved.\n // Under Vite the scope this hook sees is the whole project root rather\n // than the module graph, so most of what arrives here has nothing to do\n // with tokens, and resolving every configuration only to discard the\n // answer ran a consumer's `config` function once per unrelated file.\n // Skipped until a build has derived a list to filter against.\n if (\n cachedMatcher &&\n !isWatchedSource(id, cachedMatcher, generatedDestinations)\n )\n return\n\n const resolved = await resolveConfigs()\n if (resolved.length === 0) return\n\n // Derived again rather than trusted from the cache, because the cache\n // is what decided this path was worth resolving and not what decides a\n // rebuild. A config edit reaches here through its own filename and can\n // have dropped the very source the cached list matched. The patterns\n // alone: the decision is about one path, and walking the tree for what\n // they match today bought nothing here.\n const { matcher, patterns } = await deriveWatchPatterns(resolved)\n // Without this check, watchChange fires for *any* changed file in the\n // host bundler's module graph — including our own generated output,\n // since consuming code imports it. Every regenerate is itself a\n // \"change\", so skipping what is not a source here is what keeps this\n // from rebuilding forever — both the files that match no pattern and\n // the ones that match only because this plugin wrote them.\n if (!isWatchedSource(id, matcher, generatedDestinations)) return\n\n // Same division as `buildStart`: on webpack the compile belongs to\n // `watchRun`, which has already run for this compilation, so all that\n // is left is to re-register the watch list below.\n if (!isWebpack) await schedule(path.basename(id))\n\n // Expanded again after the build rather than reusing the list from\n // before it, so a token file the build itself produced is registered.\n //\n // Not under a dev server, whose refresh has already handed its watcher\n // every path: Vite's `addWatchFile` reaches the watcher only for a file\n // outside the root, so this walk repeated that one. `vite build\n // --watch`, rollup, rolldown and webpack keep it — it is how they learn\n // the list.\n if (refreshServerWatchList) return\n addWatchFiles(this, await expandPatterns(patterns, log))\n },\n\n webpack: adoptCompiler,\n }\n}\n\nexport const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory)\n\nexport default unplugin\n"],"mappings":"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"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { inspect } from "node:util";
|
|
2
|
+
//#region src/options.ts
|
|
3
|
+
const FAIL_ON_ERROR = /* @__PURE__ */ new Set([
|
|
4
|
+
"build",
|
|
5
|
+
false,
|
|
6
|
+
"serve",
|
|
7
|
+
true
|
|
8
|
+
]);
|
|
9
|
+
const LOG_LEVELS = /* @__PURE__ */ new Set([
|
|
10
|
+
"info",
|
|
11
|
+
"silent",
|
|
12
|
+
"verbose",
|
|
13
|
+
"warn"
|
|
14
|
+
]);
|
|
15
|
+
function checkOptions(options) {
|
|
16
|
+
const failOnError = read(options, "failOnError");
|
|
17
|
+
if (failOnError !== void 0 && !FAIL_ON_ERROR.has(failOnError)) reject("failOnError", "true, false, 'build' or 'serve'", failOnError);
|
|
18
|
+
const logLevel = read(options, "logLevel");
|
|
19
|
+
if (logLevel !== void 0 && !LOG_LEVELS.has(logLevel)) reject("logLevel", "'silent', 'warn', 'info' or 'verbose'", logLevel);
|
|
20
|
+
const platforms = read(options, "platforms");
|
|
21
|
+
if (platforms !== void 0 && !isPlatformSelection(platforms)) reject("platforms", "an array of platform names, or an object with only `build` and `watch` arrays", platforms);
|
|
22
|
+
}
|
|
23
|
+
function isNameList(value) {
|
|
24
|
+
return Array.isArray(value) && value.every((name) => typeof name === "string");
|
|
25
|
+
}
|
|
26
|
+
function isPlatformSelection(value) {
|
|
27
|
+
if (isNameList(value)) return true;
|
|
28
|
+
if (typeof value !== "object" || value === null) return false;
|
|
29
|
+
return Object.entries(value).every(([key, names]) => (key === "build" || key === "watch") && (names === void 0 || isNameList(names)));
|
|
30
|
+
}
|
|
31
|
+
function read(options, key) {
|
|
32
|
+
return Reflect.get(options, key);
|
|
33
|
+
}
|
|
34
|
+
function reject(name, accepts, received) {
|
|
35
|
+
throw new TypeError(`[unplugin-style-dictionary] The ${name} option must be ${accepts}, and was ${inspect(received)}.`);
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { checkOptions };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","names":[],"sources":["../src/options.ts"],"sourcesContent":["// The options whose values a consumer can get wrong without anything saying\n// so, checked once when the plugin is created.\n//\n// The types rule these values out, but only in a typed config: the README's\n// CommonJS webpack config and any JavaScript Vite or rollup config are not\n// checked. Unchecked, each wrong value was read as whatever it came closest\n// to. `failOnError: 'always'`, or the string `'true'` from an environment\n// variable, behaved as `false`, so a broken build exited 0. `platforms: 'js'`\n// and `platforms: { serve: ['js'] }` built every platform.\n//\n// `watch` is not here: a non-string entry already throws from `path` the first\n// time a watch list is derived. Nothing here holds state.\n\nimport { inspect } from 'node:util'\n\nconst FAIL_ON_ERROR = new Set<unknown>(['build', false, 'serve', true])\nconst LOG_LEVELS = new Set<unknown>(['info', 'silent', 'verbose', 'warn'])\n\n// Throws a `TypeError` naming the option, what it accepts and what it was\n// given, for the first option whose value is outside its declared type.\nexport function checkOptions(options: object): void {\n const failOnError = read(options, 'failOnError')\n if (failOnError !== undefined && !FAIL_ON_ERROR.has(failOnError)) {\n reject('failOnError', \"true, false, 'build' or 'serve'\", failOnError)\n }\n\n // An unknown level fell through to the plugin's progress lines and Style\n // Dictionary's default verbosity: noisier rather than less safe, and checked\n // for consistency. `'error'` is the likely one, and there is no such level.\n const logLevel = read(options, 'logLevel')\n if (logLevel !== undefined && !LOG_LEVELS.has(logLevel)) {\n reject('logLevel', \"'silent', 'warn', 'info' or 'verbose'\", logLevel)\n }\n\n const platforms = read(options, 'platforms')\n if (platforms !== undefined && !isPlatformSelection(platforms)) {\n reject(\n 'platforms',\n 'an array of platform names, or an object with only `build` and `watch` arrays',\n platforms,\n )\n }\n}\n\nfunction isNameList(value: unknown): boolean {\n return (\n Array.isArray(value) &&\n value.every((name: unknown) => typeof name === 'string')\n )\n}\n\n// An array of names, or an object keyed by `build` and `watch` alone. `serve`\n// is the slip worth catching: `failOnError` says `'serve'` where this says\n// `watch`, and an object with an unknown key built every platform, as though\n// nothing had been selected.\nfunction isPlatformSelection(value: unknown): boolean {\n if (isNameList(value)) return true\n if (typeof value !== 'object' || value === null) return false\n\n return Object.entries(value).every(\n ([key, names]) =>\n (key === 'build' || key === 'watch') &&\n (names === undefined || isNameList(names)),\n )\n}\n\n// Read structurally, so the check sees what was passed rather than what the\n// type says was.\nfunction read(options: object, key: string): unknown {\n return Reflect.get(options, key)\n}\n\nfunction reject(name: string, accepts: string, received: unknown): never {\n throw new TypeError(\n `[unplugin-style-dictionary] The ${name} option must be ${accepts}, and was ${inspect(received)}.`,\n )\n}\n"],"mappings":";;AAeA,MAAM,gCAAgB,IAAI,IAAa;CAAC;CAAS;CAAO;CAAS;AAAI,CAAC;AACtE,MAAM,6BAAa,IAAI,IAAa;CAAC;CAAQ;CAAU;CAAW;AAAM,CAAC;AAIzE,SAAgB,aAAa,SAAuB;CAClD,MAAM,cAAc,KAAK,SAAS,aAAa;CAC/C,IAAI,gBAAgB,KAAA,KAAa,CAAC,cAAc,IAAI,WAAW,GAC7D,OAAO,eAAe,mCAAmC,WAAW;CAMtE,MAAM,WAAW,KAAK,SAAS,UAAU;CACzC,IAAI,aAAa,KAAA,KAAa,CAAC,WAAW,IAAI,QAAQ,GACpD,OAAO,YAAY,yCAAyC,QAAQ;CAGtE,MAAM,YAAY,KAAK,SAAS,WAAW;CAC3C,IAAI,cAAc,KAAA,KAAa,CAAC,oBAAoB,SAAS,GAC3D,OACE,aACA,iFACA,SACF;AAEJ;AAEA,SAAS,WAAW,OAAyB;CAC3C,OACE,MAAM,QAAQ,KAAK,KACnB,MAAM,OAAO,SAAkB,OAAO,SAAS,QAAQ;AAE3D;AAMA,SAAS,oBAAoB,OAAyB;CACpD,IAAI,WAAW,KAAK,GAAG,OAAO;CAC9B,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CAExD,OAAO,OAAO,QAAQ,KAAK,CAAC,CAAC,OAC1B,CAAC,KAAK,YACJ,QAAQ,WAAW,QAAQ,aAC3B,UAAU,KAAA,KAAa,WAAW,KAAK,EAC5C;AACF;AAIA,SAAS,KAAK,SAAiB,KAAsB;CACnD,OAAO,QAAQ,IAAI,SAAS,GAAG;AACjC;AAEA,SAAS,OAAO,MAAc,SAAiB,UAA0B;CACvE,MAAM,IAAI,UACR,mCAAmC,KAAK,kBAAkB,QAAQ,YAAY,QAAQ,QAAQ,EAAE,EAClG;AACF"}
|
package/dist/patterns.js
CHANGED
|
@@ -25,6 +25,13 @@ async function patternsMatchingNothing({ globs, literals }) {
|
|
|
25
25
|
} catch {}
|
|
26
26
|
return barren;
|
|
27
27
|
}
|
|
28
|
+
function realpathOf(file) {
|
|
29
|
+
try {
|
|
30
|
+
return fs.realpathSync(file).replace(/\\/g, "/");
|
|
31
|
+
} catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
28
35
|
function sourcePatternsOf(configObj) {
|
|
29
36
|
const patterns = {
|
|
30
37
|
globs: [],
|
|
@@ -84,6 +91,6 @@ function staticParentOf(pattern) {
|
|
|
84
91
|
return (firstGlob === -1 ? path.posix.dirname(pattern) : segments.slice(0, firstGlob).join("/")).replace(/\\(.)/g, "$1");
|
|
85
92
|
}
|
|
86
93
|
//#endregion
|
|
87
|
-
export { expandPatterns, patternsMatchingNothing, sourcePatternsOf, watchOptionPatterns, watchPatternsOf };
|
|
94
|
+
export { expandPatterns, patternsMatchingNothing, realpathOf, sourcePatternsOf, watchOptionPatterns, watchPatternsOf };
|
|
88
95
|
|
|
89
96
|
//# sourceMappingURL=patterns.js.map
|