@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.
- package/CHANGELOG.md +596 -0
- package/LICENSE +1 -1
- package/README.md +335 -129
- package/dist/arrange/analyze.d.ts +10 -0
- package/dist/arrange/cli-schema.d.ts +50 -0
- package/dist/arrange/command.d.ts +7 -0
- package/dist/arrange/domain/analyze-service.d.ts +18 -0
- package/dist/arrange/domain/ast/ast-node.d.ts +394 -0
- package/dist/arrange/domain/ast/collectors-cn.d.ts +26 -0
- package/dist/arrange/domain/ast/collectors-jsx.d.ts +8 -0
- package/dist/arrange/domain/ast/collectors-tv.d.ts +34 -0
- package/dist/arrange/domain/ast/helpers.d.ts +36 -0
- package/dist/arrange/domain/ast/helpers.js +1 -0
- package/dist/arrange/domain/ast/simplify-targets.d.ts +22 -0
- package/dist/arrange/domain/ast/targets.d.ts +20 -0
- package/dist/arrange/domain/constants.d.ts +111 -0
- package/dist/arrange/domain/grouping-service.d.ts +100 -0
- package/dist/arrange/domain/grouping.d.ts +21 -0
- package/dist/arrange/domain/imports.d.ts +14 -0
- package/dist/arrange/domain/source-text-formatters.d.ts +33 -0
- package/dist/arrange/domain/tailwind-token.d.ts +24 -0
- package/dist/arrange/domain/token-classifier.d.ts +47 -0
- package/dist/arrange/domain/types.d.ts +208 -0
- package/dist/arrange/output.d.ts +26 -0
- package/dist/arrange/process-file.d.ts +11 -0
- package/dist/arrange/resolve-target.d.ts +10 -0
- package/dist/arrange/resolve-target.js +3 -16
- package/dist/arrange/scan-target.d.ts +7 -0
- package/dist/arrange/simplify-process-file.d.ts +11 -0
- package/dist/arrange/simplify-sync.d.ts +13 -0
- package/dist/arrange/source-parse.d.ts +7 -0
- package/dist/arrange/suggest.d.ts +8 -0
- package/dist/arrange/sync.d.ts +11 -0
- package/dist/arrange/typescript-ast-translator.d.ts +31 -0
- package/dist/arrange/workspace.d.ts +13 -0
- package/dist/arrange/workspace.js +2 -2
- package/dist/audit/cli-schema.d.ts +93 -0
- package/dist/audit/cli-schema.js +14 -3
- package/dist/audit/command.d.ts +8 -0
- package/dist/audit/command.js +52 -12
- package/dist/audit/domain/audit-file.d.ts +7 -0
- package/dist/audit/domain/comment-content.d.ts +26 -0
- package/dist/audit/domain/comment-dividers.d.ts +62 -0
- package/dist/audit/domain/comment-dividers.js +48 -20
- package/dist/audit/domain/display-names.d.ts +11 -0
- package/dist/audit/domain/display-names.js +71 -0
- package/dist/audit/domain/import-policy.d.ts +34 -0
- package/dist/audit/domain/import-policy.js +147 -0
- package/dist/audit/domain/link-references.d.ts +40 -0
- package/dist/audit/domain/mappings.d.ts +45 -0
- package/dist/audit/domain/markdown-links.d.ts +44 -0
- package/dist/audit/domain/since-versions.d.ts +26 -0
- package/dist/audit/domain/tokenize.d.ts +14 -0
- package/dist/audit/domain/tsdoc-syntax.d.ts +20 -0
- package/dist/audit/domain/types.d.ts +171 -0
- package/dist/audit/output.d.ts +91 -0
- package/dist/audit/output.js +52 -11
- package/dist/audit/prepare.d.ts +70 -0
- package/dist/audit/prepare.js +41 -10
- package/dist/audit/run-comments.d.ts +17 -0
- package/dist/audit/run-comments.js +22 -18
- package/dist/audit/run-display-names.d.ts +14 -0
- package/dist/audit/run-display-names.js +60 -0
- package/dist/audit/run-imports.d.ts +14 -0
- package/dist/audit/{run-react.js → run-imports.js} +15 -5
- package/dist/audit/run-links.d.ts +14 -0
- package/dist/audit/run.d.ts +14 -0
- package/dist/bin.d.ts +2 -0
- package/dist/cli.d.ts +6 -0
- package/dist/core/cli/format-error.d.ts +7 -0
- package/dist/core/cli/global-options.d.ts +15 -0
- package/dist/core/cli/positional.d.ts +6 -0
- package/dist/core/cli/result-handle.d.ts +19 -0
- package/dist/core/config/define-config.d.ts +7 -0
- package/dist/core/config/define-config.js +8 -0
- package/dist/core/config/loader.d.ts +18 -0
- package/dist/core/config/loader.js +2 -7
- package/dist/core/config/schema.d.ts +99 -0
- package/dist/core/config/schema.js +7 -75
- package/dist/core/config/warnings.d.ts +6 -0
- package/dist/core/config.d.ts +12 -0
- package/dist/core/errors.d.ts +25 -0
- package/dist/core/exit-codes.d.ts +18 -0
- package/dist/core/filesystem/node.d.ts +7 -0
- package/dist/core/filesystem/node.js +1 -0
- package/dist/core/filesystem/port.d.ts +44 -0
- package/dist/core/glob.d.ts +19 -0
- package/dist/core/logger.d.ts +9 -0
- package/dist/core/result.d.ts +30 -0
- package/dist/core/schema-parse.d.ts +9 -0
- package/dist/core/source-text-edit.d.ts +33 -0
- package/dist/core/verbose-diagnostics.d.ts +6 -0
- package/dist/core/workspace/ancestor-directories.d.ts +12 -0
- package/dist/core/workspace/ancestor-directories.js +30 -0
- package/dist/core/workspace/markdown-walk.d.ts +7 -0
- package/dist/core/workspace/markdown-walk.js +2 -20
- package/dist/core/workspace/package-version.d.ts +9 -0
- package/dist/core/workspace/package-version.js +8 -12
- package/dist/core/workspace/resolver.d.ts +39 -0
- package/dist/core/workspace/resolver.js +58 -75
- package/dist/core/workspace/skip-directories.d.ts +6 -0
- package/dist/core/workspace/source-walk.d.ts +16 -0
- package/dist/core/workspace/source-walk.js +14 -20
- package/dist/core/workspace/typescript-walk.d.ts +7 -0
- package/dist/core/workspace/typescript-walk.js +2 -23
- package/dist/core/workspace/walk-files.d.ts +7 -0
- package/dist/core/workspace/walk-files.js +27 -0
- package/dist/core/workspace/well-known-files.d.ts +18 -0
- package/dist/core/workspace/well-known-files.js +18 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/mirror/cli-result.d.ts +13 -0
- package/dist/mirror/cli-schema.d.ts +8 -0
- package/dist/mirror/command.d.ts +7 -0
- package/dist/mirror/dist-filesystem-impl.d.ts +8 -0
- package/dist/mirror/domain/constants.d.ts +18 -0
- package/dist/mirror/domain/constants.js +0 -12
- package/dist/mirror/domain/dirent-guard.d.ts +10 -0
- package/dist/mirror/domain/dist-filesystem.d.ts +9 -0
- package/dist/mirror/domain/errors.d.ts +24 -0
- package/dist/mirror/domain/exports.d.ts +36 -0
- package/dist/mirror/domain/package-display-name.d.ts +8 -0
- package/dist/mirror/domain/path-normalizer.d.ts +6 -0
- package/dist/mirror/domain/types.d.ts +131 -0
- package/dist/mirror/output.d.ts +23 -0
- package/dist/mirror/package-path.d.ts +19 -0
- package/dist/mirror/prepare.d.ts +15 -0
- package/dist/mirror/prepare.js +2 -2
- package/dist/mirror/supplement-exports.d.ts +27 -0
- package/dist/mirror/supplement-exports.js +2 -2
- package/dist/mirror/sync-reporter.d.ts +60 -0
- package/dist/mirror/sync-reporter.js +4 -0
- package/dist/mirror/sync-types.d.ts +43 -0
- package/dist/mirror/sync-workspace-package.d.ts +9 -0
- package/dist/mirror/sync-workspace-package.js +3 -3
- package/dist/mirror/sync.d.ts +12 -0
- package/dist/mirror/sync.js +5 -3
- package/dist/mirror/write-exports.d.ts +15 -0
- package/dist/pack-slim/cli-result.d.ts +13 -0
- package/dist/pack-slim/cli-schema.d.ts +17 -0
- package/dist/pack-slim/command.d.ts +7 -0
- package/dist/pack-slim/command.js +5 -4
- package/dist/pack-slim/domain/transform.d.ts +69 -0
- package/dist/pack-slim/domain/transform.js +141 -15
- package/dist/pack-slim/domain/types.d.ts +46 -0
- package/dist/pack-slim/output.d.ts +15 -0
- package/dist/pack-slim/output.js +10 -1
- package/dist/pack-slim/sync.d.ts +23 -0
- package/dist/pack-slim/sync.js +14 -8
- package/dist/pack-slim/working-tree.d.ts +20 -0
- package/dist/tag/cli-result.d.ts +7 -0
- package/dist/tag/cli-schema.d.ts +8 -0
- package/dist/tag/command.d.ts +7 -0
- package/dist/tag/domain/types.d.ts +111 -0
- package/dist/tag/output.d.ts +17 -0
- package/dist/tag/prepare.d.ts +13 -0
- package/dist/tag/prepare.js +2 -2
- package/dist/tag/resolve-target-path.d.ts +10 -0
- package/dist/tag/since-writer.d.ts +32 -0
- package/dist/tag/sync.d.ts +42 -0
- package/dist/tag/target-candidates.d.ts +8 -0
- package/dist/tag/target-candidates.js +1 -1
- package/dist/tag/target-runner.d.ts +8 -0
- package/dist/tag/version-resolver.d.ts +7 -0
- package/package.json +16 -33
- package/dist/audit/domain/react-imports.js +0 -91
package/dist/audit/output.js
CHANGED
|
@@ -83,19 +83,19 @@ export function formatLinkAuditJsonOutput(result, rootDir) {
|
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* Exit `1` when any non-allowlisted
|
|
86
|
+
* Exit `1` when any non-allowlisted import-policy violation remains.
|
|
87
87
|
*
|
|
88
|
-
* @since 0.
|
|
88
|
+
* @since 0.10.0
|
|
89
89
|
*/
|
|
90
|
-
export function
|
|
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
|
|
94
|
+
* Human-readable import-policy report.
|
|
95
95
|
*
|
|
96
|
-
* @since 0.
|
|
96
|
+
* @since 0.10.0
|
|
97
97
|
*/
|
|
98
|
-
export function
|
|
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}
|
|
107
|
+
logger.out(`\n✖ ${result.violationCount} import-policy violation(s)${allowlistSuffix}`);
|
|
108
108
|
}
|
|
109
109
|
else {
|
|
110
|
-
logger.out(`✓ No
|
|
110
|
+
logger.out(`✓ No import-policy violations across ${result.scannedFileCount} file(s)${allowlistSuffix}`);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
/**
|
|
114
|
-
* Machine-readable
|
|
114
|
+
* Machine-readable import-policy summary for `--json`.
|
|
115
115
|
*
|
|
116
|
-
* @since 0.
|
|
116
|
+
* @since 0.10.0
|
|
117
117
|
*/
|
|
118
|
-
export function
|
|
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>>;
|
package/dist/audit/prepare.js
CHANGED
|
@@ -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 {
|
|
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(
|
|
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(
|
|
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
|
|
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.
|
|
79
|
+
* @since 0.10.0
|
|
80
80
|
*/
|
|
81
|
-
export async function
|
|
81
|
+
export async function prepareImportsAudit(fs, args) {
|
|
82
82
|
let rootDir;
|
|
83
83
|
try {
|
|
84
|
-
rootDir = fs.canonicalPathSync(
|
|
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
|
|
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:
|
|
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(
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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[
|
|
79
|
+
breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect[finding.defect] });
|
|
86
80
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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 {
|
|
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
|
|
9
|
+
* Scans a target path for import-policy violations, applying each rule only to files in its scope.
|
|
8
10
|
*
|
|
9
|
-
* @since 0.
|
|
11
|
+
* @since 0.10.0
|
|
10
12
|
*/
|
|
11
|
-
export function
|
|
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 =
|
|
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
package/dist/cli.d.ts
ADDED
|
@@ -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,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,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
|
-
|
|
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
|
}
|