@tailor-platform/sdk-codemod 0.3.0-next.2 ā 0.3.0-next.4
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 +137 -7
- package/dist/codemods/ast-grep-helpers-D3FXAKNz.js +171 -0
- package/dist/codemods/v2/apply-to-deploy/scripts/transform.js +1 -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 +13 -6
- package/dist/codemods/v2/db-type-to-table/scripts/transform.js +383 -0
- 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/principal-unify/scripts/transform.js +428 -5
- 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/wait-point-rename/scripts/transform.js +126 -0
- package/dist/index.js +868 -60
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -5,18 +5,22 @@ import * as path from "pathe";
|
|
|
5
5
|
import { readPackageJSON } from "pkg-types";
|
|
6
6
|
import { arg, defineCommand, runMain } from "politty";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import { gte, lt, valid } from "semver";
|
|
8
|
+
import { gte, lt, parse, valid } from "semver";
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
|
-
import {
|
|
10
|
+
import { realpathSync } from "node:fs";
|
|
11
|
+
import { Lang, parse as parse$1 } from "@ast-grep/napi";
|
|
11
12
|
import chalk from "chalk";
|
|
12
13
|
import { structuredPatch } from "diff";
|
|
13
14
|
import picomatch from "picomatch";
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
14
16
|
//#region src/migration-doc.ts
|
|
15
17
|
/**
|
|
16
18
|
* Classify how much of a migration the codemod automates.
|
|
17
19
|
* - `Automatic`: a transform fully covers it, with no residual to flag.
|
|
18
20
|
* - `Partially automatic`: a transform covers the common cases but flags
|
|
19
|
-
* residuals (via `legacyPatterns`/`
|
|
21
|
+
* residuals (via `legacyPatterns`/`sourceStringLegacyPatterns`/
|
|
22
|
+
* `sourceTextLegacyPatterns`/`suspiciousPatterns`/
|
|
23
|
+
* `sourceStringSuspiciousPatterns`/`prompt`) to finish.
|
|
20
24
|
* - `Manual`: no transform; the change is migrated by hand (optionally guided
|
|
21
25
|
* by a `prompt`). Whether a person or an LLM does it does not matter here.
|
|
22
26
|
* @param codemod - The codemod registry entry
|
|
@@ -24,11 +28,95 @@ import picomatch from "picomatch";
|
|
|
24
28
|
*/
|
|
25
29
|
function automationLevel(codemod) {
|
|
26
30
|
if (!codemod.scriptPath) return "Manual";
|
|
27
|
-
return (codemod.legacyPatterns?.length ?? 0) > 0 || (codemod.suspiciousPatterns?.length ?? 0) > 0 || codemod.prompt != null ? "Partially automatic" : "Automatic";
|
|
31
|
+
return (codemod.legacyPatterns?.length ?? 0) > 0 || (codemod.sourceStringLegacyPatterns?.length ?? 0) > 0 || (codemod.sourceTextLegacyPatterns?.length ?? 0) > 0 || (codemod.suspiciousPatterns?.length ?? 0) > 0 || (codemod.sourceStringSuspiciousPatterns?.length ?? 0) > 0 || codemod.prompt != null ? "Partially automatic" : "Automatic";
|
|
28
32
|
}
|
|
29
33
|
//#endregion
|
|
30
34
|
//#region src/registry.ts
|
|
31
35
|
const CODEMODS_ROOT = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), "codemods");
|
|
36
|
+
const RENAME_BIN_SOURCE_VALUE_FLAGS = [
|
|
37
|
+
"--env-file-if-exists",
|
|
38
|
+
"--env-file",
|
|
39
|
+
"--profile",
|
|
40
|
+
"--config",
|
|
41
|
+
"--workspace-id",
|
|
42
|
+
"--arg",
|
|
43
|
+
"--query",
|
|
44
|
+
"--file",
|
|
45
|
+
"--name",
|
|
46
|
+
"--namespace",
|
|
47
|
+
"--dir",
|
|
48
|
+
"-e",
|
|
49
|
+
"-p",
|
|
50
|
+
"-c",
|
|
51
|
+
"-w",
|
|
52
|
+
"-a",
|
|
53
|
+
"-q",
|
|
54
|
+
"-f",
|
|
55
|
+
"-n"
|
|
56
|
+
];
|
|
57
|
+
const RENAME_BIN_SOURCE_COMMANDS = [
|
|
58
|
+
"api",
|
|
59
|
+
"apply",
|
|
60
|
+
"authconnection",
|
|
61
|
+
"completion",
|
|
62
|
+
"crash-report",
|
|
63
|
+
"crashreport",
|
|
64
|
+
"deploy",
|
|
65
|
+
"executor",
|
|
66
|
+
"function",
|
|
67
|
+
"generate",
|
|
68
|
+
"init",
|
|
69
|
+
"login",
|
|
70
|
+
"logout",
|
|
71
|
+
"machineuser",
|
|
72
|
+
"oauth2client",
|
|
73
|
+
"open",
|
|
74
|
+
"organization",
|
|
75
|
+
"profile",
|
|
76
|
+
"query",
|
|
77
|
+
"remove",
|
|
78
|
+
"secret",
|
|
79
|
+
"setup",
|
|
80
|
+
"show",
|
|
81
|
+
"skills",
|
|
82
|
+
"staticwebsite",
|
|
83
|
+
"tailordb",
|
|
84
|
+
"upgrade",
|
|
85
|
+
"user",
|
|
86
|
+
"workflow",
|
|
87
|
+
"workspace"
|
|
88
|
+
];
|
|
89
|
+
function escapeRegExp(value) {
|
|
90
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
91
|
+
}
|
|
92
|
+
const RENAME_BIN_SOURCE_VALUE_GUARDS = RENAME_BIN_SOURCE_VALUE_FLAGS.flatMap((flag) => {
|
|
93
|
+
const escaped = escapeRegExp(flag);
|
|
94
|
+
return [`(?<!${escaped}\\s+)`, `(?<!${escaped}=)`];
|
|
95
|
+
}).join("");
|
|
96
|
+
const RENAME_BIN_SOURCE_COMMAND_OR_FLAG = `(?:--?[\\w-]+|${RENAME_BIN_SOURCE_COMMANDS.join("|")})`;
|
|
97
|
+
const RENAME_BIN_SOURCE_COMMAND_TOKEN = "tailor-sdk(?:(?:\\.(?:cmd|ps1|exe))|(?:@[^\\s'\"`;|&)]+))?(?![\\w-])";
|
|
98
|
+
const RENAME_BIN_SOURCE_LEGACY_PATTERN = new RegExp([
|
|
99
|
+
"(?<![.\\w-])",
|
|
100
|
+
"(?<![\"'])",
|
|
101
|
+
"(?<!\\\\[\"'])",
|
|
102
|
+
RENAME_BIN_SOURCE_VALUE_GUARDS,
|
|
103
|
+
RENAME_BIN_SOURCE_COMMAND_TOKEN,
|
|
104
|
+
`(?=\\s*(?:$|${RENAME_BIN_SOURCE_COMMAND_OR_FLAG}\\b))`
|
|
105
|
+
].join(""));
|
|
106
|
+
const RENAME_BIN_QUOTED_SOURCE_LEGACY_PATTERN = new RegExp([
|
|
107
|
+
"(?:^|[\\s;&|\\x00])(?:sh|bash|zsh)\\s+-\\w*c\\w*\\s+\\\\?[\"']",
|
|
108
|
+
RENAME_BIN_SOURCE_COMMAND_TOKEN,
|
|
109
|
+
`(?=\\s*(?:$|${RENAME_BIN_SOURCE_COMMAND_OR_FLAG}\\b))`
|
|
110
|
+
].join(""));
|
|
111
|
+
const RENAME_BIN_QUOTED_LEGACY_COMMAND_PATTERN = new RegExp([
|
|
112
|
+
RENAME_BIN_SOURCE_VALUE_GUARDS,
|
|
113
|
+
"[\"']",
|
|
114
|
+
RENAME_BIN_SOURCE_COMMAND_TOKEN,
|
|
115
|
+
"(?=\\s*(?:apply\\b|crash-report\\b|[^\"'`]*\\s--machineuser\\b))"
|
|
116
|
+
].join(""));
|
|
117
|
+
const V2_NEXT_1 = "2.0.0-next.1";
|
|
118
|
+
const V2_NEXT_2 = "2.0.0-next.2";
|
|
119
|
+
const V2_NEXT_3 = "2.0.0-next.3";
|
|
32
120
|
/** All registered codemods, in registration order. */
|
|
33
121
|
const allCodemods = [
|
|
34
122
|
{
|
|
@@ -37,6 +125,7 @@ const allCodemods = [
|
|
|
37
125
|
description: "Migrate defineGenerators() tuple syntax to definePlugins() with explicit plugin imports",
|
|
38
126
|
since: "1.0.0",
|
|
39
127
|
until: "2.0.0",
|
|
128
|
+
prereleaseUntil: V2_NEXT_1,
|
|
40
129
|
scriptPath: "v2/define-generators-to-plugins/scripts/transform.js",
|
|
41
130
|
legacyPatterns: ["defineGenerators"],
|
|
42
131
|
examples: [{
|
|
@@ -68,6 +157,7 @@ const allCodemods = [
|
|
|
68
157
|
description: "Rewrite deprecated plugin re-export imports (kyselyTypePlugin, enumConstantsPlugin, fileUtilsPlugin, seedPlugin) from `@tailor-platform/sdk/cli` to their dedicated plugin subpaths",
|
|
69
158
|
since: "1.0.0",
|
|
70
159
|
until: "2.0.0",
|
|
160
|
+
prereleaseUntil: V2_NEXT_1,
|
|
71
161
|
scriptPath: "v2/plugin-cli-import/scripts/transform.js",
|
|
72
162
|
examples: [{
|
|
73
163
|
before: "import { kyselyTypePlugin } from \"@tailor-platform/sdk/cli\";",
|
|
@@ -77,9 +167,10 @@ const allCodemods = [
|
|
|
77
167
|
{
|
|
78
168
|
id: "v2/test-run-arg-input",
|
|
79
169
|
name: "function test-run --arg input unwrap",
|
|
80
|
-
description: "Strip the deprecated {input: ...} wrapper from `tailor
|
|
170
|
+
description: "Strip the deprecated {input: ...} wrapper from `tailor function test-run --arg` JSON in scripts and docs",
|
|
81
171
|
since: "1.0.0",
|
|
82
172
|
until: "2.0.0",
|
|
173
|
+
prereleaseUntil: V2_NEXT_1,
|
|
83
174
|
scriptPath: "v2/test-run-arg-input/scripts/transform.js",
|
|
84
175
|
filePatterns: [
|
|
85
176
|
"**/package.json",
|
|
@@ -88,16 +179,17 @@ const allCodemods = [
|
|
|
88
179
|
],
|
|
89
180
|
examples: [{
|
|
90
181
|
lang: "sh",
|
|
91
|
-
before: "tailor
|
|
92
|
-
after: "tailor
|
|
182
|
+
before: "tailor function test-run resolvers/add.ts --arg '{\"input\":{\"a\":1}}'",
|
|
183
|
+
after: "tailor function test-run resolvers/add.ts --arg '{\"a\":1}'"
|
|
93
184
|
}]
|
|
94
185
|
},
|
|
95
186
|
{
|
|
96
187
|
id: "v2/sdk-skills-shim",
|
|
97
|
-
name: "tailor-sdk-skills ā tailor
|
|
98
|
-
description: "Replace deprecated `tailor-sdk-skills` invocations with `tailor
|
|
188
|
+
name: "tailor-sdk-skills ā tailor skills add",
|
|
189
|
+
description: "Replace deprecated `tailor-sdk-skills` invocations with `tailor skills add`",
|
|
99
190
|
since: "1.0.0",
|
|
100
191
|
until: "2.0.0",
|
|
192
|
+
prereleaseUntil: V2_NEXT_1,
|
|
101
193
|
scriptPath: "v2/sdk-skills-shim/scripts/transform.js",
|
|
102
194
|
filePatterns: [
|
|
103
195
|
"**/package.json",
|
|
@@ -108,13 +200,13 @@ const allCodemods = [
|
|
|
108
200
|
examples: [{
|
|
109
201
|
lang: "sh",
|
|
110
202
|
before: "npx tailor-sdk-skills",
|
|
111
|
-
after: "tailor
|
|
203
|
+
after: "tailor skills add"
|
|
112
204
|
}],
|
|
113
205
|
prompt: [
|
|
114
|
-
"The standalone tailor-sdk-skills binary is removed in v2; call the skills
|
|
115
|
-
"subcommand on the main tailor
|
|
206
|
+
"The standalone tailor-sdk-skills binary is removed in v2; call the skills add",
|
|
207
|
+
"subcommand on the main tailor CLI instead. Replace any remaining",
|
|
116
208
|
"tailor-sdk-skills invocations the codemod did not rewrite with",
|
|
117
|
-
"`tailor
|
|
209
|
+
"`tailor skills add`."
|
|
118
210
|
].join("\n")
|
|
119
211
|
},
|
|
120
212
|
{
|
|
@@ -123,6 +215,7 @@ const allCodemods = [
|
|
|
123
215
|
description: "Rename TailorUser/TailorActor/TailorActorType/TailorInvoker to TailorPrincipal, drop unauthenticatedTailorUser, rename resolver body `user` to `caller`, and rename TailorDB callback `user` to `invoker`",
|
|
124
216
|
since: "1.0.0",
|
|
125
217
|
until: "2.0.0",
|
|
218
|
+
prereleaseUntil: V2_NEXT_2,
|
|
126
219
|
scriptPath: "v2/principal-unify/scripts/transform.js",
|
|
127
220
|
legacyPatterns: [
|
|
128
221
|
"TailorUser",
|
|
@@ -162,12 +255,40 @@ const allCodemods = [
|
|
|
162
255
|
"Use TailorPrincipal for the unified user/actor/invoker type."
|
|
163
256
|
].join("\n")
|
|
164
257
|
},
|
|
258
|
+
{
|
|
259
|
+
id: "v2/auth-attributes-rename",
|
|
260
|
+
name: "AttributeMap ā Attributes",
|
|
261
|
+
description: "Rename auth attribute module augmentation and related SDK type names from `AttributeMap` to `Attributes`",
|
|
262
|
+
since: "1.0.0",
|
|
263
|
+
until: "2.0.0",
|
|
264
|
+
scriptPath: "v2/auth-attributes-rename/scripts/transform.js",
|
|
265
|
+
legacyPatterns: [
|
|
266
|
+
"AttributeMap",
|
|
267
|
+
"interface AttributeMap",
|
|
268
|
+
"UserAttributeMap",
|
|
269
|
+
"InferredAttributeMap"
|
|
270
|
+
],
|
|
271
|
+
examples: [{
|
|
272
|
+
caption: "Module augmentation uses `Attributes`:",
|
|
273
|
+
before: "declare module \"@tailor-platform/sdk\" {\n interface AttributeMap {\n role: string;\n }\n}",
|
|
274
|
+
after: "declare module \"@tailor-platform/sdk\" {\n interface Attributes {\n role: string;\n }\n}"
|
|
275
|
+
}],
|
|
276
|
+
prompt: [
|
|
277
|
+
"In Tailor SDK v2, the auth attribute type API is renamed from `AttributeMap`",
|
|
278
|
+
"to `Attributes`; related SDK types are renamed to `UserAttributes` and",
|
|
279
|
+
"`InferredAttributes`. The codemod rewrites SDK imports, re-exports,",
|
|
280
|
+
"namespace-qualified references, import() type references, and module",
|
|
281
|
+
"augmentations. Review any remaining matches manually and leave unrelated",
|
|
282
|
+
"local names or deploy/proto wire field names unchanged."
|
|
283
|
+
].join("\n")
|
|
284
|
+
},
|
|
165
285
|
{
|
|
166
286
|
id: "v2/apply-to-deploy",
|
|
167
287
|
name: "tailor-sdk apply ā tailor-sdk deploy",
|
|
168
288
|
description: "Rewrite `tailor-sdk apply` invocations in package.json scripts, shell scripts, CI configs, and docs to the canonical v2 `tailor-sdk deploy` command",
|
|
169
289
|
since: "1.0.0",
|
|
170
290
|
until: "2.0.0",
|
|
291
|
+
prereleaseUntil: V2_NEXT_1,
|
|
171
292
|
scriptPath: "v2/apply-to-deploy/scripts/transform.js",
|
|
172
293
|
filePatterns: [
|
|
173
294
|
"**/package.json",
|
|
@@ -187,6 +308,7 @@ const allCodemods = [
|
|
|
187
308
|
description: "Rewrite `tailor-sdk crash-report` to `tailor-sdk crashreport` and `--machineuser` to `--machine-user` across package.json scripts, shell scripts, CI configs, and docs",
|
|
188
309
|
since: "1.0.0",
|
|
189
310
|
until: "2.0.0",
|
|
311
|
+
prereleaseUntil: V2_NEXT_1,
|
|
190
312
|
scriptPath: "v2/cli-rename/scripts/transform.js",
|
|
191
313
|
filePatterns: [
|
|
192
314
|
"**/package.json",
|
|
@@ -206,12 +328,98 @@ const allCodemods = [
|
|
|
206
328
|
"happen to use `--machineuser` alone."
|
|
207
329
|
].join("\n")
|
|
208
330
|
},
|
|
331
|
+
{
|
|
332
|
+
id: "v2/env-var-rename",
|
|
333
|
+
name: "SDK environment variable rename",
|
|
334
|
+
description: "Rewrite unambiguous removed SDK environment variable names to their v2 `TAILOR_*` names and flag generic names for manual review",
|
|
335
|
+
since: "1.0.0",
|
|
336
|
+
until: "2.0.0",
|
|
337
|
+
scriptPath: "v2/env-var-rename/scripts/transform.js",
|
|
338
|
+
filePatterns: [
|
|
339
|
+
"**/package.json",
|
|
340
|
+
"**/.env",
|
|
341
|
+
"**/.env.*",
|
|
342
|
+
"**/*.{env,sh,bash,zsh,yml,yaml,json,md}",
|
|
343
|
+
"**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"
|
|
344
|
+
],
|
|
345
|
+
legacyPatterns: [
|
|
346
|
+
"TAILOR_PLATFORM_SDK_CONFIG_PATH",
|
|
347
|
+
"TAILOR_PLATFORM_SDK_DTS_PATH",
|
|
348
|
+
"TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION",
|
|
349
|
+
"TAILOR_PLATFORM_SDK_BUILD_ONLY",
|
|
350
|
+
"TAILOR_SDK_OUTPUT_DIR",
|
|
351
|
+
"TAILOR_SDK_SKILLS_SOURCE",
|
|
352
|
+
"TAILOR_SDK_VERSION",
|
|
353
|
+
"PLATFORM_URL",
|
|
354
|
+
"PLATFORM_OAUTH2_CLIENT_ID",
|
|
355
|
+
"TAILOR_ENABLE_INLINE_SOURCEMAP",
|
|
356
|
+
"TAILOR_PLATFORM_QUERY_NEWLINE_ON_ENTER",
|
|
357
|
+
"LOG_LEVEL",
|
|
358
|
+
"TAILOR_TOKEN"
|
|
359
|
+
],
|
|
360
|
+
sourceStringLegacyPatterns: [
|
|
361
|
+
"PLATFORM_URL",
|
|
362
|
+
"PLATFORM_OAUTH2_CLIENT_ID",
|
|
363
|
+
"LOG_LEVEL"
|
|
364
|
+
],
|
|
365
|
+
examples: [{
|
|
366
|
+
lang: "sh",
|
|
367
|
+
before: "TAILOR_PLATFORM_SDK_BUILD_ONLY=true tailor-sdk deploy",
|
|
368
|
+
after: "TAILOR_DEPLOY_BUILD_ONLY=true tailor-sdk deploy"
|
|
369
|
+
}, {
|
|
370
|
+
before: "const token = process.env.TAILOR_TOKEN;",
|
|
371
|
+
after: "const token = process.env.TAILOR_PLATFORM_TOKEN;"
|
|
372
|
+
}],
|
|
373
|
+
prompt: [
|
|
374
|
+
"Review any remaining removed SDK environment variable names after the codemod",
|
|
375
|
+
"runs. The codemod intentionally leaves generic names such as `LOG_LEVEL`,",
|
|
376
|
+
"`PLATFORM_URL`, and `PLATFORM_OAUTH2_CLIENT_ID` for manual review because",
|
|
377
|
+
"they can configure non-SDK tools. Replace only actual SDK usages with their",
|
|
378
|
+
"v2 names. If a remaining match is an unrelated local identifier, fixture",
|
|
379
|
+
"label, or historical documentation that intentionally does not configure the",
|
|
380
|
+
"SDK, leave it unchanged."
|
|
381
|
+
].join("\n")
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: "v2/auth-invoker-call-unwrap",
|
|
385
|
+
name: "auth.invoker(\"name\") ā \"name\"",
|
|
386
|
+
description: "Replace statically identified SDK `auth.invoker(\"name\")` option values with the bare `\"name\"` string while preserving the `authInvoker` key for SDK versions before the option rename.",
|
|
387
|
+
since: "1.0.0",
|
|
388
|
+
until: "2.0.0",
|
|
389
|
+
prereleaseUntil: V2_NEXT_1,
|
|
390
|
+
scriptPath: "v2/auth-invoker-call-unwrap/scripts/transform.js",
|
|
391
|
+
suspiciousPatterns: ["auth.invoker"],
|
|
392
|
+
reviewSupersededBy: ["v2/auth-invoker-unwrap"],
|
|
393
|
+
prompt: [
|
|
394
|
+
"In Tailor SDK v2 the auth.invoker() helper is removed; an invoker is now the",
|
|
395
|
+
"machine user name passed directly as a string. The codemod already rewrote the",
|
|
396
|
+
"statically identified SDK option form authInvoker: auth.invoker(\"name\") to authInvoker: \"name\". These files still contain",
|
|
397
|
+
"auth.invoker(...) calls that need manual review.",
|
|
398
|
+
"",
|
|
399
|
+
"For each remaining auth.invoker(<expr>) call:",
|
|
400
|
+
"1. Replace the whole call with <expr> only where the target option expects a",
|
|
401
|
+
" machine user name string; platform/runtime authInvoker payloads still expect",
|
|
402
|
+
" the object form.",
|
|
403
|
+
"2. Keep the authInvoker key when targeting SDK versions before the invoker",
|
|
404
|
+
" option rename; later v2 targets run a separate codemod for that key rename.",
|
|
405
|
+
"3. After removing every auth.invoker usage in a file, delete the now-unused auth",
|
|
406
|
+
" import (keeping it pulls Node-only config modules into runtime bundles); leave",
|
|
407
|
+
" the import if auth is still referenced elsewhere.",
|
|
408
|
+
"",
|
|
409
|
+
"Do not change behavior beyond the auth.invoker() removal."
|
|
410
|
+
].join("\n"),
|
|
411
|
+
examples: [{
|
|
412
|
+
before: "createResolver({ authInvoker: auth.invoker(\"manager\") });",
|
|
413
|
+
after: "createResolver({ authInvoker: \"manager\" });"
|
|
414
|
+
}]
|
|
415
|
+
},
|
|
209
416
|
{
|
|
210
417
|
id: "v2/auth-invoker-unwrap",
|
|
211
418
|
name: "auth.invoker(\"name\") ā invoker: \"name\"",
|
|
212
419
|
description: "Rename statically identified SDK `authInvoker` options to `invoker`, replace `auth.invoker(\"name\")` there with the bare `\"name\"` string, and drop the `auth` import when no other reference remains. Ambiguous workflow `.trigger()` calls are left for manual review. The `auth.invoker()` helper is removed in v2 because importing `auth` from `tailor.config.ts` into runtime files pulls Node-only modules into the bundle.",
|
|
213
420
|
since: "1.0.0",
|
|
214
421
|
until: "2.0.0",
|
|
422
|
+
prereleaseUntil: V2_NEXT_2,
|
|
215
423
|
scriptPath: "v2/auth-invoker-unwrap/scripts/transform.js",
|
|
216
424
|
suspiciousPatterns: [
|
|
217
425
|
"auth.invoker",
|
|
@@ -254,12 +462,94 @@ const allCodemods = [
|
|
|
254
462
|
after: "createResolver({ invoker: \"manager\" });"
|
|
255
463
|
}]
|
|
256
464
|
},
|
|
465
|
+
{
|
|
466
|
+
id: "v2/auth-connection-token-helper",
|
|
467
|
+
name: "auth.getConnectionToken() ā runtime authconnection",
|
|
468
|
+
description: "The deprecated `auth.getConnectionToken()` helper returned by `defineAuth()` is removed in v2. Use `authconnection.getConnectionToken(...)` from `@tailor-platform/sdk/runtime` in resolvers, executors, and workflows instead.",
|
|
469
|
+
since: "1.0.0",
|
|
470
|
+
until: "2.0.0",
|
|
471
|
+
prereleaseUntil: V2_NEXT_2,
|
|
472
|
+
scriptPath: "v2/auth-connection-token-helper/scripts/transform.js",
|
|
473
|
+
filePatterns: ["**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"],
|
|
474
|
+
examples: [{
|
|
475
|
+
before: "import { auth } from \"../tailor.config\";\n\nconst token = await auth.getConnectionToken(\"google\");",
|
|
476
|
+
after: "import { authconnection } from \"@tailor-platform/sdk/runtime\";\n\nconst token = await authconnection.getConnectionToken(\"google\");"
|
|
477
|
+
}],
|
|
478
|
+
prompt: [
|
|
479
|
+
"In Tailor SDK v2 the auth.getConnectionToken() helper returned by defineAuth()",
|
|
480
|
+
"is removed. Runtime code should call authconnection.getConnectionToken(...) from",
|
|
481
|
+
"@tailor-platform/sdk/runtime instead of importing auth from tailor.config.ts.",
|
|
482
|
+
"",
|
|
483
|
+
"For each getConnectionToken usage where <receiver> is a defineAuth() result",
|
|
484
|
+
"imported from tailor.config.ts:",
|
|
485
|
+
"1. Replace <receiver>.getConnectionToken(<expr>) calls with",
|
|
486
|
+
" authconnection.getConnectionToken(<expr>).",
|
|
487
|
+
"2. Update non-call references, including <receiver>.getConnectionToken,",
|
|
488
|
+
" <receiver>[\"getConnectionToken\"], and destructuring from <receiver>, to",
|
|
489
|
+
" reference authconnection instead.",
|
|
490
|
+
"3. Add or reuse `import { authconnection } from \"@tailor-platform/sdk/runtime\"`.",
|
|
491
|
+
"4. Remove the auth import from tailor.config.ts only when no other auth reference",
|
|
492
|
+
" remains in the file.",
|
|
493
|
+
"",
|
|
494
|
+
"Leave usages unchanged when the receiver is already the runtime authconnection",
|
|
495
|
+
"wrapper or global tailor.authconnection."
|
|
496
|
+
].join("\n")
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
id: "v2/runtime-subpath-namespace",
|
|
500
|
+
name: "Runtime subpath imports use namespace objects",
|
|
501
|
+
description: "Rewrite `@tailor-platform/sdk/runtime/*` namespace-star and flat value imports to self-named namespace imports, and aggregate `file.deleteFile` calls to `file.delete`. `TailorContextAPI` and `TailorWorkflowAPI` now describe SDK wrappers; direct platform globals use `PlatformContextAPI` and `PlatformWorkflowAPI`.",
|
|
502
|
+
since: "1.0.0",
|
|
503
|
+
until: "2.0.0",
|
|
504
|
+
prereleaseUntil: V2_NEXT_3,
|
|
505
|
+
scriptPath: "v2/runtime-subpath-namespace/scripts/transform.js",
|
|
506
|
+
filePatterns: ["**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"],
|
|
507
|
+
legacyPatterns: [
|
|
508
|
+
"@tailor-platform/sdk/runtime/iconv",
|
|
509
|
+
"@tailor-platform/sdk/runtime/secretmanager",
|
|
510
|
+
"@tailor-platform/sdk/runtime/authconnection",
|
|
511
|
+
"@tailor-platform/sdk/runtime/idp",
|
|
512
|
+
"@tailor-platform/sdk/runtime/workflow",
|
|
513
|
+
"@tailor-platform/sdk/runtime/context",
|
|
514
|
+
"@tailor-platform/sdk/runtime/file",
|
|
515
|
+
"@tailor-platform/sdk/runtime/aigateway"
|
|
516
|
+
],
|
|
517
|
+
examples: [
|
|
518
|
+
{
|
|
519
|
+
before: "import * as iconv from \"@tailor-platform/sdk/runtime/iconv\";\niconv.convert(value, \"UTF-8\", \"Shift_JIS\");",
|
|
520
|
+
after: "import { iconv } from \"@tailor-platform/sdk/runtime/iconv\";\niconv.convert(value, \"UTF-8\", \"Shift_JIS\");"
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
before: "import { get } from \"@tailor-platform/sdk/runtime/aigateway\";\nconst gateway = await get(\"main\");",
|
|
524
|
+
after: "import { aigateway } from \"@tailor-platform/sdk/runtime/aigateway\";\nconst gateway = await aigateway.get(\"main\");"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
before: "import { file } from \"@tailor-platform/sdk/runtime\";\nawait file.deleteFile(\"ns\", \"Doc\", \"blob\", \"record-id\");",
|
|
528
|
+
after: "import { file } from \"@tailor-platform/sdk/runtime\";\nawait file.delete(\"ns\", \"Doc\", \"blob\", \"record-id\");"
|
|
529
|
+
}
|
|
530
|
+
],
|
|
531
|
+
prompt: [
|
|
532
|
+
"In Tailor SDK v2, runtime subpath modules export only a self-named namespace",
|
|
533
|
+
"object (for example, `iconv` from `@tailor-platform/sdk/runtime/iconv`).",
|
|
534
|
+
"Default and flat value imports such as",
|
|
535
|
+
"`import { get } from \"@tailor-platform/sdk/runtime/aigateway\"` are removed.",
|
|
536
|
+
"The codemod rewrites straightforward namespace-star imports and flat named value",
|
|
537
|
+
"imports. It also rewrites direct `file.deleteFile` calls on the aggregate runtime",
|
|
538
|
+
"namespace to `file.delete`. Destructured aggregate `deleteFile` references require",
|
|
539
|
+
"manual migration. Review any remaining runtime imports manually, especially when",
|
|
540
|
+
"a local binding or nested scope shadows an imported value, or when",
|
|
541
|
+
"type-position namespace member references need explicit top-level type imports.",
|
|
542
|
+
"For direct platform globals, replace `TailorContextAPI` and `TailorWorkflowAPI`",
|
|
543
|
+
"type references with `PlatformContextAPI` and `PlatformWorkflowAPI` respectively."
|
|
544
|
+
].join("\n")
|
|
545
|
+
},
|
|
257
546
|
{
|
|
258
547
|
id: "v2/tailordb-namespace",
|
|
259
548
|
name: "Tailordb ā tailordb (lowercase ambient namespace)",
|
|
260
549
|
description: "Rewrite references to the removed capital-cased `Tailordb` ambient namespace (`Tailordb.QueryResult`, `Tailordb.CommandType`, `Tailordb.Client`, `typeof Tailordb.Client`) to the lowercase `tailordb.*` namespace exposed by `@tailor-platform/sdk/runtime/globals`. Because v2 no longer activates ambient declarations automatically, each file that contains `tailordb.*` references after the rewrite must also add `import \"@tailor-platform/sdk/runtime/globals\"`.",
|
|
261
550
|
since: "1.0.0",
|
|
262
551
|
until: "2.0.0",
|
|
552
|
+
prereleaseUntil: V2_NEXT_1,
|
|
263
553
|
scriptPath: "v2/tailordb-namespace/scripts/transform.js",
|
|
264
554
|
legacyPatterns: ["Tailordb."],
|
|
265
555
|
examples: [{
|
|
@@ -277,12 +567,39 @@ const allCodemods = [
|
|
|
277
567
|
"declarations automatically on SDK import."
|
|
278
568
|
].join("\n")
|
|
279
569
|
},
|
|
570
|
+
{
|
|
571
|
+
id: "v2/db-type-to-table",
|
|
572
|
+
name: "db.type() ā db.table()",
|
|
573
|
+
description: "Rename TailorDB schema builder calls from `db.type()` to `db.table()`. TailorDB schema definitions now use table terminology in SDK projects.",
|
|
574
|
+
since: "1.0.0",
|
|
575
|
+
until: "2.0.0",
|
|
576
|
+
prereleaseUntil: V2_NEXT_3,
|
|
577
|
+
scriptPath: "v2/db-type-to-table/scripts/transform.js",
|
|
578
|
+
filePatterns: ["**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"],
|
|
579
|
+
legacyPatterns: ["db.type"],
|
|
580
|
+
examples: [{
|
|
581
|
+
before: "import { db } from \"@tailor-platform/sdk\";\n\nexport const user = db.type(\"User\", {\n name: db.string(),\n});",
|
|
582
|
+
after: "import { db } from \"@tailor-platform/sdk\";\n\nexport const user = db.table(\"User\", {\n name: db.string(),\n});"
|
|
583
|
+
}],
|
|
584
|
+
prompt: [
|
|
585
|
+
"In Tailor SDK v2, TailorDB schema definitions use db.table(...) instead of",
|
|
586
|
+
"db.type(...). The codemod rewrites member accesses on db imported from",
|
|
587
|
+
"@tailor-platform/sdk, including aliases such as `import { db as schema }`.",
|
|
588
|
+
"It flags destructured builder aliases such as `const { type } = db` and",
|
|
589
|
+
"local builder aliases such as `const schema = db`, `schema = db`, or",
|
|
590
|
+
"`function make(schema = db) { ... }` for manual review because the local",
|
|
591
|
+
"alias may require call-site renaming.",
|
|
592
|
+
"Review any remaining db.type references and rename SDK TailorDB schema builder",
|
|
593
|
+
"calls to db.table. Leave unrelated local objects with a .type() method unchanged."
|
|
594
|
+
].join("\n")
|
|
595
|
+
},
|
|
280
596
|
{
|
|
281
597
|
id: "v2/execute-script-arg",
|
|
282
598
|
name: "executeScript arg JSON.stringify ā value",
|
|
283
599
|
description: "Unwrap `JSON.stringify(...)` passed as the `executeScript` `arg` option. In v2 `arg` takes a JSON-serializable value and is serialized internally, so a pre-stringified argument double-encodes.",
|
|
284
600
|
since: "1.0.0",
|
|
285
601
|
until: "2.0.0",
|
|
602
|
+
prereleaseUntil: V2_NEXT_2,
|
|
286
603
|
scriptPath: "v2/execute-script-arg/scripts/transform.js",
|
|
287
604
|
filePatterns: ["**/*.{ts,tsx,mts,cts,mjs,cjs,js}"],
|
|
288
605
|
suspiciousPatterns: [[
|
|
@@ -309,12 +626,26 @@ const allCodemods = [
|
|
|
309
626
|
after: "await executeScript({ ...opts, arg: { a: 1 } });"
|
|
310
627
|
}]
|
|
311
628
|
},
|
|
629
|
+
{
|
|
630
|
+
id: "v2/wait-point-rename",
|
|
631
|
+
name: "defineWaitPoint/defineWaitPoints ā createWaitPoint/createWaitPoints",
|
|
632
|
+
description: "Rename `defineWaitPoint` and `defineWaitPoints` to `createWaitPoint` and `createWaitPoints`. The functions create runtime instances with `.wait()` / `.resolve()` methods, so the `create*` prefix is used consistently.",
|
|
633
|
+
since: "1.0.0",
|
|
634
|
+
until: "2.0.0",
|
|
635
|
+
scriptPath: "v2/wait-point-rename/scripts/transform.js",
|
|
636
|
+
legacyPatterns: ["defineWaitPoint", "defineWaitPoints"],
|
|
637
|
+
examples: [{
|
|
638
|
+
before: "import { defineWaitPoints } from \"@tailor-platform/sdk\";\n\nexport const { approval } = defineWaitPoints((define) => ({\n approval: define<{ message: string }, { approved: boolean }>(),\n}));",
|
|
639
|
+
after: "import { createWaitPoints } from \"@tailor-platform/sdk\";\n\nexport const { approval } = createWaitPoints((define) => ({\n approval: define<{ message: string }, { approved: boolean }>(),\n}));"
|
|
640
|
+
}]
|
|
641
|
+
},
|
|
312
642
|
{
|
|
313
643
|
id: "v2/open-download-stream",
|
|
314
644
|
name: "openDownloadStream ā downloadStream",
|
|
315
645
|
description: "The deprecated `openDownloadStream` file-streaming API is removed in v2. Use `downloadStream` for streamed file downloads. The generated file utilities now emit `downloadFileStream` (which calls `downloadStream` and returns `FileDownloadStreamResponse`) instead of the removed `openFileDownloadStream` helper.",
|
|
316
646
|
since: "1.0.0",
|
|
317
647
|
until: "2.0.0",
|
|
648
|
+
prereleaseUntil: V2_NEXT_2,
|
|
318
649
|
filePatterns: ["**/*.{ts,tsx,mts,cts,mjs,cjs,js}"],
|
|
319
650
|
suspiciousPatterns: ["openDownloadStream", "openFileDownloadStream"],
|
|
320
651
|
examples: [{
|
|
@@ -331,14 +662,18 @@ const allCodemods = [
|
|
|
331
662
|
{
|
|
332
663
|
id: "v2/runtime-globals-opt-in",
|
|
333
664
|
name: "Ambient runtime globals are opt-in",
|
|
334
|
-
description: "Importing `@tailor-platform/sdk` no longer activates the ambient `tailor.*` / `tailordb.*` global declarations.
|
|
665
|
+
description: "Importing `@tailor-platform/sdk` no longer activates the ambient `tailor.*` / `tailordb.*` global declarations. The codemod rewrites simple direct `new tailor.idp.Client(...)` calls to the typed `idp.Client` wrapper from `@tailor-platform/sdk/runtime`; broader runtime global usage remains review-only. Only if you relied on the ambient globals directly, add `import \"@tailor-platform/sdk/runtime/globals\"`. (The capital-cased `Tailordb.*` namespace is removed separately ā see the `Tailordb ā tailordb` codemod.)",
|
|
335
666
|
since: "1.0.0",
|
|
336
667
|
until: "2.0.0",
|
|
337
|
-
|
|
668
|
+
prereleaseUntil: V2_NEXT_1,
|
|
669
|
+
scriptPath: "v2/runtime-globals-opt-in/scripts/transform.js",
|
|
670
|
+
filePatterns: ["**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"],
|
|
338
671
|
suspiciousPatterns: [
|
|
339
672
|
"tailor.context",
|
|
340
673
|
"tailor.iconv",
|
|
341
674
|
"tailor.idp",
|
|
675
|
+
"tailor.secretmanager",
|
|
676
|
+
"tailor.authconnection",
|
|
342
677
|
"tailor.workflow",
|
|
343
678
|
"tailor[",
|
|
344
679
|
"tailordb.Client",
|
|
@@ -351,6 +686,21 @@ const allCodemods = [
|
|
|
351
686
|
"TailorErrorMessage",
|
|
352
687
|
"TailorErrors"
|
|
353
688
|
],
|
|
689
|
+
sourceStringSuspiciousPatterns: [
|
|
690
|
+
"new tailor.idp.Client",
|
|
691
|
+
/[=(:,[]\s*tailor\.idp\.Client\b/,
|
|
692
|
+
/(?:(?:=>|[=(:,<{]|\[)\s*|\b(?:return|await|typeof)\s+)tailor\.(?:authconnection|context|iconv|idp|secretmanager|workflow)(?:\.[A-Za-z_$][\w$]*)?\b/,
|
|
693
|
+
/\btailor\.(?:authconnection|context|iconv|idp|secretmanager|workflow)\.[A-Za-z_$][\w$]*\s*\(/,
|
|
694
|
+
"tailor[",
|
|
695
|
+
/\btailordb\.file\.[A-Za-z_$][\w$]*\s*\(/,
|
|
696
|
+
/(?:(?:=>|[=(:,<{]|\[)\s*|\b(?:return|await|typeof)\s+)tailordb\.file\b/,
|
|
697
|
+
/(?:\bnew\s+|(?:=>|[=(:,<{]|\[)\s*|\b(?:return|await|typeof)\s+)tailordb\.(?:Client|CommandType|QueryResult)\b/,
|
|
698
|
+
/<\s*tailordb\.(?:Client|CommandType|QueryResult)\b/,
|
|
699
|
+
"tailordb[",
|
|
700
|
+
/(?:\bnew\s+|\bthrow\s+|\binstanceof\s+)Tailor(?:DBFileError|Errors|ErrorMessage)\b/,
|
|
701
|
+
/(?:[:=<]\s*|\bas\s+)Tailor(?:DBFileError|Errors|ErrorMessage|ErrorItem)\b/,
|
|
702
|
+
/[:<]\s*TailorErrorItem\b/
|
|
703
|
+
],
|
|
354
704
|
examples: [{
|
|
355
705
|
caption: "Preferred: switch to the typed wrappers from `@tailor-platform/sdk/runtime` and drop the ambient globals:",
|
|
356
706
|
before: "const client = new tailor.idp.Client();",
|
|
@@ -364,10 +714,11 @@ const allCodemods = [
|
|
|
364
714
|
"The v2 SDK no longer enables ambient Tailor runtime globals from",
|
|
365
715
|
"`@tailor-platform/sdk`. For each flagged file that uses `tailor.*`,",
|
|
366
716
|
"`tailordb.*`, or Tailor runtime error globals, prefer migrating to the",
|
|
367
|
-
"typed wrappers from `@tailor-platform/sdk/runtime
|
|
368
|
-
"`new tailor.idp.Client()`
|
|
369
|
-
"
|
|
370
|
-
"
|
|
717
|
+
"typed wrappers from `@tailor-platform/sdk/runtime`. The codemod already",
|
|
718
|
+
"rewrites direct `new tailor.idp.Client(...)` calls to `new idp.Client(...)`",
|
|
719
|
+
"when the file has no conflicting `tailor` or `idp` binding. For any remaining",
|
|
720
|
+
"`tailor.idp.Client` references, either resolve the binding collision and use",
|
|
721
|
+
"`idp.Client`, or keep the ambient global deliberately.",
|
|
371
722
|
"",
|
|
372
723
|
"Only when the file must keep referencing the bare `tailor.*` names directly,",
|
|
373
724
|
"opt into the global declarations instead by adding one of these:",
|
|
@@ -376,7 +727,9 @@ const allCodemods = [
|
|
|
376
727
|
" the relevant tsconfig compilerOptions",
|
|
377
728
|
"",
|
|
378
729
|
"Leave files unchanged when the matching name is local, imported from another",
|
|
379
|
-
"module, or appears only in comments or strings."
|
|
730
|
+
"module, or appears only in comments or prose strings. Embedded code strings",
|
|
731
|
+
"that use runtime globals are review-only findings; do not insert imports inside",
|
|
732
|
+
"string literals."
|
|
380
733
|
].join("\n")
|
|
381
734
|
},
|
|
382
735
|
{
|
|
@@ -385,6 +738,7 @@ const allCodemods = [
|
|
|
385
738
|
description: "Workflow job `.trigger()` now aligns with the platform runtime: it returns the job result directly instead of a Promise wrapper, and tests no longer run job bodies locally. Mock trigger responses with `mockWorkflow()` (`setJobHandler` / `enqueueResult`, assert via `triggeredJobs`), or use `runWorkflowLocally()` for a full-chain local run.",
|
|
386
739
|
since: "1.0.0",
|
|
387
740
|
until: "2.0.0",
|
|
741
|
+
prereleaseUntil: V2_NEXT_1,
|
|
388
742
|
suspiciousPatterns: [".trigger("],
|
|
389
743
|
examples: [{
|
|
390
744
|
caption: "Tests must mock the workflow runtime instead of running bodies locally:",
|
|
@@ -405,6 +759,7 @@ const allCodemods = [
|
|
|
405
759
|
description: "CLI login tokens are stored in the OS keyring by default when available, falling back to the platform config file when it is not. No source change is required; re-login if you need tokens moved into the keyring.",
|
|
406
760
|
since: "1.0.0",
|
|
407
761
|
until: "2.0.0",
|
|
762
|
+
prereleaseUntil: V2_NEXT_2,
|
|
408
763
|
notice: true
|
|
409
764
|
},
|
|
410
765
|
{
|
|
@@ -413,12 +768,87 @@ const allCodemods = [
|
|
|
413
768
|
description: "The CLI stores human users by their stable subject ID instead of email (email is kept for display). Legacy email-keyed entries are migrated automatically on the next login or token refresh. No source change is required.",
|
|
414
769
|
since: "1.0.0",
|
|
415
770
|
until: "2.0.0",
|
|
771
|
+
prereleaseUntil: V2_NEXT_1,
|
|
416
772
|
notice: true
|
|
417
773
|
},
|
|
418
774
|
{
|
|
419
775
|
id: "v2/function-logs-content-hash",
|
|
420
776
|
name: "function logs require a content hash for source mapping",
|
|
421
|
-
description: "`tailor
|
|
777
|
+
description: "`tailor function logs` maps stack traces against the function bundle only when the execution recorded a `contentHash`. Executions without one now show raw stack traces instead of mapped frames. No source change is required.",
|
|
778
|
+
since: "1.0.0",
|
|
779
|
+
until: "2.0.0",
|
|
780
|
+
prereleaseUntil: V2_NEXT_1,
|
|
781
|
+
notice: true
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
id: "v2/rename-bin",
|
|
785
|
+
name: "tailor-sdk binary ā tailor",
|
|
786
|
+
description: "Rename the CLI binary from `tailor-sdk` to `tailor` in package.json scripts, shell scripts, CI workflows, source files, generated declaration comments, and documentation. Does not rename `.tailor-sdk` directory paths or the `create-tailor-sdk` scaffolding package. Note: v2 also changes the default generated output directory from `.tailor-sdk/` to `.tailor/` and the setup lock file from `.github/tailor-sdk.lock` to `.github/tailor.lock`. Run `mv .tailor-sdk .tailor` to migrate the generated output directory (preserves auth connection state and other local files). Run `git mv .github/tailor-sdk.lock .github/tailor.lock` if the old lock file exists; without it `tailor setup check` will treat all managed workflows as missing. Exact ignore-file entries for `.tailor-sdk/` are handled by the generated-output ignore codemod.",
|
|
787
|
+
since: "1.0.0",
|
|
788
|
+
until: "2.0.0",
|
|
789
|
+
scriptPath: "v2/rename-bin/scripts/transform.js",
|
|
790
|
+
filePatterns: [
|
|
791
|
+
"**/package.json",
|
|
792
|
+
"**/*.{sh,bash,zsh,yml,yaml}",
|
|
793
|
+
"**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
|
|
794
|
+
"**/*.md"
|
|
795
|
+
],
|
|
796
|
+
legacyPatterns: ["tailor-sdk"],
|
|
797
|
+
sourceStringLegacyPatterns: [
|
|
798
|
+
RENAME_BIN_SOURCE_LEGACY_PATTERN,
|
|
799
|
+
RENAME_BIN_QUOTED_SOURCE_LEGACY_PATTERN,
|
|
800
|
+
RENAME_BIN_QUOTED_LEGACY_COMMAND_PATTERN
|
|
801
|
+
],
|
|
802
|
+
sourceTextLegacyPatterns: [
|
|
803
|
+
RENAME_BIN_SOURCE_LEGACY_PATTERN,
|
|
804
|
+
RENAME_BIN_QUOTED_SOURCE_LEGACY_PATTERN,
|
|
805
|
+
RENAME_BIN_QUOTED_LEGACY_COMMAND_PATTERN
|
|
806
|
+
],
|
|
807
|
+
examples: [{
|
|
808
|
+
lang: "sh",
|
|
809
|
+
before: "tailor-sdk deploy\nnpx tailor-sdk@latest login",
|
|
810
|
+
after: "tailor deploy\nnpx @tailor-platform/sdk@latest login"
|
|
811
|
+
}],
|
|
812
|
+
prompt: [
|
|
813
|
+
"Rename any remaining `tailor-sdk` binary invocations to `tailor`. Only rewrite",
|
|
814
|
+
"the binary name ā leave `.tailor-sdk` directory paths and `create-tailor-sdk`",
|
|
815
|
+
"package references unchanged."
|
|
816
|
+
].join("\n")
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
id: "v2/tailor-output-ignore-dir",
|
|
820
|
+
name: ".tailor-sdk ignore entries ā .tailor",
|
|
821
|
+
description: "Rewrite exact ignore-file entries for the v1 generated output directory from `.tailor-sdk` to the v2 `.tailor` directory. Other `.tailor-sdk` paths and prose are left unchanged.",
|
|
822
|
+
since: "1.0.0",
|
|
823
|
+
until: "2.0.0",
|
|
824
|
+
scriptPath: "v2/tailor-output-ignore-dir/scripts/transform.js",
|
|
825
|
+
filePatterns: [
|
|
826
|
+
"**/.gitignore",
|
|
827
|
+
"**/.npmignore",
|
|
828
|
+
"**/.dockerignore",
|
|
829
|
+
"**/gitignore",
|
|
830
|
+
"**/npmignore",
|
|
831
|
+
"**/dockerignore",
|
|
832
|
+
"**/_gitignore",
|
|
833
|
+
"**/_npmignore",
|
|
834
|
+
"**/_dockerignore",
|
|
835
|
+
"**/__dot__gitignore",
|
|
836
|
+
"**/__dot__npmignore",
|
|
837
|
+
"**/__dot__dockerignore",
|
|
838
|
+
"**/*.gitignore",
|
|
839
|
+
"**/*.npmignore",
|
|
840
|
+
"**/*.dockerignore"
|
|
841
|
+
],
|
|
842
|
+
examples: [{
|
|
843
|
+
lang: "gitignore",
|
|
844
|
+
before: ".tailor-sdk/",
|
|
845
|
+
after: ".tailor/"
|
|
846
|
+
}]
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
id: "v2/node-minimum-22-15-0",
|
|
850
|
+
name: "Node.js minimum version raised to 22.15.0",
|
|
851
|
+
description: "v2 requires Node.js **22.15.0** or later. This is the first version that includes `module.registerHooks()`, which the SDK uses to register its TypeScript loader hook synchronously in the main thread. No source change is required; ensure your environment runs Node.js 22.15.0+.",
|
|
422
852
|
since: "1.0.0",
|
|
423
853
|
until: "2.0.0",
|
|
424
854
|
notice: true
|
|
@@ -432,9 +862,32 @@ const allCodemods = [
|
|
|
432
862
|
function resolveCodemodScript(scriptPath) {
|
|
433
863
|
return path.resolve(CODEMODS_ROOT, scriptPath);
|
|
434
864
|
}
|
|
865
|
+
function reachesCodemodBoundary(toVersion, codemod) {
|
|
866
|
+
if (gte(toVersion, codemod.until)) return true;
|
|
867
|
+
if (codemod.prereleaseUntil === void 0 || !gte(toVersion, codemod.prereleaseUntil)) return false;
|
|
868
|
+
const target = parse(toVersion);
|
|
869
|
+
const boundary = parse(codemod.until);
|
|
870
|
+
return target.prerelease.length > 0 && target.major === boundary.major && target.minor === boundary.minor && target.patch === boundary.patch;
|
|
871
|
+
}
|
|
872
|
+
function effectiveCodemodBoundary(codemod) {
|
|
873
|
+
return codemod.prereleaseUntil ?? codemod.until;
|
|
874
|
+
}
|
|
875
|
+
function assertCodemodBoundaries(codemods) {
|
|
876
|
+
for (const codemod of codemods) {
|
|
877
|
+
const boundary = parse(codemod.until);
|
|
878
|
+
if (boundary === null) throw new Error(`Codemod ${codemod.id} until must be a valid semver version: ${codemod.until}`);
|
|
879
|
+
if (boundary.prerelease.length > 0) throw new Error(`Codemod ${codemod.id} until must be a stable version: ${codemod.until}`);
|
|
880
|
+
if (codemod.prereleaseUntil === void 0) continue;
|
|
881
|
+
const prereleaseBoundary = parse(codemod.prereleaseUntil);
|
|
882
|
+
if (prereleaseBoundary === null) throw new Error(`Codemod ${codemod.id} prereleaseUntil must be a valid semver version: ${codemod.prereleaseUntil}`);
|
|
883
|
+
if (prereleaseBoundary.prerelease.length === 0) throw new Error(`Codemod ${codemod.id} prereleaseUntil must be a prerelease version: ${codemod.prereleaseUntil}`);
|
|
884
|
+
if (prereleaseBoundary.major !== boundary.major || prereleaseBoundary.minor !== boundary.minor || prereleaseBoundary.patch !== boundary.patch) throw new Error(`Codemod ${codemod.id} prereleaseUntil must target the same version as until: ${codemod.prereleaseUntil}`);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
435
887
|
/**
|
|
436
888
|
* Get codemod packages applicable for a version range.
|
|
437
|
-
* A codemod applies when: since <= fromVersion <
|
|
889
|
+
* A codemod applies when: since <= fromVersion < boundary <= toVersion.
|
|
890
|
+
* A target prerelease reaches `until` only when the codemod declares `prereleaseUntil`.
|
|
438
891
|
* @param fromVersion - Current SDK version (semver)
|
|
439
892
|
* @param toVersion - Target SDK version (semver)
|
|
440
893
|
* @returns Array of applicable codemod packages in registration order
|
|
@@ -442,20 +895,21 @@ function resolveCodemodScript(scriptPath) {
|
|
|
442
895
|
function getApplicableCodemods(fromVersion, toVersion) {
|
|
443
896
|
if (!valid(fromVersion)) throw new Error(`Invalid fromVersion: ${fromVersion}`);
|
|
444
897
|
if (!valid(toVersion)) throw new Error(`Invalid toVersion: ${toVersion}`);
|
|
445
|
-
|
|
898
|
+
assertCodemodBoundaries(allCodemods);
|
|
899
|
+
return allCodemods.filter((codemod) => gte(fromVersion, codemod.since) && lt(fromVersion, effectiveCodemodBoundary(codemod)) && reachesCodemodBoundary(toVersion, codemod));
|
|
446
900
|
}
|
|
447
901
|
//#endregion
|
|
448
902
|
//#region src/runner.ts
|
|
449
903
|
/** Default file patterns for TypeScript files. */
|
|
450
904
|
const DEFAULT_FILE_PATTERNS = ["**/*.{ts,tsx,mts,cts}"];
|
|
451
905
|
/** Directory names always excluded from file scanning. */
|
|
452
|
-
const EXCLUDE_DIRS = new Set([
|
|
906
|
+
const EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
453
907
|
"node_modules",
|
|
454
908
|
"dist",
|
|
455
909
|
".git"
|
|
456
910
|
]);
|
|
457
|
-
const ALLOWED_DOT_DIRS = new Set([".github", ".circleci"]);
|
|
458
|
-
const SOURCE_EXTENSIONS = new Set([
|
|
911
|
+
const ALLOWED_DOT_DIRS = /* @__PURE__ */ new Set([".github", ".circleci"]);
|
|
912
|
+
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
459
913
|
".ts",
|
|
460
914
|
".tsx",
|
|
461
915
|
".mts",
|
|
@@ -465,12 +919,36 @@ const SOURCE_EXTENSIONS = new Set([
|
|
|
465
919
|
".mjs",
|
|
466
920
|
".cjs"
|
|
467
921
|
]);
|
|
468
|
-
const
|
|
922
|
+
const SOURCE_STRING_FRAGMENT_SEPARATOR = "\0";
|
|
923
|
+
const MASKED_SOURCE_NODE_KINDS = /* @__PURE__ */ new Set([
|
|
469
924
|
"comment",
|
|
470
925
|
"string",
|
|
471
926
|
"regex",
|
|
472
|
-
"string_fragment"
|
|
927
|
+
"string_fragment",
|
|
928
|
+
"jsx_text"
|
|
473
929
|
]);
|
|
930
|
+
const SOURCE_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
931
|
+
"--env-file-if-exists",
|
|
932
|
+
"--env-file",
|
|
933
|
+
"--profile",
|
|
934
|
+
"--config",
|
|
935
|
+
"--workspace-id",
|
|
936
|
+
"--arg",
|
|
937
|
+
"--query",
|
|
938
|
+
"--file",
|
|
939
|
+
"--name",
|
|
940
|
+
"--namespace",
|
|
941
|
+
"--dir",
|
|
942
|
+
"-e",
|
|
943
|
+
"-p",
|
|
944
|
+
"-c",
|
|
945
|
+
"-w",
|
|
946
|
+
"-a",
|
|
947
|
+
"-q",
|
|
948
|
+
"-f",
|
|
949
|
+
"-n"
|
|
950
|
+
]);
|
|
951
|
+
const SOURCE_CLI_BINARY_RE = /^(?:(?:.*[\\/])?tailor(?:\.(?:cmd|ps1|exe))?|(?:.*[\\/])?tailor-sdk(?:@[^\s'"`;|&)]+)?(?:\.(?:cmd|ps1|exe))?|@tailor-platform\/sdk(?:@[^\s'"`;|&)]+)?)$/;
|
|
474
952
|
function shouldSkipDirectory(name) {
|
|
475
953
|
return EXCLUDE_DIRS.has(name) || name.startsWith(".") && !ALLOWED_DOT_DIRS.has(name);
|
|
476
954
|
}
|
|
@@ -514,25 +992,229 @@ function printDiff(filePath, before, after) {
|
|
|
514
992
|
* Load a transform module from a TypeScript file path.
|
|
515
993
|
* Expects the module to have a default export that is a TransformFn.
|
|
516
994
|
* @param scriptPath - Absolute path to the transform script
|
|
517
|
-
* @returns The transform function
|
|
995
|
+
* @returns The transform function and optional review detector
|
|
518
996
|
*/
|
|
519
|
-
async function
|
|
997
|
+
async function loadTransformModule(scriptPath) {
|
|
520
998
|
const mod = await import(url.pathToFileURL(scriptPath).href);
|
|
521
999
|
if (typeof mod.default !== "function") throw new Error(`Transform at ${scriptPath} does not have a default export function`);
|
|
522
|
-
return
|
|
1000
|
+
return {
|
|
1001
|
+
transform: mod.default,
|
|
1002
|
+
reviewFindings: typeof mod.reviewFindings === "function" ? mod.reviewFindings : void 0
|
|
1003
|
+
};
|
|
523
1004
|
}
|
|
524
1005
|
function contentForResidualMatching(relative, content) {
|
|
525
1006
|
const ext = path.extname(relative).toLowerCase();
|
|
526
1007
|
return SOURCE_EXTENSIONS.has(ext) ? maskSourceNonCode(relative, content) : content;
|
|
527
1008
|
}
|
|
1009
|
+
function sourceStringFragmentGapForResidualMatching(gap) {
|
|
1010
|
+
if (/^\\["']$/.test(gap)) return gap.slice(1);
|
|
1011
|
+
return /^(?:\\(?:[nrtvf]|\r\n|\r|\n)|\s)+$/.test(gap) ? " " : SOURCE_STRING_FRAGMENT_SEPARATOR;
|
|
1012
|
+
}
|
|
1013
|
+
function sourceStringNodeContentForResidualMatching(node, content) {
|
|
1014
|
+
const parts = [];
|
|
1015
|
+
let previousFragmentEnd = null;
|
|
1016
|
+
for (const child of node.children()) {
|
|
1017
|
+
if (child.kind() !== "string_fragment") continue;
|
|
1018
|
+
const range = child.range();
|
|
1019
|
+
if (previousFragmentEnd != null && range.start.index > previousFragmentEnd) parts.push(sourceStringFragmentGapForResidualMatching(content.slice(previousFragmentEnd, range.start.index)));
|
|
1020
|
+
parts.push(child.text());
|
|
1021
|
+
previousFragmentEnd = range.end.index;
|
|
1022
|
+
}
|
|
1023
|
+
return parts.length === 0 ? null : parts.join("");
|
|
1024
|
+
}
|
|
1025
|
+
function sourceStringContentForResidualMatching(relative, content) {
|
|
1026
|
+
const ext = path.extname(relative).toLowerCase();
|
|
1027
|
+
if (!SOURCE_EXTENSIONS.has(ext)) return null;
|
|
1028
|
+
let root;
|
|
1029
|
+
try {
|
|
1030
|
+
root = parse$1(sourceLang(relative), content).root();
|
|
1031
|
+
} catch {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
const sourceStrings = [];
|
|
1035
|
+
const visit = (node) => {
|
|
1036
|
+
if (node.kind() === "arguments") {
|
|
1037
|
+
const value = sourceArgumentsCommandContent(node, content);
|
|
1038
|
+
if (value != null) sourceStrings.push(value);
|
|
1039
|
+
}
|
|
1040
|
+
if (node.kind() === "array") {
|
|
1041
|
+
const value = sourceArrayCommandContent(node, content);
|
|
1042
|
+
if (value != null) sourceStrings.push(value);
|
|
1043
|
+
}
|
|
1044
|
+
const kind = node.kind();
|
|
1045
|
+
if (kind === "string" || kind === "template_string") {
|
|
1046
|
+
if (isSourceTailorSdkValueArgument(node, content)) return;
|
|
1047
|
+
const sourceString = sourceStringNodeContentForResidualMatching(node, content);
|
|
1048
|
+
if (sourceString != null) sourceStrings.push(sourceString);
|
|
1049
|
+
}
|
|
1050
|
+
for (const child of node.children()) {
|
|
1051
|
+
if (child.kind() === "string_fragment") continue;
|
|
1052
|
+
visit(child);
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
visit(root);
|
|
1056
|
+
return sourceStrings.join(SOURCE_STRING_FRAGMENT_SEPARATOR);
|
|
1057
|
+
}
|
|
1058
|
+
function isConstVariableDeclarator(node) {
|
|
1059
|
+
return node.parent()?.children().some((child) => child.kind() === "const") ?? false;
|
|
1060
|
+
}
|
|
1061
|
+
function sourceTextContentForResidualMatching(relative, content) {
|
|
1062
|
+
const ext = path.extname(relative).toLowerCase();
|
|
1063
|
+
if (!SOURCE_EXTENSIONS.has(ext)) return null;
|
|
1064
|
+
let root;
|
|
1065
|
+
try {
|
|
1066
|
+
root = parse$1(sourceLang(relative), content).root();
|
|
1067
|
+
} catch {
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
const fragments = [];
|
|
1071
|
+
const visit = (node) => {
|
|
1072
|
+
if (node.kind() === "comment" || node.kind() === "jsx_text") {
|
|
1073
|
+
fragments.push(node.text());
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
for (const child of node.children()) visit(child);
|
|
1077
|
+
};
|
|
1078
|
+
visit(root);
|
|
1079
|
+
return fragments.join(SOURCE_STRING_FRAGMENT_SEPARATOR);
|
|
1080
|
+
}
|
|
1081
|
+
function sourceArgumentsCommandContent(node, source) {
|
|
1082
|
+
const args = sourceArrayElements(node);
|
|
1083
|
+
const executable = args[0] == null ? null : sourceStringLikeNodeContent(args[0], source);
|
|
1084
|
+
const argv = args[1];
|
|
1085
|
+
if (executable == null || argv?.kind() !== "array") return null;
|
|
1086
|
+
const values = sourceArrayCommandValues(argv, source);
|
|
1087
|
+
return values.length === 0 ? null : [executable, ...values].join(" ");
|
|
1088
|
+
}
|
|
1089
|
+
function sourceArrayCommandContent(node, source) {
|
|
1090
|
+
const values = sourceArrayCommandValues(node, source);
|
|
1091
|
+
return values.length < 2 ? null : values.join(" ");
|
|
1092
|
+
}
|
|
1093
|
+
function sourceArrayCommandValues(node, source) {
|
|
1094
|
+
const values = [];
|
|
1095
|
+
for (const element of sourceArrayElements(node)) {
|
|
1096
|
+
if (isSourceValueArgument(element, source)) continue;
|
|
1097
|
+
const value = sourceStringLikeNodeContent(element, source);
|
|
1098
|
+
if (value != null) values.push(value);
|
|
1099
|
+
}
|
|
1100
|
+
return values;
|
|
1101
|
+
}
|
|
1102
|
+
function isSourceTailorSdkValueArgument(fragment, source) {
|
|
1103
|
+
const text = fragment.kind() === "string_fragment" ? fragment.text() : sourceStringLikeNodeContent(fragment, source);
|
|
1104
|
+
return text != null && text.includes("tailor-sdk") && isSourceValueArgument(fragment, source);
|
|
1105
|
+
}
|
|
1106
|
+
function isSyntaxOnlyNode(node) {
|
|
1107
|
+
const kind = node.kind();
|
|
1108
|
+
return kind === "[" || kind === "]" || kind === "(" || kind === ")" || kind === "," || kind === "comment";
|
|
1109
|
+
}
|
|
1110
|
+
function sourceArrayElements(node) {
|
|
1111
|
+
return node.children().filter((child) => !isSyntaxOnlyNode(child));
|
|
1112
|
+
}
|
|
1113
|
+
function nodeRangeKey(node) {
|
|
1114
|
+
const range = node.range();
|
|
1115
|
+
return `${range.start.index}:${range.end.index}`;
|
|
1116
|
+
}
|
|
1117
|
+
function sourceStringLikeNodeContent(node, source) {
|
|
1118
|
+
const directValue = sourceStringNodeContent(node, source);
|
|
1119
|
+
if (directValue != null) return directValue;
|
|
1120
|
+
return node.kind() === "identifier" ? sourceScopedStringVariableContent(node, source) : null;
|
|
1121
|
+
}
|
|
1122
|
+
function sourceScopedStringVariableContent(identifier, source) {
|
|
1123
|
+
const name = identifier.text();
|
|
1124
|
+
const before = identifier.range().start.index;
|
|
1125
|
+
let current = identifier.parent();
|
|
1126
|
+
while (current != null) {
|
|
1127
|
+
if (isSourceScopeNode(current)) {
|
|
1128
|
+
const value = findSourceStringVariableInScope(current, name, before, source);
|
|
1129
|
+
if (value != null) return value;
|
|
1130
|
+
}
|
|
1131
|
+
current = current.parent();
|
|
1132
|
+
}
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
function findSourceStringVariableInScope(scope, name, before, source) {
|
|
1136
|
+
let value = null;
|
|
1137
|
+
const visit = (node) => {
|
|
1138
|
+
if (node !== scope && isSourceScopeNode(node)) return;
|
|
1139
|
+
if (node.kind() === "variable_declarator" && node.range().end.index < before) {
|
|
1140
|
+
const declarationValue = sourceStringVariableDeclarationValue(node, name, source);
|
|
1141
|
+
if (declarationValue != null) value = declarationValue;
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
for (const child of node.children()) visit(child);
|
|
1145
|
+
};
|
|
1146
|
+
visit(scope);
|
|
1147
|
+
return value;
|
|
1148
|
+
}
|
|
1149
|
+
function sourceStringVariableDeclarationValue(node, name, source) {
|
|
1150
|
+
if (!isConstVariableDeclarator(node)) return null;
|
|
1151
|
+
const children = node.children();
|
|
1152
|
+
if (children.find((child) => child.kind() === "identifier")?.text() !== name) return null;
|
|
1153
|
+
const initializer = children.findLast((child) => sourceConstInitializerContent(child, source) != null);
|
|
1154
|
+
return initializer == null ? null : sourceConstInitializerContent(initializer, source);
|
|
1155
|
+
}
|
|
1156
|
+
function sourceConstInitializerContent(node, source) {
|
|
1157
|
+
const directValue = sourceStringNodeContent(node, source);
|
|
1158
|
+
if (directValue != null) return directValue;
|
|
1159
|
+
if (node.kind() !== "as_expression" && node.kind() !== "satisfies_expression" && node.kind() !== "parenthesized_expression") return null;
|
|
1160
|
+
for (const child of node.children()) {
|
|
1161
|
+
const childValue = sourceConstInitializerContent(child, source);
|
|
1162
|
+
if (childValue != null) return childValue;
|
|
1163
|
+
}
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
function isSourceScopeNode(node) {
|
|
1167
|
+
const kind = node.kind();
|
|
1168
|
+
return kind === "program" || kind === "statement_block" || kind === "function_declaration" || kind === "arrow_function" || kind === "method_definition";
|
|
1169
|
+
}
|
|
1170
|
+
function sourceStringNodeContent(node, source) {
|
|
1171
|
+
const kind = node.kind();
|
|
1172
|
+
if (kind !== "string" && kind !== "template_string") return null;
|
|
1173
|
+
if (kind === "template_string" && node.children().some((child) => child.kind() === "template_substitution")) return null;
|
|
1174
|
+
const range = node.range();
|
|
1175
|
+
return source.slice(range.start.index + 1, range.end.index - 1);
|
|
1176
|
+
}
|
|
1177
|
+
function isSourceValueArgument(fragment, source) {
|
|
1178
|
+
const stringNode = fragment.kind() === "string_fragment" ? fragment.parent() : fragment;
|
|
1179
|
+
if (stringNode == null) return false;
|
|
1180
|
+
const parent = stringNode.parent();
|
|
1181
|
+
if (parent?.kind() !== "array") return false;
|
|
1182
|
+
const elements = sourceArrayElements(parent);
|
|
1183
|
+
const index = elements.findIndex((element) => nodeRangeKey(element) === nodeRangeKey(stringNode));
|
|
1184
|
+
if (index <= 0) return false;
|
|
1185
|
+
if (!isTailorCliArgumentArray(parent, index, source)) return false;
|
|
1186
|
+
const previous = sourceStringLikeNodeContent(elements[index - 1], source);
|
|
1187
|
+
return previous != null && SOURCE_VALUE_FLAGS.has(previous.split("=", 1)[0]) && !previous.includes("=");
|
|
1188
|
+
}
|
|
1189
|
+
function isTailorCliArgumentArray(arrayNode, index, source) {
|
|
1190
|
+
const argumentsNode = arrayNode.parent();
|
|
1191
|
+
if (argumentsNode?.kind() === "arguments") {
|
|
1192
|
+
const callArgs = sourceArrayElements(argumentsNode);
|
|
1193
|
+
const executable = callArgs[0] == null ? null : sourceStringLikeNodeContent(callArgs[0], source);
|
|
1194
|
+
if (executable != null && SOURCE_CLI_BINARY_RE.test(executable)) return true;
|
|
1195
|
+
}
|
|
1196
|
+
return sourceArrayElements(arrayNode).slice(0, index).some((element) => {
|
|
1197
|
+
const value = sourceStringLikeNodeContent(element, source);
|
|
1198
|
+
return value != null && SOURCE_CLI_BINARY_RE.test(value);
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
528
1201
|
function sourceLang(relative) {
|
|
529
1202
|
const ext = path.extname(relative).toLowerCase();
|
|
530
|
-
return ext === ".tsx" || ext === ".jsx" ? Lang.Tsx : Lang.TypeScript;
|
|
1203
|
+
return ext === ".tsx" || ext === ".jsx" || ext === ".js" ? Lang.Tsx : Lang.TypeScript;
|
|
1204
|
+
}
|
|
1205
|
+
function isProcessEnvSubscriptKey(node) {
|
|
1206
|
+
const stringNode = node.kind() === "string_fragment" ? node.parent() : node;
|
|
1207
|
+
if (stringNode == null) return false;
|
|
1208
|
+
const stringNodeKind = stringNode.kind();
|
|
1209
|
+
if (stringNodeKind !== "string" && stringNodeKind !== "template_string") return false;
|
|
1210
|
+
const parent = stringNode.parent();
|
|
1211
|
+
return parent?.kind() === "subscript_expression" && /^process\.env\s*\[/.test(parent.text());
|
|
531
1212
|
}
|
|
532
1213
|
function collectMaskedRanges(root) {
|
|
533
1214
|
const ranges = [];
|
|
534
1215
|
const visit = (node) => {
|
|
535
1216
|
if (MASKED_SOURCE_NODE_KINDS.has(node.kind())) {
|
|
1217
|
+
if (isProcessEnvSubscriptKey(node)) return;
|
|
536
1218
|
const range = node.range();
|
|
537
1219
|
ranges.push([range.start.index, range.end.index]);
|
|
538
1220
|
return;
|
|
@@ -545,7 +1227,7 @@ function collectMaskedRanges(root) {
|
|
|
545
1227
|
function maskSourceNonCode(relative, content) {
|
|
546
1228
|
let ranges;
|
|
547
1229
|
try {
|
|
548
|
-
ranges = collectMaskedRanges(parse(sourceLang(relative), content).root());
|
|
1230
|
+
ranges = collectMaskedRanges(parse$1(sourceLang(relative), content).root());
|
|
549
1231
|
} catch {
|
|
550
1232
|
return content;
|
|
551
1233
|
}
|
|
@@ -581,13 +1263,31 @@ function matchResidualPattern(content, pattern) {
|
|
|
581
1263
|
if (!Array.isArray(pattern)) return matchesPattern(content, pattern) ? patternLabel(pattern) : null;
|
|
582
1264
|
return pattern.every((p) => matchesPattern(content, p)) ? pattern.map((p) => patternLabel(p)).join(" + ") : null;
|
|
583
1265
|
}
|
|
584
|
-
function
|
|
1266
|
+
function matchResidualPatternFragment(content, pattern) {
|
|
1267
|
+
for (const fragment of content.split(SOURCE_STRING_FRAGMENT_SEPARATOR)) {
|
|
1268
|
+
const label = matchResidualPattern(fragment, pattern);
|
|
1269
|
+
if (label != null) return label;
|
|
1270
|
+
}
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
function legacyPatternWarnings(relative, content, sourceStringContent, sourceTextContent, transforms) {
|
|
585
1274
|
return transforms.flatMap((lt) => {
|
|
586
|
-
const found = lt.legacyPatterns.map((p) => matchResidualPattern(content, p)).filter((label) => label !== null);
|
|
587
|
-
if (
|
|
588
|
-
|
|
1275
|
+
const found = new Set(lt.legacyPatterns.map((p) => matchResidualPattern(content, p)).filter((label) => label !== null));
|
|
1276
|
+
if (sourceStringContent != null) for (const pattern of lt.sourceStringLegacyPatterns) {
|
|
1277
|
+
const label = matchResidualPatternFragment(sourceStringContent, pattern);
|
|
1278
|
+
if (label != null) found.add(label);
|
|
1279
|
+
}
|
|
1280
|
+
if (sourceTextContent != null) for (const pattern of lt.sourceTextLegacyPatterns) {
|
|
1281
|
+
const label = matchResidualPatternFragment(sourceTextContent, pattern);
|
|
1282
|
+
if (label != null) found.add(label);
|
|
1283
|
+
}
|
|
1284
|
+
if (found.size === 0) return [];
|
|
1285
|
+
return [`${relative}: contains ${Array.from(found).join(", ")} but was not migrated automatically (rule: ${lt.id}). Manual migration may be needed.`];
|
|
589
1286
|
});
|
|
590
1287
|
}
|
|
1288
|
+
function compareReviewFindings(a, b) {
|
|
1289
|
+
return a.file.localeCompare(b.file) || a.line - b.line || a.message.localeCompare(b.message) || a.excerpt.localeCompare(b.excerpt);
|
|
1290
|
+
}
|
|
591
1291
|
/**
|
|
592
1292
|
* Run multiple codemods on a project directory using in-memory chaining.
|
|
593
1293
|
* Each file is processed through all transforms whose filePatterns match it.
|
|
@@ -603,13 +1303,19 @@ async function runCodemods(codemods, targetPath, dryRun) {
|
|
|
603
1303
|
const loaded = [];
|
|
604
1304
|
for (const { codemod, scriptPath } of codemods) {
|
|
605
1305
|
const patterns = codemod.filePatterns ?? DEFAULT_FILE_PATTERNS;
|
|
1306
|
+
const loadedModule = scriptPath ? await loadTransformModule(scriptPath) : void 0;
|
|
606
1307
|
loaded.push({
|
|
607
1308
|
id: codemod.id,
|
|
608
|
-
transform:
|
|
1309
|
+
transform: loadedModule?.transform,
|
|
1310
|
+
reviewFindings: loadedModule?.reviewFindings,
|
|
609
1311
|
matches: picomatch(patterns, { dot: true }),
|
|
610
1312
|
legacyPatterns: codemod.legacyPatterns ?? [],
|
|
1313
|
+
sourceStringLegacyPatterns: codemod.sourceStringLegacyPatterns ?? [],
|
|
1314
|
+
sourceTextLegacyPatterns: codemod.sourceTextLegacyPatterns ?? [],
|
|
611
1315
|
suspiciousPatterns: codemod.suspiciousPatterns ?? [],
|
|
612
|
-
|
|
1316
|
+
sourceStringSuspiciousPatterns: codemod.sourceStringSuspiciousPatterns ?? [],
|
|
1317
|
+
prompt: codemod.prompt,
|
|
1318
|
+
reviewSupersededBy: codemod.reviewSupersededBy ?? []
|
|
613
1319
|
});
|
|
614
1320
|
}
|
|
615
1321
|
const filesModified = [];
|
|
@@ -617,6 +1323,7 @@ async function runCodemods(codemods, targetPath, dryRun) {
|
|
|
617
1323
|
const appliedCodemodIds = /* @__PURE__ */ new Set();
|
|
618
1324
|
const seen = /* @__PURE__ */ new Set();
|
|
619
1325
|
const suspiciousByCodemod = /* @__PURE__ */ new Map();
|
|
1326
|
+
const findingsByCodemod = /* @__PURE__ */ new Map();
|
|
620
1327
|
for await (const relative of walkFiles(targetPath)) {
|
|
621
1328
|
const absolute = path.resolve(targetPath, relative);
|
|
622
1329
|
if (seen.has(absolute)) continue;
|
|
@@ -644,26 +1351,51 @@ async function runCodemods(codemods, targetPath, dryRun) {
|
|
|
644
1351
|
else await fs.promises.writeFile(absolute, current, "utf-8");
|
|
645
1352
|
}
|
|
646
1353
|
const residualContent = contentForResidualMatching(relative, current);
|
|
647
|
-
|
|
1354
|
+
const sourceStringContent = sourceStringContentForResidualMatching(relative, current);
|
|
1355
|
+
const sourceTextContent = sourceTextContentForResidualMatching(relative, current);
|
|
1356
|
+
warnings.push(...legacyPatternWarnings(relative, residualContent, sourceStringContent, sourceTextContent, matchedTransforms));
|
|
648
1357
|
for (const lt of matchedTransforms) {
|
|
649
|
-
if (!lt.prompt
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
files
|
|
653
|
-
|
|
1358
|
+
if (!lt.prompt) continue;
|
|
1359
|
+
const filesForReview = () => {
|
|
1360
|
+
let files = suspiciousByCodemod.get(lt.id);
|
|
1361
|
+
if (!files) {
|
|
1362
|
+
files = /* @__PURE__ */ new Set();
|
|
1363
|
+
suspiciousByCodemod.set(lt.id, files);
|
|
1364
|
+
}
|
|
1365
|
+
return files;
|
|
1366
|
+
};
|
|
1367
|
+
if (lt.reviewFindings) {
|
|
1368
|
+
const findings = await lt.reviewFindings(current, absolute, relative);
|
|
1369
|
+
if (findings.length > 0) {
|
|
1370
|
+
const files = filesForReview();
|
|
1371
|
+
for (const finding of findings) files.add(finding.file);
|
|
1372
|
+
let existing = findingsByCodemod.get(lt.id);
|
|
1373
|
+
if (!existing) {
|
|
1374
|
+
existing = [];
|
|
1375
|
+
findingsByCodemod.set(lt.id, existing);
|
|
1376
|
+
}
|
|
1377
|
+
existing.push(...findings);
|
|
1378
|
+
}
|
|
654
1379
|
}
|
|
1380
|
+
if (lt.suspiciousPatterns.some((p) => matchResidualPattern(residualContent, p) !== null) || sourceStringContent != null && lt.sourceStringSuspiciousPatterns.some((p) => matchResidualPattern(sourceStringContent, p) !== null)) filesForReview().add(relative);
|
|
655
1381
|
}
|
|
656
1382
|
}
|
|
657
1383
|
const llmReviews = [];
|
|
1384
|
+
const loadedIds = new Set(loaded.map((lt) => lt.id));
|
|
658
1385
|
for (const lt of loaded) {
|
|
659
1386
|
if (!lt.prompt) continue;
|
|
660
|
-
if (lt.
|
|
1387
|
+
if (lt.reviewSupersededBy.some((id) => loadedIds.has(id))) continue;
|
|
1388
|
+
if (lt.suspiciousPatterns.length > 0 || lt.sourceStringSuspiciousPatterns.length > 0 || lt.reviewFindings) {
|
|
661
1389
|
const files = suspiciousByCodemod.get(lt.id);
|
|
662
|
-
if (files)
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1390
|
+
if (files) {
|
|
1391
|
+
const findings = findingsByCodemod.get(lt.id)?.toSorted(compareReviewFindings);
|
|
1392
|
+
llmReviews.push({
|
|
1393
|
+
codemodId: lt.id,
|
|
1394
|
+
prompt: lt.prompt,
|
|
1395
|
+
files: Array.from(files).toSorted(),
|
|
1396
|
+
...findings && findings.length > 0 ? { findings } : {}
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
667
1399
|
} else if (lt.legacyPatterns.length === 0) llmReviews.push({
|
|
668
1400
|
codemodId: lt.id,
|
|
669
1401
|
prompt: lt.prompt,
|
|
@@ -679,12 +1411,64 @@ async function runCodemods(codemods, targetPath, dryRun) {
|
|
|
679
1411
|
};
|
|
680
1412
|
}
|
|
681
1413
|
//#endregion
|
|
1414
|
+
//#region src/runner-metadata.ts
|
|
1415
|
+
const SOURCE_PACKAGE_PATH = "packages/sdk-codemod";
|
|
1416
|
+
const LOCAL_BUILD_COMMAND = "pnpm --dir packages/sdk-codemod build";
|
|
1417
|
+
function createRunnerMetadata({ packageName, packageVersion, packageRoot, readGit = readGitOutput, realpath = safeRealpath }) {
|
|
1418
|
+
const metadata = {
|
|
1419
|
+
packageName,
|
|
1420
|
+
packageVersion
|
|
1421
|
+
};
|
|
1422
|
+
const gitRoot = readGit(packageRoot, ["rev-parse", "--show-toplevel"]);
|
|
1423
|
+
if (!gitRoot) return metadata;
|
|
1424
|
+
if (path.normalize(path.relative(realpath(gitRoot), realpath(packageRoot))).replaceAll("\\", "/") !== SOURCE_PACKAGE_PATH) return metadata;
|
|
1425
|
+
const gitCommit = readGit(packageRoot, [
|
|
1426
|
+
"rev-parse",
|
|
1427
|
+
"--verify",
|
|
1428
|
+
"HEAD"
|
|
1429
|
+
]);
|
|
1430
|
+
if (!gitCommit) return metadata;
|
|
1431
|
+
return {
|
|
1432
|
+
...metadata,
|
|
1433
|
+
gitCommit,
|
|
1434
|
+
localBuildCommand: LOCAL_BUILD_COMMAND
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
function readGitOutput(cwd, args) {
|
|
1438
|
+
try {
|
|
1439
|
+
return execFileSync("git", [
|
|
1440
|
+
"-C",
|
|
1441
|
+
cwd,
|
|
1442
|
+
...args
|
|
1443
|
+
], {
|
|
1444
|
+
encoding: "utf-8",
|
|
1445
|
+
stdio: [
|
|
1446
|
+
"ignore",
|
|
1447
|
+
"pipe",
|
|
1448
|
+
"ignore"
|
|
1449
|
+
]
|
|
1450
|
+
}).trim() || void 0;
|
|
1451
|
+
} catch {
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
function safeRealpath(value) {
|
|
1456
|
+
try {
|
|
1457
|
+
return realpathSync(value);
|
|
1458
|
+
} catch {
|
|
1459
|
+
return path.resolve(value);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
//#endregion
|
|
682
1463
|
//#region src/index.ts
|
|
683
|
-
const
|
|
1464
|
+
const packageRoot = path.dirname(fileURLToPath(import.meta.url)) + "/..";
|
|
1465
|
+
const packageJson = await readPackageJSON(packageRoot);
|
|
1466
|
+
const packageName = packageJson.name ?? "sdk-codemod";
|
|
1467
|
+
const packageVersion = packageJson.version ?? "0.0.0";
|
|
684
1468
|
const listCommand = defineCommand({
|
|
685
1469
|
name: "list",
|
|
686
1470
|
description: "List the available codemod rules (id, name, kind, version range).",
|
|
687
|
-
args: z.
|
|
1471
|
+
args: z.strictObject({}),
|
|
688
1472
|
run: () => {
|
|
689
1473
|
const rules = allCodemods.map((codemod) => ({
|
|
690
1474
|
id: codemod.id,
|
|
@@ -706,11 +1490,26 @@ const listCommand = defineCommand({
|
|
|
706
1490
|
function printLlmReview(review) {
|
|
707
1491
|
const scope = review.files.length > 0 ? "the codemod cannot safely migrate these automatically" : "review the project for this manual change";
|
|
708
1492
|
process.stderr.write(`\nš¤ LLM-assisted review suggested (${review.codemodId}) ā ${scope}:\n`);
|
|
709
|
-
|
|
1493
|
+
const findingsByFile = /* @__PURE__ */ new Map();
|
|
1494
|
+
for (const finding of review.findings ?? []) {
|
|
1495
|
+
let findings = findingsByFile.get(finding.file);
|
|
1496
|
+
if (!findings) {
|
|
1497
|
+
findings = [];
|
|
1498
|
+
findingsByFile.set(finding.file, findings);
|
|
1499
|
+
}
|
|
1500
|
+
findings.push(finding);
|
|
1501
|
+
}
|
|
1502
|
+
for (const file of review.files) {
|
|
1503
|
+
process.stderr.write(` - ${file}\n`);
|
|
1504
|
+
for (const finding of findingsByFile.get(file) ?? []) {
|
|
1505
|
+
process.stderr.write(` - line ${finding.line}: ${finding.message}\n`);
|
|
1506
|
+
process.stderr.write(` ${finding.excerpt}\n`);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
710
1509
|
process.stderr.write(`\nPrompt for an LLM:\n${review.prompt.trim()}\n`);
|
|
711
1510
|
}
|
|
712
1511
|
runMain(defineCommand({
|
|
713
|
-
name:
|
|
1512
|
+
name: packageName,
|
|
714
1513
|
description: packageJson.description ?? "Codemod runner for Tailor Platform SDK upgrades",
|
|
715
1514
|
subCommands: { list: listCommand },
|
|
716
1515
|
notes: `Applies the codemods matching the \`--from\`/\`--to\` version range to the
|
|
@@ -719,8 +1518,11 @@ runMain(defineCommand({
|
|
|
719
1518
|
- \`filesModified\`: files a codemod changed
|
|
720
1519
|
- \`warnings\`: files that may still need manual migration
|
|
721
1520
|
- \`llmReviews\`: changes the codemods could not fully migrate on their own. Each
|
|
722
|
-
entry has the affected \`files
|
|
723
|
-
an LLM (or follow it yourself) to
|
|
1521
|
+
entry has the affected \`files\`, optional file-local \`findings\`, and a
|
|
1522
|
+
\`prompt\` ā hand the prompt and files to an LLM (or follow it yourself) to
|
|
1523
|
+
finish those cases.
|
|
1524
|
+
- \`runner\`: exact codemod runner identity. Local source builds include the
|
|
1525
|
+
repository commit and the build command used to produce \`dist/index.js\`.
|
|
724
1526
|
|
|
725
1527
|
Progress, warnings, and the LLM-review prompts are also printed to \`stderr\` in
|
|
726
1528
|
human-readable form, so \`stdout\` stays pure JSON for piping.`,
|
|
@@ -731,7 +1533,7 @@ human-readable form, so \`stdout\` stays pure JSON for piping.`,
|
|
|
731
1533
|
cmd: "--from 1.64.0 --to 2.0.0 --dry-run",
|
|
732
1534
|
desc: "Preview the changes and any LLM-review prompts without writing files"
|
|
733
1535
|
}],
|
|
734
|
-
args: z.
|
|
1536
|
+
args: z.strictObject({
|
|
735
1537
|
from: arg(z.string(), { description: "Source SDK version (the version before upgrade)" }),
|
|
736
1538
|
to: arg(z.string(), { description: "Target SDK version (the version after upgrade)" }),
|
|
737
1539
|
target: arg(z.string().default("."), { description: "Project directory to transform" }),
|
|
@@ -739,12 +1541,18 @@ human-readable form, so \`stdout\` stays pure JSON for piping.`,
|
|
|
739
1541
|
alias: "d",
|
|
740
1542
|
description: "Preview changes without modifying files"
|
|
741
1543
|
})
|
|
742
|
-
})
|
|
1544
|
+
}),
|
|
743
1545
|
run: async (args) => {
|
|
744
1546
|
const targetPath = path.resolve(args.target);
|
|
745
1547
|
const dryRun = args["dry-run"];
|
|
1548
|
+
const runner = createRunnerMetadata({
|
|
1549
|
+
packageName,
|
|
1550
|
+
packageVersion,
|
|
1551
|
+
packageRoot
|
|
1552
|
+
});
|
|
746
1553
|
const codemods = getApplicableCodemods(args.from, args.to);
|
|
747
1554
|
const output = {
|
|
1555
|
+
runner,
|
|
748
1556
|
codemodsApplied: 0,
|
|
749
1557
|
codemodsSkipped: 0,
|
|
750
1558
|
filesModified: [],
|
|
@@ -782,6 +1590,6 @@ human-readable form, so \`stdout\` stays pure JSON for piping.`,
|
|
|
782
1590
|
process.stdout.write(JSON.stringify(output) + "\n");
|
|
783
1591
|
if (output.errors.length > 0) process.exit(1);
|
|
784
1592
|
}
|
|
785
|
-
}), { version:
|
|
1593
|
+
}), { version: packageVersion });
|
|
786
1594
|
//#endregion
|
|
787
1595
|
export {};
|