@codefast/cli 0.3.13-canary.0 → 0.3.13-canary.1
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/dist/bin.mjs +1 -1
- package/dist/commands/arrange.mjs +106 -1
- package/dist/commands/mirror.mjs +65 -1
- package/dist/commands/tag.mjs +57 -1
- package/dist/lib/arrange/application/analyze.mjs +91 -1
- package/dist/lib/arrange/application/group-file.mjs +112 -1
- package/dist/lib/arrange/application/run-target.mjs +64 -1
- package/dist/lib/arrange/domain/ast/ast-helpers.mjs +114 -1
- package/dist/lib/arrange/domain/ast/collectors-cn.mjs +60 -1
- package/dist/lib/arrange/domain/ast/collectors-jsx.mjs +19 -1
- package/dist/lib/arrange/domain/ast/collectors-tv.mjs +169 -1
- package/dist/lib/arrange/domain/ast/targets.mjs +105 -1
- package/dist/lib/arrange/domain/constants.mjs +168 -1
- package/dist/lib/arrange/domain/errors.mjs +18 -1
- package/dist/lib/arrange/domain/grouping.mjs +234 -1
- package/dist/lib/arrange/domain/imports.mjs +53 -1
- package/dist/lib/arrange/domain/tokenizer.mjs +215 -1
- package/dist/lib/arrange/index.mjs +14 -15
- package/dist/lib/arrange/infra/walk.mjs +32 -1
- package/dist/lib/arrange/presentation/formatters.mjs +51 -1
- package/dist/lib/arrange/presentation/report.mjs +23 -1
- package/dist/lib/config/domain/schema.mjs +24 -1
- package/dist/lib/config/index.mjs +2 -3
- package/dist/lib/config/infra/loader.mjs +77 -1
- package/dist/lib/infra/caught-unknown-message.mjs +19 -1
- package/dist/lib/infra/config-reporter.mjs +9 -1
- package/dist/lib/infra/node-io.mjs +29 -1
- package/dist/lib/infra/workspace/repo-root.mjs +21 -1
- package/dist/lib/mirror/application/engine.mjs +247 -1
- package/dist/lib/mirror/application/sync.mjs +161 -1
- package/dist/lib/mirror/domain/constants.mjs +11 -1
- package/dist/lib/mirror/domain/errors.mjs +14 -1
- package/dist/lib/mirror/index.mjs +1 -2
- package/dist/lib/mirror/infra/dirent-list.mjs +13 -1
- package/dist/lib/mirror/infra/package-filter.mjs +25 -1
- package/dist/lib/mirror/infra/path-normalizer.mjs +6 -1
- package/dist/lib/mirror/infra/update-pkg.mjs +104 -1
- package/dist/lib/mirror/infra/workspace-packages.mjs +158 -1
- package/dist/lib/mirror/presentation/reporter.mjs +111 -2
- package/dist/lib/tag/application/engine.mjs +286 -1
- package/dist/lib/tag/index.mjs +3 -4
- package/dist/lib/tag/infra/target-resolver.mjs +169 -1
- package/dist/lib/tag/presentation/colors.mjs +11 -1
- package/dist/lib/tag/presentation/tag-presenter.mjs +62 -1
- package/dist/program.mjs +33 -1
- package/package.json +2 -2
- package/dist/analyze-DvtJ3j9m.mjs +0 -92
- package/dist/arrange-Bbh62DrU.mjs +0 -107
- package/dist/arrange-BqP2DI3w.mjs +0 -8
- package/dist/ast-helpers-MDRYuIAk.mjs +0 -115
- package/dist/caught-unknown-message-CRhkDWO7.mjs +0 -20
- package/dist/collectors-cn-BS8TTQ8O.mjs +0 -61
- package/dist/collectors-jsx-D9tL2ZPM.mjs +0 -20
- package/dist/collectors-tv-D2PXZaRt.mjs +0 -170
- package/dist/colors-BjdkCfGK.mjs +0 -12
- package/dist/commands/mirror.test.mjs +0 -48
- package/dist/config-DVia_f9a.mjs +0 -3
- package/dist/config-reporter-SCcWDZc1.mjs +0 -10
- package/dist/constants-AtPhF-Ct.mjs +0 -169
- package/dist/constants-Bq5wdyLC.mjs +0 -8
- package/dist/dirent-list-BSDeRG5i.mjs +0 -14
- package/dist/engine-CcWEHSCq.mjs +0 -287
- package/dist/engine-DRTdkc5u.mjs +0 -236
- package/dist/errors-CFH4w6ic.mjs +0 -15
- package/dist/errors-CpU1ogfA.mjs +0 -19
- package/dist/formatters-DGE8jJ53.mjs +0 -52
- package/dist/group-file-ClW7XdD6.mjs +0 -113
- package/dist/grouping-c1grpCQT.mjs +0 -235
- package/dist/imports-DSVq1xhB.mjs +0 -54
- package/dist/lib/arrange/application/analyze.test.mjs +0 -171
- package/dist/lib/arrange/application/group-file.test.mjs +0 -273
- package/dist/lib/arrange/application/run-target.test.mjs +0 -97
- package/dist/lib/arrange/domain/ast/ast-helpers.test.mjs +0 -148
- package/dist/lib/arrange/domain/ast/collectors-cn.test.mjs +0 -58
- package/dist/lib/arrange/domain/ast/collectors-jsx.test.mjs +0 -20
- package/dist/lib/arrange/domain/ast/collectors-tv.test.mjs +0 -145
- package/dist/lib/arrange/domain/ast/targets.test.mjs +0 -50
- package/dist/lib/arrange/domain/grouping.test.mjs +0 -186
- package/dist/lib/arrange/domain/imports.test.mjs +0 -48
- package/dist/lib/arrange/domain/tokenizer.test.mjs +0 -165
- package/dist/lib/arrange/infra/walk.test.mjs +0 -50
- package/dist/lib/arrange/presentation/formatters.test.mjs +0 -73
- package/dist/lib/config/infra/loader.test.mjs +0 -116
- package/dist/lib/infra/workspace/repo-root.test.mjs +0 -45
- package/dist/lib/mirror/application/sync.test.mjs +0 -482
- package/dist/lib/mirror/infra/dirent-list.test.mjs +0 -18
- package/dist/lib/mirror/infra/package-filter.test.mjs +0 -32
- package/dist/lib/mirror/infra/workspace-packages.test.mjs +0 -167
- package/dist/lib/tag/application/engine.test.mjs +0 -159
- package/dist/lib/tag/presentation/tag-presenter.test.mjs +0 -29
- package/dist/loader-DpN-zl5g.mjs +0 -78
- package/dist/mirror-CbGiLXKT.mjs +0 -2
- package/dist/mirror-DqJE3ejL.mjs +0 -66
- package/dist/node-io-DFRbHO6m.mjs +0 -30
- package/dist/package-filter-C_8ryqvz.mjs +0 -26
- package/dist/path-normalizer-D4ceBO3l.mjs +0 -7
- package/dist/program-DR-H4tH3.mjs +0 -34
- package/dist/repo-root-HmtIEm6J.mjs +0 -22
- package/dist/report-DYxvw34T.mjs +0 -24
- package/dist/reporter-CwIe_tAM.mjs +0 -108
- package/dist/run-target-CJWrqNT4.mjs +0 -65
- package/dist/schema-Or8RAARd.mjs +0 -25
- package/dist/sync-e1Sp-shi.mjs +0 -157
- package/dist/tag-Cd9JujU5.mjs +0 -2
- package/dist/tag-hEKVZ_-7.mjs +0 -58
- package/dist/tag-presenter-Cs66UBsH.mjs +0 -63
- package/dist/target-resolver-CR3FXMTy.mjs +0 -170
- package/dist/targets-pCTslnFh.mjs +0 -106
- package/dist/tokenizer-CeZx73Zu.mjs +0 -216
- package/dist/update-pkg-CnGfjWBy.mjs +0 -37
- package/dist/walk-DjgQn66n.mjs +0 -33
- package/dist/workspace-packages-K2NYTHJq.mjs +0 -159
|
@@ -1,2 +1,287 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { messageFromCaughtUnknown } from "../../infra/caught-unknown-message.mjs";
|
|
2
|
+
import { applyEditsDescending, indentOfLineContaining } from "../../arrange/domain/ast/ast-helpers.mjs";
|
|
3
|
+
import { walkTsxFiles } from "../../arrange/infra/walk.mjs";
|
|
4
|
+
import "../../arrange/index.mjs";
|
|
5
|
+
import { resolveTagTargetCandidates } from "../infra/target-resolver.mjs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
//#region src/lib/tag/application/engine.ts
|
|
9
|
+
const PACKAGE_JSON = "package.json";
|
|
10
|
+
const VERSION_TAG = "@since";
|
|
11
|
+
function hasExportModifier(node) {
|
|
12
|
+
return (ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
13
|
+
}
|
|
14
|
+
function isTaggableDeclaration(node) {
|
|
15
|
+
return ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isVariableStatement(node);
|
|
16
|
+
}
|
|
17
|
+
function addDeclarationName(registry, name, declaration) {
|
|
18
|
+
if (!registry.has(name)) {
|
|
19
|
+
registry.set(name, new Set([declaration]));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
registry.get(name).add(declaration);
|
|
23
|
+
}
|
|
24
|
+
function collectLocalNamedDeclarations(sf) {
|
|
25
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const statement of sf.statements) {
|
|
27
|
+
if (ts.isFunctionDeclaration(statement) && statement.name) {
|
|
28
|
+
addDeclarationName(declarations, statement.name.text, statement);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (ts.isClassDeclaration(statement) && statement.name) {
|
|
32
|
+
addDeclarationName(declarations, statement.name.text, statement);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (ts.isInterfaceDeclaration(statement)) {
|
|
36
|
+
addDeclarationName(declarations, statement.name.text, statement);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (ts.isTypeAliasDeclaration(statement)) {
|
|
40
|
+
addDeclarationName(declarations, statement.name.text, statement);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (ts.isEnumDeclaration(statement)) {
|
|
44
|
+
addDeclarationName(declarations, statement.name.text, statement);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
48
|
+
for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name)) addDeclarationName(declarations, declaration.name.text, statement);
|
|
49
|
+
}
|
|
50
|
+
return declarations;
|
|
51
|
+
}
|
|
52
|
+
function collectExportedDeclarations(sf) {
|
|
53
|
+
const exported = /* @__PURE__ */ new Set();
|
|
54
|
+
const localNamed = collectLocalNamedDeclarations(sf);
|
|
55
|
+
for (const statement of sf.statements) {
|
|
56
|
+
if (isTaggableDeclaration(statement) && hasExportModifier(statement)) {
|
|
57
|
+
exported.add(statement);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (ts.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
61
|
+
for (const element of statement.exportClause.elements) {
|
|
62
|
+
const localName = element.propertyName?.text ?? element.name.text;
|
|
63
|
+
const declarations = localNamed.get(localName);
|
|
64
|
+
if (!declarations) continue;
|
|
65
|
+
for (const declaration of declarations) exported.add(declaration);
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!ts.isExportAssignment(statement) || !ts.isIdentifier(statement.expression)) continue;
|
|
70
|
+
const declarations = localNamed.get(statement.expression.text);
|
|
71
|
+
if (!declarations) continue;
|
|
72
|
+
for (const declaration of declarations) exported.add(declaration);
|
|
73
|
+
}
|
|
74
|
+
return exported;
|
|
75
|
+
}
|
|
76
|
+
function makeJSDocSinceLine(existingComment, sourceText, version) {
|
|
77
|
+
const commentText = sourceText.slice(existingComment.pos, existingComment.end);
|
|
78
|
+
const baseIndent = indentOfLineContaining(sourceText, existingComment.pos);
|
|
79
|
+
const normalizedBodyLines = commentText.replace(/^\/\*\*\s?/, "").replace(/\s*\*\/$/, "").split("\n").map((line) => line.replace(/^\s*\*\s?/, "").replace(/\s+$/, "")).filter((line, lineIndex, lines) => {
|
|
80
|
+
if (line.length > 0) return true;
|
|
81
|
+
const hasNonEmptyBefore = lines.slice(0, lineIndex).some((value) => value.length > 0);
|
|
82
|
+
const hasNonEmptyAfter = lines.slice(lineIndex + 1).some((value) => value.length > 0);
|
|
83
|
+
return hasNonEmptyBefore && hasNonEmptyAfter;
|
|
84
|
+
});
|
|
85
|
+
const replacement = `/**\n${normalizedBodyLines.length > 0 ? `${normalizedBodyLines.map((line) => `${baseIndent} * ${line}`).join("\n")}\n${baseIndent} *\n` : ""}${baseIndent} * ${VERSION_TAG} ${version}\n${baseIndent} */`;
|
|
86
|
+
return {
|
|
87
|
+
start: existingComment.pos,
|
|
88
|
+
end: existingComment.end,
|
|
89
|
+
replacement
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function makeSinceOnlyJSDocBlock(declarationIndent, version) {
|
|
93
|
+
return `/**\n${declarationIndent} * ${VERSION_TAG} ${version}\n${declarationIndent} */`;
|
|
94
|
+
}
|
|
95
|
+
function makeDeclarationSinceLine(declaration, sf, sourceText, version) {
|
|
96
|
+
if (ts.getJSDocTags(declaration).some((tag) => tag.tagName.text === "since")) return;
|
|
97
|
+
const lastJsDoc = ts.getJSDocCommentsAndTags(declaration).filter(ts.isJSDoc).at(-1);
|
|
98
|
+
if (lastJsDoc) return makeJSDocSinceLine(lastJsDoc, sourceText, version);
|
|
99
|
+
const start = declaration.getStart(sf);
|
|
100
|
+
const indent = indentOfLineContaining(sourceText, start);
|
|
101
|
+
return {
|
|
102
|
+
start,
|
|
103
|
+
end: start,
|
|
104
|
+
replacement: `${indent}${makeSinceOnlyJSDocBlock(indent, version)}\n${indent}`
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function applySinceTagsToFile(filePath, version, fs, write) {
|
|
108
|
+
const sourceText = fs.readFileSync(filePath, "utf8");
|
|
109
|
+
const sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
110
|
+
const edits = [];
|
|
111
|
+
for (const declaration of collectExportedDeclarations(sf)) {
|
|
112
|
+
const edit = makeDeclarationSinceLine(declaration, sf, sourceText, version);
|
|
113
|
+
if (edit) edits.push(edit);
|
|
114
|
+
}
|
|
115
|
+
if (edits.length > 0 && write) {
|
|
116
|
+
const updated = applyEditsDescending(sourceText, edits);
|
|
117
|
+
fs.writeFileSync(filePath, updated, "utf8");
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
filePath,
|
|
121
|
+
taggedDeclarations: edits.length,
|
|
122
|
+
changed: edits.length > 0
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function resolveNearestPackageVersion(targetPath, fs) {
|
|
126
|
+
const resolved = path.resolve(targetPath);
|
|
127
|
+
let current = fs.statSync(resolved).isDirectory() ? resolved : path.dirname(resolved);
|
|
128
|
+
while (true) {
|
|
129
|
+
const packageJsonPath = path.join(current, PACKAGE_JSON);
|
|
130
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
131
|
+
const raw = fs.readFileSync(packageJsonPath, "utf8");
|
|
132
|
+
const version = JSON.parse(raw).version;
|
|
133
|
+
if (typeof version === "string" && version.length > 0) return version;
|
|
134
|
+
throw new Error(`Missing or invalid version in ${packageJsonPath}`);
|
|
135
|
+
}
|
|
136
|
+
const parent = path.dirname(current);
|
|
137
|
+
if (parent === current) break;
|
|
138
|
+
current = parent;
|
|
139
|
+
}
|
|
140
|
+
throw new Error(`Unable to locate ${PACKAGE_JSON} from target: ${targetPath}`);
|
|
141
|
+
}
|
|
142
|
+
function runTagOnTarget(targetPath, opts, fs) {
|
|
143
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
144
|
+
const version = resolveNearestPackageVersion(resolvedTarget, fs);
|
|
145
|
+
const tsFiles = (fs.statSync(resolvedTarget).isDirectory() ? walkTsxFiles(resolvedTarget, fs) : [resolvedTarget]).filter((filePath) => filePath.endsWith(".ts") || filePath.endsWith(".tsx"));
|
|
146
|
+
const fileResults = tsFiles.map((filePath) => applySinceTagsToFile(filePath, version, fs, opts.write));
|
|
147
|
+
const filesChanged = fileResults.filter((result) => result.changed).length;
|
|
148
|
+
const taggedDeclarations = fileResults.reduce((sum, result) => sum + result.taggedDeclarations, 0);
|
|
149
|
+
return {
|
|
150
|
+
version,
|
|
151
|
+
filesScanned: tsFiles.length,
|
|
152
|
+
filesChanged,
|
|
153
|
+
taggedDeclarations,
|
|
154
|
+
fileResults
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async function runTagOnAfterWriteHook(hook, modifiedFiles) {
|
|
158
|
+
if (!hook || modifiedFiles.length === 0) return null;
|
|
159
|
+
try {
|
|
160
|
+
await hook({ files: modifiedFiles });
|
|
161
|
+
return null;
|
|
162
|
+
} catch (caughtHookError) {
|
|
163
|
+
return `[tag] onAfterWrite hook failed: ${messageFromCaughtUnknown(caughtHookError)}`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function summarizeVersions(targetResults) {
|
|
167
|
+
const distinctVersions = extractDistinctVersions(targetResults);
|
|
168
|
+
if (distinctVersions.size === 0) return "none";
|
|
169
|
+
if (distinctVersions.size > 1) return "mixed";
|
|
170
|
+
return [...distinctVersions][0];
|
|
171
|
+
}
|
|
172
|
+
function extractDistinctVersions(targetResults) {
|
|
173
|
+
return new Set(targetResults.map((targetResult) => targetResult.result?.version).filter((version) => typeof version === "string" && version.length > 0));
|
|
174
|
+
}
|
|
175
|
+
function chooseWorkspacePackageTargetPath(candidate, fs) {
|
|
176
|
+
if (candidate.source !== "workspace-package") return {
|
|
177
|
+
targetPath: candidate.candidatePath,
|
|
178
|
+
source: candidate.source
|
|
179
|
+
};
|
|
180
|
+
const preferredSourceDir = path.join(candidate.candidatePath, "src");
|
|
181
|
+
if (fs.existsSync(preferredSourceDir) && fs.statSync(preferredSourceDir).isDirectory()) return {
|
|
182
|
+
targetPath: preferredSourceDir,
|
|
183
|
+
source: "workspace-package-selected-src"
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
targetPath: candidate.candidatePath,
|
|
187
|
+
source: "workspace-package-selected-root"
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function resolveTargetSelection(candidate, rootDir, fs) {
|
|
191
|
+
const selectedTarget = chooseWorkspacePackageTargetPath(candidate, fs);
|
|
192
|
+
const rootRelativeTargetPath = path.relative(rootDir, selectedTarget.targetPath).split(path.sep).join("/");
|
|
193
|
+
return {
|
|
194
|
+
targetPath: selectedTarget.targetPath,
|
|
195
|
+
rootRelativeTargetPath: rootRelativeTargetPath || ".",
|
|
196
|
+
source: selectedTarget.source,
|
|
197
|
+
packageDir: candidate.packageDir,
|
|
198
|
+
packageName: candidate.packageName
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function filterSkippedCandidates(targetCandidates, skipPackages) {
|
|
202
|
+
if (!skipPackages || skipPackages.length === 0) return {
|
|
203
|
+
includedCandidates: targetCandidates,
|
|
204
|
+
skippedPackages: []
|
|
205
|
+
};
|
|
206
|
+
const skipPackageSet = new Set(skipPackages);
|
|
207
|
+
const includedCandidates = [];
|
|
208
|
+
const skippedPackages = [];
|
|
209
|
+
for (const candidate of targetCandidates) {
|
|
210
|
+
const packageName = candidate.packageName;
|
|
211
|
+
if (packageName && skipPackageSet.has(packageName)) {
|
|
212
|
+
skippedPackages.push(packageName);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
includedCandidates.push(candidate);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
includedCandidates,
|
|
219
|
+
skippedPackages
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async function runOnResolvedTarget(resolvedTarget, write, fs, listener) {
|
|
223
|
+
listener?.onTargetStarted(resolvedTarget);
|
|
224
|
+
const absoluteTargetPath = path.resolve(resolvedTarget.targetPath);
|
|
225
|
+
if (!fs.existsSync(absoluteTargetPath)) {
|
|
226
|
+
const missingTargetResult = {
|
|
227
|
+
target: resolvedTarget,
|
|
228
|
+
targetExists: false,
|
|
229
|
+
runError: `Not found: ${absoluteTargetPath}`,
|
|
230
|
+
result: null
|
|
231
|
+
};
|
|
232
|
+
listener?.onTargetCompleted(resolvedTarget, missingTargetResult);
|
|
233
|
+
return missingTargetResult;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const targetRunResult = {
|
|
237
|
+
target: resolvedTarget,
|
|
238
|
+
targetExists: true,
|
|
239
|
+
runError: null,
|
|
240
|
+
result: runTagOnTarget(absoluteTargetPath, { write }, fs)
|
|
241
|
+
};
|
|
242
|
+
listener?.onTargetCompleted(resolvedTarget, targetRunResult);
|
|
243
|
+
return targetRunResult;
|
|
244
|
+
} catch (caughtRunError) {
|
|
245
|
+
const failedTargetRunResult = {
|
|
246
|
+
target: resolvedTarget,
|
|
247
|
+
targetExists: true,
|
|
248
|
+
runError: messageFromCaughtUnknown(caughtRunError),
|
|
249
|
+
result: null
|
|
250
|
+
};
|
|
251
|
+
listener?.onTargetCompleted(resolvedTarget, failedTargetRunResult);
|
|
252
|
+
return failedTargetRunResult;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* CLI entry: run tagging and optional `onAfterWrite` using config injected by the command layer.
|
|
257
|
+
* Returns structured execution data; presentation/logging belongs to command layer.
|
|
258
|
+
*/
|
|
259
|
+
async function runTagSync(opts) {
|
|
260
|
+
const { fs } = opts;
|
|
261
|
+
const { includedCandidates, skippedPackages } = filterSkippedCandidates(await resolveTagTargetCandidates(opts.rootDir, opts.targetPath, fs), opts.skipPackages);
|
|
262
|
+
const selectedTargets = includedCandidates.map((candidate) => resolveTargetSelection(candidate, opts.rootDir, fs));
|
|
263
|
+
const targetResults = await Promise.all(selectedTargets.map((resolvedTarget) => runOnResolvedTarget(resolvedTarget, opts.write, fs, opts.listener)));
|
|
264
|
+
const allFileResults = targetResults.flatMap((targetResult) => targetResult.result?.fileResults ?? []);
|
|
265
|
+
const filesScanned = targetResults.reduce((sum, targetResult) => sum + (targetResult.result?.filesScanned ?? 0), 0);
|
|
266
|
+
const filesChanged = targetResults.reduce((sum, targetResult) => sum + (targetResult.result?.filesChanged ?? 0), 0);
|
|
267
|
+
const taggedDeclarations = targetResults.reduce((sum, targetResult) => sum + (targetResult.result?.taggedDeclarations ?? 0), 0);
|
|
268
|
+
const modifiedFiles = allFileResults.filter((entry) => entry.changed).map((entry) => entry.filePath);
|
|
269
|
+
const hookError = opts.write && modifiedFiles.length > 0 ? await runTagOnAfterWriteHook(opts.config?.onAfterWrite, modifiedFiles) : null;
|
|
270
|
+
const distinctVersions = [...extractDistinctVersions(targetResults)].sort((left, right) => left.localeCompare(right));
|
|
271
|
+
return {
|
|
272
|
+
mode: opts.write ? "applied" : "dry-run",
|
|
273
|
+
selectedTargets,
|
|
274
|
+
resolvedTargets: selectedTargets,
|
|
275
|
+
skippedPackages,
|
|
276
|
+
targetResults,
|
|
277
|
+
filesScanned,
|
|
278
|
+
filesChanged,
|
|
279
|
+
taggedDeclarations,
|
|
280
|
+
versionSummary: summarizeVersions(targetResults),
|
|
281
|
+
distinctVersions,
|
|
282
|
+
modifiedFiles,
|
|
283
|
+
hookError
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
2
287
|
export { resolveNearestPackageVersion, runTagOnTarget, runTagSync };
|
package/dist/lib/tag/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import "../../tag-Cd9JujU5.mjs";
|
|
1
|
+
import { createNodeCliFs, createNodeCliLogger } from "../infra/node-io.mjs";
|
|
2
|
+
import { resolveNearestPackageVersion, runTagOnTarget, runTagSync } from "./application/engine.mjs";
|
|
3
|
+
import { createTagProgressListener, formatProgress, formatSummary, formatTargetTable, formatWarningsAndErrors } from "./presentation/tag-presenter.mjs";
|
|
5
4
|
export { createNodeCliFs, createNodeCliLogger, createTagProgressListener, formatProgress, formatSummary, formatTargetTable, formatWarningsAndErrors, resolveNearestPackageVersion, runTagOnTarget, runTagSync };
|
|
@@ -1,2 +1,170 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { messageFromCaughtUnknown } from "../../infra/caught-unknown-message.mjs";
|
|
2
|
+
import { globSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import picomatch from "picomatch";
|
|
6
|
+
import { parse } from "yaml";
|
|
7
|
+
//#region src/lib/tag/infra/target-resolver.ts
|
|
8
|
+
const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
9
|
+
const PACKAGE_JSON_FILE = "package.json";
|
|
10
|
+
const DEFAULT_WORKSPACE_PACKAGE_PATTERNS = ["packages/*"];
|
|
11
|
+
const PnpmWorkspaceSchema = z.object({ packages: z.array(z.string()).optional() }).passthrough();
|
|
12
|
+
const PackageNameSchema = z.object({ name: z.string().min(1).optional() }).passthrough();
|
|
13
|
+
function toPosix(filePath) {
|
|
14
|
+
return filePath.split(path.sep).join("/");
|
|
15
|
+
}
|
|
16
|
+
function workspacePatternToPackageJsonGlob(pattern) {
|
|
17
|
+
const normalizedPattern = toPosix(pattern.trim()).replace(/\/+$/, "");
|
|
18
|
+
if (!normalizedPattern) return `**/${PACKAGE_JSON_FILE}`;
|
|
19
|
+
return `${normalizedPattern}/${PACKAGE_JSON_FILE}`;
|
|
20
|
+
}
|
|
21
|
+
function splitWorkspacePackagePatterns(rawPatterns) {
|
|
22
|
+
const include = [];
|
|
23
|
+
const exclude = [];
|
|
24
|
+
for (const rawPattern of rawPatterns) {
|
|
25
|
+
const trimmedPattern = rawPattern.trim();
|
|
26
|
+
if (!trimmedPattern) continue;
|
|
27
|
+
if (trimmedPattern.startsWith("!")) {
|
|
28
|
+
exclude.push(trimmedPattern.slice(1).trim());
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
include.push(trimmedPattern);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
include,
|
|
35
|
+
exclude
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async function readWorkspacePackagePatterns(rootDir, fs) {
|
|
39
|
+
const workspaceYamlPath = path.join(rootDir, PNPM_WORKSPACE_FILE);
|
|
40
|
+
if (!fs.existsSync(workspaceYamlPath)) return {
|
|
41
|
+
patterns: [...DEFAULT_WORKSPACE_PACKAGE_PATTERNS],
|
|
42
|
+
hasWorkspaceFile: false,
|
|
43
|
+
declaredEmptyPackages: false
|
|
44
|
+
};
|
|
45
|
+
let workspaceYamlRaw = "";
|
|
46
|
+
try {
|
|
47
|
+
workspaceYamlRaw = await fs.readFile(workspaceYamlPath, "utf8");
|
|
48
|
+
} catch (caughtReadError) {
|
|
49
|
+
throw new Error(`Failed to read ${PNPM_WORKSPACE_FILE}: ${messageFromCaughtUnknown(caughtReadError)}`);
|
|
50
|
+
}
|
|
51
|
+
let parsedWorkspaceDoc;
|
|
52
|
+
try {
|
|
53
|
+
parsedWorkspaceDoc = parse(workspaceYamlRaw);
|
|
54
|
+
} catch (caughtParseError) {
|
|
55
|
+
throw new Error(`Failed to parse ${PNPM_WORKSPACE_FILE}: ${messageFromCaughtUnknown(caughtParseError)}`);
|
|
56
|
+
}
|
|
57
|
+
const parsedWorkspaceResult = PnpmWorkspaceSchema.safeParse(parsedWorkspaceDoc);
|
|
58
|
+
if (!parsedWorkspaceResult.success) {
|
|
59
|
+
const issuePath = parsedWorkspaceResult.error.issues[0]?.path.join(".") ?? "<root>";
|
|
60
|
+
const issueMessage = parsedWorkspaceResult.error.issues[0]?.message ?? "Invalid value";
|
|
61
|
+
throw new Error(`Invalid ${PNPM_WORKSPACE_FILE} schema at "${issuePath}": ${issueMessage}`);
|
|
62
|
+
}
|
|
63
|
+
const hasPackagesKey = typeof parsedWorkspaceDoc === "object" && parsedWorkspaceDoc !== null && Object.prototype.hasOwnProperty.call(parsedWorkspaceDoc, "packages");
|
|
64
|
+
const workspacePackages = parsedWorkspaceResult.data.packages;
|
|
65
|
+
if (!workspacePackages) return {
|
|
66
|
+
patterns: [...DEFAULT_WORKSPACE_PACKAGE_PATTERNS],
|
|
67
|
+
hasWorkspaceFile: true,
|
|
68
|
+
declaredEmptyPackages: false
|
|
69
|
+
};
|
|
70
|
+
if (workspacePackages.length === 0 && hasPackagesKey) return {
|
|
71
|
+
patterns: [],
|
|
72
|
+
hasWorkspaceFile: true,
|
|
73
|
+
declaredEmptyPackages: true
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
patterns: workspacePackages,
|
|
77
|
+
hasWorkspaceFile: true,
|
|
78
|
+
declaredEmptyPackages: false
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async function discoverWorkspacePackageDirs(rootDir, fs) {
|
|
82
|
+
const workspacePackages = await readWorkspacePackagePatterns(rootDir, fs);
|
|
83
|
+
const { include, exclude } = splitWorkspacePackagePatterns(workspacePackages.patterns);
|
|
84
|
+
const includePatterns = include.length > 0 ? include : [...DEFAULT_WORKSPACE_PACKAGE_PATTERNS];
|
|
85
|
+
const foundPackageDirs = /* @__PURE__ */ new Set();
|
|
86
|
+
const globOptions = {
|
|
87
|
+
cwd: rootDir,
|
|
88
|
+
posix: true,
|
|
89
|
+
...path.sep === "\\" ? { windowsPathsNoEscape: true } : {}
|
|
90
|
+
};
|
|
91
|
+
for (const pattern of includePatterns) {
|
|
92
|
+
const matches = globSync(workspacePatternToPackageJsonGlob(pattern), globOptions);
|
|
93
|
+
for (const matchedPath of matches) {
|
|
94
|
+
if (!matchedPath.endsWith(`/${PACKAGE_JSON_FILE}`)) continue;
|
|
95
|
+
const packageRelDir = matchedPath.slice(0, -`/${PACKAGE_JSON_FILE}`.length);
|
|
96
|
+
if (!packageRelDir) continue;
|
|
97
|
+
foundPackageDirs.add(path.resolve(rootDir, packageRelDir));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (exclude.length === 0) return {
|
|
101
|
+
packageDirs: [...foundPackageDirs].sort((leftDir, rightDir) => leftDir.localeCompare(rightDir)),
|
|
102
|
+
hasWorkspaceFile: workspacePackages.hasWorkspaceFile,
|
|
103
|
+
declaredEmptyPackages: workspacePackages.declaredEmptyPackages
|
|
104
|
+
};
|
|
105
|
+
const excludeMatchers = exclude.map((excludePattern) => picomatch(excludePattern, { dot: true }));
|
|
106
|
+
const discoveredDirs = [...foundPackageDirs].filter((packageDir) => {
|
|
107
|
+
const relativePackagePath = toPosix(path.relative(rootDir, packageDir));
|
|
108
|
+
return !excludeMatchers.some((excludeMatcher) => excludeMatcher(relativePackagePath));
|
|
109
|
+
});
|
|
110
|
+
discoveredDirs.sort((leftDir, rightDir) => leftDir.localeCompare(rightDir));
|
|
111
|
+
return {
|
|
112
|
+
packageDirs: discoveredDirs,
|
|
113
|
+
hasWorkspaceFile: workspacePackages.hasWorkspaceFile,
|
|
114
|
+
declaredEmptyPackages: workspacePackages.declaredEmptyPackages
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function readPackageName(packageDir, fs) {
|
|
118
|
+
const packageJsonPath = path.join(packageDir, PACKAGE_JSON_FILE);
|
|
119
|
+
if (!fs.existsSync(packageJsonPath)) return null;
|
|
120
|
+
try {
|
|
121
|
+
const rawPackageJson = fs.readFileSync(packageJsonPath, "utf8");
|
|
122
|
+
const parsedPackageJson = JSON.parse(rawPackageJson);
|
|
123
|
+
const parsedPackageName = PackageNameSchema.safeParse(parsedPackageJson);
|
|
124
|
+
if (!parsedPackageName.success) return null;
|
|
125
|
+
return parsedPackageName.data.name ?? null;
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function toRootRelativePath(rootDir, absolutePath) {
|
|
131
|
+
const relativePath = path.relative(rootDir, absolutePath);
|
|
132
|
+
return relativePath ? toPosix(relativePath) : ".";
|
|
133
|
+
}
|
|
134
|
+
function toPackageTargetCandidate(rootDir, packageDir, fs) {
|
|
135
|
+
const packageName = readPackageName(packageDir, fs);
|
|
136
|
+
return {
|
|
137
|
+
candidatePath: packageDir,
|
|
138
|
+
rootRelativeCandidatePath: toRootRelativePath(rootDir, packageDir),
|
|
139
|
+
source: "workspace-package",
|
|
140
|
+
packageDir,
|
|
141
|
+
packageName
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async function resolveTagTargetCandidates(rootDir, explicitTarget, fs) {
|
|
145
|
+
if (explicitTarget) {
|
|
146
|
+
const resolvedExplicitTarget = path.resolve(explicitTarget);
|
|
147
|
+
return [{
|
|
148
|
+
candidatePath: resolvedExplicitTarget,
|
|
149
|
+
rootRelativeCandidatePath: toRootRelativePath(rootDir, resolvedExplicitTarget),
|
|
150
|
+
source: "explicit-target",
|
|
151
|
+
packageDir: null,
|
|
152
|
+
packageName: null
|
|
153
|
+
}];
|
|
154
|
+
}
|
|
155
|
+
const workspaceDiscovery = await discoverWorkspacePackageDirs(rootDir, fs);
|
|
156
|
+
if (workspaceDiscovery.packageDirs.length === 0) {
|
|
157
|
+
if (workspaceDiscovery.hasWorkspaceFile) return [];
|
|
158
|
+
const repoSourceDir = path.join(rootDir, "src");
|
|
159
|
+
return [{
|
|
160
|
+
candidatePath: repoSourceDir,
|
|
161
|
+
rootRelativeCandidatePath: toRootRelativePath(rootDir, repoSourceDir),
|
|
162
|
+
source: "repo-src-fallback",
|
|
163
|
+
packageDir: null,
|
|
164
|
+
packageName: null
|
|
165
|
+
}];
|
|
166
|
+
}
|
|
167
|
+
return workspaceDiscovery.packageDirs.map((packageDir) => toPackageTargetCandidate(rootDir, packageDir, fs));
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
2
170
|
export { resolveTagTargetCandidates };
|
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/lib/tag/presentation/colors.ts
|
|
2
|
+
const TAG_COLORS = {
|
|
3
|
+
RESET: "\x1B[0m",
|
|
4
|
+
RED: "\x1B[31m",
|
|
5
|
+
GREEN: "\x1B[32m",
|
|
6
|
+
YELLOW: "\x1B[33m"
|
|
7
|
+
};
|
|
8
|
+
function withTagColor(line, colorCode) {
|
|
9
|
+
return `${colorCode}${line}${TAG_COLORS.RESET}`;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
2
12
|
export { TAG_COLORS, withTagColor };
|
|
@@ -1,2 +1,63 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TAG_COLORS, withTagColor } from "./colors.mjs";
|
|
2
|
+
//#region src/lib/tag/presentation/tag-presenter.ts
|
|
3
|
+
function targetDisplayName(target) {
|
|
4
|
+
return target.packageName ?? target.rootRelativeTargetPath;
|
|
5
|
+
}
|
|
6
|
+
function warningsAndErrorsFromResult(result) {
|
|
7
|
+
const entries = [];
|
|
8
|
+
for (const targetResult of result.targetResults) if (targetResult.runError) entries.push(targetResult.runError);
|
|
9
|
+
if (result.hookError) entries.push(result.hookError);
|
|
10
|
+
return entries;
|
|
11
|
+
}
|
|
12
|
+
function formatProgress(event) {
|
|
13
|
+
if (event.type === "target-started") return `[tag] Processing ${targetDisplayName(event.target)}...`;
|
|
14
|
+
const changedFiles = event.result.result?.filesChanged ?? 0;
|
|
15
|
+
return `[tag] Done ${targetDisplayName(event.target)} (${changedFiles} changes)`;
|
|
16
|
+
}
|
|
17
|
+
function formatTargetTable(targets, rootDir) {
|
|
18
|
+
const packageColumnWidth = Math.max(7, ...targets.map((target) => (target.packageName ?? "-").length));
|
|
19
|
+
const pathColumnWidth = Math.max(4, ...targets.map((target) => target.rootRelativeTargetPath.length));
|
|
20
|
+
const lines = [];
|
|
21
|
+
lines.push(`[tag] Root: ${rootDir}`);
|
|
22
|
+
lines.push(`[tag] Resolved targets: ${targets.length}`);
|
|
23
|
+
lines.push("[tag] Targets:");
|
|
24
|
+
lines.push(` ${"package".padEnd(packageColumnWidth)} ${"path".padEnd(pathColumnWidth)} source`);
|
|
25
|
+
lines.push(` ${"-".repeat(packageColumnWidth)} ${"-".repeat(pathColumnWidth)} ${"-".repeat(6)}`);
|
|
26
|
+
for (const target of targets) lines.push(` ${(target.packageName ?? "-").padEnd(packageColumnWidth)} ${target.rootRelativeTargetPath.padEnd(pathColumnWidth)} ${target.source}`);
|
|
27
|
+
return lines.join("\n");
|
|
28
|
+
}
|
|
29
|
+
function formatWarningsAndErrors(result) {
|
|
30
|
+
const entries = warningsAndErrorsFromResult(result);
|
|
31
|
+
if (entries.length === 0) return null;
|
|
32
|
+
const lines = [withTagColor("⚠️ Warnings & Errors", TAG_COLORS.YELLOW)];
|
|
33
|
+
for (const entry of entries) lines.push(withTagColor(`- ${entry}`, TAG_COLORS.RED));
|
|
34
|
+
return lines.join("\n");
|
|
35
|
+
}
|
|
36
|
+
function formatSummary(result) {
|
|
37
|
+
const isDryRun = result.mode === "dry-run";
|
|
38
|
+
const summaryPrefix = isDryRun ? "[tag:dry-run]" : "[tag]";
|
|
39
|
+
const versionSuffix = result.versionSummary === "mixed" && result.distinctVersions.length > 0 ? ` [${result.distinctVersions.join(", ")}]` : "";
|
|
40
|
+
const summaryColor = result.targetResults.some((targetResult) => targetResult.runError !== null) || result.hookError !== null ? TAG_COLORS.RED : isDryRun ? TAG_COLORS.YELLOW : TAG_COLORS.GREEN;
|
|
41
|
+
const lines = [withTagColor(`${summaryPrefix} version=${result.versionSummary}${versionSuffix} files=${result.filesChanged}/${result.filesScanned} declarations=${result.taggedDeclarations}`, summaryColor)];
|
|
42
|
+
if (result.skippedPackages.length > 0) lines.push(`[tag] Skipped: ${result.skippedPackages.length} package(s)`);
|
|
43
|
+
return lines.join("\n");
|
|
44
|
+
}
|
|
45
|
+
function createTagProgressListener(onProgressLine) {
|
|
46
|
+
return {
|
|
47
|
+
onTargetStarted: (target) => {
|
|
48
|
+
onProgressLine(formatProgress({
|
|
49
|
+
type: "target-started",
|
|
50
|
+
target
|
|
51
|
+
}));
|
|
52
|
+
},
|
|
53
|
+
onTargetCompleted: (target, result) => {
|
|
54
|
+
onProgressLine(formatProgress({
|
|
55
|
+
type: "target-completed",
|
|
56
|
+
target,
|
|
57
|
+
result
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
2
63
|
export { createTagProgressListener, formatProgress, formatSummary, formatTargetTable, formatWarningsAndErrors };
|
package/dist/program.mjs
CHANGED
|
@@ -1,2 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { registerArrangeCommand } from "./commands/arrange.mjs";
|
|
2
|
+
import { registerMirrorCommand } from "./commands/mirror.mjs";
|
|
3
|
+
import { registerTagCommand } from "./commands/tag.mjs";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
//#region src/program.ts
|
|
10
|
+
function readVersion() {
|
|
11
|
+
try {
|
|
12
|
+
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const raw = readFileSync(path.join(dir, "..", "package.json"), "utf-8");
|
|
14
|
+
const pkgVersion = JSON.parse(raw).version;
|
|
15
|
+
return typeof pkgVersion === "string" && pkgVersion.length > 0 ? pkgVersion : "unknown";
|
|
16
|
+
} catch {
|
|
17
|
+
return "unknown";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function createProgram() {
|
|
21
|
+
const program = new Command();
|
|
22
|
+
program.name("codefast").description("Codefast monorepo developer CLI").version(readVersion()).option("--no-color", "Disable ANSI color output").configureHelp({ sortSubcommands: true }).showHelpAfterError("(use --help for usage)");
|
|
23
|
+
registerMirrorCommand(program);
|
|
24
|
+
registerArrangeCommand(program);
|
|
25
|
+
registerTagCommand(program);
|
|
26
|
+
return program;
|
|
27
|
+
}
|
|
28
|
+
async function runCli(argv) {
|
|
29
|
+
await createProgram().parseAsync(argv, { from: "node" });
|
|
30
|
+
const code = process.exitCode ?? 0;
|
|
31
|
+
return typeof code === "number" ? code : Number(code) || 0;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
2
34
|
export { createProgram, runCli };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codefast/cli",
|
|
3
|
-
"version": "0.3.13-canary.
|
|
3
|
+
"version": "0.3.13-canary.1",
|
|
4
4
|
"description": "Developer CLI for the Codefast monorepo (mirror, arrange)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@typescript/native-preview": "7.0.0-dev.20260411.1",
|
|
61
61
|
"jest": "^30.3.0",
|
|
62
62
|
"tsdown": "^0.21.8",
|
|
63
|
-
"@codefast/typescript-config": "0.3.13-canary.
|
|
63
|
+
"@codefast/typescript-config": "0.3.13-canary.1"
|
|
64
64
|
},
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=22.0.0"
|