@codefast/cli 0.9.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 (279) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +345 -151
  3. package/dist/arrange/command.d.ts +7 -0
  4. package/dist/arrange/command.js +96 -132
  5. package/dist/arrange/domain/ast/ast-node.d.ts +394 -0
  6. package/dist/arrange/domain/ast/collectors-cn.d.ts +26 -0
  7. package/dist/arrange/domain/ast/collectors-jsx.d.ts +8 -0
  8. package/dist/arrange/domain/ast/collectors-tv.d.ts +34 -0
  9. package/dist/arrange/domain/ast/helpers.d.ts +36 -0
  10. package/dist/arrange/domain/ast/helpers.js +1 -0
  11. package/dist/arrange/domain/ast/simplify-targets.d.ts +22 -0
  12. package/dist/arrange/domain/ast/simplify-targets.js +18 -23
  13. package/dist/arrange/domain/ast/targets.d.ts +20 -0
  14. package/dist/arrange/domain/ast/translator.d.ts +32 -0
  15. package/dist/arrange/{typescript-ast-translator.js → domain/ast/translator.js} +19 -22
  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/group/cli-result.d.ts +7 -0
  25. package/dist/arrange/group/cli-result.js +12 -0
  26. package/dist/arrange/group/cli-schema.d.ts +17 -0
  27. package/dist/arrange/group/cli-schema.js +13 -0
  28. package/dist/arrange/group/output.d.ts +7 -0
  29. package/dist/arrange/group/output.js +10 -0
  30. package/dist/arrange/group/suggest.d.ts +8 -0
  31. package/dist/arrange/inspect/cli-result.d.ts +7 -0
  32. package/dist/arrange/inspect/cli-result.js +8 -0
  33. package/dist/arrange/inspect/cli-schema.d.ts +15 -0
  34. package/dist/arrange/inspect/cli-schema.js +9 -0
  35. package/dist/arrange/inspect/domain/analyze-service.d.ts +18 -0
  36. package/dist/arrange/inspect/output.d.ts +7 -0
  37. package/dist/arrange/inspect/output.js +42 -0
  38. package/dist/arrange/inspect/run.d.ts +10 -0
  39. package/dist/arrange/{analyze.js → inspect/run.js} +2 -2
  40. package/dist/arrange/prepare.d.ts +13 -0
  41. package/dist/arrange/{workspace.js → prepare.js} +5 -8
  42. package/dist/arrange/regroup/cli-result.d.ts +13 -0
  43. package/dist/arrange/regroup/cli-result.js +23 -0
  44. package/dist/arrange/regroup/cli-schema.d.ts +20 -0
  45. package/dist/arrange/regroup/cli-schema.js +14 -0
  46. package/dist/arrange/regroup/output.d.ts +14 -0
  47. package/dist/arrange/regroup/output.js +72 -0
  48. package/dist/arrange/regroup/process-file.d.ts +11 -0
  49. package/dist/arrange/regroup/run.d.ts +11 -0
  50. package/dist/arrange/{sync.js → regroup/run.js} +2 -2
  51. package/dist/arrange/resolve-target.d.ts +10 -0
  52. package/dist/arrange/resolve-target.js +3 -16
  53. package/dist/arrange/scan-target.d.ts +7 -0
  54. package/dist/arrange/simplify/cli-result.d.ts +7 -0
  55. package/dist/arrange/simplify/cli-result.js +8 -0
  56. package/dist/arrange/simplify/cli-schema.d.ts +17 -0
  57. package/dist/arrange/simplify/cli-schema.js +11 -0
  58. package/dist/arrange/simplify/fold-targets.d.ts +13 -0
  59. package/dist/arrange/simplify/fold-targets.js +139 -0
  60. package/dist/arrange/simplify/output.d.ts +7 -0
  61. package/dist/arrange/simplify/output.js +15 -0
  62. package/dist/arrange/simplify/process-file.d.ts +13 -0
  63. package/dist/arrange/simplify/process-file.js +49 -0
  64. package/dist/arrange/simplify/run.d.ts +14 -0
  65. package/dist/arrange/simplify/run.js +38 -0
  66. package/dist/arrange/simplify/variant-classname-probe.d.ts +35 -0
  67. package/dist/arrange/simplify/variant-classname-probe.js +95 -0
  68. package/dist/arrange/source-parse.d.ts +7 -0
  69. package/dist/arrange/source-parse.js +1 -1
  70. package/dist/audit/command.d.ts +8 -0
  71. package/dist/audit/command.js +134 -211
  72. package/dist/audit/comments/cli-result.d.ts +13 -0
  73. package/dist/audit/comments/cli-result.js +22 -0
  74. package/dist/audit/comments/cli-schema.d.ts +19 -0
  75. package/dist/audit/comments/cli-schema.js +13 -0
  76. package/dist/audit/comments/domain/comment-content.d.ts +26 -0
  77. package/dist/audit/comments/domain/comment-dividers.d.ts +62 -0
  78. package/dist/audit/comments/domain/link-references.d.ts +40 -0
  79. package/dist/audit/comments/domain/since-versions.d.ts +26 -0
  80. package/dist/audit/comments/domain/tsdoc-syntax.d.ts +20 -0
  81. package/dist/audit/comments/output.d.ts +7 -0
  82. package/dist/audit/comments/output.js +27 -0
  83. package/dist/audit/comments/prepare.d.ts +16 -0
  84. package/dist/audit/comments/prepare.js +12 -0
  85. package/dist/audit/comments/run.d.ts +17 -0
  86. package/dist/audit/{run-comments.js → comments/run.js} +5 -5
  87. package/dist/audit/display-names/cli-result.d.ts +13 -0
  88. package/dist/audit/display-names/cli-result.js +22 -0
  89. package/dist/audit/display-names/cli-schema.d.ts +18 -0
  90. package/dist/audit/display-names/cli-schema.js +12 -0
  91. package/dist/audit/display-names/domain/display-names.d.ts +11 -0
  92. package/dist/audit/display-names/output.d.ts +7 -0
  93. package/dist/audit/display-names/output.js +21 -0
  94. package/dist/audit/display-names/prepare.d.ts +16 -0
  95. package/dist/audit/display-names/prepare.js +12 -0
  96. package/dist/audit/display-names/run.d.ts +14 -0
  97. package/dist/audit/{run-display-names.js → display-names/run.js} +1 -1
  98. package/dist/audit/domain/types.d.ts +171 -0
  99. package/dist/audit/imports/cli-result.d.ts +13 -0
  100. package/dist/audit/imports/cli-result.js +22 -0
  101. package/dist/audit/imports/cli-schema.d.ts +18 -0
  102. package/dist/audit/imports/cli-schema.js +12 -0
  103. package/dist/audit/imports/domain/import-policy.d.ts +34 -0
  104. package/dist/audit/imports/domain/import-policy.js +140 -0
  105. package/dist/audit/imports/output.d.ts +7 -0
  106. package/dist/audit/imports/output.js +21 -0
  107. package/dist/audit/imports/prepare.d.ts +16 -0
  108. package/dist/audit/imports/prepare.js +12 -0
  109. package/dist/audit/imports/run.d.ts +14 -0
  110. package/dist/audit/{run-react.js → imports/run.js} +15 -5
  111. package/dist/audit/links/cli-result.d.ts +13 -0
  112. package/dist/audit/links/cli-result.js +22 -0
  113. package/dist/audit/links/cli-schema.d.ts +18 -0
  114. package/dist/audit/links/cli-schema.js +12 -0
  115. package/dist/audit/links/domain/markdown-links.d.ts +44 -0
  116. package/dist/audit/links/output.d.ts +7 -0
  117. package/dist/audit/links/output.js +21 -0
  118. package/dist/audit/links/prepare.d.ts +16 -0
  119. package/dist/audit/links/prepare.js +12 -0
  120. package/dist/audit/links/run.d.ts +14 -0
  121. package/dist/audit/{run-links.js → links/run.js} +1 -1
  122. package/dist/audit/prepare.d.ts +31 -0
  123. package/dist/audit/prepare.js +11 -134
  124. package/dist/audit/rtl/cli-result.d.ts +13 -0
  125. package/dist/audit/rtl/cli-result.js +22 -0
  126. package/dist/audit/rtl/cli-schema.d.ts +18 -0
  127. package/dist/audit/rtl/cli-schema.js +12 -0
  128. package/dist/audit/rtl/domain/audit-file.d.ts +7 -0
  129. package/dist/audit/{domain → rtl/domain}/audit-file.js +2 -2
  130. package/dist/audit/rtl/domain/mappings.d.ts +45 -0
  131. package/dist/audit/rtl/domain/tokenize.d.ts +14 -0
  132. package/dist/audit/rtl/output.d.ts +7 -0
  133. package/dist/audit/rtl/output.js +21 -0
  134. package/dist/audit/rtl/prepare.d.ts +13 -0
  135. package/dist/audit/rtl/prepare.js +41 -0
  136. package/dist/audit/rtl/run.d.ts +14 -0
  137. package/dist/audit/{run.js → rtl/run.js} +1 -1
  138. package/dist/bin.d.ts +2 -0
  139. package/dist/cli.d.ts +6 -0
  140. package/dist/core/cli/command-pipeline.d.ts +91 -0
  141. package/dist/core/cli/command-pipeline.js +83 -0
  142. package/dist/core/cli/format-error.d.ts +7 -0
  143. package/dist/core/cli/global-options.d.ts +15 -0
  144. package/dist/core/cli/global-options.js +1 -1
  145. package/dist/core/cli/positional.d.ts +6 -0
  146. package/dist/core/cli/resolve-root.d.ts +9 -0
  147. package/dist/core/cli/resolve-root.js +16 -0
  148. package/dist/core/cli/result-handle.d.ts +13 -0
  149. package/dist/core/cli/result-handle.js +0 -13
  150. package/dist/core/config/define-config.d.ts +7 -0
  151. package/dist/core/config/define-config.js +8 -0
  152. package/dist/core/config/loader.d.ts +18 -0
  153. package/dist/core/config/loader.js +2 -7
  154. package/dist/core/config/schema.d.ts +99 -0
  155. package/dist/core/config/schema.js +8 -86
  156. package/dist/core/config/warnings.d.ts +6 -0
  157. package/dist/core/config.d.ts +12 -0
  158. package/dist/core/errors.d.ts +25 -0
  159. package/dist/core/exit-codes.d.ts +18 -0
  160. package/dist/core/filesystem/filesystem.d.ts +44 -0
  161. package/dist/core/filesystem/node.d.ts +7 -0
  162. package/dist/core/filesystem/node.js +2 -1
  163. package/dist/core/glob.d.ts +19 -0
  164. package/dist/core/logger.d.ts +9 -0
  165. package/dist/core/result.d.ts +30 -0
  166. package/dist/core/schema-parse.d.ts +9 -0
  167. package/dist/core/source-text-edit.d.ts +47 -0
  168. package/dist/core/source-text-edit.js +22 -0
  169. package/dist/core/verbose-diagnostics.d.ts +6 -0
  170. package/dist/core/workspace/ancestor-directories.d.ts +12 -0
  171. package/dist/core/workspace/ancestor-directories.js +30 -0
  172. package/dist/core/workspace/markdown-walk.d.ts +7 -0
  173. package/dist/core/workspace/markdown-walk.js +2 -20
  174. package/dist/core/workspace/package-version.d.ts +9 -0
  175. package/dist/core/workspace/package-version.js +8 -12
  176. package/dist/core/workspace/resolver.d.ts +39 -0
  177. package/dist/core/workspace/resolver.js +58 -75
  178. package/dist/core/workspace/skip-directories.d.ts +6 -0
  179. package/dist/core/workspace/source-walk.d.ts +16 -0
  180. package/dist/core/workspace/source-walk.js +2 -19
  181. package/dist/core/workspace/typescript-walk.d.ts +7 -0
  182. package/dist/core/workspace/typescript-walk.js +2 -23
  183. package/dist/core/workspace/walk-files.d.ts +7 -0
  184. package/dist/core/workspace/walk-files.js +27 -0
  185. package/dist/core/workspace/well-known-files.d.ts +18 -0
  186. package/dist/core/workspace/well-known-files.js +18 -0
  187. package/dist/index.d.ts +6 -0
  188. package/dist/index.js +5 -0
  189. package/dist/mirror/cli-result.d.ts +13 -0
  190. package/dist/mirror/cli-schema.d.ts +8 -0
  191. package/dist/mirror/cli-schema.js +1 -1
  192. package/dist/mirror/command.d.ts +7 -0
  193. package/dist/mirror/command.js +31 -60
  194. package/dist/mirror/dist-filesystem-node.d.ts +8 -0
  195. package/dist/mirror/{dist-filesystem-impl.js → dist-filesystem-node.js} +1 -1
  196. package/dist/mirror/domain/constants.d.ts +18 -0
  197. package/dist/mirror/domain/constants.js +0 -12
  198. package/dist/mirror/domain/dirent-guard.d.ts +10 -0
  199. package/dist/mirror/domain/dist-filesystem.d.ts +9 -0
  200. package/dist/mirror/domain/errors.d.ts +24 -0
  201. package/dist/mirror/domain/exports.d.ts +36 -0
  202. package/dist/mirror/domain/package-display-name.d.ts +8 -0
  203. package/dist/mirror/domain/path-normalizer.d.ts +6 -0
  204. package/dist/mirror/domain/types.d.ts +188 -0
  205. package/dist/mirror/output.d.ts +21 -0
  206. package/dist/mirror/output.js +126 -1
  207. package/dist/mirror/package-path.d.ts +19 -0
  208. package/dist/mirror/prepare.d.ts +15 -0
  209. package/dist/mirror/prepare.js +6 -10
  210. package/dist/mirror/run.d.ts +12 -0
  211. package/dist/mirror/{sync.js → run.js} +5 -3
  212. package/dist/mirror/supplement-exports.d.ts +27 -0
  213. package/dist/mirror/supplement-exports.js +2 -2
  214. package/dist/mirror/sync-workspace-package.d.ts +9 -0
  215. package/dist/mirror/sync-workspace-package.js +4 -4
  216. package/dist/mirror/write-exports.d.ts +15 -0
  217. package/dist/pack-slim/cli-result.d.ts +13 -0
  218. package/dist/pack-slim/cli-schema.d.ts +17 -0
  219. package/dist/pack-slim/cli-schema.js +1 -1
  220. package/dist/pack-slim/command.d.ts +7 -0
  221. package/dist/pack-slim/command.js +31 -66
  222. package/dist/pack-slim/domain/transform.d.ts +69 -0
  223. package/dist/pack-slim/domain/types.d.ts +46 -0
  224. package/dist/pack-slim/output.d.ts +15 -0
  225. package/dist/pack-slim/prepare.d.ts +21 -0
  226. package/dist/pack-slim/prepare.js +14 -0
  227. package/dist/pack-slim/run.d.ts +23 -0
  228. package/dist/pack-slim/{sync.js → run.js} +4 -4
  229. package/dist/pack-slim/working-tree.d.ts +20 -0
  230. package/dist/tag/cli-result.d.ts +13 -0
  231. package/dist/tag/cli-result.js +14 -1
  232. package/dist/tag/cli-schema.d.ts +8 -0
  233. package/dist/tag/cli-schema.js +3 -4
  234. package/dist/tag/command.d.ts +7 -0
  235. package/dist/tag/command.js +33 -60
  236. package/dist/tag/domain/skip-filter.d.ts +13 -0
  237. package/dist/tag/domain/skip-filter.js +29 -0
  238. package/dist/tag/domain/types.d.ts +131 -0
  239. package/dist/tag/domain/version-summary.d.ts +13 -0
  240. package/dist/tag/domain/version-summary.js +24 -0
  241. package/dist/tag/output.d.ts +17 -0
  242. package/dist/tag/output.js +6 -9
  243. package/dist/tag/prepare.d.ts +13 -0
  244. package/dist/tag/prepare.js +4 -4
  245. package/dist/tag/run.d.ts +10 -0
  246. package/dist/tag/{sync.js → run.js} +20 -50
  247. package/dist/tag/target/candidates.d.ts +8 -0
  248. package/dist/tag/{target-candidates.js → target/candidates.js} +2 -2
  249. package/dist/tag/target/resolve-path.d.ts +10 -0
  250. package/dist/tag/target/runner.d.ts +8 -0
  251. package/dist/tag/{target-runner.js → target/runner.js} +2 -2
  252. package/dist/tag/writer/since-writer.d.ts +32 -0
  253. package/dist/tag/writer/version-resolver.d.ts +7 -0
  254. package/package.json +21 -2
  255. package/dist/arrange/cli-schema.js +0 -34
  256. package/dist/arrange/output.js +0 -127
  257. package/dist/arrange/simplify-process-file.js +0 -30
  258. package/dist/arrange/simplify-sync.js +0 -30
  259. package/dist/audit/cli-schema.js +0 -66
  260. package/dist/audit/domain/react-imports.js +0 -91
  261. package/dist/audit/output.js +0 -213
  262. package/dist/mirror/sync-reporter.js +0 -124
  263. package/dist/mirror/sync-types.js +0 -1
  264. /package/dist/arrange/{suggest.js → group/suggest.js} +0 -0
  265. /package/dist/arrange/{domain → inspect/domain}/analyze-service.js +0 -0
  266. /package/dist/arrange/{process-file.js → regroup/process-file.js} +0 -0
  267. /package/dist/audit/{domain → comments/domain}/comment-content.js +0 -0
  268. /package/dist/audit/{domain → comments/domain}/comment-dividers.js +0 -0
  269. /package/dist/audit/{domain → comments/domain}/link-references.js +0 -0
  270. /package/dist/audit/{domain → comments/domain}/since-versions.js +0 -0
  271. /package/dist/audit/{domain → comments/domain}/tsdoc-syntax.js +0 -0
  272. /package/dist/audit/{domain → display-names/domain}/display-names.js +0 -0
  273. /package/dist/audit/{domain → links/domain}/markdown-links.js +0 -0
  274. /package/dist/audit/{domain → rtl/domain}/mappings.js +0 -0
  275. /package/dist/audit/{domain → rtl/domain}/tokenize.js +0 -0
  276. /package/dist/core/filesystem/{port.js → filesystem.js} +0 -0
  277. /package/dist/tag/{resolve-target-path.js → target/resolve-path.js} +0 -0
  278. /package/dist/tag/{since-writer.js → writer/since-writer.js} +0 -0
  279. /package/dist/tag/{version-resolver.js → writer/version-resolver.js} +0 -0
@@ -0,0 +1,12 @@
1
+ import { prepareRepoRootAudit } from "#/audit/prepare";
2
+ /**
3
+ * Loads config and resolves the scan target for `audit imports`.
4
+ *
5
+ * @remarks Defaults to the repo root: the import policy is repo-wide, and generated or vendored
6
+ * trees are already excluded by the shared walk.
7
+ *
8
+ * @since 0.10.0
9
+ */
10
+ export async function prepareImportsAudit(fs, args) {
11
+ return prepareRepoRootAudit(fs, args, (config) => config.audit?.imports?.allowlist ?? []);
12
+ }
@@ -0,0 +1,14 @@
1
+ import type { ImportsAuditResult } from "#/audit/domain/types";
2
+ import { AppError } from "#/core/errors";
3
+ import type { Filesystem } from "#/core/filesystem/filesystem";
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: Filesystem, 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/imports/domain/import-policy";
3
+ import { auditImportPolicySource } from "#/audit/imports/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,13 @@
1
+ import type { LinkAuditResult } from "#/audit/domain/types";
2
+ /**
3
+ * Exit `1` when any non-allowlisted broken link remains.
4
+ *
5
+ * @since 0.5.0
6
+ */
7
+ export declare function exitCodeForLinkAuditResult(result: LinkAuditResult): number;
8
+ /**
9
+ * Machine-readable link audit summary for `--json`.
10
+ *
11
+ * @since 0.5.0
12
+ */
13
+ export declare function formatLinkAuditJsonOutput(result: LinkAuditResult, rootDir: string): string;
@@ -0,0 +1,22 @@
1
+ import { CLI_EXIT_GENERAL_ERROR, CLI_EXIT_SUCCESS } from "#/core/exit-codes";
2
+ /**
3
+ * Exit `1` when any non-allowlisted broken link remains.
4
+ *
5
+ * @since 0.5.0
6
+ */
7
+ export function exitCodeForLinkAuditResult(result) {
8
+ return result.breakageCount > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
9
+ }
10
+ /**
11
+ * Machine-readable link audit summary for `--json`.
12
+ *
13
+ * @since 0.5.0
14
+ */
15
+ export function formatLinkAuditJsonOutput(result, rootDir) {
16
+ return JSON.stringify({
17
+ schemaVersion: 1,
18
+ ok: result.breakageCount === 0,
19
+ cwd: rootDir,
20
+ result,
21
+ });
22
+ }
@@ -0,0 +1,18 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * Resolved request for a single link audit run.
4
+ *
5
+ * @since 0.5.0
6
+ */
7
+ export type LinkAuditRunRequest = {
8
+ readonly rootDir: string;
9
+ readonly targetPath: string;
10
+ readonly allowlist?: ReadonlyArray<string> | undefined;
11
+ readonly json: boolean;
12
+ };
13
+ /**
14
+ * Zod schema for {@link LinkAuditRunRequest}.
15
+ *
16
+ * @since 0.5.0
17
+ */
18
+ export declare const linkAuditRunRequestSchema: z.ZodType<LinkAuditRunRequest>;
@@ -0,0 +1,12 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * Zod schema for {@link LinkAuditRunRequest}.
4
+ *
5
+ * @since 0.5.0
6
+ */
7
+ export const linkAuditRunRequestSchema = z.object({
8
+ rootDir: z.string().min(1),
9
+ targetPath: z.string().min(1),
10
+ allowlist: z.array(z.string()).optional(),
11
+ json: z.boolean(),
12
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Extracts the two things a markdown cross-reference can get wrong: where it points and what it lands on.
3
+ */
4
+ /**
5
+ * One `[text](target)` whose target is a path in this repository.
6
+ *
7
+ * @since 0.5.0
8
+ */
9
+ type MarkdownLinkReference = {
10
+ readonly line: number;
11
+ /** The path as written, with any fragment stripped. Empty when the link is fragment-only. */
12
+ readonly targetPath: string;
13
+ /** The `#fragment`, without the hash, or `null`. */
14
+ readonly anchor: string | null;
15
+ };
16
+ /**
17
+ * The anchors a document offers, and the references it makes.
18
+ *
19
+ * @since 0.5.0
20
+ */
21
+ export type MarkdownLinkScan = {
22
+ readonly references: ReadonlyArray<MarkdownLinkReference>;
23
+ readonly anchors: ReadonlySet<string>;
24
+ };
25
+ /**
26
+ * The anchor ids a rendered document exposes: explicit `<a id>` targets plus every heading's slug.
27
+ *
28
+ * @remarks Slugging matches GitHub's — lowercase, drop everything that is not a letter, number, space
29
+ * or hyphen, then hyphenate spaces. Duplicate headings get a `-1` suffix there; this returns the base
30
+ * only, so a link to the second copy reads as dangling rather than being silently accepted.
31
+ *
32
+ * @since 0.5.0
33
+ */
34
+ export declare function collectMarkdownAnchors(content: string): Set<string>;
35
+ /**
36
+ * Every repo-local link a document makes, with the anchors it offers.
37
+ *
38
+ * @remarks Fenced code is stripped first: a fence showing a link is an example, not a reference, and
39
+ * checking it would make the audit fail on documentation that is doing its job.
40
+ *
41
+ * @since 0.5.0
42
+ */
43
+ export declare function scanMarkdownLinks(content: string): MarkdownLinkScan;
44
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { LinkAuditResult } from "#/audit/domain/types";
2
+ /**
3
+ * Human-readable link audit report.
4
+ *
5
+ * @since 0.5.0
6
+ */
7
+ export declare function presentLinkAuditResult(result: LinkAuditResult): void;
@@ -0,0 +1,21 @@
1
+ import { logger } from "#/core/logger";
2
+ /**
3
+ * Human-readable link audit report.
4
+ *
5
+ * @since 0.5.0
6
+ */
7
+ export function presentLinkAuditResult(result) {
8
+ for (const file of result.files) {
9
+ logger.out(`\n${file.relativePath}`);
10
+ for (const { line, raw, reason } of file.breakages) {
11
+ logger.out(` ${line}: ${raw} → ${reason}`);
12
+ }
13
+ }
14
+ const allowlistSuffix = result.allowlistedCount > 0 ? ` (${result.allowlistedCount} allowlisted)` : "";
15
+ if (result.breakageCount > 0) {
16
+ logger.out(`\n✖ ${result.breakageCount} broken link(s)${allowlistSuffix}`);
17
+ }
18
+ else {
19
+ logger.out(`✓ ${result.linkCount} repo-local link(s) across ${result.scannedFileCount} document(s) all resolve${allowlistSuffix}`);
20
+ }
21
+ }
@@ -0,0 +1,16 @@
1
+ import type { AuditCommandPrelude } from "#/audit/prepare";
2
+ import type { AppError } from "#/core/errors";
3
+ import type { Filesystem } from "#/core/filesystem/filesystem";
4
+ import type { Result } from "#/core/result";
5
+ /**
6
+ * Loads config and resolves the scan target for `audit links`.
7
+ *
8
+ * @remarks Defaults to the repo root rather than a configured path: a link audit that only covers one
9
+ * package cannot see the cross-package references that are the ones most likely to rot.
10
+ *
11
+ * @since 0.5.0
12
+ */
13
+ export declare function prepareLinkAudit(fs: Filesystem, args: {
14
+ readonly currentWorkingDirectory: string;
15
+ readonly rawTarget: string | undefined;
16
+ }): Promise<Result<AuditCommandPrelude, AppError>>;
@@ -0,0 +1,12 @@
1
+ import { prepareRepoRootAudit } from "#/audit/prepare";
2
+ /**
3
+ * Loads config and resolves the scan target for `audit links`.
4
+ *
5
+ * @remarks Defaults to the repo root rather than a configured path: a link audit that only covers one
6
+ * package cannot see the cross-package references that are the ones most likely to rot.
7
+ *
8
+ * @since 0.5.0
9
+ */
10
+ export async function prepareLinkAudit(fs, args) {
11
+ return prepareRepoRootAudit(fs, args, (config) => config.audit?.links?.allowlist ?? []);
12
+ }
@@ -0,0 +1,14 @@
1
+ import type { LinkAuditResult } from "#/audit/domain/types";
2
+ import { AppError } from "#/core/errors";
3
+ import type { Filesystem } from "#/core/filesystem/filesystem";
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: Filesystem, args: {
11
+ readonly rootDir: string;
12
+ readonly targetPath: string;
13
+ readonly allowlist: ReadonlyArray<string>;
14
+ }): Result<LinkAuditResult, AppError>;
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { collectMarkdownAnchors, scanMarkdownLinks } from "#/audit/domain/markdown-links";
2
+ import { collectMarkdownAnchors, scanMarkdownLinks } from "#/audit/links/domain/markdown-links";
3
3
  import { AppError, messageFrom } from "#/core/errors";
4
4
  import { err, ok } from "#/core/result";
5
5
  import { walkMarkdownFiles } from "#/core/workspace/markdown-walk";
@@ -0,0 +1,31 @@
1
+ import type { CodefastConfig } from "#/core/config/schema";
2
+ import { AppError } from "#/core/errors";
3
+ import type { Filesystem } from "#/core/filesystem/filesystem";
4
+ import type { Result } from "#/core/result";
5
+ /**
6
+ * Shared prelude for an audit: repo root and the canonicalized scan target with its allowlist.
7
+ *
8
+ * @since 0.5.0-canary.6
9
+ */
10
+ export type AuditCommandPrelude = {
11
+ readonly rootDir: string;
12
+ readonly targetPath: string;
13
+ readonly allowlist: ReadonlyArray<string>;
14
+ };
15
+ /**
16
+ * Resolves a path that may be absolute or relative to `rootDir`.
17
+ *
18
+ * @since 0.5.0-canary.6
19
+ */
20
+ export declare function resolveRepoRelativePath(rootDir: string, maybeRelative: string): string;
21
+ /**
22
+ * Loads config and resolves the repo root as the scan target, taking the allowlist the caller selects.
23
+ *
24
+ * @remarks Every repo-wide audit shares this prelude; only the config key its allowlist comes from differs.
25
+ *
26
+ * @since 0.11.0
27
+ */
28
+ export declare function prepareRepoRootAudit(fs: Filesystem, args: {
29
+ readonly currentWorkingDirectory: string;
30
+ readonly rawTarget: string | undefined;
31
+ }, selectAllowlist: (config: CodefastConfig) => ReadonlyArray<string>): Promise<Result<AuditCommandPrelude, AppError>>;
@@ -1,149 +1,27 @@
1
- import { resolveRepoRelativePath } from "#/audit/cli-schema";
1
+ import path from "node:path";
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
- * Loads config and resolves the scan target for `audit rtl`.
7
+ * Resolves a path that may be absolute or relative to `rootDir`.
8
8
  *
9
9
  * @since 0.5.0-canary.6
10
10
  */
11
- export async function prepareRtlAudit(fs, args) {
12
- let rootDir;
13
- try {
14
- // Realpath so allowlist keys (`path.relative(rootDir, file)`) stay stable when cwd is a symlink.
15
- rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
16
- }
17
- catch (caughtError) {
18
- return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
19
- }
20
- const loadedOutcome = await loadCodefastConfig(rootDir, fs);
21
- if (!loadedOutcome.ok) {
22
- return loadedOutcome;
23
- }
24
- const { config } = loadedOutcome.value;
25
- const rtlConfig = config.audit?.rtl ?? {};
26
- const targetFromCli = args.rawTarget;
27
- const targetFromConfig = rtlConfig.target;
28
- const resolvedTargetInput = targetFromCli ?? targetFromConfig;
29
- if (resolvedTargetInput === undefined) {
30
- return err(new AppError("VALIDATION_ERROR", "Missing scan target: pass a path argument or set audit.rtl.target in codefast.config"));
31
- }
32
- const targetPath = resolveRepoRelativePath(targetFromCli !== undefined ? args.currentWorkingDirectory : rootDir, resolvedTargetInput);
33
- if (!fs.existsSync(targetPath)) {
34
- return err(new AppError("NOT_FOUND", `Not found: ${targetPath}`));
35
- }
36
- return ok({
37
- rootDir,
38
- targetPath: fs.canonicalPathSync(targetPath),
39
- allowlist: rtlConfig.allowlist ?? [],
40
- });
41
- }
42
- /**
43
- * Loads config and resolves the scan target for `audit links`.
44
- *
45
- * @remarks Defaults to the repo root rather than a configured path: a link audit that only covers one
46
- * package cannot see the cross-package references that are the ones most likely to rot.
47
- *
48
- * @since 0.5.0
49
- */
50
- export async function prepareLinkAudit(fs, args) {
51
- let rootDir;
52
- try {
53
- rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
54
- }
55
- catch (caughtError) {
56
- return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
57
- }
58
- const loadedOutcome = await loadCodefastConfig(rootDir, fs);
59
- if (!loadedOutcome.ok) {
60
- return loadedOutcome;
61
- }
62
- const linksConfig = loadedOutcome.value.config.audit?.links ?? {};
63
- const targetPath = args.rawTarget === undefined ? rootDir : resolveRepoRelativePath(args.currentWorkingDirectory, args.rawTarget);
64
- if (!fs.existsSync(targetPath)) {
65
- return err(new AppError("NOT_FOUND", `Not found: ${targetPath}`));
66
- }
67
- return ok({
68
- rootDir,
69
- targetPath: fs.canonicalPathSync(targetPath),
70
- allowlist: linksConfig.allowlist ?? [],
71
- });
72
- }
73
- /**
74
- * Loads config and resolves the scan target for `audit react`.
75
- *
76
- * @remarks Defaults to the repo root: the import policy is repo-wide, and generated or vendored
77
- * trees are already excluded by the shared walk.
78
- *
79
- * @since 0.8.0
80
- */
81
- export async function prepareReactAudit(fs, args) {
82
- let rootDir;
83
- try {
84
- rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
85
- }
86
- catch (caughtError) {
87
- return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
88
- }
89
- const loadedOutcome = await loadCodefastConfig(rootDir, fs);
90
- if (!loadedOutcome.ok) {
91
- return loadedOutcome;
92
- }
93
- const reactConfig = loadedOutcome.value.config.audit?.react ?? {};
94
- const targetPath = args.rawTarget === undefined ? rootDir : resolveRepoRelativePath(args.currentWorkingDirectory, args.rawTarget);
95
- if (!fs.existsSync(targetPath)) {
96
- return err(new AppError("NOT_FOUND", `Not found: ${targetPath}`));
97
- }
98
- return ok({
99
- rootDir,
100
- targetPath: fs.canonicalPathSync(targetPath),
101
- allowlist: reactConfig.allowlist ?? [],
102
- });
103
- }
104
- /**
105
- * Loads config and resolves the scan target for `audit comments`.
106
- *
107
- * @remarks Defaults to the repo root: a divider convention that only holds inside one package
108
- * is not a convention.
109
- *
110
- * @since 0.6.0
111
- */
112
- export async function prepareCommentAudit(fs, args) {
113
- let rootDir;
114
- try {
115
- rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
116
- }
117
- catch (caughtError) {
118
- return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
119
- }
120
- const loadedOutcome = await loadCodefastConfig(rootDir, fs);
121
- if (!loadedOutcome.ok) {
122
- return loadedOutcome;
123
- }
124
- const commentsConfig = loadedOutcome.value.config.audit?.comments ?? {};
125
- const targetPath = args.rawTarget === undefined ? rootDir : resolveRepoRelativePath(args.currentWorkingDirectory, args.rawTarget);
126
- if (!fs.existsSync(targetPath)) {
127
- return err(new AppError("NOT_FOUND", `Not found: ${targetPath}`));
128
- }
129
- return ok({
130
- rootDir,
131
- targetPath: fs.canonicalPathSync(targetPath),
132
- allowlist: commentsConfig.allowlist ?? [],
133
- });
11
+ export function resolveRepoRelativePath(rootDir, maybeRelative) {
12
+ return path.isAbsolute(maybeRelative) ? path.resolve(maybeRelative) : path.resolve(rootDir, maybeRelative);
134
13
  }
135
14
  /**
136
- * Loads config and resolves the scan target for `audit display-names`.
15
+ * Loads config and resolves the repo root as the scan target, taking the allowlist the caller selects.
137
16
  *
138
- * @remarks Defaults to the repo root: a display name collides across packages, so the convention
139
- * has to hold across them.
17
+ * @remarks Every repo-wide audit shares this prelude; only the config key its allowlist comes from differs.
140
18
  *
141
- * @since 0.9.0
19
+ * @since 0.11.0
142
20
  */
143
- export async function prepareDisplayNameAudit(fs, args) {
21
+ export async function prepareRepoRootAudit(fs, args, selectAllowlist) {
144
22
  let rootDir;
145
23
  try {
146
- rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
24
+ rootDir = fs.canonicalPathSync(resolveProjectRoot(args.currentWorkingDirectory, fs).rootDir);
147
25
  }
148
26
  catch (caughtError) {
149
27
  return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
@@ -152,7 +30,6 @@ export async function prepareDisplayNameAudit(fs, args) {
152
30
  if (!loadedOutcome.ok) {
153
31
  return loadedOutcome;
154
32
  }
155
- const displayNamesConfig = loadedOutcome.value.config.audit?.displayNames ?? {};
156
33
  const targetPath = args.rawTarget === undefined ? rootDir : resolveRepoRelativePath(args.currentWorkingDirectory, args.rawTarget);
157
34
  if (!fs.existsSync(targetPath)) {
158
35
  return err(new AppError("NOT_FOUND", `Not found: ${targetPath}`));
@@ -160,6 +37,6 @@ export async function prepareDisplayNameAudit(fs, args) {
160
37
  return ok({
161
38
  rootDir,
162
39
  targetPath: fs.canonicalPathSync(targetPath),
163
- allowlist: displayNamesConfig.allowlist ?? [],
40
+ allowlist: selectAllowlist(loadedOutcome.value.config),
164
41
  });
165
42
  }
@@ -0,0 +1,13 @@
1
+ import type { RtlAuditResult } from "#/audit/domain/types";
2
+ /**
3
+ * Exit `1` when any non-allowlisted violation remains.
4
+ *
5
+ * @since 0.5.0-canary.6
6
+ */
7
+ export declare function exitCodeForRtlAuditResult(result: RtlAuditResult): number;
8
+ /**
9
+ * Machine-readable RTL audit summary for `--json`.
10
+ *
11
+ * @since 0.5.0-canary.6
12
+ */
13
+ export declare function formatRtlAuditJsonOutput(result: RtlAuditResult, rootDir: string): string;
@@ -0,0 +1,22 @@
1
+ import { CLI_EXIT_GENERAL_ERROR, CLI_EXIT_SUCCESS } from "#/core/exit-codes";
2
+ /**
3
+ * Exit `1` when any non-allowlisted violation remains.
4
+ *
5
+ * @since 0.5.0-canary.6
6
+ */
7
+ export function exitCodeForRtlAuditResult(result) {
8
+ return result.violationCount > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
9
+ }
10
+ /**
11
+ * Machine-readable RTL audit summary for `--json`.
12
+ *
13
+ * @since 0.5.0-canary.6
14
+ */
15
+ export function formatRtlAuditJsonOutput(result, rootDir) {
16
+ return JSON.stringify({
17
+ schemaVersion: 1,
18
+ ok: result.violationCount === 0,
19
+ cwd: rootDir,
20
+ result,
21
+ });
22
+ }
@@ -0,0 +1,18 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * Resolved request for a single RTL audit run.
4
+ *
5
+ * @since 0.5.0-canary.6
6
+ */
7
+ export type RtlAuditRunRequest = {
8
+ readonly rootDir: string;
9
+ readonly targetPath: string;
10
+ readonly allowlist?: ReadonlyArray<string> | undefined;
11
+ readonly json: boolean;
12
+ };
13
+ /**
14
+ * Zod schema for {@link RtlAuditRunRequest}.
15
+ *
16
+ * @since 0.5.0-canary.6
17
+ */
18
+ export declare const rtlAuditRunRequestSchema: z.ZodType<RtlAuditRunRequest>;
@@ -0,0 +1,12 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * Zod schema for {@link RtlAuditRunRequest}.
4
+ *
5
+ * @since 0.5.0-canary.6
6
+ */
7
+ export const rtlAuditRunRequestSchema = z.object({
8
+ rootDir: z.string().min(1),
9
+ targetPath: z.string().min(1),
10
+ allowlist: z.array(z.string()).optional(),
11
+ json: z.boolean(),
12
+ });
@@ -0,0 +1,7 @@
1
+ import type { RtlViolation } from "#/audit/domain/types";
2
+ /**
3
+ * Detects physical-direction Tailwind classes that should be logical or rtl:-paired.
4
+ *
5
+ * @since 0.5.0-canary.6
6
+ */
7
+ export declare function auditFileContent(content: string): Array<RtlViolation>;
@@ -1,5 +1,5 @@
1
- import { DIRECTION_RESOLVED_VARIANT, PHYSICAL_SIDE_VARIANT, RTL_MAPPINGS, RTL_REVERSE_MAPPINGS, RTL_SWAP_MAPPINGS, RTL_TRANSLATE_X_MAPPINGS, SLIDE_PREFIXES, } from "#/audit/domain/mappings";
2
- import { collectTokens } from "#/audit/domain/tokenize";
1
+ import { DIRECTION_RESOLVED_VARIANT, PHYSICAL_SIDE_VARIANT, RTL_MAPPINGS, RTL_REVERSE_MAPPINGS, RTL_SWAP_MAPPINGS, RTL_TRANSLATE_X_MAPPINGS, SLIDE_PREFIXES, } from "#/audit/rtl/domain/mappings";
2
+ import { collectTokens } from "#/audit/rtl/domain/tokenize";
3
3
  /**
4
4
  * A token satisfies an "rtl companion" requirement when the same file has a
5
5
  * token whose variants include `rtl` and whose value matches. Variant order
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Physical → logical replacements. Order matters: negative before positive,
3
+ * specific corners before general edges, with-value before bare.
4
+ *
5
+ * @since 0.5.0-canary.6
6
+ */
7
+ export declare const RTL_MAPPINGS: ReadonlyArray<readonly [string, string]>;
8
+ /**
9
+ * translate-x has no logical equivalent — it needs an rtl:-negated twin.
10
+ *
11
+ * @since 0.5.0-canary.6
12
+ */
13
+ export declare const RTL_TRANSLATE_X_MAPPINGS: ReadonlyArray<readonly [string, string]>;
14
+ /**
15
+ * Classes that need an rtl:*-reverse companion.
16
+ *
17
+ * @since 0.5.0-canary.6
18
+ */
19
+ export declare const RTL_REVERSE_MAPPINGS: ReadonlyArray<readonly [string, string]>;
20
+ /**
21
+ * Classes that need an rtl: companion with the swapped value.
22
+ *
23
+ * @since 0.5.0-canary.6
24
+ */
25
+ export declare const RTL_SWAP_MAPPINGS: ReadonlyArray<readonly [string, string]>;
26
+ /**
27
+ * Anything anchored to a physical side variant stays physical: Radix resolves
28
+ * `side` per direction, and a border/position/slide tied to that side must follow it.
29
+ *
30
+ * @since 0.5.0-canary.6
31
+ */
32
+ export declare const PHYSICAL_SIDE_VARIANT: RegExp;
33
+ /**
34
+ * Slide animations under direction-resolved contexts are correct as-is:
35
+ * Radix flips `side`/`motion` values itself under DirectionProvider.
36
+ *
37
+ * @since 0.5.0-canary.6
38
+ */
39
+ export declare const DIRECTION_RESOLVED_VARIANT: RegExp;
40
+ /**
41
+ * The physical slide-animation class prefixes the RTL audit inspects.
42
+ *
43
+ * @since 0.5.0-canary.6
44
+ */
45
+ export declare const SLIDE_PREFIXES: readonly ["slide-in-from-left", "slide-in-from-right", "slide-out-to-left", "slide-out-to-right"];
@@ -0,0 +1,14 @@
1
+ import type { RtlClassToken } from "#/audit/domain/types";
2
+ /**
3
+ * Splits a class token into [variant, value, modifier], colon/slash-aware of
4
+ * brackets and parens (arbitrary values like data-[side=left] or calc(...)).
5
+ *
6
+ * @since 0.5.0-canary.6
7
+ */
8
+ export declare function splitClassName(token: string): [string | null, string, string | null];
9
+ /**
10
+ * Collects the parsed class tokens from every string literal in a file's content.
11
+ *
12
+ * @since 0.5.0-canary.6
13
+ */
14
+ export declare function collectTokens(content: string): Array<RtlClassToken>;
@@ -0,0 +1,7 @@
1
+ import type { RtlAuditResult } from "#/audit/domain/types";
2
+ /**
3
+ * Human-readable RTL audit report (matches the former packages/ui script shape).
4
+ *
5
+ * @since 0.5.0-canary.6
6
+ */
7
+ export declare function presentRtlAuditResult(result: RtlAuditResult): void;
@@ -0,0 +1,21 @@
1
+ import { logger } from "#/core/logger";
2
+ /**
3
+ * Human-readable RTL audit report (matches the former packages/ui script shape).
4
+ *
5
+ * @since 0.5.0-canary.6
6
+ */
7
+ export function presentRtlAuditResult(result) {
8
+ for (const file of result.files) {
9
+ logger.out(`\n${file.relativePath}`);
10
+ for (const { line, raw, suggestion } of file.violations) {
11
+ logger.out(` ${line}: ${raw} → ${suggestion}`);
12
+ }
13
+ }
14
+ const allowlistSuffix = result.allowlistedCount > 0 ? ` (${result.allowlistedCount} allowlisted)` : "";
15
+ if (result.violationCount > 0) {
16
+ logger.out(`\n✖ ${result.violationCount} RTL violation(s)${allowlistSuffix}`);
17
+ }
18
+ else {
19
+ logger.out(`✓ No physical-direction classes outside the allowlist${allowlistSuffix}`);
20
+ }
21
+ }