@codefast/cli 0.8.0 → 0.9.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 +568 -0
- package/LICENSE +1 -1
- package/README.md +150 -50
- package/dist/arrange/analyze.js +1 -2
- package/dist/arrange/cli-schema.js +1 -2
- package/dist/arrange/command.js +1 -2
- package/dist/arrange/domain/analyze-service.js +1 -2
- package/dist/arrange/domain/ast/ast-node.js +1 -2
- package/dist/arrange/domain/ast/collectors-cn.js +1 -2
- package/dist/arrange/domain/ast/collectors-jsx.js +1 -2
- package/dist/arrange/domain/ast/collectors-tv.js +1 -2
- package/dist/arrange/domain/ast/helpers.js +1 -2
- package/dist/arrange/domain/ast/simplify-targets.js +1 -2
- package/dist/arrange/domain/ast/targets.js +1 -2
- package/dist/arrange/domain/constants.js +1 -2
- package/dist/arrange/domain/grouping-service.js +1 -2
- package/dist/arrange/domain/grouping.js +1 -2
- package/dist/arrange/domain/imports.js +1 -2
- package/dist/arrange/domain/source-text-formatters.js +1 -2
- package/dist/arrange/domain/tailwind-token.js +1 -2
- package/dist/arrange/domain/token-classifier.js +1 -2
- package/dist/arrange/domain/types.js +1 -2
- package/dist/arrange/output.js +1 -2
- package/dist/arrange/process-file.js +1 -2
- package/dist/arrange/resolve-target.js +1 -2
- package/dist/arrange/scan-target.js +1 -2
- package/dist/arrange/simplify-process-file.js +1 -2
- package/dist/arrange/simplify-sync.js +1 -2
- package/dist/arrange/source-parse.js +1 -2
- package/dist/arrange/suggest.js +1 -2
- package/dist/arrange/sync.js +1 -2
- package/dist/arrange/typescript-ast-translator.js +1 -2
- package/dist/arrange/workspace.js +1 -2
- package/dist/audit/cli-schema.js +12 -2
- package/dist/audit/command.js +44 -5
- package/dist/audit/domain/audit-file.js +2 -3
- package/dist/audit/domain/comment-content.js +1 -2
- package/dist/audit/domain/comment-dividers.js +49 -22
- package/dist/audit/domain/display-names.js +71 -0
- package/dist/audit/domain/link-references.js +1 -2
- package/dist/audit/domain/mappings.js +1 -2
- package/dist/audit/domain/markdown-links.js +1 -2
- package/dist/audit/domain/react-imports.js +1 -2
- package/dist/audit/domain/since-versions.js +1 -2
- package/dist/audit/domain/tokenize.js +2 -3
- package/dist/audit/domain/tsdoc-syntax.js +1 -2
- package/dist/audit/domain/types.js +1 -2
- package/dist/audit/output.js +41 -1
- package/dist/audit/prepare.js +31 -1
- package/dist/audit/run-comments.js +23 -20
- package/dist/audit/run-display-names.js +60 -0
- package/dist/audit/run-links.js +1 -2
- package/dist/audit/run-react.js +1 -2
- package/dist/audit/run.js +1 -2
- package/dist/bin.js +1 -2
- package/dist/cli.js +3 -2
- package/dist/core/cli/format-error.js +1 -2
- package/dist/core/cli/global-options.js +1 -2
- package/dist/core/cli/positional.js +1 -2
- package/dist/core/cli/result-handle.js +1 -22
- package/dist/core/config/loader.js +1 -2
- package/dist/core/config/schema.js +19 -10
- package/dist/core/config/warnings.js +1 -2
- package/dist/core/config.js +1 -2
- package/dist/core/errors.js +1 -2
- package/dist/core/exit-codes.js +1 -2
- package/dist/core/filesystem/node.js +1 -2
- package/dist/core/filesystem/port.js +1 -2
- package/dist/core/glob.js +1 -2
- package/dist/core/logger.js +1 -2
- package/dist/core/result.js +1 -2
- package/dist/core/schema-parse.js +1 -2
- package/dist/core/source-text-edit.js +1 -2
- package/dist/core/verbose-diagnostics.js +1 -2
- package/dist/core/workspace/markdown-walk.js +1 -2
- package/dist/core/workspace/package-version.js +1 -2
- package/dist/core/workspace/resolver.js +1 -2
- package/dist/core/workspace/skip-directories.js +1 -2
- package/dist/core/workspace/source-walk.js +14 -4
- package/dist/core/workspace/typescript-walk.js +1 -2
- package/dist/mirror/cli-result.js +1 -2
- package/dist/mirror/cli-schema.js +1 -2
- package/dist/mirror/command.js +1 -2
- package/dist/mirror/dist-filesystem-impl.js +1 -2
- package/dist/mirror/domain/constants.js +1 -2
- package/dist/mirror/domain/dirent-guard.js +1 -2
- package/dist/mirror/domain/dist-filesystem.js +1 -2
- package/dist/mirror/domain/errors.js +1 -2
- package/dist/mirror/domain/exports.js +1 -2
- package/dist/mirror/domain/package-display-name.js +1 -2
- package/dist/mirror/domain/path-normalizer.js +1 -2
- package/dist/mirror/domain/types.js +1 -2
- package/dist/mirror/output.js +1 -2
- package/dist/mirror/package-path.js +1 -2
- package/dist/mirror/prepare.js +1 -2
- package/dist/mirror/supplement-exports.js +1 -2
- package/dist/mirror/sync-reporter.js +1 -2
- package/dist/mirror/sync-types.js +1 -2
- package/dist/mirror/sync-workspace-package.js +1 -2
- package/dist/mirror/sync.js +1 -2
- package/dist/mirror/write-exports.js +1 -2
- package/dist/pack-slim/cli-result.js +23 -0
- package/dist/pack-slim/cli-schema.js +11 -0
- package/dist/pack-slim/command.js +80 -0
- package/dist/pack-slim/domain/transform.js +221 -0
- package/dist/pack-slim/domain/types.js +2 -0
- package/dist/pack-slim/output.js +62 -0
- package/dist/pack-slim/sync.js +157 -0
- package/dist/pack-slim/working-tree.js +45 -0
- package/dist/tag/cli-result.js +1 -2
- package/dist/tag/cli-schema.js +1 -2
- package/dist/tag/command.js +1 -2
- package/dist/tag/domain/types.js +1 -2
- package/dist/tag/output.js +1 -2
- package/dist/tag/prepare.js +1 -2
- package/dist/tag/resolve-target-path.js +1 -2
- package/dist/tag/since-writer.js +1 -2
- package/dist/tag/sync.js +1 -2
- package/dist/tag/target-candidates.js +1 -2
- package/dist/tag/target-runner.js +1 -2
- package/dist/tag/version-resolver.js +1 -2
- package/package.json +8 -43
- package/dist/arrange/analyze.js.map +0 -1
- package/dist/arrange/cli-schema.js.map +0 -1
- package/dist/arrange/command.js.map +0 -1
- package/dist/arrange/domain/analyze-service.js.map +0 -1
- package/dist/arrange/domain/ast/ast-node.js.map +0 -1
- package/dist/arrange/domain/ast/collectors-cn.js.map +0 -1
- package/dist/arrange/domain/ast/collectors-jsx.js.map +0 -1
- package/dist/arrange/domain/ast/collectors-tv.js.map +0 -1
- package/dist/arrange/domain/ast/helpers.js.map +0 -1
- package/dist/arrange/domain/ast/simplify-targets.js.map +0 -1
- package/dist/arrange/domain/ast/targets.js.map +0 -1
- package/dist/arrange/domain/constants.js.map +0 -1
- package/dist/arrange/domain/grouping-service.js.map +0 -1
- package/dist/arrange/domain/grouping.js.map +0 -1
- package/dist/arrange/domain/imports.js.map +0 -1
- package/dist/arrange/domain/source-text-formatters.js.map +0 -1
- package/dist/arrange/domain/tailwind-token.js.map +0 -1
- package/dist/arrange/domain/token-classifier.js.map +0 -1
- package/dist/arrange/domain/types.js.map +0 -1
- package/dist/arrange/output.js.map +0 -1
- package/dist/arrange/process-file.js.map +0 -1
- package/dist/arrange/resolve-target.js.map +0 -1
- package/dist/arrange/scan-target.js.map +0 -1
- package/dist/arrange/simplify-process-file.js.map +0 -1
- package/dist/arrange/simplify-sync.js.map +0 -1
- package/dist/arrange/source-parse.js.map +0 -1
- package/dist/arrange/suggest.js.map +0 -1
- package/dist/arrange/sync.js.map +0 -1
- package/dist/arrange/typescript-ast-translator.js.map +0 -1
- package/dist/arrange/workspace.js.map +0 -1
- package/dist/audit/cli-schema.js.map +0 -1
- package/dist/audit/command.js.map +0 -1
- package/dist/audit/domain/audit-file.js.map +0 -1
- package/dist/audit/domain/comment-content.js.map +0 -1
- package/dist/audit/domain/comment-dividers.js.map +0 -1
- package/dist/audit/domain/link-references.js.map +0 -1
- package/dist/audit/domain/mappings.js.map +0 -1
- package/dist/audit/domain/markdown-links.js.map +0 -1
- package/dist/audit/domain/react-imports.js.map +0 -1
- package/dist/audit/domain/since-versions.js.map +0 -1
- package/dist/audit/domain/tokenize.js.map +0 -1
- package/dist/audit/domain/tsdoc-syntax.js.map +0 -1
- package/dist/audit/domain/types.js.map +0 -1
- package/dist/audit/output.js.map +0 -1
- package/dist/audit/prepare.js.map +0 -1
- package/dist/audit/run-comments.js.map +0 -1
- package/dist/audit/run-links.js.map +0 -1
- package/dist/audit/run-react.js.map +0 -1
- package/dist/audit/run.js.map +0 -1
- package/dist/bin.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/core/cli/format-error.js.map +0 -1
- package/dist/core/cli/global-options.js.map +0 -1
- package/dist/core/cli/positional.js.map +0 -1
- package/dist/core/cli/result-handle.js.map +0 -1
- package/dist/core/config/loader.js.map +0 -1
- package/dist/core/config/schema.js.map +0 -1
- package/dist/core/config/warnings.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/errors.js.map +0 -1
- package/dist/core/exit-codes.js.map +0 -1
- package/dist/core/filesystem/node.js.map +0 -1
- package/dist/core/filesystem/port.js.map +0 -1
- package/dist/core/glob.js.map +0 -1
- package/dist/core/logger.js.map +0 -1
- package/dist/core/result.js.map +0 -1
- package/dist/core/schema-parse.js.map +0 -1
- package/dist/core/source-text-edit.js.map +0 -1
- package/dist/core/verbose-diagnostics.js.map +0 -1
- package/dist/core/workspace/markdown-walk.js.map +0 -1
- package/dist/core/workspace/package-version.js.map +0 -1
- package/dist/core/workspace/resolver.js.map +0 -1
- package/dist/core/workspace/skip-directories.js.map +0 -1
- package/dist/core/workspace/source-walk.js.map +0 -1
- package/dist/core/workspace/typescript-walk.js.map +0 -1
- package/dist/mirror/cli-result.js.map +0 -1
- package/dist/mirror/cli-schema.js.map +0 -1
- package/dist/mirror/command.js.map +0 -1
- package/dist/mirror/dist-filesystem-impl.js.map +0 -1
- package/dist/mirror/domain/constants.js.map +0 -1
- package/dist/mirror/domain/dirent-guard.js.map +0 -1
- package/dist/mirror/domain/dist-filesystem.js.map +0 -1
- package/dist/mirror/domain/errors.js.map +0 -1
- package/dist/mirror/domain/exports.js.map +0 -1
- package/dist/mirror/domain/package-display-name.js.map +0 -1
- package/dist/mirror/domain/path-normalizer.js.map +0 -1
- package/dist/mirror/domain/types.js.map +0 -1
- package/dist/mirror/output.js.map +0 -1
- package/dist/mirror/package-path.js.map +0 -1
- package/dist/mirror/prepare.js.map +0 -1
- package/dist/mirror/supplement-exports.js.map +0 -1
- package/dist/mirror/sync-reporter.js.map +0 -1
- package/dist/mirror/sync-types.js.map +0 -1
- package/dist/mirror/sync-workspace-package.js.map +0 -1
- package/dist/mirror/sync.js.map +0 -1
- package/dist/mirror/write-exports.js.map +0 -1
- package/dist/tag/cli-result.js.map +0 -1
- package/dist/tag/cli-schema.js.map +0 -1
- package/dist/tag/command.js.map +0 -1
- package/dist/tag/domain/types.js.map +0 -1
- package/dist/tag/output.js.map +0 -1
- package/dist/tag/prepare.js.map +0 -1
- package/dist/tag/resolve-target-path.js.map +0 -1
- package/dist/tag/since-writer.js.map +0 -1
- package/dist/tag/sync.js.map +0 -1
- package/dist/tag/target-candidates.js.map +0 -1
- package/dist/tag/target-runner.js.map +0 -1
- package/dist/tag/version-resolver.js.map +0 -1
package/dist/audit/command.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { commentAuditRunRequestSchema, linkAuditRunRequestSchema, reactAuditRunRequestSchema, rtlAuditRunRequestSchema, } from "#/audit/cli-schema";
|
|
4
|
-
import { exitCodeForCommentAuditResult, exitCodeForLinkAuditResult, exitCodeForReactAuditResult, exitCodeForRtlAuditResult, formatCommentAuditJsonOutput, formatLinkAuditJsonOutput, formatReactAuditJsonOutput, formatRtlAuditJsonOutput, presentCommentAuditResult, presentLinkAuditResult, presentReactAuditResult, presentRtlAuditResult, } from "#/audit/output";
|
|
5
|
-
import { prepareCommentAudit, prepareLinkAudit, prepareReactAudit, prepareRtlAudit } from "#/audit/prepare";
|
|
3
|
+
import { commentAuditRunRequestSchema, linkAuditRunRequestSchema, reactAuditRunRequestSchema, rtlAuditRunRequestSchema, displayNameAuditRunRequestSchema, } from "#/audit/cli-schema";
|
|
4
|
+
import { exitCodeForCommentAuditResult, exitCodeForLinkAuditResult, exitCodeForReactAuditResult, exitCodeForRtlAuditResult, exitCodeForDisplayNameAuditResult, formatCommentAuditJsonOutput, formatLinkAuditJsonOutput, formatReactAuditJsonOutput, formatRtlAuditJsonOutput, formatDisplayNameAuditJsonOutput, presentCommentAuditResult, presentLinkAuditResult, presentReactAuditResult, presentRtlAuditResult, presentDisplayNameAuditResult, } from "#/audit/output";
|
|
5
|
+
import { prepareCommentAudit, prepareLinkAudit, prepareReactAudit, prepareRtlAudit, prepareDisplayNameAudit, } from "#/audit/prepare";
|
|
6
6
|
import { runRtlAudit } from "#/audit/run";
|
|
7
7
|
import { runCommentAudit } from "#/audit/run-comments";
|
|
8
|
+
import { runDisplayNameAudit } from "#/audit/run-display-names";
|
|
8
9
|
import { runLinkAudit } from "#/audit/run-links";
|
|
9
10
|
import { runReactAudit } from "#/audit/run-react";
|
|
10
11
|
import { readOptionalPositionalArg } from "#/core/cli/positional";
|
|
@@ -137,6 +138,45 @@ export function createAuditCommand() {
|
|
|
137
138
|
}
|
|
138
139
|
process.exitCode = exitCodeForReactAuditResult(outcome.value);
|
|
139
140
|
});
|
|
141
|
+
cmd
|
|
142
|
+
.command("display-names")
|
|
143
|
+
.description("Report token(), tag() and module display names that break the <namespace>:<Name> convention")
|
|
144
|
+
.argument("[target]", "Directory or file to scan (default: the repo root)")
|
|
145
|
+
.option("--json", "Print one JSON summary on stdout", false)
|
|
146
|
+
.action(async (target, opts) => {
|
|
147
|
+
const prelude = await prepareDisplayNameAudit(nodeFilesystem, {
|
|
148
|
+
currentWorkingDirectory: process.cwd(),
|
|
149
|
+
rawTarget: readOptionalPositionalArg(target),
|
|
150
|
+
});
|
|
151
|
+
if (!consumeCliAppError(prelude)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const { rootDir, targetPath, allowlist } = prelude.value;
|
|
155
|
+
const parsed = parseWithSchema(displayNameAuditRunRequestSchema, {
|
|
156
|
+
rootDir,
|
|
157
|
+
targetPath,
|
|
158
|
+
allowlist,
|
|
159
|
+
json: !!opts.json,
|
|
160
|
+
});
|
|
161
|
+
if (!consumeCliAppError(parsed)) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const outcome = runDisplayNameAudit(nodeFilesystem, {
|
|
165
|
+
rootDir: parsed.value.rootDir,
|
|
166
|
+
targetPath: parsed.value.targetPath,
|
|
167
|
+
allowlist: parsed.value.allowlist ?? [],
|
|
168
|
+
});
|
|
169
|
+
if (!consumeCliAppError(outcome)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (parsed.value.json) {
|
|
173
|
+
logger.out(formatDisplayNameAuditJsonOutput(outcome.value, rootDir));
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
presentDisplayNameAuditResult(outcome.value);
|
|
177
|
+
}
|
|
178
|
+
process.exitCode = exitCodeForDisplayNameAuditResult(outcome.value);
|
|
179
|
+
});
|
|
140
180
|
cmd
|
|
141
181
|
.command("comments")
|
|
142
182
|
.description("Report section dividers that are not in the repo's one allowed form")
|
|
@@ -180,5 +220,4 @@ export function createAuditCommand() {
|
|
|
180
220
|
process.exitCode = exitCodeForCommentAuditResult(outcome.value);
|
|
181
221
|
});
|
|
182
222
|
return cmd;
|
|
183
|
-
}
|
|
184
|
-
//# sourceMappingURL=command.js.map
|
|
223
|
+
}
|
|
@@ -7,7 +7,7 @@ import { collectTokens } from "#/audit/domain/tokenize";
|
|
|
7
7
|
*
|
|
8
8
|
* @since 0.5.0-canary.6
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
function hasRtlCompanion(fileTokens, expectedValue) {
|
|
11
11
|
return fileTokens.some(({ variant, value }) => {
|
|
12
12
|
if (!variant) {
|
|
13
13
|
return false;
|
|
@@ -97,5 +97,4 @@ export function auditFileContent(content) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
return violations;
|
|
100
|
-
}
|
|
101
|
-
//# sourceMappingURL=audit-file.js.map
|
|
100
|
+
}
|
|
@@ -11,12 +11,31 @@ const RULE_GLYPH = "─";
|
|
|
11
11
|
const LEAD_GLYPHS = "──";
|
|
12
12
|
/** Every glyph a divider has historically been drawn with, so legacy forms are recognised too. */
|
|
13
13
|
const RULE_CHARACTER_CLASS = String.raw `[-=─_*~#]`;
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
// The comment lead each syntax opens a divider with — `#` for ignore files, slash forms for code.
|
|
15
|
+
const commentLeadByLanguage = {
|
|
16
|
+
css: String.raw `\/\/|\/\*|\*`,
|
|
17
|
+
ignore: String.raw `#`,
|
|
18
|
+
js: String.raw `\/\/|\/\*|\*`,
|
|
19
|
+
};
|
|
20
|
+
// One compiled pattern set per language — the comment lead is the only part that varies.
|
|
21
|
+
const patternsByLanguage = new Map();
|
|
22
|
+
function patternsFor(language) {
|
|
23
|
+
const cached = patternsByLanguage.get(language);
|
|
24
|
+
if (cached !== undefined) {
|
|
25
|
+
return cached;
|
|
26
|
+
}
|
|
27
|
+
const lead = commentLeadByLanguage[language];
|
|
28
|
+
const built = {
|
|
29
|
+
ruleOnly: new RegExp(String.raw `^(?<indent>[ \t]*)(?:${lead})[ \t]*${RULE_CHARACTER_CLASS}{4,}[ \t]*(?:\*\/)?[ \t]*$`),
|
|
30
|
+
titled: new RegExp(String.raw `^(?<indent>[ \t]*)(?:${lead})[ \t]*${RULE_CHARACTER_CLASS}{2,}[ \t]+(?<title>.*?)[ \t]+${RULE_CHARACTER_CLASS}{2,}[ \t]*(?:\*\/)?[ \t]*$`),
|
|
31
|
+
commentLine: new RegExp(String.raw `^[ \t]*(?:${lead})`),
|
|
32
|
+
bareRuleClose: new RegExp(String.raw `^[ \t]*${RULE_CHARACTER_CLASS}{4,}[ \t]*\*\/[ \t]*$`),
|
|
33
|
+
commentPrefix: new RegExp(String.raw `^[ \t]*(?:${lead})[ \t]?`),
|
|
34
|
+
};
|
|
35
|
+
patternsByLanguage.set(language, built);
|
|
36
|
+
return built;
|
|
37
|
+
}
|
|
18
38
|
const rulesOnlyPattern = new RegExp(String.raw `^(?:${RULE_CHARACTER_CLASS}|[ \t])*$`);
|
|
19
|
-
const commentPrefixPattern = /^[ \t]*(?:\/\/|\/\*|\*)[ \t]?/;
|
|
20
39
|
const commentSuffixPattern = /[ \t]*\*\/[ \t]*$/;
|
|
21
40
|
/** A banner spanning more lines than this is prose that happens to start with a rule, not a divider. */
|
|
22
41
|
const MAX_BANNER_SPAN = 16;
|
|
@@ -36,7 +55,8 @@ export function renderDivider(indent, title, language) {
|
|
|
36
55
|
const head = `${indent}/* ${LEAD_GLYPHS} ${title} `;
|
|
37
56
|
return `${head}${RULE_GLYPH.repeat(Math.max(2, DIVIDER_COLUMN - head.length - 3))} */`;
|
|
38
57
|
}
|
|
39
|
-
const
|
|
58
|
+
const prefix = language === "ignore" ? "#" : "//";
|
|
59
|
+
const head = `${indent}${prefix} ${LEAD_GLYPHS} ${title} `;
|
|
40
60
|
return `${head}${RULE_GLYPH.repeat(Math.max(2, DIVIDER_COLUMN - head.length))}`;
|
|
41
61
|
}
|
|
42
62
|
/**
|
|
@@ -80,7 +100,8 @@ export function applyCommentDividerFixes(content, language) {
|
|
|
80
100
|
}
|
|
81
101
|
function readRegionAt(lines, index, language) {
|
|
82
102
|
const line = lines[index];
|
|
83
|
-
const
|
|
103
|
+
const patterns = patternsFor(language);
|
|
104
|
+
const titled = patterns.titled.exec(line);
|
|
84
105
|
const title = titled?.groups?.title?.trim();
|
|
85
106
|
if (titled !== null && title !== undefined && title.length > 0 && !rulesOnlyPattern.test(title)) {
|
|
86
107
|
const indent = titled.groups?.indent ?? "";
|
|
@@ -94,26 +115,29 @@ function readRegionAt(lines, index, language) {
|
|
|
94
115
|
defect: line === canonical ? null : usesCanonicalGlyphs(line) ? "bad-width" : "legacy-form",
|
|
95
116
|
};
|
|
96
117
|
}
|
|
97
|
-
const ruleOnly =
|
|
118
|
+
const ruleOnly = patterns.ruleOnly.exec(line);
|
|
98
119
|
if (ruleOnly === null) {
|
|
99
120
|
return null;
|
|
100
121
|
}
|
|
101
|
-
return readBannerAt(lines, index, ruleOnly.groups?.indent ?? "");
|
|
122
|
+
return readBannerAt(lines, index, ruleOnly.groups?.indent ?? "", language);
|
|
102
123
|
}
|
|
103
|
-
function readBannerAt(lines, index, indent) {
|
|
124
|
+
function readBannerAt(lines, index, indent, language) {
|
|
104
125
|
const line = lines[index];
|
|
126
|
+
const patterns = patternsFor(language);
|
|
105
127
|
// An unterminated `/*` opens a block whose body needs no per-line marker, so the run ends at `*/`.
|
|
106
128
|
const insideBlock = line.trimStart().startsWith("/*") && !line.includes("*/");
|
|
107
129
|
const body = [];
|
|
108
130
|
let cursor = index + 1;
|
|
109
|
-
while (cursor < lines.length &&
|
|
110
|
-
|
|
131
|
+
while (cursor < lines.length &&
|
|
132
|
+
cursor - index <= MAX_BANNER_SPAN &&
|
|
133
|
+
!isBannerClose(lines[cursor], insideBlock, language)) {
|
|
134
|
+
if (!insideBlock && !patterns.commentLine.test(lines[cursor])) {
|
|
111
135
|
break;
|
|
112
136
|
}
|
|
113
|
-
body.push(stripCommentPrefix(lines[cursor]));
|
|
137
|
+
body.push(stripCommentPrefix(lines[cursor], language));
|
|
114
138
|
cursor++;
|
|
115
139
|
}
|
|
116
|
-
const closed = cursor < lines.length && cursor - index <= MAX_BANNER_SPAN && isBannerClose(lines[cursor], insideBlock);
|
|
140
|
+
const closed = cursor < lines.length && cursor - index <= MAX_BANNER_SPAN && isBannerClose(lines[cursor], insideBlock, language);
|
|
117
141
|
const meaningful = body.filter((entry) => entry.length > 0);
|
|
118
142
|
// A frame around prose is a doc block, and an unclosed rule is prose formatting inside one.
|
|
119
143
|
if (!closed || meaningful.length !== 1 || !looksLikeTitle(meaningful[0])) {
|
|
@@ -131,16 +155,19 @@ function readBannerAt(lines, index, indent) {
|
|
|
131
155
|
function looksLikeTitle(text) {
|
|
132
156
|
return text.length <= MAX_TITLE_LENGTH && !text.endsWith(".");
|
|
133
157
|
}
|
|
134
|
-
function isBannerClose(line, insideBlock) {
|
|
135
|
-
|
|
158
|
+
function isBannerClose(line, insideBlock, language) {
|
|
159
|
+
const patterns = patternsFor(language);
|
|
160
|
+
if (patterns.ruleOnly.test(line)) {
|
|
136
161
|
return true;
|
|
137
162
|
}
|
|
138
|
-
return insideBlock &&
|
|
163
|
+
return insideBlock && patterns.bareRuleClose.test(line);
|
|
139
164
|
}
|
|
140
165
|
function usesCanonicalGlyphs(line) {
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
166
|
+
const trimmed = line.trimStart();
|
|
167
|
+
return (trimmed.startsWith(`// ${LEAD_GLYPHS} `) ||
|
|
168
|
+
trimmed.startsWith(`/* ${LEAD_GLYPHS} `) ||
|
|
169
|
+
trimmed.startsWith(`# ${LEAD_GLYPHS} `));
|
|
145
170
|
}
|
|
146
|
-
|
|
171
|
+
function stripCommentPrefix(line, language) {
|
|
172
|
+
return line.replace(patternsFor(language).commentPrefix, "").replace(commentSuffixPattern, "").trim();
|
|
173
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** The owner: a kebab-case package, app or feature slug, or a scoped package name. */
|
|
2
|
+
const NAMESPACE = /^(?:@[a-z0-9-]+\/)?[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3
|
+
/** A token or module stands for a type or a unit of composition. */
|
|
4
|
+
const PASCAL_CASE = /^[A-Z][A-Za-z0-9]*$/;
|
|
5
|
+
/** A tag key stands for an attribute a request selects on, so it reads like a property. */
|
|
6
|
+
const CAMEL_CASE = /^[a-z][A-Za-z0-9]*$/;
|
|
7
|
+
/**
|
|
8
|
+
* A bare `token(…)` / `tag(…)` call, or a module factory call, whose first argument is a string literal.
|
|
9
|
+
*
|
|
10
|
+
* @remarks Generic arguments are skipped up to four levels of nesting, which is deeper than any
|
|
11
|
+
* declaration in the repo. Template literals are not matched: an interpolated name has no fixed
|
|
12
|
+
* text to check. The closing parenthesis is kept when the name is the only argument, so the
|
|
13
|
+
* reported text reads as the call was written.
|
|
14
|
+
*/
|
|
15
|
+
const DISPLAY_NAME_CALL = /(?<![\w$.])(?:(token|tag)\s*(?:<(?:[^<>]|<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)*>)?|((?:Sync|Async)?Module)\s*\.\s*create(?:Async)?)\s*\(\s*(["'])((?:(?!\3)[^\\\n]|\\.)*)\3(\s*\))?/g;
|
|
16
|
+
const KIND_LABEL = {
|
|
17
|
+
token: "token display name",
|
|
18
|
+
tag: "tag key",
|
|
19
|
+
module: "module name",
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Scans one source or markdown text for `token()`, `tag()` and module display names that break the convention.
|
|
23
|
+
*
|
|
24
|
+
* @remarks Runs on markdown as well as TypeScript because a doc sample is what a reader copies:
|
|
25
|
+
* a convention the docs break is not one the docs teach.
|
|
26
|
+
*
|
|
27
|
+
* @since 0.9.0
|
|
28
|
+
*/
|
|
29
|
+
export function auditDisplayNames(sourceText) {
|
|
30
|
+
const violations = [];
|
|
31
|
+
for (const match of sourceText.matchAll(DISPLAY_NAME_CALL)) {
|
|
32
|
+
const kind = match[1] === undefined ? "module" : match[1];
|
|
33
|
+
const reason = reasonFor(kind, match[4]);
|
|
34
|
+
if (reason === null) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
violations.push({ line: lineOfOffset(sourceText, match.index), raw: match[0], reason });
|
|
38
|
+
}
|
|
39
|
+
return violations;
|
|
40
|
+
}
|
|
41
|
+
/** Why a display name breaks the convention, or `null` when it holds. */
|
|
42
|
+
function reasonFor(kind, name) {
|
|
43
|
+
const segments = name.split(":");
|
|
44
|
+
const label = KIND_LABEL[kind];
|
|
45
|
+
if (segments.length < 2) {
|
|
46
|
+
return `${label} '${name}' has no namespace — write '<namespace>:${name}'`;
|
|
47
|
+
}
|
|
48
|
+
const local = segments.at(-1);
|
|
49
|
+
for (const namespace of segments.slice(0, -1)) {
|
|
50
|
+
if (!NAMESPACE.test(namespace)) {
|
|
51
|
+
return `namespace '${namespace}' in '${name}' is not kebab-case (or a scoped package name)`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (kind === "tag") {
|
|
55
|
+
return CAMEL_CASE.test(local)
|
|
56
|
+
? null
|
|
57
|
+
: `${label} '${local}' in '${name}' is not camelCase — a tag key names an attribute`;
|
|
58
|
+
}
|
|
59
|
+
return PASCAL_CASE.test(local)
|
|
60
|
+
? null
|
|
61
|
+
: `${label} '${local}' in '${name}' is not PascalCase — a ${kind} stands for a ${kind === "token" ? "type" : "unit of composition"}`;
|
|
62
|
+
}
|
|
63
|
+
function lineOfOffset(sourceText, offset) {
|
|
64
|
+
let line = 1;
|
|
65
|
+
for (let index = 0; index < offset; index++) {
|
|
66
|
+
if (sourceText.charCodeAt(index) === 10) {
|
|
67
|
+
line++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return line;
|
|
71
|
+
}
|
|
@@ -52,7 +52,7 @@ export function splitClassName(token) {
|
|
|
52
52
|
*
|
|
53
53
|
* @since 0.5.0-canary.6
|
|
54
54
|
*/
|
|
55
|
-
|
|
55
|
+
function* stringLiterals(content) {
|
|
56
56
|
const pattern = /"([^"\n]*)"|'([^'\n]*)'|`([^`]*)`/g;
|
|
57
57
|
for (const match of content.matchAll(pattern)) {
|
|
58
58
|
const text = match[1] ?? match[2] ?? match[3] ?? "";
|
|
@@ -81,5 +81,4 @@ export function collectTokens(content) {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
return tokens;
|
|
84
|
-
}
|
|
85
|
-
//# sourceMappingURL=tokenize.js.map
|
|
84
|
+
}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export {};
|
|
2
|
-
//# sourceMappingURL=types.js.map
|
|
1
|
+
export {};
|
package/dist/audit/output.js
CHANGED
|
@@ -170,4 +170,44 @@ export function formatCommentAuditJsonOutput(result, rootDir) {
|
|
|
170
170
|
function truncate(raw) {
|
|
171
171
|
return raw.length <= 60 ? raw : `${raw.slice(0, 57)}…`;
|
|
172
172
|
}
|
|
173
|
-
|
|
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
|
+
});
|
|
213
|
+
}
|
package/dist/audit/prepare.js
CHANGED
|
@@ -132,4 +132,34 @@ export async function prepareCommentAudit(fs, args) {
|
|
|
132
132
|
allowlist: commentsConfig.allowlist ?? [],
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
|
-
|
|
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(findRepoRoot(args.currentWorkingDirectory, fs));
|
|
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
|
+
});
|
|
165
|
+
}
|
|
@@ -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) {
|
|
@@ -186,5 +190,4 @@ function collectScanPaths(fs, targetPath) {
|
|
|
186
190
|
}
|
|
187
191
|
function toPosixPath(filePath) {
|
|
188
192
|
return filePath.split(path.sep).join("/");
|
|
189
|
-
}
|
|
190
|
-
//# sourceMappingURL=run-comments.js.map
|
|
193
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/audit/run-links.js
CHANGED
package/dist/audit/run-react.js
CHANGED
package/dist/audit/run.js
CHANGED
package/dist/bin.js
CHANGED
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
import { createArrangeCommand } from "#/arrange/command";
|
|
7
7
|
import { createAuditCommand } from "#/audit/command";
|
|
8
8
|
import { createMirrorCommand } from "#/mirror/command";
|
|
9
|
+
import { createPackSlimCommand } from "#/pack-slim/command";
|
|
9
10
|
import { createTagCommand } from "#/tag/command";
|
|
10
11
|
function readVersion() {
|
|
11
12
|
try {
|
|
@@ -39,9 +40,9 @@ export async function runCli(argv) {
|
|
|
39
40
|
program.addCommand(createArrangeCommand());
|
|
40
41
|
program.addCommand(createAuditCommand());
|
|
41
42
|
program.addCommand(createMirrorCommand());
|
|
43
|
+
program.addCommand(createPackSlimCommand());
|
|
42
44
|
program.addCommand(createTagCommand());
|
|
43
45
|
await program.parseAsync(argv, { from: "node" });
|
|
44
46
|
const code = process.exitCode ?? 0;
|
|
45
47
|
return typeof code === "number" ? code : Number(code) || 0;
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=cli.js.map
|
|
48
|
+
}
|