@codefast/cli 0.3.7-canary.0 → 0.3.13-canary.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.
- package/README.md +178 -262
- package/dist/analyze-DvtJ3j9m.mjs +92 -0
- package/dist/arrange-Bbh62DrU.mjs +107 -0
- package/dist/arrange-BqP2DI3w.mjs +8 -0
- package/dist/ast-helpers-MDRYuIAk.mjs +115 -0
- package/dist/bin.mjs +8 -0
- package/dist/caught-unknown-message-CRhkDWO7.mjs +20 -0
- package/dist/collectors-cn-BS8TTQ8O.mjs +61 -0
- package/dist/collectors-jsx-D9tL2ZPM.mjs +20 -0
- package/dist/collectors-tv-D2PXZaRt.mjs +170 -0
- package/dist/colors-BjdkCfGK.mjs +12 -0
- package/dist/commands/arrange.mjs +2 -0
- package/dist/commands/mirror.mjs +2 -0
- package/dist/commands/mirror.test.mjs +48 -0
- package/dist/commands/tag.mjs +2 -0
- package/dist/config-DVia_f9a.mjs +3 -0
- package/dist/config-reporter-SCcWDZc1.mjs +10 -0
- package/dist/constants-AtPhF-Ct.mjs +169 -0
- package/dist/constants-Bq5wdyLC.mjs +8 -0
- package/dist/dirent-list-BSDeRG5i.mjs +14 -0
- package/dist/engine-CcWEHSCq.mjs +287 -0
- package/dist/engine-DRTdkc5u.mjs +236 -0
- package/dist/errors-CFH4w6ic.mjs +15 -0
- package/dist/errors-CpU1ogfA.mjs +19 -0
- package/dist/formatters-DGE8jJ53.mjs +52 -0
- package/dist/group-file-ClW7XdD6.mjs +113 -0
- package/dist/grouping-c1grpCQT.mjs +235 -0
- package/dist/imports-DSVq1xhB.mjs +54 -0
- package/dist/lib/arrange/application/analyze.mjs +2 -0
- package/dist/lib/arrange/application/analyze.test.mjs +171 -0
- package/dist/lib/arrange/application/group-file.mjs +2 -0
- package/dist/lib/arrange/application/group-file.test.mjs +273 -0
- package/dist/lib/arrange/application/run-target.mjs +2 -0
- package/dist/lib/arrange/application/run-target.test.mjs +97 -0
- package/dist/lib/arrange/domain/ast/ast-helpers.mjs +2 -0
- package/dist/lib/arrange/domain/ast/ast-helpers.test.mjs +148 -0
- package/dist/lib/arrange/domain/ast/collectors-cn.mjs +2 -0
- package/dist/lib/arrange/domain/ast/collectors-cn.test.mjs +58 -0
- package/dist/lib/arrange/domain/ast/collectors-jsx.mjs +2 -0
- package/dist/lib/arrange/domain/ast/collectors-jsx.test.mjs +20 -0
- package/dist/lib/arrange/domain/ast/collectors-tv.mjs +2 -0
- package/dist/lib/arrange/domain/ast/collectors-tv.test.mjs +145 -0
- package/dist/lib/arrange/domain/ast/targets.mjs +2 -0
- package/dist/lib/arrange/domain/ast/targets.test.mjs +50 -0
- package/dist/lib/arrange/domain/constants.mjs +2 -0
- package/dist/lib/arrange/domain/errors.mjs +2 -0
- package/dist/lib/arrange/domain/grouping.mjs +2 -0
- package/dist/lib/arrange/domain/grouping.test.mjs +186 -0
- package/dist/lib/arrange/domain/imports.mjs +2 -0
- package/dist/lib/arrange/domain/imports.test.mjs +48 -0
- package/dist/lib/arrange/domain/tokenizer.mjs +2 -0
- package/dist/lib/arrange/domain/tokenizer.test.mjs +165 -0
- package/dist/lib/arrange/domain/types.mjs +1 -0
- package/dist/lib/arrange/index.mjs +16 -0
- package/dist/lib/arrange/infra/walk.mjs +2 -0
- package/dist/lib/arrange/infra/walk.test.mjs +50 -0
- package/dist/lib/arrange/presentation/formatters.mjs +2 -0
- package/dist/lib/arrange/presentation/formatters.test.mjs +73 -0
- package/dist/lib/arrange/presentation/report.mjs +2 -0
- package/dist/lib/config/domain/schema.mjs +2 -0
- package/dist/lib/config/index.mjs +4 -0
- package/dist/lib/config/infra/loader.mjs +2 -0
- package/dist/lib/config/infra/loader.test.mjs +116 -0
- package/dist/lib/infra/caught-unknown-message.mjs +2 -0
- package/dist/lib/infra/config-reporter.mjs +2 -0
- package/dist/lib/infra/fs-contract.mjs +1 -0
- package/dist/lib/infra/node-io.mjs +2 -0
- package/dist/lib/infra/workspace/repo-root.mjs +2 -0
- package/dist/lib/infra/workspace/repo-root.test.mjs +45 -0
- package/dist/lib/mirror/application/engine.mjs +2 -0
- package/dist/lib/mirror/application/sync.mjs +2 -0
- package/dist/lib/mirror/application/sync.test.mjs +482 -0
- package/dist/lib/mirror/domain/constants.mjs +2 -0
- package/dist/lib/mirror/domain/errors.mjs +2 -0
- package/dist/lib/mirror/domain/types.mjs +1 -0
- package/dist/lib/mirror/index.mjs +3 -0
- package/dist/lib/mirror/infra/dirent-list.mjs +2 -0
- package/dist/lib/mirror/infra/dirent-list.test.mjs +18 -0
- package/dist/lib/mirror/infra/package-filter.mjs +2 -0
- package/dist/lib/mirror/infra/package-filter.test.mjs +32 -0
- package/dist/lib/mirror/infra/path-normalizer.mjs +2 -0
- package/dist/lib/mirror/infra/update-pkg.mjs +2 -0
- package/dist/lib/mirror/infra/workspace-packages.mjs +2 -0
- package/dist/lib/mirror/infra/workspace-packages.test.mjs +167 -0
- package/dist/lib/mirror/presentation/reporter.mjs +2 -0
- package/dist/lib/tag/application/engine.mjs +2 -0
- package/dist/lib/tag/application/engine.test.mjs +159 -0
- package/dist/lib/tag/domain/types.mjs +1 -0
- package/dist/lib/tag/index.mjs +5 -0
- package/dist/lib/tag/infra/target-resolver.mjs +2 -0
- package/dist/lib/tag/presentation/colors.mjs +2 -0
- package/dist/lib/tag/presentation/tag-presenter.mjs +2 -0
- package/dist/lib/tag/presentation/tag-presenter.test.mjs +29 -0
- package/dist/loader-DpN-zl5g.mjs +78 -0
- package/dist/mirror-CbGiLXKT.mjs +2 -0
- package/dist/mirror-DqJE3ejL.mjs +66 -0
- package/dist/node-io-DFRbHO6m.mjs +30 -0
- package/dist/package-filter-C_8ryqvz.mjs +26 -0
- package/dist/path-normalizer-D4ceBO3l.mjs +7 -0
- package/dist/program-DR-H4tH3.mjs +34 -0
- package/dist/program.mjs +2 -0
- package/dist/repo-root-HmtIEm6J.mjs +22 -0
- package/dist/report-DYxvw34T.mjs +24 -0
- package/dist/reporter-CwIe_tAM.mjs +108 -0
- package/dist/run-target-CJWrqNT4.mjs +65 -0
- package/dist/schema-Or8RAARd.mjs +25 -0
- package/dist/sync-e1Sp-shi.mjs +157 -0
- package/dist/tag-Cd9JujU5.mjs +2 -0
- package/dist/tag-hEKVZ_-7.mjs +58 -0
- package/dist/tag-presenter-Cs66UBsH.mjs +63 -0
- package/dist/target-resolver-CR3FXMTy.mjs +170 -0
- package/dist/targets-pCTslnFh.mjs +106 -0
- package/dist/tokenizer-CeZx73Zu.mjs +216 -0
- package/dist/update-pkg-CnGfjWBy.mjs +37 -0
- package/dist/walk-DjgQn66n.mjs +33 -0
- package/dist/workspace-packages-K2NYTHJq.mjs +159 -0
- package/package.json +42 -53
- package/CHANGELOG.md +0 -15
- package/dist/cjs/index.cjs +0 -2
- package/dist/cjs/src/index.d.ts +0 -3
- package/dist/cjs/src/index.d.ts.map +0 -1
- package/dist/esm/index.js +0 -2
- package/dist/esm/src/index.d.ts +0 -3
- package/dist/esm/src/index.d.ts.map +0 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { a as VALID_JS_EXTENSIONS, i as PACKAGE_JSON_EXPORT, n as DTS_EXTENSION } from "./constants-Bq5wdyLC.mjs";
|
|
2
|
+
import { t as isDirentList } from "./dirent-list-BSDeRG5i.mjs";
|
|
3
|
+
import { t as normalizePath } from "./path-normalizer-D4ceBO3l.mjs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
//#region src/lib/mirror/application/engine.ts
|
|
6
|
+
function resolvePackageScopedConfig(configMap, pkgMeta) {
|
|
7
|
+
if (!configMap) return void 0;
|
|
8
|
+
return configMap[pkgMeta.packageName];
|
|
9
|
+
}
|
|
10
|
+
async function scanDirectoryFiles(fs, dir, baseDir = dir) {
|
|
11
|
+
try {
|
|
12
|
+
const raw = await fs.readdir(dir, {
|
|
13
|
+
recursive: true,
|
|
14
|
+
withFileTypes: true
|
|
15
|
+
});
|
|
16
|
+
if (!isDirentList(raw)) return [];
|
|
17
|
+
return raw.filter((dirent) => dirent.isFile()).map((dirent) => {
|
|
18
|
+
const fullPath = path.join(dirent.parentPath, dirent.name);
|
|
19
|
+
return normalizePath(path.relative(baseDir, fullPath));
|
|
20
|
+
});
|
|
21
|
+
} catch (caughtError) {
|
|
22
|
+
if (caughtError && typeof caughtError === "object" && "code" in caughtError) {
|
|
23
|
+
if (caughtError.code === "ENOENT" || caughtError.code === "EACCES") return [];
|
|
24
|
+
}
|
|
25
|
+
throw caughtError;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function isDirectoryCssOnly(fs, distDir, dirPath) {
|
|
29
|
+
try {
|
|
30
|
+
const raw = await fs.readdir(path.join(distDir, dirPath), { withFileTypes: true });
|
|
31
|
+
if (!isDirentList(raw)) return false;
|
|
32
|
+
if (raw.length === 0) return true;
|
|
33
|
+
return raw.every((dirent) => dirent.isFile() && dirent.name.endsWith(".css"));
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function groupFilesByModule(files) {
|
|
39
|
+
const modules = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
let ext;
|
|
42
|
+
let modulePath;
|
|
43
|
+
if (file.endsWith(".d.ts")) {
|
|
44
|
+
ext = DTS_EXTENSION;
|
|
45
|
+
modulePath = file.slice(0, -DTS_EXTENSION.length);
|
|
46
|
+
} else {
|
|
47
|
+
ext = path.extname(file);
|
|
48
|
+
if (!VALID_JS_EXTENSIONS.has(ext)) continue;
|
|
49
|
+
modulePath = file.slice(0, -ext.length);
|
|
50
|
+
}
|
|
51
|
+
if (!path.basename(modulePath)) continue;
|
|
52
|
+
let distModule = modules.get(modulePath);
|
|
53
|
+
if (!distModule) {
|
|
54
|
+
distModule = {
|
|
55
|
+
path: modulePath,
|
|
56
|
+
files: {
|
|
57
|
+
js: null,
|
|
58
|
+
cjs: null,
|
|
59
|
+
dts: null
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
modules.set(modulePath, distModule);
|
|
63
|
+
}
|
|
64
|
+
if (ext === ".js") distModule.files.js = file;
|
|
65
|
+
else if (ext === ".cjs") distModule.files.cjs = file;
|
|
66
|
+
else if (ext === ".d.ts") distModule.files.dts = file;
|
|
67
|
+
}
|
|
68
|
+
return modules;
|
|
69
|
+
}
|
|
70
|
+
function toExportPath(distPath) {
|
|
71
|
+
if (distPath === "index") return ".";
|
|
72
|
+
if (distPath.endsWith("/index")) return `./${distPath.slice(0, -6)}`;
|
|
73
|
+
return `./${distPath}`;
|
|
74
|
+
}
|
|
75
|
+
const GROUP_ORDER = {
|
|
76
|
+
components: 100,
|
|
77
|
+
hooks: 200,
|
|
78
|
+
primitives: 300,
|
|
79
|
+
core: 400,
|
|
80
|
+
loaders: 500,
|
|
81
|
+
utils: 600,
|
|
82
|
+
adapters: 700,
|
|
83
|
+
script: 750,
|
|
84
|
+
css: 900
|
|
85
|
+
};
|
|
86
|
+
function getExportGroup(exportPath, pathTransform) {
|
|
87
|
+
if (exportPath === ".") return [
|
|
88
|
+
".",
|
|
89
|
+
"",
|
|
90
|
+
0,
|
|
91
|
+
0
|
|
92
|
+
];
|
|
93
|
+
const cleanPath = exportPath.startsWith("./") ? exportPath.slice(2) : exportPath;
|
|
94
|
+
if (cleanPath.endsWith("/*")) {
|
|
95
|
+
const dirName = cleanPath.slice(0, -2);
|
|
96
|
+
return [
|
|
97
|
+
dirName,
|
|
98
|
+
"",
|
|
99
|
+
dirName === "css" ? 900 : 100,
|
|
100
|
+
0
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
const parts = cleanPath.split("/");
|
|
104
|
+
if (parts.length === 1) {
|
|
105
|
+
const name = parts[0];
|
|
106
|
+
if (name in GROUP_ORDER) return [
|
|
107
|
+
name,
|
|
108
|
+
"",
|
|
109
|
+
GROUP_ORDER[name] ?? 0,
|
|
110
|
+
0
|
|
111
|
+
];
|
|
112
|
+
if (pathTransform) return [
|
|
113
|
+
"components",
|
|
114
|
+
name,
|
|
115
|
+
100,
|
|
116
|
+
1
|
|
117
|
+
];
|
|
118
|
+
return [
|
|
119
|
+
"",
|
|
120
|
+
name,
|
|
121
|
+
800,
|
|
122
|
+
1
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
const group = parts[0] ?? "";
|
|
126
|
+
return [
|
|
127
|
+
group,
|
|
128
|
+
parts.slice(1).join("/"),
|
|
129
|
+
GROUP_ORDER[group] ?? 700,
|
|
130
|
+
1
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
function createPathTransform(config, pkgMeta) {
|
|
134
|
+
const pathConfig = resolvePackageScopedConfig(config?.pathTransformations, pkgMeta);
|
|
135
|
+
if (!pathConfig) return null;
|
|
136
|
+
const { removePrefix } = pathConfig;
|
|
137
|
+
if (!removePrefix) return null;
|
|
138
|
+
return (exportPath) => {
|
|
139
|
+
if (!exportPath.startsWith(removePrefix)) return exportPath;
|
|
140
|
+
const result = exportPath.slice(removePrefix.length);
|
|
141
|
+
if (result && result !== "." && !result.startsWith("./")) return `./${result}`;
|
|
142
|
+
return result;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function getSortTuple(exportPath, pathTransform) {
|
|
146
|
+
const [group, subpath, order, isIndex] = getExportGroup(exportPath, pathTransform);
|
|
147
|
+
return [
|
|
148
|
+
order,
|
|
149
|
+
group,
|
|
150
|
+
isIndex,
|
|
151
|
+
subpath
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
function compareTuples(left, right) {
|
|
155
|
+
if (left[0] !== right[0]) return left[0] - right[0];
|
|
156
|
+
if (left[1] !== right[1]) return left[1] < right[1] ? -1 : 1;
|
|
157
|
+
if (left[2] !== right[2]) return left[2] - right[2];
|
|
158
|
+
if (left[3] !== right[3]) return left[3] < right[3] ? -1 : 1;
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
async function generateCssExports(fs, distDir, cssConfig) {
|
|
162
|
+
if (cssConfig === false) return {};
|
|
163
|
+
if (cssConfig === true) cssConfig = { enabled: true };
|
|
164
|
+
if (!cssConfig || cssConfig.enabled === false) return {};
|
|
165
|
+
const cssFiles = (await scanDirectoryFiles(fs, distDir)).filter((relativeDistPath) => relativeDistPath.endsWith(".css"));
|
|
166
|
+
if (!cssFiles.length) return {};
|
|
167
|
+
const cssExports = { ...cssConfig.customExports };
|
|
168
|
+
const cssByDir = /* @__PURE__ */ new Map();
|
|
169
|
+
const rootCss = [];
|
|
170
|
+
for (const file of cssFiles) {
|
|
171
|
+
const dirName = normalizePath(path.dirname(file));
|
|
172
|
+
if (dirName === ".") rootCss.push(file);
|
|
173
|
+
else {
|
|
174
|
+
const arr = cssByDir.get(dirName) || [];
|
|
175
|
+
arr.push(file);
|
|
176
|
+
cssByDir.set(dirName, arr);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
for (const file of rootCss) {
|
|
180
|
+
const exportPath = `./${file}`;
|
|
181
|
+
if (!(exportPath in cssExports)) cssExports[exportPath] = `./dist/${file}`;
|
|
182
|
+
}
|
|
183
|
+
for (const [dirName, dirFiles] of cssByDir.entries()) if (await isDirectoryCssOnly(fs, distDir, dirName) && !cssConfig.forceExportFiles) {
|
|
184
|
+
const wildcardExport = `./${dirName}/*`;
|
|
185
|
+
if (!(wildcardExport in cssExports)) cssExports[wildcardExport] = `./dist/${dirName}/*`;
|
|
186
|
+
} else for (const file of dirFiles) {
|
|
187
|
+
const exportPath = `./${file}`;
|
|
188
|
+
if (!(exportPath in cssExports)) cssExports[exportPath] = `./dist/${file}`;
|
|
189
|
+
}
|
|
190
|
+
return cssExports;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Compute `package.json#exports` from a built `dist/` tree. No logging; no writes.
|
|
194
|
+
*/
|
|
195
|
+
async function generateExports(fs, distDir, pathTransform, cssConfig, customExports) {
|
|
196
|
+
const files = await scanDirectoryFiles(fs, distDir);
|
|
197
|
+
if (!files.length) return {
|
|
198
|
+
exports: { [PACKAGE_JSON_EXPORT]: PACKAGE_JSON_EXPORT },
|
|
199
|
+
jsCount: 0,
|
|
200
|
+
cssCount: 0
|
|
201
|
+
};
|
|
202
|
+
const modules = groupFilesByModule(files);
|
|
203
|
+
const validModules = Array.from(modules.values()).filter((mod) => mod.files.js && mod.files.dts);
|
|
204
|
+
if (!validModules.length) return {
|
|
205
|
+
exports: { [PACKAGE_JSON_EXPORT]: PACKAGE_JSON_EXPORT },
|
|
206
|
+
jsCount: 0,
|
|
207
|
+
cssCount: 0
|
|
208
|
+
};
|
|
209
|
+
const exportsObj = {};
|
|
210
|
+
for (const distModuleEntry of validModules) {
|
|
211
|
+
let exportPath = toExportPath(distModuleEntry.path);
|
|
212
|
+
if (pathTransform) exportPath = pathTransform(exportPath);
|
|
213
|
+
const distPath = `./dist/${distModuleEntry.path}`;
|
|
214
|
+
const entry = { types: `${distPath}.d.ts` };
|
|
215
|
+
if (distModuleEntry.files.js) entry.import = `${distPath}.js`;
|
|
216
|
+
if (distModuleEntry.files.cjs) entry.require = `${distPath}.cjs`;
|
|
217
|
+
exportsObj[exportPath] = entry;
|
|
218
|
+
}
|
|
219
|
+
let sortedKeys = Object.keys(exportsObj).sort((pathA, pathB) => compareTuples(getSortTuple(pathA, pathTransform), getSortTuple(pathB, pathTransform)));
|
|
220
|
+
const sortedExports = {};
|
|
221
|
+
for (const exportKey of sortedKeys) sortedExports[exportKey] = exportsObj[exportKey];
|
|
222
|
+
const cssExports = await generateCssExports(fs, distDir, cssConfig ?? { enabled: true });
|
|
223
|
+
Object.assign(sortedExports, cssExports);
|
|
224
|
+
for (const [specifier, mappedPath] of Object.entries(customExports || {})) if (specifier !== "./package.json") sortedExports[specifier] = mappedPath;
|
|
225
|
+
sortedKeys = Object.keys(sortedExports).filter((exportKey) => exportKey !== PACKAGE_JSON_EXPORT).sort((pathA, pathB) => compareTuples(getSortTuple(pathA, pathTransform), getSortTuple(pathB, pathTransform)));
|
|
226
|
+
const finalExports = {};
|
|
227
|
+
for (const exportKey of sortedKeys) finalExports[exportKey] = sortedExports[exportKey];
|
|
228
|
+
finalExports[PACKAGE_JSON_EXPORT] = PACKAGE_JSON_EXPORT;
|
|
229
|
+
return {
|
|
230
|
+
exports: finalExports,
|
|
231
|
+
jsCount: Object.keys(exportsObj).length,
|
|
232
|
+
cssCount: Object.keys(cssExports).length
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
export { generateExports as n, createPathTransform as t };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/lib/mirror/domain/errors.ts
|
|
2
|
+
const MirrorErrorCode = {
|
|
3
|
+
FATAL: "FATAL",
|
|
4
|
+
PACKAGE_WRITE: "PACKAGE_WRITE"
|
|
5
|
+
};
|
|
6
|
+
var MirrorError = class extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code, message, options) {
|
|
9
|
+
super(message, options);
|
|
10
|
+
this.name = "MirrorError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { MirrorErrorCode as n, MirrorError as t };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/lib/arrange/domain/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Structured errors for the arrange CLI library (throw from lib; handle exit in commands).
|
|
4
|
+
*/
|
|
5
|
+
const ArrangeErrorCode = {
|
|
6
|
+
TARGET_NOT_FOUND: "TARGET_NOT_FOUND",
|
|
7
|
+
INVALID_INPUT: "INVALID_INPUT",
|
|
8
|
+
INTERNAL: "INTERNAL"
|
|
9
|
+
};
|
|
10
|
+
var ArrangeError = class extends Error {
|
|
11
|
+
code;
|
|
12
|
+
constructor(code, message, options) {
|
|
13
|
+
super(message, options);
|
|
14
|
+
this.name = "ArrangeError";
|
|
15
|
+
this.code = code;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { ArrangeErrorCode as n, ArrangeError as t };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { i as indentOfLineContaining } from "./ast-helpers-MDRYuIAk.mjs";
|
|
2
|
+
//#region src/lib/arrange/presentation/formatters.ts
|
|
3
|
+
function escapeTsStringLiteralContent(group) {
|
|
4
|
+
return group.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Multiline fragment for use *inside* an existing `cn(...)` — replaces one
|
|
8
|
+
* string argument with several, without producing `cn(cn(...))`.
|
|
9
|
+
*/
|
|
10
|
+
function formatCnArguments(groups, options) {
|
|
11
|
+
const indent = options?.indent ?? " ";
|
|
12
|
+
const commaAfterLast = options?.commaAfterLastGroup ?? options?.trailingClassName ?? false;
|
|
13
|
+
const lines = [];
|
|
14
|
+
for (let i = 0; i < groups.length; i++) {
|
|
15
|
+
const group = groups[i];
|
|
16
|
+
const comma = i < groups.length - 1 || commaAfterLast ? "," : "";
|
|
17
|
+
lines.push(`${indent}"${escapeTsStringLiteralContent(group)}"${comma}`);
|
|
18
|
+
}
|
|
19
|
+
if (options?.trailingClassName) lines.push(`${indent}className,`);
|
|
20
|
+
return lines.join("\n");
|
|
21
|
+
}
|
|
22
|
+
function formatCnCall(groups, options) {
|
|
23
|
+
const lines = ["cn("];
|
|
24
|
+
const commaOnEachStringLine = groups.length > 1 || Boolean(options?.trailingClassName);
|
|
25
|
+
for (let i = 0; i < groups.length; i++) {
|
|
26
|
+
const group = groups[i];
|
|
27
|
+
const comma = commaOnEachStringLine ? "," : "";
|
|
28
|
+
lines.push(` "${escapeTsStringLiteralContent(group)}"${comma}`);
|
|
29
|
+
}
|
|
30
|
+
if (options?.trailingClassName) lines.push(" className,");
|
|
31
|
+
lines.push(")");
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
}
|
|
34
|
+
function formatArray(groups) {
|
|
35
|
+
const lines = ["["];
|
|
36
|
+
for (let i = 0; i < groups.length; i++) {
|
|
37
|
+
const group = groups[i];
|
|
38
|
+
const comma = i < groups.length - 1 || groups.length > 1 ? "," : "";
|
|
39
|
+
lines.push(` "${escapeTsStringLiteralContent(group)}"${comma}`);
|
|
40
|
+
}
|
|
41
|
+
lines.push("]");
|
|
42
|
+
return lines.join("\n");
|
|
43
|
+
}
|
|
44
|
+
function formatJsxCnAttributeValue(groups, source, valueNodeStart) {
|
|
45
|
+
const baseIndent = indentOfLineContaining(source, valueNodeStart);
|
|
46
|
+
return `{cn(\n${formatCnArguments(groups, {
|
|
47
|
+
indent: `${baseIndent} `,
|
|
48
|
+
commaAfterLastGroup: groups.length > 1
|
|
49
|
+
})}\n${baseIndent})}`;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { formatJsxCnAttributeValue as a, formatCnCall as i, formatArray as n, formatCnArguments as r, escapeTsStringLiteralContent as t };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { l as unwrapCnInsideTvCallReplacement, n as applyEditsDescending, o as lineOf, r as buildKnownCnTvBindings } from "./ast-helpers-MDRYuIAk.mjs";
|
|
2
|
+
import { a as listAllCnCallsInsideTvInSourceFile } from "./collectors-tv-D2PXZaRt.mjs";
|
|
3
|
+
import { a as targetReplaceStart, i as planGroupEditForTarget, t as collectGroupTargets } from "./targets-pCTslnFh.mjs";
|
|
4
|
+
import { n as ensureCnImport } from "./imports-DSVq1xhB.mjs";
|
|
5
|
+
import ts from "typescript";
|
|
6
|
+
//#region src/lib/arrange/application/group-file.ts
|
|
7
|
+
function printGroupFilePreview(logger, args) {
|
|
8
|
+
const { out } = logger;
|
|
9
|
+
const { filePath, reportTotal, cnInTvNoReplacement, cnInTvCalls, unwrapReplacementByCall, sourceText, sf, unwrapEdits, plannedGroupEdits } = args;
|
|
10
|
+
let header = `\n── ${filePath} (${reportTotal} site(s)`;
|
|
11
|
+
if (cnInTvNoReplacement > 0) header += `; plus ${cnInTvNoReplacement} cn() inside tv left unchanged (0 args)`;
|
|
12
|
+
header += `) ──`;
|
|
13
|
+
out(header);
|
|
14
|
+
for (const call of cnInTvCalls) {
|
|
15
|
+
const replacement = unwrapReplacementByCall.get(call);
|
|
16
|
+
if (replacement === void 0) {
|
|
17
|
+
out(` Line ${lineOf(sf, call)} [tv ⊃ cn]: cn(...) has no arguments — skipped`);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const start = call.getStart(sf);
|
|
21
|
+
const end = call.getEnd();
|
|
22
|
+
if (sourceText.slice(start, end) === replacement) continue;
|
|
23
|
+
out(` Line ${lineOf(sf, call)} [tv ⊃ cn → string/array]:`);
|
|
24
|
+
out(` ${replacement.split("\n").join("\n ")}`);
|
|
25
|
+
}
|
|
26
|
+
if (unwrapEdits.length > 0 && plannedGroupEdits.length > 0) out(" ([cn] / [tv] / [JSX className] lines below reflect content after unwrap of cn inside tv.)");
|
|
27
|
+
for (const plan of plannedGroupEdits) {
|
|
28
|
+
out(` Line ${lineOf(plan.lineSf, plan.reportNode)} [${plan.label}]:`);
|
|
29
|
+
out(` ${plan.replacement.split("\n").join("\n ")}`);
|
|
30
|
+
out(` // Buckets: ${JSON.stringify(plan.bucketSummary)}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function applyGroupFileWrites(filePath, options, fs, logger, unwrapEdits, textAfterUnwrap, plannedGroupEdits, cnInTvNoReplacement) {
|
|
34
|
+
const groupEdits = plannedGroupEdits.map((plannedEdit) => ({
|
|
35
|
+
start: plannedEdit.start,
|
|
36
|
+
end: plannedEdit.end,
|
|
37
|
+
replacement: plannedEdit.replacement,
|
|
38
|
+
jsxCn: plannedEdit.jsxCn
|
|
39
|
+
}));
|
|
40
|
+
const touchedJsxCn = groupEdits.some((groupEdit) => groupEdit.jsxCn);
|
|
41
|
+
let newText = groupEdits.length > 0 ? applyEditsDescending(textAfterUnwrap, groupEdits) : textAfterUnwrap;
|
|
42
|
+
const changed = unwrapEdits.length + groupEdits.length;
|
|
43
|
+
if (changed > 0) {
|
|
44
|
+
if (touchedJsxCn) newText = ensureCnImport(newText, filePath, options.cnImport);
|
|
45
|
+
fs.writeFileSync(filePath, newText, "utf8");
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
filePath,
|
|
49
|
+
totalFound: changed + cnInTvNoReplacement,
|
|
50
|
+
changed
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function groupFile(filePath, options, fs, logger) {
|
|
54
|
+
const sourceText = fs.readFileSync(filePath, "utf8");
|
|
55
|
+
const sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
56
|
+
const cnInTvCalls = listAllCnCallsInsideTvInSourceFile(sf, buildKnownCnTvBindings(sf));
|
|
57
|
+
const unwrapPlans = cnInTvCalls.map((call) => {
|
|
58
|
+
const replacement = unwrapCnInsideTvCallReplacement(call, sourceText, sf);
|
|
59
|
+
if (replacement === void 0) return void 0;
|
|
60
|
+
return {
|
|
61
|
+
start: call.getStart(sf),
|
|
62
|
+
end: call.getEnd(),
|
|
63
|
+
replacement,
|
|
64
|
+
call
|
|
65
|
+
};
|
|
66
|
+
}).filter((planOrUndefined) => planOrUndefined !== void 0);
|
|
67
|
+
const unwrapReplacementByCall = new Map(unwrapPlans.map((unwrapPlan) => [unwrapPlan.call, unwrapPlan.replacement]));
|
|
68
|
+
const unwrapEdits = unwrapPlans.filter((unwrapPlan) => sourceText.slice(unwrapPlan.start, unwrapPlan.end) !== unwrapPlan.replacement);
|
|
69
|
+
const textAfterUnwrap = unwrapEdits.length > 0 ? applyEditsDescending(sourceText, unwrapEdits) : sourceText;
|
|
70
|
+
const groupTargets = collectGroupTargets(ts.createSourceFile(filePath, textAfterUnwrap, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS), filePath);
|
|
71
|
+
const cnInTvNoReplacement = cnInTvCalls.length - unwrapPlans.length;
|
|
72
|
+
const sortedTargets = [...groupTargets].sort((leftTarget, rightTarget) => targetReplaceStart(rightTarget) - targetReplaceStart(leftTarget));
|
|
73
|
+
const plannedGroupEdits = [];
|
|
74
|
+
for (const groupTarget of sortedTargets) {
|
|
75
|
+
const plan = planGroupEditForTarget(groupTarget, textAfterUnwrap, options.withClassName);
|
|
76
|
+
if (plan === void 0) continue;
|
|
77
|
+
if (textAfterUnwrap.slice(plan.start, plan.end) === plan.replacement) continue;
|
|
78
|
+
plannedGroupEdits.push(plan);
|
|
79
|
+
}
|
|
80
|
+
const editSitesCount = unwrapEdits.length + plannedGroupEdits.length;
|
|
81
|
+
const reportTotal = editSitesCount + cnInTvNoReplacement;
|
|
82
|
+
if (cnInTvCalls.length === 0 && groupTargets.length === 0) return {
|
|
83
|
+
filePath,
|
|
84
|
+
totalFound: 0,
|
|
85
|
+
changed: 0
|
|
86
|
+
};
|
|
87
|
+
if (!options.write) {
|
|
88
|
+
if (editSitesCount === 0 && cnInTvNoReplacement === 0) return {
|
|
89
|
+
filePath,
|
|
90
|
+
totalFound: 0,
|
|
91
|
+
changed: 0
|
|
92
|
+
};
|
|
93
|
+
printGroupFilePreview(logger, {
|
|
94
|
+
filePath,
|
|
95
|
+
reportTotal,
|
|
96
|
+
cnInTvNoReplacement,
|
|
97
|
+
cnInTvCalls,
|
|
98
|
+
unwrapReplacementByCall,
|
|
99
|
+
sourceText,
|
|
100
|
+
sf,
|
|
101
|
+
unwrapEdits,
|
|
102
|
+
plannedGroupEdits
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
filePath,
|
|
106
|
+
totalFound: reportTotal,
|
|
107
|
+
changed: 0
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return applyGroupFileWrites(filePath, options, fs, logger, unwrapEdits, textAfterUnwrap, plannedGroupEdits, cnInTvNoReplacement);
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
export { groupFile as t };
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { n as BUCKET_ORDER } from "./constants-AtPhF-Ct.mjs";
|
|
2
|
+
import { a as compositeSecondaryOrder, c as selectorKey, d as tokenizeClassString, i as classifyToken, l as stateKey, n as bucketsMergeCompatible, t as bucketsCompatible, u as stripVariants } from "./tokenizer-CeZx73Zu.mjs";
|
|
3
|
+
//#region src/lib/arrange/domain/grouping.ts
|
|
4
|
+
function isVariantKeyedBucket(bucket) {
|
|
5
|
+
return bucket === "selector" || bucket === "state" || bucket === "starting";
|
|
6
|
+
}
|
|
7
|
+
function variantGroupKey(bucket, classToken) {
|
|
8
|
+
return bucket === "selector" ? selectorKey(classToken) : stateKey(classToken);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* True when `staticLiteralTexts` carries the **same partition** of Tailwind tokens as
|
|
12
|
+
* `suggestedGroups` from {@link suggestCnGroups}, ignoring order of arguments and
|
|
13
|
+
* order of tokens within each chunk.
|
|
14
|
+
*/
|
|
15
|
+
function areCnTailwindPartitionsEquivalent(staticLiteralTexts, suggestedGroups) {
|
|
16
|
+
const partitionSignatures = (chunks) => chunks.map((chunk) => {
|
|
17
|
+
const classTokens = tokenizeClassString(chunk);
|
|
18
|
+
return classTokens.length === 0 ? "" : [...classTokens].sort().join(" ");
|
|
19
|
+
}).filter((s) => s.length > 0).sort((a, b) => a.localeCompare(b));
|
|
20
|
+
const staticPartitionSigs = partitionSignatures(staticLiteralTexts);
|
|
21
|
+
const suggestedPartitionSigs = partitionSignatures(suggestedGroups);
|
|
22
|
+
if (staticPartitionSigs.length !== suggestedPartitionSigs.length) return false;
|
|
23
|
+
for (let i = 0; i < staticPartitionSigs.length; i++) if (staticPartitionSigs[i] !== suggestedPartitionSigs[i]) return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/** Dominant bucket of a whitespace-delimited class group (for merge heuristics). */
|
|
27
|
+
function dominantBucketOfGroup(groupStr) {
|
|
28
|
+
const counts = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const classToken of tokenizeClassString(groupStr)) {
|
|
30
|
+
const tokenBucket = classifyToken(classToken);
|
|
31
|
+
counts.set(tokenBucket, (counts.get(tokenBucket) ?? 0) + 1);
|
|
32
|
+
}
|
|
33
|
+
let best = "other";
|
|
34
|
+
let bestN = 0;
|
|
35
|
+
for (const [bucket, count] of counts) {
|
|
36
|
+
if (count > bestN) {
|
|
37
|
+
best = bucket;
|
|
38
|
+
bestN = count;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (count === bestN && BUCKET_ORDER[bucket] < BUCKET_ORDER[best]) best = bucket;
|
|
42
|
+
}
|
|
43
|
+
return best;
|
|
44
|
+
}
|
|
45
|
+
/** Dynamic cap: more tokens → allow more groups, within [BASE, CAP]. */
|
|
46
|
+
function dynamicMaxGroups(tokenCount) {
|
|
47
|
+
const byTokens = Math.ceil(tokenCount / 2) + 2;
|
|
48
|
+
return Math.max(4, Math.min(24, byTokens));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Merge singleton groups (< MIN_GROUP_TOKENS tokens) into their nearest
|
|
52
|
+
* neighbour. Prefers merging toward a bucket-compatible neighbor.
|
|
53
|
+
*
|
|
54
|
+
* Loop termination: each iteration either splices one element (shrinking the
|
|
55
|
+
* array, guaranteeing progress) or finds no singleton that can merge and exits
|
|
56
|
+
* via `changed = false`. Two mutually-incompatible singletons both hit
|
|
57
|
+
* `continue` and `changed` stays false, so the loop exits without spinning.
|
|
58
|
+
*/
|
|
59
|
+
function mergeSingletons(groups) {
|
|
60
|
+
if (groups.length <= 1) return groups;
|
|
61
|
+
const result = [...groups];
|
|
62
|
+
let changed = true;
|
|
63
|
+
while (changed) {
|
|
64
|
+
changed = false;
|
|
65
|
+
for (let i = 0; i < result.length; i++) if (tokenizeClassString(result[i]).length < 2) {
|
|
66
|
+
if (result.length === 1) break;
|
|
67
|
+
const myBucket = dominantBucketOfGroup(result[i]);
|
|
68
|
+
const prevCompat = i > 0 && bucketsMergeCompatible(myBucket, dominantBucketOfGroup(result[i - 1]));
|
|
69
|
+
const nextCompat = i < result.length - 1 && bucketsMergeCompatible(myBucket, dominantBucketOfGroup(result[i + 1]));
|
|
70
|
+
let mergeDir;
|
|
71
|
+
if (nextCompat && !prevCompat) mergeDir = "forward";
|
|
72
|
+
else if (prevCompat && !nextCompat) mergeDir = "backward";
|
|
73
|
+
else if (prevCompat && nextCompat) mergeDir = i < result.length - 1 ? "forward" : "backward";
|
|
74
|
+
else continue;
|
|
75
|
+
if (mergeDir === "forward") result[i + 1] = result[i] + " " + result[i + 1];
|
|
76
|
+
else result[i - 1] = result[i - 1] + " " + result[i];
|
|
77
|
+
result.splice(i, 1);
|
|
78
|
+
changed = true;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/** Tie-break when two merge candidates are both {@link bucketsMergeCompatible} (same bucket or COMPATIBLE_BUCKET_SETS). */
|
|
85
|
+
function capMergePenalty(leftBucket, rightBucket) {
|
|
86
|
+
if (leftBucket === rightBucket) return 0;
|
|
87
|
+
if (bucketsCompatible(leftBucket, rightBucket)) return 0;
|
|
88
|
+
return 500;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Merge adjacent groups until total count ≤ maxGroups.
|
|
92
|
+
* Only merges pairs allowed by {@link bucketsMergeCompatible} — never glues incompatible
|
|
93
|
+
* buckets (which previously defaulted to penalty 0 and merged layout+state when over cap).
|
|
94
|
+
*
|
|
95
|
+
* Complexity: O(n²) in the number of groups — each merge pass scans all remaining adjacent
|
|
96
|
+
* pairs. In practice `n` is bounded by {@link MAX_GROUPS_CAP} (24) and typical class strings
|
|
97
|
+
* are well below that, so this is not a concern for the current usage pattern. If this ever
|
|
98
|
+
* runs in a high-throughput batch mode, consider a priority-queue approach.
|
|
99
|
+
*/
|
|
100
|
+
function capGroups(groups, maxGroups) {
|
|
101
|
+
const result = [...groups];
|
|
102
|
+
const lengths = result.map((groupStr) => tokenizeClassString(groupStr).length);
|
|
103
|
+
while (result.length > maxGroups) {
|
|
104
|
+
const mergePairCandidates = [];
|
|
105
|
+
for (let i = 0; i < result.length - 1; i++) {
|
|
106
|
+
const dominantLeft = dominantBucketOfGroup(result[i]);
|
|
107
|
+
const dominantRight = dominantBucketOfGroup(result[i + 1]);
|
|
108
|
+
if (!bucketsMergeCompatible(dominantLeft, dominantRight)) continue;
|
|
109
|
+
mergePairCandidates.push({
|
|
110
|
+
i,
|
|
111
|
+
size: lengths[i] + lengths[i + 1],
|
|
112
|
+
penalty: capMergePenalty(dominantLeft, dominantRight)
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (mergePairCandidates.length === 0) break;
|
|
116
|
+
let best = mergePairCandidates[0];
|
|
117
|
+
for (const candidate of mergePairCandidates) if (candidate.penalty < best.penalty || candidate.penalty === best.penalty && candidate.size < best.size) best = candidate;
|
|
118
|
+
const bestIdx = best.i;
|
|
119
|
+
result[bestIdx] = result[bestIdx] + " " + result[bestIdx + 1];
|
|
120
|
+
lengths[bestIdx] = lengths[bestIdx] + lengths[bestIdx + 1];
|
|
121
|
+
result.splice(bestIdx + 1, 1);
|
|
122
|
+
lengths.splice(bestIdx + 1, 1);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* If a chunk is **only** bare `ease-*` timing (Motion), buffer it and prepend to the **first**
|
|
128
|
+
* **later** chunk that is predominantly `state` **and** contains some `animate-*` utility —
|
|
129
|
+
* skipping intermediate `state` chunks (`sm:…`, `group-data-[…]:…`, etc.) that sit between
|
|
130
|
+
* `ease-ui` and `data-open:animate-*`. If no such chunk exists, the ease chunk is left as-is.
|
|
131
|
+
*/
|
|
132
|
+
function mergeEaseTimingIntoFollowingAnimatedState(groups) {
|
|
133
|
+
const result = [...groups];
|
|
134
|
+
let changed = true;
|
|
135
|
+
while (changed) {
|
|
136
|
+
changed = false;
|
|
137
|
+
for (let i = 0; i < result.length; i++) {
|
|
138
|
+
if (!chunkIsOnlyEaseTimingMotion(result[i])) continue;
|
|
139
|
+
const easeStr = result[i];
|
|
140
|
+
let target = -1;
|
|
141
|
+
for (let j = i + 1; j < result.length; j++) {
|
|
142
|
+
if (dominantBucketOfGroup(result[j]) !== "state") continue;
|
|
143
|
+
if (!tokenizeClassString(result[j]).some((classToken) => /^animate/.test(stripVariants(classToken)))) continue;
|
|
144
|
+
target = j;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
if (target === -1) continue;
|
|
148
|
+
result[target] = `${easeStr} ${result[target]}`.trim();
|
|
149
|
+
result.splice(i, 1);
|
|
150
|
+
changed = true;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
function chunkIsOnlyEaseTimingMotion(groupStr) {
|
|
157
|
+
const classTokens = tokenizeClassString(groupStr);
|
|
158
|
+
if (classTokens.length === 0) return false;
|
|
159
|
+
return classTokens.every((classToken) => classifyToken(classToken) === "motion" && /^ease-/.test(stripVariants(classToken)));
|
|
160
|
+
}
|
|
161
|
+
function suggestCnGroups(classString) {
|
|
162
|
+
const tokens = tokenizeClassString(classString);
|
|
163
|
+
if (tokens.length === 0) return [];
|
|
164
|
+
const classified = tokens.map((classToken, index) => ({
|
|
165
|
+
classToken,
|
|
166
|
+
bucket: classifyToken(classToken),
|
|
167
|
+
index
|
|
168
|
+
}));
|
|
169
|
+
classified.sort((left, right) => {
|
|
170
|
+
const bucketOrderDiff = BUCKET_ORDER[left.bucket] - BUCKET_ORDER[right.bucket];
|
|
171
|
+
if (bucketOrderDiff !== 0) return bucketOrderDiff;
|
|
172
|
+
if (left.bucket === "composite" && right.bucket === "composite") {
|
|
173
|
+
const compositeOrderDiff = compositeSecondaryOrder(stripVariants(left.classToken)) - compositeSecondaryOrder(stripVariants(right.classToken));
|
|
174
|
+
if (compositeOrderDiff !== 0) return compositeOrderDiff;
|
|
175
|
+
}
|
|
176
|
+
return left.index - right.index;
|
|
177
|
+
});
|
|
178
|
+
const rawGroups = [];
|
|
179
|
+
/** Bucket of the last token already placed in the current run (pairwise compat with `COMPATIBLE_BUCKET_SETS`). */
|
|
180
|
+
let lastBucketInRun = null;
|
|
181
|
+
let currentStateKey = null;
|
|
182
|
+
let currentTokens = [];
|
|
183
|
+
const flush = () => {
|
|
184
|
+
if (currentTokens.length > 0) {
|
|
185
|
+
rawGroups.push(currentTokens.join(" "));
|
|
186
|
+
currentTokens = [];
|
|
187
|
+
}
|
|
188
|
+
lastBucketInRun = null;
|
|
189
|
+
currentStateKey = null;
|
|
190
|
+
};
|
|
191
|
+
for (const { classToken, bucket: tokenBucket } of classified) {
|
|
192
|
+
if (lastBucketInRun === null) {
|
|
193
|
+
lastBucketInRun = tokenBucket;
|
|
194
|
+
currentStateKey = isVariantKeyedBucket(tokenBucket) ? variantGroupKey(tokenBucket, classToken) : null;
|
|
195
|
+
currentTokens.push(classToken);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (isVariantKeyedBucket(tokenBucket)) {
|
|
199
|
+
const key = variantGroupKey(tokenBucket, classToken);
|
|
200
|
+
if (currentStateKey !== null && key !== currentStateKey) {
|
|
201
|
+
flush();
|
|
202
|
+
lastBucketInRun = tokenBucket;
|
|
203
|
+
currentStateKey = key;
|
|
204
|
+
currentTokens.push(classToken);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (!bucketsCompatible(tokenBucket, lastBucketInRun)) {
|
|
209
|
+
flush();
|
|
210
|
+
lastBucketInRun = tokenBucket;
|
|
211
|
+
currentStateKey = isVariantKeyedBucket(tokenBucket) ? variantGroupKey(tokenBucket, classToken) : null;
|
|
212
|
+
currentTokens.push(classToken);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (isVariantKeyedBucket(tokenBucket)) currentStateKey = variantGroupKey(tokenBucket, classToken);
|
|
216
|
+
else if (currentStateKey !== null) currentStateKey = null;
|
|
217
|
+
lastBucketInRun = tokenBucket;
|
|
218
|
+
currentTokens.push(classToken);
|
|
219
|
+
}
|
|
220
|
+
flush();
|
|
221
|
+
const withEaseMerged = mergeEaseTimingIntoFollowingAnimatedState(mergeSingletons(rawGroups));
|
|
222
|
+
return capGroups(withEaseMerged, Math.max(dynamicMaxGroups(tokens.length), withEaseMerged.length));
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* One label per suggested group: a single bucket name, or `mixed:a+b` when that chunk
|
|
226
|
+
* spans multiple Tailwind buckets (matches `codefast arrange group` stdout).
|
|
227
|
+
*/
|
|
228
|
+
function summarizeGroupBucketLabels(groups) {
|
|
229
|
+
return groups.map((g) => {
|
|
230
|
+
const uniq = new Set(tokenizeClassString(g).map(classifyToken));
|
|
231
|
+
return uniq.size === 1 ? [...uniq][0] : `mixed:${[...uniq].sort().join("+")}`;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
export { suggestCnGroups as a, mergeSingletons as i, capGroups as n, summarizeGroupBucketLabels as o, mergeEaseTimingIntoFollowingAnimatedState as r, areCnTailwindPartitionsEquivalent as t };
|