@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,54 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
//#region src/lib/arrange/domain/imports.ts
|
|
3
|
+
function sourceFileImportsCn(sf) {
|
|
4
|
+
for (const statement of sf.statements) {
|
|
5
|
+
if (!ts.isImportDeclaration(statement) || !statement.importClause) continue;
|
|
6
|
+
const clause = statement.importClause;
|
|
7
|
+
if (clause.name?.text === "cn") return true;
|
|
8
|
+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
9
|
+
for (const importedBinding of clause.namedBindings.elements) if (importedBinding.name.text === "cn") return true;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the module specifier used when injecting a `cn` import into a file.
|
|
16
|
+
*/
|
|
17
|
+
function cnModuleSpecifierForFile(filePath, override) {
|
|
18
|
+
if (override) return override;
|
|
19
|
+
return "@codefast/tailwind-variants";
|
|
20
|
+
}
|
|
21
|
+
function findImportDeclarationFromModule(sf, moduleSpecifier) {
|
|
22
|
+
for (const statement of sf.statements) {
|
|
23
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
24
|
+
const spec = statement.moduleSpecifier;
|
|
25
|
+
if (ts.isStringLiteral(spec) && spec.text === moduleSpecifier) return statement;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function ensureCnImport(sourceText, filePath, cnImportOverride) {
|
|
29
|
+
const sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
30
|
+
if (sourceFileImportsCn(sf)) return sourceText;
|
|
31
|
+
const moduleSpecifier = cnModuleSpecifierForFile(filePath, cnImportOverride);
|
|
32
|
+
const decl = findImportDeclarationFromModule(sf, moduleSpecifier);
|
|
33
|
+
if (decl?.importClause && !decl.importClause.isTypeOnly && decl.importClause.namedBindings && ts.isNamedImports(decl.importClause.namedBindings)) {
|
|
34
|
+
const elements = decl.importClause.namedBindings.elements;
|
|
35
|
+
if (elements.length > 0) {
|
|
36
|
+
const pos = elements[0].getStart(sf);
|
|
37
|
+
return `${sourceText.slice(0, pos)}cn, ${sourceText.slice(pos)}`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const importLine = `import { cn } from "${moduleSpecifier}";`;
|
|
41
|
+
let firstImport = -1;
|
|
42
|
+
for (const statement of sf.statements) if (ts.isImportDeclaration(statement)) {
|
|
43
|
+
firstImport = statement.getStart(sf);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
const useClient = /^["']use client["'];?\s*\r?\n/.exec(sourceText);
|
|
47
|
+
let insertAt;
|
|
48
|
+
if (useClient) insertAt = useClient[0].length;
|
|
49
|
+
else if (firstImport !== -1) insertAt = firstImport;
|
|
50
|
+
else insertAt = 0;
|
|
51
|
+
return `${sourceText.slice(0, insertAt)}${importLine}\n${sourceText.slice(insertAt)}`;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { ensureCnImport as n, cnModuleSpecifierForFile as t };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { t as analyzeDirectory } from "../../../analyze-DvtJ3j9m.mjs";
|
|
2
|
+
import { t as printAnalyzeReport } from "../../../report-DYxvw34T.mjs";
|
|
3
|
+
import { n as createNodeCliLogger, t as createNodeCliFs } from "../../../node-io-DFRbHO6m.mjs";
|
|
4
|
+
import "../../../arrange-BqP2DI3w.mjs";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
//#region src/lib/arrange/application/analyze.test.ts
|
|
9
|
+
const arrangeFs = createNodeCliFs();
|
|
10
|
+
const arrangeLogger = createNodeCliLogger();
|
|
11
|
+
function captureStdout(fn) {
|
|
12
|
+
const chunks = [];
|
|
13
|
+
const spy = jest.spyOn(process.stdout, "write").mockImplementation((chunk) => {
|
|
14
|
+
chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
|
15
|
+
return true;
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
fn();
|
|
19
|
+
} finally {
|
|
20
|
+
spy.mockRestore();
|
|
21
|
+
}
|
|
22
|
+
return chunks.join("");
|
|
23
|
+
}
|
|
24
|
+
describe("analyzeDirectory + printAnalyzeReport", () => {
|
|
25
|
+
it("collects long cn / tv / JSX literals and nested cn in tv", () => {
|
|
26
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "arr-analyze-"));
|
|
27
|
+
const long = "peer flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground shadow-xs outline-hidden transition hover:not-disabled:not-aria-checked:border-ring/60 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50";
|
|
28
|
+
const source = `import { cn, tv } from "@codefast/tailwind-variants";
|
|
29
|
+
|
|
30
|
+
cn("${long}");
|
|
31
|
+
export const styles = tv({ base: "${long}" });
|
|
32
|
+
export function X() { return <div className={"${long}"} />; }
|
|
33
|
+
export const nested = tv({ base: cn("x", "${long}") });
|
|
34
|
+
`;
|
|
35
|
+
try {
|
|
36
|
+
fs.writeFileSync(path.join(dir, "Report.tsx"), source, "utf8");
|
|
37
|
+
const report = analyzeDirectory(dir, arrangeFs);
|
|
38
|
+
expect(report.files).toBe(1);
|
|
39
|
+
expect(report.cnCallExpressions).toBeGreaterThanOrEqual(1);
|
|
40
|
+
expect(report.tvCallExpressions).toBeGreaterThanOrEqual(1);
|
|
41
|
+
expect(report.longCnStringLiterals.length).toBeGreaterThanOrEqual(1);
|
|
42
|
+
expect(report.longTvStringLiterals.length).toBeGreaterThanOrEqual(1);
|
|
43
|
+
expect(report.longJsxClassNameLiterals.length).toBeGreaterThanOrEqual(1);
|
|
44
|
+
expect(report.cnInsideTvCalls.length).toBeGreaterThanOrEqual(1);
|
|
45
|
+
const printed = captureStdout(() => printAnalyzeReport(dir, report, arrangeLogger));
|
|
46
|
+
expect(printed).toContain("Long cn(...)");
|
|
47
|
+
expect(printed).toContain("nested inside tv");
|
|
48
|
+
} finally {
|
|
49
|
+
fs.rmSync(dir, {
|
|
50
|
+
recursive: true,
|
|
51
|
+
force: true
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
it("truncates long-cn listing after MAX_REPORT_LINES", () => {
|
|
56
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "arr-analyze-many-"));
|
|
57
|
+
const long = "peer flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground shadow-xs outline-hidden transition hover:not-disabled:not-aria-checked:border-ring/60 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50";
|
|
58
|
+
try {
|
|
59
|
+
let body = `import { cn } from "@codefast/tailwind-variants";\n`;
|
|
60
|
+
for (let i = 0; i < 42; i++) body += `cn("${long}");\n`;
|
|
61
|
+
fs.writeFileSync(path.join(dir, "Many.tsx"), body, "utf8");
|
|
62
|
+
const report = analyzeDirectory(dir, arrangeFs);
|
|
63
|
+
const printed = captureStdout(() => printAnalyzeReport(dir, report, arrangeLogger));
|
|
64
|
+
expect(report.longCnStringLiterals.length).toBeGreaterThan(40);
|
|
65
|
+
expect(printed).toMatch(/… and \d+ more/);
|
|
66
|
+
} finally {
|
|
67
|
+
fs.rmSync(dir, {
|
|
68
|
+
recursive: true,
|
|
69
|
+
force: true
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
it("truncates long-tv, JSX, and cn-inside-tv sections", () => {
|
|
74
|
+
const row = {
|
|
75
|
+
file: "x.tsx",
|
|
76
|
+
line: 1,
|
|
77
|
+
tokenCount: 22,
|
|
78
|
+
preview: "p".repeat(40)
|
|
79
|
+
};
|
|
80
|
+
const manyTv = Array.from({ length: 42 }, (_, i) => ({
|
|
81
|
+
...row,
|
|
82
|
+
line: i + 1
|
|
83
|
+
}));
|
|
84
|
+
const manyJsx = Array.from({ length: 42 }, (_, i) => ({
|
|
85
|
+
...row,
|
|
86
|
+
line: i + 1
|
|
87
|
+
}));
|
|
88
|
+
const manyNested = Array.from({ length: 42 }, (_, i) => ({
|
|
89
|
+
file: "x.tsx",
|
|
90
|
+
line: i + 1,
|
|
91
|
+
argCount: 1,
|
|
92
|
+
preview: "cn(...)"
|
|
93
|
+
}));
|
|
94
|
+
const matches = captureStdout(() => printAnalyzeReport("/tmp/arr-truncate", {
|
|
95
|
+
files: 1,
|
|
96
|
+
cnCallExpressions: 0,
|
|
97
|
+
tvCallExpressions: 1,
|
|
98
|
+
cnInsideTvCalls: manyNested,
|
|
99
|
+
longCnStringLiterals: [],
|
|
100
|
+
longTvStringLiterals: manyTv,
|
|
101
|
+
longJsxClassNameLiterals: manyJsx
|
|
102
|
+
}, arrangeLogger)).match(/… and \d+ more/g);
|
|
103
|
+
expect(matches?.length).toBeGreaterThanOrEqual(3);
|
|
104
|
+
});
|
|
105
|
+
it("does not count JSX className={identifier} as long literal", () => {
|
|
106
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "arr-jsx-dyn-"));
|
|
107
|
+
try {
|
|
108
|
+
fs.writeFileSync(path.join(dir, "Dyn.tsx"), `export function F(cls: string) { return <div className={cls} />; }`, "utf8");
|
|
109
|
+
const report = analyzeDirectory(dir, arrangeFs);
|
|
110
|
+
expect(report.longJsxClassNameLiterals.length).toBe(0);
|
|
111
|
+
} finally {
|
|
112
|
+
fs.rmSync(dir, {
|
|
113
|
+
recursive: true,
|
|
114
|
+
force: true
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
it("analyzes tv compoundVariants with quoted className key", () => {
|
|
119
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "arr-tv-quoted-"));
|
|
120
|
+
const long = "peer flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground shadow-xs outline-hidden transition hover:not-disabled:not-aria-checked:border-ring/60 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50";
|
|
121
|
+
try {
|
|
122
|
+
fs.writeFileSync(path.join(dir, "Quoted.tsx"), `import { tv } from "@codefast/tailwind-variants";
|
|
123
|
+
export const styles = tv({ compoundVariants: [{ "className": "${long}" }] });`, "utf8");
|
|
124
|
+
const report = analyzeDirectory(dir, arrangeFs);
|
|
125
|
+
expect(report.longTvStringLiterals.length).toBeGreaterThanOrEqual(1);
|
|
126
|
+
} finally {
|
|
127
|
+
fs.rmSync(dir, {
|
|
128
|
+
recursive: true,
|
|
129
|
+
force: true
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
it("analyzes tv array slots with nested cn", () => {
|
|
134
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "arr-analyze-tv-array-"));
|
|
135
|
+
const long = "peer flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground shadow-xs outline-hidden transition hover:not-disabled:not-aria-checked:border-ring/60 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50";
|
|
136
|
+
try {
|
|
137
|
+
fs.writeFileSync(path.join(dir, "TvArray.tsx"), `import { cn, tv } from "@codefast/tailwind-variants";
|
|
138
|
+
export const styles = tv({ base: [cn("${long}"), "py-0"] });`, "utf8");
|
|
139
|
+
const report = analyzeDirectory(dir, arrangeFs);
|
|
140
|
+
expect(report.tvCallExpressions).toBeGreaterThanOrEqual(1);
|
|
141
|
+
expect(report.cnInsideTvCalls.length).toBeGreaterThanOrEqual(1);
|
|
142
|
+
} finally {
|
|
143
|
+
fs.rmSync(dir, {
|
|
144
|
+
recursive: true,
|
|
145
|
+
force: true
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
it("accepts legacy tailwind-variants imports during analyze pass (backward compat)", () => {
|
|
150
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "arr-analyze-legacy-"));
|
|
151
|
+
const long = "peer flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground shadow-xs outline-hidden transition hover:not-disabled:not-aria-checked:border-ring/60 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50";
|
|
152
|
+
const source = `import { cn, tv } from "tailwind-variants";
|
|
153
|
+
|
|
154
|
+
cn("${long}");
|
|
155
|
+
export const styles = tv({ base: "${long}" });
|
|
156
|
+
`;
|
|
157
|
+
try {
|
|
158
|
+
fs.writeFileSync(path.join(dir, "Legacy.tsx"), source, "utf8");
|
|
159
|
+
const report = analyzeDirectory(dir, arrangeFs);
|
|
160
|
+
expect(report.cnCallExpressions).toBeGreaterThanOrEqual(1);
|
|
161
|
+
expect(report.tvCallExpressions).toBeGreaterThanOrEqual(1);
|
|
162
|
+
} finally {
|
|
163
|
+
fs.rmSync(dir, {
|
|
164
|
+
recursive: true,
|
|
165
|
+
force: true
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
//#endregion
|
|
171
|
+
export {};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { t as groupFile } from "../../../group-file-ClW7XdD6.mjs";
|
|
2
|
+
import { n as createNodeCliLogger, t as createNodeCliFs } from "../../../node-io-DFRbHO6m.mjs";
|
|
3
|
+
import "../../../arrange-BqP2DI3w.mjs";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
//#region src/lib/arrange/application/group-file.test.ts
|
|
8
|
+
const arrangeFs = createNodeCliFs();
|
|
9
|
+
const arrangeLogger = createNodeCliLogger();
|
|
10
|
+
function withTempFixture(name, source, fn) {
|
|
11
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "group-file-"));
|
|
12
|
+
const filePath = path.join(dir, name);
|
|
13
|
+
try {
|
|
14
|
+
fs.writeFileSync(filePath, source, "utf8");
|
|
15
|
+
fn(filePath);
|
|
16
|
+
} finally {
|
|
17
|
+
fs.rmSync(dir, {
|
|
18
|
+
recursive: true,
|
|
19
|
+
force: true
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function captureStdout(fn) {
|
|
24
|
+
const chunks = [];
|
|
25
|
+
const spy = jest.spyOn(process.stdout, "write").mockImplementation((chunk) => {
|
|
26
|
+
chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
|
27
|
+
return true;
|
|
28
|
+
});
|
|
29
|
+
try {
|
|
30
|
+
fn();
|
|
31
|
+
} finally {
|
|
32
|
+
spy.mockRestore();
|
|
33
|
+
}
|
|
34
|
+
return chunks.join("");
|
|
35
|
+
}
|
|
36
|
+
describe("groupFile", () => {
|
|
37
|
+
it("unwraps cn() inside tv base into string/array form", () => {
|
|
38
|
+
withTempFixture("FixtureTv.tsx", `import { cn, tv } from "@codefast/tailwind-variants";
|
|
39
|
+
|
|
40
|
+
export const styles = tv({
|
|
41
|
+
base: cn("flex gap-2 text-sm rounded-md border px-3 font-medium"),
|
|
42
|
+
});
|
|
43
|
+
`, (filePath) => {
|
|
44
|
+
const result = groupFile(filePath, {
|
|
45
|
+
write: true,
|
|
46
|
+
withClassName: false
|
|
47
|
+
}, arrangeFs, arrangeLogger);
|
|
48
|
+
const after = fs.readFileSync(filePath, "utf8");
|
|
49
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
50
|
+
expect(after.includes("base: cn(")).toBe(false);
|
|
51
|
+
expect(after).toMatch(/base:\s*(\[|"[^"]+)/);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
it("converts long JSX className and inserts cn import after use client", () => {
|
|
55
|
+
withTempFixture("FixtureUseClient.tsx", `"use client";
|
|
56
|
+
|
|
57
|
+
export function Fixture() {
|
|
58
|
+
return <div className="flex items-center gap-2 px-4 py-2 text-sm rounded-md border bg-card" />;
|
|
59
|
+
}
|
|
60
|
+
`, (filePath) => {
|
|
61
|
+
const result = groupFile(filePath, {
|
|
62
|
+
write: true,
|
|
63
|
+
withClassName: false
|
|
64
|
+
}, arrangeFs, arrangeLogger);
|
|
65
|
+
const after = fs.readFileSync(filePath, "utf8");
|
|
66
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
67
|
+
expect(after.startsWith(`"use client";\n\nimport { cn }`)).toBe(true);
|
|
68
|
+
expect(after).toContain("className={cn(");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
it("supports namespace imports for tw.tv / tw.cn and rewrites tw.cn usage", () => {
|
|
72
|
+
withTempFixture("FixtureNamespace.tsx", `import * as tw from "@codefast/tailwind-variants";
|
|
73
|
+
|
|
74
|
+
export const styles = tw.tv({
|
|
75
|
+
base: tw.cn("flex gap-2 text-sm rounded-md border px-3 font-medium hover:bg-accent"),
|
|
76
|
+
});
|
|
77
|
+
`, (filePath) => {
|
|
78
|
+
const result = groupFile(filePath, {
|
|
79
|
+
write: true,
|
|
80
|
+
withClassName: false
|
|
81
|
+
}, arrangeFs, arrangeLogger);
|
|
82
|
+
const after = fs.readFileSync(filePath, "utf8");
|
|
83
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
84
|
+
expect(after).toContain("tw.tv(");
|
|
85
|
+
expect(after.includes("tw.cn(")).toBe(false);
|
|
86
|
+
expect(after).toMatch(/base:\s*(\[|"[^"]+)/);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
it("supports namespace imports from legacy tailwind-variants package (backward compat)", () => {
|
|
90
|
+
withTempFixture("FixtureNamespaceLegacy.tsx", `import * as tw from "tailwind-variants";
|
|
91
|
+
|
|
92
|
+
export const styles = tw.tv({
|
|
93
|
+
base: tw.cn("flex gap-2 text-sm rounded-md border px-3 font-medium hover:bg-accent"),
|
|
94
|
+
});
|
|
95
|
+
`, (filePath) => {
|
|
96
|
+
const result = groupFile(filePath, {
|
|
97
|
+
write: true,
|
|
98
|
+
withClassName: false
|
|
99
|
+
}, arrangeFs, arrangeLogger);
|
|
100
|
+
const after = fs.readFileSync(filePath, "utf8");
|
|
101
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
102
|
+
expect(after).toContain("tw.tv(");
|
|
103
|
+
expect(after.includes("tw.cn(")).toBe(false);
|
|
104
|
+
expect(after).toMatch(/base:\s*(\[|"[^"]+)/);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
it("recognizes cn/tv imports from local ./utils re-export", () => {
|
|
108
|
+
withTempFixture("FixtureLocalUtils.tsx", `import { cn, tv } from "./utils";
|
|
109
|
+
export const styles = tv({ base: cn("flex gap-2 text-sm rounded-md border px-3 font-medium hover:bg-accent") });`, (filePath) => {
|
|
110
|
+
const result = groupFile(filePath, {
|
|
111
|
+
write: true,
|
|
112
|
+
withClassName: false
|
|
113
|
+
}, arrangeFs, arrangeLogger);
|
|
114
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
it("recognizes cn/tv imports from modules containing /utils/", () => {
|
|
118
|
+
withTempFixture("FixtureUtilsPath.tsx", `import { cn, tv } from "pkg/utils/cn";
|
|
119
|
+
export const styles = tv({ base: cn("flex gap-2 text-sm rounded-md border px-3 font-medium hover:bg-accent") });`, (filePath) => {
|
|
120
|
+
const result = groupFile(filePath, {
|
|
121
|
+
write: true,
|
|
122
|
+
withClassName: false
|
|
123
|
+
}, arrangeFs, arrangeLogger);
|
|
124
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
it("recognizes cn/tv imports from ./cn.ts shim", () => {
|
|
128
|
+
withTempFixture("FixtureCnTs.tsx", `import { cn, tv } from "./cn.ts";
|
|
129
|
+
export const styles = tv({ base: cn("flex gap-2 text-sm rounded-md border px-3 font-medium hover:bg-accent") });`, (filePath) => {
|
|
130
|
+
const result = groupFile(filePath, {
|
|
131
|
+
write: true,
|
|
132
|
+
withClassName: false
|
|
133
|
+
}, arrangeFs, arrangeLogger);
|
|
134
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
it("returns totalFound 0 for files without cn/tv/long class targets", () => {
|
|
138
|
+
withTempFixture("FixtureEmpty.tsx", "export const only = 1;\n", (filePath) => {
|
|
139
|
+
expect(groupFile(filePath, {
|
|
140
|
+
write: false,
|
|
141
|
+
withClassName: false
|
|
142
|
+
}, arrangeFs, arrangeLogger)).toEqual({
|
|
143
|
+
filePath,
|
|
144
|
+
totalFound: 0,
|
|
145
|
+
changed: 0
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
it("dry-run returns totalFound 0 when cn partition is already idempotent", () => {
|
|
150
|
+
withTempFixture("FixturePartitionEq.tsx", `import { cn } from "@codefast/tailwind-variants";
|
|
151
|
+
cn("flex gap-2", "text-sm");`, (filePath) => {
|
|
152
|
+
expect(groupFile(filePath, {
|
|
153
|
+
write: false,
|
|
154
|
+
withClassName: false
|
|
155
|
+
}, arrangeFs, arrangeLogger)).toEqual({
|
|
156
|
+
filePath,
|
|
157
|
+
totalFound: 0,
|
|
158
|
+
changed: 0
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
it("counts cn() inside tv with zero args as found but unchanged", () => {
|
|
163
|
+
withTempFixture("FixtureZeroArg.tsx", `import { cn, tv } from "@codefast/tailwind-variants";
|
|
164
|
+
export const broken = tv({ base: cn() });`, (filePath) => {
|
|
165
|
+
const dry = groupFile(filePath, {
|
|
166
|
+
write: false,
|
|
167
|
+
withClassName: false
|
|
168
|
+
}, arrangeFs, arrangeLogger);
|
|
169
|
+
expect(dry).toEqual({
|
|
170
|
+
filePath,
|
|
171
|
+
totalFound: 1,
|
|
172
|
+
changed: 0
|
|
173
|
+
});
|
|
174
|
+
const wet = groupFile(filePath, {
|
|
175
|
+
write: true,
|
|
176
|
+
withClassName: false
|
|
177
|
+
}, arrangeFs, arrangeLogger);
|
|
178
|
+
expect(wet).toEqual({
|
|
179
|
+
filePath,
|
|
180
|
+
totalFound: 1,
|
|
181
|
+
changed: 0
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
it("inserts cn import before first existing import", () => {
|
|
186
|
+
withTempFixture("FixtureImportOrder.tsx", `import React from "react";
|
|
187
|
+
export function Fixture() {
|
|
188
|
+
return <div className="flex items-center gap-2 px-4 py-2 text-sm rounded-md border bg-card" />;
|
|
189
|
+
}`, (filePath) => {
|
|
190
|
+
const result = groupFile(filePath, {
|
|
191
|
+
write: true,
|
|
192
|
+
withClassName: false
|
|
193
|
+
}, arrangeFs, arrangeLogger);
|
|
194
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
195
|
+
const after = fs.readFileSync(filePath, "utf8");
|
|
196
|
+
expect(after.indexOf("import { cn }")).toBeLessThan(after.indexOf("import React"));
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
it("passes withClassName option through cn replacement", () => {
|
|
200
|
+
withTempFixture("FixtureWithClassName.tsx", `import { cn } from "@codefast/tailwind-variants";
|
|
201
|
+
export function Fixture() {
|
|
202
|
+
return <div className="flex items-center gap-2 px-4 py-2 text-sm rounded-md border bg-card" />;
|
|
203
|
+
}`, (filePath) => {
|
|
204
|
+
const result = groupFile(filePath, {
|
|
205
|
+
write: true,
|
|
206
|
+
withClassName: true
|
|
207
|
+
}, arrangeFs, arrangeLogger);
|
|
208
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
209
|
+
const after = fs.readFileSync(filePath, "utf8");
|
|
210
|
+
expect(after).toContain("className={cn(");
|
|
211
|
+
expect(after).toContain("className");
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
it("merges cn into existing named import when cnImport override matches", () => {
|
|
215
|
+
withTempFixture("FixtureMergeImport.tsx", `import { tv } from "clsx";
|
|
216
|
+
export function Fixture() {
|
|
217
|
+
return <div className="flex items-center gap-2 px-4 py-2 text-sm rounded-md border bg-card" />;
|
|
218
|
+
}`, (filePath) => {
|
|
219
|
+
const result = groupFile(filePath, {
|
|
220
|
+
write: true,
|
|
221
|
+
withClassName: false,
|
|
222
|
+
cnImport: "clsx"
|
|
223
|
+
}, arrangeFs, arrangeLogger);
|
|
224
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
225
|
+
const after = fs.readFileSync(filePath, "utf8");
|
|
226
|
+
expect(after).toContain("import { cn, tv } from \"clsx\"");
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
it("groups compoundVariants class array slots", () => {
|
|
230
|
+
withTempFixture("FixtureCvClassArray.tsx", `import { tv } from "@codefast/tailwind-variants";
|
|
231
|
+
export const sheet = tv({
|
|
232
|
+
compoundVariants: [{ className: ["flex gap-2 text-sm rounded-md border px-3 font-medium", "shadow-xs"] }],
|
|
233
|
+
});`, (filePath) => {
|
|
234
|
+
const result = groupFile(filePath, {
|
|
235
|
+
write: true,
|
|
236
|
+
withClassName: false
|
|
237
|
+
}, arrangeFs, arrangeLogger);
|
|
238
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
239
|
+
const after = fs.readFileSync(filePath, "utf8");
|
|
240
|
+
expect(after).toContain("compoundVariants");
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
it("groups compoundVariants mixed class array slot with cn(...) and plain strings", () => {
|
|
244
|
+
withTempFixture("FixtureCvClassCn.tsx", `import { cn, tv } from "@codefast/tailwind-variants";
|
|
245
|
+
export const sheet = tv({
|
|
246
|
+
compoundVariants: [{ class: ["py-1", cn("flex gap-2 text-sm rounded-md border px-3 font-medium shadow-xs")] }],
|
|
247
|
+
});`, (filePath) => {
|
|
248
|
+
const result = groupFile(filePath, {
|
|
249
|
+
write: true,
|
|
250
|
+
withClassName: false
|
|
251
|
+
}, arrangeFs, arrangeLogger);
|
|
252
|
+
expect(result.changed).toBeGreaterThan(0);
|
|
253
|
+
const after = fs.readFileSync(filePath, "utf8");
|
|
254
|
+
expect(after).toContain("class:");
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
it("dry-run prints combined unwrap + grouping note", () => {
|
|
258
|
+
const long = "flex items-center gap-2 px-4 py-2 text-sm rounded-md border bg-card font-medium shadow-xs";
|
|
259
|
+
withTempFixture("FixtureDryCombined.tsx", `import { cn, tv } from "@codefast/tailwind-variants";
|
|
260
|
+
cn("${long}");
|
|
261
|
+
export const styles = tv({ base: cn("${long}") });`, (filePath) => {
|
|
262
|
+
const output = captureStdout(() => {
|
|
263
|
+
groupFile(filePath, {
|
|
264
|
+
write: false,
|
|
265
|
+
withClassName: false
|
|
266
|
+
}, arrangeFs, arrangeLogger);
|
|
267
|
+
});
|
|
268
|
+
expect(output).toContain("[cn] / [tv] / [JSX className]");
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
//#endregion
|
|
273
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { n as ArrangeErrorCode, t as ArrangeError } from "../../../errors-CpU1ogfA.mjs";
|
|
2
|
+
import { n as runOnTarget } from "../../../run-target-CJWrqNT4.mjs";
|
|
3
|
+
import { n as createNodeCliLogger, t as createNodeCliFs } from "../../../node-io-DFRbHO6m.mjs";
|
|
4
|
+
import "../../../arrange-BqP2DI3w.mjs";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
//#region src/lib/arrange/application/run-target.test.ts
|
|
9
|
+
const arrangeFs = createNodeCliFs();
|
|
10
|
+
const arrangeLogger = createNodeCliLogger();
|
|
11
|
+
function captureStdout(fn) {
|
|
12
|
+
const chunks = [];
|
|
13
|
+
const spy = jest.spyOn(process.stdout, "write").mockImplementation((chunk) => {
|
|
14
|
+
chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
|
15
|
+
return true;
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
fn();
|
|
19
|
+
} finally {
|
|
20
|
+
spy.mockRestore();
|
|
21
|
+
}
|
|
22
|
+
return chunks.join("");
|
|
23
|
+
}
|
|
24
|
+
describe("runOnTarget", () => {
|
|
25
|
+
it("prints dry-run totals for directory targets", () => {
|
|
26
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "arr-run-dry-"));
|
|
27
|
+
const long = "peer flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground shadow-xs outline-hidden transition hover:not-disabled:not-aria-checked:border-ring/60 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50";
|
|
28
|
+
try {
|
|
29
|
+
fs.writeFileSync(path.join(dir, "Page.tsx"), `import { cn } from "@codefast/tailwind-variants"; export function P(){ cn("${long}"); return null; }`, "utf8");
|
|
30
|
+
const out = captureStdout(() => runOnTarget(dir, {
|
|
31
|
+
write: false,
|
|
32
|
+
withClassName: false
|
|
33
|
+
}, arrangeFs, arrangeLogger));
|
|
34
|
+
expect(out).toContain("Total:");
|
|
35
|
+
expect(out).toContain("to review");
|
|
36
|
+
} finally {
|
|
37
|
+
fs.rmSync(dir, {
|
|
38
|
+
recursive: true,
|
|
39
|
+
force: true
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
it("throws ArrangeError for missing target path", () => {
|
|
44
|
+
let caught;
|
|
45
|
+
try {
|
|
46
|
+
runOnTarget(path.join(os.tmpdir(), "no-such-arrange-target"), {
|
|
47
|
+
write: false,
|
|
48
|
+
withClassName: false
|
|
49
|
+
}, arrangeFs, arrangeLogger);
|
|
50
|
+
} catch (caughtError) {
|
|
51
|
+
caught = caughtError;
|
|
52
|
+
}
|
|
53
|
+
expect(caught).toBeInstanceOf(ArrangeError);
|
|
54
|
+
expect(caught.code).toBe(ArrangeErrorCode.TARGET_NOT_FOUND);
|
|
55
|
+
});
|
|
56
|
+
it("prints apply summary in write mode", () => {
|
|
57
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "arr-run-apply-"));
|
|
58
|
+
const before = `import { cn } from "@codefast/tailwind-variants";
|
|
59
|
+
export function Fixture() {
|
|
60
|
+
return <div className="flex items-center gap-2 px-4 py-2 text-sm rounded-md border bg-card" />;
|
|
61
|
+
}`;
|
|
62
|
+
try {
|
|
63
|
+
fs.writeFileSync(path.join(dir, "Apply.tsx"), before, "utf8");
|
|
64
|
+
const out = captureStdout(() => runOnTarget(dir, {
|
|
65
|
+
write: true,
|
|
66
|
+
withClassName: false
|
|
67
|
+
}, arrangeFs, arrangeLogger));
|
|
68
|
+
expect(out).toContain("Applied:");
|
|
69
|
+
expect(out).toContain("Note:");
|
|
70
|
+
} finally {
|
|
71
|
+
fs.rmSync(dir, {
|
|
72
|
+
recursive: true,
|
|
73
|
+
force: true
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
it("accepts legacy tailwind-variants imports when running directory dry-run (backward compat)", () => {
|
|
78
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "arr-run-dry-legacy-"));
|
|
79
|
+
const long = "peer flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground shadow-xs outline-hidden transition hover:not-disabled:not-aria-checked:border-ring/60 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50";
|
|
80
|
+
try {
|
|
81
|
+
fs.writeFileSync(path.join(dir, "Page.tsx"), `import { cn } from "tailwind-variants"; export function P(){ cn("${long}"); return null; }`, "utf8");
|
|
82
|
+
const out = captureStdout(() => runOnTarget(dir, {
|
|
83
|
+
write: false,
|
|
84
|
+
withClassName: false
|
|
85
|
+
}, arrangeFs, arrangeLogger));
|
|
86
|
+
expect(out).toContain("Total:");
|
|
87
|
+
expect(out).toContain("to review");
|
|
88
|
+
} finally {
|
|
89
|
+
fs.rmSync(dir, {
|
|
90
|
+
recursive: true,
|
|
91
|
+
force: true
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
//#endregion
|
|
97
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as isCnOrTvIdentifier, c as propertyAssignmentNameText, i as indentOfLineContaining, l as unwrapCnInsideTvCallReplacement, n as applyEditsDescending, o as lineOf, r as buildKnownCnTvBindings, s as moduleLooksLikeCnTvReexport, t as KNOWN_CN_TV_MODULES } from "../../../../ast-helpers-MDRYuIAk.mjs";
|
|
2
|
+
export { KNOWN_CN_TV_MODULES, applyEditsDescending, buildKnownCnTvBindings, indentOfLineContaining, isCnOrTvIdentifier, lineOf, moduleLooksLikeCnTvReexport, propertyAssignmentNameText, unwrapCnInsideTvCallReplacement };
|