@tailor-platform/sdk-codemod 0.3.8 → 0.4.0-next.9
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 +261 -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/execute-script-arg/scripts/transform.js +60 -0
- package/dist/codemods/v2/forward-relation-name/scripts/transform.js +115 -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/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 +122 -0
- package/dist/index.js +1713 -51
- package/package.json +5 -4
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Lang, parse } from "@ast-grep/napi";
|
|
2
|
+
//#region codemods/v2/auth-attributes-rename/scripts/transform.ts
|
|
3
|
+
const SDK_MODULE = "@tailor-platform/sdk";
|
|
4
|
+
const TYPE_RENAME_MAP = {
|
|
5
|
+
AttributeMap: "Attributes",
|
|
6
|
+
UserAttributeMap: "UserAttributes",
|
|
7
|
+
InferredAttributeMap: "InferredAttributes"
|
|
8
|
+
};
|
|
9
|
+
function quickFilter(source) {
|
|
10
|
+
return source.includes(SDK_MODULE) && Object.keys(TYPE_RENAME_MAP).some((name) => source.includes(name));
|
|
11
|
+
}
|
|
12
|
+
function isSdkModuleLiteral(node) {
|
|
13
|
+
return node.kind() === "string" && /^["']@tailor-platform\/sdk["']$/.test(node.text());
|
|
14
|
+
}
|
|
15
|
+
function hasSdkModuleLiteral(node) {
|
|
16
|
+
return node.findAll({ rule: { kind: "string" } }).some(isSdkModuleLiteral);
|
|
17
|
+
}
|
|
18
|
+
function moduleSpecifierLiteral(node) {
|
|
19
|
+
const directLiteral = node.children().find((child) => child.kind() === "string");
|
|
20
|
+
if (directLiteral) return directLiteral;
|
|
21
|
+
return node.children().find((child) => child.kind() === "module")?.children().find((child) => child.kind() === "string");
|
|
22
|
+
}
|
|
23
|
+
function hasSdkModuleSpecifier(node) {
|
|
24
|
+
const literal = moduleSpecifierLiteral(node);
|
|
25
|
+
return literal ? isSdkModuleLiteral(literal) : false;
|
|
26
|
+
}
|
|
27
|
+
function identifierChildren(node) {
|
|
28
|
+
return node.children().filter((child) => child.kind() === "identifier");
|
|
29
|
+
}
|
|
30
|
+
function typeIdentifierChildren(node) {
|
|
31
|
+
return node.children().filter((child) => child.kind() === "type_identifier");
|
|
32
|
+
}
|
|
33
|
+
function sameRange(a, b) {
|
|
34
|
+
const ar = a.range();
|
|
35
|
+
const br = b.range();
|
|
36
|
+
return ar.start.index === br.start.index && ar.end.index === br.end.index;
|
|
37
|
+
}
|
|
38
|
+
function addReplacement(edits, editedRanges, node, replacement) {
|
|
39
|
+
if (node.text() === replacement) return;
|
|
40
|
+
const r = node.range();
|
|
41
|
+
const key = `${r.start.index}:${r.end.index}`;
|
|
42
|
+
if (editedRanges.has(key)) return;
|
|
43
|
+
editedRanges.add(key);
|
|
44
|
+
edits.push(node.replace(replacement));
|
|
45
|
+
}
|
|
46
|
+
function renamedType(name) {
|
|
47
|
+
return TYPE_RENAME_MAP[name];
|
|
48
|
+
}
|
|
49
|
+
function isDeclarationName(node) {
|
|
50
|
+
const parent = node.parent();
|
|
51
|
+
if (!parent || ![
|
|
52
|
+
"class_declaration",
|
|
53
|
+
"enum_declaration",
|
|
54
|
+
"interface_declaration",
|
|
55
|
+
"type_alias_declaration",
|
|
56
|
+
"type_parameter"
|
|
57
|
+
].includes(parent.kind())) return false;
|
|
58
|
+
const name = parent?.field("name");
|
|
59
|
+
return !!name && sameRange(name, node);
|
|
60
|
+
}
|
|
61
|
+
function isNestedTypeName(node) {
|
|
62
|
+
return node.parent()?.kind() === "nested_type_identifier";
|
|
63
|
+
}
|
|
64
|
+
function declarationName(node) {
|
|
65
|
+
if ([
|
|
66
|
+
"class_declaration",
|
|
67
|
+
"enum_declaration",
|
|
68
|
+
"interface_declaration",
|
|
69
|
+
"type_alias_declaration"
|
|
70
|
+
].includes(node.kind())) return node.field("name") ?? void 0;
|
|
71
|
+
if (node.kind() !== "export_statement") return void 0;
|
|
72
|
+
return node.children().find((child) => [
|
|
73
|
+
"class_declaration",
|
|
74
|
+
"enum_declaration",
|
|
75
|
+
"interface_declaration",
|
|
76
|
+
"type_alias_declaration"
|
|
77
|
+
].includes(child.kind()))?.field("name") ?? void 0;
|
|
78
|
+
}
|
|
79
|
+
function scopeDeclaresType(scope, name, reference) {
|
|
80
|
+
return scope.children().some((child) => {
|
|
81
|
+
const declaredName = declarationName(child);
|
|
82
|
+
return !!declaredName && declaredName.text() === name && !sameRange(declaredName, reference);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function hasTypeParameterShadow(node, name) {
|
|
86
|
+
return node.findAll({ rule: { kind: "type_parameter" } }).some((parameter) => typeIdentifierChildren(parameter)[0]?.text() === name);
|
|
87
|
+
}
|
|
88
|
+
function isShadowedTypeReference(node, name) {
|
|
89
|
+
let current = node.parent();
|
|
90
|
+
while (current) {
|
|
91
|
+
if (current.kind() === "statement_block" && scopeDeclaresType(current, name, node)) return true;
|
|
92
|
+
if ([
|
|
93
|
+
"class_declaration",
|
|
94
|
+
"function_declaration",
|
|
95
|
+
"interface_declaration",
|
|
96
|
+
"method_definition",
|
|
97
|
+
"type_alias_declaration"
|
|
98
|
+
].includes(current.kind()) && hasTypeParameterShadow(current, name)) return true;
|
|
99
|
+
current = current.parent();
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
function collectSdkImports(root, edits, editedRanges) {
|
|
104
|
+
const localTypeRenames = /* @__PURE__ */ new Map();
|
|
105
|
+
const namespaceNames = /* @__PURE__ */ new Set();
|
|
106
|
+
const importStmts = root.findAll({ rule: { kind: "import_statement" } });
|
|
107
|
+
for (const importStmt of importStmts) {
|
|
108
|
+
if (!hasSdkModuleSpecifier(importStmt)) continue;
|
|
109
|
+
const namespaceImports = importStmt.findAll({ rule: { kind: "namespace_import" } });
|
|
110
|
+
for (const namespaceImport of namespaceImports) {
|
|
111
|
+
const localName = identifierChildren(namespaceImport).at(-1)?.text();
|
|
112
|
+
if (localName) namespaceNames.add(localName);
|
|
113
|
+
}
|
|
114
|
+
const specs = importStmt.findAll({ rule: { kind: "import_specifier" } });
|
|
115
|
+
for (const spec of specs) {
|
|
116
|
+
const identifiers = identifierChildren(spec);
|
|
117
|
+
const imported = identifiers[0];
|
|
118
|
+
if (!imported) continue;
|
|
119
|
+
const replacement = renamedType(imported.text());
|
|
120
|
+
if (!replacement) continue;
|
|
121
|
+
addReplacement(edits, editedRanges, imported, replacement);
|
|
122
|
+
if (identifiers.length === 1) localTypeRenames.set(imported.text(), replacement);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
localTypeRenames,
|
|
127
|
+
namespaceNames
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function rewriteSdkExports(root, edits, editedRanges) {
|
|
131
|
+
const exportStmts = root.findAll({ rule: { kind: "export_statement" } });
|
|
132
|
+
for (const exportStmt of exportStmts) {
|
|
133
|
+
if (!hasSdkModuleSpecifier(exportStmt)) continue;
|
|
134
|
+
const specs = exportStmt.findAll({ rule: { kind: "export_specifier" } });
|
|
135
|
+
for (const spec of specs) {
|
|
136
|
+
const exported = identifierChildren(spec)[0];
|
|
137
|
+
if (!exported) continue;
|
|
138
|
+
const replacement = renamedType(exported.text());
|
|
139
|
+
if (replacement) addReplacement(edits, editedRanges, exported, replacement);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function rewriteModuleAugmentations(root, edits, editedRanges) {
|
|
144
|
+
const declarations = root.findAll({ rule: { kind: "ambient_declaration" } });
|
|
145
|
+
for (const declaration of declarations) {
|
|
146
|
+
if (!hasSdkModuleSpecifier(declaration)) continue;
|
|
147
|
+
const interfaces = declaration.findAll({ rule: { kind: "interface_declaration" } });
|
|
148
|
+
for (const iface of interfaces) {
|
|
149
|
+
const name = typeIdentifierChildren(iface)[0];
|
|
150
|
+
if (name?.text() === "AttributeMap") addReplacement(edits, editedRanges, name, "Attributes");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function rewriteLocalTypeReferences(root, edits, editedRanges, localTypeRenames) {
|
|
155
|
+
if (localTypeRenames.size === 0) return;
|
|
156
|
+
const typeIdentifiers = root.findAll({ rule: { kind: "type_identifier" } });
|
|
157
|
+
for (const typeIdentifier of typeIdentifiers) {
|
|
158
|
+
if (isDeclarationName(typeIdentifier) || isNestedTypeName(typeIdentifier)) continue;
|
|
159
|
+
const replacement = localTypeRenames.get(typeIdentifier.text());
|
|
160
|
+
if (replacement && isShadowedTypeReference(typeIdentifier, typeIdentifier.text())) continue;
|
|
161
|
+
if (replacement) addReplacement(edits, editedRanges, typeIdentifier, replacement);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function rewriteNamespaceTypeReferences(root, edits, editedRanges, namespaceNames) {
|
|
165
|
+
if (namespaceNames.size === 0) return;
|
|
166
|
+
const nestedTypes = root.findAll({ rule: { kind: "nested_type_identifier" } });
|
|
167
|
+
for (const nestedType of nestedTypes) {
|
|
168
|
+
const namespaceName = identifierChildren(nestedType)[0]?.text();
|
|
169
|
+
if (!namespaceName || !namespaceNames.has(namespaceName)) continue;
|
|
170
|
+
const typeName = typeIdentifierChildren(nestedType).at(-1);
|
|
171
|
+
if (!typeName) continue;
|
|
172
|
+
const replacement = renamedType(typeName.text());
|
|
173
|
+
if (replacement) addReplacement(edits, editedRanges, typeName, replacement);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function isSdkImportCall(node) {
|
|
177
|
+
return node.kind() === "call_expression" && hasSdkModuleLiteral(node);
|
|
178
|
+
}
|
|
179
|
+
function rewriteImportTypeReferences(root, edits, editedRanges) {
|
|
180
|
+
const members = root.findAll({ rule: { kind: "member_expression" } });
|
|
181
|
+
for (const member of members) {
|
|
182
|
+
const object = member.field("object");
|
|
183
|
+
if (!object || !isSdkImportCall(object)) continue;
|
|
184
|
+
const property = member.field("property");
|
|
185
|
+
if (!property || property.kind() !== "property_identifier") continue;
|
|
186
|
+
const replacement = renamedType(property.text());
|
|
187
|
+
if (replacement) addReplacement(edits, editedRanges, property, replacement);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Rename the v1 auth attribute type API to its v2 names only when a reference
|
|
192
|
+
* can be tied to `@tailor-platform/sdk`.
|
|
193
|
+
* @param source - File contents
|
|
194
|
+
* @param _filePath - Absolute path to the file (kept for the runner signature)
|
|
195
|
+
* @returns Transformed source or null when nothing matched.
|
|
196
|
+
*/
|
|
197
|
+
function transform(source, _filePath) {
|
|
198
|
+
if (!quickFilter(source)) return null;
|
|
199
|
+
const filePath = _filePath?.toLowerCase();
|
|
200
|
+
const root = parse(filePath?.endsWith(".tsx") || filePath?.endsWith(".jsx") ? Lang.Tsx : filePath ? Lang.TypeScript : source.includes("</") || source.includes("/>") ? Lang.Tsx : Lang.TypeScript, source).root();
|
|
201
|
+
const edits = [];
|
|
202
|
+
const editedRanges = /* @__PURE__ */ new Set();
|
|
203
|
+
const { localTypeRenames, namespaceNames } = collectSdkImports(root, edits, editedRanges);
|
|
204
|
+
rewriteSdkExports(root, edits, editedRanges);
|
|
205
|
+
rewriteModuleAugmentations(root, edits, editedRanges);
|
|
206
|
+
rewriteLocalTypeReferences(root, edits, editedRanges, localTypeRenames);
|
|
207
|
+
rewriteNamespaceTypeReferences(root, edits, editedRanges, namespaceNames);
|
|
208
|
+
rewriteImportTypeReferences(root, edits, editedRanges);
|
|
209
|
+
if (edits.length === 0) return null;
|
|
210
|
+
return root.commitEdits(edits);
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
export { transform as default };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { a as importSource, c as localDeclarationNames, i as importBindings, o as importSpecNames, r as findImportStatements, s as isTypeOnlyImport, t as buildAddNamedImportEdit } from "../../../ast-grep-helpers-CXtWn3RB.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 };
|