@tailor-platform/sdk-codemod 0.3.9 → 0.4.0-next.10
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 +301 -0
- package/dist/codemods/ast-grep-helpers-CXtWn3RB.js +171 -0
- package/dist/codemods/v2/apply-to-deploy/scripts/transform.js +22 -4
- package/dist/codemods/v2/auth-attributes-rename/scripts/transform.js +213 -0
- package/dist/codemods/v2/auth-connection-token-helper/scripts/transform.js +243 -0
- package/dist/codemods/v2/auth-invoker-call-unwrap/scripts/transform.js +7 -0
- package/dist/codemods/v2/auth-invoker-unwrap/scripts/transform.js +108 -13
- package/dist/codemods/v2/cli-rename/scripts/transform.js +373 -14
- package/dist/codemods/v2/db-type-to-table/scripts/transform.js +383 -0
- package/dist/codemods/v2/env-var-rename/scripts/transform.js +88 -0
- package/dist/codemods/v2/erd-site-to-plugin/scripts/transform.js +195 -0
- package/dist/codemods/v2/exec-job-function-rename/scripts/transform.js +95 -0
- package/dist/codemods/v2/execute-script-arg/scripts/transform.js +60 -0
- package/dist/codemods/v2/forward-relation-name/scripts/transform.js +115 -0
- package/dist/codemods/v2/idp-publish-events-rename/scripts/transform.js +186 -0
- package/dist/codemods/v2/principal-unify/scripts/transform.js +1555 -44
- package/dist/codemods/v2/rename-bin/scripts/transform.js +1087 -0
- package/dist/codemods/v2/runtime-globals-opt-in/scripts/transform.js +103 -0
- package/dist/codemods/v2/runtime-subpath-namespace/scripts/transform.js +792 -0
- package/dist/codemods/v2/sdk-skills-shim/scripts/transform.js +3 -3
- package/dist/codemods/v2/seed-exec-to-cli-plugin/scripts/transform.js +115 -0
- package/dist/codemods/v2/tailor-output-ignore-dir/scripts/transform.js +14 -0
- package/dist/codemods/v2/tailordb-namespace/scripts/transform.js +5 -4
- package/dist/codemods/v2/wait-point-rename/scripts/transform.js +126 -0
- package/dist/codemods/v2/workflow-trigger-rename/scripts/transform.js +123 -0
- package/dist/index.js +1962 -51
- package/package.json +3 -2
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { a as importSource, i as importBindings, r as findImportStatements } from "../../../ast-grep-helpers-CXtWn3RB.js";
|
|
2
|
+
import { Lang, parse } from "@ast-grep/napi";
|
|
3
|
+
//#region codemods/v2/db-type-to-table/scripts/transform.ts
|
|
4
|
+
const SDK_MODULE = "@tailor-platform/sdk";
|
|
5
|
+
function sourceLang(filePath, source) {
|
|
6
|
+
return filePath.endsWith(".tsx") || filePath.endsWith(".jsx") || source.includes("</") ? Lang.Tsx : Lang.TypeScript;
|
|
7
|
+
}
|
|
8
|
+
function namespaceImportNames(importStmt) {
|
|
9
|
+
return importStmt.findAll({ rule: { kind: "namespace_import" } }).flatMap((node) => node.children().filter((child) => child.kind() === "identifier")).map((node) => node.text());
|
|
10
|
+
}
|
|
11
|
+
function isInsideImportStatement(node) {
|
|
12
|
+
let current = node.parent();
|
|
13
|
+
while (current) {
|
|
14
|
+
if (current.kind() === "import_statement") return true;
|
|
15
|
+
current = current.parent();
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
function isBindingLeafKind(kind) {
|
|
20
|
+
return kind === "identifier" || kind === "shorthand_property_identifier_pattern";
|
|
21
|
+
}
|
|
22
|
+
function isBindingPatternKind(kind) {
|
|
23
|
+
return isBindingLeafKind(kind) || kind === "object_pattern" || kind === "array_pattern" || kind === "rest_pattern";
|
|
24
|
+
}
|
|
25
|
+
function collectBindingNodes(node, names, result) {
|
|
26
|
+
if (isBindingLeafKind(node.kind())) {
|
|
27
|
+
if (names.has(node.text())) result.push(node);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const child of node.children()) {
|
|
31
|
+
if (child.kind() === "property_identifier") continue;
|
|
32
|
+
if (child.kind() === "=") break;
|
|
33
|
+
collectBindingNodes(child, names, result);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function bindingNodes(node, names) {
|
|
37
|
+
const result = [];
|
|
38
|
+
collectBindingNodes(node, names, result);
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function directBindingNodes(node, names) {
|
|
42
|
+
const result = [];
|
|
43
|
+
for (const child of node.children()) {
|
|
44
|
+
if (child.kind() === "=") break;
|
|
45
|
+
if (isBindingPatternKind(child.kind())) collectBindingNodes(child, names, result);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
function firstDeclaratorChild(node) {
|
|
50
|
+
return node.children().find((child) => child.kind() !== "=") ?? null;
|
|
51
|
+
}
|
|
52
|
+
function declaratorValue(node) {
|
|
53
|
+
const children = node.children();
|
|
54
|
+
const equalsIndex = children.findIndex((child) => child.kind() === "=");
|
|
55
|
+
if (equalsIndex === -1) return null;
|
|
56
|
+
return children.slice(equalsIndex + 1).find((child) => child.kind() !== "comment") ?? null;
|
|
57
|
+
}
|
|
58
|
+
function assignmentTarget(node) {
|
|
59
|
+
const children = node.children();
|
|
60
|
+
const equalsIndex = children.findIndex((child) => child.kind() === "=");
|
|
61
|
+
if (equalsIndex === -1) return null;
|
|
62
|
+
return children.slice(0, equalsIndex).find((child) => child.kind() !== "comment") ?? null;
|
|
63
|
+
}
|
|
64
|
+
function assignmentValue(node) {
|
|
65
|
+
const children = node.children();
|
|
66
|
+
const equalsIndex = children.findIndex((child) => child.kind() === "=");
|
|
67
|
+
if (equalsIndex === -1) return null;
|
|
68
|
+
return children.slice(equalsIndex + 1).find((child) => child.kind() !== "comment") ?? null;
|
|
69
|
+
}
|
|
70
|
+
function parameterDefaultTarget(node) {
|
|
71
|
+
const children = node.children();
|
|
72
|
+
const equalsIndex = children.findIndex((child) => child.kind() === "=");
|
|
73
|
+
if (equalsIndex === -1) return null;
|
|
74
|
+
return children.slice(0, equalsIndex).find((child) => isBindingPatternKind(child.kind())) ?? null;
|
|
75
|
+
}
|
|
76
|
+
function parameterDefaultValue(node) {
|
|
77
|
+
const children = node.children();
|
|
78
|
+
const equalsIndex = children.findIndex((child) => child.kind() === "=");
|
|
79
|
+
if (equalsIndex === -1) return null;
|
|
80
|
+
return children.slice(equalsIndex + 1).find((child) => child.kind() !== "comment") ?? null;
|
|
81
|
+
}
|
|
82
|
+
function addShadowedRange(shadowedRanges, name, scopeNode) {
|
|
83
|
+
const range = scopeNode.range();
|
|
84
|
+
if (!shadowedRanges.has(name)) shadowedRanges.set(name, []);
|
|
85
|
+
shadowedRanges.get(name).push({
|
|
86
|
+
start: range.start.index,
|
|
87
|
+
end: range.end.index
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function nearestScope(node) {
|
|
91
|
+
let current = node.parent();
|
|
92
|
+
while (current) {
|
|
93
|
+
const kind = current.kind();
|
|
94
|
+
if (kind === "statement_block" || kind === "program" || kind === "switch_body" || kind === "for_statement" || kind === "for_in_statement") return current;
|
|
95
|
+
current = current.parent();
|
|
96
|
+
}
|
|
97
|
+
return node;
|
|
98
|
+
}
|
|
99
|
+
function functionScope(node) {
|
|
100
|
+
let current = node.parent();
|
|
101
|
+
while (current) {
|
|
102
|
+
const kind = current.kind();
|
|
103
|
+
if (kind === "function_declaration" || kind === "function_expression" || kind === "arrow_function" || kind === "method_definition" || kind === "program") return current;
|
|
104
|
+
current = current.parent();
|
|
105
|
+
}
|
|
106
|
+
return node;
|
|
107
|
+
}
|
|
108
|
+
function variableDeclarationScope(node) {
|
|
109
|
+
const declaration = node.parent();
|
|
110
|
+
if (/^var\b/.test(declaration?.text().trimStart() ?? "")) return functionScope(node);
|
|
111
|
+
return nearestScope(node);
|
|
112
|
+
}
|
|
113
|
+
function parameterScope(node) {
|
|
114
|
+
let current = node.parent();
|
|
115
|
+
while (current) {
|
|
116
|
+
const kind = current.kind();
|
|
117
|
+
if (kind === "formal_parameters") {
|
|
118
|
+
current = current.parent();
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (kind === "function_declaration" || kind === "function_expression" || kind === "arrow_function" || kind === "method_definition") return current;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
return nearestScope(node);
|
|
125
|
+
}
|
|
126
|
+
function buildShadowedRanges(root, names) {
|
|
127
|
+
const shadowedRanges = /* @__PURE__ */ new Map();
|
|
128
|
+
for (const decl of root.findAll({ rule: { kind: "variable_declarator" } })) {
|
|
129
|
+
if (isInsideImportStatement(decl)) continue;
|
|
130
|
+
const binding = firstDeclaratorChild(decl);
|
|
131
|
+
if (!binding) continue;
|
|
132
|
+
for (const name of bindingNodes(binding, names)) addShadowedRange(shadowedRanges, name.text(), variableDeclarationScope(decl));
|
|
133
|
+
}
|
|
134
|
+
for (const decl of root.findAll({ rule: { any: [
|
|
135
|
+
{ kind: "function_declaration" },
|
|
136
|
+
{ kind: "class_declaration" },
|
|
137
|
+
{ kind: "enum_declaration" }
|
|
138
|
+
] } })) {
|
|
139
|
+
const name = decl.children().find((child) => child.kind() === "identifier" && names.has(child.text()));
|
|
140
|
+
if (name) addShadowedRange(shadowedRanges, name.text(), nearestScope(decl));
|
|
141
|
+
}
|
|
142
|
+
for (const param of root.findAll({ rule: { any: [{ kind: "required_parameter" }, { kind: "optional_parameter" }] } })) for (const name of directBindingNodes(param, names)) addShadowedRange(shadowedRanges, name.text(), parameterScope(param));
|
|
143
|
+
for (const arrow of root.findAll({ rule: { kind: "arrow_function" } })) {
|
|
144
|
+
const children = arrow.children();
|
|
145
|
+
const arrowIndex = children.findIndex((child) => child.kind() === "=>");
|
|
146
|
+
if (arrowIndex === -1) continue;
|
|
147
|
+
for (const child of children.slice(0, arrowIndex)) {
|
|
148
|
+
if (child.kind() === "=") break;
|
|
149
|
+
if (!isBindingPatternKind(child.kind())) continue;
|
|
150
|
+
for (const name of bindingNodes(child, names)) addShadowedRange(shadowedRanges, name.text(), arrow);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const catchClause of root.findAll({ rule: { kind: "catch_clause" } })) for (const name of directBindingNodes(catchClause, names)) addShadowedRange(shadowedRanges, name.text(), catchClause);
|
|
154
|
+
for (const loop of root.findAll({ rule: { kind: "for_in_statement" } })) {
|
|
155
|
+
const children = loop.children();
|
|
156
|
+
const keywordIndex = children.findIndex((child) => child.kind() === "in" || child.kind() === "of");
|
|
157
|
+
if (keywordIndex === -1) continue;
|
|
158
|
+
for (const child of children.slice(0, keywordIndex)) for (const name of bindingNodes(child, names)) addShadowedRange(shadowedRanges, name.text(), loop);
|
|
159
|
+
}
|
|
160
|
+
return shadowedRanges;
|
|
161
|
+
}
|
|
162
|
+
function isShadowed(node, shadowedRanges) {
|
|
163
|
+
const ranges = shadowedRanges.get(node.text());
|
|
164
|
+
if (!ranges) return false;
|
|
165
|
+
const position = node.range().start.index;
|
|
166
|
+
return ranges.some((range) => position >= range.start && position < range.end);
|
|
167
|
+
}
|
|
168
|
+
function unwrapExpression(node) {
|
|
169
|
+
let current = node;
|
|
170
|
+
while (current) {
|
|
171
|
+
const kind = current.kind();
|
|
172
|
+
if (kind === "parenthesized_expression") {
|
|
173
|
+
current = current.children().find((child) => child.kind() !== "(" && child.kind() !== ")") ?? null;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (kind === "as_expression" || kind === "satisfies_expression" || kind === "non_null_expression") {
|
|
177
|
+
current = current.children()[0] ?? null;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (kind === "type_assertion") {
|
|
181
|
+
current = current.children().find((child) => child.kind() !== "type_arguments") ?? null;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
return current;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
function isSdkDbMember(object, dbNames, namespaceNames, shadowedRanges) {
|
|
189
|
+
const unwrapped = unwrapExpression(object);
|
|
190
|
+
if (!unwrapped) return false;
|
|
191
|
+
if (unwrapped.kind() === "identifier") return dbNames.has(unwrapped.text()) && !isShadowed(unwrapped, shadowedRanges);
|
|
192
|
+
if (unwrapped.kind() !== "member_expression") return false;
|
|
193
|
+
const base = unwrapExpression(unwrapped.field("object"));
|
|
194
|
+
const property = memberProperty(unwrapped);
|
|
195
|
+
return base?.kind() === "identifier" && namespaceNames.has(base.text()) && !isShadowed(base, shadowedRanges) && property?.text() === "db";
|
|
196
|
+
}
|
|
197
|
+
function memberProperty(member) {
|
|
198
|
+
const children = member.children();
|
|
199
|
+
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
200
|
+
const child = children[index];
|
|
201
|
+
if (child.kind() === "property_identifier") return child;
|
|
202
|
+
}
|
|
203
|
+
return member.field("property");
|
|
204
|
+
}
|
|
205
|
+
function typeStringLiteral(node) {
|
|
206
|
+
if (!node) return null;
|
|
207
|
+
const kind = node.kind();
|
|
208
|
+
if (kind !== "string" && kind !== "template_string") return null;
|
|
209
|
+
const fragments = node.children().filter((child) => child.kind() === "string_fragment");
|
|
210
|
+
return fragments.length === 1 && fragments[0].text() === "type" ? node : null;
|
|
211
|
+
}
|
|
212
|
+
function replaceStringLiteralValue(node, value) {
|
|
213
|
+
const text = node.text();
|
|
214
|
+
const quote = text.startsWith("'") ? "'" : text.startsWith("`") ? "`" : "\"";
|
|
215
|
+
return node.replace(`${quote}${value}${quote}`);
|
|
216
|
+
}
|
|
217
|
+
function hasTypeBuilderUse(root, name, afterIndex) {
|
|
218
|
+
for (const member of root.findAll({ rule: { kind: "member_expression" } })) {
|
|
219
|
+
if (member.range().start.index <= afterIndex) continue;
|
|
220
|
+
if (memberProperty(member)?.text() !== "type") continue;
|
|
221
|
+
const object = unwrapExpression(member.field("object"));
|
|
222
|
+
if (object?.kind() === "identifier" && object.text() === name) return true;
|
|
223
|
+
}
|
|
224
|
+
for (const subscript of root.findAll({ rule: { kind: "subscript_expression" } })) {
|
|
225
|
+
if (subscript.range().start.index <= afterIndex) continue;
|
|
226
|
+
if (!typeStringLiteral(subscript.field("index"))) continue;
|
|
227
|
+
const object = unwrapExpression(subscript.field("object"));
|
|
228
|
+
if (object?.kind() === "identifier" && object.text() === name) return true;
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
function transform(source, filePath) {
|
|
233
|
+
if (!source.includes("type") || !source.includes(SDK_MODULE)) return null;
|
|
234
|
+
let root;
|
|
235
|
+
try {
|
|
236
|
+
root = parse(sourceLang(filePath, source), source).root();
|
|
237
|
+
} catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const imports = findImportStatements(root).filter((importStmt) => importSource(importStmt) === SDK_MODULE);
|
|
241
|
+
if (imports.length === 0) return null;
|
|
242
|
+
const dbNames = /* @__PURE__ */ new Set();
|
|
243
|
+
const namespaceNames = /* @__PURE__ */ new Set();
|
|
244
|
+
for (const importStmt of imports) {
|
|
245
|
+
for (const binding of importBindings(importStmt)) if (binding.importedName === "db") dbNames.add(binding.localName);
|
|
246
|
+
for (const name of namespaceImportNames(importStmt)) namespaceNames.add(name);
|
|
247
|
+
}
|
|
248
|
+
if (dbNames.size === 0 && namespaceNames.size === 0) return null;
|
|
249
|
+
const shadowedRanges = buildShadowedRanges(root, /* @__PURE__ */ new Set([...dbNames, ...namespaceNames]));
|
|
250
|
+
const edits = [];
|
|
251
|
+
for (const member of root.findAll({ rule: { kind: "member_expression" } })) {
|
|
252
|
+
const property = memberProperty(member);
|
|
253
|
+
if (property?.text() !== "type") continue;
|
|
254
|
+
if (!isSdkDbMember(member.field("object"), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
255
|
+
edits.push(property.replace("table"));
|
|
256
|
+
}
|
|
257
|
+
for (const subscript of root.findAll({ rule: { kind: "subscript_expression" } })) {
|
|
258
|
+
const index = typeStringLiteral(subscript.field("index"));
|
|
259
|
+
if (!index) continue;
|
|
260
|
+
if (!isSdkDbMember(subscript.field("object"), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
261
|
+
edits.push(replaceStringLiteralValue(index, "table"));
|
|
262
|
+
}
|
|
263
|
+
return edits.length > 0 ? root.commitEdits(edits) : null;
|
|
264
|
+
}
|
|
265
|
+
function lineForIndex(source, index) {
|
|
266
|
+
return source.slice(0, index).split(/\r\n|\r|\n/).length;
|
|
267
|
+
}
|
|
268
|
+
function excerptAtIndex(source, index) {
|
|
269
|
+
const lineStart = Math.max(source.lastIndexOf("\n", index - 1) + 1, 0);
|
|
270
|
+
const lineEnd = source.indexOf("\n", index);
|
|
271
|
+
return source.slice(lineStart, lineEnd === -1 ? source.length : lineEnd).trim();
|
|
272
|
+
}
|
|
273
|
+
function objectPatternHasTypeProperty(pattern) {
|
|
274
|
+
return pattern.findAll({ rule: { any: [{
|
|
275
|
+
kind: "property_identifier",
|
|
276
|
+
regex: "^type$"
|
|
277
|
+
}, {
|
|
278
|
+
kind: "shorthand_property_identifier_pattern",
|
|
279
|
+
regex: "^type$"
|
|
280
|
+
}] } }).some((node) => node.text() === "type");
|
|
281
|
+
}
|
|
282
|
+
function namespaceDbAliasBindings(pattern) {
|
|
283
|
+
const aliases = [];
|
|
284
|
+
for (const child of pattern.children()) {
|
|
285
|
+
if (child.kind() === "shorthand_property_identifier_pattern" && child.text() === "db") {
|
|
286
|
+
aliases.push(child);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (child.kind() !== "pair_pattern") continue;
|
|
290
|
+
if (child.field("key")?.text() !== "db") continue;
|
|
291
|
+
const value = child.field("value");
|
|
292
|
+
if (value?.kind() === "identifier") aliases.push(value);
|
|
293
|
+
}
|
|
294
|
+
return aliases;
|
|
295
|
+
}
|
|
296
|
+
function isSdkNamespaceMember(node, namespaceNames, shadowedRanges) {
|
|
297
|
+
const unwrapped = unwrapExpression(node);
|
|
298
|
+
return unwrapped?.kind() === "identifier" && namespaceNames.has(unwrapped.text()) && !isShadowed(unwrapped, shadowedRanges);
|
|
299
|
+
}
|
|
300
|
+
function reviewFindings(source, filePath, relativePath) {
|
|
301
|
+
if (!source.includes("type") || !source.includes(SDK_MODULE)) return [];
|
|
302
|
+
let root;
|
|
303
|
+
try {
|
|
304
|
+
root = parse(sourceLang(filePath, source), source).root();
|
|
305
|
+
} catch {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
const imports = findImportStatements(root).filter((importStmt) => importSource(importStmt) === SDK_MODULE);
|
|
309
|
+
if (imports.length === 0) return [];
|
|
310
|
+
const dbNames = /* @__PURE__ */ new Set();
|
|
311
|
+
const namespaceNames = /* @__PURE__ */ new Set();
|
|
312
|
+
for (const importStmt of imports) {
|
|
313
|
+
for (const binding of importBindings(importStmt)) if (binding.importedName === "db") dbNames.add(binding.localName);
|
|
314
|
+
for (const name of namespaceImportNames(importStmt)) namespaceNames.add(name);
|
|
315
|
+
}
|
|
316
|
+
if (dbNames.size === 0 && namespaceNames.size === 0) return [];
|
|
317
|
+
const shadowedRanges = buildShadowedRanges(root, /* @__PURE__ */ new Set([...dbNames, ...namespaceNames]));
|
|
318
|
+
const findings = [];
|
|
319
|
+
for (const decl of root.findAll({ rule: { kind: "variable_declarator" } })) {
|
|
320
|
+
const binding = firstDeclaratorChild(decl);
|
|
321
|
+
if (binding?.kind() !== "object_pattern" || !objectPatternHasTypeProperty(binding)) continue;
|
|
322
|
+
if (!isSdkDbMember(declaratorValue(decl), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
323
|
+
findings.push({
|
|
324
|
+
file: relativePath,
|
|
325
|
+
line: lineForIndex(source, binding.range().start.index),
|
|
326
|
+
message: "Review destructured db.type builder usage and migrate it to db.table.",
|
|
327
|
+
excerpt: excerptAtIndex(source, binding.range().start.index)
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
for (const decl of root.findAll({ rule: { kind: "variable_declarator" } })) {
|
|
331
|
+
const binding = firstDeclaratorChild(decl);
|
|
332
|
+
if (binding?.kind() !== "object_pattern") continue;
|
|
333
|
+
if (!isSdkNamespaceMember(declaratorValue(decl), namespaceNames, shadowedRanges)) continue;
|
|
334
|
+
for (const alias of namespaceDbAliasBindings(binding)) {
|
|
335
|
+
if (!hasTypeBuilderUse(root, alias.text(), decl.range().end.index)) continue;
|
|
336
|
+
findings.push({
|
|
337
|
+
file: relativePath,
|
|
338
|
+
line: lineForIndex(source, binding.range().start.index),
|
|
339
|
+
message: "Review SDK db alias usage and migrate db.type builder calls to db.table.",
|
|
340
|
+
excerpt: excerptAtIndex(source, binding.range().start.index)
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const decl of root.findAll({ rule: { kind: "variable_declarator" } })) {
|
|
345
|
+
const binding = firstDeclaratorChild(decl);
|
|
346
|
+
if (binding?.kind() !== "identifier") continue;
|
|
347
|
+
if (!isSdkDbMember(declaratorValue(decl), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
348
|
+
if (!hasTypeBuilderUse(root, binding.text(), decl.range().end.index)) continue;
|
|
349
|
+
findings.push({
|
|
350
|
+
file: relativePath,
|
|
351
|
+
line: lineForIndex(source, binding.range().start.index),
|
|
352
|
+
message: "Review SDK db alias usage and migrate db.type builder calls to db.table.",
|
|
353
|
+
excerpt: excerptAtIndex(source, binding.range().start.index)
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
for (const assignment of root.findAll({ rule: { kind: "assignment_expression" } })) {
|
|
357
|
+
const target = assignmentTarget(assignment);
|
|
358
|
+
if (target?.kind() !== "identifier") continue;
|
|
359
|
+
if (!isSdkDbMember(assignmentValue(assignment), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
360
|
+
if (!hasTypeBuilderUse(root, target.text(), assignment.range().end.index)) continue;
|
|
361
|
+
findings.push({
|
|
362
|
+
file: relativePath,
|
|
363
|
+
line: lineForIndex(source, assignment.range().start.index),
|
|
364
|
+
message: "Review SDK db alias usage and migrate db.type builder calls to db.table.",
|
|
365
|
+
excerpt: excerptAtIndex(source, assignment.range().start.index)
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
for (const param of root.findAll({ rule: { any: [{ kind: "required_parameter" }, { kind: "optional_parameter" }] } })) {
|
|
369
|
+
const target = parameterDefaultTarget(param);
|
|
370
|
+
if (target?.kind() !== "identifier") continue;
|
|
371
|
+
if (!isSdkDbMember(parameterDefaultValue(param), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
372
|
+
if (!hasTypeBuilderUse(root, target.text(), param.range().end.index)) continue;
|
|
373
|
+
findings.push({
|
|
374
|
+
file: relativePath,
|
|
375
|
+
line: lineForIndex(source, param.range().start.index),
|
|
376
|
+
message: "Review SDK db alias usage and migrate db.type builder calls to db.table.",
|
|
377
|
+
excerpt: excerptAtIndex(source, param.range().start.index)
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return findings;
|
|
381
|
+
}
|
|
382
|
+
//#endregion
|
|
383
|
+
export { transform as default, reviewFindings };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Lang, parse } from "@ast-grep/napi";
|
|
2
|
+
import * as path from "pathe";
|
|
3
|
+
//#region codemods/v2/env-var-rename/scripts/transform.ts
|
|
4
|
+
const ENV_RENAMES = [
|
|
5
|
+
["TAILOR_PLATFORM_SDK_CONFIG_PATH", "TAILOR_CONFIG_PATH"],
|
|
6
|
+
["TAILOR_PLATFORM_SDK_DTS_PATH", "TAILOR_DTS_PATH"],
|
|
7
|
+
["TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION", "TAILOR_CI_ALLOW_ID_INJECTION"],
|
|
8
|
+
["TAILOR_PLATFORM_SDK_BUILD_ONLY", "TAILOR_DEPLOY_BUILD_ONLY"],
|
|
9
|
+
["TAILOR_SDK_OUTPUT_DIR", "TAILOR_BUILD_OUTPUT_DIR"],
|
|
10
|
+
["TAILOR_SDK_SKILLS_SOURCE", "TAILOR_SKILLS_SOURCE"],
|
|
11
|
+
["TAILOR_SDK_VERSION", "TAILOR_TEMPLATE_SDK_VERSION"],
|
|
12
|
+
["TAILOR_ENABLE_INLINE_SOURCEMAP", "TAILOR_INLINE_SOURCEMAP"],
|
|
13
|
+
["TAILOR_PLATFORM_QUERY_NEWLINE_ON_ENTER", "TAILOR_QUERY_NEWLINE_ON_ENTER"],
|
|
14
|
+
["TAILOR_TOKEN", "TAILOR_PLATFORM_TOKEN"]
|
|
15
|
+
];
|
|
16
|
+
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
17
|
+
".ts",
|
|
18
|
+
".tsx",
|
|
19
|
+
".mts",
|
|
20
|
+
".cts",
|
|
21
|
+
".js",
|
|
22
|
+
".jsx",
|
|
23
|
+
".mjs",
|
|
24
|
+
".cjs"
|
|
25
|
+
]);
|
|
26
|
+
const ENV_BOUNDARY = "[A-Za-z0-9_]";
|
|
27
|
+
const RENAME_PATTERNS = ENV_RENAMES.map(([from, to]) => ({
|
|
28
|
+
pattern: new RegExp(`(?<!${ENV_BOUNDARY})${from}(?!${ENV_BOUNDARY})`, "g"),
|
|
29
|
+
to
|
|
30
|
+
}));
|
|
31
|
+
function escapeRegExp(value) {
|
|
32
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
33
|
+
}
|
|
34
|
+
function replaceTextTokens(source) {
|
|
35
|
+
let updated = source;
|
|
36
|
+
for (const { pattern, to } of RENAME_PATTERNS) updated = updated.replace(pattern, to);
|
|
37
|
+
return updated;
|
|
38
|
+
}
|
|
39
|
+
function sourceLang(filePath) {
|
|
40
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
41
|
+
return ext === ".tsx" || ext === ".jsx" ? Lang.Tsx : Lang.TypeScript;
|
|
42
|
+
}
|
|
43
|
+
function collectStringFragmentEdits(root, source) {
|
|
44
|
+
const edits = [];
|
|
45
|
+
const visit = (node) => {
|
|
46
|
+
if (node.kind() === "string_fragment") {
|
|
47
|
+
const range = node.range();
|
|
48
|
+
const text = source.slice(range.start.index, range.end.index);
|
|
49
|
+
const replacement = replaceTextTokens(text);
|
|
50
|
+
if (replacement !== text) edits.push([
|
|
51
|
+
range.start.index,
|
|
52
|
+
range.end.index,
|
|
53
|
+
replacement
|
|
54
|
+
]);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (const child of node.children()) visit(child);
|
|
58
|
+
};
|
|
59
|
+
visit(root);
|
|
60
|
+
return edits;
|
|
61
|
+
}
|
|
62
|
+
function replaceSourceStringFragments(source, filePath) {
|
|
63
|
+
let root;
|
|
64
|
+
try {
|
|
65
|
+
root = parse(sourceLang(filePath), source).root();
|
|
66
|
+
} catch {
|
|
67
|
+
return source;
|
|
68
|
+
}
|
|
69
|
+
let updated = source;
|
|
70
|
+
const edits = collectStringFragmentEdits(root, source).toSorted(([a], [b]) => b - a);
|
|
71
|
+
for (const [start, end, replacement] of edits) updated = `${updated.slice(0, start)}${replacement}${updated.slice(end)}`;
|
|
72
|
+
return updated;
|
|
73
|
+
}
|
|
74
|
+
function replaceSourceTokens(source, filePath) {
|
|
75
|
+
let updated = source;
|
|
76
|
+
for (const [from, to] of ENV_RENAMES) {
|
|
77
|
+
const escaped = escapeRegExp(from);
|
|
78
|
+
updated = updated.replace(new RegExp(`\\bprocess\\.env\\.${escaped}(?![A-Za-z0-9_$])`, "g"), `process.env.${to}`).replace(new RegExp(`\\bprocess\\.env\\[(["'\`])${escaped}\\1\\]`, "g"), `process.env[$1${to}$1]`).replace(new RegExp(`([,{]\\s*)${escaped}(?=\\s*:)`, "g"), `$1${to}`);
|
|
79
|
+
}
|
|
80
|
+
return replaceSourceStringFragments(updated, filePath);
|
|
81
|
+
}
|
|
82
|
+
function transform(source, filePath) {
|
|
83
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
84
|
+
const updated = SOURCE_EXTENSIONS.has(ext) ? replaceSourceTokens(source, filePath) : replaceTextTokens(source);
|
|
85
|
+
return updated === source ? null : updated;
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { transform as default };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Lang, parse } from "@ast-grep/napi";
|
|
2
|
+
//#region codemods/v2/erd-site-to-plugin/scripts/transform.ts
|
|
3
|
+
const PLUGIN_IMPORT = "import { tailordbErdPlugin } from \"@tailor-platform/sdk-plugin-tailordb-erd\";";
|
|
4
|
+
const DEFINE_PLUGINS_IMPORT = "import { definePlugins } from \"@tailor-platform/sdk\";";
|
|
5
|
+
const SDK_VALUE_IMPORT_REGEX = /(^|\n)import\s*\{[^}\n]*\}\s*from\s*["']@tailor-platform\/sdk["'];?/;
|
|
6
|
+
const FUNCTION_KINDS = /* @__PURE__ */ new Set([
|
|
7
|
+
"arrow_function",
|
|
8
|
+
"function_declaration",
|
|
9
|
+
"function_expression",
|
|
10
|
+
"generator_function",
|
|
11
|
+
"generator_function_declaration",
|
|
12
|
+
"method_definition"
|
|
13
|
+
]);
|
|
14
|
+
function unquote(text) {
|
|
15
|
+
return text.replace(/^["']|["']$/g, "");
|
|
16
|
+
}
|
|
17
|
+
function propertyName(pair) {
|
|
18
|
+
const key = pair.field("key");
|
|
19
|
+
if (!key || key.kind() === "computed_property_name") return null;
|
|
20
|
+
return unquote(key.text());
|
|
21
|
+
}
|
|
22
|
+
function insideFunction(node) {
|
|
23
|
+
for (let current = node.parent(); current; current = current.parent()) if (FUNCTION_KINDS.has(current.kind())) return true;
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the local binding name of `definePlugins` imported from
|
|
28
|
+
* `@tailor-platform/sdk`, honoring `import { definePlugins as alias }`.
|
|
29
|
+
* @param tree - Parsed source tree root.
|
|
30
|
+
* @returns Local binding name, or null when it is not imported.
|
|
31
|
+
*/
|
|
32
|
+
function definePluginsLocalName(tree) {
|
|
33
|
+
const importStatements = tree.findAll({ rule: {
|
|
34
|
+
kind: "import_statement",
|
|
35
|
+
has: {
|
|
36
|
+
kind: "string",
|
|
37
|
+
regex: "^[\"']@tailor-platform/sdk[\"']$"
|
|
38
|
+
}
|
|
39
|
+
} });
|
|
40
|
+
for (const statement of importStatements) for (const specifier of statement.findAll({ rule: { kind: "import_specifier" } })) {
|
|
41
|
+
const name = specifier.field("name");
|
|
42
|
+
if (name?.text() !== "definePlugins") continue;
|
|
43
|
+
return (specifier.field("alias") ?? name).text();
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build an edit that removes a property pair from an object literal, cleaning
|
|
49
|
+
* up the separating comma, an inline line comment that documented the removed
|
|
50
|
+
* property, and the removed line's indentation.
|
|
51
|
+
* @param objectNode - Object literal containing the pair.
|
|
52
|
+
* @param pairNode - Property pair to remove.
|
|
53
|
+
* @returns Edit replacing the object literal with the pair removed.
|
|
54
|
+
*/
|
|
55
|
+
function removePairEdit(objectNode, pairNode) {
|
|
56
|
+
const objText = objectNode.text();
|
|
57
|
+
const objStart = objectNode.range().start.index;
|
|
58
|
+
const start = pairNode.range().start.index - objStart;
|
|
59
|
+
const end = pairNode.range().end.index - objStart;
|
|
60
|
+
const before = objText.slice(0, start);
|
|
61
|
+
const after = objText.slice(end);
|
|
62
|
+
let removeFrom = start;
|
|
63
|
+
let removeTo = end;
|
|
64
|
+
const trailing = after.match(/^[ \t]*,[ \t]*(?:\/\/[^\n]*)?\n?/);
|
|
65
|
+
if (trailing) {
|
|
66
|
+
removeTo = end + trailing[0].length;
|
|
67
|
+
const indent = before.match(/\n[ \t]*$/);
|
|
68
|
+
if (indent) removeFrom = start - (indent[0].length - 1);
|
|
69
|
+
} else {
|
|
70
|
+
const leading = before.match(/,\s*$/);
|
|
71
|
+
if (leading) removeFrom = start - leading[0].length;
|
|
72
|
+
const inlineComment = after.match(/^[ \t]*\/\/[^\n]*/);
|
|
73
|
+
if (inlineComment) removeTo = end + inlineComment[0].length;
|
|
74
|
+
}
|
|
75
|
+
return objectNode.replace(objText.slice(0, removeFrom) + objText.slice(removeTo));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build an edit that appends an argument to a call expression, preserving the
|
|
79
|
+
* call's single-line or multi-line formatting. Insertion points are derived
|
|
80
|
+
* from argument-list AST nodes so a trailing line comment after the last
|
|
81
|
+
* argument cannot swallow the separating comma.
|
|
82
|
+
* @param callNode - Call expression to extend.
|
|
83
|
+
* @param arg - Argument expression to append.
|
|
84
|
+
* @returns Edit replacing the call expression with the argument appended.
|
|
85
|
+
*/
|
|
86
|
+
function appendArgEdit(callNode, arg) {
|
|
87
|
+
const callText = callNode.text();
|
|
88
|
+
const base = callNode.range().start.index;
|
|
89
|
+
const children = callNode.field("arguments").children();
|
|
90
|
+
const args = children.filter((child) => child.isNamed() && child.kind() !== "comment");
|
|
91
|
+
const closeOffset = children.at(-1).range().start.index - base;
|
|
92
|
+
const multiline = callText.includes("\n");
|
|
93
|
+
const closeIndent = callText.slice(0, closeOffset).match(/\n([ \t]*)$/)?.[1] ?? "";
|
|
94
|
+
const argIndent = `${closeIndent} `;
|
|
95
|
+
if (args.length === 0) {
|
|
96
|
+
const head = callText.slice(0, closeOffset).replace(/[ \t]*$/, "");
|
|
97
|
+
const rewritten = multiline ? `${head}${argIndent}${arg},\n${closeIndent})` : `${head}${arg})`;
|
|
98
|
+
return callNode.replace(rewritten);
|
|
99
|
+
}
|
|
100
|
+
const lastArg = args.at(-1);
|
|
101
|
+
const followers = children.slice(children.indexOf(lastArg) + 1);
|
|
102
|
+
const trailingComma = followers.find((child) => !child.isNamed() && child.text() === ",");
|
|
103
|
+
const anchor = trailingComma ?? lastArg;
|
|
104
|
+
if (!multiline) {
|
|
105
|
+
const insertAt = anchor.range().end.index - base;
|
|
106
|
+
const insertion = `${trailingComma ? "" : ","} ${arg}`;
|
|
107
|
+
return callNode.replace(callText.slice(0, insertAt) + insertion + callText.slice(insertAt));
|
|
108
|
+
}
|
|
109
|
+
const argInsertAt = (followers.findLast((child) => child.kind() === "comment" && child.range().start.index >= anchor.range().end.index && child.range().start.line === anchor.range().end.line) ?? anchor).range().end.index - base;
|
|
110
|
+
let rewritten = callText.slice(0, argInsertAt) + `\n${argIndent}${arg},` + callText.slice(argInsertAt);
|
|
111
|
+
if (!trailingComma) {
|
|
112
|
+
const commaAt = lastArg.range().end.index - base;
|
|
113
|
+
rewritten = rewritten.slice(0, commaAt) + "," + rewritten.slice(commaAt);
|
|
114
|
+
}
|
|
115
|
+
return callNode.replace(rewritten);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Add `definePlugins` to an existing single-line value import from
|
|
119
|
+
* `@tailor-platform/sdk`, or return null when no such import exists.
|
|
120
|
+
* @param source - Source code to modify.
|
|
121
|
+
* @returns Modified source, or null when a separate import line is needed.
|
|
122
|
+
*/
|
|
123
|
+
function addDefinePluginsSpecifier(source) {
|
|
124
|
+
const match = source.match(SDK_VALUE_IMPORT_REGEX);
|
|
125
|
+
if (!match) return null;
|
|
126
|
+
const updated = match[0].replace(/,?\s*\}/, ", definePlugins }");
|
|
127
|
+
return source.replace(match[0], updated);
|
|
128
|
+
}
|
|
129
|
+
function insertImports(source, importLines) {
|
|
130
|
+
const sdkImportRegex = /^import\s+.*from\s+["']@tailor-platform\/sdk[^"']*["'];?$/gm;
|
|
131
|
+
let lastMatch = null;
|
|
132
|
+
for (let match = sdkImportRegex.exec(source); match; match = sdkImportRegex.exec(source)) lastMatch = match;
|
|
133
|
+
const block = importLines.join("\n");
|
|
134
|
+
if (lastMatch) {
|
|
135
|
+
const insertPos = lastMatch.index + lastMatch[0].length;
|
|
136
|
+
return source.slice(0, insertPos) + "\n" + block + source.slice(insertPos);
|
|
137
|
+
}
|
|
138
|
+
return block + "\n" + source;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Move `db.<namespace>.erdSite` entries in defineConfig() into a
|
|
142
|
+
* `tailordbErdPlugin({ sites })` argument of definePlugins():
|
|
143
|
+
*
|
|
144
|
+
* 1. Remove each `erdSite` property from `db.<namespace>` objects of
|
|
145
|
+
* top-level defineConfig() calls (factory-wrapped configs are left for
|
|
146
|
+
* manual review, since their erdSite values may reference local bindings)
|
|
147
|
+
* 2. Append `tailordbErdPlugin({ sites: { <namespace>: <value> } })` to the
|
|
148
|
+
* existing definePlugins() call (honoring an import alias), or add a
|
|
149
|
+
* `plugins` export when none exists
|
|
150
|
+
* 3. Add the plugin import (and a definePlugins import when newly needed)
|
|
151
|
+
* @param source - Source code to transform
|
|
152
|
+
* @returns Transformed source or null if no changes needed
|
|
153
|
+
*/
|
|
154
|
+
function transform(source) {
|
|
155
|
+
if (!source.includes("erdSite") || !source.includes("@tailor-platform/sdk")) return null;
|
|
156
|
+
if (source.includes("tailordbErdPlugin")) return null;
|
|
157
|
+
const tree = parse(Lang.TypeScript, source).root();
|
|
158
|
+
const edits = [];
|
|
159
|
+
const siteEntries = [];
|
|
160
|
+
for (const call of tree.findAll({ rule: { pattern: "defineConfig($CONFIG)" } })) {
|
|
161
|
+
if (insideFunction(call)) continue;
|
|
162
|
+
const config = call.getMatch("CONFIG");
|
|
163
|
+
if (!config || config.kind() !== "object") continue;
|
|
164
|
+
const dbObject = config.children().find((child) => child.kind() === "pair" && propertyName(child) === "db")?.field("value");
|
|
165
|
+
if (!dbObject || dbObject.kind() !== "object") continue;
|
|
166
|
+
for (const nsPair of dbObject.children().filter((child) => child.kind() === "pair")) {
|
|
167
|
+
const nsKey = nsPair.field("key");
|
|
168
|
+
const nsObject = nsPair.field("value");
|
|
169
|
+
if (!nsKey || nsKey.kind() === "computed_property_name") continue;
|
|
170
|
+
if (!nsObject || nsObject.kind() !== "object") continue;
|
|
171
|
+
const erdPair = nsObject.children().find((child) => child.kind() === "pair" && propertyName(child) === "erdSite");
|
|
172
|
+
const valueNode = erdPair?.field("value");
|
|
173
|
+
if (!erdPair || !valueNode) continue;
|
|
174
|
+
siteEntries.push(`${nsKey.text()}: ${valueNode.text()}`);
|
|
175
|
+
edits.push(removePairEdit(nsObject, erdPair));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (siteEntries.length === 0) return null;
|
|
179
|
+
const pluginExpr = `tailordbErdPlugin({ sites: { ${siteEntries.join(", ")} } })`;
|
|
180
|
+
const localDefinePlugins = definePluginsLocalName(tree);
|
|
181
|
+
const pluginsCall = tree.find({ rule: { pattern: `${localDefinePlugins ?? "definePlugins"}($$$ARGS)` } });
|
|
182
|
+
if (pluginsCall) edits.push(appendArgEdit(pluginsCall, pluginExpr));
|
|
183
|
+
let result = tree.commitEdits(edits);
|
|
184
|
+
const importLines = [PLUGIN_IMPORT];
|
|
185
|
+
if (!pluginsCall && !localDefinePlugins) {
|
|
186
|
+
const merged = addDefinePluginsSpecifier(result);
|
|
187
|
+
if (merged !== null) result = merged;
|
|
188
|
+
else importLines.push(DEFINE_PLUGINS_IMPORT);
|
|
189
|
+
}
|
|
190
|
+
result = insertImports(result, importLines);
|
|
191
|
+
if (!pluginsCall) result = result.replace(/\s*$/, "\n\n") + `export const plugins = ${localDefinePlugins ?? "definePlugins"}(\n ${pluginExpr},\n);\n`;
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
195
|
+
export { transform as default };
|