@tailor-platform/sdk 2.37.0 → 2.39.0

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.
Files changed (113) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/dist/{application-83WVdH85.mjs → application-OeNMQzOC.mjs} +1 -1
  3. package/dist/application-mNOJx9Y2.mjs +207 -0
  4. package/dist/application-mNOJx9Y2.mjs.map +1 -0
  5. package/dist/cli/commands/deploy/deploy.d.mts +1 -0
  6. package/dist/cli/commands/deploy/tailordb/migration-validation.d.mts +2 -0
  7. package/dist/cli/commands/executor/trigger.d.mts +1 -0
  8. package/dist/cli/commands/generate/seed/bundler.d.mts +7 -5
  9. package/dist/cli/commands/workspace/transform.d.mts +1 -2
  10. package/dist/cli/lib.d.mts +5 -3
  11. package/dist/cli/lib.mjs +1 -1
  12. package/dist/cli/lib.mjs.map +1 -1
  13. package/dist/cli/main.mjs +67 -61
  14. package/dist/cli/main.mjs.map +1 -1
  15. package/dist/cli/shared/args.d.mts +14 -5
  16. package/dist/cli/shared/build-options.d.mts +13 -0
  17. package/dist/cli/shared/client.d.mts +15 -3
  18. package/dist/cli/shared/error-json.d.mts +12 -26
  19. package/dist/cli/shared/script-workflow.d.mts +65 -0
  20. package/dist/cli/shared/seed-chunker.d.mts +3 -3
  21. package/dist/client-CqiPQgMG.mjs +2 -0
  22. package/dist/client-CqiPQgMG.mjs.map +1 -0
  23. package/dist/client-DO2_Xz_z.mjs +1 -0
  24. package/dist/completion/zsh-worker.zsh +193 -74
  25. package/dist/configure/index.mjs +1 -1
  26. package/dist/configure/index.mjs.map +1 -1
  27. package/dist/configure/services/auth/types.d.mts +20 -3
  28. package/dist/configure/services/executor/operation.d.mts +5 -4
  29. package/dist/configure/services/executor/trigger/webhook.d.mts +8 -1
  30. package/dist/configure/services/http-adapter/http-adapter.d.mts +1 -1
  31. package/dist/configure/services/index.d.mts +0 -1
  32. package/dist/configure/services/tailordb/types.d.mts +9 -0
  33. package/dist/context-CYBcQZyO.mjs +1 -0
  34. package/dist/context-aAgFGDBD.mjs +23 -0
  35. package/dist/context-aAgFGDBD.mjs.map +1 -0
  36. package/dist/{crashreport-D_wf4GZh.mjs → crashreport-CQTAlnbA.mjs} +2 -2
  37. package/dist/{crashreport-D_wf4GZh.mjs.map → crashreport-CQTAlnbA.mjs.map} +1 -1
  38. package/dist/crashreport-GRnWyDnF.mjs +1 -0
  39. package/dist/{errors-Dmdnq0zi.mjs → errors-9OOL3xOM.mjs} +6 -5
  40. package/dist/errors-9OOL3xOM.mjs.map +1 -0
  41. package/dist/field_behavior_pb-B196sUx6.mjs +2 -0
  42. package/dist/field_behavior_pb-B196sUx6.mjs.map +1 -0
  43. package/dist/{kysely-type-DVL90_oX.mjs → kysely-type-DhAZqdQg.mjs} +14 -10
  44. package/dist/kysely-type-DhAZqdQg.mjs.map +1 -0
  45. package/dist/notification_resource_pb-BZ0hCA01.mjs +1 -0
  46. package/dist/notification_resource_pb-Dwzah7gO.mjs +2 -0
  47. package/dist/notification_resource_pb-Dwzah7gO.mjs.map +1 -0
  48. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  49. package/dist/register-ts-hook-CORbtrSN.mjs +1105 -0
  50. package/dist/register-ts-hook-CORbtrSN.mjs.map +1 -0
  51. package/dist/repl-editor-Bposemei.mjs +2 -0
  52. package/dist/repl-editor-Bposemei.mjs.map +1 -0
  53. package/dist/resource_pb-elY668ZB.mjs +2 -0
  54. package/dist/resource_pb-elY668ZB.mjs.map +1 -0
  55. package/dist/{secret-file-DiuEcLFP.mjs → secret-file-CSXzcg4c.mjs} +2 -2
  56. package/dist/{secret-file-DiuEcLFP.mjs.map → secret-file-CSXzcg4c.mjs.map} +1 -1
  57. package/dist/service-B2vx6CGM.mjs +2 -0
  58. package/dist/{service-DDrbdyd-.mjs.map → service-B2vx6CGM.mjs.map} +1 -1
  59. package/dist/service-Bkz_TB5v.mjs +9 -0
  60. package/dist/service-Bkz_TB5v.mjs.map +1 -0
  61. package/dist/service-Dffd4how.mjs +1 -0
  62. package/dist/service_pb-Bethc2NW.mjs +1 -0
  63. package/dist/service_pb-CfpSriCy.mjs +2 -0
  64. package/dist/service_pb-CfpSriCy.mjs.map +1 -0
  65. package/dist/syntax-diagnostic-BQgOV8Fz.mjs +2 -0
  66. package/dist/syntax-diagnostic-BQgOV8Fz.mjs.map +1 -0
  67. package/dist/validation-errors-ChvCG6wT.mjs +3 -0
  68. package/dist/validation-errors-ChvCG6wT.mjs.map +1 -0
  69. package/{LICENSE → dist/vendor/graphql/LICENSE} +6 -6
  70. package/dist/vendor/graphql/language/KindTypeMap.d.mts +3 -0
  71. package/dist/vendor/graphql/language/ast.d.mts +784 -0
  72. package/dist/vendor/graphql/language/kinds_.d.mts +203 -0
  73. package/dist/vendor/graphql/language/source.d.mts +46 -0
  74. package/dist/vendor/graphql/language/tokenKind.d.mts +35 -0
  75. package/dist/vitest/index.mjs +1 -1
  76. package/dist/vitest/index.mjs.map +1 -1
  77. package/dist/workspace_resource_pb-Cow7mrJY.mjs +2 -0
  78. package/dist/workspace_resource_pb-Cow7mrJY.mjs.map +1 -0
  79. package/docs/cli/application.md +16 -12
  80. package/docs/cli/executor.md +13 -17
  81. package/docs/cli/function.md +1 -1
  82. package/docs/cli/notification.md +128 -0
  83. package/docs/cli/tailordb.md +26 -57
  84. package/docs/cli/workspace.md +3 -4
  85. package/docs/cli-reference.md +228 -48
  86. package/docs/configuration.md +28 -0
  87. package/docs/github-actions.md +241 -38
  88. package/docs/migration/v2.md +5 -3
  89. package/docs/services/auth.md +5 -0
  90. package/docs/services/executor.md +2 -0
  91. package/docs/services/tailordb/index.md +6 -2
  92. package/docs/services/tailordb/migration.md +54 -57
  93. package/docs/testing.md +0 -1
  94. package/package.json +25 -21
  95. package/dist/application-Cida-j1g.mjs +0 -207
  96. package/dist/application-Cida-j1g.mjs.map +0 -1
  97. package/dist/configure/services/http-adapter/index.d.mts +0 -2
  98. package/dist/crashreport-C2AcQNTe.mjs +0 -1
  99. package/dist/errors-Dmdnq0zi.mjs.map +0 -1
  100. package/dist/kysely-type-DVL90_oX.mjs.map +0 -1
  101. package/dist/register-ts-hook-Ht4zCseV.mjs +0 -1115
  102. package/dist/register-ts-hook-Ht4zCseV.mjs.map +0 -1
  103. package/dist/repl-editor-DkrCZmmg.mjs +0 -2
  104. package/dist/repl-editor-DkrCZmmg.mjs.map +0 -1
  105. package/dist/service-CZ0EdD2-.mjs +0 -10
  106. package/dist/service-CZ0EdD2-.mjs.map +0 -1
  107. package/dist/service-DDrbdyd-.mjs +0 -2
  108. package/dist/service-WhSyhaho.mjs +0 -1
  109. package/dist/service_pb-BMHhVot2.mjs +0 -1
  110. package/dist/service_pb-BxMSK0gg.mjs +0 -2
  111. package/dist/service_pb-BxMSK0gg.mjs.map +0 -1
  112. package/dist/workspace_resource_pb-7smKERdZ.mjs +0 -2
  113. package/dist/workspace_resource_pb-7smKERdZ.mjs.map +0 -1
@@ -173,8 +173,8 @@ and file blobs are not copied.
173
173
 
174
174
  Two setups are supported:
175
175
 
176
- - **Source is the plan/deploy workspace (default).** The job uses the same GitHub
177
- Environment as the plan and deploy jobs, so it reads the same
176
+ - **Source is the deploy workspace (default).** The job uses the same GitHub
177
+ Environment as the deploy job, so it reads the same
178
178
  `TAILOR_PLATFORM_WORKSPACE_ID` variable and machine-user secrets. Nothing
179
179
  extra to configure. If that workspace holds too little data to be a useful
180
180
  test, point the job at a workspace that does with the next setup.
@@ -204,6 +204,51 @@ the source workspace holds production data.
204
204
  Fork pull requests cannot read secrets, so the job is skipped for them.
205
205
  `--migration-test` is not available together with several `--dir` values.
206
206
 
207
+ #### Running the plan job in its own environment
208
+
209
+ The plan job runs on pull requests, and a pull request from a branch of the same
210
+ repository can change the workflow it runs, so its code can read every secret
211
+ and variable of the environment the plan job enters. When plan and deploy share
212
+ one environment, that includes the deploy machine user. Pass
213
+ `--split-plan-environment` on a branch target to run the plan job in an
214
+ environment of its own:
215
+
216
+ ```bash
217
+ tailor setup ci branch --name my-app-stg --split-plan-environment
218
+ ```
219
+
220
+ The plan job then enters an environment named after the deploy environment with
221
+ `-plan` appended (`my-app-stg-plan` here). The deploy job, and the migration test job unless you
222
+ pass `--migration-test-environment`, stay in the deploy environment. Set these
223
+ on the plan environment:
224
+
225
+ - `TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` and
226
+ `TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET` of a machine user of its own. The
227
+ plan only reads, so a viewer role on the organization or folder that holds the
228
+ workspace is enough. Set them on the plan environment itself: otherwise the job
229
+ reads the ones set on the repository or organization, and
230
+ `tailor setup ci env --check` reports them as missing.
231
+ - `TAILOR_PLATFORM_WORKSPACE_ID`, the same workspace the deploy environment
232
+ deploys to.
233
+
234
+ Keep the deploy machine user off the plan environment. `tailor setup ci env`
235
+ lists what each of the two environments needs.
236
+
237
+ The flag alone does not keep the deploy credentials from a pull request, which
238
+ can point the plan job back at the deploy environment. Under **Settings →
239
+ Environments**, limit the deploy environment's **Deployment branches and tags**
240
+ to the target branch, so that GitHub refuses a pull request job in it. With that
241
+ limit in place:
242
+
243
+ - The migration test job also runs on pull requests, so give it its own
244
+ environment with `--migration-test-environment` (see
245
+ [Choosing the source workspace](#choosing-the-source-workspace)).
246
+ - The jobs of a preview target run on pull requests too, so pass
247
+ `setup ci preview` an `--environment` other than the limited one.
248
+
249
+ `setup update` keeps the flag, but re-running `setup ci branch` does not carry it
250
+ over: pass it each time, or the plan job moves back into the deploy environment.
251
+
207
252
  ### Tag target (recommended for production)
208
253
 
209
254
  The tag target fires when a tag matching `--tag-pattern` (default `v*`) is
@@ -259,14 +304,53 @@ that workspace when the pull request is closed:
259
304
  tailor setup ci preview --name my-app --region asia-northeast
260
305
  ```
261
306
 
262
- Fork pull requests are skipped. Every other pull request gets a preview, draft
263
- pull requests included. `--require` narrows that to one of:
307
+ It writes two workflows. `tailor-my-app-preview.yml` deploys the pull request
308
+ when it is opened or pushed to, and `tailor-my-app-preview-cleanup.yml` deletes
309
+ the workspace when the pull request is closed. The cleanup workflow starts on
310
+ `pull_request_target`, which GitHub also triggers for a pull request with merge
311
+ conflicts, where workflows on `pull_request` do not start. A
312
+ `pull_request_target` workflow can read the repository's secrets whoever opened
313
+ the pull request, so GitHub runs it from the default branch: the workflow file
314
+ and the code it checks out are the default branch's, never the pull request's.
315
+ Therefore:
316
+
317
+ - The cleanup workflow runs only once it is on the default branch, and it
318
+ installs dependencies and runs `tailor` from the default branch's checkout.
319
+ If the preview target deploys pull requests into another branch
320
+ (`--branch`), commit the cleanup workflow, the lockfile, and the app's
321
+ `tailor.config.ts` to the default branch too.
322
+ - If the target's GitHub Environment restricts which branches can deploy,
323
+ allow the default branch: GitHub checks the cleanup job against it, and the
324
+ deploy job against the pull request's merge ref.
325
+
326
+ When `tailor setup update` regenerates a preview workflow generated before the
327
+ cleanup workflow existed, it moves the `tailor-preview-cleanup` job into the
328
+ cleanup workflow, together with the steps and settings you added to it, and
329
+ copies the top-level `env`, `defaults`, and `concurrency` of the preview
330
+ workflow into the cleanup workflow. Your steps then run from the default branch
331
+ as described above. A job of yours in the preview workflow that runs when the
332
+ pull request is closed no longer runs, because that workflow no longer starts
333
+ then; move it into the cleanup workflow. `setup` warns about each job of yours
334
+ in the preview workflow whose `if` contains an `== 'closed'` comparison.
335
+
336
+ #### Pull requests that get a preview
337
+
338
+ Fork pull requests are skipped, and so are events on a closed pull request,
339
+ such as a label added after merging. Every other pull request gets a preview,
340
+ draft pull requests included. `--require` narrows that to one of:
264
341
 
265
342
  - `--require ready` skips draft pull requests. The preview is deployed when the
266
343
  draft is marked ready for review.
267
344
  - `--require label` deploys only pull requests labeled `tailor:preview`. A
268
345
  labeled draft deploys too, because the label asks for the preview.
269
346
 
347
+ With app directories other than the repository root (`--dir`), a pull request
348
+ gets no preview either when none of its changed files counts. A file counts
349
+ when it is under an app directory, is the generated workflow file, or matches a
350
+ `--paths` pattern, and the last pattern that matches it does not start with `!`
351
+ (see
352
+ [Running on changes outside the app directories](#running-on-changes-outside-the-app-directories)).
353
+
270
354
  When `tailor setup update` regenerates a preview target generated before
271
355
  `--require` existed, it reads the target as `--require ready`, or as
272
356
  `--require label` if it required the label. The first keeps skipping drafts; the
@@ -288,7 +372,8 @@ tailor setup ci autofix --dir apps/erp --dir apps/users # run tailor generate i
288
372
  It writes `.github/workflows/tailor-autofix.yml`, which runs `tailor generate`
289
373
  on every pull request and, when that changes files, commits them to the pull
290
374
  request branch as `chore: run tailor generate`. Only the changes
291
- `tailor generate` makes are committed: files that earlier steps leave in the
375
+ `tailor generate` makes, and with `--codemod deterministic` those of
376
+ `tailor upgrade`, are committed: files that earlier steps leave in the
292
377
  checkout, such as an `.npmrc`, are not. The deploy workflows keep their check,
293
378
  which fails on the commit that needed the fix and passes on the fix commit.
294
379
 
@@ -313,9 +398,10 @@ right away.
313
398
  - **A branch that moved on:** if someone pushes to the branch while the
314
399
  workflow runs, it does not commit; the run for the new commit regenerates the
315
400
  files.
316
- - **Output that keeps changing:** if `tailor generate` changes files again
317
- right after the fix commit, the workflow fails instead of committing again.
318
- Check that running it twice in a row changes nothing.
401
+ - **Output that keeps changing:** if `tailor generate` (or, with
402
+ `--codemod deterministic`, `tailor upgrade`) changes files again right after
403
+ the fix commit, the workflow fails instead of committing again. Check that
404
+ running it twice in a row changes nothing.
319
405
  - **Approval rules:** the fix commit becomes the most recent push to the pull
320
406
  request, and a bot pushed it. A branch protection rule that requires the most
321
407
  recent push to be approved by someone other than its pusher is then satisfied
@@ -340,6 +426,60 @@ right away.
340
426
  To stop committing, delete the workflow with
341
427
  `tailor setup delete .github/workflows/tailor-autofix.yml`.
342
428
 
429
+ ### Applying codemods on SDK upgrades
430
+
431
+ A pull request that upgrades `@tailor-platform/sdk`, such as one from Renovate,
432
+ may need the codemods that [`tailor upgrade`](./cli/upgrade.md) runs. Pass
433
+ `--codemod` to have the autofix workflow handle them; without it, the
434
+ workflow runs only `tailor generate`:
435
+
436
+ ```bash
437
+ tailor setup ci autofix --codemod error # fail the pull request until they are applied
438
+ tailor setup ci autofix --codemod deterministic # commit what they rewrite
439
+ ```
440
+
441
+ | `--codemod` | On a pull request that upgrades the SDK | Check fails |
442
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- |
443
+ | `warn` | Runs `tailor upgrade --dry-run` and reports the files it would rewrite and the changes it leaves for review, as warnings | No |
444
+ | `error` | Reports the same as errors, and fails the `tailor-codemod-result` job | Yes |
445
+ | `deterministic` | Commits what `tailor upgrade` rewrites together with what `tailor generate` changes, and reports the rest as warnings | No |
446
+
447
+ - **Which upgrade:** for each `--dir`, the workflow upgrades from the SDK
448
+ version that the lockfile at the repository root resolves on the pull
449
+ request's base branch to the version the pull request installs. When the
450
+ base branch has no lockfile of that name, such as when the pull request
451
+ switches package managers, it upgrades from 2.37.0, the oldest version
452
+ `tailor setup ci autofix` supports. A pull request that keeps the version,
453
+ or keeps the SDK linked from a workspace (such as with `workspace:*`), runs
454
+ no codemod.
455
+ - **Reports:** the files left for you get annotations, and the job summary
456
+ lists every file, with the migration prompt of each change left for review.
457
+ Hand the prompt to an AI coding assistant, or follow it yourself.
458
+ - **Required check:** with `error`, require the `tailor-codemod-result` check
459
+ to block the merge. The workflow still commits what `tailor generate`
460
+ changes. `tailor-codemod-result` also fails when the `tailor-autofix` job
461
+ fails or is cancelled, and is skipped, which counts as passing, when that
462
+ job succeeds with nothing left.
463
+ - **Reviews keep matching:** a change left for review is found by the code it
464
+ touches, and some codemods ask for a review of the whole app, so a review
465
+ can still be reported after you finish the migration. With `error`, such a
466
+ pull request keeps failing until the base branch has the new version.
467
+ - **Workflow files:** GitHub refuses a change to `.github/workflows` from the
468
+ workflow token or from an App without the Workflows permission, so
469
+ `deterministic` leaves those files out of the fix commit and reports them.
470
+ Run `tailor upgrade --from <base version>` and commit them yourself.
471
+ - **Warnings** that `tailor upgrade` prints for code that may still need a
472
+ manual migration stay warnings with every value.
473
+ - **Lockfile:** bun needs the text `bun.lock`, not `bun.lockb`. A `yarn.lock`
474
+ does not record which app uses which version, so when the base branch has
475
+ several SDK versions and the pull request changes them, the workflow fails and
476
+ you run `tailor upgrade` yourself. Yarn 1 leaves workspace packages out of
477
+ `yarn.lock`, so the workflow also fails for an app that takes the SDK from a
478
+ workspace there.
479
+ - **Skipped pull requests:** fork and Dependabot pull requests skip the whole
480
+ workflow (except fork pull requests with `autofix-ci`), so `error` does not
481
+ check them, and a skipped check counts as passing.
482
+
343
483
  ## Targeting a workspace
344
484
 
345
485
  The generated `plan` and `deploy` jobs target a workspace by **id**, read from
@@ -349,7 +489,9 @@ environment before the first deploy.
349
489
 
350
490
  Because the variable is scoped to a GitHub Environment, both the `plan` and
351
491
  `deploy` jobs declare `environment:` so the value resolves. When you omit
352
- `--environment`, the environment name defaults to the workspace name.
492
+ `--environment`, the environment name defaults to the workspace name. With
493
+ `--split-plan-environment`, set the variable on the plan environment as well (see
494
+ [Running the plan job in its own environment](#running-the-plan-job-in-its-own-environment)).
353
495
 
354
496
  1. Provision the workspace (once per environment) and obtain its id. For now
355
497
  this is a manual step:
@@ -374,8 +516,9 @@ provisioned yet instead of running a dry-run.
374
516
 
375
517
  If the target environment has required reviewers, that approval gate applies to
376
518
  the `plan` job as well as `deploy`, because `plan` must enter the environment to
377
- read the variable. (A token-based read that lets `plan` run without entering the
378
- environment is planned.)
519
+ read the variable, unless a branch target runs `plan` in its own environment with
520
+ `--split-plan-environment`. (A token-based read that lets `plan` run without
521
+ entering the environment is planned.)
379
522
 
380
523
  ## Package manager
381
524
 
@@ -437,9 +580,9 @@ Everything else is yours, and re-running `setup` keeps it:
437
580
  (such as `playwright install`) _after_ it. A job of your own can depend on a
438
581
  managed job with `needs: tailor-deploy`.
439
582
  - **Your own top-level keys**, such as a workflow-level `env:` or `defaults:`.
440
- - **The top-level `concurrency:` of a preview workflow.** The SDK writes a
441
- default, and re-running `setup` keeps your edits to it. If you delete it,
442
- re-running `setup` adds the default back.
583
+ - **The top-level `concurrency:` of the preview and cleanup workflows.** The
584
+ SDK writes a default, and re-running `setup` keeps your edits to it. If you
585
+ delete it, re-running `setup` adds the default back.
443
586
  - **Runtime settings of managed jobs:** `runs-on`, `timeout-minutes`,
444
587
  `container`, and `env`. If every managed job that re-running `setup` keeps
445
588
  uses the same `runs-on` you set, such as self-hosted runners, a managed job
@@ -481,9 +624,8 @@ A job that runs `tailor` against the workspace needs the machine-user
481
624
  (the `--environment` value, or the workspace name when omitted) as above. A job
482
625
  that only uses `app-url`, such as end-to-end tests, does not.
483
626
 
484
- The job is skipped whenever `tailor-preview-deploy` is skipped: when a pull
485
- request is closed, for fork pull requests, for draft pull requests with
486
- `--require ready`, and for unlabeled ones with `--require label`.
627
+ The job is skipped whenever `tailor-preview-deploy` is skipped (see
628
+ [Pull requests that get a preview](#pull-requests-that-get-a-preview)).
487
629
 
488
630
  A preview workspace expires 7 days (`ttl: 7d` on `tailor-preview-deploy`) after
489
631
  the last push to its pull request. When a pull request is closed,
@@ -496,13 +638,32 @@ open; the next push creates a fresh one. Change `ttl` to keep previews longer or
496
638
  shorter. Workflows generated before this was added get these settings the next
497
639
  time you run `tailor setup update`.
498
640
 
499
- Each new preview workflow run for a pull request, such as after a push or when
500
- the pull request is closed, cancels the run still in progress for it, including
501
- your jobs, so they do not keep running against a workspace that the new run
502
- may redeploy or delete. Events on a pull request that stays closed, such as a
503
- label added after merging, do not cancel anything. A job with `if: always()`
504
- still runs when its run is cancelled, and the new run waits for it to finish;
505
- use `if: ${{ !cancelled() }}` instead so it stops with the run.
641
+ A preview workspace last deployed before `ttl` was added to its workflow records
642
+ no expiry, so this sweep never deletes it. To find such leftovers, list the
643
+ preview workspaces created more than 7 days ago:
644
+
645
+ ```bash
646
+ tailor workspace prune --name 'my-app-pr-[0-9]+' --older-than 7d \
647
+ --folder-id <folder id> --limit 0 --dry-run
648
+ ```
649
+
650
+ Pass `--organization-root <organization id>` instead of `--folder-id` when
651
+ `TAILOR_PLATFORM_FOLDER_ID` is unset. The list can include the previews of pull
652
+ requests that are still open: keep each of them with
653
+ `--exclude my-app-pr-<number>`, then run the command again without `--dry-run`.
654
+
655
+ Each new preview workflow run for a pull request, such as after a push, cancels
656
+ the run still in progress for it, including your jobs, so they do not keep
657
+ running against a workspace that the new run may redeploy. Closing the pull
658
+ request cancels it too, because the cleanup workflow runs in the same
659
+ concurrency group. If you change the group in one of the two workflows, change
660
+ it in the other, and build it only from values that are the same in both, such
661
+ as `github.event.pull_request.number`: `github.ref`, `github.workflow`, and
662
+ `github.event_name` differ between them, and `github.ref` is the default branch
663
+ for every pull request in the cleanup workflow. Events on a pull request that
664
+ stays closed, such as a label added after merging, do not cancel anything. A job with `if: always()` still
665
+ runs when its run is cancelled, and the new run waits for it to finish; use
666
+ `if: ${{ !cancelled() }}` instead so it stops with the run.
506
667
 
507
668
  Likewise, the `tailor-deploy` job of a branch or tag workflow exposes the
508
669
  deployed workspace as the outputs `workspace-id` and `app-url` to a job with
@@ -528,7 +689,9 @@ job back. To start over from a clean template, delete the file and re-run
528
689
  When a template update removes a managed job that contains steps of yours, or
529
690
  a job of yours `needs` a removed job, `setup` stops and names them. Move those
530
691
  steps into a job of your own (or update the `needs`) and re-run. `--force` drops
531
- steps left inside a removed job.
692
+ steps left inside a removed job. The `tailor-preview-cleanup` job, which moved
693
+ into its own workflow, keeps your steps instead (see
694
+ [Preview target](#preview-target-per-pull-request-workspace)).
532
695
 
533
696
  `setup check` reports a job or step of yours whose `id` starts with `tailor-`,
534
697
  and re-running `setup` stops on it, naming the id. Rename it; `--force` does
@@ -562,7 +725,6 @@ tailor-preview-result:
562
725
  name: tailor-preview-result (my-app)
563
726
  needs:
564
727
  - tailor-preview-deploy
565
- - tailor-preview-cleanup
566
728
  - seed
567
729
  - e2e
568
730
  ```
@@ -660,7 +822,13 @@ keeps them alongside that environment's `TAILOR_PLATFORM_WORKSPACE_ID`
660
822
  variable. You can also set the client secret as a repository-level secret and the
661
823
  client ID as a repository-level variable if every target shares one machine user,
662
824
  but then any workflow on any branch can read them, so the environment's
663
- protection rules no longer guard your deploys.
825
+ protection rules no longer guard your deploys, and `tailor setup ci env --check`
826
+ warns about the client secret (see
827
+ [Checking what is registered](#checking-what-is-registered)). To share one machine
828
+ user and keep that protection, set the same client secret on each environment
829
+ instead. The plan environment of `--split-plan-environment` is the exception: give
830
+ it a machine user of its own (see
831
+ [Running the plan job in its own environment](#running-the-plan-job-in-its-own-environment)).
664
832
 
665
833
  ### Setting secrets and variables
666
834
 
@@ -704,6 +872,16 @@ The autofix workflow uses no GitHub Environment, so its two entries are set on
704
872
  the repository; the output lists them under the repository instead of an
705
873
  environment.
706
874
 
875
+ A branch target generated with `--split-plan-environment` reads from two
876
+ environments. The plan environment needs its own
877
+ `TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` and
878
+ `TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET`, for which a viewer role is enough,
879
+ and `TAILOR_PLATFORM_WORKSPACE_ID`; of the optional entries, only the plan job's
880
+ `TAILOR_PLATFORM_FAIL_ON_DRIFT` and `SEED_USER_PASSWORD` apply to it. A manual dry
881
+ run reads `SEED_USER_PASSWORD` from there, and pull requests can read it too, like
882
+ everything on that environment. The deploy environment needs the rest, without
883
+ `TAILOR_PLATFORM_FAIL_ON_DRIFT`.
884
+
707
885
  See [Account management](https://docs.tailor.tech/administration/account-management)
708
886
  for how organizations, folders, workspaces, and machine users relate.
709
887
 
@@ -735,13 +913,29 @@ to manage those settings in Terraform instead.
735
913
  environment and for the repository entries of the autofix workflow, which of
736
914
  these entries are registered. It fails when a required
737
915
  entry is missing, reports missing optional entries without failing, and prints
738
- the `gh` commands that set only the missing entries, with where each value comes
739
- from. It changes nothing, and it checks names only, since GitHub never returns
740
- secret values.
916
+ the `gh` commands that set the missing entries, with where each value comes
917
+ from, and that move the client secret onto its environment (see below). It
918
+ changes nothing, and it checks names only, since GitHub never returns secret
919
+ values.
741
920
 
742
921
  - An entry set on the repository, or on the organization and shared with the
743
922
  repository, counts as registered, because the workflow reads it from there when
744
923
  the environment does not set it. The report says where each entry was found.
924
+ - `TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET` set on the repository or the
925
+ organization is reported with ⚠, even when the environment also sets it: any
926
+ workflow on any branch can read it there, so the environment's protection rules
927
+ do not guard it. The check still passes. The printed commands set it on the
928
+ environment, with the value you set on the repository or organization, and list
929
+ a commented-out `gh secret delete` for the repository copy: run it once every
930
+ environment and workflow that reads that secret has its own. An organization
931
+ owner has to stop sharing an organization secret with the repository. The
932
+ warning stops once the client secret is set only on the environments; when
933
+ every target shares one machine user, set the same client secret on each of
934
+ them.
935
+ - On the plan environment of `--split-plan-environment`, the machine user's
936
+ client ID and client secret count only when set on that environment, since
937
+ the plan job would otherwise sign in as the machine user set on the repository
938
+ or organization.
745
939
  - An entry registered as the other kind, such as `TAILOR_PLATFORM_WORKSPACE_ID`
746
940
  set as a secret, is reported as missing, because the workflow does not read it.
747
941
  `TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` set as a secret still counts, with a
@@ -773,7 +967,10 @@ environment-scoped secrets there.
773
967
 
774
968
  Required reviewers gate the `plan` job as well, because `plan` must enter the
775
969
  environment to read `TAILOR_PLATFORM_WORKSPACE_ID`. (A token-based read that
776
- lets `plan` run without entering the environment is planned.)
970
+ lets `plan` run without entering the environment is planned.) On a branch target,
971
+ `--split-plan-environment` moves `plan` into an environment of its own, whose
972
+ settings then apply to it instead (see
973
+ [Running the plan job in its own environment](#running-the-plan-job-in-its-own-environment)).
777
974
 
778
975
  ## Manual runs and dry-run
779
976
 
@@ -808,7 +1005,11 @@ can deploy, configure **Deployment branches and tags** on the target's GitHub
808
1005
  Environment and keep the machine-user secrets on that environment. Because the
809
1006
  plan job also enters the environment, allow `refs/pull/*/merge` as well so pull
810
1007
  request plans keep running, and note that dry runs from other branches are then
811
- rejected too.
1008
+ rejected too. On a branch target, `--split-plan-environment` moves the plan job,
1009
+ dry runs included, into an environment of its own instead, so the deploy
1010
+ environment can allow the target branch alone once the migration test job, if
1011
+ any, has its own environment as well (see
1012
+ [Running the plan job in its own environment](#running-the-plan-job-in-its-own-environment)).
812
1013
 
813
1014
  ## Seeding the workspace after deploy
814
1015
 
@@ -863,8 +1064,10 @@ Before enabling it, note that:
863
1064
  and validation run, `updatedAt` changes, and executors with
864
1065
  `recordUpdatedTrigger` fire once per seeded row on every deploy.
865
1066
  - Every preview deploy deletes the data reviewers added to the preview
866
- workspace. That is every push, and with `--require label` also every
867
- label added to the pull request, since each one redeploys the preview.
1067
+ workspace. That is every push to a pull request that
1068
+ [gets a preview](#pull-requests-that-get-a-preview), and with
1069
+ `--require label` also every label added to it, since each one redeploys the
1070
+ preview.
868
1071
  - A seed failure fails the deploy job and its notification, although the deploy
869
1072
  itself went through.
870
1073
  - The step shares the deploy job's timeout. IdP users take about a second each.
@@ -1126,10 +1329,10 @@ workflows it reports.
1126
1329
 
1127
1330
  Workflows generated by `setup ci branch`, `setup ci tag`, and `setup ci preview`
1128
1331
  self-audit: each contains a `tailor-drift-check` step that
1129
- runs the check in CI. Preview workflows run it alongside each preview deploy,
1130
- so pull requests that deploy no preview (drafts with `--require ready`, fork
1131
- PRs, and unlabeled PRs with `--require label`) skip the check. A single run audits every workflow
1132
- recorded in `.github/tailor.lock`.
1332
+ runs the check in CI. Preview workflows run it in `tailor-preview-deploy`, so
1333
+ the check is skipped whenever that job is (see
1334
+ [Pull requests that get a preview](#pull-requests-that-get-a-preview)). A single
1335
+ run audits every workflow recorded in `.github/tailor.lock`.
1133
1336
 
1134
1337
  Drift findings are advisory by default. Set the repository variable
1135
1338
  `TAILOR_PLATFORM_FAIL_ON_DRIFT` to `true` to make unsuppressed findings fail
@@ -1612,7 +1612,7 @@ See the SDK testing guide for the full environment setup.
1612
1612
 
1613
1613
  **Migration:** Manual
1614
1614
 
1615
- The name-keyed option types exported from `@tailor-platform/sdk/cli` — `GetWorkflowOptions`, `StartWorkflowOptions`, `ListWorkflowExecutionsOptions`, `GetExecutorOptions`, `TriggerExecutorOptions`, `ListExecutorJobsOptions`, `GetExecutorJobOptions`, `WatchExecutorJobOptions` — are removed in v2, together with the function overloads that accepted them. Pass the workflow or executor definition itself instead: `{ workflow: myWorkflow, invoker: "admin" }` / `{ executor: myExecutor }`, matching the `*TypedOptions` shape that types `arg` and `payload` from the definition. No codemod ships for this migration: rewriting a name string into a definition requires importing the module that defines the workflow or executor, which a source-local transform cannot resolve.
1615
+ The name-keyed option types exported from `@tailor-platform/sdk/cli` — `GetWorkflowOptions`, `StartWorkflowOptions`, `ListWorkflowExecutionsOptions`, `GetExecutorOptions`, `TriggerExecutorOptions`, `ListExecutorJobsOptions`, `GetExecutorJobOptions`, `WatchExecutorJobOptions` — are removed in v2, together with the function overloads that accepted them. Pass the workflow or executor definition itself instead: `{ workflow: myWorkflow, invoker: "admin" }` / `{ executor: myExecutor }`, matching the `*TypedOptions` shape that types `arg` from the definition and accepts `payload` only for an executor with an incoming webhook trigger. No codemod ships for this migration: rewriting a name string into a definition requires importing the module that defines the workflow or executor, which a source-local transform cannot resolve.
1616
1616
 
1617
1617
  Before:
1618
1618
 
@@ -1662,8 +1662,10 @@ flagged call site:
1662
1662
  invoker, typed against the machine users declared in tailor.config.ts.
1663
1663
  4. Replace imported option types with the *TypedOptions equivalent
1664
1664
  (e.g. GetWorkflowOptions → GetWorkflowTypedOptions<typeof myWorkflow>).
1665
- Note that arg (startWorkflow) and payload (triggerExecutor) are now typed
1666
- from the definition, so a mistyped argument becomes a type error.
1665
+ Note that arg (startWorkflow) is now typed from the definition, so a
1666
+ mistyped argument becomes a type error. Passing payload to
1667
+ triggerExecutor is a type error unless the executor has an incoming
1668
+ webhook trigger.
1667
1669
 
1668
1670
  When the name is only known at runtime (read from argv or an environment
1669
1671
  variable), the CLI command itself — `tailor workflow start <name>` /
@@ -26,6 +26,7 @@ Configure Auth service using `defineAuth()`:
26
26
 
27
27
  - **One auth per application**: Each application can have exactly one Auth service
28
28
  - **Configuration location**: Define in `tailor.config.ts` using `defineAuth()` and reference directly in the config's `auth` field
29
+ - **Referencing another app's auth**: When another app in the same repository defines the auth, reference it with `asExternal()`. See [Referencing another app's Auth with `asExternal()`](../configuration.md#referencing-another-apps-auth-with-asexternal)
29
30
  - **`userProfile` and `machineUserAttributes` are mutually exclusive**: Specifying both fails at deploy/generate time with ``Specify either `userProfile` or `machineUserAttributes`, not both.``
30
31
 
31
32
  ```typescript
@@ -314,6 +315,10 @@ export default createResolver({
314
315
 
315
316
  Type narrowing is provided by the generated `tailor.d.ts` (the `MachineUserNameRegistry` interface). Run `tailor generate` (or `deploy`) after defining new machine users to refresh it.
316
317
 
318
+ `tailor deploy` checks the string `invoker` of every resolver and executor against the machine users of the Auth service it resolves to, and stops with `INVOKER_MACHINE_USER_NOT_FOUND` when the name does not exist. The `invoker` passed to `workflow.start()` is not checked at deploy time. When a config deployed in the same run defines the Auth service, the machine users it lists are the ones checked, because deploy removes the machine users it no longer lists. When the Auth service is only referenced as external, the machine users that exist in the workspace are checked.
319
+
320
+ When the Auth service is an external one (`auth: { name: "...", external: true }`), the machine users are not in your config, so `tailor.d.ts` cannot narrow the invoker type and any string passes type checking. For resolvers and executors, the deploy-time check is what catches a misspelled name in that case.
321
+
317
322
  ## OAuth 2.0 Clients
318
323
 
319
324
  Configure OAuth 2.0 clients for third-party applications:
@@ -543,6 +543,8 @@ export default createExecutor({
543
543
 
544
544
  The webhook URL is public, so verify the signature over the unparsed request body before trusting `body`. `rawBody` is delivered base64-encoded; decode it to the original bytes before computing the signature.
545
545
 
546
+ When an executor is triggered manually with [`tailor executor trigger`](../cli/executor.md#executor-trigger) or `triggerExecutor` from `@tailor-platform/sdk/cli`, it receives empty `headers`, even when `--header` is given, and no `rawBody`. To test an executor that reads headers or verifies a signature, send a request to its webhook URL, which [`tailor executor webhook list`](../cli/executor.md#executor-webhook-list) shows.
547
+
546
548
  **With custom response:**
547
549
 
548
550
  ```typescript
@@ -226,7 +226,9 @@ const user = db.table("User", {
226
226
  });
227
227
  ```
228
228
 
229
- A relation can also be set on a field inside `db.object()`. The inner field gets the foreign key constraint only: no index, no unique constraint, and no forward or backward relationship, as with `type: "keyOnly"`.
229
+ A `keyOnly` relation does not make the field unique; add `.unique()` to the field to make it unique.
230
+
231
+ A relation can also be set on a field inside `db.object()`, where it adds only the foreign key constraint to the inner field, and `.unique()` and `.index()` set on that field work as described in [Index / Unique](#index--unique).
230
232
 
231
233
  ```typescript
232
234
  const order = db.table("Order", {
@@ -477,6 +479,8 @@ db.string().serial({
477
479
  });
478
480
  ```
479
481
 
482
+ `format` holds exactly one of `%d` (decimal), `%o` (octal), `%x` (lowercase hexadecimal), or `%X` (uppercase hexadecimal), optionally with a width: `%5d` pads with spaces and `%05d` with zeros, to at least 5 characters. Write a literal `%` as `%%`. The format is at most 32 characters.
483
+
480
484
  A serial field cannot be used inside a `db.object()` field.
481
485
 
482
486
  > **Preview:** The `gapless` option is a preview feature. Its behavior and configuration may change before general availability.
@@ -571,7 +575,7 @@ db.table("User", {
571
575
  - When not specified, `deploy` sets it from the executors taking part in the same run: `true` while one of them uses this table with `recordCreatedTrigger`, `recordUpdatedTrigger`, or `recordDeletedTrigger`, and `false` once none does. Removing the last such trigger turns publishing back off on the next `deploy`
572
576
  - When explicitly set to `false` while an executor taking part in the same run uses this table, `deploy` fails
573
577
  - An executor declared with `disabled: true` never runs, so it does not count as using the table
574
- - While a `deploy` applies pending migrations with [maintenance mode](./migration.md#maintenance-mode) on, every table in the migrating namespace is restricted: the GraphQL create, update, and delete operations and bulk upsert are disabled, and the table's `read` setting is not switched off for the migration (a table the migration creates uses its configured setting), so nothing writes an intermediate schema and readable tables are not shut down. Record event publishing is also switched off so a migration script's writes do not reach executors from the previous deploy. With `maintenanceMode: false`, these restrictions are not applied. After success, the configured settings take effect. If a later migration fails, settings from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior settings and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified; the deploy then reports the namespace whose tables stay restricted and points to `tailor tailordb migration status --namespace <namespace>`. When a failed migration or a failed restoration leaves tables restricted, the deploy lists their names, and deploying again releases them once the migration completes. A table that a failed rollback could not delete or revert is the exception: the deploy also lists it, but the next deploy stops with remote schema drift until you delete or repair that table by hand. A table left behind by a failed post-checkpoint deletion also remains read-only for manual recovery, and the deploy does not list it by name
578
+ - While a `deploy` applies pending migrations with [maintenance mode](./migration.md#maintenance-mode) on, every table in the migrating namespace is restricted: the GraphQL create, update, and delete operations and bulk upsert are disabled, and the table's `read` setting is not switched off for the migration (a table the migration creates uses its configured setting), so nothing writes an intermediate schema and readable tables are not shut down. Record event publishing is also switched off so a migration script's writes do not reach executors from the previous deploy. With `maintenanceMode: false`, these restrictions are not applied. After success, the configured settings take effect. If a later migration fails, settings from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior settings and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified; the deploy then reports the namespace whose tables stay restricted and points to `tailor tailordb migration status --namespace <namespace>`. When a failed migration or a failed restoration leaves tables restricted, the deploy lists their names, and deploying again releases them once the migration completes. A table that a failed rollback could not delete or revert is also listed: when it still has the migration's pre-migration schema, deploying again runs the migration again; otherwise the next deploy stops with remote schema drift until you delete or repair that table by hand. A table left behind by a failed post-checkpoint deletion also remains read-only, and the deploy does not list it by name; the next deploy deletes it
575
579
 
576
580
  **Use cases:**
577
581