@tailor-platform/sdk-codemod 0.3.0-next.0 → 0.3.0-next.2

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 CHANGED
@@ -1,10 +1,96 @@
1
1
  # @tailor-platform/sdk-codemod
2
2
 
3
+ ## 0.3.0-next.2
4
+ ### Minor Changes
5
+
6
+
7
+
8
+ - [#1473](https://github.com/tailor-platform/sdk/pull/1473) [`7ddf3c7`](https://github.com/tailor-platform/sdk/commit/7ddf3c716adf85a66a75d554da7730b5406f84b1) Thanks [@dqn](https://github.com/dqn)! - Add the `v2/principal-unify` codemod so `tailor-sdk upgrade` can migrate SDK principal APIs to `TailorPrincipal`.
9
+
10
+
11
+ ### Patch Changes
12
+
13
+
14
+
15
+ - [#1515](https://github.com/tailor-platform/sdk/pull/1515) [`dcf66a1`](https://github.com/tailor-platform/sdk/commit/dcf66a1e648f5287eaea9ea330eb4ad726a4d363) Thanks [@dqn](https://github.com/dqn)! - Rewrite `tailor-sdk apply` to `tailor-sdk deploy` in source files that contain embedded CLI command strings.
16
+
17
+
18
+
19
+ - [#1482](https://github.com/tailor-platform/sdk/pull/1482) [`8b5870e`](https://github.com/tailor-platform/sdk/commit/8b5870e85db1efec7647acb98226f8161e3d1583) Thanks [@toiroakr](https://github.com/toiroakr)! - Add LLM-assisted review support to the codemod runner. A codemod can declare `suspiciousPatterns` plus a `prompt`; after running, files whose post-transform content still matches a suspicious pattern are reported as `llmReviews` (in the JSON output and on stderr) together with the codemod's migration prompt. This surfaces the cases a deterministic transform cannot safely complete (e.g. a value reached through a variable) so they can be finished with an LLM. The `auth.invoker(...)` codemod adopts this for its non-literal-argument cases.
20
+
21
+
22
+
23
+ - [#1495](https://github.com/tailor-platform/sdk/pull/1495) [`6234022`](https://github.com/tailor-platform/sdk/commit/6234022d7dc03813b8dade831b86f63a5f7a20e6) Thanks [@toiroakr](https://github.com/toiroakr)! - Generate the v2 migration guide (`packages/sdk/docs/migration/v2.md`) from the codemod registry, which is the single source of truth. Each entry renders its name, automation level (Automatic / Partially automatic / Manual), description, optional before/after `examples`, and — for changes the codemods cannot fully migrate on their own — the LLM/manual migration prompt. Run `pnpm codemod:docs:update` to regenerate and `pnpm codemod:docs:check` (wired into `pnpm check`) to verify it is in sync.
24
+
25
+ `scriptPath` is now optional, so the registry can also describe codemod-less ("manual") migrations that ship only guidance (`examples` / `prompt` / `suspiciousPatterns`) with no automatic transform. A manual entry with a `prompt` but no scoping pattern is surfaced as a project-wide `llmReviews` entry at runtime.
26
+
27
+ Add a `sdk-codemod list` command that prints every registered rule (id, name, kind, version range).
28
+
29
+
30
+ - [#1517](https://github.com/tailor-platform/sdk/pull/1517) [`a649764`](https://github.com/tailor-platform/sdk/commit/a6497649be2786b3f6e410c8aa98c4247a599258) Thanks [@dqn](https://github.com/dqn)! - Reduce false-positive v2 codemod warnings and LLM-review prompts from source comments, string literals, and identifier substring matches.
31
+
32
+
33
+
34
+ - [#1476](https://github.com/tailor-platform/sdk/pull/1476) [`fa83075`](https://github.com/tailor-platform/sdk/commit/fa83075f5e0e91085c0ef0cb44b7058a28a79ec3) Thanks [@toiroakr](https://github.com/toiroakr)! - `executeScript` now takes its `arg` as a JSON-serializable value instead of a pre-serialized JSON string. Pass the value directly (e.g. `arg: { a: 1 }`) instead of `arg: JSON.stringify({ a: 1 })`.
35
+
36
+ Add the `v2/execute-script-arg` codemod, which unwraps `JSON.stringify(...)` passed as the `executeScript` `arg` option. Indirect forms (a stringified value held in a variable, etc.) cannot be rewritten automatically and are surfaced as an LLM-assisted review task with a migration prompt.
37
+
38
+
39
+ - [#1518](https://github.com/tailor-platform/sdk/pull/1518) [`ab10b1f`](https://github.com/tailor-platform/sdk/commit/ab10b1fea309ec5496e09bdca394d46d58603f5f) Thanks [@dqn](https://github.com/dqn)! - Reduce noisy `executeScript` LLM-review prompts by flagging files only when unresolved `arg` stringification remains likely.
40
+
41
+
42
+
43
+ - [#1509](https://github.com/tailor-platform/sdk/pull/1509) [`7cadaa7`](https://github.com/tailor-platform/sdk/commit/7cadaa7c4987b81130ca80ba80bc5d5b26276394) Thanks [@dqn](https://github.com/dqn)! - Rename resolver, executor, workflow trigger, and typed workflow start machine-user options from `authInvoker` to `invoker`.
44
+
45
+ Update create-sdk templates and the v2 auth invoker codemod to generate the new `invoker` option.
46
+
47
+
48
+ - [#1519](https://github.com/tailor-platform/sdk/pull/1519) [`4e3fa47`](https://github.com/tailor-platform/sdk/commit/4e3fa47d24e6bb1145eac13c355e976f2d594851) Thanks [@dqn](https://github.com/dqn)! - Limit the `openDownloadStream` migration review prompt to files that reference deprecated download stream APIs.
49
+
50
+
51
+
52
+ - [#1521](https://github.com/tailor-platform/sdk/pull/1521) [`2d0689e`](https://github.com/tailor-platform/sdk/commit/2d0689e8ac0079473294fab367799a5431c130f4) Thanks [@dqn](https://github.com/dqn)! - Flag files that need project-specific review after the v2 principal migration, including resolver helper adapters and nullable `caller` follow-ups.
53
+
54
+
55
+
56
+ - [#1525](https://github.com/tailor-platform/sdk/pull/1525) [`425a19d`](https://github.com/tailor-platform/sdk/commit/425a19dd58da6e373b739d3b3e838c2ff3d1736a) Thanks [@renovate](https://github.com/apps/renovate)! - fix(deps): update dependency semver to v7.8.5
57
+
58
+
59
+
60
+ - [#1520](https://github.com/tailor-platform/sdk/pull/1520) [`ed3d338`](https://github.com/tailor-platform/sdk/commit/ed3d338ce71d68904ef1fb83afbbd06a7e5f6973) Thanks [@dqn](https://github.com/dqn)! - Flag files that still reference ambient Tailor runtime globals so the v2 migration can opt them into `@tailor-platform/sdk/runtime/globals`.
61
+
62
+
63
+
64
+ - [#1439](https://github.com/tailor-platform/sdk/pull/1439) [`c5b10d2`](https://github.com/tailor-platform/sdk/commit/c5b10d2841ded08927285bce538c05220cde5e4c) Thanks [@dqn](https://github.com/dqn)! - Unify function principal context around `TailorPrincipal`.
65
+
66
+ Resolver contexts now use `caller` and `invoker` as `TailorPrincipal | null`, workflow and executor invokers also use `TailorPrincipal | null`, and event executor `actor` uses `TailorPrincipal | null` with `id`/`type` fields. The legacy `TailorUser`, `TailorInvoker`, `TailorActor`, `TailorActorType`, and `unauthenticatedTailorUser` exports are removed.
67
+
68
+ ## 0.3.0-next.1
69
+ ### Patch Changes
70
+
71
+
72
+
73
+ - [#1460](https://github.com/tailor-platform/sdk/pull/1460) [`f49c6d1`](https://github.com/tailor-platform/sdk/commit/f49c6d1b5a856969cb4e04ae7d3a87ed34aa020f) Thanks [@dqn](https://github.com/dqn)! - Remove the v1 runtime globals compatibility layer. Importing from `@tailor-platform/sdk` no longer activates the ambient `tailor.*` / `tailordb.*` declarations; opt into globals with `@tailor-platform/sdk/runtime/globals` or use the typed wrappers from `@tailor-platform/sdk/runtime`.
74
+
75
+ The capital-cased `Tailordb.*` namespace is removed. If your project still references `Tailordb.QueryResult`, `Tailordb.CommandType`, `Tailordb.Client`, or `typeof Tailordb.Client`, migrate before upgrading: run `pnpm dlx @tailor-platform/sdk-codemod v2/tailordb-namespace` to rewrite them to lowercase `tailordb.*`, then add `import "@tailor-platform/sdk/runtime/globals"` so the rewritten references resolve.
76
+
77
+
78
+ - [#1457](https://github.com/tailor-platform/sdk/pull/1457) [`84325f8`](https://github.com/tailor-platform/sdk/commit/84325f8602a5631b7c323c997b1425235509920e) Thanks [@dqn](https://github.com/dqn)! - Remove deprecated CLI aliases for the v2 command surface. Use `tailor-sdk deploy` instead of `tailor-sdk apply`, `tailor-sdk crashreport` instead of `tailor-sdk crash-report`, and the hyphenated `--machine-user` option instead of the hidden `--machineuser` alias.
79
+
80
+ Fix the v2 CLI rename codemod to migrate the hidden `--machineuser` option to `--machine-user`.
81
+
3
82
  ## 0.3.0-next.0
4
83
  ### Minor Changes
5
84
 
6
85
 
7
86
 
87
+ - [#1435](https://github.com/tailor-platform/sdk/pull/1435) [`49c0cc9`](https://github.com/tailor-platform/sdk/commit/49c0cc99171d7e317a50a18804a21067d89f9493) Thanks [@dqn](https://github.com/dqn)! - Add the `v2/plugin-cli-import` codemod so `tailor-sdk upgrade` rewrites deprecated plugin imports from `@tailor-platform/sdk/cli` (`kyselyTypePlugin`, `enumConstantsPlugin`, `fileUtilsPlugin`, `seedPlugin`) to their dedicated `@tailor-platform/sdk/plugin/*` subpaths, splitting any non-plugin specifiers onto a separate import.
88
+
89
+ ## 0.3.0
90
+ ### Minor Changes
91
+
92
+
93
+
8
94
  - [#1435](https://github.com/tailor-platform/sdk/pull/1435) [`49c0cc9`](https://github.com/tailor-platform/sdk/commit/49c0cc99171d7e317a50a18804a21067d89f9493) Thanks [@dqn](https://github.com/dqn)! - Add the `v2/plugin-cli-import` codemod so `tailor-sdk upgrade` rewrites deprecated plugin imports from `@tailor-platform/sdk/cli` (`kyselyTypePlugin`, `enumConstantsPlugin`, `fileUtilsPlugin`, `seedPlugin`) to their dedicated `@tailor-platform/sdk/plugin/*` subpaths, splitting any non-plugin specifiers onto a separate import.
9
95
 
10
96
  ## 0.2.7
@@ -1,8 +1,20 @@
1
1
  import * as path from "pathe";
2
2
  //#region codemods/v2/apply-to-deploy/scripts/transform.ts
3
- const APPLY_PATTERN = /\btailor-sdk(@[^\s'"`]+)?(\s+)apply(?![-\w])/g;
3
+ const ARG_VALUE = `(?:[^\\s'"\`;&|]+|'[^']*'|"(?:(?:\\\\.)|[^"\\\\])*")`;
4
+ const GLOBAL_ARG_PATTERN = `(?:(?:\\s+(?:--verbose|--json|-j))|(?:\\s+(?:--env-file|--env-file-if-exists|-e)(?:=${ARG_VALUE}|\\s+${ARG_VALUE})))*`;
5
+ const APPLY_PATTERN = new RegExp(`(?<![\\w-])tailor-sdk(?:@[^\\s'"\`]+)?(?![\\w-])(${GLOBAL_ARG_PATTERN}\\s+)apply(?![-\\w])`, "g");
6
+ const SOURCE_EXTENSIONS = new Set([
7
+ ".ts",
8
+ ".tsx",
9
+ ".mts",
10
+ ".cts",
11
+ ".js",
12
+ ".jsx",
13
+ ".mjs",
14
+ ".cjs"
15
+ ]);
4
16
  function replaceApply(value) {
5
- return value.replace(APPLY_PATTERN, (_match, ver, sep) => `tailor-sdk${ver ?? ""}${sep}deploy`);
17
+ return value.replace(APPLY_PATTERN, (match) => `${match.slice(0, -5)}deploy`);
6
18
  }
7
19
  function transformText(source) {
8
20
  if (!APPLY_PATTERN.test(source)) return null;
@@ -10,6 +22,10 @@ function transformText(source) {
10
22
  const updated = replaceApply(source);
11
23
  return updated === source ? null : updated;
12
24
  }
25
+ function transformSourceText(source) {
26
+ const updated = source.split(/(\r\n|\n|\r)/).map((part, index) => index % 2 === 0 ? replaceApply(part) : part).join("");
27
+ return updated === source ? null : updated;
28
+ }
13
29
  function transformPackageJson(source) {
14
30
  let parsed;
15
31
  try {
@@ -35,14 +51,16 @@ function transformPackageJson(source) {
35
51
  /**
36
52
  * Replace `tailor-sdk apply` invocations with `tailor-sdk deploy`.
37
53
  *
38
- * `deploy` is a v1 alias of `apply` and the recommended name going forward.
54
+ * `deploy` is the canonical v2 command name.
39
55
  * @param source - File contents
40
56
  * @param filePath - Absolute path to the file (used to dispatch package.json vs text)
41
57
  * @returns Transformed source or null when nothing matched.
42
58
  */
43
59
  function transform(source, filePath) {
44
60
  if (!source.includes("tailor-sdk")) return null;
45
- if (path.extname(filePath).toLowerCase() === ".json") return transformPackageJson(source);
61
+ const ext = path.extname(filePath).toLowerCase();
62
+ if (ext === ".json") return transformPackageJson(source);
63
+ if (SOURCE_EXTENSIONS.has(ext)) return transformSourceText(source);
46
64
  return transformText(source);
47
65
  }
48
66
  //#endregion
@@ -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 QUICK_FILTER_NEEDLE = "auth.invoker";
3
+ const QUICK_FILTER_NEEDLES = ["auth.invoker", "authInvoker"];
4
4
  function quickFilter(source) {
5
- return source.includes(QUICK_FILTER_NEEDLE);
5
+ return QUICK_FILTER_NEEDLES.some((needle) => source.includes(needle));
6
6
  }
7
7
  function isInsideImportStatement(node) {
8
8
  let current = node.parent();
@@ -121,14 +121,101 @@ 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 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 deprecated in favor of passing the machine user name
130
- * directly; carrying the `auth` import only for `.invoker()` would otherwise
131
- * pull config-layer (Node-only) modules into runtime bundles.
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)
134
221
  * @returns Transformed source or null when nothing matched.
@@ -136,15 +223,16 @@ function findAuthImports(root) {
136
223
  function transform(source, _filePath) {
137
224
  if (!quickFilter(source)) return null;
138
225
  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;
226
+ const calls = findInvokerCalls(root).filter((c) => isSupportedInvokerValueCall(c.callNode));
141
227
  const edits = calls.map((c) => c.callNode.replace(c.argText));
142
- if (countRemainingAuthRefs(root, calls.map((c) => c.range)) === 0) for (const importStmt of findAuthImports(root)) {
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")));
231
+ if (calls.length > 0 && countRemainingAuthRefs(root, calls.map((c) => c.range)) === 0) for (const importStmt of findAuthImports(root)) {
143
232
  const edit = buildAuthImportRemovalEdit(source, importStmt);
144
233
  if (edit) edits.push(edit);
145
234
  }
146
- if (edits.length === 0) return null;
147
- let result = root.commitEdits(edits);
235
+ let result = edits.length === 0 ? source : root.commitEdits(edits);
148
236
  result = result.replace(/^[\t ]*\n+/, "").replace(/\n{3,}/g, "\n\n");
149
237
  return result === source ? null : result;
150
238
  }