@epilot/volt-ui-experimental-griffel 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/ADOPTING.md +416 -0
  2. package/README.md +96 -17
  3. package/dist/_virtual/jsx-dev-runtime.js +5 -0
  4. package/dist/_virtual/jsx-dev-runtime.js.map +1 -0
  5. package/dist/_virtual/react-jsx-dev-runtime.development.js +5 -0
  6. package/dist/_virtual/react-jsx-dev-runtime.development.js.map +1 -0
  7. package/dist/_virtual/react-jsx-dev-runtime.production.js +5 -0
  8. package/dist/_virtual/react-jsx-dev-runtime.production.js.map +1 -0
  9. package/dist/components/accordion/accordion.js +126 -66
  10. package/dist/components/accordion/accordion.js.map +1 -1
  11. package/dist/components/alert-dialog/alert-dialog.js +239 -137
  12. package/dist/components/alert-dialog/alert-dialog.js.map +1 -1
  13. package/dist/components/avatar/avatar-group.js +102 -47
  14. package/dist/components/avatar/avatar-group.js.map +1 -1
  15. package/dist/components/avatar/avatar.js +99 -76
  16. package/dist/components/avatar/avatar.js.map +1 -1
  17. package/dist/components/badge/badge.js +56 -40
  18. package/dist/components/badge/badge.js.map +1 -1
  19. package/dist/components/breadcrumb/breadcrumb.js +97 -49
  20. package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
  21. package/dist/components/button/button.js +89 -78
  22. package/dist/components/button/button.js.map +1 -1
  23. package/dist/components/button-group/button-group.js +57 -33
  24. package/dist/components/button-group/button-group.js.map +1 -1
  25. package/dist/components/callout/callout.js +288 -164
  26. package/dist/components/callout/callout.js.map +1 -1
  27. package/dist/components/card/card.js +138 -82
  28. package/dist/components/card/card.js.map +1 -1
  29. package/dist/components/checkbox/checkbox.js +100 -49
  30. package/dist/components/checkbox/checkbox.js.map +1 -1
  31. package/dist/components/collapsible-sidebar/collapsible-sidebar.js +155 -107
  32. package/dist/components/collapsible-sidebar/collapsible-sidebar.js.map +1 -1
  33. package/dist/components/command/command.js +295 -156
  34. package/dist/components/command/command.js.map +1 -1
  35. package/dist/components/data-table/data-table-body.js +114 -54
  36. package/dist/components/data-table/data-table-body.js.map +1 -1
  37. package/dist/components/data-table/data-table-column-header.js +117 -45
  38. package/dist/components/data-table/data-table-column-header.js.map +1 -1
  39. package/dist/components/data-table/data-table-column-visibility.js +98 -39
  40. package/dist/components/data-table/data-table-column-visibility.js.map +1 -1
  41. package/dist/components/data-table/data-table-content.js +90 -58
  42. package/dist/components/data-table/data-table-content.js.map +1 -1
  43. package/dist/components/data-table/data-table-error.js +86 -42
  44. package/dist/components/data-table/data-table-error.js.map +1 -1
  45. package/dist/components/data-table/data-table-header.js +88 -54
  46. package/dist/components/data-table/data-table-header.js.map +1 -1
  47. package/dist/components/data-table/data-table-loading.js +30 -18
  48. package/dist/components/data-table/data-table-loading.js.map +1 -1
  49. package/dist/components/data-table/data-table-pagination.js +27 -19
  50. package/dist/components/data-table/data-table-pagination.js.map +1 -1
  51. package/dist/components/data-table/data-table-row.js +87 -64
  52. package/dist/components/data-table/data-table-row.js.map +1 -1
  53. package/dist/components/data-table/data-table-toolbar.js +24 -16
  54. package/dist/components/data-table/data-table-toolbar.js.map +1 -1
  55. package/dist/components/data-table/data-table.js +182 -145
  56. package/dist/components/data-table/data-table.js.map +1 -1
  57. package/dist/components/date-range-picker/date-range-picker.js +342 -214
  58. package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
  59. package/dist/components/date-range-picker/range-calendar.js +132 -92
  60. package/dist/components/date-range-picker/range-calendar.js.map +1 -1
  61. package/dist/components/date-time-picker/calendar.js +507 -323
  62. package/dist/components/date-time-picker/calendar.js.map +1 -1
  63. package/dist/components/date-time-picker/date-time-picker-context.js +64 -60
  64. package/dist/components/date-time-picker/date-time-picker-context.js.map +1 -1
  65. package/dist/components/date-time-picker/date-time-picker-input.js +249 -149
  66. package/dist/components/date-time-picker/date-time-picker-input.js.map +1 -1
  67. package/dist/components/date-time-picker/date-time-picker-panel.js +270 -156
  68. package/dist/components/date-time-picker/date-time-picker-panel.js.map +1 -1
  69. package/dist/components/date-time-picker/date-time-picker-popover.js +22 -14
  70. package/dist/components/date-time-picker/date-time-picker-popover.js.map +1 -1
  71. package/dist/components/date-time-picker/date-time-picker.js +118 -70
  72. package/dist/components/date-time-picker/date-time-picker.js.map +1 -1
  73. package/dist/components/date-time-picker/time-picker.js +204 -132
  74. package/dist/components/date-time-picker/time-picker.js.map +1 -1
  75. package/dist/components/density-provider/density-provider.js +48 -0
  76. package/dist/components/density-provider/density-provider.js.map +1 -0
  77. package/dist/components/dialog/dialog.js +281 -151
  78. package/dist/components/dialog/dialog.js.map +1 -1
  79. package/dist/components/drawer/drawer.js +272 -162
  80. package/dist/components/drawer/drawer.js.map +1 -1
  81. package/dist/components/dropdown-menu/dropdown-menu.js +376 -193
  82. package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
  83. package/dist/components/field/field-combobox.js +447 -240
  84. package/dist/components/field/field-combobox.js.map +1 -1
  85. package/dist/components/field/field-select.js +23 -19
  86. package/dist/components/field/field-select.js.map +1 -1
  87. package/dist/components/field/field.js +315 -206
  88. package/dist/components/field/field.js.map +1 -1
  89. package/dist/components/label/label.js +24 -16
  90. package/dist/components/label/label.js.map +1 -1
  91. package/dist/components/list/list.js +623 -369
  92. package/dist/components/list/list.js.map +1 -1
  93. package/dist/components/option-card/option-card.js +323 -159
  94. package/dist/components/option-card/option-card.js.map +1 -1
  95. package/dist/components/pill/pill.js +91 -72
  96. package/dist/components/pill/pill.js.map +1 -1
  97. package/dist/components/popover/popover.js +99 -54
  98. package/dist/components/popover/popover.js.map +1 -1
  99. package/dist/components/radio/radio.js +92 -49
  100. package/dist/components/radio/radio.js.map +1 -1
  101. package/dist/components/scroll-overflow/scroll-overflow.js +349 -197
  102. package/dist/components/scroll-overflow/scroll-overflow.js.map +1 -1
  103. package/dist/components/segmented-control/segmented-control.js +141 -97
  104. package/dist/components/segmented-control/segmented-control.js.map +1 -1
  105. package/dist/components/select/select.js +452 -227
  106. package/dist/components/select/select.js.map +1 -1
  107. package/dist/components/separator/separator.js +24 -16
  108. package/dist/components/separator/separator.js.map +1 -1
  109. package/dist/components/skeleton/skeleton.js +78 -38
  110. package/dist/components/skeleton/skeleton.js.map +1 -1
  111. package/dist/components/spinner/spinner.js +44 -20
  112. package/dist/components/spinner/spinner.js.map +1 -1
  113. package/dist/components/switch/switch.js +163 -86
  114. package/dist/components/switch/switch.js.map +1 -1
  115. package/dist/components/table/table-pagination.js +468 -210
  116. package/dist/components/table/table-pagination.js.map +1 -1
  117. package/dist/components/table/table.js +212 -144
  118. package/dist/components/table/table.js.map +1 -1
  119. package/dist/components/tabs/tabs.js +94 -58
  120. package/dist/components/tabs/tabs.js.map +1 -1
  121. package/dist/components/text/text.js +40 -32
  122. package/dist/components/text/text.js.map +1 -1
  123. package/dist/components/toast/toast.js +189 -113
  124. package/dist/components/toast/toast.js.map +1 -1
  125. package/dist/components/toggle/toggle-group.js +40 -24
  126. package/dist/components/toggle/toggle-group.js.map +1 -1
  127. package/dist/components/toggle/toggle.js +50 -42
  128. package/dist/components/toggle/toggle.js.map +1 -1
  129. package/dist/components/tooltip/tooltip.js +101 -55
  130. package/dist/components/tooltip/tooltip.js.map +1 -1
  131. package/dist/index.d.ts +1675 -1068
  132. package/dist/index.js +286 -280
  133. package/dist/index.js.map +1 -1
  134. package/dist/lib/clear-button.js +27 -15
  135. package/dist/lib/clear-button.js.map +1 -1
  136. package/dist/lib/icons.js +18 -6
  137. package/dist/lib/icons.js.map +1 -1
  138. package/dist/packages/volt-runtime/dist/sheet.js +27 -10
  139. package/dist/packages/volt-runtime/dist/sheet.js.map +1 -1
  140. package/dist/packages/volt-runtime/dist/styles/recipe.js +76 -0
  141. package/dist/packages/volt-runtime/dist/styles/recipe.js.map +1 -0
  142. package/dist/styles/document-context.js +12 -0
  143. package/dist/styles/document-context.js.map +1 -0
  144. package/dist/styles/make-styles.js +18 -10
  145. package/dist/styles/make-styles.js.map +1 -1
  146. package/dist/tokens/core.generated.js +1239 -1153
  147. package/dist/tokens/core.generated.js.map +1 -1
  148. package/dist/tokens/index.d.ts +1103 -1054
  149. package/dist/tokens/index.js +1045 -996
  150. package/dist/tokens/index.js.map +1 -1
  151. package/dist/tokens/install.js +25 -9
  152. package/dist/tokens/install.js.map +1 -1
  153. package/dist/tokens/palette.generated.js +1323 -1293
  154. package/dist/tokens/palette.generated.js.map +1 -1
  155. package/dist/tokens/use-palette-tokens.js +20 -0
  156. package/dist/tokens/use-palette-tokens.js.map +1 -0
  157. package/dist/tokens.json +127 -2
  158. package/dist/vendor/react/cjs/react-jsx-dev-runtime.development.js +236 -0
  159. package/dist/vendor/react/cjs/react-jsx-dev-runtime.development.js.map +1 -0
  160. package/dist/vendor/react/cjs/react-jsx-dev-runtime.production.js +21 -0
  161. package/dist/vendor/react/cjs/react-jsx-dev-runtime.production.js.map +1 -0
  162. package/dist/vendor/react/jsx-dev-runtime.js +9 -0
  163. package/dist/vendor/react/jsx-dev-runtime.js.map +1 -0
  164. package/package.json +17 -4
  165. package/scripts/migration-diagnostics-core.mjs +859 -0
  166. package/scripts/migration-diagnostics.mjs +353 -0
  167. package/scripts/migration-sweep.mjs +179 -0
  168. package/testing/jest-preset.cjs +64 -0
  169. package/dist/packages/volt-runtime/dist/styles/make-styles.js +0 -22
  170. package/dist/packages/volt-runtime/dist/styles/make-styles.js.map +0 -1
@@ -0,0 +1,353 @@
1
+ /**
2
+ * fs-bound migration-diagnostics ORCHESTRATION for a migrated (v1 -> Griffel) consumer tree —
3
+ * directory/file walking, symlink boundary checks, hashing, package identity resolution, and
4
+ * TypeScript resolution from the local filesystem. The actual rule logic (what counts as a
5
+ * defect) lives in the sibling `migration-diagnostics-core.mjs`, which this module delegates every
6
+ * read file to via `analyzeSource` — see that module for the rule catalogue and the
7
+ * supported-analysis-boundary documentation. This split exists so a future non-fs caller (e.g. a
8
+ * remote tool analyzing a submitted string) can reuse the identical rule implementation without
9
+ * pulling in any of this module's filesystem/process access (WP6B-2 extraction — see
10
+ * `WP6B-DECISION-CORE-EXTRACTION.md`; no such caller is wired up yet).
11
+ *
12
+ * Used by the CLI wrapper (`migration-sweep.mjs`) and by tests. Importing this module has no
13
+ * side effects (no process.exit, no argv parsing, no eager TypeScript import — see below).
14
+ *
15
+ * TypeScript itself is resolved LAZILY from the consumer's own installation (see
16
+ * `resolveConsumerTypeScript`) — this package does not depend on or bundle `typescript` at
17
+ * runtime. If it cannot be resolved, rules `legacy-volt-class` and `hand-written-volt-css-var`
18
+ * (pure text, no TypeScript needed) still run; `unestablished-palette-install` is skipped with an
19
+ * explicit, actionable note rather than crashing the whole scan.
20
+ */
21
+ import { createHash } from "node:crypto"
22
+ import { createRequire } from "node:module"
23
+ import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs"
24
+ import { join, relative } from "node:path"
25
+ import { pathToFileURL } from "node:url"
26
+
27
+ import { analyzeSource, makeFinding, RULES, SCRIPT_EXT_RE } from "./migration-diagnostics-core.mjs"
28
+
29
+ // Re-exported so existing imports of `RULES` from this module keep working unchanged — the rule
30
+ // catalogue itself now lives in the core (single source of truth), per WP6B-2.
31
+ export { RULES }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Package/token-family identity — derived from the built package, never hardcoded
35
+ // ---------------------------------------------------------------------------
36
+
37
+ function sha256(content) {
38
+ return createHash("sha256").update(content).digest("hex")
39
+ }
40
+
41
+ function readGeneratedFamilies(packageDir, file) {
42
+ const full = join(packageDir, "dist/tokens", file)
43
+ if (!existsSync(full)) {
44
+ throw new Error(
45
+ `Missing generated token file: dist/tokens/${file} under ${packageDir}. The package is not built — run \`bun run build\` there first.`
46
+ )
47
+ }
48
+ const content = readFileSync(full, "utf8")
49
+ const families = new Set([...content.matchAll(/--volt2-[a-z0-9]+-([a-z]+)-/g)].map((m) => m[1]))
50
+ if (families.size === 0) {
51
+ throw new Error(
52
+ `Generated token file dist/tokens/${file} under ${packageDir} contains no recognizable family declarations. ` +
53
+ "It exists but is empty or malformed (does not match the expected --volt2-<hash>-<family>- pattern) — " +
54
+ "the package build is likely stale or broken. Re-run `bun run build` there, do not proceed with an empty family set."
55
+ )
56
+ }
57
+ return { families, content }
58
+ }
59
+
60
+ /**
61
+ * Resolves package name/version, its core/palette token family sets, and a deterministic hash of
62
+ * the generated-token inputs those sets were read from, from a built package directory. Throws
63
+ * (never silently falls back to empty sets) on a missing/unreadable package.json, missing
64
+ * generated token files, or generated files with no recognizable family declarations — "invalid
65
+ * package selection" is a hard error, not a quietly-disabled rule.
66
+ */
67
+ export function resolvePackageIdentity(packageDir) {
68
+ const pkgJsonPath = join(packageDir, "package.json")
69
+ if (!existsSync(pkgJsonPath)) {
70
+ throw new Error(`Invalid package selection: no package.json at ${packageDir}`)
71
+ }
72
+ let pkgJson
73
+ try {
74
+ pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"))
75
+ } catch (err) {
76
+ throw new Error(`Invalid package selection: could not parse ${pkgJsonPath}: ${err.message}`)
77
+ }
78
+ if (!pkgJson.name || !pkgJson.version) {
79
+ throw new Error(`Invalid package selection: ${pkgJsonPath} is missing "name" or "version"`)
80
+ }
81
+
82
+ const coreRead = readGeneratedFamilies(packageDir, "core.generated.js")
83
+ const paletteRead = readGeneratedFamilies(packageDir, "palette.generated.js")
84
+ const core = coreRead.families
85
+ const palette = new Set([...paletteRead.families].filter((f) => !core.has(f)))
86
+ const generatedInputHash = sha256(`${coreRead.content}\u0000${paletteRead.content}`)
87
+
88
+ return {
89
+ packageDir,
90
+ packageName: pkgJson.name,
91
+ packageVersion: pkgJson.version,
92
+ core,
93
+ palette,
94
+ generatedInputHash,
95
+ }
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // TypeScript — lazily resolved from the consumer's own installation, never bundled/depended-on
100
+ // ---------------------------------------------------------------------------
101
+
102
+ /**
103
+ * Resolves `typescript` using Node's own module resolution, searching from each of `searchRoots`
104
+ * in order (first match wins). Never installs anything. Throws a clear, actionable error — never
105
+ * a cryptic MODULE_NOT_FOUND — naming exactly what to do, if none of the roots have it.
106
+ */
107
+ export async function resolveConsumerTypeScript(searchRoots) {
108
+ const roots = searchRoots.filter(Boolean)
109
+ const req = createRequire(join(roots[0] ?? process.cwd(), "noop-resolver.cjs"))
110
+ let tsPath
111
+ for (const root of roots) {
112
+ try {
113
+ tsPath = req.resolve("typescript", { paths: [root] })
114
+ break
115
+ } catch {
116
+ // try the next root
117
+ }
118
+ }
119
+ if (!tsPath) {
120
+ throw new Error(
121
+ 'Could not resolve a "typescript" installation from: ' +
122
+ roots.join(", ") +
123
+ '. The "unestablished-palette-install" rule parses source with your project\'s own TypeScript — ' +
124
+ "it is not bundled with this package. Install it as a devDependency in the project being scanned " +
125
+ "(`npm install -D typescript` or `bun add -d typescript`) and re-run. " +
126
+ "legacy-volt-class and hand-written-volt-css-var do not need TypeScript and are unaffected."
127
+ )
128
+ }
129
+ const mod = await import(pathToFileURL(tsPath).href)
130
+ return { ts: mod.default ?? mod, tsPath }
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // File walking
135
+ // ---------------------------------------------------------------------------
136
+
137
+ const SCAN_EXT_RE = /\.(tsx?|jsx?|s?css)$/
138
+ /** `*.generated.*` build output — the generator's own emitted source of truth, never
139
+ * "hand-written" by a consumer. See §4 of the WP6A report. */
140
+ const GENERATED_FILE_RE = /\.generated\.[a-z]+$/i
141
+ /** A leading `/* GENERATED ... *\/` / `// GENERATED ...` marker — this repo's own convention
142
+ * (`packages/tokens/scripts/build.ts`), and common generally; catches generated files that don't
143
+ * follow the filename convention (e.g. tokens/index.ts). */
144
+ const GENERATED_HEADER_RE = /^\s*(\/\*|\/\/|<!--)\s*generated\b/i
145
+
146
+ function isExcluded(rel, excludes) {
147
+ return excludes.some((ex) => rel === ex || rel.startsWith(`${ex}/`))
148
+ }
149
+
150
+ function isGeneratedByHeader(filePath) {
151
+ try {
152
+ const fd = readFileSync(filePath, { encoding: "utf8", flag: "r" }).slice(0, 200)
153
+ return GENERATED_HEADER_RE.test(fd.split("\n")[0] ?? "")
154
+ } catch {
155
+ return false
156
+ }
157
+ }
158
+
159
+ function walk(dir, { excludes, root, skippedSymlinks, skippedGenerated }) {
160
+ const out = []
161
+ let entries
162
+ try {
163
+ entries = readdirSync(dir, { withFileTypes: true })
164
+ } catch (err) {
165
+ throw new Error(`Cannot read directory ${dir}: ${err.message}`)
166
+ }
167
+ for (const entry of entries) {
168
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue
169
+ const full = join(dir, entry.name)
170
+ const rel = relative(root, full)
171
+ if (isExcluded(rel, excludes)) continue
172
+ if (entry.isSymbolicLink()) {
173
+ skippedSymlinks.push(rel)
174
+ continue
175
+ }
176
+ if (entry.isDirectory()) {
177
+ out.push(...walk(full, { excludes, root, skippedSymlinks, skippedGenerated }))
178
+ } else if (SCAN_EXT_RE.test(entry.name)) {
179
+ if (GENERATED_FILE_RE.test(entry.name) || isGeneratedByHeader(full)) {
180
+ skippedGenerated.push(rel)
181
+ continue
182
+ }
183
+ out.push(full)
184
+ }
185
+ }
186
+ return out
187
+ }
188
+
189
+ /** Reads one file and delegates the actual analysis to the core's `analyzeSource` — this function
190
+ * is the ENTIRE fs-touching surface for per-file analysis; `analyzeSource` itself never reads a
191
+ * file. `relPath` doubles as `analyzeSource`'s `fileNameHint` (parse-mode detection + the `file`
192
+ * label on findings) — the same string was already used for both purposes before this split. */
193
+ function analyzeFile(ts, identity, filePath, relPath) {
194
+ const raw = readFileSync(filePath, "utf8")
195
+ const findings = analyzeSource(raw, { identity, ts, fileNameHint: relPath })
196
+ return { findings, contentHash: sha256(raw) }
197
+ }
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Public entrypoint
201
+ // ---------------------------------------------------------------------------
202
+
203
+ /**
204
+ * Resolves `targetPath` into a boundary-checked root: rejects a target (or its auto-selected
205
+ * `src/`) that is itself a symlink, rather than silently following it (which could escape the
206
+ * requested scope entirely). Supports both a directory target and a single FILE target.
207
+ */
208
+ function resolveScanRoot(targetPath) {
209
+ const targetLstat = lstatSync(targetPath)
210
+ if (targetLstat.isSymbolicLink()) {
211
+ return { error: `Target path is a symlink (${targetPath} -> ${realpathSync(targetPath)}); point directly at the real path instead of a symlink.` }
212
+ }
213
+ if (targetLstat.isFile()) {
214
+ return { file: targetPath }
215
+ }
216
+ if (!targetLstat.isDirectory()) {
217
+ return { error: `Target path is neither a file nor a directory: ${targetPath}` }
218
+ }
219
+ const srcDir = join(targetPath, "src")
220
+ if (existsSync(srcDir)) {
221
+ const srcLstat = lstatSync(srcDir)
222
+ if (srcLstat.isSymbolicLink()) {
223
+ return {
224
+ error: `The target's auto-selected src/ is a symlink (${srcDir} -> ${realpathSync(srcDir)}); this would scan outside the requested scope. Point the target directly at the real directory to scan, or pass it as the target itself.`,
225
+ }
226
+ }
227
+ if (srcLstat.isDirectory()) {
228
+ return { root: srcDir }
229
+ }
230
+ }
231
+ return { root: targetPath }
232
+ }
233
+
234
+ /**
235
+ * Scans one target (a repo root, whose `src/` is scanned if present; any directory directly; or a
236
+ * single file) and returns `{ target, filesScanned, targetInputHash, skippedSymlinks,
237
+ * skippedGenerated, toolingWarning?, findings }` on success, or `{ target, error }` for a
238
+ * missing/unreadable path, a symlinked target/src boundary, or an internal walk failure — never a
239
+ * silent "skipped" a caller could read as success by checking only `findings`.
240
+ */
241
+ export async function analyzeTarget(targetPath, { identity, excludes = [], resolveTs } = {}) {
242
+ if (!identity) {
243
+ throw new Error("analyzeTarget requires a resolved package `identity` — see resolvePackageIdentity()")
244
+ }
245
+ if (!existsSync(targetPath)) {
246
+ return { target: targetPath, error: `Target path does not exist: ${targetPath}` }
247
+ }
248
+
249
+ let scanRoot
250
+ try {
251
+ scanRoot = resolveScanRoot(targetPath)
252
+ } catch (err) {
253
+ return { target: targetPath, error: `Target path is not readable: ${targetPath} (${err.message})` }
254
+ }
255
+ if (scanRoot.error) {
256
+ return { target: targetPath, error: scanRoot.error }
257
+ }
258
+
259
+ let files
260
+ const skippedSymlinks = []
261
+ const skippedGenerated = []
262
+ if (scanRoot.file) {
263
+ if (GENERATED_FILE_RE.test(scanRoot.file) || isGeneratedByHeader(scanRoot.file)) {
264
+ skippedGenerated.push(relative(targetPath, scanRoot.file) || scanRoot.file)
265
+ files = []
266
+ } else if (SCAN_EXT_RE.test(scanRoot.file)) {
267
+ files = [scanRoot.file]
268
+ } else {
269
+ return { target: targetPath, error: `Target file has an unsupported extension for scanning: ${scanRoot.file}` }
270
+ }
271
+ } else {
272
+ try {
273
+ files = walk(scanRoot.root, { excludes, root: targetPath, skippedSymlinks, skippedGenerated })
274
+ } catch (err) {
275
+ return { target: targetPath, error: err.message }
276
+ }
277
+ }
278
+
279
+ // unestablished-palette-install only applies to script files — a CSS/SCSS-only target never
280
+ // needs TypeScript at all, and must not report a tooling problem for a rule that could not have
281
+ // run there anyway. Reuses the core's own SCRIPT_EXT_RE so this check can never drift from what
282
+ // analyzeSource itself treats as script-parseable.
283
+ const hasScriptFiles = files.some((f) => SCRIPT_EXT_RE.test(f))
284
+
285
+ let ts = null
286
+ let toolingWarning
287
+ let resolvedTypeScript
288
+ let analysisIncomplete = false
289
+ if (hasScriptFiles) {
290
+ // No explicit resolver from the caller does NOT mean "skip rule 3 silently" — the engine's
291
+ // own default path still attempts real resolution (cwd, the target, the package dir) so a
292
+ // caller of the public API who simply omits `resolveTs` gets the same honest
293
+ // toolingWarning/analysisIncomplete signal a CLI invocation would.
294
+ const resolver = resolveTs ?? (() => resolveConsumerTypeScript([process.cwd(), targetPath, identity.packageDir]))
295
+ try {
296
+ const resolved = await resolver()
297
+ ts = resolved.ts
298
+ let tsVersion
299
+ try {
300
+ tsVersion = JSON.parse(readFileSync(join(resolved.tsPath, "..", "..", "package.json"), "utf8")).version
301
+ } catch {
302
+ tsVersion = undefined
303
+ }
304
+ resolvedTypeScript = { path: resolved.tsPath, version: tsVersion }
305
+ } catch (err) {
306
+ toolingWarning = err.message
307
+ // A required rule could not run at all for this target — the scan is INCOMPLETE, not clean.
308
+ // A caller must not read `findings` alone (which may still be a truthful, non-empty result
309
+ // for the two textual rules) as proof "everything was checked."
310
+ analysisIncomplete = true
311
+ }
312
+ }
313
+
314
+ const findings = []
315
+ const perFileHashes = []
316
+ for (const file of files) {
317
+ const rel = relative(targetPath, file) || file.split("/").pop()
318
+ try {
319
+ const { findings: fileFindings, contentHash } = analyzeFile(ts, identity, file, rel)
320
+ findings.push(...fileFindings)
321
+ perFileHashes.push(`${rel}:${contentHash}`)
322
+ } catch (err) {
323
+ findings.push(
324
+ makeFinding(identity, {
325
+ code: "analysis-error",
326
+ severity: "error",
327
+ confidence: "high",
328
+ file: rel,
329
+ line: 1,
330
+ column: 1,
331
+ evidence: String(err?.message ?? err),
332
+ explanation: "The diagnostic engine could not parse or read this file.",
333
+ suggestion:
334
+ "Fix the underlying syntax/read error, or pass --exclude for this path if it is intentionally out of scope (e.g. generated code).",
335
+ })
336
+ )
337
+ }
338
+ }
339
+
340
+ const targetInputHash = sha256(perFileHashes.sort().join("\n"))
341
+
342
+ return {
343
+ target: targetPath,
344
+ filesScanned: files.length,
345
+ targetInputHash,
346
+ skippedSymlinks,
347
+ skippedGenerated,
348
+ ...(toolingWarning ? { toolingWarning } : {}),
349
+ ...(resolvedTypeScript ? { resolvedTypeScript } : {}),
350
+ ...(analysisIncomplete ? { analysisIncomplete: true } : {}),
351
+ findings,
352
+ }
353
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * CLI for the migration-diagnostics engine — checks a migrated consumer for the defects that
3
+ * survive every other gate (see migration-diagnostics.mjs for the rule catalogue and the
4
+ * analysis-boundary documentation; this file is argv parsing and output formatting only).
5
+ *
6
+ * bun scripts/migration-sweep.mjs [--json] [--package <dir>] [--exclude <path>]... <path> [...]
7
+ *
8
+ * Exit code: 2 if any target has a hard input/argument error OR the requested analysis was
9
+ * INCOMPLETE (a script-file target whose TypeScript could not be resolved — a required rule did
10
+ * not run, which is not the same as "it ran and found nothing"); else 1 if any finding has
11
+ * severity "error" (legacy-volt-class / hand-written-volt-css-var — definite, unconditional textual
12
+ * defects); else 0. unestablished-palette-install never reports severity "error" — single-file
13
+ * analysis cannot prove a defect it might be wrong about (a bootstrap/provider elsewhere), so its
14
+ * findings are always "warning" and never independently produce a nonzero exit.
15
+ */
16
+ import { resolve } from "node:path"
17
+ import { analyzeTarget, resolveConsumerTypeScript, resolvePackageIdentity, RULES } from "./migration-diagnostics.mjs"
18
+
19
+ const DEFAULT_PACKAGE_DIR = resolve(import.meta.dirname, "..")
20
+ const KNOWN_FLAGS = new Set(["--json", "--package", "--exclude"])
21
+
22
+ function usage() {
23
+ return (
24
+ "usage: node migration-sweep.mjs [--json] [--package <dir>] [--exclude <path>]... <path> [...]\n" +
25
+ " <path> a repo root (scans its src/), a directory, or a single source file\n" +
26
+ " --json machine-readable JSON output instead of text (errors too)\n" +
27
+ " --package <dir> Griffel package to read token families from (default: this package)\n" +
28
+ " --exclude <path> path relative to the target to skip, repeatable\n" +
29
+ " During a flag-gated migration, point it at the NEW tree:\n" +
30
+ " node migration-sweep.mjs src/components/<name>-griffel src/surface"
31
+ )
32
+ }
33
+
34
+ /** Returns `{ targets, excludes, json, packageDir }` or `{ argError }` for a missing operand or
35
+ * an unrecognized `--flag` — never silently treats either as a target path. */
36
+ function parseArgs(argv) {
37
+ const targets = []
38
+ const excludes = []
39
+ let json = false
40
+ let packageDir = DEFAULT_PACKAGE_DIR
41
+
42
+ for (let i = 0; i < argv.length; i++) {
43
+ const a = argv[i]
44
+ if (!a.startsWith("--")) {
45
+ targets.push(a)
46
+ continue
47
+ }
48
+ if (!KNOWN_FLAGS.has(a)) {
49
+ return { argError: `Unknown flag: ${a}` }
50
+ }
51
+ if (a === "--json") {
52
+ json = true
53
+ continue
54
+ }
55
+ const operand = argv[i + 1]
56
+ if (operand === undefined || operand.startsWith("--")) {
57
+ return { argError: `Missing operand for ${a}` }
58
+ }
59
+ i++
60
+ if (a === "--package") packageDir = resolve(operand)
61
+ else if (a === "--exclude") excludes.push(operand)
62
+ }
63
+
64
+ return { targets, excludes, json, packageDir }
65
+ }
66
+
67
+ function emitError(json, message, extra = {}) {
68
+ if (json) {
69
+ console.log(JSON.stringify({ schemaVersion: 1, error: message, ...extra }, null, 2))
70
+ } else {
71
+ console.error(message)
72
+ if (extra.usage) console.error(extra.usage)
73
+ }
74
+ }
75
+
76
+ export async function runCli(argv) {
77
+ const parsed = parseArgs(argv)
78
+ // Flag parsing errors can occur before we know whether --json was requested (e.g. an unknown
79
+ // flag before --json itself) — best-effort: honor --json if it appears anywhere in argv.
80
+ const jsonRequested = parsed.json ?? argv.includes("--json")
81
+
82
+ if (parsed.argError) {
83
+ emitError(jsonRequested, parsed.argError, { usage: usage() })
84
+ return 2
85
+ }
86
+
87
+ const { targets, excludes, json, packageDir } = parsed
88
+ if (!targets.length) {
89
+ emitError(json, "No target path(s) given.", { usage: usage() })
90
+ return 2
91
+ }
92
+
93
+ let identity
94
+ try {
95
+ identity = resolvePackageIdentity(packageDir)
96
+ } catch (err) {
97
+ emitError(json, String(err?.message ?? err))
98
+ return 2
99
+ }
100
+
101
+ const resolveTs = () => resolveConsumerTypeScript([process.cwd(), ...targets.map((t) => resolve(t)), packageDir])
102
+
103
+ const results = []
104
+ for (const t of targets) {
105
+ results.push(await analyzeTarget(resolve(t), { identity, excludes, resolveTs }))
106
+ }
107
+
108
+ if (json) {
109
+ console.log(
110
+ JSON.stringify(
111
+ {
112
+ schemaVersion: 1,
113
+ package: {
114
+ name: identity.packageName,
115
+ version: identity.packageVersion,
116
+ generatedInputHash: identity.generatedInputHash,
117
+ },
118
+ rules: RULES,
119
+ results,
120
+ },
121
+ null,
122
+ 2
123
+ )
124
+ )
125
+ } else {
126
+ for (const r of results) {
127
+ const name = r.target.split("/").pop()
128
+ if (r.error) {
129
+ console.log(` ✗ ${name} — ${r.error}`)
130
+ continue
131
+ }
132
+ if (r.resolvedTypeScript) {
133
+ console.log(` typescript: ${r.resolvedTypeScript.version ?? "unknown version"} (${r.resolvedTypeScript.path})`)
134
+ }
135
+ if (r.skippedGenerated?.length) {
136
+ console.log(` skipped as generated: ${r.skippedGenerated.join(", ")}`)
137
+ }
138
+ if (r.skippedSymlinks?.length) {
139
+ console.log(` skipped symlinks: ${r.skippedSymlinks.join(", ")}`)
140
+ }
141
+ if (r.toolingWarning) {
142
+ // Never a green check here, even if findings is empty — the required rule did not run,
143
+ // so "no findings" means "not fully checked", not "checked and clean".
144
+ console.log(
145
+ ` ! ${name} — INCOMPLETE ANALYSIS: ${r.toolingWarning}` +
146
+ (r.findings.length ? ` (${r.findings.length} finding(s) from the rules that did run, below)` : "")
147
+ )
148
+ for (const f of r.findings) {
149
+ console.log(
150
+ ` [${f.severity}] ${f.code.padEnd(28)} ${f.file}:${f.line}:${f.column} ${f.evidence}`
151
+ )
152
+ }
153
+ continue
154
+ }
155
+ if (!r.findings.length) {
156
+ console.log(` ✓ ${name} (${r.filesScanned} file(s) scanned, targetInputHash ${r.targetInputHash.slice(0, 12)}…)`)
157
+ continue
158
+ }
159
+ console.log(` ✗ ${name} — ${r.findings.length} finding(s)`)
160
+ for (const f of r.findings) {
161
+ console.log(
162
+ ` [${f.severity}] ${f.code.padEnd(28)} ${f.file}:${f.line}:${f.column} ${f.evidence}`
163
+ )
164
+ }
165
+ }
166
+ console.log("")
167
+ }
168
+
169
+ const hasIncompleteOrInputError = results.some((r) => r.error || r.analysisIncomplete)
170
+ if (hasIncompleteOrInputError) return 2
171
+
172
+ const hasHardFailure = results.some((r) => r.findings?.some((f) => f.severity === "error"))
173
+ return hasHardFailure ? 1 : 0
174
+ }
175
+
176
+ if (import.meta.url === `file://${process.argv[1]}`) {
177
+ const code = await runCli(process.argv.slice(2))
178
+ process.exit(code)
179
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Jest/Vitest configuration for consumers of this package.
3
+ *
4
+ * WHY THIS EXISTS. The package is ESM-only. Jest 26 and 27 ignore the `exports` field, so before
5
+ * 0.2.2 a bare `@epilot/volt-ui-experimental-griffel` resolved to nothing and every suite that
6
+ * rendered a component died with "Cannot find module". Both pilot MFEs hit it; one hand-wrote
7
+ * `moduleNameMapper: { '^@epilot/…$': '<rootDir>/../../../node_modules/@epilot/…/dist/index.js' }`,
8
+ * a path that breaks the moment hoisting changes.
9
+ *
10
+ * 0.2.2 adds a `main` field, so those resolvers now find `dist/index.js` on their own and no
11
+ * mapping is needed. What is still needed is a TRANSFORM: the file they resolve is ESM, and Jest's
12
+ * default `transformIgnorePatterns` skips everything in `node_modules`. That is what this file
13
+ * supplies.
14
+ *
15
+ * There is deliberately no CommonJS build. The runtime keeps module-level state — `sheet.ts` holds
16
+ * the `installed` Set that dedups token sheets, and one shared renderer — and a dual ESM/CJS build
17
+ * would give a consumer two copies of it. That breaks the single-insertion-point and single-renderer
18
+ * guarantees `gate:isolation` and `gate:griffel-contract` assert. One format is a correctness
19
+ * requirement here, not an oversight. See DECISIONS D13.
20
+ *
21
+ * USAGE — spread it into your own config:
22
+ *
23
+ * const griffel = require("@epilot/volt-ui-experimental-griffel/testing/jest-preset.cjs")
24
+ *
25
+ * module.exports = griffel.withGriffel({
26
+ * preset: "ts-jest",
27
+ * // …your config…
28
+ * }, { alsoTransform: ["@xyflow"] })
29
+ *
30
+ * Requires `babel-jest` and `@babel/preset-env` as devDependencies. If you already transform JS in
31
+ * `node_modules` with something else, take `transformIgnorePatterns` from here and keep your own
32
+ * `transform`.
33
+ */
34
+ const PACKAGE_NAME = "@epilot/volt-ui-experimental-griffel"
35
+
36
+ /** Transform for the ESM this package ships. Keyed on `.js`/`.mjs`, which is all it emits. */
37
+ const transform = {
38
+ "^.+\\.m?js$": [
39
+ "babel-jest",
40
+ { presets: [["@babel/preset-env", { targets: { node: "current" } }]] },
41
+ ],
42
+ }
43
+
44
+ /**
45
+ * Jest ignores a file if ANY pattern matches, so an extra pattern cannot un-ignore what the
46
+ * default `/node_modules/` already caught — the array has to be replaced, not appended to. Hence
47
+ * the explicit `alsoTransform` list rather than a merge.
48
+ */
49
+ const transformIgnorePatterns = (alsoTransform = []) => [
50
+ `node_modules/(?!(${[PACKAGE_NAME, ...alsoTransform].join("|")})/)`,
51
+ ]
52
+
53
+ const withGriffel = (config = {}, { alsoTransform = [] } = {}) => ({
54
+ ...config,
55
+ transform: { ...transform, ...config.transform },
56
+ transformIgnorePatterns: transformIgnorePatterns(alsoTransform),
57
+ })
58
+
59
+ module.exports = {
60
+ PACKAGE_NAME,
61
+ transform,
62
+ transformIgnorePatterns: transformIgnorePatterns(),
63
+ withGriffel,
64
+ }
@@ -1,22 +0,0 @@
1
- import { getVoltRenderer as r } from "../renderer/index.js";
2
- import { makeStyles as n } from "../vendor/@griffel/core/src/makeStyles.js";
3
- import { makeResetStyles as o } from "../vendor/@griffel/core/src/makeResetStyles.js";
4
- function a(t) {
5
- const e = n(
6
- t
7
- );
8
- return function() {
9
- return e({ dir: "ltr", renderer: r() });
10
- };
11
- }
12
- function l(t) {
13
- const e = o(t);
14
- return function() {
15
- return e({ dir: "ltr", renderer: r() });
16
- };
17
- }
18
- export {
19
- l as makeVoltResetStyles,
20
- a as makeVoltStyles
21
- };
22
- //# sourceMappingURL=make-styles.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"make-styles.js","sources":["../../../../../../volt-runtime/dist/styles/make-styles.js"],"sourcesContent":["import { getVoltRenderer as r } from \"../renderer/index.js\";\nimport { makeStyles as n } from \"../vendor/@griffel/core/src/makeStyles.js\";\nimport { makeResetStyles as l } from \"../vendor/@griffel/core/src/makeResetStyles.js\";\nfunction i(e) {\n const t = n(\n e\n );\n return function() {\n return t({ dir: \"ltr\", renderer: r() });\n };\n}\nfunction c(e) {\n const t = l(e);\n return function() {\n return t({ dir: \"ltr\", renderer: r() });\n };\n}\nexport {\n c as makeVoltResetStyles,\n i as makeVoltStyles\n};\n//# sourceMappingURL=make-styles.js.map\n"],"names":["i","e","t","n","r","c","l"],"mappings":";;;AAGA,SAASA,EAAEC,GAAG;AACZ,QAAMC,IAAIC;AAAAA,IACRF;AAAA,EACJ;AACE,SAAO,WAAW;AAChB,WAAOC,EAAE,EAAE,KAAK,OAAO,UAAUE,EAAC,GAAI;AAAA,EACxC;AACF;AACA,SAASC,EAAEJ,GAAG;AACZ,QAAMC,IAAII,EAAEL,CAAC;AACb,SAAO,WAAW;AAChB,WAAOC,EAAE,EAAE,KAAK,OAAO,UAAUE,EAAC,GAAI;AAAA,EACxC;AACF;"}