@codefast/cli 0.3.7-canary.0 → 0.3.13-canary.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/README.md +178 -262
- package/dist/analyze-DvtJ3j9m.mjs +92 -0
- package/dist/arrange-Bbh62DrU.mjs +107 -0
- package/dist/arrange-BqP2DI3w.mjs +8 -0
- package/dist/ast-helpers-MDRYuIAk.mjs +115 -0
- package/dist/bin.mjs +8 -0
- package/dist/caught-unknown-message-CRhkDWO7.mjs +20 -0
- package/dist/collectors-cn-BS8TTQ8O.mjs +61 -0
- package/dist/collectors-jsx-D9tL2ZPM.mjs +20 -0
- package/dist/collectors-tv-D2PXZaRt.mjs +170 -0
- package/dist/colors-BjdkCfGK.mjs +12 -0
- package/dist/commands/arrange.mjs +2 -0
- package/dist/commands/mirror.mjs +2 -0
- package/dist/commands/mirror.test.mjs +48 -0
- package/dist/commands/tag.mjs +2 -0
- package/dist/config-DVia_f9a.mjs +3 -0
- package/dist/config-reporter-SCcWDZc1.mjs +10 -0
- package/dist/constants-AtPhF-Ct.mjs +169 -0
- package/dist/constants-Bq5wdyLC.mjs +8 -0
- package/dist/dirent-list-BSDeRG5i.mjs +14 -0
- package/dist/engine-CcWEHSCq.mjs +287 -0
- package/dist/engine-DRTdkc5u.mjs +236 -0
- package/dist/errors-CFH4w6ic.mjs +15 -0
- package/dist/errors-CpU1ogfA.mjs +19 -0
- package/dist/formatters-DGE8jJ53.mjs +52 -0
- package/dist/group-file-ClW7XdD6.mjs +113 -0
- package/dist/grouping-c1grpCQT.mjs +235 -0
- package/dist/imports-DSVq1xhB.mjs +54 -0
- package/dist/lib/arrange/application/analyze.mjs +2 -0
- package/dist/lib/arrange/application/analyze.test.mjs +171 -0
- package/dist/lib/arrange/application/group-file.mjs +2 -0
- package/dist/lib/arrange/application/group-file.test.mjs +273 -0
- package/dist/lib/arrange/application/run-target.mjs +2 -0
- package/dist/lib/arrange/application/run-target.test.mjs +97 -0
- package/dist/lib/arrange/domain/ast/ast-helpers.mjs +2 -0
- package/dist/lib/arrange/domain/ast/ast-helpers.test.mjs +148 -0
- package/dist/lib/arrange/domain/ast/collectors-cn.mjs +2 -0
- package/dist/lib/arrange/domain/ast/collectors-cn.test.mjs +58 -0
- package/dist/lib/arrange/domain/ast/collectors-jsx.mjs +2 -0
- package/dist/lib/arrange/domain/ast/collectors-jsx.test.mjs +20 -0
- package/dist/lib/arrange/domain/ast/collectors-tv.mjs +2 -0
- package/dist/lib/arrange/domain/ast/collectors-tv.test.mjs +145 -0
- package/dist/lib/arrange/domain/ast/targets.mjs +2 -0
- package/dist/lib/arrange/domain/ast/targets.test.mjs +50 -0
- package/dist/lib/arrange/domain/constants.mjs +2 -0
- package/dist/lib/arrange/domain/errors.mjs +2 -0
- package/dist/lib/arrange/domain/grouping.mjs +2 -0
- package/dist/lib/arrange/domain/grouping.test.mjs +186 -0
- package/dist/lib/arrange/domain/imports.mjs +2 -0
- package/dist/lib/arrange/domain/imports.test.mjs +48 -0
- package/dist/lib/arrange/domain/tokenizer.mjs +2 -0
- package/dist/lib/arrange/domain/tokenizer.test.mjs +165 -0
- package/dist/lib/arrange/domain/types.mjs +1 -0
- package/dist/lib/arrange/index.mjs +16 -0
- package/dist/lib/arrange/infra/walk.mjs +2 -0
- package/dist/lib/arrange/infra/walk.test.mjs +50 -0
- package/dist/lib/arrange/presentation/formatters.mjs +2 -0
- package/dist/lib/arrange/presentation/formatters.test.mjs +73 -0
- package/dist/lib/arrange/presentation/report.mjs +2 -0
- package/dist/lib/config/domain/schema.mjs +2 -0
- package/dist/lib/config/index.mjs +4 -0
- package/dist/lib/config/infra/loader.mjs +2 -0
- package/dist/lib/config/infra/loader.test.mjs +116 -0
- package/dist/lib/infra/caught-unknown-message.mjs +2 -0
- package/dist/lib/infra/config-reporter.mjs +2 -0
- package/dist/lib/infra/fs-contract.mjs +1 -0
- package/dist/lib/infra/node-io.mjs +2 -0
- package/dist/lib/infra/workspace/repo-root.mjs +2 -0
- package/dist/lib/infra/workspace/repo-root.test.mjs +45 -0
- package/dist/lib/mirror/application/engine.mjs +2 -0
- package/dist/lib/mirror/application/sync.mjs +2 -0
- package/dist/lib/mirror/application/sync.test.mjs +482 -0
- package/dist/lib/mirror/domain/constants.mjs +2 -0
- package/dist/lib/mirror/domain/errors.mjs +2 -0
- package/dist/lib/mirror/domain/types.mjs +1 -0
- package/dist/lib/mirror/index.mjs +3 -0
- package/dist/lib/mirror/infra/dirent-list.mjs +2 -0
- package/dist/lib/mirror/infra/dirent-list.test.mjs +18 -0
- package/dist/lib/mirror/infra/package-filter.mjs +2 -0
- package/dist/lib/mirror/infra/package-filter.test.mjs +32 -0
- package/dist/lib/mirror/infra/path-normalizer.mjs +2 -0
- package/dist/lib/mirror/infra/update-pkg.mjs +2 -0
- package/dist/lib/mirror/infra/workspace-packages.mjs +2 -0
- package/dist/lib/mirror/infra/workspace-packages.test.mjs +167 -0
- package/dist/lib/mirror/presentation/reporter.mjs +2 -0
- package/dist/lib/tag/application/engine.mjs +2 -0
- package/dist/lib/tag/application/engine.test.mjs +159 -0
- package/dist/lib/tag/domain/types.mjs +1 -0
- package/dist/lib/tag/index.mjs +5 -0
- package/dist/lib/tag/infra/target-resolver.mjs +2 -0
- package/dist/lib/tag/presentation/colors.mjs +2 -0
- package/dist/lib/tag/presentation/tag-presenter.mjs +2 -0
- package/dist/lib/tag/presentation/tag-presenter.test.mjs +29 -0
- package/dist/loader-DpN-zl5g.mjs +78 -0
- package/dist/mirror-CbGiLXKT.mjs +2 -0
- package/dist/mirror-DqJE3ejL.mjs +66 -0
- package/dist/node-io-DFRbHO6m.mjs +30 -0
- package/dist/package-filter-C_8ryqvz.mjs +26 -0
- package/dist/path-normalizer-D4ceBO3l.mjs +7 -0
- package/dist/program-DR-H4tH3.mjs +34 -0
- package/dist/program.mjs +2 -0
- package/dist/repo-root-HmtIEm6J.mjs +22 -0
- package/dist/report-DYxvw34T.mjs +24 -0
- package/dist/reporter-CwIe_tAM.mjs +108 -0
- package/dist/run-target-CJWrqNT4.mjs +65 -0
- package/dist/schema-Or8RAARd.mjs +25 -0
- package/dist/sync-e1Sp-shi.mjs +157 -0
- package/dist/tag-Cd9JujU5.mjs +2 -0
- package/dist/tag-hEKVZ_-7.mjs +58 -0
- package/dist/tag-presenter-Cs66UBsH.mjs +63 -0
- package/dist/target-resolver-CR3FXMTy.mjs +170 -0
- package/dist/targets-pCTslnFh.mjs +106 -0
- package/dist/tokenizer-CeZx73Zu.mjs +216 -0
- package/dist/update-pkg-CnGfjWBy.mjs +37 -0
- package/dist/walk-DjgQn66n.mjs +33 -0
- package/dist/workspace-packages-K2NYTHJq.mjs +159 -0
- package/package.json +42 -53
- package/CHANGELOG.md +0 -15
- package/dist/cjs/index.cjs +0 -2
- package/dist/cjs/src/index.d.ts +0 -3
- package/dist/cjs/src/index.d.ts.map +0 -1
- package/dist/esm/index.js +0 -2
- package/dist/esm/src/index.d.ts +0 -3
- package/dist/esm/src/index.d.ts.map +0 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
//#region src/lib/arrange/domain/constants.ts
|
|
2
|
+
/** Analyze report: long literal threshold (token count). */
|
|
3
|
+
const LONG_STRING_TOKEN_THRESHOLD = 18;
|
|
4
|
+
/**
|
|
5
|
+
* Minimum token count for a string to be considered a candidate for grouping
|
|
6
|
+
* in the apply/preview pipeline. Intentionally much lower than
|
|
7
|
+
* {@link LONG_STRING_TOKEN_THRESHOLD} (analyze only).
|
|
8
|
+
*/
|
|
9
|
+
const APPLY_MIN_TOKENS = 2;
|
|
10
|
+
/**
|
|
11
|
+
* Minimum tokens a group must have to stand alone before singleton-merging.
|
|
12
|
+
* Set to 2 so single-token groups are not collapsed into unrelated buckets by the merger.
|
|
13
|
+
*/
|
|
14
|
+
const MIN_GROUP_TOKENS = 2;
|
|
15
|
+
/** Dynamic max groups clamp: base bound. */
|
|
16
|
+
const MAX_GROUPS_BASE = 4;
|
|
17
|
+
/** Dynamic max groups clamp: upper cap. */
|
|
18
|
+
const MAX_GROUPS_CAP = 24;
|
|
19
|
+
/**
|
|
20
|
+
* Extra slots so a few state / aria groups do not force `capGroups` to merge
|
|
21
|
+
* unrelated buckets (e.g. `bg-border` + `outline-hidden`).
|
|
22
|
+
*/
|
|
23
|
+
const MAX_GROUPS_HEADROOM = 2;
|
|
24
|
+
/** Maximum findings printed per category in the analyze report. */
|
|
25
|
+
const MAX_REPORT_LINES = 40;
|
|
26
|
+
/** Maximum recursion depth when traversing `tv()` object literals. */
|
|
27
|
+
const MAX_OBJECT_DEPTH = 12;
|
|
28
|
+
/** Maximum depth when peeling conditional / parens / arrays inside `cn(...)` args. */
|
|
29
|
+
const MAX_CLASS_EXPR_DEPTH = 12;
|
|
30
|
+
/**
|
|
31
|
+
* Maximum variant-stripping passes in {@link stripVariants}.
|
|
32
|
+
* Real-world Tailwind stacks rarely exceed 4–5 segments (e.g. `@md/sidebar:group-hover:dark:focus-visible:`);
|
|
33
|
+
* 12 is a conservative safety cap that prevents runaway loops on malformed input.
|
|
34
|
+
*/
|
|
35
|
+
const MAX_STRIP_VARIANT_PASSES = 12;
|
|
36
|
+
/**
|
|
37
|
+
* Passed when optional `knownBindings` is missing — size 0 disables matching
|
|
38
|
+
* for `cn` / `tv` identifier resolution.
|
|
39
|
+
*/
|
|
40
|
+
const EMPTY_CN_TV_BINDINGS = /* @__PURE__ */ new Set();
|
|
41
|
+
/**
|
|
42
|
+
* Bucket sort order — **render pipeline** (lower → earlier in output).
|
|
43
|
+
* Existence → Position → Layout → Sizing → Spacing → Shape → Background → Shadow
|
|
44
|
+
* → Typography → Composite → Motion → Starting → Behavior → State (variants) → Selector variants.
|
|
45
|
+
*/
|
|
46
|
+
const BUCKET_ORDER = {
|
|
47
|
+
existence: 0,
|
|
48
|
+
position: 1,
|
|
49
|
+
layout: 2,
|
|
50
|
+
sizing: 3,
|
|
51
|
+
spacing: 4,
|
|
52
|
+
shape: 5,
|
|
53
|
+
background: 6,
|
|
54
|
+
shadow: 7,
|
|
55
|
+
typography: 8,
|
|
56
|
+
composite: 9,
|
|
57
|
+
motion: 10,
|
|
58
|
+
starting: 11,
|
|
59
|
+
behavior: 12,
|
|
60
|
+
state: 13,
|
|
61
|
+
selector: 14,
|
|
62
|
+
other: 15,
|
|
63
|
+
arbitrary: 16
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Compatible bucket **pairs** — two adjacent tokens whose buckets appear in the same
|
|
67
|
+
* `Set` may stay in one `cn()` string chunk. Compatibility is **not transitive**: there is
|
|
68
|
+
* no `{position, sizing}` pair, so `fixed inset-0` and `overflow-auto` become **separate**
|
|
69
|
+
* literals unless **layout** tokens sit between them in sorted order; then
|
|
70
|
+
* `position↔layout` and `layout↔sizing` each justify one hop and the whole “box in flow”
|
|
71
|
+
* (place + tracks + scrollport) stays in one chunk — co-located because they jointly define
|
|
72
|
+
* stacking and scroll behavior for the same surface.
|
|
73
|
+
*/
|
|
74
|
+
const COMPATIBLE_BUCKET_SETS = [
|
|
75
|
+
new Set(["existence", "position"]),
|
|
76
|
+
new Set(["existence", "layout"]),
|
|
77
|
+
new Set(["position", "layout"]),
|
|
78
|
+
new Set(["layout", "sizing"]),
|
|
79
|
+
new Set(["shape", "shadow"]),
|
|
80
|
+
new Set(["background", "shadow"]),
|
|
81
|
+
new Set(["background", "typography"]),
|
|
82
|
+
new Set(["layout", "typography"]),
|
|
83
|
+
new Set(["sizing", "typography"]),
|
|
84
|
+
new Set(["motion", "behavior"]),
|
|
85
|
+
new Set(["motion", "starting"]),
|
|
86
|
+
new Set(["composite", "motion"]),
|
|
87
|
+
new Set(["sizing", "composite"]),
|
|
88
|
+
new Set(["sizing", "spacing"])
|
|
89
|
+
];
|
|
90
|
+
/**
|
|
91
|
+
* Responsive / variant prefix — Tailwind v4 aware.
|
|
92
|
+
*
|
|
93
|
+
* v3: sm: md: … — v4: @sm:, @min-[600px]:, @[480px]:, named @md/sidebar:,
|
|
94
|
+
* viewport md/sidebar:, min-[100px]: / max-[100px]:, …
|
|
95
|
+
*/
|
|
96
|
+
const RESPONSIVE_PREFIX = /^(?:@(?:min|max)-\[[^\]]+\]:|@\[[^\]]+\]:|@(?:[a-z0-9]+(?:-[a-z0-9]+)*)(?:\/[a-z][a-z0-9-]*)?:|(?:max-|min-)?(?:sm|md|lg|xl|2xl|3xl)(?:\/[a-z][a-z0-9-]*)?:|(?:max-|min-)\[[^\]]+\]:)/;
|
|
97
|
+
/** State variant stems — hoisted to module scope (not recreated per call). */
|
|
98
|
+
const STATE_PREFIXES = new Set([
|
|
99
|
+
"hover",
|
|
100
|
+
"focus",
|
|
101
|
+
"focus-within",
|
|
102
|
+
"focus-visible",
|
|
103
|
+
"active",
|
|
104
|
+
"visited",
|
|
105
|
+
"link",
|
|
106
|
+
"any-link",
|
|
107
|
+
"disabled",
|
|
108
|
+
"enabled",
|
|
109
|
+
"checked",
|
|
110
|
+
"indeterminate",
|
|
111
|
+
"required",
|
|
112
|
+
"optional",
|
|
113
|
+
"valid",
|
|
114
|
+
"invalid",
|
|
115
|
+
"in-range",
|
|
116
|
+
"out-of-range",
|
|
117
|
+
"read-only",
|
|
118
|
+
"read-write",
|
|
119
|
+
"placeholder-shown",
|
|
120
|
+
"default",
|
|
121
|
+
"autofill",
|
|
122
|
+
"open",
|
|
123
|
+
"closed",
|
|
124
|
+
"first",
|
|
125
|
+
"last",
|
|
126
|
+
"only",
|
|
127
|
+
"odd",
|
|
128
|
+
"even",
|
|
129
|
+
"first-of-type",
|
|
130
|
+
"last-of-type",
|
|
131
|
+
"only-of-type",
|
|
132
|
+
"first-child",
|
|
133
|
+
"last-child",
|
|
134
|
+
"only-child",
|
|
135
|
+
"empty",
|
|
136
|
+
"target",
|
|
137
|
+
"group",
|
|
138
|
+
"peer",
|
|
139
|
+
"aria",
|
|
140
|
+
"data",
|
|
141
|
+
"dark",
|
|
142
|
+
"light",
|
|
143
|
+
"supports",
|
|
144
|
+
"motion-reduce",
|
|
145
|
+
"motion-safe",
|
|
146
|
+
"print",
|
|
147
|
+
"screen",
|
|
148
|
+
"ltr",
|
|
149
|
+
"rtl",
|
|
150
|
+
"not",
|
|
151
|
+
"has",
|
|
152
|
+
"in",
|
|
153
|
+
"nth",
|
|
154
|
+
"file",
|
|
155
|
+
"selection",
|
|
156
|
+
"marker",
|
|
157
|
+
"placeholder",
|
|
158
|
+
"before",
|
|
159
|
+
"after",
|
|
160
|
+
"first-line",
|
|
161
|
+
"first-letter",
|
|
162
|
+
"backdrop",
|
|
163
|
+
"details-content",
|
|
164
|
+
"popover-open"
|
|
165
|
+
]);
|
|
166
|
+
/** Default directory for arrange analyze/preview/apply when no target is passed. */
|
|
167
|
+
const DEFAULT_ARRANGE_TARGET = "packages/ui/src/components";
|
|
168
|
+
//#endregion
|
|
169
|
+
export { EMPTY_CN_TV_BINDINGS as a, MAX_GROUPS_BASE as c, MAX_OBJECT_DEPTH as d, MAX_REPORT_LINES as f, STATE_PREFIXES as g, RESPONSIVE_PREFIX as h, DEFAULT_ARRANGE_TARGET as i, MAX_GROUPS_CAP as l, MIN_GROUP_TOKENS as m, BUCKET_ORDER as n, LONG_STRING_TOKEN_THRESHOLD as o, MAX_STRIP_VARIANT_PASSES as p, COMPATIBLE_BUCKET_SETS as r, MAX_CLASS_EXPR_DEPTH as s, APPLY_MIN_TOKENS as t, MAX_GROUPS_HEADROOM as u };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/lib/mirror/domain/constants.ts
|
|
2
|
+
const DIST_DIR = "dist";
|
|
3
|
+
const PACKAGE_JSON = "package.json";
|
|
4
|
+
const VALID_JS_EXTENSIONS = new Set([".js", ".cjs"]);
|
|
5
|
+
const DTS_EXTENSION = ".d.ts";
|
|
6
|
+
const PACKAGE_JSON_EXPORT = "./package.json";
|
|
7
|
+
//#endregion
|
|
8
|
+
export { VALID_JS_EXTENSIONS as a, PACKAGE_JSON_EXPORT as i, DTS_EXTENSION as n, PACKAGE_JSON as r, DIST_DIR as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/lib/mirror/infra/dirent-list.ts
|
|
2
|
+
/**
|
|
3
|
+
* Narrow `fs.promises.readdir` overload result to `Dirent[]` when `withFileTypes: true`.
|
|
4
|
+
* Accepts both historical checks (`isFile` / `isDirectory`) in one guard.
|
|
5
|
+
* Returning `true` for an empty array is intentional: callers treat empty lists
|
|
6
|
+
* the same regardless of the element type, so `[]` is safely accepted as `Dirent[]`.
|
|
7
|
+
*/
|
|
8
|
+
function isDirentList(x) {
|
|
9
|
+
if (x.length === 0) return true;
|
|
10
|
+
const first = x[0];
|
|
11
|
+
return typeof first === "object" && first !== null && "isFile" in first && "isDirectory" in first;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { isDirentList as t };
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { t as messageFromCaughtUnknown } from "./caught-unknown-message-CRhkDWO7.mjs";
|
|
2
|
+
import { i as indentOfLineContaining, n as applyEditsDescending } from "./ast-helpers-MDRYuIAk.mjs";
|
|
3
|
+
import { n as walkTsxFiles } from "./walk-DjgQn66n.mjs";
|
|
4
|
+
import "./arrange-BqP2DI3w.mjs";
|
|
5
|
+
import { t as resolveTagTargetCandidates } from "./target-resolver-CR3FXMTy.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 void 0;
|
|
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
|
|
287
|
+
export { runTagOnTarget as n, runTagSync as r, resolveNearestPackageVersion as t };
|