@tailor-platform/sdk 1.76.1 → 1.78.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/{application-BUfYmf0O.mjs → application-CUxVA7Fs.mjs} +378 -1679
  3. package/dist/application-CUxVA7Fs.mjs.map +1 -0
  4. package/dist/application-Dp_-ioiP.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/service.d.mts +5 -1
  33. package/dist/cli/commands/generate/types.d.mts +3 -1
  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/tailordb/migrate/config.d.mts +0 -1
  52. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +0 -1
  53. package/dist/cli/commands/tailordb/migrate/generate.d.mts +0 -1
  54. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +1 -1
  55. package/dist/cli/commands/tailordb/truncate.d.mts +0 -1
  56. package/dist/cli/commands/workflow/executions.d.mts +0 -1
  57. package/dist/cli/commands/workflow/get.d.mts +2 -1
  58. package/dist/cli/commands/workflow/list.d.mts +0 -1
  59. package/dist/cli/commands/workflow/resume.d.mts +0 -1
  60. package/dist/cli/commands/workflow/start.d.mts +0 -1
  61. package/dist/cli/commands/workflow/status.d.mts +1 -0
  62. package/dist/cli/commands/workflow/transform.d.mts +1 -0
  63. package/dist/cli/commands/workflow/wait.d.mts +0 -1
  64. package/dist/cli/commands/workflow/waiter.d.mts +1 -1
  65. package/dist/cli/commands/workspace/app/health.d.mts +0 -1
  66. package/dist/cli/commands/workspace/app/list.d.mts +0 -1
  67. package/dist/cli/commands/workspace/app/transform.d.mts +2 -0
  68. package/dist/cli/commands/workspace/create.d.mts +3 -1
  69. package/dist/cli/commands/workspace/delete.d.mts +0 -1
  70. package/dist/cli/commands/workspace/get.d.mts +0 -1
  71. package/dist/cli/commands/workspace/list.d.mts +1 -1
  72. package/dist/cli/commands/workspace/restore.d.mts +0 -1
  73. package/dist/cli/commands/workspace/transform.d.mts +4 -0
  74. package/dist/cli/commands/workspace/user/invite.d.mts +0 -1
  75. package/dist/cli/commands/workspace/user/list.d.mts +0 -1
  76. package/dist/cli/commands/workspace/user/remove.d.mts +0 -1
  77. package/dist/cli/commands/workspace/user/transform.d.mts +1 -0
  78. package/dist/cli/commands/workspace/user/update.d.mts +0 -1
  79. package/dist/cli/index.d.mts +3 -1
  80. package/dist/cli/index.mjs +660 -56
  81. package/dist/cli/index.mjs.map +1 -1
  82. package/dist/cli/lib.d.mts +4 -2
  83. package/dist/cli/lib.mjs +3 -3
  84. package/dist/cli/lib.mjs.map +1 -1
  85. package/dist/cli/query/index.d.mts +0 -1
  86. package/dist/cli/services/application.d.mts +12 -1
  87. package/dist/cli/services/auth/service.d.mts +2 -0
  88. package/dist/cli/services/executor/service.d.mts +1 -1
  89. package/dist/cli/services/http-adapter/bundler.d.mts +3 -1
  90. package/dist/cli/services/http-adapter/service.d.mts +2 -1
  91. package/dist/cli/services/resolver/service.d.mts +1 -1
  92. package/dist/cli/services/tailordb/service.d.mts +3 -1
  93. package/dist/cli/services/workflow/bundler.d.mts +2 -1
  94. package/dist/cli/services/workflow/service.d.mts +1 -1
  95. package/dist/cli/shared/args.d.mts +1 -1
  96. package/dist/cli/shared/client.d.mts +1 -1
  97. package/dist/cli/shared/config-loader.d.mts +0 -1
  98. package/dist/cli/shared/context.d.mts +2 -1
  99. package/dist/cli/shared/script-executor.d.mts +0 -1
  100. package/dist/cli/shared/seed-chunker.d.mts +7 -3
  101. package/dist/cli/shared/type-generator.d.mts +0 -1
  102. package/dist/cli/shared/workspace-name.d.mts +1 -0
  103. package/dist/cli/skills.mjs +1 -1
  104. package/dist/completion/zsh-worker.zsh +18 -1
  105. package/dist/configure/config/index.d.mts +0 -1
  106. package/dist/configure/config/types.d.mts +2 -2
  107. package/dist/configure/index.d.mts +2 -1
  108. package/dist/configure/index.mjs +248 -4
  109. package/dist/configure/index.mjs.map +1 -1
  110. package/dist/configure/services/aigateway/index.d.mts +0 -1
  111. package/dist/configure/services/aigateway/types.d.mts +0 -1
  112. package/dist/configure/services/auth/index.d.mts +0 -1
  113. package/dist/configure/services/auth/types.d.mts +14 -13
  114. package/dist/configure/services/executor/executor.d.mts +0 -1
  115. package/dist/configure/services/executor/index.d.mts +1 -0
  116. package/dist/configure/services/executor/operation.d.mts +1 -2
  117. package/dist/configure/services/executor/trigger/event.d.mts +0 -1
  118. package/dist/configure/services/executor/trigger/index.d.mts +0 -1
  119. package/dist/configure/services/executor/trigger/schedule.d.mts +0 -1
  120. package/dist/configure/services/executor/trigger/webhook.d.mts +0 -1
  121. package/dist/configure/services/http-adapter/http-adapter.d.mts +1 -2
  122. package/dist/configure/services/idp/index.d.mts +1 -5
  123. package/dist/configure/services/idp/permission.d.mts +4 -5
  124. package/dist/configure/services/idp/types.d.mts +0 -1
  125. package/dist/configure/services/index.d.mts +5 -0
  126. package/dist/configure/services/resolver/resolver.d.mts +0 -1
  127. package/dist/configure/services/resolver/types.d.mts +0 -1
  128. package/dist/configure/services/secrets/index.d.mts +0 -1
  129. package/dist/configure/services/secrets/types.d.mts +0 -1
  130. package/dist/configure/services/staticwebsite/index.d.mts +0 -1
  131. package/dist/configure/services/staticwebsite/types.d.mts +0 -1
  132. package/dist/configure/services/tailordb/permission.d.mts +4 -5
  133. package/dist/configure/services/tailordb/schema.d.mts +21 -8
  134. package/dist/configure/services/tailordb/types.d.mts +4 -5
  135. package/dist/configure/services/workflow/execution-policy.d.mts +0 -1
  136. package/dist/configure/services/workflow/execution-policy.types.d.mts +0 -1
  137. package/dist/configure/services/workflow/index.d.mts +1 -0
  138. package/dist/configure/services/workflow/job.d.mts +0 -1
  139. package/dist/configure/services/workflow/wait-point.d.mts +0 -1
  140. package/dist/configure/services/workflow/workflow.d.mts +0 -1
  141. package/dist/configure/types/field-runtime.d.mts +1 -1
  142. package/dist/configure/types/field.d.mts +1 -2
  143. package/dist/configure/types/field.types.d.mts +1 -2
  144. package/dist/configure/types/type.d.mts +1 -1
  145. package/dist/configure/user.d.mts +0 -1
  146. package/dist/{crashreport-ayWANEZ6.mjs → crashreport-BNWUM6Kf.mjs} +4 -3
  147. package/dist/{crashreport-ayWANEZ6.mjs.map → crashreport-BNWUM6Kf.mjs.map} +1 -1
  148. package/dist/{crashreport-Dy4ZzSkd.mjs → crashreport-BafWvjjX.mjs} +1 -1
  149. package/dist/errors-DVFS0AZk.mjs +100 -0
  150. package/dist/errors-DVFS0AZk.mjs.map +1 -0
  151. package/dist/{globals-CcU1ONiK.mjs → globals-CvizbGLY.mjs} +2 -2
  152. package/dist/{globals-CcU1ONiK.mjs.map → globals-CvizbGLY.mjs.map} +1 -1
  153. package/dist/kysely/index.d.mts +3 -4
  154. package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
  155. package/dist/logger-BwS4ppwO.mjs.map +1 -0
  156. package/dist/parser/service/auth/index.d.mts +1 -1
  157. package/dist/parser/service/auth/schema.d.mts +1 -0
  158. package/dist/parser/service/http-adapter/index.d.mts +1 -1
  159. package/dist/parser/service/idp/types.d.mts +1 -0
  160. package/dist/parser/service/tailordb/types.d.mts +0 -1
  161. package/dist/plugin/builtin/enum-constants/index.d.mts +0 -1
  162. package/dist/plugin/builtin/file-utils/index.d.mts +0 -1
  163. package/dist/plugin/builtin/kysely-type/index.d.mts +0 -1
  164. package/dist/plugin/builtin/seed/index.d.mts +0 -1
  165. package/dist/plugin/builtin/seed/index.mjs +1 -1
  166. package/dist/plugin/get-generated-type.d.mts +0 -1
  167. package/dist/plugin/manager.d.mts +3 -1
  168. package/dist/plugin/types.d.mts +0 -1
  169. package/dist/plugin/with-context.d.mts +0 -1
  170. package/dist/{registry-NfSW0BRo.mjs → registry-CYBRvfaU.mjs} +4 -49
  171. package/dist/registry-CYBRvfaU.mjs.map +1 -0
  172. package/dist/runtime/aigateway.d.mts +0 -2
  173. package/dist/runtime/authconnection.d.mts +0 -2
  174. package/dist/runtime/globals.d.mts +0 -1
  175. package/dist/runtime/index.d.mts +0 -1
  176. package/dist/runtime/types.d.mts +16 -7
  177. package/dist/{runtime-_vUgjiqT.mjs → runtime-BD2vYJFT.mjs} +2382 -1167
  178. package/dist/runtime-BD2vYJFT.mjs.map +1 -0
  179. package/dist/{schema-DrVyN-LP.mjs → schema-U6DxoZ8D.mjs} +204 -3
  180. package/dist/schema-U6DxoZ8D.mjs.map +1 -0
  181. package/dist/{secret-file-VSVGy1V0.mjs → secret-file-DBqZhjFQ.mjs} +2 -27
  182. package/dist/{secret-file-VSVGy1V0.mjs.map → secret-file-DBqZhjFQ.mjs.map} +1 -1
  183. package/dist/{secretmanager-Bd45j7an.mjs → secretmanager-IY4UvinW.mjs} +42 -9
  184. package/dist/secretmanager-IY4UvinW.mjs.map +1 -0
  185. package/dist/seed/index.d.mts +3 -2
  186. package/dist/{seed-fm0LeYP2.mjs → seed-DFFigL8V.mjs} +3 -13
  187. package/dist/seed-DFFigL8V.mjs.map +1 -0
  188. package/dist/service-CghpdV-9.mjs +3 -0
  189. package/dist/{service-DBFZQloR.mjs → service-Cp0I7ZFe.mjs} +3 -3
  190. package/dist/{service-DBFZQloR.mjs.map → service-Cp0I7ZFe.mjs.map} +1 -1
  191. package/dist/{service-BCRJ-3NV.mjs → service-D12iQGcS.mjs} +2 -2
  192. package/dist/{service-BCRJ-3NV.mjs.map → service-D12iQGcS.mjs.map} +1 -1
  193. package/dist/service_pb-BoLM0X4K.mjs +167 -0
  194. package/dist/service_pb-BoLM0X4K.mjs.map +1 -0
  195. package/dist/{service_pb-B2FF59sP.mjs → service_pb-r1T7kpJc.mjs} +1 -1
  196. package/dist/tailor-proto/src/tailor/v1/aigateway_pb.d.mts +0 -1
  197. package/dist/tailor-proto/src/tailor/v1/aigateway_resource_pb.d.mts +0 -1
  198. package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +0 -1
  199. package/dist/tailor-proto/src/tailor/v1/application_resource_pb.d.mts +0 -1
  200. package/dist/tailor-proto/src/tailor/v1/auth_pb.d.mts +0 -1
  201. package/dist/tailor-proto/src/tailor/v1/auth_resource_pb.d.mts +0 -1
  202. package/dist/tailor-proto/src/tailor/v1/events_pb.d.mts +0 -1
  203. package/dist/tailor-proto/src/tailor/v1/events_resource_pb.d.mts +0 -1
  204. package/dist/tailor-proto/src/tailor/v1/executor_pb.d.mts +0 -1
  205. package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +0 -1
  206. package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +0 -1
  207. package/dist/tailor-proto/src/tailor/v1/function_registry_pb.d.mts +0 -1
  208. package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +0 -1
  209. package/dist/tailor-proto/src/tailor/v1/http_adapter_resource_pb.d.mts +0 -1
  210. package/dist/tailor-proto/src/tailor/v1/idp_pb.d.mts +0 -1
  211. package/dist/tailor-proto/src/tailor/v1/idp_resource_pb.d.mts +0 -1
  212. package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +0 -1
  213. package/dist/tailor-proto/src/tailor/v1/metadata_resource_pb.d.mts +0 -1
  214. package/dist/tailor-proto/src/tailor/v1/meter_pb.d.mts +0 -1
  215. package/dist/tailor-proto/src/tailor/v1/pipeline_pb.d.mts +0 -1
  216. package/dist/tailor-proto/src/tailor/v1/pipeline_resource_pb.d.mts +0 -1
  217. package/dist/tailor-proto/src/tailor/v1/resource_pb.d.mts +0 -1
  218. package/dist/tailor-proto/src/tailor/v1/secret_manager_pb.d.mts +0 -1
  219. package/dist/tailor-proto/src/tailor/v1/secret_manager_resource_pb.d.mts +0 -1
  220. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +0 -1
  221. package/dist/tailor-proto/src/tailor/v1/stateflow_pb.d.mts +0 -1
  222. package/dist/tailor-proto/src/tailor/v1/stateflow_resource_pb.d.mts +0 -1
  223. package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +0 -1
  224. package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +0 -1
  225. package/dist/tailor-proto/src/tailor/v1/tailordb_pb.d.mts +0 -1
  226. package/dist/tailor-proto/src/tailor/v1/tailordb_resource_pb.d.mts +0 -1
  227. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_pb.d.mts +0 -1
  228. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +0 -1
  229. package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +0 -1
  230. package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +0 -1
  231. package/dist/tailor-proto/src/tailor/v1/workspace_pb.d.mts +0 -1
  232. package/dist/tailor-proto/src/tailor/v1/workspace_resource_pb.d.mts +0 -1
  233. package/dist/test-env-key-DuZycyWM.mjs +49 -0
  234. package/dist/test-env-key-DuZycyWM.mjs.map +1 -0
  235. package/dist/type-source-DH_LH20p.mjs +13 -0
  236. package/dist/type-source-DH_LH20p.mjs.map +1 -0
  237. package/dist/types/aigateway.generated.d.mts +5 -2
  238. package/dist/types/auth-connection.generated.d.mts +11 -5
  239. package/dist/types/auth.generated.d.mts +118 -85
  240. package/dist/types/executor.generated.d.mts +76 -84
  241. package/dist/types/helpers.d.mts +5 -5
  242. package/dist/types/http-adapter.generated.d.mts +11 -5
  243. package/dist/types/idp.generated.d.mts +536 -524
  244. package/dist/types/resolver.generated.d.mts +24 -10
  245. package/dist/types/staticwebsite.generated.d.mts +7 -3
  246. package/dist/types/tailordb.generated.d.mts +55 -29
  247. package/dist/types/workflow.generated.d.mts +9 -4
  248. package/dist/user-agent-DX2Jnw0-.mjs +28 -0
  249. package/dist/user-agent-DX2Jnw0-.mjs.map +1 -0
  250. package/dist/utils/test/index.d.mts +0 -1
  251. package/dist/utils/test/index.mjs +1 -1
  252. package/dist/utils/test/mock.d.mts +0 -1
  253. package/dist/vitest/environment.mjs +1 -1
  254. package/dist/vitest/index.d.mts +9 -9
  255. package/dist/vitest/index.mjs +600 -185
  256. package/dist/vitest/index.mjs.map +1 -1
  257. package/dist/vitest/mock-kysely.d.mts +0 -1
  258. package/dist/vitest/mock.d.mts +8 -8
  259. package/dist/vitest/mocks/aigateway.d.mts +16 -6
  260. package/dist/vitest/mocks/authconnection.d.mts +16 -6
  261. package/dist/vitest/mocks/file.d.mts +24 -6
  262. package/dist/vitest/mocks/iconv.d.mts +28 -4
  263. package/dist/vitest/mocks/idp.d.mts +26 -10
  264. package/dist/vitest/mocks/secretmanager.d.mts +16 -5
  265. package/dist/vitest/mocks/tailordb.d.mts +54 -10
  266. package/dist/vitest/mocks/workflow.d.mts +48 -11
  267. package/dist/vitest/setup.mjs +2 -2
  268. package/dist/{service_pb-B5w9rjPY.mjs → workspace_resource_pb-Db3fv68L.mjs} +57 -165
  269. package/dist/workspace_resource_pb-Db3fv68L.mjs.map +1 -0
  270. package/docs/cli/application.md +58 -11
  271. package/docs/cli/setup.md +1 -1
  272. package/docs/cli/workspace.md +6 -4
  273. package/docs/cli-reference.md +12 -8
  274. package/docs/runtime.md +3 -4
  275. package/docs/services/aigateway.md +2 -0
  276. package/docs/services/tailordb.md +52 -1
  277. package/docs/services/workflow.md +1 -1
  278. package/docs/testing.md +103 -89
  279. package/package.json +16 -16
  280. package/dist/application-BUfYmf0O.mjs.map +0 -1
  281. package/dist/application-DbvD5UUz.mjs +0 -3
  282. package/dist/cli/shared/trigger-context.d.mts +0 -1
  283. package/dist/errors-Dtf2WPaW.mjs +0 -42
  284. package/dist/errors-Dtf2WPaW.mjs.map +0 -1
  285. package/dist/field-runtime-9bfXBAOK.mjs +0 -183
  286. package/dist/field-runtime-9bfXBAOK.mjs.map +0 -1
  287. package/dist/job-D-PbD1P3.mjs +0 -55
  288. package/dist/job-D-PbD1P3.mjs.map +0 -1
  289. package/dist/logger-BEiZZ3qT.mjs.map +0 -1
  290. package/dist/registry-NfSW0BRo.mjs.map +0 -1
  291. package/dist/resource_pb-BRv7AMXK.mjs +0 -49
  292. package/dist/resource_pb-BRv7AMXK.mjs.map +0 -1
  293. package/dist/runtime-_vUgjiqT.mjs.map +0 -1
  294. package/dist/schema-DrVyN-LP.mjs.map +0 -1
  295. package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
  296. package/dist/seed-fm0LeYP2.mjs.map +0 -1
  297. package/dist/service-CPMlIg3X.mjs +0 -3
  298. package/dist/service_pb-B5w9rjPY.mjs.map +0 -1
  299. package/dist/types-BwzB2okW.mjs +0 -199
  300. package/dist/types-BwzB2okW.mjs.map +0 -1
  301. package/dist/types-KlawuVKa.mjs +0 -4
@@ -1,39 +1,48 @@
1
- import { t as db } from "./schema-DrVyN-LP.mjs";
2
- import { i as PageDirection, n as Condition_Operator, r as FilterSchema, t as ConditionSchema } from "./resource_pb-BRv7AMXK.mjs";
1
+ import { t as db } from "./schema-U6DxoZ8D.mjs";
2
+ import { $ as CreateAuthIDPConfigRequestSchema, A as CreatePipelineResolverRequestSchema, At as TenantProviderConfig_TenantProviderType, B as IdPPermissionOperator, Bt as CreateAIGatewayRequestSchema, C as UpdateStaticWebsiteRequestSchema, Ct as AuthOAuth2Client_ClientType, D as CreateSecretManagerVaultRequestSchema, Dt as AuthSCIMAttribute_Uniqueness, E as CreateSecretManagerSecretRequestSchema, Et as AuthSCIMAttribute_Type, F as PipelineResolver_OperationType, Ft as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, G as CreateExecutorExecutorRequestSchema, Gt as ConditionSchema, H as FunctionExecution_Status, I as CreateIdPServiceRequestSchema, It as UpdateApplicationRequestSchema, J as ExecutorJobStatus, Jt as PageDirection, K as UpdateExecutorExecutorRequestSchema, Kt as Condition_Operator, L as UpdateIdPServiceRequestSchema, M as UpdatePipelineResolverRequestSchema, N as UpdatePipelineServiceRequestSchema, O as UpdateSecretManagerSecretRequestSchema, Ot as AuthSCIMConfig_AuthorizationType, Pt as CreateApplicationRequestSchema, Q as CreateAuthHookRequestSchema, Rt as ApplicationSchemaUpdateAttemptStatus, S as CreateStaticWebsiteRequestSchema, St as AuthInvokerSchema, Tt as AuthSCIMAttribute_Mutability, V as IdPPermissionPermit, Vt as UpdateAIGatewayRequestSchema, X as ExecutorTriggerType, Y as ExecutorTargetType, Z as CreateAuthConnectionRequestSchema, _ as TailorDBGQLPermission_Permit, a as CreateWorkflowRequestSchema, at as CreateTenantConfigRequestSchema, b as TailorDBType_PermitAction, bt as AuthHookPoint, ct as UpdateAuthHookRequestSchema, d as CreateTailorDBServiceRequestSchema, dt as UpdateAuthOAuth2ClientRequestSchema, et as CreateAuthMachineUserRequestSchema, f as CreateTailorDBTypeRequestSchema, ft as UpdateAuthSCIMConfigRequestSchema, g as TailorDBGQLPermission_Operator, gt as UpdateUserProfileConfigRequestSchema, h as TailorDBGQLPermission_Action, ht as UpdateTenantConfigRequestSchema, i as CreateWorkflowJobFunctionRequestSchema, it as CreateAuthServiceRequestSchema, j as CreatePipelineServiceRequestSchema, jt as UserProfileProviderConfig_UserProfileProviderType, l as WorkflowExecution_Status, lt as UpdateAuthIDPConfigRequestSchema, mt as UpdateAuthServiceRequestSchema, nt as CreateAuthSCIMConfigRequestSchema, o as UpdateWorkflowJobFunctionExecutionPolicyRequestSchema, ot as CreateUserProfileConfigRequestSchema, p as UpdateTailorDBTypeRequestSchema, pt as UpdateAuthSCIMResourceRequestSchema, qt as FilterSchema, r as CreateWorkflowJobFunctionExecutionPolicyRequestSchema, rt as CreateAuthSCIMResourceRequestSchema, s as UpdateWorkflowRequestSchema, st as UpdateAuthConnectionRequestSchema, t as WorkspacePlatformUserRole, tt as CreateAuthOAuth2ClientRequestSchema, u as WorkflowJobExecution_Status, ut as UpdateAuthMachineUserRequestSchema, v as TailorDBType_Permission_Operator, vt as AuthConnection_Status, wt as AuthOAuth2Client_GrantType, x as AddCustomDomainRequestSchema, xt as AuthIDPConfig_AuthType, y as TailorDBType_Permission_Permit, yt as AuthConnection_Type, z as IdPLang, zt as Subgraph_ServiceType } from "./workspace_resource_pb-Db3fv68L.mjs";
3
3
  import { t as assertDefined } from "./assert-DBxo8jPo.mjs";
4
- import { a as parseBoolean, i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-BEiZZ3qT.mjs";
5
- import { $ as getOAuth2ClientId, B as readPlatformConfig, C as createBundleCache, Ct as toPageDirection, D as loadConfig, E as hashFile, F as loadMachineUserName, I as loadPlatformClientConfig, J as fetchMachineUserToken, K as fetchAll, M as loadAccessToken, N as loadConfigPath, Q as getConsoleBaseUrl, R as loadWorkspaceId, S as hasGenerationHooks, St as resolveMachineUserInputSource, T as hashContent, U as writePlatformConfig, Y as fetchPaged, _ as platformBundleDefinePlugin, _t as organizationArgs, at as resolveStaticWebsiteUrls, bt as parseDuration, ct as configArg, d as buildExecutorArgsExpr, dt as durationArg, et as getOrNull, f as buildResolverOperationHookExpr, ft as folderArgs, g as stringifyFunction, gt as orderArg, h as TailorDBTypeSchema, ht as nonNegativeIntArg, i as resolverBundleKey, it as isDefaultPlatform, j as hasUserTokenEntry, l as HTTP_METHODS, lt as confirmationArgs, m as assertUniqueTailorDBTypeNamesWithExternal, n as generatePluginFilesIfNeeded, o as WorkflowJobFunctionExecutionPolicySchema, ot as byName, p as assertUniqueLocalTailorDBTypeNames, q as fetchAllTolerant, r as loadApplication, rt as initOperatorClient, t as defineApplication, tt as getPlatformBaseUrl, ut as deploymentArgs, vt as pagedLogArgs, w as getDistDir, wt as workspaceArgs, x as getPluginGenerationDependencies, xt as positiveIntArg, yt as paginationArgs, z as platformConfigFromProfile } from "./application-BUfYmf0O.mjs";
6
- import { o as loadFilesWithIgnores, t as createExecutorService } from "./service-BCRJ-3NV.mjs";
4
+ import { a as parseBoolean, i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-BwS4ppwO.mjs";
5
+ import { A as fetchAllTolerant, B as initOperatorClient, C as createBundleCache, E as hashFile, F as getConsoleBaseUrl, G as byName, H as normalizeBaseUrl, I as getOAuth2ClientId, K as LOG_LEVELS, L as getOrNull, M as fetchPaged, O as defaultPlatformBaseUrl, R as getPlatformBaseUrl, S as hasGenerationHooks, T as hashContent, U as rememberPlatformConfigForToken, V as isDefaultPlatform, W as resolveStaticWebsiteUrls, _ as platformBundleDefinePlugin, d as buildExecutorArgsExpr, f as buildResolverOperationHookExpr, g as stringifyFunction, h as TailorDBTypeSchema, i as resolverBundleKey, j as fetchMachineUserToken, k as fetchAll, l as HTTP_METHODS, m as assertUniqueTailorDBTypeNamesWithExternal, n as generatePluginFilesIfNeeded, o as WorkflowJobFunctionExecutionPolicySchema, p as assertUniqueLocalTailorDBTypeNames, r as loadApplication, t as defineApplication, w as getDistDir, x as getPluginGenerationDependencies, z as initOAuth2Client } from "./application-CUxVA7Fs.mjs";
6
+ import { o as loadFilesWithIgnores, s as functionSchema, t as createExecutorService } from "./service-D12iQGcS.mjs";
7
+ import { n as enumConstantsPlugin, t as EnumConstantsGeneratorID } from "./enum-constants-j9QBF0cB.mjs";
7
8
  import { t as multiline } from "./multiline-sfHpTZZK.mjs";
9
+ import { n as fileUtilsPlugin, t as FileUtilsGeneratorID } from "./file-utils-DcyIPFQh.mjs";
10
+ import { n as kyselyTypePlugin, t as KyselyGeneratorID } from "./kysely-type-D-zflMGf.mjs";
11
+ import { n as seedPlugin, t as SeedGeneratorID } from "./seed-DFFigL8V.mjs";
8
12
  import { t as readPackageJson } from "./package-json-8b0O9TlX.mjs";
9
- import { i as userAgent } from "./secret-file-VSVGy1V0.mjs";
10
- import { n as isCLIError, t as createCLIError } from "./errors-Dtf2WPaW.mjs";
11
- import { $ as CreateAuthServiceRequestSchema, A as CreatePipelineServiceRequestSchema, At as Subgraph_ServiceType, C as CreateStaticWebsiteRequestSchema, D as CreateSecretManagerVaultRequestSchema, Dt as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, E as CreateSecretManagerSecretRequestSchema, Et as CreateApplicationRequestSchema, F as UpdateIdPServiceRequestSchema, G as ExecutorTriggerType, H as UpdateExecutorExecutorRequestSchema, I as IdPLang, J as CreateAuthIDPConfigRequestSchema, K as CreateAuthConnectionRequestSchema, L as IdPPermissionOperator, M as UpdatePipelineServiceRequestSchema, N as PipelineResolver_OperationType, O as UpdateSecretManagerSecretRequestSchema, Ot as UpdateApplicationRequestSchema, P as CreateIdPServiceRequestSchema, Q as CreateAuthSCIMResourceRequestSchema, R as IdPPermissionPermit, S as AddCustomDomainRequestSchema, St as AuthSCIMConfig_AuthorizationType, Tt as UserProfileProviderConfig_UserProfileProviderType, U as ExecutorJobStatus, V as CreateExecutorExecutorRequestSchema, W as ExecutorTargetType, X as CreateAuthOAuth2ClientRequestSchema, Y as CreateAuthMachineUserRequestSchema, Z as CreateAuthSCIMConfigRequestSchema, _ as TailorDBGQLPermission_Operator, _t as AuthOAuth2Client_ClientType, a as WorkspacePlatformUserRole, at as UpdateAuthMachineUserRequestSchema, b as TailorDBType_Permission_Permit, bt as AuthSCIMAttribute_Type, c as CreateWorkflowRequestSchema, ct as UpdateAuthSCIMResourceRequestSchema, d as WorkflowExecution_Status, dt as UpdateUserProfileConfigRequestSchema, et as CreateTenantConfigRequestSchema, f as WorkflowJobExecution_Status, ft as AuthConnection_Status, g as TailorDBGQLPermission_Action, gt as AuthInvokerSchema, h as UpdateTailorDBTypeRequestSchema, ht as AuthIDPConfig_AuthType, it as UpdateAuthIDPConfigRequestSchema, j as UpdatePipelineResolverRequestSchema, k as CreatePipelineResolverRequestSchema, kt as ApplicationSchemaUpdateAttemptStatus, l as UpdateWorkflowJobFunctionExecutionPolicyRequestSchema, lt as UpdateAuthServiceRequestSchema, m as CreateTailorDBTypeRequestSchema, mt as AuthHookPoint, nt as UpdateAuthConnectionRequestSchema, o as CreateWorkflowJobFunctionExecutionPolicyRequestSchema, ot as UpdateAuthOAuth2ClientRequestSchema, p as CreateTailorDBServiceRequestSchema, pt as AuthConnection_Type, q as CreateAuthHookRequestSchema, rt as UpdateAuthHookRequestSchema, s as CreateWorkflowJobFunctionRequestSchema, st as UpdateAuthSCIMConfigRequestSchema, t as OperatorService, tt as CreateUserProfileConfigRequestSchema, u as UpdateWorkflowRequestSchema, ut as UpdateTenantConfigRequestSchema, v as TailorDBGQLPermission_Permit, vt as AuthOAuth2Client_GrantType, w as UpdateStaticWebsiteRequestSchema, wt as TenantProviderConfig_TenantProviderType, x as TailorDBType_PermitAction, xt as AuthSCIMAttribute_Uniqueness, y as TailorDBType_Permission_Operator, yt as AuthSCIMAttribute_Mutability, z as FunctionExecution_Status } from "./service_pb-B5w9rjPY.mjs";
13
+ import { t as userAgent } from "./user-agent-DX2Jnw0-.mjs";
14
+ import { r as isCLIError, t as createCLIError } from "./errors-DVFS0AZk.mjs";
15
+ import { n as tightenSecretFilePermissions, r as writeSecretFile } from "./secret-file-DBqZhjFQ.mjs";
12
16
  import { r as withSpan } from "./telemetry-CdqJEzkj.mjs";
13
17
  import { arg, createDefineCommand, defineCommand, runCommand } from "politty";
14
18
  import { z } from "zod";
15
- import { ScalarType, create, fromJson, toJson } from "@bufbuild/protobuf";
19
+ import { create, fromJson, toJson } from "@bufbuild/protobuf";
16
20
  import * as fs$1 from "node:fs";
17
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
21
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { parseEnv } from "node:util";
18
23
  import { ValueSchema, timestampDate } from "@bufbuild/protobuf/wkt";
19
24
  import * as path from "pathe";
25
+ import { basename, dirname, join, resolve } from "pathe";
20
26
  import { formatDistanceToNowStrict } from "date-fns";
21
27
  import { getBorderCharacters, table } from "table";
22
28
  import { pathToFileURL } from "node:url";
29
+ import * as os from "node:os";
23
30
  import { tmpdir } from "node:os";
31
+ import { parseTOML, parseYAML, stringifyYAML } from "confbox";
24
32
  import { findUpSync } from "find-up-simple";
33
+ import { lt } from "semver";
25
34
  import { xdgConfig } from "xdg-basedir";
26
35
  import { Code, ConnectError } from "@connectrpc/connect";
27
36
  import pLimit from "p-limit";
28
37
  import { resolveTSConfig } from "pkg-types";
29
38
  import * as crypto$1 from "node:crypto";
30
- import { createHash } from "node:crypto";
39
+ import { createHash, randomUUID } from "node:crypto";
31
40
  import { ExitPromptError } from "@inquirer/core";
32
- import { confirm, input, password } from "@inquirer/prompts";
41
+ import { confirm, input, password, select } from "@inquirer/prompts";
33
42
  import { isCI } from "std-env";
34
43
  import * as rolldown from "rolldown";
35
44
  import * as fs from "node:fs/promises";
36
- import { glob } from "node:fs/promises";
45
+ import { glob, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
37
46
  import { parseSync } from "oxc-parser";
38
47
  import * as inflection from "inflection";
39
48
  import { pathToString } from "@bufbuild/protobuf/reflect";
@@ -47,652 +56,1200 @@ import { createPrompt } from "@toiroakr/read-multiline";
47
56
  import { astVisitor, parse } from "pgsql-ast-parser";
48
57
  import { parse as parse$1 } from "@0no-co/graphql.web";
49
58
 
50
- //#region src/cli/shared/command.ts
59
+ //#region src/cli/shared/args.ts
60
+ const unitToMs = {
61
+ ms: 1,
62
+ s: 1e3,
63
+ m: 60 * 1e3
64
+ };
65
+ const durationPattern = /^(\d+)(ms|s|m)$/;
51
66
  /**
52
- * defineCommand with global args type (CommonArgsType).
53
- * Use this for leaf commands with `run` to get type-safe access to global args.
54
- * Parent commands with only `subCommands` can use `defineCommand` from politty directly.
67
+ * Schema for duration string validation (e.g., "3s", "500ms", "1m")
68
+ * Only validates format; use parseDuration() to convert to milliseconds
55
69
  */
56
- const defineAppCommand = createDefineCommand();
57
-
58
- //#endregion
59
- //#region src/cli/shared/readonly-guard.ts
70
+ const durationArg = z.string().refine((val) => durationPattern.test(val), { message: "Invalid duration format. Expected format: '3s', '500ms', '1m'" }).refine((val) => {
71
+ const match = val.match(durationPattern);
72
+ if (!match) return false;
73
+ const digits = match[1];
74
+ return digits !== void 0 && parseInt(digits, 10) > 0;
75
+ }, { message: "Duration must be greater than 0" });
60
76
  /**
61
- * Throw a CLIError if the active profile has `readonly: true`.
62
- *
63
- * Resolves the active profile in this order:
64
- * 1. `opts.profile` (CLI flag)
65
- * 2. `process.env.TAILOR_PLATFORM_PROFILE`
66
- *
67
- * If neither is set, no profile is in scope so the call is allowed. This is
68
- * intentional: `TAILOR_PLATFORM_TOKEN` direct access (CI / machine user) and
69
- * `--workspace-id` without a profile are out-of-band paths whose authorization
70
- * is governed by the bearer token itself, not by the local profile flag.
71
- *
72
- * If the resolved profile cannot be found in the config, this function returns
73
- * silently and lets downstream loaders surface the not-found error.
74
- * @param opts - Options
75
- * @param opts.profile - Optional explicit profile name from command args
77
+ * Parse a validated duration string into milliseconds
78
+ * @param duration - Duration string (e.g., "3s", "500ms", "1m")
79
+ * @returns Duration in milliseconds
76
80
  */
77
- async function assertWritable(opts) {
78
- const profileName = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
79
- if (!profileName) return;
80
- const profile = (await readPlatformConfig()).profiles[profileName];
81
- if (!profile || profile.readonly !== true) return;
82
- throw createCLIError({
83
- code: "PROFILE_READONLY",
84
- message: `Profile "${profileName}" is read-only.`,
85
- details: "This profile blocks platform-state mutations (apply, create/update/delete, deploy, etc.). Application-data operations remain available because their permissions are governed by the machine user.",
86
- suggestion: `Use a different profile, unset TAILOR_PLATFORM_PROFILE, or run 'tailor-sdk profile update ${profileName} --permission write'.`
87
- });
88
- }
89
-
90
- //#endregion
91
- //#region src/cli/commands/api/api-call.ts
92
- function hasEnvAccessToken() {
93
- const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
94
- return Boolean(envToken);
81
+ function parseDuration(duration) {
82
+ const match = assertDefined(duration.match(durationPattern), `invalid duration format: ${duration}`);
83
+ const value = parseInt(assertDefined(match[1], "duration digits group missing"), 10);
84
+ const unit = assertDefined(match[2], "duration unit group missing");
85
+ return value * unitToMs[unit];
95
86
  }
96
87
  /**
97
- * Call Tailor Platform API endpoints directly.
98
- * If the endpoint doesn't contain "/", it defaults to `tailor.v1.OperatorService/{endpoint}`.
99
- * @param options - API call options (profile, endpoint, body)
100
- * @returns Response status and data
88
+ * Schema for positive integer validation (from string input)
89
+ * Transforms the string to a number
101
90
  */
102
- async function apiCall(options) {
103
- const accessToken = await loadAccessToken({ profile: options.profile });
104
- let platformConfig;
105
- try {
106
- platformConfig = await loadPlatformClientConfig({ profile: options.profile });
107
- } catch (error) {
108
- if (!hasEnvAccessToken()) throw error;
109
- }
110
- let endpointPath;
111
- if (options.endpoint.includes("/")) endpointPath = options.endpoint;
112
- else endpointPath = `tailor.v1.OperatorService/${options.endpoint}`;
113
- const url = new URL(endpointPath, getPlatformBaseUrl(platformConfig));
114
- const response = await fetch(url.toString(), {
115
- method: "POST",
116
- headers: {
117
- "Content-Type": "application/json",
118
- Authorization: `Bearer ${accessToken}`,
119
- "User-Agent": await userAgent()
120
- },
121
- body: options.body ?? "{}"
122
- });
123
- const data = await response.json();
124
- if (!response.ok) throw new Error(`API call failed (${response.status}): ${JSON.stringify(data)}`);
125
- return {
126
- status: response.status,
127
- data
128
- };
91
+ const positiveIntArg = z.coerce.number().int().positive();
92
+ /**
93
+ * Schema for non-negative integer validation (from string input).
94
+ * Accepts 0 (used for `--limit 0` to disable the limit).
95
+ */
96
+ const nonNegativeIntArg = z.coerce.number().int().nonnegative();
97
+ /**
98
+ * Schema for sort order (`asc` or `desc`).
99
+ */
100
+ const orderArg = z.enum(["asc", "desc"]);
101
+ /**
102
+ * Translate a CLI `--order` value into the proto `PageDirection` enum.
103
+ * Returns `undefined` when the user did not specify an order so that
104
+ * callers can omit the field and fall back to the server default.
105
+ * @param order - Order string from CLI args (`"asc"` | `"desc"` | undefined)
106
+ * @returns PageDirection, or undefined when `order` is undefined
107
+ */
108
+ function toPageDirection(order) {
109
+ if (order === void 0) return void 0;
110
+ return order === "asc" ? PageDirection.ASC : PageDirection.DESC;
111
+ }
112
+ function hasMachineUserFlag(argv) {
113
+ return argv.slice(0, argv.indexOf("--") === -1 ? argv.length : argv.indexOf("--")).some((token) => token === "-m" || token.startsWith("-m=") || token === "--machine-user" || token.startsWith("--machine-user=") || token === "--machineUser" || token.startsWith("--machineUser=") || token === "--machineuser" || token.startsWith("--machineuser="));
129
114
  }
130
-
131
- //#endregion
132
- //#region src/cli/commands/api/proto-reflect.ts
133
- const UNARY_METHODS = OperatorService.methods.filter((m) => m.methodKind === "unary");
134
- function listMethodNames() {
135
- return UNARY_METHODS.map((m) => m.name).toSorted();
136
- }
137
- /**
138
- * Returns every accepted form of the `endpoint` positional — bare method name
139
- * and fully-qualified `service/Method`. politty's expand completion keys its
140
- * static table by the literal endpoint string, so both forms must appear in
141
- * `choices` or the user typing the fully-qualified name gets no `--field`
142
- * completion candidates back at TAB time.
143
- * @returns Sorted list of accepted endpoint values
144
- */
145
- function listMethodChoices() {
146
- return UNARY_METHODS.flatMap((m) => [m.name, `${OperatorService.typeName}/${m.name}`]).toSorted();
147
- }
148
- function getMethodDescriptor(methodName) {
149
- return UNARY_METHODS.find((m) => m.name === methodName);
150
- }
151
- function extractMethodName(endpoint) {
152
- if (!endpoint.includes("/")) return endpoint;
153
- return endpoint.split("/").pop() ?? endpoint;
154
- }
155
- /**
156
- * Returns the nested message descriptor when the field is a message, a list of
157
- * messages, or a map with message values. Otherwise returns undefined.
158
- * @param field - The proto field descriptor to inspect
159
- * @returns The nested message descriptor, or undefined when the field isn't message-shaped
160
- */
161
- function nestedMessage(field) {
162
- if (field.fieldKind === "message") return field.message;
163
- if (field.fieldKind === "list" && field.listKind === "message") return field.message;
164
- if (field.fieldKind === "map" && field.mapKind === "message") return field.message;
165
- }
166
- /**
167
- * Most `google.protobuf.*` well-known types serialize as scalars in proto JSON
168
- * (Timestamp/Duration → RFC3339/duration string, FieldMask → comma-joined
169
- * string, *Value wrappers → underlying primitive, etc.), so we must not let
170
- * `--field` drill into their internal fields. Treating them as leaves keeps
171
- * the completion and the body shape consistent with what the server expects.
172
- * @param message - The proto message descriptor to inspect
173
- * @returns True when the message is a well-known type
174
- */
175
- function isWellKnownType(message) {
176
- return message.typeName.startsWith("google.protobuf.");
177
- }
178
- const UNREPRESENTABLE_WELL_KNOWN_TYPES = /* @__PURE__ */ new Set([
179
- "google.protobuf.Struct",
180
- "google.protobuf.Value",
181
- "google.protobuf.ListValue",
182
- "google.protobuf.NullValue",
183
- "google.protobuf.Any",
184
- "google.protobuf.Empty"
185
- ]);
186
115
  /**
187
- * @param message - The proto message descriptor to inspect
188
- * @returns True when the message is a well-known type that `--field` cannot represent
116
+ * Resolve whether a parsed machine user value came from an explicit CLI option or env fallback.
117
+ * @param machineUser - Parsed machine user value
118
+ * @param argv - Raw CLI argv, excluding the executable and script path
119
+ * @param options - Source resolution options
120
+ * @returns Machine user input source, or undefined when no value was parsed
189
121
  */
190
- function isUnrepresentableWellKnownType(message) {
191
- return UNREPRESENTABLE_WELL_KNOWN_TYPES.has(message.typeName);
122
+ function resolveMachineUserInputSource(machineUser, argv = process.argv.slice(2), options = {}) {
123
+ if (machineUser === void 0) return void 0;
124
+ if (options.valueIsExplicit) return "option";
125
+ if (hasMachineUserFlag(argv)) return "option";
126
+ return process.env.TAILOR_PLATFORM_MACHINE_USER_NAME === machineUser ? "env" : "option";
192
127
  }
193
128
  /**
194
- * Pre-enumerates every `--field` completion candidate for `methodName`,
195
- * walking the input message tree to produce a flat list that the shell can
196
- * prefix-filter at TAB time. Used by the `expand` completion variant so no
197
- * Node process is spawned per keystroke.
198
- *
199
- * For each leaf field, emits `key=` (key completion). Enum leaves additionally
200
- * emit `key=ENUM_VALUE` per value, bool leaves emit `key=true` and
201
- * `key=false`. For nested messages, emits `key.` (drill-down) and recurses.
129
+ * Load env files from parsed arguments.
130
+ * Processes --env-file first, then --env-file-if-exists.
202
131
  *
203
- * Returns an empty array when `methodName` is unknown so the `expand`
204
- * generator stays exception-free at script-generation time.
205
- * @param methodName - Name of the unary RPC whose input message is being walked
206
- * @returns Flat list of `{ value, description }` candidates
207
- */
208
- function enumerateAllFieldCompletions(methodName) {
209
- const method = getMethodDescriptor(methodName);
210
- if (!method) return [];
211
- const candidates = [];
212
- const visited = /* @__PURE__ */ new Set();
213
- function walk(message, prefix) {
214
- visited.add(message);
215
- for (const field of message.fields) {
216
- if (field.fieldKind === "list" || field.fieldKind === "map") continue;
217
- const fullKey = prefix + field.localName;
218
- if (field.fieldKind === "message") {
219
- if (isUnrepresentableWellKnownType(field.message)) continue;
220
- if (!isWellKnownType(field.message)) {
221
- const nested = field.message;
222
- candidates.push({
223
- value: `${fullKey}.`,
224
- description: `${fullKey} (message)`
225
- });
226
- if (!visited.has(nested)) walk(nested, `${fullKey}.`);
227
- continue;
228
- }
132
+ * Follows Node.js --env-file behavior:
133
+ * - Variables already set in the environment are NOT overwritten
134
+ * - Variables from later files override those from earlier files
135
+ * @param envFiles - Required env file path(s) that must exist
136
+ * @param envFilesIfExists - Optional env file path(s) that are loaded if they exist
137
+ */
138
+ function loadEnvFiles(envFiles, envFilesIfExists) {
139
+ const originalEnvKeys = new Set(Object.keys(process.env));
140
+ const load = (files, required) => {
141
+ for (const file of [files ?? []].flat()) {
142
+ const envPath = path.resolve(process.cwd(), file);
143
+ if (!fs$1.existsSync(envPath)) {
144
+ if (required) throw new Error(`Environment file not found: ${envPath}`);
145
+ continue;
229
146
  }
230
- candidates.push({
231
- value: `${fullKey}=`,
232
- description: `Set ${fullKey}`
233
- });
234
- if (field.fieldKind === "enum") for (const v of field.enum.values) candidates.push({
235
- value: `${fullKey}=${v.name}`,
236
- description: v.name
237
- });
238
- else if (field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
239
- candidates.push({
240
- value: `${fullKey}=true`,
241
- description: "true"
242
- });
243
- candidates.push({
244
- value: `${fullKey}=false`,
245
- description: "false"
246
- });
147
+ const parsed = parseEnv(fs$1.readFileSync(envPath, "utf-8"));
148
+ for (const [key, value] of Object.entries(parsed)) {
149
+ if (originalEnvKeys.has(key)) continue;
150
+ process.env[key] = value;
247
151
  }
248
152
  }
249
- visited.delete(message);
250
- }
251
- walk(method.input, "");
252
- return candidates;
253
- }
254
- /**
255
- * Resolves the leaf `DescField` at a dotted `--field` path within an input
256
- * message tree. Returns undefined when any segment is missing or when a
257
- * non-leaf segment isn't a singular message — callers fall back to treating
258
- * the value as a string in that case.
259
- * @param input - The RPC input message descriptor
260
- * @param path - Dot-split path segments to follow (e.g. ["application", "name"])
261
- * @returns The leaf field descriptor, or undefined when the path doesn't resolve
262
- */
263
- function resolveLeafField(input, path) {
264
- let message = input;
265
- for (let i = 0; i < path.length; i++) {
266
- const field = message.fields.find((f) => f.localName === path[i]);
267
- if (!field) return void 0;
268
- if (i === path.length - 1) {
269
- if (field.fieldKind === "message" && isUnrepresentableWellKnownType(field.message)) return;
270
- return field;
271
- }
272
- if (field.fieldKind !== "message") return void 0;
273
- if (isWellKnownType(field.message)) return void 0;
274
- message = field.message;
275
- }
153
+ };
154
+ load(envFiles, true);
155
+ load(envFilesIfExists, false);
276
156
  }
277
-
278
- //#endregion
279
- //#region src/cli/commands/api/render.ts
280
- const SCALAR_LABEL = {
281
- [ScalarType.DOUBLE]: "double",
282
- [ScalarType.FLOAT]: "float",
283
- [ScalarType.INT64]: "int64",
284
- [ScalarType.UINT64]: "uint64",
285
- [ScalarType.INT32]: "int32",
286
- [ScalarType.FIXED64]: "fixed64",
287
- [ScalarType.FIXED32]: "fixed32",
288
- [ScalarType.BOOL]: "bool",
289
- [ScalarType.STRING]: "string",
290
- [ScalarType.BYTES]: "bytes",
291
- [ScalarType.UINT32]: "uint32",
292
- [ScalarType.SFIXED32]: "sfixed32",
293
- [ScalarType.SFIXED64]: "sfixed64",
294
- [ScalarType.SINT32]: "sint32",
295
- [ScalarType.SINT64]: "sint64"
296
- };
297
- function shortName(typeName) {
298
- const dot = typeName.lastIndexOf(".");
299
- return dot < 0 ? typeName : typeName.slice(dot + 1);
300
- }
301
- function scalarLabel(scalar) {
302
- return SCALAR_LABEL[scalar] ?? `scalar(${scalar})`;
303
- }
304
- function enumLabel(enumDesc) {
305
- return `enum ${shortName(enumDesc.typeName)}`;
306
- }
307
- function enumOf(field) {
308
- if (field.fieldKind === "enum") return field.enum;
309
- if (field.fieldKind === "list" && field.listKind === "enum") return field.enum;
310
- }
311
- function describeFieldType(field) {
312
- switch (field.fieldKind) {
313
- case "scalar": return scalarLabel(field.scalar);
314
- case "enum": return enumLabel(field.enum);
315
- case "message": return shortName(field.message.typeName);
316
- case "list": {
317
- let inner;
318
- if (field.listKind === "scalar") inner = scalarLabel(field.scalar);
319
- else if (field.listKind === "enum") inner = enumLabel(field.enum);
320
- else inner = shortName(field.message.typeName);
321
- return `repeated ${inner}`;
157
+ /**
158
+ * Common arguments for all CLI commands
159
+ *
160
+ * NOTE: --env-file and --env-file-if-exists collide with Node.js flags due to a bug
161
+ * (https://github.com/nodejs/node/issues/54232). Node.js parses these even after the
162
+ * script path, causing warnings (twice due to tsx loader).
163
+ */
164
+ const commonArgs = {
165
+ "env-file": arg(z.string().optional(), {
166
+ alias: "e",
167
+ description: "Path to the environment file (error if not found)",
168
+ completion: {
169
+ type: "file",
170
+ matcher: [".env.*", ".env"]
171
+ }
172
+ }),
173
+ "env-file-if-exists": arg(z.string().optional(), {
174
+ description: "Path to the environment file (ignored if not found)",
175
+ completion: {
176
+ type: "file",
177
+ matcher: [".env.*", ".env"]
178
+ },
179
+ effect: (_value, { args }) => {
180
+ loadEnvFiles(args["env-file"], args["env-file-if-exists"]);
181
+ }
182
+ }),
183
+ verbose: arg(z.boolean().default(false), {
184
+ description: "Enable verbose logging",
185
+ effect: (value) => {
186
+ verboseMode = value;
322
187
  }
323
- case "map": {
324
- const keyType = scalarLabel(field.mapKey);
325
- let valType;
326
- if (field.mapKind === "scalar") valType = scalarLabel(field.scalar);
327
- else if (field.mapKind === "enum") valType = enumLabel(field.enum);
328
- else valType = shortName(field.message.typeName);
329
- return `map<${keyType}, ${valType}>`;
188
+ }),
189
+ json: arg(z.boolean().default(false), {
190
+ alias: "j",
191
+ description: "Output as JSON",
192
+ effect: (value) => {
193
+ logger.jsonMode = value;
330
194
  }
331
- default: return "<unknown>";
195
+ })
196
+ };
197
+ /**
198
+ * Arguments for commands that require workspace context
199
+ */
200
+ const workspaceArgs = {
201
+ "workspace-id": arg(z.string().optional(), {
202
+ alias: "w",
203
+ description: "Workspace ID",
204
+ env: "TAILOR_PLATFORM_WORKSPACE_ID",
205
+ completion: { type: "none" }
206
+ }),
207
+ profile: arg(z.string().optional(), {
208
+ alias: "p",
209
+ description: "Workspace profile",
210
+ env: "TAILOR_PLATFORM_PROFILE",
211
+ completion: { type: "none" }
212
+ })
213
+ };
214
+ /**
215
+ * Shared config arg for commands that accept a config file path
216
+ */
217
+ const configArg = { config: arg(z.string().default("tailor.config.ts"), {
218
+ alias: "c",
219
+ description: "Path to SDK config file",
220
+ env: "TAILOR_PLATFORM_SDK_CONFIG_PATH",
221
+ completion: {
222
+ type: "file",
223
+ extensions: ["ts"]
332
224
  }
333
- }
334
- function fieldToJson(field, visited) {
335
- const json = {
336
- name: field.localName,
337
- protoName: field.name,
338
- type: describeFieldType(field),
339
- fieldKind: field.fieldKind,
340
- repeated: field.fieldKind === "list"
341
- };
342
- if (field.oneof) json.oneof = field.oneof.name;
343
- const fieldEnum = enumOf(field);
344
- if (fieldEnum) json.enumValues = fieldEnum.values.map((v) => v.name);
345
- const nested = nestedMessage(field);
346
- if (nested && !visited.has(nested)) {
347
- visited.add(nested);
348
- json.message = {
349
- typeName: nested.typeName,
350
- fields: nested.fields.map((f) => fieldToJson(f, visited))
351
- };
352
- visited.delete(nested);
353
- } else if (nested) json.message = {
354
- typeName: nested.typeName,
355
- fields: [],
356
- recursive: true
357
- };
358
- return json;
359
- }
360
- function renderInspectJson(method) {
361
- const visited = /* @__PURE__ */ new Set([method.input]);
362
- return {
363
- method: method.name,
364
- input: {
365
- typeName: method.input.typeName,
366
- fields: method.input.fields.map((f) => fieldToJson(f, visited))
367
- },
368
- output: { typeName: method.output.typeName }
369
- };
370
- }
371
- function renderFieldText(field, indent, visited) {
372
- const lines = [];
373
- const oneofTag = field.oneof ? ` (oneof ${field.oneof.name})` : "";
374
- lines.push(`${indent}${field.localName}: ${describeFieldType(field)}${oneofTag}`);
375
- const fieldEnum = enumOf(field);
376
- if (fieldEnum) {
377
- const values = fieldEnum.values.map((v) => v.name).join(", ");
378
- lines.push(`${indent} values: ${values}`);
379
- }
380
- const nested = nestedMessage(field);
381
- if (nested && !visited.has(nested)) {
382
- visited.add(nested);
383
- for (const sub of nested.fields) lines.push(...renderFieldText(sub, `${indent} `, visited));
384
- visited.delete(nested);
385
- } else if (nested) lines.push(`${indent} …(recursive ${nested.typeName})`);
386
- return lines;
387
- }
388
- function renderInspectText(method) {
389
- const lines = [];
390
- lines.push(`${method.name}`);
391
- lines.push(` request: ${method.input.typeName}`);
392
- const visited = /* @__PURE__ */ new Set([method.input]);
393
- for (const f of method.input.fields) lines.push(...renderFieldText(f, " ", visited));
394
- lines.push(` response: ${method.output.typeName}`);
395
- return lines.join("\n");
225
+ }) };
226
+ /**
227
+ * Shared config arg for commands that accept one or more comma-separated config file paths
228
+ */
229
+ const multiConfigArg = { config: arg(z.string().default("tailor.config.ts"), {
230
+ alias: "c",
231
+ description: "Path to SDK config file. Use comma-separated paths to deploy multiple apps together.",
232
+ env: "TAILOR_PLATFORM_SDK_CONFIG_PATH",
233
+ completion: {
234
+ type: "file",
235
+ extensions: ["ts"]
236
+ }
237
+ }) };
238
+ /**
239
+ * Arguments for commands that interact with deployed resources (includes config)
240
+ */
241
+ const deploymentArgs = {
242
+ ...workspaceArgs,
243
+ ...configArg
244
+ };
245
+ /**
246
+ * Arguments for commands that require confirmation
247
+ */
248
+ const confirmationArgs = { yes: arg(z.boolean().default(false), {
249
+ alias: "y",
250
+ description: "Skip confirmation prompts"
251
+ }) };
252
+ /**
253
+ * Arguments for commands that require organization context
254
+ */
255
+ const organizationArgs = { "organization-id": arg(z.string(), {
256
+ alias: "o",
257
+ description: "Organization ID",
258
+ env: "TAILOR_PLATFORM_ORGANIZATION_ID",
259
+ completion: { type: "none" }
260
+ }) };
261
+ /**
262
+ * Arguments for list commands that accept `--order` / `--limit`. Sort
263
+ * order defaults to `desc` (newest first) because most callers want the
264
+ * latest items; pass `--order asc` to opt in to ascending order. The
265
+ * limit is unbounded by default so existing invocations keep returning
266
+ * every item; pass `--limit N` to cap the result size.
267
+ * @param defaultOrder - Default value for `--order` (defaults to `"desc"`)
268
+ * @returns Argument shape suitable for spreading into a command schema
269
+ */
270
+ const paginationArgs = (defaultOrder = "desc") => ({
271
+ order: arg(orderArg.default(defaultOrder), { description: "Sort order (asc or desc)" }),
272
+ limit: arg(nonNegativeIntArg.optional(), {
273
+ alias: "l",
274
+ description: "Maximum number of items to return (0 or omit: unlimited)"
275
+ })
276
+ });
277
+ /**
278
+ * Arguments for time-series log list commands. Defaults to newest-first
279
+ * (`desc`) and a 50-item cap so that listing stays responsive on busy
280
+ * workspaces. Pass `--limit 0` to disable the cap and fetch all entries.
281
+ */
282
+ const pagedLogArgs = {
283
+ order: arg(orderArg.default("desc"), { description: "Sort order (asc or desc)" }),
284
+ limit: arg(nonNegativeIntArg.default(50), {
285
+ alias: "l",
286
+ description: "Maximum number of items to return (0: unlimited)"
287
+ })
288
+ };
289
+ /**
290
+ * Arguments for commands that require folder context
291
+ */
292
+ const folderArgs = { "folder-id": arg(z.string(), {
293
+ alias: "f",
294
+ description: "Folder ID",
295
+ env: "TAILOR_PLATFORM_FOLDER_ID",
296
+ completion: { type: "none" }
297
+ }) };
298
+ let verboseMode = false;
299
+ /**
300
+ * Returns whether verbose mode is enabled.
301
+ * Used by the global cleanup handler which doesn't have access to parsed args.
302
+ * @returns Whether verbose mode is enabled
303
+ */
304
+ function isVerbose() {
305
+ return verboseMode;
396
306
  }
397
307
 
398
308
  //#endregion
399
- //#region src/cli/commands/api/inspect.ts
400
- const inspectCommand = defineAppCommand({
401
- name: "inspect",
402
- description: "Print the input message tree of an OperatorService endpoint.",
403
- notes: "Combine with the global `--json` flag for a machine-readable descriptor. Recursive type references and `oneof` membership are annotated. Use `tailor-sdk api list` to discover endpoint names.",
404
- examples: [{
405
- cmd: "GetApplication",
406
- desc: "Show fields of GetApplicationRequest."
407
- }, {
408
- cmd: "CreateExecutorExecutor",
409
- desc: "Inspect a deeply nested input with `(oneof config)` annotations."
410
- }],
411
- args: z.object({ endpoint: arg(z.string(), {
412
- positional: true,
413
- description: "API endpoint to inspect (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
414
- completion: { custom: { choices: listMethodNames() } }
415
- }) }).strict(),
416
- run: (args) => {
417
- const methodName = extractMethodName(args.endpoint);
418
- const method = getMethodDescriptor(methodName);
419
- if (!method) throw createCLIError({
420
- message: `unknown method: ${methodName}`,
421
- suggestion: "Run `tailor-sdk api list` to see available methods.",
422
- command: "api inspect"
423
- });
424
- if (logger.jsonMode) logger.out(renderInspectJson(method));
425
- else logger.out(renderInspectText(method));
426
- }
309
+ //#region src/cli/shared/command.ts
310
+ /**
311
+ * defineCommand with global args type (CommonArgsType).
312
+ * Use this for leaf commands with `run` to get type-safe access to global args.
313
+ * Parent commands with only `subCommands` can use `defineCommand` from politty directly.
314
+ */
315
+ const defineAppCommand = createDefineCommand();
316
+
317
+ //#endregion
318
+ //#region src/parser/app-config/schema.ts
319
+ const envValueSchema = z.union([
320
+ z.string(),
321
+ z.number(),
322
+ z.boolean()
323
+ ]);
324
+ const LogLevelSchema = z.enum(LOG_LEVELS);
325
+ const logLevelSchema = z.string().refine((value) => LogLevelSchema.safeParse(value.trim().toUpperCase()).success, { message: `'logLevel' must be one of: ${LOG_LEVELS.join(", ")}.` });
326
+ /**
327
+ * Structural validation schema for `defineConfig({...})`. Validates only
328
+ * top-level fields with platform-side constraints (notably `id`); fields
329
+ * that carry SDK builder objects (`auth`, `idp`, `db`, ...) are accepted
330
+ * as opaque values, since their internal shapes are validated by their
331
+ * own factory functions and parser-level schemas.
332
+ *
333
+ * The `id` is auto-managed by `deploy` and stored as a plain UUID. A
334
+ * label-compatible prefix is added at the metadata boundary, so user-facing
335
+ * configs only need to carry a UUID.
336
+ */
337
+ const AppConfigSchema = z.object({
338
+ id: z.uuid({ message: "'id' must be a UUID." }).optional(),
339
+ name: z.string().min(1, { message: "'name' must be a non-empty string." }),
340
+ env: z.record(z.string(), envValueSchema).optional(),
341
+ cors: z.array(z.string()).optional(),
342
+ allowedIpAddresses: z.array(z.string()).optional(),
343
+ disableIntrospection: z.boolean().optional(),
344
+ inlineSourcemap: z.boolean().optional(),
345
+ logLevel: logLevelSchema.optional(),
346
+ db: z.unknown().optional(),
347
+ resolver: z.unknown().optional(),
348
+ idp: z.unknown().optional(),
349
+ auth: z.unknown().optional(),
350
+ executor: z.unknown().optional(),
351
+ workflow: z.unknown().optional(),
352
+ httpAdapter: z.unknown().optional(),
353
+ staticWebsites: z.unknown().optional(),
354
+ aiGateways: z.unknown().optional(),
355
+ secrets: z.unknown().optional()
427
356
  });
428
357
 
429
358
  //#endregion
430
- //#region src/cli/commands/api/list.ts
431
- const listCommand$10 = defineAppCommand({
432
- name: "list",
433
- description: "List all invocable OperatorService methods.",
434
- notes: "Only single-request (non-streaming) methods are listed, because the CLI issues a single JSON request and reads one JSON response.",
435
- args: z.object({}).strict(),
436
- run: () => {
437
- const names = listMethodNames();
438
- if (logger.jsonMode) logger.out(names);
439
- else for (const name of names) logger.out(name);
440
- }
359
+ //#region src/parser/generator-config/schema.ts
360
+ const DependencyKindSchema = z.enum([
361
+ "tailordb",
362
+ "resolver",
363
+ "executor"
364
+ ]);
365
+ const KyselyTypeConfigSchema = z.tuple([z.literal("@tailor-platform/kysely-type"), z.object({ distPath: z.string() })]);
366
+ const SeedConfigSchema = z.tuple([z.literal("@tailor-platform/seed"), z.object({
367
+ distPath: z.string(),
368
+ machineUserName: z.string().optional(),
369
+ disableIdpUserSync: z.object({
370
+ userToIdp: z.boolean().optional(),
371
+ idpToUser: z.boolean().optional()
372
+ }).optional()
373
+ })]);
374
+ const EnumConstantsConfigSchema = z.tuple([z.literal("@tailor-platform/enum-constants"), z.object({ distPath: z.string() })]);
375
+ const FileUtilsConfigSchema = z.tuple([z.literal("@tailor-platform/file-utils"), z.object({ distPath: z.string() })]);
376
+ const CodeGeneratorSchema = z.object({
377
+ id: z.string(),
378
+ description: z.string(),
379
+ dependencies: z.array(DependencyKindSchema),
380
+ processType: z.function().optional(),
381
+ processResolver: z.function().optional(),
382
+ processExecutor: z.function().optional(),
383
+ processTailorDBNamespace: z.function().optional(),
384
+ processResolverNamespace: z.function().optional(),
385
+ aggregate: z.function({ output: z.any() })
441
386
  });
387
+ const BaseGeneratorConfigSchema = z.union([
388
+ KyselyTypeConfigSchema,
389
+ SeedConfigSchema,
390
+ EnumConstantsConfigSchema,
391
+ FileUtilsConfigSchema,
392
+ CodeGeneratorSchema
393
+ ]);
442
394
 
443
395
  //#endregion
444
- //#region src/cli/commands/api/index.ts
445
- function resolveNamespaceName(methodName, config) {
446
- if (/Auth|Tenant|UserProfile/.test(methodName)) return config.auth?.name;
447
- if (/IdP/.test(methodName)) {
448
- if (config.idp?.length === 1) return assertDefined(config.idp[0], "idp config missing").name;
449
- return;
450
- }
451
- if (/TailorDB/.test(methodName)) {
452
- const keys = Object.keys(config.db ?? {});
453
- if (keys.length === 1) return keys[0];
454
- return;
455
- }
456
- if (/Pipeline/.test(methodName)) {
457
- const keys = Object.keys(config.resolver ?? {});
458
- if (keys.length === 1) return keys[0];
459
- return;
396
+ //#region src/parser/plugin-config/schema.ts
397
+ const PluginConfigSchema = z.object({
398
+ id: z.string(),
399
+ description: z.string(),
400
+ importPath: z.string().optional(),
401
+ pluginConfig: z.unknown().optional(),
402
+ typeConfigRequired: z.union([z.boolean(), functionSchema]).optional(),
403
+ onTypeLoaded: functionSchema.optional(),
404
+ onNamespaceLoaded: functionSchema.optional(),
405
+ onTailorDBReady: functionSchema.optional(),
406
+ onResolverReady: functionSchema.optional(),
407
+ onExecutorReady: functionSchema.optional()
408
+ }).passthrough().refine((p) => {
409
+ return !(p.onTypeLoaded || p.onNamespaceLoaded) || !!p.importPath;
410
+ }, { message: "importPath is required when plugin has definition-time hooks (onTypeLoaded/onNamespaceLoaded)" }).transform((plugin) => plugin);
411
+
412
+ //#endregion
413
+ //#region src/plugin/builtin/registry.ts
414
+ const builtinPlugins = /* @__PURE__ */ new Map([
415
+ [KyselyGeneratorID, (options) => kyselyTypePlugin(options)],
416
+ [SeedGeneratorID, (options) => seedPlugin(options)],
417
+ [EnumConstantsGeneratorID, (options) => enumConstantsPlugin(options)],
418
+ [FileUtilsGeneratorID, (options) => fileUtilsPlugin(options)]
419
+ ]);
420
+
421
+ //#endregion
422
+ //#region src/cli/shared/token-store.ts
423
+ const SERVICE_NAME = "tailor-platform-cli";
424
+ let entryClass;
425
+ async function getEntryClass() {
426
+ if (entryClass !== void 0) return entryClass;
427
+ try {
428
+ const mod = await import("@napi-rs/keyring");
429
+ const probe = new mod.Entry(SERVICE_NAME, "__probe__");
430
+ probe.setPassword("probe");
431
+ probe.deletePassword();
432
+ entryClass = mod.Entry;
433
+ } catch {
434
+ logger.warn("System keyring is not available. Tokens will be stored in the config file. Set TAILOR_PLATFORM_TOKEN environment variable for CI environments.");
435
+ entryClass = false;
460
436
  }
437
+ return entryClass;
438
+ }
439
+ /**
440
+ * Check whether the OS keyring is available and functional.
441
+ * @returns true if keyring is available
442
+ */
443
+ async function isKeyringAvailable() {
444
+ return await getEntryClass() !== false;
461
445
  }
462
446
  /**
463
- * Parse a JSON body string as a plain object for field injection.
464
- * Returns undefined for invalid JSON or non-object values (null, arrays, primitives),
465
- * so callers can fall back to sending the raw string unchanged.
466
- * @param body - Raw body string
467
- * @returns Parsed object or undefined
447
+ * Load tokens from the OS keyring for a given account.
448
+ * @param account - User identifier (e.g. email or client ID)
449
+ * @returns Token data or undefined if not found or keyring unavailable
468
450
  */
469
- function parseBodyAsObject(body) {
470
- let parsed;
451
+ async function loadKeyringTokens(account) {
452
+ const Entry = await getEntryClass();
453
+ if (!Entry) return void 0;
471
454
  try {
472
- parsed = JSON.parse(body);
455
+ const raw = new Entry(SERVICE_NAME, account).getPassword();
456
+ if (raw === null) return void 0;
457
+ return JSON.parse(raw);
473
458
  } catch {
474
459
  return;
475
460
  }
476
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
477
- return parsed;
478
- }
479
- /**
480
- * Set a dotted path on a body object, replacing non-object intermediates as
481
- * needed. `--field` takes precedence over `--body`, so collisions overwrite.
482
- * @param obj - The body object to mutate
483
- * @param path - Dot-split path segments (e.g. ["application", "name"])
484
- * @param value - Value to assign at the leaf
485
- */
486
- function setNestedPath(obj, path, value) {
487
- let cursor = obj;
488
- for (let i = 0; i < path.length - 1; i++) {
489
- const key = assertDefined(path[i], "path segment missing");
490
- const next = cursor[key];
491
- if (typeof next !== "object" || next === null || Array.isArray(next)) cursor[key] = {};
492
- cursor = cursor[key];
493
- }
494
- cursor[assertDefined(path[path.length - 1], "path last segment missing")] = value;
495
- }
496
- /**
497
- * Coerces a raw `--field` string value to match the leaf proto type so the
498
- * JSON body sends a properly typed value. Today we only coerce bool — the
499
- * completion candidates explicitly suggest `=true`/`=false`, so sending the
500
- * literal string "true" would be a visible mismatch. Other scalars are left
501
- * as strings: proto JSON accepts string forms for ints/floats/int64/etc.,
502
- * and we'd rather pass through what the user typed than silently coerce.
503
- * @param field - The resolved leaf field descriptor, or undefined when the path didn't resolve
504
- * @param raw - The raw string value after `=`
505
- * @returns The value to write into the body object
506
- */
507
- function coerceFieldValue(field, raw) {
508
- if (field && field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
509
- if (raw === "true") return true;
510
- if (raw === "false") return false;
511
- throw new Error(`Invalid value for bool field: '${raw}'. Expected 'true' or 'false'.`);
512
- }
513
- return raw;
514
- }
515
- /**
516
- * Collapse top-level body keys that address a known input field by its
517
- * protobuf name (snake_case) or JSON name to the field's `localName`. protojson
518
- * accepts every alias, so converging them here lets the presence checks below
519
- * reason in a single namespace — otherwise a `--body` written as
520
- * `{"workspace_id": ...}` slips past the camelCase check and we inject a second
521
- * `workspaceId`, which the server rejects as a duplicate field. When both forms
522
- * are present the canonical key wins and the alias is dropped.
523
- * @param body - The parsed body object to mutate
524
- * @param fields - The target endpoint's input fields
525
- * @returns Whether any key was rewritten
526
- */
527
- function normalizeBodyFieldKeys(body, fields) {
528
- const aliasToLocal = /* @__PURE__ */ new Map();
529
- for (const f of fields) {
530
- aliasToLocal.set(f.name, f.localName);
531
- aliasToLocal.set(f.jsonName, f.localName);
532
- }
533
- let changed = false;
534
- for (const key of Object.keys(body)) {
535
- const local = aliasToLocal.get(key);
536
- if (!local || local === key) continue;
537
- if (!Object.hasOwn(body, local)) body[local] = body[key];
538
- delete body[key];
539
- changed = true;
540
- }
541
- return changed;
542
- }
543
- const FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set([
544
- "__proto__",
545
- "constructor",
546
- "prototype"
461
+ }
462
+ /**
463
+ * Save tokens to the OS keyring for a given account.
464
+ * @param account - User identifier (e.g. email or client ID)
465
+ * @param tokens - Token data to store
466
+ */
467
+ async function saveKeyringTokens(account, tokens) {
468
+ const Entry = await getEntryClass();
469
+ if (!Entry) return;
470
+ new Entry(SERVICE_NAME, account).setPassword(JSON.stringify(tokens));
471
+ }
472
+ /**
473
+ * Delete tokens from the OS keyring for a given account.
474
+ * @param account - User identifier (e.g. email or client ID)
475
+ */
476
+ async function deleteKeyringTokens(account) {
477
+ const Entry = await getEntryClass();
478
+ if (!Entry) return;
479
+ try {
480
+ new Entry(SERVICE_NAME, account).deletePassword();
481
+ } catch {}
482
+ }
483
+
484
+ //#endregion
485
+ //#region src/cli/shared/context.ts
486
+ const pfProfileSchema = z.object({
487
+ user: z.string(),
488
+ workspace_id: z.string(),
489
+ readonly: z.boolean().optional(),
490
+ machine_user: z.string().optional(),
491
+ machine_user_override: z.enum(["allow", "deny"]).optional(),
492
+ platform_url: z.url().optional(),
493
+ oauth2_client_id: z.string().optional(),
494
+ console_url: z.url().optional()
495
+ });
496
+ const pfUserSchemaV1 = z.object({
497
+ access_token: z.string(),
498
+ refresh_token: z.string().optional(),
499
+ token_expires_at: z.string()
500
+ });
501
+ const pfUserKeyringSchema = z.object({
502
+ storage: z.literal("keyring"),
503
+ token_expires_at: z.string()
504
+ });
505
+ const pfUserFileSchema = z.object({
506
+ storage: z.literal("file"),
507
+ token_expires_at: z.string(),
508
+ access_token: z.string(),
509
+ refresh_token: z.string().optional()
510
+ });
511
+ const pfUserSchemaV2 = z.discriminatedUnion("storage", [pfUserKeyringSchema, pfUserFileSchema]);
512
+ const pfConfigSchemaV1 = z.object({
513
+ version: z.literal(1),
514
+ users: z.partialRecord(z.string(), pfUserSchemaV1),
515
+ profiles: z.partialRecord(z.string(), pfProfileSchema),
516
+ current_user: z.string().nullable()
517
+ });
518
+ const V2_CONFIG_VERSION = 2;
519
+ const LATEST_CONFIG_VERSION = 3;
520
+ const V2_MIN_SDK_VERSION = "1.29.0";
521
+ const V3_MIN_SDK_VERSION = "1.70.0";
522
+ const semverSchema = z.templateLiteral([
523
+ z.number().int(),
524
+ ".",
525
+ z.number().int(),
526
+ ".",
527
+ z.number().int()
547
528
  ]);
548
- const fieldArg = z.string().transform((val, ctx) => {
549
- const eq = val.indexOf("=");
550
- if (eq < 0) {
551
- ctx.addIssue({
552
- code: z.ZodIssueCode.custom,
553
- message: `Invalid field format: '${val}'. Expected format: 'key=value' or 'a.b.c=value'`
554
- });
555
- return z.NEVER;
529
+ const pfConfigSchema = z.object({
530
+ version: z.union([z.literal(V2_CONFIG_VERSION), z.literal(LATEST_CONFIG_VERSION)]),
531
+ min_sdk_version: semverSchema,
532
+ latest_version: z.number().int().optional(),
533
+ latest_min_sdk_version: semverSchema.optional(),
534
+ users: z.partialRecord(z.string(), pfUserSchemaV2),
535
+ profiles: z.partialRecord(z.string(), pfProfileSchema),
536
+ current_user: z.string().nullable()
537
+ });
538
+ function platformConfigPath() {
539
+ if (!xdgConfig) throw new Error("User home directory not found");
540
+ return path.join(xdgConfig, "tailor-platform", "config.yaml");
541
+ }
542
+ /**
543
+ * Convert stored profile platform fields to platform client settings.
544
+ * @param profile - Profile platform settings
545
+ * @returns Platform client settings
546
+ */
547
+ function platformConfigFromProfile(profile) {
548
+ const config = {
549
+ ...profile?.platform_url ? { platformUrl: profile.platform_url } : {},
550
+ ...profile?.oauth2_client_id ? { oauth2ClientId: profile.oauth2_client_id } : {},
551
+ ...profile?.console_url ? { consoleUrl: profile.console_url } : {}
552
+ };
553
+ return Object.keys(config).length > 0 ? config : void 0;
554
+ }
555
+ function platformUserKey(user, config) {
556
+ const platformUrl = getPlatformBaseUrl(config);
557
+ if (platformUrl === normalizeBaseUrl("https://api.tailor.tech")) return user;
558
+ return `${platformUrl}|${user}`;
559
+ }
560
+ function canUseLegacyUserKey(platformUrl) {
561
+ try {
562
+ return getPlatformBaseUrl() === platformUrl;
563
+ } catch {
564
+ return false;
556
565
  }
557
- const key = val.slice(0, eq);
558
- if (key.length === 0) {
559
- ctx.addIssue({
560
- code: z.ZodIssueCode.custom,
561
- message: "Field key cannot be empty"
562
- });
563
- return z.NEVER;
566
+ }
567
+ function findUserEntry(config, user, platformConfig, opts = {}) {
568
+ const userKey = platformUserKey(user, platformConfig);
569
+ const userEntry = config.users[userKey];
570
+ if (userEntry) return {
571
+ userKey,
572
+ userEntry
573
+ };
574
+ const platformUrl = getPlatformBaseUrl(platformConfig);
575
+ if (userKey !== user && (opts.allowLegacyUserKey === false || !canUseLegacyUserKey(platformUrl))) return {
576
+ userKey,
577
+ userEntry
578
+ };
579
+ const legacyEntry = config.users[user];
580
+ return legacyEntry ? {
581
+ userKey: user,
582
+ userEntry: legacyEntry
583
+ } : {
584
+ userKey,
585
+ userEntry
586
+ };
587
+ }
588
+ /**
589
+ * Resolve the config user key that would be used for a user on the selected platform.
590
+ * @param config - Platform config
591
+ * @param user - User name
592
+ * @param platformConfig - Optional platform connection settings
593
+ * @param opts - Token lookup options
594
+ * @returns Resolved config user key
595
+ */
596
+ function resolveUserTokenKey(config, user, platformConfig, opts) {
597
+ return findUserEntry(config, user, platformConfig, opts).userKey;
598
+ }
599
+ /**
600
+ * Check whether tokens are registered for a user on the selected platform.
601
+ * @param config - Platform config
602
+ * @param user - User name
603
+ * @param platformConfig - Optional platform connection settings
604
+ * @param opts - Token lookup options
605
+ * @returns True when the user has a registered token entry
606
+ */
607
+ function hasUserTokenEntry(config, user, platformConfig, opts) {
608
+ return findUserEntry(config, user, platformConfig, opts).userEntry !== void 0;
609
+ }
610
+ function hasUserKeyForName(users, user) {
611
+ return users[user] !== void 0 || Object.keys(users).some((key) => key.endsWith(`|${user}`));
612
+ }
613
+ /**
614
+ * Check whether any platform has tokens registered for a user.
615
+ * @param config - Platform config
616
+ * @param user - User name
617
+ * @returns True when the user has a token entry for any platform
618
+ */
619
+ function hasAnyUserTokenEntry(config, user) {
620
+ return hasUserKeyForName(config.users, user);
621
+ }
622
+ function hasCurrentUserEntry(users, currentUser) {
623
+ return hasUserKeyForName(users, currentUser);
624
+ }
625
+ /**
626
+ * Migrate a v1 config to v2.
627
+ * Tokens are kept in the config file (storage: "file") during migration.
628
+ * They will be moved to the OS keyring on next login or token refresh.
629
+ * @param v1Config - v1 configuration to migrate
630
+ * @returns Migrated v2 configuration
631
+ */
632
+ function migrateV1ToV2(v1Config) {
633
+ const users = {};
634
+ for (const [name, v1User] of Object.entries(v1Config.users)) {
635
+ if (!v1User) continue;
636
+ users[name] = {
637
+ access_token: v1User.access_token,
638
+ refresh_token: v1User.refresh_token,
639
+ token_expires_at: v1User.token_expires_at,
640
+ storage: "file"
641
+ };
564
642
  }
565
- const segments = key.split(".");
566
- if (segments.some((seg) => seg.length === 0)) {
567
- ctx.addIssue({
568
- code: z.ZodIssueCode.custom,
569
- message: `Invalid field key: '${key}'. Dotted segments cannot be empty`
570
- });
571
- return z.NEVER;
643
+ return {
644
+ version: V2_CONFIG_VERSION,
645
+ min_sdk_version: V2_MIN_SDK_VERSION,
646
+ users,
647
+ profiles: v1Config.profiles,
648
+ current_user: v1Config.current_user
649
+ };
650
+ }
651
+ async function warnIfNewerConfigAvailable(config) {
652
+ if (!config.latest_min_sdk_version) return;
653
+ if (lt((await readPackageJson()).version ?? "0.0.0", config.latest_min_sdk_version)) logger.warn(multiline`
654
+ A newer config version (${String(config.latest_version)}) is available.
655
+ Please update your SDK to >= ${config.latest_min_sdk_version}: pnpm update @tailor-platform/sdk
656
+ `);
657
+ }
658
+ /**
659
+ * Read Tailor Platform CLI configuration, migrating from tailorctl or v1 if necessary.
660
+ * @returns Parsed platform configuration
661
+ */
662
+ async function readPlatformConfig() {
663
+ const configPath = platformConfigPath();
664
+ if (!fs$1.existsSync(configPath)) {
665
+ logger.warn(`Config not found at ${configPath}, migrating from tailorctl config...`);
666
+ const tcConfig = readTailorctlConfig();
667
+ const v1Config = tcConfig ? fromTailorctlConfig(tcConfig) : {
668
+ version: 1,
669
+ users: {},
670
+ profiles: {},
671
+ current_user: null
672
+ };
673
+ writePlatformConfig(v1Config);
674
+ return migrateV1ToV2(v1Config);
675
+ }
676
+ const rawConfig = parseYAML(fs$1.readFileSync(configPath, "utf-8"));
677
+ tightenSecretFilePermissions(configPath);
678
+ const version = rawConfig != null && typeof rawConfig === "object" && "version" in rawConfig ? rawConfig.version : void 0;
679
+ if (typeof version === "number" && version > LATEST_CONFIG_VERSION) {
680
+ const minSdk = "min_sdk_version" in rawConfig ? String(rawConfig.min_sdk_version) : void 0;
681
+ const updateHint = minSdk ? `Please update your SDK to >= ${minSdk}: pnpm update @tailor-platform/sdk` : "Please update your SDK: pnpm update @tailor-platform/sdk";
682
+ throw new Error(multiline`
683
+ Config file uses version ${String(version)}, but this SDK only supports up to version ${String(LATEST_CONFIG_VERSION)}.
684
+ ${updateHint}
685
+ `);
572
686
  }
573
- const forbidden = segments.find((seg) => FORBIDDEN_SEGMENTS.has(seg));
574
- if (forbidden) {
575
- ctx.addIssue({
576
- code: z.ZodIssueCode.custom,
577
- message: `Invalid field key: '${key}'. Segment '${forbidden}' is not allowed.`
578
- });
579
- return z.NEVER;
687
+ const configResult = pfConfigSchema.safeParse(rawConfig);
688
+ if (configResult.success) {
689
+ await warnIfNewerConfigAvailable(configResult.data);
690
+ return configResult.data;
691
+ }
692
+ const v1Result = pfConfigSchemaV1.safeParse(rawConfig);
693
+ if (v1Result.success) return migrateV1ToV2(v1Result.data);
694
+ throw new Error(multiline`
695
+ Failed to parse config file at ${configPath}.
696
+ The file may be corrupted or created by an incompatible SDK version.
697
+ `);
698
+ }
699
+ function toV1ForDisk(config) {
700
+ const users = {};
701
+ for (const [name, entry] of Object.entries(config.users)) {
702
+ if (!entry || entry.storage === "keyring") continue;
703
+ users[name] = {
704
+ access_token: entry.access_token,
705
+ refresh_token: entry.refresh_token,
706
+ token_expires_at: entry.token_expires_at
707
+ };
580
708
  }
709
+ const currentUser = config.current_user && hasCurrentUserEntry(users, config.current_user) ? config.current_user : null;
581
710
  return {
582
- path: segments,
583
- value: val.slice(eq + 1)
711
+ version: 1,
712
+ users,
713
+ profiles: config.profiles,
714
+ current_user: currentUser
584
715
  };
585
- });
586
- const apiCommand = defineAppCommand({
587
- name: "api",
588
- description: "Call Tailor Platform API endpoints directly.",
589
- notes: `Use \`tailor-sdk api list\` to enumerate invocable methods and \`tailor-sdk api inspect <endpoint>\` to print an endpoint's input message tree (combine with \`--json\` for machine-readable output).
590
-
591
- 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\`:
592
-
593
- - \`workspaceId\` — resolved from \`-w\` / \`TAILOR_PLATFORM_WORKSPACE_ID\` / the selected profile.
594
- - \`namespaceName\` — resolved from \`tailor.config.ts\` based on the endpoint's service:
595
- - Auth / Tenant / UserProfile endpoints use \`auth.name\`.
596
- - IdP / TailorDB / Pipeline endpoints use the sole configured namespace when exactly one is defined.
597
-
598
- 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.
599
-
600
- 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.`,
601
- examples: [
602
- {
603
- cmd: "GetApplication -b '{\"applicationName\":\"app-1\"}'",
604
- desc: "Call an endpoint; workspaceId is auto-injected."
605
- },
606
- {
607
- cmd: "GetApplication -f applicationName=app-1",
608
- desc: "Same as above, using --field instead of --body."
609
- },
610
- {
611
- cmd: "list",
612
- desc: "List all invocable OperatorService methods."
613
- },
614
- {
615
- cmd: "inspect GetApplication",
616
- desc: "Show the input message tree for an endpoint."
716
+ }
717
+ function hasProfilePlatformSettings(config) {
718
+ return Object.values(config.profiles).some((profile) => profile?.platform_url !== void 0 || profile?.oauth2_client_id !== void 0 || profile?.console_url !== void 0);
719
+ }
720
+ function hasScopedUserKeys(config) {
721
+ return Object.keys(config.users).some((userKey) => userKey.includes("|"));
722
+ }
723
+ function toLatestForDisk(config) {
724
+ return {
725
+ ...config.version === 1 ? migrateV1ToV2(config) : config,
726
+ version: LATEST_CONFIG_VERSION,
727
+ min_sdk_version: V3_MIN_SDK_VERSION
728
+ };
729
+ }
730
+ /**
731
+ * Write Tailor Platform CLI configuration to disk.
732
+ * By default, file-backed configs without newer fields are converted to V1 for
733
+ * backward compatibility, so an older SDK can still read the file. Configs
734
+ * containing a keyring user are kept in V2 or later because the keyring storage
735
+ * variant is not representable in V1. Configs containing profile-level Platform
736
+ * settings or platform-scoped user tokens are written in the latest
737
+ * min-SDK-gated format because older SDKs would silently drop or misread those
738
+ * settings.
739
+ * Set TAILOR_USE_KEYRING to write the current in-memory format unconditionally.
740
+ *
741
+ * The config file may contain access/refresh tokens when the OS keyring is
742
+ * unavailable, so it is written via {@link writeSecretFile} so other users
743
+ * on the host cannot read it.
744
+ * @param config - Platform configuration to write
745
+ */
746
+ function writePlatformConfig(config) {
747
+ const configPath = platformConfigPath();
748
+ const hasKeyringUser = config.version !== 1 && Object.values(config.users).some((u) => u?.storage === "keyring");
749
+ writeSecretFile(configPath, stringifyYAML(hasProfilePlatformSettings(config) || hasScopedUserKeys(config) ? toLatestForDisk(config) : config.version !== 1 && !process.env.TAILOR_USE_KEYRING && !hasKeyringUser ? toV1ForDisk(config) : config));
750
+ }
751
+ const tcContextConfigSchema = z.object({
752
+ username: z.string().optional(),
753
+ controlplaneaccesstoken: z.string().optional(),
754
+ controlplanerefreshtoken: z.string().optional(),
755
+ controlplanetokenexpiresat: z.string().optional(),
756
+ workspaceid: z.string().optional()
757
+ });
758
+ const tcConfigSchema = z.object({ global: z.object({ context: z.string().optional() }).optional() }).catchall(tcContextConfigSchema.optional());
759
+ function readTailorctlConfig() {
760
+ const configPath = path.join(os.homedir(), ".tailorctl", "config");
761
+ if (!fs$1.existsSync(configPath)) return;
762
+ const rawConfig = parseTOML(fs$1.readFileSync(configPath, "utf-8"));
763
+ return tcConfigSchema.parse(rawConfig);
764
+ }
765
+ function fromTailorctlConfig(config) {
766
+ const users = {};
767
+ const profiles = {};
768
+ let currentUser = null;
769
+ const currentContext = config.global?.context || "default";
770
+ for (const [key, val] of Object.entries(config)) {
771
+ if (key === "global") continue;
772
+ const context = val;
773
+ if (!context.username || !context.controlplaneaccesstoken || !context.controlplanerefreshtoken || !context.controlplanetokenexpiresat || !context.workspaceid) continue;
774
+ if (key === currentContext) currentUser = context.username;
775
+ profiles[key] = {
776
+ user: context.username,
777
+ workspace_id: context.workspaceid
778
+ };
779
+ const user = users[context.username];
780
+ if (!user || new Date(user.token_expires_at) < new Date(context.controlplanetokenexpiresat)) users[context.username] = {
781
+ access_token: context.controlplaneaccesstoken,
782
+ refresh_token: context.controlplanerefreshtoken,
783
+ token_expires_at: context.controlplanetokenexpiresat
784
+ };
785
+ }
786
+ return {
787
+ version: 1,
788
+ users,
789
+ profiles,
790
+ current_user: currentUser
791
+ };
792
+ }
793
+ function validateUUID(value, source) {
794
+ const result = z.uuid().safeParse(value);
795
+ if (!result.success) throw new Error(`Invalid value from ${source}: must be a valid UUID`);
796
+ return result.data;
797
+ }
798
+ /**
799
+ * Load workspace ID from command options, environment variables, or platform config.
800
+ * In CLI context, env fallback is also handled by politty's arg env option.
801
+ * Priority: opts/workspaceId > env/workspaceId > opts/profile > error
802
+ * @param opts - Workspace and profile options
803
+ * @returns Resolved workspace ID
804
+ */
805
+ async function loadWorkspaceId(opts) {
806
+ const workspaceId = await tryLoadWorkspaceId(opts);
807
+ if (workspaceId) return workspaceId;
808
+ throw new Error(multiline`
809
+ Workspace ID not found.
810
+ Please specify workspace ID via --workspace-id option or TAILOR_PLATFORM_WORKSPACE_ID environment variable.
811
+ `);
812
+ }
813
+ /**
814
+ * Load a workspace ID when one is explicitly configured.
815
+ * @param opts - Workspace and profile options
816
+ * @returns Resolved workspace ID, or undefined when no source is configured
817
+ */
818
+ async function tryLoadWorkspaceId(opts) {
819
+ const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
820
+ if (opts?.workspaceId !== void 0) return validateUUID(opts.workspaceId, "--workspace-id option");
821
+ if (process.env.TAILOR_PLATFORM_WORKSPACE_ID) return validateUUID(process.env.TAILOR_PLATFORM_WORKSPACE_ID, "TAILOR_PLATFORM_WORKSPACE_ID environment variable");
822
+ if (profile) {
823
+ const wsId = (await readPlatformConfig()).profiles[profile]?.workspace_id;
824
+ if (!wsId) throw new Error(`Profile "${profile}" not found`);
825
+ return validateUUID(wsId, `profile "${profile}"`);
826
+ }
827
+ }
828
+ /**
829
+ * Load machine user name from command options, environment variables, or platform config.
830
+ * In CLI context, env fallback is also handled by politty's arg env option.
831
+ * Priority: opts/machineUser > env/TAILOR_PLATFORM_MACHINE_USER_NAME > opts/profile (profile default) > undefined.
832
+ * An explicitly empty `opts.machineUser` is rejected with a CLIError (`MACHINE_USER_NAME_EMPTY`) rather than falling back to the env var or profile default.
833
+ * When the active profile has `machine_user_override: "deny"`, an explicit value that differs from the profile's machine user throws a CLIError with code `PROFILE_MACHINE_USER_OVERRIDE_DENIED`.
834
+ * @param opts - Machine user and profile options
835
+ * @returns Resolved machine user name, or undefined if not set
836
+ */
837
+ async function loadMachineUserName(opts) {
838
+ if (opts?.machineUser === "") throw createCLIError({
839
+ code: "MACHINE_USER_NAME_EMPTY",
840
+ message: "Machine user name cannot be empty.",
841
+ suggestion: "Pass a non-empty machine user name, or omit the option to use the environment variable or profile default."
842
+ });
843
+ const envMachineUser = process.env.TAILOR_PLATFORM_MACHINE_USER_NAME || void 0;
844
+ const explicitMachineUser = opts?.machineUser ? {
845
+ source: opts.machineUserSource ?? "option",
846
+ value: opts.machineUser
847
+ } : envMachineUser ? {
848
+ source: "env",
849
+ value: envMachineUser
850
+ } : void 0;
851
+ const explicit = explicitMachineUser?.value;
852
+ const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
853
+ if (!profile) return explicit;
854
+ const entry = (await readPlatformConfig()).profiles[profile];
855
+ if (!entry) {
856
+ if (explicit) return explicit;
857
+ throw new Error(`Profile "${profile}" not found`);
858
+ }
859
+ if (entry.machine_user && entry.machine_user_override === "deny") {
860
+ if (explicit && explicit !== entry.machine_user) {
861
+ const details = explicitMachineUser.source === "env" ? `The machine user is being set to "${explicit}" via the TAILOR_PLATFORM_MACHINE_USER_NAME environment variable, which conflicts with this profile's pinned machine user "${entry.machine_user}".` : `This profile fixes the machine user to "${entry.machine_user}" for application-data commands.`;
862
+ throw createCLIError({
863
+ code: "PROFILE_MACHINE_USER_OVERRIDE_DENIED",
864
+ message: `Profile "${profile}" denies overriding the machine user.`,
865
+ details,
866
+ suggestion: `Omit the machine user option, unset TAILOR_PLATFORM_MACHINE_USER_NAME, or run 'tailor-sdk profile update ${profile} --machine-user-override allow'.`
867
+ });
617
868
  }
618
- ],
619
- subCommands: {
620
- list: listCommand$10,
621
- inspect: inspectCommand
622
- },
623
- args: z.object({
624
- ...workspaceArgs,
625
- ...configArg,
626
- body: arg(z.string().default("{}"), {
627
- alias: "b",
628
- description: "Request body as JSON."
629
- }),
630
- field: arg(fieldArg.array().optional(), {
631
- alias: "f",
632
- description: "Set a body field as `key=value` (repeatable; dotted keys nest). Overrides --body.",
633
- completion: { custom: { expand: {
634
- dependsOn: ["endpoint"],
635
- enumerate: ({ endpoint }) => enumerateAllFieldCompletions(extractMethodName(endpoint ?? ""))
636
- } } }
637
- }),
638
- endpoint: arg(z.string(), {
639
- positional: true,
640
- description: "API endpoint to call (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
641
- completion: { custom: { choices: listMethodChoices() } }
642
- })
643
- }).strict(),
644
- run: async (args) => {
645
- await assertWritable({ profile: args.profile });
646
- const methodName = extractMethodName(args.endpoint);
647
- const method = getMethodDescriptor(methodName);
648
- const parsedBody = parseBodyAsObject(args.body);
649
- let mutated = false;
650
- if (args.field && args.field.length > 0) {
651
- if (!parsedBody) throw new Error("--field requires --body to be a JSON object (or omitted).");
652
- for (const f of args.field) {
653
- const leaf = method ? resolveLeafField(method.input, f.path) : void 0;
654
- setNestedPath(parsedBody, f.path, coerceFieldValue(leaf, f.value));
655
- }
656
- mutated = true;
869
+ return entry.machine_user;
870
+ }
871
+ return explicit || entry.machine_user;
872
+ }
873
+ /**
874
+ * Load access token from environment variables, command options, or platform config.
875
+ * In CLI context, profile env fallback is also handled by politty's arg env option.
876
+ * Priority: env/TAILOR_PLATFORM_TOKEN > env/TAILOR_TOKEN (deprecated) > opts/profile > env/profile > config/currentUser > error
877
+ * @param opts - Profile options
878
+ * @returns Resolved access token
879
+ */
880
+ async function loadAccessToken(opts) {
881
+ const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
882
+ const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
883
+ if (envToken && !process.env.TAILOR_PLATFORM_TOKEN) logger.warn("TAILOR_TOKEN is deprecated. Please use TAILOR_PLATFORM_TOKEN instead.");
884
+ if (envToken) {
885
+ rememberPlatformConfigForToken(envToken, await loadPlatformClientConfig({
886
+ profile,
887
+ allowMissingProfile: true
888
+ }));
889
+ return envToken;
890
+ }
891
+ const pfConfig = await readPlatformConfig();
892
+ const profileEntry = profile ? pfConfig.profiles[profile] : void 0;
893
+ if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
894
+ const user = profileEntry?.user ?? pfConfig.current_user;
895
+ if (!user) throw new Error(multiline`
896
+ Tailor Platform token not found.
897
+ Please specify token via TAILOR_PLATFORM_TOKEN environment variable or login using 'tailor-sdk login' command.
898
+ `);
899
+ return await fetchLatestToken(pfConfig, user, profileEntry ? platformConfigFromProfile(profileEntry) : void 0);
900
+ }
901
+ /**
902
+ * Load platform connection settings from the active profile.
903
+ * @param opts - Profile options
904
+ * @returns Resolved platform connection settings, or undefined for the default environment
905
+ */
906
+ async function loadPlatformClientConfig(opts) {
907
+ const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
908
+ if (!profile) return;
909
+ let pfConfig;
910
+ try {
911
+ pfConfig = await readPlatformConfig();
912
+ } catch (error) {
913
+ if (opts?.allowMissingProfile) return;
914
+ throw error;
915
+ }
916
+ const profileEntry = pfConfig.profiles[profile];
917
+ if (!profileEntry) {
918
+ if (opts?.allowMissingProfile) return;
919
+ throw new Error(`Profile "${profile}" not found`);
920
+ }
921
+ return platformConfigFromProfile(profileEntry);
922
+ }
923
+ /**
924
+ * Load the Tailor Platform Console base URL from environment variables or the active profile.
925
+ * @param opts - Profile options
926
+ * @returns Resolved Console base URL
927
+ */
928
+ async function loadConsoleBaseUrl(opts) {
929
+ return getConsoleBaseUrl(await loadPlatformClientConfig(opts));
930
+ }
931
+ /**
932
+ * Resolve the actual token values for a user, reading from keyring or config as appropriate.
933
+ * @param userEntry - User entry from the config
934
+ * @param user - User identifier
935
+ * @param label - User-facing identifier used in error messages
936
+ * @returns Access token and optional refresh token
937
+ */
938
+ async function resolveTokens(userEntry, user, label = user) {
939
+ if (userEntry.storage === "keyring") {
940
+ const tokens = await loadKeyringTokens(user);
941
+ if (!tokens) throw new Error(multiline`
942
+ Credentials not found in OS keyring for "${label}".
943
+ Please run 'tailor-sdk login' and try again.
944
+ `);
945
+ return tokens;
946
+ }
947
+ return {
948
+ accessToken: userEntry.access_token,
949
+ refreshToken: userEntry.refresh_token
950
+ };
951
+ }
952
+ /**
953
+ * Save tokens for a user, writing to keyring or config as appropriate.
954
+ * @param config - Platform config
955
+ * @param user - User identifier
956
+ * @param tokens - Token data to save
957
+ * @param tokens.accessToken - Access token to persist
958
+ * @param tokens.refreshToken - Refresh token to persist when available
959
+ * @param expiresAt - Token expiration date
960
+ * @param platformConfig - Optional platform connection settings
961
+ */
962
+ async function saveUserTokens(config, user, tokens, expiresAt, platformConfig) {
963
+ const userKey = platformUserKey(user, platformConfig);
964
+ if (process.env.TAILOR_USE_KEYRING && await isKeyringAvailable()) {
965
+ await saveKeyringTokens(userKey, tokens);
966
+ config.users[userKey] = {
967
+ token_expires_at: expiresAt,
968
+ storage: "keyring"
969
+ };
970
+ } else config.users[userKey] = {
971
+ access_token: tokens.accessToken,
972
+ refresh_token: tokens.refreshToken,
973
+ token_expires_at: expiresAt,
974
+ storage: "file"
975
+ };
976
+ }
977
+ /**
978
+ * Delete tokens for a user from keyring if applicable.
979
+ * @param config - Platform config
980
+ * @param user - User identifier
981
+ * @param platformConfig - Optional platform connection settings
982
+ * @param opts - Token lookup options
983
+ */
984
+ async function deleteUserTokens(config, user, platformConfig, opts) {
985
+ const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
986
+ if (userEntry?.storage === "keyring") await deleteKeyringTokens(userKey);
987
+ delete config.users[userKey];
988
+ }
989
+ /**
990
+ * Resolve stored tokens for a user on the selected platform.
991
+ * @param config - Platform config
992
+ * @param user - User name
993
+ * @param platformConfig - Optional platform connection settings
994
+ * @param opts - Token lookup options
995
+ * @returns Stored user entry and token values, or undefined when the user is not logged in
996
+ */
997
+ async function loadStoredUserTokens(config, user, platformConfig, opts) {
998
+ const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
999
+ if (!userEntry) return void 0;
1000
+ return {
1001
+ userEntry,
1002
+ ...await resolveTokens(userEntry, userKey, user)
1003
+ };
1004
+ }
1005
+ /**
1006
+ * Fetch the latest access token, refreshing if necessary.
1007
+ * @param config - Platform config
1008
+ * @param user - User name
1009
+ * @param platformConfig - Optional platform connection settings
1010
+ * @returns Latest access token
1011
+ */
1012
+ async function fetchLatestToken(config, user, platformConfig) {
1013
+ const { userKey, userEntry } = findUserEntry(config, user, platformConfig);
1014
+ if (!userEntry) throw new Error(multiline`
1015
+ User "${user}" not found.
1016
+ Please verify your user name and login using 'tailor-sdk login' command.
1017
+ `);
1018
+ const tokens = await resolveTokens(userEntry, userKey, user);
1019
+ if (new Date(userEntry.token_expires_at) > /* @__PURE__ */ new Date()) {
1020
+ rememberPlatformConfigForToken(tokens.accessToken, platformConfig);
1021
+ return tokens.accessToken;
1022
+ }
1023
+ if (!tokens.refreshToken) throw new Error(multiline`
1024
+ Token expired.
1025
+ Please run 'tailor-sdk login' and try again.
1026
+ `);
1027
+ const client = initOAuth2Client(platformConfig);
1028
+ let resp;
1029
+ try {
1030
+ resp = await client.refreshToken({
1031
+ accessToken: tokens.accessToken,
1032
+ refreshToken: tokens.refreshToken,
1033
+ expiresAt: Date.parse(userEntry.token_expires_at)
1034
+ });
1035
+ } catch {
1036
+ throw new Error(multiline`
1037
+ Failed to refresh token. Your session may have expired.
1038
+ Please run 'tailor-sdk login' and try again.
1039
+ `);
1040
+ }
1041
+ const newExpiresAt = new Date(assertDefined(resp.expiresAt, "token refresh response missing expiresAt")).toISOString();
1042
+ await saveUserTokens(config, user, {
1043
+ accessToken: resp.accessToken,
1044
+ refreshToken: resp.refreshToken ?? void 0
1045
+ }, newExpiresAt, platformConfig);
1046
+ if (userKey !== platformUserKey(user, platformConfig)) {
1047
+ if (userEntry.storage === "keyring") await deleteKeyringTokens(userKey);
1048
+ delete config.users[userKey];
1049
+ }
1050
+ writePlatformConfig(config);
1051
+ rememberPlatformConfigForToken(resp.accessToken, platformConfig);
1052
+ return resp.accessToken;
1053
+ }
1054
+ const DEFAULT_CONFIG_FILENAME = "tailor.config.ts";
1055
+ /**
1056
+ * Load config path from command options, environment variables, or search parent directories.
1057
+ * In CLI context, env fallback is also handled by politty's arg env option.
1058
+ * Priority: opts/config > env/config > search parent directories
1059
+ * @param configPath - Optional explicit config path
1060
+ * @returns Resolved config path or undefined
1061
+ */
1062
+ function loadConfigPath(configPath) {
1063
+ if (configPath) return configPath;
1064
+ if (process.env.TAILOR_PLATFORM_SDK_CONFIG_PATH) return process.env.TAILOR_PLATFORM_SDK_CONFIG_PATH;
1065
+ return findUpSync(DEFAULT_CONFIG_FILENAME);
1066
+ }
1067
+
1068
+ //#endregion
1069
+ //#region src/cli/shared/mock.ts
1070
+ /**
1071
+ * Install a stub `globalThis.tailordb` so that user code loaded by the CLI
1072
+ * (e.g. via `createGetDB` in `@tailor-platform/sdk/kysely`) can reference
1073
+ * `tailordb.Client` without hitting a `ReferenceError`. The CLI never
1074
+ * actually executes the user code paths that issue queries, so a no-op
1075
+ * client suffices.
1076
+ *
1077
+ * Exposed as a function (rather than a top-level statement) so that
1078
+ * `package.json#sideEffects` can keep the file marked side-effect-free
1079
+ * without bundlers eliminating the install step.
1080
+ */
1081
+ function installCliTailordbStub() {
1082
+ globalThis.tailordb = { Client: class {
1083
+ constructor(_config) {}
1084
+ async connect() {}
1085
+ async end() {}
1086
+ async queryObject() {
1087
+ return {};
657
1088
  }
658
- if (parsedBody && method) {
659
- if (normalizeBodyFieldKeys(parsedBody, method.input.fields)) mutated = true;
660
- const fieldNames = method.input.fields.map((f) => f.localName);
661
- if (fieldNames.includes("workspaceId") && !Object.hasOwn(parsedBody, "workspaceId")) try {
662
- parsedBody.workspaceId = await loadWorkspaceId({
663
- workspaceId: args["workspace-id"],
664
- profile: args.profile
665
- });
666
- mutated = true;
667
- } catch {}
668
- if (fieldNames.includes("namespaceName") && !Object.hasOwn(parsedBody, "namespaceName")) try {
669
- const { config } = await loadConfig(args.config);
670
- const ns = resolveNamespaceName(methodName, config);
671
- if (ns) {
672
- parsedBody.namespaceName = ns;
673
- mutated = true;
1089
+ } };
1090
+ }
1091
+
1092
+ //#endregion
1093
+ //#region src/cli/shared/config-loader.ts
1094
+ const GeneratorConfigSchema = CodeGeneratorSchema.brand("CodeGenerator");
1095
+ /**
1096
+ * Load Tailor configuration file and associated generators and plugins.
1097
+ * @param configPath - Optional explicit config path
1098
+ * @param options - Optional module import behavior.
1099
+ * @returns Loaded config, generators, plugins, and config path
1100
+ */
1101
+ async function loadConfig(configPath, options = {}) {
1102
+ installCliTailordbStub();
1103
+ const foundPath = loadConfigPath(configPath);
1104
+ if (!foundPath) throw new Error("Configuration file not found: tailor.config.ts not found in current or parent directories");
1105
+ const resolvedPath = path.resolve(process.cwd(), foundPath);
1106
+ if (!fs$1.existsSync(resolvedPath)) throw new Error(`Configuration file not found: ${configPath}`);
1107
+ const configUrl = pathToFileURL(resolvedPath);
1108
+ if (options.importNonce) configUrl.searchParams.set("tailorImportNonce", options.importNonce);
1109
+ const configModule = await import(configUrl.href);
1110
+ if (!configModule || !configModule.default) throw new Error("Invalid Tailor config module: default export not found");
1111
+ const validated = AppConfigSchema.safeParse(configModule.default);
1112
+ if (!validated.success) {
1113
+ const issues = validated.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
1114
+ throw new Error(`Invalid Tailor config in ${resolvedPath}:\n${issues}`);
1115
+ }
1116
+ const allGenerators = [];
1117
+ const allPlugins = [];
1118
+ for (const value of Object.values(configModule)) if (Array.isArray(value)) {
1119
+ const generatorParsed = value.reduce((acc, item) => {
1120
+ if (!acc.success) return acc;
1121
+ const baseResult = BaseGeneratorConfigSchema.safeParse(item);
1122
+ if (baseResult.success && Array.isArray(baseResult.data)) {
1123
+ const [id, options] = baseResult.data;
1124
+ const pluginFactory = builtinPlugins.get(id);
1125
+ if (pluginFactory) {
1126
+ acc.convertedPlugins.push(pluginFactory(options));
1127
+ return acc;
674
1128
  }
675
- } catch {}
1129
+ }
1130
+ const result = GeneratorConfigSchema.safeParse(item);
1131
+ if (result.success) acc.items.push(result.data);
1132
+ else acc.success = false;
1133
+ return acc;
1134
+ }, {
1135
+ success: true,
1136
+ items: [],
1137
+ convertedPlugins: []
1138
+ });
1139
+ if (generatorParsed.success && (generatorParsed.items.length > 0 || generatorParsed.convertedPlugins.length > 0)) {
1140
+ allGenerators.push(...generatorParsed.items);
1141
+ allPlugins.push(...generatorParsed.convertedPlugins);
1142
+ continue;
676
1143
  }
677
- const result = await apiCall({
678
- profile: args.profile,
679
- endpoint: args.endpoint,
680
- body: mutated && parsedBody ? JSON.stringify(parsedBody) : args.body
1144
+ const pluginParsed = value.reduce((acc, item) => {
1145
+ if (!acc.success) return acc;
1146
+ const result = PluginConfigSchema.safeParse(item);
1147
+ if (result.success) acc.items.push(result.data);
1148
+ else acc.success = false;
1149
+ return acc;
1150
+ }, {
1151
+ success: true,
1152
+ items: []
681
1153
  });
682
- logger.out(JSON.stringify(result.data, null, 2));
1154
+ if (pluginParsed.success && pluginParsed.items.length > 0) allPlugins.push(...pluginParsed.items);
683
1155
  }
684
- });
1156
+ return {
1157
+ config: {
1158
+ ...configModule.default,
1159
+ path: resolvedPath
1160
+ },
1161
+ generators: allGenerators,
1162
+ plugins: allPlugins
1163
+ };
1164
+ }
1165
+
1166
+ //#endregion
1167
+ //#region src/cli/shared/readonly-guard.ts
1168
+ /**
1169
+ * Throw a CLIError if the active profile has `readonly: true`.
1170
+ *
1171
+ * Resolves the active profile in this order:
1172
+ * 1. `opts.profile` (CLI flag)
1173
+ * 2. `process.env.TAILOR_PLATFORM_PROFILE`
1174
+ *
1175
+ * If neither is set, no profile is in scope so the call is allowed. This is
1176
+ * intentional: `TAILOR_PLATFORM_TOKEN` direct access (CI / machine user) and
1177
+ * `--workspace-id` without a profile are out-of-band paths whose authorization
1178
+ * is governed by the bearer token itself, not by the local profile flag.
1179
+ *
1180
+ * If the resolved profile cannot be found in the config, this function returns
1181
+ * silently and lets downstream loaders surface the not-found error.
1182
+ * @param opts - Options
1183
+ * @param opts.profile - Optional explicit profile name from command args
1184
+ */
1185
+ async function assertWritable(opts) {
1186
+ const profileName = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
1187
+ if (!profileName) return;
1188
+ const profile = (await readPlatformConfig()).profiles[profileName];
1189
+ if (!profile || profile.readonly !== true) return;
1190
+ throw createCLIError({
1191
+ code: "PROFILE_READONLY",
1192
+ message: `Profile "${profileName}" is read-only.`,
1193
+ details: "This profile blocks platform-state mutations (apply, create/update/delete, deploy, etc.). Application-data operations remain available because their permissions are governed by the machine user.",
1194
+ suggestion: `Use a different profile, unset TAILOR_PLATFORM_PROFILE, or run 'tailor-sdk profile update ${profileName} --permission write'.`
1195
+ });
1196
+ }
1197
+
1198
+ //#endregion
1199
+ //#region src/cli/commands/api/api-call.ts
1200
+ function hasEnvAccessToken() {
1201
+ const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
1202
+ return Boolean(envToken);
1203
+ }
1204
+ /**
1205
+ * Call Tailor Platform API endpoints directly.
1206
+ * If the endpoint doesn't contain "/", it defaults to `tailor.v1.OperatorService/{endpoint}`.
1207
+ * @param options - API call options (profile, endpoint, body)
1208
+ * @returns Response status and data
1209
+ */
1210
+ async function apiCall(options) {
1211
+ const accessToken = await loadAccessToken({ profile: options.profile });
1212
+ let platformConfig;
1213
+ try {
1214
+ platformConfig = await loadPlatformClientConfig({ profile: options.profile });
1215
+ } catch (error) {
1216
+ if (!hasEnvAccessToken()) throw error;
1217
+ }
1218
+ let endpointPath;
1219
+ if (options.endpoint.includes("/")) endpointPath = options.endpoint;
1220
+ else endpointPath = `tailor.v1.OperatorService/${options.endpoint}`;
1221
+ const url = new URL(endpointPath, getPlatformBaseUrl(platformConfig));
1222
+ const response = await fetch(url.toString(), {
1223
+ method: "POST",
1224
+ headers: {
1225
+ "Content-Type": "application/json",
1226
+ Authorization: `Bearer ${accessToken}`,
1227
+ "User-Agent": await userAgent()
1228
+ },
1229
+ body: options.body ?? "{}"
1230
+ });
1231
+ const data = await response.json();
1232
+ if (!response.ok) throw new Error(`API call failed (${response.status}): ${JSON.stringify(data)}`);
1233
+ return {
1234
+ status: response.status,
1235
+ data
1236
+ };
1237
+ }
685
1238
 
686
1239
  //#endregion
687
1240
  //#region src/cli/shared/prompt.ts
1241
+ function canPrompt() {
1242
+ return !isCI && process.stdin.isTTY === true && process.stdout.isTTY === true && !logger.jsonMode;
1243
+ }
688
1244
  /**
689
1245
  * Wraps a prompt function with CI guard and cancellation handling.
690
1246
  * @param fn - A prompt function from `@inquirer/prompts`
1247
+ * @param unavailableMessage - Message used when interactive input is unavailable
691
1248
  * @returns A wrapped function that throws in CI and exits on cancel
692
1249
  */
693
- function withGuard(fn) {
1250
+ function withGuard(fn, unavailableMessage) {
694
1251
  return async (...args) => {
695
- if (isCI) throw new CIPromptError();
1252
+ if (!canPrompt()) throw new CIPromptError(unavailableMessage);
696
1253
  try {
697
1254
  return await fn(...args);
698
1255
  } catch (error) {
@@ -702,9 +1259,10 @@ function withGuard(fn) {
702
1259
  };
703
1260
  }
704
1261
  const prompt = {
705
- confirm: withGuard(confirm),
1262
+ confirm: withGuard(confirm, "Interactive confirmations are not available in this environment. Use --yes to skip confirmation prompts, or provide the required options explicitly."),
706
1263
  text: withGuard(input),
707
- password: withGuard(password)
1264
+ password: withGuard(password),
1265
+ select: withGuard(select)
708
1266
  };
709
1267
 
710
1268
  //#endregion
@@ -2296,35 +2854,59 @@ const SecretsStateSchema = z.object({
2296
2854
  vaults: z.record(z.string(), z.record(z.string(), z.string())),
2297
2855
  connections: z.record(z.string(), z.string()).optional()
2298
2856
  });
2857
+ const PersistedSecretsStateSchema = z.object({
2858
+ version: z.literal(1),
2859
+ workspaceId: z.string(),
2860
+ applicationKey: z.string(),
2861
+ state: SecretsStateSchema
2862
+ });
2299
2863
  /**
2300
- * Get the file path for the secrets state JSON.
2301
- * @returns Absolute path to secrets-state.json
2864
+ * Get the file path for one workspace and application's secrets state JSON.
2865
+ * @param scope - Workspace and application identity for the deployment
2866
+ * @returns Absolute path to the scoped state file
2302
2867
  */
2303
- function getSecretsStatePath() {
2304
- return path.join(getDistDir(), "secrets-state.json");
2868
+ function getSecretsStatePath(scope) {
2869
+ const scopeHash = hashValue(JSON.stringify([scope.workspaceId, applicationStateKey(scope)]));
2870
+ return path.join(getDistDir(), "secrets-state", `${scopeHash}.json`);
2305
2871
  }
2306
- /**
2307
- * Load secrets hash state from disk.
2308
- * @returns Persisted state, or empty state if file is missing or corrupted
2309
- */
2310
- function loadSecretsState() {
2311
- const filePath = getSecretsStatePath();
2312
- if (!existsSync(filePath)) return { vaults: {} };
2872
+ function loadPersistedSecretsState(scope) {
2313
2873
  try {
2314
- const raw = readFileSync(filePath, "utf-8");
2315
- return SecretsStateSchema.parse(JSON.parse(raw));
2874
+ const raw = readFileSync(getSecretsStatePath(scope), "utf-8");
2875
+ const persistedState = PersistedSecretsStateSchema.parse(JSON.parse(raw));
2876
+ if (persistedState.workspaceId !== scope.workspaceId || persistedState.applicationKey !== applicationStateKey(scope)) return;
2877
+ return persistedState;
2316
2878
  } catch {
2317
- return { vaults: {} };
2879
+ return;
2318
2880
  }
2319
2881
  }
2882
+ function applicationStateKey(scope) {
2883
+ if (!scope.applicationId) throw new Error(`Application "${scope.applicationName}" has no stable id for secrets state`);
2884
+ return `id:${scope.applicationId}`;
2885
+ }
2886
+ /**
2887
+ * Load secrets hash state for one workspace and application from disk.
2888
+ * @param scope - Workspace and application identity for the deployment
2889
+ * @returns Persisted state, or empty state if the scope is missing or the file is invalid
2890
+ */
2891
+ function loadSecretsState(scope) {
2892
+ if (!scope.applicationId) return { vaults: {} };
2893
+ return loadPersistedSecretsState(scope)?.state ?? { vaults: {} };
2894
+ }
2320
2895
  /**
2321
- * Save secrets hash state to disk.
2896
+ * Save secrets hash state for one workspace and application to disk.
2897
+ * @param scope - Workspace and application identity for the deployment
2322
2898
  * @param state - The secrets state to persist
2323
2899
  */
2324
- function saveSecretsState(state) {
2325
- const filePath = getSecretsStatePath();
2900
+ function saveSecretsState(scope, state) {
2901
+ if (!scope.applicationId) return;
2902
+ const filePath = getSecretsStatePath(scope);
2326
2903
  mkdirSync(path.dirname(filePath), { recursive: true });
2327
- writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
2904
+ writeFileSync(filePath, JSON.stringify({
2905
+ version: 1,
2906
+ workspaceId: scope.workspaceId,
2907
+ applicationKey: applicationStateKey(scope),
2908
+ state
2909
+ }, null, 2), "utf-8");
2328
2910
  }
2329
2911
  /**
2330
2912
  * Compute SHA-256 hex digest of a value.
@@ -2390,6 +2972,11 @@ function buildUpdateMask(existing, desired, secretChanged) {
2390
2972
  * @returns Planned changes for auth connections
2391
2973
  */
2392
2974
  async function planAuthConnections(client, workspaceId, appName, appId, auths) {
2975
+ const stateScope = {
2976
+ workspaceId,
2977
+ applicationId: appId,
2978
+ applicationName: appName
2979
+ };
2393
2980
  const changeSet = createChangeSet("Auth connections");
2394
2981
  const conflicts = [];
2395
2982
  const unmanaged = [];
@@ -2413,7 +3000,7 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
2413
3000
  allLabels: metadata?.labels
2414
3001
  };
2415
3002
  }));
2416
- const state = loadSecretsState();
3003
+ const state = loadSecretsState(stateScope);
2417
3004
  for (const [name, config] of Object.entries(desiredConnections)) {
2418
3005
  const existing = existingConnections[name];
2419
3006
  const metaRequest = await buildMetaRequest({
@@ -2474,7 +3061,8 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
2474
3061
  changeSet,
2475
3062
  conflicts,
2476
3063
  unmanaged,
2477
- resourceOwners
3064
+ resourceOwners,
3065
+ stateScope
2478
3066
  };
2479
3067
  }
2480
3068
  /**
@@ -2484,7 +3072,7 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
2484
3072
  * @param phase - Apply phase
2485
3073
  */
2486
3074
  async function applyAuthConnections(client, result, phase) {
2487
- const { changeSet } = result;
3075
+ const { changeSet, stateScope } = result;
2488
3076
  if (phase === "create-update") {
2489
3077
  await Promise.all(changeSet.creates.map(async (create) => {
2490
3078
  await client.createAuthConnection(create.request);
@@ -2498,26 +3086,29 @@ async function applyAuthConnections(client, result, phase) {
2498
3086
  await Promise.all(changeSet.updates.map(async (update) => {
2499
3087
  await client.setMetadata(update.metaRequest);
2500
3088
  }));
2501
- const state = loadSecretsState();
2502
- if (!state.connections) state.connections = {};
2503
- for (const create of changeSet.creates) {
2504
- const conn = create.request.connection;
2505
- if (conn?.config?.case === "oauth2") state.connections[create.name] = hashValue(conn.config.value.clientSecret ?? "");
2506
- }
2507
- for (const replace of changeSet.replaces) {
2508
- const conn = replace.updateRequest.connection;
2509
- if (conn?.config?.case === "oauth2" && replace.updateRequest.updateMask?.paths?.includes("oauth2.client_secret")) state.connections[replace.name] = hashValue(conn.config.value.clientSecret ?? "");
3089
+ const secretReplaces = changeSet.replaces.filter((replace) => replace.updateRequest.updateMask?.paths?.includes("oauth2.client_secret"));
3090
+ if (changeSet.creates.length > 0 || secretReplaces.length > 0) {
3091
+ const state = loadSecretsState(stateScope);
3092
+ if (!state.connections) state.connections = {};
3093
+ for (const create of changeSet.creates) {
3094
+ const conn = create.request.connection;
3095
+ if (conn?.config?.case === "oauth2") state.connections[create.name] = hashValue(conn.config.value.clientSecret ?? "");
3096
+ }
3097
+ for (const replace of secretReplaces) {
3098
+ const conn = replace.updateRequest.connection;
3099
+ if (conn?.config?.case === "oauth2") state.connections[replace.name] = hashValue(conn.config.value.clientSecret ?? "");
3100
+ }
3101
+ saveSecretsState(stateScope, state);
2510
3102
  }
2511
- saveSecretsState(state);
2512
3103
  } else {
2513
3104
  await Promise.all(changeSet.deletes.map(async (del) => {
2514
3105
  await client.deleteAuthConnection(del.request);
2515
3106
  }));
2516
3107
  if (changeSet.deletes.length > 0) {
2517
- const state = loadSecretsState();
3108
+ const state = loadSecretsState(stateScope);
2518
3109
  if (state.connections) {
2519
3110
  for (const del of changeSet.deletes) delete state.connections[del.name];
2520
- saveSecretsState(state);
3111
+ saveSecretsState(stateScope, state);
2521
3112
  }
2522
3113
  }
2523
3114
  }
@@ -3680,7 +4271,10 @@ async function applyAuth(client, result, phase = "create-update") {
3680
4271
  await Promise.all([...changeSet.machineUser.creates.map((create) => client.createAuthMachineUser(create.request)), ...changeSet.machineUser.updates.map((update) => client.updateAuthMachineUser(update.request))]);
3681
4272
  };
3682
4273
  const applyCreateUpdateDependents = async () => {
3683
- await applyAuthConnections(client, { changeSet: changeSet.connection }, "create-update");
4274
+ await applyAuthConnections(client, {
4275
+ changeSet: changeSet.connection,
4276
+ stateScope: result.connectionStateScope
4277
+ }, "create-update");
3684
4278
  await Promise.all([...changeSet.idpConfig.creates.map(async (create) => {
3685
4279
  if (create.idpConfig.kind === "BuiltInIdP") assertDefined(create.request.idpConfig, "request missing idpConfig").config = await protoBuiltinIdPConfig(client, assertDefined(create.request.workspaceId, "request missing workspaceId"), create.idpConfig);
3686
4280
  return client.createAuthIDPConfig(create.request);
@@ -3723,7 +4317,10 @@ async function applyAuth(client, result, phase = "create-update") {
3723
4317
  await Promise.all(changeSet.tenantConfig.deletes.map((del) => client.deleteTenantConfig(del.request)));
3724
4318
  await Promise.all(changeSet.userProfileConfig.deletes.map((del) => client.deleteUserProfileConfig(del.request)));
3725
4319
  await Promise.all(changeSet.idpConfig.deletes.map((del) => client.deleteAuthIDPConfig(del.request)));
3726
- await applyAuthConnections(client, { changeSet: changeSet.connection }, "delete-resources");
4320
+ await applyAuthConnections(client, {
4321
+ changeSet: changeSet.connection,
4322
+ stateScope: result.connectionStateScope
4323
+ }, "delete-resources");
3727
4324
  } else if (phase === "delete-services") await Promise.all(changeSet.service.deletes.map((del) => client.deleteAuthService(del.request)));
3728
4325
  }
3729
4326
  /**
@@ -3767,7 +4364,8 @@ async function planAuth(context) {
3767
4364
  },
3768
4365
  conflicts: [...conflicts, ...connectionResult.conflicts],
3769
4366
  unmanaged: [...unmanaged, ...connectionResult.unmanaged],
3770
- resourceOwners: /* @__PURE__ */ new Set([...resourceOwners, ...connectionResult.resourceOwners])
4367
+ resourceOwners: /* @__PURE__ */ new Set([...resourceOwners, ...connectionResult.resourceOwners]),
4368
+ connectionStateScope: connectionResult.stateScope
3771
4369
  };
3772
4370
  }
3773
4371
  async function planServices$2(client, workspaceId, appName, appId, auths, forceApplyAll = false) {
@@ -5643,7 +6241,12 @@ async function planSecretManager(context) {
5643
6241
  getName: (resource) => resource.name,
5644
6242
  getTrn: (name) => resourceTrn(workspaceId, "vault", name)
5645
6243
  });
5646
- const state = loadSecretsState();
6244
+ const stateScope = {
6245
+ workspaceId,
6246
+ applicationId: application.id,
6247
+ applicationName: application.name
6248
+ };
6249
+ const state = loadSecretsState(stateScope);
5647
6250
  const skippedSecrets = [];
5648
6251
  await Promise.all(secretVaults.map(async (vault) => {
5649
6252
  const vaultName = vault.vaultName;
@@ -5752,7 +6355,8 @@ async function planSecretManager(context) {
5752
6355
  skippedSecrets,
5753
6356
  conflicts,
5754
6357
  unmanaged,
5755
- resourceOwners
6358
+ resourceOwners,
6359
+ stateScope
5756
6360
  };
5757
6361
  }
5758
6362
  /**
@@ -5760,11 +6364,11 @@ async function planSecretManager(context) {
5760
6364
  * @param client - Operator client instance
5761
6365
  * @param result - Planned secret changes
5762
6366
  * @param phase - Apply phase
5763
- * @param application - Application to read secrets from for hash state persistence
6367
+ * @param application - Application used for ownership metadata and hash state persistence
5764
6368
  * @returns Promise that resolves when secret changes are applied
5765
6369
  */
5766
6370
  async function applySecretManager(client, result, phase = "create-update", application) {
5767
- const { vaultChangeSet, secretChangeSet } = result;
6371
+ const { vaultChangeSet, secretChangeSet, stateScope } = result;
5768
6372
  if (phase === "create-update") {
5769
6373
  await Promise.all(vaultChangeSet.creates.map(async (create) => {
5770
6374
  await client.createSecretManagerVault(vaultCreateRequest(create));
@@ -5787,13 +6391,14 @@ async function applySecretManager(client, result, phase = "create-update", appli
5787
6391
  }));
5788
6392
  await Promise.all(secretChangeSet.creates.map((create) => client.createSecretManagerSecret(secretCreateRequest(create))));
5789
6393
  await Promise.all(secretChangeSet.updates.map((update) => client.updateSecretManagerSecret(secretUpdateRequest(update))));
5790
- if (application) {
5791
- const state = loadSecretsState();
5792
- for (const vault of application.secrets) {
5793
- if (!Object.hasOwn(state.vaults, vault.vaultName)) state.vaults[vault.vaultName] = {};
5794
- for (const secret of vault.secrets) if (secret.value != null) assertDefined(state.vaults[vault.vaultName], "vault state entry missing")[secret.name] = hashValue(secret.value);
6394
+ const secretHashUpdates = [...secretChangeSet.creates, ...secretChangeSet.updates];
6395
+ if (application && secretHashUpdates.length > 0) {
6396
+ const state = loadSecretsState(stateScope);
6397
+ for (const secret of secretHashUpdates) {
6398
+ if (!Object.hasOwn(state.vaults, secret.vaultName)) state.vaults[secret.vaultName] = {};
6399
+ assertDefined(state.vaults[secret.vaultName], "vault state entry missing")[secret.secretName] = hashValue(secret.value);
5795
6400
  }
5796
- saveSecretsState(state);
6401
+ saveSecretsState(stateScope, state);
5797
6402
  }
5798
6403
  } else {
5799
6404
  await Promise.all(secretChangeSet.deletes.map((del) => client.deleteSecretManagerSecret({
@@ -5806,13 +6411,13 @@ async function applySecretManager(client, result, phase = "create-update", appli
5806
6411
  secretmanagerVaultName: del.name
5807
6412
  })));
5808
6413
  if (secretChangeSet.deletes.length > 0 || vaultChangeSet.deletes.length > 0) {
5809
- const state = loadSecretsState();
6414
+ const state = loadSecretsState(stateScope);
5810
6415
  for (const del of secretChangeSet.deletes) if (Object.hasOwn(state.vaults, del.vaultName)) {
5811
6416
  delete assertDefined(state.vaults[del.vaultName], "vault state entry missing")[del.secretName];
5812
6417
  if (Object.keys(assertDefined(state.vaults[del.vaultName], "vault state entry missing")).length === 0) delete state.vaults[del.vaultName];
5813
6418
  }
5814
6419
  for (const del of vaultChangeSet.deletes) delete state.vaults[del.name];
5815
- saveSecretsState(state);
6420
+ saveSecretsState(stateScope, state);
5816
6421
  }
5817
6422
  }
5818
6423
  }
@@ -10311,11 +10916,11 @@ async function registerJobFunctions(client, changeSet, appName, appId, unchanged
10311
10916
  const allUsedJobNames = /* @__PURE__ */ new Set();
10312
10917
  unchangedWorkflowJobNames.forEach((jobName) => allUsedJobNames.add(jobName));
10313
10918
  for (const item of [...changeSet.creates, ...changeSet.updates]) for (const jobName of item.usedJobNames) allUsedJobNames.add(jobName);
10314
- const existingJobFunctions = await fetchAll(async (pageToken, _maxPageSize) => {
10919
+ const existingJobFunctions = await fetchAll(async (pageToken, maxPageSize) => {
10315
10920
  const response = await client.listWorkflowJobFunctions({
10316
10921
  workspaceId,
10317
10922
  pageToken,
10318
- pageSize: 100
10923
+ pageSize: maxPageSize
10319
10924
  });
10320
10925
  return [response.jobFunctions.map((j) => j.name), response.nextPageToken];
10321
10926
  });
@@ -10495,11 +11100,11 @@ async function planWorkflow(client, workspaceId, appName, appId, workflows, main
10495
11100
  }
10496
11101
  async function planWorkflowJobFunctionDeletes(params) {
10497
11102
  const { client, workspaceId, appName, appId, retainedWorkflowJobNames } = params;
10498
- const existingJobFunctions = await fetchAll(async (pageToken, _maxPageSize) => {
11103
+ const existingJobFunctions = await fetchAll(async (pageToken, maxPageSize) => {
10499
11104
  const response = await client.listWorkflowJobFunctions({
10500
11105
  workspaceId,
10501
11106
  pageToken,
10502
- pageSize: 100
11107
+ pageSize: maxPageSize
10503
11108
  });
10504
11109
  return [response.jobFunctions.map((jobFunction) => jobFunction.name), response.nextPageToken];
10505
11110
  });
@@ -11118,13 +11723,14 @@ function validateItems(params) {
11118
11723
  *
11119
11724
  * Collections not validated: idp client, tailorDB gqlPermission, functionRegistry — no
11120
11725
  * buf.validate annotations.
11121
- * Application cors and IdP userAuthPolicy.allowedReturnOrigins receive special
11122
- * handling: static-website URL placeholders are resolved at apply time, so the
11726
+ * Application cors, AIGateway cors, and IdP userAuthPolicy.allowedReturnOrigins receive
11727
+ * special handling: static-website URL placeholders are resolved at apply time, so the
11123
11728
  * relevant origin/URL constraints would false-positive on `<name>:url` entries
11124
- * here. Application cors is dropped entirely (no other constraint to lose); IdP
11125
- * `allowedReturnOrigins` substitutes placeholder entries with a dummy origin so
11126
- * the per-item regex and the cross-field `enable_mfa requires ≥1 origin` rule
11127
- * still get exercised on the rest of the payload.
11729
+ * here. Application cors and AIGateway cors are dropped entirely, forgoing their
11730
+ * per-item URL/origin constraints for the whole list rather than substituting
11731
+ * placeholders; IdP `allowedReturnOrigins` instead substitutes placeholder entries
11732
+ * with a dummy origin so the per-item regex and the cross-field `enable_mfa requires
11733
+ * ≥1 origin` rule still get exercised on the rest of the payload.
11128
11734
  * Workflow jobFunctions map excluded: versions are registered at apply time (registerJobFunctions)
11129
11735
  * and the map field carries no min_items constraint. Job names are validated separately via
11130
11736
  * CreateWorkflowJobFunctionRequestSchema using usedJobNames from the workflow change set.
@@ -11134,7 +11740,7 @@ function validateItems(params) {
11134
11740
  * @param input - Plan results from the plan phase
11135
11741
  */
11136
11742
  async function validatePlan(input) {
11137
- const { tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, workflowExecutionPolicy, secretManager } = input;
11743
+ const { tailorDB, staticWebsite, aiGateway, idp, auth, pipeline, app, executor, workflow, workflowExecutionPolicy, secretManager } = input;
11138
11744
  const validator = createValidator();
11139
11745
  const violations = [];
11140
11746
  function creates(schema, kind, items) {
@@ -11187,6 +11793,20 @@ async function validatePlan(input) {
11187
11793
  creates(CreateStaticWebsiteRequestSchema, "StaticWebsite", staticWebsite.changeSet.creates);
11188
11794
  updates(UpdateStaticWebsiteRequestSchema, "StaticWebsite", staticWebsite.changeSet.updates);
11189
11795
  creates(AddCustomDomainRequestSchema, "StaticWebsite custom domain", staticWebsite.customDomainChangeSet.creates);
11796
+ creates(CreateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.creates.map((item) => ({
11797
+ name: item.name,
11798
+ request: {
11799
+ ...item.request,
11800
+ cors: void 0
11801
+ }
11802
+ })));
11803
+ updates(UpdateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.updates.map((item) => ({
11804
+ name: item.name,
11805
+ request: {
11806
+ ...item.request,
11807
+ cors: void 0
11808
+ }
11809
+ })));
11190
11810
  const placeholderOriginReplacement = "https://placeholder.invalid";
11191
11811
  const substituteIdpReturnOrigins = (item) => {
11192
11812
  const request = item.request;
@@ -11329,6 +11949,831 @@ async function validatePlan(input) {
11329
11949
  throw new Error(`${violations.length} validation error(s) found in ${resourceNames.size} resource(s)`);
11330
11950
  }
11331
11951
 
11952
+ //#endregion
11953
+ //#region src/cli/shared/profile-name.ts
11954
+ const profileNameSchema = z.string().min(1, "Profile must not be empty");
11955
+
11956
+ //#endregion
11957
+ //#region src/cli/shared/workspace-name.ts
11958
+ const workspaceNameSchema = z.string().min(3, "Name must be at least 3 characters").max(63, "Name must be at most 63 characters").regex(/^[a-z0-9-]+$/, "Name can only contain lowercase letters, numbers, and hyphens").refine((name) => !name.startsWith("-") && !name.endsWith("-"), "Name cannot start or end with a hyphen");
11959
+ /**
11960
+ * Validate a workspace name for use in an interactive prompt.
11961
+ * @param name - Candidate workspace name
11962
+ * @returns True when valid, otherwise a validation message
11963
+ */
11964
+ function validateWorkspaceName(name) {
11965
+ const result = workspaceNameSchema.safeParse(name);
11966
+ return result.success ? true : assertDefined(result.error.issues[0], "Zod returned no issues").message;
11967
+ }
11968
+
11969
+ //#endregion
11970
+ //#region src/cli/shared/format.ts
11971
+ /**
11972
+ * Format a protobuf Timestamp to ISO string.
11973
+ * @param timestamp - Protobuf timestamp
11974
+ * @returns Date object or null if invalid
11975
+ */
11976
+ function formatTimestamp(timestamp) {
11977
+ if (!timestamp) return null;
11978
+ const date = timestampDate(timestamp);
11979
+ if (Number.isNaN(date.getTime())) return null;
11980
+ return date;
11981
+ }
11982
+ /**
11983
+ * Formats a table with consistent single-line border style.
11984
+ * Use this instead of importing `table` directly.
11985
+ * @param data - Table data
11986
+ * @param config - Table configuration
11987
+ * @returns Formatted table string
11988
+ */
11989
+ function formatTable(data, config) {
11990
+ return table(data, {
11991
+ ...config,
11992
+ border: getBorderCharacters("norc")
11993
+ });
11994
+ }
11995
+ /**
11996
+ * Formats a key-value table with single-line border style.
11997
+ * @param data - Key-value pairs
11998
+ * @returns Formatted key-value table string
11999
+ */
12000
+ function formatKeyValueTable(data) {
12001
+ return formatTable(data, { singleLine: true });
12002
+ }
12003
+ /**
12004
+ * Format a Date or ISO timestamp string as a human-readable relative time.
12005
+ * @param value - Date object, ISO date string, or null
12006
+ * @returns Relative time (e.g., "5 minutes ago") or "N/A" for null/invalid
12007
+ */
12008
+ function humanizeRelativeTime(value) {
12009
+ if (value === null) return "N/A";
12010
+ const date = value instanceof Date ? value : new Date(value);
12011
+ if (Number.isNaN(date.getTime())) return typeof value === "string" ? value : "N/A";
12012
+ return formatDistanceToNowStrict(date, { addSuffix: true });
12013
+ }
12014
+
12015
+ //#endregion
12016
+ //#region src/cli/commands/workspace/transform.ts
12017
+ const workspaceInfo = (workspace, folderName) => {
12018
+ const info = {
12019
+ id: workspace.id,
12020
+ name: workspace.name,
12021
+ region: workspace.region,
12022
+ ...workspace.organizationId ? { organizationId: workspace.organizationId } : {},
12023
+ ...workspace.folderId ? { folderId: workspace.folderId } : {},
12024
+ createdAt: formatTimestamp(workspace.createTime),
12025
+ updatedAt: formatTimestamp(workspace.updateTime)
12026
+ };
12027
+ return folderName ? {
12028
+ ...info,
12029
+ folderName
12030
+ } : info;
12031
+ };
12032
+ const workspaceDetails = (workspace, folderName) => {
12033
+ return {
12034
+ ...workspaceInfo(workspace, folderName),
12035
+ deleteProtection: workspace.deleteProtection,
12036
+ organizationId: workspace.organizationId,
12037
+ folderId: workspace.folderId
12038
+ };
12039
+ };
12040
+ async function resolveWorkspaceFolderName(client, workspace) {
12041
+ if (!workspace.organizationId || !workspace.folderId) return void 0;
12042
+ try {
12043
+ return (await client.getOrganizationFolder({
12044
+ organizationId: workspace.organizationId,
12045
+ folderId: workspace.folderId
12046
+ })).folder?.name || void 0;
12047
+ } catch (error) {
12048
+ if (error instanceof ConnectError && (error.code === Code.NotFound || error.code === Code.PermissionDenied)) return;
12049
+ logger.warn(`Failed to resolve workspace folder name: ${error}`);
12050
+ return;
12051
+ }
12052
+ }
12053
+ function createWorkspaceFolderNameResolver(client) {
12054
+ const cache = /* @__PURE__ */ new Map();
12055
+ return (workspace) => {
12056
+ if (!workspace.organizationId || !workspace.folderId) return Promise.resolve(void 0);
12057
+ const cacheKey = `${workspace.organizationId}/${workspace.folderId}`;
12058
+ const cached = cache.get(cacheKey);
12059
+ if (cached) return cached;
12060
+ const promise = resolveWorkspaceFolderName(client, workspace);
12061
+ cache.set(cacheKey, promise);
12062
+ return promise;
12063
+ };
12064
+ }
12065
+ async function workspaceInfoWithFolderName(client, workspace) {
12066
+ const folderName = await resolveWorkspaceFolderName(client, workspace);
12067
+ return workspaceInfo(workspace, folderName);
12068
+ }
12069
+ async function workspaceDetailsWithFolderName(client, workspace) {
12070
+ const folderName = await resolveWorkspaceFolderName(client, workspace);
12071
+ return workspaceDetails(workspace, folderName);
12072
+ }
12073
+ async function workspaceInfosWithFolderNames(client, workspaces) {
12074
+ const resolveFolderName = createWorkspaceFolderNameResolver(client);
12075
+ const limit = pLimit(5);
12076
+ return Promise.all(workspaces.map((workspace) => limit(async () => workspaceInfo(workspace, await resolveFolderName(workspace)))));
12077
+ }
12078
+ function workspaceDisplayName(workspace) {
12079
+ return workspace.folderName ? `${workspace.folderName}/${workspace.name}` : workspace.name;
12080
+ }
12081
+ function createWorkspaceNameTransformer(nameKey, folderNameKey) {
12082
+ return (value, item) => {
12083
+ const workspace = item;
12084
+ const name = workspace[nameKey];
12085
+ const folderName = workspace[folderNameKey];
12086
+ if (typeof name === "string" && typeof folderName === "string") return workspaceDisplayName({
12087
+ name,
12088
+ folderName
12089
+ });
12090
+ return String(value ?? "");
12091
+ };
12092
+ }
12093
+ const workspaceNameTransformer = createWorkspaceNameTransformer("name", "folderName");
12094
+
12095
+ //#endregion
12096
+ //#region src/cli/commands/workspace/create.ts
12097
+ /**
12098
+ * Schema for workspace creation options
12099
+ * - name: 3-63 chars, lowercase alphanumeric and hyphens, cannot start/end with hyphen
12100
+ * - organizationId, folderId: optional UUIDs
12101
+ */
12102
+ const createWorkspaceOptionsSchema = z.object({
12103
+ name: workspaceNameSchema,
12104
+ region: z.string(),
12105
+ deleteProtection: z.boolean().optional(),
12106
+ organizationId: z.uuid().optional(),
12107
+ folderId: z.uuid().optional(),
12108
+ profile: profileNameSchema.optional()
12109
+ });
12110
+ const validateRegion = async (region, client) => {
12111
+ const availableRegions = await client.listAvailableWorkspaceRegions({});
12112
+ if (!availableRegions.regions.includes(region)) throw new Error(`Region must be one of: ${availableRegions.regions.join(", ")}.`);
12113
+ };
12114
+ function profilePlatformSettings(platformConfig) {
12115
+ const hasOAuth2ClientId = platformConfig?.oauth2ClientId || process.env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID;
12116
+ const hasConsoleUrl = platformConfig?.consoleUrl || process.env.TAILOR_PLATFORM_CONSOLE_URL;
12117
+ return {
12118
+ ...isDefaultPlatform(platformConfig) ? {} : { platform_url: getPlatformBaseUrl(platformConfig) },
12119
+ ...hasOAuth2ClientId ? { oauth2_client_id: getOAuth2ClientId(platformConfig) } : {},
12120
+ ...hasConsoleUrl ? { console_url: getConsoleBaseUrl(platformConfig) } : {}
12121
+ };
12122
+ }
12123
+ /**
12124
+ * Create a new workspace with the given options.
12125
+ * @param options - Workspace creation options
12126
+ * @returns Created workspace info
12127
+ */
12128
+ async function createWorkspace$1(options) {
12129
+ const validated = validateCreateWorkspaceOptions(options);
12130
+ const client = await initOperatorClient(await loadAccessToken({ profile: validated.profile }), await loadPlatformClientConfig({ profile: validated.profile }));
12131
+ await validateRegion(validated.region, client);
12132
+ return createValidatedWorkspaceWithClient(client, validated);
12133
+ }
12134
+ /**
12135
+ * Create a workspace after its local options and region have been validated.
12136
+ * @param client - Authenticated Operator client
12137
+ * @param options - Validated workspace creation options
12138
+ * @returns Created workspace info
12139
+ */
12140
+ async function createValidatedWorkspaceWithClient(client, options) {
12141
+ return workspaceInfoWithFolderName(client, assertDefined((await client.createWorkspace({
12142
+ workspaceName: options.name,
12143
+ workspaceRegion: options.region,
12144
+ deleteProtection: options.deleteProtection ?? false,
12145
+ organizationId: options.organizationId,
12146
+ folderId: options.folderId
12147
+ })).workspace, "createWorkspace response missing workspace"));
12148
+ }
12149
+ /**
12150
+ * Validate workspace creation options without making API calls.
12151
+ * @param options - Workspace creation options
12152
+ * @returns Validated workspace creation options
12153
+ */
12154
+ function validateCreateWorkspaceOptions(options) {
12155
+ const result = createWorkspaceOptionsSchema.safeParse(options);
12156
+ if (!result.success) throw new Error(assertDefined(result.error.issues[0], "Zod returned no issues").message);
12157
+ return result.data;
12158
+ }
12159
+ const createCommand$1 = defineAppCommand({
12160
+ name: "create",
12161
+ description: "Create a new Tailor Platform workspace.",
12162
+ args: z.object({
12163
+ name: arg(z.string(), {
12164
+ alias: "n",
12165
+ description: "Workspace name"
12166
+ }),
12167
+ region: arg(z.string(), {
12168
+ alias: "r",
12169
+ description: "Workspace region (us-west, asia-northeast)"
12170
+ }),
12171
+ "delete-protection": arg(z.boolean().default(false), {
12172
+ alias: "d",
12173
+ description: "Enable delete protection"
12174
+ }),
12175
+ "organization-id": arg(z.string().optional(), {
12176
+ alias: "o",
12177
+ description: "Organization ID to workspace associate with",
12178
+ env: "TAILOR_PLATFORM_ORGANIZATION_ID"
12179
+ }),
12180
+ "folder-id": arg(z.string().optional(), {
12181
+ alias: "f",
12182
+ description: "Folder ID to workspace associate with",
12183
+ env: "TAILOR_PLATFORM_FOLDER_ID"
12184
+ }),
12185
+ "profile-name": arg(z.string().optional(), {
12186
+ alias: "p",
12187
+ description: "Profile name to create"
12188
+ }),
12189
+ profile: arg(profileNameSchema.optional(), {
12190
+ description: "Workspace profile used for authentication and Platform selection",
12191
+ env: "TAILOR_PLATFORM_PROFILE"
12192
+ }),
12193
+ "profile-user": arg(z.string().optional(), { description: "User email for the profile (defaults to current user)" }),
12194
+ permission: arg(z.enum(["write", "read"]).default("write"), { description: "Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active." })
12195
+ }).strict(),
12196
+ run: async (args) => {
12197
+ await assertWritable({ profile: args.profile });
12198
+ const profileName = args["profile-name"];
12199
+ let profileSetup;
12200
+ if (profileName) {
12201
+ const config = await readPlatformConfig();
12202
+ if (config.profiles[profileName]) throw new Error(`Profile "${profileName}" already exists.`);
12203
+ const activeProfileName = args.profile;
12204
+ const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
12205
+ const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
12206
+ const profileUser = args["profile-user"] || activeProfileEntry?.user || config.current_user;
12207
+ if (!profileUser) throw new Error("Current user not found. Please login or specify --profile-user to create a profile.");
12208
+ if (!hasUserTokenEntry(config, profileUser, platformConfig)) throw new Error(`User "${profileUser}" not found.\nPlease verify your user name and login using 'tailor-sdk login' command.`);
12209
+ profileSetup = {
12210
+ name: profileName,
12211
+ user: profileUser,
12212
+ platformSettings: profilePlatformSettings(platformConfig)
12213
+ };
12214
+ }
12215
+ const workspace = await createWorkspace$1({
12216
+ name: args.name,
12217
+ region: args.region,
12218
+ deleteProtection: args["delete-protection"],
12219
+ organizationId: args["organization-id"],
12220
+ folderId: args["folder-id"],
12221
+ profile: args.profile
12222
+ });
12223
+ let profileInfo;
12224
+ if (profileSetup) {
12225
+ const config = await readPlatformConfig();
12226
+ const platformSettings = profileSetup.platformSettings;
12227
+ config.profiles[profileSetup.name] = {
12228
+ user: profileSetup.user,
12229
+ workspace_id: workspace.id,
12230
+ ...args.permission === "read" ? { readonly: true } : {},
12231
+ ...platformSettings
12232
+ };
12233
+ writePlatformConfig(config);
12234
+ profileInfo = {
12235
+ name: profileSetup.name,
12236
+ user: profileSetup.user,
12237
+ workspaceId: workspace.id,
12238
+ permission: args.permission,
12239
+ ...platformSettings.platform_url ? { platformUrl: platformSettings.platform_url } : {},
12240
+ ...platformSettings.oauth2_client_id ? { oauth2ClientId: platformSettings.oauth2_client_id } : {},
12241
+ ...platformSettings.console_url ? { consoleUrl: platformSettings.console_url } : {}
12242
+ };
12243
+ if (!args.json) logger.success(`Profile "${profileSetup.name}" created successfully.`);
12244
+ }
12245
+ if (!args.json) logger.success(`Workspace "${workspaceDisplayName(workspace)}" created successfully.`);
12246
+ if (args.json && profileInfo) {
12247
+ logger.out({
12248
+ ...workspace,
12249
+ profile: profileInfo
12250
+ });
12251
+ return;
12252
+ }
12253
+ logger.out(workspace, { display: {
12254
+ name: workspaceNameTransformer,
12255
+ folderName: null
12256
+ } });
12257
+ if (profileInfo) {
12258
+ logger.out("Profile:");
12259
+ logger.out(profileInfo);
12260
+ }
12261
+ }
12262
+ });
12263
+
12264
+ //#endregion
12265
+ //#region src/cli/commands/workspace/list.ts
12266
+ /**
12267
+ * List workspaces with an optional order and limit.
12268
+ * @param options - Workspace listing options
12269
+ * @returns List of workspaces
12270
+ */
12271
+ async function listWorkspaces(options) {
12272
+ const profile = profileNameSchema.optional().parse(options?.profile);
12273
+ return listWorkspacesWithClient(await initOperatorClient(await loadAccessToken({ profile }), await loadPlatformClientConfig({ profile })), options);
12274
+ }
12275
+ /**
12276
+ * List workspaces using an existing Operator client.
12277
+ * @param client - Authenticated Operator client
12278
+ * @param options - Workspace listing options
12279
+ * @returns List of workspaces
12280
+ */
12281
+ async function listWorkspacesWithClient(client, options) {
12282
+ const pageDirection = toPageDirection(options?.order);
12283
+ return workspaceInfosWithFolderNames(client, await fetchPaged(async (pageToken, pageSize) => {
12284
+ const { workspaces, nextPageToken } = await client.listWorkspaces({
12285
+ pageToken,
12286
+ pageSize,
12287
+ pageDirection
12288
+ });
12289
+ return [workspaces, nextPageToken];
12290
+ }, { limit: options?.limit }));
12291
+ }
12292
+ const listCommand$9 = defineAppCommand({
12293
+ name: "list",
12294
+ description: "List all Tailor Platform workspaces.",
12295
+ args: z.object({
12296
+ ...paginationArgs(),
12297
+ profile: arg(profileNameSchema.optional(), {
12298
+ description: "Workspace profile used for authentication and Platform selection",
12299
+ env: "TAILOR_PLATFORM_PROFILE"
12300
+ })
12301
+ }).strict(),
12302
+ run: async (args) => {
12303
+ const workspaces = await listWorkspaces({
12304
+ order: args.order,
12305
+ limit: args.limit,
12306
+ profile: args.profile
12307
+ });
12308
+ logger.out(workspaces, { display: {
12309
+ name: workspaceNameTransformer,
12310
+ folderName: null,
12311
+ organizationId: null,
12312
+ folderId: null,
12313
+ updatedAt: null
12314
+ } });
12315
+ }
12316
+ });
12317
+
12318
+ //#endregion
12319
+ //#region src/cli/commands/deploy/workspace-context.ts
12320
+ const workspaceContextSchema = z.object({
12321
+ version: z.literal(1),
12322
+ platformUrl: z.url(),
12323
+ applicationId: z.string().min(1).optional(),
12324
+ workspaceId: z.uuid()
12325
+ });
12326
+ function defaultConfigPath() {
12327
+ return resolve(process.cwd(), "tailor.config.ts");
12328
+ }
12329
+ function contextPath(configPath) {
12330
+ return join(dirname(configPath), ".tailor-sdk", `${basename(configPath)}.context.json`);
12331
+ }
12332
+ /**
12333
+ * Load the project workspace context when it belongs to the current platform.
12334
+ * Missing, malformed, and cross-platform state is ignored.
12335
+ * @param platformUrl - Current Platform API base URL
12336
+ * @param configPath - Configuration file whose workspace selection is loaded
12337
+ * @param applicationId - Application identity that must match the saved context
12338
+ * @returns Valid context for the current platform, or undefined
12339
+ */
12340
+ async function loadWorkspaceContext(platformUrl, configPath = defaultConfigPath(), applicationId) {
12341
+ let contents;
12342
+ try {
12343
+ contents = await readFile(contextPath(configPath), "utf8");
12344
+ } catch (error) {
12345
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
12346
+ throw error;
12347
+ }
12348
+ let value;
12349
+ try {
12350
+ value = JSON.parse(contents);
12351
+ } catch {
12352
+ return;
12353
+ }
12354
+ const result = workspaceContextSchema.safeParse(value);
12355
+ if (!result.success || result.data.platformUrl !== platformUrl || applicationId !== void 0 && result.data.applicationId !== applicationId) return;
12356
+ return result.data;
12357
+ }
12358
+ /**
12359
+ * Persist the selected workspace as project-local SDK state.
12360
+ * @param context - Workspace context to persist
12361
+ * @param configPath - Configuration file whose workspace selection is persisted
12362
+ * @param applicationId - Application identity stored with the workspace selection
12363
+ */
12364
+ async function saveWorkspaceContext(context, configPath = defaultConfigPath(), applicationId) {
12365
+ const validated = workspaceContextSchema.parse({
12366
+ ...context,
12367
+ ...applicationId === void 0 ? {} : { applicationId }
12368
+ });
12369
+ const stateDirectory = join(dirname(configPath), ".tailor-sdk");
12370
+ const targetPath = contextPath(configPath);
12371
+ const serialized = `${JSON.stringify(validated, null, 2)}\n`;
12372
+ try {
12373
+ if (await readFile(targetPath, "utf8") === serialized) return;
12374
+ } catch {}
12375
+ const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;
12376
+ await mkdir(stateDirectory, { recursive: true });
12377
+ try {
12378
+ await writeFile(temporaryPath, serialized, { mode: 384 });
12379
+ await rename(temporaryPath, targetPath);
12380
+ } catch (error) {
12381
+ await rm(temporaryPath, { force: true }).catch(() => void 0);
12382
+ throw error;
12383
+ }
12384
+ }
12385
+
12386
+ //#endregion
12387
+ //#region src/cli/commands/deploy/workspace.ts
12388
+ function suggestedWorkspaceName() {
12389
+ const name = basename(process.cwd()).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, 63).replace(/-+$/, "");
12390
+ return validateWorkspaceName(name) === true ? name : "my-workspace";
12391
+ }
12392
+ function executableAction(args) {
12393
+ return {
12394
+ command: "tailor-sdk",
12395
+ args
12396
+ };
12397
+ }
12398
+ function deployArgs(options) {
12399
+ return options.deployArgs ?? ["deploy"];
12400
+ }
12401
+ function workspaceCommandArgs(options) {
12402
+ return options.workspaceCommandArgs ?? [];
12403
+ }
12404
+ function createDeployArgs(options, name, region) {
12405
+ return [
12406
+ ...deployArgs(options),
12407
+ "--create-workspace",
12408
+ "--workspace-name",
12409
+ name,
12410
+ "--workspace-region",
12411
+ region,
12412
+ ...options.organizationId ? ["--organization-id", options.organizationId] : [],
12413
+ ...options.folderId ? ["--folder-id", options.folderId] : []
12414
+ ];
12415
+ }
12416
+ function selectDeployArgs(options) {
12417
+ return [
12418
+ ...deployArgs(options),
12419
+ "--workspace-id",
12420
+ "<workspace-id>"
12421
+ ];
12422
+ }
12423
+ function workspaceLabel(workspace) {
12424
+ const organization = workspace.organizationId ?? "personal";
12425
+ return `${workspaceDisplayName(workspace)} (${workspace.region}, org: ${organization}, id: ${workspace.id})`;
12426
+ }
12427
+ function workspaceMatchesRequestedIdentity(workspace, options) {
12428
+ return options.workspaceName === workspace.name && options.workspaceRegion === workspace.region && options.organizationId === workspace.organizationId && options.folderId === workspace.folderId;
12429
+ }
12430
+ function workspaceIdentity(workspace) {
12431
+ const { id, name, region, organizationId, folderId } = workspace;
12432
+ return {
12433
+ id,
12434
+ name,
12435
+ region,
12436
+ organizationId,
12437
+ folderId
12438
+ };
12439
+ }
12440
+ function projectContextTargets(contextTargets) {
12441
+ if (!contextTargets) return void 0;
12442
+ return [...new Map(contextTargets.map((target) => [target.configPath, target])).values()];
12443
+ }
12444
+ async function loadProjectContexts(platformUrl, contextTargets) {
12445
+ const targets = projectContextTargets(contextTargets);
12446
+ if (!targets || targets.length === 0) {
12447
+ const context = await loadWorkspaceContext(platformUrl);
12448
+ return context ? [context] : [];
12449
+ }
12450
+ return (await Promise.all(targets.map(({ configPath, applicationId }) => loadWorkspaceContext(platformUrl, configPath, applicationId)))).filter((context) => context !== void 0);
12451
+ }
12452
+ async function persistWorkspaceContext(context, contextTargets) {
12453
+ const targets = projectContextTargets(contextTargets);
12454
+ if (!targets || targets.length === 0) {
12455
+ await saveWorkspaceContext(context);
12456
+ return;
12457
+ }
12458
+ const failures = (await Promise.allSettled(targets.map(({ configPath, applicationId }) => saveWorkspaceContext(context, configPath, applicationId)))).filter((result) => result.status === "rejected");
12459
+ if (failures.length > 0) throw new Error(failures.map(({ reason }) => reason instanceof Error ? reason.message : String(reason)).join("; "));
12460
+ }
12461
+ async function rememberWorkspaceContext(context, options, failurePolicy = "error-on-partial") {
12462
+ if (options.dryRun) return;
12463
+ try {
12464
+ await persistWorkspaceContext(context, options.contextTargets);
12465
+ } catch (error) {
12466
+ if ((projectContextTargets(options.contextTargets)?.length ?? 0) > 1 && failurePolicy === "error-on-partial") throw createCLIError({
12467
+ code: "WORKSPACE_CONTEXT_SAVE_FAILED",
12468
+ message: "The workspace selection could not be saved for every configuration file.",
12469
+ details: error instanceof Error ? error.message : String(error),
12470
+ suggestion: "Fix project state permissions, then rerun deploy with the workspace ID.",
12471
+ next: executableAction([
12472
+ ...deployArgs(options),
12473
+ "--workspace-id",
12474
+ context.workspaceId
12475
+ ]),
12476
+ context: {
12477
+ workspaceId: context.workspaceId,
12478
+ configPaths: options.contextTargets?.map(({ configPath }) => configPath)
12479
+ }
12480
+ });
12481
+ logger.warn(`Could not save the project workspace selection (${error instanceof Error ? error.message : String(error)}). Continue with --workspace-id ${context.workspaceId} on later runs.`);
12482
+ }
12483
+ }
12484
+ async function useWorkspace(client, platformUrl, workspace, options, failurePolicy = "error-on-partial") {
12485
+ await rememberWorkspaceContext({
12486
+ version: 1,
12487
+ platformUrl,
12488
+ workspaceId: workspace.id
12489
+ }, options, failurePolicy);
12490
+ logger.info(`Using workspace: ${workspaceLabel(workspace)}`);
12491
+ return {
12492
+ client,
12493
+ workspaceId: workspace.id
12494
+ };
12495
+ }
12496
+ function useRememberedWorkspace(client, workspace) {
12497
+ logger.warn(`Using saved workspace selection: ${workspaceLabel(workspace)}`);
12498
+ return {
12499
+ client,
12500
+ workspaceId: workspace.id
12501
+ };
12502
+ }
12503
+ const createNewWorkspaceSelection = "create-new-workspace";
12504
+ async function chooseWorkspace(client, platformUrl, workspaces, options) {
12505
+ const workspaceId = await prompt.select({
12506
+ message: "Select a workspace",
12507
+ choices: [...workspaces.map((workspace) => ({
12508
+ name: workspaceLabel(workspace),
12509
+ value: workspace.id
12510
+ })), {
12511
+ name: "Create new workspace",
12512
+ value: createNewWorkspaceSelection
12513
+ }]
12514
+ });
12515
+ if (workspaceId === createNewWorkspaceSelection) return createWorkspaceForDeploy(client, platformUrl, options);
12516
+ const workspace = workspaces.find(({ id }) => id === workspaceId);
12517
+ if (!workspace) throw new Error("Selected workspace was not found");
12518
+ return useWorkspace(client, platformUrl, workspace, options);
12519
+ }
12520
+ function invalidCreateOptionsError(options, name, region, details) {
12521
+ return createCLIError({
12522
+ code: "WORKSPACE_CREATE_OPTIONS_INVALID",
12523
+ message: "Workspace creation options are invalid.",
12524
+ details,
12525
+ suggestion: "Correct the workspace creation options and rerun deploy.",
12526
+ next: executableAction(createDeployArgs(options, name, region))
12527
+ });
12528
+ }
12529
+ async function createWorkspace(client, platformUrl, options, availableRegions, validatedOptions) {
12530
+ let name = options.workspaceName;
12531
+ let region = options.workspaceRegion;
12532
+ const interactive = canPrompt();
12533
+ if (interactive) {
12534
+ name ??= await prompt.text({
12535
+ message: "Workspace name",
12536
+ default: suggestedWorkspaceName(),
12537
+ validate: validateWorkspaceName
12538
+ });
12539
+ region ??= await prompt.select({
12540
+ message: "Workspace region",
12541
+ choices: availableRegions.map((value) => ({
12542
+ name: value,
12543
+ value
12544
+ }))
12545
+ });
12546
+ } else {
12547
+ const missingOptions = [...name === void 0 ? ["--workspace-name"] : [], ...region === void 0 ? ["--workspace-region"] : []];
12548
+ if (missingOptions.length > 0) throw createCLIError({
12549
+ code: "WORKSPACE_CREATE_OPTIONS_REQUIRED",
12550
+ message: "Workspace creation requires a name and region in non-interactive mode.",
12551
+ suggestion: "Provide every workspace creation option and rerun deploy.",
12552
+ next: executableAction(createDeployArgs(options, name ?? "<name>", region ?? "<region>")),
12553
+ context: { missingOptions }
12554
+ });
12555
+ }
12556
+ if (!name || !region) throw new Error("Workspace creation options were not resolved");
12557
+ let validated = validatedOptions;
12558
+ if (!validated) {
12559
+ try {
12560
+ validated = validateCreateWorkspaceOptions({
12561
+ name,
12562
+ region,
12563
+ organizationId: options.organizationId,
12564
+ folderId: options.folderId
12565
+ });
12566
+ } catch (error) {
12567
+ throw invalidCreateOptionsError(options, name, region, error instanceof Error ? error.message : String(error));
12568
+ }
12569
+ if (!availableRegions.includes(region)) throw invalidCreateOptionsError(options, name, region, `Region must be one of: ${availableRegions.join(", ")}.`);
12570
+ }
12571
+ if (interactive) {
12572
+ const scope = [options.organizationId ? `organization: ${options.organizationId}` : void 0, options.folderId ? `folder: ${options.folderId}` : void 0].filter((value) => value !== void 0);
12573
+ if (!await prompt.confirm({
12574
+ message: `Create workspace "${name}" in ${region}${scope.length > 0 ? ` (${scope.join(", ")})` : ""}?`,
12575
+ default: true
12576
+ })) throw createCLIError({
12577
+ code: "WORKSPACE_CREATION_CANCELLED",
12578
+ message: "Workspace creation was cancelled."
12579
+ });
12580
+ }
12581
+ let workspace;
12582
+ try {
12583
+ workspace = await createValidatedWorkspaceWithClient(client, validated);
12584
+ } catch (error) {
12585
+ if (error instanceof ConnectError && ![
12586
+ Code.Canceled,
12587
+ Code.Unknown,
12588
+ Code.DeadlineExceeded,
12589
+ Code.Aborted,
12590
+ Code.Internal,
12591
+ Code.Unavailable,
12592
+ Code.DataLoss
12593
+ ].includes(error.code)) throw error;
12594
+ throw createCLIError({
12595
+ code: "WORKSPACE_CREATION_FAILED",
12596
+ message: "Workspace creation did not complete successfully.",
12597
+ details: error instanceof Error ? error.message : String(error),
12598
+ suggestion: "The outcome may be uncertain. List workspaces before retrying creation to avoid duplicates.",
12599
+ next: executableAction([
12600
+ "workspace",
12601
+ "list",
12602
+ ...workspaceCommandArgs(options),
12603
+ "--json"
12604
+ ])
12605
+ });
12606
+ }
12607
+ await rememberWorkspaceContext({
12608
+ version: 1,
12609
+ platformUrl,
12610
+ workspaceId: workspace.id
12611
+ }, options);
12612
+ logger.success(`Created workspace: ${workspaceLabel(workspace)}`);
12613
+ logger.info(`Reuse this workspace with: tailor-sdk deploy --workspace-id ${workspace.id}`);
12614
+ logger.info(`Or set TAILOR_PLATFORM_WORKSPACE_ID=${workspace.id}.`);
12615
+ return {
12616
+ client,
12617
+ workspaceId: workspace.id
12618
+ };
12619
+ }
12620
+ async function createWorkspaceForDeploy(client, platformUrl, options, requestedRegions, validatedOptions) {
12621
+ const regions = requestedRegions ?? (await client.listAvailableWorkspaceRegions({})).regions;
12622
+ if (options.dryRun) throw createCLIError({
12623
+ code: "WORKSPACE_CREATION_DISABLED_IN_DRY_RUN",
12624
+ message: "Dry-run cannot create the workspace required to build a deployment plan.",
12625
+ suggestion: "Create a workspace explicitly, then rerun the same dry-run with its workspace ID.",
12626
+ context: { availableRegions: regions }
12627
+ });
12628
+ return createWorkspace(client, platformUrl, options, regions, validatedOptions);
12629
+ }
12630
+ /**
12631
+ * Resolve or provision the workspace used by deploy.
12632
+ * Explicit configuration wins over project context and account discovery.
12633
+ * @param options - Deploy workspace selection and creation options
12634
+ * @returns Authenticated client and resolved workspace ID
12635
+ */
12636
+ async function resolveDeployWorkspace(options = {}) {
12637
+ const createOptionNames = [options.workspaceName !== void 0 ? "--workspace-name" : void 0, options.workspaceRegion !== void 0 ? "--workspace-region" : void 0].filter((name) => name !== void 0);
12638
+ if (!options.createWorkspace && createOptionNames.length > 0) throw createCLIError({
12639
+ code: "WORKSPACE_CREATE_FLAG_REQUIRED",
12640
+ message: "Workspace creation options require --create-workspace.",
12641
+ suggestion: "Add --create-workspace or remove the workspace creation options.",
12642
+ next: executableAction(createDeployArgs(options, options.workspaceName ?? "<name>", options.workspaceRegion ?? "<region>")),
12643
+ context: { options: createOptionNames }
12644
+ });
12645
+ if (options.createWorkspace && options.workspaceName !== void 0) {
12646
+ const nameValidation = validateWorkspaceName(options.workspaceName);
12647
+ if (nameValidation !== true) throw invalidCreateOptionsError(options, options.workspaceName, options.workspaceRegion ?? "<region>", nameValidation);
12648
+ }
12649
+ if (options.createWorkspace && options.workspaceRegion === "") throw invalidCreateOptionsError(options, options.workspaceName ?? "<name>", options.workspaceRegion, "Region must not be empty.");
12650
+ const explicitWorkspaceId = await tryLoadWorkspaceId({
12651
+ workspaceId: options.workspaceId,
12652
+ profile: options.profile
12653
+ });
12654
+ const accessToken = await loadAccessToken({ profile: options.profile });
12655
+ const platformConfig = await loadPlatformClientConfig({
12656
+ profile: options.profile,
12657
+ allowMissingProfile: explicitWorkspaceId !== void 0
12658
+ });
12659
+ const platformUrl = getPlatformBaseUrl(platformConfig);
12660
+ const client = await initOperatorClient(accessToken, platformConfig);
12661
+ if (explicitWorkspaceId) {
12662
+ let response;
12663
+ try {
12664
+ response = await client.getWorkspace({ workspaceId: explicitWorkspaceId });
12665
+ } catch (error) {
12666
+ if (error instanceof ConnectError && error.code === Code.NotFound) throw createCLIError({
12667
+ code: "WORKSPACE_NOT_FOUND",
12668
+ message: `Workspace "${explicitWorkspaceId}" was not found.`
12669
+ });
12670
+ throw error;
12671
+ }
12672
+ if (!response.workspace) throw createCLIError({
12673
+ code: "WORKSPACE_NOT_FOUND",
12674
+ message: `Workspace "${explicitWorkspaceId}" was not found.`
12675
+ });
12676
+ return useWorkspace(client, platformUrl, workspaceInfo(response.workspace), options, "warn");
12677
+ }
12678
+ let requestedCreateOptions;
12679
+ if (options.createWorkspace && options.workspaceName !== void 0 && options.workspaceRegion !== void 0) try {
12680
+ requestedCreateOptions = validateCreateWorkspaceOptions({
12681
+ name: options.workspaceName,
12682
+ region: options.workspaceRegion,
12683
+ organizationId: options.organizationId,
12684
+ folderId: options.folderId
12685
+ });
12686
+ } catch (error) {
12687
+ throw invalidCreateOptionsError(options, options.workspaceName, options.workspaceRegion, error instanceof Error ? error.message : String(error));
12688
+ }
12689
+ const [contexts, workspaces] = await Promise.all([loadProjectContexts(platformUrl, options.contextTargets), listWorkspacesWithClient(client)]);
12690
+ const interactive = canPrompt();
12691
+ const contextWorkspaceIds = new Set(contexts.map(({ workspaceId }) => workspaceId));
12692
+ const linkedWorkspace = contextWorkspaceIds.size === 1 ? workspaces.find(({ id }) => contextWorkspaceIds.has(id)) : void 0;
12693
+ if (linkedWorkspace && (!options.createWorkspace || workspaceMatchesRequestedIdentity(linkedWorkspace, options))) {
12694
+ const contextTargetCount = projectContextTargets(options.contextTargets)?.length ?? 1;
12695
+ return contexts.length === contextTargetCount ? useRememberedWorkspace(client, linkedWorkspace) : useWorkspace(client, platformUrl, linkedWorkspace, options);
12696
+ }
12697
+ const onlyWorkspace = workspaces.length === 1 ? workspaces[0] : void 0;
12698
+ const canReuseOnlyWorkspace = onlyWorkspace !== void 0 && requestedCreateOptions !== void 0 && workspaceMatchesRequestedIdentity(onlyWorkspace, options);
12699
+ let requestedRegions;
12700
+ if (requestedCreateOptions && !canReuseOnlyWorkspace) {
12701
+ requestedRegions = await client.listAvailableWorkspaceRegions({});
12702
+ if (!requestedRegions.regions.includes(requestedCreateOptions.region)) throw invalidCreateOptionsError(options, requestedCreateOptions.name, requestedCreateOptions.region, `Region must be one of: ${requestedRegions.regions.join(", ")}.`);
12703
+ }
12704
+ if (workspaces.length === 0 && (interactive || options.createWorkspace || contexts.length === 0)) {
12705
+ const regions = requestedRegions?.regions ?? (await client.listAvailableWorkspaceRegions({})).regions;
12706
+ if (options.dryRun || interactive || options.createWorkspace) return createWorkspaceForDeploy(client, platformUrl, options, regions, requestedCreateOptions);
12707
+ throw createCLIError({
12708
+ code: "WORKSPACE_NOT_FOUND",
12709
+ message: "No workspaces are available for this account.",
12710
+ suggestion: "Create one during deploy by providing the workspace name and one of the available regions.",
12711
+ next: executableAction(createDeployArgs(options, "<name>", "<region>")),
12712
+ context: { availableRegions: regions }
12713
+ });
12714
+ }
12715
+ if (contextWorkspaceIds.size > 1) {
12716
+ const canReplaceStaleContexts = options.createWorkspace === true && options.workspaceName !== void 0 && options.workspaceRegion !== void 0 && workspaces.length === 0;
12717
+ if (!interactive && !canReplaceStaleContexts) throw createCLIError({
12718
+ code: "WORKSPACE_CONTEXT_CONFLICT",
12719
+ message: "The deployed configuration files are linked to different workspaces.",
12720
+ suggestion: "Choose one workspace explicitly for the combined deployment.",
12721
+ next: executableAction(selectDeployArgs(options)),
12722
+ context: { savedWorkspaceIds: [...contextWorkspaceIds] }
12723
+ });
12724
+ if (workspaces.length > 0 && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
12725
+ }
12726
+ if (!linkedWorkspace && contexts.length > 0) {
12727
+ const explicitlyEnsuringSingleTarget = options.createWorkspace === true && options.workspaceName !== void 0 && options.workspaceRegion !== void 0 && workspaces.length <= 1;
12728
+ if (!interactive && !explicitlyEnsuringSingleTarget) throw createCLIError({
12729
+ code: "WORKSPACE_CONTEXT_STALE",
12730
+ message: "The saved project workspace is no longer available.",
12731
+ suggestion: "Choose an available workspace explicitly before deploying.",
12732
+ next: executableAction(selectDeployArgs(options)),
12733
+ context: {
12734
+ savedWorkspaceIds: [...contextWorkspaceIds],
12735
+ workspaces: workspaces.map(workspaceIdentity)
12736
+ }
12737
+ });
12738
+ if (workspaces.length > 0 && interactive && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
12739
+ }
12740
+ if (workspaces.length === 1) {
12741
+ const [workspace] = workspaces;
12742
+ if (!workspace) throw new Error("Workspace discovery returned an invalid result");
12743
+ if (options.createWorkspace && !workspaceMatchesRequestedIdentity(workspace, options)) throw createCLIError({
12744
+ code: "WORKSPACE_CREATE_CONFLICT",
12745
+ message: "The existing workspace does not match the requested workspace.",
12746
+ suggestion: "Use the existing workspace, or run the explicit workspace create command to create another one.",
12747
+ next: executableAction([
12748
+ "workspace",
12749
+ "create",
12750
+ ...workspaceCommandArgs(options),
12751
+ ...options.workspaceCommandJson ? ["--json"] : [],
12752
+ "--name",
12753
+ options.workspaceName ?? "<name>",
12754
+ "--region",
12755
+ options.workspaceRegion ?? "<region>",
12756
+ ...options.organizationId ? ["--organization-id", options.organizationId] : [],
12757
+ ...options.folderId ? ["--folder-id", options.folderId] : []
12758
+ ]),
12759
+ context: { existingWorkspace: workspaceIdentity(workspace) }
12760
+ });
12761
+ if (interactive && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
12762
+ return useWorkspace(client, platformUrl, workspace, options);
12763
+ }
12764
+ if (workspaces.length > 1) {
12765
+ if (!interactive) throw createCLIError({
12766
+ code: "WORKSPACE_SELECTION_REQUIRED",
12767
+ message: "Multiple workspaces are available.",
12768
+ suggestion: "Choose one explicitly for non-interactive deployment.",
12769
+ next: executableAction(selectDeployArgs(options)),
12770
+ context: { workspaces: workspaces.map(workspaceIdentity) }
12771
+ });
12772
+ return chooseWorkspace(client, platformUrl, workspaces, options);
12773
+ }
12774
+ throw new Error("Workspace discovery returned an invalid result");
12775
+ }
12776
+
11332
12777
  //#endregion
11333
12778
  //#region src/cli/commands/deploy/deploy.ts
11334
12779
  /**
@@ -11746,19 +13191,40 @@ function parseDeployConfigPaths(configPath) {
11746
13191
  if (configPaths.some((entry) => entry.length === 0)) throw new Error("--config must contain one or more non-empty config paths.");
11747
13192
  return configPaths;
11748
13193
  }
13194
+ function recoveryEnvironmentArgs(options, cliContext) {
13195
+ return [
13196
+ ...cliContext?.envFile ? ["--env-file", path.resolve(process.cwd(), cliContext.envFile)] : [],
13197
+ ...cliContext?.envFileIfExists ? ["--env-file-if-exists", path.resolve(process.cwd(), cliContext.envFileIfExists)] : [],
13198
+ ...options?.profile ? ["--profile", options.profile] : []
13199
+ ];
13200
+ }
13201
+ function recoveryOutputArgs(cliContext) {
13202
+ return [...cliContext?.verbose ? ["--verbose"] : [], ...cliContext?.json || logger.jsonMode ? ["--json"] : []];
13203
+ }
13204
+ function retryDeployArgs(options, configPaths, cliContext) {
13205
+ return [
13206
+ "deploy",
13207
+ "--config",
13208
+ configPaths.join(","),
13209
+ ...recoveryEnvironmentArgs(options, cliContext),
13210
+ ...options?.dryRun ? ["--dry-run"] : [],
13211
+ ...options?.yes ? ["--yes"] : [],
13212
+ ...options?.noSchemaCheck ? ["--no-schema-check"] : [],
13213
+ ...options?.noValidate ? ["--no-validate"] : [],
13214
+ ...options?.noCache ? ["--no-cache"] : [],
13215
+ ...options?.cleanCache ? ["--clean-cache"] : [],
13216
+ ...recoveryOutputArgs(cliContext)
13217
+ ];
13218
+ }
13219
+ function workspaceRecoveryArgs(options, cliContext) {
13220
+ return [...recoveryEnvironmentArgs(options, cliContext), ...cliContext?.verbose ? ["--verbose"] : []];
13221
+ }
11749
13222
  async function buildDeploymentTarget(params) {
11750
- const { configPath, dryRun, buildOnly, noCache, packageVersion, cacheDir } = params;
11751
- const { config, plugins } = await withSpan("build.loadConfig", async () => {
11752
- const foundPath = loadConfigPath(configPath);
11753
- if (foundPath) {
11754
- const resolvedPath = path.resolve(process.cwd(), foundPath);
11755
- if (fs$1.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
11756
- configPath: resolvedPath,
11757
- dryRun,
11758
- buildOnly
11759
- });
11760
- }
11761
- return loadConfig(configPath);
13223
+ const { configPath, loadedConfig, dryRun, buildOnly, noCache, packageVersion, cacheDir } = params;
13224
+ const { config, plugins } = loadedConfig ?? await loadDeployConfig({
13225
+ configPath,
13226
+ dryRun,
13227
+ buildOnly
11762
13228
  });
11763
13229
  const configDir = path.dirname(config.path);
11764
13230
  const lockfilePath = findUpSync("pnpm-lock.yaml", { cwd: configDir }) ?? findUpSync("package-lock.json", { cwd: configDir }) ?? findUpSync("yarn.lock", { cwd: configDir }) ?? findUpSync("bun.lock", { cwd: configDir });
@@ -11799,11 +13265,27 @@ async function buildDeploymentTarget(params) {
11799
13265
  bundledScripts
11800
13266
  };
11801
13267
  }
13268
+ async function loadDeployConfig(params) {
13269
+ const { configPath, dryRun, buildOnly } = params;
13270
+ return withSpan("build.loadConfig", async () => {
13271
+ const foundPath = loadConfigPath(configPath);
13272
+ if (foundPath) {
13273
+ const resolvedPath = path.resolve(process.cwd(), foundPath);
13274
+ if (fs$1.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
13275
+ configPath: resolvedPath,
13276
+ dryRun,
13277
+ buildOnly
13278
+ });
13279
+ }
13280
+ return loadConfig(configPath);
13281
+ });
13282
+ }
11802
13283
  async function buildDeploymentTargets(params) {
11803
- const { configPaths, buildTarget = buildDeploymentTarget, ...targetParams } = params;
11804
- return Promise.all(configPaths.map((configPath) => buildTarget({
13284
+ const { configPaths, loadedConfigs, buildTarget = buildDeploymentTarget, ...targetParams } = params;
13285
+ return Promise.all(configPaths.map((configPath, index) => buildTarget({
11805
13286
  ...targetParams,
11806
- configPath
13287
+ configPath,
13288
+ loadedConfig: loadedConfigs?.[index]
11807
13289
  })));
11808
13290
  }
11809
13291
  function setBundledScript(target, name, code, kind) {
@@ -11972,50 +13454,25 @@ function deploymentPlanResults(deployment) {
11972
13454
  const { application: _application, ...results } = deployment;
11973
13455
  return results;
11974
13456
  }
13457
+ /**
13458
+ * Every `PlanResults` entry except `app` carries ownership-tracking fields;
13459
+ * deriving the list from `results` itself (instead of naming each key) keeps
13460
+ * these collectors in sync with `PlannedDeployment` as resource types are added.
13461
+ * @param results - Plan results for a single deployment
13462
+ * @returns The ownership-tracking plan entries, `app` excluded
13463
+ */
13464
+ function ownershipTrackedPlans(results) {
13465
+ const { app: _app, ...rest } = results;
13466
+ return Object.values(rest);
13467
+ }
11975
13468
  function collectOwnerConflicts(results) {
11976
- return [
11977
- ...results.functionRegistry.conflicts,
11978
- ...results.tailorDB.conflicts,
11979
- ...results.staticWebsite.conflicts,
11980
- ...results.aiGateway.conflicts,
11981
- ...results.idp.conflicts,
11982
- ...results.auth.conflicts,
11983
- ...results.pipeline.conflicts,
11984
- ...results.executor.conflicts,
11985
- ...results.workflow.conflicts,
11986
- ...results.workflowExecutionPolicy.conflicts,
11987
- ...results.secretManager.conflicts
11988
- ];
13469
+ return ownershipTrackedPlans(results).flatMap((plan) => plan.conflicts);
11989
13470
  }
11990
13471
  function collectUnmanagedResources(results) {
11991
- return [
11992
- ...results.functionRegistry.unmanaged,
11993
- ...results.tailorDB.unmanaged,
11994
- ...results.staticWebsite.unmanaged,
11995
- ...results.aiGateway.unmanaged,
11996
- ...results.idp.unmanaged,
11997
- ...results.auth.unmanaged,
11998
- ...results.pipeline.unmanaged,
11999
- ...results.executor.unmanaged,
12000
- ...results.workflow.unmanaged,
12001
- ...results.workflowExecutionPolicy.unmanaged,
12002
- ...results.secretManager.unmanaged
12003
- ];
13472
+ return ownershipTrackedPlans(results).flatMap((plan) => plan.unmanaged);
12004
13473
  }
12005
13474
  function collectResourceOwners(results) {
12006
- return /* @__PURE__ */ new Set([
12007
- ...results.functionRegistry.resourceOwners,
12008
- ...results.tailorDB.resourceOwners,
12009
- ...results.staticWebsite.resourceOwners,
12010
- ...results.aiGateway.resourceOwners,
12011
- ...results.idp.resourceOwners,
12012
- ...results.auth.resourceOwners,
12013
- ...results.pipeline.resourceOwners,
12014
- ...results.executor.resourceOwners,
12015
- ...results.workflow.resourceOwners,
12016
- ...results.workflowExecutionPolicy.resourceOwners,
12017
- ...results.secretManager.resourceOwners
12018
- ]);
13475
+ return new Set(ownershipTrackedPlans(results).flatMap((plan) => [...plan.resourceOwners]));
12019
13476
  }
12020
13477
  function collectImportantResourceDeletions(results) {
12021
13478
  const importantDeletions = [];
@@ -12407,16 +13864,41 @@ async function validateDeploymentPlans(deployments) {
12407
13864
  }
12408
13865
  /**
12409
13866
  * Deploy the configured application to the Tailor platform.
12410
- * @param options - Options for deploy execution
13867
+ * @param options - Deploy execution options
13868
+ * @param cliContext - Global CLI arguments to preserve in recovery actions
12411
13869
  * @returns Promise that resolves to `{ bundledScripts }` when `buildOnly` is true, otherwise void
12412
13870
  */
12413
- async function deploy(options) {
13871
+ async function deployInternal(options, cliContext) {
12414
13872
  return withSpan("deploy", async (rootSpan) => {
12415
13873
  rootSpan.setAttribute("deploy.dry_run", options?.dryRun ?? false);
12416
13874
  const configPaths = parseDeployConfigPaths(options?.configPath);
12417
- const { targets, buildOnly } = await withSpan("build", async () => {
12418
- const dryRun = options?.dryRun ?? false;
12419
- const buildOnly = options?.buildOnly ?? parseBoolean(process.env.TAILOR_PLATFORM_SDK_BUILD_ONLY) === true;
13875
+ const dryRun = options?.dryRun ?? false;
13876
+ const buildOnly = options?.buildOnly ?? parseBoolean(process.env.TAILOR_PLATFORM_SDK_BUILD_ONLY) === true;
13877
+ const preflightConfigs = buildOnly ? [] : await withSpan("config.preflight", () => Promise.all(configPaths.map((configPath) => loadDeployConfig({
13878
+ configPath,
13879
+ dryRun,
13880
+ buildOnly
13881
+ }))));
13882
+ const resolvedConfigPaths = preflightConfigs.map(({ config }) => config.path);
13883
+ const workspaceContextTargets = preflightConfigs.map(({ config }) => ({
13884
+ configPath: config.path,
13885
+ applicationId: config.id ?? `name:${config.name}`
13886
+ }));
13887
+ const workspace = buildOnly ? void 0 : await resolveDeployWorkspace({
13888
+ workspaceId: options?.workspaceId,
13889
+ profile: options?.profile,
13890
+ createWorkspace: options?.createWorkspace,
13891
+ workspaceName: options?.workspaceName,
13892
+ workspaceRegion: options?.workspaceRegion,
13893
+ organizationId: options?.organizationId,
13894
+ folderId: options?.folderId,
13895
+ dryRun,
13896
+ contextTargets: workspaceContextTargets,
13897
+ deployArgs: retryDeployArgs(options, resolvedConfigPaths, cliContext),
13898
+ workspaceCommandArgs: workspaceRecoveryArgs(options, cliContext),
13899
+ workspaceCommandJson: cliContext?.json || logger.jsonMode
13900
+ });
13901
+ const targets = await withSpan("build", async () => {
12420
13902
  const noCache = options?.noCache ?? false;
12421
13903
  const packageJson = await readPackageJson();
12422
13904
  const cacheDir = path.resolve(getDistDir(), "cache");
@@ -12427,25 +13909,20 @@ async function deploy(options) {
12427
13909
  });
12428
13910
  logger.info("Bundle cache cleaned");
12429
13911
  }
12430
- return {
12431
- targets: await buildDeploymentTargets({
12432
- configPaths,
12433
- dryRun,
12434
- buildOnly,
12435
- noCache,
12436
- packageVersion: packageJson.version ?? "unknown",
12437
- cacheDir
12438
- }),
12439
- buildOnly
12440
- };
13912
+ return await buildDeploymentTargets({
13913
+ configPaths,
13914
+ loadedConfigs: preflightConfigs,
13915
+ dryRun,
13916
+ buildOnly,
13917
+ noCache,
13918
+ packageVersion: packageJson.version ?? "unknown",
13919
+ cacheDir
13920
+ });
12441
13921
  });
12442
13922
  if (buildOnly) return { bundledScripts: mergeBundledScripts(targets) };
12443
13923
  assertUniqueGlobalResourceNames(targets);
12444
- const client = await initOperatorClient(await loadAccessToken({ profile: options?.profile }));
12445
- const workspaceId = await loadWorkspaceId({
12446
- workspaceId: options?.workspaceId,
12447
- profile: options?.profile
12448
- });
13924
+ if (!workspace) throw new Error("Workspace was not resolved");
13925
+ const { client, workspaceId } = workspace;
12449
13926
  rootSpan.setAttribute("app.name", targets.map((target) => target.application.name).join(","));
12450
13927
  rootSpan.setAttribute("workspace.id", workspaceId);
12451
13928
  const deployments = await planDeploymentTargets({
@@ -12455,7 +13932,6 @@ async function deploy(options) {
12455
13932
  workspaceId,
12456
13933
  noSchemaCheck: options?.noSchemaCheck
12457
13934
  });
12458
- const dryRun = options?.dryRun ?? false;
12459
13935
  const yes = options?.yes ?? false;
12460
13936
  dropCrossDeploymentManagedDeletes(deployments);
12461
13937
  await withSpan("confirm", async () => {
@@ -12479,6 +13955,23 @@ async function deploy(options) {
12479
13955
  else logger.success("Successfully applied changes.");
12480
13956
  });
12481
13957
  }
13958
+ /**
13959
+ * Deploy using the programmatic CLI API.
13960
+ * @param options - Deploy execution options
13961
+ * @returns Deploy result
13962
+ */
13963
+ function deploy(options) {
13964
+ return deployInternal(options);
13965
+ }
13966
+ /**
13967
+ * Deploy from the command adapter while preserving global CLI arguments in recovery actions.
13968
+ * @param options - Deploy execution options
13969
+ * @param cliContext - Global CLI arguments already applied by the command runner
13970
+ * @returns Deploy result
13971
+ */
13972
+ function deployFromCLI(options, cliContext) {
13973
+ return deployInternal(options, cliContext);
13974
+ }
12482
13975
 
12483
13976
  //#endregion
12484
13977
  //#region src/cli/commands/executor/status.ts
@@ -12868,52 +14361,6 @@ const getCommand$6 = defineAppCommand({
12868
14361
  }
12869
14362
  });
12870
14363
 
12871
- //#endregion
12872
- //#region src/cli/shared/format.ts
12873
- /**
12874
- * Format a protobuf Timestamp to ISO string.
12875
- * @param timestamp - Protobuf timestamp
12876
- * @returns Date object or null if invalid
12877
- */
12878
- function formatTimestamp(timestamp) {
12879
- if (!timestamp) return null;
12880
- const date = timestampDate(timestamp);
12881
- if (Number.isNaN(date.getTime())) return null;
12882
- return date;
12883
- }
12884
- /**
12885
- * Formats a table with consistent single-line border style.
12886
- * Use this instead of importing `table` directly.
12887
- * @param data - Table data
12888
- * @param config - Table configuration
12889
- * @returns Formatted table string
12890
- */
12891
- function formatTable(data, config) {
12892
- return table(data, {
12893
- ...config,
12894
- border: getBorderCharacters("norc")
12895
- });
12896
- }
12897
- /**
12898
- * Formats a key-value table with single-line border style.
12899
- * @param data - Key-value pairs
12900
- * @returns Formatted key-value table string
12901
- */
12902
- function formatKeyValueTable(data) {
12903
- return formatTable(data, { singleLine: true });
12904
- }
12905
- /**
12906
- * Format a Date or ISO timestamp string as a human-readable relative time.
12907
- * @param value - Date object, ISO date string, or null
12908
- * @returns Relative time (e.g., "5 minutes ago") or "N/A" for null/invalid
12909
- */
12910
- function humanizeRelativeTime(value) {
12911
- if (value === null) return "N/A";
12912
- const date = value instanceof Date ? value : new Date(value);
12913
- if (Number.isNaN(date.getTime())) return typeof value === "string" ? value : "N/A";
12914
- return formatDistanceToNowStrict(date, { addSuffix: true });
12915
- }
12916
-
12917
14364
  //#endregion
12918
14365
  //#region src/cli/shared/function-execution.ts
12919
14366
  /**
@@ -14312,7 +15759,7 @@ async function listExecutors(options) {
14312
15759
  return [executors, nextPageToken];
14313
15760
  }, { limit: options?.limit })).map((e) => toExecutorListInfo(e));
14314
15761
  }
14315
- const listCommand$9 = defineAppCommand({
15762
+ const listCommand$8 = defineAppCommand({
14316
15763
  name: "list",
14317
15764
  description: "List all executors",
14318
15765
  args: z.object({
@@ -14737,7 +16184,7 @@ async function listFunctionRegistries(options) {
14737
16184
  }
14738
16185
  }, { limit })).map(functionRegistryInfo);
14739
16186
  }
14740
- const listCommand$8 = defineAppCommand({
16187
+ const listCommand$7 = defineAppCommand({
14741
16188
  name: "list",
14742
16189
  description: "List function registries in a workspace",
14743
16190
  args: z.object({
@@ -15682,7 +17129,7 @@ async function listMachineUsers(options) {
15682
17129
  return [machineUsers, nextPageToken];
15683
17130
  }, { limit: options?.limit })).map(machineUserInfo);
15684
17131
  }
15685
- const listCommand$7 = defineAppCommand({
17132
+ const listCommand$6 = defineAppCommand({
15686
17133
  name: "list",
15687
17134
  description: "List all machine users in the application.",
15688
17135
  args: z.object({
@@ -15898,7 +17345,7 @@ async function listOAuth2Clients(options) {
15898
17345
  return [oauth2Clients, nextPageToken];
15899
17346
  }, { limit: options?.limit })).map(toOAuth2ClientInfo);
15900
17347
  }
15901
- const listCommand$6 = defineAppCommand({
17348
+ const listCommand$5 = defineAppCommand({
15902
17349
  name: "list",
15903
17350
  description: "List all OAuth2 clients in the application.",
15904
17351
  args: z.object({
@@ -15968,7 +17415,7 @@ async function createFolder(options) {
15968
17415
  if (!response.folder) throw new Error("Failed to create folder.");
15969
17416
  return folderInfo(response.folder);
15970
17417
  }
15971
- const createCommand$1 = defineAppCommand({
17418
+ const createCommand = defineAppCommand({
15972
17419
  name: "create",
15973
17420
  description: "Create a new folder in an organization.",
15974
17421
  args: z.object({
@@ -16116,7 +17563,7 @@ async function listFolders(options) {
16116
17563
  return [response.folders, response.nextPageToken];
16117
17564
  }, { limit })).map(folderListInfo);
16118
17565
  }
16119
- const listCommand$5 = defineAppCommand({
17566
+ const listCommand$4 = defineAppCommand({
16120
17567
  name: "list",
16121
17568
  description: "List folders in an organization.",
16122
17569
  args: z.object({
@@ -16225,7 +17672,7 @@ async function listOrganizations(options) {
16225
17672
  if (limit !== void 0) return results.slice(0, limit);
16226
17673
  return results;
16227
17674
  }
16228
- const listCommand$4 = defineAppCommand({
17675
+ const listCommand$3 = defineAppCommand({
16229
17676
  name: "list",
16230
17677
  description: "List organizations you belong to.",
16231
17678
  args: z.object({ limit: arg(positiveIntArg.optional(), {
@@ -16430,62 +17877,67 @@ async function execRemove(client, workspaceId, application, config, confirm) {
16430
17877
  forRemoval: true,
16431
17878
  config
16432
17879
  };
16433
- const tailorDB = await planTailorDB(ctx);
16434
- const staticWebsite = await planStaticWebsite(ctx);
16435
- const aiGateway = await planAIGateway(ctx);
16436
- const idp = await planIdP(ctx);
16437
- const auth = await planAuth(ctx);
16438
- const pipeline = await planPipeline(ctx);
16439
- const app = await planApplication(ctx);
16440
- const executor = await planExecutor(ctx);
16441
- const workflow = await planWorkflow(client, workspaceId, application.name, application.id, {}, {});
16442
- const functionRegistry = await planFunctionRegistry(client, workspaceId, application.name, application.id, []);
16443
- const secretManager = await planSecretManager(ctx);
17880
+ const plans = {
17881
+ tailorDB: await planTailorDB(ctx),
17882
+ staticWebsite: await planStaticWebsite(ctx),
17883
+ aiGateway: await planAIGateway(ctx),
17884
+ idp: await planIdP(ctx),
17885
+ auth: await planAuth(ctx),
17886
+ pipeline: await planPipeline(ctx),
17887
+ app: await planApplication(ctx),
17888
+ executor: await planExecutor(ctx),
17889
+ workflow: await planWorkflow(client, workspaceId, application.name, application.id, {}, {}),
17890
+ workflowExecutionPolicy: await planWorkflowJobFunctionExecutionPolicy(client, workspaceId, application.name, application.id, {}),
17891
+ functionRegistry: await planFunctionRegistry(client, workspaceId, application.name, application.id, []),
17892
+ secretManager: await planSecretManager(ctx)
17893
+ };
16444
17894
  const removeLines = [
16445
- ...functionRegistry.changeSet.lines(),
16446
- ...staticWebsite.changeSet.lines(),
16447
- ...aiGateway.changeSet.lines(),
16448
- ...app.lines(),
16449
- ...tailorDB.changeSet.service.lines(),
16450
- ...tailorDB.changeSet.type.lines(),
16451
- ...tailorDB.changeSet.gqlPermission.lines(),
16452
- ...pipeline.changeSet.service.lines(),
16453
- ...pipeline.changeSet.resolver.lines(),
16454
- ...executor.changeSet.lines(),
16455
- ...workflow.changeSet.lines(),
16456
- ...idp.changeSet.service.lines(),
16457
- ...idp.changeSet.client.lines(),
16458
- ...auth.changeSet.service.lines(),
16459
- ...auth.changeSet.idpConfig.lines(),
16460
- ...auth.changeSet.userProfileConfig.lines(),
16461
- ...auth.changeSet.tenantConfig.lines(),
16462
- ...auth.changeSet.machineUser.lines(),
16463
- ...auth.changeSet.oauth2Client.lines(),
16464
- ...auth.changeSet.authHook.lines(),
16465
- ...auth.changeSet.scim.lines(),
16466
- ...auth.changeSet.scimResource.lines(),
16467
- ...auth.changeSet.connection.lines(),
16468
- ...secretManager.vaultChangeSet.lines(),
16469
- ...secretManager.secretChangeSet.lines()
17895
+ ...plans.functionRegistry.changeSet.lines(),
17896
+ ...plans.staticWebsite.changeSet.lines(),
17897
+ ...plans.aiGateway.changeSet.lines(),
17898
+ ...plans.app.lines(),
17899
+ ...plans.tailorDB.changeSet.service.lines(),
17900
+ ...plans.tailorDB.changeSet.type.lines(),
17901
+ ...plans.tailorDB.changeSet.gqlPermission.lines(),
17902
+ ...plans.pipeline.changeSet.service.lines(),
17903
+ ...plans.pipeline.changeSet.resolver.lines(),
17904
+ ...plans.executor.changeSet.lines(),
17905
+ ...plans.workflow.changeSet.lines(),
17906
+ ...plans.workflowExecutionPolicy.changeSet.lines(),
17907
+ ...plans.idp.changeSet.service.lines(),
17908
+ ...plans.idp.changeSet.client.lines(),
17909
+ ...plans.auth.changeSet.service.lines(),
17910
+ ...plans.auth.changeSet.idpConfig.lines(),
17911
+ ...plans.auth.changeSet.userProfileConfig.lines(),
17912
+ ...plans.auth.changeSet.tenantConfig.lines(),
17913
+ ...plans.auth.changeSet.machineUser.lines(),
17914
+ ...plans.auth.changeSet.oauth2Client.lines(),
17915
+ ...plans.auth.changeSet.authHook.lines(),
17916
+ ...plans.auth.changeSet.scim.lines(),
17917
+ ...plans.auth.changeSet.scimResource.lines(),
17918
+ ...plans.auth.changeSet.connection.lines(),
17919
+ ...plans.secretManager.vaultChangeSet.lines(),
17920
+ ...plans.secretManager.secretChangeSet.lines()
16470
17921
  ];
16471
17922
  if (removeLines.length > 0) logger.log(removeLines.join("\n"));
16472
- if (tailorDB.changeSet.service.deletes.length === 0 && staticWebsite.changeSet.deletes.length === 0 && aiGateway.changeSet.deletes.length === 0 && idp.changeSet.service.deletes.length === 0 && auth.changeSet.service.deletes.length === 0 && pipeline.changeSet.service.deletes.length === 0 && app.deletes.length === 0 && executor.changeSet.deletes.length === 0 && workflow.changeSet.deletes.length === 0 && functionRegistry.changeSet.deletes.length === 0 && secretManager.vaultChangeSet.deletes.length === 0 && secretManager.secretChangeSet.deletes.length === 0) return;
17923
+ if (plans.tailorDB.changeSet.service.deletes.length === 0 && plans.staticWebsite.changeSet.deletes.length === 0 && plans.aiGateway.changeSet.deletes.length === 0 && plans.idp.changeSet.service.deletes.length === 0 && plans.auth.changeSet.service.deletes.length === 0 && plans.pipeline.changeSet.service.deletes.length === 0 && plans.app.deletes.length === 0 && plans.executor.changeSet.deletes.length === 0 && plans.workflow.changeSet.deletes.length === 0 && plans.workflowExecutionPolicy.changeSet.deletes.length === 0 && plans.functionRegistry.changeSet.deletes.length === 0 && plans.secretManager.vaultChangeSet.deletes.length === 0 && plans.secretManager.secretChangeSet.deletes.length === 0) return;
16473
17924
  if (confirm) await confirm();
16474
- await applyWorkflow(client, workflow, "delete");
16475
- await applyExecutor(client, executor, "delete");
16476
- await applyStaticWebsite(client, staticWebsite, "delete");
16477
- await applyAIGateway(client, aiGateway, "delete");
16478
- await applyApplication(client, app, "delete");
16479
- await applyPipeline(client, pipeline, "delete-resources");
16480
- await applyPipeline(client, pipeline, "delete-services");
16481
- await applyAuth(client, auth, "delete-resources");
16482
- await applyAuth(client, auth, "delete-services");
16483
- await applyIdP(client, idp, "delete-resources");
16484
- await applyIdP(client, idp, "delete-services");
16485
- await applyTailorDB(client, tailorDB, "delete-resources");
16486
- await applyTailorDB(client, tailorDB, "delete-services");
16487
- await applyFunctionRegistry(client, workspaceId, functionRegistry, "delete");
16488
- await applySecretManager(client, secretManager, "delete");
17925
+ await applyWorkflow(client, plans.workflow, "delete");
17926
+ await applyWorkflowJobFunctionExecutionPolicy(client, plans.workflowExecutionPolicy, "delete");
17927
+ await applyExecutor(client, plans.executor, "delete");
17928
+ await applyStaticWebsite(client, plans.staticWebsite, "delete");
17929
+ await applyAIGateway(client, plans.aiGateway, "delete");
17930
+ await applyApplication(client, plans.app, "delete");
17931
+ await applyPipeline(client, plans.pipeline, "delete-resources");
17932
+ await applyPipeline(client, plans.pipeline, "delete-services");
17933
+ await applyAuth(client, plans.auth, "delete-resources");
17934
+ await applyAuth(client, plans.auth, "delete-services");
17935
+ await applyIdP(client, plans.idp, "delete-resources");
17936
+ await applyIdP(client, plans.idp, "delete-services");
17937
+ await applyTailorDB(client, plans.tailorDB, "delete-resources");
17938
+ await applyTailorDB(client, plans.tailorDB, "delete-services");
17939
+ await applyFunctionRegistry(client, workspaceId, plans.functionRegistry, "delete");
17940
+ await applySecretManager(client, plans.secretManager, "delete");
16489
17941
  }
16490
17942
  /**
16491
17943
  * Remove all resources managed by the current application.
@@ -16510,141 +17962,63 @@ const removeCommand$1 = defineAppCommand({
16510
17962
  profile: args.profile,
16511
17963
  configPath: args.config
16512
17964
  });
16513
- logger.info(`Planning removal of resources managed by "${application.name}"...`);
16514
- logger.newline();
16515
- await execRemove(client, workspaceId, application, config, async () => {
16516
- if (!args.yes) {
16517
- if (!await prompt.confirm({
16518
- message: "Are you sure you want to remove all resources?",
16519
- default: false
16520
- })) throw new Error(multiline`
16521
- Remove cancelled. No resources were deleted.
16522
- To override, run again and confirm, or use --yes flag.
16523
- `);
16524
- } else logger.success("Removing all resources (--yes flag specified)...");
16525
- });
16526
- logger.success(`Successfully removed all resources managed by "${application.name}".`);
16527
- }
16528
- });
16529
-
16530
- //#endregion
16531
- //#region src/cli/shared/beta.ts
16532
- /**
16533
- * Warn that a feature is in beta.
16534
- * @param {string} featureName - Name of the beta feature (e.g., "tailordb erd", "tailordb migration")
16535
- */
16536
- function logBetaWarning(featureName) {
16537
- logger.warn(`The '${featureName}' command is a beta feature and may introduce breaking changes in future releases.`);
16538
- logger.newline();
16539
- }
16540
-
16541
- //#endregion
16542
- //#region src/cli/shared/config.ts
16543
- /**
16544
- * Extracts every namespace key declared under `config.db`, including those
16545
- * declared with `{ external: true }`.
16546
- * @param config - Loaded application configuration.
16547
- * @returns Namespace names in insertion order.
16548
- */
16549
- function extractAllNamespaces(config) {
16550
- const namespaces = /* @__PURE__ */ new Set();
16551
- if (config.db) for (const namespaceName of Object.keys(config.db)) namespaces.add(namespaceName);
16552
- return Array.from(namespaces);
16553
- }
16554
- /**
16555
- * Extracts namespace keys under `config.db` that this app owns
16556
- * (i.e. not declared with `{ external: true }`). Use this for destructive
16557
- * operations like `tailordb truncate --all` to avoid touching namespaces
16558
- * owned by other apps.
16559
- * @param config - Loaded application configuration.
16560
- * @returns Owned namespace names in insertion order.
16561
- */
16562
- function extractOwnedNamespaces(config) {
16563
- const namespaces = /* @__PURE__ */ new Set();
16564
- if (config.db) for (const [namespaceName, nsConfig] of Object.entries(config.db)) {
16565
- if ("external" in nsConfig) continue;
16566
- namespaces.add(namespaceName);
16567
- }
16568
- return Array.from(namespaces);
16569
- }
16570
-
16571
- //#endregion
16572
- //#region src/cli/commands/workspace/transform.ts
16573
- const workspaceInfo = (workspace, folderName) => {
16574
- const info = {
16575
- id: workspace.id,
16576
- name: workspace.name,
16577
- region: workspace.region,
16578
- createdAt: formatTimestamp(workspace.createTime),
16579
- updatedAt: formatTimestamp(workspace.updateTime)
16580
- };
16581
- return folderName ? {
16582
- ...info,
16583
- folderName
16584
- } : info;
16585
- };
16586
- const workspaceDetails = (workspace, folderName) => {
16587
- return {
16588
- ...workspaceInfo(workspace, folderName),
16589
- deleteProtection: workspace.deleteProtection,
16590
- organizationId: workspace.organizationId,
16591
- folderId: workspace.folderId
16592
- };
16593
- };
16594
- async function resolveWorkspaceFolderName(client, workspace) {
16595
- if (!workspace.organizationId || !workspace.folderId) return void 0;
16596
- try {
16597
- return (await client.getOrganizationFolder({
16598
- organizationId: workspace.organizationId,
16599
- folderId: workspace.folderId
16600
- })).folder?.name || void 0;
16601
- } catch (error) {
16602
- if (error instanceof ConnectError && (error.code === Code.NotFound || error.code === Code.PermissionDenied)) return;
16603
- logger.warn(`Failed to resolve workspace folder name: ${error}`);
16604
- return;
16605
- }
16606
- }
16607
- function createWorkspaceFolderNameResolver(client) {
16608
- const cache = /* @__PURE__ */ new Map();
16609
- return (workspace) => {
16610
- if (!workspace.organizationId || !workspace.folderId) return Promise.resolve(void 0);
16611
- const cacheKey = `${workspace.organizationId}/${workspace.folderId}`;
16612
- const cached = cache.get(cacheKey);
16613
- if (cached) return cached;
16614
- const promise = resolveWorkspaceFolderName(client, workspace);
16615
- cache.set(cacheKey, promise);
16616
- return promise;
16617
- };
16618
- }
16619
- async function workspaceInfoWithFolderName(client, workspace) {
16620
- const folderName = await resolveWorkspaceFolderName(client, workspace);
16621
- return workspaceInfo(workspace, folderName);
16622
- }
16623
- async function workspaceDetailsWithFolderName(client, workspace) {
16624
- const folderName = await resolveWorkspaceFolderName(client, workspace);
16625
- return workspaceDetails(workspace, folderName);
16626
- }
16627
- async function workspaceInfosWithFolderNames(client, workspaces) {
16628
- const resolveFolderName = createWorkspaceFolderNameResolver(client);
16629
- const limit = pLimit(5);
16630
- return Promise.all(workspaces.map((workspace) => limit(async () => workspaceInfo(workspace, await resolveFolderName(workspace)))));
17965
+ logger.info(`Planning removal of resources managed by "${application.name}"...`);
17966
+ logger.newline();
17967
+ await execRemove(client, workspaceId, application, config, async () => {
17968
+ if (!args.yes) {
17969
+ if (!await prompt.confirm({
17970
+ message: "Are you sure you want to remove all resources?",
17971
+ default: false
17972
+ })) throw new Error(multiline`
17973
+ Remove cancelled. No resources were deleted.
17974
+ To override, run again and confirm, or use --yes flag.
17975
+ `);
17976
+ } else logger.success("Removing all resources (--yes flag specified)...");
17977
+ });
17978
+ logger.success(`Successfully removed all resources managed by "${application.name}".`);
17979
+ }
17980
+ });
17981
+
17982
+ //#endregion
17983
+ //#region src/cli/shared/beta.ts
17984
+ /**
17985
+ * Warn that a feature is in beta.
17986
+ * @param {string} featureName - Name of the beta feature (e.g., "tailordb erd", "tailordb migration")
17987
+ */
17988
+ function logBetaWarning(featureName) {
17989
+ logger.warn(`The '${featureName}' command is a beta feature and may introduce breaking changes in future releases.`);
17990
+ logger.newline();
16631
17991
  }
16632
- function workspaceDisplayName(workspace) {
16633
- return workspace.folderName ? `${workspace.folderName}/${workspace.name}` : workspace.name;
17992
+
17993
+ //#endregion
17994
+ //#region src/cli/shared/config.ts
17995
+ /**
17996
+ * Extracts every namespace key declared under `config.db`, including those
17997
+ * declared with `{ external: true }`.
17998
+ * @param config - Loaded application configuration.
17999
+ * @returns Namespace names in insertion order.
18000
+ */
18001
+ function extractAllNamespaces(config) {
18002
+ const namespaces = /* @__PURE__ */ new Set();
18003
+ if (config.db) for (const namespaceName of Object.keys(config.db)) namespaces.add(namespaceName);
18004
+ return Array.from(namespaces);
16634
18005
  }
16635
- function createWorkspaceNameTransformer(nameKey, folderNameKey) {
16636
- return (value, item) => {
16637
- const workspace = item;
16638
- const name = workspace[nameKey];
16639
- const folderName = workspace[folderNameKey];
16640
- if (typeof name === "string" && typeof folderName === "string") return workspaceDisplayName({
16641
- name,
16642
- folderName
16643
- });
16644
- return String(value ?? "");
16645
- };
18006
+ /**
18007
+ * Extracts namespace keys under `config.db` that this app owns
18008
+ * (i.e. not declared with `{ external: true }`). Use this for destructive
18009
+ * operations like `tailordb truncate --all` to avoid touching namespaces
18010
+ * owned by other apps.
18011
+ * @param config - Loaded application configuration.
18012
+ * @returns Owned namespace names in insertion order.
18013
+ */
18014
+ function extractOwnedNamespaces(config) {
18015
+ const namespaces = /* @__PURE__ */ new Set();
18016
+ if (config.db) for (const [namespaceName, nsConfig] of Object.entries(config.db)) {
18017
+ if ("external" in nsConfig) continue;
18018
+ namespaces.add(namespaceName);
18019
+ }
18020
+ return Array.from(namespaces);
16646
18021
  }
16647
- const workspaceNameTransformer = createWorkspaceNameTransformer("name", "folderName");
16648
18022
 
16649
18023
  //#endregion
16650
18024
  //#region src/cli/commands/show.ts
@@ -16661,10 +18035,27 @@ function applicationInfo(app) {
16661
18035
  updatedAt: app.updateTime ? timestampDate(app.updateTime) : null
16662
18036
  };
16663
18037
  }
18038
+ async function fetchAIGateways(client, workspaceId, names) {
18039
+ return (await Promise.all(names.map(async (name) => {
18040
+ try {
18041
+ const { aigateway } = await client.getAIGateway({
18042
+ workspaceId,
18043
+ aigatewayName: name
18044
+ });
18045
+ return aigateway ? {
18046
+ name: aigateway.name,
18047
+ url: aigateway.url
18048
+ } : void 0;
18049
+ } catch (error) {
18050
+ if (error instanceof ConnectError && error.code === Code.NotFound) return;
18051
+ throw error;
18052
+ }
18053
+ }))).filter((gateway) => gateway !== void 0);
18054
+ }
16664
18055
  /**
16665
18056
  * Show applied application information for the current workspace.
16666
18057
  * @param options - Show options
16667
- * @returns Application information
18058
+ * @returns Deployed application, workspace, and AI Gateway information
16668
18059
  */
16669
18060
  async function show(options) {
16670
18061
  const client = await initOperatorClient(await loadAccessToken({ profile: options?.profile }));
@@ -16673,10 +18064,15 @@ async function show(options) {
16673
18064
  profile: options?.profile
16674
18065
  });
16675
18066
  const { config } = await loadConfig(options?.configPath);
16676
- const [workspaceResp, resp] = await Promise.all([client.getWorkspace({ workspaceId }), client.getApplication({
16677
- workspaceId,
16678
- applicationName: config.name
16679
- })]);
18067
+ const aiGatewayNames = config.aiGateways?.length ? [...new Set(config.aiGateways.map((gateway) => gateway.name))] : [];
18068
+ const [workspaceResp, resp, aiGateways] = await Promise.all([
18069
+ client.getWorkspace({ workspaceId }),
18070
+ client.getApplication({
18071
+ workspaceId,
18072
+ applicationName: config.name
18073
+ }),
18074
+ fetchAIGateways(client, workspaceId, aiGatewayNames)
18075
+ ]);
16680
18076
  const { name, ...appInfo } = applicationInfo(assertDefined(resp.application, `application "${config.name}" not found in workspace`));
16681
18077
  const workspace = workspaceResp.workspace;
16682
18078
  const workspaceFolderName = workspace ? await resolveWorkspaceFolderName(client, workspace) : "";
@@ -16686,7 +18082,8 @@ async function show(options) {
16686
18082
  workspaceName: workspace?.name ?? "",
16687
18083
  ...workspaceFolderName ? { workspaceFolderName } : {},
16688
18084
  workspaceRegion: workspace?.region ?? "",
16689
- ...appInfo
18085
+ ...appInfo,
18086
+ aiGateways
16690
18087
  };
16691
18088
  }
16692
18089
  const showWorkspaceNameTransformer = createWorkspaceNameTransformer("workspaceName", "workspaceFolderName");
@@ -17293,7 +18690,7 @@ async function generate(options) {
17293
18690
  if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
17294
18691
  let pluginManager;
17295
18692
  if (plugins.length > 0) pluginManager = new PluginManager(plugins);
17296
- const { defineApplication } = await import("./application-DbvD5UUz.mjs");
18693
+ const { defineApplication } = await import("./application-Dp_-ioiP.mjs");
17297
18694
  const application = defineApplication({
17298
18695
  config,
17299
18696
  pluginManager
@@ -17653,7 +19050,7 @@ async function listWorkflows(options) {
17653
19050
  return [workflows, nextPageToken];
17654
19051
  }, { limit: options?.limit })).map(toWorkflowListInfo);
17655
19052
  }
17656
- const listCommand$3 = defineAppCommand({
19053
+ const listCommand$2 = defineAppCommand({
17657
19054
  name: "list",
17658
19055
  description: "List all workflows in the workspace.",
17659
19056
  args: z.object({
@@ -17986,7 +19383,7 @@ async function listApps(options) {
17986
19383
  return [applications, nextPageToken];
17987
19384
  }, { limit })).map(appInfo);
17988
19385
  }
17989
- const listCommand$2 = defineAppCommand({
19386
+ const listCommand$1 = defineAppCommand({
17990
19387
  name: "list",
17991
19388
  description: "List applications in a workspace",
17992
19389
  args: z.object({
@@ -18009,152 +19406,6 @@ const listCommand$2 = defineAppCommand({
18009
19406
  }
18010
19407
  });
18011
19408
 
18012
- //#endregion
18013
- //#region src/cli/commands/workspace/create.ts
18014
- /**
18015
- * Schema for workspace creation options
18016
- * - name: 3-63 chars, lowercase alphanumeric and hyphens, cannot start/end with hyphen
18017
- * - organizationId, folderId: optional UUIDs
18018
- */
18019
- const createWorkspaceOptionsSchema = z.object({
18020
- name: z.string().min(3, "Name must be at least 3 characters").max(63, "Name must be at most 63 characters").regex(/^[a-z0-9-]+$/, "Name can only contain lowercase letters, numbers, and hyphens").refine((n) => !n.startsWith("-") && !n.endsWith("-"), "Name cannot start or end with a hyphen"),
18021
- region: z.string(),
18022
- deleteProtection: z.boolean().optional(),
18023
- organizationId: z.uuid().optional(),
18024
- folderId: z.uuid().optional()
18025
- });
18026
- const validateRegion = async (region, client) => {
18027
- const availableRegions = await client.listAvailableWorkspaceRegions({});
18028
- if (!availableRegions.regions.includes(region)) throw new Error(`Region must be one of: ${availableRegions.regions.join(", ")}.`);
18029
- };
18030
- function profilePlatformSettings(platformConfig) {
18031
- const hasOAuth2ClientId = platformConfig?.oauth2ClientId || process.env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID;
18032
- const hasConsoleUrl = platformConfig?.consoleUrl || process.env.TAILOR_PLATFORM_CONSOLE_URL;
18033
- return {
18034
- ...isDefaultPlatform(platformConfig) ? {} : { platform_url: getPlatformBaseUrl(platformConfig) },
18035
- ...hasOAuth2ClientId ? { oauth2_client_id: getOAuth2ClientId(platformConfig) } : {},
18036
- ...hasConsoleUrl ? { console_url: getConsoleBaseUrl(platformConfig) } : {}
18037
- };
18038
- }
18039
- /**
18040
- * Create a new workspace with the given options.
18041
- * @param options - Workspace creation options
18042
- * @returns Created workspace info
18043
- */
18044
- async function createWorkspace(options) {
18045
- const result = createWorkspaceOptionsSchema.safeParse(options);
18046
- if (!result.success) throw new Error(assertDefined(result.error.issues[0], "Zod returned no issues").message);
18047
- const validated = result.data;
18048
- const client = await initOperatorClient(await loadAccessToken());
18049
- await validateRegion(validated.region, client);
18050
- return workspaceInfoWithFolderName(client, assertDefined((await client.createWorkspace({
18051
- workspaceName: validated.name,
18052
- workspaceRegion: validated.region,
18053
- deleteProtection: validated.deleteProtection ?? false,
18054
- organizationId: validated.organizationId,
18055
- folderId: validated.folderId
18056
- })).workspace, "createWorkspace response missing workspace"));
18057
- }
18058
- const createCommand = defineAppCommand({
18059
- name: "create",
18060
- description: "Create a new Tailor Platform workspace.",
18061
- args: z.object({
18062
- name: arg(z.string(), {
18063
- alias: "n",
18064
- description: "Workspace name"
18065
- }),
18066
- region: arg(z.string(), {
18067
- alias: "r",
18068
- description: "Workspace region (us-west, asia-northeast)"
18069
- }),
18070
- "delete-protection": arg(z.boolean().default(false), {
18071
- alias: "d",
18072
- description: "Enable delete protection"
18073
- }),
18074
- "organization-id": arg(z.string().optional(), {
18075
- alias: "o",
18076
- description: "Organization ID to workspace associate with",
18077
- env: "TAILOR_PLATFORM_ORGANIZATION_ID"
18078
- }),
18079
- "folder-id": arg(z.string().optional(), {
18080
- alias: "f",
18081
- description: "Folder ID to workspace associate with",
18082
- env: "TAILOR_PLATFORM_FOLDER_ID"
18083
- }),
18084
- "profile-name": arg(z.string().optional(), {
18085
- alias: "p",
18086
- description: "Profile name to create"
18087
- }),
18088
- "profile-user": arg(z.string().optional(), { description: "User email for the profile (defaults to current user)" }),
18089
- permission: arg(z.enum(["write", "read"]).default("write"), { description: "Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active." })
18090
- }).strict(),
18091
- run: async (args) => {
18092
- await assertWritable();
18093
- const profileName = args["profile-name"];
18094
- let profileSetup;
18095
- if (profileName) {
18096
- const config = await readPlatformConfig();
18097
- if (config.profiles[profileName]) throw new Error(`Profile "${profileName}" already exists.`);
18098
- const activeProfileName = process.env.TAILOR_PLATFORM_PROFILE;
18099
- const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
18100
- const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
18101
- const profileUser = args["profile-user"] || activeProfileEntry?.user || config.current_user;
18102
- if (!profileUser) throw new Error("Current user not found. Please login or specify --profile-user to create a profile.");
18103
- if (!hasUserTokenEntry(config, profileUser, platformConfig)) throw new Error(`User "${profileUser}" not found.\nPlease verify your user name and login using 'tailor-sdk login' command.`);
18104
- profileSetup = {
18105
- name: profileName,
18106
- user: profileUser,
18107
- platformSettings: profilePlatformSettings(platformConfig)
18108
- };
18109
- }
18110
- const workspace = await createWorkspace({
18111
- name: args.name,
18112
- region: args.region,
18113
- deleteProtection: args["delete-protection"],
18114
- organizationId: args["organization-id"],
18115
- folderId: args["folder-id"]
18116
- });
18117
- let profileInfo;
18118
- if (profileSetup) {
18119
- const config = await readPlatformConfig();
18120
- const platformSettings = profileSetup.platformSettings;
18121
- config.profiles[profileSetup.name] = {
18122
- user: profileSetup.user,
18123
- workspace_id: workspace.id,
18124
- ...args.permission === "read" ? { readonly: true } : {},
18125
- ...platformSettings
18126
- };
18127
- writePlatformConfig(config);
18128
- profileInfo = {
18129
- name: profileSetup.name,
18130
- user: profileSetup.user,
18131
- workspaceId: workspace.id,
18132
- permission: args.permission,
18133
- ...platformSettings.platform_url ? { platformUrl: platformSettings.platform_url } : {},
18134
- ...platformSettings.oauth2_client_id ? { oauth2ClientId: platformSettings.oauth2_client_id } : {},
18135
- ...platformSettings.console_url ? { consoleUrl: platformSettings.console_url } : {}
18136
- };
18137
- if (!args.json) logger.success(`Profile "${profileSetup.name}" created successfully.`);
18138
- }
18139
- if (!args.json) logger.success(`Workspace "${workspaceDisplayName(workspace)}" created successfully.`);
18140
- if (args.json && profileInfo) {
18141
- logger.out({
18142
- ...workspace,
18143
- profile: profileInfo
18144
- });
18145
- return;
18146
- }
18147
- logger.out(workspace, { display: {
18148
- name: workspaceNameTransformer,
18149
- folderName: null
18150
- } });
18151
- if (profileInfo) {
18152
- logger.out("Profile:");
18153
- logger.out(profileInfo);
18154
- }
18155
- }
18156
- });
18157
-
18158
19409
  //#endregion
18159
19410
  //#region src/cli/commands/workspace/delete.ts
18160
19411
  const deleteWorkspaceOptionsSchema = z.object({ workspaceId: z.uuid({ message: "workspace-id must be a valid UUID" }) });
@@ -18268,42 +19519,6 @@ const getCommand = defineAppCommand({
18268
19519
  }
18269
19520
  });
18270
19521
 
18271
- //#endregion
18272
- //#region src/cli/commands/workspace/list.ts
18273
- /**
18274
- * List workspaces with an optional order and limit.
18275
- * @param options - Workspace listing options
18276
- * @returns List of workspaces
18277
- */
18278
- async function listWorkspaces(options) {
18279
- const client = await initOperatorClient(await loadAccessToken());
18280
- const pageDirection = toPageDirection(options?.order);
18281
- return workspaceInfosWithFolderNames(client, await fetchPaged(async (pageToken, pageSize) => {
18282
- const { workspaces, nextPageToken } = await client.listWorkspaces({
18283
- pageToken,
18284
- pageSize,
18285
- pageDirection
18286
- });
18287
- return [workspaces, nextPageToken];
18288
- }, { limit: options?.limit }));
18289
- }
18290
- const listCommand$1 = defineAppCommand({
18291
- name: "list",
18292
- description: "List all Tailor Platform workspaces.",
18293
- args: z.object({ ...paginationArgs() }).strict(),
18294
- run: async (args) => {
18295
- const workspaces = await listWorkspaces({
18296
- order: args.order,
18297
- limit: args.limit
18298
- });
18299
- logger.out(workspaces, { display: {
18300
- name: workspaceNameTransformer,
18301
- folderName: null,
18302
- updatedAt: null
18303
- } });
18304
- }
18305
- });
18306
-
18307
19522
  //#endregion
18308
19523
  //#region src/cli/commands/workspace/restore.ts
18309
19524
  const restoreWorkspaceOptionsSchema = z.object({ workspaceId: z.uuid({ message: "workspace-id must be a valid UUID" }) });
@@ -19560,5 +20775,5 @@ function isDeno() {
19560
20775
  }
19561
20776
 
19562
20777
  //#endregion
19563
- export { getOrganization as $, compareSnapshotWithRemote as $t, listCommand$3 as A, apiCommand as An, getExecutorWaitFailureMessage as At, show as B, listWorkflowExecutions as Bt, listCommand$2 as C, hasChanges as Cn, listWebhookExecutors as Ct, waitWorkflowExecution as D, PluginManager as Dn, listCommand$9 as Dt, waitCommand as E, sdkNameLabelKey as En, triggerExecutor as Et, generateCommand as F, startWorkflow as Ft, removeCommand$1 as G, deploy as Gt, extractOwnedNamespaces as H, formatKeyValueTable as Ht, generateMigrationScript as I, getCommand$5 as It, organizationTree as J, waitForExecution as Jt, updateCommand$1 as K, ensureConfigId as Kt, writeDbTypesFile as L, getWorkflow as Lt, truncate as M, assertWritable as Mn, listExecutorJobs as Mt, truncateCommand as N, defineAppCommand as Nn, watchExecutorJob as Nt, resumeCommand as O, generateUserTypes as On, listExecutors as Ot, generate as P, startCommand as Pt, getCommand$1 as Q, parseMigrationLabelNumber as Qt, getConfiguredEditorCommand as R, executionsCommand as Rt, listApps as S, formatMigrationDiff as Sn, getFunctionRegistry as St, healthCommand as T, resourceTrn as Tn, triggerCommand as Tt, logBetaWarning as U, getCommand$6 as Ut, showCommand as V, functionExecutionStatusToString as Vt, remove as W, getExecutor as Wt, listCommand$4 as X, MIGRATION_LABEL_KEY as Xt, treeCommand as Y, bundleMigrationScript as Yt, listOrganizations as Z, handleOptionalToRequiredError as Zt, getWorkspace as _, loadDiff as _n, listMachineUsers as _t, updateUser as a, MIGRATE_FILE_NAME as an, getFolder as at, createCommand as b, parseMigrationNumberArg as bn, listFunctionRegistries as bt, listCommand as c, compareLocalTypesWithSnapshot as cn, createCommand$1 as ct, inviteUser as d, getLatestMigrationNumber as dn, listOAuth2Clients as dt, generateAllTypeManifestsFromSnapshot as en, updateCommand$2 as et, restoreCommand as f, getMigrationDirPath as fn, getCommand$3 as ft, getCommand as g, isValidMigrationNumber as gn, listCommand$7 as gt, listWorkspaces as h, getNextMigrationNumber as hn, tokenCommand as ht, updateCommand as i, INITIAL_SCHEMA_NUMBER as in, getCommand$2 as it, listWorkflows as j, apiCall as jn, jobsCommand as jt, resumeWorkflow as k, prompt as kn, getExecutorJob as kt, listUsers as l, compareSnapshots as ln, createFolder as lt, listCommand$1 as m, getMigrationFiles as mn, getMachineUserToken as mt, query as n, DB_TYPES_FILE_NAME as nn, listCommand$5 as nt, removeCommand as o, SCHEMA_FILE_NAME as on, deleteCommand$1 as ot, restoreWorkspace as p, getMigrationFilePath as pn, getOAuth2Client as pt, updateOrganization as q, executeScript as qt, queryCommand as r, DIFF_FILE_NAME as rn, listFolders as rt, removeUser as s, assertValidMigrationFiles as sn, deleteFolder as st, isNativeTypeScriptRuntime as t, protoGqlPermission as tn, updateFolder as tt, inviteCommand as u, createSnapshotFromLocalTypes as un, listCommand$6 as ut, deleteCommand as v, reconstructSnapshotFromMigrations as vn, generate$1 as vt, getAppHealth as w, getNamespacesWithMigrations as wn, webhookCommand as wt, createWorkspace as x, formatDiffSummary as xn, getCommand$4 as xt, deleteWorkspace as y, formatMigrationNumber as yn, listCommand$8 as yt, openInConfiguredEditor as z, getWorkflowExecution as zt };
19564
- //# sourceMappingURL=runtime-_vUgjiqT.mjs.map
20778
+ export { listFolders as $, deploymentArgs as $n, handleOptionalToRequiredError as $t, generate as A, generateUserTypes as An, startCommand as At, remove as B, loadConsoleBaseUrl as Bn, deploy as Bt, waitWorkflowExecution as C, formatDiffSummary as Cn, listCommand$8 as Ct, listWorkflows as D, resourceTrn as Dn, jobsCommand as Dt, listCommand$2 as E, getNamespacesWithMigrations as En, getExecutorWaitFailureMessage as Et, openInConfiguredEditor as F, deleteUserTokens as Fn, getWorkflowExecution as Ft, treeCommand as G, readPlatformConfig as Gn, createWorkspace$1 as Gt, updateCommand$1 as H, loadStoredUserTokens as Hn, listCommand$9 as Ht, show as I, fetchLatestToken as In, listWorkflowExecutions as It, getCommand$1 as J, writePlatformConfig as Jn, ensureConfigId as Jt, listCommand$3 as K, resolveUserTokenKey as Kn, formatKeyValueTable as Kt, showCommand as L, hasAnyUserTokenEntry as Ln, functionExecutionStatusToString as Lt, generateMigrationScript as M, apiCall as Mn, getCommand$5 as Mt, writeDbTypesFile as N, assertWritable as Nn, getWorkflow as Nt, truncate as O, sdkNameLabelKey as On, listExecutorJobs as Ot, getConfiguredEditorCommand as P, loadConfig as Pn, executionsCommand as Pt, listCommand$4 as Q, confirmationArgs as Qn, MIGRATION_LABEL_KEY as Qt, extractOwnedNamespaces as R, hasUserTokenEntry as Rn, getCommand$6 as Rt, waitCommand as S, parseMigrationNumberArg as Sn, triggerExecutor as St, resumeWorkflow as T, hasChanges as Tn, getExecutorJob as Tt, updateOrganization as U, loadWorkspaceId as Un, listWorkspaces as Ut, removeCommand$1 as V, loadMachineUserName as Vn, deployFromCLI as Vt, organizationTree as W, platformConfigFromProfile as Wn, createCommand$1 as Wt, updateCommand$2 as X, commonArgs as Xn, waitForExecution as Xt, getOrganization as Y, defineAppCommand as Yn, executeScript as Yt, updateFolder as Z, configArg as Zn, bundleMigrationScript as Zt, deleteWorkspace as _, getNextMigrationNumber as _n, getCommand$4 as _t, updateUser as a, DIFF_FILE_NAME as an, toPageDirection as ar, createFolder as at, getAppHealth as b, reconstructSnapshotFromMigrations as bn, webhookCommand as bt, listCommand as c, SCHEMA_FILE_NAME as cn, getCommand$3 as ct, inviteUser as d, compareSnapshots as dn, tokenCommand as dt, parseMigrationLabelNumber as en, isVerbose as er, getCommand$2 as et, restoreCommand as f, createSnapshotFromLocalTypes as fn, listCommand$6 as ft, deleteCommand as g, getMigrationFiles as gn, listFunctionRegistries as gt, getWorkspace as h, getMigrationFilePath as hn, listCommand$7 as ht, updateCommand as i, DB_TYPES_FILE_NAME as in, resolveMachineUserInputSource as ir, createCommand as it, generateCommand as j, prompt as jn, startWorkflow as jt, truncateCommand as k, PluginManager as kn, watchExecutorJob as kt, listUsers as l, assertValidMigrationFiles as ln, getOAuth2Client as lt, getCommand as m, getMigrationDirPath as mn, generate$1 as mt, query as n, generateAllTypeManifestsFromSnapshot as nn, pagedLogArgs as nr, deleteCommand$1 as nt, removeCommand as o, INITIAL_SCHEMA_NUMBER as on, workspaceArgs as or, listCommand$5 as ot, restoreWorkspace as p, getLatestMigrationNumber as pn, listMachineUsers as pt, listOrganizations as q, saveUserTokens as qn, workspaceNameSchema as qt, queryCommand as r, protoGqlPermission as rn, paginationArgs as rr, deleteFolder as rt, removeUser as s, MIGRATE_FILE_NAME as sn, listOAuth2Clients as st, isNativeTypeScriptRuntime as t, compareSnapshotWithRemote as tn, multiConfigArg as tr, getFolder as tt, inviteCommand as u, compareLocalTypesWithSnapshot as un, getMachineUserToken as ut, listApps as v, isValidMigrationNumber as vn, getFunctionRegistry as vt, resumeCommand as w, formatMigrationDiff as wn, listExecutors as wt, healthCommand as x, formatMigrationNumber as xn, triggerCommand as xt, listCommand$1 as y, loadDiff as yn, listWebhookExecutors as yt, logBetaWarning as z, loadAccessToken as zn, getExecutor as zt };
20779
+ //# sourceMappingURL=runtime-BD2vYJFT.mjs.map