@codefast/cli 0.3.16-canary.3 → 0.4.0-canary.4

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 (40) hide show
  1. package/README.md +69 -62
  2. package/dist/arrange/analyze.mjs +2 -2
  3. package/dist/arrange/command.mjs +51 -27
  4. package/dist/arrange/domain/analyze-service.mjs +2 -2
  5. package/dist/arrange/domain/ast/collectors-cn.mjs +1 -1
  6. package/dist/arrange/domain/ast/collectors-tv.mjs +2 -2
  7. package/dist/arrange/domain/ast/helpers.mjs +2 -2
  8. package/dist/arrange/domain/ast/simplify-targets.mjs +109 -0
  9. package/dist/arrange/domain/ast/targets.mjs +25 -10
  10. package/dist/arrange/domain/grouping-service.mjs +1 -1
  11. package/dist/arrange/domain/grouping.mjs +5 -5
  12. package/dist/arrange/domain/imports.mjs +50 -2
  13. package/dist/arrange/domain/token-classifier.mjs +12 -12
  14. package/dist/arrange/output.mjs +11 -3
  15. package/dist/arrange/process-file.mjs +1 -1
  16. package/dist/arrange/scan-target.mjs +10 -1
  17. package/dist/arrange/simplify-process-file.mjs +35 -0
  18. package/dist/arrange/simplify-sync.mjs +32 -0
  19. package/dist/arrange/suggest.mjs +1 -1
  20. package/dist/arrange/sync.mjs +1 -1
  21. package/dist/arrange/workspace.mjs +1 -1
  22. package/dist/cli.mjs +1 -1
  23. package/dist/core/cli/result-handle.mjs +1 -1
  24. package/dist/core/source-text-edit.mjs +1 -1
  25. package/dist/core/workspace/resolver.mjs +3 -3
  26. package/dist/mirror/cli-result.mjs +2 -1
  27. package/dist/mirror/cli-schema.mjs +2 -1
  28. package/dist/mirror/command.mjs +12 -11
  29. package/dist/mirror/domain/exports.mjs +2 -2
  30. package/dist/mirror/output.mjs +3 -0
  31. package/dist/mirror/supplement-exports.mjs +2 -2
  32. package/dist/mirror/sync-reporter.mjs +4 -1
  33. package/dist/mirror/sync-workspace-package.mjs +4 -4
  34. package/dist/mirror/sync.mjs +2 -1
  35. package/dist/mirror/write-exports.mjs +3 -2
  36. package/dist/tag/command.mjs +6 -6
  37. package/dist/tag/output.mjs +1 -1
  38. package/dist/tag/sync.mjs +1 -1
  39. package/dist/tag/target-runner.mjs +1 -1
  40. package/package.json +9 -9
@@ -72,7 +72,7 @@ function compareClassifiedTailwindTokensForCnGrouping(left, right, firstVariantK
72
72
  function areCnTailwindPartitionsEquivalent(staticLiteralTexts, suggestedGroups) {
73
73
  const partitionSignatures = (chunks) => chunks.map((chunk) => {
74
74
  const classTokens = tokenizeClassString(chunk);
75
- return classTokens.length === 0 ? "" : [...classTokens].sort(compareClassTokensCanonically).join(" ");
75
+ return classTokens.length === 0 ? "" : [...classTokens].toSorted(compareClassTokensCanonically).join(" ");
76
76
  }).filter((s) => s.length > 0).sort(compareClassTokensCanonically);
77
77
  const staticPartitionSigs = partitionSignatures(staticLiteralTexts);
78
78
  const suggestedPartitionSigs = partitionSignatures(suggestedGroups);
@@ -230,7 +230,7 @@ function mergeEaseTimingIntoFollowingAnimatedState(groups) {
230
230
  const chunkJ = result[j];
231
231
  if (chunkJ === void 0) throw new Error("invariant: mergeEase following chunk missing");
232
232
  if (dominantBucketOfGroup(chunkJ) !== "state") continue;
233
- if (!tokenizeClassString(chunkJ).some((classToken) => /^animate/.test(stripVariants(classToken)))) continue;
233
+ if (!tokenizeClassString(chunkJ).some((classToken) => stripVariants(classToken).startsWith("animate"))) continue;
234
234
  target = j;
235
235
  break;
236
236
  }
@@ -248,7 +248,7 @@ function mergeEaseTimingIntoFollowingAnimatedState(groups) {
248
248
  function chunkIsOnlyEaseTimingMotion(groupStr) {
249
249
  const classTokens = tokenizeClassString(groupStr);
250
250
  if (classTokens.length === 0) return false;
251
- return classTokens.every((classToken) => classifyToken(classToken) === "motion" && /^ease-/.test(stripVariants(classToken)));
251
+ return classTokens.every((classToken) => classifyToken(classToken) === "motion" && stripVariants(classToken).startsWith("ease-"));
252
252
  }
253
253
  /**
254
254
  * @since 0.3.16-canary.0
@@ -328,9 +328,9 @@ function suggestCnGroups(classString) {
328
328
  function summarizeGroupBucketLabels(groups) {
329
329
  return groups.map((g) => {
330
330
  const uniq = new Set(tokenizeClassString(g).map(classifyToken));
331
- if (uniq.size !== 1) return `mixed:${[...uniq].sort(compareClassTokensCanonically).join("+")}`;
331
+ if (uniq.size !== 1) return `mixed:${[...uniq].toSorted(compareClassTokensCanonically).join("+")}`;
332
332
  const onlyBucket = [...uniq][0];
333
- if (onlyBucket === void 0) return `mixed:${[...uniq].sort(compareClassTokensCanonically).join("+")}`;
333
+ if (onlyBucket === void 0) return `mixed:${[...uniq].toSorted(compareClassTokensCanonically).join("+")}`;
334
334
  return onlyBucket;
335
335
  });
336
336
  }
@@ -1,4 +1,5 @@
1
- import { isDomainImportDeclaration, isDomainNamedImports, isDomainStringLiteral } from "./ast/ast-node.mjs";
1
+ import { forEachDomainChild, isDomainIdentifier, isDomainImportDeclaration, isDomainNamedImports, isDomainStringLiteral } from "./ast/ast-node.mjs";
2
+ import { endAfterOptionalCommaFollowingInSource } from "../../core/source-text-edit.mjs";
2
3
  //#region src/arrange/domain/imports.ts
3
4
  function sourceFileImportsCn(sourceFile) {
4
5
  for (const statement of sourceFile.statements) {
@@ -25,6 +26,53 @@ function findImportDeclarationFromModule(sourceFile, moduleSpecifier) {
25
26
  if (isDomainStringLiteral(spec) && spec.text === moduleSpecifier) return statement;
26
27
  }
27
28
  }
29
+ function cnIsUsedInBody(node) {
30
+ if (isDomainIdentifier(node) && node.text === "cn") return true;
31
+ let found = false;
32
+ forEachDomainChild(node, (child) => {
33
+ if (!found) found = cnIsUsedInBody(child);
34
+ });
35
+ return found;
36
+ }
37
+ function removeCnSpecifier(sourceText, specifier, allSpecifiers) {
38
+ const idx = allSpecifiers.indexOf(specifier);
39
+ if (idx === 0) {
40
+ const next = allSpecifiers[1];
41
+ if (!next) return sourceText;
42
+ return sourceText.slice(0, specifier.pos) + sourceText.slice(next.pos);
43
+ }
44
+ const prev = allSpecifiers[idx - 1];
45
+ if (!prev) return sourceText;
46
+ const endWithTrailingComma = endAfterOptionalCommaFollowingInSource(sourceText, specifier.end);
47
+ return sourceText.slice(0, prev.end) + sourceText.slice(endWithTrailingComma);
48
+ }
49
+ /**
50
+ * After simplify rewrites, drop the `cn` import specifier when `cn` is no
51
+ * longer referenced anywhere outside the import declarations.
52
+ *
53
+ * @since 0.3.16-canary.0
54
+ */
55
+ function dropCnImportIfUnused(sourceFile) {
56
+ const sourceText = sourceFile.text;
57
+ for (const stmt of sourceFile.statements) {
58
+ if (isDomainImportDeclaration(stmt)) continue;
59
+ if (cnIsUsedInBody(stmt)) return sourceText;
60
+ }
61
+ for (const stmt of sourceFile.statements) {
62
+ if (!isDomainImportDeclaration(stmt) || !stmt.importClause) continue;
63
+ const clause = stmt.importClause;
64
+ if (!clause.namedBindings || !isDomainNamedImports(clause.namedBindings)) continue;
65
+ const elements = clause.namedBindings.elements;
66
+ const cnSpecifier = elements.find((el) => el.name.text === "cn");
67
+ if (!cnSpecifier) continue;
68
+ if (elements.length === 1) {
69
+ const lineEnd = sourceText.indexOf("\n", stmt.end);
70
+ return sourceText.slice(0, stmt.pos) + (lineEnd !== -1 ? sourceText.slice(lineEnd + 1) : sourceText.slice(stmt.end));
71
+ }
72
+ return removeCnSpecifier(sourceText, cnSpecifier, elements);
73
+ }
74
+ return sourceText;
75
+ }
28
76
  /**
29
77
  * @since 0.3.16-canary.0
30
78
  */
@@ -56,4 +104,4 @@ function ensureCnImport(sourceFile, cnImportOverride) {
56
104
  return `${sourceText.slice(0, insertAt)}${importLine}\n${sourceText.slice(insertAt)}`;
57
105
  }
58
106
  //#endregion
59
- export { ensureCnImport };
107
+ export { dropCnImportIfUnused, ensureCnImport };
@@ -50,9 +50,9 @@ function isStateToken(token) {
50
50
  if (prefix.startsWith("supports-[")) return true;
51
51
  if (prefix.startsWith("in-data-")) return true;
52
52
  if (/^data-(?!\[)/.test(prefix)) return true;
53
- if (/^aria-/.test(prefix)) return true;
53
+ if (prefix.startsWith("aria-")) return true;
54
54
  if (/^(group|peer)-/.test(prefix)) return true;
55
- if (/^not-/.test(prefix)) return true;
55
+ if (prefix.startsWith("not-")) return true;
56
56
  if (isCompoundOrMediaVariantPrefix(prefix)) return true;
57
57
  if (prefix.startsWith("[") && prefix.endsWith("]")) return true;
58
58
  return false;
@@ -65,13 +65,13 @@ function isStateToken(token) {
65
65
  */
66
66
  function compositeSecondaryOrder(bareUtility) {
67
67
  const b = bareUtility;
68
- if (/^opacity(?:-|$)/.test(b) || /^mix-blend-/.test(b) || /^isolation(?:-|$)/.test(b) || b === "isolate") return 0;
68
+ if (/^opacity(?:-|$)/.test(b) || b.startsWith("mix-blend-") || /^isolation(?:-|$)/.test(b) || b === "isolate") return 0;
69
69
  if (b === "transform-3d" || /^perspective(?:-|$)/.test(b)) return 10;
70
70
  if (/^rotate-[xyz](?:-|$)/.test(b) || /^translate-z(?:-|$)/.test(b) || /^scale-z(?:-|$)/.test(b)) return 20;
71
71
  if (/^translate(?:-|$)/.test(b) || /^scale(?:-|$)/.test(b) || /^rotate(?:-|$)/.test(b) || /^skew(?:-|$)/.test(b)) return 30;
72
72
  if (/^transform(?:-(?:gpu|cpu|none))?$/.test(b)) return 35;
73
- if (/^blur(?:-|$)/.test(b) || /^backdrop-/.test(b) || /^filter(?:-|$)/.test(b) || /^brightness-/.test(b) || /^contrast-/.test(b) || /^grayscale(?:-|$)/.test(b) || /^hue-rotate-/.test(b) || /^invert(?:-|$)/.test(b) || /^saturate-/.test(b) || /^sepia(?:-|$)/.test(b)) return 40;
74
- if (/^will-change/.test(b)) return 50;
73
+ if (/^blur(?:-|$)/.test(b) || b.startsWith("backdrop-") || /^filter(?:-|$)/.test(b) || b.startsWith("brightness-") || b.startsWith("contrast-") || /^grayscale(?:-|$)/.test(b) || b.startsWith("hue-rotate-") || /^invert(?:-|$)/.test(b) || b.startsWith("saturate-") || /^sepia(?:-|$)/.test(b)) return 40;
74
+ if (b.startsWith("will-change")) return 50;
75
75
  return 99;
76
76
  }
77
77
  /**
@@ -82,7 +82,7 @@ function classifyBareUtility(bareUtility) {
82
82
  if (/^@container(?:\/[a-z][a-z0-9-]*)?$/i.test(b)) return "existence";
83
83
  if (/^(?:hidden|contents|sr-only|not-sr-only|list-item|flow-root)$/.test(b) || /^(?:block|inline-block|inline)$/.test(b)) return "existence";
84
84
  if (/^table(?:$|-)/.test(b)) return "existence";
85
- if (/^contain-/.test(b)) return "existence";
85
+ if (b.startsWith("contain-")) return "existence";
86
86
  if (/^(?:group|peer)(?:\/[a-z][a-z0-9-]*)?$/.test(b)) return "existence";
87
87
  if (/^(?:static|relative|absolute|fixed|sticky)$/.test(b)) return "position";
88
88
  if (/^-?(?:inset|top|right|bottom|left|start|end)(?:-|\/|$)/.test(b) || /^-?(?:inset-x|inset-y|inset-s|inset-e)-/.test(b)) return "position";
@@ -90,13 +90,13 @@ function classifyBareUtility(bareUtility) {
90
90
  if (/^(?:flex|inline-flex|grid|inline-grid|subgrid|masonry)(?:$|-)/.test(b) || /^(?:items|justify|justify-items|justify-self|content|self|place|place-content|place-items|place-self)-/.test(b) || /^-?(?:gap|space-[xy]|col-|row-|grid-|auto-cols|auto-rows|order-|order$)/.test(b) || /^-?(?:auto-flow|grid-flow)-/.test(b) || /^(?:columns-|break-after|break-before|break-inside)-/.test(b) || /^(?:float|clear)-/.test(b) || /^(?:wrap-|flex-wrap|flex-nowrap|flex-row|flex-col|flex-auto|flex-initial|flex-none|flex-1|flex-\[)/.test(b) || /^container$/.test(b)) return "layout";
91
91
  if (/^-?(?:w|h|min-w|max-w|min-h|max-h|size|aspect|shrink|grow|basis)-/.test(b) || /^(?:shrink|grow)(?:$|-)/.test(b) || /^-?overflow-/.test(b) || /^-?object-/.test(b)) return "sizing";
92
92
  if (/^-?(?:p|px|py|pt|pb|pl|pr|ps|pe|m|mx|my|mt|mb|ml|mr|ms|me)-/.test(b)) return "spacing";
93
- if (/^-?(?:rounded|border|ring|divide|inset-ring)(?:-|\/|$)/.test(b) || /^(?:rounded|border|ring|divide|inset-ring)$/.test(b) || /^ring-offset-/.test(b)) return "shape";
93
+ if (/^-?(?:rounded|border|ring|divide|inset-ring)(?:-|\/|$)/.test(b) || /^(?:rounded|border|ring|divide|inset-ring)$/.test(b) || b.startsWith("ring-offset-")) return "shape";
94
94
  if (/^-?bg-/.test(b) || /^(?:from|via|to)(?:-|\/|$)/.test(b) || /^bg-(?:linear|radial|conic)-/.test(b) || /^-?mask-/.test(b) || /^-?(?:fill|stroke)-/.test(b)) return "background";
95
95
  if (/^-?(?:shadow|inset-shadow|drop-shadow|text-shadow)(?:-|\/|$)/.test(b) || /^(?:shadow|inset-shadow)$/.test(b) || /^-?outline(?:-|\/|$)/.test(b) || /^outline$/.test(b)) return "shadow";
96
- if (/^-?(?:font|leading|tracking|list|indent|align|decoration|underline-offset|text-wrap|text-balance|text-pretty)-/.test(b) || /^-?(?:text-(?!shadow))/.test(b) || /^-?(?:whitespace|break|line-clamp|hyphens)-/.test(b) || /^(?:antialiased|subpixel-antialiased|italic|not-italic|overline|line-through|underline|no-underline|uppercase|lowercase|capitalize|normal-case|truncate|text-wrap|text-balance|text-pretty)$/.test(b) || /^text-wrap-/.test(b) || /^tabular-nums$|^slashed-zero$|^lining-nums$|^oldstyle-nums$|^proportional-nums$/.test(b)) return "typography";
97
- if (/^opacity(?:-|$)/.test(b) || /^mix-blend-/.test(b) || /^isolation(?:-|$)/.test(b) || b === "isolate" || /^transform(?:-(?:gpu|cpu|none))?$/.test(b) || /^-?(?:translate|scale|rotate|skew)-/.test(b) || /^perspective(?:-|$)/.test(b) || b === "transform-3d" || /^blur(?:-|$)/.test(b) || /^backdrop-/.test(b) || /^filter(?:-|$)/.test(b) || /^brightness-/.test(b) || /^contrast-/.test(b) || /^grayscale(?:-|$)/.test(b) || /^hue-rotate-/.test(b) || /^invert(?:-|$)/.test(b) || /^saturate-/.test(b) || /^sepia(?:-|$)/.test(b) || /^will-change/.test(b)) return "composite";
98
- if (/^(?:transition|duration|ease|delay|animate)(?:-|$)/.test(b) || /^ease-/.test(b)) return "motion";
99
- if (/^(?:cursor|pointer-events|select|touch|scroll|scroll-m|scroll-p|overscroll|snap|resize|field-sizing|appearance|scheme|color-scheme|accent|caret)(?:-|$)/.test(b) || /^(?:cursor|select|resize)$/.test(b) || /^scroll-behavior/.test(b) || /^scroll-snap/.test(b) || /^touch-action/.test(b) || b === "inert") return "behavior";
96
+ if (/^-?(?:font|leading|tracking|list|indent|align|decoration|underline-offset|text-wrap|text-balance|text-pretty)-/.test(b) || /^-?(?:text-(?!shadow))/.test(b) || /^-?(?:whitespace|break|line-clamp|hyphens)-/.test(b) || /^(?:antialiased|subpixel-antialiased|italic|not-italic|overline|line-through|underline|no-underline|uppercase|lowercase|capitalize|normal-case|truncate|text-wrap|text-balance|text-pretty)$/.test(b) || b.startsWith("text-wrap-") || /^tabular-nums$|^slashed-zero$|^lining-nums$|^oldstyle-nums$|^proportional-nums$/.test(b)) return "typography";
97
+ if (/^opacity(?:-|$)/.test(b) || b.startsWith("mix-blend-") || /^isolation(?:-|$)/.test(b) || b === "isolate" || /^transform(?:-(?:gpu|cpu|none))?$/.test(b) || /^-?(?:translate|scale|rotate|skew)-/.test(b) || /^perspective(?:-|$)/.test(b) || b === "transform-3d" || /^blur(?:-|$)/.test(b) || b.startsWith("backdrop-") || /^filter(?:-|$)/.test(b) || b.startsWith("brightness-") || b.startsWith("contrast-") || /^grayscale(?:-|$)/.test(b) || b.startsWith("hue-rotate-") || /^invert(?:-|$)/.test(b) || b.startsWith("saturate-") || /^sepia(?:-|$)/.test(b) || b.startsWith("will-change")) return "composite";
98
+ if (/^(?:transition|duration|ease|delay|animate)(?:-|$)/.test(b) || b.startsWith("ease-")) return "motion";
99
+ if (/^(?:cursor|pointer-events|select|touch|scroll|scroll-m|scroll-p|overscroll|snap|resize|field-sizing|appearance|scheme|color-scheme|accent|caret)(?:-|$)/.test(b) || /^(?:cursor|select|resize)$/.test(b) || b.startsWith("scroll-behavior") || b.startsWith("scroll-snap") || b.startsWith("touch-action") || b === "inert") return "behavior";
100
100
  return "other";
101
101
  }
102
102
  /**
@@ -106,7 +106,7 @@ function classifyBareUtility(bareUtility) {
106
106
  * Pure arbitrary **properties** (`[--x]:`, `[color:red]`) have no `&` in the leading `[…]` segment.
107
107
  */
108
108
  function isArbitraryParentSelectorStateToken(token) {
109
- if (/^\[&/.test(token)) return true;
109
+ if (token.startsWith("[&")) return true;
110
110
  if (!token.startsWith("[")) return false;
111
111
  const colonIdx = indexOfFirstVariantColon(token);
112
112
  if (colonIdx === -1 || colonIdx === 0 || token[colonIdx - 1] !== "]") return false;
@@ -1,6 +1,6 @@
1
- import { logger } from "../core/logger.mjs";
2
1
  import "./domain/constants.mjs";
3
2
  import { lineOf } from "./domain/ast/helpers.mjs";
3
+ import { logger } from "../core/logger.mjs";
4
4
  import path from "node:path";
5
5
  //#region src/arrange/output.ts
6
6
  /**
@@ -30,13 +30,21 @@ function printAnalyzeReport(resolvedTargetPath, report) {
30
30
  function printSyncResult(result, write) {
31
31
  logger.out(`\nTotal: ${result.filePaths.length} file(s), ${result.totalFound} site(s) (cn/tv/JSX className) to review.`);
32
32
  if (write) logger.out(`Applied: ${result.totalChanged} site(s) updated.`);
33
- else logger.out(`(Run "apply" to write changes, or "pnpm cli:arrange-apply" / "pnpm exec codefast arrange apply")`);
33
+ else logger.out(`(Re-run without --dry-run to write changes, or "pnpm cli:arrange" / "pnpm exec codefast arrange")`);
34
34
  if (write ? result.totalChanged > 0 : result.totalFound > 0) logger.out("Note: class order may change across concern groups — smoke-test the UI if you rely on cascade order.");
35
35
  if (result.hookError !== null) logger.err(result.hookError);
36
36
  }
37
37
  /**
38
38
  * @since 0.3.16-canary.0
39
39
  */
40
+ function printSimplifyResult(result, write) {
41
+ logger.out(`\nTotal: ${result.filePaths.length} file(s), ${result.totalFound} site(s) to simplify.`);
42
+ if (write) logger.out(`Applied: ${result.totalChanged} site(s) updated.`);
43
+ else logger.out(`(Run "simplify" to write changes, or "pnpm cli:arrange-simplify" / "pnpm exec codefast arrange simplify")`);
44
+ }
45
+ /**
46
+ * @since 0.3.16-canary.0
47
+ */
40
48
  function printGroupFilePreviewFromWork(work) {
41
49
  printGroupFilePreviewBody({
42
50
  filePath: work.filePath,
@@ -76,4 +84,4 @@ function printGroupFilePreviewBody(args) {
76
84
  }
77
85
  }
78
86
  //#endregion
79
- export { printAnalyzeReport, printGroupFilePreviewFromWork, printSyncResult };
87
+ export { printAnalyzeReport, printGroupFilePreviewFromWork, printSimplifyResult, printSyncResult };
@@ -1,6 +1,6 @@
1
1
  import { parseDomainSourceFile } from "./source-parse.mjs";
2
- import { buildGroupFileUnwrapState, countPersistedGroupFileEdits, groupFileDryRunNoEdits, groupFileEditsTouchJsxCn, groupFilePreviewTotals, groupFileWorkHasNothingToReport, mergeGroupFileBodyText, tryBuildGroupFileWorkPlan } from "./domain/grouping-service.mjs";
3
2
  import { ensureCnImport } from "./domain/imports.mjs";
3
+ import { buildGroupFileUnwrapState, countPersistedGroupFileEdits, groupFileDryRunNoEdits, groupFileEditsTouchJsxCn, groupFilePreviewTotals, groupFileWorkHasNothingToReport, mergeGroupFileBodyText, tryBuildGroupFileWorkPlan } from "./domain/grouping-service.mjs";
4
4
  //#region src/arrange/process-file.ts
5
5
  /**
6
6
  * @since 0.3.16-canary.0
@@ -3,13 +3,22 @@ import { logger } from "../core/logger.mjs";
3
3
  import { walkTsxFiles } from "../core/workspace/typescript-walk.mjs";
4
4
  import path from "node:path";
5
5
  //#region src/arrange/scan-target.ts
6
+ const TEST_FILE_PATTERN = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
7
+ /**
8
+ * Test/spec files are excluded from directory scans: arrange rewrites class strings,
9
+ * and a `cn(...)` inside an assertion (e.g. `expect(cn("a", "b"))`) is intentional test
10
+ * data, not styling to reformat. An explicit single-file target is still honored.
11
+ */
12
+ function isTestFile(filePath) {
13
+ return TEST_FILE_PATTERN.test(path.basename(filePath));
14
+ }
6
15
  /**
7
16
  * @since 0.3.16-canary.0
8
17
  */
9
18
  function scanArrangeTargets(fs, targetPath) {
10
19
  const resolvedTargetPath = path.resolve(targetPath);
11
20
  if (fs.statSync(resolvedTargetPath).isDirectory()) try {
12
- return walkTsxFiles(resolvedTargetPath, fs);
21
+ return walkTsxFiles(resolvedTargetPath, fs).filter((filePath) => !isTestFile(filePath));
13
22
  } catch (caughtError) {
14
23
  logger.err(`[file-walker] ${messageFrom(caughtError)}`);
15
24
  throw caughtError;
@@ -0,0 +1,35 @@
1
+ import { applyEditsDescending } from "../core/source-text-edit.mjs";
2
+ import { parseDomainSourceFile } from "./source-parse.mjs";
3
+ import { collectSimplifyTargets } from "./domain/ast/simplify-targets.mjs";
4
+ import { dropCnImportIfUnused } from "./domain/imports.mjs";
5
+ //#region src/arrange/simplify-process-file.ts
6
+ /**
7
+ * @since 0.3.16-canary.0
8
+ */
9
+ function processArrangeSimplifyFile(fs, args) {
10
+ const { filePath, write } = args;
11
+ const sourceText = fs.readFileSync(filePath, "utf8");
12
+ const meaningful = collectSimplifyTargets(parseDomainSourceFile(filePath, sourceText)).filter((edit) => sourceText.slice(edit.start, edit.end) !== edit.replacement);
13
+ const textAfterEdits = meaningful.length > 0 ? applyEditsDescending(sourceText, meaningful) : sourceText;
14
+ const textAfterImportDrop = dropCnImportIfUnused(parseDomainSourceFile(filePath, textAfterEdits));
15
+ const importDropped = textAfterImportDrop !== textAfterEdits;
16
+ const totalFound = meaningful.length + (importDropped ? 1 : 0);
17
+ if (totalFound === 0) return {
18
+ filePath,
19
+ totalFound: 0,
20
+ changed: 0
21
+ };
22
+ if (!write) return {
23
+ filePath,
24
+ totalFound,
25
+ changed: 0
26
+ };
27
+ fs.writeFileSync(filePath, textAfterImportDrop, "utf8");
28
+ return {
29
+ filePath,
30
+ totalFound,
31
+ changed: totalFound
32
+ };
33
+ }
34
+ //#endregion
35
+ export { processArrangeSimplifyFile };
@@ -0,0 +1,32 @@
1
+ import { scanArrangeTargets } from "./scan-target.mjs";
2
+ import { ok } from "../core/result.mjs";
3
+ import { processArrangeSimplifyFile } from "./simplify-process-file.mjs";
4
+ //#region src/arrange/simplify-sync.ts
5
+ /**
6
+ * @since 0.3.16-canary.0
7
+ */
8
+ async function runArrangeSimplify(fs, args) {
9
+ const filePaths = scanArrangeTargets(fs, args.targetPath);
10
+ const modifiedFiles = [];
11
+ let totalFound = 0;
12
+ let totalChanged = 0;
13
+ for (const filePath of filePaths) {
14
+ const result = processArrangeSimplifyFile(fs, {
15
+ filePath,
16
+ write: args.write
17
+ });
18
+ totalFound += result.totalFound;
19
+ totalChanged += result.changed;
20
+ if (result.changed > 0) modifiedFiles.push(result.filePath);
21
+ }
22
+ return ok({
23
+ filePaths,
24
+ modifiedFiles,
25
+ totalFound,
26
+ totalChanged,
27
+ hookError: null,
28
+ previewPlans: []
29
+ });
30
+ }
31
+ //#endregion
32
+ export { runArrangeSimplify };
@@ -1,5 +1,5 @@
1
- import { suggestCnGroups, summarizeGroupBucketLabels } from "./domain/grouping.mjs";
2
1
  import { formatArray, formatCnCall } from "./domain/source-text-formatters.mjs";
2
+ import { suggestCnGroups, summarizeGroupBucketLabels } from "./domain/grouping.mjs";
3
3
  //#region src/arrange/suggest.ts
4
4
  /**
5
5
  * @since 0.3.16-canary.0
@@ -1,6 +1,6 @@
1
1
  import { messageFrom } from "../core/errors.mjs";
2
- import { ok } from "../core/result.mjs";
3
2
  import { scanArrangeTargets } from "./scan-target.mjs";
3
+ import { ok } from "../core/result.mjs";
4
4
  import { processArrangeGroupFile } from "./process-file.mjs";
5
5
  //#region src/arrange/sync.ts
6
6
  async function runOnAfterWriteHook(hook, modifiedFiles) {
@@ -1,8 +1,8 @@
1
1
  import { AppError, messageFrom } from "../core/errors.mjs";
2
2
  import { err, ok } from "../core/result.mjs";
3
+ import { resolveArrangeTargetPath } from "./resolve-target.mjs";
3
4
  import { loadCodefastConfig } from "../core/config.mjs";
4
5
  import { findRepoRoot } from "../core/workspace/resolver.mjs";
5
- import { resolveArrangeTargetPath } from "./resolve-target.mjs";
6
6
  //#region src/arrange/workspace.ts
7
7
  /**
8
8
  * @since 0.3.16-canary.0
package/dist/cli.mjs CHANGED
@@ -22,7 +22,7 @@ function readVersion() {
22
22
  */
23
23
  async function runCli(argv) {
24
24
  const program = new Command();
25
- 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)");
25
+ program.name("codefast").description("Codefast monorepo developer CLI").version(readVersion()).option("--no-color", "Disable ANSI color output").enablePositionalOptions().configureHelp({ sortSubcommands: true }).showHelpAfterError("(use --help for usage)");
26
26
  program.addCommand(createArrangeCommand());
27
27
  program.addCommand(createMirrorCommand());
28
28
  program.addCommand(createTagCommand());
@@ -1,6 +1,6 @@
1
+ import { logger } from "../logger.mjs";
1
2
  import { formatAppError } from "./format-error.mjs";
2
3
  import "../exit-codes.mjs";
3
- import { logger } from "../logger.mjs";
4
4
  import { isVerboseCliDiagnostics } from "../verbose-diagnostics.mjs";
5
5
  import process from "node:process";
6
6
  //#region src/core/cli/result-handle.ts
@@ -47,7 +47,7 @@ function endAfterOptionalCommaFollowingInSource(source, tokenEnd) {
47
47
  * @since 0.3.16-canary.0
48
48
  */
49
49
  function applyEditsDescending(sourceText, edits) {
50
- const sorted = [...edits].sort((editA, editB) => editB.start - editA.start);
50
+ const sorted = [...edits].toSorted((editA, editB) => editB.start - editA.start);
51
51
  let out = sourceText;
52
52
  for (const edit of sorted) out = out.slice(0, edit.start) + edit.replacement + out.slice(edit.end);
53
53
  return out;
@@ -92,13 +92,13 @@ async function readWorkspaceYaml(rootDir, fs) {
92
92
  try {
93
93
  raw = await fs.readFile(workspaceYamlPath, "utf8");
94
94
  } catch (caughtReadError) {
95
- throw new Error(`Failed to read ${wf}: ${messageFrom(caughtReadError)}`);
95
+ throw new Error(`Failed to read ${wf}: ${messageFrom(caughtReadError)}`, { cause: caughtReadError });
96
96
  }
97
97
  let doc;
98
98
  try {
99
99
  doc = parse(raw);
100
100
  } catch (caughtYamlParseError) {
101
- throw new Error(`Failed to parse ${wf}: ${messageFrom(caughtYamlParseError)}`);
101
+ throw new Error(`Failed to parse ${wf}: ${messageFrom(caughtYamlParseError)}`, { cause: caughtYamlParseError });
102
102
  }
103
103
  if (doc === null || doc === void 0) throw new Error(`${wf} must define a mapping at the document root`);
104
104
  return {
@@ -153,7 +153,7 @@ async function listWorkspacePackageDirectories(rootDirectoryPathAbsolute, fs, su
153
153
  if (!suppressGlobPermissionDiagnostics) logger.out(`⚠ Skipping workspace glob "${pattern}" (${messageFrom(caughtGlobError)})`);
154
154
  continue;
155
155
  }
156
- throw new Error(`Invalid workspace glob "${pattern}": ${messageFrom(caughtGlobError)}`);
156
+ throw new Error(`Invalid workspace glob "${pattern}": ${messageFrom(caughtGlobError)}`, { cause: caughtGlobError });
157
157
  }
158
158
  for (const matchedPath of matches) {
159
159
  const posix = toPosix(matchedPath);
@@ -9,10 +9,11 @@ function exitCodeForMirrorSyncResult(stats) {
9
9
  /**
10
10
  * @since 0.3.16-canary.0
11
11
  */
12
- function formatMirrorSyncJsonOutput(stats, elapsedSeconds) {
12
+ function formatMirrorSyncJsonOutput(stats, elapsedSeconds, write = true) {
13
13
  return JSON.stringify({
14
14
  schemaVersion: 1,
15
15
  ok: stats.packagesErrored === 0,
16
+ write,
16
17
  elapsedSeconds,
17
18
  stats
18
19
  });
@@ -6,7 +6,8 @@ import { z } from "zod";
6
6
  const mirrorSyncRunRequestSchema = z.object({
7
7
  rootDir: z.string().min(1),
8
8
  packageFilter: z.string().optional(),
9
- config: z.unknown().optional()
9
+ config: z.unknown().optional(),
10
+ write: z.boolean().optional()
10
11
  });
11
12
  //#endregion
12
13
  export { mirrorSyncRunRequestSchema };
@@ -1,14 +1,14 @@
1
- import { parseWithSchema } from "../core/schema-parse.mjs";
2
1
  import { logger } from "../core/logger.mjs";
2
+ import { readOptionalPositionalArg } from "../core/cli/positional.mjs";
3
3
  import { consumeCliAppError } from "../core/cli/result-handle.mjs";
4
4
  import { nodeFilesystem } from "../core/filesystem/node.mjs";
5
- import { readOptionalPositionalArg } from "../core/cli/positional.mjs";
5
+ import { parseWithSchema } from "../core/schema-parse.mjs";
6
6
  import { globalCliCommanderOptionsSchema } from "../core/cli/global-options.mjs";
7
- import { prepareMirrorSync } from "./prepare.mjs";
8
- import { runMirrorSync } from "./sync.mjs";
9
- import { mirrorSyncRunRequestSchema } from "./cli-schema.mjs";
10
7
  import { exitCodeForMirrorSyncResult, formatMirrorSyncJsonOutput } from "./cli-result.mjs";
8
+ import { mirrorSyncRunRequestSchema } from "./cli-schema.mjs";
11
9
  import { MirrorSyncProgressPresenter } from "./output.mjs";
10
+ import { prepareMirrorSync } from "./prepare.mjs";
11
+ import { runMirrorSync } from "./sync.mjs";
12
12
  import process from "node:process";
13
13
  import { Command } from "commander";
14
14
  //#region src/mirror/command.ts
@@ -16,8 +16,7 @@ import { Command } from "commander";
16
16
  * @since 0.3.16-canary.0
17
17
  */
18
18
  function createMirrorCommand() {
19
- const cmd = new Command("mirror").description("Keep package manifests aligned with what you ship");
20
- cmd.command("sync").description("Write package.json exports from dist/ for workspace packages").argument("[package]", "Optional package path relative to repo root (e.g. packages/ui)").option("-v, --verbose", "Print extra diagnostics", false).option("--json", "Print one JSON summary on stdout (suppresses human progress)", false).action(async (packageArg, opts, command) => {
19
+ return new Command("mirror").description("Write package.json exports from dist/ for workspace packages").argument("[package]", "Optional package path relative to repo root (e.g. packages/ui)").option("--dry-run", "Report what would change without writing package.json", false).option("-v, --verbose", "Print extra diagnostics", false).option("--json", "Print one JSON summary on stdout (suppresses human progress)", false).action(async (packageArg, opts, command) => {
21
20
  const globalOptionsOutcome = parseWithSchema(globalCliCommanderOptionsSchema, command.optsWithGlobals?.() ?? command.opts());
22
21
  if (!consumeCliAppError(globalOptionsOutcome)) return;
23
22
  const prelude = await prepareMirrorSync(nodeFilesystem, {
@@ -26,11 +25,13 @@ function createMirrorCommand() {
26
25
  globals: globalOptionsOutcome.value
27
26
  });
28
27
  if (!consumeCliAppError(prelude)) return;
28
+ const write = !opts.dryRun;
29
29
  const { rootDir, config, packageFilter } = prelude.value;
30
30
  const parsed = parseWithSchema(mirrorSyncRunRequestSchema, {
31
31
  rootDir,
32
32
  config: config.mirror ?? {},
33
- packageFilter
33
+ packageFilter,
34
+ write
34
35
  });
35
36
  if (!consumeCliAppError(parsed)) return;
36
37
  const json = !!opts.json;
@@ -40,7 +41,8 @@ function createMirrorCommand() {
40
41
  const listener = json ? void 0 : progressPresenter;
41
42
  if (!json) progressPresenter.configure({
42
43
  noColor,
43
- verbose
44
+ verbose,
45
+ dryRun: !write
44
46
  });
45
47
  const startTime = performance.now();
46
48
  const outcome = await runMirrorSync(nodeFilesystem, {
@@ -48,10 +50,9 @@ function createMirrorCommand() {
48
50
  listener
49
51
  });
50
52
  if (!consumeCliAppError(outcome)) return;
51
- if (json) logger.out(formatMirrorSyncJsonOutput(outcome.value, (performance.now() - startTime) / 1e3));
53
+ if (json) logger.out(formatMirrorSyncJsonOutput(outcome.value, (performance.now() - startTime) / 1e3, write));
52
54
  process.exitCode = exitCodeForMirrorSyncResult(outcome.value);
53
55
  });
54
- return cmd;
55
56
  }
56
57
  //#endregion
57
58
  export { createMirrorCommand };
@@ -202,7 +202,7 @@ async function generateExports(fileSystemService, distDir, pathTransform, cssCon
202
202
  moduleExportsBySpecifier[exportPath] = exportEntry;
203
203
  originalPathBySpecifier[exportPath] = originalExportPath;
204
204
  }
205
- let sortedSpecifiers = Object.keys(moduleExportsBySpecifier).sort((leftSpecifier, rightSpecifier) => compareExportSortKeys(getExportSortKey(leftSpecifier, pathTransform), getExportSortKey(rightSpecifier, pathTransform)));
205
+ let sortedSpecifiers = Object.keys(moduleExportsBySpecifier).toSorted((leftSpecifier, rightSpecifier) => compareExportSortKeys(getExportSortKey(leftSpecifier, pathTransform), getExportSortKey(rightSpecifier, pathTransform)));
206
206
  const sortedExports = {};
207
207
  for (const exportKey of sortedSpecifiers) sortedExports[exportKey] = moduleExportsBySpecifier[exportKey];
208
208
  const cssExports = await generateCssExports(fileSystemService, distDir, cssConfig ?? { enabled: true });
@@ -210,7 +210,7 @@ async function generateExports(fileSystemService, distDir, pathTransform, cssCon
210
210
  for (const [specifier, mappedPath] of Object.entries(extraExports || {})) if (specifier !== "./package.json") sortedExports[specifier] = mappedPath;
211
211
  for (const cssSpecifier of Object.keys(cssExports)) if (!(cssSpecifier in originalPathBySpecifier)) originalPathBySpecifier[cssSpecifier] = cssSpecifier;
212
212
  for (const extraSpecifier of Object.keys(extraExports || {})) if (extraSpecifier !== "./package.json" && !(extraSpecifier in originalPathBySpecifier)) originalPathBySpecifier[extraSpecifier] = extraSpecifier;
213
- sortedSpecifiers = Object.keys(sortedExports).filter((exportKey) => exportKey !== PACKAGE_JSON_EXPORT).sort((leftSpecifier, rightSpecifier) => compareExportSortKeys(getExportSortKey(leftSpecifier, pathTransform), getExportSortKey(rightSpecifier, pathTransform)));
213
+ sortedSpecifiers = Object.keys(sortedExports).filter((exportKey) => exportKey !== PACKAGE_JSON_EXPORT).toSorted((leftSpecifier, rightSpecifier) => compareExportSortKeys(getExportSortKey(leftSpecifier, pathTransform), getExportSortKey(rightSpecifier, pathTransform)));
214
214
  const finalExports = {};
215
215
  for (const exportKey of sortedSpecifiers) finalExports[exportKey] = sortedExports[exportKey];
216
216
  finalExports[PACKAGE_JSON_EXPORT] = PACKAGE_JSON_EXPORT;
@@ -8,12 +8,15 @@ const cliLogger = logger;
8
8
  var MirrorSyncProgressPresenter = class {
9
9
  reporter = new MirrorSyncReporter();
10
10
  verbose = false;
11
+ dryRun = false;
11
12
  configure(options) {
12
13
  this.verbose = options.verbose;
14
+ this.dryRun = options.dryRun;
13
15
  this.reporter.configureMirrorColors(options.noColor);
14
16
  }
15
17
  onBanner() {
16
18
  this.reporter.mirrorBanner(cliLogger);
19
+ if (this.dryRun) this.reporter.mirrorDryRunNotice(cliLogger);
17
20
  }
18
21
  onProcessingMode(mode) {
19
22
  this.reporter.mirrorProcessingMode(cliLogger, mode);
@@ -70,7 +70,7 @@ function buildSupplementedEntry(specifier, existing, modulePath, distDir, fs, op
70
70
  /**
71
71
  * @since 0.3.16-canary.0
72
72
  */
73
- async function supplementExportsInPackageJson(fs, packageJsonPath, packageDir, options) {
73
+ async function supplementExportsInPackageJson(fs, packageJsonPath, packageDir, options, write = true) {
74
74
  const raw = await fs.readFile(packageJsonPath, "utf8");
75
75
  const existingExports = JSON.parse(raw).exports;
76
76
  if (!existingExports || typeof existingExports !== "object" || Array.isArray(existingExports)) return { supplementedSpecifiers: [] };
@@ -94,7 +94,7 @@ async function supplementExportsInPackageJson(fs, packageJsonPath, packageDir, o
94
94
  generatedExports: supplementedExports,
95
95
  managedExportSpecifiers: Object.keys(supplementedExports),
96
96
  originalPathBySpecifier
97
- });
97
+ }, write);
98
98
  return { supplementedSpecifiers };
99
99
  }
100
100
  /**
@@ -14,7 +14,7 @@ const ANSI = {
14
14
  brightCyan: "\x1B[96m"
15
15
  };
16
16
  /**
17
- * Console implementation: ANSI styling and layout for mirror sync.
17
+ * Console implementation: ANSI styling and layout for the mirror command.
18
18
  *
19
19
  * @since 0.3.16-canary.0
20
20
  */
@@ -27,6 +27,9 @@ var MirrorSyncReporter = class {
27
27
  logger.out(`\n${this.paint("📦 Mirror — package exports", ANSI.bold + ANSI.cyan)}`);
28
28
  logger.out(`${this.paint("═".repeat(60), ANSI.dim)}\n`);
29
29
  }
30
+ mirrorDryRunNotice(logger) {
31
+ logger.out(`${this.paint("Dry run — no files will be written.", ANSI.yellow)}\n`);
32
+ }
30
33
  mirrorProcessingMode(logger, mode) {
31
34
  if (mode.kind === "single") {
32
35
  logger.out(`${this.paint("Processing single package...", ANSI.dim)}\n`);
@@ -1,16 +1,16 @@
1
1
  import { messageFrom } from "../core/errors.mjs";
2
+ import { createMirrorDistFilesystem } from "./dist-filesystem-impl.mjs";
2
3
  import { DIST_DIR, PACKAGE_JSON } from "./domain/constants.mjs";
3
4
  import { createPathTransform, generateExports } from "./domain/exports.mjs";
4
5
  import { resolvePackageDisplayName } from "./domain/package-display-name.mjs";
5
6
  import { writePackageJsonExportsAtomic } from "./write-exports.mjs";
6
- import { createMirrorDistFilesystem } from "./dist-filesystem-impl.mjs";
7
7
  import { buildSourcePathResolver, supplementExportsInPackageJson } from "./supplement-exports.mjs";
8
8
  import path from "node:path";
9
9
  //#region src/mirror/sync-workspace-package.ts
10
10
  /**
11
11
  * @since 0.3.16-canary.0
12
12
  */
13
- async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, config) {
13
+ async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, config, write = true) {
14
14
  const pathJoin = path.join;
15
15
  const pathResolve = path.resolve;
16
16
  const pathBasename = path.basename;
@@ -79,7 +79,7 @@ async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, confi
79
79
  resolveSourcePath
80
80
  };
81
81
  if (pkgConfig?.preserve) {
82
- const { supplementedSpecifiers } = await supplementExportsInPackageJson(fs, packageJsonPath, packageDir, exportOptions);
82
+ const { supplementedSpecifiers } = await supplementExportsInPackageJson(fs, packageJsonPath, packageDir, exportOptions, write);
83
83
  pkgStats.totalExports = supplementedSpecifiers.length;
84
84
  } else {
85
85
  const pathTransform = createPathTransform(pkgConfig?.strip);
@@ -93,7 +93,7 @@ async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, confi
93
93
  generatedExports: generatedExports.exports,
94
94
  managedExportSpecifiers: Object.keys(generatedExports.exports),
95
95
  originalPathBySpecifier: generatedExports.originalPathBySpecifier
96
- });
96
+ }, write);
97
97
  pkgStats.jsModules = generatedExports.jsCount;
98
98
  pkgStats.cssExports = generatedExports.cssCount;
99
99
  pkgStats.extraExports = Object.keys(extraExports).length;
@@ -12,6 +12,7 @@ import path from "node:path";
12
12
  async function runMirrorSync(fs, input) {
13
13
  const config = input.config ?? {};
14
14
  const { listener } = input;
15
+ const write = input.write ?? true;
15
16
  listener?.onBanner();
16
17
  const startTime = performance.now();
17
18
  try {
@@ -47,7 +48,7 @@ async function runMirrorSync(fs, input) {
47
48
  }
48
49
  let ordinal = 1;
49
50
  for (const pkgPath of targetPackages) {
50
- const pkgStats = await syncExportsForWorkspacePackage(fs, input.rootDir, pkgPath, config);
51
+ const pkgStats = await syncExportsForWorkspacePackage(fs, input.rootDir, pkgPath, config, write);
51
52
  stats.packageDetails.push(pkgStats);
52
53
  listener?.onPackageComplete(pkgStats, ordinal, targetPackages.length);
53
54
  ordinal++;