@tailor-platform/sdk 2.15.0 → 2.17.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 (102) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/dist/{application-BpoMu4Af.mjs → application-ChuNPwnl.mjs} +28 -28
  3. package/dist/application-ChuNPwnl.mjs.map +1 -0
  4. package/dist/application-Oh5dMmb5.mjs +1 -0
  5. package/dist/cli/commands/executor/jobs.d.mts +1 -0
  6. package/dist/cli/commands/generate/seed/bundler.d.mts +18 -0
  7. package/dist/cli/commands/tailordb/migrate/config.d.mts +1 -0
  8. package/dist/cli/commands/tailordb/migrate/snapshot-files.d.mts +5 -1
  9. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +2 -2
  10. package/dist/cli/commands/workflow/waiter.d.mts +1 -0
  11. package/dist/cli/commands/workspace/create.d.mts +16 -1
  12. package/dist/cli/commands/workspace/expiry.d.mts +5 -0
  13. package/dist/cli/commands/workspace/get.d.mts +6 -1
  14. package/dist/cli/commands/workspace/list.d.mts +1 -0
  15. package/dist/cli/lib.d.mts +4 -3
  16. package/dist/cli/lib.mjs +1 -1
  17. package/dist/cli/lib.mjs.map +1 -1
  18. package/dist/cli/main.mjs +57 -59
  19. package/dist/cli/main.mjs.map +1 -1
  20. package/dist/cli/shared/command.d.mts +1 -1
  21. package/dist/cli/shared/error-json.d.mts +21 -1
  22. package/dist/cli/shared/errors.d.mts +1 -0
  23. package/dist/cli/shared/github-actions.d.mts +13 -0
  24. package/dist/cli/shared/logger.d.mts +18 -0
  25. package/dist/completion/zsh-worker.zsh +92 -4
  26. package/dist/configure/index.mjs +1 -1
  27. package/dist/configure/index.mjs.map +1 -1
  28. package/dist/crashreport-CgymxQDu.mjs +1 -0
  29. package/dist/crashreport-Cyuz1qiu.mjs +42 -0
  30. package/dist/crashreport-Cyuz1qiu.mjs.map +1 -0
  31. package/dist/errors-BjJnpXkK.mjs +7 -0
  32. package/dist/errors-BjJnpXkK.mjs.map +1 -0
  33. package/dist/field-parse-CzlKC4b7.mjs +2 -0
  34. package/dist/field-parse-CzlKC4b7.mjs.map +1 -0
  35. package/dist/guards-ForsrxnH.mjs +2 -0
  36. package/dist/guards-ForsrxnH.mjs.map +1 -0
  37. package/dist/kysely-type-B_oA8D1k.mjs +43 -0
  38. package/dist/kysely-type-B_oA8D1k.mjs.map +1 -0
  39. package/dist/{logger-CCjs1DuH.mjs → logger-72hM4JWZ.mjs} +4 -4
  40. package/dist/logger-72hM4JWZ.mjs.map +1 -0
  41. package/dist/manager-C26Gi1bX.mjs +2 -0
  42. package/dist/manager-C26Gi1bX.mjs.map +1 -0
  43. package/dist/node-builtins-DYfhPBnz.mjs +2 -0
  44. package/dist/node-builtins-DYfhPBnz.mjs.map +1 -0
  45. package/dist/plugin/builtin/kysely-type/index.d.mts +2 -0
  46. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  47. package/dist/plugin/builtin/seed/index.mjs +1 -1
  48. package/dist/plugin/builtin/seed/seed-type-processor.d.mts +17 -0
  49. package/dist/plugin/get-generated-table.d.mts +13 -0
  50. package/dist/plugin/index.d.mts +2 -2
  51. package/dist/plugin/index.mjs +1 -1
  52. package/dist/plugin/index.mjs.map +1 -1
  53. package/dist/register-ts-hook-Dn-XlVSD.mjs +917 -0
  54. package/dist/register-ts-hook-Dn-XlVSD.mjs.map +1 -0
  55. package/dist/schema-DRyQEabV.mjs +2 -0
  56. package/dist/schema-DRyQEabV.mjs.map +1 -0
  57. package/dist/{seed-CCc9Xk66.mjs → seed-C3P_T_Eh.mjs} +28 -9
  58. package/dist/seed-C3P_T_Eh.mjs.map +1 -0
  59. package/dist/service-B3OiWCYg.mjs +1 -0
  60. package/dist/service-B4Gh_bbL.mjs +2 -0
  61. package/dist/service-B4Gh_bbL.mjs.map +1 -0
  62. package/dist/service-eM7Fd8zS.mjs +7 -0
  63. package/dist/service-eM7Fd8zS.mjs.map +1 -0
  64. package/dist/tailordb-ddl-Fgm2cNvT.mjs +7 -0
  65. package/dist/tailordb-ddl-Fgm2cNvT.mjs.map +1 -0
  66. package/dist/utils/test/index.d.mts +7 -5
  67. package/dist/utils/test/index.mjs +1 -1
  68. package/dist/utils/test/index.mjs.map +1 -1
  69. package/dist/vitest/index.mjs +1 -1
  70. package/dist/vitest/index.mjs.map +1 -1
  71. package/dist/vitest/mocks/tailordb-pglite.d.mts +5 -4
  72. package/docs/cli/tailordb.md +9 -9
  73. package/docs/cli/workspace.md +133 -20
  74. package/docs/cli-reference.md +101 -28
  75. package/docs/plugin/custom.md +36 -4
  76. package/docs/services/tailordb-migration.md +31 -28
  77. package/docs/testing.md +23 -21
  78. package/package.json +11 -10
  79. package/dist/application-BpoMu4Af.mjs.map +0 -1
  80. package/dist/application-fIhVKX78.mjs +0 -1
  81. package/dist/crashreport-BN28xp5B.mjs +0 -42
  82. package/dist/crashreport-BN28xp5B.mjs.map +0 -1
  83. package/dist/crashreport-By23O2k-.mjs +0 -1
  84. package/dist/errors-BlX4gUw5.mjs +0 -4
  85. package/dist/errors-BlX4gUw5.mjs.map +0 -1
  86. package/dist/field-column-type-QMtF6lUp.mjs +0 -2
  87. package/dist/field-column-type-QMtF6lUp.mjs.map +0 -1
  88. package/dist/kysely-type-B-BOlXH7.mjs +0 -43
  89. package/dist/kysely-type-B-BOlXH7.mjs.map +0 -1
  90. package/dist/logger-CCjs1DuH.mjs.map +0 -1
  91. package/dist/node-builtins-TQHhwNzG.mjs +0 -2
  92. package/dist/node-builtins-TQHhwNzG.mjs.map +0 -1
  93. package/dist/register-ts-hook-CAUcxCt4.mjs +0 -711
  94. package/dist/register-ts-hook-CAUcxCt4.mjs.map +0 -1
  95. package/dist/schema-BTioi2dP.mjs +0 -2
  96. package/dist/schema-BTioi2dP.mjs.map +0 -1
  97. package/dist/seed-CCc9Xk66.mjs.map +0 -1
  98. package/dist/service-7SCy2bhm.mjs +0 -7
  99. package/dist/service-7SCy2bhm.mjs.map +0 -1
  100. package/dist/service-CWFQ8EVo.mjs +0 -1
  101. package/dist/service-CqbQXFDp.mjs +0 -2
  102. package/dist/service-CqbQXFDp.mjs.map +0 -1
@@ -29,10 +29,11 @@ interface CreatedClient {
29
29
  * PGlite instances are borrowed, never closed — close them yourself (e.g. in
30
30
  * `afterAll`).
31
31
  *
32
- * Create the tables a test needs up front with `CREATE TABLE` statements
33
- * matching the generated Kysely types. PGlite runs full PostgreSQL while
34
- * TailorDB supports a subset of it, so a statement passing here can still be
35
- * rejected by the platform.
32
+ * Create the tables a test needs up front: run the script `kyselyTypePlugin`
33
+ * writes when `pgliteSchemaPath` is set, or your own `CREATE TABLE`
34
+ * statements matching the generated Kysely types. PGlite runs full PostgreSQL
35
+ * while TailorDB supports a subset of it, so a statement passing here can
36
+ * still be rejected by the platform.
36
37
  *
37
38
  * Transactions on a shared instance are serialized: while one is open,
38
39
  * queries from other `getDB` instances on the same PGlite instance wait for
@@ -129,7 +129,7 @@ tailor tailordb migration generate [options]
129
129
  | `--namespace <NAMESPACE>` | - | Target TailorDB namespace for --data-only (required if multiple namespaces exist) | No | - | - |
130
130
  | `--rename <RENAME>` | - | Record a field, table, or nested member rename instead of remove + add (format: "Table.oldField:newField", "OldTable:NewTable", or "Table.field.oldMember:newMember"; repeatable). Renames require a migration script that copies the data. | No | - | - |
131
131
  | `--drop <DROP>` | - | Confirm that a removed field, table, or nested member is a genuine removal, not a rename (format: "Table.field", "Table", or "Table.field.member"; repeatable). Required in non-interactive runs for a removal with rename candidates. | No | - | - |
132
- | `--expand-contract <EXPAND_CONTRACT>` | - | Convert a field type through a temporary field (format: "Table.field"; repeatable). Generates two migrations. | No | - | - |
132
+ | `--expand-contract <EXPAND_CONTRACT>` | - | Convert a field type, or a single value into an array, through a temporary field (format: "Table.field"; repeatable). Generates two migrations. | No | - | - |
133
133
 
134
134
  See [Global Options](../cli-reference.md#global-options) for options available to all commands.
135
135
 
@@ -177,19 +177,19 @@ tailor tailordb migration script [options] <number>
177
177
 
178
178
  **Options**
179
179
 
180
- | Option | Alias | Description | Required | Default | Env |
181
- | ------------------------- | ----- | ----------------------------------------------------------------------------------------------------- | -------- | -------------------- | -------------------- |
182
- | `--config <CONFIG>` | `-c` | Path to Tailor config file | No | `"tailor.config.ts"` | `TAILOR_CONFIG_PATH` |
183
- | `--namespace <NAMESPACE>` | `-n` | Target TailorDB namespace (required if multiple namespaces exist) | No | - | - |
184
- | `--no-script` | - | Record that this migration intentionally runs without a migration script (requires --reason) | No | - | - |
185
- | `--reason <REASON>` | - | Reason why no migration script is needed (used with --no-script) | No | - | - |
186
- | `--with-test` | - | Also add a migrate.test.ts unit-test scaffold; when migrate.ts already exists, only the test is added | No | - | - |
180
+ | Option | Alias | Description | Required | Default | Env |
181
+ | ------------------------- | ----- | -------------------------------------------------------------------------------------------- | -------- | -------------------- | -------------------- |
182
+ | `--config <CONFIG>` | `-c` | Path to Tailor config file | No | `"tailor.config.ts"` | `TAILOR_CONFIG_PATH` |
183
+ | `--namespace <NAMESPACE>` | `-n` | Target TailorDB namespace (required if multiple namespaces exist) | No | - | - |
184
+ | `--no-script` | - | Record that this migration intentionally runs without a migration script (requires --reason) | No | - | - |
185
+ | `--reason <REASON>` | - | Reason why no migration script is needed (used with --no-script) | No | - | - |
186
+ | `--with-test` | - | Also add the migrate.test.ts and migrate.pglite.test.ts scaffolds | No | - | - |
187
187
 
188
188
  See [Global Options](../cli-reference.md#global-options) for options available to all commands.
189
189
 
190
190
  **Notes**
191
191
 
192
- When `migrate.ts` already exists, running the command clears a previously recorded `--no-script` acknowledgment.
192
+ When `migrate.ts` already exists, running the command clears a previously recorded `--no-script` acknowledgment, and `--with-test` adds only the tests that do not exist yet (writing `db.pglite.ts` if it is missing). `migrate.pglite.test.ts` is scaffolded only when `@electric-sql/pglite` is installed in the project.
193
193
 
194
194
  #### tailordb migration set
195
195
 
@@ -16,15 +16,17 @@ See [Global Options](../cli-reference.md#global-options) for options available t
16
16
 
17
17
  **Commands**
18
18
 
19
- | Command | Description |
20
- | ----------------------------------------- | ------------------------------------------- |
21
- | [`workspace app`](#workspace-app) | Manage workspace applications |
22
- | [`workspace create`](#workspace-create) | Create a new Tailor Platform workspace. |
23
- | [`workspace delete`](#workspace-delete) | Delete a Tailor Platform workspace. |
24
- | [`workspace get`](#workspace-get) | Show detailed information about a workspace |
25
- | [`workspace list`](#workspace-list) | List all Tailor Platform workspaces. |
26
- | [`workspace restore`](#workspace-restore) | Restore a deleted workspace |
27
- | [`workspace user`](#workspace-user) | Manage workspace users |
19
+ | Command | Description |
20
+ | ----------------------------------------- | ---------------------------------------------------------------------------------------------- |
21
+ | [`workspace app`](#workspace-app) | Manage workspace applications |
22
+ | [`workspace create`](#workspace-create) | Create a new Tailor Platform workspace. |
23
+ | [`workspace delete`](#workspace-delete) | Delete a Tailor Platform workspace. |
24
+ | [`workspace get`](#workspace-get) | Show detailed information about a workspace |
25
+ | [`workspace list`](#workspace-list) | List all Tailor Platform workspaces. |
26
+ | [`workspace prune`](#workspace-prune) | Delete stale temporary workspaces, by name and age or by the expiry each recorded at creation. |
27
+ | [`workspace restore`](#workspace-restore) | Restore a deleted workspace |
28
+ | [`workspace ttl`](#workspace-ttl) | Manage when a workspace becomes prunable. |
29
+ | [`workspace user`](#workspace-user) | Manage workspace users |
28
30
 
29
31
  ### workspace app
30
32
 
@@ -98,17 +100,18 @@ tailor workspace create [options]
98
100
 
99
101
  **Options**
100
102
 
101
- | Option | Alias | Description | Required | Default | Env |
102
- | ------------------------------------- | ----- | ----------------------------------------------------------------------------------------------------------- | -------- | --------- | --------------------------------- |
103
- | `--name <NAME>` | `-n` | Workspace name | Yes | - | - |
104
- | `--region <REGION>` | `-r` | Workspace region (us-west, asia-northeast) | Yes | - | - |
105
- | `--delete-protection` | `-d` | Enable delete protection | No | `false` | - |
106
- | `--organization-id <ORGANIZATION_ID>` | `-o` | Organization ID to workspace associate with | No | - | `TAILOR_PLATFORM_ORGANIZATION_ID` |
107
- | `--folder-id <FOLDER_ID>` | `-f` | Folder ID to workspace associate with | No | - | `TAILOR_PLATFORM_FOLDER_ID` |
108
- | `--profile-name <PROFILE_NAME>` | `-p` | Profile name to create | No | - | - |
109
- | `--profile <PROFILE>` | - | Workspace profile used for authentication and Platform selection | No | - | `TAILOR_PLATFORM_PROFILE` |
110
- | `--profile-user <PROFILE_USER>` | - | User email address or machine user client ID for the profile (defaults to current user) | No | - | - |
111
- | `--permission <PERMISSION>` | - | Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active. | No | `"write"` | - |
103
+ | Option | Alias | Description | Required | Default | Env |
104
+ | ------------------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------- | --------------------------------- |
105
+ | `--name <NAME>` | `-n` | Workspace name | Yes | - | - |
106
+ | `--region <REGION>` | `-r` | Workspace region (us-west, asia-northeast) | Yes | - | - |
107
+ | `--delete-protection` | `-d` | Enable delete protection | No | `false` | - |
108
+ | `--organization-id <ORGANIZATION_ID>` | `-o` | Organization ID to workspace associate with | No | - | `TAILOR_PLATFORM_ORGANIZATION_ID` |
109
+ | `--folder-id <FOLDER_ID>` | `-f` | Folder ID to workspace associate with | No | - | `TAILOR_PLATFORM_FOLDER_ID` |
110
+ | `--ttl <TTL>` | - | Record on the workspace itself when it becomes prunable, such as 30m, 24h, or 7d. `workspace prune --expired` deletes it once that has passed | No | - | - |
111
+ | `--profile-name <PROFILE_NAME>` | `-p` | Profile name to create | No | - | - |
112
+ | `--profile <PROFILE>` | - | Workspace profile used for authentication and Platform selection | No | - | `TAILOR_PLATFORM_PROFILE` |
113
+ | `--profile-user <PROFILE_USER>` | - | User email address or machine user client ID for the profile (defaults to current user) | No | - | - |
114
+ | `--permission <PERMISSION>` | - | Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active. | No | `"write"` | - |
112
115
 
113
116
  See [Global Options](../cli-reference.md#global-options) for options available to all commands.
114
117
 
@@ -170,6 +173,48 @@ tailor workspace list [options]
170
173
 
171
174
  See [Global Options](../cli-reference.md#global-options) for options available to all commands.
172
175
 
176
+ ### workspace prune
177
+
178
+ Delete stale temporary workspaces, by name and age or by the expiry each recorded at creation.
179
+
180
+ **Usage**
181
+
182
+ ```
183
+ tailor workspace prune [options]
184
+ ```
185
+
186
+ **Options**
187
+
188
+ | Option | Alias | Description | Required | Default | Env |
189
+ | --------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | ------------------------- |
190
+ | `--name <NAME>` | - | Select workspaces whose whole name matches this regular expression (repeatable) | No | - | - |
191
+ | `--older-than <OLDER_THAN>` | - | Minimum age since creation, such as 30m, 24h, or 7d. 0s disables the age check and requires a location. Required unless --expired is given | No | - | - |
192
+ | `--expired` | - | Select workspaces whose own --ttl expiry has passed, instead of by name and age. Requires --organization-root, --folder-id, or --personal | No | `false` | - |
193
+ | `--organization-root <ORGANIZATION_ID>` | - | Consider the workspaces directly under this organization, excluding those in its folders (repeatable) | No | - | - |
194
+ | `--folder-id <FOLDER_ID>` | - | Consider the workspaces in this folder (repeatable) | No | - | - |
195
+ | `--personal` | - | Consider the workspaces belonging to no organization and no folder | No | `false` | - |
196
+ | `--exclude <EXCLUDE>` | - | Keep a workspace with this exact name even when it matches (repeatable) | No | - | - |
197
+ | `--limit <LIMIT>` | - | Abort when more workspaces match than this, without deleting anything. 0 removes the cap | No | `20` | - |
198
+ | `--dry-run` | - | List the workspaces that would be deleted without deleting them | No | `false` | - |
199
+ | `--profile <PROFILE>` | - | Workspace profile used for authentication and Platform selection | No | - | `TAILOR_PLATFORM_PROFILE` |
200
+ | `--yes` | `-y` | Skip confirmation prompts | No | `false` | - |
201
+
202
+ See [Global Options](../cli-reference.md#global-options) for options available to all commands.
203
+
204
+ **Notes**
205
+
206
+ Use this to reclaim workspaces left behind by CI runs, preview deployments, or interrupted local test runs. A workspace is deleted only when its whole name matches a --name pattern, it was created at least --older-than ago, and it is not excluded, delete-protected, or outside the requested locations. Run with --dry-run first to see what would be deleted.
207
+
208
+ With --expired the workspaces select themselves instead: each one is deleted only once the --ttl expiry it recorded at creation has passed, so callers need no --name or --older-than. A workspace that records no expiry is never deleted this way, and neither is one whose recorded expiry cannot be read. Because that expiry is recorded on the workspace rather than derived from its name, anything able to write the workspace's metadata can bring its deletion forward -- and writing a workspace's metadata is a lesser permission than deleting it. --expired therefore requires at least one location, and --name still applies on top.
209
+
210
+ Every workspace lives in exactly one location, and the location options name them explicitly: --organization-root selects the workspaces directly under an organization and none inside its folders, --folder-id selects the workspaces in one folder, and --personal selects the workspaces belonging to no organization and no folder. Each option can be given more than once, they combine as a union, and none of them is read from the environment -- a sweep covers exactly the locations spelled out on the command line. Selecting --personal is a deliberate choice to accept, for every organization-less workspace visible to this login, the expiry that anyone able to write a workspace's metadata may have recorded. Without any location option, a --name / --older-than sweep considers every visible workspace.
211
+
212
+ Restoring a workspace does not clear its recorded expiry, so a workspace restored after expiring is deleted again by the next --expired run. Restore it, then run `workspace ttl set` or `workspace ttl clear` before the next run — or keep it out of that run with --exclude.
213
+
214
+ Safety guards: the command aborts without deleting anything when more workspaces match than --limit allows (--dry-run still lists them all), both --expired and --older-than 0s (no age check) are only accepted together with at least one location option, and a location option that resolves to an empty value (an unset CI secret) is rejected instead of silently widening the sweep -- even when another location is given alongside it. Each workspace is re-read immediately before it is deleted and skipped when it no longer matches the name, location, exclusion, or delete-protection criteria that selected it. Unlike `workspace delete`, a single confirmation covers every listed candidate; pass --yes to skip it in CI. Deleted workspaces can be restored with `workspace restore` for a limited time.
215
+
216
+ Only workspaces visible to the current login (or the machine user in CI) are considered.
217
+
173
218
  ### workspace restore
174
219
 
175
220
  Restore a deleted workspace
@@ -189,6 +234,74 @@ tailor workspace restore [options]
189
234
 
190
235
  See [Global Options](../cli-reference.md#global-options) for options available to all commands.
191
236
 
237
+ ### workspace ttl
238
+
239
+ Manage when a workspace becomes prunable.
240
+
241
+ **Usage**
242
+
243
+ ```
244
+ tailor workspace ttl [command]
245
+ ```
246
+
247
+ See [Global Options](../cli-reference.md#global-options) for options available to all commands.
248
+
249
+ **Commands**
250
+
251
+ | Command | Description |
252
+ | --------------------------------------------- | ---------------------------------------------------------------------------------- |
253
+ | [`workspace ttl set`](#workspace-ttl-set) | Record when a workspace becomes prunable, replacing any expiry it already records. |
254
+ | [`workspace ttl clear`](#workspace-ttl-clear) | Drop a workspace's recorded prune expiry. |
255
+
256
+ #### workspace ttl clear
257
+
258
+ Drop a workspace's recorded prune expiry.
259
+
260
+ **Usage**
261
+
262
+ ```
263
+ tailor workspace ttl clear [options]
264
+ ```
265
+
266
+ **Options**
267
+
268
+ | Option | Alias | Description | Required | Default | Env |
269
+ | ------------------------------- | ----- | ----------------- | -------- | ------- | ------------------------------ |
270
+ | `--workspace-id <WORKSPACE_ID>` | `-w` | Workspace ID | No | - | `TAILOR_PLATFORM_WORKSPACE_ID` |
271
+ | `--profile <PROFILE>` | `-p` | Workspace profile | No | - | `TAILOR_PLATFORM_PROFILE` |
272
+
273
+ See [Global Options](../cli-reference.md#global-options) for options available to all commands.
274
+
275
+ **Notes**
276
+
277
+ A workspace recording no expiry is never deleted by `workspace prune --expired`. Clearing an expiry the workspace does not record succeeds without changing anything.
278
+
279
+ #### workspace ttl set
280
+
281
+ Record when a workspace becomes prunable, replacing any expiry it already records.
282
+
283
+ **Usage**
284
+
285
+ ```
286
+ tailor workspace ttl set [options]
287
+ ```
288
+
289
+ **Options**
290
+
291
+ | Option | Alias | Description | Required | Default | Env |
292
+ | ------------------------------- | ----- | --------------------------------------------------------------------------- | -------- | ------- | ------------------------------ |
293
+ | `--workspace-id <WORKSPACE_ID>` | `-w` | Workspace ID | No | - | `TAILOR_PLATFORM_WORKSPACE_ID` |
294
+ | `--profile <PROFILE>` | `-p` | Workspace profile | No | - | `TAILOR_PLATFORM_PROFILE` |
295
+ | `--ttl <TTL>` | - | Time from now until the workspace becomes prunable, such as 30m, 24h, or 7d | Yes | - | - |
296
+
297
+ See [Global Options](../cli-reference.md#global-options) for options available to all commands.
298
+
299
+ **Notes**
300
+
301
+ The expiry runs from now, not from when the workspace was created, so `--ttl 24h` always leaves a full day regardless of the workspace's age. Use this to give a restored workspace a new expiry, or to record one after `workspace create --ttl` failed to.
302
+
303
+ This is not an auto-delete timer: it only makes the workspace eligible for `workspace prune --expired`, which still honors delete protection and its own filters.
304
+
192
305
  ### workspace user
193
306
 
194
307
  Manage workspace users
@@ -19,6 +19,12 @@ tailor <command> [options]
19
19
  | `--verbose` | - | Enable verbose logging | No | `false` |
20
20
  | `--json` | `-j` | Output as JSON | No | `false` |
21
21
 
22
+ ### Progress and Detailed Logs
23
+
24
+ `generate` and `deploy` show service progress and failures on stderr. Pass `--verbose`
25
+ to include individual loaded files, plugin table changes, and generated file paths.
26
+ Generation reports completion for each plugin that finishes processing its output files.
27
+
22
28
  ### JSON Output
23
29
 
24
30
  For commands that return structured results, passing `--json` writes one parseable JSON document
@@ -29,10 +35,68 @@ Commands that only perform side effects and do not define a structured result ma
29
35
  even when `--json` is passed.
30
36
 
31
37
  Errors, warnings, progress, and diagnostic messages are written to stderr. After argument parsing,
32
- a command failure under `--json` emits a JSON error envelope to stderr. CLI errors include a stable
33
- `error.code` and may include structured `error.next` and `error.context` fields for automated
34
- recovery. Diagnostic lines may precede the error envelope, and stdout is not guaranteed to contain
35
- an error object.
38
+ a command failure under `--json` emits a JSON error envelope to stderr. Failures you can act on — an
39
+ invalid or missing option, a resource that does not exist, an invalid configuration, or an unmet
40
+ precondition — carry a stable `error.code` such as `PROFILE_NOT_FOUND`, `TAILORDB_NAMESPACE_NOT_FOUND`,
41
+ or `MIGRATION_SCRIPT_REQUIRED`. Where a remediation exists, the envelope also includes
42
+ `error.suggestion`, `error.help` (the `--help` invocation for the failing command), `error.next` (a
43
+ runnable command), or `error.context`. `UNEXPECTED_ERROR` marks failures without a dedicated code,
44
+ including SDK-internal errors. Diagnostic lines may precede the error envelope, and stdout is not
45
+ guaranteed to contain an error object.
46
+
47
+ Authentication failures distinguish missing credentials (`AUTH_TOKEN_NOT_FOUND`), a missing saved
48
+ user (`AUTH_USER_NOT_FOUND`), an expired token (`AUTH_TOKEN_EXPIRED`), and a failed token refresh
49
+ (`AUTH_TOKEN_REFRESH_FAILED`). Login recovery preserves the selected profile. For saved identities,
50
+ the next step opens login help so you can reuse the original browser or machine-user login method. Permission and
51
+ connection failures include guidance in `error.suggestion`; API failures also identify the operation
52
+ and affected resources in `error.context`.
53
+
54
+ If deployment fails and saving recovery metadata also fails, `DEPLOY_METADATA_RECOVERY_FAILED`
55
+ includes separate `error.context.apply` and `error.context.recovery` errors, each retaining its code
56
+ and available recovery information. Inspect the current resource state before retrying: some writes
57
+ may have completed. `--verbose --json` additionally includes stack traces for both causes.
58
+
59
+ Generation hook failures use `PLUGIN_GENERATION_FAILED`. The error includes the failed hook
60
+ and each failing plugin's ID and error in `error.context.failures`; successful plugins are
61
+ excluded from that list.
62
+
63
+ ### Verbose Output
64
+
65
+ Use `--verbose` to include debug diagnostics and error stack traces. `DEBUG=true` or
66
+ `RUNNER_DEBUG=1` also enables verbose output, including stacks in JSON errors. GitHub Actions
67
+ sets `RUNNER_DEBUG=1` when debug logging is enabled, so the same command automatically includes
68
+ these details in a debug run. These settings do not enable JSON output; pass `--json` separately.
69
+
70
+ Capture the original failure's stderr and exit code before retrying. Argument parsing and failures
71
+ before the CLI starts may produce plain text even with `--json`. A failed deployment may have
72
+ already applied changes, so inspect its output before deciding to run it again.
73
+
74
+ ### GitHub Actions Annotations
75
+
76
+ When `GITHUB_ACTIONS` is exactly `true`, a command that ends in failure also writes one
77
+ `::error::` workflow command to stderr, so the failure appears as an annotation on the run
78
+ instead of only inside the scrolled log. The annotation repeats what the CLI already prints:
79
+ its `title` is the error code (`AUTH_TOKEN_NOT_FOUND`, `PLUGIN_GENERATION_FAILED`, ...), and its
80
+ body carries the same details, suggestion, and next action. Colors are stripped and newlines are
81
+ encoded, so the annotation is a single line.
82
+
83
+ Exactly one annotation is written per failed command, and only for the failure that ends it.
84
+ Warnings and individually reported problems stay plain stderr output. The bundled CLI plugins
85
+ (`seed`, `setup`, `tailordb-erd`) annotate their failures the same way. A command that exits
86
+ without reporting through the CLI's error path, such as one relaying a failed remote execution,
87
+ writes no annotation.
88
+
89
+ Set `TAILOR_GITHUB_ACTIONS_ANNOTATIONS=false` (also `off`, `no`, or `0`) to turn annotations off.
90
+ Passing `--json` also suppresses them, so a workflow step that parses `--json` output gets only the
91
+ error envelope on stderr. The flag is honored even when the command fails during argument parsing,
92
+ before the envelope itself becomes available.
93
+
94
+ An annotation does not by itself fail a step: the step still fails on the CLI's exit code, which
95
+ is unchanged. Workflows that already echo their own `::error::` around the CLI keep working;
96
+ those messages describe the workflow's own checks, which can fail even when the CLI succeeds.
97
+
98
+ Annotations do not yet carry `file=`/`line=` source locations, and `generate` and `deploy` do not
99
+ group their per-service progress.
36
100
 
37
101
  ## Common Options
38
102
 
@@ -83,6 +147,7 @@ You can use environment variables to configure workspace and authentication:
83
147
  | `TAILOR_BUNDLE_CONCURRENCY` | Max concurrent bundle workers for `deploy` (resolvers/executors/workflows). Defaults to CPU count |
84
148
  | `TAILOR_APPLY_CONCURRENCY` | Max concurrent platform RPCs during `apply`/`deploy`. Defaults to 16 |
85
149
  | `VISUAL` / `EDITOR` | Preferred editor for commands that open files (e.g., `vim`, `code`, `nano`) |
150
+ | `TAILOR_GITHUB_ACTIONS_ANNOTATIONS` | GitHub Actions failure annotations: `on` (default) or `off` |
86
151
  | `TAILOR_CRASH_REPORTS_LOCAL` | Local crash log writing: `on` (default) or `off` |
87
152
  | `TAILOR_CRASH_REPORTS_REMOTE` | Automatic crash report submission: `off` (default) or `on` |
88
153
 
@@ -145,9 +210,13 @@ Resolution rules:
145
210
  - **Lookup order:** the project's `node_modules/.bin` (nearest first, walking up from the current
146
211
  directory), then your `PATH`. So a plugin installed as a project dev-dependency takes precedence over a
147
212
  globally installed one.
148
- - **Place global flags after the plugin command.** Only the arguments following the plugin name are
149
- forwarded; a global flag placed before it (e.g. `tailor --json tailordb erd export`) is consumed by
150
- the host CLI and does not reach the plugin. Write `tailor tailordb erd export --json` instead.
213
+ - **Global flags reach the plugin from either side.** `tailor --json tailordb erd export` and
214
+ `tailor tailordb erd export --json` both forward `--json`, and likewise `--verbose` and the
215
+ `--env-file` options. A flag typed before the plugin name is consumed by the host CLI first and
216
+ then forwarded, so when the same flag appears on both sides the later one wins. A flag the host
217
+ does not define — including one only some commands declare, such as `--profile` — still has to be
218
+ typed after the plugin's own subcommand. `--help` and `--version` are answered by the host CLI and
219
+ never dispatch a plugin.
151
220
 
152
221
  Because resolution is based on `node_modules/.bin` and `PATH`, any package manager that populates
153
222
  `node_modules/.bin` works for project-local plugins — npm, pnpm (its content-addressable store is
@@ -265,27 +334,31 @@ Commands for managing organizations and folders.
265
334
 
266
335
  Commands for managing workspaces and profiles.
267
336
 
268
- | Command | Description |
269
- | ----------------------------------------------------------------- | ---------------------------------------------------------- |
270
- | [workspace](./cli/workspace.md#workspace) | Manage Tailor Platform workspaces. |
271
- | [workspace app](./cli/workspace.md#workspace-app) | Manage workspace applications |
272
- | [workspace app health](./cli/workspace.md#workspace-app-health) | Check application schema health |
273
- | [workspace app list](./cli/workspace.md#workspace-app-list) | List applications in a workspace |
274
- | [workspace create](./cli/workspace.md#workspace-create) | Create a new Tailor Platform workspace. |
275
- | [workspace delete](./cli/workspace.md#workspace-delete) | Delete a Tailor Platform workspace. |
276
- | [workspace get](./cli/workspace.md#workspace-get) | Show detailed information about a workspace |
277
- | [workspace list](./cli/workspace.md#workspace-list) | List all Tailor Platform workspaces. |
278
- | [workspace restore](./cli/workspace.md#workspace-restore) | Restore a deleted workspace |
279
- | [workspace user](./cli/workspace.md#workspace-user) | Manage workspace users |
280
- | [workspace user invite](./cli/workspace.md#workspace-user-invite) | Invite a user to a workspace |
281
- | [workspace user list](./cli/workspace.md#workspace-user-list) | List users in a workspace |
282
- | [workspace user remove](./cli/workspace.md#workspace-user-remove) | Remove a user from a workspace |
283
- | [workspace user update](./cli/workspace.md#workspace-user-update) | Update a user's role in a workspace |
284
- | [profile](./cli/workspace.md#profile) | Manage workspace profiles (user + workspace combinations). |
285
- | [profile create](./cli/workspace.md#profile-create) | Create a new profile. |
286
- | [profile delete](./cli/workspace.md#profile-delete) | Delete a profile. |
287
- | [profile list](./cli/workspace.md#profile-list) | List all profiles. |
288
- | [profile update](./cli/workspace.md#profile-update) | Update profile properties. |
337
+ | Command | Description |
338
+ | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
339
+ | [workspace](./cli/workspace.md#workspace) | Manage Tailor Platform workspaces. |
340
+ | [workspace app](./cli/workspace.md#workspace-app) | Manage workspace applications |
341
+ | [workspace app health](./cli/workspace.md#workspace-app-health) | Check application schema health |
342
+ | [workspace app list](./cli/workspace.md#workspace-app-list) | List applications in a workspace |
343
+ | [workspace create](./cli/workspace.md#workspace-create) | Create a new Tailor Platform workspace. |
344
+ | [workspace delete](./cli/workspace.md#workspace-delete) | Delete a Tailor Platform workspace. |
345
+ | [workspace get](./cli/workspace.md#workspace-get) | Show detailed information about a workspace |
346
+ | [workspace list](./cli/workspace.md#workspace-list) | List all Tailor Platform workspaces. |
347
+ | [workspace prune](./cli/workspace.md#workspace-prune) | Delete stale temporary workspaces, by name and age or by the expiry each recorded at creation. |
348
+ | [workspace restore](./cli/workspace.md#workspace-restore) | Restore a deleted workspace |
349
+ | [workspace ttl](./cli/workspace.md#workspace-ttl) | Manage when a workspace becomes prunable. |
350
+ | [workspace ttl clear](./cli/workspace.md#workspace-ttl-clear) | Drop a workspace's recorded prune expiry. |
351
+ | [workspace ttl set](./cli/workspace.md#workspace-ttl-set) | Record when a workspace becomes prunable, replacing any expiry it already records. |
352
+ | [workspace user](./cli/workspace.md#workspace-user) | Manage workspace users |
353
+ | [workspace user invite](./cli/workspace.md#workspace-user-invite) | Invite a user to a workspace |
354
+ | [workspace user list](./cli/workspace.md#workspace-user-list) | List users in a workspace |
355
+ | [workspace user remove](./cli/workspace.md#workspace-user-remove) | Remove a user from a workspace |
356
+ | [workspace user update](./cli/workspace.md#workspace-user-update) | Update a user's role in a workspace |
357
+ | [profile](./cli/workspace.md#profile) | Manage workspace profiles (user + workspace combinations). |
358
+ | [profile create](./cli/workspace.md#profile-create) | Create a new profile. |
359
+ | [profile delete](./cli/workspace.md#profile-delete) | Delete a profile. |
360
+ | [profile list](./cli/workspace.md#profile-list) | List all profiles. |
361
+ | [profile update](./cli/workspace.md#profile-update) | Update profile properties. |
289
362
 
290
363
  ### [Auth Resource Commands](./cli/auth.md)
291
364
 
@@ -324,6 +324,37 @@ const AuditLog = await getGeneratedTable(configPath, "@example/audit-log", null,
324
324
  5. Caches the result to avoid redundant processing
325
325
  6. Returns the generated table matching the specified kind
326
326
 
327
+ ## getExtendedTable Helper
328
+
329
+ A table that plugins are attached to gains the fields those plugins return in `extends.fields`, but only in the table `tailor generate` registers — the object exported from the table's source file stays as written. `getExtendedTable()` returns the table with every plugin-added field applied, so tooling that reads the table at runtime sees the same fields `tailor generate` does.
330
+
331
+ ```typescript
332
+ import { join } from "node:path";
333
+ import { getExtendedTable } from "@tailor-platform/sdk/plugin";
334
+ import { customer } from "./tailordb/customer";
335
+
336
+ const configPath = join(import.meta.dirname, "./tailor.config.ts");
337
+
338
+ const extendedCustomer = await getExtendedTable(configPath, customer);
339
+ extendedCustomer.fields.deletedAt; // added by a plugin attached with .plugin()
340
+ ```
341
+
342
+ **Parameters:**
343
+
344
+ - `configPath`: Path to `tailor.config.ts` (absolute or relative to cwd)
345
+ - `sourceTable`: The TailorDB table as exported from its source file
346
+
347
+ **How it works:**
348
+
349
+ 1. Returns `sourceTable` itself when no plugin is attached to it
350
+ 2. Loads and caches the config from the given path
351
+ 3. Auto-resolves the namespace from config
352
+ 4. Calls each attached plugin's `onTableLoaded()` in the order of the `.plugin()` calls, each seeing the fields the plugins before it added
353
+ 5. Caches the result per config path and table
354
+ 6. Returns a new table with the added fields; `sourceTable` is not changed
355
+
356
+ The seed schema files `tailor generate` writes for tables with plugins attached use this helper, so `tailor seed validate` checks plugin-added fields like the table's own.
357
+
327
358
  ## Examples
328
359
 
329
360
  ### Definition-time Plugin (Soft Delete)
@@ -646,10 +677,11 @@ of whether `.files()` or `.plugin()` was called first. `tailor generate` also re
646
677
  collision at runtime, as a backstop for any case a table's static type doesn't otherwise catch.
647
678
 
648
679
  This only affects the table's static type. The corresponding field exists on the table's
649
- generated schema, and on the table object's own `fields`, only after `tailor generate` actually
650
- applies `extends.fields`. Before that, reading an injected field directly off the table
651
- (`table.fields.status`) returns `undefined`, and `pickFields(["status"])` throws — call these only
652
- with the table's originally declared fields, not ones a plugin injects.
680
+ generated schema, and on the table `tailor generate` registers, only once `extends.fields` is
681
+ applied; the table object exported from the source file never gains it. Reading an injected field
682
+ directly off that object (`table.fields.status`) returns `undefined`, and `pickFields(["status"])`
683
+ throws — call these only with the table's originally declared fields, or load the table with
684
+ [`getExtendedTable()`](#getextendedtable-helper) first.
653
685
 
654
686
  To keep the declared type and the runtime implementation in sync, give `Plugin`'s optional third
655
687
  type parameter the same shape and use it inside `onTableLoaded`: