@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.
Files changed (27) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/dist/codemods/ast-grep-helpers-CXtWn3RB.js +171 -0
  3. package/dist/codemods/v2/apply-to-deploy/scripts/transform.js +22 -4
  4. package/dist/codemods/v2/auth-attributes-rename/scripts/transform.js +213 -0
  5. package/dist/codemods/v2/auth-connection-token-helper/scripts/transform.js +243 -0
  6. package/dist/codemods/v2/auth-invoker-call-unwrap/scripts/transform.js +7 -0
  7. package/dist/codemods/v2/auth-invoker-unwrap/scripts/transform.js +108 -13
  8. package/dist/codemods/v2/cli-rename/scripts/transform.js +373 -14
  9. package/dist/codemods/v2/db-type-to-table/scripts/transform.js +383 -0
  10. package/dist/codemods/v2/env-var-rename/scripts/transform.js +88 -0
  11. package/dist/codemods/v2/erd-site-to-plugin/scripts/transform.js +195 -0
  12. package/dist/codemods/v2/exec-job-function-rename/scripts/transform.js +95 -0
  13. package/dist/codemods/v2/execute-script-arg/scripts/transform.js +60 -0
  14. package/dist/codemods/v2/forward-relation-name/scripts/transform.js +115 -0
  15. package/dist/codemods/v2/idp-publish-events-rename/scripts/transform.js +186 -0
  16. package/dist/codemods/v2/principal-unify/scripts/transform.js +1555 -44
  17. package/dist/codemods/v2/rename-bin/scripts/transform.js +1087 -0
  18. package/dist/codemods/v2/runtime-globals-opt-in/scripts/transform.js +103 -0
  19. package/dist/codemods/v2/runtime-subpath-namespace/scripts/transform.js +792 -0
  20. package/dist/codemods/v2/sdk-skills-shim/scripts/transform.js +3 -3
  21. package/dist/codemods/v2/seed-exec-to-cli-plugin/scripts/transform.js +115 -0
  22. package/dist/codemods/v2/tailor-output-ignore-dir/scripts/transform.js +14 -0
  23. package/dist/codemods/v2/tailordb-namespace/scripts/transform.js +5 -4
  24. package/dist/codemods/v2/wait-point-rename/scripts/transform.js +126 -0
  25. package/dist/codemods/v2/workflow-trigger-rename/scripts/transform.js +123 -0
  26. package/dist/index.js +1962 -51
  27. 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 };