@tailor-platform/sdk 2.24.0 → 2.25.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 (36) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/application-ChqHuhZW.mjs +1 -0
  3. package/dist/{application-CfqvzV3I.mjs → application-DS0XKBtK.mjs} +4 -4
  4. package/dist/application-DS0XKBtK.mjs.map +1 -0
  5. package/dist/cli/commands/deploy/deployment-target.d.mts +1 -0
  6. package/dist/cli/commands/machineuser/list.d.mts +1 -0
  7. package/dist/cli/commands/show.d.mts +13 -1
  8. package/dist/cli/lib.d.mts +2 -2
  9. package/dist/cli/lib.mjs +1 -1
  10. package/dist/cli/lib.mjs.map +1 -1
  11. package/dist/cli/main.mjs +46 -46
  12. package/dist/cli/main.mjs.map +1 -1
  13. package/dist/completion/zsh-worker.zsh +3 -3
  14. package/dist/configure/index.d.mts +2 -2
  15. package/dist/configure/index.mjs.map +1 -1
  16. package/dist/plugin/index.mjs +1 -1
  17. package/dist/plugin/index.mjs.map +1 -1
  18. package/dist/plugin/types.d.mts +97 -0
  19. package/dist/register-ts-hook-DPAW0Z4M.mjs +924 -0
  20. package/dist/register-ts-hook-DPAW0Z4M.mjs.map +1 -0
  21. package/docs/cli/application.md +23 -0
  22. package/docs/cli/secret.md +24 -16
  23. package/docs/cli-reference.md +25 -13
  24. package/docs/github-actions.md +69 -4
  25. package/docs/plugin/custom.md +76 -1
  26. package/docs/plugin/frontend.md +124 -0
  27. package/docs/plugin/index.md +24 -2
  28. package/docs/services/auth.md +2 -0
  29. package/docs/services/secret.md +5 -4
  30. package/docs/services/staticwebsite.md +2 -0
  31. package/docs/services/tailordb-migration.md +1 -1
  32. package/package.json +5 -5
  33. package/dist/application-BDqze-wy.mjs +0 -1
  34. package/dist/application-CfqvzV3I.mjs.map +0 -1
  35. package/dist/register-ts-hook-CVlI9Jzl.mjs +0 -922
  36. package/dist/register-ts-hook-CVlI9Jzl.mjs.map +0 -1
@@ -74,6 +74,29 @@ tailor deploy [options]
74
74
  | `--clean-cache` | - | Clean the bundle cache before building | No | - | - |
75
75
 
76
76
  See [Global Options](../cli-reference.md#global-options) for options available to all commands.
77
+ **JSON result:**
78
+
79
+ After a successful `tailor deploy --json`, stdout includes `status: "applied"`,
80
+ `summary`, `workspaceId`, and `applications` in config order. Each entry includes
81
+ the config's `name`, `configPath`, and, when configured, `id`, plus `aiGateways`
82
+ and `staticWebsites` keyed by site name. Endpoint `url` and `domain` are included
83
+ when a Platform Application with that name exists. If Auth is configured, `auth`
84
+ includes its `namespace` and `oauth2Clients` with each client's `name` and public `clientId`.
85
+ Client secrets are excluded. These fields are returned even when no deploy plugin
86
+ is registered or no resources changed.
87
+
88
+ Plugins that return outputs add entries under `deployedHooks`. For example, the
89
+ frontend plugin provides upload results in `deployedHooks[].outputs.frontends`.
90
+
91
+ ```sh
92
+ tailor deploy --json > deploy-result.json
93
+ jq '.applications[] | {name, url, staticWebsites, auth}' deploy-result.json
94
+ ```
95
+
96
+ Dry-run and build-only deployments do not return deployed application information.
97
+ If resources were applied but loading the JSON result fails, the command reports
98
+ `DEPLOY_RESULT_LOAD_FAILED`; fix the error and run `tailor deploy` again.
99
+
77
100
  **Workspace Selection:**
78
101
 
79
102
  After validating the configuration file, `deploy` resolves a workspace before bundling the
@@ -36,17 +36,21 @@ tailor secret create [options]
36
36
 
37
37
  **Options**
38
38
 
39
- | Option | Alias | Description | Required | Default | Env |
40
- | ------------------------------- | ----- | ------------------------- | -------- | ------- | ------------------------------ |
41
- | `--workspace-id <WORKSPACE_ID>` | `-w` | Workspace ID | No | - | `TAILOR_PLATFORM_WORKSPACE_ID` |
42
- | `--profile <PROFILE>` | `-p` | Workspace profile | No | - | `TAILOR_PLATFORM_PROFILE` |
43
- | `--vault-name <VAULT_NAME>` | `-V` | Vault name | Yes | - | - |
44
- | `--name <NAME>` | `-n` | Secret name | Yes | - | - |
45
- | `--value <VALUE>` | `-v` | Secret value | Yes | - | - |
46
- | `--yes` | `-y` | Skip confirmation prompts | No | `false` | - |
39
+ | Option | Alias | Description | Required | Default | Env |
40
+ | ------------------------------- | ----- | ---------------------------------------------------- | -------- | ------- | ------------------------------ |
41
+ | `--workspace-id <WORKSPACE_ID>` | `-w` | Workspace ID | No | - | `TAILOR_PLATFORM_WORKSPACE_ID` |
42
+ | `--profile <PROFILE>` | `-p` | Workspace profile | No | - | `TAILOR_PLATFORM_PROFILE` |
43
+ | `--vault-name <VAULT_NAME>` | `-V` | Vault name | Yes | - | - |
44
+ | `--name <NAME>` | `-n` | Secret name | Yes | - | - |
45
+ | `--value <VALUE>` | `-v` | Secret value (read from standard input when omitted) | No | - | - |
46
+ | `--yes` | `-y` | Skip confirmation prompts | No | `false` | - |
47
47
 
48
48
  See [Global Options](../cli-reference.md#global-options) for options available to all commands.
49
49
 
50
+ **Notes**
51
+
52
+ Pass the value with `--value`, or omit `--value` and pipe the value in to keep it out of shell history and process listings, for example `printf '%s' "$STRIPE_KEY" | tailor secret create --vault-name api-keys --name stripe-secret-key`. A piped value can be up to 128 KiB, and one trailing newline is removed from it. In a vault managed by `defineSecretManager()`, the command asks for confirmation before releasing the vault from the config, which needs an interactive terminal, so pass `--yes` when piping the value.
53
+
50
54
  ### secret delete
51
55
 
52
56
  Delete a secret in a vault.
@@ -103,17 +107,21 @@ tailor secret update [options]
103
107
 
104
108
  **Options**
105
109
 
106
- | Option | Alias | Description | Required | Default | Env |
107
- | ------------------------------- | ----- | ------------------------- | -------- | ------- | ------------------------------ |
108
- | `--workspace-id <WORKSPACE_ID>` | `-w` | Workspace ID | No | - | `TAILOR_PLATFORM_WORKSPACE_ID` |
109
- | `--profile <PROFILE>` | `-p` | Workspace profile | No | - | `TAILOR_PLATFORM_PROFILE` |
110
- | `--vault-name <VAULT_NAME>` | `-V` | Vault name | Yes | - | - |
111
- | `--name <NAME>` | `-n` | Secret name | Yes | - | - |
112
- | `--value <VALUE>` | `-v` | Secret value | Yes | - | - |
113
- | `--yes` | `-y` | Skip confirmation prompts | No | `false` | - |
110
+ | Option | Alias | Description | Required | Default | Env |
111
+ | ------------------------------- | ----- | ---------------------------------------------------- | -------- | ------- | ------------------------------ |
112
+ | `--workspace-id <WORKSPACE_ID>` | `-w` | Workspace ID | No | - | `TAILOR_PLATFORM_WORKSPACE_ID` |
113
+ | `--profile <PROFILE>` | `-p` | Workspace profile | No | - | `TAILOR_PLATFORM_PROFILE` |
114
+ | `--vault-name <VAULT_NAME>` | `-V` | Vault name | Yes | - | - |
115
+ | `--name <NAME>` | `-n` | Secret name | Yes | - | - |
116
+ | `--value <VALUE>` | `-v` | Secret value (read from standard input when omitted) | No | - | - |
117
+ | `--yes` | `-y` | Skip confirmation prompts | No | `false` | - |
114
118
 
115
119
  See [Global Options](../cli-reference.md#global-options) for options available to all commands.
116
120
 
121
+ **Notes**
122
+
123
+ Pass the value with `--value`, or omit `--value` and pipe the value in to keep it out of shell history and process listings, for example `printf '%s' "$STRIPE_KEY" | tailor secret update --vault-name api-keys --name stripe-secret-key`. A piped value can be up to 128 KiB, and one trailing newline is removed from it. In a vault managed by `defineSecretManager()`, the command asks for confirmation before releasing the vault from the config, which needs an interactive terminal, so pass `--yes` when piping the value.
124
+
117
125
  ### secret vault
118
126
 
119
127
  Manage Secret Manager vaults.
@@ -31,8 +31,16 @@ For commands that return structured results, passing `--json` writes one parseab
31
31
  to stdout on success. Empty successful result sets are emitted as JSON values such as `[]`, not as
32
32
  human-readable text or empty stdout.
33
33
 
34
- Commands that only perform side effects and do not define a structured result may leave stdout empty
35
- even when `--json` is passed.
34
+ Many commands that change state print a JSON object describing the outcome. Its `changed` field is
35
+ `true` when the command did work and `false` when it did nothing, for example because the requested
36
+ state was already in place. The other fields identify what the command acted on and report details of
37
+ the outcome. Other state-changing commands print a result without `changed`, or do not report a
38
+ result yet and leave stdout empty even when `--json` is passed.
39
+
40
+ List commands return at most `--limit` items. When more exist, the list is followed by a notice on
41
+ stderr, `More results exist beyond --limit N. Raise --limit to see more.`, so `--json` output stays a
42
+ plain array of the listed items. Log listings such as `executor jobs`, `function logs`, and
43
+ `workflow executions` default to `--limit 50`; pass `--limit 0` to list everything.
36
44
 
37
45
  Set `TAILOR_JSON_OUTPUT=true` (or `1`) to default every command to JSON without passing `--json`
38
46
  each time. This is intended for agents, scripts, and CI steps that parse CLI output. An explicit
@@ -42,11 +50,13 @@ keep table output. Other values are rejected. JSON mode also disables interactiv
42
50
  variable per invocation or per job rather than exporting it from a shell profile; a command that
43
51
  needed a prompt names what selected JSON when it refuses.
44
52
 
45
- Errors, warnings, progress, and diagnostic messages are written to stderr. After argument parsing,
46
- a command failure under `--json` emits a JSON error envelope to stderr. Failures you can act on — an
47
- invalid or missing option, a resource that does not exist, an invalid configuration, or an unmet
48
- precondition — carry a stable `error.code` such as `PROFILE_NOT_FOUND`, `TAILORDB_NAMESPACE_NOT_FOUND`,
49
- or `MIGRATION_SCRIPT_REQUIRED`. Where a remediation exists, the envelope also includes
53
+ Errors, warnings, progress, and diagnostic messages are written to stderr. A command failure under
54
+ `--json` or `TAILOR_JSON_OUTPUT` emits a JSON error envelope to stderr, including a failure while
55
+ parsing arguments: an unknown option or subcommand, or an option value that fails validation, is
56
+ reported as `INVALID_ARGUMENTS`. Failures you can act on — an invalid or missing option, a resource
57
+ that does not exist, an invalid configuration, or an unmet precondition — carry a stable `error.code`
58
+ such as `PROFILE_NOT_FOUND`, `TAILORDB_NAMESPACE_NOT_FOUND`, or `MIGRATION_SCRIPT_REQUIRED`. Where a
59
+ remediation exists, the envelope also includes
50
60
  `error.suggestion`, `error.help` (the `--help` invocation for the failing command), `error.next` (a
51
61
  runnable command), or `error.context`. `UNEXPECTED_ERROR` marks failures without a dedicated code,
52
62
  including SDK-internal errors. Diagnostic lines may precede the error envelope, and stdout is not
@@ -75,9 +85,11 @@ Use `--verbose` to include debug diagnostics and error stack traces. `DEBUG=true
75
85
  sets `RUNNER_DEBUG=1` when debug logging is enabled, so the same command automatically includes
76
86
  these details in a debug run. These settings do not enable JSON output; pass `--json` separately.
77
87
 
78
- Capture the original failure's stderr and exit code before retrying. Argument parsing and failures
79
- before the CLI starts may produce plain text even with `--json`. A failed deployment may have
80
- already applied changes, so inspect its output before deciding to run it again.
88
+ Capture the original failure's stderr and exit code before retrying. Failures before the CLI starts,
89
+ a rejected `--json` or `TAILOR_JSON_OUTPUT` value, argument errors in the bundled CLI plugins, and
90
+ the install hint for a CLI plugin that is not installed may still produce plain text when JSON output
91
+ is requested. A failed deployment may have already
92
+ applied changes, so inspect its output before deciding to run it again.
81
93
 
82
94
  ### GitHub Actions Annotations
83
95
 
@@ -95,9 +107,9 @@ without reporting through the CLI's error path, such as one relaying a failed re
95
107
  writes no annotation.
96
108
 
97
109
  Set `TAILOR_GITHUB_ACTIONS_ANNOTATIONS=false` (also `off`, `no`, or `0`) to turn annotations off.
98
- Passing `--json` also suppresses them, so a workflow step that parses `--json` output gets only the
99
- error envelope on stderr. The flag is honored even when the command fails during argument parsing,
100
- before the envelope itself becomes available.
110
+ Passing `--json` or setting `TAILOR_JSON_OUTPUT` also suppresses them, so a workflow step that parses
111
+ JSON output gets only the error envelope on stderr, even when the command fails during argument
112
+ parsing.
101
113
 
102
114
  An annotation does not by itself fail a step: the step still fails on the CLI's exit code, which
103
115
  is unchanged. Workflows that already echo their own `::error::` around the CLI keep working;
@@ -199,6 +199,14 @@ Running a workflow setup subcommand creates or updates:
199
199
  The workflow file. The `name:` field is set to `Tailor (<workspace-name>)` so
200
200
  you can distinguish multiple workspaces in the Actions UI.
201
201
 
202
+ Every checkout in the workflow sets `persist-credentials: false`, so the GitHub
203
+ token is not left in `.git/config` while the job installs and runs your
204
+ project's code. The tag guard step passes the job token only to its
205
+ `git fetch`, and the plan step passes it to its `git fetch` and to the step that
206
+ posts the plan comment on the pull request; neither writes it to `.git/config`.
207
+ If installing your dependencies fetches git repositories
208
+ that require authentication, configure the credentials in your own step.
209
+
202
210
  See [Customizing the generated workflow](#customizing-the-generated-workflow)
203
211
  for what you can edit.
204
212
 
@@ -218,7 +226,12 @@ them. Everything else is yours, and re-running `setup` keeps it:
218
226
  managed job with `needs: tailor-deploy`.
219
227
  - **Your own top-level keys**, such as a workflow-level `env:` or `defaults:`.
220
228
  - **Runtime settings of managed jobs:** `runs-on`, `timeout-minutes`,
221
- `container`, and `env`.
229
+ `container`, and `env`. On a managed job generated without an
230
+ `environment:` (such as `tailor-tag-guard` or `tailor-erd-preview`), you can
231
+ also add one, for example so a step you added there can read that GitHub
232
+ Environment's secrets. The `environment:` of `tailor-plan`, `tailor-deploy`,
233
+ and the preview jobs comes from `--environment`, so change it with that
234
+ option instead.
222
235
  - **These inputs of managed steps:** `ignore` on `tailor-generate-check` and
223
236
  `tailor-drift-check`, `fail-on-drift` on `tailor-drift-check`,
224
237
  `install-command` on `tailor-install`, `node-version-file` on
@@ -479,9 +492,61 @@ For a monorepo where your SDK app lives in a subdirectory, pass `--dir`:
479
492
  tailor setup ci branch --name my-app --dir apps/backend
480
493
  ```
481
494
 
482
- The generated workflow adds a `paths` filter on `apps/backend/**` so the
483
- workflow only runs when that subdirectory changes. The `working-directory` for
484
- SDK commands is set accordingly.
495
+ The `working-directory` for SDK commands is set accordingly, and the workflow
496
+ only plans and deploys when that subdirectory changes. The workflow itself
497
+ starts on every pull request and push: a `tailor-changes` job checks whether
498
+ the change touches `apps/backend/**`, and the plan, deploy, and ERD preview jobs
499
+ are skipped when it does not. A skipped job reports success, so you can make
500
+ these checks required in branch protection; a workflow that a `paths` trigger
501
+ filter never started would leave them pending instead. If the `tailor-changes` job
502
+ itself fails (for example, on a GitHub API error), the plan, deploy, and ERD
503
+ preview jobs fail too instead of being skipped, so a required check blocks
504
+ merging and nothing is deployed. Re-run the failed jobs once the cause is gone.
505
+
506
+ ### Deploying several apps together
507
+
508
+ To deploy several apps to the same workspace from one workflow, repeat `--dir`
509
+ and pass `--name` for the workflow:
510
+
511
+ ```bash
512
+ tailor setup ci branch --name erp --dir apps/erp/backend --dir apps/users/backend
513
+ ```
514
+
515
+ `setup ci branch`, `setup ci tag`, and `setup ci preview` accept repeated
516
+ `--dir`. The workflow plans and deploys every app's `tailor.config.ts` in one
517
+ [multi-config deploy](./cli/application.md#deploy) from the repository root, so
518
+ an app can reference resources of another app with `external: true`. Add
519
+ `@tailor-platform/sdk` to the root `package.json` so the `tailor` CLI resolves
520
+ there; setup stops until it is declared. The generate check runs for each app
521
+ directory, as do seed validation and the migration drift check on branch and tag
522
+ workflows, and a change under any app directory runs the workflow's jobs.
523
+
524
+ With `--erd-preview`, each TailorDB namespace is previewed from the app that
525
+ owns it. A namespace may be owned by only one app; the others reference it with
526
+ `external: true`.
527
+
528
+ With more than one app, the preview comment does not link an application URL.
529
+
530
+ ### Running on changes outside the app directories
531
+
532
+ When the apps depend on code outside their directories, such as a frontend or
533
+ shared packages, add those paths with `--paths` on `setup ci branch` or
534
+ `setup ci preview`. Repeat it for each pattern:
535
+
536
+ ```bash
537
+ tailor setup ci preview --name erp --region asia-northeast \
538
+ --dir apps/erp/backend --dir apps/users/backend \
539
+ --paths "apps/*/frontend/**" --paths "modules/**" --paths pnpm-lock.yaml
540
+ ```
541
+
542
+ The patterns are checked after the app directories, in order, and the last
543
+ pattern that matches a changed file decides whether it counts. `*` matches
544
+ within one path segment, `**` matches any number of segments, and a pattern
545
+ starting with `!` excludes matching paths, so it can also exclude files inside
546
+ an app directory, for example `--paths '!apps/erp/backend/**/*.md'`. Other glob
547
+ characters (`?`, `+`, `[ ]`, `{ }`, `( )`, and `\`) are not supported, and
548
+ `setup ci` rejects a pattern that contains them. An app at the repository root already
549
+ runs on every change, so `--paths` is not accepted with `--dir .`.
485
550
 
486
551
  ## Rollback
487
552
 
@@ -6,7 +6,7 @@ Create your own plugins by implementing the `Plugin` interface.
6
6
 
7
7
  ## Requirements
8
8
 
9
- **Plugins must use default export**:
9
+ **Plugins with definition-time hooks must use default export**:
10
10
 
11
11
  ```typescript
12
12
  // plugin.ts
@@ -48,6 +48,9 @@ interface Plugin<TableConfig = unknown, PluginConfig = unknown> {
48
48
  onExecutorReady?(
49
49
  context: ExecutorReadyContext<PluginConfig>,
50
50
  ): GeneratorResult | Promise<GeneratorResult>;
51
+ onDeployed?(
52
+ context: DeployedContext<PluginConfig>,
53
+ ): void | DeployedHookResult | Promise<void | DeployedHookResult>;
51
54
  }
52
55
  ```
53
56
 
@@ -254,6 +257,78 @@ onExecutorReady(ctx) {
254
257
  },
255
258
  ```
256
259
 
260
+ ### onDeployed
261
+
262
+ Runs once after every application in the deploy has been applied, including when
263
+ there are no resource changes. Hooks run sequentially in config order, then in
264
+ `definePlugins()` registration order. No `importPath` or table attachment is required.
265
+
266
+ Use `ctx.application` for the registering application's ID, URL, domain, AI
267
+ Gateway URLs, static websites, and public OAuth client IDs. `ctx.application.id`
268
+ is the `id` from `defineConfig()` and is `undefined` when the config has none.
269
+ `ctx.applications` contains all applications in a multi-config deploy with the
270
+ same fields. OAuth client secrets are never included.
271
+
272
+ `ctx.configPath` is the absolute path of the registering config. `ctx.workspaceId`
273
+ and `ctx.pluginConfig` identify the deployment workspace and the plugin's options.
274
+ Use `ctx.logger.info`, `warn`, and `success` for diagnostics; they write to stderr
275
+ and preserve deploy's JSON stdout.
276
+
277
+ ```typescript
278
+ import { definePlugins, type Plugin } from "@tailor-platform/sdk";
279
+
280
+ const deployedInfo: Plugin = {
281
+ id: "@example/deployed-info",
282
+ description: "Reports the deployed application URL",
283
+ onDeployed(ctx) {
284
+ const url = ctx.application.url;
285
+ if (!url) return;
286
+ ctx.logger.success(`Application ready: ${url}`);
287
+ return { outputs: { url } };
288
+ },
289
+ };
290
+
291
+ export const plugins = definePlugins(deployedInfo);
292
+ ```
293
+
294
+ To publish assets, look up the site with `ctx.application.staticWebsites[name]`
295
+ and await its `publish(dir)` with an absolute directory path. Only websites
296
+ declared in the registering config appear there; any other name is `undefined`,
297
+ so check the site before publishing. Websites of other configs are listed under
298
+ `ctx.applications` with their `name` and `url` only, so a plugin can read their
299
+ URLs but cannot publish to them. The result contains `url` and `skippedFiles`; report skipped
300
+ files so users can correct files that could not be uploaded.
301
+
302
+ To run a shell command such as a build, await `ctx.exec(command, { workingDir, env })`.
303
+ It rejects when the command exits with a non-zero code. By default the command's
304
+ stdout and stderr both go to stderr so `tailor deploy --json` stdout stays parseable;
305
+ pass `output: "capture"` to receive them as `stdout` and `stderr` instead, or
306
+ `output: "ignore"` to discard them. Use `ctx.exec` rather than importing
307
+ `node:child_process` in the plugin: `tailor.config.ts` is also bundled into
308
+ functions that run on the Tailor Platform, such as auth hooks, and a plugin module
309
+ that imports Node.js-only modules makes that bundle fail.
310
+
311
+ Return JSON-serializable `outputs` to include a result in `deployedHooks` under
312
+ `tailor deploy --json`. Each entry identifies the application and plugin. This
313
+ key is omitted when no hook supplies outputs. `outputs` must be a plain object whose
314
+ values are only strings, finite numbers, booleans, `null`, arrays, and plain objects;
315
+ any other value, such as `undefined`, a function, or a `Date`, fails the hook.
316
+ Output values may be typed with an `interface`. The type checker rejects a
317
+ `Date`, function, `Map`, or other non-JSON value placed directly in `outputs` or in
318
+ an object or array literal, but a `Date` or class instance nested inside an
319
+ interface-typed value is caught only when `tailor deploy` runs the hook.
320
+
321
+ A failed hook stops later hooks with `DEPLOYED_HOOK_FAILED`. The same error is
322
+ reported when the deployed information passed to hooks cannot be loaded. Platform
323
+ resources have already been applied and are not rolled back. The error lists any hooks
324
+ that did not run, with the application each belongs to. Fix the hook and run `tailor deploy` again; hooks run again even
325
+ if there are no resource changes.
326
+
327
+ Hooks do not run during dry-run, build-only, or migration test deployments.
328
+ Dry-run lists the pending hooks; with `--json`, they appear under
329
+ `pendingDeployedHooks` as `{ application, pluginId }` entries. Calling `deploy()` from the programmatic CLI
330
+ API runs hooks under the same conditions as `tailor deploy`.
331
+
257
332
  ## Hook Scheduling Rules
258
333
 
259
334
  Each generation-time hook runs at its own pipeline phase, regardless of what other hooks the same plugin implements:
@@ -0,0 +1,124 @@
1
+ # Frontend Plugin
2
+
3
+ `frontendPlugin` builds frontend assets and uploads them to Static Websites after
4
+ `tailor deploy` applies your application. It can provide deployed URLs and public
5
+ OAuth client IDs as build environment variables.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ pnpm add -D @tailor-platform/sdk-plugin-frontend
11
+ ```
12
+
13
+ Use an SDK version that supports `onDeployed` hooks. When testing a PR before
14
+ that SDK release, install both the SDK and frontend plugin from the same
15
+ `pkg.pr.new` commit.
16
+
17
+ ## Monorepo example
18
+
19
+ Given this layout:
20
+
21
+ ```text
22
+ apps/
23
+ backend/
24
+ tailor.config.ts
25
+ web/
26
+ package.json
27
+ dist/
28
+ ```
29
+
30
+ Register the plugin in `apps/backend/tailor.config.ts`:
31
+
32
+ ```typescript
33
+ import { defineConfig, definePlugins, defineStaticWebSite } from "@tailor-platform/sdk";
34
+ import { frontendPlugin } from "@tailor-platform/sdk-plugin-frontend";
35
+
36
+ const website = defineStaticWebSite("my-frontend", { description: "Web app" });
37
+
38
+ export default defineConfig({
39
+ name: "my-app",
40
+ staticWebsites: [website],
41
+ });
42
+
43
+ export const plugins = definePlugins(
44
+ frontendPlugin({
45
+ site: website,
46
+ workingDir: "../web",
47
+ build: "pnpm run build",
48
+ distDir: "dist",
49
+ env: ({ site, application }) => ({
50
+ ...(application.url ? { VITE_TAILOR_APP_URL: application.url } : {}),
51
+ VITE_SITE_URL: site.url,
52
+ VITE_OAUTH2_CLIENT_ID:
53
+ application.auth?.oauth2Clients.find((client) => client.name === "web")?.clientId ?? "",
54
+ }),
55
+ }),
56
+ );
57
+ ```
58
+
59
+ Run `tailor deploy --config apps/backend/tailor.config.ts` from the repository
60
+ root. `workingDir` is where `build` runs, relative to the config's directory, so
61
+ this example builds in `apps/web`. `distDir` is the directory your build writes
62
+ its assets to, relative to `workingDir`, so this example publishes `apps/web/dist`.
63
+ Set it to match your build tool's output setting; the plugin does not change where
64
+ the build writes. Omitting `workingDir` uses the config's directory. Absolute paths are
65
+ also accepted.
66
+
67
+ `site` accepts either a `defineStaticWebSite()` result or a site name. The site
68
+ must be declared in `staticWebsites` of the same config that registers
69
+ `frontendPlugin`. With multiple configs, register each frontend in the config that
70
+ declares its site; a site from another config fails before the build starts. The
71
+ `env` callback can still read the URLs of other configs' sites from `applications`.
72
+
73
+ ## Build environment
74
+
75
+ `env` is an optional function that returns environment variables, synchronously
76
+ or asynchronously. Its values override matching variables inherited from the
77
+ parent process. Choose variable names for your frontend framework; the plugin
78
+ does not add a prefix.
79
+
80
+ The callback receives the destination `site`, the registering `application`, all
81
+ `applications` in the deploy, and `workspaceId`. Each application lists its static
82
+ websites by name under `staticWebsites`, so `application.staticWebsites.admin?.url`
83
+ reads another site of the same config.
84
+ Only public OAuth client IDs are provided. Values embedded into browser assets
85
+ are visible to visitors, so supply only values intended for public use.
86
+
87
+ Build commands run in a shell. Their stdout and stderr both go to stderr, keeping
88
+ `tailor deploy --json` stdout available for the JSON result. Builds have no timeout.
89
+
90
+ ## Upload existing assets
91
+
92
+ Omit `build` to upload an existing directory:
93
+
94
+ ```typescript
95
+ export const plugins = definePlugins(
96
+ frontendPlugin({ site: "my-frontend", workingDir: "../web", distDir: "dist" }),
97
+ );
98
+ ```
99
+
100
+ For multiple frontends, pass each one as another argument, as in
101
+ `frontendPlugin(web, admin)`; register the plugin only once. Each site may appear
102
+ only once. Frontends are built and uploaded sequentially, in argument order. At
103
+ least one frontend is required, and each `distDir` must be non-empty. A `build` that is empty or only whitespace is rejected; omit `build` instead to publish without building.
104
+
105
+ ## Deploy behavior and failures
106
+
107
+ The plugin runs even if no platform resources changed. It does not run during
108
+ dry-run, build-only, generation, or migration test deployments. Dry-run lists the
109
+ plugin as a pending deploy hook.
110
+
111
+ An unknown site, a site declared in another config, failed build, missing output directory, or failed upload stops
112
+ later frontends and deploy hooks. Platform resources have already been applied;
113
+ fix the error and run `tailor deploy` again. Successfully uploaded frontends are
114
+ not rolled back. Skipped upload files produce warnings and are listed in the result.
115
+
116
+ With `--json`, the result contains a `deployedHooks` entry for
117
+ `@tailor-platform/frontend`. Its `outputs.frontends` array contains each site's
118
+ `site`, published `url`, and `skippedFiles`.
119
+
120
+ The same deploy result includes `workspaceId` and `applications`, including each
121
+ config's Static Website URLs, AI Gateway URLs, and public OAuth client IDs. The
122
+ application endpoint URL and domain are present when a Platform Application with
123
+ the config's name exists. Read these directly from `tailor deploy --json`; a
124
+ separate `show` command is not required.
@@ -115,7 +115,7 @@ e.g. `@example/soft-delete` → `example-soft-delete`), such as:
115
115
 
116
116
  ## Plugin Lifecycle
117
117
 
118
- Plugins have 5 hooks across two lifecycle phases. Each hook fires at a specific point in the `tailor generate` pipeline:
118
+ Plugins have definition-time, generation-time, and deploy-time hooks. The generation lifecycle is:
119
119
 
120
120
  ```
121
121
  tailor generate
@@ -156,7 +156,29 @@ These hooks produce TailorDB tables, resolvers, and executors that become part o
156
156
 
157
157
  These hooks receive all finalized data and produce output files (TypeScript code, etc.). No `importPath` required.
158
158
 
159
- A plugin can implement hooks from either or both phases.
159
+ ### Deploy-time hooks
160
+
161
+ ```
162
+ tailor deploy
163
+ │
164
+ ├─ Build and review resource changes
165
+ ├─ Apply all applications and services
166
+ └─ onDeployed ← each registered plugin, in config order
167
+ ```
168
+
169
+ | Hook | Available data | Can do |
170
+ | ------------ | ---------------------------------------------------------------- | ---------------------------------------- |
171
+ | `onDeployed` | Deployed application URLs, website URLs, public OAuth client IDs | Build assets and publish static websites |
172
+
173
+ Deploy hooks run even when there are no resource changes. They do not run during
174
+ `tailor generate`, dry-run, build-only, or migration test deployments. Dry-run lists
175
+ which hooks would run. A deploy-only plugin needs neither `importPath` nor table attachments.
176
+
177
+ A plugin can implement hooks from any combination of phases.
178
+
179
+ ## Deploying Frontends
180
+
181
+ See [Frontend Plugin](./frontend.md) to build frontends and upload them to static websites as part of `tailor deploy`.
160
182
 
161
183
  ## Creating Custom Plugins
162
184
 
@@ -351,6 +351,8 @@ Get OAuth2 client credentials using the CLI:
351
351
  tailor oauth2client get <name>
352
352
  ```
353
353
 
354
+ `tailor show` also lists the client ID, without the secret, of each OAuth2 client defined in `oauth2Clients` once it has been deployed. If your credentials cannot list OAuth2 clients, as with the workspace viewer role, it warns and reports `oauth2Clients` as `null`.
355
+
354
356
  ## Identity Provider
355
357
 
356
358
  Connect to an external identity provider:
@@ -197,13 +197,14 @@ tailor secret create \
197
197
  --name stripe-secret-key \
198
198
  --value sk_live_xxxxx
199
199
 
200
- # Update a secret
201
- tailor secret update \
200
+ # Update a secret, reading the value from standard input
201
+ printf '%s' "$STRIPE_SECRET_KEY" | tailor secret update \
202
202
  --vault-name api-keys \
203
- --name stripe-secret-key \
204
- --value sk_live_yyyyy
203
+ --name stripe-secret-key
205
204
  ```
206
205
 
206
+ A value passed with `--value` can show up in your shell history and in process listings. Without `--value`, the command reads the value from standard input instead, accepting up to 128 KiB and removing one trailing newline.
207
+
207
208
  ### List Secrets
208
209
 
209
210
  ```bash
@@ -130,6 +130,8 @@ export default defineConfig({
130
130
 
131
131
  Resolver, executor, workflow job, and auth before-login hook code, and TailorDB migration scripts, that read [`env`](../configuration.md#environment-variables) receive the deployed URL, even when the same deploy both creates the website and reads its URL — one `deploy` call resolves it, with no second, manually-triggered `deploy` needed. If the referenced website does not exist at all, the CLI warns and leaves the unresolved reference in place. If the reference still can't be resolved after this deploy's rebuild, the deploy fails instead of shipping the unresolved reference. This platform lookup only happens during `deploy`; `function run` passes the literal `<name>:url` string unchanged, since it never talks to the platform to resolve it.
132
132
 
133
+ The deployed URL is also shown by `tailor show`, which lists the URL of each static website defined in `staticWebsites` once it has been deployed.
134
+
133
135
  ## Complete Example
134
136
 
135
137
  ```typescript
@@ -240,7 +240,7 @@ This writes a numbered migration with an empty `diff.json`, a `migrate.ts` skele
240
240
 
241
241
  The command requires a clean state: if the namespace has schema changes that are not yet in migration files, generate the schema migration first. With multiple namespaces, pass `--namespace` to name the target. `--data-only` cannot be combined with `--init`, `--rename`, `--drop`, or `--expand-contract`.
242
242
 
243
- A data-only migration runs in **every** workspace the history is applied to, including freshly created ones. Write the script so it is safe against tables with no matching rows (a set-based `UPDATE` with a `WHERE` clause is naturally a no-op on an empty table). For a fix that should run in a single environment only, or that is too large for one transaction, run it outside the migration history instead.
243
+ A data-only migration runs in **every** workspace the history is applied to, including freshly created ones. Write the script so it is safe against tables with no matching rows (a set-based `UPDATE` with a `WHERE` clause is naturally a no-op on an empty table). For a fix that should run in a single environment only, or that is too large for one transaction, run it outside the migration history instead, for example as a one-off script scaffolded with [`tailor function script`](../cli/function.md#function-script) and executed against a single workspace with [`tailor function run`](../cli/function.md#function-run).
244
244
 
245
245
  ## Configuration
246
246
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk",
3
- "version": "2.24.0",
3
+ "version": "2.25.0",
4
4
  "description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -164,7 +164,7 @@
164
164
  "@opentelemetry/resources": "2.11.0",
165
165
  "@opentelemetry/sdk-trace-node": "2.11.0",
166
166
  "@opentelemetry/semantic-conventions": "1.43.0",
167
- "@oxc-project/types": "0.151.0",
167
+ "@oxc-project/types": "0.152.0",
168
168
  "@politty/zod": "0.3.0",
169
169
  "@secretlint/core": "13.0.6",
170
170
  "@secretlint/secretlint-rule-preset-recommend": "13.0.6",
@@ -191,7 +191,7 @@
191
191
  "pathe": "2.0.3",
192
192
  "pgsql-ast-parser": "12.0.2",
193
193
  "pkg-types": "2.3.3",
194
- "rolldown": "1.2.11",
194
+ "rolldown": "1.2.12",
195
195
  "semver": "7.8.5",
196
196
  "sql-highlight": "6.1.0",
197
197
  "std-env": "4.2.0",
@@ -213,8 +213,8 @@
213
213
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
214
214
  "@vitest/coverage-v8": "5.0.1",
215
215
  "eslint-plugin-zod": "4.14.2",
216
- "oxfmt": "0.70.0",
217
- "oxlint": "1.85.0",
216
+ "oxfmt": "0.71.0",
217
+ "oxlint": "1.86.0",
218
218
  "oxlint-tsgolint": "7.0.2003",
219
219
  "sonda": "0.14.0",
220
220
  "tsdown": "0.23.0",
@@ -1 +0,0 @@
1
- import{n as e,t}from"./application-CfqvzV3I.mjs";export{t as defineApplication,e as generatePluginFilesIfNeeded};