@codefast/cli 0.3.12 → 0.3.13-canary.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.
Files changed (159) hide show
  1. package/README.md +206 -51
  2. package/dist/{bin.js → bin.mjs} +4 -1
  3. package/dist/commands/arrange.mjs +107 -0
  4. package/dist/commands/mirror.mjs +66 -0
  5. package/dist/commands/tag.mjs +58 -0
  6. package/dist/lib/arrange/application/analyze.mjs +92 -0
  7. package/dist/lib/arrange/application/group-file.mjs +113 -0
  8. package/dist/lib/arrange/application/run-target.mjs +65 -0
  9. package/dist/lib/arrange/domain/ast/ast-helpers.mjs +115 -0
  10. package/dist/lib/arrange/domain/ast/collectors-cn.mjs +61 -0
  11. package/dist/lib/arrange/domain/ast/collectors-jsx.mjs +20 -0
  12. package/dist/lib/arrange/domain/ast/collectors-tv.mjs +170 -0
  13. package/dist/lib/arrange/domain/ast/targets.mjs +106 -0
  14. package/dist/lib/arrange/domain/constants.mjs +169 -0
  15. package/dist/lib/arrange/domain/errors.mjs +19 -0
  16. package/dist/lib/arrange/domain/grouping.mjs +235 -0
  17. package/dist/lib/arrange/domain/imports.mjs +54 -0
  18. package/dist/lib/arrange/domain/tokenizer.mjs +216 -0
  19. package/dist/lib/arrange/index.mjs +15 -0
  20. package/dist/lib/arrange/infra/walk.mjs +33 -0
  21. package/dist/lib/arrange/presentation/formatters.mjs +52 -0
  22. package/dist/lib/arrange/presentation/report.mjs +24 -0
  23. package/dist/lib/config/domain/schema.mjs +25 -0
  24. package/dist/lib/config/index.mjs +3 -0
  25. package/dist/lib/config/infra/loader.mjs +78 -0
  26. package/dist/lib/infra/caught-unknown-message.mjs +20 -0
  27. package/dist/lib/infra/config-reporter.mjs +10 -0
  28. package/dist/lib/infra/node-io.mjs +30 -0
  29. package/dist/lib/infra/workspace/repo-root.mjs +22 -0
  30. package/dist/lib/mirror/application/engine.mjs +248 -0
  31. package/dist/lib/mirror/application/sync.mjs +162 -0
  32. package/dist/lib/mirror/domain/constants.mjs +12 -0
  33. package/dist/lib/mirror/domain/errors.mjs +15 -0
  34. package/dist/lib/mirror/domain/types.mjs +1 -0
  35. package/dist/lib/mirror/index.mjs +2 -0
  36. package/dist/lib/mirror/infra/dirent-list.mjs +14 -0
  37. package/dist/lib/mirror/infra/package-filter.mjs +26 -0
  38. package/dist/lib/mirror/infra/path-normalizer.mjs +7 -0
  39. package/dist/lib/mirror/infra/update-pkg.mjs +105 -0
  40. package/dist/lib/mirror/infra/workspace-packages.mjs +159 -0
  41. package/dist/lib/mirror/presentation/reporter.mjs +111 -0
  42. package/dist/lib/tag/application/engine.mjs +287 -0
  43. package/dist/lib/tag/domain/types.mjs +1 -0
  44. package/dist/lib/tag/index.mjs +4 -0
  45. package/dist/lib/tag/infra/target-resolver.mjs +170 -0
  46. package/dist/lib/tag/presentation/colors.mjs +12 -0
  47. package/dist/lib/tag/presentation/tag-presenter.mjs +63 -0
  48. package/dist/program.mjs +34 -0
  49. package/package.json +11 -11
  50. package/dist/bin.d.ts +0 -3
  51. package/dist/bin.d.ts.map +0 -1
  52. package/dist/commands/arrange.d.ts +0 -3
  53. package/dist/commands/arrange.d.ts.map +0 -1
  54. package/dist/commands/arrange.js +0 -114
  55. package/dist/commands/mirror.d.ts +0 -4
  56. package/dist/commands/mirror.d.ts.map +0 -1
  57. package/dist/commands/mirror.js +0 -63
  58. package/dist/lib/arrange/analyze.d.ts +0 -4
  59. package/dist/lib/arrange/analyze.d.ts.map +0 -1
  60. package/dist/lib/arrange/analyze.js +0 -105
  61. package/dist/lib/arrange/ast/collectors-cn.d.ts +0 -10
  62. package/dist/lib/arrange/ast/collectors-cn.d.ts.map +0 -1
  63. package/dist/lib/arrange/ast/collectors-cn.js +0 -84
  64. package/dist/lib/arrange/ast/collectors-jsx.d.ts +0 -4
  65. package/dist/lib/arrange/ast/collectors-jsx.d.ts.map +0 -1
  66. package/dist/lib/arrange/ast/collectors-jsx.js +0 -18
  67. package/dist/lib/arrange/ast/collectors-tv.d.ts +0 -13
  68. package/dist/lib/arrange/ast/collectors-tv.d.ts.map +0 -1
  69. package/dist/lib/arrange/ast/collectors-tv.js +0 -273
  70. package/dist/lib/arrange/ast/targets.d.ts +0 -8
  71. package/dist/lib/arrange/ast/targets.d.ts.map +0 -1
  72. package/dist/lib/arrange/ast/targets.js +0 -143
  73. package/dist/lib/arrange/ast/utils.d.ts +0 -36
  74. package/dist/lib/arrange/ast/utils.d.ts.map +0 -1
  75. package/dist/lib/arrange/ast/utils.js +0 -138
  76. package/dist/lib/arrange/constants.d.ts +0 -68
  77. package/dist/lib/arrange/constants.d.ts.map +0 -1
  78. package/dist/lib/arrange/constants.js +0 -179
  79. package/dist/lib/arrange/errors.d.ts +0 -14
  80. package/dist/lib/arrange/errors.d.ts.map +0 -1
  81. package/dist/lib/arrange/errors.js +0 -16
  82. package/dist/lib/arrange/formatters.d.ts +0 -16
  83. package/dist/lib/arrange/formatters.d.ts.map +0 -1
  84. package/dist/lib/arrange/formatters.js +0 -57
  85. package/dist/lib/arrange/group-file.d.ts +0 -4
  86. package/dist/lib/arrange/group-file.d.ts.map +0 -1
  87. package/dist/lib/arrange/group-file.js +0 -115
  88. package/dist/lib/arrange/grouping.d.ts +0 -41
  89. package/dist/lib/arrange/grouping.d.ts.map +0 -1
  90. package/dist/lib/arrange/grouping.js +0 -280
  91. package/dist/lib/arrange/imports.d.ts +0 -6
  92. package/dist/lib/arrange/imports.d.ts.map +0 -1
  93. package/dist/lib/arrange/imports.js +0 -74
  94. package/dist/lib/arrange/report.d.ts +0 -4
  95. package/dist/lib/arrange/report.d.ts.map +0 -1
  96. package/dist/lib/arrange/report.js +0 -37
  97. package/dist/lib/arrange/run-target.d.ts +0 -4
  98. package/dist/lib/arrange/run-target.d.ts.map +0 -1
  99. package/dist/lib/arrange/run-target.js +0 -28
  100. package/dist/lib/arrange/tokenizer.d.ts +0 -37
  101. package/dist/lib/arrange/tokenizer.d.ts.map +0 -1
  102. package/dist/lib/arrange/tokenizer.js +0 -390
  103. package/dist/lib/arrange/types.d.ts +0 -104
  104. package/dist/lib/arrange/types.d.ts.map +0 -1
  105. package/dist/lib/arrange/types.js +0 -7
  106. package/dist/lib/arrange/walk.d.ts +0 -4
  107. package/dist/lib/arrange/walk.d.ts.map +0 -1
  108. package/dist/lib/arrange/walk.js +0 -34
  109. package/dist/lib/arrange.d.ts +0 -41
  110. package/dist/lib/arrange.d.ts.map +0 -1
  111. package/dist/lib/arrange.js +0 -38
  112. package/dist/lib/infra/fs-contract.d.ts +0 -28
  113. package/dist/lib/infra/fs-contract.d.ts.map +0 -1
  114. package/dist/lib/infra/node-io.d.ts +0 -4
  115. package/dist/lib/infra/node-io.d.ts.map +0 -1
  116. package/dist/lib/infra/node-io.js +0 -27
  117. package/dist/lib/mirror/config.d.ts +0 -7
  118. package/dist/lib/mirror/config.d.ts.map +0 -1
  119. package/dist/lib/mirror/config.js +0 -146
  120. package/dist/lib/mirror/constants.d.ts +0 -10
  121. package/dist/lib/mirror/constants.d.ts.map +0 -1
  122. package/dist/lib/mirror/constants.js +0 -13
  123. package/dist/lib/mirror/engine.d.ts +0 -12
  124. package/dist/lib/mirror/engine.d.ts.map +0 -1
  125. package/dist/lib/mirror/engine.js +0 -248
  126. package/dist/lib/mirror/errors.d.ts +0 -10
  127. package/dist/lib/mirror/errors.d.ts.map +0 -1
  128. package/dist/lib/mirror/errors.js +0 -12
  129. package/dist/lib/mirror/package-filter.d.ts +0 -8
  130. package/dist/lib/mirror/package-filter.d.ts.map +0 -1
  131. package/dist/lib/mirror/package-filter.js +0 -33
  132. package/dist/lib/mirror/reporter.d.ts +0 -24
  133. package/dist/lib/mirror/reporter.d.ts.map +0 -1
  134. package/dist/lib/mirror/reporter.js +0 -126
  135. package/dist/lib/mirror/sync.d.ts +0 -4
  136. package/dist/lib/mirror/sync.d.ts.map +0 -1
  137. package/dist/lib/mirror/sync.js +0 -135
  138. package/dist/lib/mirror/types.d.ts +0 -81
  139. package/dist/lib/mirror/types.d.ts.map +0 -1
  140. package/dist/lib/mirror/update-pkg.d.ts +0 -13
  141. package/dist/lib/mirror/update-pkg.d.ts.map +0 -1
  142. package/dist/lib/mirror/update-pkg.js +0 -39
  143. package/dist/lib/mirror/workspace-packages.d.ts +0 -26
  144. package/dist/lib/mirror/workspace-packages.d.ts.map +0 -1
  145. package/dist/lib/mirror/workspace-packages.js +0 -160
  146. package/dist/lib/mirror.d.ts +0 -7
  147. package/dist/lib/mirror.d.ts.map +0 -1
  148. package/dist/lib/mirror.js +0 -5
  149. package/dist/lib/repo-root.d.ts +0 -7
  150. package/dist/lib/repo-root.d.ts.map +0 -1
  151. package/dist/lib/repo-root.js +0 -23
  152. package/dist/lib/shared/utils.d.ts +0 -9
  153. package/dist/lib/shared/utils.d.ts.map +0 -1
  154. package/dist/lib/shared/utils.js +0 -12
  155. package/dist/program.d.ts +0 -4
  156. package/dist/program.d.ts.map +0 -1
  157. package/dist/program.js +0 -38
  158. /package/dist/lib/{infra/fs-contract.js → arrange/domain/types.mjs} +0 -0
  159. /package/dist/lib/{mirror/types.js → infra/fs-contract.mjs} +0 -0
@@ -0,0 +1,170 @@
1
+ import { messageFromCaughtUnknown } from "../../infra/caught-unknown-message.mjs";
2
+ import { globSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { z } from "zod";
5
+ import picomatch from "picomatch";
6
+ import { parse } from "yaml";
7
+ //#region src/lib/tag/infra/target-resolver.ts
8
+ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
9
+ const PACKAGE_JSON_FILE = "package.json";
10
+ const DEFAULT_WORKSPACE_PACKAGE_PATTERNS = ["packages/*"];
11
+ const PnpmWorkspaceSchema = z.object({ packages: z.array(z.string()).optional() }).passthrough();
12
+ const PackageNameSchema = z.object({ name: z.string().min(1).optional() }).passthrough();
13
+ function toPosix(filePath) {
14
+ return filePath.split(path.sep).join("/");
15
+ }
16
+ function workspacePatternToPackageJsonGlob(pattern) {
17
+ const normalizedPattern = toPosix(pattern.trim()).replace(/\/+$/, "");
18
+ if (!normalizedPattern) return `**/${PACKAGE_JSON_FILE}`;
19
+ return `${normalizedPattern}/${PACKAGE_JSON_FILE}`;
20
+ }
21
+ function splitWorkspacePackagePatterns(rawPatterns) {
22
+ const include = [];
23
+ const exclude = [];
24
+ for (const rawPattern of rawPatterns) {
25
+ const trimmedPattern = rawPattern.trim();
26
+ if (!trimmedPattern) continue;
27
+ if (trimmedPattern.startsWith("!")) {
28
+ exclude.push(trimmedPattern.slice(1).trim());
29
+ continue;
30
+ }
31
+ include.push(trimmedPattern);
32
+ }
33
+ return {
34
+ include,
35
+ exclude
36
+ };
37
+ }
38
+ async function readWorkspacePackagePatterns(rootDir, fs) {
39
+ const workspaceYamlPath = path.join(rootDir, PNPM_WORKSPACE_FILE);
40
+ if (!fs.existsSync(workspaceYamlPath)) return {
41
+ patterns: [...DEFAULT_WORKSPACE_PACKAGE_PATTERNS],
42
+ hasWorkspaceFile: false,
43
+ declaredEmptyPackages: false
44
+ };
45
+ let workspaceYamlRaw = "";
46
+ try {
47
+ workspaceYamlRaw = await fs.readFile(workspaceYamlPath, "utf8");
48
+ } catch (caughtReadError) {
49
+ throw new Error(`Failed to read ${PNPM_WORKSPACE_FILE}: ${messageFromCaughtUnknown(caughtReadError)}`);
50
+ }
51
+ let parsedWorkspaceDoc;
52
+ try {
53
+ parsedWorkspaceDoc = parse(workspaceYamlRaw);
54
+ } catch (caughtParseError) {
55
+ throw new Error(`Failed to parse ${PNPM_WORKSPACE_FILE}: ${messageFromCaughtUnknown(caughtParseError)}`);
56
+ }
57
+ const parsedWorkspaceResult = PnpmWorkspaceSchema.safeParse(parsedWorkspaceDoc);
58
+ if (!parsedWorkspaceResult.success) {
59
+ const issuePath = parsedWorkspaceResult.error.issues[0]?.path.join(".") ?? "<root>";
60
+ const issueMessage = parsedWorkspaceResult.error.issues[0]?.message ?? "Invalid value";
61
+ throw new Error(`Invalid ${PNPM_WORKSPACE_FILE} schema at "${issuePath}": ${issueMessage}`);
62
+ }
63
+ const hasPackagesKey = typeof parsedWorkspaceDoc === "object" && parsedWorkspaceDoc !== null && Object.prototype.hasOwnProperty.call(parsedWorkspaceDoc, "packages");
64
+ const workspacePackages = parsedWorkspaceResult.data.packages;
65
+ if (!workspacePackages) return {
66
+ patterns: [...DEFAULT_WORKSPACE_PACKAGE_PATTERNS],
67
+ hasWorkspaceFile: true,
68
+ declaredEmptyPackages: false
69
+ };
70
+ if (workspacePackages.length === 0 && hasPackagesKey) return {
71
+ patterns: [],
72
+ hasWorkspaceFile: true,
73
+ declaredEmptyPackages: true
74
+ };
75
+ return {
76
+ patterns: workspacePackages,
77
+ hasWorkspaceFile: true,
78
+ declaredEmptyPackages: false
79
+ };
80
+ }
81
+ async function discoverWorkspacePackageDirs(rootDir, fs) {
82
+ const workspacePackages = await readWorkspacePackagePatterns(rootDir, fs);
83
+ const { include, exclude } = splitWorkspacePackagePatterns(workspacePackages.patterns);
84
+ const includePatterns = include.length > 0 ? include : [...DEFAULT_WORKSPACE_PACKAGE_PATTERNS];
85
+ const foundPackageDirs = /* @__PURE__ */ new Set();
86
+ const globOptions = {
87
+ cwd: rootDir,
88
+ posix: true,
89
+ ...path.sep === "\\" ? { windowsPathsNoEscape: true } : {}
90
+ };
91
+ for (const pattern of includePatterns) {
92
+ const matches = globSync(workspacePatternToPackageJsonGlob(pattern), globOptions);
93
+ for (const matchedPath of matches) {
94
+ if (!matchedPath.endsWith(`/${PACKAGE_JSON_FILE}`)) continue;
95
+ const packageRelDir = matchedPath.slice(0, -`/${PACKAGE_JSON_FILE}`.length);
96
+ if (!packageRelDir) continue;
97
+ foundPackageDirs.add(path.resolve(rootDir, packageRelDir));
98
+ }
99
+ }
100
+ if (exclude.length === 0) return {
101
+ packageDirs: [...foundPackageDirs].sort((leftDir, rightDir) => leftDir.localeCompare(rightDir)),
102
+ hasWorkspaceFile: workspacePackages.hasWorkspaceFile,
103
+ declaredEmptyPackages: workspacePackages.declaredEmptyPackages
104
+ };
105
+ const excludeMatchers = exclude.map((excludePattern) => picomatch(excludePattern, { dot: true }));
106
+ const discoveredDirs = [...foundPackageDirs].filter((packageDir) => {
107
+ const relativePackagePath = toPosix(path.relative(rootDir, packageDir));
108
+ return !excludeMatchers.some((excludeMatcher) => excludeMatcher(relativePackagePath));
109
+ });
110
+ discoveredDirs.sort((leftDir, rightDir) => leftDir.localeCompare(rightDir));
111
+ return {
112
+ packageDirs: discoveredDirs,
113
+ hasWorkspaceFile: workspacePackages.hasWorkspaceFile,
114
+ declaredEmptyPackages: workspacePackages.declaredEmptyPackages
115
+ };
116
+ }
117
+ function readPackageName(packageDir, fs) {
118
+ const packageJsonPath = path.join(packageDir, PACKAGE_JSON_FILE);
119
+ if (!fs.existsSync(packageJsonPath)) return null;
120
+ try {
121
+ const rawPackageJson = fs.readFileSync(packageJsonPath, "utf8");
122
+ const parsedPackageJson = JSON.parse(rawPackageJson);
123
+ const parsedPackageName = PackageNameSchema.safeParse(parsedPackageJson);
124
+ if (!parsedPackageName.success) return null;
125
+ return parsedPackageName.data.name ?? null;
126
+ } catch {
127
+ return null;
128
+ }
129
+ }
130
+ function toRootRelativePath(rootDir, absolutePath) {
131
+ const relativePath = path.relative(rootDir, absolutePath);
132
+ return relativePath ? toPosix(relativePath) : ".";
133
+ }
134
+ function toPackageTargetCandidate(rootDir, packageDir, fs) {
135
+ const packageName = readPackageName(packageDir, fs);
136
+ return {
137
+ candidatePath: packageDir,
138
+ rootRelativeCandidatePath: toRootRelativePath(rootDir, packageDir),
139
+ source: "workspace-package",
140
+ packageDir,
141
+ packageName
142
+ };
143
+ }
144
+ async function resolveTagTargetCandidates(rootDir, explicitTarget, fs) {
145
+ if (explicitTarget) {
146
+ const resolvedExplicitTarget = path.resolve(explicitTarget);
147
+ return [{
148
+ candidatePath: resolvedExplicitTarget,
149
+ rootRelativeCandidatePath: toRootRelativePath(rootDir, resolvedExplicitTarget),
150
+ source: "explicit-target",
151
+ packageDir: null,
152
+ packageName: null
153
+ }];
154
+ }
155
+ const workspaceDiscovery = await discoverWorkspacePackageDirs(rootDir, fs);
156
+ if (workspaceDiscovery.packageDirs.length === 0) {
157
+ if (workspaceDiscovery.hasWorkspaceFile) return [];
158
+ const repoSourceDir = path.join(rootDir, "src");
159
+ return [{
160
+ candidatePath: repoSourceDir,
161
+ rootRelativeCandidatePath: toRootRelativePath(rootDir, repoSourceDir),
162
+ source: "repo-src-fallback",
163
+ packageDir: null,
164
+ packageName: null
165
+ }];
166
+ }
167
+ return workspaceDiscovery.packageDirs.map((packageDir) => toPackageTargetCandidate(rootDir, packageDir, fs));
168
+ }
169
+ //#endregion
170
+ export { resolveTagTargetCandidates };
@@ -0,0 +1,12 @@
1
+ //#region src/lib/tag/presentation/colors.ts
2
+ const TAG_COLORS = {
3
+ RESET: "\x1B[0m",
4
+ RED: "\x1B[31m",
5
+ GREEN: "\x1B[32m",
6
+ YELLOW: "\x1B[33m"
7
+ };
8
+ function withTagColor(line, colorCode) {
9
+ return `${colorCode}${line}${TAG_COLORS.RESET}`;
10
+ }
11
+ //#endregion
12
+ export { TAG_COLORS, withTagColor };
@@ -0,0 +1,63 @@
1
+ import { TAG_COLORS, withTagColor } from "./colors.mjs";
2
+ //#region src/lib/tag/presentation/tag-presenter.ts
3
+ function targetDisplayName(target) {
4
+ return target.packageName ?? target.rootRelativeTargetPath;
5
+ }
6
+ function warningsAndErrorsFromResult(result) {
7
+ const entries = [];
8
+ for (const targetResult of result.targetResults) if (targetResult.runError) entries.push(targetResult.runError);
9
+ if (result.hookError) entries.push(result.hookError);
10
+ return entries;
11
+ }
12
+ function formatProgress(event) {
13
+ if (event.type === "target-started") return `[tag] Processing ${targetDisplayName(event.target)}...`;
14
+ const changedFiles = event.result.result?.filesChanged ?? 0;
15
+ return `[tag] Done ${targetDisplayName(event.target)} (${changedFiles} changes)`;
16
+ }
17
+ function formatTargetTable(targets, rootDir) {
18
+ const packageColumnWidth = Math.max(7, ...targets.map((target) => (target.packageName ?? "-").length));
19
+ const pathColumnWidth = Math.max(4, ...targets.map((target) => target.rootRelativeTargetPath.length));
20
+ const lines = [];
21
+ lines.push(`[tag] Root: ${rootDir}`);
22
+ lines.push(`[tag] Resolved targets: ${targets.length}`);
23
+ lines.push("[tag] Targets:");
24
+ lines.push(` ${"package".padEnd(packageColumnWidth)} ${"path".padEnd(pathColumnWidth)} source`);
25
+ lines.push(` ${"-".repeat(packageColumnWidth)} ${"-".repeat(pathColumnWidth)} ${"-".repeat(6)}`);
26
+ for (const target of targets) lines.push(` ${(target.packageName ?? "-").padEnd(packageColumnWidth)} ${target.rootRelativeTargetPath.padEnd(pathColumnWidth)} ${target.source}`);
27
+ return lines.join("\n");
28
+ }
29
+ function formatWarningsAndErrors(result) {
30
+ const entries = warningsAndErrorsFromResult(result);
31
+ if (entries.length === 0) return null;
32
+ const lines = [withTagColor("⚠️ Warnings & Errors", TAG_COLORS.YELLOW)];
33
+ for (const entry of entries) lines.push(withTagColor(`- ${entry}`, TAG_COLORS.RED));
34
+ return lines.join("\n");
35
+ }
36
+ function formatSummary(result) {
37
+ const isDryRun = result.mode === "dry-run";
38
+ const summaryPrefix = isDryRun ? "[tag:dry-run]" : "[tag]";
39
+ const versionSuffix = result.versionSummary === "mixed" && result.distinctVersions.length > 0 ? ` [${result.distinctVersions.join(", ")}]` : "";
40
+ const summaryColor = result.targetResults.some((targetResult) => targetResult.runError !== null) || result.hookError !== null ? TAG_COLORS.RED : isDryRun ? TAG_COLORS.YELLOW : TAG_COLORS.GREEN;
41
+ const lines = [withTagColor(`${summaryPrefix} version=${result.versionSummary}${versionSuffix} files=${result.filesChanged}/${result.filesScanned} declarations=${result.taggedDeclarations}`, summaryColor)];
42
+ if (result.skippedPackages.length > 0) lines.push(`[tag] Skipped: ${result.skippedPackages.length} package(s)`);
43
+ return lines.join("\n");
44
+ }
45
+ function createTagProgressListener(onProgressLine) {
46
+ return {
47
+ onTargetStarted: (target) => {
48
+ onProgressLine(formatProgress({
49
+ type: "target-started",
50
+ target
51
+ }));
52
+ },
53
+ onTargetCompleted: (target, result) => {
54
+ onProgressLine(formatProgress({
55
+ type: "target-completed",
56
+ target,
57
+ result
58
+ }));
59
+ }
60
+ };
61
+ }
62
+ //#endregion
63
+ export { createTagProgressListener, formatProgress, formatSummary, formatTargetTable, formatWarningsAndErrors };
@@ -0,0 +1,34 @@
1
+ import { registerArrangeCommand } from "./commands/arrange.mjs";
2
+ import { registerMirrorCommand } from "./commands/mirror.mjs";
3
+ import { registerTagCommand } from "./commands/tag.mjs";
4
+ import process from "node:process";
5
+ import { readFileSync } from "node:fs";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { Command } from "commander";
9
+ //#region src/program.ts
10
+ function readVersion() {
11
+ try {
12
+ const dir = path.dirname(fileURLToPath(import.meta.url));
13
+ const raw = readFileSync(path.join(dir, "..", "package.json"), "utf-8");
14
+ const pkgVersion = JSON.parse(raw).version;
15
+ return typeof pkgVersion === "string" && pkgVersion.length > 0 ? pkgVersion : "unknown";
16
+ } catch {
17
+ return "unknown";
18
+ }
19
+ }
20
+ function createProgram() {
21
+ const program = new Command();
22
+ program.name("codefast").description("Codefast monorepo developer CLI").version(readVersion()).option("--no-color", "Disable ANSI color output").configureHelp({ sortSubcommands: true }).showHelpAfterError("(use --help for usage)");
23
+ registerMirrorCommand(program);
24
+ registerArrangeCommand(program);
25
+ registerTagCommand(program);
26
+ return program;
27
+ }
28
+ async function runCli(argv) {
29
+ await createProgram().parseAsync(argv, { from: "node" });
30
+ const code = process.exitCode ?? 0;
31
+ return typeof code === "number" ? code : Number(code) || 0;
32
+ }
33
+ //#endregion
34
+ export { createProgram, runCli };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codefast/cli",
3
- "version": "0.3.12",
3
+ "version": "0.3.13-canary.1",
4
4
  "description": "Developer CLI for the Codefast monorepo (mirror, arrange)",
5
5
  "keywords": [
6
6
  "cli",
@@ -22,7 +22,7 @@
22
22
  "directory": "packages/cli"
23
23
  },
24
24
  "bin": {
25
- "codefast": "./dist/bin.js"
25
+ "codefast": "./dist/bin.mjs"
26
26
  },
27
27
  "files": [
28
28
  "dist",
@@ -33,14 +33,11 @@
33
33
  "type": "module",
34
34
  "imports": {
35
35
  "#*": [
36
- "./dist/*.js",
37
- "./dist/*/index.js",
38
- "./dist/*",
36
+ "./src/*",
39
37
  "./src/*.ts",
40
38
  "./src/*.tsx",
41
39
  "./src/*/index.ts",
42
- "./src/*/index.tsx",
43
- "./src/*"
40
+ "./src/*/index.tsx"
44
41
  ]
45
42
  },
46
43
  "publishConfig": {
@@ -48,9 +45,11 @@
48
45
  },
49
46
  "dependencies": {
50
47
  "commander": "^14.0.3",
48
+ "jiti": "^2.6.1",
51
49
  "picomatch": "^4.0.4",
52
50
  "typescript": "^6.0.2",
53
- "yaml": "^2.8.3"
51
+ "yaml": "^2.8.3",
52
+ "zod": "^4.3.6"
54
53
  },
55
54
  "devDependencies": {
56
55
  "@swc/core": "^1.15.24",
@@ -60,13 +59,14 @@
60
59
  "@types/picomatch": "^4.0.3",
61
60
  "@typescript/native-preview": "7.0.0-dev.20260411.1",
62
61
  "jest": "^30.3.0",
63
- "@codefast/typescript-config": "0.3.12"
62
+ "tsdown": "^0.21.8",
63
+ "@codefast/typescript-config": "0.3.13-canary.1"
64
64
  },
65
65
  "engines": {
66
- "node": ">=24.0.0"
66
+ "node": ">=22.0.0"
67
67
  },
68
68
  "scripts": {
69
- "build": "rm -rf dist && tsgo -p tsconfig.build.json",
69
+ "build": "tsdown",
70
70
  "check-types": "tsgo --noEmit",
71
71
  "clean": "rm -rf dist",
72
72
  "test": "jest",
package/dist/bin.d.ts DELETED
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=bin.d.ts.map
package/dist/bin.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
@@ -1,3 +0,0 @@
1
- import type { Command } from "commander";
2
- export declare function registerArrangeCommand(program: Command): void;
3
- //# sourceMappingURL=arrange.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"arrange.d.ts","sourceRoot":"","sources":["../../src/commands/arrange.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4EzC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsF7D"}
@@ -1,114 +0,0 @@
1
- import path from "node:path";
2
- import process from "node:process";
3
- import { Option } from "commander";
4
- import { ArrangeError, ArrangeErrorCode, analyzeDirectory, createNodeCliFs, createNodeCliLogger, DEFAULT_ARRANGE_TARGET, formatArray, formatCnCall, printAnalyzeReport, runOnTarget, suggestCnGroups, summarizeGroupBucketLabels, } from "#lib/arrange";
5
- /** Commander attribute `withClassName` (second long flag `--with-class-name`). */
6
- function createWithClassNameOption() {
7
- return new Option("--with-classname, --with-class-name", "Append className as final cn() argument").default(false);
8
- }
9
- function defaultTargetPath() {
10
- return path.resolve(process.cwd(), DEFAULT_ARRANGE_TARGET);
11
- }
12
- function checkTargetExists(resolved, fs, logger) {
13
- if (!fs.existsSync(resolved)) {
14
- logger.err(`Not found: ${resolved}`);
15
- process.exitCode = 1;
16
- return false;
17
- }
18
- return true;
19
- }
20
- function handleArrangeLibError(e, logger) {
21
- if (e instanceof ArrangeError && e.code === ArrangeErrorCode.TARGET_NOT_FOUND) {
22
- logger.err(e.message);
23
- process.exitCode = 1;
24
- return true;
25
- }
26
- return false;
27
- }
28
- function runArrangeAction(resolvedTarget, runOpts, fs, logger) {
29
- try {
30
- runOnTarget(resolvedTarget, {
31
- write: runOpts.write,
32
- withClassName: !!runOpts.withClassName,
33
- cnImport: runOpts.cnImport,
34
- }, fs, logger);
35
- }
36
- catch (e) {
37
- if (!handleArrangeLibError(e, logger))
38
- throw e;
39
- }
40
- }
41
- export function registerArrangeCommand(program) {
42
- const arrange = program
43
- .command("arrange")
44
- .description("Analyze and regroup Tailwind classes in cn() / tv() calls (Tailwind v4)");
45
- arrange
46
- .command("analyze")
47
- .description("Report long strings, nested cn in tv(), and related findings")
48
- .argument("[target]", "Directory or file (default: packages/ui/src/components)")
49
- .action((target) => {
50
- const fs = createNodeCliFs();
51
- const logger = createNodeCliLogger();
52
- const resolved = target ? path.resolve(target) : defaultTargetPath();
53
- if (!checkTargetExists(resolved, fs, logger))
54
- return;
55
- printAnalyzeReport(resolved, analyzeDirectory(resolved, fs), logger);
56
- });
57
- arrange
58
- .command("preview")
59
- .description("Dry-run: print suggested replacements without writing files")
60
- .argument("[target]", "Directory or file (default: packages/ui/src/components)")
61
- .addOption(createWithClassNameOption())
62
- .option("--cn-import <spec>", "Override module specifier when adding cn import")
63
- .action((target, opts) => {
64
- const fs = createNodeCliFs();
65
- const logger = createNodeCliLogger();
66
- const resolved = target ? path.resolve(target) : defaultTargetPath();
67
- if (!checkTargetExists(resolved, fs, logger))
68
- return;
69
- runArrangeAction(resolved, {
70
- write: false,
71
- withClassName: opts.withClassName,
72
- cnImport: opts.cnImport,
73
- }, fs, logger);
74
- });
75
- arrange
76
- .command("apply")
77
- .description("Apply grouping and cn-in-tv unwrap edits to files")
78
- .argument("[target]", "Directory or file (default: packages/ui/src/components)")
79
- .addOption(createWithClassNameOption())
80
- .option("--cn-import <spec>", "Override module specifier when adding cn import")
81
- .action((target, opts) => {
82
- const fs = createNodeCliFs();
83
- const logger = createNodeCliLogger();
84
- const resolved = target ? path.resolve(target) : defaultTargetPath();
85
- if (!checkTargetExists(resolved, fs, logger))
86
- return;
87
- runArrangeAction(resolved, {
88
- write: true,
89
- withClassName: opts.withClassName,
90
- cnImport: opts.cnImport,
91
- }, fs, logger);
92
- });
93
- arrange
94
- .command("group")
95
- .description("Try grouping on a pasted class string (stdout: cn(...) or tv array with --tv)")
96
- .argument("[tokens...]", "Class tokens (quote a single string if it contains spaces)")
97
- .option("--tv", "Emit tv()-style array instead of cn() call", false)
98
- .addOption(createWithClassNameOption())
99
- .action((tokens, opts) => {
100
- const inlineClasses = tokens.join(" ").trim();
101
- if (!inlineClasses) {
102
- process.stderr.write('Pass a class string. Example: codefast arrange group "flex gap-2 text-sm rounded-md"\n');
103
- process.exitCode = 1;
104
- return;
105
- }
106
- const groups = suggestCnGroups(inlineClasses);
107
- const result = opts.tv
108
- ? formatArray(groups)
109
- : formatCnCall(groups, { trailingClassName: !!opts.withClassName });
110
- process.stdout.write(`${result}\n`);
111
- const bucketSummary = summarizeGroupBucketLabels(groups);
112
- process.stdout.write(`\n// Buckets: ${JSON.stringify(bucketSummary)}\n`);
113
- });
114
- }
@@ -1,4 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function packageArgToRelative(rootDir: string, arg: string | undefined): string | undefined;
3
- export declare function registerMirrorCommand(program: Command): void;
4
- //# sourceMappingURL=mirror.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mirror.d.ts","sourceRoot":"","sources":["../../src/commands/mirror.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAiBjG;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmC5D"}
@@ -1,63 +0,0 @@
1
- import { realpathSync } from "node:fs";
2
- import path from "node:path";
3
- import process from "node:process";
4
- import { createNodeCliFs } from "#lib/infra/node-io";
5
- import { normalizePath, runMirrorSync } from "#lib/mirror";
6
- import { findRepoRoot } from "#lib/repo-root";
7
- function tryRealpath(entryPath) {
8
- try {
9
- return realpathSync.native(entryPath);
10
- }
11
- catch {
12
- return path.resolve(entryPath);
13
- }
14
- }
15
- export function packageArgToRelative(rootDir, arg) {
16
- if (!arg)
17
- return undefined;
18
- const rootReal = tryRealpath(path.resolve(rootDir));
19
- const cwdReal = tryRealpath(process.cwd());
20
- const resolved = path.isAbsolute(arg) ? path.resolve(arg) : path.resolve(cwdReal, arg);
21
- const targetReal = tryRealpath(resolved);
22
- const rel = path.relative(rootReal, targetReal);
23
- const normalized = normalizePath(rel);
24
- if (normalized.startsWith("..") ||
25
- path.isAbsolute(normalized) ||
26
- normalized === "" ||
27
- normalized === ".") {
28
- throw new Error(`Package path must be a subdirectory under monorepo root: ${rootDir}`);
29
- }
30
- return normalized;
31
- }
32
- export function registerMirrorCommand(program) {
33
- const mirror = program
34
- .command("mirror")
35
- .description("Keep package manifests aligned with what you ship");
36
- mirror
37
- .command("sync")
38
- .description("Write package.json exports from dist/ for workspace packages")
39
- .argument("[package]", "Optional package path relative to repo root (e.g. packages/ui)")
40
- .option("-v, --verbose", "Print extra diagnostics", false)
41
- .action(async function (pkg, options) {
42
- const globals = this.optsWithGlobals();
43
- const fs = createNodeCliFs();
44
- const rootDir = findRepoRoot(fs);
45
- let packageFilter;
46
- try {
47
- packageFilter = packageArgToRelative(rootDir, pkg);
48
- }
49
- catch (e) {
50
- this.error(e instanceof Error ? e.message : String(e));
51
- return;
52
- }
53
- const exitCode = await runMirrorSync({
54
- rootDir,
55
- verbose: options.verbose,
56
- /** Commander sets `color: false` when `--no-color` is passed (default `color: true`). */
57
- noColor: globals.color === false,
58
- packageFilter,
59
- fs,
60
- });
61
- process.exitCode = exitCode;
62
- });
63
- }
@@ -1,4 +0,0 @@
1
- import type { CliFs } from "#lib/infra/fs-contract";
2
- import type { AnalyzeReport } from "#lib/arrange/types";
3
- export declare function analyzeDirectory(target: string, fs: CliFs): AnalyzeReport;
4
- //# sourceMappingURL=analyze.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../src/lib/arrange/analyze.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA8FxD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,GAAG,aAAa,CAsCzE"}
@@ -1,105 +0,0 @@
1
- import ts from "typescript";
2
- import { LONG_STRING_TOKEN_THRESHOLD } from "#lib/arrange/constants";
3
- import { tokenizeClassString } from "#lib/arrange/tokenizer";
4
- import { forEachStringLiteralInClassExpression } from "#lib/arrange/ast/collectors-cn";
5
- import { jsxClassNameStaticLiteral } from "#lib/arrange/ast/collectors-jsx";
6
- import { collectCnCallsInsideTv, traverseTvObject } from "#lib/arrange/ast/collectors-tv";
7
- import { buildKnownCnTvBindings, isCnOrTvIdentifier, lineOf } from "#lib/arrange/ast/utils";
8
- import { walkTsxFiles } from "#lib/arrange/walk";
9
- function analyzeCnCall(sf, call, report) {
10
- for (const arg of call.arguments) {
11
- forEachStringLiteralInClassExpression(arg, (lit) => {
12
- const text = lit.text;
13
- const tokenCount = tokenizeClassString(text).length;
14
- if (tokenCount >= LONG_STRING_TOKEN_THRESHOLD) {
15
- report.longCnStringLiterals.push({
16
- file: sf.fileName,
17
- line: lineOf(sf, lit),
18
- tokenCount,
19
- preview: text.length > 72 ? `${text.slice(0, 72)}…` : text,
20
- });
21
- }
22
- });
23
- }
24
- }
25
- function visitCallExpressionForArrangeAnalyze(node, sf, sourceText, knownBindings, report) {
26
- if (isCnOrTvIdentifier(node.expression, "cn", knownBindings)) {
27
- report.cnCallExpressions++;
28
- analyzeCnCall(sf, node, report);
29
- return;
30
- }
31
- if (!isCnOrTvIdentifier(node.expression, "tv", knownBindings))
32
- return;
33
- report.tvCallExpressions++;
34
- const arg0 = node.arguments[0];
35
- if (!arg0 || !ts.isObjectLiteralExpression(arg0))
36
- return;
37
- for (const nestedCn of collectCnCallsInsideTv(sf, arg0, knownBindings, 0)) {
38
- const src = sourceText.slice(nestedCn.getStart(sf), nestedCn.getEnd());
39
- const preview = src.length > 72 ? `${src.slice(0, 72)}…` : src;
40
- report.cnInsideTvCalls.push({
41
- file: sf.fileName,
42
- line: lineOf(sf, nestedCn),
43
- argCount: nestedCn.arguments.length,
44
- preview,
45
- });
46
- }
47
- traverseTvObject(sf, arg0, (strNode) => {
48
- const text = strNode.text;
49
- const tokenCount = tokenizeClassString(text).length;
50
- if (tokenCount >= LONG_STRING_TOKEN_THRESHOLD) {
51
- report.longTvStringLiterals.push({
52
- file: sf.fileName,
53
- line: lineOf(sf, strNode),
54
- tokenCount,
55
- preview: text.length > 72 ? `${text.slice(0, 72)}…` : text,
56
- });
57
- }
58
- }, 0, knownBindings);
59
- }
60
- function visitJsxAttributeForArrangeAnalyze(node, sf, filePath, report) {
61
- if (!filePath.endsWith(".tsx"))
62
- return;
63
- const parsed = jsxClassNameStaticLiteral(node);
64
- if (!parsed)
65
- return;
66
- const text = parsed.lit.text;
67
- const tokenCount = tokenizeClassString(text).length;
68
- if (tokenCount >= LONG_STRING_TOKEN_THRESHOLD) {
69
- report.longJsxClassNameLiterals.push({
70
- file: sf.fileName,
71
- line: lineOf(sf, parsed.lit),
72
- tokenCount,
73
- preview: text.length > 72 ? `${text.slice(0, 72)}…` : text,
74
- });
75
- }
76
- }
77
- export function analyzeDirectory(target, fs) {
78
- const report = {
79
- files: 0,
80
- cnCallExpressions: 0,
81
- tvCallExpressions: 0,
82
- cnInsideTvCalls: [],
83
- longCnStringLiterals: [],
84
- longTvStringLiterals: [],
85
- longJsxClassNameLiterals: [],
86
- };
87
- const files = fs.statSync(target).isDirectory() ? walkTsxFiles(target, fs) : [target];
88
- for (const filePath of files) {
89
- const sourceText = fs.readFileSync(filePath, "utf8");
90
- const sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
91
- report.files++;
92
- const knownBindings = buildKnownCnTvBindings(sf);
93
- const visit = (node) => {
94
- if (ts.isCallExpression(node)) {
95
- visitCallExpressionForArrangeAnalyze(node, sf, sourceText, knownBindings, report);
96
- }
97
- if (ts.isJsxAttribute(node)) {
98
- visitJsxAttributeForArrangeAnalyze(node, sf, filePath, report);
99
- }
100
- ts.forEachChild(node, visit);
101
- };
102
- visit(sf);
103
- }
104
- return report;
105
- }