Skip to content

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

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

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

Conversation

@dqn
Copy link
Copy Markdown
Contributor

@dqn dqn commented Apr 29, 2026

First batch of v2 upgrade codemods. Refs tailor-inc/platform-planning#583. 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
  • db.type / t.object retirement (waiting on new createTable API spec)
  • applydeploy rename (waiting on naming decision)
  • CLI single-word command / --separated option rename (waiting on rename map)
  • attributes / attributeList map / uuid list normalization (waiting on platform spec)
  • auth utility node dependency cleanup (waiting on relocation decision, refs chore(deps): update dependency bufbuild/buf to v1.68.4 #1049)

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.

Main Changes

  • Add three 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: 8833ca4

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@8833ca4
pnpm dlx https://pkg.pr.new/@tailor-platform/sdk@8833ca4 --help

@tailor-platform/create-sdk

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

commit: 8833ca4

@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
Copy link
Copy Markdown

Code Metrics Report (packages/sdk)

main (e88296c) #1104 (44c2b83) +/-
Coverage 60.6% 60.6% 0.0%
Code to Test Ratio 1:0.4 1:0.4 0.0
Details
  |                    | main (e88296c) | #1104 (44c2b83) | +/-  |
  |--------------------|----------------|-----------------|------|
  | Coverage           |          60.6% |           60.6% | 0.0% |
  |   Files            |            356 |             356 |    0 |
  |   Lines            |          12104 |           12104 |    0 |
  |   Covered          |           7339 |            7339 |    0 |
  | Code to Test Ratio |          1:0.4 |           1:0.4 |  0.0 |
  |   Code             |          79598 |           79598 |    0 |
  |   Test             |          32624 |           32624 |    0 |

SDK Configure Bundle Size

main (e88296c) #1104 (44c2b83) +/-
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 (e88296c) #1104 (44c2b83) +/-
Generate Median 2,403ms 2,609ms 206ms
Generate Max 2,447ms 2,707ms 260ms
Apply Build Median 2,466ms 2,663ms 197ms
Apply Build Max 2,485ms 2,748ms 263ms

Type Performance (instantiations)

main (e88296c) #1104 (44c2b83) +/-
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