Skip to content

feat(sdk-codemod): add v2 codemods#1104

Draft
dqn wants to merge 16 commits intomainfrom
feat/v2-codemods
Draft

feat(sdk-codemod): add v2 codemods#1104
dqn wants to merge 16 commits intomainfrom
feat/v2-codemods

Conversation

@dqn
Copy link
Copy Markdown
Contributor

@dqn dqn commented Apr 29, 2026

First batch of v2 upgrade codemods. More codemods will be appended to this branch as specs land.

Codemod Checklist

  • v2/define-generators-to-pluginsdefineGeneratorsdefinePlugins (already shipped)
  • v2/test-run-arg-input — strip { "input": ... } wrapper from function test-run --arg JSON
  • v2/sdk-skills-shimtailor-sdk-skillstailor-sdk skills install
  • v2/principal-unify — unify TailorUser / TailorActor / TailorInvoker into TailorPrincipal, drop unauthenticatedTailorUser, rename resolver usercaller
  • v2/apply-to-deploytailor-sdk applytailor-sdk deploy
  • v2/cli-rename — single-word command name (crash-reportcrashreport)
  • v2/auth-invoker-unwrapauth.invoker("name")"name" (and drop the auth import when unused)
  • db.type / t.object retirement (waiting on new createTable API spec)
  • attributes / attributeList map / uuid list normalization (waiting on platform spec)

Out of codemod scope:

  • updatedAt default change (Version Packages #607). Internal behavior change, no user code transform needed.
  • keyring config format change. Handled by a local user config migration script.

Examples

v2/test-run-arg-input

- tailor-sdk function test-run resolvers/add.ts --arg '{"input":{"a":1,"b":2}}'
+ tailor-sdk function test-run resolvers/add.ts --arg '{"a":1,"b":2}'

v2/sdk-skills-shim

- "postinstall": "tailor-sdk-skills"
+ "postinstall": "tailor-sdk skills install"

v2/principal-unify

- import { createResolver, type TailorUser } from "@tailor-platform/sdk";
+ import { createResolver, type TailorPrincipal } from "@tailor-platform/sdk";

  export default createResolver({
    ...
-   body: ({ input, user }) => ({ id: user.id }),
+   body: ({ input, caller }) => ({ id: caller.id }),
  });

principal-unify is scope-aware: when a resolver body locally redeclares user, references inside the redeclaring scope are left intact and only the param-bound references are renamed to caller. When the unauthenticatedTailorUser import is the only remaining specifier, the empty import is removed along with its trailing newline and any leading blank lines that result.

v2/apply-to-deploy

- "deploy": "tailor-sdk apply --profile prod"
+ "deploy": "tailor-sdk deploy --profile prod"

apply-to-deploy preserves optional @version pins (tailor-sdk@latest, tailor-sdk@1.45.2) since apply and deploy are the same subcommand on the same binary. Hypothetical sibling subcommands (apply-foo, applyConfig) and bare prose mentions of "apply" are left intact.

v2/cli-rename

- "report:tail": "tailor-sdk crash-report list"
+ "report:tail": "tailor-sdk crashreport list"

cli-rename only rewrites the crash-report command token when preceded by tailor-sdk (with an optional @version pin). Word continuation (crash-reporter) and bare prose mentions of "crash-report" are left intact. The matching SDK-side command rename ships as #1145.

The originally-scoped camelCase long-option transform (--executionId--execution-id, etc.) was dropped because the targeted identifiers turned out to be positional arguments in the SDK CLI, not long flags, so user scripts never carry them. The positional argument key rename is a SDK-side display-only change handled by #1145.

v2/auth-invoker-unwrap

- import { auth } from "../tailor.config";
  import orderProcessingWorkflow from "../workflows/order-processing";

  export default createResolver({
    body: async ({ input }) => {
      const workflowRunId = await orderProcessingWorkflow.trigger(
        { orderId: input.orderId, customerId: input.customerId },
-       { authInvoker: auth.invoker("manager-machine-user") },
+       { authInvoker: "manager-machine-user" },
      );
    },
  });

auth-invoker-unwrap only rewrites calls whose argument is a literal string. Calls like auth.invoker(machineUserName) (variable) or auth.invoker(\my-${suffix}`)(template) are left untouched because the v2MachineUserNameRegistrytype narrowing only works for literals; the author can decide what to do with the dynamic case. After everyauth.invoker(...)call has been unwrapped, theauthimport is dropped if no other reference toauthremains in the file. Whenauth` was the only specifier in the import statement the entire import line is removed and any leading blank line is collapsed.

Main Changes

  • Add six codemods under packages/sdk-codemod/codemods/v2/, each with codemod.yaml, scripts/transform.ts, and a tests/ fixture tree.
  • Generalize transform.test.ts to auto-discover tests/<case>/ directories with arbitrary file extensions, treating cases without expected.* as no-change expectations.
  • Wire the new transforms into src/registry.ts and tsdown.config.ts.
  • Ignore the codemod fixture trees from oxlint and oxfmt to keep representative user-code samples intact.

… and TailorPrincipal unification

Add three codemods that the upgrade runner applies when migrating from
1.x to 2.x:

- `v2/test-run-arg-input` strips the deprecated `{ "input": ... }`
  wrapper from `tailor-sdk function test-run --arg` JSON in
  package.json scripts, shell scripts, and Markdown code blocks.
- `v2/sdk-skills-shim` rewrites `tailor-sdk-skills` invocations to
  `tailor-sdk skills install` across package.json, shell, YAML, and
  Markdown files.
- `v2/principal-unify` renames TailorUser, TailorActor, and
  TailorInvoker to the unified TailorPrincipal, drops
  `unauthenticatedTailorUser` (replacing value references with
  `null`), and renames `user` to `caller` inside `createResolver`
  body parameters and member accesses.

Generalize the fixture harness so each codemod can declare multiple
`tests/<case>/` directories with arbitrary file extensions, and treat
cases with no `expected.*` as no-change expectations. Wire the new
transforms into the registry and tsdown entries, and ignore the
fixture trees from oxlint and oxfmt to keep representative user code
samples intact.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 29, 2026

🦋 Changeset detected

Latest commit: 3b605d6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@tailor-platform/sdk-codemod Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 29, 2026

⚡ pkg.pr.new

@tailor-platform/sdk

pnpm add https://pkg.pr.new/@tailor-platform/sdk@3b605d6
pnpm dlx https://pkg.pr.new/@tailor-platform/sdk@3b605d6 --help

@tailor-platform/create-sdk

pnpm add https://pkg.pr.new/@tailor-platform/create-sdk@3b605d6
pnpm dlx https://pkg.pr.new/@tailor-platform/create-sdk@3b605d6 my-app

commit: 3b605d6

@github-actions

This comment has been minimized.

… cleanup in v2/principal-unify

Resolve the two caveats noted on the codemod:

- When `unauthenticatedTailorUser` import removal empties an SDK
  import statement, post-process the result to drop leading blank
  lines and collapse runs of three or more newlines down to one
  empty line.
- For `createResolver` bodies that locally redeclare `user`,
  collect the byte ranges of the enclosing scopes and skip
  renaming `user` references within them. Param-level destructure
  and references outside the shadow zone are still rewritten to
  `caller`.

Add a `shadow` fixture covering an inner-block redeclaration and
trim the leading blank lines from the `unauthenticated` expected
fixture.
@github-actions

This comment has been minimized.

- Skip the null rewrite when unauthenticatedTailorUser is the object of a
  member expression so the codemod no longer emits null.id; the import is
  still dropped so the resulting type error points authors at the broken
  access.
- Match @tailor-platform/sdk/test imports as well so the documented test
  helper unauthenticatedTailorUser is removed there too.
- Restrict resolver-body destructuring rewrites to the top-level object
  pattern. Nested patterns like ({ input: { user } }) are left alone, and
  aliased pairs like ({ user: currentUser }) now rewrite the property key
  to caller while preserving the local binding.
- Dedupe TailorPrincipal across multiple SDK import statements so a file
  that splits TailorUser and TailorActor across imports collapses to one
  TailorPrincipal binding.
- Skip ctx.user rewrites that sit inside nested functions which re-bind
  the resolver context name (items.map((ctx) => ctx.user.id)).

Add fixtures for each case and update the unauthenticated expected
output, which previously asserted the broken null.id behavior.
@github-actions

This comment has been minimized.

dqn added 3 commits April 29, 2026 20:54
…cture, chained shell commands

principal-unify:
- Track local names introduced by aliased imports (e.g. unauthenticatedTailorUser
  as testUser) and rewrite their references to null too.
- Include shorthand_property_identifier (object literal shorthand) in body
  rewrites so ({ user }) => ({ user }) becomes ({ caller }) => ({ caller })
  instead of leaving the return value referencing an undefined binding.
- Rewrite destructures that read from the resolver context, so
  const { user } = ctx becomes const { caller: user } = ctx, preserving the
  local binding name.

test-run-arg-input:
- Tokenize lines on unquoted shell command boundaries (;, &&, ||, |, &) and
  only run the {input: ...} unwrap on segments that actually contain
  tailor-sdk function test-run, so chained commands like
  `tailor-sdk function test-run ... --arg '{"input":...}' && other-cli --arg '{"input":...}'`
  no longer corrupt the unrelated argument.

Add fixtures for each new case (aliased-unauthenticated, object-shorthand-return,
ctx-destructure, chained-commands).
…s, handle version pins and continuations

principal-unify:
- Object literal shorthand (`{ user }` in a return value) is now expanded to
  `{ user: caller }` instead of being collapsed to `{ caller }`, so the
  resolver's emitted output schema stays the same after the binding rename.
- Type renames are restricted to TailorUser/TailorActor/TailorInvoker that are
  imported from `@tailor-platform/sdk` (or `/test`) without an alias. Local
  types like `import { TailorUser } from "./domain"` are left alone, and
  aliased SDK imports keep working through the alias.
- The unauthenticated-import rewrite is now driven by names actually imported
  from the SDK and skips identifier references that fall inside scopes which
  rebind the same name (variable declarations or function parameters), so
  shadowing arrow params do not get replaced with `null` and produce
  `(null) => null`.

sdk-skills-shim:
- The shim regex now consumes the optional `@version` suffix used by
  `npx tailor-sdk-skills@latest` / `pnpm dlx tailor-sdk-skills@1.2.3` and an
  optional ` install` subcommand, so the rewrite no longer leaves `@latest`
  attached to `tailor-sdk skills install`.

test-run-arg-input:
- Backslash-newline line continuations are folded into a single logical line
  via a sentinel before transformation, so a multi-line invocation where
  `tailor-sdk function test-run` and `--arg '{"input":...}'` sit on separate
  physical lines still has the wrapper unwrapped.

Add fixtures (local-tailor-types, unauthenticated-shadowed, version-qualified,
multiline-shell) and update object-shorthand-return to assert the new output
shape.
…nd defaulted user destructures

principal-unify:
- Detect when the resolver pattern or body already binds `caller` (as another
  destructure entry, a let/const declaration, or a function parameter) and
  skip the rewrite for that resolver. The previous code would emit duplicate
  bindings or silently re-target an unrelated value.
- Resolve the local name(s) of `createResolver` from the SDK import block, so
  aliased forms like `import { createResolver as makeResolver } ...` are now
  migrated, and unrelated local helpers named `createResolver` (when the SDK
  import does not actually bring it in) are left alone. Calls inside scopes
  that shadow the binding are also skipped.
- Handle the `{ user = fallback }` defaulted destructure
  (`object_assignment_pattern`) by renaming the inner shorthand identifier and
  preserving the default expression.

Add fixtures (caller-collision, aliased-create-resolver, defaulted-destructure)
covering each branch.
@github-actions

This comment has been minimized.

…ler key conflict, arg continuations

principal-unify:
- Body-identifier rewrites in the destructured-resolver path now go through
  collectAllShadowRanges, so a nested arrow that re-binds `user` as its own
  parameter (e.g. `items.map((user) => user.id)`) keeps its inner reference
  pointing at the inner binding instead of being incorrectly renamed.
- collectCtxShadowRanges also treats `var ctx = ...` / `let ctx = ...` style
  re-bindings of the resolver context name as shadows, so subsequent
  destructures of the rebound variable are not mistaken for the resolver
  context.
- hasCallerBindingConflict additionally checks the property `key` of pair
  patterns, so `({ user, caller: x })` is detected as conflicting with the
  shorthand rename instead of producing a duplicate-key destructure.

test-run-arg-input:
- The shell-line argument regex accepts the JOIN_MARKER as part of the
  separator group, so a backslash-newline continuation between `--arg` and
  the quoted JSON still matches and the wrapper is unwrapped.
- The marker itself is now plain ASCII (`SDK_CODEMOD_JOIN`) so the regex can
  reference it as a literal alternative without escaping or stray bytes.

Add fixtures (param-shadow-user, ctx-rebound, caller-key-conflict,
multiline-arg-continuation) covering each branch.
@github-actions

This comment has been minimized.

dqn added 4 commits April 29, 2026 21:58
… and pattern-aware

principal-unify:
- collectAllShadowRanges previously used `inside: { kind: "variable_declarator" }`,
  which also matched value-side identifier references such as `user` in
  `const userId = user.id`. Walking up to the enclosing scope from such a
  match marked the entire body as shadowed, so every body rename was
  silently suppressed and the codemod produced `({ caller }) => { const x =
  user.id; ... }` instead. The scan now iterates `variable_declarator` nodes
  directly and only consults `field("name")`.
- Added a shared `patternBindsName` helper that recurses through
  `object_pattern`, `array_pattern`, `object_assignment_pattern`, `pair_pattern`,
  `assignment_pattern`, and `rest_pattern` so destructured parameters like
  `({ user }) => ...` and `(({ user, items }) => items.map(({ user }) => user))`
  are recognised as re-bindings of `user`. functionRebindsName uses the same
  helper for both single-arrow params and `formal_parameters` children.

test-run-arg-input:
- Updated the JOIN_MARKER comment to describe the actual ASCII strategy
  instead of the inaccurate "Unicode private-use area" claim left over from
  an earlier iteration.

Add fixtures (body-with-derived-const, nested-destructure-shadow) covering
the previously-broken behaviour.
test-run-arg-input:
- Build SHELL_ARG_PATTERN via new RegExp with the JOIN_MARKER constant
  interpolated, instead of duplicating the literal token inside the regex
  source. The marker is now defined in one place.

principal-unify:
- Extract iterateImportSpecs generator that yields each specifier's
  imported name, alias node, and effective local name. The three import
  walks (rebuildImportStatement, sdkRenameSourceNames collection,
  createResolverLocalNames collection) consume it so the spec parsing
  logic lives in one place.
…re scope-walk helper

principal-unify:
- quickFilter now derives its needle list from Object.keys(TYPE_RENAME_MAP)
  plus the unauthenticated/createResolver tokens, so adding another renamed
  type only needs the map entry.
- collectAllShadowRanges and collectCtxShadowRanges shared the same
  declarator-to-enclosing-scope walk; extracted enclosingScopeRange and
  consume it from both call sites.
…liased type imports

test-run-arg-input:
- equals-form fixture exercises the SHELL_ARG_PATTERN \s*=\s* separator
  branch (tailor-sdk function test-run --arg='{"input":...}'), which the
  prior fixtures only hit through the whitespace separator.
- double-quoted-shell fixture exercises shell-context double-quoted JSON
  with backslash-escaped quotes, which was previously only exercised via
  package.json scripts.

principal-unify:
- aliased-type-import fixture exercises the aliased TailorUser/TailorActor/
  TailorInvoker import path, asserting the alias is preserved
  (`TailorPrincipal as MyUser`) and the body's MyUser type references stay
  untouched.

Also exclude `packages/sdk-codemod/codemods/**/tests/**` from the lefthook
format hook to match the existing `.oxfmtrc.json` and
`packages/sdk-codemod/.oxlintrc.json` ignore patterns. Without this entry,
committing only fixture files makes oxfmt receive an empty staged-files
list and exit with "Expected at least one target file."
@github-actions

This comment has been minimized.

dqn added 2 commits May 9, 2026 17:34
Rewrite `tailor-sdk apply` invocations in package.json scripts,
shell scripts, CI YAML, and Markdown to the v2-recommended
`tailor-sdk deploy` alias. Optional `@version` pins are preserved.
@github-actions

This comment has been minimized.

Apply v2 CLI naming conventions to user code: collapse the
`crash-report` multi-word command to `crashreport` and rewrite
camelCase long options (`--executionId`, `--executorName`,
`--jobId`) to their kebab-case form across package.json scripts,
shell, CI YAML, and Markdown. Optional `@version` pins on the
binary are preserved.
@github-actions

This comment has been minimized.

Drop the camelCase long option transform (`--executionId` → `--execution-id`,
etc.). The targeted identifiers are positional arguments in the SDK CLI, not
long flags, so user scripts don't carry them and the transform had no
real-world target. The transform now only rewrites
`tailor-sdk crash-report` → `tailor-sdk crashreport`. Update fixtures,
registry description, and changeset bullet to match. Add a no-match case
asserting the camelCase positional tokens are intentionally left alone.
@github-actions

This comment has been minimized.

Replace `auth.invoker("name")` calls with the bare `"name"` string
literal and drop the `auth` import when no other reference remains.
Calls whose argument is not a literal string (`auth.invoker(variable)`,
template literals) are intentionally left untouched. Mirrors the
deprecation in #971: importing `auth` from
`tailor.config.ts` into runtime files pulls Node-only modules into
the bundle.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

Code Metrics Report (packages/sdk)

main (f3674c0) #1104 (beaed67) +/-
Coverage 60.9% 60.9% 0.0%
Code to Test Ratio 1:0.4 1:0.4 0.0
Details
  |                    | main (f3674c0) | #1104 (beaed67) | +/-  |
  |--------------------|----------------|-----------------|------|
  | Coverage           |          60.9% |           60.9% | 0.0% |
  |   Files            |            358 |             358 |    0 |
  |   Lines            |          12217 |           12217 |    0 |
  |   Covered          |           7448 |            7448 |    0 |
  | Code to Test Ratio |          1:0.4 |           1:0.4 |  0.0 |
  |   Code             |          80084 |           80084 |    0 |
  |   Test             |          32929 |           32929 |    0 |

SDK Configure Bundle Size

main (f3674c0) #1104 (beaed67) +/-
configure-index-size 17.78KB 17.78KB 0KB
dependency-chunks-size 33.56KB 33.56KB 0KB
total-bundle-size 51.34KB 51.34KB 0KB

Runtime Performance

main (f3674c0) #1104 (beaed67) +/-
Generate Median 2,551ms 2,570ms 19ms
Generate Max 2,733ms 2,605ms -128ms
Apply Build Median 2,605ms 2,616ms 11ms
Apply Build Max 2,628ms 2,665ms 37ms

Type Performance (instantiations)

main (f3674c0) #1104 (beaed67) +/-
tailordb-basic 35,130 35,130 0
tailordb-optional 3,841 3,841 0
tailordb-relation 7,428 7,428 0
tailordb-validate 2,566 2,566 0
tailordb-hooks 5,767 5,767 0
tailordb-object 12,136 12,136 0
tailordb-enum 2,462 2,462 0
resolver-basic 9,424 9,424 0
resolver-nested 26,111 26,111 0
resolver-array 18,187 18,187 0
executor-schedule 4,234 4,234 0
executor-webhook 873 873 0
executor-record 8,166 8,166 0
executor-resolver 4,369 4,369 0
executor-operation-function 869 869 0
executor-operation-gql 869 869 0
executor-operation-webhook 888 888 0
executor-operation-workflow 1,714 1,714 0

Reported by octocov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant