@birdycom/omniacss-migrate 0.0.0-stage → 0.11.1

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/src/cli.mjs ADDED
@@ -0,0 +1,139 @@
1
+ // File: packages/migrate/src/cli.mjs
2
+ // OmniaCSS migrate CLI (used by bin/omniacss-migrate.mjs and `omniacss migrate`): scan | setup | codemod | build | css
3
+ // Author: Serge Cools | Framework: OmniaCSS
4
+
5
+ import path from "node:path";
6
+ import fs from "node:fs/promises";
7
+ import { parseArgs } from "node:util";
8
+ import { loadProject } from "./project.mjs";
9
+ import { scanDir, formatScan } from "./scan.mjs";
10
+ import { runCodemod, formatCodemod } from "./codemod.mjs";
11
+ import { planSetup, applySetup, formatPlan } from "./setup.mjs";
12
+ import { compileToken } from "./resolve.mjs";
13
+
14
+ export const HELP = `
15
+ omniacss-migrate - switch from Tailwind CSS to OmniaCSS and keep your class names
16
+
17
+ omniacss-migrate scan <dir> coverage report: how many classes the compat layer supports
18
+ omniacss-migrate setup <project> [--write] plan (default) or apply the switch (deps, vite/postcss config, CSS)
19
+ omniacss-migrate codemod <dir> [--write] rewrite classes to native OmniaCSS names where 1:1 (dry run by default)
20
+ omniacss-migrate build [-c glob]... [-o file] write the compat + core CSS once (no Vite/PostCSS needed)
21
+ omniacss-migrate css "<class>" print the CSS for one class
22
+
23
+ Bootstrap 3/4/5 -> OmniaCSS + OmniaUI
24
+ omniacss-migrate bootstrap scan <dir> report: what converts, what stays (grid), what needs a hand
25
+ omniacss-migrate bootstrap codemod <dir> --write rewrite classes, swap the <link>, write bootstrap-compat.css
26
+
27
+ Options
28
+ --write apply changes (setup, codemod)
29
+ --top <n> rows in the unsupported list (scan, codemod; default 40)
30
+ --json machine readable output (scan)
31
+ --breakpoints <omnia|tailwind> sm/md/lg values (default omnia)
32
+ --dark <class|media> dark mode strategy (default: detected)
33
+ --tailwind <3|4> naming flavor (default: detected)
34
+ --keep-brand-colors keep literal brand colors from tailwind.config instead of mapping to Omnia tokens
35
+ --bs <3|4|5> (bootstrap) Bootstrap version (default: detected)
36
+ --breakpoints exact (bootstrap) keep Bootstrap's px breakpoints as min-[…]: variants instead of the nearest Omnia breakpoint
37
+ --compat <file> (bootstrap) where to write the compat stylesheet (default bootstrap-compat.css)
38
+ --omnia-href, --omniaui-href, --compat-href (bootstrap) stylesheet URLs that replace the Bootstrap <link>
39
+ `;
40
+
41
+ function parse(args) {
42
+ return parseArgs({
43
+ args, allowPositionals: true,
44
+ options: {
45
+ write: { type: "boolean" }, top: { type: "string" }, json: { type: "boolean" }, help: { type: "boolean", short: "h" },
46
+ breakpoints: { type: "string" }, dark: { type: "string" }, tailwind: { type: "string" }, "keep-brand-colors": { type: "boolean" },
47
+ content: { type: "string", short: "c", multiple: true }, output: { type: "string", short: "o" }, backup: { type: "boolean" },
48
+ bs: { type: "string" }, compat: { type: "string" }, "omnia-href": { type: "string" }, "omniaui-href": { type: "string" }, "compat-href": { type: "string" },
49
+ },
50
+ });
51
+ }
52
+
53
+ let values, positionals;
54
+
55
+ function ctxOptions() {
56
+ return {
57
+ breakpoints: values.breakpoints, darkMode: values.dark, v4: values.tailwind ? Number(values.tailwind) >= 4 : undefined,
58
+ keepBrandColors: values["keep-brand-colors"],
59
+ };
60
+ }
61
+
62
+ export async function run(args = process.argv.slice(2)) {
63
+ ({ values, positionals } = parse(args));
64
+ const [command, target] = positionals;
65
+ if (!command || values.help || command === "help") { console.log(HELP); return; }
66
+ const cwd = process.cwd();
67
+ const dir = path.resolve(cwd, target || ".");
68
+ const top = Number(values.top || 40);
69
+
70
+ if (command === "bootstrap") {
71
+ const [, sub, where] = positionals;
72
+ if (!["scan", "codemod"].includes(sub)) { console.log(HELP); process.exitCode = 1; return; }
73
+ const { migrateBootstrap, formatBootstrap } = await import("./bootstrap/migrate.mjs");
74
+ const report = await migrateBootstrap(path.resolve(cwd, where || "."), {
75
+ write: sub === "codemod" && !!values.write, version: values.bs ? Number(values.bs) : undefined,
76
+ breakpoints: values.breakpoints === "exact" ? "exact" : "omnia", compatFile: values.compat,
77
+ omniaHref: values["omnia-href"], omniauiHref: values["omniaui-href"], compatHref: values["compat-href"],
78
+ });
79
+ console.log(formatBootstrap(report, { top }));
80
+ return;
81
+ }
82
+
83
+ if (command === "scan") {
84
+ const { ctx, info } = await loadProject(dir, ctxOptions());
85
+ const stats = await scanDir(dir, { ctx });
86
+ if (values.json) {
87
+ console.log(JSON.stringify({ dir, files: stats.files, occurrences: stats.occurrences, supported: stats.supported, coverage: +stats.coverage.toFixed(2),
88
+ unsupported: Object.fromEntries([...stats.unsupported].sort((a, b) => b[1] - a[1]).slice(0, top)) }, null, 2));
89
+ } else {
90
+ console.log(`Tailwind ${info.tailwindVersion ?? "?"} project, dark mode: ${ctx.darkMode}, ${info.themeVars.size} custom theme variables\n`);
91
+ console.log(formatScan(stats, { top }));
92
+ }
93
+ return;
94
+ }
95
+
96
+ if (command === "setup") {
97
+ const plan = await planSetup(dir, { keepBrandColors: values["keep-brand-colors"] });
98
+ if (values.write) await applySetup(plan, { backup: values.backup });
99
+ console.log(formatPlan(plan, dir, { write: !!values.write }));
100
+ return;
101
+ }
102
+
103
+ if (command === "codemod") {
104
+ const { ctx } = await loadProject(dir, { ...ctxOptions(), breakpoints: values.breakpoints || "omnia" });
105
+ const report = await runCodemod(dir, { ctx, write: !!values.write, cwd: dir });
106
+ console.log(formatCodemod(report, { top }));
107
+ return;
108
+ }
109
+
110
+ if (command === "build") {
111
+ const postcss = (await import("postcss")).default;
112
+ const plugin = (await import("./postcss.mjs")).default;
113
+ const output = values.output || "omnia-compat.css";
114
+ const content = values.content?.length ? values.content : undefined;
115
+ const input = '@import "tailwindcss";\n';
116
+ const result = await postcss([plugin({ cwd: dir, content, ...ctxOptionsForPlugin() })]).process(input, { from: path.join(dir, "entry.css") });
117
+ await fs.writeFile(path.resolve(cwd, output), result.css);
118
+ console.log(`Wrote ${output} (${(result.css.length / 1024).toFixed(1)} KB)`);
119
+ return;
120
+ }
121
+
122
+ if (command === "css") {
123
+ const { ctx } = await loadProject(cwd, ctxOptions());
124
+ for (const token of positionals.slice(1).length ? positionals.slice(1) : [target]) {
125
+ const css = compileToken(token, ctx);
126
+ console.log(css ?? `/* ${token}: not supported */`);
127
+ }
128
+ return;
129
+ }
130
+
131
+ console.error(`Unknown command: ${command}`);
132
+ console.log(HELP);
133
+ process.exitCode = 1;
134
+ }
135
+
136
+ function ctxOptionsForPlugin() {
137
+ return { breakpoints: values.breakpoints, darkMode: values.dark, tailwind: values.tailwind ? Number(values.tailwind) : undefined };
138
+ }
139
+
@@ -0,0 +1,82 @@
1
+ // File: packages/migrate/src/codemod.mjs
2
+ // Codemod: rewrite Tailwind-style class names to native OmniaCSS classes where the declarations are identical.
3
+ // Everything else is left untouched and reported. Dry-run unless `write` is true.
4
+ // Author: Serge Cools | Framework: OmniaCSS
5
+
6
+ import fs from "node:fs/promises";
7
+ import path from "node:path";
8
+ import { classStrings, walk, DEFAULT_EXTENSIONS } from "./extract.mjs";
9
+ import { isClassString, makeKind } from "./scan.mjs";
10
+ import { loadOmniaIndex, nativeFor } from "./native.mjs";
11
+
12
+ export const MANIFEST = "omniacss-migrate.manifest.json";
13
+
14
+ /**
15
+ * @param {string} dir
16
+ * @param {{ ctx: object, write?: boolean, extensions?: string[], cwd?: string }} o
17
+ */
18
+ export async function runCodemod(dir, o) {
19
+ const { ctx, write = false } = o;
20
+ const index = loadOmniaIndex(o.cwd || dir);
21
+ const kind = makeKind(ctx);
22
+ const memo = new Map();
23
+ const map = (t) => { if (!memo.has(t)) memo.set(t, nativeFor(t, ctx, index)); return memo.get(t); };
24
+ const files = await walk(dir, { extensions: o.extensions || DEFAULT_EXTENSIONS });
25
+ const report = { dir, write, files: [], total: 0, rewritten: 0, unchanged: 0, leftovers: new Map(), collisions: new Map(), mapping: new Map() };
26
+ for (const file of files) {
27
+ let text;
28
+ try { text = await fs.readFile(file, "utf8"); } catch { continue; }
29
+ const edits = [];
30
+ for (const s of classStrings(text)) {
31
+ if (!s.words.length) continue;
32
+ const kinds = s.words.map(kind);
33
+ if (!isClassString(kinds, s.hinted)) continue;
34
+ s.spans.forEach((sp, i) => {
35
+ if (kinds[i] !== "supported") return;
36
+ report.total++;
37
+ const r = map(sp.token);
38
+ if (r?.native && r.native !== sp.token) {
39
+ edits.push({ ...sp, to: r.native });
40
+ report.rewritten++;
41
+ report.mapping.set(sp.token, r.native);
42
+ } else if (r?.collision) {
43
+ report.collisions.set(sp.token, (report.collisions.get(sp.token) || 0) + 1);
44
+ report.leftovers.set(sp.token, (report.leftovers.get(sp.token) || 0) + 1);
45
+ } else if (r?.native) report.unchanged++;
46
+ else report.leftovers.set(sp.token, (report.leftovers.get(sp.token) || 0) + 1);
47
+ });
48
+ }
49
+ if (!edits.length) continue;
50
+ let out = text;
51
+ for (const e of edits.sort((a, b) => b.start - a.start)) out = out.slice(0, e.start) + e.to + out.slice(e.end);
52
+ report.files.push({ file, changes: edits.length, sample: edits.slice(0, 5).map((e) => `${e.token} -> ${e.to}`) });
53
+ if (write) await fs.writeFile(file, out);
54
+ }
55
+ const native = new Set(report.mapping.values());
56
+ const manifestFile = path.join(o.manifestDir || dir, MANIFEST);
57
+ if (write && native.size) {
58
+ const prev = JSON.parse(await fs.readFile(manifestFile, "utf8").catch(() => "{}"));
59
+ for (const t of prev.native || []) native.add(t);
60
+ const ambiguous = [...native].filter((t) => report.leftovers.has(t));
61
+ await fs.writeFile(manifestFile, JSON.stringify({ note: "Written by omniacss-migrate codemod: native OmniaCSS classes the compat layer must not emit.", native: [...native].sort(), ambiguous }, null, 1) + "\n");
62
+ report.manifestFile = manifestFile;
63
+ }
64
+ report.ambiguous = [...native].filter((t) => report.leftovers.has(t));
65
+ return report;
66
+ }
67
+
68
+ export function formatCodemod(r, { top = 25 } = {}) {
69
+ const pct = r.total ? ((r.rewritten / r.total) * 100).toFixed(1) : "0";
70
+ const lines = [];
71
+ lines.push(`${r.write ? "Rewrote" : "Dry run, would rewrite"} ${r.rewritten} of ${r.total} class occurrences (${pct}%) in ${r.files.length} files under ${r.dir}`);
72
+ for (const f of r.files.slice(0, 15)) lines.push(` ${f.file} (${f.changes}) e.g. ${f.sample.slice(0, 3).join(", ")}`);
73
+ if (r.files.length > 15) lines.push(` ... and ${r.files.length - 15} more files`);
74
+ const left = [...r.leftovers.entries()].sort((a, b) => b[1] - a[1]);
75
+ lines.push("", `Left as they are (no 1:1 native class): ${left.reduce((n, [, c]) => n + c, 0)} occurrences, ${left.length} unique`);
76
+ for (const [t, n] of left.slice(0, top)) lines.push(` ${String(n).padStart(5)} ${t}${r.collisions.has(t) ? " (name exists in OmniaCSS with different meaning)" : ""}`);
77
+ if (r.manifestFile) lines.push("", `Wrote ${r.manifestFile}: the compat layer skips these native classes so names never collide.`);
78
+ if (r.ambiguous?.length) lines.push(`Ambiguous (both a native class and a Tailwind-style leftover): ${r.ambiguous.slice(0, 10).join(", ")}`);
79
+ lines.push("", "Note: leftover classes keep working only while the compat layer (@birdycom/omniacss-migrate/postcss) stays enabled.");
80
+ if (!r.write) lines.push("Run again with --write to apply.");
81
+ return lines.join("\n");
82
+ }
package/src/core.mjs ADDED
@@ -0,0 +1,67 @@
1
+ // File: packages/migrate/src/core.mjs
2
+ // Bridge to @birdycom/omniacss: the on-demand engine that emits tokens + Omnia-native classes.
3
+ // Author: Serge Cools | Framework: OmniaCSS
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { createRequire } from "node:module";
8
+ import { fileURLToPath, pathToFileURL } from "node:url";
9
+
10
+ const here = path.dirname(fileURLToPath(import.meta.url));
11
+ let enginePromise;
12
+
13
+ /** Load @birdycom/omniacss/engine (installed package first, monorepo sibling as fallback). */
14
+ export function loadEngine(cwd = process.cwd()) {
15
+ enginePromise ||= (async () => {
16
+ const tries = [
17
+ () => createRequire(path.join(cwd, "package.json")).resolve("@birdycom/omniacss/engine"),
18
+ () => createRequire(import.meta.url).resolve("@birdycom/omniacss/engine"),
19
+ () => path.resolve(here, "../../core/src/engine.mjs"),
20
+ ];
21
+ for (const t of tries) {
22
+ try { const p = t(); await fs.access(p); return await import(pathToFileURL(p).href); } catch { /* next */ }
23
+ }
24
+ throw new Error("@birdycom/omniacss-migrate: cannot find @birdycom/omniacss. Install it with: npm install @birdycom/omniacss");
25
+ })();
26
+ return enginePromise;
27
+ }
28
+
29
+ const cache = new Map();
30
+ let classNames, classNamesFor;
31
+
32
+ /** Every class name that exists in the full Omnia CSS (cached): lets us ignore unrelated words in the content. */
33
+ export function omniaClassNames(full) {
34
+ if (classNames && classNamesFor === full.length) return classNames;
35
+ classNames = new Set();
36
+ for (const m of full.matchAll(/\.((?:\\.|[A-Za-z0-9_-])+)/g)) classNames.add(m[1].replace(/\\(.)/g, "$1"));
37
+ classNamesFor = full.length;
38
+ return classNames;
39
+ }
40
+
41
+ /**
42
+ * Omnia core CSS for a project: tokens (every var(--color-*) the compat output uses) plus the Omnia-native
43
+ * classes found in the content. Classes the compat layer resolves are excluded, so a Tailwind-style `p-4`
44
+ * never collides with Omnia's own `p-4`.
45
+ * @param {{ candidates: Set<string>, compatCss: string, exclude: Set<string>, cwd?: string, layer?: string|false, mode?: true|"tokens" }} o
46
+ */
47
+ export async function buildCoreCss({ candidates, compatCss, exclude, cwd, layer = "omnia", mode = true }) {
48
+ const engine = await loadEngine(cwd);
49
+ const full = mode === true ? await engine.loadFullCss() : await fs.readFile(path.join(engine.DIST, "oc-colors.css"), "utf8");
50
+ const set = new Set();
51
+ if (mode === true) {
52
+ const known = omniaClassNames(full);
53
+ for (const c of candidates) if (known.has(c) && !exclude.has(c)) set.add(c);
54
+ }
55
+ const key = `${layer}|${mode}|${hash(compatCss)}|${[...set].sort().join(" ")}`;
56
+ if (cache.has(key)) return cache.get(key);
57
+ const css = engine.generate(full, set, { layer, referencedBy: [compatCss] });
58
+ cache.clear();
59
+ cache.set(key, css);
60
+ return css;
61
+ }
62
+
63
+ function hash(s) {
64
+ let h = 5381;
65
+ for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) | 0;
66
+ return h;
67
+ }
@@ -0,0 +1,290 @@
1
+ {
2
+ "amber-50": "--color-beige-2",
3
+ "amber-100": "--color-wheat-2",
4
+ "amber-200": "--color-orange-2",
5
+ "amber-300": "--color-orange-10",
6
+ "amber-400": "--color-orange-40",
7
+ "amber-500": "--color-orange-60",
8
+ "amber-600": "--color-peru-60",
9
+ "amber-700": "--color-peru-80",
10
+ "amber-800": "--color-peru-95",
11
+ "amber-900": "--color-brown-70",
12
+ "amber-950": "--color-black-20",
13
+ "black": "--color-black-100",
14
+ "blue-50": "--color-frostblue-2",
15
+ "blue-100": "--color-frostblue-50",
16
+ "blue-200": "--color-mistblue-10",
17
+ "blue-300": "--color-vividblue-20",
18
+ "blue-400": "--color-vividblue-60",
19
+ "blue-500": "--color-vividblue-80",
20
+ "blue-600": "--color-blue-30",
21
+ "blue-700": "--color-blue-50",
22
+ "blue-800": "--color-vividblue-95",
23
+ "blue-900": "--color-stormblue-50",
24
+ "blue-950": "--color-stormblue-80",
25
+ "cyan-50": "--color-azure-50",
26
+ "cyan-100": "--color-azure-60",
27
+ "cyan-200": "--color-skycyan-2",
28
+ "cyan-300": "--color-cyan-60",
29
+ "cyan-400": "--color-skycyan-60",
30
+ "cyan-500": "--color-skycyan-70",
31
+ "cyan-600": "--color-skycyan-90",
32
+ "cyan-700": "--color-teal-60",
33
+ "cyan-800": "--color-teal-80",
34
+ "cyan-900": "--color-duskblue-30",
35
+ "cyan-950": "--color-twilightblue-50",
36
+ "emerald-50": "--color-azure-50",
37
+ "emerald-100": "--color-azure-60",
38
+ "emerald-200": "--color-turquoise-2",
39
+ "emerald-300": "--color-turquoise-40",
40
+ "emerald-400": "--color-green-2",
41
+ "emerald-500": "--color-green-10",
42
+ "emerald-600": "--color-green-40",
43
+ "emerald-700": "--color-teal-60",
44
+ "emerald-800": "--color-teal-80",
45
+ "emerald-900": "--color-teal-95",
46
+ "emerald-950": "--color-black-20",
47
+ "fuchsia-50": "--color-frostblue-2",
48
+ "fuchsia-100": "--color-frostblue-40",
49
+ "fuchsia-200": "--color-mistblue-2",
50
+ "fuchsia-300": "--color-violet-20",
51
+ "fuchsia-400": "--color-violet-60",
52
+ "fuchsia-500": "--color-magenta-60",
53
+ "fuchsia-600": "--color-magenta-70",
54
+ "fuchsia-700": "--color-violet-95",
55
+ "fuchsia-800": "--color-purple-50",
56
+ "fuchsia-900": "--color-purple-60",
57
+ "fuchsia-950": "--color-purple-95",
58
+ "gray-50": "--color-snow-50",
59
+ "gray-100": "--color-linen-30",
60
+ "gray-200": "--color-silver-2",
61
+ "gray-300": "--color-silver-20",
62
+ "gray-400": "--color-gray-20",
63
+ "gray-500": "--color-black-2",
64
+ "gray-600": "--color-duskblue-20",
65
+ "gray-700": "--color-shallowblue-30",
66
+ "gray-800": "--color-shallowblue-40",
67
+ "gray-900": "--color-shallowblue-50",
68
+ "gray-950": "--color-black-40",
69
+ "green-50": "--color-azure-50",
70
+ "green-100": "--color-beige-50",
71
+ "green-200": "--color-turquoise-2",
72
+ "green-300": "--color-turquoise-20",
73
+ "green-400": "--color-green-2",
74
+ "green-500": "--color-green-10",
75
+ "green-600": "--color-green-30",
76
+ "green-700": "--color-green-50",
77
+ "green-800": "--color-green-70",
78
+ "green-900": "--color-teal-90",
79
+ "green-950": "--color-black-20",
80
+ "indigo-50": "--color-frostblue-20",
81
+ "indigo-100": "--color-frostblue-50",
82
+ "indigo-200": "--color-mistblue-10",
83
+ "indigo-300": "--color-vividblue-30",
84
+ "indigo-400": "--color-vividblue-60",
85
+ "indigo-500": "--color-blue-30",
86
+ "indigo-600": "--color-blue-40",
87
+ "indigo-700": "--color-blue-50",
88
+ "indigo-800": "--color-indigo-50",
89
+ "indigo-900": "--color-stormblue-60",
90
+ "indigo-950": "--color-stormblue-90",
91
+ "lime-50": "--color-beige-2",
92
+ "lime-100": "--color-beige-50",
93
+ "lime-200": "--color-yellow-2",
94
+ "lime-300": "--color-yellow-60",
95
+ "lime-400": "--color-yellow-70",
96
+ "lime-500": "--color-green-2",
97
+ "lime-600": "--color-green-20",
98
+ "lime-700": "--color-green-50",
99
+ "lime-800": "--color-green-70",
100
+ "lime-900": "--color-green-80",
101
+ "lime-950": "--color-black-20",
102
+ "mauve-50": "--color-snow-50",
103
+ "mauve-100": "--color-linen-50",
104
+ "mauve-200": "--color-silver-2",
105
+ "mauve-300": "--color-silver-20",
106
+ "mauve-400": "--color-silver-70",
107
+ "mauve-500": "--color-silver-95",
108
+ "mauve-600": "--color-shallowblue-20",
109
+ "mauve-700": "--color-shallowblue-30",
110
+ "mauve-800": "--color-duskblue-50",
111
+ "mauve-900": "--color-black-30",
112
+ "mauve-950": "--color-black-40",
113
+ "mist-50": "--color-snow-50",
114
+ "mist-100": "--color-linen-50",
115
+ "mist-200": "--color-silver-2",
116
+ "mist-300": "--color-silver-20",
117
+ "mist-400": "--color-gray-20",
118
+ "mist-500": "--color-black-2",
119
+ "mist-600": "--color-duskblue-20",
120
+ "mist-700": "--color-black-10",
121
+ "mist-800": "--color-black-20",
122
+ "mist-900": "--color-black-30",
123
+ "mist-950": "--color-black-40",
124
+ "neutral-50": "--color-snow-50",
125
+ "neutral-100": "--color-linen-20",
126
+ "neutral-200": "--color-silver-2",
127
+ "neutral-300": "--color-silver-20",
128
+ "neutral-400": "--color-gray-20",
129
+ "neutral-500": "--color-black-2",
130
+ "neutral-600": "--color-shallowblue-20",
131
+ "neutral-700": "--color-black-10",
132
+ "neutral-800": "--color-black-20",
133
+ "neutral-900": "--color-black-30",
134
+ "neutral-950": "--color-black-40",
135
+ "olive-50": "--color-snow-50",
136
+ "olive-100": "--color-linen-40",
137
+ "olive-200": "--color-snow-60",
138
+ "olive-300": "--color-silver-10",
139
+ "olive-400": "--color-gray-10",
140
+ "olive-500": "--color-black-2",
141
+ "olive-600": "--color-gray-90",
142
+ "olive-700": "--color-black-10",
143
+ "olive-800": "--color-black-20",
144
+ "olive-900": "--color-black-30",
145
+ "olive-950": "--color-black-40",
146
+ "orange-50": "--color-linen-2",
147
+ "orange-100": "--color-wheat-2",
148
+ "orange-200": "--color-wheat-50",
149
+ "orange-300": "--color-orange-30",
150
+ "orange-400": "--color-orange-60",
151
+ "orange-500": "--color-red-30",
152
+ "orange-600": "--color-red-50",
153
+ "orange-700": "--color-brown-40",
154
+ "orange-800": "--color-brown-60",
155
+ "orange-900": "--color-brown-70",
156
+ "orange-950": "--color-black-20",
157
+ "pink-50": "--color-linen-20",
158
+ "pink-100": "--color-white-60",
159
+ "pink-200": "--color-pink-30",
160
+ "pink-300": "--color-pink-60",
161
+ "pink-400": "--color-violet-60",
162
+ "pink-500": "--color-violet-70",
163
+ "pink-600": "--color-red-60",
164
+ "pink-700": "--color-brown-50",
165
+ "pink-800": "--color-brown-60",
166
+ "pink-900": "--color-brown-70",
167
+ "pink-950": "--color-brown-95",
168
+ "purple-50": "--color-frostblue-2",
169
+ "purple-100": "--color-frostblue-50",
170
+ "purple-200": "--color-mistblue-2",
171
+ "purple-300": "--color-violet-20",
172
+ "purple-400": "--color-indigo-2",
173
+ "purple-500": "--color-violet-80",
174
+ "purple-600": "--color-indigo-20",
175
+ "purple-700": "--color-indigo-30",
176
+ "purple-800": "--color-indigo-40",
177
+ "purple-900": "--color-indigo-50",
178
+ "purple-950": "--color-indigo-70",
179
+ "red-50": "--color-linen-20",
180
+ "red-100": "--color-snow-60",
181
+ "red-200": "--color-pink-40",
182
+ "red-300": "--color-pink-60",
183
+ "red-400": "--color-red-30",
184
+ "red-500": "--color-red-50",
185
+ "red-600": "--color-red-60",
186
+ "red-700": "--color-brown-50",
187
+ "red-800": "--color-brown-60",
188
+ "red-900": "--color-brown-80",
189
+ "red-950": "--color-black-30",
190
+ "rose-50": "--color-linen-30",
191
+ "rose-100": "--color-snow-60",
192
+ "rose-200": "--color-pink-30",
193
+ "rose-300": "--color-pink-60",
194
+ "rose-400": "--color-brown-10",
195
+ "rose-500": "--color-red-50",
196
+ "rose-600": "--color-red-60",
197
+ "rose-700": "--color-brown-50",
198
+ "rose-800": "--color-brown-60",
199
+ "rose-900": "--color-brown-70",
200
+ "rose-950": "--color-brown-95",
201
+ "sky-50": "--color-frostblue-2",
202
+ "sky-100": "--color-frostblue-40",
203
+ "sky-200": "--color-skycyan-10",
204
+ "sky-300": "--color-skycyan-50",
205
+ "sky-400": "--color-skycyan-70",
206
+ "sky-500": "--color-skycyan-80",
207
+ "sky-600": "--color-vividblue-80",
208
+ "sky-700": "--color-vividblue-95",
209
+ "sky-800": "--color-stormblue-40",
210
+ "sky-900": "--color-stormblue-50",
211
+ "sky-950": "--color-twilightblue-50",
212
+ "slate-50": "--color-snow-50",
213
+ "slate-100": "--color-frostblue-10",
214
+ "slate-200": "--color-azure-60",
215
+ "slate-300": "--color-silver-20",
216
+ "slate-400": "--color-frostblue-90",
217
+ "slate-500": "--color-twilightblue-10",
218
+ "slate-600": "--color-duskblue-20",
219
+ "slate-700": "--color-twilightblue-40",
220
+ "slate-800": "--color-duskblue-50",
221
+ "slate-900": "--color-duskblue-60",
222
+ "slate-950": "--color-shallowblue-90",
223
+ "stone-50": "--color-snow-50",
224
+ "stone-100": "--color-linen-20",
225
+ "stone-200": "--color-silver-2",
226
+ "stone-300": "--color-silver-20",
227
+ "stone-400": "--color-silver-70",
228
+ "stone-500": "--color-black-2",
229
+ "stone-600": "--color-gray-90",
230
+ "stone-700": "--color-black-10",
231
+ "stone-800": "--color-black-20",
232
+ "stone-900": "--color-black-30",
233
+ "stone-950": "--color-black-40",
234
+ "taupe-50": "--color-snow-50",
235
+ "taupe-100": "--color-linen-50",
236
+ "taupe-200": "--color-silver-2",
237
+ "taupe-300": "--color-silver-20",
238
+ "taupe-400": "--color-silver-70",
239
+ "taupe-500": "--color-gray-60",
240
+ "taupe-600": "--color-gray-90",
241
+ "taupe-700": "--color-black-10",
242
+ "taupe-800": "--color-black-20",
243
+ "taupe-900": "--color-black-30",
244
+ "taupe-950": "--color-black-40",
245
+ "teal-50": "--color-azure-50",
246
+ "teal-100": "--color-azure-60",
247
+ "teal-200": "--color-turquoise-2",
248
+ "teal-300": "--color-turquoise-40",
249
+ "teal-400": "--color-turquoise-60",
250
+ "teal-500": "--color-turquoise-70",
251
+ "teal-600": "--color-teal-40",
252
+ "teal-700": "--color-teal-60",
253
+ "teal-800": "--color-teal-80",
254
+ "teal-900": "--color-teal-90",
255
+ "teal-950": "--color-shallowblue-40",
256
+ "violet-50": "--color-frostblue-10",
257
+ "violet-100": "--color-frostblue-50",
258
+ "violet-200": "--color-mistblue-2",
259
+ "violet-300": "--color-mistblue-40",
260
+ "violet-400": "--color-indigo-2",
261
+ "violet-500": "--color-indigo-10",
262
+ "violet-600": "--color-indigo-20",
263
+ "violet-700": "--color-indigo-30",
264
+ "violet-800": "--color-indigo-40",
265
+ "violet-900": "--color-indigo-50",
266
+ "violet-950": "--color-indigo-70",
267
+ "white": "--color-white-100",
268
+ "yellow-50": "--color-beige-2",
269
+ "yellow-100": "--color-wheat-2",
270
+ "yellow-200": "--color-yellow-2",
271
+ "yellow-300": "--color-yellow-60",
272
+ "yellow-400": "--color-orange-20",
273
+ "yellow-500": "--color-orange-50",
274
+ "yellow-600": "--color-orange-70",
275
+ "yellow-700": "--color-peru-80",
276
+ "yellow-800": "--color-peru-95",
277
+ "yellow-900": "--color-peru-95",
278
+ "yellow-950": "--color-black-20",
279
+ "zinc-50": "--color-snow-50",
280
+ "zinc-100": "--color-linen-30",
281
+ "zinc-200": "--color-silver-2",
282
+ "zinc-300": "--color-silver-20",
283
+ "zinc-400": "--color-silver-70",
284
+ "zinc-500": "--color-black-2",
285
+ "zinc-600": "--color-shallowblue-20",
286
+ "zinc-700": "--color-black-10",
287
+ "zinc-800": "--color-black-20",
288
+ "zinc-900": "--color-black-30",
289
+ "zinc-950": "--color-black-40"
290
+ }
@@ -0,0 +1,91 @@
1
+ // File: packages/migrate/src/extract.mjs
2
+ // Extract class-like tokens from source text (Blade, Vue, JSX/TSX, JS, HTML …), keeping arbitrary values
3
+ // such as `w-[37px]`, `[&_svg:not([class*='size-'])]:size-4` and `bg-(--brand)` intact.
4
+ // Author: Serge Cools | Framework: OmniaCSS
5
+
6
+ import fs from "node:fs/promises";
7
+ import path from "node:path";
8
+
9
+ export const DEFAULT_EXTENSIONS = ["blade.php", "php", "vue", "jsx", "tsx", "js", "ts", "mjs", "html", "svelte", "astro", "mdx", "twig", "erb"];
10
+ const IGNORE_DIRS = new Set(["node_modules", "vendor", ".git", "storage", "dist", "build", "public", ".next", ".nuxt", ".output", "bootstrap", ".cache", ".turbo", ".astro"]);
11
+
12
+ const DELIM = /[\s"'`{},;<>=|\\$+?^~]/;
13
+
14
+ /**
15
+ * Call `cb(token, start, end)` for every class-like token in `text`.
16
+ * Arbitrary values such as `w-[37px]` stay in one piece.
17
+ */
18
+ export function scanTokens(text, cb) {
19
+ let cur = "", depth = 0, mark = 0, start = 0;
20
+ const flush = (end) => {
21
+ if (cur) {
22
+ const lead = cur.length - cur.replace(/^[.:]+/, "").length;
23
+ const t = cur.replace(/^[.:]+/, "").replace(/[.:,]+$/, "");
24
+ if (t && t.length <= 250 && /^[A-Za-z0-9!@\[*(-]/.test(t)) cb(t, start + lead, start + lead + t.length);
25
+ }
26
+ cur = "";
27
+ };
28
+ for (let i = 0; i < text.length; i++) {
29
+ const ch = text[i];
30
+ if (depth > 0) {
31
+ if (/\s/.test(ch) || cur.length > 250) { i = mark; cur = ""; depth = 0; continue; } // not an arbitrary value after all
32
+ cur += ch;
33
+ if (ch === "[" || ch === "(") depth++;
34
+ else if (ch === "]" || ch === ")") depth--;
35
+ continue;
36
+ }
37
+ if (ch === "[" || ch === "(") {
38
+ const prev = cur.slice(-1);
39
+ const next = text[i + 1];
40
+ const attach = ch === "[" ? (cur === "" ? !!next && !/[\s"'`\]]/.test(next) : /[-:!]/.test(prev)) : /[-:!]/.test(prev);
41
+ if (attach) { if (!cur) start = i; mark = i; depth = 1; cur += ch; continue; }
42
+ flush(i); continue;
43
+ }
44
+ if (DELIM.test(ch) || ch === ")" || ch === "]" || ch === "&" || (ch === "#" && cur === "")) { flush(i); continue; }
45
+ if (!cur) start = i;
46
+ cur += ch;
47
+ }
48
+ flush(text.length);
49
+ }
50
+
51
+ /** Class-like tokens in a text (duplicates are kept, so callers can count occurrences). */
52
+ export function extractTokens(text) {
53
+ const out = [];
54
+ scanTokens(text, (t) => out.push(t));
55
+ return out;
56
+ }
57
+
58
+ /** Walk `dir` and return files with one of the given extensions. */
59
+ export async function walk(dir, { extensions = DEFAULT_EXTENSIONS, ignore = IGNORE_DIRS } = {}) {
60
+ const files = [];
61
+ async function rec(d) {
62
+ let entries;
63
+ try { entries = await fs.readdir(d, { withFileTypes: true }); } catch { return; }
64
+ for (const e of entries) {
65
+ if (e.isDirectory()) { if (!ignore.has(e.name) && !e.name.startsWith(".")) await rec(path.join(d, e.name)); continue; }
66
+ if (extensions.some((x) => e.name.endsWith("." + x))) files.push(path.join(d, e.name));
67
+ }
68
+ }
69
+ await rec(dir);
70
+ return files;
71
+ }
72
+
73
+ const STRING_RE = /"(?:[^"\\\n]|\\.){0,3000}"|'(?:[^'\\\n]|\\.){0,3000}'|`(?:[^`\\]|\\.){0,4000}`/g;
74
+ const CLASS_CONTEXT = /(class(Name)?|:class|\bcn|clsx|cva|twMerge|classNames|tv|@class)\W{0,6}$/;
75
+
76
+ /**
77
+ * Candidate class strings: quoted strings (and attribute values) with their preceding context.
78
+ * @returns {Array<{ words: string[], spans: Array<{token:string,start:number,end:number}>, hinted: boolean }>}
79
+ */
80
+ export function classStrings(text) {
81
+ const out = [];
82
+ for (const m of text.matchAll(STRING_RE)) {
83
+ const inner = m[0].slice(1, -1);
84
+ if (!inner.trim()) continue;
85
+ const before = text.slice(Math.max(0, m.index - 24), m.index);
86
+ const tokens = [];
87
+ scanTokens(inner, (t, s, e) => tokens.push({ token: t, start: m.index + 1 + s, end: m.index + 1 + e }));
88
+ out.push({ words: tokens.map((t) => t.token), spans: tokens, hinted: CLASS_CONTEXT.test(before) });
89
+ }
90
+ return out;
91
+ }