@tailor-platform/sdk 2.0.0-next.4 → 2.0.0-next.6

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 (321) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/dist/{application-BX13RArL.mjs → application-BJXRpQj5.mjs} +602 -1842
  3. package/dist/application-BJXRpQj5.mjs.map +1 -0
  4. package/dist/application-BV-AXawv.mjs +3 -0
  5. package/dist/cli/cache/bundle-cache.d.mts +2 -1
  6. package/dist/cli/cache/store.d.mts +1 -1
  7. package/dist/cli/commands/api/index.d.mts +1 -0
  8. package/dist/cli/commands/deploy/aigateway.d.mts +5 -1
  9. package/dist/cli/commands/deploy/application.d.mts +7 -1
  10. package/dist/cli/commands/deploy/apply-phases.d.mts +14 -1
  11. package/dist/cli/commands/deploy/auth.d.mts +5 -1
  12. package/dist/cli/commands/deploy/deploy.d.mts +13 -3
  13. package/dist/cli/commands/deploy/executor.d.mts +7 -1
  14. package/dist/cli/commands/deploy/function-registry.d.mts +5 -0
  15. package/dist/cli/commands/deploy/idp.d.mts +5 -1
  16. package/dist/cli/commands/deploy/resolver.d.mts +5 -1
  17. package/dist/cli/commands/deploy/secret-manager.d.mts +5 -1
  18. package/dist/cli/commands/deploy/staticwebsite.d.mts +5 -1
  19. package/dist/cli/commands/deploy/tailordb/index.d.mts +8 -1
  20. package/dist/cli/commands/deploy/types.d.mts +3 -1
  21. package/dist/cli/commands/deploy/workflow-execution-policy.d.mts +3 -1
  22. package/dist/cli/commands/deploy/workflow.d.mts +4 -1
  23. package/dist/cli/commands/executor/get.d.mts +0 -1
  24. package/dist/cli/commands/executor/jobs.d.mts +0 -1
  25. package/dist/cli/commands/executor/list.d.mts +0 -1
  26. package/dist/cli/commands/executor/transform.d.mts +1 -0
  27. package/dist/cli/commands/executor/trigger.d.mts +0 -1
  28. package/dist/cli/commands/executor/webhook.d.mts +0 -1
  29. package/dist/cli/commands/function/get.d.mts +0 -1
  30. package/dist/cli/commands/function/list.d.mts +0 -1
  31. package/dist/cli/commands/function/transform.d.mts +1 -0
  32. package/dist/cli/commands/generate/options.d.mts +0 -1
  33. package/dist/cli/commands/generate/service.d.mts +6 -2
  34. package/dist/cli/commands/machineuser/list.d.mts +0 -1
  35. package/dist/cli/commands/machineuser/token.d.mts +0 -1
  36. package/dist/cli/commands/oauth2client/get.d.mts +0 -1
  37. package/dist/cli/commands/oauth2client/list.d.mts +0 -1
  38. package/dist/cli/commands/oauth2client/transform.d.mts +1 -0
  39. package/dist/cli/commands/organization/folder/create.d.mts +0 -1
  40. package/dist/cli/commands/organization/folder/delete.d.mts +0 -1
  41. package/dist/cli/commands/organization/folder/get.d.mts +0 -1
  42. package/dist/cli/commands/organization/folder/list.d.mts +0 -1
  43. package/dist/cli/commands/organization/folder/update.d.mts +0 -1
  44. package/dist/cli/commands/organization/get.d.mts +0 -1
  45. package/dist/cli/commands/organization/list.d.mts +0 -1
  46. package/dist/cli/commands/organization/transform.d.mts +2 -0
  47. package/dist/cli/commands/organization/tree.d.mts +0 -1
  48. package/dist/cli/commands/organization/update.d.mts +0 -1
  49. package/dist/cli/commands/remove.d.mts +0 -1
  50. package/dist/cli/commands/show.d.mts +9 -4
  51. package/dist/cli/commands/staticwebsite/deploy.d.mts +19 -0
  52. package/dist/cli/commands/tailordb/migrate/config.d.mts +0 -1
  53. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +16 -3
  54. package/dist/cli/commands/tailordb/migrate/generate.d.mts +0 -1
  55. package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +6 -0
  56. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +1 -1
  57. package/dist/cli/commands/tailordb/truncate.d.mts +0 -1
  58. package/dist/cli/commands/workflow/executions.d.mts +0 -1
  59. package/dist/cli/commands/workflow/get.d.mts +2 -1
  60. package/dist/cli/commands/workflow/list.d.mts +0 -1
  61. package/dist/cli/commands/workflow/resume.d.mts +0 -1
  62. package/dist/cli/commands/workflow/start.d.mts +0 -1
  63. package/dist/cli/commands/workflow/status.d.mts +1 -0
  64. package/dist/cli/commands/workflow/transform.d.mts +1 -0
  65. package/dist/cli/commands/workflow/wait.d.mts +0 -1
  66. package/dist/cli/commands/workflow/waiter.d.mts +1 -1
  67. package/dist/cli/commands/workspace/app/health.d.mts +0 -1
  68. package/dist/cli/commands/workspace/app/list.d.mts +0 -1
  69. package/dist/cli/commands/workspace/app/transform.d.mts +2 -0
  70. package/dist/cli/commands/workspace/create.d.mts +3 -1
  71. package/dist/cli/commands/workspace/delete.d.mts +0 -1
  72. package/dist/cli/commands/workspace/get.d.mts +0 -1
  73. package/dist/cli/commands/workspace/list.d.mts +1 -1
  74. package/dist/cli/commands/workspace/restore.d.mts +0 -1
  75. package/dist/cli/commands/workspace/transform.d.mts +4 -0
  76. package/dist/cli/commands/workspace/user/invite.d.mts +0 -1
  77. package/dist/cli/commands/workspace/user/list.d.mts +0 -1
  78. package/dist/cli/commands/workspace/user/remove.d.mts +0 -1
  79. package/dist/cli/commands/workspace/user/transform.d.mts +1 -0
  80. package/dist/cli/commands/workspace/user/update.d.mts +0 -1
  81. package/dist/cli/index.d.mts +6 -3
  82. package/dist/cli/index.mjs +871 -1577
  83. package/dist/cli/index.mjs.map +1 -1
  84. package/dist/cli/lib.d.mts +10 -4
  85. package/dist/cli/lib.mjs +50 -4
  86. package/dist/cli/lib.mjs.map +1 -1
  87. package/dist/cli/query/index.d.mts +0 -1
  88. package/dist/cli/services/application.d.mts +12 -1
  89. package/dist/cli/services/auth/service.d.mts +2 -0
  90. package/dist/cli/services/executor/service.d.mts +1 -1
  91. package/dist/cli/services/http-adapter/bundler.d.mts +3 -1
  92. package/dist/cli/services/http-adapter/service.d.mts +2 -1
  93. package/dist/cli/services/resolver/service.d.mts +1 -1
  94. package/dist/cli/services/tailordb/service.d.mts +3 -1
  95. package/dist/cli/services/workflow/bundler.d.mts +2 -1
  96. package/dist/cli/services/workflow/service.d.mts +1 -1
  97. package/dist/cli/shared/args.d.mts +1 -1
  98. package/dist/cli/shared/client.d.mts +1 -1
  99. package/dist/cli/shared/config-loader.d.mts +1 -2
  100. package/dist/cli/shared/context.d.mts +2 -1
  101. package/dist/cli/shared/readonly-guard.d.mts +25 -0
  102. package/dist/cli/shared/script-executor.d.mts +0 -1
  103. package/dist/cli/shared/seed-chunker.d.mts +7 -3
  104. package/dist/cli/shared/tailordb-namespaces.d.mts +37 -0
  105. package/dist/cli/shared/type-generator.d.mts +0 -1
  106. package/dist/cli/shared/workspace-name.d.mts +1 -0
  107. package/dist/completion/zsh-worker.zsh +15 -303
  108. package/dist/configure/config/index.d.mts +0 -1
  109. package/dist/configure/config/types.d.mts +2 -2
  110. package/dist/configure/index.d.mts +14 -4
  111. package/dist/configure/index.mjs +201 -6
  112. package/dist/configure/index.mjs.map +1 -1
  113. package/dist/configure/services/aigateway/index.d.mts +0 -1
  114. package/dist/configure/services/aigateway/types.d.mts +0 -1
  115. package/dist/configure/services/auth/index.d.mts +0 -1
  116. package/dist/configure/services/auth/types.d.mts +15 -14
  117. package/dist/configure/services/executor/executor.d.mts +2 -13
  118. package/dist/configure/services/executor/index.d.mts +1 -0
  119. package/dist/configure/services/executor/operation.d.mts +9 -4
  120. package/dist/configure/services/executor/trigger/event.d.mts +0 -1
  121. package/dist/configure/services/executor/trigger/index.d.mts +0 -1
  122. package/dist/configure/services/executor/trigger/schedule.d.mts +0 -1
  123. package/dist/configure/services/executor/trigger/webhook.d.mts +0 -1
  124. package/dist/configure/services/http-adapter/http-adapter.d.mts +1 -2
  125. package/dist/configure/services/idp/index.d.mts +1 -5
  126. package/dist/configure/services/idp/permission.d.mts +4 -5
  127. package/dist/configure/services/idp/types.d.mts +0 -1
  128. package/dist/configure/services/index.d.mts +5 -0
  129. package/dist/configure/services/resolver/resolver.d.mts +1 -0
  130. package/dist/configure/services/resolver/types.d.mts +0 -1
  131. package/dist/configure/services/secrets/index.d.mts +0 -1
  132. package/dist/configure/services/secrets/types.d.mts +0 -1
  133. package/dist/configure/services/staticwebsite/index.d.mts +0 -1
  134. package/dist/configure/services/staticwebsite/types.d.mts +0 -1
  135. package/dist/configure/services/tailordb/permission.d.mts +4 -5
  136. package/dist/configure/services/tailordb/schema.d.mts +86 -56
  137. package/dist/configure/services/tailordb/types.d.mts +66 -17
  138. package/dist/configure/services/workflow/execution-policy.d.mts +2 -3
  139. package/dist/configure/services/workflow/execution-policy.types.d.mts +0 -1
  140. package/dist/configure/services/workflow/index.d.mts +1 -0
  141. package/dist/configure/services/workflow/job.d.mts +0 -1
  142. package/dist/configure/services/workflow/wait-point.d.mts +0 -1
  143. package/dist/configure/services/workflow/workflow.d.mts +1 -1
  144. package/dist/configure/types/field.d.mts +1 -2
  145. package/dist/configure/types/field.types.d.mts +6 -45
  146. package/dist/configure/types/index.d.mts +1 -0
  147. package/dist/configure/types/type.d.mts +2 -11
  148. package/dist/{crashreport-D3RjuSYb.mjs → crashreport-0ioNRZ4K.mjs} +1 -1
  149. package/dist/{crashreport-Cg-pqRJV.mjs → crashreport-CxHne11E.mjs} +4 -3
  150. package/dist/{crashreport-Cg-pqRJV.mjs.map → crashreport-CxHne11E.mjs.map} +1 -1
  151. package/dist/errors-DfnF4ibq.mjs +100 -0
  152. package/dist/errors-DfnF4ibq.mjs.map +1 -0
  153. package/dist/{globals-BjhJJ2dN.mjs → globals-D-YbJKW-.mjs} +16 -10
  154. package/dist/globals-D-YbJKW-.mjs.map +1 -0
  155. package/dist/kysely/index.d.mts +3 -4
  156. package/dist/{kysely-type-D-zflMGf.mjs → kysely-type-DqIRgDYR.mjs} +4 -3
  157. package/dist/kysely-type-DqIRgDYR.mjs.map +1 -0
  158. package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
  159. package/dist/logger-BwS4ppwO.mjs.map +1 -0
  160. package/dist/parser/service/auth/index.d.mts +1 -1
  161. package/dist/parser/service/auth/schema.d.mts +2 -0
  162. package/dist/parser/service/http-adapter/index.d.mts +1 -1
  163. package/dist/parser/service/idp/types.d.mts +1 -0
  164. package/dist/parser/service/tailordb/type-source.d.mts +10 -0
  165. package/dist/parser/service/tailordb/types.d.mts +6 -1
  166. package/dist/plugin/builtin/enum-constants/index.d.mts +0 -1
  167. package/dist/plugin/builtin/file-utils/index.d.mts +0 -1
  168. package/dist/plugin/builtin/kysely-type/index.d.mts +0 -1
  169. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  170. package/dist/plugin/builtin/seed/index.d.mts +0 -1
  171. package/dist/plugin/get-generated-type.d.mts +0 -1
  172. package/dist/plugin/manager.d.mts +2 -1
  173. package/dist/plugin/types.d.mts +0 -1
  174. package/dist/plugin/with-context.d.mts +0 -1
  175. package/dist/{register-ts-hook-BM1caLT0.mjs → register-ts-hook-DL31O2W9.mjs} +3076 -1744
  176. package/dist/register-ts-hook-DL31O2W9.mjs.map +1 -0
  177. package/dist/{registry-BSbq6SPo.mjs → registry-CC3CbQiF.mjs} +3 -3
  178. package/dist/registry-CC3CbQiF.mjs.map +1 -0
  179. package/dist/runtime/aigateway.d.mts +0 -1
  180. package/dist/runtime/authconnection.d.mts +0 -1
  181. package/dist/runtime/context.d.mts +0 -1
  182. package/dist/runtime/field-parse.d.mts +10 -0
  183. package/dist/runtime/globals.d.mts +5 -3
  184. package/dist/runtime/index.d.mts +0 -1
  185. package/dist/runtime/index.mjs +1 -1
  186. package/dist/runtime/types.d.mts +9 -4
  187. package/dist/runtime/workflow.d.mts +70 -10
  188. package/dist/runtime/workflow.mjs +1 -1
  189. package/dist/{schema-C4LYbXVG.mjs → schema--xYWRGfe.mjs} +272 -51
  190. package/dist/schema--xYWRGfe.mjs.map +1 -0
  191. package/dist/{secret-file-aoNXJmKa.mjs → secret-file-C3J4Gy8r.mjs} +2 -27
  192. package/dist/secret-file-C3J4Gy8r.mjs.map +1 -0
  193. package/dist/{secretmanager-Bd45j7an.mjs → secretmanager-IY4UvinW.mjs} +42 -9
  194. package/dist/secretmanager-IY4UvinW.mjs.map +1 -0
  195. package/dist/seed/index.d.mts +3 -2
  196. package/dist/{service-ufH7sS8o.mjs → service-CnHz9rwz.mjs} +47 -20
  197. package/dist/service-CnHz9rwz.mjs.map +1 -0
  198. package/dist/{service-DeZeYa06.mjs → service-DaeJlBoH.mjs} +3 -3
  199. package/dist/{service-DeZeYa06.mjs.map → service-DaeJlBoH.mjs.map} +1 -1
  200. package/dist/service-nU6ITOHL.mjs +3 -0
  201. package/dist/service_pb-BoLM0X4K.mjs +167 -0
  202. package/dist/service_pb-BoLM0X4K.mjs.map +1 -0
  203. package/dist/{service_pb-B2FF59sP.mjs → service_pb-r1T7kpJc.mjs} +1 -1
  204. package/dist/tailor-proto/src/tailor/v1/aigateway_pb.d.mts +0 -1
  205. package/dist/tailor-proto/src/tailor/v1/aigateway_resource_pb.d.mts +0 -1
  206. package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +0 -1
  207. package/dist/tailor-proto/src/tailor/v1/application_resource_pb.d.mts +0 -1
  208. package/dist/tailor-proto/src/tailor/v1/auth_pb.d.mts +0 -1
  209. package/dist/tailor-proto/src/tailor/v1/auth_resource_pb.d.mts +0 -1
  210. package/dist/tailor-proto/src/tailor/v1/events_pb.d.mts +0 -1
  211. package/dist/tailor-proto/src/tailor/v1/events_resource_pb.d.mts +0 -1
  212. package/dist/tailor-proto/src/tailor/v1/executor_pb.d.mts +0 -1
  213. package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +0 -1
  214. package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +0 -1
  215. package/dist/tailor-proto/src/tailor/v1/function_registry_pb.d.mts +0 -1
  216. package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +0 -1
  217. package/dist/tailor-proto/src/tailor/v1/http_adapter_resource_pb.d.mts +0 -1
  218. package/dist/tailor-proto/src/tailor/v1/idp_pb.d.mts +0 -1
  219. package/dist/tailor-proto/src/tailor/v1/idp_resource_pb.d.mts +0 -1
  220. package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +0 -1
  221. package/dist/tailor-proto/src/tailor/v1/metadata_resource_pb.d.mts +0 -1
  222. package/dist/tailor-proto/src/tailor/v1/meter_pb.d.mts +0 -1
  223. package/dist/tailor-proto/src/tailor/v1/pipeline_pb.d.mts +0 -1
  224. package/dist/tailor-proto/src/tailor/v1/pipeline_resource_pb.d.mts +0 -1
  225. package/dist/tailor-proto/src/tailor/v1/resource_pb.d.mts +0 -1
  226. package/dist/tailor-proto/src/tailor/v1/secret_manager_pb.d.mts +0 -1
  227. package/dist/tailor-proto/src/tailor/v1/secret_manager_resource_pb.d.mts +0 -1
  228. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +0 -1
  229. package/dist/tailor-proto/src/tailor/v1/stateflow_pb.d.mts +0 -1
  230. package/dist/tailor-proto/src/tailor/v1/stateflow_resource_pb.d.mts +0 -1
  231. package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +0 -1
  232. package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +0 -1
  233. package/dist/tailor-proto/src/tailor/v1/tailordb_pb.d.mts +0 -1
  234. package/dist/tailor-proto/src/tailor/v1/tailordb_resource_pb.d.mts +0 -1
  235. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_pb.d.mts +0 -1
  236. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +0 -1
  237. package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +0 -1
  238. package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +0 -1
  239. package/dist/tailor-proto/src/tailor/v1/workspace_pb.d.mts +0 -1
  240. package/dist/tailor-proto/src/tailor/v1/workspace_resource_pb.d.mts +0 -1
  241. package/dist/types/aigateway.generated.d.mts +5 -2
  242. package/dist/types/auth-connection.generated.d.mts +24 -11
  243. package/dist/types/auth.generated.d.mts +115 -84
  244. package/dist/types/executor.generated.d.mts +133 -123
  245. package/dist/types/helpers.d.mts +7 -6
  246. package/dist/types/http-adapter.generated.d.mts +11 -5
  247. package/dist/types/idp.generated.d.mts +536 -524
  248. package/dist/types/resolver.generated.d.mts +26 -11
  249. package/dist/types/staticwebsite.generated.d.mts +7 -3
  250. package/dist/types/tailordb.generated.d.mts +57 -29
  251. package/dist/types/workflow.generated.d.mts +9 -4
  252. package/dist/user-agent-Bgsszb5I.mjs +28 -0
  253. package/dist/user-agent-Bgsszb5I.mjs.map +1 -0
  254. package/dist/utils/test/index.d.mts +1 -2
  255. package/dist/utils/test/index.mjs +36 -10
  256. package/dist/utils/test/index.mjs.map +1 -1
  257. package/dist/utils/test/mock.d.mts +0 -1
  258. package/dist/vitest/environment.mjs +1 -1
  259. package/dist/vitest/index.d.mts +9 -9
  260. package/dist/vitest/index.mjs +591 -173
  261. package/dist/vitest/index.mjs.map +1 -1
  262. package/dist/vitest/mock-kysely.d.mts +0 -1
  263. package/dist/vitest/mock.d.mts +8 -8
  264. package/dist/vitest/mocks/aigateway.d.mts +16 -6
  265. package/dist/vitest/mocks/authconnection.d.mts +16 -6
  266. package/dist/vitest/mocks/file.d.mts +23 -6
  267. package/dist/vitest/mocks/iconv.d.mts +28 -4
  268. package/dist/vitest/mocks/idp.d.mts +26 -10
  269. package/dist/vitest/mocks/secretmanager.d.mts +16 -5
  270. package/dist/vitest/mocks/tailordb.d.mts +54 -10
  271. package/dist/vitest/mocks/workflow.d.mts +72 -21
  272. package/dist/vitest/setup.mjs +2 -2
  273. package/dist/vitest/workflow-local.d.mts +1 -0
  274. package/dist/workflow-9kHGKxF2.mjs +64 -0
  275. package/dist/workflow-9kHGKxF2.mjs.map +1 -0
  276. package/dist/{service_pb-B5w9rjPY.mjs → workspace_resource_pb-Db3fv68L.mjs} +57 -165
  277. package/dist/workspace_resource_pb-Db3fv68L.mjs.map +1 -0
  278. package/docs/cli/application.md +61 -15
  279. package/docs/cli/setup.md +1 -1
  280. package/docs/cli/tailordb.md +8 -140
  281. package/docs/cli/workspace.md +6 -4
  282. package/docs/cli-reference.md +18 -14
  283. package/docs/github-actions.md +8 -0
  284. package/docs/migration/v2.md +247 -0
  285. package/docs/runtime.md +5 -6
  286. package/docs/services/aigateway.md +2 -0
  287. package/docs/services/executor.md +5 -0
  288. package/docs/services/tailordb.md +144 -68
  289. package/docs/services/workflow.md +3 -3
  290. package/docs/testing.md +105 -89
  291. package/package.json +22 -25
  292. package/dist/application-BO4ZF8wP.mjs +0 -3
  293. package/dist/application-BX13RArL.mjs.map +0 -1
  294. package/dist/cli/erd-viewer-assets/app.js +0 -1424
  295. package/dist/cli/erd-viewer-assets/index.html +0 -77
  296. package/dist/cli/erd-viewer-assets/serve.json +0 -13
  297. package/dist/cli/erd-viewer-assets/styles.css +0 -1036
  298. package/dist/cli/shared/trigger-context.d.mts +0 -1
  299. package/dist/configure/types/field-runtime.d.mts +0 -17
  300. package/dist/errors-118wUDRr.mjs +0 -42
  301. package/dist/errors-118wUDRr.mjs.map +0 -1
  302. package/dist/field-runtime-CAnh_oow.mjs +0 -183
  303. package/dist/field-runtime-CAnh_oow.mjs.map +0 -1
  304. package/dist/globals-BjhJJ2dN.mjs.map +0 -1
  305. package/dist/kysely-type-D-zflMGf.mjs.map +0 -1
  306. package/dist/logger-BEiZZ3qT.mjs.map +0 -1
  307. package/dist/register-ts-hook-BM1caLT0.mjs.map +0 -1
  308. package/dist/registry-BSbq6SPo.mjs.map +0 -1
  309. package/dist/resource_pb-BRv7AMXK.mjs +0 -49
  310. package/dist/resource_pb-BRv7AMXK.mjs.map +0 -1
  311. package/dist/schema-C4LYbXVG.mjs.map +0 -1
  312. package/dist/secret-file-aoNXJmKa.mjs.map +0 -1
  313. package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
  314. package/dist/service-CkIwEctX.mjs +0 -3
  315. package/dist/service-ufH7sS8o.mjs.map +0 -1
  316. package/dist/service_pb-B5w9rjPY.mjs.map +0 -1
  317. package/dist/types-C-lQ4se3.mjs +0 -4
  318. package/dist/types-CXF2OYdR.mjs +0 -199
  319. package/dist/types-CXF2OYdR.mjs.map +0 -1
  320. package/dist/workflow-GNuSnsTx.mjs +0 -22
  321. package/dist/workflow-GNuSnsTx.mjs.map +0 -1
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import { r as parseInputFields } from "../schema--xYWRGfe.mjs";
3
+ import { St as AuthInvokerSchema, T as CustomDomainStatus, U as FunctionExecution_Type, kt as PATScope } from "../workspace_resource_pb-Db3fv68L.mjs";
2
4
  import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
3
- import { n as logger, r as styles } from "../logger-BEiZZ3qT.mjs";
4
- import { $ as fetchUserInfo, A as hasAnyUserTokenEntry, B as readPlatformConfig, D as loadConfig, Et as workspaceArgs, F as loadMachineUserName, G as writePlatformConfig, H as resolveConfigUser, I as loadPlatformClientConfig, J as fetchAll, K as closeConnectionPool, L as loadStoredUserTokens, M as loadAccessToken, N as loadConfigPath, O as deleteUserTokens, P as loadConsoleBaseUrl, Q as fetchPlatformMachineUserToken, R as loadWorkspaceId, T as hashContent$1, Tt as toPageDirection, U as resolveUserTokenKey, V as removeLegacyUserAlias, W as saveUserTokens, Y as fetchAllTolerant, Z as fetchPaged, _ as platformBundleDefinePlugin, a as resolveInlineSourcemap, at as initOperatorClient, b as resolveBundleLogLevel, bt as pagedLogArgs, c as WorkflowJobSchema, d as INVOKER_EXPR, dt as confirmationArgs, ft as deploymentArgs, gt as multiConfigArg, ht as isVerbose, it as initOAuth2Client, j as hasUserTokenEntry, k as fetchLatestToken, l as ResolverSchema, lt as commonArgs, nt as getOrNull, ot as isDefaultPlatform, q as defaultPlatformBaseUrl, rt as getPlatformBaseUrl, t as defineApplication, tt as getOAuth2ClientId, ut as configArg, v as composeFunctionTreeshakeOptions, w as getDistDir, wt as resolveMachineUserInputSource, xt as paginationArgs, y as createLogLevelTreeshakeOptions, z as platformConfigFromProfile } from "../application-BX13RArL.mjs";
5
- import { $t as compareSnapshotWithRemote, A as listCommand$13, An as apiCommand, C as listCommand$14, Cn as hasChanges, Dn as PluginManager, Dt as listCommand$7, E as waitCommand, En as sdkNameLabelKey, F as generateCommand$1, G as removeCommand$1, Gt as deploy, H as extractOwnedNamespaces, Ht as formatKeyValueTable, I as generateMigrationScript, It as getCommand$6, K as updateCommand$3, Kt as ensureConfigId, L as writeDbTypesFile, Mn as assertWritable, N as truncateCommand, Nn as defineAppCommand, O as resumeCommand, On as generateUserTypes, Pt as startCommand, Q as getCommand$5, Qt as parseMigrationLabelNumber, R as getConfiguredEditorCommand, Rt as executionsCommand, Sn as formatMigrationDiff, T as healthCommand, Tn as resourceTrn, Tt as triggerCommand, U as logBetaWarning, Ut as getCommand$1, V as showCommand, 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 } from "../register-ts-hook-BM1caLT0.mjs";
6
- import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-ufH7sS8o.mjs";
5
+ import { n as logger, r as styles } from "../logger-BwS4ppwO.mjs";
6
+ import { $n as saveUserTokens, An as sdkNameLabelKey, B as extractOwnedNamespaces, Bn as hasUserTokenEntry, Bt as getCommand$1, Cn as formatMigrationNumber, Ct as triggerCommand, Dn as hasChanges, E as listCommand$14, En as formatMigrationDiff, F as openInConfiguredEditor, Fn as assertWritable, Gn as loadPlatformClientConfig, Hn as loadConfigPath, I as deployCommand$1, In as loadConfig, It as executionsCommand, J as listCommand$13, Jn as platformConfigFromProfile, Jt as formatKeyValueTable, Kn as loadStoredUserTokens, Kt as createCommand$4, Ln as deleteUserTokens, M as generateMigrationScript, Mt as startCommand, N as writeDbTypesFile, Nn as prompt, On as getNamespacesWithMigrations, P as getConfiguredEditorCommand, Pn as apiCall, Pt as getCommand$6, Q as updateCommand$2, Qn as resolveUserTokenKey, Rn as fetchLatestToken, S as waitCommand, Sn as reconstructSnapshotFromMigrations, St as webhookCommand, Tt as listCommand$8, U as removeCommand$1, Un as loadConsoleBaseUrl, Ut as deployFromCLI, V as logBetaWarning, Vn as loadAccessToken, W as updateCommand$3, Wn as loadMachineUserName, Wt as listCommand$17, X as getCommand$5, Xn as removeLegacyUserAlias, Xt as ensureConfigId, Yn as readPlatformConfig, Yt as workspaceNameSchema, Zn as resolveConfigUser, Zt as executeScript, _n as getMigrationFilePath, _t as listCommand$9, an as protoGqlPermission, ar as deploymentArgs, bn as isValidMigrationNumber, c as listCommand$16, cn as INITIAL_SCHEMA_NUMBER, cr as pagedLogArgs, ct as listCommand$11, dn as assertValidMigrationFiles, dr as toPageDirection, en as MIGRATION_LABEL_KEY, er as writePlatformConfig, et as listCommand$12, f as restoreCommand, fn as compareLocalTypesWithSnapshot, fr as workspaceArgs, g as deleteCommand$5, gt as generate, hn as getLatestMigrationNumber, i as updateCommand$4, in as generateAllTypeManifestsFromSnapshot, ir as confirmationArgs, it as deleteCommand$4, j as generateCommand$1, jn as PluginManager, k as truncateCommand, kn as resourceTrn, kt as jobsCommand, lr as paginationArgs, m as getCommand$7, mn as createSnapshotFromLocalTypes, mt as listCommand$10, nn as parseMigrationLabelNumber, nr as commonArgs, nt as getCommand$4, o as removeCommand, or as isVerbose, ot as createCommand$3, pt as tokenCommand$1, q as treeCommand, qn as loadWorkspaceId, r as queryCommand, rn as compareSnapshotWithRemote, rr as configArg, sr as multiConfigArg, t as registerTsHook, tn as handleOptionalToRequiredError, tr as defineAppCommand, u as inviteCommand, ur as resolveMachineUserInputSource, ut as getCommand$3, vn as getMigrationFiles, w as resumeCommand, wn as parseMigrationNumberArg, x as healthCommand, xn as loadDiff, y as listCommand$15, yt as getCommand$2, z as showCommand, zn as hasAnyUserTokenEntry, zt as functionExecutionStatusToString } from "../register-ts-hook-DL31O2W9.mjs";
7
+ import { A as fetchAllTolerant, B as initOperatorClient, D as closeConnectionPool, I as getOAuth2ClientId, L as getOrNull, M as fetchPaged, N as fetchPlatformMachineUserToken, O as defaultPlatformBaseUrl, P as fetchUserInfo, R as getPlatformBaseUrl, V as isDefaultPlatform, _ as platformBundleDefinePlugin, a as resolveInlineSourcemap, b as resolveBundleLogLevel, c as WorkflowJobSchema, d as INVOKER_EXPR, k as fetchAll, l as ResolverSchema, v as composeFunctionTreeshakeOptions, w as getDistDir, y as createLogLevelTreeshakeOptions, z as initOAuth2Client } from "../application-BJXRpQj5.mjs";
8
+ import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-CnHz9rwz.mjs";
7
9
  import { t as multiline } from "../multiline-sfHpTZZK.mjs";
8
10
  import { t as readPackageJson } from "../package-json-8b0O9TlX.mjs";
9
- import { i as userAgent } from "../secret-file-aoNXJmKa.mjs";
10
- import { n as isCLIError } from "../errors-118wUDRr.mjs";
11
- import { B as FunctionExecution_Type, Ct as PATScope, T as CustomDomainStatus, gt as AuthInvokerSchema } from "../service_pb-B5w9rjPY.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";
13
- import { t as isPluginGeneratedType } from "../type-source-DH_LH20p.mjs";
11
+ import { t as userAgent } from "../user-agent-Bgsszb5I.mjs";
12
+ import { n as errorToJson, r as isCLIError, t as createCLIError } from "../errors-DfnF4ibq.mjs";
13
+ import { t as OperatorService } from "../service_pb-BoLM0X4K.mjs";
14
+ import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-CxHne11E.mjs";
14
15
  import * as path from "pathe";
15
16
  import { dirname, resolve } from "pathe";
16
17
  import { resolvePackageJSON, resolveTSConfig } from "pkg-types";
@@ -18,29 +19,578 @@ import { arg, defineCommand, runCommand, runMain } from "politty";
18
19
  import { withCompletionCommand } from "politty/completion";
19
20
  import { withSkillCommand } from "politty/skill";
20
21
  import { z } from "zod";
21
- import { create } from "@bufbuild/protobuf";
22
- import * as fs$1 from "node:fs";
22
+ import { ScalarType, create } from "@bufbuild/protobuf";
23
+ import * as fs from "node:fs";
23
24
  import { accessSync, constants, readdirSync } from "node:fs";
24
25
  import { timestampDate } from "@bufbuild/protobuf/wkt";
25
- import { fileURLToPath, pathToFileURL } from "node:url";
26
+ import { pathToFileURL } from "node:url";
26
27
  import { generateCodeVerifier } from "@badgateway/oauth2-client";
27
28
  import { Code, ConnectError } from "@connectrpc/connect";
28
- import pLimit from "p-limit";
29
29
  import * as crypto from "node:crypto";
30
30
  import { createHash } from "node:crypto";
31
31
  import * as http from "node:http";
32
32
  import open from "open";
33
33
  import * as rolldown from "rolldown";
34
- import * as fsPromises from "node:fs/promises";
35
- import { glob } from "node:fs/promises";
36
34
  import * as os from "node:os";
37
- import { setTimeout as setTimeout$1 } from "node:timers/promises";
35
+ import * as fsPromises from "node:fs/promises";
38
36
  import { TraceMap, generatedPositionFor, originalPositionFor } from "@jridgewell/trace-mapping";
39
37
  import { spawn, spawnSync } from "node:child_process";
40
- import { watch } from "chokidar";
41
- import * as fs from "fs";
42
- import { lookup } from "mime-types";
43
38
 
39
+ //#region src/cli/commands/api/proto-reflect.ts
40
+ const UNARY_METHODS = OperatorService.methods.filter((m) => m.methodKind === "unary");
41
+ function listMethodNames() {
42
+ return UNARY_METHODS.map((m) => m.name).toSorted();
43
+ }
44
+ /**
45
+ * Returns every accepted form of the `endpoint` positional — bare method name
46
+ * and fully-qualified `service/Method`. politty's expand completion keys its
47
+ * static table by the literal endpoint string, so both forms must appear in
48
+ * `choices` or the user typing the fully-qualified name gets no `--field`
49
+ * completion candidates back at TAB time.
50
+ * @returns Sorted list of accepted endpoint values
51
+ */
52
+ function listMethodChoices() {
53
+ return UNARY_METHODS.flatMap((m) => [m.name, `${OperatorService.typeName}/${m.name}`]).toSorted();
54
+ }
55
+ function getMethodDescriptor(methodName) {
56
+ return UNARY_METHODS.find((m) => m.name === methodName);
57
+ }
58
+ function extractMethodName(endpoint) {
59
+ if (!endpoint.includes("/")) return endpoint;
60
+ return endpoint.split("/").pop() ?? endpoint;
61
+ }
62
+ /**
63
+ * Returns the nested message descriptor when the field is a message, a list of
64
+ * messages, or a map with message values. Otherwise returns undefined.
65
+ * @param field - The proto field descriptor to inspect
66
+ * @returns The nested message descriptor, or undefined when the field isn't message-shaped
67
+ */
68
+ function nestedMessage(field) {
69
+ if (field.fieldKind === "message") return field.message;
70
+ if (field.fieldKind === "list" && field.listKind === "message") return field.message;
71
+ if (field.fieldKind === "map" && field.mapKind === "message") return field.message;
72
+ }
73
+ /**
74
+ * Most `google.protobuf.*` well-known types serialize as scalars in proto JSON
75
+ * (Timestamp/Duration → RFC3339/duration string, FieldMask → comma-joined
76
+ * string, *Value wrappers → underlying primitive, etc.), so we must not let
77
+ * `--field` drill into their internal fields. Treating them as leaves keeps
78
+ * the completion and the body shape consistent with what the server expects.
79
+ * @param message - The proto message descriptor to inspect
80
+ * @returns True when the message is a well-known type
81
+ */
82
+ function isWellKnownType(message) {
83
+ return message.typeName.startsWith("google.protobuf.");
84
+ }
85
+ const UNREPRESENTABLE_WELL_KNOWN_TYPES = /* @__PURE__ */ new Set([
86
+ "google.protobuf.Struct",
87
+ "google.protobuf.Value",
88
+ "google.protobuf.ListValue",
89
+ "google.protobuf.NullValue",
90
+ "google.protobuf.Any",
91
+ "google.protobuf.Empty"
92
+ ]);
93
+ /**
94
+ * @param message - The proto message descriptor to inspect
95
+ * @returns True when the message is a well-known type that `--field` cannot represent
96
+ */
97
+ function isUnrepresentableWellKnownType(message) {
98
+ return UNREPRESENTABLE_WELL_KNOWN_TYPES.has(message.typeName);
99
+ }
100
+ /**
101
+ * Pre-enumerates every `--field` completion candidate for `methodName`,
102
+ * walking the input message tree to produce a flat list that the shell can
103
+ * prefix-filter at TAB time. Used by the `expand` completion variant so no
104
+ * Node process is spawned per keystroke.
105
+ *
106
+ * For each leaf field, emits `key=` (key completion). Enum leaves additionally
107
+ * emit `key=ENUM_VALUE` per value, bool leaves emit `key=true` and
108
+ * `key=false`. For nested messages, emits `key.` (drill-down) and recurses.
109
+ *
110
+ * Returns an empty array when `methodName` is unknown so the `expand`
111
+ * generator stays exception-free at script-generation time.
112
+ * @param methodName - Name of the unary RPC whose input message is being walked
113
+ * @returns Flat list of `{ value, description }` candidates
114
+ */
115
+ function enumerateAllFieldCompletions(methodName) {
116
+ const method = getMethodDescriptor(methodName);
117
+ if (!method) return [];
118
+ const candidates = [];
119
+ const visited = /* @__PURE__ */ new Set();
120
+ function walk(message, prefix) {
121
+ visited.add(message);
122
+ for (const field of message.fields) {
123
+ if (field.fieldKind === "list" || field.fieldKind === "map") continue;
124
+ const fullKey = prefix + field.localName;
125
+ if (field.fieldKind === "message") {
126
+ if (isUnrepresentableWellKnownType(field.message)) continue;
127
+ if (!isWellKnownType(field.message)) {
128
+ const nested = field.message;
129
+ candidates.push({
130
+ value: `${fullKey}.`,
131
+ description: `${fullKey} (message)`
132
+ });
133
+ if (!visited.has(nested)) walk(nested, `${fullKey}.`);
134
+ continue;
135
+ }
136
+ }
137
+ candidates.push({
138
+ value: `${fullKey}=`,
139
+ description: `Set ${fullKey}`
140
+ });
141
+ if (field.fieldKind === "enum") for (const v of field.enum.values) candidates.push({
142
+ value: `${fullKey}=${v.name}`,
143
+ description: v.name
144
+ });
145
+ else if (field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
146
+ candidates.push({
147
+ value: `${fullKey}=true`,
148
+ description: "true"
149
+ });
150
+ candidates.push({
151
+ value: `${fullKey}=false`,
152
+ description: "false"
153
+ });
154
+ }
155
+ }
156
+ visited.delete(message);
157
+ }
158
+ walk(method.input, "");
159
+ return candidates;
160
+ }
161
+ /**
162
+ * Resolves the leaf `DescField` at a dotted `--field` path within an input
163
+ * message tree. Returns undefined when any segment is missing or when a
164
+ * non-leaf segment isn't a singular message — callers fall back to treating
165
+ * the value as a string in that case.
166
+ * @param input - The RPC input message descriptor
167
+ * @param path - Dot-split path segments to follow (e.g. ["application", "name"])
168
+ * @returns The leaf field descriptor, or undefined when the path doesn't resolve
169
+ */
170
+ function resolveLeafField(input, path) {
171
+ let message = input;
172
+ for (let i = 0; i < path.length; i++) {
173
+ const field = message.fields.find((f) => f.localName === path[i]);
174
+ if (!field) return void 0;
175
+ if (i === path.length - 1) {
176
+ if (field.fieldKind === "message" && isUnrepresentableWellKnownType(field.message)) return;
177
+ return field;
178
+ }
179
+ if (field.fieldKind !== "message") return void 0;
180
+ if (isWellKnownType(field.message)) return void 0;
181
+ message = field.message;
182
+ }
183
+ }
184
+
185
+ //#endregion
186
+ //#region src/cli/commands/api/render.ts
187
+ const SCALAR_LABEL = {
188
+ [ScalarType.DOUBLE]: "double",
189
+ [ScalarType.FLOAT]: "float",
190
+ [ScalarType.INT64]: "int64",
191
+ [ScalarType.UINT64]: "uint64",
192
+ [ScalarType.INT32]: "int32",
193
+ [ScalarType.FIXED64]: "fixed64",
194
+ [ScalarType.FIXED32]: "fixed32",
195
+ [ScalarType.BOOL]: "bool",
196
+ [ScalarType.STRING]: "string",
197
+ [ScalarType.BYTES]: "bytes",
198
+ [ScalarType.UINT32]: "uint32",
199
+ [ScalarType.SFIXED32]: "sfixed32",
200
+ [ScalarType.SFIXED64]: "sfixed64",
201
+ [ScalarType.SINT32]: "sint32",
202
+ [ScalarType.SINT64]: "sint64"
203
+ };
204
+ function shortName(typeName) {
205
+ const dot = typeName.lastIndexOf(".");
206
+ return dot < 0 ? typeName : typeName.slice(dot + 1);
207
+ }
208
+ function scalarLabel(scalar) {
209
+ return SCALAR_LABEL[scalar] ?? `scalar(${scalar})`;
210
+ }
211
+ function enumLabel(enumDesc) {
212
+ return `enum ${shortName(enumDesc.typeName)}`;
213
+ }
214
+ function enumOf(field) {
215
+ if (field.fieldKind === "enum") return field.enum;
216
+ if (field.fieldKind === "list" && field.listKind === "enum") return field.enum;
217
+ }
218
+ function describeFieldType(field) {
219
+ switch (field.fieldKind) {
220
+ case "scalar": return scalarLabel(field.scalar);
221
+ case "enum": return enumLabel(field.enum);
222
+ case "message": return shortName(field.message.typeName);
223
+ case "list": {
224
+ let inner;
225
+ if (field.listKind === "scalar") inner = scalarLabel(field.scalar);
226
+ else if (field.listKind === "enum") inner = enumLabel(field.enum);
227
+ else inner = shortName(field.message.typeName);
228
+ return `repeated ${inner}`;
229
+ }
230
+ case "map": {
231
+ const keyType = scalarLabel(field.mapKey);
232
+ let valType;
233
+ if (field.mapKind === "scalar") valType = scalarLabel(field.scalar);
234
+ else if (field.mapKind === "enum") valType = enumLabel(field.enum);
235
+ else valType = shortName(field.message.typeName);
236
+ return `map<${keyType}, ${valType}>`;
237
+ }
238
+ default: return "<unknown>";
239
+ }
240
+ }
241
+ function fieldToJson(field, visited) {
242
+ const json = {
243
+ name: field.localName,
244
+ protoName: field.name,
245
+ type: describeFieldType(field),
246
+ fieldKind: field.fieldKind,
247
+ repeated: field.fieldKind === "list"
248
+ };
249
+ if (field.oneof) json.oneof = field.oneof.name;
250
+ const fieldEnum = enumOf(field);
251
+ if (fieldEnum) json.enumValues = fieldEnum.values.map((v) => v.name);
252
+ const nested = nestedMessage(field);
253
+ if (nested && !visited.has(nested)) {
254
+ visited.add(nested);
255
+ json.message = {
256
+ typeName: nested.typeName,
257
+ fields: nested.fields.map((f) => fieldToJson(f, visited))
258
+ };
259
+ visited.delete(nested);
260
+ } else if (nested) json.message = {
261
+ typeName: nested.typeName,
262
+ fields: [],
263
+ recursive: true
264
+ };
265
+ return json;
266
+ }
267
+ function renderInspectJson(method) {
268
+ const visited = /* @__PURE__ */ new Set([method.input]);
269
+ return {
270
+ method: method.name,
271
+ input: {
272
+ typeName: method.input.typeName,
273
+ fields: method.input.fields.map((f) => fieldToJson(f, visited))
274
+ },
275
+ output: { typeName: method.output.typeName }
276
+ };
277
+ }
278
+ function renderFieldText(field, indent, visited) {
279
+ const lines = [];
280
+ const oneofTag = field.oneof ? ` (oneof ${field.oneof.name})` : "";
281
+ lines.push(`${indent}${field.localName}: ${describeFieldType(field)}${oneofTag}`);
282
+ const fieldEnum = enumOf(field);
283
+ if (fieldEnum) {
284
+ const values = fieldEnum.values.map((v) => v.name).join(", ");
285
+ lines.push(`${indent} values: ${values}`);
286
+ }
287
+ const nested = nestedMessage(field);
288
+ if (nested && !visited.has(nested)) {
289
+ visited.add(nested);
290
+ for (const sub of nested.fields) lines.push(...renderFieldText(sub, `${indent} `, visited));
291
+ visited.delete(nested);
292
+ } else if (nested) lines.push(`${indent} …(recursive ${nested.typeName})`);
293
+ return lines;
294
+ }
295
+ function renderInspectText(method) {
296
+ const lines = [];
297
+ lines.push(`${method.name}`);
298
+ lines.push(` request: ${method.input.typeName}`);
299
+ const visited = /* @__PURE__ */ new Set([method.input]);
300
+ for (const f of method.input.fields) lines.push(...renderFieldText(f, " ", visited));
301
+ lines.push(` response: ${method.output.typeName}`);
302
+ return lines.join("\n");
303
+ }
304
+
305
+ //#endregion
306
+ //#region src/cli/commands/api/inspect.ts
307
+ const inspectCommand = defineAppCommand({
308
+ name: "inspect",
309
+ description: "Print the input message tree of an OperatorService endpoint.",
310
+ notes: "Combine with the global `--json` flag for a machine-readable descriptor. Recursive type references and `oneof` membership are annotated. Use `tailor api list` to discover endpoint names.",
311
+ examples: [{
312
+ cmd: "GetApplication",
313
+ desc: "Show fields of GetApplicationRequest."
314
+ }, {
315
+ cmd: "CreateExecutorExecutor",
316
+ desc: "Inspect a deeply nested input with `(oneof config)` annotations."
317
+ }],
318
+ args: z.strictObject({ endpoint: arg(z.string(), {
319
+ positional: true,
320
+ description: "API endpoint to inspect (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
321
+ completion: { custom: { choices: listMethodNames() } }
322
+ }) }),
323
+ run: (args) => {
324
+ const methodName = extractMethodName(args.endpoint);
325
+ const method = getMethodDescriptor(methodName);
326
+ if (!method) throw createCLIError({
327
+ message: `unknown method: ${methodName}`,
328
+ suggestion: "Run `tailor api list` to see available methods.",
329
+ command: "api inspect"
330
+ });
331
+ if (logger.jsonMode) logger.out(renderInspectJson(method));
332
+ else logger.out(renderInspectText(method));
333
+ }
334
+ });
335
+
336
+ //#endregion
337
+ //#region src/cli/commands/api/list.ts
338
+ const listCommand$7 = defineAppCommand({
339
+ name: "list",
340
+ description: "List all invocable OperatorService methods.",
341
+ notes: "Only single-request (non-streaming) methods are listed, because the CLI issues a single JSON request and reads one JSON response.",
342
+ args: z.strictObject({}),
343
+ run: () => {
344
+ const names = listMethodNames();
345
+ if (logger.jsonMode) logger.out(names);
346
+ else for (const name of names) logger.out(name);
347
+ }
348
+ });
349
+
350
+ //#endregion
351
+ //#region src/cli/commands/api/index.ts
352
+ function resolveNamespaceName(methodName, config) {
353
+ if (/Auth|Tenant|UserProfile/.test(methodName)) return config.auth?.name;
354
+ if (/IdP/.test(methodName)) {
355
+ if (config.idp?.length === 1) return assertDefined(config.idp[0], "idp config missing").name;
356
+ return;
357
+ }
358
+ if (/TailorDB/.test(methodName)) {
359
+ const keys = Object.keys(config.db ?? {});
360
+ if (keys.length === 1) return keys[0];
361
+ return;
362
+ }
363
+ if (/Pipeline/.test(methodName)) {
364
+ const keys = Object.keys(config.resolver ?? {});
365
+ if (keys.length === 1) return keys[0];
366
+ return;
367
+ }
368
+ }
369
+ /**
370
+ * Parse a JSON body string as a plain object for field injection.
371
+ * Returns undefined for invalid JSON or non-object values (null, arrays, primitives),
372
+ * so callers can fall back to sending the raw string unchanged.
373
+ * @param body - Raw body string
374
+ * @returns Parsed object or undefined
375
+ */
376
+ function parseBodyAsObject(body) {
377
+ let parsed;
378
+ try {
379
+ parsed = JSON.parse(body);
380
+ } catch {
381
+ return;
382
+ }
383
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
384
+ return parsed;
385
+ }
386
+ /**
387
+ * Set a dotted path on a body object, replacing non-object intermediates as
388
+ * needed. `--field` takes precedence over `--body`, so collisions overwrite.
389
+ * @param obj - The body object to mutate
390
+ * @param path - Dot-split path segments (e.g. ["application", "name"])
391
+ * @param value - Value to assign at the leaf
392
+ */
393
+ function setNestedPath(obj, path, value) {
394
+ let cursor = obj;
395
+ for (let i = 0; i < path.length - 1; i++) {
396
+ const key = assertDefined(path[i], "path segment missing");
397
+ const next = cursor[key];
398
+ if (typeof next !== "object" || next === null || Array.isArray(next)) cursor[key] = {};
399
+ cursor = cursor[key];
400
+ }
401
+ cursor[assertDefined(path[path.length - 1], "path last segment missing")] = value;
402
+ }
403
+ /**
404
+ * Coerces a raw `--field` string value to match the leaf proto type so the
405
+ * JSON body sends a properly typed value. Today we only coerce bool — the
406
+ * completion candidates explicitly suggest `=true`/`=false`, so sending the
407
+ * literal string "true" would be a visible mismatch. Other scalars are left
408
+ * as strings: proto JSON accepts string forms for ints/floats/int64/etc.,
409
+ * and we'd rather pass through what the user typed than silently coerce.
410
+ * @param field - The resolved leaf field descriptor, or undefined when the path didn't resolve
411
+ * @param raw - The raw string value after `=`
412
+ * @returns The value to write into the body object
413
+ */
414
+ function coerceFieldValue(field, raw) {
415
+ if (field && field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
416
+ if (raw === "true") return true;
417
+ if (raw === "false") return false;
418
+ throw new Error(`Invalid value for bool field: '${raw}'. Expected 'true' or 'false'.`);
419
+ }
420
+ return raw;
421
+ }
422
+ /**
423
+ * Collapse top-level body keys that address a known input field by its
424
+ * protobuf name (snake_case) or JSON name to the field's `localName`. protojson
425
+ * accepts every alias, so converging them here lets the presence checks below
426
+ * reason in a single namespace — otherwise a `--body` written as
427
+ * `{"workspace_id": ...}` slips past the camelCase check and we inject a second
428
+ * `workspaceId`, which the server rejects as a duplicate field. When both forms
429
+ * are present the canonical key wins and the alias is dropped.
430
+ * @param body - The parsed body object to mutate
431
+ * @param fields - The target endpoint's input fields
432
+ * @returns Whether any key was rewritten
433
+ */
434
+ function normalizeBodyFieldKeys(body, fields) {
435
+ const aliasToLocal = /* @__PURE__ */ new Map();
436
+ for (const f of fields) {
437
+ aliasToLocal.set(f.name, f.localName);
438
+ aliasToLocal.set(f.jsonName, f.localName);
439
+ }
440
+ let changed = false;
441
+ for (const key of Object.keys(body)) {
442
+ const local = aliasToLocal.get(key);
443
+ if (!local || local === key) continue;
444
+ if (!Object.hasOwn(body, local)) body[local] = body[key];
445
+ delete body[key];
446
+ changed = true;
447
+ }
448
+ return changed;
449
+ }
450
+ const FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set([
451
+ "__proto__",
452
+ "constructor",
453
+ "prototype"
454
+ ]);
455
+ const fieldArg = z.string().transform((val, ctx) => {
456
+ const eq = val.indexOf("=");
457
+ if (eq < 0) {
458
+ ctx.addIssue({
459
+ code: z.ZodIssueCode.custom,
460
+ message: `Invalid field format: '${val}'. Expected format: 'key=value' or 'a.b.c=value'`
461
+ });
462
+ return z.NEVER;
463
+ }
464
+ const key = val.slice(0, eq);
465
+ if (key.length === 0) {
466
+ ctx.addIssue({
467
+ code: z.ZodIssueCode.custom,
468
+ message: "Field key cannot be empty"
469
+ });
470
+ return z.NEVER;
471
+ }
472
+ const segments = key.split(".");
473
+ if (segments.some((seg) => seg.length === 0)) {
474
+ ctx.addIssue({
475
+ code: z.ZodIssueCode.custom,
476
+ message: `Invalid field key: '${key}'. Dotted segments cannot be empty`
477
+ });
478
+ return z.NEVER;
479
+ }
480
+ const forbidden = segments.find((seg) => FORBIDDEN_SEGMENTS.has(seg));
481
+ if (forbidden) {
482
+ ctx.addIssue({
483
+ code: z.ZodIssueCode.custom,
484
+ message: `Invalid field key: '${key}'. Segment '${forbidden}' is not allowed.`
485
+ });
486
+ return z.NEVER;
487
+ }
488
+ return {
489
+ path: segments,
490
+ value: val.slice(eq + 1)
491
+ };
492
+ });
493
+ const apiCommand = defineAppCommand({
494
+ name: "api",
495
+ description: "Call Tailor Platform API endpoints directly.",
496
+ notes: `Use \`tailor api list\` to enumerate invocable methods and \`tailor api inspect <endpoint>\` to print an endpoint's input message tree (combine with \`--json\` for machine-readable output).
497
+
498
+ The request body is inferred from the target endpoint's request schema, and commonly required fields are auto-injected so they can be omitted from \`--body\`:
499
+
500
+ - \`workspaceId\` — resolved from \`-w\` / \`TAILOR_PLATFORM_WORKSPACE_ID\` / the selected profile.
501
+ - \`namespaceName\` — resolved from \`tailor.config.ts\` based on the endpoint's service:
502
+ - Auth / Tenant / UserProfile endpoints use \`auth.name\`.
503
+ - IdP / TailorDB / Pipeline endpoints use the sole configured namespace when exactly one is defined.
504
+
505
+ Values already present in \`--body\` are never overridden. If a value cannot be resolved (e.g. no config found), injection is silently skipped and the server-side validation error takes precedence.
506
+
507
+ Use \`--field key=value\` (repeatable) to set request body fields without writing JSON. Dotted keys (e.g. \`application.name=foo\`) build nested objects. \`--field\` overrides matching fields in \`--body\` and tab-completes from the endpoint's request schema.`,
508
+ examples: [
509
+ {
510
+ cmd: "GetApplication -b '{\"applicationName\":\"app-1\"}'",
511
+ desc: "Call an endpoint; workspaceId is auto-injected."
512
+ },
513
+ {
514
+ cmd: "GetApplication -f applicationName=app-1",
515
+ desc: "Same as above, using --field instead of --body."
516
+ },
517
+ {
518
+ cmd: "list",
519
+ desc: "List all invocable OperatorService methods."
520
+ },
521
+ {
522
+ cmd: "inspect GetApplication",
523
+ desc: "Show the input message tree for an endpoint."
524
+ }
525
+ ],
526
+ subCommands: {
527
+ list: listCommand$7,
528
+ inspect: inspectCommand
529
+ },
530
+ args: z.strictObject({
531
+ ...workspaceArgs,
532
+ ...configArg,
533
+ body: arg(z.string().default("{}"), {
534
+ alias: "b",
535
+ description: "Request body as JSON."
536
+ }),
537
+ field: arg(fieldArg.array().optional(), {
538
+ alias: "f",
539
+ description: "Set a body field as `key=value` (repeatable; dotted keys nest). Overrides --body.",
540
+ completion: { custom: { expand: {
541
+ dependsOn: ["endpoint"],
542
+ enumerate: ({ endpoint }) => enumerateAllFieldCompletions(extractMethodName(endpoint ?? ""))
543
+ } } }
544
+ }),
545
+ endpoint: arg(z.string(), {
546
+ positional: true,
547
+ description: "API endpoint to call (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
548
+ completion: { custom: { choices: listMethodChoices() } }
549
+ })
550
+ }),
551
+ run: async (args) => {
552
+ await assertWritable({ profile: args.profile });
553
+ const methodName = extractMethodName(args.endpoint);
554
+ const method = getMethodDescriptor(methodName);
555
+ const parsedBody = parseBodyAsObject(args.body);
556
+ let mutated = false;
557
+ if (args.field && args.field.length > 0) {
558
+ if (!parsedBody) throw new Error("--field requires --body to be a JSON object (or omitted).");
559
+ for (const f of args.field) {
560
+ const leaf = method ? resolveLeafField(method.input, f.path) : void 0;
561
+ setNestedPath(parsedBody, f.path, coerceFieldValue(leaf, f.value));
562
+ }
563
+ mutated = true;
564
+ }
565
+ if (parsedBody && method) {
566
+ if (normalizeBodyFieldKeys(parsedBody, method.input.fields)) mutated = true;
567
+ const fieldNames = method.input.fields.map((f) => f.localName);
568
+ if (fieldNames.includes("workspaceId") && !Object.hasOwn(parsedBody, "workspaceId")) try {
569
+ parsedBody.workspaceId = await loadWorkspaceId({
570
+ workspaceId: args["workspace-id"],
571
+ profile: args.profile
572
+ });
573
+ mutated = true;
574
+ } catch {}
575
+ if (fieldNames.includes("namespaceName") && !Object.hasOwn(parsedBody, "namespaceName")) try {
576
+ const { config } = await loadConfig(args.config);
577
+ const ns = resolveNamespaceName(methodName, config);
578
+ if (ns) {
579
+ parsedBody.namespaceName = ns;
580
+ mutated = true;
581
+ }
582
+ } catch {}
583
+ }
584
+ const result = await apiCall({
585
+ profile: args.profile,
586
+ endpoint: args.endpoint,
587
+ body: mutated && parsedBody ? JSON.stringify(parsedBody) : args.body
588
+ });
589
+ logger.out(JSON.stringify(result.data, null, 2));
590
+ }
591
+ });
592
+
593
+ //#endregion
44
594
  //#region src/cli/commands/auth/token.ts
45
595
  const tokenCommand = defineAppCommand({
46
596
  name: "token",
@@ -396,7 +946,7 @@ const listCommand$6 = defineAppCommand({
396
946
  }
397
947
  let entries;
398
948
  try {
399
- entries = fs$1.readdirSync(config.localDir);
949
+ entries = fs.readdirSync(config.localDir);
400
950
  } catch {
401
951
  logger.info("No crash reports found.");
402
952
  if (jsonOutput) logger.out([]);
@@ -436,7 +986,7 @@ const sendCommand = defineAppCommand({
436
986
  run: async (args) => {
437
987
  let content;
438
988
  try {
439
- content = fs$1.readFileSync(args.file, "utf-8");
989
+ content = fs.readFileSync(args.file, "utf-8");
440
990
  } catch {
441
991
  logger.error(`Crash report file not found: ${args.file}`);
442
992
  process.exit(1);
@@ -491,13 +1041,24 @@ const crashReportCommand = defineCommand({
491
1041
 
492
1042
  //#endregion
493
1043
  //#region src/cli/commands/deploy/index.ts
494
- const deployCommand$1 = defineAppCommand({
1044
+ const deployCommand = defineAppCommand({
495
1045
  name: "deploy",
496
1046
  description: "Deploy your application by applying the Tailor configuration.",
497
1047
  args: z.strictObject({
498
1048
  ...workspaceArgs,
499
1049
  ...multiConfigArg,
500
1050
  ...confirmationArgs,
1051
+ "create-workspace": arg(z.boolean().optional(), { description: "Create a workspace when the account has none" }),
1052
+ "workspace-name": arg(z.string().optional(), { description: "Name for a workspace created during deploy" }),
1053
+ "workspace-region": arg(z.string().optional(), { description: "Region for a workspace created during deploy" }),
1054
+ "organization-id": arg(z.string().optional(), {
1055
+ description: "Organization ID for a workspace created during deploy",
1056
+ env: "TAILOR_PLATFORM_ORGANIZATION_ID"
1057
+ }),
1058
+ "folder-id": arg(z.string().optional(), {
1059
+ description: "Folder ID for a workspace created during deploy",
1060
+ env: "TAILOR_PLATFORM_FOLDER_ID"
1061
+ }),
501
1062
  "dry-run": arg(z.boolean().optional(), {
502
1063
  alias: "d",
503
1064
  description: "Run the command without making any changes"
@@ -511,16 +1072,26 @@ const deployCommand$1 = defineAppCommand({
511
1072
  await assertWritable({ profile: args.profile });
512
1073
  const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
513
1074
  await initTelemetry();
514
- await deploy({
1075
+ await deployFromCLI({
515
1076
  workspaceId: args["workspace-id"],
516
1077
  profile: args.profile,
517
1078
  configPath: args.config,
518
1079
  dryRun: args["dry-run"],
519
1080
  yes: args.yes,
1081
+ createWorkspace: args["create-workspace"],
1082
+ workspaceName: args["workspace-name"],
1083
+ workspaceRegion: args["workspace-region"],
1084
+ organizationId: args["organization-id"],
1085
+ folderId: args["folder-id"],
520
1086
  noSchemaCheck: args["no-schema-check"],
521
1087
  noValidate: args["no-validate"],
522
1088
  noCache: args["no-cache"],
523
1089
  cleanCache: args["clean-cache"]
1090
+ }, {
1091
+ envFile: args["env-file"],
1092
+ envFileIfExists: args["env-file-if-exists"],
1093
+ verbose: args.verbose,
1094
+ json: args.json
524
1095
  });
525
1096
  }
526
1097
  });
@@ -533,12 +1104,12 @@ const executorCommand = defineCommand({
533
1104
  subCommands: {
534
1105
  trigger: triggerCommand,
535
1106
  jobs: jobsCommand,
536
- list: listCommand$7,
1107
+ list: listCommand$8,
537
1108
  get: getCommand$1,
538
1109
  webhook: webhookCommand
539
1110
  },
540
1111
  async run() {
541
- await runCommand(listCommand$7, []);
1112
+ await runCommand(listCommand$8, []);
542
1113
  }
543
1114
  });
544
1115
 
@@ -1119,12 +1690,12 @@ async function bundleForTestRun(options) {
1119
1690
  const inlineSourcemap = resolveInlineSourcemap(options.inlineSourcemap);
1120
1691
  const bundleLogLevel = resolveBundleLogLevel(options.logLevel);
1121
1692
  const outputDir = path.resolve(getDistDir(), "test-run");
1122
- fs$1.mkdirSync(outputDir, { recursive: true });
1693
+ fs.mkdirSync(outputDir, { recursive: true });
1123
1694
  const baseName = `test-run--${detected.name}`;
1124
1695
  const scriptName = `${baseName}.js`;
1125
1696
  const entryPath = path.join(outputDir, `${baseName}.entry.js`);
1126
1697
  const entryContent = generateEntry(detected, sourceFile, env, machineUser, workspaceId);
1127
- fs$1.writeFileSync(entryPath, entryContent);
1698
+ fs.writeFileSync(entryPath, entryContent);
1128
1699
  let tsconfig;
1129
1700
  try {
1130
1701
  tsconfig = await resolveTSConfig();
@@ -1273,8 +1844,11 @@ async function detectFunctionType(options) {
1273
1844
  const rawInput = module.default.input;
1274
1845
  let inputSchema;
1275
1846
  if (rawInput) {
1276
- const { t } = await import("../types-C-lQ4se3.mjs");
1277
- inputSchema = t.object(rawInput);
1847
+ const fields = rawInput;
1848
+ inputSchema = { parse: (args) => parseInputFields({
1849
+ ...args,
1850
+ fields
1851
+ }) };
1278
1852
  }
1279
1853
  return {
1280
1854
  type: "resolver",
@@ -1420,7 +1994,7 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
1420
1994
  run: async (args) => {
1421
1995
  const jsonOutput = logger.jsonMode;
1422
1996
  const filePath = path.resolve(args.file);
1423
- if (!fs$1.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
1997
+ if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
1424
1998
  const { config } = await loadConfig(args.config);
1425
1999
  const authNamespace = resolveAuthNamespace(config.auth);
1426
2000
  const machineUserName = await resolveMachineUserName({
@@ -1449,7 +2023,7 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
1449
2023
  let functionName;
1450
2024
  if (isPreBundled) {
1451
2025
  scriptName = path.basename(filePath);
1452
- bundledCode = fs$1.readFileSync(filePath, "utf-8");
2026
+ bundledCode = fs.readFileSync(filePath, "utf-8");
1453
2027
  logger.info(`Using pre-bundled script ${styles.bold(scriptName)}`);
1454
2028
  } else {
1455
2029
  logger.info(`Detecting function type from ${styles.path(relativePath)}`);
@@ -1603,12 +2177,12 @@ const functionCommand = defineCommand({
1603
2177
  description: "Manage functions",
1604
2178
  subCommands: {
1605
2179
  get: getCommand$2,
1606
- list: listCommand$8,
2180
+ list: listCommand$9,
1607
2181
  logs: logsCommand,
1608
2182
  "test-run": testRunCommand
1609
2183
  },
1610
2184
  async run() {
1611
- await runCommand(listCommand$8, []);
2185
+ await runCommand(listCommand$9, []);
1612
2186
  }
1613
2187
  });
1614
2188
 
@@ -1617,23 +2191,14 @@ const functionCommand = defineCommand({
1617
2191
  const generateCommand = defineAppCommand({
1618
2192
  name: "generate",
1619
2193
  description: "Generate files using Tailor configuration.",
1620
- args: z.strictObject({
1621
- config: arg(z.string().default("tailor.config.ts"), {
1622
- alias: "c",
1623
- description: "Path to SDK config file"
1624
- }),
1625
- watch: arg(z.boolean().default(false), {
1626
- alias: "W",
1627
- description: "Watch for type/resolver changes and regenerate"
1628
- })
1629
- }),
2194
+ args: z.strictObject({ config: arg(z.string().default("tailor.config.ts"), {
2195
+ alias: "c",
2196
+ description: "Path to SDK config file"
2197
+ }) }),
1630
2198
  run: async (args) => {
1631
2199
  const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
1632
2200
  await initTelemetry();
1633
- await generate({
1634
- configPath: args.config,
1635
- watch: args.watch
1636
- });
2201
+ await generate({ configPath: args.config });
1637
2202
  }
1638
2203
  });
1639
2204
 
@@ -1692,8 +2257,47 @@ const redirectUri = `http://localhost:${redirectPort}/callback`;
1692
2257
  function randomState() {
1693
2258
  return crypto.randomBytes(32).toString("base64url");
1694
2259
  }
1695
- function assertProfileLoginUser(args, authenticatedUser, authenticatedSubject) {
1696
- 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}".`);
2260
+ function getProfileUserMismatch(args, authenticatedUser, authenticatedSubject) {
2261
+ if (!args.profile || !args.profileUser || authenticatedUser === args.profileUser || authenticatedSubject === args.profileUser) return;
2262
+ return {
2263
+ profile: args.profile,
2264
+ oldUser: args.profileUser,
2265
+ authenticatedUser,
2266
+ loginMode: args.loginMode ?? "user"
2267
+ };
2268
+ }
2269
+ function quoteCommandArg(value, platform = process.platform) {
2270
+ if (platform === "win32") {
2271
+ if (/^[A-Za-z0-9_./:@+=,-]+$/.test(value)) return value;
2272
+ return;
2273
+ }
2274
+ return `'${value.replaceAll("'", "'\\''")}'`;
2275
+ }
2276
+ function profileUpdateCommand(mismatch) {
2277
+ const profileArg = quoteCommandArg(mismatch.profile);
2278
+ const userArg = quoteCommandArg(mismatch.authenticatedUser);
2279
+ if (profileArg && userArg) return `tailor-sdk profile update --user ${userArg} -- ${profileArg}`;
2280
+ return [
2281
+ "tailor-sdk profile update --user <authenticated-user> -- <profile>",
2282
+ `profile = ${JSON.stringify(mismatch.profile)}`,
2283
+ `authenticated user = ${JSON.stringify(mismatch.authenticatedUser)}`
2284
+ ].join("\n");
2285
+ }
2286
+ function retryInstruction(mismatch) {
2287
+ if (mismatch.loginMode === "machine-user") return "Then retry the original machine-user login command.";
2288
+ const profileArg = quoteCommandArg(mismatch.profile);
2289
+ if (!profileArg) return "Then retry the browser login with the same profile value.";
2290
+ return `Then run:\n tailor-sdk login --profile ${profileArg}`;
2291
+ }
2292
+ function profileUserMismatchError(mismatch) {
2293
+ const updateCommand = profileUpdateCommand(mismatch);
2294
+ const nextStep = retryInstruction(mismatch);
2295
+ return new Error(multiline`
2296
+ Profile "${mismatch.profile}" is configured for "${mismatch.oldUser}", but login authenticated "${mismatch.authenticatedUser}".
2297
+ The authenticated user has been saved. To use it with this profile, run:
2298
+ ${updateCommand}
2299
+ ${nextStep}
2300
+ `);
1697
2301
  }
1698
2302
  function shouldUpdateCurrentUser(profile, platformConfig) {
1699
2303
  if (!profile) return true;
@@ -1713,7 +2317,6 @@ const startAuthServer = async (args = {}) => {
1713
2317
  codeVerifier
1714
2318
  });
1715
2319
  const userInfo = await fetchUserInfo(tokens.accessToken, args.platformConfig);
1716
- assertProfileLoginUser(args, userInfo.email, userInfo.sub);
1717
2320
  const pfConfig = await readPlatformConfig();
1718
2321
  await saveUserTokens(pfConfig, userInfo.sub, {
1719
2322
  accessToken: tokens.accessToken,
@@ -1722,6 +2325,11 @@ const startAuthServer = async (args = {}) => {
1722
2325
  platformConfig: args.platformConfig,
1723
2326
  email: userInfo.email
1724
2327
  });
2328
+ const mismatch = getProfileUserMismatch(args, userInfo.email, userInfo.sub);
2329
+ if (mismatch) {
2330
+ writePlatformConfig(pfConfig);
2331
+ throw profileUserMismatchError(mismatch);
2332
+ }
1725
2333
  if (args.updateCurrentUser ?? true) pfConfig.current_user = userInfo.sub;
1726
2334
  await removeLegacyUserAlias(pfConfig, userInfo.email, userInfo.sub, args.platformConfig);
1727
2335
  writePlatformConfig(pfConfig);
@@ -1765,11 +2373,15 @@ const startAuthServer = async (args = {}) => {
1765
2373
  });
1766
2374
  };
1767
2375
  async function loginAsMachineUser(args) {
1768
- assertProfileLoginUser(args, args.clientId);
1769
2376
  const clientSecret = args.clientSecret ?? await prompt.password({ message: "Client secret" });
1770
2377
  const tokens = await fetchPlatformMachineUserToken(args.clientId, clientSecret, args.platformConfig);
1771
2378
  const pfConfig = await readPlatformConfig();
1772
2379
  await saveUserTokens(pfConfig, args.clientId, { accessToken: tokens.accessToken }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString(), { platformConfig: args.platformConfig });
2380
+ const mismatch = getProfileUserMismatch(args, args.clientId);
2381
+ if (mismatch) {
2382
+ writePlatformConfig(pfConfig);
2383
+ throw profileUserMismatchError(mismatch);
2384
+ }
1773
2385
  if (args.updateCurrentUser ?? true) pfConfig.current_user = args.clientId;
1774
2386
  writePlatformConfig(pfConfig);
1775
2387
  }
@@ -1801,31 +2413,36 @@ const loginCommand = defineAppCommand({
1801
2413
  })
1802
2414
  }).describe("Machine User Login")]),
1803
2415
  run: async (args) => {
1804
- let platformConfig;
1805
- let profileUser;
1806
- if ("profile" in args && args.profile) {
1807
- const profileEntry = (await readPlatformConfig()).profiles[args.profile];
1808
- if (!profileEntry) throw new Error(`Profile "${args.profile}" not found`);
1809
- platformConfig = platformConfigFromProfile(profileEntry);
1810
- profileUser = profileEntry.user;
2416
+ try {
2417
+ let platformConfig;
2418
+ let profileUser;
2419
+ if ("profile" in args && args.profile) {
2420
+ const profileEntry = (await readPlatformConfig()).profiles[args.profile];
2421
+ if (!profileEntry) throw new Error(`Profile "${args.profile}" not found`);
2422
+ platformConfig = platformConfigFromProfile(profileEntry);
2423
+ profileUser = profileEntry.user;
2424
+ }
2425
+ const updateCurrentUser = shouldUpdateCurrentUser(args.profile, platformConfig);
2426
+ if ("machine-user" in args) await loginAsMachineUser({
2427
+ clientId: args.clientId,
2428
+ clientSecret: args.clientSecret,
2429
+ profile: args.profile,
2430
+ profileUser,
2431
+ platformConfig,
2432
+ updateCurrentUser,
2433
+ loginMode: "machine-user"
2434
+ });
2435
+ else await startAuthServer({
2436
+ profile: args.profile,
2437
+ profileUser,
2438
+ platformConfig,
2439
+ updateCurrentUser,
2440
+ loginMode: "user"
2441
+ });
2442
+ logger.success("Successfully logged in to Tailor Platform.");
2443
+ } finally {
2444
+ await closeConnectionPool();
1811
2445
  }
1812
- const updateCurrentUser = shouldUpdateCurrentUser(args.profile, platformConfig);
1813
- if ("machine-user" in args) await loginAsMachineUser({
1814
- clientId: args.clientId,
1815
- clientSecret: args.clientSecret,
1816
- profile: args.profile,
1817
- profileUser,
1818
- platformConfig,
1819
- updateCurrentUser
1820
- });
1821
- else await startAuthServer({
1822
- profile: args.profile,
1823
- profileUser,
1824
- platformConfig,
1825
- updateCurrentUser
1826
- });
1827
- logger.success("Successfully logged in to Tailor Platform.");
1828
- await closeConnectionPool();
1829
2446
  }
1830
2447
  });
1831
2448
 
@@ -1892,11 +2509,11 @@ const machineuserCommand = defineCommand({
1892
2509
  name: "machineuser",
1893
2510
  description: "Manage machine users in your Tailor Platform application.",
1894
2511
  subCommands: {
1895
- list: listCommand$9,
2512
+ list: listCommand$10,
1896
2513
  token: tokenCommand$1
1897
2514
  },
1898
2515
  async run() {
1899
- await runCommand(listCommand$9, []);
2516
+ await runCommand(listCommand$10, []);
1900
2517
  }
1901
2518
  });
1902
2519
 
@@ -1906,11 +2523,11 @@ const oauth2clientCommand = defineCommand({
1906
2523
  name: "oauth2client",
1907
2524
  description: "Manage OAuth2 clients in your Tailor Platform application.",
1908
2525
  subCommands: {
1909
- list: listCommand$10,
2526
+ list: listCommand$11,
1910
2527
  get: getCommand$3
1911
2528
  },
1912
2529
  async run() {
1913
- await runCommand(listCommand$10, []);
2530
+ await runCommand(listCommand$11, []);
1914
2531
  }
1915
2532
  });
1916
2533
 
@@ -1967,7 +2584,7 @@ const folderCommand = defineCommand({
1967
2584
  create: createCommand$3,
1968
2585
  delete: deleteCommand$4,
1969
2586
  get: getCommand$4,
1970
- list: listCommand$11,
2587
+ list: listCommand$12,
1971
2588
  update: updateCommand$2
1972
2589
  }
1973
2590
  });
@@ -1980,12 +2597,12 @@ const organizationCommand = defineCommand({
1980
2597
  subCommands: {
1981
2598
  folder: folderCommand,
1982
2599
  get: getCommand$5,
1983
- list: listCommand$12,
2600
+ list: listCommand$13,
1984
2601
  tree: treeCommand,
1985
2602
  update: updateCommand$3
1986
2603
  },
1987
2604
  async run() {
1988
- await runCommand(listCommand$12, []);
2605
+ await runCommand(listCommand$13, []);
1989
2606
  }
1990
2607
  });
1991
2608
 
@@ -2033,6 +2650,12 @@ const BUILTIN_COMMAND_NAMES = [
2033
2650
 
2034
2651
  //#endregion
2035
2652
  //#region src/cli/shared/plugin.ts
2653
+ /**
2654
+ * npm packages known to provide a plugin slug, keyed by the slug the
2655
+ * dispatcher resolves (command path joined with `-`). Used to suggest an
2656
+ * install command when the plugin executable is not found.
2657
+ */
2658
+ const KNOWN_PLUGIN_PACKAGES = { "tailordb-erd": "@tailor-platform/sdk-tailordb-erd-plugin" };
2036
2659
  /** PATH entry separator (`;` on Windows, `:` elsewhere). */
2037
2660
  const pathDelimiter = process.platform === "win32" ? ";" : ":";
2038
2661
  /**
@@ -2210,17 +2833,7 @@ async function resolveActiveUser(profile) {
2210
2833
  */
2211
2834
  async function buildPluginEnv(options = {}) {
2212
2835
  const { profile } = options;
2213
- let platformConfig;
2214
- try {
2215
- platformConfig = await loadPlatformClientConfig({
2216
- profile,
2217
- allowMissingProfile: true
2218
- });
2219
- } catch {}
2220
- const env = {
2221
- TAILOR_PLATFORM_URL: getPlatformBaseUrl(platformConfig),
2222
- TAILOR_PLATFORM_OAUTH2_CLIENT_ID: getOAuth2ClientId(platformConfig)
2223
- };
2836
+ const env = {};
2224
2837
  const binPath = process.argv[1];
2225
2838
  if (binPath) env.TAILOR_BIN = binPath;
2226
2839
  try {
@@ -2229,6 +2842,16 @@ async function buildPluginEnv(options = {}) {
2229
2842
  } catch {}
2230
2843
  const configPath = loadConfigPath();
2231
2844
  if (configPath) env.TAILOR_CONFIG_PATH = configPath;
2845
+ if (options.skipPlatformContext) return env;
2846
+ let platformConfig;
2847
+ try {
2848
+ platformConfig = await loadPlatformClientConfig({
2849
+ profile,
2850
+ allowMissingProfile: true
2851
+ });
2852
+ } catch {}
2853
+ env.TAILOR_PLATFORM_URL = getPlatformBaseUrl(platformConfig);
2854
+ env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID = getOAuth2ClientId(platformConfig);
2232
2855
  try {
2233
2856
  env.TAILOR_PLATFORM_WORKSPACE_ID = await loadWorkspaceId({ profile });
2234
2857
  } catch {}
@@ -2300,21 +2923,72 @@ function buildSpawnTarget(pluginPath, args) {
2300
2923
  };
2301
2924
  }
2302
2925
  /**
2926
+ * Check whether args forwarded to a plugin carry an explicit env-file flag.
2927
+ * Scanning stops at a `--` terminator. The check is presence-only:
2928
+ * `--env-file-if-exists` counts even when the named file does not exist, so
2929
+ * no platform context is injected in that case either.
2930
+ * @param args - Args forwarded to the plugin
2931
+ * @returns Whether an `--env-file`/`-e`/`--env-file-if-exists` flag is present
2932
+ */
2933
+ function hasEnvFileFlag(args) {
2934
+ for (const token of args) {
2935
+ if (token === "--") return false;
2936
+ if (token === "--env-file" || token === "-e" || token === "--env-file-if-exists") return true;
2937
+ if (token.startsWith("--env-file=") || token.startsWith("-e=") || token.startsWith("--env-file-if-exists=")) return true;
2938
+ }
2939
+ return false;
2940
+ }
2941
+ /**
2942
+ * Extract an explicit `--profile`/`-p` value from args forwarded to a plugin,
2943
+ * so the injected platform context matches the profile the plugin will use.
2944
+ * Scanning stops at a `--` terminator; the last occurrence wins.
2945
+ * @param args - Args forwarded to the plugin
2946
+ * @returns The explicit profile value, or undefined when not present
2947
+ */
2948
+ function explicitProfileFromArgs(args) {
2949
+ let profile;
2950
+ for (let i = 0; i < args.length; i++) {
2951
+ const token = args[i];
2952
+ if (token === void 0 || token === "--") break;
2953
+ if (token === "--profile" || token === "-p") {
2954
+ const next = args[i + 1];
2955
+ if (next !== void 0 && !next.startsWith("-")) {
2956
+ profile = next;
2957
+ i++;
2958
+ }
2959
+ continue;
2960
+ }
2961
+ if (token.startsWith("--profile=")) profile = token.slice(10);
2962
+ else if (token.startsWith("-p=")) profile = token.slice(3);
2963
+ }
2964
+ return profile || void 0;
2965
+ }
2966
+ /**
2967
+ * Build the plugin slug from the known command path plus the unknown name,
2968
+ * so `tailor tailordb erd` resolves `tailor-tailordb-erd`.
2969
+ * @param commandPath - Parent command path preceding the unknown subcommand
2970
+ * @param name - Unknown subcommand name
2971
+ * @returns The plugin slug
2972
+ */
2973
+ function pluginSlug(commandPath, name) {
2974
+ return [...commandPath ?? [], name].join("-");
2975
+ }
2976
+ /**
2303
2977
  * Resolve and execute a plugin, forwarding stdio and propagating its exit code.
2304
2978
  * @param params - Dispatch parameters
2305
- * @param params.name - Plugin name (without the `<cli>-` prefix)
2306
- * @param params.args - Args to forward to the plugin
2307
- * @param params.cliName - The host CLI name
2308
- * @param params.profile - Active profile name, if any
2309
2979
  * @returns The plugin's exit code, or undefined when no matching plugin exists
2310
2980
  */
2311
2981
  async function dispatchPlugin(params) {
2312
- const slug = [...params.commandPath ?? [], params.name].join("-");
2982
+ const slug = pluginSlug(params.commandPath, params.name);
2313
2983
  const plugin = resolvePlugin(slug, params.cliName);
2314
2984
  if (!plugin) return;
2985
+ const profile = explicitProfileFromArgs(params.args) ?? params.profile;
2315
2986
  const env = {
2316
2987
  ...process.env,
2317
- ...await buildPluginEnv({ profile: params.profile })
2988
+ ...await buildPluginEnv({
2989
+ profile,
2990
+ skipPlatformContext: hasEnvFileFlag(params.args)
2991
+ })
2318
2992
  };
2319
2993
  const { command, args, shell } = buildSpawnTarget(plugin.path, params.args);
2320
2994
  return await new Promise((resolve) => {
@@ -2333,6 +3007,27 @@ async function dispatchPlugin(params) {
2333
3007
  });
2334
3008
  });
2335
3009
  }
3010
+ /**
3011
+ * Dispatch a plugin, printing an install hint when the subcommand maps to a
3012
+ * known plugin package that is not installed.
3013
+ * @param params - Dispatch parameters
3014
+ * @returns The plugin's exit code (1 after an install hint), or undefined when
3015
+ * the subcommand matches no plugin and no known package
3016
+ */
3017
+ async function dispatchPluginWithInstallHint(params) {
3018
+ const exitCode = await dispatchPlugin(params);
3019
+ if (exitCode !== void 0) return exitCode;
3020
+ const knownPackage = KNOWN_PLUGIN_PACKAGES[pluginSlug(params.commandPath, params.name)];
3021
+ if (!knownPackage) return;
3022
+ const fullCommand = [
3023
+ params.cliName,
3024
+ ...params.commandPath ?? [],
3025
+ params.name
3026
+ ].join(" ");
3027
+ logger.error(`"${fullCommand}" is provided by the ${knownPackage} CLI plugin, which is not installed.`);
3028
+ logger.info(`Install it with: npm install -D ${knownPackage}`);
3029
+ return 1;
3030
+ }
2336
3031
 
2337
3032
  //#endregion
2338
3033
  //#region src/cli/commands/plugin/list.ts
@@ -3135,10 +3830,10 @@ function lockPath(outputDir) {
3135
3830
  */
3136
3831
  function readLock(outputDir) {
3137
3832
  const file = lockPath(outputDir);
3138
- if (!fs$1.existsSync(file)) return null;
3833
+ if (!fs.existsSync(file)) return null;
3139
3834
  let parsed;
3140
3835
  try {
3141
- parsed = JSON.parse(fs$1.readFileSync(file, "utf-8"));
3836
+ parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
3142
3837
  } catch (cause) {
3143
3838
  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 });
3144
3839
  }
@@ -3154,8 +3849,8 @@ function readLock(outputDir) {
3154
3849
  */
3155
3850
  function writeLock(outputDir, lock) {
3156
3851
  const file = lockPath(outputDir);
3157
- fs$1.mkdirSync(path.dirname(file), { recursive: true });
3158
- fs$1.writeFileSync(file, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
3852
+ fs.mkdirSync(path.dirname(file), { recursive: true });
3853
+ fs.writeFileSync(file, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
3159
3854
  }
3160
3855
  /**
3161
3856
  * Find a lock target by identity. Targets are identified by (kind,
@@ -3171,23 +3866,23 @@ function findTarget(lock, kind, workspaceName) {
3171
3866
 
3172
3867
  //#endregion
3173
3868
  //#region src/cli/commands/setup/action.yml
3174
- 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 config:\n description: SDK config path, or comma-separated config paths for multi-config deploys\n required: false\n default: \"tailor.config.ts\"\n deploy:\n description: Whether to run the Tailor deploy step after build steps\n required: false\n default: \"true\"\n build-site:\n description: Whether to run the static website build slot when present\n required: false\n default: \"true\"\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 if: inputs.build-site == 'true'\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 if: inputs.deploy == 'true'\n uses: tailor-platform/actions/deploy@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n env:\n TAILOR_PLATFORM_SDK_CONFIG_PATH: ${{ inputs.config }}\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";
3869
+ 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 config:\n description: SDK config path, or comma-separated config paths for multi-config deploys\n required: false\n default: \"tailor.config.ts\"\n deploy:\n description: Whether to run the Tailor deploy step after build steps\n required: false\n default: \"true\"\n build-site:\n description: Whether to run the static website build slot when present\n required: false\n default: \"true\"\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 if: inputs.build-site == 'true'\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 if: inputs.deploy == 'true'\n uses: tailor-platform/actions/deploy@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n env:\n TAILOR_PLATFORM_SDK_CONFIG_PATH: ${{ inputs.config }}\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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";
3175
3870
 
3176
3871
  //#endregion
3177
3872
  //#region src/cli/commands/setup/branch.workflow.yml
3178
- 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";
3873
+ 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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.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 # __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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.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@820762786026740c76f36085b0efc47a31fe5020 # v7\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.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 # __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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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";
3179
3874
 
3180
3875
  //#endregion
3181
3876
  //#region src/cli/commands/setup/coordinate.workflow.yml
3182
- 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";
3877
+ 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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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";
3183
3878
 
3184
3879
  //#endregion
3185
3880
  //#region src/cli/commands/setup/preview.workflow.yml
3186
- 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";
3881
+ 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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n - id: tailor-preview-deploy\n uses: tailor-platform/actions/preview-deploy@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-preview-cleanup\n uses: tailor-platform/actions/preview-cleanup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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";
3187
3882
 
3188
3883
  //#endregion
3189
3884
  //#region src/cli/commands/setup/tag.workflow.yml
3190
- 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";
3885
+ 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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.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 # __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@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\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";
3191
3886
 
3192
3887
  //#endregion
3193
3888
  //#region src/cli/commands/setup/templates.ts
@@ -3206,10 +3901,10 @@ const HEADER = `# Generated by \`tailor setup\` — managed by the Tailor SDK.
3206
3901
  * @returns Detected package manager, defaults to npm
3207
3902
  */
3208
3903
  function detectPackageManager(dir) {
3209
- if (fs$1.existsSync(path.join(dir, "pnpm-lock.yaml"))) return "pnpm";
3210
- if (fs$1.existsSync(path.join(dir, "yarn.lock"))) return "yarn";
3211
- if (fs$1.existsSync(path.join(dir, "bun.lockb")) || fs$1.existsSync(path.join(dir, "bun.lock"))) return "bun";
3212
- if (fs$1.existsSync(path.join(dir, "package-lock.json"))) return "npm";
3904
+ if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) return "pnpm";
3905
+ if (fs.existsSync(path.join(dir, "yarn.lock"))) return "yarn";
3906
+ if (fs.existsSync(path.join(dir, "bun.lockb")) || fs.existsSync(path.join(dir, "bun.lock"))) return "bun";
3907
+ if (fs.existsSync(path.join(dir, "package-lock.json"))) return "npm";
3213
3908
  return "npm";
3214
3909
  }
3215
3910
  function block(content, name, keep) {
@@ -3357,7 +4052,8 @@ function renderActionWorkflow(params) {
3357
4052
  generatedIds: ["tailor-apply", "tailor-notify"]
3358
4053
  };
3359
4054
  }
3360
- const ACTIONS_SHA = "e74173d4bdd931d036ad359cf5f7729fc6aa7067";
4055
+ const ACTIONS_SHA = "a3c3032e14277e81039eb26ae1a425adb22eb532";
4056
+ const ACTIONS_VERSION = "v1.7.0";
3361
4057
  /**
3362
4058
  * Render the coordinator workflow that orchestrates per-app composite actions.
3363
4059
  *
@@ -3399,7 +4095,7 @@ function renderCoordinateWorkflow(params) {
3399
4095
  }
3400
4096
  const driftCheckStep = [
3401
4097
  `- id: tailor-drift-check`,
3402
- ` uses: tailor-platform/actions/drift-check@${ACTIONS_SHA} # v1.5.1`,
4098
+ ` uses: tailor-platform/actions/drift-check@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
3403
4099
  ` with:`,
3404
4100
  ` package-manager: ${packageManager}`
3405
4101
  ].join("\n");
@@ -3416,7 +4112,7 @@ function renderCoordinateWorkflow(params) {
3416
4112
  return [
3417
4113
  `- id: tailor-plan-${group.id}`,
3418
4114
  ` if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork`,
3419
- ` uses: tailor-platform/actions/plan@${ACTIONS_SHA} # v1.5.1`,
4115
+ ` uses: tailor-platform/actions/plan@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
3420
4116
  ` with:`,
3421
4117
  ` workspace-id: \${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}`,
3422
4118
  ` package-manager: ${packageManager}${wdLine}`,
@@ -3429,7 +4125,7 @@ function renderCoordinateWorkflow(params) {
3429
4125
  const multiConfigPlanStep = (group) => [
3430
4126
  `- id: tailor-plan-${group.id}`,
3431
4127
  ` if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork`,
3432
- ` uses: tailor-platform/actions/plan@${ACTIONS_SHA} # v1.5.1`,
4128
+ ` uses: tailor-platform/actions/plan@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
3433
4129
  ` env:`,
3434
4130
  ` TAILOR_PLATFORM_SDK_CONFIG_PATH: ${groupConfigPath(group)}`,
3435
4131
  ` with:`,
@@ -3446,7 +4142,7 @@ function renderCoordinateWorkflow(params) {
3446
4142
  const wdLine = wd ? `\n working-directory: ${wd}` : "";
3447
4143
  return [
3448
4144
  `- id: tailor-generate-check-${app.name}`,
3449
- ` uses: tailor-platform/actions/generate-check@${ACTIONS_SHA} # v1.5.1`,
4145
+ ` uses: tailor-platform/actions/generate-check@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
3450
4146
  ` with:`,
3451
4147
  ` package-manager: ${packageManager}${wdLine}`
3452
4148
  ];
@@ -3541,11 +4237,11 @@ function renderTailorSetupAction(params) {
3541
4237
  `runs:`,
3542
4238
  ` using: composite`,
3543
4239
  ` steps:`,
3544
- ` - uses: tailor-platform/actions/setup@${ACTIONS_SHA} # v1.5.1`,
4240
+ ` - uses: tailor-platform/actions/setup@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
3545
4241
  ` with:`,
3546
4242
  ` package-manager: ${packageManager}`,
3547
4243
  ` # Add custom steps here (e.g. private registry authentication)`,
3548
- ` - uses: tailor-platform/actions/install@${ACTIONS_SHA} # v1.5.1`,
4244
+ ` - uses: tailor-platform/actions/install@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
3549
4245
  ` with:`,
3550
4246
  ` package-manager: ${packageManager}`,
3551
4247
  ` # editable: install-command:`,
@@ -3594,9 +4290,8 @@ async function defaultLoadHasStaticWebsites$1(configPath) {
3594
4290
  const { config } = await loadConfig(configPath);
3595
4291
  return (config.staticWebsites?.length ?? 0) > 0;
3596
4292
  }
3597
- const WORKSPACE_NAME_RE = /^[a-z0-9-]+$/;
3598
4293
  function validateWorkspaceName(name) {
3599
- 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.`);
4294
+ if (!workspaceNameSchema.safeParse(name).success) 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.`);
3600
4295
  }
3601
4296
  const BRANCH_RE = /^[A-Za-z0-9._/-]+$/;
3602
4297
  const TAG_PATTERN_RE = /^[A-Za-z0-9._/*?![\]-]+$/;
@@ -3639,13 +4334,13 @@ function resolveConfigPath(outputDir, dir) {
3639
4334
  const appDir = path.resolve(outputDir, dir);
3640
4335
  const rel = path.relative(outputDir, appDir);
3641
4336
  if (path.isAbsolute(dir) || escapesRoot$1(rel)) throw new Error(`--dir must be a relative path inside the repository (got "${dir}").`);
3642
- if (fs$1.existsSync(appDir)) {
3643
- const realAppDir = path.normalize(fs$1.realpathSync(appDir));
3644
- const realOutputDir = path.normalize(fs$1.realpathSync(outputDir));
4337
+ if (fs.existsSync(appDir)) {
4338
+ const realAppDir = path.normalize(fs.realpathSync(appDir));
4339
+ const realOutputDir = path.normalize(fs.realpathSync(outputDir));
3645
4340
  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).`);
3646
4341
  }
3647
4342
  const configPath = path.join(appDir, "tailor.config.ts");
3648
- 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.`);
4343
+ if (!fs.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.`);
3649
4344
  return configPath;
3650
4345
  }
3651
4346
  /**
@@ -3881,8 +4576,8 @@ async function setupTarget(options) {
3881
4576
  file: resolved.file
3882
4577
  });
3883
4578
  const existing = findTarget(lock, resolved.kind, resolved.workspaceName);
3884
- const fileExists = fs$1.existsSync(absFile);
3885
- const currentContent = fileExists ? fs$1.readFileSync(absFile, "utf-8") : null;
4579
+ const fileExists = fs.existsSync(absFile);
4580
+ const currentContent = fileExists ? fs.readFileSync(absFile, "utf-8") : null;
3886
4581
  const normalize = resolved.kind === "action" ? normalizeActionContent : void 0;
3887
4582
  const decision = decideAction({
3888
4583
  existing,
@@ -3900,8 +4595,8 @@ async function setupTarget(options) {
3900
4595
  const existingBody = extractBuildSiteRunBody(currentContent);
3901
4596
  if (existingBody !== null) contentToWrite = injectBuildSiteRunBody(contentToWrite, existingBody);
3902
4597
  }
3903
- fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
3904
- fs$1.writeFileSync(absFile, contentToWrite, "utf-8");
4598
+ fs.mkdirSync(path.dirname(absFile), { recursive: true });
4599
+ fs.writeFileSync(absFile, contentToWrite, "utf-8");
3905
4600
  const newTarget = {
3906
4601
  kind: resolved.kind,
3907
4602
  workspaceName: resolved.workspaceName,
@@ -4005,21 +4700,21 @@ async function setupCoordinate(options) {
4005
4700
  file
4006
4701
  });
4007
4702
  const existing = findTarget(lock, "coordinate", coordinatorName);
4008
- const fileExists = fs$1.existsSync(absFile);
4703
+ const fileExists = fs.existsSync(absFile);
4009
4704
  const decision = decideAction({
4010
4705
  existing,
4011
4706
  fileExists,
4012
- currentContent: fileExists ? fs$1.readFileSync(absFile, "utf-8") : null,
4707
+ currentContent: fileExists ? fs.readFileSync(absFile, "utf-8") : null,
4013
4708
  force
4014
4709
  });
4015
4710
  if (decision.action === "conflict") throw new Error(`${file}: ${decision.reason}`);
4016
- fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
4017
- fs$1.writeFileSync(absFile, render.content, "utf-8");
4711
+ fs.mkdirSync(path.dirname(absFile), { recursive: true });
4712
+ fs.writeFileSync(absFile, render.content, "utf-8");
4018
4713
  const tailorSetupFile = ".github/actions/tailor-setup/action.yml";
4019
4714
  const absTailorSetupFile = path.join(outputDir, tailorSetupFile);
4020
- if (!fs$1.existsSync(absTailorSetupFile)) {
4021
- fs$1.mkdirSync(path.dirname(absTailorSetupFile), { recursive: true });
4022
- fs$1.writeFileSync(absTailorSetupFile, renderTailorSetupAction({ packageManager }), "utf-8");
4715
+ if (!fs.existsSync(absTailorSetupFile)) {
4716
+ fs.mkdirSync(path.dirname(absTailorSetupFile), { recursive: true });
4717
+ fs.writeFileSync(absTailorSetupFile, renderTailorSetupAction({ packageManager }), "utf-8");
4023
4718
  logger.success(`Generated ${styles.path(tailorSetupFile)}`);
4024
4719
  }
4025
4720
  const newTarget = {
@@ -4146,13 +4841,13 @@ function resolveWithinRoot(outputDir, relPath) {
4146
4841
  const abs = path.join(outputDir, relPath);
4147
4842
  if (escapesRoot(path.relative(outputDir, abs))) return null;
4148
4843
  try {
4149
- if (escapesRoot(path.relative(fs$1.realpathSync(outputDir), fs$1.realpathSync(abs)))) return null;
4844
+ if (escapesRoot(path.relative(fs.realpathSync(outputDir), fs.realpathSync(abs)))) return null;
4150
4845
  } catch {}
4151
4846
  return abs;
4152
4847
  }
4153
4848
  function readHash(absFile, normalize) {
4154
4849
  try {
4155
- const content = fs$1.readFileSync(absFile, "utf-8");
4850
+ const content = fs.readFileSync(absFile, "utf-8");
4156
4851
  return hashContent(normalize ? normalize(content) : content);
4157
4852
  } catch {
4158
4853
  return null;
@@ -4193,7 +4888,7 @@ async function checkGitHub(options) {
4193
4888
  }
4194
4889
  const slackToken = Boolean(process.env["TAILOR_SLACK_BOT_TOKEN"]);
4195
4890
  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.`);
4196
- const exists = options.configExistsAt ?? ((p) => fs$1.existsSync(p));
4891
+ const exists = options.configExistsAt ?? ((p) => fs.existsSync(p));
4197
4892
  const defaultBranch = detectDefaultBranchSafe(outputDir, options.gitRunner);
4198
4893
  const loadErdNamespaces = options.loadErdNamespaces ?? defaultLoadErdNamespaces;
4199
4894
  const loadHasMigrations = options.loadHasMigrations ?? defaultLoadHasMigrations;
@@ -4282,10 +4977,10 @@ async function setupDelete(options) {
4282
4977
  const remaining = [...lock.targets];
4283
4978
  try {
4284
4979
  for (const { relPath, absPath, target } of matches) {
4285
- fs$1.rmSync(absPath, { force: true });
4980
+ fs.rmSync(absPath, { force: true });
4286
4981
  if (target.kind === "action") {
4287
4982
  const dir = path.dirname(absPath);
4288
- if (fs$1.existsSync(dir) && fs$1.readdirSync(dir).length === 0) fs$1.rmdirSync(dir);
4983
+ if (fs.existsSync(dir) && fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);
4289
4984
  }
4290
4985
  const index = remaining.findIndex((t) => targetKey(t) === targetKey(target));
4291
4986
  if (index !== -1) remaining.splice(index, 1);
@@ -4489,203 +5184,6 @@ const setupCommand = defineCommand({
4489
5184
  }
4490
5185
  });
4491
5186
 
4492
- //#endregion
4493
- //#region src/cli/shared/progress.ts
4494
- /**
4495
- * Create a simple progress reporter that writes updates to stderr.
4496
- * @param label - Label to prefix progress output
4497
- * @param total - Total number of steps
4498
- * @returns Progress helpers
4499
- */
4500
- function createProgress(label, total) {
4501
- let current = 0;
4502
- const update = () => {
4503
- current += 1;
4504
- const percent = Math.round(current / total * 100);
4505
- process.stderr.write(`\r${label} ${current}/${total} (${percent}%)`);
4506
- };
4507
- const finish = () => {
4508
- process.stderr.write("\n");
4509
- };
4510
- return {
4511
- update,
4512
- finish
4513
- };
4514
- }
4515
- /**
4516
- * Wrap a promise with a timeout, rejecting if the timeout elapses first.
4517
- * @template T
4518
- * @param p - Promise to await
4519
- * @param ms - Timeout in milliseconds
4520
- * @param message - Error message on timeout
4521
- * @returns Result of the original promise if it completes in time
4522
- */
4523
- async function withTimeout(p, ms, message) {
4524
- return await Promise.race([p, setTimeout$1(ms).then(() => {
4525
- throw new Error(message);
4526
- })]);
4527
- }
4528
-
4529
- //#endregion
4530
- //#region src/cli/commands/staticwebsite/deploy.ts
4531
- const CHUNK_SIZE = 64 * 1024;
4532
- const IGNORED_FILES = /* @__PURE__ */ new Set([
4533
- ".DS_Store",
4534
- "thumbs.db",
4535
- "desktop.ini"
4536
- ]);
4537
- function shouldIgnoreFile(filePath) {
4538
- const fileName = path.basename(filePath).toLowerCase();
4539
- return IGNORED_FILES.has(fileName);
4540
- }
4541
- /**
4542
- * Deploy a static website by creating a deployment, uploading files, and publishing it.
4543
- * @param client - Operator client instance
4544
- * @param workspaceId - Workspace ID
4545
- * @param name - Static website name
4546
- * @param distDir - Directory containing static site files
4547
- * @param showProgress - Whether to show upload progress
4548
- * @returns Deployment result with URL and skipped files
4549
- */
4550
- async function deployStaticWebsite(client, workspaceId, name, distDir, showProgress = true) {
4551
- const { deploymentId } = await client.createDeployment({
4552
- workspaceId,
4553
- name
4554
- });
4555
- if (!deploymentId) throw new Error("createDeployment returned empty deploymentId");
4556
- const skippedFiles = await uploadDirectory(client, workspaceId, deploymentId, distDir, showProgress);
4557
- const { url } = await client.publishDeployment({
4558
- workspaceId,
4559
- deploymentId
4560
- });
4561
- if (!url) throw new Error("publishDeployment returned empty url");
4562
- return {
4563
- url,
4564
- skippedFiles
4565
- };
4566
- }
4567
- async function uploadDirectory(client, workspaceId, deploymentId, rootDir, showProgress) {
4568
- const files = await collectFiles(rootDir);
4569
- if (files.length === 0) {
4570
- logger.warn(`No files found under ${rootDir}`);
4571
- return [];
4572
- }
4573
- const limit = pLimit(5);
4574
- const total = files.length;
4575
- const progress = showProgress ? createProgress("Uploading files", total) : void 0;
4576
- const skippedFiles = [];
4577
- await Promise.all(files.map((relativePath) => limit(async () => {
4578
- await uploadSingleFile(client, workspaceId, deploymentId, rootDir, relativePath, skippedFiles);
4579
- if (progress) progress.update();
4580
- })));
4581
- if (progress) progress.finish();
4582
- return skippedFiles;
4583
- }
4584
- /**
4585
- * Recursively collect all deployable files under the given directory.
4586
- * @param rootDir - Root directory to scan
4587
- * @param currentDir - Current relative directory (for recursion)
4588
- * @returns List of file paths relative to rootDir
4589
- */
4590
- async function collectFiles(rootDir, currentDir = "") {
4591
- const dirPath = path.join(rootDir, currentDir);
4592
- const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
4593
- const files = [];
4594
- for (const entry of entries) {
4595
- const rel = path.join(currentDir, entry.name);
4596
- if (entry.isDirectory()) {
4597
- const sub = await collectFiles(rootDir, rel);
4598
- files.push(...sub);
4599
- } else if (entry.isFile() && !entry.isSymbolicLink() && !shouldIgnoreFile(rel)) files.push(rel);
4600
- }
4601
- return files;
4602
- }
4603
- async function uploadSingleFile(client, workspaceId, deploymentId, rootDir, filePath, skippedFiles) {
4604
- const absPath = path.join(rootDir, filePath);
4605
- const mime = lookup(filePath);
4606
- if (!mime) {
4607
- skippedFiles.push(`${filePath} (unsupported content type; no MIME mapping found)`);
4608
- return;
4609
- }
4610
- const contentType = mime;
4611
- const readStream = fs.createReadStream(absPath, { highWaterMark: CHUNK_SIZE });
4612
- async function* requestStream() {
4613
- yield { payload: {
4614
- case: "initialMetadata",
4615
- value: {
4616
- workspaceId,
4617
- deploymentId,
4618
- filePath,
4619
- contentType
4620
- }
4621
- } };
4622
- for await (const chunk of readStream) yield { payload: {
4623
- case: "chunkData",
4624
- value: chunk
4625
- } };
4626
- }
4627
- async function uploadWithLogging() {
4628
- try {
4629
- await client.uploadFile(requestStream());
4630
- } catch (error) {
4631
- if (error instanceof ConnectError && error.code === Code.InvalidArgument) {
4632
- skippedFiles.push(`${filePath} (server rejected file as invalid: ${error.message})`);
4633
- return;
4634
- }
4635
- throw error;
4636
- }
4637
- }
4638
- await withTimeout(uploadWithLogging(), 2 * 6e4, `Upload timed out for "${filePath}"`);
4639
- }
4640
- /**
4641
- * Log skipped files after a deployment, including reasons for skipping.
4642
- * @param skippedFiles - List of skipped file descriptions
4643
- */
4644
- function logSkippedFiles(skippedFiles) {
4645
- if (skippedFiles.length === 0) return;
4646
- logger.warn("Deployment completed, but some files failed to upload. These files may have unsupported content types or other validation issues. Please review the list below:");
4647
- for (const file of skippedFiles) logger.log(` - ${file}`);
4648
- }
4649
- const deployCommand = defineAppCommand({
4650
- name: "deploy",
4651
- description: "Deploy a static website from a local build directory.",
4652
- args: z.strictObject({
4653
- ...workspaceArgs,
4654
- name: arg(z.string(), {
4655
- alias: "n",
4656
- description: "Static website name"
4657
- }),
4658
- dir: arg(z.string(), {
4659
- alias: "d",
4660
- description: "Path to the static website files",
4661
- completion: { type: "directory" }
4662
- })
4663
- }),
4664
- run: async (args) => {
4665
- await assertWritable({ profile: args.profile });
4666
- logger.info(`Deploying static website "${args.name}" from directory: ${args.dir}`);
4667
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
4668
- const name = args.name;
4669
- const dir = path.resolve(process.cwd(), args.dir);
4670
- const workspaceId = await loadWorkspaceId({
4671
- workspaceId: args["workspace-id"],
4672
- profile: args.profile
4673
- });
4674
- if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) throw new Error(`Directory not found or not a directory: ${dir}`);
4675
- const { url, skippedFiles } = await withTimeout(deployStaticWebsite(client, workspaceId, name, dir, !args.json), 10 * 6e4, "Deployment timed out after 10 minutes.");
4676
- if (args.json) logger.out({
4677
- name,
4678
- workspaceId,
4679
- url,
4680
- skippedFiles
4681
- });
4682
- else {
4683
- logger.success(`Static website "${name}" deployed successfully. URL: ${url}`);
4684
- logSkippedFiles(skippedFiles);
4685
- }
4686
- }
4687
- });
4688
-
4689
5187
  //#endregion
4690
5188
  //#region src/cli/commands/staticwebsite/domain/status.ts
4691
5189
  const statusLabels = {
@@ -4896,7 +5394,7 @@ const staticwebsiteCommand = defineCommand({
4896
5394
  name: "staticwebsite",
4897
5395
  description: "Manage static websites in your workspace.",
4898
5396
  subCommands: {
4899
- deploy: deployCommand,
5397
+ deploy: deployCommand$1,
4900
5398
  domain: domainCommand,
4901
5399
  list: listCommand$2,
4902
5400
  get: getCommand
@@ -4906,1201 +5404,6 @@ const staticwebsiteCommand = defineCommand({
4906
5404
  }
4907
5405
  });
4908
5406
 
4909
- //#endregion
4910
- //#region src/cli/commands/tailordb/erd/local-schema.ts
4911
- /**
4912
- * Resolve TailorDB namespaces that need local type loading for ERD generation.
4913
- * @param config - Loaded Tailor config.
4914
- * @param options - Namespace selection options.
4915
- * @returns Namespace names to load, or undefined to load all owned namespaces.
4916
- */
4917
- function resolveLocalErdSchemaNamespaces(config, options) {
4918
- if (options.namespaces) return options.namespaces;
4919
- if (!options.requireErdSite) return;
4920
- return Object.entries(config.db ?? {}).flatMap(([namespace, dbConfig]) => "external" in dbConfig || !dbConfig.erdSite ? [] : [namespace]);
4921
- }
4922
- /**
4923
- * Load local TailorDB namespaces exactly as SDK generation/deploy sees them.
4924
- * @param options - Local schema loading options.
4925
- * @returns Loaded TailorDB namespace data.
4926
- */
4927
- async function loadLocalErdSchema(options) {
4928
- const { config, plugins } = await loadConfig(options.configPath);
4929
- await generateUserTypes({
4930
- config,
4931
- configPath: config.path
4932
- });
4933
- const application = defineApplication({
4934
- config,
4935
- pluginManager: plugins.length > 0 ? new PluginManager(plugins) : void 0
4936
- });
4937
- const namespaceNames = resolveLocalErdSchemaNamespaces(config, {
4938
- namespaces: options.namespaces,
4939
- requireErdSite: options.requireErdSite
4940
- });
4941
- const namespaceFilter = namespaceNames ? new Set(namespaceNames) : void 0;
4942
- const services = namespaceFilter ? application.tailorDBServices.filter((db) => namespaceFilter.has(db.namespace)) : application.tailorDBServices;
4943
- if (namespaceFilter && services.length !== namespaceFilter.size) {
4944
- const available = application.tailorDBServices.map((db) => db.namespace).join(", ");
4945
- const requested = [...namespaceFilter].join(", ");
4946
- throw new Error(`TailorDB namespace "${requested}" not found in local config.db.` + (available ? ` Available owned namespaces: ${available}` : ""));
4947
- }
4948
- const namespaces = [];
4949
- for (const db of services) {
4950
- await db.loadTypes();
4951
- await db.processNamespacePlugins();
4952
- namespaces.push({
4953
- namespace: db.namespace,
4954
- types: { ...db.types },
4955
- sourceInfo: new Map(Object.entries(db.typeSourceInfo)),
4956
- pluginAttachments: db.pluginAttachments
4957
- });
4958
- }
4959
- return {
4960
- config,
4961
- namespaces
4962
- };
4963
- }
4964
-
4965
- //#endregion
4966
- //#region src/cli/commands/tailordb/erd/schema.ts
4967
- const CLEAN_ROOM_NOTES = [
4968
- "Generated by a TailorDB-specific viewer implementation.",
4969
- "The implementation is based on TailorDB schema contracts, public Liam documentation, Liam CLI help, and black-box generated-output observation.",
4970
- "It does not copy Liam source code, generated JavaScript/CSS, parser internals, or layout internals."
4971
- ];
4972
- function buildRevision(schema) {
4973
- return hashContent$1(JSON.stringify(schema)).slice(0, 16);
4974
- }
4975
- function toTypeSource(source) {
4976
- if (!source) return void 0;
4977
- if (isPluginGeneratedType(source)) return {
4978
- kind: "plugin",
4979
- exportName: source.exportName,
4980
- pluginId: source.pluginId,
4981
- pluginImportPath: source.pluginImportPath,
4982
- originalExportName: source.originalExportName,
4983
- generatedTypeKind: source.generatedTypeKind,
4984
- namespace: source.namespace
4985
- };
4986
- return {
4987
- kind: "user",
4988
- exportName: source.exportName
4989
- };
4990
- }
4991
- function toRelationships(relationships) {
4992
- return Object.entries(relationships).toSorted(([a], [b]) => a.localeCompare(b)).map(([name, relationship]) => ({
4993
- name,
4994
- targetType: relationship.targetType,
4995
- targetField: relationship.targetField,
4996
- sourceField: relationship.sourceField,
4997
- isArray: relationship.isArray,
4998
- ...relationship.description && { description: relationship.description }
4999
- }));
5000
- }
5001
- function toIndexes(type) {
5002
- return Object.entries(type.indexes ?? {}).toSorted(([a], [b]) => a.localeCompare(b)).map(([name, index]) => ({
5003
- name,
5004
- fields: [...index.fields],
5005
- unique: index.unique === true
5006
- }));
5007
- }
5008
- function toColumnRelation(options) {
5009
- const { fieldConfig, parsedField } = options;
5010
- if (!fieldConfig.foreignKey || !fieldConfig.foreignKeyType) return;
5011
- const required = fieldConfig.required !== false;
5012
- const kind = parsedField?.relation || fieldConfig.rawRelation ? "relation" : "foreignKey";
5013
- return {
5014
- targetTable: fieldConfig.foreignKeyType,
5015
- targetColumn: fieldConfig.foreignKeyField || "id",
5016
- kind,
5017
- required,
5018
- ...fieldConfig.rawRelation?.type && { relationType: fieldConfig.rawRelation.type },
5019
- ...parsedField?.relation?.forwardName && { forwardName: parsedField.relation.forwardName },
5020
- ...parsedField?.relation?.backwardName && { backwardName: parsedField.relation.backwardName }
5021
- };
5022
- }
5023
- function toColumn(options) {
5024
- const { fieldName, fieldConfig } = options;
5025
- const indexNames = options.indexEntries.filter(([, index]) => index.fields.includes(fieldName)).map(([name]) => name);
5026
- const uniqueIndexNames = options.indexEntries.filter(([, index]) => index.unique && index.fields.includes(fieldName)).map(([name]) => name);
5027
- const enumValues = fieldConfig.allowedValues?.map((value) => value.value) ?? [];
5028
- const enumValueDescriptions = Object.fromEntries((fieldConfig.allowedValues ?? []).flatMap((value) => value.description ? [[value.value, value.description]] : []));
5029
- const nestedFields = Object.entries(fieldConfig.fields ?? {}).map(([nestedName, nestedConfig]) => toColumn({
5030
- fieldName: nestedName,
5031
- fieldConfig: nestedConfig,
5032
- indexEntries: []
5033
- }));
5034
- const relation = toColumnRelation(options);
5035
- return {
5036
- name: fieldName,
5037
- type: fieldConfig.type || "string",
5038
- required: fieldConfig.required !== false,
5039
- array: fieldConfig.array === true,
5040
- ...fieldConfig.description && { description: fieldConfig.description },
5041
- ...fieldConfig.unique && { unique: true },
5042
- ...(fieldConfig.index || indexNames.length > 0) && { index: true },
5043
- ...indexNames.length > 0 && { indexNames },
5044
- ...uniqueIndexNames.length > 0 && { uniqueIndexNames },
5045
- ...enumValues.length > 0 && { enumValues },
5046
- ...Object.keys(enumValueDescriptions).length > 0 && { enumValueDescriptions },
5047
- ...fieldConfig.vector && { vector: true },
5048
- ...fieldConfig.serial && { serial: { ...fieldConfig.serial } },
5049
- ...fieldConfig.scale !== void 0 && { scale: fieldConfig.scale },
5050
- ...fieldConfig.validate?.length && { validations: fieldConfig.validate.length },
5051
- ...fieldConfig.hooks && { hooks: {
5052
- ...fieldConfig.hooks.create && { create: true },
5053
- ...fieldConfig.hooks.update && { update: true }
5054
- } },
5055
- ...nestedFields.length > 0 && { fields: nestedFields },
5056
- ...relation && { relation }
5057
- };
5058
- }
5059
- function toTable(type, source) {
5060
- const indexes = toIndexes(type);
5061
- const indexEntries = indexes.map((index) => [index.name, index]);
5062
- const fieldColumns = Object.entries(type.fields).filter(([fieldName]) => fieldName !== "id").map(([fieldName, field]) => toColumn({
5063
- fieldName,
5064
- fieldConfig: field.config,
5065
- parsedField: field,
5066
- indexEntries
5067
- }));
5068
- const typeSource = toTypeSource(source);
5069
- return {
5070
- name: type.name,
5071
- pluralForm: type.pluralForm,
5072
- ...type.description && { description: type.description },
5073
- ...typeSource && { source: typeSource },
5074
- columns: [{
5075
- name: "id",
5076
- type: "uuid",
5077
- required: true,
5078
- array: false,
5079
- primaryKey: true,
5080
- unique: true
5081
- }, ...fieldColumns],
5082
- indexes,
5083
- forwardRelationships: toRelationships(type.forwardRelationships),
5084
- backwardRelationships: toRelationships(type.backwardRelationships)
5085
- };
5086
- }
5087
- function toRelation(sourceTable, field) {
5088
- const relation = toColumnRelation({
5089
- fieldName: field.name,
5090
- fieldConfig: field.config,
5091
- parsedField: field,
5092
- indexEntries: []
5093
- });
5094
- if (!relation) return void 0;
5095
- return {
5096
- name: `${sourceTable}.${field.name}->${relation.targetTable}.${relation.targetColumn}`,
5097
- sourceTable,
5098
- sourceColumns: [field.name],
5099
- targetTable: relation.targetTable,
5100
- targetColumns: [relation.targetColumn],
5101
- required: relation.required,
5102
- unique: field.config.unique === true || field.relation?.unique === true,
5103
- kind: relation.kind,
5104
- ...relation.relationType && { relationType: relation.relationType },
5105
- ...relation.forwardName && { forwardName: relation.forwardName },
5106
- ...relation.backwardName && { backwardName: relation.backwardName }
5107
- };
5108
- }
5109
- function buildRelations(types) {
5110
- const relations = [];
5111
- for (const type of Object.values(types)) for (const field of Object.values(type.fields)) {
5112
- const relation = toRelation(type.name, field);
5113
- if (relation) relations.push(relation);
5114
- }
5115
- return relations.toSorted((a, b) => a.name.localeCompare(b.name));
5116
- }
5117
- /**
5118
- * Build the TailorDB ERD viewer schema for one namespace.
5119
- * @param options - Schema build options.
5120
- * @returns TailorDB ERD viewer schema.
5121
- */
5122
- function buildTailorDbErdSchema(options) {
5123
- const { namespaceData } = options;
5124
- const tables = Object.values(namespaceData.types).toSorted((a, b) => a.name.localeCompare(b.name)).map((type) => toTable(type, namespaceData.sourceInfo.get(type.name)));
5125
- const schemaWithoutRevision = {
5126
- version: 1,
5127
- namespace: namespaceData.namespace,
5128
- source: options.source ?? "local",
5129
- cleanRoom: {
5130
- implementation: "tailor",
5131
- notes: CLEAN_ROOM_NOTES
5132
- },
5133
- tables,
5134
- relations: buildRelations(namespaceData.types)
5135
- };
5136
- return {
5137
- ...schemaWithoutRevision,
5138
- generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
5139
- revision: buildRevision(schemaWithoutRevision)
5140
- };
5141
- }
5142
-
5143
- //#endregion
5144
- //#region src/cli/commands/tailordb/erd/utils.ts
5145
- /**
5146
- * Initialize shared ERD command behavior.
5147
- */
5148
- function initErdCommand() {
5149
- logBetaWarning("tailordb erd");
5150
- }
5151
- /**
5152
- * Initialize platform context for ERD deployment.
5153
- * @param args - CLI arguments.
5154
- * @returns Initialized deploy context.
5155
- */
5156
- async function initErdDeployContext(args) {
5157
- initErdCommand();
5158
- return {
5159
- client: await initOperatorClient(await loadAccessToken({ profile: args.profile })),
5160
- workspaceId: await loadWorkspaceId({
5161
- workspaceId: args.workspaceId,
5162
- profile: args.profile
5163
- })
5164
- };
5165
- }
5166
-
5167
- //#endregion
5168
- //#region src/cli/commands/tailordb/erd/viewer.ts
5169
- const VIEWER_ASSETS_DIR = "erd-viewer-assets";
5170
- const STYLES_LINK = "<link rel=\"stylesheet\" href=\"./styles.css\" />";
5171
- const APP_SCRIPT = "<script src=\"./app.js\" type=\"module\"><\/script>";
5172
- function escapeHtml(value) {
5173
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
5174
- }
5175
- function assetDirCandidates() {
5176
- const currentDir = path.dirname(fileURLToPath(import.meta.url));
5177
- return [
5178
- path.join(currentDir, "viewer-assets"),
5179
- path.join(currentDir, VIEWER_ASSETS_DIR),
5180
- path.join(currentDir, "commands", "tailordb", "erd", VIEWER_ASSETS_DIR),
5181
- path.resolve(process.cwd(), "packages/sdk/src/cli/commands/tailordb/erd/viewer-assets")
5182
- ];
5183
- }
5184
- /**
5185
- * Resolve the packaged ERD viewer asset directory.
5186
- * @returns Absolute path to the viewer asset directory.
5187
- */
5188
- function resolveViewerAssetsDir() {
5189
- for (const candidate of assetDirCandidates()) if (fs$1.existsSync(path.join(candidate, "index.html"))) return candidate;
5190
- throw new Error(`ERD viewer assets not found. Checked: ${assetDirCandidates().join(", ")}`);
5191
- }
5192
- /**
5193
- * Build the self-contained ERD viewer HTML document. CSS, JS, and the schema
5194
- * are inlined as separately extractable blocks: a `<style>` element, a
5195
- * `<script type="module">`, and a `<script type="application/json"
5196
- * id="erd-schema">` data block. This renders without any sibling asset files
5197
- * and lets external tooling (including the ERD diff command) pull out the
5198
- * schema via `JSON.parse`.
5199
- * @param options - Viewer build options.
5200
- * @returns The self-contained HTML document.
5201
- */
5202
- function buildViewerHtml(options) {
5203
- const assetsDir = resolveViewerAssetsDir();
5204
- const html = fs$1.readFileSync(path.join(assetsDir, "index.html"), "utf8");
5205
- const css = fs$1.readFileSync(path.join(assetsDir, "styles.css"), "utf8");
5206
- const appJs = fs$1.readFileSync(path.join(assetsDir, "app.js"), "utf8");
5207
- if (!html.includes(STYLES_LINK) || !html.includes(APP_SCRIPT)) throw new Error("ERD viewer index.html is missing expected asset references for inlining.");
5208
- const embedScript = `<script type="application/json" id="erd-schema">${JSON.stringify(options.schema).replaceAll("<", "\\u003c")}<\/script>`;
5209
- const currentSchemaScript = options.currentSchema === void 0 ? "" : `\n <script type="application/json" id="erd-current-schema">${JSON.stringify(options.currentSchema).replaceAll("<", "\\u003c")}<\/script>`;
5210
- const diffScript = options.diff === void 0 ? "" : `\n <script type="application/json" id="erd-diff">${JSON.stringify(options.diff).replaceAll("<", "\\u003c")}<\/script>`;
5211
- const inlineScript = `<script type="module">\n${appJs.replace(/<\/script/gi, "<\\/script")}\n<\/script>`;
5212
- 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}`);
5213
- }
5214
- /**
5215
- * Write the self-contained TailorDB ERD viewer to `<distDir>/index.html`.
5216
- * @param options - Viewer dist write options.
5217
- */
5218
- function writeViewerDist(options) {
5219
- fs$1.rmSync(options.distDir, {
5220
- recursive: true,
5221
- force: true
5222
- });
5223
- fs$1.mkdirSync(options.distDir, { recursive: true });
5224
- fs$1.writeFileSync(path.join(options.distDir, "index.html"), buildViewerHtml({ schema: options.schema }), "utf8");
5225
- }
5226
-
5227
- //#endregion
5228
- //#region src/cli/commands/tailordb/erd/export.ts
5229
- const DEFAULT_ERD_BASE_DIR$1 = ".tailor/erd";
5230
- function getErdSite(context, namespace) {
5231
- const dbConfig = context.config.db?.[namespace];
5232
- if (!dbConfig || "external" in dbConfig) return;
5233
- return dbConfig.erdSite;
5234
- }
5235
- function resolveExplicitTarget(options) {
5236
- const namespaceData = options.context.namespaces.find((candidate) => candidate.namespace === options.namespace);
5237
- if (!namespaceData) {
5238
- const available = options.context.namespaces.map((candidate) => candidate.namespace).join(", ");
5239
- throw new Error(`TailorDB namespace "${options.namespace}" not found in local config.db.` + (available ? ` Available owned namespaces: ${available}` : ""));
5240
- }
5241
- const erdSite = getErdSite(options.context, namespaceData.namespace);
5242
- if (options.requireErdSite && !erdSite) throw new Error(`No erdSite configured for namespace "${namespaceData.namespace}". Add erdSite: "<static-website-name>" to db.${namespaceData.namespace} in tailor.config.ts.`);
5243
- return toTarget(options.outputDir, namespaceData, erdSite);
5244
- }
5245
- function resolveAllTargets(options) {
5246
- const namespaces = options.context.namespaces.filter((namespaceData) => !options.requireErdSite || getErdSite(options.context, namespaceData.namespace));
5247
- if (namespaces.length === 0) throw new Error(options.requireErdSite ? "No namespaces with erdSite configured found. Add erdSite: \"<static-website-name>\" to db.<namespace> in tailor.config.ts." : "No TailorDB namespaces found in config. Please define db services in tailor.config.ts.");
5248
- logger.info(`Found ${namespaces.length} namespace(s)${options.requireErdSite ? " with erdSite configured" : ""}.`);
5249
- return namespaces.map((namespaceData) => toTarget(options.outputDir, namespaceData, getErdSite(options.context, namespaceData.namespace)));
5250
- }
5251
- function toTarget(outputDir, namespaceData, erdSite) {
5252
- return {
5253
- namespaceData,
5254
- erdSite,
5255
- distDir: path.join(outputDir, namespaceData.namespace, "dist")
5256
- };
5257
- }
5258
- function resolveTargets(options) {
5259
- if (options.namespace) return [resolveExplicitTarget(options)];
5260
- return resolveAllTargets(options);
5261
- }
5262
- function prepareErdBuild(target) {
5263
- writeViewerDist({
5264
- schema: buildTailorDbErdSchema({ namespaceData: target.namespaceData }),
5265
- distDir: target.distDir
5266
- });
5267
- const relativePath = path.relative(process.cwd(), target.distDir);
5268
- logger.success(`Built ERD to ${relativePath}`);
5269
- return {
5270
- namespace: target.namespaceData.namespace,
5271
- erdSite: target.erdSite,
5272
- distDir: target.distDir
5273
- };
5274
- }
5275
- /**
5276
- * Prepare TailorDB ERD static viewer builds for one or more namespaces.
5277
- * @param options - Build options.
5278
- * @returns Build results by namespace.
5279
- */
5280
- async function prepareErdBuilds(options) {
5281
- return prepareErdBuildsFromContext({
5282
- context: await loadLocalErdSchema({
5283
- configPath: options.configPath,
5284
- namespaces: options.namespace ? [options.namespace] : void 0,
5285
- requireErdSite: options.requireErdSite
5286
- }),
5287
- namespace: options.namespace,
5288
- outputDir: options.outputDir,
5289
- requireErdSite: options.requireErdSite
5290
- });
5291
- }
5292
- /**
5293
- * Prepare TailorDB ERD static viewer builds from an already loaded schema context.
5294
- * @param options - Build options.
5295
- * @returns Build results by namespace.
5296
- */
5297
- function prepareErdBuildsFromContext(options) {
5298
- const outputDir = path.resolve(process.cwd(), options.outputDir ?? DEFAULT_ERD_BASE_DIR$1);
5299
- return resolveTargets({
5300
- context: options.context,
5301
- namespace: options.namespace,
5302
- outputDir,
5303
- requireErdSite: options.requireErdSite
5304
- }).map((target) => prepareErdBuild(target));
5305
- }
5306
- const erdExportCommand = defineAppCommand({
5307
- name: "export",
5308
- description: "Export TailorDB ERD static viewer from local TailorDB schema.",
5309
- args: z.strictObject({
5310
- ...configArg,
5311
- namespace: arg(z.string().optional(), {
5312
- alias: "n",
5313
- description: "TailorDB namespace name (optional if only one namespace is defined in config)"
5314
- }),
5315
- output: arg(z.string().default(DEFAULT_ERD_BASE_DIR$1), {
5316
- alias: "o",
5317
- description: "Output directory path for TailorDB ERD viewer files (writes to `<outputDir>/<namespace>/dist`)",
5318
- completion: { type: "directory" }
5319
- })
5320
- }),
5321
- run: async (args) => {
5322
- initErdCommand();
5323
- const results = await prepareErdBuilds({
5324
- configPath: args.config,
5325
- namespace: args.namespace,
5326
- outputDir: args.output
5327
- });
5328
- logger.newline();
5329
- if (args.json) logger.out(results.map((result) => ({
5330
- namespace: result.namespace,
5331
- distDir: result.distDir
5332
- })));
5333
- else for (const result of results) {
5334
- logger.out(`Exported ERD for namespace "${result.namespace}"`);
5335
- logger.out(` - ERD viewer: ${path.join(result.distDir, "index.html")}`);
5336
- }
5337
- }
5338
- });
5339
-
5340
- //#endregion
5341
- //#region src/cli/commands/tailordb/erd/deploy.ts
5342
- const erdDeployCommand = defineAppCommand({
5343
- name: "deploy",
5344
- description: "Deploy ERD static website for TailorDB namespace(s).",
5345
- args: z.strictObject({
5346
- ...deploymentArgs,
5347
- namespace: arg(z.string().optional(), {
5348
- alias: "n",
5349
- description: "TailorDB namespace name (optional - deploys all namespaces with erdSite if omitted)"
5350
- })
5351
- }),
5352
- run: async (args) => {
5353
- await assertWritable({ profile: args.profile });
5354
- const { client, workspaceId } = await initErdDeployContext(args);
5355
- const buildResults = await prepareErdBuilds({
5356
- configPath: args.config,
5357
- namespace: args.namespace,
5358
- requireErdSite: true
5359
- });
5360
- const deployResults = await Promise.all(buildResults.map(async (result) => {
5361
- if (!result.erdSite) throw new Error(`No erdSite configured for namespace "${result.namespace}". Add erdSite: "<static-website-name>" to db.${result.namespace} in tailor.config.ts.`);
5362
- if (!args.json) logger.info(`Deploying ERD for namespace "${result.namespace}" to site "${result.erdSite}"...`);
5363
- const { url, skippedFiles } = await deployStaticWebsite(client, workspaceId, result.erdSite, result.distDir, !args.json);
5364
- return {
5365
- namespace: result.namespace,
5366
- erdSite: result.erdSite,
5367
- url,
5368
- skippedFiles
5369
- };
5370
- }));
5371
- logger.newline();
5372
- if (args.json) logger.out(deployResults);
5373
- else for (const result of deployResults) {
5374
- logSkippedFiles(result.skippedFiles);
5375
- logger.newline();
5376
- logger.success(`ERD site "${result.erdSite}" deployed successfully.`);
5377
- logger.out(result.url);
5378
- }
5379
- }
5380
- });
5381
-
5382
- //#endregion
5383
- //#region src/cli/commands/tailordb/erd/diff.ts
5384
- const SCHEMA_BLOCK_PATTERN = /<script type="application\/json" id="erd-schema">([\s\S]*?)<\/script>/;
5385
- function stableValue(value) {
5386
- if (Array.isArray(value)) return value.map((item) => stableValue(item));
5387
- if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, stableValue(item)]));
5388
- return value;
5389
- }
5390
- function stableJson(value) {
5391
- return JSON.stringify(stableValue(value));
5392
- }
5393
- function changedFields(base, head) {
5394
- return [.../* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(head)])].toSorted((a, b) => a.localeCompare(b)).filter((key) => stableJson(base[key]) !== stableJson(head[key]));
5395
- }
5396
- function detailForChangedFields(fields) {
5397
- return `Changed fields: ${fields.join(", ")}`;
5398
- }
5399
- function mapByName(items) {
5400
- return new Map(items.map((item) => [item.name, item]));
5401
- }
5402
- function compareCommonNamed(baseItems, headItems, addChange) {
5403
- const baseByName = mapByName(baseItems);
5404
- const headByName = mapByName(headItems);
5405
- const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5406
- for (const name of commonNames) {
5407
- const base = baseByName.get(name);
5408
- const head = headByName.get(name);
5409
- if (!base || !head) continue;
5410
- const fields = changedFields(base, head);
5411
- if (fields.length > 0) addChange(name, fields);
5412
- }
5413
- }
5414
- function removedNames(baseItems, headItems) {
5415
- const headByName = mapByName(headItems);
5416
- return baseItems.map((item) => item.name).filter((name) => !headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5417
- }
5418
- function addedNames(baseItems, headItems) {
5419
- const baseByName = mapByName(baseItems);
5420
- return headItems.map((item) => item.name).filter((name) => !baseByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5421
- }
5422
- function tableMetadata(table) {
5423
- return {
5424
- backwardRelationships: table.backwardRelationships,
5425
- description: table.description,
5426
- forwardRelationships: table.forwardRelationships,
5427
- pluralForm: table.pluralForm,
5428
- source: table.source
5429
- };
5430
- }
5431
- function pushChange(changes, change) {
5432
- changes.push(change);
5433
- }
5434
- function diffColumns(changes, tableName, baseColumns, headColumns) {
5435
- compareCommonNamed(baseColumns, headColumns, (name, fields) => {
5436
- pushChange(changes, {
5437
- action: "changed",
5438
- entity: "column",
5439
- path: `${tableName}.${name}`,
5440
- detail: detailForChangedFields(fields)
5441
- });
5442
- });
5443
- for (const name of removedNames(baseColumns, headColumns)) pushChange(changes, {
5444
- action: "removed",
5445
- entity: "column",
5446
- path: `${tableName}.${name}`,
5447
- detail: "Column removed"
5448
- });
5449
- for (const name of addedNames(baseColumns, headColumns)) pushChange(changes, {
5450
- action: "added",
5451
- entity: "column",
5452
- path: `${tableName}.${name}`,
5453
- detail: "Column added"
5454
- });
5455
- }
5456
- function diffIndexes(changes, tableName, baseIndexes, headIndexes) {
5457
- compareCommonNamed(baseIndexes, headIndexes, (name, fields) => {
5458
- pushChange(changes, {
5459
- action: "changed",
5460
- entity: "index",
5461
- path: `${tableName}.${name}`,
5462
- detail: detailForChangedFields(fields)
5463
- });
5464
- });
5465
- for (const name of removedNames(baseIndexes, headIndexes)) pushChange(changes, {
5466
- action: "removed",
5467
- entity: "index",
5468
- path: `${tableName}.${name}`,
5469
- detail: "Index removed"
5470
- });
5471
- for (const name of addedNames(baseIndexes, headIndexes)) pushChange(changes, {
5472
- action: "added",
5473
- entity: "index",
5474
- path: `${tableName}.${name}`,
5475
- detail: "Index added"
5476
- });
5477
- }
5478
- function diffTables(changes, baseTables, headTables) {
5479
- const baseByName = mapByName(baseTables);
5480
- const headByName = mapByName(headTables);
5481
- for (const name of removedNames(baseTables, headTables)) pushChange(changes, {
5482
- action: "removed",
5483
- entity: "table",
5484
- path: name,
5485
- detail: "Table removed"
5486
- });
5487
- for (const name of addedNames(baseTables, headTables)) pushChange(changes, {
5488
- action: "added",
5489
- entity: "table",
5490
- path: name,
5491
- detail: "Table added"
5492
- });
5493
- const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5494
- for (const name of commonNames) {
5495
- const base = baseByName.get(name);
5496
- const head = headByName.get(name);
5497
- if (!base || !head) continue;
5498
- const tableFields = changedFields(tableMetadata(base), tableMetadata(head));
5499
- if (tableFields.length > 0) pushChange(changes, {
5500
- action: "changed",
5501
- entity: "table",
5502
- path: name,
5503
- detail: detailForChangedFields(tableFields)
5504
- });
5505
- diffColumns(changes, name, base.columns, head.columns);
5506
- diffIndexes(changes, name, base.indexes, head.indexes);
5507
- }
5508
- }
5509
- function diffRelations(changes, baseRelations, headRelations) {
5510
- compareCommonNamed(baseRelations, headRelations, (name, fields) => {
5511
- pushChange(changes, {
5512
- action: "changed",
5513
- entity: "relation",
5514
- path: name,
5515
- detail: detailForChangedFields(fields)
5516
- });
5517
- });
5518
- for (const name of removedNames(baseRelations, headRelations)) pushChange(changes, {
5519
- action: "removed",
5520
- entity: "relation",
5521
- path: name,
5522
- detail: "Relation removed"
5523
- });
5524
- for (const name of addedNames(baseRelations, headRelations)) pushChange(changes, {
5525
- action: "added",
5526
- entity: "relation",
5527
- path: name,
5528
- detail: "Relation added"
5529
- });
5530
- }
5531
- function summarize(changes) {
5532
- return {
5533
- added: changes.filter((change) => change.action === "added").length,
5534
- changed: changes.filter((change) => change.action === "changed").length,
5535
- removed: changes.filter((change) => change.action === "removed").length
5536
- };
5537
- }
5538
- function extractEmbeddedErdSchema(html) {
5539
- const match = SCHEMA_BLOCK_PATTERN.exec(html);
5540
- if (!match?.[1]) throw new Error("ERD schema block not found.");
5541
- try {
5542
- return JSON.parse(match[1]);
5543
- } catch (error) {
5544
- throw new Error(`Failed to parse ERD schema block: ${String(error)}`, { cause: error });
5545
- }
5546
- }
5547
- function createEmptyErdSchema(options) {
5548
- return {
5549
- version: 1,
5550
- namespace: options.namespace,
5551
- generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
5552
- revision: options.revision,
5553
- source: "local",
5554
- cleanRoom: {
5555
- implementation: "tailor",
5556
- notes: ["Synthetic empty schema used for ERD diff generation."]
5557
- },
5558
- tables: [],
5559
- relations: []
5560
- };
5561
- }
5562
- function buildErdSchemaDiff(options) {
5563
- const { base, head } = options;
5564
- if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
5565
- const changes = [];
5566
- diffTables(changes, base.tables, head.tables);
5567
- diffRelations(changes, base.relations, head.relations);
5568
- const summary = summarize(changes);
5569
- return {
5570
- namespace: head.namespace,
5571
- baseRevision: base.revision,
5572
- headRevision: head.revision,
5573
- changed: changes.length > 0,
5574
- summary,
5575
- changes
5576
- };
5577
- }
5578
- function mergeNamedByHead(baseItems, headItems) {
5579
- const headByName = mapByName(headItems);
5580
- const removedItems = baseItems.filter((item) => !headByName.has(item.name)).toSorted((a, b) => a.name.localeCompare(b.name));
5581
- return [...headItems, ...removedItems];
5582
- }
5583
- function mergeDiffViewerTable(baseTable, headTable) {
5584
- return {
5585
- ...headTable,
5586
- columns: mergeNamedByHead(baseTable.columns, headTable.columns),
5587
- indexes: mergeNamedByHead(baseTable.indexes, headTable.indexes)
5588
- };
5589
- }
5590
- function mergeDiffViewerTables(baseTables, headTables) {
5591
- const baseByName = mapByName(baseTables);
5592
- const headByName = mapByName(headTables);
5593
- const merged = headTables.map((headTable) => {
5594
- const baseTable = baseByName.get(headTable.name);
5595
- return baseTable ? mergeDiffViewerTable(baseTable, headTable) : headTable;
5596
- });
5597
- const removedTables = baseTables.filter((table) => !headByName.has(table.name)).toSorted((a, b) => a.name.localeCompare(b.name));
5598
- return [...merged, ...removedTables];
5599
- }
5600
- /**
5601
- * Build the schema rendered by the diff viewer. The ordinary ERD viewer only
5602
- * knows how to draw objects present in its schema, so the diff schema keeps
5603
- * base-only tables, columns, indexes, and relations visible for removal
5604
- * highlighting while preferring head-side metadata for unchanged objects.
5605
- * @param options - Base and head schemas to merge for diff rendering.
5606
- * @returns A TailorDB ERD schema suitable for the visual diff viewer.
5607
- */
5608
- function buildErdDiffViewerSchema(options) {
5609
- const { base, head } = options;
5610
- if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
5611
- return {
5612
- ...head,
5613
- tables: mergeDiffViewerTables(base.tables, head.tables),
5614
- relations: mergeNamedByHead(base.relations, head.relations)
5615
- };
5616
- }
5617
- function renderErdDiffHtml(options) {
5618
- return buildViewerHtml({
5619
- schema: options.schema,
5620
- currentSchema: options.currentSchema,
5621
- diff: options.diff,
5622
- title: `TailorDB ERD diff - ${options.diff.namespace}`
5623
- });
5624
- }
5625
-
5626
- //#endregion
5627
- //#region src/cli/commands/tailordb/erd/diff-command.ts
5628
- function readSchema(filePath) {
5629
- if (!filePath) return void 0;
5630
- return extractEmbeddedErdSchema(fs$1.readFileSync(filePath, "utf8"));
5631
- }
5632
- function resolveNamespace(options) {
5633
- const namespace = options.namespace ?? options.head?.namespace ?? options.base?.namespace;
5634
- if (!namespace) throw new Error("Missing --namespace when one side of the ERD diff is omitted.");
5635
- if (options.base && options.base.namespace !== namespace) throw new Error(`Base ERD namespace "${options.base.namespace}" does not match "${namespace}".`);
5636
- if (options.head && options.head.namespace !== namespace) throw new Error(`Head ERD namespace "${options.head.namespace}" does not match "${namespace}".`);
5637
- return namespace;
5638
- }
5639
- function writeFile(filePath, content) {
5640
- fs$1.mkdirSync(path.dirname(filePath), { recursive: true });
5641
- fs$1.writeFileSync(filePath, content, "utf8");
5642
- }
5643
- function writeErdDiff(options) {
5644
- const base = readSchema(options.baseHtml);
5645
- const head = readSchema(options.headHtml);
5646
- if (!base && !head) throw new Error("At least one of --base-html or --head-html is required.");
5647
- const namespace = resolveNamespace({
5648
- namespace: options.namespace,
5649
- base,
5650
- head
5651
- });
5652
- const baseSchema = base ?? createEmptyErdSchema({
5653
- namespace,
5654
- revision: "missing-base"
5655
- });
5656
- const headSchema = head ?? createEmptyErdSchema({
5657
- namespace,
5658
- revision: "missing-head"
5659
- });
5660
- const diff = buildErdSchemaDiff({
5661
- base: baseSchema,
5662
- head: headSchema
5663
- });
5664
- const viewerSchema = buildErdDiffViewerSchema({
5665
- base: baseSchema,
5666
- head: headSchema
5667
- });
5668
- writeFile(options.outputHtml, renderErdDiffHtml({
5669
- schema: viewerSchema,
5670
- currentSchema: headSchema,
5671
- diff
5672
- }));
5673
- if (options.outputJson) writeFile(options.outputJson, `${JSON.stringify(diff, null, 2)}\n`);
5674
- return {
5675
- namespace,
5676
- outputHtml: options.outputHtml,
5677
- outputJson: options.outputJson,
5678
- diff
5679
- };
5680
- }
5681
- const erdDiffCommand = defineAppCommand({
5682
- name: "diff",
5683
- description: "Render TailorDB ERD schema diff HTML from exported ERD viewers.",
5684
- args: z.strictObject({
5685
- "base-html": arg(z.string().optional(), {
5686
- description: "Base ERD viewer HTML file",
5687
- completion: {
5688
- type: "file",
5689
- matcher: [".html"]
5690
- }
5691
- }),
5692
- "head-html": arg(z.string().optional(), {
5693
- description: "Head ERD viewer HTML file",
5694
- completion: {
5695
- type: "file",
5696
- matcher: [".html"]
5697
- }
5698
- }),
5699
- namespace: arg(z.string().optional(), {
5700
- alias: "n",
5701
- description: "TailorDB namespace name (defaults to the provided ERD schema namespace)"
5702
- }),
5703
- output: arg(z.string().min(1), {
5704
- alias: "o",
5705
- description: "Output ERD diff HTML file",
5706
- completion: {
5707
- type: "file",
5708
- matcher: [".html"]
5709
- }
5710
- }),
5711
- "output-json": arg(z.string().optional(), {
5712
- description: "Optional output JSON file for the computed diff",
5713
- completion: {
5714
- type: "file",
5715
- matcher: [".json"]
5716
- }
5717
- })
5718
- }),
5719
- run: (args) => {
5720
- initErdCommand();
5721
- const result = writeErdDiff({
5722
- baseHtml: args["base-html"],
5723
- headHtml: args["head-html"],
5724
- namespace: args.namespace,
5725
- outputHtml: args.output,
5726
- outputJson: args["output-json"]
5727
- });
5728
- if (args.json) logger.out({
5729
- namespace: result.namespace,
5730
- outputHtml: result.outputHtml,
5731
- outputJson: result.outputJson,
5732
- summary: result.diff.summary
5733
- });
5734
- else logger.success(`Wrote ERD diff to ${path.relative(process.cwd(), result.outputHtml)}`);
5735
- }
5736
- });
5737
-
5738
- //#endregion
5739
- //#region src/cli/commands/tailordb/erd/serve.ts
5740
- const DEFAULT_ERD_BASE_DIR = ".tailor/erd";
5741
- const LOCAL_HOST = "127.0.0.1";
5742
- const GLOB_CHARS = /[*?[\]{}()!+@]/;
5743
- function formatServeCommand(namespace) {
5744
- return `tailor tailordb erd serve --namespace ${namespace}`;
5745
- }
5746
- function getCacheControl(filePath) {
5747
- return filePath.endsWith(".html") || filePath.endsWith(".json") ? "no-cache" : "public, max-age=3600";
5748
- }
5749
- function resolveRequestPath(distDir, requestUrl) {
5750
- const url = new URL(requestUrl ?? "/", "http://localhost");
5751
- let pathname;
5752
- try {
5753
- pathname = decodeURIComponent(url.pathname);
5754
- } catch {
5755
- return;
5756
- }
5757
- if (pathname === "/" || pathname.endsWith("/")) pathname = path.join(pathname, "index.html");
5758
- const root = path.resolve(distDir);
5759
- const filePath = path.resolve(root, `.${pathname}`);
5760
- if (filePath !== root && !filePath.startsWith(`${root}${path.sep}`)) return;
5761
- return filePath;
5762
- }
5763
- function openStaticFile(filePath) {
5764
- let fd;
5765
- try {
5766
- fd = fs$1.openSync(filePath, "r");
5767
- if (!fs$1.fstatSync(fd).isFile()) {
5768
- fs$1.closeSync(fd);
5769
- return;
5770
- }
5771
- return {
5772
- filePath,
5773
- fd
5774
- };
5775
- } catch {
5776
- if (fd !== void 0) fs$1.closeSync(fd);
5777
- return;
5778
- }
5779
- }
5780
- function serveFile(distDir, req, res) {
5781
- const filePath = resolveRequestPath(distDir, req.url);
5782
- if (!filePath) {
5783
- res.writeHead(403);
5784
- res.end("Forbidden");
5785
- return;
5786
- }
5787
- const fallbackPath = path.join(distDir, "index.html");
5788
- const target = openStaticFile(filePath) ?? openStaticFile(fallbackPath);
5789
- if (!target) {
5790
- res.writeHead(503, {
5791
- "Content-Type": "text/plain; charset=utf-8",
5792
- "Cache-Control": "no-cache",
5793
- "Retry-After": "1"
5794
- });
5795
- res.end("ERD build is refreshing. Please retry.");
5796
- return;
5797
- }
5798
- const mimeType = lookup(target.filePath) || "application/octet-stream";
5799
- const stream = fs$1.createReadStream(target.filePath, {
5800
- fd: target.fd,
5801
- autoClose: true
5802
- });
5803
- stream.on("error", () => {
5804
- if (!res.headersSent) {
5805
- res.writeHead(503, {
5806
- "Content-Type": "text/plain; charset=utf-8",
5807
- "Cache-Control": "no-cache",
5808
- "Retry-After": "1"
5809
- });
5810
- res.end("ERD build is refreshing. Please retry.");
5811
- return;
5812
- }
5813
- res.destroy();
5814
- });
5815
- res.writeHead(200, {
5816
- "Content-Type": mimeType,
5817
- "Cache-Control": getCacheControl(target.filePath)
5818
- });
5819
- stream.pipe(res);
5820
- }
5821
- async function startStaticServer(options) {
5822
- const server = http.createServer((req, res) => {
5823
- serveFile(options.distDir, req, res);
5824
- });
5825
- return await new Promise((resolve, reject) => {
5826
- server.once("error", reject);
5827
- server.listen(options.port, LOCAL_HOST, () => {
5828
- server.off("error", reject);
5829
- const address = server.address();
5830
- if (!address || typeof address === "string") {
5831
- reject(/* @__PURE__ */ new Error("Failed to determine ERD server address."));
5832
- return;
5833
- }
5834
- resolve({
5835
- server,
5836
- url: `http://${LOCAL_HOST}:${address.port}`
5837
- });
5838
- });
5839
- });
5840
- }
5841
- function getWatchPatterns(config, results) {
5842
- const namespaces = new Set(results.map((result) => result.namespace));
5843
- const patterns = [config.path];
5844
- for (const namespace of namespaces) {
5845
- const dbConfig = config.db?.[namespace];
5846
- if (dbConfig && !("external" in dbConfig)) patterns.push(...dbConfig.files);
5847
- }
5848
- return [...new Set(patterns)];
5849
- }
5850
- function hasGlobPattern(pattern) {
5851
- return GLOB_CHARS.test(pattern);
5852
- }
5853
- function globBaseDir(pattern) {
5854
- const absolutePattern = path.resolve(pattern);
5855
- const parsed = path.parse(absolutePattern);
5856
- const relativePattern = absolutePattern.slice(parsed.root.length);
5857
- const literalParts = [];
5858
- for (const part of relativePattern.split(path.sep)) {
5859
- if (!part || GLOB_CHARS.test(part)) break;
5860
- literalParts.push(part);
5861
- }
5862
- const literalPath = literalParts.length > 0 ? path.join(parsed.root, ...literalParts) : parsed.root;
5863
- if (!literalPath || literalPath === parsed.root) return parsed.root || process.cwd();
5864
- if (!fs$1.existsSync(literalPath)) return path.dirname(literalPath);
5865
- return fs$1.statSync(literalPath).isDirectory() ? literalPath : path.dirname(literalPath);
5866
- }
5867
- async function expandWatchPattern(pattern) {
5868
- if (!hasGlobPattern(pattern)) return [path.resolve(pattern)];
5869
- const paths = /* @__PURE__ */ new Set();
5870
- for await (const file of glob(pattern)) paths.add(path.resolve(file));
5871
- const baseDir = globBaseDir(pattern);
5872
- if (fs$1.existsSync(baseDir) && fs$1.statSync(baseDir).isDirectory()) paths.add(baseDir);
5873
- return [...paths];
5874
- }
5875
- async function resolveWatchPathsFromConfig(config, results) {
5876
- const paths = /* @__PURE__ */ new Set();
5877
- for (const pattern of getWatchPatterns(config, results)) for (const watchPath of await expandWatchPattern(pattern)) paths.add(watchPath);
5878
- return [...paths];
5879
- }
5880
- async function resolveWatchPaths(context, results) {
5881
- return await resolveWatchPathsFromConfig(context.config, results);
5882
- }
5883
- function parseFreshErdExportResults(stdout) {
5884
- const lines = stdout.trim().split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
5885
- for (const line of lines.toReversed()) try {
5886
- const parsed = JSON.parse(line);
5887
- if (!Array.isArray(parsed)) continue;
5888
- return parsed.map((entry) => {
5889
- const result = entry;
5890
- if (typeof result.namespace !== "string" || typeof result.distDir !== "string") throw new Error("Invalid ERD export JSON output.");
5891
- return {
5892
- namespace: result.namespace,
5893
- distDir: result.distDir
5894
- };
5895
- });
5896
- } catch {
5897
- continue;
5898
- }
5899
- throw new Error("Failed to parse ERD export JSON output.");
5900
- }
5901
- function freshErdExportArgs(options) {
5902
- const cliEntry = process.argv[1];
5903
- if (!cliEntry) throw new Error("Cannot rebuild ERD schema in a fresh process: CLI entrypoint is unavailable.");
5904
- const args = [
5905
- cliEntry,
5906
- "tailordb",
5907
- "erd",
5908
- "export",
5909
- "--output",
5910
- options.outputDir,
5911
- "--json"
5912
- ];
5913
- if (options.configPath) args.push("--config", options.configPath);
5914
- if (options.namespace) args.push("--namespace", options.namespace);
5915
- return args;
5916
- }
5917
- async function runFreshErdExport(options) {
5918
- return await new Promise((resolve, reject) => {
5919
- const child = spawn(process.execPath, freshErdExportArgs(options), {
5920
- cwd: process.cwd(),
5921
- env: process.env,
5922
- stdio: [
5923
- "ignore",
5924
- "pipe",
5925
- "pipe"
5926
- ]
5927
- });
5928
- let stdout = "";
5929
- let stderr = "";
5930
- child.stdout.setEncoding("utf8");
5931
- child.stderr.setEncoding("utf8");
5932
- child.stdout.on("data", (chunk) => {
5933
- stdout += chunk;
5934
- });
5935
- child.stderr.on("data", (chunk) => {
5936
- stderr += chunk;
5937
- });
5938
- child.on("error", reject);
5939
- child.on("close", (code, signal) => {
5940
- if (code !== 0) {
5941
- const detail = stderr.trim() || signal || `exit code ${code}`;
5942
- reject(/* @__PURE__ */ new Error(`Fresh ERD export failed: ${detail}`));
5943
- return;
5944
- }
5945
- try {
5946
- resolve(parseFreshErdExportResults(stdout));
5947
- } catch (error) {
5948
- reject(error);
5949
- }
5950
- });
5951
- });
5952
- }
5953
- function selectPrimaryResult(results) {
5954
- const [primary, ...rest] = results;
5955
- if (!primary) throw new Error("No ERD build results found.");
5956
- logger.info(`Serving ERD for namespace "${primary.namespace}".`);
5957
- if (rest.length > 0) {
5958
- const commands = rest.map((result) => ` - ${formatServeCommand(result.namespace)}`).join("\n");
5959
- logger.warn(`Multiple namespaces found. To serve another namespace, run:\n${commands}`);
5960
- }
5961
- return primary;
5962
- }
5963
- async function createErdWatcher(options) {
5964
- let rebuilding = false;
5965
- let pending = false;
5966
- let watchPaths = await resolveWatchPaths(options.initialContext, options.initialResults);
5967
- let importNonce = 0;
5968
- const watcher = watch(watchPaths, {
5969
- ignored: /node_modules/,
5970
- ignoreInitial: true,
5971
- awaitWriteFinish: {
5972
- stabilityThreshold: 100,
5973
- pollInterval: 100
5974
- }
5975
- });
5976
- async function rebuild() {
5977
- if (rebuilding) {
5978
- pending = true;
5979
- return;
5980
- }
5981
- rebuilding = true;
5982
- try {
5983
- const results = await runFreshErdExport({
5984
- configPath: options.configPath,
5985
- namespace: options.namespace,
5986
- outputDir: options.outputDir
5987
- });
5988
- const { config } = await loadConfig(options.configPath, { importNonce: String(importNonce += 1) });
5989
- const nextWatchPaths = await resolveWatchPathsFromConfig(config, results);
5990
- watcher.unwatch(watchPaths);
5991
- watcher.add(nextWatchPaths);
5992
- watchPaths = nextWatchPaths;
5993
- logger.success(`Rebuilt ERD schema (${results.map((result) => result.namespace).join(", ")})`, { mode: "stream" });
5994
- } catch (error) {
5995
- logger.error("Failed to rebuild ERD schema. Serving the last successful build.", { mode: "stream" });
5996
- logger.error(String(error));
5997
- } finally {
5998
- rebuilding = false;
5999
- if (pending) {
6000
- pending = false;
6001
- await rebuild();
6002
- }
6003
- }
6004
- }
6005
- let debounceTimer;
6006
- const scheduleRebuild = (changedPath) => {
6007
- logger.info(`Schema source changed: ${path.relative(process.cwd(), changedPath)}`, { mode: "stream" });
6008
- if (debounceTimer) clearTimeout(debounceTimer);
6009
- debounceTimer = setTimeout(() => {
6010
- rebuild();
6011
- }, 150);
6012
- };
6013
- watcher.on("add", scheduleRebuild);
6014
- watcher.on("change", scheduleRebuild);
6015
- watcher.on("unlink", scheduleRebuild);
6016
- watcher.on("error", (error) => {
6017
- logger.error(`ERD watcher error: ${String(error)}`, { mode: "stream" });
6018
- });
6019
- return watcher;
6020
- }
6021
- async function waitForShutdown(server, watcher) {
6022
- return await new Promise((resolve) => {
6023
- const shutdown = () => {
6024
- watcher.close().finally(() => {
6025
- server.close(() => {
6026
- logger.info("ERD server stopped.");
6027
- resolve();
6028
- });
6029
- });
6030
- };
6031
- process.once("SIGINT", shutdown);
6032
- process.once("SIGTERM", shutdown);
6033
- });
6034
- }
6035
- const erdServeCommand = defineAppCommand({
6036
- name: "serve",
6037
- description: "Generate and serve TailorDB ERD locally with watch reload. (beta)",
6038
- args: z.strictObject({
6039
- ...configArg,
6040
- namespace: arg(z.string().optional(), {
6041
- alias: "n",
6042
- description: "TailorDB namespace name (uses first namespace in config if not specified)"
6043
- }),
6044
- port: arg(z.coerce.number().int().min(0).max(65535).default(0), { description: "Local server port (0 selects a free port)" }),
6045
- open: arg(z.boolean().default(false), { description: "Open the ERD viewer in the default browser" })
6046
- }),
6047
- run: async (args) => {
6048
- initErdCommand();
6049
- const outputDir = path.resolve(process.cwd(), DEFAULT_ERD_BASE_DIR);
6050
- const context = await loadLocalErdSchema({
6051
- configPath: args.config,
6052
- namespaces: args.namespace ? [args.namespace] : void 0
6053
- });
6054
- const results = prepareErdBuildsFromContext({
6055
- context,
6056
- namespace: args.namespace,
6057
- outputDir
6058
- });
6059
- const primary = selectPrimaryResult(results);
6060
- const { server, url } = await startStaticServer({
6061
- distDir: primary.distDir,
6062
- port: args.port
6063
- });
6064
- const watchUrl = `${url}/?watch=1`;
6065
- const watcher = await createErdWatcher({
6066
- configPath: args.config,
6067
- namespace: args.namespace,
6068
- outputDir,
6069
- initialContext: context,
6070
- initialResults: results
6071
- });
6072
- logger.newline();
6073
- if (args.json) logger.out({
6074
- namespace: primary.namespace,
6075
- url: watchUrl,
6076
- distDir: primary.distDir
6077
- });
6078
- else {
6079
- logger.success("ERD server started.");
6080
- logger.out(watchUrl);
6081
- }
6082
- if (args.open) try {
6083
- await open(watchUrl);
6084
- } catch {
6085
- logger.warn("Failed to open browser automatically. Please open the URL above manually.");
6086
- }
6087
- await waitForShutdown(server, watcher);
6088
- }
6089
- });
6090
-
6091
- //#endregion
6092
- //#region src/cli/commands/tailordb/erd/index.ts
6093
- const erdCommand = defineCommand({
6094
- name: "erd",
6095
- description: "Generate TailorDB ERD viewer artifacts from local TailorDB schema. (beta)",
6096
- subCommands: {
6097
- export: erdExportCommand,
6098
- diff: erdDiffCommand,
6099
- serve: erdServeCommand,
6100
- deploy: erdDeployCommand
6101
- }
6102
- });
6103
-
6104
5407
  //#endregion
6105
5408
  //#region src/cli/commands/tailordb/migrate/script.ts
6106
5409
  /**
@@ -6125,9 +5428,9 @@ async function script(options) {
6125
5428
  const targetNamespace = resolveTargetNamespace(namespacesWithMigrations, options.namespace);
6126
5429
  const { migrationsDir } = assertDefined(namespacesWithMigrations.find((ns) => ns.namespace === targetNamespace), "namespace with migrations not found");
6127
5430
  const diffPath = getMigrationFilePath(migrationsDir, migrationNumber, "diff");
6128
- if (!fs$1.existsSync(diffPath)) throw new Error(`Migration ${options.number} not found in ${migrationsDir}. Expected ${diffPath}.`);
5431
+ if (!fs.existsSync(diffPath)) throw new Error(`Migration ${options.number} not found in ${migrationsDir}. Expected ${diffPath}.`);
6129
5432
  const migratePath = getMigrationFilePath(migrationsDir, migrationNumber, "migrate");
6130
- if (fs$1.existsSync(migratePath)) throw new Error(`Migration script already exists at ${migratePath}.`);
5433
+ if (fs.existsSync(migratePath)) throw new Error(`Migration script already exists at ${migratePath}.`);
6131
5434
  const diff = loadDiff(diffPath);
6132
5435
  const previousSnapshot = reconstructSnapshotFromMigrations(migrationsDir, migrationNumber - 1);
6133
5436
  if (!previousSnapshot) throw new Error(`Could not reconstruct previous schema for migration ${options.number}. Make sure migration ${0} exists.`);
@@ -6312,7 +5615,7 @@ async function collectMigrationStatuses(options) {
6312
5615
  const pendingMigrations = getMigrationFiles(migrationsDir).map((f) => f.number).filter((n, i, arr) => arr.indexOf(n) === i).toSorted((a, b) => a - b).filter((n) => n > currentMigration).map((num) => {
6313
5616
  const diffPath = getMigrationFilePath(migrationsDir, num, "diff");
6314
5617
  let description;
6315
- if (fs$1.existsSync(diffPath)) try {
5618
+ if (fs.existsSync(diffPath)) try {
6316
5619
  description = loadDiff(diffPath).description;
6317
5620
  } catch {}
6318
5621
  return {
@@ -6427,7 +5730,7 @@ async function fetchRemoteTypes(client, workspaceId, namespace) {
6427
5730
  async function assertMigrationsReproduceLocalTypes(loaded, target) {
6428
5731
  const { config, plugins } = loaded;
6429
5732
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
6430
- const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-BO4ZF8wP.mjs");
5733
+ const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-BV-AXawv.mjs");
6431
5734
  const application = defineApplication({
6432
5735
  config,
6433
5736
  pluginManager
@@ -6439,7 +5742,7 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
6439
5742
  await service.processNamespacePlugins();
6440
5743
  }
6441
5744
  const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, application.tailorDBServices, config.path);
6442
- const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-CkIwEctX.mjs")).createExecutorService({ config: { files: [] } }) : void 0);
5745
+ const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-nU6ITOHL.mjs")).createExecutorService({ config: { files: [] } }) : void 0);
6443
5746
  await executorService?.loadExecutors();
6444
5747
  if (pluginExecutorFiles.length > 0) await executorService?.loadPluginExecutorFiles([...pluginExecutorFiles]);
6445
5748
  const executorUsedTypes = /* @__PURE__ */ new Set();
@@ -6684,10 +5987,10 @@ const migrationCommand = defineCommand({
6684
5987
  const tailordbCommand = defineCommand({
6685
5988
  name: "tailordb",
6686
5989
  description: "Manage TailorDB tables and data.",
5990
+ notes: "The `tailordb erd` commands are provided by the @tailor-platform/sdk-tailordb-erd-plugin CLI plugin.",
6687
5991
  subCommands: {
6688
5992
  truncate: truncateCommand,
6689
- migration: migrationCommand,
6690
- erd: erdCommand
5993
+ migration: migrationCommand
6691
5994
  }
6692
5995
  });
6693
5996
 
@@ -6710,7 +6013,7 @@ const upgradeCommand = defineAppCommand({
6710
6013
  run: async (args) => {
6711
6014
  const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
6712
6015
  await initTelemetry();
6713
- const { upgrade } = await import("../service-DeZeYa06.mjs");
6016
+ const { upgrade } = await import("../service-DaeJlBoH.mjs");
6714
6017
  await upgrade({
6715
6018
  from: args.from,
6716
6019
  dryRun: args["dry-run"],
@@ -7055,7 +6358,7 @@ const workflowCommand = defineCommand({
7055
6358
  name: "workflow",
7056
6359
  description: "Manage workflows and workflow executions.",
7057
6360
  subCommands: {
7058
- list: listCommand$13,
6361
+ list: listCommand$14,
7059
6362
  get: getCommand$6,
7060
6363
  start: startCommand,
7061
6364
  wait: waitCommand,
@@ -7063,7 +6366,7 @@ const workflowCommand = defineCommand({
7063
6366
  resume: resumeCommand
7064
6367
  },
7065
6368
  async run() {
7066
- await runCommand(listCommand$13, []);
6369
+ await runCommand(listCommand$14, []);
7067
6370
  }
7068
6371
  });
7069
6372
 
@@ -7074,10 +6377,10 @@ const appCommand = defineCommand({
7074
6377
  description: "Manage workspace applications",
7075
6378
  subCommands: {
7076
6379
  health: healthCommand,
7077
- list: listCommand$14
6380
+ list: listCommand$15
7078
6381
  },
7079
6382
  async run() {
7080
- await runCommand(listCommand$14, []);
6383
+ await runCommand(listCommand$15, []);
7081
6384
  }
7082
6385
  });
7083
6386
 
@@ -7088,12 +6391,12 @@ const userCommand = defineCommand({
7088
6391
  description: "Manage workspace users",
7089
6392
  subCommands: {
7090
6393
  invite: inviteCommand,
7091
- list: listCommand$15,
6394
+ list: listCommand$16,
7092
6395
  remove: removeCommand,
7093
6396
  update: updateCommand$4
7094
6397
  },
7095
6398
  async run() {
7096
- await runCommand(listCommand$15, []);
6399
+ await runCommand(listCommand$16, []);
7097
6400
  }
7098
6401
  });
7099
6402
 
@@ -7107,12 +6410,12 @@ const workspaceCommand = defineCommand({
7107
6410
  create: createCommand$4,
7108
6411
  delete: deleteCommand$5,
7109
6412
  get: getCommand$7,
7110
- list: listCommand$16,
6413
+ list: listCommand$17,
7111
6414
  restore: restoreCommand,
7112
6415
  user: userCommand
7113
6416
  },
7114
6417
  async run() {
7115
- await runCommand(listCommand$16, []);
6418
+ await runCommand(listCommand$17, []);
7116
6419
  }
7117
6420
  });
7118
6421
 
@@ -7124,37 +6427,22 @@ const packageJson = await readPackageJson();
7124
6427
  const cliName = Object.keys(packageJson.bin ?? {})[0] || "tailor";
7125
6428
  const packageName = packageJson.name ?? "@tailor-platform/sdk";
7126
6429
  const bundledSkillsDir = resolve(dirname(await resolvePackageJSON(import.meta.url)), "agent-skills");
7127
- function replaceCommandArgs(command, removals = []) {
7128
- const args = command.args;
7129
- if (!args?.shape) return command;
7130
- const shape = { ...args.shape };
7131
- for (const name of removals) delete shape[name];
7132
- return {
7133
- ...command,
7134
- args: z.strictObject({ ...shape })
7135
- };
7136
- }
7137
- function removeCommandAliases(command) {
7138
- const next = { ...command };
7139
- delete next.aliases;
7140
- return next;
7141
- }
7142
6430
  function alignSkillCommand(command) {
7143
6431
  const subCommands = command.subCommands ?? {};
7144
6432
  const add = {
7145
- ...removeCommandAliases(replaceCommandArgs(subCommands.add, ["verbose"])),
6433
+ ...subCommands.add,
7146
6434
  description: "Install Tailor SDK agent skills."
7147
6435
  };
7148
6436
  const list = {
7149
- ...replaceCommandArgs(subCommands.list, ["json"]),
6437
+ ...subCommands.list,
7150
6438
  description: "List Tailor SDK agent skills."
7151
6439
  };
7152
6440
  const remove = {
7153
- ...removeCommandAliases(replaceCommandArgs(subCommands.remove)),
6441
+ ...subCommands.remove,
7154
6442
  description: "Remove installed Tailor SDK agent skills."
7155
6443
  };
7156
6444
  const sync = {
7157
- ...replaceCommandArgs(subCommands.sync, ["verbose"]),
6445
+ ...subCommands.sync,
7158
6446
  description: "Remove and reinstall Tailor SDK agent skills."
7159
6447
  };
7160
6448
  return {
@@ -7183,7 +6471,7 @@ Run \`${cliName} plugin list\` to see which plugins are installed and where they
7183
6471
  auth: authCommand,
7184
6472
  authconnection: authconnectionCommand,
7185
6473
  crashreport: crashReportCommand,
7186
- deploy: deployCommand$1,
6474
+ deploy: deployCommand,
7187
6475
  executor: executorCommand,
7188
6476
  function: functionCommand,
7189
6477
  generate: generateCommand,
@@ -7212,21 +6500,26 @@ Run \`${cliName} plugin list\` to see which plugins are installed and where they
7212
6500
  sourceDir: bundledSkillsDir,
7213
6501
  package: packageName,
7214
6502
  mode: "copy",
7215
- descriptionAppend: false
6503
+ descriptionAppend: false,
6504
+ globalArgs: z.object(commonArgs),
6505
+ commandMap: {
6506
+ add: ["add"],
6507
+ remove: ["remove"]
6508
+ },
6509
+ unknownKeys: "strict"
7216
6510
  });
7217
- const commandWithSkillsSubCommands = commandWithSkills.subCommands ?? {};
7218
6511
  const mainCommand = withCompletionCommand({
7219
6512
  ...commandWithSkills,
7220
6513
  subCommands: {
7221
- ...commandWithSkillsSubCommands,
7222
- skills: alignSkillCommand(commandWithSkillsSubCommands.skills)
6514
+ ...commandWithSkills.subCommands,
6515
+ skills: alignSkillCommand(commandWithSkills.subCommands.skills)
7223
6516
  }
7224
6517
  });
7225
6518
  runMain(mainCommand, {
7226
6519
  version: packageJson.version,
7227
6520
  globalArgs: z.object(commonArgs),
7228
6521
  displayErrors: false,
7229
- onUnknownSubcommand: ({ commandPath, name, args }) => dispatchPlugin({
6522
+ onUnknownSubcommand: ({ commandPath, name, args }) => dispatchPluginWithInstallHint({
7230
6523
  commandPath,
7231
6524
  name,
7232
6525
  args,
@@ -7235,7 +6528,8 @@ runMain(mainCommand, {
7235
6528
  }),
7236
6529
  cleanup: async ({ error }) => {
7237
6530
  if (error) {
7238
- if (isCLIError(error)) {
6531
+ if (logger.jsonMode) logger.log(JSON.stringify(errorToJson(error, { includeStack: isVerbose() })));
6532
+ else if (isCLIError(error)) {
7239
6533
  logger.log(error.format());
7240
6534
  if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
7241
6535
  } else if (error instanceof Error) {
@@ -7243,7 +6537,7 @@ runMain(mainCommand, {
7243
6537
  if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
7244
6538
  } else logger.error(`Unknown error: ${error}`);
7245
6539
  if (!isCLIError(error) && (!(error instanceof Error) || error instanceof TypeError || error instanceof RangeError)) {
7246
- const { reportCrash } = await import("../crashreport-D3RjuSYb.mjs");
6540
+ const { reportCrash } = await import("../crashreport-0ioNRZ4K.mjs");
7247
6541
  await reportCrash(error, "handledError");
7248
6542
  }
7249
6543
  }