@tailor-platform/sdk 2.0.0-next.2 → 2.0.0-next.3

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 (410) hide show
  1. package/CHANGELOG.md +466 -10
  2. package/README.md +9 -9
  3. package/agent-skills/{tailor-sdk → tailor}/SKILL.md +3 -1
  4. package/dist/aigateway-B3oZZE6v.mjs +15 -0
  5. package/dist/aigateway-B3oZZE6v.mjs.map +1 -0
  6. package/dist/application-D2hSK1ZL.mjs +3 -0
  7. package/dist/application-eHp9jXld.mjs +6393 -0
  8. package/dist/application-eHp9jXld.mjs.map +1 -0
  9. package/dist/{authconnection-D2MhtTN5.mjs → authconnection-BGQM8FZI.mjs} +2 -2
  10. package/dist/authconnection-BGQM8FZI.mjs.map +1 -0
  11. package/dist/cli/cache/bundle-cache.d.mts +1 -0
  12. package/dist/cli/cache/store.d.mts +1 -0
  13. package/dist/cli/cache/types.d.mts +1 -0
  14. package/dist/cli/commands/api/api-call.d.mts +19 -0
  15. package/dist/cli/commands/api/index.d.mts +3 -0
  16. package/dist/cli/commands/deploy/aigateway.d.mts +1 -0
  17. package/dist/cli/commands/deploy/application.d.mts +1 -0
  18. package/dist/cli/commands/deploy/apply-phases.d.mts +1 -0
  19. package/dist/cli/commands/deploy/auth.d.mts +1 -0
  20. package/dist/cli/commands/deploy/deploy.d.mts +24 -0
  21. package/dist/cli/commands/deploy/executor.d.mts +1 -0
  22. package/dist/cli/commands/deploy/function-registry-types.d.mts +12 -0
  23. package/dist/cli/commands/deploy/function-registry.d.mts +2 -0
  24. package/dist/cli/commands/deploy/idp.d.mts +1 -0
  25. package/dist/cli/commands/deploy/resolver.d.mts +1 -0
  26. package/dist/cli/commands/deploy/secret-manager.d.mts +1 -0
  27. package/dist/cli/commands/deploy/staticwebsite.d.mts +1 -0
  28. package/dist/cli/commands/deploy/tailordb/index.d.mts +1 -0
  29. package/dist/cli/commands/deploy/types.d.mts +1 -0
  30. package/dist/cli/commands/deploy/workflow.d.mts +1 -0
  31. package/dist/cli/commands/executor/get.d.mts +29 -0
  32. package/dist/cli/commands/executor/jobs.d.mts +123 -0
  33. package/dist/cli/commands/executor/list.d.mts +19 -0
  34. package/dist/cli/commands/executor/transform.d.mts +41 -0
  35. package/dist/cli/commands/executor/trigger.d.mts +41 -0
  36. package/dist/cli/commands/executor/webhook.d.mts +22 -0
  37. package/dist/cli/commands/function/get.d.mts +18 -0
  38. package/dist/cli/commands/function/list.d.mts +22 -0
  39. package/dist/cli/commands/function/transform.d.mts +11 -0
  40. package/dist/cli/commands/generate/options.d.mts +7 -0
  41. package/dist/cli/commands/generate/seed/bundler.d.mts +26 -0
  42. package/dist/cli/commands/generate/service.d.mts +11 -0
  43. package/dist/cli/commands/machineuser/list.d.mts +27 -0
  44. package/dist/cli/commands/machineuser/token.d.mts +22 -0
  45. package/dist/cli/commands/oauth2client/get.d.mts +18 -0
  46. package/dist/cli/commands/oauth2client/list.d.mts +20 -0
  47. package/dist/cli/commands/oauth2client/transform.d.mts +20 -0
  48. package/dist/cli/commands/organization/folder/create.d.mts +18 -0
  49. package/dist/cli/commands/organization/folder/delete.d.mts +16 -0
  50. package/dist/cli/commands/organization/folder/get.d.mts +17 -0
  51. package/dist/cli/commands/organization/folder/list.d.mts +22 -0
  52. package/dist/cli/commands/organization/folder/update.d.mts +18 -0
  53. package/dist/cli/commands/organization/get.d.mts +16 -0
  54. package/dist/cli/commands/organization/list.d.mts +15 -0
  55. package/dist/cli/commands/organization/transform.d.mts +27 -0
  56. package/dist/cli/commands/organization/tree.d.mts +25 -0
  57. package/dist/cli/commands/organization/update.d.mts +17 -0
  58. package/dist/cli/commands/remove.d.mts +16 -0
  59. package/dist/cli/commands/show.d.mts +34 -0
  60. package/dist/cli/commands/tailordb/migrate/bundler.d.mts +28 -0
  61. package/dist/cli/commands/tailordb/migrate/config.d.mts +19 -0
  62. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +223 -0
  63. package/dist/cli/commands/tailordb/migrate/generate.d.mts +17 -0
  64. package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +202 -0
  65. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +98 -0
  66. package/dist/cli/commands/tailordb/migrate/types.d.mts +7 -0
  67. package/dist/cli/commands/tailordb/truncate.d.mts +19 -0
  68. package/dist/cli/commands/workflow/executions.d.mts +75 -0
  69. package/dist/cli/commands/workflow/get.d.mts +29 -0
  70. package/dist/cli/commands/workflow/list.d.mts +19 -0
  71. package/dist/cli/commands/workflow/resume.d.mts +23 -0
  72. package/dist/cli/commands/workflow/start.d.mts +61 -0
  73. package/dist/cli/commands/workflow/status.d.mts +5 -0
  74. package/dist/cli/commands/workflow/transform.d.mts +33 -0
  75. package/dist/cli/commands/workflow/wait.d.mts +16 -0
  76. package/dist/cli/commands/workflow/waiter.d.mts +23 -0
  77. package/dist/cli/commands/workspace/app/health.d.mts +18 -0
  78. package/dist/cli/commands/workspace/app/list.d.mts +22 -0
  79. package/dist/cli/commands/workspace/app/transform.d.mts +18 -0
  80. package/dist/cli/commands/workspace/create.d.mts +25 -0
  81. package/dist/cli/commands/workspace/delete.d.mts +15 -0
  82. package/dist/cli/commands/workspace/get.d.mts +17 -0
  83. package/dist/cli/commands/workspace/list.d.mts +17 -0
  84. package/dist/cli/commands/workspace/restore.d.mts +15 -0
  85. package/dist/cli/commands/workspace/transform.d.mts +16 -0
  86. package/dist/cli/commands/workspace/user/invite.d.mts +22 -0
  87. package/dist/cli/commands/workspace/user/list.d.mts +22 -0
  88. package/dist/cli/commands/workspace/user/remove.d.mts +17 -0
  89. package/dist/cli/commands/workspace/user/transform.d.mts +8 -0
  90. package/dist/cli/commands/workspace/user/update.d.mts +22 -0
  91. package/dist/cli/erd-viewer-assets/app.js +279 -36
  92. package/dist/cli/erd-viewer-assets/index.html +4 -0
  93. package/dist/cli/erd-viewer-assets/styles.css +252 -5
  94. package/dist/cli/index.d.mts +18 -3
  95. package/dist/cli/index.mjs +2136 -519
  96. package/dist/cli/index.mjs.map +1 -1
  97. package/dist/cli/lib.d.mts +81 -21107
  98. package/dist/cli/lib.mjs +6 -9
  99. package/dist/cli/lib.mjs.map +1 -1
  100. package/dist/cli/query/index.d.mts +35 -0
  101. package/dist/cli/services/application.d.mts +1 -0
  102. package/dist/cli/services/auth/service.d.mts +1 -0
  103. package/dist/cli/services/executor/service.d.mts +1 -0
  104. package/dist/cli/services/http-adapter/bundler.d.mts +1 -0
  105. package/dist/cli/services/http-adapter/service.d.mts +1 -0
  106. package/dist/cli/services/resolver/service.d.mts +1 -0
  107. package/dist/cli/services/tailordb/service.d.mts +1 -0
  108. package/dist/cli/services/workflow/bundler.d.mts +1 -0
  109. package/dist/cli/services/workflow/service.d.mts +1 -0
  110. package/dist/cli/shared/args.d.mts +13 -0
  111. package/dist/cli/shared/client.d.mts +1273 -0
  112. package/dist/cli/shared/config-loader.d.mts +26 -0
  113. package/dist/cli/shared/context.d.mts +28 -0
  114. package/dist/cli/shared/script-executor.d.mts +75 -0
  115. package/dist/cli/shared/seed-chunker.d.mts +46 -0
  116. package/dist/cli/shared/trigger-context.d.mts +1 -0
  117. package/dist/cli/shared/type-generator.d.mts +20 -0
  118. package/dist/cli/ts-hook.d.mts +4 -0
  119. package/dist/cli/ts-hook.mjs +303 -0
  120. package/dist/completion/zsh-worker.zsh +1523 -1123
  121. package/dist/configure/config/index.d.mts +21 -0
  122. package/dist/configure/config/types.d.mts +103 -0
  123. package/dist/configure/index.d.mts +122 -9
  124. package/dist/configure/index.mjs +24 -21
  125. package/dist/configure/index.mjs.map +1 -1
  126. package/dist/configure/services/aigateway/index.d.mts +17 -0
  127. package/dist/configure/services/aigateway/types.d.mts +11 -0
  128. package/dist/configure/services/auth/index.d.mts +34 -0
  129. package/dist/configure/services/auth/types.d.mts +172 -0
  130. package/dist/configure/services/executor/executor.d.mts +81 -0
  131. package/dist/configure/services/executor/index.d.mts +8 -0
  132. package/dist/configure/services/executor/operation.d.mts +45 -0
  133. package/dist/configure/services/executor/trigger/event.d.mts +272 -0
  134. package/dist/configure/services/executor/trigger/index.d.mts +8 -0
  135. package/dist/configure/services/executor/trigger/schedule.d.mts +25 -0
  136. package/dist/configure/services/executor/trigger/webhook.d.mts +44 -0
  137. package/dist/configure/services/executor/types.d.mts +2 -0
  138. package/dist/configure/services/http-adapter/http-adapter.d.mts +125 -0
  139. package/dist/configure/services/http-adapter/index.d.mts +2 -0
  140. package/dist/configure/services/idp/index.d.mts +64 -0
  141. package/dist/configure/services/idp/permission.d.mts +80 -0
  142. package/dist/configure/services/idp/types.d.mts +20 -0
  143. package/dist/configure/services/index.d.mts +32 -0
  144. package/dist/configure/services/resolver/index.d.mts +5 -0
  145. package/dist/configure/services/resolver/resolver.d.mts +75 -0
  146. package/dist/configure/services/resolver/types.d.mts +6 -0
  147. package/dist/configure/services/secrets/index.d.mts +38 -0
  148. package/dist/configure/services/secrets/types.d.mts +11 -0
  149. package/dist/configure/services/staticwebsite/index.d.mts +19 -0
  150. package/dist/configure/services/staticwebsite/types.d.mts +11 -0
  151. package/dist/configure/services/tailordb/index.d.mts +4 -0
  152. package/dist/configure/services/tailordb/permission.d.mts +119 -0
  153. package/dist/configure/services/tailordb/schema.d.mts +456 -0
  154. package/dist/configure/services/tailordb/types.d.mts +149 -0
  155. package/dist/configure/services/workflow/index.d.mts +6 -0
  156. package/dist/configure/services/workflow/job.d.mts +79 -0
  157. package/dist/configure/services/workflow/types.d.mts +2 -0
  158. package/dist/configure/services/workflow/wait-point.d.mts +73 -0
  159. package/dist/configure/services/workflow/workflow.d.mts +54 -0
  160. package/dist/configure/types/aigateway-name.d.mts +12 -0
  161. package/dist/configure/types/connection-name.d.mts +12 -0
  162. package/dist/configure/types/field-runtime.d.mts +17 -0
  163. package/dist/configure/types/field.d.mts +9 -0
  164. package/dist/configure/types/field.types.d.mts +109 -0
  165. package/dist/configure/types/idp-name.d.mts +12 -0
  166. package/dist/configure/types/index.d.mts +4 -0
  167. package/dist/configure/types/machine-user.d.mts +2 -0
  168. package/dist/configure/types/type.d.mts +82 -0
  169. package/dist/context-Bd266-ru.mjs.map +1 -1
  170. package/dist/{crashreport-DFq-vsU0.mjs → crashreport-Cg-pqRJV.mjs} +6 -6
  171. package/dist/{crashreport-DFq-vsU0.mjs.map → crashreport-Cg-pqRJV.mjs.map} +1 -1
  172. package/dist/{crashreport-BMWcxeSE.mjs → crashreport-D3RjuSYb.mjs} +1 -1
  173. package/dist/enum-constants-j9QBF0cB.mjs.map +1 -1
  174. package/dist/{errors-Dtf2WPaW.mjs → errors-118wUDRr.mjs} +2 -2
  175. package/dist/errors-118wUDRr.mjs.map +1 -0
  176. package/dist/field-runtime-CAnh_oow.mjs +183 -0
  177. package/dist/field-runtime-CAnh_oow.mjs.map +1 -0
  178. package/dist/file-utils-CYZnO1pX.mjs.map +1 -1
  179. package/dist/{globals-Cf0sxIt8.mjs → globals-DhwZj0RB.mjs} +4 -46
  180. package/dist/globals-DhwZj0RB.mjs.map +1 -0
  181. package/dist/{idp-BDbK5gjm.mjs → idp-Bx25ZBdN.mjs} +9 -1
  182. package/dist/idp-Bx25ZBdN.mjs.map +1 -0
  183. package/dist/{interceptor-D-q1rvRl.mjs → interceptor-e33JtsC-.mjs} +2 -2
  184. package/dist/interceptor-e33JtsC-.mjs.map +1 -0
  185. package/dist/kysely/index.d.mts +2 -3
  186. package/dist/{kysely-type-DR8uzZTA.mjs → kysely-type-5eAChaHO.mjs} +4 -2
  187. package/dist/kysely-type-5eAChaHO.mjs.map +1 -0
  188. package/dist/{logger-CxF-Ex5d.mjs → logger-BEiZZ3qT.mjs} +3 -3
  189. package/dist/{logger-CxF-Ex5d.mjs.map → logger-BEiZZ3qT.mjs.map} +1 -1
  190. package/dist/parser/service/auth/index.d.mts +1 -0
  191. package/dist/parser/service/auth/schema.d.mts +1 -0
  192. package/dist/parser/service/http-adapter/index.d.mts +1 -0
  193. package/dist/parser/service/http-adapter/schema.d.mts +1 -0
  194. package/dist/parser/service/idp/types.d.mts +4 -0
  195. package/dist/parser/service/tailordb/types.d.mts +126 -0
  196. package/dist/platform-serialize-RoRtBS0v.mjs +46 -0
  197. package/dist/platform-serialize-RoRtBS0v.mjs.map +1 -0
  198. package/dist/plugin/builtin/enum-constants/index.d.mts +16 -1
  199. package/dist/plugin/builtin/file-utils/index.d.mts +16 -1
  200. package/dist/plugin/builtin/kysely-type/index.d.mts +16 -1
  201. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  202. package/dist/plugin/builtin/seed/index.d.mts +45 -1
  203. package/dist/plugin/builtin/seed/index.mjs +1 -1
  204. package/dist/plugin/get-generated-type.d.mts +17 -0
  205. package/dist/plugin/index.d.mts +3 -118
  206. package/dist/plugin/index.mjs +1 -1
  207. package/dist/plugin/index.mjs.map +1 -1
  208. package/dist/plugin/manager.d.mts +1 -0
  209. package/dist/plugin/types.d.mts +243 -0
  210. package/dist/plugin/with-context.d.mts +102 -0
  211. package/dist/{runtime-CY4JvrDj.mjs → register-ts-hook-ZpBacA07.mjs} +3426 -2041
  212. package/dist/register-ts-hook-ZpBacA07.mjs.map +1 -0
  213. package/dist/registry-DH4m7eYo.mjs.map +1 -1
  214. package/dist/{repl-editor-DmGr9zMw.mjs → repl-editor-BCozyiNq.mjs} +6 -5
  215. package/dist/{repl-editor-DmGr9zMw.mjs.map → repl-editor-BCozyiNq.mjs.map} +1 -1
  216. package/dist/runtime/aigateway.d.mts +35 -0
  217. package/dist/runtime/aigateway.mjs +3 -0
  218. package/dist/runtime/authconnection.d.mts +31 -2
  219. package/dist/runtime/authconnection.mjs +1 -1
  220. package/dist/runtime/context.d.mts +46 -2
  221. package/dist/runtime/file.d.mts +210 -2
  222. package/dist/runtime/globals.d.mts +31 -19
  223. package/dist/runtime/iconv.d.mts +121 -2
  224. package/dist/runtime/idp.d.mts +186 -2
  225. package/dist/runtime/idp.mjs +1 -1
  226. package/dist/runtime/index.d.mts +47 -9
  227. package/dist/runtime/index.mjs +5 -4
  228. package/dist/runtime/secretmanager.d.mts +54 -2
  229. package/dist/{types-BZ7QKVE8.d.mts → runtime/types.d.mts} +4 -5
  230. package/dist/runtime/workflow.d.mts +112 -2
  231. package/dist/runtime/workflow.mjs +2 -2
  232. package/dist/{schema-Dtw9Orye.mjs → schema-89dYxNC2.mjs} +17 -182
  233. package/dist/schema-89dYxNC2.mjs.map +1 -0
  234. package/dist/{secret-file-VSVGy1V0.mjs → secret-file-aoNXJmKa.mjs} +3 -3
  235. package/dist/secret-file-aoNXJmKa.mjs.map +1 -0
  236. package/dist/secretmanager-Bd45j7an.mjs +98 -0
  237. package/dist/secretmanager-Bd45j7an.mjs.map +1 -0
  238. package/dist/seed/index.d.mts +1 -2
  239. package/dist/{seed-izIEyP3z.mjs → seed-CRSQJI_z.mjs} +14 -3
  240. package/dist/seed-CRSQJI_z.mjs.map +1 -0
  241. package/dist/service-CkIwEctX.mjs +3 -0
  242. package/dist/{service-DU1mVzri2.mjs → service-DeZeYa06.mjs} +3 -3
  243. package/dist/service-DeZeYa06.mjs.map +1 -0
  244. package/dist/{service-DjyqbCaJ.mjs → service-ufH7sS8o.mjs} +396 -58
  245. package/dist/service-ufH7sS8o.mjs.map +1 -0
  246. package/dist/service_pb-CIrhGwHk.mjs +988 -0
  247. package/dist/service_pb-CIrhGwHk.mjs.map +1 -0
  248. package/dist/service_pb-DjwIn4jO.mjs +3 -0
  249. package/dist/tailor-proto/src/tailor/v1/aigateway_pb.d.mts +204 -0
  250. package/dist/tailor-proto/src/tailor/v1/aigateway_resource_pb.d.mts +58 -0
  251. package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +316 -0
  252. package/dist/tailor-proto/src/tailor/v1/application_resource_pb.d.mts +161 -0
  253. package/dist/tailor-proto/src/tailor/v1/auth_pb.d.mts +2070 -0
  254. package/dist/tailor-proto/src/tailor/v1/auth_resource_pb.d.mts +861 -0
  255. package/dist/tailor-proto/src/tailor/v1/events_pb.d.mts +112 -0
  256. package/dist/tailor-proto/src/tailor/v1/events_resource_pb.d.mts +69 -0
  257. package/dist/tailor-proto/src/tailor/v1/executor_pb.d.mts +439 -0
  258. package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +564 -0
  259. package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +145 -0
  260. package/dist/tailor-proto/src/tailor/v1/function_registry_pb.d.mts +451 -0
  261. package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +192 -0
  262. package/dist/tailor-proto/src/tailor/v1/http_adapter_resource_pb.d.mts +50 -0
  263. package/dist/tailor-proto/src/tailor/v1/idp_pb.d.mts +407 -0
  264. package/dist/tailor-proto/src/tailor/v1/idp_resource_pb.d.mts +430 -0
  265. package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +73 -0
  266. package/dist/tailor-proto/src/tailor/v1/metadata_resource_pb.d.mts +17 -0
  267. package/dist/tailor-proto/src/tailor/v1/meter_pb.d.mts +309 -0
  268. package/dist/tailor-proto/src/tailor/v1/pipeline_pb.d.mts +591 -0
  269. package/dist/tailor-proto/src/tailor/v1/pipeline_resource_pb.d.mts +330 -0
  270. package/dist/tailor-proto/src/tailor/v1/resource_pb.d.mts +113 -0
  271. package/dist/tailor-proto/src/tailor/v1/secret_manager_pb.d.mts +346 -0
  272. package/dist/tailor-proto/src/tailor/v1/secret_manager_resource_pb.d.mts +54 -0
  273. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +3576 -0
  274. package/dist/tailor-proto/src/tailor/v1/stateflow_pb.d.mts +187 -0
  275. package/dist/tailor-proto/src/tailor/v1/stateflow_resource_pb.d.mts +29 -0
  276. package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +429 -0
  277. package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +90 -0
  278. package/dist/tailor-proto/src/tailor/v1/tailordb_pb.d.mts +684 -0
  279. package/dist/tailor-proto/src/tailor/v1/tailordb_resource_pb.d.mts +797 -0
  280. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_pb.d.mts +330 -0
  281. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +183 -0
  282. package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +842 -0
  283. package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +276 -0
  284. package/dist/tailor-proto/src/tailor/v1/workspace_pb.d.mts +2015 -0
  285. package/dist/tailor-proto/src/tailor/v1/workspace_resource_pb.d.mts +483 -0
  286. package/dist/{telemetry-CdqJEzkj.mjs → telemetry-BRVdwh14.mjs} +3 -3
  287. package/dist/telemetry-BRVdwh14.mjs.map +1 -0
  288. package/dist/telemetry-CkbkeJxl.mjs +3 -0
  289. package/dist/types/aigateway.generated.d.mts +9 -0
  290. package/dist/types/app-config.generated.d.mts +4 -0
  291. package/dist/types/auth-connection.generated.d.mts +20 -0
  292. package/dist/types/auth.generated.d.mts +145 -0
  293. package/dist/types/executor.generated.d.mts +177 -0
  294. package/dist/types/helpers.d.mts +39 -0
  295. package/dist/types/http-adapter.generated.d.mts +17 -0
  296. package/dist/{workflow.generated-ClEjBYhm.d.mts → types/idp.generated.d.mts} +98 -185
  297. package/dist/types/resolver.generated.d.mts +39 -0
  298. package/dist/types/secrets.generated.d.mts +14 -0
  299. package/dist/types/staticwebsite.generated.d.mts +10 -0
  300. package/dist/{types-CdcQh4Z2.d.mts → types/tailordb.generated.d.mts} +2 -287
  301. package/dist/types/workflow.generated.d.mts +12 -0
  302. package/dist/types-C-lQ4se3.mjs +4 -0
  303. package/dist/types-CXF2OYdR.mjs +199 -0
  304. package/dist/types-CXF2OYdR.mjs.map +1 -0
  305. package/dist/utils/test/index.d.mts +5 -89
  306. package/dist/utils/test/index.mjs.map +1 -1
  307. package/dist/utils/test/mock.d.mts +87 -0
  308. package/dist/vitest/environment.d.mts +1 -2
  309. package/dist/vitest/environment.mjs +2 -2
  310. package/dist/vitest/environment.mjs.map +1 -1
  311. package/dist/vitest/index.d.mts +12 -428
  312. package/dist/vitest/index.mjs +739 -8
  313. package/dist/vitest/index.mjs.map +1 -1
  314. package/dist/vitest/mock-kysely.d.mts +62 -0
  315. package/dist/vitest/mock.d.mts +9 -0
  316. package/dist/vitest/mocks/aigateway.d.mts +30 -0
  317. package/dist/vitest/mocks/authconnection.d.mts +28 -0
  318. package/dist/vitest/mocks/file.d.mts +41 -0
  319. package/dist/vitest/mocks/iconv.d.mts +27 -0
  320. package/dist/vitest/mocks/idp.d.mts +44 -0
  321. package/dist/vitest/mocks/secretmanager.d.mts +33 -0
  322. package/dist/vitest/mocks/tailordb.d.mts +71 -0
  323. package/dist/vitest/mocks/workflow.d.mts +115 -0
  324. package/dist/vitest/setup.d.mts +1 -2
  325. package/dist/vitest/setup.mjs +2 -2
  326. package/dist/vitest/workflow-local.d.mts +40 -0
  327. package/dist/{workflow-BOmaZwwG.mjs → workflow-Bo59B75H.mjs} +9 -2
  328. package/dist/workflow-Bo59B75H.mjs.map +1 -0
  329. package/docs/cli/application.md +98 -250
  330. package/docs/cli/auth.md +48 -292
  331. package/docs/cli/completion.md +1 -25
  332. package/docs/cli/crashreport.md +3 -61
  333. package/docs/cli/executor.md +23 -187
  334. package/docs/cli/function.md +14 -130
  335. package/docs/cli/organization.md +11 -221
  336. package/docs/cli/plugin.md +29 -0
  337. package/docs/cli/query.md +2 -22
  338. package/docs/cli/secret.md +71 -251
  339. package/docs/cli/setup.md +137 -41
  340. package/docs/cli/skills.md +50 -39
  341. package/docs/cli/staticwebsite.md +32 -180
  342. package/docs/cli/tailordb.md +97 -323
  343. package/docs/cli/upgrade.md +2 -22
  344. package/docs/cli/user.md +78 -247
  345. package/docs/cli/workflow.md +44 -203
  346. package/docs/cli/workspace.md +165 -538
  347. package/docs/cli-reference.md +167 -67
  348. package/docs/configuration.md +10 -4
  349. package/docs/github-actions.md +46 -19
  350. package/docs/migration/v2.md +286 -26
  351. package/docs/multi-environment.md +29 -7
  352. package/docs/plugin/index.md +6 -6
  353. package/docs/quickstart.md +8 -7
  354. package/docs/runtime.md +5 -1
  355. package/docs/services/aigateway.md +18 -2
  356. package/docs/services/auth.md +32 -40
  357. package/docs/services/http-adapter.md +16 -1
  358. package/docs/services/idp.md +55 -2
  359. package/docs/services/resolver.md +1 -1
  360. package/docs/services/secret.md +11 -11
  361. package/docs/services/staticwebsite.md +8 -2
  362. package/docs/services/tailordb-migration.md +24 -22
  363. package/docs/services/tailordb.md +25 -2
  364. package/docs/services/workflow.md +14 -14
  365. package/docs/testing.md +49 -3
  366. package/package.json +42 -31
  367. package/postinstall.mjs +4 -6
  368. package/dist/application-Dtqap5jM.mjs +0 -3
  369. package/dist/application-XuMWK4eq.mjs +0 -7257
  370. package/dist/application-XuMWK4eq.mjs.map +0 -1
  371. package/dist/authconnection-D2MhtTN5.mjs.map +0 -1
  372. package/dist/authconnection-DvUQAjQS.d.mts +0 -39
  373. package/dist/context-C2lEi9uw.d.mts +0 -47
  374. package/dist/errors-Dtf2WPaW.mjs.map +0 -1
  375. package/dist/field-DOsJCPFa.mjs +0 -22
  376. package/dist/field-DOsJCPFa.mjs.map +0 -1
  377. package/dist/file-Dq3NIt_F.d.mts +0 -211
  378. package/dist/globals-Cf0sxIt8.mjs.map +0 -1
  379. package/dist/http-adapter.generated-DFsXDdm5.d.mts +0 -581
  380. package/dist/iconv-Co-TOPuH.d.mts +0 -122
  381. package/dist/idp-BDbK5gjm.mjs.map +0 -1
  382. package/dist/idp-DrhVrLmV.d.mts +0 -160
  383. package/dist/index-BI-_j9Z3.d.mts +0 -1013
  384. package/dist/index-C4JirJH8.d.mts +0 -18
  385. package/dist/index-CZfWhr0a.d.mts +0 -18
  386. package/dist/index-Cg8VKAdN.d.mts +0 -46
  387. package/dist/index-DYRjoLXD.d.mts +0 -47
  388. package/dist/index-lFpcjHPU.d.mts +0 -201
  389. package/dist/index-nW7hE6oE.d.mts +0 -18
  390. package/dist/interceptor-D-q1rvRl.mjs.map +0 -1
  391. package/dist/kysely-type-DR8uzZTA.mjs.map +0 -1
  392. package/dist/mock-FPxmnt-y.mjs +0 -759
  393. package/dist/mock-FPxmnt-y.mjs.map +0 -1
  394. package/dist/runtime-CY4JvrDj.mjs.map +0 -1
  395. package/dist/schema-Dtw9Orye.mjs.map +0 -1
  396. package/dist/secret-file-VSVGy1V0.mjs.map +0 -1
  397. package/dist/secretmanager-B3n4KHfm.d.mts +0 -55
  398. package/dist/seed-izIEyP3z.mjs.map +0 -1
  399. package/dist/service-DCqIWibD.mjs +0 -3
  400. package/dist/service-DU1mVzri2.mjs.map +0 -1
  401. package/dist/service-DjyqbCaJ.mjs.map +0 -1
  402. package/dist/telemetry-CdqJEzkj.mjs.map +0 -1
  403. package/dist/telemetry-ClwW5ohF.mjs +0 -3
  404. package/dist/types-74etvaxy.mjs +0 -4
  405. package/dist/types-BDRml5C3.d.mts +0 -574
  406. package/dist/types-BQijbo4m.mjs +0 -371
  407. package/dist/types-BQijbo4m.mjs.map +0 -1
  408. package/dist/types-BX4q6Mo6.d.mts +0 -339
  409. package/dist/workflow-BOmaZwwG.mjs.map +0 -1
  410. package/dist/workflow-BVy4XWjS.d.mts +0 -101
@@ -1,28 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import { A as loadAccessToken, At as FunctionExecution_Type, B as fetchAll, C as getDistDir, D as deleteUserTokens, E as loadConfig, F as removeLegacyUserAlias, G as initOAuth2Client, H as fetchPaged, I as resolveTokens, K as initOperatorClient, L as saveUserTokens, M as loadMachineUserName, N as loadWorkspaceId, O as fetchLatestToken, P as readPlatformConfig, R as writePlatformConfig, U as fetchPlatformMachineUserToken, W as fetchUserInfo, _ as composeFunctionTreeshakeOptions, dn as PATScope, g as platformBundleDefinePlugin, ht as CustomDomainStatus, i as resolveInlineSourcemap, in as AuthInvokerSchema, k as findConfigUserKey, l as INVOKER_EXPR, o as WorkflowJobSchema, s as ResolverSchema, t as defineApplication, v as createLogLevelTreeshakeOptions, w as hashContent$1, y as resolveBundleLogLevel, z as closeConnectionPool } from "../application-XuMWK4eq.mjs";
2
+ import { C as CustomDomainStatus, R as FunctionExecution_Type, mt as AuthInvokerSchema, xt as PATScope } from "../service_pb-CIrhGwHk.mjs";
3
3
  import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
4
- import { n as logger, r as styles } from "../logger-CxF-Ex5d.mjs";
5
- import { $ as updateCommand$2, $t as protoGqlPermission, A as listCommand$12, At as jobsCommand, Bn as toPageDirection, Bt as functionExecutionStatusToString, C as listCommand$13, Cn as ensureConfigId, Ct as webhookCommand, Dn as generateUserTypes, E as waitCommand, En as PluginManager, Et as listCommand$6, F as generateCommand$1, Fn as confirmationArgs, Ft as getCommand$6, G as updateCommand$3, Gt as executeScript, H as logBetaWarning, Ht as getCommand$1, I as generateMigrationScript, In as deploymentArgs, J as treeCommand, Jt as MIGRATION_LABEL_KEY, L as writeDbTypesFile, Ln as isVerbose, Lt as executionsCommand, Mn as defineAppCommand, N as truncateCommand, Nn as commonArgs, Nt as startCommand, O as resumeCommand, On as prompt, Pn as configArg, Qt as generateAllTypeManifestsFromSnapshot, R as getConfiguredEditorCommand, Rn as pagedLogArgs, Sn as getNamespacesWithMigrations, T as healthCommand, Tn as sdkNameLabelKey, V as showCommand, Vn as workspaceArgs, Vt as formatKeyValueTable, W as removeCommand$1, Wt as deploy, Xt as parseMigrationLabelNumber, Y as listCommand$11, Yt as handleOptionalToRequiredError, Z as getCommand$5, Zt as compareSnapshotWithRemote, _n as formatMigrationNumber, _t as generate, an as assertValidMigrationFiles, at as deleteCommand$3, b as createCommand$4, bn as formatMigrationDiff, bt as getCommand$2, c as listCommand$14, cn as createSnapshotFromLocalTypes, dn as getMigrationFilePath, dt as getCommand$3, f as restoreCommand, fn as getMigrationFiles, g as getCommand$7, gn as reconstructSnapshotFromMigrations, hn as loadDiff, ht as listCommand$8, i as updateCommand$4, jn as assertWritable, kn as apiCommand, ln as getLatestMigrationNumber, lt as listCommand$9, m as listCommand$15, mn as isValidMigrationNumber, mt as tokenCommand, nn as INITIAL_SCHEMA_NUMBER, o as removeCommand, on as compareLocalTypesWithSnapshot, r as queryCommand, rt as getCommand$4, st as createCommand$3, t as isNativeTypeScriptRuntime, tt as listCommand$10, u as inviteCommand, v as deleteCommand$4, vn as parseMigrationNumberArg, vt as listCommand$7, wn as resourceTrn, wt as triggerCommand, xn as hasChanges, z as openInConfiguredEditor, zn as paginationArgs } from "../runtime-CY4JvrDj.mjs";
6
- import { n as ExecutorSchema } from "../service-DjyqbCaJ.mjs";
4
+ import { n as logger, r as styles } from "../logger-BEiZZ3qT.mjs";
5
+ import { $t as compareSnapshotWithRemote, A as listCommand$13, An as apiCommand, Bn as pagedLogArgs, C as listCommand$14, Cn as hasChanges, Dn as PluginManager, Dt as listCommand$7, E as waitCommand, En as sdkNameLabelKey, F as generateCommand$1, Fn as configArg, G as removeCommand$1, Gt as deploy, H as extractOwnedNamespaces, Hn as toPageDirection, Ht as formatKeyValueTable, I as generateMigrationScript, In as confirmationArgs, It as getCommand$6, K as updateCommand$3, Kt as ensureConfigId, L as writeDbTypesFile, Ln as deploymentArgs, Mn as assertWritable, N as truncateCommand, Nn as defineAppCommand, O as resumeCommand, On as generateUserTypes, Pn as commonArgs, Pt as startCommand, Q as getCommand$5, Qt as parseMigrationLabelNumber, R as getConfiguredEditorCommand, Rn as isVerbose, Rt as executionsCommand, Sn as formatMigrationDiff, T as healthCommand, Tn as resourceTrn, Tt as triggerCommand, U as logBetaWarning, Un as workspaceArgs, Ut as getCommand$1, V as showCommand, Vn as paginationArgs, Vt as functionExecutionStatusToString, X as listCommand$12, Xt as MIGRATION_LABEL_KEY, Y as treeCommand, Zt as handleOptionalToRequiredError, _n as loadDiff, b as createCommand$4, bn as parseMigrationNumberArg, c as listCommand$15, cn as compareLocalTypesWithSnapshot, ct as createCommand$3, dn as getLatestMigrationNumber, en as generateAllTypeManifestsFromSnapshot, et as updateCommand$2, f as restoreCommand, ft as getCommand$3, g as getCommand$7, gn as isValidMigrationNumber, gt as listCommand$9, ht as tokenCommand$1, i as updateCommand$4, in as INITIAL_SCHEMA_NUMBER, it as getCommand$4, jt as jobsCommand, kn as prompt, m as listCommand$16, mn as getMigrationFiles, nt as listCommand$11, o as removeCommand, ot as deleteCommand$4, pn as getMigrationFilePath, qt as executeScript, r as queryCommand, sn as assertValidMigrationFiles, t as registerTsHook, tn as protoGqlPermission, u as inviteCommand, un as createSnapshotFromLocalTypes, ut as listCommand$10, v as deleteCommand$5, vn as reconstructSnapshotFromMigrations, vt as generate, wn as getNamespacesWithMigrations, wt as webhookCommand, xt as getCommand$2, yn as formatMigrationNumber, yt as listCommand$8, z as openInConfiguredEditor, zn as multiConfigArg } from "../register-ts-hook-ZpBacA07.mjs";
6
+ import { A as hasUserTokenEntry, B as removeLegacyUserAlias, C as getDistDir, D as deleteUserTokens, E as loadConfig, F as loadPlatformClientConfig, G as closeConnectionPool, H as resolveUserTokenKey, I as loadStoredUserTokens, J as fetchAllTolerant, K as defaultPlatformBaseUrl, L as loadWorkspaceId, M as loadConfigPath, N as loadConsoleBaseUrl, O as fetchLatestToken, P as loadMachineUserName, Q as fetchUserInfo, R as platformConfigFromProfile, U as saveUserTokens, V as resolveConfigUser, W as writePlatformConfig, X as fetchPaged, Z as fetchPlatformMachineUserToken, _ as composeFunctionTreeshakeOptions, a as resolveInlineSourcemap, at as isDefaultPlatform, c as ResolverSchema, et as getOAuth2ClientId, g as platformBundleDefinePlugin, it as initOperatorClient, j as loadAccessToken, k as hasAnyUserTokenEntry, nt as getPlatformBaseUrl, q as fetchAll, rt as initOAuth2Client, s as WorkflowJobSchema, t as defineApplication, tt as getOrNull, u as INVOKER_EXPR, v as createLogLevelTreeshakeOptions, w as hashContent$1, y as resolveBundleLogLevel, z as readPlatformConfig } from "../application-eHp9jXld.mjs";
7
+ import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-ufH7sS8o.mjs";
7
8
  import { t as multiline } from "../multiline-sfHpTZZK.mjs";
8
9
  import { t as readPackageJson } from "../package-json-8b0O9TlX.mjs";
9
- import { i as userAgent } from "../secret-file-VSVGy1V0.mjs";
10
- import { n as isCLIError } from "../errors-Dtf2WPaW.mjs";
11
- import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-DFq-vsU0.mjs";
10
+ import { i as userAgent } from "../secret-file-aoNXJmKa.mjs";
11
+ import { n as isCLIError } from "../errors-118wUDRr.mjs";
12
+ import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-Cg-pqRJV.mjs";
12
13
  import { t as isPluginGeneratedType } from "../type-source-DH_LH20p.mjs";
14
+ import * as path from "pathe";
15
+ import { dirname, resolve } from "pathe";
16
+ import { resolvePackageJSON, resolveTSConfig } from "pkg-types";
13
17
  import { arg, defineCommand, runCommand, runMain } from "politty";
14
18
  import { withCompletionCommand } from "politty/completion";
19
+ import { withSkillCommand } from "politty/skill";
15
20
  import { z } from "zod";
16
21
  import { create } from "@bufbuild/protobuf";
17
22
  import * as fs$1 from "node:fs";
23
+ import { accessSync, constants, readdirSync } from "node:fs";
18
24
  import { timestampDate } from "@bufbuild/protobuf/wkt";
19
- import * as path from "pathe";
20
- import { dirname, resolve } from "pathe";
21
25
  import { fileURLToPath, pathToFileURL } from "node:url";
22
26
  import { generateCodeVerifier } from "@badgateway/oauth2-client";
23
27
  import { Code, ConnectError } from "@connectrpc/connect";
24
28
  import pLimit from "p-limit";
25
- import { resolvePackageJSON, resolveTSConfig } from "pkg-types";
26
29
  import * as crypto from "node:crypto";
27
30
  import { createHash } from "node:crypto";
28
31
  import * as http from "node:http";
@@ -30,6 +33,7 @@ import open from "open";
30
33
  import * as rolldown from "rolldown";
31
34
  import * as fsPromises from "node:fs/promises";
32
35
  import { glob } from "node:fs/promises";
36
+ import * as os from "node:os";
33
37
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
34
38
  import { TraceMap, generatedPositionFor, originalPositionFor } from "@jridgewell/trace-mapping";
35
39
  import { spawn, spawnSync } from "node:child_process";
@@ -37,6 +41,26 @@ import { watch } from "chokidar";
37
41
  import * as fs from "fs";
38
42
  import { lookup } from "mime-types";
39
43
 
44
+ //#region src/cli/commands/auth/token.ts
45
+ const tokenCommand = defineAppCommand({
46
+ name: "token",
47
+ description: "Print a valid Tailor Platform access token to stdout, refreshing it first if expired.",
48
+ args: z.strictObject({ profile: workspaceArgs.profile }),
49
+ run: async ({ profile }) => {
50
+ const token = await loadAccessToken({ profile });
51
+ logger.out(token);
52
+ }
53
+ });
54
+
55
+ //#endregion
56
+ //#region src/cli/commands/auth/index.ts
57
+ const authCommand = defineCommand({
58
+ name: "auth",
59
+ description: "Authentication helpers for scripts and plugins.",
60
+ subCommands: { token: tokenCommand }
61
+ });
62
+
63
+ //#endregion
40
64
  //#region src/cli/commands/authconnection/args.ts
41
65
  /**
42
66
  * Arguments for identifying an auth connection
@@ -67,13 +91,13 @@ function randomState$1() {
67
91
  const authorizeAuthConnectionCommand = defineAppCommand({
68
92
  name: "authorize",
69
93
  description: "Authorize an auth connection via OAuth2 flow.",
70
- args: z.object({
94
+ args: z.strictObject({
71
95
  ...workspaceArgs,
72
96
  ...connectionNameArgs,
73
97
  scopes: z.string().optional().default(defaultScopes).describe("OAuth2 scopes to request (comma-separated)"),
74
98
  port: z.coerce.number().optional().default(defaultPort).describe("Local callback server port"),
75
99
  "no-browser": z.boolean().optional().default(false).describe("Don't open browser automatically")
76
- }).strict(),
100
+ }),
77
101
  run: async (args) => {
78
102
  await assertWritable({ profile: args.profile });
79
103
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
@@ -147,13 +171,13 @@ const authorizeAuthConnectionCommand = defineAppCommand({
147
171
  clearTimeout(timeout);
148
172
  const code = err.code;
149
173
  const portHint = code === "EADDRINUSE" || code === "EACCES" ? `Try a different port with --port, or authorize via the Console instead:` : `Authorize via the Console instead:`;
150
- logger.warn(`Could not start the local callback server on port ${args.port}${code ? ` (${code})` : ""}.\n${portHint}\n tailor-sdk authconnection open`);
174
+ logger.warn(`Could not start the local callback server on port ${args.port}${code ? ` (${code})` : ""}.\n${portHint}\n tailor authconnection open`);
151
175
  reject(err);
152
176
  });
153
177
  server.listen(args.port, async () => {
154
178
  const authorizeUrl = authUrl.toString();
155
179
  logger.info(args["no-browser"] ? `Open this URL in your browser to authorize:\n\n${authorizeUrl}\n` : `Opening browser for authorization:\n\n${authorizeUrl}\n`);
156
- logger.info("If this flow doesn't complete, you can authorize via the Console instead:\n tailor-sdk authconnection open");
180
+ logger.info("If this flow doesn't complete, you can authorize via the Console instead:\n tailor authconnection open");
157
181
  if (!args["no-browser"]) try {
158
182
  await open(authorizeUrl);
159
183
  } catch {
@@ -170,11 +194,11 @@ const authorizeAuthConnectionCommand = defineAppCommand({
170
194
  const deleteAuthConnectionCommand = defineAppCommand({
171
195
  name: "delete",
172
196
  description: "Delete an auth connection entirely.",
173
- args: z.object({
197
+ args: z.strictObject({
174
198
  ...workspaceArgs,
175
199
  ...connectionNameArgs,
176
200
  ...confirmationArgs
177
- }).strict(),
201
+ }),
178
202
  run: async (args) => {
179
203
  await assertWritable({ profile: args.profile });
180
204
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
@@ -219,10 +243,10 @@ function connectionInfo(connection) {
219
243
  const listAuthConnectionCommand = defineAppCommand({
220
244
  name: "list",
221
245
  description: "List all auth connections.",
222
- args: z.object({
246
+ args: z.strictObject({
223
247
  ...workspaceArgs,
224
248
  ...paginationArgs()
225
- }).strict(),
249
+ }),
226
250
  run: async (args) => {
227
251
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
228
252
  const workspaceId = await loadWorkspaceId({
@@ -253,18 +277,21 @@ const listAuthConnectionCommand = defineAppCommand({
253
277
 
254
278
  //#endregion
255
279
  //#region src/cli/commands/authconnection/open.ts
256
- const consoleBaseUrl$1 = "https://console.tailor.tech";
257
280
  const openAuthConnectionCommand = defineAppCommand({
258
281
  name: "open",
259
282
  description: "Open the auth connections page in the Tailor Platform Console.",
260
- args: z.object({ ...workspaceArgs }).strict(),
283
+ args: z.strictObject({ ...workspaceArgs }),
261
284
  run: async (args) => {
262
285
  const workspaceId = await loadWorkspaceId({
263
286
  workspaceId: args["workspace-id"],
264
287
  profile: args.profile
265
288
  });
266
289
  const consolePath = `/workspaces/${workspaceId}/settings/connections`;
267
- const consoleUrl = new URL(consolePath, consoleBaseUrl$1).toString();
290
+ const consoleBaseUrl = await loadConsoleBaseUrl({
291
+ profile: args.profile,
292
+ ...args["workspace-id"] !== void 0 ? { allowMissingProfile: true } : {}
293
+ });
294
+ const consoleUrl = new URL(consolePath, consoleBaseUrl).toString();
268
295
  const jsonOutput = logger.jsonMode;
269
296
  logger.info("Opening auth connections page in Tailor Platform Console...");
270
297
  let opened = true;
@@ -294,11 +321,11 @@ const revokeAuthConnectionCommand = defineAppCommand({
294
321
  name: "revoke",
295
322
  description: "Revoke an auth connection's tokens (keeps the connection; use 'delete' to remove it).",
296
323
  notes: "Revoke invalidates the connection's active session and tokens but keeps the connection and its stored credentials, so it can be re-authorized later. Use `delete` to remove the connection entirely.",
297
- args: z.object({
324
+ args: z.strictObject({
298
325
  ...workspaceArgs,
299
326
  ...connectionNameArgs,
300
327
  ...confirmationArgs
301
- }).strict(),
328
+ }),
302
329
  run: async (args) => {
303
330
  await assertWritable({ profile: args.profile });
304
331
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
@@ -355,10 +382,10 @@ function formatCrashReportFiles(files, localDir) {
355
382
  path: path.join(localDir, file)
356
383
  }));
357
384
  }
358
- const listCommand$5 = defineAppCommand({
385
+ const listCommand$6 = defineAppCommand({
359
386
  name: "list",
360
387
  description: "List local crash report files.",
361
- args: z.object({ ...paginationArgs() }).strict(),
388
+ args: z.strictObject({ ...paginationArgs() }),
362
389
  run: async (args) => {
363
390
  const config = parseCrashReportConfig();
364
391
  const jsonOutput = logger.jsonMode;
@@ -398,14 +425,14 @@ const listCommand$5 = defineAppCommand({
398
425
  const sendCommand = defineAppCommand({
399
426
  name: "send",
400
427
  description: "Submit a crash report to help improve the SDK.",
401
- args: z.object({ file: arg(z.string(), {
428
+ args: z.strictObject({ file: arg(z.string(), {
402
429
  description: "Path to the crash report file",
403
430
  required: true,
404
431
  completion: {
405
432
  type: "file",
406
433
  extensions: ["log"]
407
434
  }
408
- }) }).strict(),
435
+ }) }),
409
436
  run: async (args) => {
410
437
  let content;
411
438
  try {
@@ -454,11 +481,11 @@ const crashReportCommand = defineCommand({
454
481
  name: "crashreport",
455
482
  description: "Manage crash reports.",
456
483
  subCommands: {
457
- list: listCommand$5,
484
+ list: listCommand$6,
458
485
  send: sendCommand
459
486
  },
460
487
  async run() {
461
- await runCommand(listCommand$5, []);
488
+ await runCommand(listCommand$6, []);
462
489
  }
463
490
  });
464
491
 
@@ -467,8 +494,9 @@ const crashReportCommand = defineCommand({
467
494
  const deployCommand$1 = defineAppCommand({
468
495
  name: "deploy",
469
496
  description: "Deploy your application by applying the Tailor configuration.",
470
- args: z.object({
471
- ...deploymentArgs,
497
+ args: z.strictObject({
498
+ ...workspaceArgs,
499
+ ...multiConfigArg,
472
500
  ...confirmationArgs,
473
501
  "dry-run": arg(z.boolean().optional(), {
474
502
  alias: "d",
@@ -478,10 +506,10 @@ const deployCommand$1 = defineAppCommand({
478
506
  "no-validate": arg(z.boolean().optional(), { description: "Skip client-side validation against platform resource constraints" }),
479
507
  "no-cache": arg(z.boolean().optional(), { description: "Disable bundle caching for this run" }),
480
508
  "clean-cache": arg(z.boolean().optional(), { description: "Clean the bundle cache before building" })
481
- }).strict(),
509
+ }),
482
510
  run: async (args) => {
483
511
  await assertWritable({ profile: args.profile });
484
- const { initTelemetry } = await import("../telemetry-ClwW5ohF.mjs");
512
+ const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
485
513
  await initTelemetry();
486
514
  await deploy({
487
515
  workspaceId: args["workspace-id"],
@@ -505,12 +533,12 @@ const executorCommand = defineCommand({
505
533
  subCommands: {
506
534
  trigger: triggerCommand,
507
535
  jobs: jobsCommand,
508
- list: listCommand$6,
536
+ list: listCommand$7,
509
537
  get: getCommand$1,
510
538
  webhook: webhookCommand
511
539
  },
512
540
  async run() {
513
- await runCommand(listCommand$6, []);
541
+ await runCommand(listCommand$7, []);
514
542
  }
515
543
  });
516
544
 
@@ -1020,14 +1048,14 @@ Stack traces are mapped only when the execution includes a content hash for the
1020
1048
  desc: "Get execution details as JSON"
1021
1049
  }
1022
1050
  ],
1023
- args: z.object({
1051
+ args: z.strictObject({
1024
1052
  ...workspaceArgs,
1025
1053
  ...pagedLogArgs,
1026
1054
  "execution-id": arg(z.string().optional(), {
1027
1055
  positional: true,
1028
1056
  description: "Execution ID (if provided, shows details with logs)"
1029
1057
  })
1030
- }).strict(),
1058
+ }),
1031
1059
  run: async (args) => {
1032
1060
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
1033
1061
  const workspaceId = await loadWorkspaceId({
@@ -1245,7 +1273,7 @@ async function detectFunctionType(options) {
1245
1273
  const rawInput = module.default.input;
1246
1274
  let inputSchema;
1247
1275
  if (rawInput) {
1248
- const { t } = await import("../types-74etvaxy.mjs");
1276
+ const { t } = await import("../types-C-lQ4se3.mjs");
1249
1277
  inputSchema = t.object(rawInput);
1250
1278
  }
1251
1279
  return {
@@ -1255,7 +1283,7 @@ async function detectFunctionType(options) {
1255
1283
  inputSchema
1256
1284
  };
1257
1285
  }
1258
- const executorResult = ExecutorSchema.safeParse(module.default);
1286
+ const executorResult = ExecutorSchema.safeParse(stripExecutorTriggerArgs(module.default));
1259
1287
  if (executorResult.success) {
1260
1288
  const { operation } = executorResult.data;
1261
1289
  if (operation.kind === "function" || operation.kind === "jobFunction") return {
@@ -1337,7 +1365,7 @@ function deriveNameFromPath(filePath) {
1337
1365
  //#endregion
1338
1366
  //#region src/cli/commands/function/test-run.ts
1339
1367
  /**
1340
- * `tailor-sdk function test-run` command
1368
+ * `tailor function test-run` command
1341
1369
  *
1342
1370
  * Bundles and executes a function on the Tailor Platform server
1343
1371
  * without deploying (applying) the application.
@@ -1530,7 +1558,7 @@ async function resolveMachineUserName(options) {
1530
1558
  if (keys.length > 0) return assertDefined(keys[0], "machine user key missing");
1531
1559
  }
1532
1560
  }
1533
- throw new Error("Machine user is required. Provide --machine-user, set TAILOR_PLATFORM_MACHINE_USER_NAME, set a profile default with 'tailor-sdk profile update <profile> --machine-user <name>', or ensure tailor.config.ts has machine users configured.");
1561
+ throw new Error("Machine user is required. Provide --machine-user, set TAILOR_PLATFORM_MACHINE_USER_NAME, set a profile default with 'tailor profile update <profile> --machine-user <name>', or ensure tailor.config.ts has machine users configured.");
1534
1562
  }
1535
1563
  /**
1536
1564
  * Resolve full machine user info: name, id (from API), and attributes (from config).
@@ -1573,12 +1601,12 @@ const functionCommand = defineCommand({
1573
1601
  description: "Manage functions",
1574
1602
  subCommands: {
1575
1603
  get: getCommand$2,
1576
- list: listCommand$7,
1604
+ list: listCommand$8,
1577
1605
  logs: logsCommand,
1578
1606
  "test-run": testRunCommand
1579
1607
  },
1580
1608
  async run() {
1581
- await runCommand(listCommand$7, []);
1609
+ await runCommand(listCommand$8, []);
1582
1610
  }
1583
1611
  });
1584
1612
 
@@ -1587,7 +1615,7 @@ const functionCommand = defineCommand({
1587
1615
  const generateCommand = defineAppCommand({
1588
1616
  name: "generate",
1589
1617
  description: "Generate files using Tailor configuration.",
1590
- args: z.object({
1618
+ args: z.strictObject({
1591
1619
  config: arg(z.string().default("tailor.config.ts"), {
1592
1620
  alias: "c",
1593
1621
  description: "Path to SDK config file"
@@ -1596,9 +1624,9 @@ const generateCommand = defineAppCommand({
1596
1624
  alias: "W",
1597
1625
  description: "Watch for type/resolver changes and regenerate"
1598
1626
  })
1599
- }).strict(),
1627
+ }),
1600
1628
  run: async (args) => {
1601
- const { initTelemetry } = await import("../telemetry-ClwW5ohF.mjs");
1629
+ const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
1602
1630
  await initTelemetry();
1603
1631
  await generate({
1604
1632
  configPath: args.config,
@@ -1625,7 +1653,7 @@ const detectPackageManager$1 = () => {
1625
1653
  const initCommand = defineAppCommand({
1626
1654
  name: "init",
1627
1655
  description: "Initialize a new project using create-sdk.",
1628
- args: z.object({
1656
+ args: z.strictObject({
1629
1657
  name: arg(z.string().optional(), {
1630
1658
  positional: true,
1631
1659
  description: "Project name"
@@ -1634,7 +1662,7 @@ const initCommand = defineAppCommand({
1634
1662
  alias: "t",
1635
1663
  description: "Template name"
1636
1664
  })
1637
- }).strict(),
1665
+ }),
1638
1666
  run: async (args) => {
1639
1667
  const packageJson = await readPackageJson();
1640
1668
  const version = packageJson.version && packageJson.version !== "0.0.0" ? packageJson.version : "latest";
@@ -1662,8 +1690,15 @@ const redirectUri = `http://localhost:${redirectPort}/callback`;
1662
1690
  function randomState() {
1663
1691
  return crypto.randomBytes(32).toString("base64url");
1664
1692
  }
1665
- const startAuthServer = async () => {
1666
- const client = initOAuth2Client();
1693
+ function assertProfileLoginUser(args, authenticatedUser, authenticatedSubject) {
1694
+ if (args.profile && args.profileUser && authenticatedUser !== args.profileUser && authenticatedSubject !== args.profileUser) throw new Error(`Profile "${args.profile}" is configured for "${args.profileUser}", but login authenticated "${authenticatedUser}".`);
1695
+ }
1696
+ function shouldUpdateCurrentUser(profile, platformConfig) {
1697
+ if (!profile) return true;
1698
+ return isDefaultPlatform(platformConfig);
1699
+ }
1700
+ const startAuthServer = async (args = {}) => {
1701
+ const client = initOAuth2Client(args.platformConfig);
1667
1702
  const state = randomState();
1668
1703
  const codeVerifier = await generateCodeVerifier();
1669
1704
  return new Promise((resolve, reject) => {
@@ -1675,14 +1710,18 @@ const startAuthServer = async () => {
1675
1710
  state,
1676
1711
  codeVerifier
1677
1712
  });
1678
- const userInfo = await fetchUserInfo(tokens.accessToken);
1713
+ const userInfo = await fetchUserInfo(tokens.accessToken, args.platformConfig);
1714
+ assertProfileLoginUser(args, userInfo.email, userInfo.sub);
1679
1715
  const pfConfig = await readPlatformConfig();
1680
1716
  await saveUserTokens(pfConfig, userInfo.sub, {
1681
1717
  accessToken: tokens.accessToken,
1682
1718
  refreshToken: tokens.refreshToken ?? void 0
1683
- }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString(), { email: userInfo.email });
1684
- await removeLegacyUserAlias(pfConfig, userInfo.email, userInfo.sub);
1685
- pfConfig.current_user = userInfo.sub;
1719
+ }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString(), {
1720
+ platformConfig: args.platformConfig,
1721
+ email: userInfo.email
1722
+ });
1723
+ if (args.updateCurrentUser ?? true) pfConfig.current_user = userInfo.sub;
1724
+ await removeLegacyUserAlias(pfConfig, userInfo.email, userInfo.sub, args.platformConfig);
1686
1725
  writePlatformConfig(pfConfig);
1687
1726
  res.writeHead(200, { "Content-Type": "application/json" });
1688
1727
  res.end(JSON.stringify({
@@ -1724,17 +1763,22 @@ const startAuthServer = async () => {
1724
1763
  });
1725
1764
  };
1726
1765
  async function loginAsMachineUser(args) {
1766
+ assertProfileLoginUser(args, args.clientId);
1727
1767
  const clientSecret = args.clientSecret ?? await prompt.password({ message: "Client secret" });
1728
- const tokens = await fetchPlatformMachineUserToken(args.clientId, clientSecret);
1768
+ const tokens = await fetchPlatformMachineUserToken(args.clientId, clientSecret, args.platformConfig);
1729
1769
  const pfConfig = await readPlatformConfig();
1730
- await saveUserTokens(pfConfig, args.clientId, { accessToken: tokens.accessToken }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString());
1731
- pfConfig.current_user = args.clientId;
1770
+ await saveUserTokens(pfConfig, args.clientId, { accessToken: tokens.accessToken }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString(), { platformConfig: args.platformConfig });
1771
+ if (args.updateCurrentUser ?? true) pfConfig.current_user = args.clientId;
1732
1772
  writePlatformConfig(pfConfig);
1733
1773
  }
1734
1774
  const loginCommand = defineAppCommand({
1735
1775
  name: "login",
1736
1776
  description: "Login to Tailor Platform.",
1737
- args: z.xor([z.object({}).strict().describe("User Login"), z.object({
1777
+ args: z.xor([z.strictObject({ profile: arg(z.string().optional(), {
1778
+ alias: "p",
1779
+ description: "Workspace profile whose platform settings should be used for login.",
1780
+ env: "TAILOR_PLATFORM_PROFILE"
1781
+ }) }).describe("User Login"), z.strictObject({
1738
1782
  "machine-user": arg(z.literal(true), {
1739
1783
  description: "Login as a platform machine user.",
1740
1784
  required: true
@@ -1747,14 +1791,37 @@ const loginCommand = defineAppCommand({
1747
1791
  "client-secret": arg(z.string().optional(), {
1748
1792
  description: "Client secret",
1749
1793
  env: "TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET"
1794
+ }),
1795
+ profile: arg(z.string().optional(), {
1796
+ alias: "p",
1797
+ description: "Workspace profile whose platform settings should be used for login.",
1798
+ env: "TAILOR_PLATFORM_PROFILE"
1750
1799
  })
1751
- }).strict().describe("Machine User Login")]),
1800
+ }).describe("Machine User Login")]),
1752
1801
  run: async (args) => {
1802
+ let platformConfig;
1803
+ let profileUser;
1804
+ if ("profile" in args && args.profile) {
1805
+ const profileEntry = (await readPlatformConfig()).profiles[args.profile];
1806
+ if (!profileEntry) throw new Error(`Profile "${args.profile}" not found`);
1807
+ platformConfig = platformConfigFromProfile(profileEntry);
1808
+ profileUser = profileEntry.user;
1809
+ }
1810
+ const updateCurrentUser = shouldUpdateCurrentUser(args.profile, platformConfig);
1753
1811
  if ("machine-user" in args) await loginAsMachineUser({
1754
1812
  clientId: args.clientId,
1755
- clientSecret: args.clientSecret
1813
+ clientSecret: args.clientSecret,
1814
+ profile: args.profile,
1815
+ profileUser,
1816
+ platformConfig,
1817
+ updateCurrentUser
1818
+ });
1819
+ else await startAuthServer({
1820
+ profile: args.profile,
1821
+ profileUser,
1822
+ platformConfig,
1823
+ updateCurrentUser
1756
1824
  });
1757
- else await startAuthServer();
1758
1825
  logger.success("Successfully logged in to Tailor Platform.");
1759
1826
  await closeConnectionPool();
1760
1827
  }
@@ -1765,30 +1832,53 @@ const loginCommand = defineAppCommand({
1765
1832
  const logoutCommand = defineAppCommand({
1766
1833
  name: "logout",
1767
1834
  description: "Logout from Tailor Platform.",
1768
- args: z.object({}).strict(),
1769
- run: async () => {
1835
+ args: z.strictObject({ profile: arg(z.string().optional(), {
1836
+ alias: "p",
1837
+ description: "Workspace profile whose platform settings should be used for logout.",
1838
+ env: "TAILOR_PLATFORM_PROFILE"
1839
+ }) }),
1840
+ run: async (args) => {
1841
+ const profile = args.profile || process.env.TAILOR_PLATFORM_PROFILE;
1770
1842
  const pfConfig = await readPlatformConfig();
1771
- const currentUser = pfConfig.current_user;
1772
- const userEntry = currentUser ? pfConfig.users[currentUser] : void 0;
1773
- if (!userEntry || !currentUser) {
1843
+ const profileEntry = profile ? pfConfig.profiles[profile] : void 0;
1844
+ if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
1845
+ const platformConfig = profileEntry ? platformConfigFromProfile(profileEntry) : void 0;
1846
+ const currentUser = profileEntry ? profileEntry.user : pfConfig.current_user;
1847
+ const deletesDefaultToken = isDefaultPlatform(platformConfig);
1848
+ const lookupOptions = profile ? { allowLegacyUserKey: true } : void 0;
1849
+ if (!currentUser) {
1774
1850
  logger.info("You are not logged in.");
1775
1851
  return;
1776
1852
  }
1853
+ const hasDefaultUserToken = () => hasUserTokenEntry(pfConfig, currentUser, { platformUrl: defaultPlatformBaseUrl });
1854
+ const shouldClearCurrentUser = () => pfConfig.current_user === currentUser && (deletesDefaultToken ? !hasDefaultUserToken() : !hasAnyUserTokenEntry(pfConfig, currentUser) && !hasDefaultUserToken());
1855
+ let storedTokens;
1856
+ let tokenLoadFailed = false;
1777
1857
  try {
1778
- const { accessToken, refreshToken } = await resolveTokens(userEntry, currentUser);
1779
- const client = initOAuth2Client();
1780
- const tokenTypeHint = refreshToken ? "refresh_token" : "access_token";
1858
+ storedTokens = await loadStoredUserTokens(pfConfig, currentUser, platformConfig, lookupOptions);
1859
+ } catch (error) {
1860
+ tokenLoadFailed = true;
1861
+ logger.warn(`Failed to revoke token: ${error instanceof Error ? error.message : error}`);
1862
+ }
1863
+ if (!storedTokens && !tokenLoadFailed) {
1864
+ logger.info("You are not logged in.");
1865
+ if (shouldClearCurrentUser()) pfConfig.current_user = null;
1866
+ writePlatformConfig(pfConfig);
1867
+ return;
1868
+ }
1869
+ if (storedTokens) try {
1870
+ const client = initOAuth2Client(platformConfig);
1871
+ const tokenTypeHint = storedTokens.refreshToken ? "refresh_token" : "access_token";
1781
1872
  await client.revoke({
1782
- accessToken,
1783
- refreshToken: refreshToken ?? null,
1784
- expiresAt: Date.parse(userEntry.token_expires_at)
1873
+ accessToken: storedTokens.accessToken,
1874
+ refreshToken: storedTokens.refreshToken ?? null,
1875
+ expiresAt: Date.parse(storedTokens.userEntry.token_expires_at)
1785
1876
  }, tokenTypeHint);
1786
1877
  } catch (error) {
1787
1878
  logger.warn(`Failed to revoke token: ${error instanceof Error ? error.message : error}`);
1788
1879
  }
1789
- await deleteUserTokens(pfConfig, currentUser);
1790
- delete pfConfig.users[currentUser];
1791
- pfConfig.current_user = null;
1880
+ await deleteUserTokens(pfConfig, currentUser, platformConfig, lookupOptions);
1881
+ if (shouldClearCurrentUser()) pfConfig.current_user = null;
1792
1882
  writePlatformConfig(pfConfig);
1793
1883
  logger.success("Successfully logged out from Tailor Platform.");
1794
1884
  }
@@ -1800,11 +1890,11 @@ const machineuserCommand = defineCommand({
1800
1890
  name: "machineuser",
1801
1891
  description: "Manage machine users in your Tailor Platform application.",
1802
1892
  subCommands: {
1803
- list: listCommand$8,
1804
- token: tokenCommand
1893
+ list: listCommand$9,
1894
+ token: tokenCommand$1
1805
1895
  },
1806
1896
  async run() {
1807
- await runCommand(listCommand$8, []);
1897
+ await runCommand(listCommand$9, []);
1808
1898
  }
1809
1899
  });
1810
1900
 
@@ -1814,21 +1904,20 @@ const oauth2clientCommand = defineCommand({
1814
1904
  name: "oauth2client",
1815
1905
  description: "Manage OAuth2 clients in your Tailor Platform application.",
1816
1906
  subCommands: {
1817
- list: listCommand$9,
1907
+ list: listCommand$10,
1818
1908
  get: getCommand$3
1819
1909
  },
1820
1910
  async run() {
1821
- await runCommand(listCommand$9, []);
1911
+ await runCommand(listCommand$10, []);
1822
1912
  }
1823
1913
  });
1824
1914
 
1825
1915
  //#endregion
1826
1916
  //#region src/cli/commands/open.ts
1827
- const consoleBaseUrl = "https://console.tailor.tech";
1828
1917
  const openCommand = defineAppCommand({
1829
1918
  name: "open",
1830
1919
  description: "Open Tailor Platform Console.",
1831
- args: z.object({ ...deploymentArgs }).strict(),
1920
+ args: z.strictObject({ ...deploymentArgs }),
1832
1921
  run: async (args) => {
1833
1922
  const workspaceId = await loadWorkspaceId({
1834
1923
  workspaceId: args["workspace-id"],
@@ -1837,6 +1926,10 @@ const openCommand = defineAppCommand({
1837
1926
  const { config } = await loadConfig(args.config);
1838
1927
  const applicationName = config.name;
1839
1928
  const consolePath = `/workspaces/${workspaceId}/applications/${encodeURIComponent(applicationName)}/overview`;
1929
+ const consoleBaseUrl = await loadConsoleBaseUrl({
1930
+ profile: args.profile,
1931
+ ...args["workspace-id"] !== void 0 ? { allowMissingProfile: true } : {}
1932
+ });
1840
1933
  const consoleUrl = new URL(consolePath, consoleBaseUrl).toString();
1841
1934
  const jsonOutput = logger.jsonMode;
1842
1935
  logger.info("Opening Tailor Platform Console...");
@@ -1870,9 +1963,9 @@ const folderCommand = defineCommand({
1870
1963
  description: "Manage organization folders.",
1871
1964
  subCommands: {
1872
1965
  create: createCommand$3,
1873
- delete: deleteCommand$3,
1966
+ delete: deleteCommand$4,
1874
1967
  get: getCommand$4,
1875
- list: listCommand$10,
1968
+ list: listCommand$11,
1876
1969
  update: updateCommand$2
1877
1970
  }
1878
1971
  });
@@ -1885,12 +1978,399 @@ const organizationCommand = defineCommand({
1885
1978
  subCommands: {
1886
1979
  folder: folderCommand,
1887
1980
  get: getCommand$5,
1888
- list: listCommand$11,
1981
+ list: listCommand$12,
1889
1982
  tree: treeCommand,
1890
1983
  update: updateCommand$3
1891
1984
  },
1892
1985
  async run() {
1893
- await runCommand(listCommand$11, []);
1986
+ await runCommand(listCommand$12, []);
1987
+ }
1988
+ });
1989
+
1990
+ //#endregion
1991
+ //#region src/cli/shared/builtin-commands.ts
1992
+ /**
1993
+ * Top-level builtin command names. A plugin named the same as one of these is
1994
+ * shadowed by the builtin and can never be dispatched.
1995
+ *
1996
+ * This list is the single source of truth used by `plugin list` to flag
1997
+ * shadowed plugins without importing the (cyclic) command tree. It is kept in
1998
+ * sync with `main-command.ts` by a drift test in `options.test.ts`.
1999
+ */
2000
+ const BUILTIN_COMMAND_NAMES = [
2001
+ "api",
2002
+ "auth",
2003
+ "authconnection",
2004
+ "crashreport",
2005
+ "deploy",
2006
+ "executor",
2007
+ "function",
2008
+ "generate",
2009
+ "init",
2010
+ "login",
2011
+ "logout",
2012
+ "machineuser",
2013
+ "oauth2client",
2014
+ "open",
2015
+ "organization",
2016
+ "plugin",
2017
+ "profile",
2018
+ "query",
2019
+ "remove",
2020
+ "secret",
2021
+ "setup",
2022
+ "show",
2023
+ "skills",
2024
+ "staticwebsite",
2025
+ "tailordb",
2026
+ "upgrade",
2027
+ "user",
2028
+ "workflow",
2029
+ "workspace"
2030
+ ];
2031
+
2032
+ //#endregion
2033
+ //#region src/cli/shared/plugin.ts
2034
+ /** PATH entry separator (`;` on Windows, `:` elsewhere). */
2035
+ const pathDelimiter = process.platform === "win32" ? ";" : ":";
2036
+ /**
2037
+ * Yield `start` and each ancestor directory up to the filesystem root.
2038
+ * @param start - Directory to start from
2039
+ * @yields Each ancestor directory, starting with `start`
2040
+ */
2041
+ function* ancestorDirs(start) {
2042
+ let dir = path.resolve(start);
2043
+ let parent = path.dirname(dir);
2044
+ while (parent !== dir) {
2045
+ yield dir;
2046
+ dir = parent;
2047
+ parent = path.dirname(dir);
2048
+ }
2049
+ yield dir;
2050
+ }
2051
+ /** Fallback executable extensions when `PATHEXT` is unset (Windows). */
2052
+ const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.PS1";
2053
+ /**
2054
+ * Lowercased executable extensions from `PATHEXT` (Windows).
2055
+ * @returns Recognized executable extensions (e.g. `.exe`, `.cmd`)
2056
+ */
2057
+ function windowsExecutableExts() {
2058
+ return (process.env.PATHEXT ?? DEFAULT_PATHEXT).split(";").map((e) => e.trim().toLowerCase()).filter(Boolean);
2059
+ }
2060
+ /**
2061
+ * Check whether a path exists and is runnable: X_OK on POSIX; on Windows, the
2062
+ * file must exist and either be extension-less (shebang script) or carry a
2063
+ * `PATHEXT` extension, so plain data files are not mistaken for plugins.
2064
+ * @param filePath - Path to check
2065
+ * @returns Whether the path is an executable file
2066
+ */
2067
+ function isExecutable(filePath) {
2068
+ try {
2069
+ accessSync(filePath, process.platform === "win32" ? constants.F_OK : constants.X_OK);
2070
+ } catch {
2071
+ return false;
2072
+ }
2073
+ if (process.platform === "win32") {
2074
+ const ext = path.extname(filePath).toLowerCase();
2075
+ return ext === "" || windowsExecutableExts().includes(ext);
2076
+ }
2077
+ return true;
2078
+ }
2079
+ /**
2080
+ * Candidate file names for a plugin binary, accounting for Windows extensions.
2081
+ * @param binName - Base binary name (e.g. `tailor-foo`)
2082
+ * @returns Ordered candidate file names
2083
+ */
2084
+ function binaryCandidates(binName) {
2085
+ if (process.platform !== "win32") return [binName];
2086
+ return [binName, ...windowsExecutableExts().map((ext) => `${binName}${ext}`)];
2087
+ }
2088
+ /**
2089
+ * Directories on the user's PATH.
2090
+ * @returns PATH entries
2091
+ */
2092
+ function pathDirs() {
2093
+ return (process.env.PATH ?? "").split(pathDelimiter).filter(Boolean);
2094
+ }
2095
+ /**
2096
+ * Nearest `node_modules/.bin` directories, walking up from the current working
2097
+ * directory. Project-local bins take precedence over those higher in the tree.
2098
+ * @returns Ordered `node_modules/.bin` directories
2099
+ */
2100
+ function nodeModulesBinDirs() {
2101
+ const dirs = [];
2102
+ for (const dir of ancestorDirs(process.cwd())) dirs.push(path.join(dir, "node_modules", ".bin"));
2103
+ return dirs;
2104
+ }
2105
+ /**
2106
+ * Find the first existing executable for the given base name across a set of
2107
+ * directories.
2108
+ * @param dirs - Directories to search
2109
+ * @param binName - Base binary name
2110
+ * @returns Absolute path, or undefined when not found
2111
+ */
2112
+ function findExecutableIn(dirs, binName) {
2113
+ const candidates = binaryCandidates(binName);
2114
+ for (const dir of dirs) for (const candidate of candidates) {
2115
+ const full = path.join(dir, candidate);
2116
+ if (isExecutable(full)) return full;
2117
+ }
2118
+ }
2119
+ /**
2120
+ * Resolve a plugin executable by name.
2121
+ * Search order: project-local `node_modules/.bin` (nearest first), then PATH.
2122
+ * @param name - Plugin name (without the `<cli>-` prefix)
2123
+ * @param cliName - The host CLI name (e.g. `tailor`)
2124
+ * @returns The discovered plugin, or null when not found
2125
+ */
2126
+ function resolvePlugin(name, cliName) {
2127
+ if (name.includes("/") || name.includes("\\") || name.includes("\0")) return null;
2128
+ const binName = `${cliName}-${name}`;
2129
+ const fromNodeModules = findExecutableIn(nodeModulesBinDirs(), binName);
2130
+ if (fromNodeModules) return {
2131
+ name,
2132
+ path: fromNodeModules,
2133
+ source: "node_modules"
2134
+ };
2135
+ const fromPath = findExecutableIn(pathDirs(), binName);
2136
+ if (fromPath) return {
2137
+ name,
2138
+ path: fromPath,
2139
+ source: "path"
2140
+ };
2141
+ return null;
2142
+ }
2143
+ /**
2144
+ * Strip a known binary extension from a file name (Windows).
2145
+ * @param fileName - File name
2146
+ * @returns File name without a recognized executable extension
2147
+ */
2148
+ function stripBinExt(fileName) {
2149
+ if (process.platform !== "win32") return fileName;
2150
+ const ext = path.extname(fileName);
2151
+ return ext ? fileName.slice(0, -ext.length) : fileName;
2152
+ }
2153
+ /**
2154
+ * Discover all plugins reachable from `node_modules/.bin` and PATH.
2155
+ * Earlier entries win on name collisions (project-local over PATH).
2156
+ * @param cliName - The host CLI name (e.g. `tailor`)
2157
+ * @returns Discovered plugins, deduped by name
2158
+ */
2159
+ function listPlugins(cliName) {
2160
+ const prefix = `${cliName}-`;
2161
+ const seen = /* @__PURE__ */ new Map();
2162
+ const scan = (dirs, source) => {
2163
+ for (const dir of dirs) {
2164
+ let entries;
2165
+ try {
2166
+ entries = readdirSync(dir);
2167
+ } catch {
2168
+ continue;
2169
+ }
2170
+ for (const entry of entries) {
2171
+ const base = stripBinExt(entry);
2172
+ if (!base.startsWith(prefix) || base.length === prefix.length) continue;
2173
+ const name = base.slice(prefix.length);
2174
+ if (seen.has(name)) continue;
2175
+ const full = path.join(dir, entry);
2176
+ if (!isExecutable(full)) continue;
2177
+ seen.set(name, {
2178
+ name,
2179
+ path: full,
2180
+ source
2181
+ });
2182
+ }
2183
+ }
2184
+ };
2185
+ scan(nodeModulesBinDirs(), "node_modules");
2186
+ scan(pathDirs(), "path");
2187
+ return [...seen.values()];
2188
+ }
2189
+ /**
2190
+ * Resolve the active user identifier, preferring the stored email over the
2191
+ * internal user key.
2192
+ * @param profile - Active profile name, if any
2193
+ * @returns The resolved user (email when known), or undefined when not logged in
2194
+ */
2195
+ async function resolveActiveUser(profile) {
2196
+ const config = await readPlatformConfig();
2197
+ const user = profile ? config.profiles[profile]?.user : config.current_user;
2198
+ if (!user) return void 0;
2199
+ return config.users[user]?.email ?? user;
2200
+ }
2201
+ /**
2202
+ * Build the environment variables injected into a dispatched plugin.
2203
+ * Workspace, user, and token are resolved best-effort: whichever the current
2204
+ * context can provide is injected, and the rest are omitted so auth-free
2205
+ * plugins still run.
2206
+ * @param options - Plugin context options
2207
+ * @returns Environment variables to merge into the child process env
2208
+ */
2209
+ async function buildPluginEnv(options = {}) {
2210
+ const { profile } = options;
2211
+ let platformConfig;
2212
+ try {
2213
+ platformConfig = await loadPlatformClientConfig({
2214
+ profile,
2215
+ allowMissingProfile: true
2216
+ });
2217
+ } catch {}
2218
+ const env = {
2219
+ TAILOR_PLATFORM_URL: getPlatformBaseUrl(platformConfig),
2220
+ TAILOR_PLATFORM_OAUTH2_CLIENT_ID: getOAuth2ClientId(platformConfig)
2221
+ };
2222
+ const binPath = process.argv[1];
2223
+ if (binPath) env.TAILOR_BIN = binPath;
2224
+ try {
2225
+ const { version } = await readPackageJson();
2226
+ if (version) env.TAILOR_VERSION = version;
2227
+ } catch {}
2228
+ const configPath = loadConfigPath();
2229
+ if (configPath) env.TAILOR_CONFIG_PATH = configPath;
2230
+ try {
2231
+ env.TAILOR_PLATFORM_WORKSPACE_ID = await loadWorkspaceId({ profile });
2232
+ } catch {}
2233
+ try {
2234
+ const user = await resolveActiveUser(profile);
2235
+ if (user) env.TAILOR_PLATFORM_USER = user;
2236
+ } catch {}
2237
+ try {
2238
+ const token = await loadAccessToken({ profile });
2239
+ if (token) env.TAILOR_PLATFORM_TOKEN = token;
2240
+ } catch {}
2241
+ return env;
2242
+ }
2243
+ /**
2244
+ * Map an exit signal to a conventional exit code (128 + signal number).
2245
+ * @param signal - Signal name
2246
+ * @returns Exit code
2247
+ */
2248
+ function signalToExitCode(signal) {
2249
+ const num = os.constants.signals[signal];
2250
+ return typeof num === "number" ? 128 + num : 1;
2251
+ }
2252
+ /**
2253
+ * Build the command + args for spawning a plugin, handling Windows shebang
2254
+ * scripts via `sh` (following the `gh` extension dispatcher).
2255
+ * @param pluginPath - Resolved plugin executable path
2256
+ * @param args - Args to forward to the plugin
2257
+ * @returns Spawn command, args, and whether a shell is required
2258
+ */
2259
+ function buildSpawnTarget(pluginPath, args) {
2260
+ if (process.platform !== "win32") return {
2261
+ command: pluginPath,
2262
+ args: [...args],
2263
+ shell: false
2264
+ };
2265
+ const ext = path.extname(pluginPath).toLowerCase();
2266
+ if (ext === ".exe" || ext === ".com") return {
2267
+ command: pluginPath,
2268
+ args: [...args],
2269
+ shell: false
2270
+ };
2271
+ if (ext === ".cmd" || ext === ".bat") return {
2272
+ command: pluginPath,
2273
+ args: [...args],
2274
+ shell: true
2275
+ };
2276
+ if (ext === ".ps1") return {
2277
+ command: findExecutableIn(pathDirs(), "pwsh") ?? findExecutableIn(pathDirs(), "powershell") ?? "powershell",
2278
+ args: [
2279
+ "-NoProfile",
2280
+ "-ExecutionPolicy",
2281
+ "Bypass",
2282
+ "-File",
2283
+ pluginPath,
2284
+ ...args
2285
+ ],
2286
+ shell: false
2287
+ };
2288
+ return {
2289
+ command: findExecutableIn(pathDirs(), "sh") ?? "sh",
2290
+ args: [
2291
+ "-c",
2292
+ "command \"$@\"",
2293
+ "--",
2294
+ pluginPath,
2295
+ ...args
2296
+ ],
2297
+ shell: false
2298
+ };
2299
+ }
2300
+ /**
2301
+ * Resolve and execute a plugin, forwarding stdio and propagating its exit code.
2302
+ * @param params - Dispatch parameters
2303
+ * @param params.name - Plugin name (without the `<cli>-` prefix)
2304
+ * @param params.args - Args to forward to the plugin
2305
+ * @param params.cliName - The host CLI name
2306
+ * @param params.profile - Active profile name, if any
2307
+ * @returns The plugin's exit code, or undefined when no matching plugin exists
2308
+ */
2309
+ async function dispatchPlugin(params) {
2310
+ const slug = [...params.commandPath ?? [], params.name].join("-");
2311
+ const plugin = resolvePlugin(slug, params.cliName);
2312
+ if (!plugin) return;
2313
+ const env = {
2314
+ ...process.env,
2315
+ ...await buildPluginEnv({ profile: params.profile })
2316
+ };
2317
+ const { command, args, shell } = buildSpawnTarget(plugin.path, params.args);
2318
+ return await new Promise((resolve) => {
2319
+ const child = spawn(command, args, {
2320
+ stdio: "inherit",
2321
+ env,
2322
+ shell
2323
+ });
2324
+ child.on("error", (error) => {
2325
+ logger.error(`Failed to run plugin "${params.cliName}-${slug}": ${error.message}`);
2326
+ resolve(1);
2327
+ });
2328
+ child.on("close", (code, signal) => {
2329
+ if (signal) resolve(signalToExitCode(signal));
2330
+ else resolve(code ?? 0);
2331
+ });
2332
+ });
2333
+ }
2334
+
2335
+ //#endregion
2336
+ //#region src/cli/commands/plugin/list.ts
2337
+ const listCommand$5 = defineAppCommand({
2338
+ name: "list",
2339
+ description: "List discovered plugins (executables named `<cli>-<name>` on PATH or node_modules/.bin).",
2340
+ args: z.strictObject({}),
2341
+ run: async () => {
2342
+ const pkg = await readPackageJson();
2343
+ const cliName = Object.keys(pkg.bin ?? {})[0] || "tailor";
2344
+ const builtins = new Set(BUILTIN_COMMAND_NAMES);
2345
+ const plugins = listPlugins(cliName);
2346
+ if (plugins.length === 0) {
2347
+ logger.info(multiline`
2348
+ No plugins found.
2349
+ Install an executable named "${cliName}-<name>" on your PATH or in node_modules/.bin,
2350
+ then run it with "${cliName} <name>".
2351
+ `);
2352
+ if (logger.jsonMode) logger.out([]);
2353
+ return;
2354
+ }
2355
+ const items = plugins.map((plugin) => ({
2356
+ name: plugin.name,
2357
+ source: plugin.source,
2358
+ path: plugin.path,
2359
+ shadowed: builtins.has(plugin.name)
2360
+ }));
2361
+ logger.out(items);
2362
+ for (const item of items) if (item.shadowed) logger.warn(`Plugin "${cliName}-${item.name}" is shadowed by the builtin "${cliName} ${item.name}" command and will not be dispatched.`);
2363
+ }
2364
+ });
2365
+
2366
+ //#endregion
2367
+ //#region src/cli/commands/plugin/index.ts
2368
+ const pluginCommand = defineCommand({
2369
+ name: "plugin",
2370
+ description: "Manage and inspect CLI plugins (beta).",
2371
+ subCommands: { list: listCommand$5 },
2372
+ async run() {
2373
+ await runCommand(listCommand$5, []);
1894
2374
  }
1895
2375
  });
1896
2376
 
@@ -1899,7 +2379,7 @@ const organizationCommand = defineCommand({
1899
2379
  const createCommand$2 = defineAppCommand({
1900
2380
  name: "create",
1901
2381
  description: "Create a new profile.",
1902
- args: z.object({
2382
+ args: z.strictObject({
1903
2383
  name: arg(z.string(), {
1904
2384
  positional: true,
1905
2385
  description: "Profile name"
@@ -1917,14 +2397,32 @@ const createCommand$2 = defineAppCommand({
1917
2397
  alias: "m",
1918
2398
  description: "Default machine user name for application-data commands (query, workflow start, function test-run, machineuser token)."
1919
2399
  }),
1920
- "machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user." })
1921
- }).strict(),
2400
+ "machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user." }),
2401
+ "platform-url": arg(z.url().optional(), {
2402
+ description: "Platform API base URL for this profile.",
2403
+ env: "TAILOR_PLATFORM_URL"
2404
+ }),
2405
+ "oauth2-client-id": arg(z.string().optional(), {
2406
+ description: "OAuth2 client ID for logging in to this profile's platform.",
2407
+ env: "TAILOR_PLATFORM_OAUTH2_CLIENT_ID"
2408
+ }),
2409
+ "console-url": arg(z.url().optional(), {
2410
+ description: "Console base URL for this profile.",
2411
+ env: "TAILOR_PLATFORM_CONSOLE_URL"
2412
+ })
2413
+ }),
1922
2414
  run: async (args) => {
1923
2415
  if (args["machine-user-override"] === "deny" && !args["machine-user"]) throw new Error("--machine-user-override deny requires --machine-user.");
1924
2416
  const config = await readPlatformConfig();
1925
2417
  if (config.profiles[args.name]) throw new Error(`Profile "${args.name}" already exists.`);
1926
- const { accessToken: token, user: resolvedUser } = await fetchLatestToken(config, args.user);
1927
- const client = await initOperatorClient(token);
2418
+ const platformConfigInput = {
2419
+ ...args["platform-url"] ? { platformUrl: args["platform-url"] } : {},
2420
+ ...args["oauth2-client-id"] ? { oauth2ClientId: args["oauth2-client-id"] } : {},
2421
+ ...args["console-url"] ? { consoleUrl: args["console-url"] } : {}
2422
+ };
2423
+ const platformConfig = Object.keys(platformConfigInput).length > 0 ? platformConfigInput : void 0;
2424
+ const { accessToken: token, user: resolvedUser } = await fetchLatestToken(config, args.user, platformConfig);
2425
+ const client = await initOperatorClient(token, platformConfig);
1928
2426
  if (!(await fetchAll(async (pageToken, maxPageSize) => {
1929
2427
  const { workspaces, nextPageToken } = await client.listWorkspaces({
1930
2428
  pageToken,
@@ -1937,7 +2435,10 @@ const createCommand$2 = defineAppCommand({
1937
2435
  workspace_id: args["workspace-id"],
1938
2436
  ...args.permission === "read" ? { readonly: true } : {},
1939
2437
  ...args["machine-user"] ? { machine_user: args["machine-user"] } : {},
1940
- ...args["machine-user-override"] === "deny" ? { machine_user_override: "deny" } : {}
2438
+ ...args["machine-user-override"] === "deny" ? { machine_user_override: "deny" } : {},
2439
+ ...args["platform-url"] ? { platform_url: args["platform-url"] } : {},
2440
+ ...args["oauth2-client-id"] ? { oauth2_client_id: args["oauth2-client-id"] } : {},
2441
+ ...args["console-url"] ? { console_url: args["console-url"] } : {}
1941
2442
  };
1942
2443
  writePlatformConfig(config);
1943
2444
  if (!args.json) logger.success(`Profile "${args.name}" created successfully.`);
@@ -1949,7 +2450,10 @@ const createCommand$2 = defineAppCommand({
1949
2450
  ...args["machine-user"] ? {
1950
2451
  machineUser: args["machine-user"],
1951
2452
  machineUserOverride: args["machine-user-override"] ?? "allow"
1952
- } : {}
2453
+ } : {},
2454
+ ...args["platform-url"] ? { platformUrl: args["platform-url"] } : {},
2455
+ ...args["oauth2-client-id"] ? { oauth2ClientId: args["oauth2-client-id"] } : {},
2456
+ ...args["console-url"] ? { consoleUrl: args["console-url"] } : {}
1953
2457
  };
1954
2458
  logger.out(profileInfo);
1955
2459
  }
@@ -1957,13 +2461,13 @@ const createCommand$2 = defineAppCommand({
1957
2461
 
1958
2462
  //#endregion
1959
2463
  //#region src/cli/commands/profile/delete.ts
1960
- const deleteCommand$2 = defineAppCommand({
2464
+ const deleteCommand$3 = defineAppCommand({
1961
2465
  name: "delete",
1962
2466
  description: "Delete a profile.",
1963
- args: z.object({ name: arg(z.string(), {
2467
+ args: z.strictObject({ name: arg(z.string(), {
1964
2468
  positional: true,
1965
2469
  description: "Profile name"
1966
- }) }).strict(),
2470
+ }) }),
1967
2471
  run: async (args) => {
1968
2472
  const config = await readPlatformConfig();
1969
2473
  if (!config.profiles[args.name]) throw new Error(`Profile "${args.name}" not found.`);
@@ -1978,7 +2482,7 @@ const deleteCommand$2 = defineAppCommand({
1978
2482
  const listCommand$4 = defineAppCommand({
1979
2483
  name: "list",
1980
2484
  description: "List all profiles.",
1981
- args: z.object({}).strict(),
2485
+ args: z.strictObject({}),
1982
2486
  run: async () => {
1983
2487
  const config = await readPlatformConfig();
1984
2488
  const jsonOutput = logger.jsonMode;
@@ -1986,7 +2490,7 @@ const listCommand$4 = defineAppCommand({
1986
2490
  if (profiles.length === 0) {
1987
2491
  logger.info(multiline`
1988
2492
  No profiles found.
1989
- Please create a profile first using 'tailor-sdk profile create' command.
2493
+ Please create a profile first using 'tailor profile create' command.
1990
2494
  `);
1991
2495
  if (jsonOutput) logger.out([]);
1992
2496
  return;
@@ -2001,7 +2505,10 @@ const listCommand$4 = defineAppCommand({
2001
2505
  ...p.machine_user ? {
2002
2506
  machineUser: p.machine_user,
2003
2507
  machineUserOverride: p.machine_user_override ?? "allow"
2004
- } : {}
2508
+ } : {},
2509
+ ...p.platform_url ? { platformUrl: p.platform_url } : {},
2510
+ ...p.oauth2_client_id ? { oauth2ClientId: p.oauth2_client_id } : {},
2511
+ ...p.console_url ? { consoleUrl: p.console_url } : {}
2005
2512
  };
2006
2513
  });
2007
2514
  logger.out(profileInfos);
@@ -2013,7 +2520,7 @@ const listCommand$4 = defineAppCommand({
2013
2520
  const updateCommand$1 = defineAppCommand({
2014
2521
  name: "update",
2015
2522
  description: "Update profile properties.",
2016
- args: z.object({
2523
+ args: z.strictObject({
2017
2524
  name: arg(z.string(), {
2018
2525
  positional: true,
2019
2526
  description: "Profile name"
@@ -2031,13 +2538,16 @@ const updateCommand$1 = defineAppCommand({
2031
2538
  alias: "m",
2032
2539
  description: "Default machine user name for application-data commands (query, workflow start, function test-run, machineuser token). Pass an empty string to clear."
2033
2540
  }),
2034
- "machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user; 'allow' lifts the restriction." })
2035
- }).strict(),
2541
+ "machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user; 'allow' lifts the restriction." }),
2542
+ "platform-url": arg(z.union([z.url(), z.literal("")]).optional(), { description: "Platform API base URL for this profile. Pass an empty string to clear." }),
2543
+ "oauth2-client-id": arg(z.string().optional(), { description: "OAuth2 client ID for logging in to this profile's platform. Pass an empty string to clear." }),
2544
+ "console-url": arg(z.union([z.url(), z.literal("")]).optional(), { description: "Console base URL for this profile. Pass an empty string to clear." })
2545
+ }),
2036
2546
  run: async (args) => {
2037
2547
  const config = await readPlatformConfig();
2038
2548
  const profile = config.profiles[args.name];
2039
2549
  if (!profile) throw new Error(`Profile "${args.name}" not found.`);
2040
- if (!args.user && !args["workspace-id"] && args.permission === void 0 && args["machine-user"] === void 0 && args["machine-user-override"] === void 0) throw new Error("Please provide at least one property to update.");
2550
+ if (!args.user && !args["workspace-id"] && args.permission === void 0 && args["machine-user"] === void 0 && args["machine-user-override"] === void 0 && args["platform-url"] === void 0 && args["oauth2-client-id"] === void 0 && args["console-url"] === void 0) throw new Error("Please provide at least one property to update.");
2041
2551
  const oldUser = profile.user;
2042
2552
  const newUser = args.user || oldUser;
2043
2553
  const oldWorkspaceId = profile.workspace_id;
@@ -2045,14 +2555,24 @@ const updateCommand$1 = defineAppCommand({
2045
2555
  let resolvedUser = newUser;
2046
2556
  const finalMachineUser = args["machine-user"] === "" ? void 0 : args["machine-user"] ?? profile.machine_user;
2047
2557
  const finalOverride = args["machine-user-override"] === "allow" ? void 0 : args["machine-user-override"] ?? profile.machine_user_override;
2558
+ const finalPlatformUrl = args["platform-url"] === "" ? void 0 : args["platform-url"] ?? profile.platform_url;
2559
+ const finalOAuth2ClientId = args["oauth2-client-id"] === "" ? void 0 : args["oauth2-client-id"] ?? profile.oauth2_client_id;
2560
+ const finalConsoleUrl = args["console-url"] === "" ? void 0 : args["console-url"] ?? profile.console_url;
2561
+ const finalPlatformConfigInput = {
2562
+ ...finalPlatformUrl ? { platformUrl: finalPlatformUrl } : {},
2563
+ ...finalOAuth2ClientId ? { oauth2ClientId: finalOAuth2ClientId } : {},
2564
+ ...finalConsoleUrl ? { consoleUrl: finalConsoleUrl } : {}
2565
+ };
2566
+ const finalPlatformConfig = Object.keys(finalPlatformConfigInput).length > 0 ? finalPlatformConfigInput : void 0;
2567
+ const tokenLookupPlatformConfig = args["platform-url"] === "" ? platformConfigFromProfile(profile) : finalPlatformConfig;
2048
2568
  if ((args["machine-user"] !== void 0 || args["machine-user-override"] !== void 0) && finalOverride === "deny" && !finalMachineUser) {
2049
2569
  if (args["machine-user-override"] === "deny") throw new Error("--machine-user-override deny requires --machine-user.");
2050
2570
  throw new Error(`Cannot clear the machine user while machine-user-override is "deny". Also pass --machine-user-override allow.`);
2051
2571
  }
2052
- if (args.user !== void 0 || args["workspace-id"] !== void 0) {
2053
- const refreshed = await fetchLatestToken(config, newUser);
2572
+ if (args.user !== void 0 || args["workspace-id"] !== void 0 || args["platform-url"] !== void 0) {
2573
+ const refreshed = await fetchLatestToken(config, newUser, tokenLookupPlatformConfig);
2054
2574
  resolvedUser = refreshed.user;
2055
- const client = await initOperatorClient(refreshed.accessToken);
2575
+ const client = await initOperatorClient(refreshed.accessToken, finalPlatformConfig);
2056
2576
  if (!(await fetchAll(async (pageToken, maxPageSize) => {
2057
2577
  const { workspaces, nextPageToken } = await client.listWorkspaces({
2058
2578
  pageToken,
@@ -2069,6 +2589,12 @@ const updateCommand$1 = defineAppCommand({
2069
2589
  else profile.machine_user = args["machine-user"];
2070
2590
  if (args["machine-user-override"] === "deny") profile.machine_user_override = "deny";
2071
2591
  else if (args["machine-user-override"] === "allow") delete profile.machine_user_override;
2592
+ if (args["platform-url"] !== void 0) if (args["platform-url"] === "") delete profile.platform_url;
2593
+ else profile.platform_url = args["platform-url"];
2594
+ if (args["oauth2-client-id"] !== void 0) if (args["oauth2-client-id"] === "") delete profile.oauth2_client_id;
2595
+ else profile.oauth2_client_id = args["oauth2-client-id"];
2596
+ if (args["console-url"] !== void 0) if (args["console-url"] === "") delete profile.console_url;
2597
+ else profile.console_url = args["console-url"];
2072
2598
  writePlatformConfig(config);
2073
2599
  if (!args.json) logger.success(`Profile "${args.name}" updated successfully`);
2074
2600
  const profileInfo = {
@@ -2079,7 +2605,10 @@ const updateCommand$1 = defineAppCommand({
2079
2605
  ...profile.machine_user ? {
2080
2606
  machineUser: profile.machine_user,
2081
2607
  machineUserOverride: profile.machine_user_override ?? "allow"
2082
- } : {}
2608
+ } : {},
2609
+ ...profile.platform_url ? { platformUrl: profile.platform_url } : {},
2610
+ ...profile.oauth2_client_id ? { oauth2ClientId: profile.oauth2_client_id } : {},
2611
+ ...profile.console_url ? { consoleUrl: profile.console_url } : {}
2083
2612
  };
2084
2613
  logger.out(profileInfo);
2085
2614
  }
@@ -2092,7 +2621,7 @@ const profileCommand = defineCommand({
2092
2621
  description: "Manage workspace profiles (user + workspace combinations).",
2093
2622
  subCommands: {
2094
2623
  create: createCommand$2,
2095
- delete: deleteCommand$2,
2624
+ delete: deleteCommand$3,
2096
2625
  list: listCommand$4,
2097
2626
  update: updateCommand$1
2098
2627
  },
@@ -2187,11 +2716,11 @@ async function releaseVaultOwnership(params) {
2187
2716
  const createSecretCommand = defineAppCommand({
2188
2717
  name: "create",
2189
2718
  description: "Create a secret in a vault.",
2190
- args: z.object({
2719
+ args: z.strictObject({
2191
2720
  ...workspaceArgs,
2192
2721
  ...secretValueArgs,
2193
2722
  ...confirmationArgs
2194
- }).strict(),
2723
+ }),
2195
2724
  run: async (args) => {
2196
2725
  await assertWritable({ profile: args.profile });
2197
2726
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
@@ -2237,11 +2766,11 @@ const createSecretCommand = defineAppCommand({
2237
2766
  const deleteSecretCommand = defineAppCommand({
2238
2767
  name: "delete",
2239
2768
  description: "Delete a secret in a vault.",
2240
- args: z.object({
2769
+ args: z.strictObject({
2241
2770
  ...workspaceArgs,
2242
2771
  ...secretIdentifyArgs,
2243
2772
  ...confirmationArgs
2244
- }).strict(),
2773
+ }),
2245
2774
  run: async (args) => {
2246
2775
  await assertWritable({ profile: args.profile });
2247
2776
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
@@ -2313,11 +2842,11 @@ async function secretList(options) {
2313
2842
  const listSecretCommand = defineAppCommand({
2314
2843
  name: "list",
2315
2844
  description: "List all secrets in a vault.",
2316
- args: z.object({
2845
+ args: z.strictObject({
2317
2846
  ...workspaceArgs,
2318
2847
  ...vaultArgs,
2319
2848
  ...paginationArgs()
2320
- }).strict(),
2849
+ }),
2321
2850
  run: async (args) => {
2322
2851
  try {
2323
2852
  const secrets = await secretList({
@@ -2340,11 +2869,11 @@ const listSecretCommand = defineAppCommand({
2340
2869
  const updateSecretCommand = defineAppCommand({
2341
2870
  name: "update",
2342
2871
  description: "Update a secret in a vault.",
2343
- args: z.object({
2872
+ args: z.strictObject({
2344
2873
  ...workspaceArgs,
2345
2874
  ...secretValueArgs,
2346
2875
  ...confirmationArgs
2347
- }).strict(),
2876
+ }),
2348
2877
  run: async (args) => {
2349
2878
  await assertWritable({ profile: args.profile });
2350
2879
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
@@ -2394,10 +2923,10 @@ const nameArgs = { name: arg(z.string(), {
2394
2923
  const createCommand$1 = defineAppCommand({
2395
2924
  name: "create",
2396
2925
  description: "Create a new Secret Manager vault.",
2397
- args: z.object({
2926
+ args: z.strictObject({
2398
2927
  ...workspaceArgs,
2399
2928
  ...nameArgs
2400
- }).strict(),
2929
+ }),
2401
2930
  run: async (args) => {
2402
2931
  await assertWritable({ profile: args.profile });
2403
2932
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
@@ -2420,14 +2949,14 @@ const createCommand$1 = defineAppCommand({
2420
2949
 
2421
2950
  //#endregion
2422
2951
  //#region src/cli/commands/secret/vault/delete.ts
2423
- const deleteCommand$1 = defineAppCommand({
2952
+ const deleteCommand$2 = defineAppCommand({
2424
2953
  name: "delete",
2425
2954
  description: "Delete a Secret Manager vault.",
2426
- args: z.object({
2955
+ args: z.strictObject({
2427
2956
  ...workspaceArgs,
2428
2957
  ...nameArgs,
2429
2958
  ...confirmationArgs
2430
- }).strict(),
2959
+ }),
2431
2960
  run: async (args) => {
2432
2961
  await assertWritable({ profile: args.profile });
2433
2962
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
@@ -2494,10 +3023,10 @@ async function vaultList(options) {
2494
3023
  const listCommand$3 = defineAppCommand({
2495
3024
  name: "list",
2496
3025
  description: "List all Secret Manager vaults in the workspace.",
2497
- args: z.object({
3026
+ args: z.strictObject({
2498
3027
  ...workspaceArgs,
2499
3028
  ...paginationArgs()
2500
- }).strict(),
3029
+ }),
2501
3030
  run: async (args) => {
2502
3031
  const vaults = await vaultList({
2503
3032
  workspaceId: args["workspace-id"],
@@ -2516,7 +3045,7 @@ const vaultCommand = defineCommand({
2516
3045
  description: "Manage Secret Manager vaults.",
2517
3046
  subCommands: {
2518
3047
  create: createCommand$1,
2519
- delete: deleteCommand$1,
3048
+ delete: deleteCommand$2,
2520
3049
  list: listCommand$3
2521
3050
  },
2522
3051
  async run() {
@@ -2577,7 +3106,7 @@ function detectDefaultBranch(cwd, run = defaultGitRunner) {
2577
3106
  /** Current lock schema version. Bumped only on breaking lock-format changes. */
2578
3107
  const LOCK_VERSION = 1;
2579
3108
  /** Lock file path, relative to the repository root. */
2580
- const LOCK_FILENAME = ".github/tailor-sdk.lock";
3109
+ const LOCK_FILENAME = ".github/tailor.lock";
2581
3110
  /**
2582
3111
  * Compute the lock content hash for a rendered workflow file.
2583
3112
  * @param content - File content to hash
@@ -2609,11 +3138,11 @@ function readLock(outputDir) {
2609
3138
  try {
2610
3139
  parsed = JSON.parse(fs$1.readFileSync(file, "utf-8"));
2611
3140
  } catch (cause) {
2612
- throw new Error(`${LOCK_FILENAME} is not valid JSON. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor-sdk.lock) and re-run setup.`, { cause });
3141
+ throw new Error(`${LOCK_FILENAME} is not valid JSON. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor.lock) and re-run setup.`, { cause });
2613
3142
  }
2614
- if (typeof parsed.version !== "number") throw new Error(`${LOCK_FILENAME} has no valid 'version' field. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor-sdk.lock) and re-run setup.`);
3143
+ if (typeof parsed.version !== "number") throw new Error(`${LOCK_FILENAME} has no valid 'version' field. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor.lock) and re-run setup.`);
2615
3144
  if (parsed.version > 1) throw new Error(`${LOCK_FILENAME} was written by a newer SDK (lock version ${String(parsed.version)}). Update @tailor-platform/sdk to continue (e.g. pnpm update @tailor-platform/sdk).`);
2616
- if (!Array.isArray(parsed.targets)) throw new Error(`${LOCK_FILENAME} has no valid 'targets' array. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor-sdk.lock) and re-run setup.`);
3145
+ if (!Array.isArray(parsed.targets)) throw new Error(`${LOCK_FILENAME} has no valid 'targets' array. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor.lock) and re-run setup.`);
2617
3146
  return parsed;
2618
3147
  }
2619
3148
  /**
@@ -2638,22 +3167,34 @@ function findTarget(lock, kind, workspaceName) {
2638
3167
  return lock?.targets.find((t) => t.kind === kind && t.workspaceName === workspaceName);
2639
3168
  }
2640
3169
 
3170
+ //#endregion
3171
+ //#region src/cli/commands/setup/action.yml
3172
+ var action_default = "# __HEADER__\nname: __WORKSPACE_NAME__\ndescription: >-\n Deploy __WORKSPACE_NAME__ to Tailor Platform.\n Checkout and dependency installation are expected to be handled by the caller.\n\ninputs:\n workspace-id:\n description: Tailor Platform workspace id\n required: true\n name:\n description: Workspace name (used in Slack notifications)\n required: true\n working-directory:\n description: App directory relative to the repository root\n required: false\n default: \".\"\n platform-client-id:\n description: Machine-user client id\n required: true\n platform-client-secret:\n description: Machine-user client secret\n required: true\n slack-token:\n description: Slack bot token\n required: false\n slack-channel-id:\n description: Slack channel id\n required: false\n user-mapping:\n description: JSON string mapping GitHub usernames to Slack user IDs\n required: false\n package-manager:\n description: Package manager used for dependency installation (pnpm, npm, yarn, bun)\n required: false\n default: pnpm\n\noutputs:\n app-url:\n description: Deployed application URL\n value: ${{ steps.tailor-apply.outputs.app-url }}\n\nruns:\n using: composite\n steps:\n # __STATIC_WEBSITE_BUILD_START__\n - id: build-site\n shell: bash\n run: |\n # Replace with your static website build command.\n # Example: pnpm --filter <package-name> run build:static\n # Note: this run: command is user-editable. Custom build commands are preserved across\n # reruns and are not treated as drift by setup check. Other edits to this file are flagged\n # as drift and will be overwritten if you re-run setup action with --force.\n true\n # __STATIC_WEBSITE_BUILD_END__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n workspace-id: ${{ inputs.workspace-id }}\n working-directory: ${{ inputs.working-directory }}\n package-manager: ${{ inputs.package-manager }}\n platform-client-id: ${{ inputs.platform-client-id }}\n platform-client-secret: ${{ inputs.platform-client-secret }}\n - id: tailor-notify\n if: failure() && inputs.slack-token != '' && inputs.slack-channel-id != ''\n uses: tailor-platform/actions/notify@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n provider: slack\n status: failure\n workspace-name: ${{ inputs.name }}\n slack-token: ${{ inputs.slack-token }}\n slack-channel-id: ${{ inputs.slack-channel-id }}\n user-mapping: ${{ inputs.user-mapping }}\n";
3173
+
2641
3174
  //#endregion
2642
3175
  //#region src/cli/commands/setup/branch.workflow.yml
2643
- var branch_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n # __PULL_REQUEST_END__\n push:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n workflow_dispatch:\n # __DISPATCH_INPUTS_START__\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n # __DISPATCH_INPUTS_END__\n\npermissions:\n contents: read\n\njobs:\n # __PLAN_JOB_START__\n tailor-plan:\n if: >-\n github.event_name == 'pull_request' ||\n (github.event_name == 'workflow_dispatch' && inputs['dry-run'])\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n - id: tailor-plan\n # Fork PRs cannot read secrets; the checks above still run for them.\n if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork\n uses: tailor-platform/actions/plan@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n # __PLAN_JOB_END__\n tailor-deploy:\n # __DEPLOY_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n environment: __ENVIRONMENT__\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n";
3176
+ var branch_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n # __PULL_REQUEST_END__\n push:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n workflow_dispatch:\n # __DISPATCH_INPUTS_START__\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n # __DISPATCH_INPUTS_END__\n\npermissions:\n contents: read\n\njobs:\n # __PLAN_JOB_START__\n tailor-plan:\n if: >-\n github.event_name == 'pull_request' ||\n (github.event_name == 'workflow_dispatch' && inputs['dry-run'])\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n uses: tailor-platform/actions/seed-validate@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\n - id: tailor-plan\n # Fork PRs cannot read secrets; the checks above still run for them.\n if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork\n uses: tailor-platform/actions/plan@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n # __PLAN_JOB_END__\n # __ERD_PREVIEW_JOB_START__\n tailor-erd-preview-matrix:\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n namespaces: ${{ steps.tailor-erd-preview-matrix.outputs.namespaces }}\n base-app-dir: ${{ steps.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-erd-preview-matrix\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n WORKSPACE_NAME: \"__WORKSPACE_NAME__\"\n run: |\n set -euo pipefail\n node <<'NODE'\n const fs = require(\"node:fs\");\n\n const appDir = process.env.APP_DIR || \".\";\n const workspaceName = process.env.WORKSPACE_NAME;\n const output = process.env.GITHUB_OUTPUT;\n const dirPattern = /^[A-Za-z0-9._/-]+$/;\n const namespacePattern = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\n function lockTarget(file) {\n try {\n const lock = JSON.parse(fs.readFileSync(file, \"utf8\"));\n return lock.targets?.find(\n (candidate) =>\n candidate.kind === \"branch\" &&\n candidate.workspaceName === workspaceName,\n );\n } catch (error) {\n if (error && error.code === \"ENOENT\") return undefined;\n throw error;\n }\n }\n\n function namespacesFromTarget(target, label) {\n const namespaces =\n target?.inputs?.erdPreview === true && Array.isArray(target.inputs?.erdNamespaces)\n ? target.inputs.erdNamespaces\n : [];\n for (const namespace of namespaces) {\n if (typeof namespace !== \"string\" || !namespacePattern.test(namespace)) {\n throw new Error(`Invalid ERD namespace in ${label}: ${String(namespace)}`);\n }\n }\n return namespaces;\n }\n\n function normalizeDir(value, label) {\n const raw = typeof value === \"string\" && value.length > 0 ? value : appDir;\n const normalized =\n raw.replaceAll(\"\\\\\", \"/\").replace(/\\/{2,}/g, \"/\").replace(/^\\.\\//, \"\").replace(/\\/$/, \"\") ||\n \".\";\n if (\n !dirPattern.test(normalized) ||\n normalized.startsWith(\"/\") ||\n normalized.split(\"/\").includes(\"..\")\n ) {\n throw new Error(`Invalid ERD app dir in ${label}: ${raw}`);\n }\n return normalized;\n }\n\n const headTarget = lockTarget(\".github/tailor.lock\");\n const baseTarget = lockTarget(\".tailor-erd-base/.github/tailor.lock\");\n const baseAppDir = normalizeDir(baseTarget?.inputs?.dir, \"base setup lock\");\n const namespaces = [\n ...new Set([\n ...namespacesFromTarget(headTarget, \"head setup lock\"),\n ...namespacesFromTarget(baseTarget, \"base setup lock\"),\n ]),\n ].sort((a, b) => a.localeCompare(b));\n\n if (namespaces.length === 0) {\n console.error(\"No ERD preview namespaces found in the head or base setup lock.\");\n process.exit(1);\n }\n\n fs.appendFileSync(output, `namespaces=${JSON.stringify(namespaces)}\\n`);\n fs.appendFileSync(output, `base-app-dir=${baseAppDir}\\n`);\n NODE\n\n tailor-erd-preview:\n needs: tailor-erd-preview-matrix\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n strategy:\n fail-fast: false\n matrix:\n namespace: ${{ fromJSON(needs.tailor-erd-preview-matrix.outputs.namespaces) }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-detect-base-package-manager\n shell: bash\n run: |\n set -euo pipefail\n cd .tailor-erd-base\n base_package_manager=\"$(\n if [ -f pnpm-lock.yaml ]; then\n echo pnpm\n elif [ -f yarn.lock ]; then\n echo yarn\n elif [ -f bun.lockb ] || [ -f bun.lock ]; then\n echo bun\n elif [ -f package-lock.json ]; then\n echo npm\n else\n echo \"__PACKAGE_MANAGER__\"\n fi\n )\"\n echo \"package-manager=$base_package_manager\" >> \"$GITHUB_OUTPUT\"\n if [ \"$base_package_manager\" = \"pnpm\" ]; then\n base_pnpm_version=\"$(node <<'NODE'\n const fs = require(\"node:fs\");\n\n const fallback = \"10\";\n\n function readManifest(file) {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf8\"));\n } catch {\n return {};\n }\n }\n\n function pnpmVersion(manifest) {\n const packageManager = manifest.packageManager;\n if (typeof packageManager === \"string\" && packageManager.startsWith(\"pnpm@\")) {\n return packageManager.slice(\"pnpm@\".length).split(\"+\")[0];\n }\n\n const devPackageManager = manifest.devEngines?.packageManager;\n if (devPackageManager?.name === \"pnpm\" && devPackageManager.version) {\n return devPackageManager.version;\n }\n\n return undefined;\n }\n\n process.stdout.write(\n pnpmVersion(readManifest(\"package.json\")) ?? fallback,\n );\n NODE\n )\"\n echo \"pnpm-version=$base_pnpm_version\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-setup-base-pnpm\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'pnpm'\n uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9\n with:\n version: ${{ steps.tailor-detect-base-package-manager.outputs['pnpm-version'] }}\n package_json_file: .tailor-erd-base/package.json\n run_install: false\n - id: tailor-setup-base-node\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] != 'bun'\n uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: .tailor-erd-base/package.json\n - id: tailor-setup-base-yarn\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'yarn'\n shell: bash\n working-directory: .tailor-erd-base\n run: |\n set -euo pipefail\n corepack enable\n - id: tailor-setup-base-bun\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'bun'\n uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n - id: tailor-install-base\n shell: bash\n working-directory: .tailor-erd-base\n env:\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n run: |\n set -euo pipefail\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) pnpm install --frozen-lockfile ;;\n npm) npm ci ;;\n yarn)\n if grep -q '^__metadata:' yarn.lock; then\n yarn install --immutable\n else\n yarn install --frozen-lockfile\n fi\n ;;\n bun) bun install --frozen-lockfile ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n - id: tailor-restore-head-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-build-erd-preview\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n BASE_APP_DIR: ${{ needs.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n NAMESPACE: ${{ matrix.namespace }}\n run: |\n set -euo pipefail\n\n run_tailor_cli() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec tailor \"$@\" ;;\n npm) npx tailor \"$@\" ;;\n yarn) yarn tailor \"$@\" ;;\n bun) bunx tailor \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n run_head_node() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec node \"$@\" ;;\n npm) node \"$@\" ;;\n yarn) yarn node \"$@\" ;;\n bun) node \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n tailor_cli_bin=\"$(\n run_head_node - <<'NODE'\n const path = require(\"node:path\");\n const { createRequire } = require(\"node:module\");\n const appDir = process.env.APP_DIR || \".\";\n const githubWorkspace = process.env.GITHUB_WORKSPACE;\n if (!githubWorkspace) {\n throw new Error(\"GITHUB_WORKSPACE is required.\");\n }\n\n let lastError;\n for (const manifestPath of [\n path.join(githubWorkspace, appDir, \"package.json\"),\n path.join(githubWorkspace, \"package.json\"),\n ]) {\n try {\n const requireFromManifest = createRequire(manifestPath);\n process.stdout.write(\n path.join(\n path.dirname(requireFromManifest.resolve(\"@tailor-platform/sdk/cli\")),\n \"index.mjs\",\n ),\n );\n process.exit(0);\n } catch (error) {\n lastError = error;\n }\n }\n throw lastError || new Error(\"Failed to resolve @tailor-platform/sdk/cli.\");\n NODE\n )\"\n\n run_head_tailor_cli_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_CLI_BIN=\"$tailor_cli_bin\" TAILOR_CLI_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$GITHUB_WORKSPACE\" || exit 1\n case \"__PACKAGE_MANAGER__\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n run_base_tailor_cli_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_CLI_BIN=\"$tailor_cli_bin\" TAILOR_CLI_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$command_cwd\" || exit 1\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n head_output=\"$RUNNER_TEMP/tailor-erd/head\"\n base_output=\"$RUNNER_TEMP/tailor-erd/base\"\n preview_output=\"$RUNNER_TEMP/tailor-erd/preview\"\n dts_output=\"$RUNNER_TEMP/tailor-erd/dts\"\n head_cli_runner=\"$RUNNER_TEMP/tailor-erd/run-head-cli.mjs\"\n head_log=\"$RUNNER_TEMP/tailor-erd/head-$NAMESPACE.log\"\n base_log=\"$RUNNER_TEMP/tailor-erd/base-$NAMESPACE.log\"\n rm -rf \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n mkdir -p \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n cat > \"$head_cli_runner\" <<'NODE'\n import { pathToFileURL } from \"node:url\";\n\n const cliBin = process.env.TAILOR_CLI_BIN;\n const commandCwd = process.env.TAILOR_CLI_CWD;\n if (!cliBin || !commandCwd) {\n throw new Error(\"TAILOR_CLI_BIN and TAILOR_CLI_CWD are required.\");\n }\n\n process.chdir(commandCwd);\n process.argv = [process.argv[0], cliBin, ...process.argv.slice(2)];\n await import(pathToFileURL(cliBin).href);\n NODE\n\n head_config=\"$GITHUB_WORKSPACE/$APP_DIR/tailor.config.ts\"\n base_config=\"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR/tailor.config.ts\"\n head_html=\"$head_output/$NAMESPACE/dist/index.html\"\n base_html=\"$base_output/$NAMESPACE/dist/index.html\"\n preview_html=\"$preview_output/$NAMESPACE-viewer.html\"\n\n head_missing=\"false\"\n if [ ! -f \"$head_config\" ]; then\n echo \"Head ERD config not found; rendering base objects as removed.\"\n head_missing=\"true\"\n elif ! (\n cd \"$GITHUB_WORKSPACE/$APP_DIR\" || exit 1\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/head-$NAMESPACE.d.ts\" \\\n run_tailor_cli tailordb erd export --config \"$head_config\" --namespace \"$NAMESPACE\" --output \"$head_output\"\n ) >\"$head_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$head_log\"; then\n cat \"$head_log\"\n echo \"Head ERD namespace '$NAMESPACE' not found; rendering base objects as removed.\"\n head_missing=\"true\"\n else\n cat \"$head_log\"\n exit 1\n fi\n elif [ -s \"$head_log\" ]; then\n cat \"$head_log\"\n fi\n\n base_missing=\"false\"\n if [ ! -f \"$base_config\" ]; then\n echo \"Base ERD config not found; rendering current objects as added.\"\n base_missing=\"true\"\n elif ! (\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/base-$NAMESPACE.d.ts\" \\\n run_base_tailor_cli_bin \"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR\" tailordb erd export --config \"$base_config\" --namespace \"$NAMESPACE\" --output \"$base_output\"\n ) >\"$base_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$base_log\"; then\n cat \"$base_log\"\n echo \"Base ERD namespace '$NAMESPACE' not found; rendering current objects as added.\"\n base_missing=\"true\"\n else\n cat \"$base_log\"\n exit 1\n fi\n elif [ -s \"$base_log\" ]; then\n cat \"$base_log\"\n fi\n\n if [ \"$head_missing\" = \"true\" ] && [ \"$base_missing\" = \"true\" ]; then\n echo \"::error::ERD namespace '$NAMESPACE' was not found in head or base.\"\n exit 1\n fi\n if [ \"$head_missing\" = \"false\" ] && [ ! -f \"$head_html\" ]; then\n echo \"::error::Head ERD viewer was not generated at $head_html\"\n exit 1\n fi\n if [ \"$base_missing\" = \"false\" ] && [ ! -f \"$base_html\" ]; then\n echo \"::error::Base ERD viewer was not generated at $base_html\"\n exit 1\n fi\n\n diff_args=(--namespace \"$NAMESPACE\" --output \"$preview_html\")\n if [ \"$head_missing\" = \"false\" ]; then\n diff_args+=(--head-html \"$head_html\")\n fi\n if [ \"$base_missing\" = \"false\" ]; then\n diff_args+=(--base-html \"$base_html\")\n fi\n run_head_tailor_cli_bin \"$GITHUB_WORKSPACE\" tailordb erd diff \"${diff_args[@]}\"\n - id: tailor-upload-erd-viewer\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: ${{ matrix.namespace }}-viewer.html\n path: ${{ runner.temp }}/tailor-erd/preview/${{ matrix.namespace }}-viewer.html\n if-no-files-found: error\n archive: false\n retention-days: 7\n\n # __ERD_PREVIEW_JOB_END__\n # __ERD_PREVIEW_COMMENT_JOB_START__\n tailor-erd-preview-comment:\n needs: tailor-erd-preview\n if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n actions: read\n pull-requests: write\n steps:\n - id: tailor-comment-erd-preview\n env:\n GH_TOKEN: ${{ github.token }}\n REPO: ${{ github.repository }}\n RUN_ID: ${{ github.run_id }}\n SERVER: ${{ github.server_url }}\n PR_NUMBER: ${{ github.event.pull_request.number }}\n BASE_REF: ${{ github.event.pull_request.base.ref }}\n HEAD_SHA: ${{ github.event.pull_request.head.sha }}\n MARKER: \"<!-- tailor-erd-preview: __WORKSPACE_NAME__ -->\"\n run: |\n set -euo pipefail\n\n current_head=$(gh api \"repos/$REPO/pulls/$PR_NUMBER\" --jq '.head.sha')\n if [ \"$current_head\" != \"$HEAD_SHA\" ]; then\n echo \"Skipping stale ERD preview comment for $HEAD_SHA; current head is $current_head.\"\n exit 0\n fi\n\n body=\"$MARKER\"$'\\n'\"### TailorDB ERD preview (__WORKSPACE_NAME__)\"$'\\n\\n'\n body+=\"Self-contained ERD viewer HTML artifacts for this pull request. Each viewer can switch between the current schema and a diff against base branch \\`$BASE_REF\\`.\"$'\\n'\n\n while IFS=$'\\t' read -r name id; do\n [ -z \"$name\" ] && continue\n if [[ \"$name\" != *-viewer.html ]]; then\n continue\n fi\n ns=${name%-viewer.html}\n label=\"$ns viewer\"\n body+=$'\\n'\"- **$label**: [$name]($SERVER/$REPO/actions/runs/$RUN_ID/artifacts/$id)\"\n done < <(gh api \"repos/$REPO/actions/runs/$RUN_ID/artifacts\" --paginate \\\n --jq '.artifacts[] | select(.name | endswith(\"-viewer.html\")) | [.name, (.id|tostring)] | @tsv' | sort)\n\n existing=$(gh api \"repos/$REPO/issues/$PR_NUMBER/comments\" --paginate \\\n --jq 'map(select(.body | contains(env.MARKER))) | .[0].id // empty')\n if [ -n \"$existing\" ]; then\n gh api --method PATCH \"repos/$REPO/issues/comments/$existing\" -f body=\"$body\" >/dev/null\n else\n gh api --method POST \"repos/$REPO/issues/$PR_NUMBER/comments\" -f body=\"$body\" >/dev/null\n fi\n\n # __ERD_PREVIEW_COMMENT_JOB_END__\n tailor-deploy:\n # __DEPLOY_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n environment: __ENVIRONMENT__\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
3177
+
3178
+ //#endregion
3179
+ //#region src/cli/commands/setup/coordinate.workflow.yml
3180
+ var coordinate_workflow_default = "# __HEADER__\nname: Tailor Coordinator (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PULL_REQUEST_END__\n push:\n # __PUSH_BRANCHES_START__\n branches: [\"__BRANCH__\"]\n # __PUSH_BRANCHES_END__\n # __PUSH_TAGS_START__\n tags: [__TAG_PATTERN__]\n # __PUSH_TAGS_END__\n workflow_dispatch:\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n\npermissions:\n contents: read\n\njobs:\n # __TAG_GUARD_JOB_START__\n tailor-tag-guard:\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n on-branch: ${{ steps.tailor-tag-guard.outputs.on-branch }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n target-branch: __BRANCH__\n # __TAG_GUARD_JOB_END__\n\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 60\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: ./.github/actions/tailor-setup\n # __PER_APP_PLAN_STEPS__\n\n tailor-deploy:\n # __DEPLOY_NEEDS__\n # __DEPLOY_IF__\n # __DEPLOY_ENVIRONMENT__\n runs-on: ubuntu-latest\n timeout-minutes: 60\n permissions:\n contents: read\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: ./.github/actions/tailor-setup\n # __PER_APP_DEPLOY_STEPS__\n";
3181
+
3182
+ //#endregion
3183
+ //#region src/cli/commands/setup/preview.workflow.yml
3184
+ var preview_workflow_default = "# __HEADER__\nname: Tailor Preview (__WORKSPACE_NAME__)\n\non:\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PR_TYPES__\n # To deploy only for PRs that carry the `tailor:preview` label instead of all PRs,\n # re-run: tailor setup preview --require-preview-label\n # __PATHS__\n\npermissions:\n contents: read\n pull-requests: write\n\njobs:\n tailor-preview-deploy:\n # __DEPLOY_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-preview-__WORKSPACE_NAME__-${{ github.event.pull_request.number }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n - id: tailor-preview-deploy\n uses: tailor-platform/actions/preview-deploy@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n workspace-name-prefix: __WORKSPACE_NAME__\n region: \"__REGION__\"\n organization-id: ${{ vars.TAILOR_PLATFORM_ORGANIZATION_ID }}\n folder-id: ${{ vars.TAILOR_PLATFORM_FOLDER_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n - id: tailor-preview-comment\n if: always()\n uses: tailor-platform/actions/preview-comment@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n workspace-id: ${{ steps.tailor-preview-deploy.outputs.workspace-id }}\n workspace-name: ${{ steps.tailor-preview-deploy.outputs.workspace-name }}\n status: ${{ job.status == 'success' && 'success' || 'failure' }}\n app-url: ${{ steps.tailor-preview-deploy.outputs.app-url }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n\n tailor-preview-cleanup:\n # __CLEANUP_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 15\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-preview-cleanup\n uses: tailor-platform/actions/preview-cleanup@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n workspace-name-prefix: __WORKSPACE_NAME__\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n";
2644
3185
 
2645
3186
  //#endregion
2646
3187
  //#region src/cli/commands/setup/tag.workflow.yml
2647
- var tag_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n push:\n tags: [\"__TAG_PATTERN__\"]\n workflow_dispatch:\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n\npermissions:\n contents: read\n\njobs:\n # __TAG_GUARD_JOB_START__\n tailor-tag-guard:\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: read\n outputs:\n on-branch: ${{ steps.tailor-tag-guard.outputs.on-branch }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n target-branch: \"__BRANCH__\"\n # __TAG_GUARD_JOB_END__\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n steps:\n - id: tailor-checkout\n uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n - id: tailor-plan\n uses: tailor-platform/actions/plan@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n\n tailor-deploy:\n needs: tailor-plan\n if: ${{ !(github.event_name == 'workflow_dispatch' && inputs['dry-run']) }}\n environment: __ENVIRONMENT__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n";
3188
+ var tag_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n push:\n tags: [\"__TAG_PATTERN__\"]\n workflow_dispatch:\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n\npermissions:\n contents: read\n\njobs:\n # __TAG_GUARD_JOB_START__\n tailor-tag-guard:\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: read\n outputs:\n on-branch: ${{ steps.tailor-tag-guard.outputs.on-branch }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n target-branch: \"__BRANCH__\"\n # __TAG_GUARD_JOB_END__\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n uses: tailor-platform/actions/seed-validate@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\n - id: tailor-plan\n uses: tailor-platform/actions/plan@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n\n tailor-deploy:\n needs: tailor-plan\n if: ${{ !(github.event_name == 'workflow_dispatch' && inputs['dry-run']) }}\n environment: __ENVIRONMENT__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
2648
3189
 
2649
3190
  //#endregion
2650
3191
  //#region src/cli/commands/setup/templates.ts
2651
- const HEADER = `# Generated by \`tailor-sdk setup\` — managed by the Tailor SDK.
3192
+ const HEADER = `# Generated by \`tailor setup\` — managed by the Tailor SDK.
2652
3193
  #
2653
3194
  # - Jobs and steps whose id starts with \`tailor-\` are managed by the SDK.
2654
3195
  # Do not edit or rename them.
2655
- # - State is tracked in .github/tailor-sdk.lock (machine-owned: commit it, never edit it).
2656
- # - Re-running \`tailor-sdk setup\` regenerates this file. If you have
3196
+ # - State is tracked in .github/tailor.lock (machine-owned: commit it, never edit it).
3197
+ # - Re-running \`tailor setup\` regenerates this file. If you have
2657
3198
  # edited it by hand, regeneration stops and asks for --force (which discards
2658
3199
  # your edits), so prefer keeping customizations in your own jobs/steps and
2659
3200
  # re-running setup after SDK updates.`;
@@ -2684,29 +3225,43 @@ function applyCommon(content, params) {
2684
3225
  const { workingDirectory, environment, packageManager } = params;
2685
3226
  let out = line(content, "HEADER", HEADER);
2686
3227
  out = line(out, "WORKING_DIRECTORY", workingDirectory ? `working-directory: ${workingDirectory}` : void 0);
2687
- return out.replaceAll("__WORKSPACE_NAME__", () => params.workspaceName).replaceAll("__ENVIRONMENT__", () => environment).replaceAll("__PACKAGE_MANAGER__", () => packageManager);
3228
+ return out.replaceAll("__WORKSPACE_NAME__", () => params.workspaceName).replaceAll("__ENVIRONMENT__", () => environment).replaceAll("__PACKAGE_MANAGER__", () => packageManager).replaceAll("__APP_DIR__", () => workingDirectory ?? ".");
2688
3229
  }
2689
3230
  /**
2690
3231
  * Render the branch-target deploy workflow.
2691
- *
2692
- * When `plan` is false the plan job, the pull_request trigger, and the
2693
- * workflow_dispatch dry-run input are all removed (a plan-less workflow has no
2694
- * meaningful dry-run), and the deploy job runs unconditionally.
2695
3232
  * @param params - Workspace and rendering configuration
2696
3233
  * @returns Rendered YAML and the list of managed job/step ids
2697
3234
  */
2698
3235
  function renderBranchWorkflow(params) {
2699
- const { branch, plan } = params;
3236
+ const { branch } = params;
3237
+ const seedValidate = params.seedValidate ?? false;
3238
+ const migrationDriftCheck = params.migrationDriftCheck ?? false;
3239
+ const erdPreview = params.erdPreview;
2700
3240
  let out = branch_workflow_default;
2701
- out = block(out, "PLAN_JOB", plan);
2702
- out = block(out, "PULL_REQUEST", plan);
2703
- out = block(out, "DISPATCH_INPUTS", plan);
2704
- out = line(out, "DEPLOY_IF", plan ? `if: >-\n github.event_name == 'push' ||\n (github.event_name == 'workflow_dispatch' && !inputs['dry-run'])` : void 0);
3241
+ out = block(out, "PLAN_JOB", true);
3242
+ out = block(out, "ERD_PREVIEW_JOB", erdPreview !== null);
3243
+ out = block(out, "ERD_PREVIEW_COMMENT_JOB", erdPreview !== null);
3244
+ out = block(out, "PULL_REQUEST", true);
3245
+ out = block(out, "DISPATCH_INPUTS", true);
3246
+ out = block(out, "SEED_VALIDATE", seedValidate);
3247
+ out = block(out, "MIGRATION_DRIFT_CHECK", migrationDriftCheck);
3248
+ out = block(out, "SEED_DATA", false);
3249
+ out = line(out, "DEPLOY_IF", `if: >-\n github.event_name == 'push' ||\n (github.event_name == 'workflow_dispatch' && !inputs['dry-run'])`);
2705
3250
  out = line(out, "PATHS", params.workingDirectory ? `paths: ["${params.workingDirectory}/**"]` : void 0);
2706
3251
  out = applyCommon(out, params).replaceAll("__BRANCH__", () => branch);
2707
- const generatedIds = [];
2708
- if (plan) generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", "tailor-plan/tailor-setup", "tailor-plan/tailor-generate-check", "tailor-plan/tailor-plan");
2709
- generatedIds.push("tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup", "tailor-deploy/tailor-apply");
3252
+ const generatedIds = [
3253
+ "tailor-plan",
3254
+ "tailor-plan/tailor-checkout",
3255
+ "tailor-plan/tailor-setup",
3256
+ "tailor-plan/tailor-install",
3257
+ "tailor-plan/tailor-generate-check"
3258
+ ];
3259
+ if (seedValidate) generatedIds.push("tailor-plan/tailor-seed-validate");
3260
+ generatedIds.push("tailor-plan/tailor-drift-check");
3261
+ if (migrationDriftCheck) generatedIds.push("tailor-plan/tailor-migration-drift-check");
3262
+ generatedIds.push("tailor-plan/tailor-plan");
3263
+ if (erdPreview) generatedIds.push("tailor-erd-preview-matrix", "tailor-erd-preview-matrix/tailor-checkout", "tailor-erd-preview-matrix/tailor-checkout-base", "tailor-erd-preview-matrix/tailor-erd-preview-matrix", "tailor-erd-preview", "tailor-erd-preview/tailor-checkout", "tailor-erd-preview/tailor-setup", "tailor-erd-preview/tailor-checkout-base", "tailor-erd-preview/tailor-detect-base-package-manager", "tailor-erd-preview/tailor-setup-base-pnpm", "tailor-erd-preview/tailor-setup-base-node", "tailor-erd-preview/tailor-setup-base-yarn", "tailor-erd-preview/tailor-setup-base-bun", "tailor-erd-preview/tailor-install-base", "tailor-erd-preview/tailor-restore-head-setup", "tailor-erd-preview/tailor-build-erd-preview", "tailor-erd-preview/tailor-upload-erd-viewer", "tailor-erd-preview-comment", "tailor-erd-preview-comment/tailor-comment-erd-preview");
3264
+ generatedIds.push("tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup", "tailor-deploy/tailor-install", "tailor-deploy/tailor-apply", "tailor-deploy/tailor-notify");
2710
3265
  return {
2711
3266
  content: out,
2712
3267
  generatedIds
@@ -2723,122 +3278,208 @@ function renderBranchWorkflow(params) {
2723
3278
  function renderTagWorkflow(params) {
2724
3279
  const { tagPattern, branch } = params;
2725
3280
  const hasGuard = branch !== void 0;
3281
+ const seedValidate = params.seedValidate ?? false;
3282
+ const migrationDriftCheck = params.migrationDriftCheck ?? false;
2726
3283
  let out = tag_workflow_default;
2727
3284
  out = block(out, "TAG_GUARD_JOB", hasGuard);
3285
+ out = block(out, "SEED_VALIDATE", seedValidate);
3286
+ out = block(out, "MIGRATION_DRIFT_CHECK", migrationDriftCheck);
3287
+ out = block(out, "SEED_DATA", false);
2728
3288
  out = line(out, "PLAN_NEEDS", hasGuard ? "needs: tailor-tag-guard" : void 0);
2729
3289
  out = line(out, "PLAN_IF", hasGuard ? `if: >-\n github.event_name == 'workflow_dispatch' ||\n needs.tailor-tag-guard.outputs.on-branch == 'true'` : void 0);
2730
3290
  out = applyCommon(out, params).replaceAll("__TAG_PATTERN__", () => tagPattern);
2731
3291
  if (hasGuard) out = out.replaceAll("__BRANCH__", () => branch);
2732
3292
  const generatedIds = [];
2733
3293
  if (hasGuard) generatedIds.push("tailor-tag-guard", "tailor-tag-guard/tailor-checkout", "tailor-tag-guard/tailor-tag-guard");
2734
- generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", "tailor-plan/tailor-setup", "tailor-plan/tailor-generate-check", "tailor-plan/tailor-plan", "tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup", "tailor-deploy/tailor-apply");
3294
+ generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", "tailor-plan/tailor-setup", "tailor-plan/tailor-install", "tailor-plan/tailor-generate-check");
3295
+ if (seedValidate) generatedIds.push("tailor-plan/tailor-seed-validate");
3296
+ generatedIds.push("tailor-plan/tailor-drift-check");
3297
+ if (migrationDriftCheck) generatedIds.push("tailor-plan/tailor-migration-drift-check");
3298
+ generatedIds.push("tailor-plan/tailor-plan", "tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup", "tailor-deploy/tailor-install", "tailor-deploy/tailor-apply", "tailor-deploy/tailor-notify");
2735
3299
  return {
2736
3300
  content: out,
2737
3301
  generatedIds
2738
3302
  };
2739
3303
  }
2740
-
2741
- //#endregion
2742
- //#region src/cli/commands/setup/check.ts
2743
3304
  /**
2744
- * Compute drift findings for one target by comparing its recorded lock state
2745
- * against the current repository/config state.
2746
- * @param target - The lock target being audited
2747
- * @param state - Current repository/config state for this target
2748
- * @returns Drift findings (empty when the target is in sync)
3305
+ * Render the preview workflow (PR open/sync/close triggers).
3306
+ *
3307
+ * The deploy job runs on opened/synchronize/reopened events; the cleanup job
3308
+ * runs on the closed event. Both jobs run in the same workflow so PR number
3309
+ * context is always available.
3310
+ * @param params - Workspace and rendering configuration
3311
+ * @returns Rendered YAML and the list of managed job/step ids
2749
3312
  */
2750
- function findTargetDrift(target, state) {
2751
- const id = `${target.kind} ${target.workspaceName}`;
2752
- const findings = [];
2753
- if (!state.fileExists) findings.push({
2754
- target: id,
2755
- rule: "missing-file",
2756
- message: `${target.file} is missing or unreadable. Re-run setup to restore it.`
2757
- });
2758
- else if (state.currentHash !== null && state.currentHash !== target.contentHash) findings.push({
2759
- target: id,
2760
- rule: "hand-edit",
2761
- message: `${target.file} was edited by hand since it was generated. Re-run setup with --force to regenerate, or keep customizations in your own jobs/steps.`
2762
- });
2763
- if (target.templateVersion < state.templateVersion) findings.push({
2764
- target: id,
2765
- rule: "template-version",
2766
- message: `A newer workflow template is available (generated with v${String(target.templateVersion)}, current v${String(state.templateVersion)}). Re-run setup to update.`
2767
- });
2768
- if (!state.configExists) findings.push({
2769
- target: id,
2770
- rule: "config-dir",
2771
- message: `tailor.config.ts not found under "${target.inputs.dir}". The app directory may have moved; re-run setup with the correct --dir.`
2772
- });
2773
- if (target.kind === "branch" && target.inputs.branchAutoDetected !== false && state.defaultBranch !== null && target.inputs.branch !== null && target.inputs.branch !== state.defaultBranch) findings.push({
2774
- target: id,
2775
- rule: "default-branch",
2776
- message: `The workflow triggers on "${target.inputs.branch}" but the repository default branch is now "${state.defaultBranch}". If this is intentional, ignore this; otherwise re-run setup so the trigger matches the default branch.`
2777
- });
2778
- return findings;
2779
- }
2780
- function detectDefaultBranchSafe(cwd, run) {
2781
- try {
2782
- return detectDefaultBranch(cwd, run);
2783
- } catch {
2784
- return null;
2785
- }
2786
- }
2787
- function escapesRoot$1(rel) {
2788
- return path.isAbsolute(rel) || rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("../");
3313
+ function renderPreviewWorkflow(params) {
3314
+ const { branch } = params;
3315
+ const requirePreviewLabel = params.requirePreviewLabel ?? false;
3316
+ let out = preview_workflow_default;
3317
+ out = line(out, "PATHS", params.workingDirectory ? `paths: ["${params.workingDirectory}/**"]` : void 0);
3318
+ out = line(out, "PR_TYPES", requirePreviewLabel ? "types: [labeled, synchronize, reopened, closed]" : "types: [opened, synchronize, reopened, closed]");
3319
+ out = line(out, "DEPLOY_IF", requirePreviewLabel ? `if: >-\n contains(github.event.pull_request.labels.*.name, 'tailor:preview') &&\n github.event.action != 'closed' &&\n !github.event.pull_request.draft &&\n !github.event.pull_request.head.repo.fork` : `if: >-\n github.event.action != 'closed' &&\n !github.event.pull_request.draft &&\n !github.event.pull_request.head.repo.fork`);
3320
+ out = line(out, "CLEANUP_IF", `if: >-\n github.event.action == 'closed' &&\n !github.event.pull_request.head.repo.fork`);
3321
+ out = applyCommon(out, params).replaceAll("__BRANCH__", () => branch).replaceAll("__REGION__", () => params.region);
3322
+ return {
3323
+ content: out,
3324
+ generatedIds: [
3325
+ "tailor-preview-deploy",
3326
+ "tailor-preview-deploy/tailor-checkout",
3327
+ "tailor-preview-deploy/tailor-setup",
3328
+ "tailor-preview-deploy/tailor-install",
3329
+ "tailor-preview-deploy/tailor-generate-check",
3330
+ "tailor-preview-deploy/tailor-preview-deploy",
3331
+ "tailor-preview-deploy/tailor-preview-comment",
3332
+ "tailor-preview-cleanup",
3333
+ "tailor-preview-cleanup/tailor-checkout",
3334
+ "tailor-preview-cleanup/tailor-setup",
3335
+ "tailor-preview-cleanup/tailor-install",
3336
+ "tailor-preview-cleanup/tailor-preview-cleanup"
3337
+ ]
3338
+ };
2789
3339
  }
2790
- function resolveWithinRoot(outputDir, relPath) {
2791
- if (path.isAbsolute(relPath)) return null;
2792
- const abs = path.join(outputDir, relPath);
2793
- if (escapesRoot$1(path.relative(outputDir, abs))) return null;
2794
- try {
2795
- if (escapesRoot$1(path.relative(fs$1.realpathSync(outputDir), fs$1.realpathSync(abs)))) return null;
2796
- } catch {}
2797
- return abs;
3340
+ /**
3341
+ * Render the per-app composite action.
3342
+ *
3343
+ * The action contains only the deploy step and a failure notification.
3344
+ * Checkout, setup, and install are the caller's responsibility (coordinator workflow).
3345
+ * @param params - Workspace and rendering configuration
3346
+ * @returns Rendered YAML and the list of managed step ids
3347
+ */
3348
+ function renderActionWorkflow(params) {
3349
+ let out = action_default;
3350
+ out = line(out, "HEADER", HEADER);
3351
+ out = block(out, "STATIC_WEBSITE_BUILD", params.hasStaticWebsites ?? false);
3352
+ out = out.replaceAll("__WORKSPACE_NAME__", () => params.workspaceName);
3353
+ return {
3354
+ content: out,
3355
+ generatedIds: ["tailor-apply", "tailor-notify"]
3356
+ };
2798
3357
  }
2799
- function readHash(absFile) {
2800
- try {
2801
- return hashContent(fs$1.readFileSync(absFile, "utf-8"));
2802
- } catch {
2803
- return null;
3358
+ const ACTIONS_SHA = "e74173d4bdd931d036ad359cf5f7729fc6aa7067";
3359
+ /**
3360
+ * Render the coordinator workflow that orchestrates per-app composite actions.
3361
+ *
3362
+ * Generates per-app plan steps (generate-check, drift-check, plan) and per-app
3363
+ * deploy steps (calling each app's composite action at .github/actions/tailor-<name>).
3364
+ * Checkout, setup, and install are done once per job using the local tailor-setup action.
3365
+ * @param params - Coordinator and app configuration
3366
+ * @returns Rendered YAML and the list of managed job/step ids
3367
+ */
3368
+ function renderCoordinateWorkflow(params) {
3369
+ const { coordinatorName, kind, apps, environment, packageManager } = params;
3370
+ const isTag = kind === "tag";
3371
+ const branch = params.branch;
3372
+ const tagPattern = params.tagPattern ?? "v*";
3373
+ let out = coordinate_workflow_default;
3374
+ out = line(out, "HEADER", HEADER);
3375
+ out = block(out, "PULL_REQUEST", !isTag);
3376
+ out = block(out, "PUSH_BRANCHES", !isTag);
3377
+ out = block(out, "PUSH_TAGS", isTag);
3378
+ out = block(out, "TAG_GUARD_JOB", isTag && branch !== void 0);
3379
+ if (isTag && branch !== void 0) {
3380
+ out = line(out, "PLAN_NEEDS", "needs: tailor-tag-guard");
3381
+ out = line(out, "PLAN_IF", `if: >-\n github.event_name == 'workflow_dispatch' ||\n needs.tailor-tag-guard.outputs.on-branch == 'true'`);
3382
+ } else if (isTag) {
3383
+ out = line(out, "PLAN_NEEDS", void 0);
3384
+ out = line(out, "PLAN_IF", void 0);
3385
+ } else {
3386
+ out = line(out, "PLAN_NEEDS", void 0);
3387
+ out = line(out, "PLAN_IF", `if: >-\n github.event_name == 'pull_request' ||\n (github.event_name == 'workflow_dispatch' && inputs['dry-run'])`);
2804
3388
  }
3389
+ if (isTag) {
3390
+ out = line(out, "DEPLOY_NEEDS", "needs: tailor-plan");
3391
+ out = line(out, "DEPLOY_IF", `if: \${{ !(github.event_name == 'workflow_dispatch' && inputs['dry-run']) }}`);
3392
+ out = line(out, "DEPLOY_ENVIRONMENT", `environment: ${environment}`);
3393
+ } else {
3394
+ out = line(out, "DEPLOY_NEEDS", void 0);
3395
+ out = line(out, "DEPLOY_IF", `if: >-\n github.event_name == 'push' ||\n (github.event_name == 'workflow_dispatch' && !inputs['dry-run'])`);
3396
+ out = line(out, "DEPLOY_ENVIRONMENT", `environment: ${environment}`);
3397
+ }
3398
+ const driftCheckStep = [
3399
+ `- id: tailor-drift-check`,
3400
+ ` uses: tailor-platform/actions/drift-check@${ACTIONS_SHA} # v1.5.1`,
3401
+ ` with:`,
3402
+ ` package-manager: ${packageManager}`
3403
+ ].join("\n");
3404
+ const perAppPlanSteps = apps.flatMap((app) => {
3405
+ const wd = app.dir !== "." ? app.dir : void 0;
3406
+ const wdLine = wd ? `\n working-directory: ${wd}` : "";
3407
+ return [
3408
+ `- id: tailor-generate-check-${app.name}`,
3409
+ ` uses: tailor-platform/actions/generate-check@${ACTIONS_SHA} # v1.5.1`,
3410
+ ` with:`,
3411
+ ` package-manager: ${packageManager}${wdLine}`,
3412
+ `- id: tailor-plan-${app.name}`,
3413
+ ` if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork`,
3414
+ ` uses: tailor-platform/actions/plan@${ACTIONS_SHA} # v1.5.1`,
3415
+ ` with:`,
3416
+ ` workspace-id: \${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}`,
3417
+ ` package-manager: ${packageManager}${wdLine}`,
3418
+ ` label: ${coordinatorName}/${app.name}`,
3419
+ ` platform-client-id: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}`,
3420
+ ` platform-client-secret: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}`,
3421
+ ` github-token: \${{ secrets.GITHUB_TOKEN }}`
3422
+ ];
3423
+ }).join("\n");
3424
+ out = line(out, "PER_APP_PLAN_STEPS", `${driftCheckStep}\n${perAppPlanSteps}`);
3425
+ const deploySteps = apps.flatMap((app) => [
3426
+ `- id: tailor-deploy-${app.name}`,
3427
+ ` uses: ./.github/actions/tailor-${app.name}`,
3428
+ ` with:`,
3429
+ ` workspace-id: \${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}`,
3430
+ ` name: ${app.name}`,
3431
+ ` working-directory: ${app.dir}`,
3432
+ ` package-manager: ${packageManager}`,
3433
+ ` platform-client-id: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}`,
3434
+ ` platform-client-secret: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}`,
3435
+ ` slack-token: \${{ secrets.TAILOR_SLACK_BOT_TOKEN }}`,
3436
+ ` slack-channel-id: \${{ vars.TAILOR_SLACK_CHANNEL_ID }}`,
3437
+ ` # editable: api-url:`
3438
+ ]).join("\n");
3439
+ out = line(out, "PER_APP_DEPLOY_STEPS", deploySteps);
3440
+ out = out.replaceAll("__WORKSPACE_NAME__", () => coordinatorName);
3441
+ out = out.replaceAll("__ENVIRONMENT__", () => environment);
3442
+ if (branch !== void 0) out = out.replaceAll("__BRANCH__", () => branch);
3443
+ if (isTag) out = out.replaceAll("__TAG_PATTERN__", () => tagPattern);
3444
+ const generatedIds = [];
3445
+ if (isTag && branch !== void 0) generatedIds.push("tailor-tag-guard", "tailor-tag-guard/tailor-checkout", "tailor-tag-guard/tailor-tag-guard");
3446
+ generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", "tailor-plan/tailor-setup", "tailor-plan/tailor-drift-check");
3447
+ for (const app of apps) generatedIds.push(`tailor-plan/tailor-generate-check-${app.name}`, `tailor-plan/tailor-plan-${app.name}`);
3448
+ generatedIds.push("tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup");
3449
+ for (const app of apps) generatedIds.push(`tailor-deploy/tailor-deploy-${app.name}`);
3450
+ return {
3451
+ content: out,
3452
+ generatedIds
3453
+ };
2805
3454
  }
2806
3455
  /**
2807
- * Audit the generated workflows for drift against the current config/repo
2808
- * state. Read-only: never writes files, the lock, or the config.
3456
+ * Render the content of the user-owned .github/actions/tailor-setup/action.yml local action.
2809
3457
  *
2810
- * Throws when drift is found (so it composes like the other `:check`
2811
- * commands). The workflow drift-check step layers advisory behaviour on top
2812
- * (per-rule ignore / continue-on-error); the CLI itself reports via exit code.
2813
- * @param options - Check options
3458
+ * This file is generated once by `setup coordinate` and never overwritten.
3459
+ * It wraps the setup and install actions with the package manager baked in.
3460
+ * @param params - Package manager configuration
3461
+ * @returns File content string
2814
3462
  */
2815
- function checkGitHub(options) {
2816
- logBetaWarning("setup");
2817
- const { outputDir } = options;
2818
- const lock = readLock(outputDir);
2819
- if (!lock || lock.targets.length === 0) throw new Error("No managed workflows found (.github/tailor-sdk.lock is missing or empty). Run `tailor-sdk setup` first.");
2820
- const exists = options.configExistsAt ?? ((p) => fs$1.existsSync(p));
2821
- const defaultBranch = detectDefaultBranchSafe(outputDir, options.gitRunner);
2822
- const findings = [];
2823
- for (const target of lock.targets) {
2824
- const absFile = resolveWithinRoot(outputDir, target.file);
2825
- const currentHash = absFile === null ? null : readHash(absFile);
2826
- const configAbs = resolveWithinRoot(outputDir, path.join(target.inputs.dir, "tailor.config.ts"));
2827
- findings.push(...findTargetDrift(target, {
2828
- fileExists: currentHash !== null,
2829
- currentHash,
2830
- configExists: configAbs !== null && exists(configAbs),
2831
- defaultBranch,
2832
- templateVersion: 2
2833
- }));
2834
- }
2835
- const count = lock.targets.length;
2836
- if (findings.length === 0) {
2837
- logger.success(`No drift detected across ${String(count)} target(s).`);
2838
- return;
2839
- }
2840
- for (const finding of findings) logger.warn(`[${finding.target}] ${finding.message} (ignore key: ${finding.rule})`);
2841
- throw new Error(`Detected ${String(findings.length)} drift finding(s) across ${String(count)} target(s). Re-run \`tailor-sdk setup\` to regenerate, or address each finding above.`);
3463
+ function renderTailorSetupAction(params) {
3464
+ const { packageManager } = params;
3465
+ return [
3466
+ `# This file is user-owned. Generated once by \`tailor setup coordinate\` and never overwritten.`,
3467
+ `# Customize freely: add pre/post steps, change install flags, etc.`,
3468
+ `name: Tailor Setup`,
3469
+ `description: Install dependencies and run tailor setup for composite action callers.`,
3470
+ `runs:`,
3471
+ ` using: composite`,
3472
+ ` steps:`,
3473
+ ` - uses: tailor-platform/actions/setup@${ACTIONS_SHA} # v1.5.1`,
3474
+ ` with:`,
3475
+ ` package-manager: ${packageManager}`,
3476
+ ` # Add custom steps here (e.g. private registry authentication)`,
3477
+ ` - uses: tailor-platform/actions/install@${ACTIONS_SHA} # v1.5.1`,
3478
+ ` with:`,
3479
+ ` package-manager: ${packageManager}`,
3480
+ ` # editable: install-command:`,
3481
+ ``
3482
+ ].join("\n");
2842
3483
  }
2843
3484
 
2844
3485
  //#endregion
@@ -2847,9 +3488,25 @@ async function defaultLoadConfigName(configPath) {
2847
3488
  const { config } = await loadConfig(configPath);
2848
3489
  return config.name;
2849
3490
  }
3491
+ async function defaultLoadErdNamespaces$1(configPath) {
3492
+ const { config } = await loadConfig(configPath);
3493
+ return extractOwnedNamespaces(config);
3494
+ }
3495
+ async function defaultLoadHasMigrations$1(configPath) {
3496
+ const { config } = await loadConfig(configPath);
3497
+ return getNamespacesWithMigrations(config, path.dirname(configPath)).length > 0;
3498
+ }
3499
+ async function defaultLoadHasSeeds$1(configPath) {
3500
+ const { plugins } = await loadConfig(configPath);
3501
+ return plugins.some((p) => p.id === "@tailor-platform/seed");
3502
+ }
3503
+ async function defaultLoadHasStaticWebsites$1(configPath) {
3504
+ const { config } = await loadConfig(configPath);
3505
+ return (config.staticWebsites?.length ?? 0) > 0;
3506
+ }
2850
3507
  const WORKSPACE_NAME_RE = /^[a-z0-9-]+$/;
2851
3508
  function validateWorkspaceName(name) {
2852
- if (name.length < 3 || name.length > 63 || !WORKSPACE_NAME_RE.test(name) || name.startsWith("-") || name.endsWith("-")) throw new Error(`Invalid workspace name "${name}". Names must be 3-63 characters of lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen. Pass a valid name with --workspace-name.`);
3509
+ if (name.length < 3 || name.length > 63 || !WORKSPACE_NAME_RE.test(name) || name.startsWith("-") || name.endsWith("-")) throw new Error(`Invalid workspace name "${name}". Names must be 3-63 characters of lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen. Pass a valid name with --name.`);
2853
3510
  }
2854
3511
  const BRANCH_RE = /^[A-Za-z0-9._/-]+$/;
2855
3512
  const TAG_PATTERN_RE = /^[A-Za-z0-9._/*?![\]-]+$/;
@@ -2863,11 +3520,19 @@ const ENVIRONMENT_RE = /^[A-Za-z0-9._/-]+$/;
2863
3520
  function validateEnvironment(environment) {
2864
3521
  if (!ENVIRONMENT_RE.test(environment)) throw new Error(`Invalid environment name "${environment}". Only letters, numbers, ".", "_", "/", and "-" are supported.`);
2865
3522
  }
3523
+ const REGION_RE = /^[A-Za-z0-9._-]+$/;
3524
+ function validateRegion(region) {
3525
+ if (!REGION_RE.test(region)) throw new Error(`Invalid region "${region}". Only letters, numbers, ".", "_", and "-" are supported.`);
3526
+ }
2866
3527
  const DIR_RE = /^[A-Za-z0-9._/-]+$/;
2867
3528
  function validateDir(dir) {
2868
3529
  if (!DIR_RE.test(dir)) throw new Error(`Invalid --dir "${dir}". Only letters, numbers, ".", "_", "/", and "-" are supported.`);
2869
3530
  }
2870
- function escapesRoot(rel) {
3531
+ const ERD_NAMESPACE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
3532
+ function validateErdNamespaces(namespaces) {
3533
+ for (const namespace of namespaces) if (!ERD_NAMESPACE_RE.test(namespace)) throw new Error(`TailorDB namespace "${namespace}" cannot be used in --erd-preview. Only letters, numbers, '.', '_', and '-' are supported, and the name must start with a letter or number.`);
3534
+ }
3535
+ function escapesRoot$1(rel) {
2871
3536
  return rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("../") || path.isAbsolute(rel);
2872
3537
  }
2873
3538
  /**
@@ -2883,11 +3548,11 @@ function escapesRoot(rel) {
2883
3548
  function resolveConfigPath(outputDir, dir) {
2884
3549
  const appDir = path.resolve(outputDir, dir);
2885
3550
  const rel = path.relative(outputDir, appDir);
2886
- if (path.isAbsolute(dir) || escapesRoot(rel)) throw new Error(`--dir must be a relative path inside the repository (got "${dir}").`);
3551
+ if (path.isAbsolute(dir) || escapesRoot$1(rel)) throw new Error(`--dir must be a relative path inside the repository (got "${dir}").`);
2887
3552
  if (fs$1.existsSync(appDir)) {
2888
3553
  const realAppDir = path.normalize(fs$1.realpathSync(appDir));
2889
3554
  const realOutputDir = path.normalize(fs$1.realpathSync(outputDir));
2890
- if (escapesRoot(path.relative(realOutputDir, realAppDir))) throw new Error(`--dir must resolve to a directory inside the repository (got "${dir}", which links outside it).`);
3555
+ if (escapesRoot$1(path.relative(realOutputDir, realAppDir))) throw new Error(`--dir must resolve to a directory inside the repository (got "${dir}", which links outside it).`);
2891
3556
  }
2892
3557
  const configPath = path.join(appDir, "tailor.config.ts");
2893
3558
  if (!fs$1.existsSync(configPath)) throw new Error(`tailor.config.ts not found at ${configPath}. Run this from your SDK project root, or pass the app directory with --dir.`);
@@ -2899,16 +3564,15 @@ function resolveConfigPath(outputDir, dir) {
2899
3564
  * @returns Resolved target metadata and rendered content
2900
3565
  */
2901
3566
  async function resolve$1(options) {
2902
- if (options.tag && !options.plan) throw new Error("--no-plan cannot be combined with --tag (tag targets always run plan before deploy). Drop --no-plan or use a branch target.");
2903
3567
  const dir = options.dir.replaceAll("\\", "/").replace(/\/{2,}/g, "/").replace(/^\.\//, "").replace(/\/$/, "") || ".";
2904
3568
  validateDir(dir);
2905
3569
  const workingDirectory = dir !== "." ? dir : void 0;
2906
3570
  const configPath = resolveConfigPath(options.outputDir, dir);
2907
3571
  const loadName = options.loadConfigName ?? defaultLoadConfigName;
2908
3572
  const workspaceName = options.workspaceName ?? await loadName(configPath);
2909
- if (!workspaceName) throw new Error("Could not determine the workspace name. Pass --workspace-name, or set 'name' in tailor.config.ts.");
3573
+ if (!workspaceName) throw new Error("Could not determine the workspace name. Pass --name, or set 'name' in tailor.config.ts.");
2910
3574
  validateWorkspaceName(workspaceName);
2911
- const kind = options.tag ? "tag" : "branch";
3575
+ const { kind } = options;
2912
3576
  const packageManager = detectPackageManager(options.outputDir);
2913
3577
  const environment = options.environment ?? workspaceName;
2914
3578
  validateEnvironment(environment);
@@ -2916,39 +3580,86 @@ async function resolve$1(options) {
2916
3580
  let branch = null;
2917
3581
  let branchAutoDetected = false;
2918
3582
  let render;
3583
+ let erdNamespaces = [];
3584
+ let hasMigrations = false;
3585
+ let hasSeeds = false;
3586
+ let hasStaticWebsites = false;
3587
+ const loadHasMigrations = options.loadHasMigrations ?? defaultLoadHasMigrations$1;
3588
+ const loadHasSeeds = options.loadHasSeeds ?? defaultLoadHasSeeds$1;
3589
+ const loadHasStaticWebsites = options.loadHasStaticWebsites ?? defaultLoadHasStaticWebsites$1;
2919
3590
  if (kind === "branch") {
3591
+ if (options.erdPreview) {
3592
+ erdNamespaces = await (options.loadErdNamespaces ?? defaultLoadErdNamespaces$1)(configPath);
3593
+ if (erdNamespaces.length === 0) throw new Error("No TailorDB namespaces found for --erd-preview. Define owned db namespaces in tailor.config.ts.");
3594
+ validateErdNamespaces(erdNamespaces);
3595
+ }
2920
3596
  branchAutoDetected = options.branch === void 0;
2921
3597
  branch = options.branch ?? detectDefaultBranch(options.outputDir, options.gitRunner);
2922
3598
  validateBranch(branch);
3599
+ hasMigrations = await loadHasMigrations(configPath);
3600
+ hasSeeds = await loadHasSeeds(configPath);
2923
3601
  render = renderBranchWorkflow({
2924
3602
  workspaceName,
2925
3603
  branch,
2926
3604
  workingDirectory,
2927
3605
  environment,
2928
3606
  packageManager,
2929
- plan: options.plan
3607
+ erdPreview: options.erdPreview ? { namespaces: erdNamespaces } : null,
3608
+ migrationDriftCheck: hasMigrations,
3609
+ seedValidate: hasSeeds
2930
3610
  });
2931
- } else {
3611
+ } else if (kind === "tag") {
2932
3612
  branch = options.branch ?? null;
2933
3613
  if (branch !== null) validateBranch(branch);
3614
+ hasMigrations = await loadHasMigrations(configPath);
3615
+ hasSeeds = await loadHasSeeds(configPath);
2934
3616
  render = renderTagWorkflow({
2935
3617
  workspaceName,
2936
3618
  tagPattern: options.tagPattern,
2937
3619
  branch: options.branch,
2938
3620
  workingDirectory,
2939
3621
  environment,
2940
- packageManager
3622
+ packageManager,
3623
+ migrationDriftCheck: hasMigrations,
3624
+ seedValidate: hasSeeds
3625
+ });
3626
+ } else if (kind === "preview") {
3627
+ branchAutoDetected = options.branch === void 0;
3628
+ branch = options.branch ?? detectDefaultBranch(options.outputDir, options.gitRunner);
3629
+ validateBranch(branch);
3630
+ validateRegion(options.region);
3631
+ render = renderPreviewWorkflow({
3632
+ workspaceName,
3633
+ branch,
3634
+ workingDirectory,
3635
+ environment,
3636
+ packageManager,
3637
+ region: options.region,
3638
+ requirePreviewLabel: options.requirePreviewLabel ?? false
3639
+ });
3640
+ } else {
3641
+ hasStaticWebsites = await loadHasStaticWebsites(configPath);
3642
+ render = renderActionWorkflow({
3643
+ workspaceName,
3644
+ workingDirectory,
3645
+ hasStaticWebsites
2941
3646
  });
2942
3647
  }
2943
- const file = `.github/workflows/tailor-${workspaceName}.yml`;
3648
+ const file = kind === "action" ? `.github/actions/tailor-${workspaceName}/action.yml` : `.github/workflows/tailor-${workspaceName}${kind === "tag" ? "-tag" : kind === "preview" ? "-preview" : ""}.yml`;
2944
3649
  const inputs = {
2945
- branch,
2946
- branchAutoDetected: kind === "branch" ? branchAutoDetected : void 0,
3650
+ branch: kind === "action" ? null : branch,
3651
+ branchAutoDetected: kind === "branch" || kind === "preview" ? branchAutoDetected : void 0,
2947
3652
  tagPattern: kind === "tag" ? options.tagPattern : null,
2948
3653
  environment,
2949
3654
  dir,
2950
3655
  packageManager,
2951
- plan: kind === "branch" ? options.plan : true
3656
+ region: kind === "preview" ? options.region : void 0,
3657
+ requirePreviewLabel: kind === "preview" ? options.requirePreviewLabel ?? false : void 0,
3658
+ erdPreview: kind === "branch" ? options.erdPreview : false,
3659
+ erdNamespaces: kind === "branch" && options.erdPreview ? erdNamespaces : void 0,
3660
+ migrationDriftCheck: kind === "branch" || kind === "tag" ? hasMigrations : void 0,
3661
+ seedValidate: kind === "branch" || kind === "tag" ? hasSeeds : void 0,
3662
+ hasStaticWebsites: kind === "action" ? hasStaticWebsites : void 0
2952
3663
  };
2953
3664
  return {
2954
3665
  kind,
@@ -2956,12 +3667,47 @@ async function resolve$1(options) {
2956
3667
  branch,
2957
3668
  environment,
2958
3669
  packageManager,
3670
+ erdNamespaces,
2959
3671
  render,
2960
3672
  inputs,
2961
3673
  file,
2962
3674
  configPath
2963
3675
  };
2964
3676
  }
3677
+ const BUILD_SITE_RUN_RE = /([ \t]*- id: build-site\n[ \t]+shell: bash\n[ \t]+run: \|)([\s\S]*?)(\n[ \t]*- |\n*$)/;
3678
+ /**
3679
+ * Strip the mutable run: body of the build-site step before hashing.
3680
+ * When hasStaticWebsites is true the step is present; users are expected to
3681
+ * replace the placeholder command with their actual build command. We only
3682
+ * hash the structural parts (step id, shell declaration, run: key) so that
3683
+ * editing the build command does not look like unintended drift.
3684
+ * When hasStaticWebsites is false the step is absent, so the regex is a no-op.
3685
+ * @param content - Raw action workflow content
3686
+ * @returns Normalised content with the build-site run body replaced by a placeholder
3687
+ */
3688
+ function normalizeActionContent(content) {
3689
+ return content.replace(BUILD_SITE_RUN_RE, (_, header, _body, tail) => `${header}\n true${tail}`);
3690
+ }
3691
+ /**
3692
+ * Extract the run: body of the build-site step from action content.
3693
+ * Returns null when the step is absent (hasStaticWebsites is false).
3694
+ * @param content - Action workflow content
3695
+ * @returns The captured run body string (includes leading newline), or null
3696
+ */
3697
+ function extractBuildSiteRunBody(content) {
3698
+ const m = BUILD_SITE_RUN_RE.exec(content);
3699
+ return m ? m[2] ?? null : null;
3700
+ }
3701
+ /**
3702
+ * Replace the run: body of the build-site step in action content.
3703
+ * No-op when the step is absent.
3704
+ * @param content - Target action workflow content
3705
+ * @param body - Run body to inject (includes leading newline and indentation)
3706
+ * @returns Content with the run body replaced
3707
+ */
3708
+ function injectBuildSiteRunBody(content, body) {
3709
+ return content.replace(BUILD_SITE_RUN_RE, (_, header, _placeholder, tail) => `${header}${body}${tail}`);
3710
+ }
2965
3711
  /**
2966
3712
  * Decide how to reconcile a target with the on-disk file and lock state.
2967
3713
  * @param obj - Decision inputs
@@ -2969,10 +3715,11 @@ async function resolve$1(options) {
2969
3715
  * @param obj.fileExists - Whether the workflow file is present on disk
2970
3716
  * @param obj.currentContent - On-disk content when present
2971
3717
  * @param obj.force - Whether --force was passed
3718
+ * @param obj.normalize - Optional content normaliser applied before hashing
2972
3719
  * @returns The reconciliation action
2973
3720
  */
2974
3721
  function decideAction(obj) {
2975
- const { existing, fileExists, currentContent, force } = obj;
3722
+ const { existing, fileExists, currentContent, force, normalize } = obj;
2976
3723
  if (!existing) {
2977
3724
  if (!fileExists) return { action: "create" };
2978
3725
  if (force) return { action: "regenerate" };
@@ -2982,7 +3729,9 @@ function decideAction(obj) {
2982
3729
  };
2983
3730
  }
2984
3731
  if (!fileExists) return { action: "restore" };
2985
- if (currentContent !== null && hashContent(currentContent) === existing.contentHash) return { action: "regenerate" };
3732
+ if (currentContent !== null) {
3733
+ if (hashContent(normalize ? normalize(currentContent) : currentContent) === existing.contentHash) return { action: "regenerate" };
3734
+ }
2986
3735
  if (force) return { action: "regenerate" };
2987
3736
  return {
2988
3737
  action: "conflict",
@@ -3000,7 +3749,7 @@ function decideAction(obj) {
3000
3749
  function assertNoKindCollision(obj) {
3001
3750
  const { lock, kind, workspaceName, file } = obj;
3002
3751
  const collision = lock?.targets.find((t) => t.file === file && !(t.kind === kind && t.workspaceName === workspaceName));
3003
- if (collision) throw new Error(`A ${collision.kind} target already owns ${file}, which conflicts with this ${kind} target. Pass a different name with --workspace-name to generate a separate workflow.`);
3752
+ if (collision) throw new Error(`A ${collision.kind} target already owns ${file}, which conflicts with this ${kind} target. Pass a different name with --name to generate a separate workflow.`);
3004
3753
  }
3005
3754
  /**
3006
3755
  * Print next-step guidance after generating workflow files.
@@ -3018,20 +3767,19 @@ function printNextSteps(obj) {
3018
3767
  logger.log(` gh secret set TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET --env ${environment}`);
3019
3768
  logger.newline();
3020
3769
  logger.log(`2. Provision the workspace and set its id as the TAILOR_PLATFORM_WORKSPACE_ID variable on the "${environment}" environment:`);
3021
- logger.log(" tailor-sdk workspace create # if it does not exist yet; copy the id");
3770
+ logger.log(" tailor workspace create # if it does not exist yet; copy the id");
3022
3771
  logger.log(` gh variable set TAILOR_PLATFORM_WORKSPACE_ID --env ${environment}`);
3023
3772
  logger.newline();
3024
3773
  logger.log("3. Commit the generated files:");
3025
3774
  logger.log(" - .github/workflows/tailor-*.yml");
3026
- logger.log(" - .github/tailor-sdk.lock");
3775
+ logger.log(" - .github/tailor.lock");
3027
3776
  if (idInjected) logger.log(" - tailor.config.ts (app id was added)");
3028
3777
  }
3029
3778
  /**
3030
- * Generate the GitHub Actions workflow for a deploy target and reconcile it
3031
- * with the lock file.
3779
+ * Generate a deploy target workflow and reconcile it with the lock file.
3032
3780
  * @param options - Setup options
3033
3781
  */
3034
- async function setupGitHub(options) {
3782
+ async function setupTarget(options) {
3035
3783
  logBetaWarning("setup");
3036
3784
  const resolved = await resolve$1(options);
3037
3785
  const lock = readLock(options.outputDir);
@@ -3044,27 +3792,35 @@ async function setupGitHub(options) {
3044
3792
  });
3045
3793
  const existing = findTarget(lock, resolved.kind, resolved.workspaceName);
3046
3794
  const fileExists = fs$1.existsSync(absFile);
3795
+ const currentContent = fileExists ? fs$1.readFileSync(absFile, "utf-8") : null;
3796
+ const normalize = resolved.kind === "action" ? normalizeActionContent : void 0;
3047
3797
  const decision = decideAction({
3048
3798
  existing,
3049
3799
  fileExists,
3050
- currentContent: fileExists ? fs$1.readFileSync(absFile, "utf-8") : null,
3051
- force: options.force
3800
+ currentContent,
3801
+ force: options.force,
3802
+ normalize
3052
3803
  });
3053
3804
  if (decision.action === "conflict") throw new Error(`${resolved.file}: ${decision.reason}`);
3054
3805
  const idResult = await ensureConfigId(resolved.configPath);
3055
3806
  if (idResult === null) logger.warn("Could not find a defineConfig() call to confirm an app id. The CI deploy will fail unless your config resolves to one with an 'id'.");
3056
3807
  const idInjected = idResult?.injected ?? false;
3808
+ let contentToWrite = resolved.render.content;
3809
+ if (decision.action === "regenerate" && normalize && currentContent !== null) {
3810
+ const existingBody = extractBuildSiteRunBody(currentContent);
3811
+ if (existingBody !== null) contentToWrite = injectBuildSiteRunBody(contentToWrite, existingBody);
3812
+ }
3057
3813
  fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
3058
- fs$1.writeFileSync(absFile, resolved.render.content, "utf-8");
3814
+ fs$1.writeFileSync(absFile, contentToWrite, "utf-8");
3059
3815
  const newTarget = {
3060
3816
  kind: resolved.kind,
3061
3817
  workspaceName: resolved.workspaceName,
3062
3818
  file: resolved.file,
3063
- templateVersion: 2,
3819
+ templateVersion: 6,
3064
3820
  inputs: resolved.inputs,
3065
3821
  generatedIds: resolved.render.generatedIds,
3066
3822
  ejectedIds: existing?.ejectedIds ?? [],
3067
- contentHash: hashContent(resolved.render.content)
3823
+ contentHash: hashContent(normalize ? normalize(resolved.render.content) : resolved.render.content)
3068
3824
  };
3069
3825
  const targets = [...lock?.targets ?? []];
3070
3826
  const index = targets.findIndex((t) => t.kind === newTarget.kind && t.workspaceName === newTarget.workspaceName);
@@ -3077,150 +3833,559 @@ async function setupGitHub(options) {
3077
3833
  if (decision.action === "restore") logger.success(`Regenerated ${styles.path(resolved.file)} (was missing on disk)`);
3078
3834
  else if (decision.action === "regenerate") logger.success(`Regenerated ${styles.path(resolved.file)}`);
3079
3835
  else logger.success(`Generated ${styles.path(resolved.file)}`);
3080
- printNextSteps({
3836
+ if (resolved.kind === "action") {
3837
+ logger.newline();
3838
+ logger.info("Next steps:");
3839
+ logger.newline();
3840
+ logger.log(`The composite action has been generated at ${styles.path(resolved.file)}.`);
3841
+ logger.log("Use `tailor setup coordinate` to generate a coordinator workflow that orchestrates this action.");
3842
+ } else printNextSteps({
3081
3843
  environment: resolved.environment,
3082
3844
  idInjected
3083
3845
  });
3084
3846
  }
3847
+ /**
3848
+ * Generate the coordinator workflow that orchestrates per-app composite actions.
3849
+ *
3850
+ * Unlike `setupTarget`, this function does not read a Tailor config. The coordinator
3851
+ * name is required via `--name`. App working directories are resolved from
3852
+ * the lock file entries created by `setup action`.
3853
+ * @param options - Coordinate setup options
3854
+ */
3855
+ async function setupCoordinate(options) {
3856
+ logBetaWarning("setup");
3857
+ const { coordinatorName, coordinateKind, actions, force, outputDir } = options;
3858
+ validateWorkspaceName(coordinatorName);
3859
+ if (actions.length === 0) throw new Error("At least one --action is required. Run `tailor setup action --dir <app-dir>` for each app first.");
3860
+ const environment = options.environment ?? coordinatorName;
3861
+ validateEnvironment(environment);
3862
+ let branch = null;
3863
+ let branchAutoDetected = false;
3864
+ if (coordinateKind === "branch") {
3865
+ branchAutoDetected = options.branch === void 0;
3866
+ branch = options.branch ?? detectDefaultBranch(outputDir, options.gitRunner);
3867
+ validateBranch(branch);
3868
+ } else if (options.branch !== void 0) {
3869
+ validateBranch(options.branch);
3870
+ branch = options.branch;
3871
+ }
3872
+ const tagPattern = options.tagPattern ?? "v*";
3873
+ if (coordinateKind === "tag") validateTagPattern(tagPattern);
3874
+ const packageManager = detectPackageManager(outputDir);
3875
+ const lock = readLock(outputDir);
3876
+ if (!lock) throw new Error(".github/tailor.lock not found. Run `tailor setup action --name <name>` for each app before running setup coordinate.");
3877
+ const seenNames = /* @__PURE__ */ new Set();
3878
+ const apps = actions.map((actionName) => {
3879
+ const name = actionName.startsWith("tailor-") ? actionName.slice(7) : actionName;
3880
+ if (seenNames.has(name)) throw new Error(`Duplicate --action "${name}". Each composite action can only appear once in a coordinator.`);
3881
+ seenNames.add(name);
3882
+ const entry = lock.targets.find((t) => t.kind === "action" && t.workspaceName === name);
3883
+ if (!entry) throw new Error(`Action target "${name}" not found in .github/tailor.lock. Run \`tailor setup action --name ${name}\` first.`);
3884
+ validateDir(entry.inputs.dir);
3885
+ return {
3886
+ name,
3887
+ dir: entry.inputs.dir
3888
+ };
3889
+ });
3890
+ const render = renderCoordinateWorkflow({
3891
+ coordinatorName,
3892
+ kind: coordinateKind,
3893
+ apps,
3894
+ branch: branch ?? void 0,
3895
+ tagPattern: coordinateKind === "tag" ? tagPattern : void 0,
3896
+ environment,
3897
+ packageManager
3898
+ });
3899
+ const file = `.github/workflows/tailor-coordinate-${coordinatorName}${coordinateKind === "tag" ? "-tag" : ""}.yml`;
3900
+ const absFile = path.join(outputDir, file);
3901
+ assertNoKindCollision({
3902
+ lock,
3903
+ kind: "coordinate",
3904
+ workspaceName: coordinatorName,
3905
+ file
3906
+ });
3907
+ const existing = findTarget(lock, "coordinate", coordinatorName);
3908
+ const fileExists = fs$1.existsSync(absFile);
3909
+ const decision = decideAction({
3910
+ existing,
3911
+ fileExists,
3912
+ currentContent: fileExists ? fs$1.readFileSync(absFile, "utf-8") : null,
3913
+ force
3914
+ });
3915
+ if (decision.action === "conflict") throw new Error(`${file}: ${decision.reason}`);
3916
+ fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
3917
+ fs$1.writeFileSync(absFile, render.content, "utf-8");
3918
+ const tailorSetupFile = ".github/actions/tailor-setup/action.yml";
3919
+ const absTailorSetupFile = path.join(outputDir, tailorSetupFile);
3920
+ if (!fs$1.existsSync(absTailorSetupFile)) {
3921
+ fs$1.mkdirSync(path.dirname(absTailorSetupFile), { recursive: true });
3922
+ fs$1.writeFileSync(absTailorSetupFile, renderTailorSetupAction({ packageManager }), "utf-8");
3923
+ logger.success(`Generated ${styles.path(tailorSetupFile)}`);
3924
+ }
3925
+ const newTarget = {
3926
+ kind: "coordinate",
3927
+ workspaceName: coordinatorName,
3928
+ file,
3929
+ templateVersion: 6,
3930
+ inputs: {
3931
+ branch,
3932
+ branchAutoDetected: coordinateKind === "branch" ? branchAutoDetected : branch !== null ? false : void 0,
3933
+ tagPattern: coordinateKind === "tag" ? tagPattern : null,
3934
+ environment,
3935
+ dir: ".",
3936
+ packageManager,
3937
+ actionDirs: apps.map((a) => a.dir)
3938
+ },
3939
+ generatedIds: render.generatedIds,
3940
+ ejectedIds: existing?.ejectedIds ?? [],
3941
+ contentHash: hashContent(render.content)
3942
+ };
3943
+ const targets = [...lock.targets];
3944
+ const idx = targets.findIndex((t) => t.kind === newTarget.kind && t.workspaceName === newTarget.workspaceName);
3945
+ if (idx === -1) targets.push(newTarget);
3946
+ else targets[idx] = newTarget;
3947
+ writeLock(outputDir, {
3948
+ version: 1,
3949
+ targets
3950
+ });
3951
+ if (decision.action === "restore") logger.success(`Regenerated ${styles.path(file)} (was missing on disk)`);
3952
+ else if (decision.action === "regenerate") logger.success(`Regenerated ${styles.path(file)}`);
3953
+ else logger.success(`Generated ${styles.path(file)}`);
3954
+ logger.newline();
3955
+ logger.info("Next steps:");
3956
+ logger.newline();
3957
+ logger.log(`1. Set the machine-user credentials as secrets on the "${environment}" environment:`);
3958
+ logger.log(` gh secret set TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID --env ${environment}`);
3959
+ logger.log(` gh secret set TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET --env ${environment}`);
3960
+ logger.newline();
3961
+ logger.log(`2. Provision each workspace and set TAILOR_PLATFORM_WORKSPACE_ID on the "${environment}" environment:`);
3962
+ logger.log(" tailor workspace create # one per app; copy the id");
3963
+ logger.log(` gh variable set TAILOR_PLATFORM_WORKSPACE_ID --env ${environment}`);
3964
+ logger.newline();
3965
+ logger.log("3. Commit the generated files:");
3966
+ logger.log(` - ${file}`);
3967
+ logger.log(` - ${tailorSetupFile} (if newly created)`);
3968
+ logger.log(" - .github/tailor.lock");
3969
+ }
3970
+
3971
+ //#endregion
3972
+ //#region src/cli/commands/setup/check.ts
3973
+ /**
3974
+ * Compute drift findings for one target by comparing its recorded lock state
3975
+ * against the current repository/config state.
3976
+ * @param target - The lock target being audited
3977
+ * @param state - Current repository/config state for this target
3978
+ * @returns Drift findings (empty when the target is in sync)
3979
+ */
3980
+ function findTargetDrift(target, state) {
3981
+ const id = `${target.kind} ${target.workspaceName}`;
3982
+ const findings = [];
3983
+ if (!state.fileExists) findings.push({
3984
+ target: id,
3985
+ rule: "missing-file",
3986
+ message: `${target.file} is missing or unreadable. Re-run setup to restore it.`
3987
+ });
3988
+ else if (state.currentHash !== null && state.currentHash !== target.contentHash) findings.push({
3989
+ target: id,
3990
+ rule: "hand-edit",
3991
+ message: `${target.file} was edited by hand since it was generated. Re-run setup with --force to regenerate, or keep customizations in your own jobs/steps.`
3992
+ });
3993
+ if (target.templateVersion < state.templateVersion) findings.push({
3994
+ target: id,
3995
+ rule: "template-version",
3996
+ message: `A newer workflow template is available (generated with v${String(target.templateVersion)}, current v${String(state.templateVersion)}). Re-run setup to update.`
3997
+ });
3998
+ if (!state.configExists) findings.push({
3999
+ target: id,
4000
+ rule: "config-dir",
4001
+ message: `tailor.config.ts not found under "${target.inputs.dir}". The app directory may have moved; re-run setup with the correct --dir.`
4002
+ });
4003
+ if ((target.kind === "branch" || target.kind === "coordinate" || target.kind === "preview") && target.inputs.branchAutoDetected !== false && state.defaultBranch !== null && target.inputs.branch !== null && target.inputs.branch !== state.defaultBranch) findings.push({
4004
+ target: id,
4005
+ rule: "default-branch",
4006
+ message: `The workflow triggers on "${target.inputs.branch}" but the repository default branch is now "${state.defaultBranch}". If this is intentional, ignore this; otherwise re-run setup so the trigger matches the default branch.`
4007
+ });
4008
+ if (target.kind === "branch" && target.inputs.erdPreview && state.configExists) {
4009
+ const recorded = [...target.inputs.erdNamespaces ?? []].toSorted((a, b) => a.localeCompare(b));
4010
+ const current = state.erdNamespaces?.toSorted((a, b) => a.localeCompare(b)) ?? null;
4011
+ if (current === null || recorded.length !== current.length || recorded.some((namespace, index) => namespace !== current[index])) findings.push({
4012
+ target: id,
4013
+ rule: "erd-namespaces",
4014
+ message: "TailorDB namespaces for ERD preview changed. Re-run setup so the ERD preview matrix is regenerated."
4015
+ });
4016
+ }
4017
+ if ((target.kind === "branch" || target.kind === "tag") && target.inputs.migrationDriftCheck === false && state.hasMigrations === true) findings.push({
4018
+ target: id,
4019
+ rule: "migration-drift",
4020
+ message: "TailorDB namespaces with migrations were added to the config. Re-run setup so the tailor-migration-drift-check step is included in the plan job."
4021
+ });
4022
+ if ((target.kind === "branch" || target.kind === "tag") && target.inputs.seedValidate === false && state.hasSeeds === true) findings.push({
4023
+ target: id,
4024
+ rule: "seed-validate",
4025
+ message: "Seed plugin detected in the current config. Re-run setup so the tailor-seed-validate step is included in the plan job."
4026
+ });
4027
+ if (target.kind === "action" && target.inputs.hasStaticWebsites === false && state.hasStaticWebsites === true) findings.push({
4028
+ target: id,
4029
+ rule: "static-websites",
4030
+ message: "Static websites were added to the config. Re-run setup so the build-site slot is included in the composite action."
4031
+ });
4032
+ return findings;
4033
+ }
4034
+ function detectDefaultBranchSafe(cwd, run) {
4035
+ try {
4036
+ return detectDefaultBranch(cwd, run);
4037
+ } catch {
4038
+ return null;
4039
+ }
4040
+ }
4041
+ function escapesRoot(rel) {
4042
+ return path.isAbsolute(rel) || rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("../");
4043
+ }
4044
+ function resolveWithinRoot(outputDir, relPath) {
4045
+ if (path.isAbsolute(relPath)) return null;
4046
+ const abs = path.join(outputDir, relPath);
4047
+ if (escapesRoot(path.relative(outputDir, abs))) return null;
4048
+ try {
4049
+ if (escapesRoot(path.relative(fs$1.realpathSync(outputDir), fs$1.realpathSync(abs)))) return null;
4050
+ } catch {}
4051
+ return abs;
4052
+ }
4053
+ function readHash(absFile, normalize) {
4054
+ try {
4055
+ const content = fs$1.readFileSync(absFile, "utf-8");
4056
+ return hashContent(normalize ? normalize(content) : content);
4057
+ } catch {
4058
+ return null;
4059
+ }
4060
+ }
4061
+ async function defaultLoadErdNamespaces(configPath) {
4062
+ const { config } = await loadConfig(configPath);
4063
+ return extractOwnedNamespaces(config);
4064
+ }
4065
+ async function defaultLoadHasMigrations(configPath) {
4066
+ const { config } = await loadConfig(configPath);
4067
+ return getNamespacesWithMigrations(config, path.dirname(configPath)).length > 0;
4068
+ }
4069
+ async function defaultLoadHasSeeds(configPath) {
4070
+ const { plugins } = await loadConfig(configPath);
4071
+ return plugins.some((p) => p.id === "@tailor-platform/seed");
4072
+ }
4073
+ async function defaultLoadHasStaticWebsites(configPath) {
4074
+ const { config } = await loadConfig(configPath);
4075
+ return (config.staticWebsites?.length ?? 0) > 0;
4076
+ }
4077
+ /**
4078
+ * Audit the generated workflows for drift against the current config/repo
4079
+ * state. Read-only: never writes files, the lock, or the config.
4080
+ *
4081
+ * Throws when drift is found (so it composes like the other `:check`
4082
+ * commands). The workflow drift-check step layers advisory behaviour on top
4083
+ * (per-rule ignore / continue-on-error); the CLI itself reports via exit code.
4084
+ * @param options - Check options
4085
+ */
4086
+ async function checkGitHub(options) {
4087
+ logBetaWarning("setup");
4088
+ const { outputDir } = options;
4089
+ const lock = readLock(outputDir);
4090
+ if (!lock || lock.targets.length === 0) throw new Error("No managed workflows found (.github/tailor.lock is missing or empty). Run `tailor setup branch` (or another setup subcommand) first.");
4091
+ if (!options.ci) {
4092
+ if (lock.targets.some((t) => t.kind === "branch" || t.kind === "tag" || t.kind === "coordinate") && !process.env["TAILOR_PLATFORM_WORKSPACE_ID"]) throw new Error("TAILOR_PLATFORM_WORKSPACE_ID is not set. Provision the workspace and set the variable:\n tailor workspace create # if it does not exist yet; copy the id\n gh variable set TAILOR_PLATFORM_WORKSPACE_ID --env <environment>");
4093
+ }
4094
+ const slackToken = Boolean(process.env["TAILOR_SLACK_BOT_TOKEN"]);
4095
+ if (slackToken !== Boolean(process.env["TAILOR_SLACK_CHANNEL_ID"])) throw new Error(`Slack is partially configured: ${slackToken ? "TAILOR_SLACK_BOT_TOKEN is set" : "TAILOR_SLACK_CHANNEL_ID is set"} but not both. TAILOR_SLACK_BOT_TOKEN (secret) and TAILOR_SLACK_CHANNEL_ID (variable) must be set together, or neither should be set.`);
4096
+ const exists = options.configExistsAt ?? ((p) => fs$1.existsSync(p));
4097
+ const defaultBranch = detectDefaultBranchSafe(outputDir, options.gitRunner);
4098
+ const loadErdNamespaces = options.loadErdNamespaces ?? defaultLoadErdNamespaces;
4099
+ const loadHasMigrations = options.loadHasMigrations ?? defaultLoadHasMigrations;
4100
+ const loadHasSeeds = options.loadHasSeeds ?? defaultLoadHasSeeds;
4101
+ const loadHasStaticWebsites = options.loadHasStaticWebsites ?? defaultLoadHasStaticWebsites;
4102
+ const findings = [];
4103
+ for (const target of lock.targets) {
4104
+ const absFile = resolveWithinRoot(outputDir, target.file);
4105
+ const currentHash = absFile === null ? null : readHash(absFile, target.kind === "action" ? normalizeActionContent : void 0);
4106
+ const configAbs = target.kind === "coordinate" ? null : resolveWithinRoot(outputDir, path.join(target.inputs.dir, "tailor.config.ts"));
4107
+ const configExists = target.kind === "coordinate" || configAbs !== null && exists(configAbs);
4108
+ const erdNamespaces = target.kind === "branch" && target.inputs.erdPreview && configAbs !== null && configExists ? await loadErdNamespaces(configAbs) : null;
4109
+ const hasMigrations = (target.kind === "branch" || target.kind === "tag") && target.inputs.migrationDriftCheck !== void 0 && configAbs !== null && configExists ? await loadHasMigrations(configAbs) : void 0;
4110
+ const hasSeeds = (target.kind === "branch" || target.kind === "tag") && target.inputs.seedValidate !== void 0 && configAbs !== null && configExists ? await loadHasSeeds(configAbs) : void 0;
4111
+ const hasStaticWebsites = target.kind === "action" && target.inputs.hasStaticWebsites !== void 0 && configAbs !== null && configExists ? await loadHasStaticWebsites(configAbs) : void 0;
4112
+ findings.push(...findTargetDrift(target, {
4113
+ fileExists: currentHash !== null,
4114
+ currentHash,
4115
+ configExists,
4116
+ defaultBranch,
4117
+ templateVersion: 6,
4118
+ erdNamespaces,
4119
+ hasMigrations,
4120
+ hasSeeds,
4121
+ hasStaticWebsites
4122
+ }));
4123
+ }
4124
+ const count = lock.targets.length;
4125
+ if (findings.length === 0) {
4126
+ logger.success(`No drift detected across ${String(count)} target(s).`);
4127
+ return;
4128
+ }
4129
+ for (const finding of findings) logger.warn(`[${finding.target}] ${finding.message} (ignore key: ${finding.rule})`);
4130
+ throw new Error(`Detected ${String(findings.length)} drift finding(s) across ${String(count)} target(s). Re-run \`tailor setup\` to regenerate, or address each finding above.`);
4131
+ }
4132
+
4133
+ //#endregion
4134
+ //#region src/cli/commands/setup/delete.ts
4135
+ function normalizeRelPath(input) {
4136
+ return input.replaceAll("\\", "/").replace(/\/{2,}/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
4137
+ }
4138
+ function targetKey(target) {
4139
+ return `${target.kind}:${target.workspaceName}`;
4140
+ }
4141
+ function resolveMatch(outputDir, targets, input) {
4142
+ const relPath = normalizeRelPath(input);
4143
+ const absPath = resolveWithinRoot(outputDir, relPath);
4144
+ if (absPath === null) throw new Error(`"${input}" must be a path inside the repository.`);
4145
+ const target = targets.find((t) => t.file === relPath);
4146
+ if (!target) throw new Error(`"${relPath}" is not a managed workflow/action file (not recorded in .github/tailor-sdk.lock). Only files generated by \`tailor-sdk setup\` can be deleted with this command.`);
4147
+ return {
4148
+ relPath,
4149
+ absPath,
4150
+ target
4151
+ };
4152
+ }
4153
+ function warnCoordinatorReferences(allTargets, deletedKeys, match) {
4154
+ if (match.target.kind !== "action") return;
4155
+ for (const coordinator of allTargets) if (coordinator.kind === "coordinate" && coordinator.inputs.actionDirs?.includes(match.target.inputs.dir) && !deletedKeys.has(targetKey(coordinator))) logger.warn(`Coordinator "${coordinator.workspaceName}" still references this action's directory (${match.target.inputs.dir}). Re-run \`tailor-sdk setup coordinate\` without --action ${match.target.workspaceName} to remove it there too.`);
4156
+ }
4157
+ /**
4158
+ * Delete managed workflow/action files and their `.github/tailor-sdk.lock` entries.
4159
+ *
4160
+ * Refuses to touch files that are not recorded in the lock, so it never
4161
+ * deletes hand-written workflows or the user-owned tailor-setup action.
4162
+ * @param options - Delete options
4163
+ */
4164
+ async function setupDelete(options) {
4165
+ logBetaWarning("setup");
4166
+ const { outputDir, yes } = options;
4167
+ const lock = readLock(outputDir);
4168
+ if (!lock || lock.targets.length === 0) throw new Error("No managed workflows found (.github/tailor-sdk.lock is missing or empty). Run `tailor-sdk setup branch` (or another setup subcommand) first.");
4169
+ const matches = [...new Set(options.files.map(normalizeRelPath))].map((relPath) => resolveMatch(outputDir, lock.targets, relPath));
4170
+ const deletedKeys = new Set(matches.map((m) => targetKey(m.target)));
4171
+ for (const match of matches) warnCoordinatorReferences(lock.targets, deletedKeys, match);
4172
+ if (!yes) {
4173
+ const list = matches.map((m) => m.relPath).join("\n ");
4174
+ if (!await prompt.confirm({
4175
+ message: `This will delete the following file(s) and their .github/tailor-sdk.lock entries:\n ${list}\nContinue?`,
4176
+ default: false
4177
+ })) {
4178
+ logger.info("Delete cancelled.");
4179
+ return;
4180
+ }
4181
+ }
4182
+ const remaining = [...lock.targets];
4183
+ try {
4184
+ for (const { relPath, absPath, target } of matches) {
4185
+ fs$1.rmSync(absPath, { force: true });
4186
+ if (target.kind === "action") {
4187
+ const dir = path.dirname(absPath);
4188
+ if (fs$1.existsSync(dir) && fs$1.readdirSync(dir).length === 0) fs$1.rmdirSync(dir);
4189
+ }
4190
+ const index = remaining.findIndex((t) => targetKey(t) === targetKey(target));
4191
+ if (index !== -1) remaining.splice(index, 1);
4192
+ logger.success(`Deleted ${styles.path(relPath)} (${target.kind} ${target.workspaceName})`);
4193
+ }
4194
+ } finally {
4195
+ if (remaining.length !== lock.targets.length) writeLock(outputDir, {
4196
+ version: 1,
4197
+ targets: remaining
4198
+ });
4199
+ }
4200
+ }
3085
4201
 
3086
4202
  //#endregion
3087
4203
  //#region src/cli/commands/setup/index.ts
3088
4204
  const checkCommand = defineAppCommand({
3089
4205
  name: "check",
3090
4206
  description: "Audit generated workflows for drift against the current config/repo (read-only).",
3091
- args: z.object({}).strict(),
3092
- run: () => {
3093
- checkGitHub({ outputDir: process.cwd() });
4207
+ args: z.strictObject({ ci: arg(z.boolean().default(false), { description: "Run in CI mode: skip checks that are handled by the runtime (e.g. TAILOR_PLATFORM_WORKSPACE_ID)." }) }),
4208
+ run: async (args) => {
4209
+ await checkGitHub({
4210
+ outputDir: process.cwd(),
4211
+ ci: args.ci
4212
+ });
4213
+ }
4214
+ });
4215
+ const coordinateCommand = defineAppCommand({
4216
+ name: "coordinate",
4217
+ description: "Generate a coordinator workflow that orchestrates multiple --action-generated composite actions.",
4218
+ args: z.strictObject({
4219
+ name: arg(z.string().min(1), {
4220
+ alias: "n",
4221
+ description: "Coordinator name (used in the generated workflow file name and job names)"
4222
+ }),
4223
+ action: arg(z.array(z.string().min(1)).min(1), { description: "Composite action to include (can be specified multiple times). tailor- prefix optional." }),
4224
+ branch: arg(z.string().min(1).optional(), { description: "Branch target: deploy trigger branch (defaults to the detected default branch)" }),
4225
+ tag: arg(z.boolean().default(false), { description: "Generate a tag target coordinator" }),
4226
+ environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs" }),
4227
+ force: arg(z.boolean().default(false), { description: "Discard hand edits and regenerate" })
4228
+ }),
4229
+ run: async (args) => {
4230
+ const coordinateKind = args.tag ? "tag" : "branch";
4231
+ await setupCoordinate({
4232
+ coordinatorName: args.name,
4233
+ coordinateKind,
4234
+ actions: args.action,
4235
+ branch: args.branch,
4236
+ environment: args.environment,
4237
+ force: args.force,
4238
+ outputDir: process.cwd()
4239
+ });
4240
+ }
4241
+ });
4242
+ const actionCommand = defineAppCommand({
4243
+ name: "action",
4244
+ description: "Generate a per-app composite action for use with setup coordinate (monorepo multi-app deploys).",
4245
+ args: z.strictObject({
4246
+ name: arg(z.string().min(1).optional(), {
4247
+ alias: "n",
4248
+ description: "Name (defaults to the config 'name')"
4249
+ }),
4250
+ dir: arg(z.string().min(1).default("."), {
4251
+ alias: "d",
4252
+ description: "App directory"
4253
+ }),
4254
+ environment: arg(z.string().min(1).optional(), { description: "GitHub Environment (defaults to the workspace name)" }),
4255
+ force: arg(z.boolean().default(false), { description: "Discard hand edits and regenerate" })
4256
+ }),
4257
+ run: async (args) => {
4258
+ await setupTarget({
4259
+ kind: "action",
4260
+ workspaceName: args.name,
4261
+ dir: args.dir,
4262
+ environment: args.environment,
4263
+ force: args.force,
4264
+ outputDir: process.cwd()
4265
+ });
3094
4266
  }
3095
4267
  });
3096
- const setupCommand = defineAppCommand({
3097
- name: "setup",
3098
- description: "Generate a CI deploy workflow for your project. (beta)",
3099
- args: z.object({
3100
- provider: arg(z.enum(["github"], { message: "Only the 'github' provider is supported." }).default("github"), {
3101
- alias: "p",
3102
- description: "CI provider to generate for (only 'github' is supported)"
4268
+ const branchCommand = defineAppCommand({
4269
+ name: "branch",
4270
+ description: "Generate a branch-target deploy workflow (push to branch triggers deploy).",
4271
+ args: z.strictObject({
4272
+ name: arg(z.string().min(1).optional(), {
4273
+ alias: "n",
4274
+ description: "Name (defaults to the config 'name')"
4275
+ }),
4276
+ branch: arg(z.string().min(1).optional(), { description: "Deploy trigger branch (defaults to the detected default branch)" }),
4277
+ environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs (defaults to the workspace name)" }),
4278
+ "erd-preview": arg(z.boolean().default(false), { description: "Add PR ERD viewer artifacts with current/diff previews for TailorDB namespaces" }),
4279
+ dir: arg(z.string().min(1).default("."), {
4280
+ alias: "d",
4281
+ description: "App directory (for monorepo setups)"
3103
4282
  }),
3104
- "workspace-name": arg(z.string().min(1).optional(), {
4283
+ force: arg(z.boolean().default(false), { description: "Discard hand edits / take over unmanaged files and regenerate" })
4284
+ }),
4285
+ run: async (args) => {
4286
+ await setupTarget({
4287
+ kind: "branch",
4288
+ workspaceName: args.name,
4289
+ branch: args.branch,
4290
+ environment: args.environment,
4291
+ erdPreview: args["erd-preview"],
4292
+ dir: args.dir,
4293
+ force: args.force,
4294
+ outputDir: process.cwd()
4295
+ });
4296
+ }
4297
+ });
4298
+ const tagCommand = defineAppCommand({
4299
+ name: "tag",
4300
+ description: "Generate a tag-target deploy workflow (tag push triggers deploy).",
4301
+ args: z.strictObject({
4302
+ name: arg(z.string().min(1).optional(), {
3105
4303
  alias: "n",
3106
- description: "Workspace name (defaults to the config 'name')"
4304
+ description: "Name (defaults to the config 'name')"
3107
4305
  }),
3108
- branch: arg(z.string().min(1).optional(), { description: "Branch target: deploy trigger branch (defaults to the detected default branch). Tag target: tag-reachability guard branch (no guard when omitted)" }),
3109
- tag: arg(z.boolean().default(false), { description: "Generate a tag target (deploy on tag push)" }),
3110
- "tag-pattern": arg(z.string().min(1).optional(), { description: "Tag glob to match (requires --tag; defaults to v*)" }),
4306
+ "tag-pattern": arg(z.string().min(1).default("v*"), { description: "Tag glob to match (defaults to v*)" }),
4307
+ branch: arg(z.string().min(1).optional(), { description: "Tag-reachability guard branch (no guard when omitted)" }),
3111
4308
  environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs (defaults to the workspace name)" }),
3112
- "no-plan": arg(z.boolean().default(false), { description: "Disable the plan job for a branch target (cannot be combined with --tag)" }),
3113
4309
  dir: arg(z.string().min(1).default("."), {
3114
4310
  alias: "d",
3115
4311
  description: "App directory (for monorepo setups)"
3116
4312
  }),
3117
4313
  force: arg(z.boolean().default(false), { description: "Discard hand edits / take over unmanaged files and regenerate" })
3118
- }).strict(),
3119
- subCommands: { check: checkCommand },
4314
+ }),
3120
4315
  run: async (args) => {
3121
- if (args["tag-pattern"] !== void 0 && !args.tag) throw new Error("--tag-pattern requires --tag.");
3122
- if (args["no-plan"] && args.tag) throw new Error("--no-plan cannot be combined with --tag.");
3123
- await setupGitHub({
3124
- workspaceName: args["workspace-name"],
4316
+ await setupTarget({
4317
+ kind: "tag",
4318
+ workspaceName: args.name,
4319
+ tagPattern: args["tag-pattern"],
3125
4320
  branch: args.branch,
3126
- tag: args.tag,
3127
- tagPattern: args["tag-pattern"] ?? "v*",
3128
4321
  environment: args.environment,
3129
- plan: !args["no-plan"],
3130
4322
  dir: args.dir,
3131
4323
  force: args.force,
3132
4324
  outputDir: process.cwd()
3133
4325
  });
3134
4326
  }
3135
4327
  });
3136
-
3137
- //#endregion
3138
- //#region src/cli/shared/skills-installer.ts
3139
- const SKILL_NAME = "tailor-sdk";
3140
- const SKILLS_SOURCE_ENV_KEY = "TAILOR_SDK_SKILLS_SOURCE";
3141
- function resolveNpxCommand(platform = process.platform) {
3142
- return platform === "win32" ? "npx.cmd" : "npx";
3143
- }
3144
- function resolveSkillsSource(source) {
3145
- return process.env[SKILLS_SOURCE_ENV_KEY] ?? source;
3146
- }
3147
- /**
3148
- * Build CLI arguments for `skills add` with the fixed tailor-sdk skill target.
3149
- * `--copy` is included so the installed skill survives `pnpm install` wiping `node_modules`.
3150
- * @param options - Options controlling the generated `skills add` arguments
3151
- * @param options.source
3152
- * @param options.agent
3153
- * @param options.yes
3154
- * @returns CLI arguments for `npx skills add`
3155
- */
3156
- function buildSkillsAddArgs(options) {
3157
- const args = [
3158
- "skills",
3159
- "add",
3160
- resolveSkillsSource(options.source),
3161
- "--skill",
3162
- SKILL_NAME,
3163
- "--copy"
3164
- ];
3165
- if (options.agent) args.push("--agent", options.agent);
3166
- if (options.yes) args.push("--yes");
3167
- return args;
3168
- }
3169
- /**
3170
- * Run `npx skills add` to install the tailor-sdk skill.
3171
- * @param options - Runtime options for skill installation
3172
- * @returns Process exit code from the spawned `npx` command
3173
- */
3174
- async function runSkillsInstaller(options) {
3175
- const args = buildSkillsAddArgs({
3176
- source: options.source,
3177
- agent: options.agent,
3178
- yes: options.yes
3179
- });
3180
- const childProcess = (options.spawnFn ?? ((command, spawnArgs, spawnOptions) => spawn(command, spawnArgs, spawnOptions)))(resolveNpxCommand(), args, { stdio: "inherit" });
3181
- return await new Promise((resolve, reject) => {
3182
- childProcess.on("error", (error) => reject(error));
3183
- childProcess.on("close", (code) => resolve(code ?? 1));
3184
- });
3185
- }
3186
-
3187
- //#endregion
3188
- //#region src/cli/commands/skills/install.ts
3189
- async function resolveBundledSkillsDir() {
3190
- return resolve(dirname(await resolvePackageJSON(import.meta.url)), "agent-skills");
3191
- }
3192
- const DEFAULT_AGENT = "claude-code";
3193
- const installCommand = defineAppCommand({
3194
- name: "install",
3195
- description: "Install the tailor-sdk agent skill from the installed SDK package.",
3196
- args: z.object({
3197
- agent: arg(z.string().default(DEFAULT_AGENT), {
3198
- alias: "a",
3199
- description: `vercel/skills agent name (e.g. ${DEFAULT_AGENT}, codex). Defaults to ${DEFAULT_AGENT}.`
4328
+ const previewCommand = defineAppCommand({
4329
+ name: "preview",
4330
+ description: "Generate a preview workflow (PR open/sync triggers deploy to a per-PR workspace).",
4331
+ args: z.strictObject({
4332
+ name: arg(z.string().min(1).optional(), {
4333
+ alias: "n",
4334
+ description: "Name (defaults to the config 'name')"
4335
+ }),
4336
+ branch: arg(z.string().min(1).optional(), { description: "Branch to filter PRs by (defaults to the detected default branch)" }),
4337
+ region: arg(z.string().min(1), { description: "Workspace region for preview workspace creation (e.g. us-west). Required." }),
4338
+ "require-preview-label": arg(z.boolean().default(false), { description: "Deploy preview only for PRs labeled `tailor:preview` instead of all PRs." }),
4339
+ environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the preview jobs (defaults to the workspace name)" }),
4340
+ dir: arg(z.string().min(1).default("."), {
4341
+ alias: "d",
4342
+ description: "App directory (for monorepo setups)"
3200
4343
  }),
3201
- yes: arg(z.boolean().default(false), {
3202
- alias: "y",
3203
- description: "Auto-approve prompts."
4344
+ force: arg(z.boolean().default(false), { description: "Discard hand edits / take over unmanaged files and regenerate" })
4345
+ }),
4346
+ run: async (args) => {
4347
+ await setupTarget({
4348
+ kind: "preview",
4349
+ workspaceName: args.name,
4350
+ branch: args.branch,
4351
+ region: args.region,
4352
+ requirePreviewLabel: args["require-preview-label"],
4353
+ environment: args.environment,
4354
+ dir: args.dir,
4355
+ force: args.force,
4356
+ outputDir: process.cwd()
4357
+ });
4358
+ }
4359
+ });
4360
+ const deleteCommand$1 = defineAppCommand({
4361
+ name: "delete",
4362
+ description: "Delete managed workflow/action file(s) and their .github/tailor.lock entries.",
4363
+ args: z.strictObject({
4364
+ ...confirmationArgs,
4365
+ files: arg(z.string().array().min(1), {
4366
+ positional: true,
4367
+ description: "Workflow/action file(s) to delete, as generated under .github/workflows or .github/actions"
3204
4368
  })
3205
- }).strict(),
4369
+ }),
3206
4370
  run: async (args) => {
3207
- const exitCode = await runSkillsInstaller({
3208
- source: await resolveBundledSkillsDir(),
3209
- agent: args.agent,
3210
- yes: args.yes
4371
+ await setupDelete({
4372
+ files: args.files,
4373
+ yes: args.yes,
4374
+ outputDir: process.cwd()
3211
4375
  });
3212
- if (exitCode !== 0) process.exit(exitCode);
3213
4376
  }
3214
4377
  });
3215
-
3216
- //#endregion
3217
- //#region src/cli/commands/skills/index.ts
3218
- const skillsCommand = defineCommand({
3219
- name: "skills",
3220
- description: "Manage Tailor SDK agent skills.",
3221
- subCommands: { install: installCommand },
3222
- async run() {
3223
- await runCommand(installCommand, []);
4378
+ const setupCommand = defineCommand({
4379
+ name: "setup",
4380
+ description: "Generate CI deploy workflows for your project. (beta)",
4381
+ subCommands: {
4382
+ branch: branchCommand,
4383
+ tag: tagCommand,
4384
+ preview: previewCommand,
4385
+ action: actionCommand,
4386
+ coordinate: coordinateCommand,
4387
+ check: checkCommand,
4388
+ delete: deleteCommand$1
3224
4389
  }
3225
4390
  });
3226
4391
 
@@ -3264,7 +4429,7 @@ async function withTimeout(p, ms, message) {
3264
4429
  //#endregion
3265
4430
  //#region src/cli/commands/staticwebsite/deploy.ts
3266
4431
  const CHUNK_SIZE = 64 * 1024;
3267
- const IGNORED_FILES = new Set([
4432
+ const IGNORED_FILES = /* @__PURE__ */ new Set([
3268
4433
  ".DS_Store",
3269
4434
  "thumbs.db",
3270
4435
  "desktop.ini"
@@ -3384,7 +4549,7 @@ function logSkippedFiles(skippedFiles) {
3384
4549
  const deployCommand = defineAppCommand({
3385
4550
  name: "deploy",
3386
4551
  description: "Deploy a static website from a local build directory.",
3387
- args: z.object({
4552
+ args: z.strictObject({
3388
4553
  ...workspaceArgs,
3389
4554
  name: arg(z.string(), {
3390
4555
  alias: "n",
@@ -3395,7 +4560,7 @@ const deployCommand = defineAppCommand({
3395
4560
  description: "Path to the static website files",
3396
4561
  completion: { type: "directory" }
3397
4562
  })
3398
- }).strict(),
4563
+ }),
3399
4564
  run: async (args) => {
3400
4565
  await assertWritable({ profile: args.profile });
3401
4566
  logger.info(`Deploying static website "${args.name}" from directory: ${args.dir}`);
@@ -3437,13 +4602,13 @@ const statusLabels = {
3437
4602
  const domainGetCommand = defineAppCommand({
3438
4603
  name: "get",
3439
4604
  description: "Get details of a custom domain.",
3440
- args: z.object({
4605
+ args: z.strictObject({
3441
4606
  ...workspaceArgs,
3442
4607
  domain: arg(z.string(), {
3443
4608
  positional: true,
3444
4609
  description: "Custom domain name"
3445
4610
  })
3446
- }).strict(),
4611
+ }),
3447
4612
  run: async (args) => {
3448
4613
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3449
4614
  const workspaceId = await loadWorkspaceId({
@@ -3477,13 +4642,13 @@ const domainGetCommand = defineAppCommand({
3477
4642
  const domainListCommand = defineAppCommand({
3478
4643
  name: "list",
3479
4644
  description: "List custom domains for a static website.",
3480
- args: z.object({
4645
+ args: z.strictObject({
3481
4646
  ...workspaceArgs,
3482
4647
  name: arg(z.string(), {
3483
4648
  positional: true,
3484
4649
  description: "Static website name"
3485
4650
  })
3486
- }).strict(),
4651
+ }),
3487
4652
  run: async (args) => {
3488
4653
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3489
4654
  const workspaceId = await loadWorkspaceId({
@@ -3529,13 +4694,13 @@ const domainCommand = defineCommand({
3529
4694
  const getCommand = defineAppCommand({
3530
4695
  name: "get",
3531
4696
  description: "Get details of a specific static website.",
3532
- args: z.object({
4697
+ args: z.strictObject({
3533
4698
  ...workspaceArgs,
3534
4699
  name: arg(z.string(), {
3535
4700
  positional: true,
3536
4701
  description: "Static website name"
3537
4702
  })
3538
- }).strict(),
4703
+ }),
3539
4704
  run: async (args) => {
3540
4705
  const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3541
4706
  const workspaceId = await loadWorkspaceId({
@@ -3597,10 +4762,10 @@ async function listStaticWebsites(options) {
3597
4762
  const listCommand$2 = defineAppCommand({
3598
4763
  name: "list",
3599
4764
  description: "List all static websites in a workspace.",
3600
- args: z.object({
4765
+ args: z.strictObject({
3601
4766
  ...workspaceArgs,
3602
4767
  ...paginationArgs()
3603
- }).strict(),
4768
+ }),
3604
4769
  run: async (args) => {
3605
4770
  const jsonOutput = logger.jsonMode;
3606
4771
  const websites = await listStaticWebsites({
@@ -3862,7 +5027,7 @@ function buildTailorDbErdSchema(options) {
3862
5027
  namespace: namespaceData.namespace,
3863
5028
  source: options.source ?? "local",
3864
5029
  cleanRoom: {
3865
- implementation: "tailor-sdk",
5030
+ implementation: "tailor",
3866
5031
  notes: CLEAN_ROOM_NOTES
3867
5032
  },
3868
5033
  tables,
@@ -3904,6 +5069,9 @@ async function initErdDeployContext(args) {
3904
5069
  const VIEWER_ASSETS_DIR = "erd-viewer-assets";
3905
5070
  const STYLES_LINK = "<link rel=\"stylesheet\" href=\"./styles.css\" />";
3906
5071
  const APP_SCRIPT = "<script src=\"./app.js\" type=\"module\"><\/script>";
5072
+ function escapeHtml(value) {
5073
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
5074
+ }
3907
5075
  function assetDirCandidates() {
3908
5076
  const currentDir = path.dirname(fileURLToPath(import.meta.url));
3909
5077
  return [
@@ -3926,8 +5094,8 @@ function resolveViewerAssetsDir() {
3926
5094
  * are inlined as separately extractable blocks: a `<style>` element, a
3927
5095
  * `<script type="module">`, and a `<script type="application/json"
3928
5096
  * id="erd-schema">` data block. This renders without any sibling asset files
3929
- * and lets external tooling (e.g. a future ERD diff) pull out the schema via
3930
- * `JSON.parse`.
5097
+ * and lets external tooling (including the ERD diff command) pull out the
5098
+ * schema via `JSON.parse`.
3931
5099
  * @param options - Viewer build options.
3932
5100
  * @returns The self-contained HTML document.
3933
5101
  */
@@ -3938,8 +5106,10 @@ function buildViewerHtml(options) {
3938
5106
  const appJs = fs$1.readFileSync(path.join(assetsDir, "app.js"), "utf8");
3939
5107
  if (!html.includes(STYLES_LINK) || !html.includes(APP_SCRIPT)) throw new Error("ERD viewer index.html is missing expected asset references for inlining.");
3940
5108
  const embedScript = `<script type="application/json" id="erd-schema">${JSON.stringify(options.schema).replaceAll("<", "\\u003c")}<\/script>`;
5109
+ const currentSchemaScript = options.currentSchema === void 0 ? "" : `\n <script type="application/json" id="erd-current-schema">${JSON.stringify(options.currentSchema).replaceAll("<", "\\u003c")}<\/script>`;
5110
+ const diffScript = options.diff === void 0 ? "" : `\n <script type="application/json" id="erd-diff">${JSON.stringify(options.diff).replaceAll("<", "\\u003c")}<\/script>`;
3941
5111
  const inlineScript = `<script type="module">\n${appJs.replace(/<\/script/gi, "<\\/script")}\n<\/script>`;
3942
- return html.replace(STYLES_LINK, `<style>\n${css}\n</style>`).replace(APP_SCRIPT, `${embedScript}\n ${inlineScript}`);
5112
+ return html.replace("<title>TailorDB ERD</title>", () => `<title>${escapeHtml(options.title ?? "TailorDB ERD")}</title>`).replace(STYLES_LINK, () => `<style>\n${css}\n</style>`).replace(APP_SCRIPT, () => `${embedScript}${currentSchemaScript}${diffScript}\n ${inlineScript}`);
3943
5113
  }
3944
5114
  /**
3945
5115
  * Write the self-contained TailorDB ERD viewer to `<distDir>/index.html`.
@@ -3956,7 +5126,7 @@ function writeViewerDist(options) {
3956
5126
 
3957
5127
  //#endregion
3958
5128
  //#region src/cli/commands/tailordb/erd/export.ts
3959
- const DEFAULT_ERD_BASE_DIR$1 = ".tailor-sdk/erd";
5129
+ const DEFAULT_ERD_BASE_DIR$1 = ".tailor/erd";
3960
5130
  function getErdSite(context, namespace) {
3961
5131
  const dbConfig = context.config.db?.[namespace];
3962
5132
  if (!dbConfig || "external" in dbConfig) return;
@@ -4036,7 +5206,7 @@ function prepareErdBuildsFromContext(options) {
4036
5206
  const erdExportCommand = defineAppCommand({
4037
5207
  name: "export",
4038
5208
  description: "Export TailorDB ERD static viewer from local TailorDB schema.",
4039
- args: z.object({
5209
+ args: z.strictObject({
4040
5210
  ...configArg,
4041
5211
  namespace: arg(z.string().optional(), {
4042
5212
  alias: "n",
@@ -4047,7 +5217,7 @@ const erdExportCommand = defineAppCommand({
4047
5217
  description: "Output directory path for TailorDB ERD viewer files (writes to `<outputDir>/<namespace>/dist`)",
4048
5218
  completion: { type: "directory" }
4049
5219
  })
4050
- }).strict(),
5220
+ }),
4051
5221
  run: async (args) => {
4052
5222
  initErdCommand();
4053
5223
  const results = await prepareErdBuilds({
@@ -4072,13 +5242,13 @@ const erdExportCommand = defineAppCommand({
4072
5242
  const erdDeployCommand = defineAppCommand({
4073
5243
  name: "deploy",
4074
5244
  description: "Deploy ERD static website for TailorDB namespace(s).",
4075
- args: z.object({
5245
+ args: z.strictObject({
4076
5246
  ...deploymentArgs,
4077
5247
  namespace: arg(z.string().optional(), {
4078
5248
  alias: "n",
4079
5249
  description: "TailorDB namespace name (optional - deploys all namespaces with erdSite if omitted)"
4080
5250
  })
4081
- }).strict(),
5251
+ }),
4082
5252
  run: async (args) => {
4083
5253
  await assertWritable({ profile: args.profile });
4084
5254
  const { client, workspaceId } = await initErdDeployContext(args);
@@ -4109,13 +5279,369 @@ const erdDeployCommand = defineAppCommand({
4109
5279
  }
4110
5280
  });
4111
5281
 
5282
+ //#endregion
5283
+ //#region src/cli/commands/tailordb/erd/diff.ts
5284
+ const SCHEMA_BLOCK_PATTERN = /<script type="application\/json" id="erd-schema">([\s\S]*?)<\/script>/;
5285
+ function stableValue(value) {
5286
+ if (Array.isArray(value)) return value.map((item) => stableValue(item));
5287
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, stableValue(item)]));
5288
+ return value;
5289
+ }
5290
+ function stableJson(value) {
5291
+ return JSON.stringify(stableValue(value));
5292
+ }
5293
+ function changedFields(base, head) {
5294
+ return [.../* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(head)])].toSorted((a, b) => a.localeCompare(b)).filter((key) => stableJson(base[key]) !== stableJson(head[key]));
5295
+ }
5296
+ function detailForChangedFields(fields) {
5297
+ return `Changed fields: ${fields.join(", ")}`;
5298
+ }
5299
+ function mapByName(items) {
5300
+ return new Map(items.map((item) => [item.name, item]));
5301
+ }
5302
+ function compareCommonNamed(baseItems, headItems, addChange) {
5303
+ const baseByName = mapByName(baseItems);
5304
+ const headByName = mapByName(headItems);
5305
+ const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5306
+ for (const name of commonNames) {
5307
+ const base = baseByName.get(name);
5308
+ const head = headByName.get(name);
5309
+ if (!base || !head) continue;
5310
+ const fields = changedFields(base, head);
5311
+ if (fields.length > 0) addChange(name, fields);
5312
+ }
5313
+ }
5314
+ function removedNames(baseItems, headItems) {
5315
+ const headByName = mapByName(headItems);
5316
+ return baseItems.map((item) => item.name).filter((name) => !headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5317
+ }
5318
+ function addedNames(baseItems, headItems) {
5319
+ const baseByName = mapByName(baseItems);
5320
+ return headItems.map((item) => item.name).filter((name) => !baseByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5321
+ }
5322
+ function tableMetadata(table) {
5323
+ return {
5324
+ backwardRelationships: table.backwardRelationships,
5325
+ description: table.description,
5326
+ forwardRelationships: table.forwardRelationships,
5327
+ pluralForm: table.pluralForm,
5328
+ source: table.source
5329
+ };
5330
+ }
5331
+ function pushChange(changes, change) {
5332
+ changes.push(change);
5333
+ }
5334
+ function diffColumns(changes, tableName, baseColumns, headColumns) {
5335
+ compareCommonNamed(baseColumns, headColumns, (name, fields) => {
5336
+ pushChange(changes, {
5337
+ action: "changed",
5338
+ entity: "column",
5339
+ path: `${tableName}.${name}`,
5340
+ detail: detailForChangedFields(fields)
5341
+ });
5342
+ });
5343
+ for (const name of removedNames(baseColumns, headColumns)) pushChange(changes, {
5344
+ action: "removed",
5345
+ entity: "column",
5346
+ path: `${tableName}.${name}`,
5347
+ detail: "Column removed"
5348
+ });
5349
+ for (const name of addedNames(baseColumns, headColumns)) pushChange(changes, {
5350
+ action: "added",
5351
+ entity: "column",
5352
+ path: `${tableName}.${name}`,
5353
+ detail: "Column added"
5354
+ });
5355
+ }
5356
+ function diffIndexes(changes, tableName, baseIndexes, headIndexes) {
5357
+ compareCommonNamed(baseIndexes, headIndexes, (name, fields) => {
5358
+ pushChange(changes, {
5359
+ action: "changed",
5360
+ entity: "index",
5361
+ path: `${tableName}.${name}`,
5362
+ detail: detailForChangedFields(fields)
5363
+ });
5364
+ });
5365
+ for (const name of removedNames(baseIndexes, headIndexes)) pushChange(changes, {
5366
+ action: "removed",
5367
+ entity: "index",
5368
+ path: `${tableName}.${name}`,
5369
+ detail: "Index removed"
5370
+ });
5371
+ for (const name of addedNames(baseIndexes, headIndexes)) pushChange(changes, {
5372
+ action: "added",
5373
+ entity: "index",
5374
+ path: `${tableName}.${name}`,
5375
+ detail: "Index added"
5376
+ });
5377
+ }
5378
+ function diffTables(changes, baseTables, headTables) {
5379
+ const baseByName = mapByName(baseTables);
5380
+ const headByName = mapByName(headTables);
5381
+ for (const name of removedNames(baseTables, headTables)) pushChange(changes, {
5382
+ action: "removed",
5383
+ entity: "table",
5384
+ path: name,
5385
+ detail: "Table removed"
5386
+ });
5387
+ for (const name of addedNames(baseTables, headTables)) pushChange(changes, {
5388
+ action: "added",
5389
+ entity: "table",
5390
+ path: name,
5391
+ detail: "Table added"
5392
+ });
5393
+ const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5394
+ for (const name of commonNames) {
5395
+ const base = baseByName.get(name);
5396
+ const head = headByName.get(name);
5397
+ if (!base || !head) continue;
5398
+ const tableFields = changedFields(tableMetadata(base), tableMetadata(head));
5399
+ if (tableFields.length > 0) pushChange(changes, {
5400
+ action: "changed",
5401
+ entity: "table",
5402
+ path: name,
5403
+ detail: detailForChangedFields(tableFields)
5404
+ });
5405
+ diffColumns(changes, name, base.columns, head.columns);
5406
+ diffIndexes(changes, name, base.indexes, head.indexes);
5407
+ }
5408
+ }
5409
+ function diffRelations(changes, baseRelations, headRelations) {
5410
+ compareCommonNamed(baseRelations, headRelations, (name, fields) => {
5411
+ pushChange(changes, {
5412
+ action: "changed",
5413
+ entity: "relation",
5414
+ path: name,
5415
+ detail: detailForChangedFields(fields)
5416
+ });
5417
+ });
5418
+ for (const name of removedNames(baseRelations, headRelations)) pushChange(changes, {
5419
+ action: "removed",
5420
+ entity: "relation",
5421
+ path: name,
5422
+ detail: "Relation removed"
5423
+ });
5424
+ for (const name of addedNames(baseRelations, headRelations)) pushChange(changes, {
5425
+ action: "added",
5426
+ entity: "relation",
5427
+ path: name,
5428
+ detail: "Relation added"
5429
+ });
5430
+ }
5431
+ function summarize(changes) {
5432
+ return {
5433
+ added: changes.filter((change) => change.action === "added").length,
5434
+ changed: changes.filter((change) => change.action === "changed").length,
5435
+ removed: changes.filter((change) => change.action === "removed").length
5436
+ };
5437
+ }
5438
+ function extractEmbeddedErdSchema(html) {
5439
+ const match = SCHEMA_BLOCK_PATTERN.exec(html);
5440
+ if (!match?.[1]) throw new Error("ERD schema block not found.");
5441
+ try {
5442
+ return JSON.parse(match[1]);
5443
+ } catch (error) {
5444
+ throw new Error(`Failed to parse ERD schema block: ${String(error)}`, { cause: error });
5445
+ }
5446
+ }
5447
+ function createEmptyErdSchema(options) {
5448
+ return {
5449
+ version: 1,
5450
+ namespace: options.namespace,
5451
+ generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
5452
+ revision: options.revision,
5453
+ source: "local",
5454
+ cleanRoom: {
5455
+ implementation: "tailor",
5456
+ notes: ["Synthetic empty schema used for ERD diff generation."]
5457
+ },
5458
+ tables: [],
5459
+ relations: []
5460
+ };
5461
+ }
5462
+ function buildErdSchemaDiff(options) {
5463
+ const { base, head } = options;
5464
+ if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
5465
+ const changes = [];
5466
+ diffTables(changes, base.tables, head.tables);
5467
+ diffRelations(changes, base.relations, head.relations);
5468
+ const summary = summarize(changes);
5469
+ return {
5470
+ namespace: head.namespace,
5471
+ baseRevision: base.revision,
5472
+ headRevision: head.revision,
5473
+ changed: changes.length > 0,
5474
+ summary,
5475
+ changes
5476
+ };
5477
+ }
5478
+ function mergeNamedByHead(baseItems, headItems) {
5479
+ const headByName = mapByName(headItems);
5480
+ const removedItems = baseItems.filter((item) => !headByName.has(item.name)).toSorted((a, b) => a.name.localeCompare(b.name));
5481
+ return [...headItems, ...removedItems];
5482
+ }
5483
+ function mergeDiffViewerTable(baseTable, headTable) {
5484
+ return {
5485
+ ...headTable,
5486
+ columns: mergeNamedByHead(baseTable.columns, headTable.columns),
5487
+ indexes: mergeNamedByHead(baseTable.indexes, headTable.indexes)
5488
+ };
5489
+ }
5490
+ function mergeDiffViewerTables(baseTables, headTables) {
5491
+ const baseByName = mapByName(baseTables);
5492
+ const headByName = mapByName(headTables);
5493
+ const merged = headTables.map((headTable) => {
5494
+ const baseTable = baseByName.get(headTable.name);
5495
+ return baseTable ? mergeDiffViewerTable(baseTable, headTable) : headTable;
5496
+ });
5497
+ const removedTables = baseTables.filter((table) => !headByName.has(table.name)).toSorted((a, b) => a.name.localeCompare(b.name));
5498
+ return [...merged, ...removedTables];
5499
+ }
5500
+ /**
5501
+ * Build the schema rendered by the diff viewer. The ordinary ERD viewer only
5502
+ * knows how to draw objects present in its schema, so the diff schema keeps
5503
+ * base-only tables, columns, indexes, and relations visible for removal
5504
+ * highlighting while preferring head-side metadata for unchanged objects.
5505
+ * @param options - Base and head schemas to merge for diff rendering.
5506
+ * @returns A TailorDB ERD schema suitable for the visual diff viewer.
5507
+ */
5508
+ function buildErdDiffViewerSchema(options) {
5509
+ const { base, head } = options;
5510
+ if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
5511
+ return {
5512
+ ...head,
5513
+ tables: mergeDiffViewerTables(base.tables, head.tables),
5514
+ relations: mergeNamedByHead(base.relations, head.relations)
5515
+ };
5516
+ }
5517
+ function renderErdDiffHtml(options) {
5518
+ return buildViewerHtml({
5519
+ schema: options.schema,
5520
+ currentSchema: options.currentSchema,
5521
+ diff: options.diff,
5522
+ title: `TailorDB ERD diff - ${options.diff.namespace}`
5523
+ });
5524
+ }
5525
+
5526
+ //#endregion
5527
+ //#region src/cli/commands/tailordb/erd/diff-command.ts
5528
+ function readSchema(filePath) {
5529
+ if (!filePath) return void 0;
5530
+ return extractEmbeddedErdSchema(fs$1.readFileSync(filePath, "utf8"));
5531
+ }
5532
+ function resolveNamespace(options) {
5533
+ const namespace = options.namespace ?? options.head?.namespace ?? options.base?.namespace;
5534
+ if (!namespace) throw new Error("Missing --namespace when one side of the ERD diff is omitted.");
5535
+ if (options.base && options.base.namespace !== namespace) throw new Error(`Base ERD namespace "${options.base.namespace}" does not match "${namespace}".`);
5536
+ if (options.head && options.head.namespace !== namespace) throw new Error(`Head ERD namespace "${options.head.namespace}" does not match "${namespace}".`);
5537
+ return namespace;
5538
+ }
5539
+ function writeFile(filePath, content) {
5540
+ fs$1.mkdirSync(path.dirname(filePath), { recursive: true });
5541
+ fs$1.writeFileSync(filePath, content, "utf8");
5542
+ }
5543
+ function writeErdDiff(options) {
5544
+ const base = readSchema(options.baseHtml);
5545
+ const head = readSchema(options.headHtml);
5546
+ if (!base && !head) throw new Error("At least one of --base-html or --head-html is required.");
5547
+ const namespace = resolveNamespace({
5548
+ namespace: options.namespace,
5549
+ base,
5550
+ head
5551
+ });
5552
+ const baseSchema = base ?? createEmptyErdSchema({
5553
+ namespace,
5554
+ revision: "missing-base"
5555
+ });
5556
+ const headSchema = head ?? createEmptyErdSchema({
5557
+ namespace,
5558
+ revision: "missing-head"
5559
+ });
5560
+ const diff = buildErdSchemaDiff({
5561
+ base: baseSchema,
5562
+ head: headSchema
5563
+ });
5564
+ const viewerSchema = buildErdDiffViewerSchema({
5565
+ base: baseSchema,
5566
+ head: headSchema
5567
+ });
5568
+ writeFile(options.outputHtml, renderErdDiffHtml({
5569
+ schema: viewerSchema,
5570
+ currentSchema: headSchema,
5571
+ diff
5572
+ }));
5573
+ if (options.outputJson) writeFile(options.outputJson, `${JSON.stringify(diff, null, 2)}\n`);
5574
+ return {
5575
+ namespace,
5576
+ outputHtml: options.outputHtml,
5577
+ outputJson: options.outputJson,
5578
+ diff
5579
+ };
5580
+ }
5581
+ const erdDiffCommand = defineAppCommand({
5582
+ name: "diff",
5583
+ description: "Render TailorDB ERD schema diff HTML from exported ERD viewers.",
5584
+ args: z.strictObject({
5585
+ "base-html": arg(z.string().optional(), {
5586
+ description: "Base ERD viewer HTML file",
5587
+ completion: {
5588
+ type: "file",
5589
+ matcher: [".html"]
5590
+ }
5591
+ }),
5592
+ "head-html": arg(z.string().optional(), {
5593
+ description: "Head ERD viewer HTML file",
5594
+ completion: {
5595
+ type: "file",
5596
+ matcher: [".html"]
5597
+ }
5598
+ }),
5599
+ namespace: arg(z.string().optional(), {
5600
+ alias: "n",
5601
+ description: "TailorDB namespace name (defaults to the provided ERD schema namespace)"
5602
+ }),
5603
+ output: arg(z.string().min(1), {
5604
+ alias: "o",
5605
+ description: "Output ERD diff HTML file",
5606
+ completion: {
5607
+ type: "file",
5608
+ matcher: [".html"]
5609
+ }
5610
+ }),
5611
+ "output-json": arg(z.string().optional(), {
5612
+ description: "Optional output JSON file for the computed diff",
5613
+ completion: {
5614
+ type: "file",
5615
+ matcher: [".json"]
5616
+ }
5617
+ })
5618
+ }),
5619
+ run: (args) => {
5620
+ initErdCommand();
5621
+ const result = writeErdDiff({
5622
+ baseHtml: args["base-html"],
5623
+ headHtml: args["head-html"],
5624
+ namespace: args.namespace,
5625
+ outputHtml: args.output,
5626
+ outputJson: args["output-json"]
5627
+ });
5628
+ if (args.json) logger.out({
5629
+ namespace: result.namespace,
5630
+ outputHtml: result.outputHtml,
5631
+ outputJson: result.outputJson,
5632
+ summary: result.diff.summary
5633
+ });
5634
+ else logger.success(`Wrote ERD diff to ${path.relative(process.cwd(), result.outputHtml)}`);
5635
+ }
5636
+ });
5637
+
4112
5638
  //#endregion
4113
5639
  //#region src/cli/commands/tailordb/erd/serve.ts
4114
- const DEFAULT_ERD_BASE_DIR = ".tailor-sdk/erd";
5640
+ const DEFAULT_ERD_BASE_DIR = ".tailor/erd";
4115
5641
  const LOCAL_HOST = "127.0.0.1";
4116
5642
  const GLOB_CHARS = /[*?[\]{}()!+@]/;
4117
5643
  function formatServeCommand(namespace) {
4118
- return `tailor-sdk tailordb erd serve --namespace ${namespace}`;
5644
+ return `tailor tailordb erd serve --namespace ${namespace}`;
4119
5645
  }
4120
5646
  function getCacheControl(filePath) {
4121
5647
  return filePath.endsWith(".html") || filePath.endsWith(".json") ? "no-cache" : "public, max-age=3600";
@@ -4409,7 +5935,7 @@ async function waitForShutdown(server, watcher) {
4409
5935
  const erdServeCommand = defineAppCommand({
4410
5936
  name: "serve",
4411
5937
  description: "Generate and serve TailorDB ERD locally with watch reload. (beta)",
4412
- args: z.object({
5938
+ args: z.strictObject({
4413
5939
  ...configArg,
4414
5940
  namespace: arg(z.string().optional(), {
4415
5941
  alias: "n",
@@ -4417,7 +5943,7 @@ const erdServeCommand = defineAppCommand({
4417
5943
  }),
4418
5944
  port: arg(z.coerce.number().int().min(0).max(65535).default(0), { description: "Local server port (0 selects a free port)" }),
4419
5945
  open: arg(z.boolean().default(false), { description: "Open the ERD viewer in the default browser" })
4420
- }).strict(),
5946
+ }),
4421
5947
  run: async (args) => {
4422
5948
  initErdCommand();
4423
5949
  const outputDir = path.resolve(process.cwd(), DEFAULT_ERD_BASE_DIR);
@@ -4469,6 +5995,7 @@ const erdCommand = defineCommand({
4469
5995
  description: "Generate TailorDB ERD viewer artifacts from local TailorDB schema. (beta)",
4470
5996
  subCommands: {
4471
5997
  export: erdExportCommand,
5998
+ diff: erdDiffCommand,
4472
5999
  serve: erdServeCommand,
4473
6000
  deploy: erdDeployCommand
4474
6001
  }
@@ -4512,7 +6039,7 @@ async function script(options) {
4512
6039
  logger.info(` DB types: ${getMigrationFilePath(migrationsDir, migrationNumber, "db")}`);
4513
6040
  logger.newline();
4514
6041
  logger.log("Edit the script to implement your data migration logic.");
4515
- logger.log("It will be executed by 'tailor-sdk deploy' between Pre and Post phases.");
6042
+ logger.log("It will be executed by 'tailor deploy' between Pre and Post phases.");
4516
6043
  const editor = getConfiguredEditorCommand();
4517
6044
  if (!editor) return;
4518
6045
  logger.newline();
@@ -4537,7 +6064,7 @@ function resolveTargetNamespace(namespacesWithMigrations, requested) {
4537
6064
  const scriptCommand = defineAppCommand({
4538
6065
  name: "script",
4539
6066
  description: "Add a migration script (migrate.ts) template to an existing migration directory.",
4540
- args: z.object({
6067
+ args: z.strictObject({
4541
6068
  ...configArg,
4542
6069
  number: arg(z.string(), {
4543
6070
  positional: true,
@@ -4547,7 +6074,7 @@ const scriptCommand = defineAppCommand({
4547
6074
  alias: "n",
4548
6075
  description: "Target TailorDB namespace (required if multiple namespaces exist)"
4549
6076
  })
4550
- }).strict(),
6077
+ }),
4551
6078
  run: async (args) => {
4552
6079
  await script({
4553
6080
  configPath: args.config,
@@ -4633,7 +6160,7 @@ async function set(options) {
4633
6160
  const setCommand = defineAppCommand({
4634
6161
  name: "set",
4635
6162
  description: "Set migration checkpoint to a specific number.",
4636
- args: z.object({
6163
+ args: z.strictObject({
4637
6164
  ...deploymentArgs,
4638
6165
  ...confirmationArgs,
4639
6166
  number: arg(z.string(), {
@@ -4644,7 +6171,7 @@ const setCommand = defineAppCommand({
4644
6171
  alias: "n",
4645
6172
  description: "Target TailorDB namespace (required if multiple namespaces exist)"
4646
6173
  })
4647
- }).strict(),
6174
+ }),
4648
6175
  run: async (args) => {
4649
6176
  await assertWritable({ profile: args.profile });
4650
6177
  await set({
@@ -4732,13 +6259,13 @@ async function status(options) {
4732
6259
  const statusCommand = defineAppCommand({
4733
6260
  name: "status",
4734
6261
  description: "Show the current migration status for TailorDB namespaces, including applied and pending migrations.",
4735
- args: z.object({
6262
+ args: z.strictObject({
4736
6263
  ...deploymentArgs,
4737
6264
  namespace: arg(z.string().optional(), {
4738
6265
  alias: "n",
4739
6266
  description: "Target TailorDB namespace (shows all namespaces if not specified)"
4740
6267
  })
4741
- }).strict(),
6268
+ }),
4742
6269
  run: async (args) => {
4743
6270
  await status({
4744
6271
  configPath: args.config,
@@ -4753,19 +6280,14 @@ const statusCommand = defineAppCommand({
4753
6280
  //#endregion
4754
6281
  //#region src/cli/commands/tailordb/migrate/sync.ts
4755
6282
  async function fetchRemoteGqlPermissions(client, workspaceId, namespace) {
4756
- return fetchAll(async (pageToken, maxPageSize) => {
4757
- try {
4758
- const { permissions, nextPageToken } = await client.listTailorDBGQLPermissions({
4759
- workspaceId,
4760
- namespaceName: namespace,
4761
- pageToken,
4762
- pageSize: maxPageSize
4763
- });
4764
- return [permissions, nextPageToken];
4765
- } catch (error) {
4766
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
4767
- throw error;
4768
- }
6283
+ return fetchAllTolerant(async (pageToken, maxPageSize) => {
6284
+ const { permissions, nextPageToken } = await client.listTailorDBGQLPermissions({
6285
+ workspaceId,
6286
+ namespaceName: namespace,
6287
+ pageToken,
6288
+ pageSize: maxPageSize
6289
+ });
6290
+ return [permissions, nextPageToken];
4769
6291
  });
4770
6292
  }
4771
6293
  async function fetchRemoteTypes(client, workspaceId, namespace) {
@@ -4805,7 +6327,7 @@ async function fetchRemoteTypes(client, workspaceId, namespace) {
4805
6327
  async function assertMigrationsReproduceLocalTypes(loaded, target) {
4806
6328
  const { config, plugins } = loaded;
4807
6329
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
4808
- const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-Dtqap5jM.mjs");
6330
+ const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-D2hSK1ZL.mjs");
4809
6331
  const application = defineApplication({
4810
6332
  config,
4811
6333
  pluginManager
@@ -4817,7 +6339,7 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
4817
6339
  await service.processNamespacePlugins();
4818
6340
  }
4819
6341
  const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, application.tailorDBServices, config.path);
4820
- const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-DCqIWibD.mjs")).createExecutorService({ config: { files: [] } }) : void 0);
6342
+ const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-CkIwEctX.mjs")).createExecutorService({ config: { files: [] } }) : void 0);
4821
6343
  await executorService?.loadExecutors();
4822
6344
  if (pluginExecutorFiles.length > 0) await executorService?.loadPluginExecutorFiles([...pluginExecutorFiles]);
4823
6345
  const executorUsedTypes = /* @__PURE__ */ new Set();
@@ -4835,7 +6357,7 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
4835
6357
  logger.newline();
4836
6358
  logger.info("This usually means one of the following:");
4837
6359
  logger.info(" - Migration files were edited and replaying them no longer matches the type definitions — fix the migration files.", { mode: "plain" });
4838
- logger.info(" - Type definitions changed without a new migration — run 'tailor-sdk tailordb migration generate' first.", { mode: "plain" });
6360
+ logger.info(" - Type definitions changed without a new migration — run 'tailor tailordb migration generate' first.", { mode: "plain" });
4839
6361
  logger.newline();
4840
6362
  throw new Error("Refusing to sync: the migration history must reproduce the current local schema before it can be applied to the remote.");
4841
6363
  }
@@ -4852,21 +6374,15 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
4852
6374
  * @returns Existing labels and the parsed current migration number
4853
6375
  */
4854
6376
  async function fetchRemoteMigrationState(client, trn) {
4855
- try {
6377
+ const labels = (await getOrNull(async () => {
4856
6378
  const { metadata } = await client.getMetadata({ trn });
4857
- const labels = metadata?.labels ?? {};
4858
- const label = labels[MIGRATION_LABEL_KEY];
4859
- return {
4860
- labels,
4861
- current: label ? parseMigrationLabelNumber(label) : null
4862
- };
4863
- } catch (error) {
4864
- if (error instanceof ConnectError && error.code === Code.NotFound) return {
4865
- labels: {},
4866
- current: null
4867
- };
4868
- throw error;
4869
- }
6379
+ return metadata;
6380
+ }))?.labels ?? {};
6381
+ const label = labels[MIGRATION_LABEL_KEY];
6382
+ return {
6383
+ labels,
6384
+ current: label ? parseMigrationLabelNumber(label) : null
6385
+ };
4870
6386
  }
4871
6387
  function selectTargetNamespace(namespacesWithMigrations, requested) {
4872
6388
  if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
@@ -4977,7 +6493,7 @@ async function sync(options) {
4977
6493
  tailordbType
4978
6494
  }))]);
4979
6495
  } catch (error) {
4980
- handleOptionalToRequiredError(error, ["The target snapshot marks a field as required, but existing remote records have no value for it.", "Populate those records first (e.g. with a migration script applied via 'tailor-sdk deploy'), then re-run the sync."]);
6496
+ handleOptionalToRequiredError(error, ["The target snapshot marks a field as required, but existing remote records have no value for it.", "Populate those records first (e.g. with a migration script applied via 'tailor deploy'), then re-run the sync."]);
4981
6497
  }
4982
6498
  await Promise.all(desiredGqlPermissions.map(({ typeName, permission }) => {
4983
6499
  const request = {
@@ -5008,13 +6524,13 @@ async function sync(options) {
5008
6524
  logger.success(`Synced namespace ${styles.bold(target.namespace)} to migration ${styles.bold(formatMigrationNumber(targetVersion))}.`);
5009
6525
  if (targetVersion < latest) {
5010
6526
  logger.newline();
5011
- logger.info(`Run 'tailor-sdk deploy' to apply migrations ${formatMigrationNumber(targetVersion + 1)}–${formatMigrationNumber(latest)} from the working tree.`);
6527
+ logger.info(`Run 'tailor deploy' to apply migrations ${formatMigrationNumber(targetVersion + 1)}–${formatMigrationNumber(latest)} from the working tree.`);
5012
6528
  }
5013
6529
  }
5014
6530
  const syncCommand = defineAppCommand({
5015
6531
  name: "sync",
5016
6532
  description: "Sync remote TailorDB schema to a specific migration snapshot (recovery from --no-schema-check drift).",
5017
- args: z.object({
6533
+ args: z.strictObject({
5018
6534
  ...deploymentArgs,
5019
6535
  ...confirmationArgs,
5020
6536
  number: arg(z.string(), {
@@ -5025,7 +6541,7 @@ const syncCommand = defineAppCommand({
5025
6541
  alias: "n",
5026
6542
  description: "Target TailorDB namespace (required if multiple namespaces exist)"
5027
6543
  })
5028
- }).strict(),
6544
+ }),
5029
6545
  run: async (args) => {
5030
6546
  await assertWritable({ profile: args.profile });
5031
6547
  await sync({
@@ -5080,7 +6596,7 @@ const tailordbCommand = defineCommand({
5080
6596
  const upgradeCommand = defineAppCommand({
5081
6597
  name: "upgrade",
5082
6598
  description: "Run codemods to upgrade your project to a newer SDK version.",
5083
- args: z.object({
6599
+ args: z.strictObject({
5084
6600
  from: arg(z.string(), { description: "SDK version before the upgrade (e.g., 1.33.0)" }),
5085
6601
  "dry-run": arg(z.boolean().default(false), {
5086
6602
  alias: "d",
@@ -5090,11 +6606,11 @@ const upgradeCommand = defineAppCommand({
5090
6606
  description: "Project directory to upgrade",
5091
6607
  completion: { type: "directory" }
5092
6608
  })
5093
- }).strict(),
6609
+ }),
5094
6610
  run: async (args) => {
5095
- const { initTelemetry } = await import("../telemetry-ClwW5ohF.mjs");
6611
+ const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
5096
6612
  await initTelemetry();
5097
- const { upgrade } = await import("../service-DU1mVzri2.mjs");
6613
+ const { upgrade } = await import("../service-DeZeYa06.mjs");
5098
6614
  await upgrade({
5099
6615
  from: args.from,
5100
6616
  dryRun: args["dry-run"],
@@ -5108,32 +6624,59 @@ const upgradeCommand = defineAppCommand({
5108
6624
  const currentCommand = defineAppCommand({
5109
6625
  name: "current",
5110
6626
  description: "Show current user.",
5111
- args: z.object({}).strict(),
6627
+ args: z.strictObject({}),
5112
6628
  run: async () => {
5113
6629
  const config = await readPlatformConfig();
6630
+ const profile = process.env.TAILOR_PLATFORM_PROFILE;
6631
+ const profileEntry = profile ? config.profiles[profile] : void 0;
6632
+ if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
6633
+ const platformConfig = profileEntry ? platformConfigFromProfile(profileEntry) : void 0;
6634
+ const currentUser = profile ? profileEntry?.user ?? null : config.current_user;
5114
6635
  const jsonOutput = logger.jsonMode;
5115
- if (!config.current_user) throw new Error(multiline`
6636
+ if (!currentUser) throw new Error(multiline`
5116
6637
  Current user not set.
5117
- Please login first using 'tailor-sdk login' command to register a user.
6638
+ Please login first using 'tailor login' command to register a user.
5118
6639
  `);
5119
- if (!config.users[config.current_user]) throw new Error(multiline`
5120
- Current user '${config.current_user}' not found in registered users.
5121
- Please login again using 'tailor-sdk login' command to register the user.
6640
+ if (!hasUserTokenEntry(config, currentUser, platformConfig)) throw new Error(multiline`
6641
+ Current user '${currentUser}' not found in registered users.
6642
+ Please login again using 'tailor login' command to register the user.
5122
6643
  `);
5123
6644
  if (jsonOutput) {
5124
- logger.out({ user: config.current_user });
6645
+ logger.out({ user: currentUser });
5125
6646
  return;
5126
6647
  }
5127
- logger.out(config.current_user);
6648
+ logger.out(currentUser);
5128
6649
  }
5129
6650
  });
5130
6651
 
5131
6652
  //#endregion
5132
6653
  //#region src/cli/commands/user/list.ts
6654
+ function activeCurrentUserKey(config) {
6655
+ const activeProfile = process.env.TAILOR_PLATFORM_PROFILE;
6656
+ if (!activeProfile) {
6657
+ if (!config.current_user) return null;
6658
+ return resolveUserTokenKey(config, config.current_user);
6659
+ }
6660
+ const profile = config.profiles[activeProfile];
6661
+ if (!profile) return null;
6662
+ return resolveUserTokenKey(config, profile.user, platformConfigFromProfile(profile));
6663
+ }
6664
+ function toUserListInfo(userKey, currentUserKey) {
6665
+ const separatorIndex = userKey.indexOf("|");
6666
+ const platformUrl = separatorIndex === -1 ? null : userKey.slice(0, separatorIndex);
6667
+ return {
6668
+ user: separatorIndex === -1 ? userKey : userKey.slice(separatorIndex + 1),
6669
+ platformUrl,
6670
+ current: currentUserKey === userKey
6671
+ };
6672
+ }
6673
+ function formatUserListInfo(info) {
6674
+ return `${info.user}${info.platformUrl ? ` [${info.platformUrl}]` : ""}${info.current ? " (current)" : ""}`;
6675
+ }
5133
6676
  const listCommand$1 = defineAppCommand({
5134
6677
  name: "list",
5135
6678
  description: "List all users.",
5136
- args: z.object({}).strict(),
6679
+ args: z.strictObject({}),
5137
6680
  run: async () => {
5138
6681
  const config = await readPlatformConfig();
5139
6682
  const jsonOutput = logger.jsonMode;
@@ -5141,18 +6684,20 @@ const listCommand$1 = defineAppCommand({
5141
6684
  if (users.length === 0) {
5142
6685
  logger.info(multiline`
5143
6686
  No users found.
5144
- Please login first using 'tailor-sdk login' command to register a user.
6687
+ Please login first using 'tailor login' command to register a user.
5145
6688
  `);
5146
6689
  if (jsonOutput) logger.out([]);
5147
6690
  return;
5148
6691
  }
6692
+ const currentUserKey = activeCurrentUserKey(config);
6693
+ const userInfos = users.map((user) => toUserListInfo(user, currentUserKey));
5149
6694
  if (jsonOutput) {
5150
- logger.out(users);
6695
+ logger.out([...new Set(userInfos.map((userInfo) => userInfo.user))]);
5151
6696
  return;
5152
6697
  }
5153
- users.forEach((user) => {
5154
- if (user === config.current_user) logger.success(`${user} (current)`, { mode: "plain" });
5155
- else logger.log(user);
6698
+ userInfos.forEach((userInfo) => {
6699
+ if (userInfo.current) logger.success(formatUserListInfo(userInfo), { mode: "plain" });
6700
+ else logger.log(formatUserListInfo(userInfo));
5156
6701
  });
5157
6702
  }
5158
6703
  });
@@ -5213,12 +6758,31 @@ function printCreatedToken(name, token, write, action) {
5213
6758
  `);
5214
6759
  }
5215
6760
 
6761
+ //#endregion
6762
+ //#region src/cli/commands/user/pat/user.ts
6763
+ function resolvePatUser(config) {
6764
+ const activeProfile = process.env.TAILOR_PLATFORM_PROFILE;
6765
+ if (activeProfile) return config.profiles[activeProfile]?.user ?? null;
6766
+ return config.current_user;
6767
+ }
6768
+ async function createPatOperatorClient() {
6769
+ const config = await readPlatformConfig();
6770
+ const activeProfile = process.env.TAILOR_PLATFORM_PROFILE;
6771
+ const profileEntry = activeProfile ? config.profiles[activeProfile] : void 0;
6772
+ if (activeProfile && !profileEntry) throw new Error(`Profile "${activeProfile}" not found`);
6773
+ const platformConfig = profileEntry ? platformConfigFromProfile(profileEntry) : void 0;
6774
+ const user = resolvePatUser(config);
6775
+ if (!user) throw new Error("No user logged in.\nPlease login first using 'tailor login' command.");
6776
+ const { accessToken } = await fetchLatestToken(config, user, platformConfig);
6777
+ return await initOperatorClient(accessToken, platformConfig);
6778
+ }
6779
+
5216
6780
  //#endregion
5217
6781
  //#region src/cli/commands/user/pat/create.ts
5218
6782
  const createCommand = defineAppCommand({
5219
6783
  name: "create",
5220
6784
  description: "Create a new personal access token.",
5221
- args: z.object({
6785
+ args: z.strictObject({
5222
6786
  name: arg(z.string(), {
5223
6787
  positional: true,
5224
6788
  description: "Token name"
@@ -5227,16 +6791,10 @@ const createCommand = defineAppCommand({
5227
6791
  alias: "W",
5228
6792
  description: "Grant write permission (default: read-only)"
5229
6793
  })
5230
- }).strict(),
6794
+ }),
5231
6795
  run: async (args) => {
5232
6796
  await assertWritable();
5233
- const config = await readPlatformConfig();
5234
- if (!config.current_user) throw new Error(multiline`
5235
- No user logged in.
5236
- Please login first using 'tailor-sdk login' command.
5237
- `);
5238
- const { accessToken: token } = await fetchLatestToken(config, config.current_user);
5239
- const client = await initOperatorClient(token);
6797
+ const client = await createPatOperatorClient();
5240
6798
  const scopes = getScopesFromWriteFlag(args.write);
5241
6799
  const result = await client.createPersonalAccessToken({
5242
6800
  name: args.name,
@@ -5252,19 +6810,13 @@ const createCommand = defineAppCommand({
5252
6810
  const deleteCommand = defineAppCommand({
5253
6811
  name: "delete",
5254
6812
  description: "Delete a personal access token.",
5255
- args: z.object({ name: arg(z.string(), {
6813
+ args: z.strictObject({ name: arg(z.string(), {
5256
6814
  positional: true,
5257
6815
  description: "Token name"
5258
- }) }).strict(),
6816
+ }) }),
5259
6817
  run: async (args) => {
5260
6818
  await assertWritable();
5261
- const config = await readPlatformConfig();
5262
- if (!config.current_user) throw new Error(multiline`
5263
- No user logged in.
5264
- Please login first using 'tailor-sdk login' command.
5265
- `);
5266
- const { accessToken: token } = await fetchLatestToken(config, config.current_user);
5267
- await (await initOperatorClient(token)).deletePersonalAccessToken({ name: args.name });
6819
+ await (await createPatOperatorClient()).deletePersonalAccessToken({ name: args.name });
5268
6820
  logger.success(`Personal access token "${args.name}" deleted successfully.`);
5269
6821
  }
5270
6822
  });
@@ -5274,16 +6826,10 @@ const deleteCommand = defineAppCommand({
5274
6826
  const listCommand = defineAppCommand({
5275
6827
  name: "list",
5276
6828
  description: "List all personal access tokens.",
5277
- args: z.object({ ...paginationArgs() }).strict(),
6829
+ args: z.strictObject({ ...paginationArgs() }),
5278
6830
  run: async (args) => {
5279
6831
  const jsonOutput = logger.jsonMode;
5280
- const config = await readPlatformConfig();
5281
- if (!config.current_user) throw new Error(multiline`
5282
- No user logged in.
5283
- Please login first using 'tailor-sdk login' command.
5284
- `);
5285
- const { accessToken: token } = await fetchLatestToken(config, config.current_user);
5286
- const client = await initOperatorClient(token);
6832
+ const client = await createPatOperatorClient();
5287
6833
  const pageDirection = toPageDirection(args.order);
5288
6834
  const pats = await fetchPaged(async (pageToken, pageSize) => {
5289
6835
  const { personalAccessTokens, nextPageToken } = await client.listPersonalAccessTokens({
@@ -5296,7 +6842,7 @@ const listCommand = defineAppCommand({
5296
6842
  if (pats.length === 0) {
5297
6843
  logger.info(multiline`
5298
6844
  No personal access tokens found.
5299
- Please create a token using 'tailor-sdk user pat create' command.
6845
+ Please create a token using 'tailor user pat create' command.
5300
6846
  `);
5301
6847
  if (!jsonOutput) return;
5302
6848
  }
@@ -5319,7 +6865,7 @@ const listCommand = defineAppCommand({
5319
6865
  const updateCommand = defineAppCommand({
5320
6866
  name: "update",
5321
6867
  description: "Update a personal access token (delete and recreate).",
5322
- args: z.object({
6868
+ args: z.strictObject({
5323
6869
  name: arg(z.string(), {
5324
6870
  positional: true,
5325
6871
  description: "Token name"
@@ -5328,16 +6874,10 @@ const updateCommand = defineAppCommand({
5328
6874
  alias: "W",
5329
6875
  description: "Grant write permission (if not specified, keeps read-only)"
5330
6876
  })
5331
- }).strict(),
6877
+ }),
5332
6878
  run: async (args) => {
5333
6879
  await assertWritable();
5334
- const config = await readPlatformConfig();
5335
- if (!config.current_user) throw new Error(multiline`
5336
- No user logged in.
5337
- Please login first using 'tailor-sdk login' command.
5338
- `);
5339
- const { accessToken: token } = await fetchLatestToken(config, config.current_user);
5340
- const client = await initOperatorClient(token);
6880
+ const client = await createPatOperatorClient();
5341
6881
  await client.deletePersonalAccessToken({ name: args.name });
5342
6882
  const scopes = getScopesFromWriteFlag(args.write);
5343
6883
  const result = await client.createPersonalAccessToken({
@@ -5370,20 +6910,26 @@ const patCommand = defineCommand({
5370
6910
  const switchCommand = defineAppCommand({
5371
6911
  name: "switch",
5372
6912
  description: "Set current user.",
5373
- args: z.object({ user: arg(z.string(), {
6913
+ args: z.strictObject({ user: arg(z.string(), {
5374
6914
  positional: true,
5375
6915
  description: "User email address or machine user client ID"
5376
- }) }).strict(),
6916
+ }) }),
5377
6917
  run: async (args) => {
5378
6918
  const config = await readPlatformConfig();
5379
- const user = findConfigUserKey(config, args.user);
6919
+ const activeProfileName = process.env.TAILOR_PLATFORM_PROFILE;
6920
+ const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
6921
+ if (activeProfileName && !activeProfileEntry) throw new Error(`Profile "${activeProfileName}" not found`);
6922
+ const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
6923
+ if (args.user.includes("|")) throw new Error(`User "${args.user}" looks like a platform-scoped token key. Pass the user name without the platform URL and select the platform with TAILOR_PLATFORM_URL or a profile.`);
6924
+ const user = resolveConfigUser(config, args.user, platformConfig);
5380
6925
  if (!user) throw new Error(multiline`
5381
6926
  User "${args.user}" not found.
5382
- Please login first using 'tailor-sdk login' command to register this user.
6927
+ Please login first using 'tailor login' command to register this user.
5383
6928
  `);
5384
- config.current_user = user;
6929
+ if (activeProfileEntry) activeProfileEntry.user = user;
6930
+ else config.current_user = user;
5385
6931
  writePlatformConfig(config);
5386
- logger.success(`Current user set to "${args.user}" successfully.`);
6932
+ logger.success(`Current user set to "${user}" successfully.`);
5387
6933
  }
5388
6934
  });
5389
6935
 
@@ -5409,7 +6955,7 @@ const workflowCommand = defineCommand({
5409
6955
  name: "workflow",
5410
6956
  description: "Manage workflows and workflow executions.",
5411
6957
  subCommands: {
5412
- list: listCommand$12,
6958
+ list: listCommand$13,
5413
6959
  get: getCommand$6,
5414
6960
  start: startCommand,
5415
6961
  wait: waitCommand,
@@ -5417,7 +6963,7 @@ const workflowCommand = defineCommand({
5417
6963
  resume: resumeCommand
5418
6964
  },
5419
6965
  async run() {
5420
- await runCommand(listCommand$12, []);
6966
+ await runCommand(listCommand$13, []);
5421
6967
  }
5422
6968
  });
5423
6969
 
@@ -5428,10 +6974,10 @@ const appCommand = defineCommand({
5428
6974
  description: "Manage workspace applications",
5429
6975
  subCommands: {
5430
6976
  health: healthCommand,
5431
- list: listCommand$13
6977
+ list: listCommand$14
5432
6978
  },
5433
6979
  async run() {
5434
- await runCommand(listCommand$13, []);
6980
+ await runCommand(listCommand$14, []);
5435
6981
  }
5436
6982
  });
5437
6983
 
@@ -5442,12 +6988,12 @@ const userCommand = defineCommand({
5442
6988
  description: "Manage workspace users",
5443
6989
  subCommands: {
5444
6990
  invite: inviteCommand,
5445
- list: listCommand$14,
6991
+ list: listCommand$15,
5446
6992
  remove: removeCommand,
5447
6993
  update: updateCommand$4
5448
6994
  },
5449
6995
  async run() {
5450
- await runCommand(listCommand$14, []);
6996
+ await runCommand(listCommand$15, []);
5451
6997
  }
5452
6998
  });
5453
6999
 
@@ -5459,31 +7005,82 @@ const workspaceCommand = defineCommand({
5459
7005
  subCommands: {
5460
7006
  app: appCommand,
5461
7007
  create: createCommand$4,
5462
- delete: deleteCommand$4,
7008
+ delete: deleteCommand$5,
5463
7009
  get: getCommand$7,
5464
- list: listCommand$15,
7010
+ list: listCommand$16,
5465
7011
  restore: restoreCommand,
5466
7012
  user: userCommand
5467
7013
  },
5468
7014
  async run() {
5469
- await runCommand(listCommand$15, []);
7015
+ await runCommand(listCommand$16, []);
5470
7016
  }
5471
7017
  });
5472
7018
 
5473
7019
  //#endregion
5474
7020
  //#region src/cli/index.ts
5475
- if (!isNativeTypeScriptRuntime()) {
5476
- const { register } = await import("tsx/esm/api");
5477
- register();
5478
- }
7021
+ await registerTsHook(new URL("./ts-hook.mjs", import.meta.url));
5479
7022
  initCrashReporting();
5480
7023
  const packageJson = await readPackageJson();
5481
- const cliName = Object.keys(packageJson.bin ?? {})[0] || "tailor-sdk";
5482
- const mainCommand = withCompletionCommand(defineCommand({
7024
+ const cliName = Object.keys(packageJson.bin ?? {})[0] || "tailor";
7025
+ const packageName = packageJson.name ?? "@tailor-platform/sdk";
7026
+ const bundledSkillsDir = resolve(dirname(await resolvePackageJSON(import.meta.url)), "agent-skills");
7027
+ function replaceCommandArgs(command, removals = []) {
7028
+ const args = command.args;
7029
+ if (!args?.shape) return command;
7030
+ const shape = { ...args.shape };
7031
+ for (const name of removals) delete shape[name];
7032
+ return {
7033
+ ...command,
7034
+ args: z.strictObject({ ...shape })
7035
+ };
7036
+ }
7037
+ function removeCommandAliases(command) {
7038
+ const next = { ...command };
7039
+ delete next.aliases;
7040
+ return next;
7041
+ }
7042
+ function alignSkillCommand(command) {
7043
+ const subCommands = command.subCommands ?? {};
7044
+ const add = {
7045
+ ...removeCommandAliases(replaceCommandArgs(subCommands.add, ["verbose"])),
7046
+ description: "Install Tailor SDK agent skills."
7047
+ };
7048
+ const list = {
7049
+ ...replaceCommandArgs(subCommands.list, ["json"]),
7050
+ description: "List Tailor SDK agent skills."
7051
+ };
7052
+ const remove = {
7053
+ ...removeCommandAliases(replaceCommandArgs(subCommands.remove)),
7054
+ description: "Remove installed Tailor SDK agent skills."
7055
+ };
7056
+ const sync = {
7057
+ ...replaceCommandArgs(subCommands.sync, ["verbose"]),
7058
+ description: "Remove and reinstall Tailor SDK agent skills."
7059
+ };
7060
+ return {
7061
+ ...command,
7062
+ description: "Manage Tailor SDK agent skills.",
7063
+ async run() {
7064
+ const result = await runCommand(add, []);
7065
+ if (!result.success) throw result.error;
7066
+ },
7067
+ subCommands: {
7068
+ ...subCommands,
7069
+ add,
7070
+ list,
7071
+ remove,
7072
+ sync
7073
+ }
7074
+ };
7075
+ }
7076
+ const commandWithSkills = withSkillCommand(defineCommand({
5483
7077
  name: cliName,
5484
7078
  description: packageJson.description || "Tailor CLI for managing Tailor Platform SDK applications",
7079
+ notes: `CLI plugins (beta): an unknown subcommand is dispatched to an external plugin executable named \`${cliName}-<name>\` (found on your PATH or in node_modules/.bin), similar to \`gh\` extensions.
7080
+ Run \`${cliName} plugin list\` to see which plugins are installed and where they resolve from.`,
5485
7081
  subCommands: {
5486
7082
  api: apiCommand,
7083
+ auth: authCommand,
5487
7084
  authconnection: authconnectionCommand,
5488
7085
  crashreport: crashReportCommand,
5489
7086
  deploy: deployCommand$1,
@@ -5497,13 +7094,13 @@ const mainCommand = withCompletionCommand(defineCommand({
5497
7094
  oauth2client: oauth2clientCommand,
5498
7095
  open: openCommand,
5499
7096
  organization: organizationCommand,
7097
+ plugin: pluginCommand,
5500
7098
  profile: profileCommand,
5501
7099
  query: queryCommand,
5502
7100
  remove: removeCommand$1,
5503
7101
  secret: secretCommand,
5504
7102
  setup: setupCommand,
5505
7103
  show: showCommand,
5506
- skills: skillsCommand,
5507
7104
  staticwebsite: staticwebsiteCommand,
5508
7105
  tailordb: tailordbCommand,
5509
7106
  upgrade: upgradeCommand,
@@ -5511,11 +7108,31 @@ const mainCommand = withCompletionCommand(defineCommand({
5511
7108
  workflow: workflowCommand,
5512
7109
  workspace: workspaceCommand
5513
7110
  }
5514
- }));
7111
+ }), {
7112
+ sourceDir: bundledSkillsDir,
7113
+ package: packageName,
7114
+ mode: "copy",
7115
+ descriptionAppend: false
7116
+ });
7117
+ const commandWithSkillsSubCommands = commandWithSkills.subCommands ?? {};
7118
+ const mainCommand = withCompletionCommand({
7119
+ ...commandWithSkills,
7120
+ subCommands: {
7121
+ ...commandWithSkillsSubCommands,
7122
+ skills: alignSkillCommand(commandWithSkillsSubCommands.skills)
7123
+ }
7124
+ });
5515
7125
  runMain(mainCommand, {
5516
7126
  version: packageJson.version,
5517
7127
  globalArgs: z.object(commonArgs),
5518
7128
  displayErrors: false,
7129
+ onUnknownSubcommand: ({ commandPath, name, args }) => dispatchPlugin({
7130
+ commandPath,
7131
+ name,
7132
+ args,
7133
+ cliName,
7134
+ profile: process.env.TAILOR_PLATFORM_PROFILE
7135
+ }),
5519
7136
  cleanup: async ({ error }) => {
5520
7137
  if (error) {
5521
7138
  if (isCLIError(error)) {
@@ -5526,11 +7143,11 @@ runMain(mainCommand, {
5526
7143
  if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
5527
7144
  } else logger.error(`Unknown error: ${error}`);
5528
7145
  if (!isCLIError(error) && (!(error instanceof Error) || error instanceof TypeError || error instanceof RangeError)) {
5529
- const { reportCrash } = await import("../crashreport-BMWcxeSE.mjs");
7146
+ const { reportCrash } = await import("../crashreport-D3RjuSYb.mjs");
5530
7147
  await reportCrash(error, "handledError");
5531
7148
  }
5532
7149
  }
5533
- const { shutdownTelemetry } = await import("../telemetry-ClwW5ohF.mjs");
7150
+ const { shutdownTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
5534
7151
  await shutdownTelemetry();
5535
7152
  }
5536
7153
  });