@tailor-platform/sdk-codemod 0.3.0-next.1 → 0.3.0-next.3
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 +174 -2
- package/dist/codemods/ast-grep-helpers-Bfn39biW.js +171 -0
- package/dist/codemods/v2/apply-to-deploy/scripts/transform.js +17 -1
- 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/define-generators-to-plugins/scripts/transform.js +2 -1
- package/dist/codemods/v2/env-var-rename/scripts/transform.js +88 -0
- package/dist/codemods/v2/execute-script-arg/scripts/transform.js +60 -0
- package/dist/codemods/v2/principal-unify/scripts/transform.js +1556 -45
- 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/sdk-skills-shim/scripts/transform.js +3 -3
- package/dist/codemods/v2/tailor-output-ignore-dir/scripts/transform.js +14 -0
- package/dist/codemods/v2/wait-point-rename/scripts/transform.js +126 -0
- package/dist/index.js +1236 -40
- package/package.json +10 -9
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { a as importSpecNames, i as importSource, n as findImportStatements, o as isTypeOnlyImport, r as importBindings, s as localDeclarationNames, t as buildAddNamedImportEdit } from "../../../ast-grep-helpers-Bfn39biW.js";
|
|
2
|
+
import { Lang, parse } from "@ast-grep/napi";
|
|
3
|
+
//#region codemods/v2/auth-connection-token-helper/scripts/transform.ts
|
|
4
|
+
const RUNTIME_MODULE = "@tailor-platform/sdk/runtime";
|
|
5
|
+
const AUTHCONNECTION = "authconnection";
|
|
6
|
+
const GET_CONNECTION_TOKEN = "getConnectionToken";
|
|
7
|
+
const JSX_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
|
|
8
|
+
const JS_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
9
|
+
".js",
|
|
10
|
+
".mjs",
|
|
11
|
+
".cjs"
|
|
12
|
+
]);
|
|
13
|
+
function quickFilter(source) {
|
|
14
|
+
return source.includes(GET_CONNECTION_TOKEN);
|
|
15
|
+
}
|
|
16
|
+
function sourceLang(filePath, source) {
|
|
17
|
+
const lower = filePath.toLowerCase();
|
|
18
|
+
const extension = lower.slice(lower.lastIndexOf("."));
|
|
19
|
+
if (JSX_FILE_EXTENSIONS.has(extension)) return Lang.Tsx;
|
|
20
|
+
if (JS_FILE_EXTENSIONS.has(extension) && /<>|<\/>|<[A-Za-z][\w.$:-]/.test(source)) return Lang.Tsx;
|
|
21
|
+
return Lang.TypeScript;
|
|
22
|
+
}
|
|
23
|
+
function parseRoot(source, filePath) {
|
|
24
|
+
if (!quickFilter(source)) return null;
|
|
25
|
+
try {
|
|
26
|
+
return parse(sourceLang(filePath, source), source).root();
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function isTailorConfigSource(source) {
|
|
32
|
+
return /(^|\/)tailor\.config(?:\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs))?$/.test(source);
|
|
33
|
+
}
|
|
34
|
+
function findAuthImports(imports) {
|
|
35
|
+
const authImports = [];
|
|
36
|
+
for (const importStmt of imports) {
|
|
37
|
+
const source = importSource(importStmt);
|
|
38
|
+
if (!source || !isTailorConfigSource(source) || isTypeOnlyImport(importStmt)) continue;
|
|
39
|
+
for (const spec of importStmt.findAll({ rule: { kind: "import_specifier" } })) {
|
|
40
|
+
const names = importSpecNames(spec);
|
|
41
|
+
if (names?.importedName !== "auth" || names.typeOnly) continue;
|
|
42
|
+
authImports.push({
|
|
43
|
+
importStmt,
|
|
44
|
+
localName: names.localName,
|
|
45
|
+
spec
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return authImports;
|
|
50
|
+
}
|
|
51
|
+
function runtimeAuthconnectionReference(imports) {
|
|
52
|
+
for (const importStmt of imports) {
|
|
53
|
+
if (importSource(importStmt) !== RUNTIME_MODULE || isTypeOnlyImport(importStmt)) continue;
|
|
54
|
+
for (const binding of importBindings(importStmt)) if (binding.importedName === AUTHCONNECTION && !binding.typeOnly) return binding.localName;
|
|
55
|
+
const local = (importStmt.children().find((child) => child.kind() === "import_clause")?.children().find((child) => child.kind() === "namespace_import"))?.children().find((child) => child.kind() === "identifier");
|
|
56
|
+
if (local) return `${local.text()}.${AUTHCONNECTION}`;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function hasRuntimeImportCollision(root, imports) {
|
|
61
|
+
if (localDeclarationNames(root).has(AUTHCONNECTION)) return true;
|
|
62
|
+
return imports.some((importStmt) => importSource(importStmt) !== RUNTIME_MODULE && importBindings(importStmt).some((binding) => binding.localName === AUTHCONNECTION && !binding.typeOnly));
|
|
63
|
+
}
|
|
64
|
+
function hasAuthLocalCollision(root, authLocalNames) {
|
|
65
|
+
const localNames = localDeclarationNames(root);
|
|
66
|
+
return Array.from(authLocalNames).some((name) => localNames.has(name));
|
|
67
|
+
}
|
|
68
|
+
function findDirectAuthCalls(root, authLocalNames) {
|
|
69
|
+
const calls = [];
|
|
70
|
+
for (const call of root.findAll({ rule: { kind: "call_expression" } })) {
|
|
71
|
+
const callee = call.field("function");
|
|
72
|
+
if (callee?.kind() !== "member_expression") continue;
|
|
73
|
+
const object = callee.field("object");
|
|
74
|
+
const property = callee.field("property");
|
|
75
|
+
if (object?.kind() !== "identifier" || property?.text() !== GET_CONNECTION_TOKEN || !authLocalNames.has(object.text())) continue;
|
|
76
|
+
const range = object.range();
|
|
77
|
+
calls.push({
|
|
78
|
+
objectNode: object,
|
|
79
|
+
localName: object.text(),
|
|
80
|
+
range: [range.start.index, range.end.index]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return calls;
|
|
84
|
+
}
|
|
85
|
+
function isInsideImportStatement(node) {
|
|
86
|
+
let current = node.parent();
|
|
87
|
+
while (current) {
|
|
88
|
+
if (current.kind() === "import_statement") return true;
|
|
89
|
+
current = current.parent();
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
function isInsideScheduledRange(node, ranges) {
|
|
94
|
+
const start = node.range().start.index;
|
|
95
|
+
return ranges.some(([rangeStart, rangeEnd]) => start >= rangeStart && start < rangeEnd);
|
|
96
|
+
}
|
|
97
|
+
function countRemainingRefs(root, localName, scheduledRanges) {
|
|
98
|
+
return root.findAll({ rule: { any: [{ kind: "identifier" }, { kind: "shorthand_property_identifier" }] } }).filter((node) => node.text() === localName).filter((node) => !isInsideImportStatement(node) && !isInsideScheduledRange(node, scheduledRanges)).length;
|
|
99
|
+
}
|
|
100
|
+
function importInsertionIndex(root, imports, source) {
|
|
101
|
+
const lastImport = imports.at(-1);
|
|
102
|
+
if (lastImport) return lastImport.range().end.index;
|
|
103
|
+
if (source.startsWith("#!")) {
|
|
104
|
+
const newlineIndex = source.indexOf("\n");
|
|
105
|
+
return newlineIndex === -1 ? source.length : newlineIndex + 1;
|
|
106
|
+
}
|
|
107
|
+
return root.children().find((child) => child.kind() !== "comment")?.range().start.index ?? 0;
|
|
108
|
+
}
|
|
109
|
+
function buildAddRuntimeImportEdit(root, source, imports) {
|
|
110
|
+
return buildAddNamedImportEdit({
|
|
111
|
+
importName: AUTHCONNECTION,
|
|
112
|
+
imports,
|
|
113
|
+
insertionIndex: importInsertionIndex,
|
|
114
|
+
moduleName: RUNTIME_MODULE,
|
|
115
|
+
root,
|
|
116
|
+
source
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function lineStartIndex(source, index) {
|
|
120
|
+
let pos = index;
|
|
121
|
+
while (pos > 0 && source[pos - 1] !== "\n" && source[pos - 1] !== "\r") pos--;
|
|
122
|
+
return pos;
|
|
123
|
+
}
|
|
124
|
+
function consumeLineBreak(source, index) {
|
|
125
|
+
if (source[index] === "\r") return source[index + 1] === "\n" ? index + 2 : index + 1;
|
|
126
|
+
if (source[index] === "\n") return index + 1;
|
|
127
|
+
return index;
|
|
128
|
+
}
|
|
129
|
+
function isHorizontalWhitespace(source, start, end) {
|
|
130
|
+
for (let index = start; index < end; index++) if (source[index] !== " " && source[index] !== " ") return false;
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
function buildImportRemovalEdit(source, binding) {
|
|
134
|
+
if (binding.importStmt.findAll({ rule: { kind: "import_specifier" } }).length === 1) {
|
|
135
|
+
const range = binding.importStmt.range();
|
|
136
|
+
let end = range.end.index;
|
|
137
|
+
if (source[end] === "\n") end++;
|
|
138
|
+
return {
|
|
139
|
+
startPos: range.start.index,
|
|
140
|
+
endPos: end,
|
|
141
|
+
insertedText: ""
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const range = binding.spec.range();
|
|
145
|
+
let start = range.start.index;
|
|
146
|
+
let end = range.end.index;
|
|
147
|
+
const specEnd = end;
|
|
148
|
+
while (end < source.length && /[ \t]/.test(source[end])) end++;
|
|
149
|
+
if (source[end] === ",") {
|
|
150
|
+
end++;
|
|
151
|
+
while (end < source.length && /[ \t]/.test(source[end])) end++;
|
|
152
|
+
const nextLine = consumeLineBreak(source, end);
|
|
153
|
+
const lineStart = lineStartIndex(source, start);
|
|
154
|
+
if (nextLine !== end && isHorizontalWhitespace(source, lineStart, start)) return {
|
|
155
|
+
startPos: lineStart,
|
|
156
|
+
endPos: nextLine,
|
|
157
|
+
insertedText: ""
|
|
158
|
+
};
|
|
159
|
+
return {
|
|
160
|
+
startPos: start,
|
|
161
|
+
endPos: end,
|
|
162
|
+
insertedText: ""
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const nextLine = consumeLineBreak(source, end);
|
|
166
|
+
const lineStart = lineStartIndex(source, start);
|
|
167
|
+
if (nextLine !== end && isHorizontalWhitespace(source, lineStart, start)) return {
|
|
168
|
+
startPos: lineStart,
|
|
169
|
+
endPos: nextLine,
|
|
170
|
+
insertedText: ""
|
|
171
|
+
};
|
|
172
|
+
while (start > 0 && /[ \t]/.test(source[start - 1])) start--;
|
|
173
|
+
if (source[start - 1] === ",") start--;
|
|
174
|
+
return {
|
|
175
|
+
startPos: start,
|
|
176
|
+
endPos: specEnd,
|
|
177
|
+
insertedText: ""
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function applyEdits(source, edits) {
|
|
181
|
+
return edits.toSorted((a, b) => b.startPos - a.startPos || b.endPos - a.endPos).reduce((current, edit) => `${current.slice(0, edit.startPos)}${edit.insertedText}${current.slice(edit.endPos)}`, source).replace(/^\n+/, "");
|
|
182
|
+
}
|
|
183
|
+
function transformParsed(source, root) {
|
|
184
|
+
const imports = findImportStatements(root);
|
|
185
|
+
const authImports = findAuthImports(imports);
|
|
186
|
+
if (authImports.length === 0) return null;
|
|
187
|
+
const authLocalNames = new Set(authImports.map((binding) => binding.localName));
|
|
188
|
+
if (hasAuthLocalCollision(root, authLocalNames)) return null;
|
|
189
|
+
const calls = findDirectAuthCalls(root, authLocalNames);
|
|
190
|
+
if (calls.length === 0) return null;
|
|
191
|
+
const existingRuntimeRef = runtimeAuthconnectionReference(imports);
|
|
192
|
+
if (!existingRuntimeRef && hasRuntimeImportCollision(root, imports)) return null;
|
|
193
|
+
const runtimeRef = existingRuntimeRef ?? AUTHCONNECTION;
|
|
194
|
+
const edits = calls.map((call) => call.objectNode.replace(runtimeRef));
|
|
195
|
+
if (!existingRuntimeRef) edits.push(buildAddRuntimeImportEdit(root, source, imports));
|
|
196
|
+
const scheduledRangesByLocalName = /* @__PURE__ */ new Map();
|
|
197
|
+
for (const call of calls) {
|
|
198
|
+
const ranges = scheduledRangesByLocalName.get(call.localName) ?? [];
|
|
199
|
+
ranges.push(call.range);
|
|
200
|
+
scheduledRangesByLocalName.set(call.localName, ranges);
|
|
201
|
+
}
|
|
202
|
+
for (const binding of authImports) {
|
|
203
|
+
if (!scheduledRangesByLocalName.has(binding.localName)) continue;
|
|
204
|
+
if (countRemainingRefs(root, binding.localName, scheduledRangesByLocalName.get(binding.localName) ?? []) > 0) continue;
|
|
205
|
+
const edit = buildImportRemovalEdit(source, binding);
|
|
206
|
+
if (edit) edits.push(edit);
|
|
207
|
+
}
|
|
208
|
+
const result = applyEdits(source, edits);
|
|
209
|
+
return result === source ? null : result;
|
|
210
|
+
}
|
|
211
|
+
function transform(source, filePath) {
|
|
212
|
+
const root = parseRoot(source, filePath);
|
|
213
|
+
return root ? transformParsed(source, root) : null;
|
|
214
|
+
}
|
|
215
|
+
function lineForIndex(source, index) {
|
|
216
|
+
return source.slice(0, index).split(/\r\n|\r|\n/).length;
|
|
217
|
+
}
|
|
218
|
+
function excerptForLine(line) {
|
|
219
|
+
return line.trim();
|
|
220
|
+
}
|
|
221
|
+
function isReviewLine(excerpt) {
|
|
222
|
+
if (!excerpt.includes(GET_CONNECTION_TOKEN)) return false;
|
|
223
|
+
if (excerpt.includes(`${AUTHCONNECTION}.${GET_CONNECTION_TOKEN}`) || excerpt.includes(`tailor.${AUTHCONNECTION}.${GET_CONNECTION_TOKEN}`)) return false;
|
|
224
|
+
return excerpt.includes(`.${GET_CONNECTION_TOKEN}`) || excerpt.includes(`["${GET_CONNECTION_TOKEN}"]`) || excerpt.includes(`['${GET_CONNECTION_TOKEN}']`) || new RegExp(`[,{]\\s*${GET_CONNECTION_TOKEN}\\s*[:}=,]`).test(excerpt);
|
|
225
|
+
}
|
|
226
|
+
function reviewFindings(source, _filePath, relativePath) {
|
|
227
|
+
if (!quickFilter(source)) return [];
|
|
228
|
+
const findings = [];
|
|
229
|
+
let offset = 0;
|
|
230
|
+
for (const line of source.split(/\n/)) {
|
|
231
|
+
const excerpt = excerptForLine(line);
|
|
232
|
+
if (isReviewLine(excerpt)) findings.push({
|
|
233
|
+
file: relativePath,
|
|
234
|
+
line: lineForIndex(source, offset),
|
|
235
|
+
message: "Replace defineAuth auth.getConnectionToken() with runtime authconnection.",
|
|
236
|
+
excerpt
|
|
237
|
+
});
|
|
238
|
+
offset += line.length + 1;
|
|
239
|
+
}
|
|
240
|
+
return findings;
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
export { transform as default, reviewFindings };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { transformAuthInvoker } from "../../auth-invoker-unwrap/scripts/transform.js";
|
|
2
|
+
//#region codemods/v2/auth-invoker-call-unwrap/scripts/transform.ts
|
|
3
|
+
function transform(source, filePath) {
|
|
4
|
+
return transformAuthInvoker(source, filePath, { renameOptionKeys: false });
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { transform as default };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Lang, parse } from "@ast-grep/napi";
|
|
2
2
|
//#region codemods/v2/auth-invoker-unwrap/scripts/transform.ts
|
|
3
|
-
const
|
|
3
|
+
const QUICK_FILTER_NEEDLES = ["auth.invoker", "authInvoker"];
|
|
4
4
|
function quickFilter(source) {
|
|
5
|
-
return source.includes(
|
|
5
|
+
return QUICK_FILTER_NEEDLES.some((needle) => source.includes(needle));
|
|
6
6
|
}
|
|
7
7
|
function isInsideImportStatement(node) {
|
|
8
8
|
let current = node.parent();
|
|
@@ -121,32 +121,127 @@ function findAuthImports(root) {
|
|
|
121
121
|
return false;
|
|
122
122
|
});
|
|
123
123
|
}
|
|
124
|
+
function sameRange(a, b) {
|
|
125
|
+
const ar = a.range();
|
|
126
|
+
const br = b.range();
|
|
127
|
+
return ar.start.index === br.start.index && ar.end.index === br.end.index;
|
|
128
|
+
}
|
|
129
|
+
function keyText(node) {
|
|
130
|
+
if (!node) return null;
|
|
131
|
+
return node.text().replace(/^['"]|['"]$/g, "");
|
|
132
|
+
}
|
|
133
|
+
function expressionArguments(args) {
|
|
134
|
+
return args.children().filter((child) => ![
|
|
135
|
+
"(",
|
|
136
|
+
")",
|
|
137
|
+
","
|
|
138
|
+
].includes(child.kind()));
|
|
139
|
+
}
|
|
140
|
+
function argumentCallForObject(objectNode) {
|
|
141
|
+
const args = objectNode.parent();
|
|
142
|
+
const call = args?.parent();
|
|
143
|
+
if (args?.kind() !== "arguments" || call?.kind() !== "call_expression") return null;
|
|
144
|
+
const index = expressionArguments(args).findIndex((arg) => sameRange(arg, objectNode));
|
|
145
|
+
return index === -1 ? null : {
|
|
146
|
+
call,
|
|
147
|
+
index
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function calleeText(call) {
|
|
151
|
+
return call.field("function")?.text() ?? "";
|
|
152
|
+
}
|
|
153
|
+
function isCreateCallOptionObject(objectNode, functionName) {
|
|
154
|
+
const callInfo = argumentCallForObject(objectNode);
|
|
155
|
+
return callInfo?.index === 0 && callInfo.call.field("function")?.kind() === "identifier" && calleeText(callInfo.call) === functionName;
|
|
156
|
+
}
|
|
157
|
+
function isExecutorOperationObject(objectNode) {
|
|
158
|
+
const operationPair = objectNode.parent();
|
|
159
|
+
if (operationPair?.kind() !== "pair" || keyText(operationPair.field("key")) !== "operation") return false;
|
|
160
|
+
const configObject = operationPair.parent();
|
|
161
|
+
return configObject?.kind() === "object" && isCreateCallOptionObject(configObject, "createExecutor");
|
|
162
|
+
}
|
|
163
|
+
function isSupportedInvokerOptionObject(objectNode) {
|
|
164
|
+
return isCreateCallOptionObject(objectNode, "createResolver") || isCreateCallOptionObject(objectNode, "startWorkflow") || isExecutorOperationObject(objectNode);
|
|
165
|
+
}
|
|
166
|
+
function optionObjectForPairKey(node) {
|
|
167
|
+
const parent = node.parent();
|
|
168
|
+
if (!parent || parent.kind() !== "pair") return null;
|
|
169
|
+
const key = parent.field("key");
|
|
170
|
+
if (!key || !sameRange(key, node)) return null;
|
|
171
|
+
const objectNode = parent.parent();
|
|
172
|
+
return objectNode?.kind() === "object" ? objectNode : null;
|
|
173
|
+
}
|
|
174
|
+
function isSupportedInvokerOptionKey(node) {
|
|
175
|
+
const objectNode = optionObjectForPairKey(node) ?? node.parent();
|
|
176
|
+
return objectNode?.kind() === "object" && isSupportedInvokerOptionObject(objectNode);
|
|
177
|
+
}
|
|
178
|
+
function isSupportedInvokerValueCall(node) {
|
|
179
|
+
const pair = node.parent();
|
|
180
|
+
if (pair?.kind() !== "pair") return false;
|
|
181
|
+
const value = pair.field("value");
|
|
182
|
+
if (!value || !sameRange(value, node)) return false;
|
|
183
|
+
const key = keyText(pair.field("key"));
|
|
184
|
+
if (key !== "authInvoker" && key !== "invoker") return false;
|
|
185
|
+
const objectNode = pair.parent();
|
|
186
|
+
return objectNode?.kind() === "object" && isSupportedInvokerOptionObject(objectNode);
|
|
187
|
+
}
|
|
188
|
+
function findAuthInvokerShorthands(root) {
|
|
189
|
+
return root.findAll({ rule: {
|
|
190
|
+
kind: "shorthand_property_identifier",
|
|
191
|
+
regex: "^authInvoker$"
|
|
192
|
+
} }).filter(isSupportedInvokerOptionKey);
|
|
193
|
+
}
|
|
194
|
+
function findAuthInvokerPropertyKeys(root) {
|
|
195
|
+
return root.findAll({ rule: {
|
|
196
|
+
kind: "property_identifier",
|
|
197
|
+
regex: "^authInvoker$"
|
|
198
|
+
} }).filter(isSupportedInvokerOptionKey);
|
|
199
|
+
}
|
|
200
|
+
function findQuotedAuthInvokerPropertyKeys(root) {
|
|
201
|
+
return root.findAll({ rule: {
|
|
202
|
+
kind: "string",
|
|
203
|
+
regex: "^['\"]authInvoker['\"]$"
|
|
204
|
+
} }).filter(isSupportedInvokerOptionKey);
|
|
205
|
+
}
|
|
206
|
+
function renameQuotedKey(node) {
|
|
207
|
+
const quote = node.text().startsWith("'") ? "'" : "\"";
|
|
208
|
+
return `${quote}invoker${quote}`;
|
|
209
|
+
}
|
|
124
210
|
/**
|
|
125
|
-
* Replace `auth.invoker("name")` calls with the bare `"name"` string literal
|
|
211
|
+
* Replace `auth.invoker("name")` calls with the bare `"name"` string literal
|
|
212
|
+
* and optionally rename `authInvoker:` option keys to `invoker:`.
|
|
126
213
|
* If no other `auth` references remain after the rewrite, drop the `auth`
|
|
127
214
|
* specifier (or the entire import line when `auth` was its sole specifier).
|
|
128
215
|
*
|
|
129
|
-
* `auth.invoker()` was
|
|
130
|
-
* directly
|
|
131
|
-
* pull config-layer
|
|
216
|
+
* `auth.invoker()` was removed in favor of passing the machine user name
|
|
217
|
+
* directly to `invoker`; carrying the `auth` import only for `.invoker()`
|
|
218
|
+
* would otherwise pull config-layer modules into runtime bundles.
|
|
132
219
|
* @param source - File contents
|
|
133
220
|
* @param filePath - Absolute path to the file (kept for the runner signature)
|
|
221
|
+
* @param options - Transform behavior flags
|
|
134
222
|
* @returns Transformed source or null when nothing matched.
|
|
135
223
|
*/
|
|
136
|
-
function
|
|
224
|
+
function transformAuthInvoker(source, _filePath, options = {}) {
|
|
137
225
|
if (!quickFilter(source)) return null;
|
|
226
|
+
const renameOptionKeys = options.renameOptionKeys ?? true;
|
|
138
227
|
const root = parse(source.includes("</") || source.includes("/>") ? Lang.Tsx : Lang.TypeScript, source).root();
|
|
139
|
-
const calls = findInvokerCalls(root);
|
|
140
|
-
if (calls.length === 0) return null;
|
|
228
|
+
const calls = findInvokerCalls(root).filter((c) => isSupportedInvokerValueCall(c.callNode));
|
|
141
229
|
const edits = calls.map((c) => c.callNode.replace(c.argText));
|
|
142
|
-
if (
|
|
230
|
+
if (renameOptionKeys) {
|
|
231
|
+
edits.push(...findAuthInvokerPropertyKeys(root).map((node) => node.replace("invoker")));
|
|
232
|
+
edits.push(...findQuotedAuthInvokerPropertyKeys(root).map((node) => node.replace(renameQuotedKey(node))));
|
|
233
|
+
edits.push(...findAuthInvokerShorthands(root).map((node) => node.replace("invoker: authInvoker")));
|
|
234
|
+
}
|
|
235
|
+
if (calls.length > 0 && countRemainingAuthRefs(root, calls.map((c) => c.range)) === 0) for (const importStmt of findAuthImports(root)) {
|
|
143
236
|
const edit = buildAuthImportRemovalEdit(source, importStmt);
|
|
144
237
|
if (edit) edits.push(edit);
|
|
145
238
|
}
|
|
146
|
-
|
|
147
|
-
let result = root.commitEdits(edits);
|
|
239
|
+
let result = edits.length === 0 ? source : root.commitEdits(edits);
|
|
148
240
|
result = result.replace(/^[\t ]*\n+/, "").replace(/\n{3,}/g, "\n\n");
|
|
149
241
|
return result === source ? null : result;
|
|
150
242
|
}
|
|
243
|
+
function transform(source, filePath) {
|
|
244
|
+
return transformAuthInvoker(source, filePath);
|
|
245
|
+
}
|
|
151
246
|
//#endregion
|
|
152
|
-
export { transform as default };
|
|
247
|
+
export { transform as default, transformAuthInvoker };
|
|
@@ -47,7 +47,8 @@ function transform(source) {
|
|
|
47
47
|
if (arg.kind() === "array") {
|
|
48
48
|
const children = arg.children().filter((c) => c.isNamed() && c.kind() !== "comment");
|
|
49
49
|
if (children.length >= 1) {
|
|
50
|
-
const
|
|
50
|
+
const packageName = children[0].text().replace(/^["']|["']$/g, "");
|
|
51
|
+
const mapping = PLUGIN_MAP[packageName];
|
|
51
52
|
if (mapping) {
|
|
52
53
|
migratedArgs++;
|
|
53
54
|
importsToAdd.set(mapping.importPath, mapping.functionName);
|
|
@@ -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,60 @@
|
|
|
1
|
+
import { Lang, parse } from "@ast-grep/napi";
|
|
2
|
+
//#region codemods/v2/execute-script-arg/scripts/transform.ts
|
|
3
|
+
const NEEDLE = "executeScript";
|
|
4
|
+
function quickFilter(source) {
|
|
5
|
+
return source.includes(NEEDLE) && source.includes("JSON.stringify");
|
|
6
|
+
}
|
|
7
|
+
function pairKeyText(pair) {
|
|
8
|
+
const key = pair.children()[0];
|
|
9
|
+
if (!key) return null;
|
|
10
|
+
return key.text().replace(/^['"]|['"]$/g, "");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* True when `stringifyCall` is the value of a top-level `arg:` property in the
|
|
14
|
+
* object literal passed directly to `executeScript(...)`. The chain checked is
|
|
15
|
+
* `JSON.stringify(...)` → pair (`arg:`) → object → arguments → `executeScript`
|
|
16
|
+
* call, so a nested `arg:` (e.g. `executeScript({ opts: { arg: ... } })`) or an
|
|
17
|
+
* unrelated `JSON.stringify` is left untouched.
|
|
18
|
+
*/
|
|
19
|
+
function isExecuteScriptArg(stringifyCall) {
|
|
20
|
+
const pair = stringifyCall.parent();
|
|
21
|
+
if (!pair || pair.kind() !== "pair") return false;
|
|
22
|
+
if (pairKeyText(pair) !== "arg") return false;
|
|
23
|
+
const obj = pair.parent();
|
|
24
|
+
if (!obj || obj.kind() !== "object") return false;
|
|
25
|
+
const args = obj.parent();
|
|
26
|
+
if (!args || args.kind() !== "arguments") return false;
|
|
27
|
+
const call = args.parent();
|
|
28
|
+
if (!call || call.kind() !== "call_expression") return false;
|
|
29
|
+
const callee = call.children()[0];
|
|
30
|
+
return !!callee && callee.text() === NEEDLE;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Rewrite `executeScript({ ..., arg: JSON.stringify(X), ... })` to
|
|
34
|
+
* `executeScript({ ..., arg: X, ... })`.
|
|
35
|
+
*
|
|
36
|
+
* In v2 the `executeScript` `arg` option takes a JSON-serializable value and
|
|
37
|
+
* serializes it internally, so a pre-stringified argument double-encodes. Only
|
|
38
|
+
* the literal `arg: JSON.stringify(<single expr>)` form is rewritten; indirect
|
|
39
|
+
* forms (a stringified value held in a variable, `JSON.stringify(x, null, 2)`,
|
|
40
|
+
* etc.) are left for manual migration.
|
|
41
|
+
* @param source - File contents
|
|
42
|
+
* @param _filePath - Absolute path to the file (kept for the runner signature)
|
|
43
|
+
* @returns Transformed source or null when nothing matched.
|
|
44
|
+
*/
|
|
45
|
+
function transform(source, _filePath) {
|
|
46
|
+
if (!quickFilter(source)) return null;
|
|
47
|
+
const root = parse(source.includes("</") || source.includes("/>") ? Lang.Tsx : Lang.TypeScript, source).root();
|
|
48
|
+
const edits = [];
|
|
49
|
+
for (const match of root.findAll({ rule: { pattern: "JSON.stringify($X)" } })) {
|
|
50
|
+
if (!isExecuteScriptArg(match)) continue;
|
|
51
|
+
const inner = match.getMatch("X");
|
|
52
|
+
if (!inner) continue;
|
|
53
|
+
edits.push(match.replace(inner.text()));
|
|
54
|
+
}
|
|
55
|
+
if (edits.length === 0) return null;
|
|
56
|
+
const result = root.commitEdits(edits);
|
|
57
|
+
return result === source ? null : result;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { transform as default };
|