@codefast/cli 0.8.1 → 0.10.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 (166) hide show
  1. package/CHANGELOG.md +596 -0
  2. package/LICENSE +1 -1
  3. package/README.md +335 -129
  4. package/dist/arrange/analyze.d.ts +10 -0
  5. package/dist/arrange/cli-schema.d.ts +50 -0
  6. package/dist/arrange/command.d.ts +7 -0
  7. package/dist/arrange/domain/analyze-service.d.ts +18 -0
  8. package/dist/arrange/domain/ast/ast-node.d.ts +394 -0
  9. package/dist/arrange/domain/ast/collectors-cn.d.ts +26 -0
  10. package/dist/arrange/domain/ast/collectors-jsx.d.ts +8 -0
  11. package/dist/arrange/domain/ast/collectors-tv.d.ts +34 -0
  12. package/dist/arrange/domain/ast/helpers.d.ts +36 -0
  13. package/dist/arrange/domain/ast/helpers.js +1 -0
  14. package/dist/arrange/domain/ast/simplify-targets.d.ts +22 -0
  15. package/dist/arrange/domain/ast/targets.d.ts +20 -0
  16. package/dist/arrange/domain/constants.d.ts +111 -0
  17. package/dist/arrange/domain/grouping-service.d.ts +100 -0
  18. package/dist/arrange/domain/grouping.d.ts +21 -0
  19. package/dist/arrange/domain/imports.d.ts +14 -0
  20. package/dist/arrange/domain/source-text-formatters.d.ts +33 -0
  21. package/dist/arrange/domain/tailwind-token.d.ts +24 -0
  22. package/dist/arrange/domain/token-classifier.d.ts +47 -0
  23. package/dist/arrange/domain/types.d.ts +208 -0
  24. package/dist/arrange/output.d.ts +26 -0
  25. package/dist/arrange/process-file.d.ts +11 -0
  26. package/dist/arrange/resolve-target.d.ts +10 -0
  27. package/dist/arrange/resolve-target.js +3 -16
  28. package/dist/arrange/scan-target.d.ts +7 -0
  29. package/dist/arrange/simplify-process-file.d.ts +11 -0
  30. package/dist/arrange/simplify-sync.d.ts +13 -0
  31. package/dist/arrange/source-parse.d.ts +7 -0
  32. package/dist/arrange/suggest.d.ts +8 -0
  33. package/dist/arrange/sync.d.ts +11 -0
  34. package/dist/arrange/typescript-ast-translator.d.ts +31 -0
  35. package/dist/arrange/workspace.d.ts +13 -0
  36. package/dist/arrange/workspace.js +2 -2
  37. package/dist/audit/cli-schema.d.ts +93 -0
  38. package/dist/audit/cli-schema.js +14 -3
  39. package/dist/audit/command.d.ts +8 -0
  40. package/dist/audit/command.js +52 -12
  41. package/dist/audit/domain/audit-file.d.ts +7 -0
  42. package/dist/audit/domain/comment-content.d.ts +26 -0
  43. package/dist/audit/domain/comment-dividers.d.ts +62 -0
  44. package/dist/audit/domain/comment-dividers.js +48 -20
  45. package/dist/audit/domain/display-names.d.ts +11 -0
  46. package/dist/audit/domain/display-names.js +71 -0
  47. package/dist/audit/domain/import-policy.d.ts +34 -0
  48. package/dist/audit/domain/import-policy.js +147 -0
  49. package/dist/audit/domain/link-references.d.ts +40 -0
  50. package/dist/audit/domain/mappings.d.ts +45 -0
  51. package/dist/audit/domain/markdown-links.d.ts +44 -0
  52. package/dist/audit/domain/since-versions.d.ts +26 -0
  53. package/dist/audit/domain/tokenize.d.ts +14 -0
  54. package/dist/audit/domain/tsdoc-syntax.d.ts +20 -0
  55. package/dist/audit/domain/types.d.ts +171 -0
  56. package/dist/audit/output.d.ts +91 -0
  57. package/dist/audit/output.js +52 -11
  58. package/dist/audit/prepare.d.ts +70 -0
  59. package/dist/audit/prepare.js +41 -10
  60. package/dist/audit/run-comments.d.ts +17 -0
  61. package/dist/audit/run-comments.js +22 -18
  62. package/dist/audit/run-display-names.d.ts +14 -0
  63. package/dist/audit/run-display-names.js +60 -0
  64. package/dist/audit/run-imports.d.ts +14 -0
  65. package/dist/audit/{run-react.js → run-imports.js} +15 -5
  66. package/dist/audit/run-links.d.ts +14 -0
  67. package/dist/audit/run.d.ts +14 -0
  68. package/dist/bin.d.ts +2 -0
  69. package/dist/cli.d.ts +6 -0
  70. package/dist/core/cli/format-error.d.ts +7 -0
  71. package/dist/core/cli/global-options.d.ts +15 -0
  72. package/dist/core/cli/positional.d.ts +6 -0
  73. package/dist/core/cli/result-handle.d.ts +19 -0
  74. package/dist/core/config/define-config.d.ts +7 -0
  75. package/dist/core/config/define-config.js +8 -0
  76. package/dist/core/config/loader.d.ts +18 -0
  77. package/dist/core/config/loader.js +2 -7
  78. package/dist/core/config/schema.d.ts +99 -0
  79. package/dist/core/config/schema.js +7 -75
  80. package/dist/core/config/warnings.d.ts +6 -0
  81. package/dist/core/config.d.ts +12 -0
  82. package/dist/core/errors.d.ts +25 -0
  83. package/dist/core/exit-codes.d.ts +18 -0
  84. package/dist/core/filesystem/node.d.ts +7 -0
  85. package/dist/core/filesystem/node.js +1 -0
  86. package/dist/core/filesystem/port.d.ts +44 -0
  87. package/dist/core/glob.d.ts +19 -0
  88. package/dist/core/logger.d.ts +9 -0
  89. package/dist/core/result.d.ts +30 -0
  90. package/dist/core/schema-parse.d.ts +9 -0
  91. package/dist/core/source-text-edit.d.ts +33 -0
  92. package/dist/core/verbose-diagnostics.d.ts +6 -0
  93. package/dist/core/workspace/ancestor-directories.d.ts +12 -0
  94. package/dist/core/workspace/ancestor-directories.js +30 -0
  95. package/dist/core/workspace/markdown-walk.d.ts +7 -0
  96. package/dist/core/workspace/markdown-walk.js +2 -20
  97. package/dist/core/workspace/package-version.d.ts +9 -0
  98. package/dist/core/workspace/package-version.js +8 -12
  99. package/dist/core/workspace/resolver.d.ts +39 -0
  100. package/dist/core/workspace/resolver.js +58 -75
  101. package/dist/core/workspace/skip-directories.d.ts +6 -0
  102. package/dist/core/workspace/source-walk.d.ts +16 -0
  103. package/dist/core/workspace/source-walk.js +14 -20
  104. package/dist/core/workspace/typescript-walk.d.ts +7 -0
  105. package/dist/core/workspace/typescript-walk.js +2 -23
  106. package/dist/core/workspace/walk-files.d.ts +7 -0
  107. package/dist/core/workspace/walk-files.js +27 -0
  108. package/dist/core/workspace/well-known-files.d.ts +18 -0
  109. package/dist/core/workspace/well-known-files.js +18 -0
  110. package/dist/index.d.ts +6 -0
  111. package/dist/index.js +5 -0
  112. package/dist/mirror/cli-result.d.ts +13 -0
  113. package/dist/mirror/cli-schema.d.ts +8 -0
  114. package/dist/mirror/command.d.ts +7 -0
  115. package/dist/mirror/dist-filesystem-impl.d.ts +8 -0
  116. package/dist/mirror/domain/constants.d.ts +18 -0
  117. package/dist/mirror/domain/constants.js +0 -12
  118. package/dist/mirror/domain/dirent-guard.d.ts +10 -0
  119. package/dist/mirror/domain/dist-filesystem.d.ts +9 -0
  120. package/dist/mirror/domain/errors.d.ts +24 -0
  121. package/dist/mirror/domain/exports.d.ts +36 -0
  122. package/dist/mirror/domain/package-display-name.d.ts +8 -0
  123. package/dist/mirror/domain/path-normalizer.d.ts +6 -0
  124. package/dist/mirror/domain/types.d.ts +131 -0
  125. package/dist/mirror/output.d.ts +23 -0
  126. package/dist/mirror/package-path.d.ts +19 -0
  127. package/dist/mirror/prepare.d.ts +15 -0
  128. package/dist/mirror/prepare.js +2 -2
  129. package/dist/mirror/supplement-exports.d.ts +27 -0
  130. package/dist/mirror/supplement-exports.js +2 -2
  131. package/dist/mirror/sync-reporter.d.ts +60 -0
  132. package/dist/mirror/sync-reporter.js +4 -0
  133. package/dist/mirror/sync-types.d.ts +43 -0
  134. package/dist/mirror/sync-workspace-package.d.ts +9 -0
  135. package/dist/mirror/sync-workspace-package.js +3 -3
  136. package/dist/mirror/sync.d.ts +12 -0
  137. package/dist/mirror/sync.js +5 -3
  138. package/dist/mirror/write-exports.d.ts +15 -0
  139. package/dist/pack-slim/cli-result.d.ts +13 -0
  140. package/dist/pack-slim/cli-schema.d.ts +17 -0
  141. package/dist/pack-slim/command.d.ts +7 -0
  142. package/dist/pack-slim/command.js +5 -4
  143. package/dist/pack-slim/domain/transform.d.ts +69 -0
  144. package/dist/pack-slim/domain/transform.js +141 -15
  145. package/dist/pack-slim/domain/types.d.ts +46 -0
  146. package/dist/pack-slim/output.d.ts +15 -0
  147. package/dist/pack-slim/output.js +10 -1
  148. package/dist/pack-slim/sync.d.ts +23 -0
  149. package/dist/pack-slim/sync.js +14 -8
  150. package/dist/pack-slim/working-tree.d.ts +20 -0
  151. package/dist/tag/cli-result.d.ts +7 -0
  152. package/dist/tag/cli-schema.d.ts +8 -0
  153. package/dist/tag/command.d.ts +7 -0
  154. package/dist/tag/domain/types.d.ts +111 -0
  155. package/dist/tag/output.d.ts +17 -0
  156. package/dist/tag/prepare.d.ts +13 -0
  157. package/dist/tag/prepare.js +2 -2
  158. package/dist/tag/resolve-target-path.d.ts +10 -0
  159. package/dist/tag/since-writer.d.ts +32 -0
  160. package/dist/tag/sync.d.ts +42 -0
  161. package/dist/tag/target-candidates.d.ts +8 -0
  162. package/dist/tag/target-candidates.js +1 -1
  163. package/dist/tag/target-runner.d.ts +8 -0
  164. package/dist/tag/version-resolver.d.ts +7 -0
  165. package/package.json +16 -33
  166. package/dist/audit/domain/react-imports.js +0 -91
@@ -83,19 +83,19 @@ export function formatLinkAuditJsonOutput(result, rootDir) {
83
83
  });
84
84
  }
85
85
  /**
86
- * Exit `1` when any non-allowlisted React import-policy violation remains.
86
+ * Exit `1` when any non-allowlisted import-policy violation remains.
87
87
  *
88
- * @since 0.8.0
88
+ * @since 0.10.0
89
89
  */
90
- export function exitCodeForReactAuditResult(result) {
90
+ export function exitCodeForImportsAuditResult(result) {
91
91
  return result.violationCount > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
92
92
  }
93
93
  /**
94
- * Human-readable React import-policy report.
94
+ * Human-readable import-policy report.
95
95
  *
96
- * @since 0.8.0
96
+ * @since 0.10.0
97
97
  */
98
- export function presentReactAuditResult(result) {
98
+ export function presentImportsAuditResult(result) {
99
99
  for (const file of result.files) {
100
100
  logger.out(`\n${file.relativePath}`);
101
101
  for (const { line, raw, reason } of file.violations) {
@@ -104,18 +104,18 @@ export function presentReactAuditResult(result) {
104
104
  }
105
105
  const allowlistSuffix = result.allowlistedCount > 0 ? ` (${result.allowlistedCount} allowlisted)` : "";
106
106
  if (result.violationCount > 0) {
107
- logger.out(`\n✖ ${result.violationCount} React import violation(s)${allowlistSuffix}`);
107
+ logger.out(`\n✖ ${result.violationCount} import-policy violation(s)${allowlistSuffix}`);
108
108
  }
109
109
  else {
110
- logger.out(`✓ No namespace/default React imports or React.* UMD globals across ${result.scannedFileCount} file(s)${allowlistSuffix}`);
110
+ logger.out(`✓ No import-policy violations across ${result.scannedFileCount} file(s)${allowlistSuffix}`);
111
111
  }
112
112
  }
113
113
  /**
114
- * Machine-readable React import-policy summary for `--json`.
114
+ * Machine-readable import-policy summary for `--json`.
115
115
  *
116
- * @since 0.8.0
116
+ * @since 0.10.0
117
117
  */
118
- export function formatReactAuditJsonOutput(result, rootDir) {
118
+ export function formatImportsAuditJsonOutput(result, rootDir) {
119
119
  return JSON.stringify({
120
120
  schemaVersion: 1,
121
121
  ok: result.violationCount === 0,
@@ -169,4 +169,45 @@ export function formatCommentAuditJsonOutput(result, rootDir) {
169
169
  }
170
170
  function truncate(raw) {
171
171
  return raw.length <= 60 ? raw : `${raw.slice(0, 57)}…`;
172
+ }
173
+ /**
174
+ * Exit `1` when any non-allowlisted display-name violation remains.
175
+ *
176
+ * @since 0.9.0
177
+ */
178
+ export function exitCodeForDisplayNameAuditResult(result) {
179
+ return result.violationCount > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
180
+ }
181
+ /**
182
+ * Human-readable display-name report.
183
+ *
184
+ * @since 0.9.0
185
+ */
186
+ export function presentDisplayNameAuditResult(result) {
187
+ for (const file of result.files) {
188
+ logger.out(`\n${file.relativePath}`);
189
+ for (const { line, raw, reason } of file.violations) {
190
+ logger.out(` ${line}: ${raw} → ${reason}`);
191
+ }
192
+ }
193
+ const allowlistSuffix = result.allowlistedCount > 0 ? ` (${result.allowlistedCount} allowlisted)` : "";
194
+ if (result.violationCount > 0) {
195
+ logger.out(`\n✖ ${result.violationCount} display name(s) off the convention${allowlistSuffix}`);
196
+ }
197
+ else {
198
+ logger.out(`✓ Every token, tag and module display name follows <namespace>:<Name> across ${result.scannedFileCount} file(s)${allowlistSuffix}`);
199
+ }
200
+ }
201
+ /**
202
+ * Machine-readable display-name summary for `--json`.
203
+ *
204
+ * @since 0.9.0
205
+ */
206
+ export function formatDisplayNameAuditJsonOutput(result, rootDir) {
207
+ return JSON.stringify({
208
+ schemaVersion: 1,
209
+ ok: result.violationCount === 0,
210
+ cwd: rootDir,
211
+ result,
212
+ });
172
213
  }
@@ -0,0 +1,70 @@
1
+ import { AppError } from "#/core/errors";
2
+ import type { FilesystemPort } from "#/core/filesystem/port";
3
+ import type { Result } from "#/core/result";
4
+ /**
5
+ * Shared prelude for `audit rtl`: repo root and the canonicalized scan target with its allowlist.
6
+ *
7
+ * @since 0.5.0-canary.6
8
+ */
9
+ export type RtlAuditCommandPrelude = {
10
+ readonly rootDir: string;
11
+ readonly targetPath: string;
12
+ readonly allowlist: ReadonlyArray<string>;
13
+ };
14
+ /**
15
+ * Loads config and resolves the scan target for `audit rtl`.
16
+ *
17
+ * @since 0.5.0-canary.6
18
+ */
19
+ export declare function prepareRtlAudit(fs: FilesystemPort, args: {
20
+ readonly currentWorkingDirectory: string;
21
+ readonly rawTarget: string | undefined;
22
+ }): Promise<Result<RtlAuditCommandPrelude, AppError>>;
23
+ /**
24
+ * Loads config and resolves the scan target for `audit links`.
25
+ *
26
+ * @remarks Defaults to the repo root rather than a configured path: a link audit that only covers one
27
+ * package cannot see the cross-package references that are the ones most likely to rot.
28
+ *
29
+ * @since 0.5.0
30
+ */
31
+ export declare function prepareLinkAudit(fs: FilesystemPort, args: {
32
+ readonly currentWorkingDirectory: string;
33
+ readonly rawTarget: string | undefined;
34
+ }): Promise<Result<RtlAuditCommandPrelude, AppError>>;
35
+ /**
36
+ * Loads config and resolves the scan target for `audit imports`.
37
+ *
38
+ * @remarks Defaults to the repo root: the import policy is repo-wide, and generated or vendored
39
+ * trees are already excluded by the shared walk.
40
+ *
41
+ * @since 0.10.0
42
+ */
43
+ export declare function prepareImportsAudit(fs: FilesystemPort, args: {
44
+ readonly currentWorkingDirectory: string;
45
+ readonly rawTarget: string | undefined;
46
+ }): Promise<Result<RtlAuditCommandPrelude, AppError>>;
47
+ /**
48
+ * Loads config and resolves the scan target for `audit comments`.
49
+ *
50
+ * @remarks Defaults to the repo root: a divider convention that only holds inside one package
51
+ * is not a convention.
52
+ *
53
+ * @since 0.6.0
54
+ */
55
+ export declare function prepareCommentAudit(fs: FilesystemPort, args: {
56
+ readonly currentWorkingDirectory: string;
57
+ readonly rawTarget: string | undefined;
58
+ }): Promise<Result<RtlAuditCommandPrelude, AppError>>;
59
+ /**
60
+ * Loads config and resolves the scan target for `audit display-names`.
61
+ *
62
+ * @remarks Defaults to the repo root: a display name collides across packages, so the convention
63
+ * has to hold across them.
64
+ *
65
+ * @since 0.9.0
66
+ */
67
+ export declare function prepareDisplayNameAudit(fs: FilesystemPort, args: {
68
+ readonly currentWorkingDirectory: string;
69
+ readonly rawTarget: string | undefined;
70
+ }): Promise<Result<RtlAuditCommandPrelude, AppError>>;
@@ -2,7 +2,7 @@ import { resolveRepoRelativePath } from "#/audit/cli-schema";
2
2
  import { loadCodefastConfig } from "#/core/config";
3
3
  import { AppError, messageFrom } from "#/core/errors";
4
4
  import { err, ok } from "#/core/result";
5
- import { findRepoRoot } from "#/core/workspace/resolver";
5
+ import { resolveProjectRoot } from "#/core/workspace/resolver";
6
6
  /**
7
7
  * Loads config and resolves the scan target for `audit rtl`.
8
8
  *
@@ -12,7 +12,7 @@ export async function prepareRtlAudit(fs, args) {
12
12
  let rootDir;
13
13
  try {
14
14
  // Realpath so allowlist keys (`path.relative(rootDir, file)`) stay stable when cwd is a symlink.
15
- rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
15
+ rootDir = fs.canonicalPathSync(resolveProjectRoot(args.currentWorkingDirectory, fs).rootDir);
16
16
  }
17
17
  catch (caughtError) {
18
18
  return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
@@ -50,7 +50,7 @@ export async function prepareRtlAudit(fs, args) {
50
50
  export async function prepareLinkAudit(fs, args) {
51
51
  let rootDir;
52
52
  try {
53
- rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
53
+ rootDir = fs.canonicalPathSync(resolveProjectRoot(args.currentWorkingDirectory, fs).rootDir);
54
54
  }
55
55
  catch (caughtError) {
56
56
  return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
@@ -71,17 +71,17 @@ export async function prepareLinkAudit(fs, args) {
71
71
  });
72
72
  }
73
73
  /**
74
- * Loads config and resolves the scan target for `audit react`.
74
+ * Loads config and resolves the scan target for `audit imports`.
75
75
  *
76
76
  * @remarks Defaults to the repo root: the import policy is repo-wide, and generated or vendored
77
77
  * trees are already excluded by the shared walk.
78
78
  *
79
- * @since 0.8.0
79
+ * @since 0.10.0
80
80
  */
81
- export async function prepareReactAudit(fs, args) {
81
+ export async function prepareImportsAudit(fs, args) {
82
82
  let rootDir;
83
83
  try {
84
- rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
84
+ rootDir = fs.canonicalPathSync(resolveProjectRoot(args.currentWorkingDirectory, fs).rootDir);
85
85
  }
86
86
  catch (caughtError) {
87
87
  return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
@@ -90,7 +90,7 @@ export async function prepareReactAudit(fs, args) {
90
90
  if (!loadedOutcome.ok) {
91
91
  return loadedOutcome;
92
92
  }
93
- const reactConfig = loadedOutcome.value.config.audit?.react ?? {};
93
+ const importsConfig = loadedOutcome.value.config.audit?.imports ?? {};
94
94
  const targetPath = args.rawTarget === undefined ? rootDir : resolveRepoRelativePath(args.currentWorkingDirectory, args.rawTarget);
95
95
  if (!fs.existsSync(targetPath)) {
96
96
  return err(new AppError("NOT_FOUND", `Not found: ${targetPath}`));
@@ -98,7 +98,7 @@ export async function prepareReactAudit(fs, args) {
98
98
  return ok({
99
99
  rootDir,
100
100
  targetPath: fs.canonicalPathSync(targetPath),
101
- allowlist: reactConfig.allowlist ?? [],
101
+ allowlist: importsConfig.allowlist ?? [],
102
102
  });
103
103
  }
104
104
  /**
@@ -112,7 +112,7 @@ export async function prepareReactAudit(fs, args) {
112
112
  export async function prepareCommentAudit(fs, args) {
113
113
  let rootDir;
114
114
  try {
115
- rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
115
+ rootDir = fs.canonicalPathSync(resolveProjectRoot(args.currentWorkingDirectory, fs).rootDir);
116
116
  }
117
117
  catch (caughtError) {
118
118
  return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
@@ -131,4 +131,35 @@ export async function prepareCommentAudit(fs, args) {
131
131
  targetPath: fs.canonicalPathSync(targetPath),
132
132
  allowlist: commentsConfig.allowlist ?? [],
133
133
  });
134
+ }
135
+ /**
136
+ * Loads config and resolves the scan target for `audit display-names`.
137
+ *
138
+ * @remarks Defaults to the repo root: a display name collides across packages, so the convention
139
+ * has to hold across them.
140
+ *
141
+ * @since 0.9.0
142
+ */
143
+ export async function prepareDisplayNameAudit(fs, args) {
144
+ let rootDir;
145
+ try {
146
+ rootDir = fs.canonicalPathSync(resolveProjectRoot(args.currentWorkingDirectory, fs).rootDir);
147
+ }
148
+ catch (caughtError) {
149
+ return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
150
+ }
151
+ const loadedOutcome = await loadCodefastConfig(rootDir, fs);
152
+ if (!loadedOutcome.ok) {
153
+ return loadedOutcome;
154
+ }
155
+ const displayNamesConfig = loadedOutcome.value.config.audit?.displayNames ?? {};
156
+ const targetPath = args.rawTarget === undefined ? rootDir : resolveRepoRelativePath(args.currentWorkingDirectory, args.rawTarget);
157
+ if (!fs.existsSync(targetPath)) {
158
+ return err(new AppError("NOT_FOUND", `Not found: ${targetPath}`));
159
+ }
160
+ return ok({
161
+ rootDir,
162
+ targetPath: fs.canonicalPathSync(targetPath),
163
+ allowlist: displayNamesConfig.allowlist ?? [],
164
+ });
134
165
  }
@@ -0,0 +1,17 @@
1
+ import type { CommentAuditResult } from "#/audit/domain/types";
2
+ import { AppError } from "#/core/errors";
3
+ import type { FilesystemPort } from "#/core/filesystem/port";
4
+ import type { Result } from "#/core/result";
5
+ /**
6
+ * Reports every comment off the repo's conventions: divider form (which `fix` rewrites), banned
7
+ * content — document pointers, JSDoc types, tag misuse — `{@link}` targets nothing declares, and
8
+ * every diagnostic the official TSDoc parser raises against a doc block's grammar.
9
+ *
10
+ * @since 0.6.0
11
+ */
12
+ export declare function runCommentAudit(fs: FilesystemPort, args: {
13
+ readonly rootDir: string;
14
+ readonly targetPath: string;
15
+ readonly allowlist: ReadonlyArray<string>;
16
+ readonly fix: boolean;
17
+ }): Result<CommentAuditResult, AppError>;
@@ -68,30 +68,34 @@ export function runCommentAudit(fs, args) {
68
68
  }
69
69
  breakages.push({ line: region.startLine, raw: region.raw, reason: reasonByDefect[region.defect] });
70
70
  }
71
- for (const finding of scanCommentContent(content, language)) {
72
- if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
73
- allowlistedCount++;
74
- continue;
75
- }
76
- breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect[finding.defect] });
77
- }
78
- const packageVersion = nearestVersion(fs, absolutePath, versionByDirectory);
79
- if (packageVersion !== null) {
80
- for (const finding of scanImpossibleSinceTags(content, packageVersion)) {
71
+ // The content rules — banned fragments, `@since`, TSDoc grammar — govern doc comments in
72
+ // code; an ignore file carries only the divider convention.
73
+ if (language !== "ignore") {
74
+ for (const finding of scanCommentContent(content, language)) {
81
75
  if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
82
76
  allowlistedCount++;
83
77
  continue;
84
78
  }
85
- breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect["since-impossible"] });
79
+ breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect[finding.defect] });
86
80
  }
87
- }
88
- if (language === "js") {
89
- for (const finding of scanTsdocSyntax(content)) {
90
- if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
91
- allowlistedCount++;
92
- continue;
81
+ const packageVersion = nearestVersion(fs, absolutePath, versionByDirectory);
82
+ if (packageVersion !== null) {
83
+ for (const finding of scanImpossibleSinceTags(content, packageVersion)) {
84
+ if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
85
+ allowlistedCount++;
86
+ continue;
87
+ }
88
+ breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect["since-impossible"] });
89
+ }
90
+ }
91
+ if (language === "js") {
92
+ for (const finding of scanTsdocSyntax(content)) {
93
+ if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
94
+ allowlistedCount++;
95
+ continue;
96
+ }
97
+ breakages.push({ line: finding.line, raw: finding.raw, reason: finding.reason });
93
98
  }
94
- breakages.push({ line: finding.line, raw: finding.raw, reason: finding.reason });
95
99
  }
96
100
  }
97
101
  if (breakages.length > 0) {
@@ -0,0 +1,14 @@
1
+ import type { DisplayNameAuditResult } from "#/audit/domain/types";
2
+ import { AppError } from "#/core/errors";
3
+ import type { FilesystemPort } from "#/core/filesystem/port";
4
+ import type { Result } from "#/core/result";
5
+ /**
6
+ * Scans a target path for `token()`, `tag()` and module display names that break the convention.
7
+ *
8
+ * @since 0.9.0
9
+ */
10
+ export declare function runDisplayNameAudit(fs: FilesystemPort, args: {
11
+ readonly rootDir: string;
12
+ readonly targetPath: string;
13
+ readonly allowlist: ReadonlyArray<string>;
14
+ }): Result<DisplayNameAuditResult, AppError>;
@@ -0,0 +1,60 @@
1
+ import path from "node:path";
2
+ import { auditDisplayNames } from "#/audit/domain/display-names";
3
+ import { AppError, messageFrom } from "#/core/errors";
4
+ import { err, ok } from "#/core/result";
5
+ import { walkMarkdownFiles } from "#/core/workspace/markdown-walk";
6
+ import { walkTsxFiles } from "#/core/workspace/typescript-walk";
7
+ /**
8
+ * Trees the convention does not reach: a test or benchmark token is scoped by its file and never
9
+ * meets another author's, and a changelog quotes names as they were.
10
+ */
11
+ const SKIPPED_SEGMENTS = new Set(["tests", "benchmarks", ".changeset"]);
12
+ const SKIPPED_BASENAMES = new Set(["CHANGELOG.md"]);
13
+ /**
14
+ * Scans a target path for `token()`, `tag()` and module display names that break the convention.
15
+ *
16
+ * @since 0.9.0
17
+ */
18
+ export function runDisplayNameAudit(fs, args) {
19
+ try {
20
+ const allowlist = new Set(args.allowlist);
21
+ const { rootDir, targetPath } = args;
22
+ const filesToScan = collectScanPaths(fs, rootDir, targetPath);
23
+ const files = [];
24
+ let violationCount = 0;
25
+ let allowlistedCount = 0;
26
+ for (const absolutePath of filesToScan) {
27
+ const relativePath = toPosixPath(path.relative(rootDir, absolutePath));
28
+ const content = fs.readFileSync(absolutePath, "utf8");
29
+ const remaining = auditDisplayNames(content).filter(({ raw }) => {
30
+ const isAllowed = allowlist.has(raw) || allowlist.has(`${relativePath}:${raw}`);
31
+ if (isAllowed) {
32
+ allowlistedCount++;
33
+ }
34
+ return !isAllowed;
35
+ });
36
+ if (remaining.length === 0) {
37
+ continue;
38
+ }
39
+ violationCount += remaining.length;
40
+ files.push({ relativePath, violations: remaining });
41
+ }
42
+ return ok({ files, violationCount, allowlistedCount, scannedFileCount: filesToScan.length });
43
+ }
44
+ catch (caughtError) {
45
+ return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
46
+ }
47
+ }
48
+ function collectScanPaths(fs, rootDir, targetPath) {
49
+ const stats = fs.statSync(targetPath);
50
+ const candidates = stats.isFile()
51
+ ? [targetPath]
52
+ : [...walkTsxFiles(targetPath, fs), ...walkMarkdownFiles(targetPath, fs)];
53
+ return candidates.filter((absolutePath) => {
54
+ const relative = path.relative(rootDir, absolutePath).split(path.sep);
55
+ return !relative.some((segment) => SKIPPED_SEGMENTS.has(segment)) && !SKIPPED_BASENAMES.has(relative.at(-1));
56
+ });
57
+ }
58
+ function toPosixPath(filePath) {
59
+ return filePath.split(path.sep).join("/");
60
+ }
@@ -0,0 +1,14 @@
1
+ import type { ImportsAuditResult } from "#/audit/domain/types";
2
+ import { AppError } from "#/core/errors";
3
+ import type { FilesystemPort } from "#/core/filesystem/port";
4
+ import type { Result } from "#/core/result";
5
+ /**
6
+ * Scans a target path for import-policy violations, applying each rule only to files in its scope.
7
+ *
8
+ * @since 0.10.0
9
+ */
10
+ export declare function runImportsAudit(fs: FilesystemPort, args: {
11
+ readonly rootDir: string;
12
+ readonly targetPath: string;
13
+ readonly allowlist: ReadonlyArray<string>;
14
+ }): Result<ImportsAuditResult, AppError>;
@@ -1,25 +1,35 @@
1
1
  import path from "node:path";
2
- import { auditReactImportSource } from "#/audit/domain/react-imports";
2
+ import { defaultImportPolicyRules } from "#/audit/domain/import-policy";
3
+ import { auditImportPolicySource } from "#/audit/domain/import-policy";
3
4
  import { AppError, messageFrom } from "#/core/errors";
5
+ import { createAnyGlobMatcher } from "#/core/glob";
4
6
  import { err, ok } from "#/core/result";
5
7
  import { walkTsxFiles } from "#/core/workspace/typescript-walk";
6
8
  /**
7
- * Scans a target path for React import-policy violations.
9
+ * Scans a target path for import-policy violations, applying each rule only to files in its scope.
8
10
  *
9
- * @since 0.8.0
11
+ * @since 0.10.0
10
12
  */
11
- export function runReactAudit(fs, args) {
13
+ export function runImportsAudit(fs, args) {
12
14
  try {
13
15
  const allowlist = new Set(args.allowlist);
14
16
  const { rootDir, targetPath } = args;
17
+ const scopedRules = defaultImportPolicyRules.map((rule) => ({
18
+ rule,
19
+ isInScope: rule.scope ? createAnyGlobMatcher(rule.scope, { dot: true }) : () => true,
20
+ }));
15
21
  const filesToScan = collectScanPaths(fs, targetPath);
16
22
  const files = [];
17
23
  let violationCount = 0;
18
24
  let allowlistedCount = 0;
19
25
  for (const absolutePath of filesToScan) {
20
26
  const relativePath = toPosixPath(path.relative(rootDir, absolutePath));
27
+ const applicableRules = scopedRules.filter(({ isInScope }) => isInScope(relativePath)).map(({ rule }) => rule);
28
+ if (applicableRules.length === 0) {
29
+ continue;
30
+ }
21
31
  const content = fs.readFileSync(absolutePath, "utf8");
22
- const remaining = auditReactImportSource(absolutePath, content).filter(({ raw }) => {
32
+ const remaining = auditImportPolicySource(absolutePath, content, applicableRules).filter(({ raw }) => {
23
33
  const isAllowed = allowlist.has(raw) || allowlist.has(`${relativePath}:${raw}`);
24
34
  if (isAllowed) {
25
35
  allowlistedCount++;
@@ -0,0 +1,14 @@
1
+ import type { LinkAuditResult } from "#/audit/domain/types";
2
+ import { AppError } from "#/core/errors";
3
+ import type { FilesystemPort } from "#/core/filesystem/port";
4
+ import type { Result } from "#/core/result";
5
+ /**
6
+ * Reports markdown links that point at nothing — a missing path, or an anchor the target does not offer.
7
+ *
8
+ * @since 0.5.0
9
+ */
10
+ export declare function runLinkAudit(fs: FilesystemPort, args: {
11
+ readonly rootDir: string;
12
+ readonly targetPath: string;
13
+ readonly allowlist: ReadonlyArray<string>;
14
+ }): Result<LinkAuditResult, AppError>;
@@ -0,0 +1,14 @@
1
+ import type { RtlAuditResult } from "#/audit/domain/types";
2
+ import { AppError } from "#/core/errors";
3
+ import type { FilesystemPort } from "#/core/filesystem/port";
4
+ import type { Result } from "#/core/result";
5
+ /**
6
+ * Scans a target path for physical-direction Tailwind class violations.
7
+ *
8
+ * @since 0.5.0-canary.6
9
+ */
10
+ export declare function runRtlAudit(fs: FilesystemPort, args: {
11
+ readonly rootDir: string;
12
+ readonly targetPath: string;
13
+ readonly allowlist: ReadonlyArray<string>;
14
+ }): Result<RtlAuditResult, AppError>;
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Runs the `codefast` CLI over the given argv and returns the process exit code.
3
+ *
4
+ * @since 0.3.16-canary.0
5
+ */
6
+ export declare function runCli(argv: Array<string>): Promise<number>;
@@ -0,0 +1,7 @@
1
+ import type { AppError } from "#/core/errors";
2
+ /**
3
+ * Formats an `AppError` as a `[CODE] message` line for CLI output.
4
+ *
5
+ * @since 0.3.16-canary.0
6
+ */
7
+ export declare function formatAppError(error: AppError): string;
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The validated global CLI options shared by every subcommand.
4
+ *
5
+ * @since 0.3.16-canary.0
6
+ */
7
+ export interface GlobalCliOptions {
8
+ color?: boolean | undefined;
9
+ }
10
+ /**
11
+ * Commander global opts shape validated before mirror prelude (`color` from `--no-color`).
12
+ *
13
+ * @since 0.3.16-canary.0
14
+ */
15
+ export declare const globalCliCommanderOptionsSchema: z.ZodType<GlobalCliOptions>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Coerce a raw Commander positional argument to a string, or undefined when absent.
3
+ *
4
+ * @since 0.3.16-canary.0
5
+ */
6
+ export declare function readOptionalPositionalArg(candidate: unknown): string | undefined;
@@ -0,0 +1,19 @@
1
+ import type { AppError } from "#/core/errors";
2
+ import type { Result } from "#/core/result";
3
+ /**
4
+ * Narrows a `Result`, reporting a failure and setting the exit code, or logging an optional success message.
5
+ *
6
+ * @since 0.3.16-canary.0
7
+ */
8
+ export declare function consumeCliAppError<Value>(outcome: Result<Value, AppError>, options?: {
9
+ readonly successMessage?: string;
10
+ }): outcome is {
11
+ readonly ok: true;
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>;
@@ -0,0 +1,7 @@
1
+ import type { CodefastConfig } from "#/core/config/schema";
2
+ /**
3
+ * Types a `codefast.config.*` object for editor autocomplete and type-checking; returns it unchanged.
4
+ *
5
+ * @since 0.10.0
6
+ */
7
+ export declare function defineConfig(config: CodefastConfig): CodefastConfig;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Types a `codefast.config.*` object for editor autocomplete and type-checking; returns it unchanged.
3
+ *
4
+ * @since 0.10.0
5
+ */
6
+ export function defineConfig(config) {
7
+ return config;
8
+ }
@@ -0,0 +1,18 @@
1
+ import type { CodefastConfig } from "#/core/config/schema";
2
+ import type { FilesystemPort } from "#/core/filesystem/port";
3
+ /**
4
+ * A loaded config together with its schema warnings and the path it was read from.
5
+ *
6
+ * @since 0.3.16-canary.0
7
+ */
8
+ export type LoadConfigPayload = {
9
+ readonly config: CodefastConfig;
10
+ readonly warnings: Array<string>;
11
+ readonly configPath?: string;
12
+ };
13
+ /**
14
+ * Loads and caches the config payload for a directory, sharing one load per resolved path.
15
+ *
16
+ * @since 0.3.16-canary.0
17
+ */
18
+ export declare function loadConfigPayload(startDir: string, fs: FilesystemPort): Promise<LoadConfigPayload>;
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import jiti from "jiti";
3
3
  import { codefastConfigRootSchema } from "#/core/config/schema";
4
+ import { ancestorDirectories } from "#/core/workspace/ancestor-directories";
4
5
  function formatZodError(error, filePath) {
5
6
  const formatted = error.issues
6
7
  .map((issue) => {
@@ -22,8 +23,7 @@ const configJson = "codefast.config.json";
22
23
  const cachedLoads = new Map();
23
24
  function listConfigCandidates(startDir, fs) {
24
25
  const candidates = [];
25
- let current = path.resolve(startDir);
26
- while (true) {
26
+ for (const current of ancestorDirectories(startDir)) {
27
27
  for (const name of configJsPriority) {
28
28
  const candidate = path.join(current, name);
29
29
  if (fs.existsSync(candidate)) {
@@ -34,11 +34,6 @@ function listConfigCandidates(startDir, fs) {
34
34
  if (fs.existsSync(jsonCandidate)) {
35
35
  candidates.push(jsonCandidate);
36
36
  }
37
- const parent = path.dirname(current);
38
- if (parent === current) {
39
- break;
40
- }
41
- current = parent;
42
37
  }
43
38
  return candidates;
44
39
  }