@tailor-platform/sdk-codemod 0.3.0-next.2 → 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.
@@ -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 };
@@ -209,7 +209,7 @@ function renameQuotedKey(node) {
209
209
  }
210
210
  /**
211
211
  * Replace `auth.invoker("name")` calls with the bare `"name"` string literal
212
- * and rename `authInvoker:` option keys to `invoker:`.
212
+ * and optionally rename `authInvoker:` option keys to `invoker:`.
213
213
  * If no other `auth` references remain after the rewrite, drop the `auth`
214
214
  * specifier (or the entire import line when `auth` was its sole specifier).
215
215
  *
@@ -218,16 +218,20 @@ function renameQuotedKey(node) {
218
218
  * would otherwise pull config-layer modules into runtime bundles.
219
219
  * @param source - File contents
220
220
  * @param filePath - Absolute path to the file (kept for the runner signature)
221
+ * @param options - Transform behavior flags
221
222
  * @returns Transformed source or null when nothing matched.
222
223
  */
223
- function transform(source, _filePath) {
224
+ function transformAuthInvoker(source, _filePath, options = {}) {
224
225
  if (!quickFilter(source)) return null;
226
+ const renameOptionKeys = options.renameOptionKeys ?? true;
225
227
  const root = parse(source.includes("</") || source.includes("/>") ? Lang.Tsx : Lang.TypeScript, source).root();
226
228
  const calls = findInvokerCalls(root).filter((c) => isSupportedInvokerValueCall(c.callNode));
227
229
  const edits = calls.map((c) => c.callNode.replace(c.argText));
228
- edits.push(...findAuthInvokerPropertyKeys(root).map((node) => node.replace("invoker")));
229
- edits.push(...findQuotedAuthInvokerPropertyKeys(root).map((node) => node.replace(renameQuotedKey(node))));
230
- edits.push(...findAuthInvokerShorthands(root).map((node) => node.replace("invoker: authInvoker")));
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
+ }
231
235
  if (calls.length > 0 && countRemainingAuthRefs(root, calls.map((c) => c.range)) === 0) for (const importStmt of findAuthImports(root)) {
232
236
  const edit = buildAuthImportRemovalEdit(source, importStmt);
233
237
  if (edit) edits.push(edit);
@@ -236,5 +240,8 @@ function transform(source, _filePath) {
236
240
  result = result.replace(/^[\t ]*\n+/, "").replace(/\n{3,}/g, "\n\n");
237
241
  return result === source ? null : result;
238
242
  }
243
+ function transform(source, filePath) {
244
+ return transformAuthInvoker(source, filePath);
245
+ }
239
246
  //#endregion
240
- 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 mapping = PLUGIN_MAP[children[0].text().replace(/^["']|["']$/g, "")];
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 };