@codefast/cli 0.10.0 → 0.11.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.
Files changed (220) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +16 -2
  3. package/dist/arrange/command.js +96 -132
  4. package/dist/arrange/domain/ast/simplify-targets.js +18 -23
  5. package/dist/arrange/{typescript-ast-translator.d.ts → domain/ast/translator.d.ts} +5 -4
  6. package/dist/arrange/{typescript-ast-translator.js → domain/ast/translator.js} +19 -22
  7. package/dist/arrange/group/cli-result.d.ts +7 -0
  8. package/dist/arrange/group/cli-result.js +12 -0
  9. package/dist/arrange/group/cli-schema.d.ts +17 -0
  10. package/dist/arrange/group/cli-schema.js +13 -0
  11. package/dist/arrange/group/output.d.ts +7 -0
  12. package/dist/arrange/group/output.js +10 -0
  13. package/dist/arrange/{suggest.d.ts → group/suggest.d.ts} +1 -1
  14. package/dist/arrange/inspect/cli-result.d.ts +7 -0
  15. package/dist/arrange/inspect/cli-result.js +8 -0
  16. package/dist/arrange/inspect/cli-schema.d.ts +15 -0
  17. package/dist/arrange/inspect/cli-schema.js +9 -0
  18. package/dist/arrange/inspect/output.d.ts +7 -0
  19. package/dist/arrange/inspect/output.js +42 -0
  20. package/dist/arrange/{analyze.d.ts → inspect/run.d.ts} +2 -2
  21. package/dist/arrange/{analyze.js → inspect/run.js} +2 -2
  22. package/dist/arrange/{workspace.d.ts → prepare.d.ts} +2 -2
  23. package/dist/arrange/{workspace.js → prepare.js} +5 -8
  24. package/dist/arrange/regroup/cli-result.d.ts +13 -0
  25. package/dist/arrange/regroup/cli-result.js +23 -0
  26. package/dist/arrange/regroup/cli-schema.d.ts +20 -0
  27. package/dist/arrange/regroup/cli-schema.js +14 -0
  28. package/dist/arrange/regroup/output.d.ts +14 -0
  29. package/dist/arrange/regroup/output.js +72 -0
  30. package/dist/arrange/{process-file.d.ts → regroup/process-file.d.ts} +2 -2
  31. package/dist/arrange/{sync.d.ts → regroup/run.d.ts} +3 -3
  32. package/dist/arrange/{sync.js → regroup/run.js} +2 -2
  33. package/dist/arrange/resolve-target.d.ts +2 -2
  34. package/dist/arrange/scan-target.d.ts +2 -2
  35. package/dist/arrange/simplify/cli-result.d.ts +7 -0
  36. package/dist/arrange/simplify/cli-result.js +8 -0
  37. package/dist/arrange/simplify/cli-schema.d.ts +17 -0
  38. package/dist/arrange/simplify/cli-schema.js +11 -0
  39. package/dist/arrange/simplify/fold-targets.d.ts +13 -0
  40. package/dist/arrange/simplify/fold-targets.js +139 -0
  41. package/dist/arrange/simplify/output.d.ts +7 -0
  42. package/dist/arrange/simplify/output.js +15 -0
  43. package/dist/arrange/{simplify-process-file.d.ts → simplify/process-file.d.ts} +4 -2
  44. package/dist/arrange/simplify/process-file.js +49 -0
  45. package/dist/arrange/{simplify-sync.d.ts → simplify/run.d.ts} +3 -2
  46. package/dist/arrange/simplify/run.js +38 -0
  47. package/dist/arrange/simplify/variant-classname-probe.d.ts +35 -0
  48. package/dist/arrange/simplify/variant-classname-probe.js +95 -0
  49. package/dist/arrange/source-parse.js +1 -1
  50. package/dist/audit/command.js +134 -211
  51. package/dist/audit/comments/cli-result.d.ts +13 -0
  52. package/dist/audit/comments/cli-result.js +22 -0
  53. package/dist/audit/comments/cli-schema.d.ts +19 -0
  54. package/dist/audit/comments/cli-schema.js +13 -0
  55. package/dist/audit/comments/output.d.ts +7 -0
  56. package/dist/audit/comments/output.js +27 -0
  57. package/dist/audit/comments/prepare.d.ts +16 -0
  58. package/dist/audit/comments/prepare.js +12 -0
  59. package/dist/audit/{run-comments.d.ts → comments/run.d.ts} +2 -2
  60. package/dist/audit/{run-comments.js → comments/run.js} +5 -5
  61. package/dist/audit/display-names/cli-result.d.ts +13 -0
  62. package/dist/audit/display-names/cli-result.js +22 -0
  63. package/dist/audit/display-names/cli-schema.d.ts +18 -0
  64. package/dist/audit/display-names/cli-schema.js +12 -0
  65. package/dist/audit/display-names/output.d.ts +7 -0
  66. package/dist/audit/display-names/output.js +21 -0
  67. package/dist/audit/display-names/prepare.d.ts +16 -0
  68. package/dist/audit/display-names/prepare.js +12 -0
  69. package/dist/audit/{run-display-names.d.ts → display-names/run.d.ts} +2 -2
  70. package/dist/audit/{run-display-names.js → display-names/run.js} +1 -1
  71. package/dist/audit/imports/cli-result.d.ts +13 -0
  72. package/dist/audit/imports/cli-result.js +22 -0
  73. package/dist/audit/imports/cli-schema.d.ts +18 -0
  74. package/dist/audit/imports/cli-schema.js +12 -0
  75. package/dist/audit/{domain → imports/domain}/import-policy.d.ts +2 -2
  76. package/dist/audit/{domain → imports/domain}/import-policy.js +4 -11
  77. package/dist/audit/imports/output.d.ts +7 -0
  78. package/dist/audit/imports/output.js +21 -0
  79. package/dist/audit/imports/prepare.d.ts +16 -0
  80. package/dist/audit/imports/prepare.js +12 -0
  81. package/dist/audit/{run-imports.d.ts → imports/run.d.ts} +2 -2
  82. package/dist/audit/{run-imports.js → imports/run.js} +2 -2
  83. package/dist/audit/links/cli-result.d.ts +13 -0
  84. package/dist/audit/links/cli-result.js +22 -0
  85. package/dist/audit/links/cli-schema.d.ts +18 -0
  86. package/dist/audit/links/cli-schema.js +12 -0
  87. package/dist/audit/links/output.d.ts +7 -0
  88. package/dist/audit/links/output.js +21 -0
  89. package/dist/audit/links/prepare.d.ts +16 -0
  90. package/dist/audit/links/prepare.js +12 -0
  91. package/dist/audit/{run-links.d.ts → links/run.d.ts} +2 -2
  92. package/dist/audit/{run-links.js → links/run.js} +1 -1
  93. package/dist/audit/prepare.d.ts +11 -50
  94. package/dist/audit/prepare.js +9 -132
  95. package/dist/audit/rtl/cli-result.d.ts +13 -0
  96. package/dist/audit/rtl/cli-result.js +22 -0
  97. package/dist/audit/rtl/cli-schema.d.ts +18 -0
  98. package/dist/audit/rtl/cli-schema.js +12 -0
  99. package/dist/audit/{domain → rtl/domain}/audit-file.js +2 -2
  100. package/dist/audit/rtl/output.d.ts +7 -0
  101. package/dist/audit/rtl/output.js +21 -0
  102. package/dist/audit/rtl/prepare.d.ts +13 -0
  103. package/dist/audit/rtl/prepare.js +41 -0
  104. package/dist/audit/{run.d.ts → rtl/run.d.ts} +2 -2
  105. package/dist/audit/{run.js → rtl/run.js} +1 -1
  106. package/dist/core/cli/command-pipeline.d.ts +91 -0
  107. package/dist/core/cli/command-pipeline.js +83 -0
  108. package/dist/core/cli/global-options.d.ts +1 -1
  109. package/dist/core/cli/global-options.js +1 -1
  110. package/dist/core/cli/resolve-root.d.ts +9 -0
  111. package/dist/core/cli/resolve-root.js +16 -0
  112. package/dist/core/cli/result-handle.d.ts +1 -7
  113. package/dist/core/cli/result-handle.js +0 -13
  114. package/dist/core/config/loader.d.ts +2 -2
  115. package/dist/core/config/schema.d.ts +1 -1
  116. package/dist/core/config/schema.js +1 -1
  117. package/dist/core/config.d.ts +2 -2
  118. package/dist/core/filesystem/{port.d.ts → filesystem.d.ts} +1 -1
  119. package/dist/core/filesystem/node.d.ts +3 -3
  120. package/dist/core/filesystem/node.js +1 -1
  121. package/dist/core/source-text-edit.d.ts +14 -0
  122. package/dist/core/source-text-edit.js +22 -0
  123. package/dist/core/workspace/markdown-walk.d.ts +2 -2
  124. package/dist/core/workspace/package-version.d.ts +2 -2
  125. package/dist/core/workspace/resolver.d.ts +3 -3
  126. package/dist/core/workspace/source-walk.d.ts +2 -2
  127. package/dist/core/workspace/typescript-walk.d.ts +2 -2
  128. package/dist/core/workspace/walk-files.d.ts +2 -2
  129. package/dist/mirror/cli-schema.d.ts +2 -2
  130. package/dist/mirror/cli-schema.js +1 -1
  131. package/dist/mirror/command.js +31 -60
  132. package/dist/mirror/{dist-filesystem-impl.d.ts → dist-filesystem-node.d.ts} +3 -3
  133. package/dist/mirror/{dist-filesystem-impl.js → dist-filesystem-node.js} +1 -1
  134. package/dist/mirror/domain/dirent-guard.d.ts +1 -1
  135. package/dist/mirror/domain/types.d.ts +57 -0
  136. package/dist/mirror/output.d.ts +1 -3
  137. package/dist/mirror/output.js +126 -1
  138. package/dist/mirror/package-path.d.ts +3 -3
  139. package/dist/mirror/prepare.d.ts +3 -3
  140. package/dist/mirror/prepare.js +6 -10
  141. package/dist/mirror/run.d.ts +12 -0
  142. package/dist/mirror/supplement-exports.d.ts +3 -3
  143. package/dist/mirror/sync-workspace-package.d.ts +2 -2
  144. package/dist/mirror/sync-workspace-package.js +1 -1
  145. package/dist/mirror/write-exports.d.ts +2 -2
  146. package/dist/pack-slim/cli-schema.d.ts +1 -1
  147. package/dist/pack-slim/cli-schema.js +1 -1
  148. package/dist/pack-slim/command.js +31 -66
  149. package/dist/pack-slim/prepare.d.ts +21 -0
  150. package/dist/pack-slim/prepare.js +14 -0
  151. package/dist/pack-slim/{sync.d.ts → run.d.ts} +2 -2
  152. package/dist/tag/cli-result.d.ts +8 -2
  153. package/dist/tag/cli-result.js +14 -1
  154. package/dist/tag/cli-schema.d.ts +4 -4
  155. package/dist/tag/cli-schema.js +3 -4
  156. package/dist/tag/command.js +33 -60
  157. package/dist/tag/domain/skip-filter.d.ts +13 -0
  158. package/dist/tag/domain/skip-filter.js +29 -0
  159. package/dist/tag/domain/types.d.ts +21 -1
  160. package/dist/tag/domain/version-summary.d.ts +13 -0
  161. package/dist/tag/domain/version-summary.js +24 -0
  162. package/dist/tag/output.d.ts +4 -4
  163. package/dist/tag/output.js +6 -9
  164. package/dist/tag/prepare.d.ts +2 -2
  165. package/dist/tag/prepare.js +2 -2
  166. package/dist/tag/run.d.ts +10 -0
  167. package/dist/tag/{sync.js → run.js} +20 -50
  168. package/dist/tag/{target-candidates.d.ts → target/candidates.d.ts} +2 -2
  169. package/dist/tag/{target-candidates.js → target/candidates.js} +1 -1
  170. package/dist/tag/{resolve-target-path.d.ts → target/resolve-path.d.ts} +2 -2
  171. package/dist/tag/{target-runner.d.ts → target/runner.d.ts} +2 -2
  172. package/dist/tag/{target-runner.js → target/runner.js} +2 -2
  173. package/dist/tag/{since-writer.d.ts → writer/since-writer.d.ts} +2 -2
  174. package/dist/tag/{version-resolver.d.ts → writer/version-resolver.d.ts} +2 -2
  175. package/package.json +10 -2
  176. package/dist/arrange/cli-schema.d.ts +0 -50
  177. package/dist/arrange/cli-schema.js +0 -34
  178. package/dist/arrange/output.d.ts +0 -26
  179. package/dist/arrange/output.js +0 -127
  180. package/dist/arrange/simplify-process-file.js +0 -30
  181. package/dist/arrange/simplify-sync.js +0 -30
  182. package/dist/audit/cli-schema.d.ts +0 -93
  183. package/dist/audit/cli-schema.js +0 -66
  184. package/dist/audit/output.d.ts +0 -91
  185. package/dist/audit/output.js +0 -213
  186. package/dist/mirror/sync-reporter.d.ts +0 -60
  187. package/dist/mirror/sync-reporter.js +0 -128
  188. package/dist/mirror/sync-types.d.ts +0 -43
  189. package/dist/mirror/sync-types.js +0 -1
  190. package/dist/mirror/sync.d.ts +0 -12
  191. package/dist/tag/sync.d.ts +0 -42
  192. /package/dist/arrange/{suggest.js → group/suggest.js} +0 -0
  193. /package/dist/arrange/{domain → inspect/domain}/analyze-service.d.ts +0 -0
  194. /package/dist/arrange/{domain → inspect/domain}/analyze-service.js +0 -0
  195. /package/dist/arrange/{process-file.js → regroup/process-file.js} +0 -0
  196. /package/dist/audit/{domain → comments/domain}/comment-content.d.ts +0 -0
  197. /package/dist/audit/{domain → comments/domain}/comment-content.js +0 -0
  198. /package/dist/audit/{domain → comments/domain}/comment-dividers.d.ts +0 -0
  199. /package/dist/audit/{domain → comments/domain}/comment-dividers.js +0 -0
  200. /package/dist/audit/{domain → comments/domain}/link-references.d.ts +0 -0
  201. /package/dist/audit/{domain → comments/domain}/link-references.js +0 -0
  202. /package/dist/audit/{domain → comments/domain}/since-versions.d.ts +0 -0
  203. /package/dist/audit/{domain → comments/domain}/since-versions.js +0 -0
  204. /package/dist/audit/{domain → comments/domain}/tsdoc-syntax.d.ts +0 -0
  205. /package/dist/audit/{domain → comments/domain}/tsdoc-syntax.js +0 -0
  206. /package/dist/audit/{domain → display-names/domain}/display-names.d.ts +0 -0
  207. /package/dist/audit/{domain → display-names/domain}/display-names.js +0 -0
  208. /package/dist/audit/{domain → links/domain}/markdown-links.d.ts +0 -0
  209. /package/dist/audit/{domain → links/domain}/markdown-links.js +0 -0
  210. /package/dist/audit/{domain → rtl/domain}/audit-file.d.ts +0 -0
  211. /package/dist/audit/{domain → rtl/domain}/mappings.d.ts +0 -0
  212. /package/dist/audit/{domain → rtl/domain}/mappings.js +0 -0
  213. /package/dist/audit/{domain → rtl/domain}/tokenize.d.ts +0 -0
  214. /package/dist/audit/{domain → rtl/domain}/tokenize.js +0 -0
  215. /package/dist/core/filesystem/{port.js → filesystem.js} +0 -0
  216. /package/dist/mirror/{sync.js → run.js} +0 -0
  217. /package/dist/pack-slim/{sync.js → run.js} +0 -0
  218. /package/dist/tag/{resolve-target-path.js → target/resolve-path.js} +0 -0
  219. /package/dist/tag/{since-writer.js → writer/since-writer.js} +0 -0
  220. /package/dist/tag/{version-resolver.js → writer/version-resolver.js} +0 -0
@@ -10,10 +10,4 @@ export declare function consumeCliAppError<Value>(outcome: Result<Value, AppErro
10
10
  }): outcome is {
11
11
  readonly ok: true;
12
12
  readonly value: Value;
13
- };
14
- /**
15
- * Awaits a `Result`, hands a success to `onSuccess`, and records the returned exit code on the process.
16
- *
17
- * @since 0.3.16-canary.0
18
- */
19
- export declare function runCliResultAsync<Value>(outcomePromise: Promise<Result<Value, AppError>>, onSuccess: (value: Value) => number | void | Promise<number | void>): Promise<void>;
13
+ };
@@ -38,17 +38,4 @@ export function consumeCliAppError(outcome, options) {
38
38
  logger.out(options.successMessage);
39
39
  }
40
40
  return true;
41
- }
42
- /**
43
- * Awaits a `Result`, hands a success to `onSuccess`, and records the returned exit code on the process.
44
- *
45
- * @since 0.3.16-canary.0
46
- */
47
- export async function runCliResultAsync(outcomePromise, onSuccess) {
48
- const outcome = await outcomePromise;
49
- if (!consumeCliAppError(outcome)) {
50
- return;
51
- }
52
- const exit = await onSuccess(outcome.value);
53
- process.exitCode = exit === undefined ? 0 : exit;
54
41
  }
@@ -1,5 +1,5 @@
1
1
  import type { CodefastConfig } from "#/core/config/schema";
2
- import type { FilesystemPort } from "#/core/filesystem/port";
2
+ import type { Filesystem } from "#/core/filesystem/filesystem";
3
3
  /**
4
4
  * A loaded config together with its schema warnings and the path it was read from.
5
5
  *
@@ -15,4 +15,4 @@ export type LoadConfigPayload = {
15
15
  *
16
16
  * @since 0.3.16-canary.0
17
17
  */
18
- export declare function loadConfigPayload(startDir: string, fs: FilesystemPort): Promise<LoadConfigPayload>;
18
+ export declare function loadConfigPayload(startDir: string, fs: Filesystem): Promise<LoadConfigPayload>;
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  /**
3
3
  * A config hook invoked with the written file paths after a command rewrites files.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  const afterWriteHookSchema = z.custom((value) => typeof value === "function", {
3
3
  message: "Expected a function",
4
4
  });
@@ -1,12 +1,12 @@
1
1
  import type { CodefastConfig } from "#/core/config/schema";
2
2
  import { AppError } from "#/core/errors";
3
- import type { FilesystemPort } from "#/core/filesystem/port";
3
+ import type { Filesystem } from "#/core/filesystem/filesystem";
4
4
  import type { Result } from "#/core/result";
5
5
  /**
6
6
  * Loads the `codefast.config.js` for a workspace root, reporting schema warnings along the way.
7
7
  *
8
8
  * @since 0.3.16-canary.0
9
9
  */
10
- export declare function loadCodefastConfig(rootDir: string, fs: FilesystemPort): Promise<Result<{
10
+ export declare function loadCodefastConfig(rootDir: string, fs: Filesystem): Promise<Result<{
11
11
  config: CodefastConfig;
12
12
  }, AppError>>;
@@ -20,7 +20,7 @@ export interface DirectoryEntry {
20
20
  *
21
21
  * @since 0.3.16-canary.0
22
22
  */
23
- export interface FilesystemPort {
23
+ export interface Filesystem {
24
24
  existsSync(filePath: string): boolean;
25
25
  canonicalPathSync(inputPath: string): string;
26
26
  statSync(filePath: string): {
@@ -1,7 +1,7 @@
1
- import type { FilesystemPort } from "#/core/filesystem/port";
1
+ import type { Filesystem } from "#/core/filesystem/filesystem";
2
2
  /**
3
- * The `FilesystemPort` implementation backed by Node's real filesystem.
3
+ * The `Filesystem` implementation backed by Node's real filesystem.
4
4
  *
5
5
  * @since 0.3.16-canary.0
6
6
  */
7
- export declare const nodeFilesystem: FilesystemPort;
7
+ export declare const nodeFilesystem: Filesystem;
@@ -2,7 +2,7 @@ import fsSync from "node:fs";
2
2
  import fsPromises from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  /**
5
- * The `FilesystemPort` implementation backed by Node's real filesystem.
5
+ * The `Filesystem` implementation backed by Node's real filesystem.
6
6
  *
7
7
  * @since 0.3.16-canary.0
8
8
  */
@@ -24,6 +24,20 @@ export declare function textPrefixFromLineStartToPosition(source: string, pos: n
24
24
  * @since 0.3.16-canary.0
25
25
  */
26
26
  export declare function endAfterOptionalCommaFollowingInSource(source: string, tokenEnd: number): number;
27
+ /**
28
+ * Returns the edits with every overlap removed, keeping the outermost of any overlapping pair.
29
+ *
30
+ * @remarks A planned edit replaces a whole AST node, so two ranges either nest or are disjoint and
31
+ * the outer replacement already contains the inner span verbatim — dropping the nested edit keeps
32
+ * the output valid. Higher-priority edits must come first so an exact-range tie keeps the earlier
33
+ * one. This is the invariant `applyEditsDescending` assumes; run it before applying.
34
+ *
35
+ * @since 0.11.0
36
+ */
37
+ export declare function dropOverlappingEdits<Edit extends {
38
+ start: number;
39
+ end: number;
40
+ }>(edits: ReadonlyArray<Edit>): Array<Edit>;
27
41
  /**
28
42
  * Applies non-overlapping text edits from the highest offset down and returns the edited source.
29
43
  *
@@ -54,6 +54,28 @@ export function endAfterOptionalCommaFollowingInSource(source, tokenEnd) {
54
54
  }
55
55
  return tokenEnd;
56
56
  }
57
+ /**
58
+ * Returns the edits with every overlap removed, keeping the outermost of any overlapping pair.
59
+ *
60
+ * @remarks A planned edit replaces a whole AST node, so two ranges either nest or are disjoint and
61
+ * the outer replacement already contains the inner span verbatim — dropping the nested edit keeps
62
+ * the output valid. Higher-priority edits must come first so an exact-range tie keeps the earlier
63
+ * one. This is the invariant `applyEditsDescending` assumes; run it before applying.
64
+ *
65
+ * @since 0.11.0
66
+ */
67
+ export function dropOverlappingEdits(edits) {
68
+ const ordered = [...edits].toSorted((editA, editB) => editA.start - editB.start || editB.end - editA.end);
69
+ const kept = [];
70
+ let lastEnd = -1;
71
+ for (const edit of ordered) {
72
+ if (edit.start >= lastEnd) {
73
+ kept.push(edit);
74
+ lastEnd = edit.end;
75
+ }
76
+ }
77
+ return kept;
78
+ }
57
79
  /**
58
80
  * Applies non-overlapping text edits from the highest offset down and returns the edited source.
59
81
  *
@@ -1,7 +1,7 @@
1
- import type { FilesystemPort } from "#/core/filesystem/port";
1
+ import type { Filesystem } from "#/core/filesystem/filesystem";
2
2
  /**
3
3
  * Every markdown file under a root, skipping build output and vendored trees.
4
4
  *
5
5
  * @since 0.5.0
6
6
  */
7
- export declare function walkMarkdownFiles(rootDirectoryPath: string, fs: FilesystemPort): Array<string>;
7
+ export declare function walkMarkdownFiles(rootDirectoryPath: string, fs: Filesystem): Array<string>;
@@ -1,9 +1,9 @@
1
- import type { FilesystemPort } from "#/core/filesystem/port";
1
+ import type { Filesystem } from "#/core/filesystem/filesystem";
2
2
  /**
3
3
  * Finds the `version` of the nearest enclosing `package.json`, or null when the first
4
4
  * one found declares none (the private workspace root is version-less by design).
5
5
  *
6
6
  * @since 0.8.0
7
7
  */
8
- declare function findNearestPackageVersion(fs: FilesystemPort, targetPath: string): string | null;
8
+ declare function findNearestPackageVersion(fs: Filesystem, targetPath: string): string | null;
9
9
  export { findNearestPackageVersion };
@@ -1,4 +1,4 @@
1
- import type { FilesystemPort } from "#/core/filesystem/port";
1
+ import type { Filesystem } from "#/core/filesystem/filesystem";
2
2
  /**
3
3
  * A resolved project root and whether it is a pnpm workspace or a standalone single package.
4
4
  *
@@ -13,7 +13,7 @@ export type ResolvedProjectRoot = {
13
13
  *
14
14
  * @since 0.10.0
15
15
  */
16
- export declare function resolveProjectRoot(fromDirectory: string, fs: FilesystemPort): ResolvedProjectRoot;
16
+ export declare function resolveProjectRoot(fromDirectory: string, fs: Filesystem): ResolvedProjectRoot;
17
17
  /**
18
18
  * Where the workspace package patterns came from.
19
19
  *
@@ -35,5 +35,5 @@ export type WorkspacePackageLayoutOutcome = {
35
35
  *
36
36
  * @since 0.3.16-canary.0
37
37
  */
38
- export declare function listWorkspacePackageDirectories(rootDirectoryPathAbsolute: string, fs: FilesystemPort, suppressGlobPermissionDiagnostics?: boolean): Promise<WorkspacePackageLayoutOutcome>;
38
+ export declare function listWorkspacePackageDirectories(rootDirectoryPathAbsolute: string, fs: Filesystem, suppressGlobPermissionDiagnostics?: boolean): Promise<WorkspacePackageLayoutOutcome>;
39
39
  export {};
@@ -1,4 +1,4 @@
1
- import type { FilesystemPort } from "#/core/filesystem/port";
1
+ import type { Filesystem } from "#/core/filesystem/filesystem";
2
2
  /**
3
3
  * Every hand-written source file a comment convention applies to — `.ts`, `.tsx`, `.css`, and ignore files.
4
4
  *
@@ -7,7 +7,7 @@ import type { FilesystemPort } from "#/core/filesystem/port";
7
7
  *
8
8
  * @since 0.6.0
9
9
  */
10
- export declare function walkSourceFiles(rootDirectoryPath: string, fs: FilesystemPort): Array<string>;
10
+ export declare function walkSourceFiles(rootDirectoryPath: string, fs: Filesystem): Array<string>;
11
11
  /**
12
12
  * Which comment syntax a path is written in, or `null` when the convention does not cover it.
13
13
  *
@@ -1,7 +1,7 @@
1
- import type { FilesystemPort } from "#/core/filesystem/port";
1
+ import type { Filesystem } from "#/core/filesystem/filesystem";
2
2
  /**
3
3
  * Recursively collects the `.ts`/`.tsx` file paths under a root, skipping `.d.ts` and skip-listed directories.
4
4
  *
5
5
  * @since 0.3.16-canary.0
6
6
  */
7
- export declare function walkTsxFiles(rootDirectoryPath: string, fs: FilesystemPort): Array<string>;
7
+ export declare function walkTsxFiles(rootDirectoryPath: string, fs: Filesystem): Array<string>;
@@ -1,7 +1,7 @@
1
- import type { FilesystemPort } from "#/core/filesystem/port";
1
+ import type { Filesystem } from "#/core/filesystem/filesystem";
2
2
  /**
3
3
  * Every file under a root the predicate accepts, skipping build output and vendored trees.
4
4
  *
5
5
  * @since 0.10.0
6
6
  */
7
- export declare function walkFiles(rootDirectoryPath: string, fs: FilesystemPort, shouldInclude: (filePath: string) => boolean): Array<string>;
7
+ export declare function walkFiles(rootDirectoryPath: string, fs: Filesystem, shouldInclude: (filePath: string) => boolean): Array<string>;
@@ -1,5 +1,5 @@
1
- import { z } from "zod";
2
- import type { MirrorSyncRunRequest } from "#/mirror/sync-types";
1
+ import * as z from "zod";
2
+ import type { MirrorSyncRunRequest } from "#/mirror/domain/types";
3
3
  /**
4
4
  * Zod schema validating a `MirrorSyncRunRequest`.
5
5
  *
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  /**
3
3
  * Zod schema validating a `MirrorSyncRunRequest`.
4
4
  *
@@ -1,74 +1,45 @@
1
- import process from "node:process";
2
1
  import { Command } from "commander";
3
- import { globalCliCommanderOptionsSchema } from "#/core/cli/global-options";
4
- import { readOptionalPositionalArg } from "#/core/cli/positional";
5
- import { consumeCliAppError } from "#/core/cli/result-handle";
2
+ import { applyCommandPipeline } from "#/core/cli/command-pipeline";
6
3
  import { nodeFilesystem } from "#/core/filesystem/node";
7
- import { logger } from "#/core/logger";
8
- import { parseWithSchema } from "#/core/schema-parse";
9
4
  import { exitCodeForMirrorSyncResult, formatMirrorSyncJsonOutput } from "#/mirror/cli-result";
10
5
  import { mirrorSyncRunRequestSchema } from "#/mirror/cli-schema";
11
6
  import { MirrorSyncProgressPresenter } from "#/mirror/output";
12
7
  import { prepareMirrorSync } from "#/mirror/prepare";
13
- import { runMirrorSync } from "#/mirror/sync";
8
+ import { runMirrorSync } from "#/mirror/run";
9
+ const mirrorPipeline = {
10
+ positional: { name: "[package]", help: "Optional package path relative to repo root (e.g. packages/ui)" },
11
+ schema: mirrorSyncRunRequestSchema,
12
+ configureArgv: (command) => {
13
+ command.option("--dry-run", "Report what would change without writing package.json", false);
14
+ command.option("-v, --verbose", "Print extra diagnostics", false);
15
+ },
16
+ prepare: (fs, input) => prepareMirrorSync(fs, {
17
+ currentWorkingDirectory: input.currentWorkingDirectory,
18
+ packageArg: input.rawArg,
19
+ globals: input.globals,
20
+ }),
21
+ buildRequest: ({ prelude, opts }) => ({
22
+ rootDir: prelude.rootDir,
23
+ config: prelude.config.mirror ?? {},
24
+ packageFilter: prelude.packageFilter,
25
+ write: !opts.dryRun,
26
+ }),
27
+ createPresenter: ({ opts, globals }) => {
28
+ const presenter = new MirrorSyncProgressPresenter();
29
+ presenter.configure({ noColor: globals.color === false, verbose: !!opts.verbose, dryRun: !!opts.dryRun });
30
+ return presenter;
31
+ },
32
+ run: (fs, request, presenter) => runMirrorSync(fs, { ...request, listener: presenter }),
33
+ formatJson: ({ result, opts, elapsedSeconds }) => formatMirrorSyncJsonOutput(result, elapsedSeconds, !opts.dryRun),
34
+ exitCode: exitCodeForMirrorSyncResult,
35
+ };
14
36
  /**
15
37
  * Creates the `mirror` subcommand, which writes `package.json#exports` from `dist/`.
16
38
  *
17
39
  * @since 0.3.16-canary.0
18
40
  */
19
41
  export function createMirrorCommand() {
20
- const cmd = new Command("mirror")
21
- .description("Write package.json exports from dist/ for workspace packages")
22
- .argument("[package]", "Optional package path relative to repo root (e.g. packages/ui)")
23
- .option("--dry-run", "Report what would change without writing package.json", false)
24
- .option("-v, --verbose", "Print extra diagnostics", false)
25
- .option("--json", "Print one JSON summary on stdout (suppresses human progress)", false)
26
- .action(async (packageArg, opts, command) => {
27
- const globalsOptionCarrier = command.optsWithGlobals?.() ??
28
- command.opts();
29
- const globalOptionsOutcome = parseWithSchema(globalCliCommanderOptionsSchema, globalsOptionCarrier);
30
- if (!consumeCliAppError(globalOptionsOutcome)) {
31
- return;
32
- }
33
- const prelude = await prepareMirrorSync(nodeFilesystem, {
34
- currentWorkingDirectory: process.cwd(),
35
- packageArg: readOptionalPositionalArg(packageArg),
36
- globals: globalOptionsOutcome.value,
37
- });
38
- if (!consumeCliAppError(prelude)) {
39
- return;
40
- }
41
- const write = !opts.dryRun;
42
- const { rootDir, config, packageFilter } = prelude.value;
43
- const parsed = parseWithSchema(mirrorSyncRunRequestSchema, {
44
- rootDir,
45
- config: config.mirror ?? {},
46
- packageFilter,
47
- write,
48
- });
49
- if (!consumeCliAppError(parsed)) {
50
- return;
51
- }
52
- const json = !!opts.json;
53
- const noColor = globalOptionsOutcome.value.color === false;
54
- const verbose = !!opts.verbose;
55
- const progressPresenter = new MirrorSyncProgressPresenter();
56
- const listener = json ? undefined : progressPresenter;
57
- if (!json) {
58
- progressPresenter.configure({ noColor, verbose, dryRun: !write });
59
- }
60
- const startTime = performance.now();
61
- const outcome = await runMirrorSync(nodeFilesystem, {
62
- ...parsed.value,
63
- listener,
64
- });
65
- if (!consumeCliAppError(outcome)) {
66
- return;
67
- }
68
- if (json) {
69
- logger.out(formatMirrorSyncJsonOutput(outcome.value, (performance.now() - startTime) / 1000, write));
70
- }
71
- process.exitCode = exitCodeForMirrorSyncResult(outcome.value);
72
- });
42
+ const cmd = new Command("mirror").description("Write package.json exports from dist/ for workspace packages");
43
+ applyCommandPipeline(cmd, nodeFilesystem, mirrorPipeline);
73
44
  return cmd;
74
45
  }
@@ -1,8 +1,8 @@
1
- import type { FilesystemPort } from "#/core/filesystem/port";
1
+ import type { Filesystem } from "#/core/filesystem/filesystem";
2
2
  import type { DistFilesystem } from "#/mirror/domain/dist-filesystem";
3
3
  /**
4
- * Creates the `DistFilesystem` the mirror scan uses, backed by a `FilesystemPort`.
4
+ * Creates the `DistFilesystem` the mirror scan uses, backed by a `Filesystem`.
5
5
  *
6
6
  * @since 0.3.16-canary.0
7
7
  */
8
- export declare function createMirrorDistFilesystem(fs: FilesystemPort): DistFilesystem;
8
+ export declare function createMirrorDistFilesystem(fs: Filesystem): DistFilesystem;
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import { isDirentList } from "#/mirror/domain/dirent-guard";
3
3
  import { normalizePath } from "#/mirror/domain/path-normalizer";
4
4
  /**
5
- * Creates the `DistFilesystem` the mirror scan uses, backed by a `FilesystemPort`.
5
+ * Creates the `DistFilesystem` the mirror scan uses, backed by a `Filesystem`.
6
6
  *
7
7
  * @since 0.3.16-canary.0
8
8
  */
@@ -1,4 +1,4 @@
1
- import type { DirectoryEntry } from "#/core/filesystem/port";
1
+ import type { DirectoryEntry } from "#/core/filesystem/filesystem";
2
2
  /**
3
3
  * Narrow `fs.promises.readdir` overload result to `Dirent[]` when `withFileTypes: true`.
4
4
  * Accepts both historical checks (`isFile` / `isDirectory`) in one guard.
@@ -128,4 +128,61 @@ export interface MirrorSyncCommandPrelude {
128
128
  readonly config: CodefastConfig;
129
129
  readonly packageFilter: string | undefined;
130
130
  }
131
+ /**
132
+ * The inputs a mirror run is invoked with.
133
+ *
134
+ * @since 0.3.16-canary.0
135
+ */
136
+ export type MirrorSyncRunRequest = {
137
+ rootDir: string;
138
+ packageFilter?: string | undefined;
139
+ config?: unknown;
140
+ /** When false, compute and report changes without writing package.json. Defaults to true. */
141
+ write?: boolean | undefined;
142
+ };
143
+ /**
144
+ * Whether a run targets a single package or the discovered workspace set.
145
+ *
146
+ * @since 0.3.16-canary.0
147
+ */
148
+ export type MirrorProcessingModeInput = {
149
+ kind: "single";
150
+ } | {
151
+ kind: "multi";
152
+ source: WorkspaceMultiDiscoverySource;
153
+ };
154
+ /**
155
+ * Callbacks a mirror run invokes as it progresses through packages.
156
+ *
157
+ * @since 0.3.16-canary.0
158
+ */
159
+ export type MirrorSyncProgressListener = {
160
+ configure(options: {
161
+ readonly noColor: boolean;
162
+ readonly verbose: boolean;
163
+ readonly dryRun: boolean;
164
+ }): void;
165
+ onBanner(): void;
166
+ onProcessingMode(mode: MirrorProcessingModeInput): void;
167
+ onNoPackages(): void;
168
+ onPackageComplete(pkgStats: PackageStats, ordinal: number, total: number): void;
169
+ onComplete(stats: GlobalStats, elapsedSeconds: number): void;
170
+ };
171
+ /**
172
+ * A run request paired with an optional progress listener.
173
+ *
174
+ * @since 0.3.16-canary.0
175
+ */
176
+ export type MirrorSyncExecutionInput = MirrorSyncRunRequest & {
177
+ readonly listener?: MirrorSyncProgressListener | undefined;
178
+ };
179
+ /**
180
+ * The stdout/stderr line-writer shape the reporter prints through.
181
+ *
182
+ * @since 0.3.16-canary.0
183
+ */
184
+ export type CliLoggerLike = {
185
+ out(line: string): void;
186
+ err(line: string): void;
187
+ };
131
188
  export {};
@@ -1,6 +1,4 @@
1
- import type { GlobalStats, PackageStats } from "#/mirror/domain/types";
2
- import type { MirrorProcessingModeInput } from "#/mirror/sync-reporter";
3
- import type { MirrorSyncProgressListener } from "#/mirror/sync-types";
1
+ import type { GlobalStats, MirrorProcessingModeInput, MirrorSyncProgressListener, PackageStats } from "#/mirror/domain/types";
4
2
  /**
5
3
  * A progress listener that renders mirror run events through the CLI reporter.
6
4
  *
@@ -1,5 +1,6 @@
1
+ import process from "node:process";
2
+ import { messageFrom } from "#/core/errors";
1
3
  import { logger } from "#/core/logger";
2
- import { MirrorSyncReporter } from "#/mirror/sync-reporter";
3
4
  const cliLogger = logger;
4
5
  /**
5
6
  * A progress listener that renders mirror run events through the CLI reporter.
@@ -45,4 +46,128 @@ export class MirrorSyncProgressPresenter {
45
46
  this.reporter.mirrorSummarySeparator(cliLogger);
46
47
  this.reporter.mirrorSummary(cliLogger, stats, elapsedSeconds);
47
48
  }
49
+ }
50
+ const ANSI = {
51
+ reset: "\x1b[0m",
52
+ bold: "\x1b[1m",
53
+ dim: "\x1b[2m",
54
+ green: "\x1b[32m",
55
+ yellow: "\x1b[33m",
56
+ cyan: "\x1b[36m",
57
+ gray: "\x1b[90m",
58
+ magenta: "\x1b[35m",
59
+ brightGreen: "\x1b[92m",
60
+ brightCyan: "\x1b[96m",
61
+ };
62
+ /**
63
+ * Console rendering — ANSI styling and layout — for every line a mirror run prints.
64
+ */
65
+ class MirrorSyncReporter {
66
+ colorsAreEnabled = true;
67
+ configureMirrorColors(noColor) {
68
+ this.colorsAreEnabled = !!process.stdout.isTTY && !noColor;
69
+ }
70
+ mirrorBanner(target) {
71
+ target.out(`\n${this.paint("📦 Mirror — package exports", ANSI.bold + ANSI.cyan)}`);
72
+ target.out(`${this.paint("═".repeat(60), ANSI.dim)}\n`);
73
+ }
74
+ mirrorDryRunNotice(target) {
75
+ target.out(`${this.paint("Dry run — no files will be written.", ANSI.yellow)}\n`);
76
+ }
77
+ mirrorProcessingMode(target, mode) {
78
+ if (mode.kind === "single") {
79
+ target.out(`${this.paint("Processing single package...", ANSI.dim)}\n`);
80
+ return;
81
+ }
82
+ if (mode.source === "default-patterns") {
83
+ target.out(`${this.paint("Discovering workspace packages using default patterns (packages/*)…", ANSI.dim)}\n`);
84
+ return;
85
+ }
86
+ if (mode.source === "pnpm-workspace-yaml") {
87
+ target.out(`${this.paint("Discovering workspace packages from pnpm-workspace.yaml…", ANSI.dim)}\n`);
88
+ return;
89
+ }
90
+ if (mode.source === "single-package") {
91
+ target.out(`${this.paint("No pnpm-workspace.yaml — processing the single package at the project root…", ANSI.dim)}\n`);
92
+ return;
93
+ }
94
+ target.out(`${this.paint("pnpm-workspace.yaml declares an empty workspace package list.", ANSI.dim)}\n`);
95
+ }
96
+ mirrorNoPackages(target) {
97
+ target.out(this.paint("⚠ No packages found", ANSI.yellow));
98
+ }
99
+ logSkippedWorkspacePackage(target, index, total, displayName, reason) {
100
+ const progress = this.paint(`[${index}/${total}]`, ANSI.dim);
101
+ target.out(`${progress} ${this.paint("○", ANSI.gray)} ${this.paint(displayName, ANSI.dim)}`);
102
+ target.out(` ${this.paint("└─", ANSI.dim)} ${this.paint(`Skipped: ${reason}`, ANSI.gray)}`);
103
+ target.out("");
104
+ }
105
+ logPackageSuccess(target, index, total, pkgStats, generatedDistAssetCounts, verbose) {
106
+ const progress = this.paint(`[${index}/${total}]`, ANSI.dim);
107
+ target.out(`${progress} ${this.paint("✓", ANSI.brightGreen)} ${this.paint(pkgStats.name, ANSI.bold)}`);
108
+ if (verbose) {
109
+ target.out(` ${this.paint("├─", ANSI.dim)} Path: ${pkgStats.path}`);
110
+ if (pkgStats.hasTransform) {
111
+ target.out(` ${this.paint("├─", ANSI.dim)} ${this.paint("Custom path transformation", ANSI.cyan)}`);
112
+ }
113
+ if (pkgStats.cssConfigStatus) {
114
+ const status = pkgStats.cssConfigStatus === "disabled" ? "CSS disabled" : "CSS configured";
115
+ target.out(` ${this.paint("├─", ANSI.dim)} ${this.paint(status, ANSI.cyan)}`);
116
+ }
117
+ }
118
+ const breakdown = [];
119
+ if (generatedDistAssetCounts.jsCount > 0) {
120
+ breakdown.push(this.paint(`${generatedDistAssetCounts.jsCount} modules`, ANSI.green));
121
+ }
122
+ if (generatedDistAssetCounts.cssCount > 0) {
123
+ breakdown.push(this.paint(`${generatedDistAssetCounts.cssCount} CSS`, ANSI.magenta));
124
+ }
125
+ if (pkgStats.extraExports > 0) {
126
+ breakdown.push(this.paint(`${pkgStats.extraExports} custom`, ANSI.yellow));
127
+ }
128
+ const totalExportsText = this.paint(`${pkgStats.totalExports} exports`, ANSI.brightCyan);
129
+ if (breakdown.length === 0) {
130
+ target.out(` ${this.paint("└─", ANSI.dim)} ${totalExportsText}`);
131
+ }
132
+ else {
133
+ target.out(` ${this.paint("└─", ANSI.dim)} ${breakdown.join(" + ")} = ${totalExportsText}`);
134
+ }
135
+ target.out("");
136
+ }
137
+ logPrunedStaleExport(target, exportSpecifier) {
138
+ target.out(` ${this.paint("└─", ANSI.dim)} ${this.paint(`Pruned stale export: ${exportSpecifier}`, ANSI.gray)}`);
139
+ }
140
+ logPackageError(target, index, total, displayName, errorValue, verbose) {
141
+ target.out(`${this.paint(`[${index}/${total}]`, ANSI.dim)} ${this.paint("✗", ANSI.yellow)} ${this.paint(displayName, ANSI.bold)}`);
142
+ target.out(` ${this.paint("└─", ANSI.dim)} ${this.paint(`Error: ${messageFrom(errorValue)}`, ANSI.yellow)}\n`);
143
+ if (verbose) {
144
+ target.err(errorValue instanceof Error && errorValue.stack ? errorValue.stack : messageFrom(errorValue));
145
+ }
146
+ }
147
+ mirrorSummarySeparator(target) {
148
+ target.out(this.paint("═".repeat(60), ANSI.dim));
149
+ }
150
+ mirrorSummary(target, stats, elapsedSeconds) {
151
+ target.out(`${this.paint("📊 Summary", ANSI.bold)} ${this.paint(`(completed in ${elapsedSeconds.toFixed(2)}s)`, ANSI.dim)}\n`);
152
+ target.out(` ${this.paint("Packages:", ANSI.bold)}`);
153
+ target.out(` ${this.paint("├─", ANSI.dim)} Processed: ${this.paint(String(stats.packagesProcessed), ANSI.green)}`);
154
+ if (stats.packagesSkipped > 0) {
155
+ target.out(` ${this.paint("├─", ANSI.dim)} Skipped: ${this.paint(String(stats.packagesSkipped), ANSI.gray)}`);
156
+ }
157
+ if (stats.packagesErrored > 0) {
158
+ target.out(` ${this.paint("├─", ANSI.dim)} Errors: ${this.paint(String(stats.packagesErrored), ANSI.yellow)}`);
159
+ }
160
+ target.out(` ${this.paint("└─", ANSI.dim)} Total found: ${stats.packagesFound}\n`);
161
+ target.out(` ${this.paint("Exports:", ANSI.bold)}`);
162
+ target.out(` ${this.paint("├─", ANSI.dim)} JS Modules: ${this.paint(String(stats.totalJsModules), ANSI.cyan)}`);
163
+ target.out(` ${this.paint("├─", ANSI.dim)} CSS Files: ${this.paint(String(stats.totalCssExports), ANSI.magenta)}`);
164
+ target.out(` ${this.paint("└─", ANSI.dim)} Total: ${this.paint(String(stats.totalExports), ANSI.brightCyan)}\n`);
165
+ target.out(`${this.paint("═".repeat(60), ANSI.dim)}\n`);
166
+ }
167
+ paint(text, openSequence) {
168
+ if (!this.colorsAreEnabled) {
169
+ return text;
170
+ }
171
+ return `${openSequence}${text}${ANSI.reset}`;
172
+ }
48
173
  }
@@ -1,12 +1,12 @@
1
1
  import { AppError } from "#/core/errors";
2
- import type { FilesystemPort } from "#/core/filesystem/port";
2
+ import type { Filesystem } from "#/core/filesystem/filesystem";
3
3
  import type { Result } from "#/core/result";
4
4
  /**
5
5
  * Resolves the optional positional package argument to a root-relative path, or `undefined` when absent.
6
6
  *
7
7
  * @since 0.3.16-canary.0
8
8
  */
9
- export declare function resolveMirrorPackageFromCliArg(fs: FilesystemPort, args: {
9
+ export declare function resolveMirrorPackageFromCliArg(fs: Filesystem, args: {
10
10
  readonly rootDir: string;
11
11
  readonly packageArg: string | undefined;
12
12
  readonly currentWorkingDirectory: string;
@@ -16,4 +16,4 @@ export declare function resolveMirrorPackageFromCliArg(fs: FilesystemPort, args:
16
16
  *
17
17
  * @since 0.3.16-canary.0
18
18
  */
19
- export declare function resolvePackageFilterUnderRoot(fs: FilesystemPort, rootDir: string, packageFilter: string): Result<string, AppError>;
19
+ export declare function resolvePackageFilterUnderRoot(fs: Filesystem, rootDir: string, packageFilter: string): Result<string, AppError>;
@@ -1,6 +1,6 @@
1
1
  import type { GlobalCliOptions } from "#/core/cli/global-options";
2
- import { AppError } from "#/core/errors";
3
- import type { FilesystemPort } from "#/core/filesystem/port";
2
+ import type { AppError } from "#/core/errors";
3
+ import type { Filesystem } from "#/core/filesystem/filesystem";
4
4
  import type { Result } from "#/core/result";
5
5
  import type { MirrorSyncCommandPrelude } from "#/mirror/domain/types";
6
6
  /**
@@ -8,7 +8,7 @@ import type { MirrorSyncCommandPrelude } from "#/mirror/domain/types";
8
8
  *
9
9
  * @since 0.3.16-canary.0
10
10
  */
11
- export declare function prepareMirrorSync(fs: FilesystemPort, args: {
11
+ export declare function prepareMirrorSync(fs: Filesystem, args: {
12
12
  readonly currentWorkingDirectory: string;
13
13
  readonly packageArg: string | undefined;
14
14
  readonly globals: GlobalCliOptions;