@tailor-platform/sdk 2.0.0-next.5 → 2.0.0-next.7

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 (129) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/{application-Bf8wxOi6.mjs → application-GzW98_Xr.mjs} +377 -1418
  3. package/dist/application-GzW98_Xr.mjs.map +1 -0
  4. package/dist/application-OM0taSPn.mjs +3 -0
  5. package/dist/cli/commands/deploy/deploy.d.mts +8 -3
  6. package/dist/cli/commands/generate/options.d.mts +0 -1
  7. package/dist/cli/commands/generate/service.d.mts +1 -1
  8. package/dist/cli/commands/staticwebsite/deploy.d.mts +19 -0
  9. package/dist/cli/commands/workspace/create.d.mts +3 -0
  10. package/dist/cli/commands/workspace/list.d.mts +1 -0
  11. package/dist/cli/commands/workspace/transform.d.mts +2 -0
  12. package/dist/cli/index.mjs +781 -1553
  13. package/dist/cli/index.mjs.map +1 -1
  14. package/dist/cli/lib.d.mts +7 -3
  15. package/dist/cli/lib.mjs +49 -3
  16. package/dist/cli/lib.mjs.map +1 -1
  17. package/dist/cli/shared/config-loader.d.mts +1 -1
  18. package/dist/cli/shared/readonly-guard.d.mts +25 -0
  19. package/dist/cli/shared/tailordb-namespaces.d.mts +37 -0
  20. package/dist/cli/shared/workspace-name.d.mts +1 -0
  21. package/dist/completion/zsh-worker.zsh +15 -303
  22. package/dist/configure/config/types.d.mts +5 -1
  23. package/dist/configure/index.mjs +20 -19
  24. package/dist/configure/index.mjs.map +1 -1
  25. package/dist/configure/services/auth/types.d.mts +18 -23
  26. package/dist/configure/services/executor/executor.d.mts +2 -13
  27. package/dist/configure/services/executor/operation.d.mts +8 -3
  28. package/dist/configure/services/tailordb/schema.d.mts +1 -7
  29. package/dist/configure/services/tailordb/types.d.mts +1 -1
  30. package/dist/configure/services/workflow/execution-policy.d.mts +2 -2
  31. package/dist/configure/services/workflow/job.d.mts +10 -10
  32. package/dist/configure/services/workflow/workflow.d.mts +6 -4
  33. package/dist/configure/types/type.d.mts +1 -9
  34. package/dist/{crashreport-D3RjuSYb.mjs → crashreport-0ioNRZ4K.mjs} +1 -1
  35. package/dist/{crashreport-Cg-pqRJV.mjs → crashreport-CxHne11E.mjs} +4 -3
  36. package/dist/{crashreport-Cg-pqRJV.mjs.map → crashreport-CxHne11E.mjs.map} +1 -1
  37. package/dist/errors-DfnF4ibq.mjs +100 -0
  38. package/dist/errors-DfnF4ibq.mjs.map +1 -0
  39. package/dist/{globals-BjhJJ2dN.mjs → globals-CBZ0egXT.mjs} +13 -10
  40. package/dist/globals-CBZ0egXT.mjs.map +1 -0
  41. package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
  42. package/dist/logger-BwS4ppwO.mjs.map +1 -0
  43. package/dist/parser/service/tailordb/type-source.d.mts +10 -0
  44. package/dist/{register-ts-hook-BhzVkej0.mjs → register-ts-hook-DvEs6YsL.mjs} +2571 -1636
  45. package/dist/register-ts-hook-DvEs6YsL.mjs.map +1 -0
  46. package/dist/{registry-BSbq6SPo.mjs → registry-i7EdJ-D5.mjs} +10 -10
  47. package/dist/registry-i7EdJ-D5.mjs.map +1 -0
  48. package/dist/runtime/field-parse.d.mts +10 -0
  49. package/dist/runtime/globals.d.mts +3 -3
  50. package/dist/runtime/index.mjs +1 -1
  51. package/dist/runtime/workflow.d.mts +18 -33
  52. package/dist/runtime/workflow.mjs +1 -1
  53. package/dist/{schema-iCd1ugGp.mjs → schema--xYWRGfe.mjs} +42 -24
  54. package/dist/schema--xYWRGfe.mjs.map +1 -0
  55. package/dist/{secret-file-aoNXJmKa.mjs → secret-file-C3J4Gy8r.mjs} +2 -27
  56. package/dist/secret-file-C3J4Gy8r.mjs.map +1 -0
  57. package/dist/{secretmanager-Bd45j7an.mjs → secretmanager-IY4UvinW.mjs} +42 -9
  58. package/dist/secretmanager-IY4UvinW.mjs.map +1 -0
  59. package/dist/service-BJAQ70e5.mjs +3 -0
  60. package/dist/{service-DeZeYa06.mjs → service-DaeJlBoH.mjs} +3 -3
  61. package/dist/{service-DeZeYa06.mjs.map → service-DaeJlBoH.mjs.map} +1 -1
  62. package/dist/{service-CzZEVrp9.mjs → service-Dmxa2I4i.mjs} +64 -28
  63. package/dist/service-Dmxa2I4i.mjs.map +1 -0
  64. package/dist/service_pb-BoLM0X4K.mjs +167 -0
  65. package/dist/service_pb-BoLM0X4K.mjs.map +1 -0
  66. package/dist/{service_pb-DZkfjB_6.mjs → service_pb-r1T7kpJc.mjs} +1 -1
  67. package/dist/types/executor.generated.d.mts +60 -42
  68. package/dist/user-agent-Bgsszb5I.mjs +28 -0
  69. package/dist/user-agent-Bgsszb5I.mjs.map +1 -0
  70. package/dist/utils/test/index.mjs +11 -11
  71. package/dist/utils/test/index.mjs.map +1 -1
  72. package/dist/utils/test/mock.d.mts +6 -6
  73. package/dist/vitest/environment.mjs +1 -1
  74. package/dist/vitest/index.d.mts +8 -8
  75. package/dist/vitest/index.mjs +613 -227
  76. package/dist/vitest/index.mjs.map +1 -1
  77. package/dist/vitest/mock.d.mts +8 -8
  78. package/dist/vitest/mocks/aigateway.d.mts +14 -4
  79. package/dist/vitest/mocks/authconnection.d.mts +14 -4
  80. package/dist/vitest/mocks/file.d.mts +23 -6
  81. package/dist/vitest/mocks/iconv.d.mts +28 -4
  82. package/dist/vitest/mocks/idp.d.mts +25 -9
  83. package/dist/vitest/mocks/secretmanager.d.mts +13 -4
  84. package/dist/vitest/mocks/tailordb.d.mts +48 -7
  85. package/dist/vitest/mocks/workflow.d.mts +59 -33
  86. package/dist/vitest/setup.mjs +2 -2
  87. package/dist/vitest/workflow-local.d.mts +2 -2
  88. package/dist/workflow-CsBvRE3g.mjs +34 -0
  89. package/dist/workflow-CsBvRE3g.mjs.map +1 -0
  90. package/dist/{service_pb-Bk7dL-yT.mjs → workspace_resource_pb-Db3fv68L.mjs} +47 -165
  91. package/dist/workspace_resource_pb-Db3fv68L.mjs.map +1 -0
  92. package/docs/cli/application.md +63 -15
  93. package/docs/cli/function.md +2 -2
  94. package/docs/cli/tailordb.md +8 -140
  95. package/docs/cli/workspace.md +6 -4
  96. package/docs/cli-reference.md +11 -8
  97. package/docs/configuration.md +2 -0
  98. package/docs/github-actions.md +8 -0
  99. package/docs/migration/v2.md +169 -11
  100. package/docs/runtime.md +5 -6
  101. package/docs/services/auth.md +7 -7
  102. package/docs/services/executor.md +5 -0
  103. package/docs/services/tailordb.md +44 -0
  104. package/docs/services/workflow.md +43 -43
  105. package/docs/testing.md +112 -96
  106. package/package.json +2 -5
  107. package/dist/application-Bf8wxOi6.mjs.map +0 -1
  108. package/dist/application-CUkjzfij.mjs +0 -3
  109. package/dist/cli/erd-viewer-assets/app.js +0 -1424
  110. package/dist/cli/erd-viewer-assets/index.html +0 -77
  111. package/dist/cli/erd-viewer-assets/serve.json +0 -13
  112. package/dist/cli/erd-viewer-assets/styles.css +0 -1036
  113. package/dist/configure/types/field-runtime.d.mts +0 -16
  114. package/dist/errors-118wUDRr.mjs +0 -42
  115. package/dist/errors-118wUDRr.mjs.map +0 -1
  116. package/dist/globals-BjhJJ2dN.mjs.map +0 -1
  117. package/dist/logger-BEiZZ3qT.mjs.map +0 -1
  118. package/dist/register-ts-hook-BhzVkej0.mjs.map +0 -1
  119. package/dist/registry-BSbq6SPo.mjs.map +0 -1
  120. package/dist/resource_pb-BRv7AMXK.mjs +0 -49
  121. package/dist/resource_pb-BRv7AMXK.mjs.map +0 -1
  122. package/dist/schema-iCd1ugGp.mjs.map +0 -1
  123. package/dist/secret-file-aoNXJmKa.mjs.map +0 -1
  124. package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
  125. package/dist/service-BT0GPd01.mjs +0 -3
  126. package/dist/service-CzZEVrp9.mjs.map +0 -1
  127. package/dist/service_pb-Bk7dL-yT.mjs.map +0 -1
  128. package/dist/workflow-GNuSnsTx.mjs +0 -22
  129. package/dist/workflow-GNuSnsTx.mjs.map +0 -1
@@ -1,699 +1,1269 @@
1
- import { t as db } from "./schema-iCd1ugGp.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--xYWRGfe.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 { B as readPlatformConfig, C as createBundleCache, Ct as positiveIntArg, D as loadConfig, E as hashFile, Et as workspaceArgs, F as loadMachineUserName, G as writePlatformConfig, H as resolveConfigUser, I as loadPlatformClientConfig, J as fetchAll, M as loadAccessToken, N as loadConfigPath, R as loadWorkspaceId, S as hasGenerationHooks, St as parseDuration, T as hashContent, Tt as toPageDirection, X as fetchMachineUserToken, Y as fetchAllTolerant, Z as fetchPaged, _ as platformBundleDefinePlugin, _t as nonNegativeIntArg, at as initOperatorClient, bt as pagedLogArgs, ct as byName, dt as confirmationArgs, et as getConsoleBaseUrl, f as buildExecutorArgsExpr, ft as deploymentArgs, g as stringifyFunction, h as assertUniqueTailorDBTypeNamesWithExternal, i as resolverBundleKey, m as assertUniqueLocalTailorDBTypeNames, mt as folderArgs, n as generatePluginFilesIfNeeded, nt as getOrNull, o as getApplicationAuthNamespace, ot as isDefaultPlatform, p as buildResolverOperationHookExpr, pt as durationArg, r as loadApplication, rt as getPlatformBaseUrl, s as WorkflowJobFunctionExecutionPolicySchema, st as resolveStaticWebsiteUrls, t as defineApplication, tt as getOAuth2ClientId, u as HTTP_METHODS, ut as configArg, vt as orderArg, w as getDistDir, wt as resolveMachineUserInputSource, x as getPluginGenerationDependencies, xt as paginationArgs, yt as organizationArgs, z as platformConfigFromProfile } from "./application-Bf8wxOi6.mjs";
6
- import { c as stripTailorDBTypeBuilderHelpers, h as loadFilesWithIgnores, t as createExecutorService, u as TailorDBTypeSchema } from "./service-CzZEVrp9.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, P as fetchUserInfo, R as getPlatformBaseUrl, S as hasGenerationHooks, T as hashContent, U as rememberPlatformConfigForToken, V as isDefaultPlatform, W as resolveStaticWebsiteUrls, _ as platformBundleDefinePlugin, f as buildExecutorArgsExpr, g as stringifyFunction, h as assertUniqueTailorDBTypeNamesWithExternal, i as resolverBundleKey, j as fetchMachineUserToken, k as fetchAll, m as assertUniqueLocalTailorDBTypeNames, n as generatePluginFilesIfNeeded, o as getApplicationAuthNamespace, p as buildResolverOperationHookExpr, r as loadApplication, s as WorkflowJobFunctionExecutionPolicySchema, t as defineApplication, u as HTTP_METHODS, w as getDistDir, x as getPluginGenerationDependencies, z as initOAuth2Client } from "./application-GzW98_Xr.mjs";
6
+ import { c as stripTailorDBTypeBuilderHelpers, g as functionSchema, h as loadFilesWithIgnores, t as createExecutorService, u as TailorDBTypeSchema } from "./service-Dmxa2I4i.mjs";
7
7
  import { t as multiline } from "./multiline-sfHpTZZK.mjs";
8
8
  import { t as readPackageJson } from "./package-json-8b0O9TlX.mjs";
9
- import { i as userAgent } from "./secret-file-aoNXJmKa.mjs";
10
- import { n as isCLIError, t as createCLIError } from "./errors-118wUDRr.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, Mt as UpdateAIGatewayRequestSchema, 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, jt as CreateAIGatewayRequestSchema, 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-Bk7dL-yT.mjs";
9
+ import { t as userAgent } from "./user-agent-Bgsszb5I.mjs";
10
+ import { r as isCLIError, t as createCLIError } from "./errors-DfnF4ibq.mjs";
11
+ import { n as tightenSecretFilePermissions, r as writeSecretFile } from "./secret-file-C3J4Gy8r.mjs";
12
12
  import { r as withSpan } from "./telemetry-BRVdwh14.mjs";
13
13
  import * as mod from "node:module";
14
14
  import * as path from "pathe";
15
+ import { basename, dirname, join, resolve } from "pathe";
15
16
  import { resolveTSConfig } from "pkg-types";
16
17
  import { arg, createDefineCommand, defineCommand, runCommand } from "politty";
17
18
  import { z } from "zod";
18
- import { ScalarType, create, fromJson, toJson } from "@bufbuild/protobuf";
19
- import * as fs$1 from "node:fs";
19
+ import { create, fromJson, toJson } from "@bufbuild/protobuf";
20
+ import * as fs$2 from "node:fs";
20
21
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { parseEnv } from "node:util";
21
23
  import { ValueSchema, timestampDate } from "@bufbuild/protobuf/wkt";
22
24
  import { formatDistanceToNowStrict } from "date-fns";
23
25
  import { getBorderCharacters, table } from "table";
24
26
  import { pathToFileURL } from "node:url";
27
+ import { parseYAML, stringifyYAML } from "confbox";
25
28
  import { findUpSync } from "find-up-simple";
29
+ import { lt } from "semver";
26
30
  import { xdgConfig } from "xdg-basedir";
27
31
  import { Code, ConnectError } from "@connectrpc/connect";
28
32
  import pLimit from "p-limit";
29
33
  import * as crypto$1 from "node:crypto";
30
- import { createHash } from "node:crypto";
34
+ import { createHash, randomUUID } from "node:crypto";
31
35
  import { ExitPromptError } from "@inquirer/core";
32
- import { confirm, input, password } from "@inquirer/prompts";
36
+ import { confirm, input, password, select } from "@inquirer/prompts";
33
37
  import { isCI } from "std-env";
34
38
  import * as rolldown from "rolldown";
35
- import * as fs from "node:fs/promises";
36
- import { glob } from "node:fs/promises";
37
39
  import { parseSync } from "oxc-parser";
38
40
  import * as inflection from "inflection";
39
41
  import { tmpdir } from "node:os";
40
42
  import { pathToString } from "@bufbuild/protobuf/reflect";
41
43
  import { createValidator } from "@bufbuild/protovalidate";
44
+ import * as fs from "node:fs/promises";
45
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
42
46
  import { setTimeout as setTimeout$1 } from "timers/promises";
43
47
  import { setTimeout as setTimeout$2 } from "node:timers/promises";
44
48
  import { spawn } from "node:child_process";
45
- import { watch } from "chokidar";
46
- import * as madgeModule from "madge";
49
+ import * as fs$1 from "fs";
50
+ import { lookup } from "mime-types";
47
51
  import { createPrompt } from "@toiroakr/read-multiline";
48
52
  import { astVisitor, parse } from "pgsql-ast-parser";
49
53
  import { parse as parse$1 } from "@0no-co/graphql.web";
50
54
 
51
- //#region src/cli/shared/command.ts
55
+ //#region src/cli/shared/args.ts
56
+ const unitToMs = {
57
+ ms: 1,
58
+ s: 1e3,
59
+ m: 60 * 1e3
60
+ };
61
+ const durationPattern = /^(\d+)(ms|s|m)$/;
52
62
  /**
53
- * defineCommand with global args type (CommonArgsType).
54
- * Use this for leaf commands with `run` to get type-safe access to global args.
55
- * Parent commands with only `subCommands` can use `defineCommand` from politty directly.
63
+ * Schema for duration string validation (e.g., "3s", "500ms", "1m")
64
+ * Only validates format; use parseDuration() to convert to milliseconds
56
65
  */
57
- const defineAppCommand = createDefineCommand();
58
-
59
- //#endregion
60
- //#region src/cli/shared/readonly-guard.ts
66
+ const durationArg = z.string().refine((val) => durationPattern.test(val), { message: "Invalid duration format. Expected format: '3s', '500ms', '1m'" }).refine((val) => {
67
+ const match = val.match(durationPattern);
68
+ if (!match) return false;
69
+ const digits = match[1];
70
+ return digits !== void 0 && parseInt(digits, 10) > 0;
71
+ }, { message: "Duration must be greater than 0" });
61
72
  /**
62
- * Throw a CLIError if the active profile has `readonly: true`.
63
- *
64
- * Resolves the active profile in this order:
65
- * 1. `opts.profile` (CLI flag)
66
- * 2. `process.env.TAILOR_PLATFORM_PROFILE`
67
- *
68
- * If neither is set, no profile is in scope so the call is allowed. This is
69
- * intentional: `TAILOR_PLATFORM_TOKEN` direct access (CI / machine user) and
70
- * `--workspace-id` without a profile are out-of-band paths whose authorization
71
- * is governed by the bearer token itself, not by the local profile flag.
72
- *
73
- * If the resolved profile cannot be found in the config, this function returns
74
- * silently and lets downstream loaders surface the not-found error.
75
- * @param opts - Options
76
- * @param opts.profile - Optional explicit profile name from command args
73
+ * Parse a validated duration string into milliseconds
74
+ * @param duration - Duration string (e.g., "3s", "500ms", "1m")
75
+ * @returns Duration in milliseconds
77
76
  */
78
- async function assertWritable(opts) {
79
- const profileName = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
80
- if (!profileName) return;
81
- const profile = (await readPlatformConfig()).profiles[profileName];
82
- if (!profile || profile.readonly !== true) return;
83
- throw createCLIError({
84
- code: "PROFILE_READONLY",
85
- message: `Profile "${profileName}" is read-only.`,
86
- 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.",
87
- suggestion: `Use a different profile, unset TAILOR_PLATFORM_PROFILE, or run 'tailor profile update ${profileName} --permission write'.`
88
- });
89
- }
90
-
91
- //#endregion
92
- //#region src/cli/commands/api/api-call.ts
93
- function hasEnvAccessToken() {
94
- const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
95
- return Boolean(envToken);
77
+ function parseDuration(duration) {
78
+ const match = assertDefined(duration.match(durationPattern), `invalid duration format: ${duration}`);
79
+ const value = parseInt(assertDefined(match[1], "duration digits group missing"), 10);
80
+ const unit = assertDefined(match[2], "duration unit group missing");
81
+ return value * unitToMs[unit];
96
82
  }
97
83
  /**
98
- * Call Tailor Platform API endpoints directly.
99
- * If the endpoint doesn't contain "/", it defaults to `tailor.v1.OperatorService/{endpoint}`.
100
- * @param options - API call options (profile, endpoint, body)
101
- * @returns Response status and data
84
+ * Schema for positive integer validation (from string input)
85
+ * Transforms the string to a number
102
86
  */
103
- async function apiCall(options) {
104
- const accessToken = await loadAccessToken({ profile: options.profile });
105
- let platformConfig;
106
- try {
107
- platformConfig = await loadPlatformClientConfig({ profile: options.profile });
108
- } catch (error) {
109
- if (!hasEnvAccessToken()) throw error;
110
- }
111
- let endpointPath;
112
- if (options.endpoint.includes("/")) endpointPath = options.endpoint;
113
- else endpointPath = `tailor.v1.OperatorService/${options.endpoint}`;
114
- const url = new URL(endpointPath, getPlatformBaseUrl(platformConfig));
115
- const response = await fetch(url.toString(), {
116
- method: "POST",
117
- headers: {
118
- "Content-Type": "application/json",
119
- Authorization: `Bearer ${accessToken}`,
120
- "User-Agent": await userAgent()
121
- },
122
- body: options.body ?? "{}"
123
- });
124
- const data = await response.json();
125
- if (!response.ok) throw new Error(`API call failed (${response.status}): ${JSON.stringify(data)}`);
126
- return {
127
- status: response.status,
128
- data
129
- };
87
+ const positiveIntArg = z.coerce.number().int().positive();
88
+ /**
89
+ * Schema for non-negative integer validation (from string input).
90
+ * Accepts 0 (used for `--limit 0` to disable the limit).
91
+ */
92
+ const nonNegativeIntArg = z.coerce.number().int().nonnegative();
93
+ /**
94
+ * Schema for sort order (`asc` or `desc`).
95
+ */
96
+ const orderArg = z.enum(["asc", "desc"]);
97
+ /**
98
+ * Translate a CLI `--order` value into the proto `PageDirection` enum.
99
+ * Returns `undefined` when the user did not specify an order so that
100
+ * callers can omit the field and fall back to the server default.
101
+ * @param order - Order string from CLI args (`"asc"` | `"desc"` | undefined)
102
+ * @returns PageDirection, or undefined when `order` is undefined
103
+ */
104
+ function toPageDirection(order) {
105
+ if (order === void 0) return void 0;
106
+ return order === "asc" ? PageDirection.ASC : PageDirection.DESC;
107
+ }
108
+ function hasMachineUserFlag(argv) {
109
+ 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="));
130
110
  }
131
-
132
- //#endregion
133
- //#region src/cli/commands/api/proto-reflect.ts
134
- const UNARY_METHODS = OperatorService.methods.filter((m) => m.methodKind === "unary");
135
- function listMethodNames() {
136
- return UNARY_METHODS.map((m) => m.name).toSorted();
137
- }
138
- /**
139
- * Returns every accepted form of the `endpoint` positional — bare method name
140
- * and fully-qualified `service/Method`. politty's expand completion keys its
141
- * static table by the literal endpoint string, so both forms must appear in
142
- * `choices` or the user typing the fully-qualified name gets no `--field`
143
- * completion candidates back at TAB time.
144
- * @returns Sorted list of accepted endpoint values
145
- */
146
- function listMethodChoices() {
147
- return UNARY_METHODS.flatMap((m) => [m.name, `${OperatorService.typeName}/${m.name}`]).toSorted();
148
- }
149
- function getMethodDescriptor(methodName) {
150
- return UNARY_METHODS.find((m) => m.name === methodName);
151
- }
152
- function extractMethodName(endpoint) {
153
- if (!endpoint.includes("/")) return endpoint;
154
- return endpoint.split("/").pop() ?? endpoint;
155
- }
156
- /**
157
- * Returns the nested message descriptor when the field is a message, a list of
158
- * messages, or a map with message values. Otherwise returns undefined.
159
- * @param field - The proto field descriptor to inspect
160
- * @returns The nested message descriptor, or undefined when the field isn't message-shaped
161
- */
162
- function nestedMessage(field) {
163
- if (field.fieldKind === "message") return field.message;
164
- if (field.fieldKind === "list" && field.listKind === "message") return field.message;
165
- if (field.fieldKind === "map" && field.mapKind === "message") return field.message;
166
- }
167
- /**
168
- * Most `google.protobuf.*` well-known types serialize as scalars in proto JSON
169
- * (Timestamp/Duration → RFC3339/duration string, FieldMask → comma-joined
170
- * string, *Value wrappers → underlying primitive, etc.), so we must not let
171
- * `--field` drill into their internal fields. Treating them as leaves keeps
172
- * the completion and the body shape consistent with what the server expects.
173
- * @param message - The proto message descriptor to inspect
174
- * @returns True when the message is a well-known type
175
- */
176
- function isWellKnownType(message) {
177
- return message.typeName.startsWith("google.protobuf.");
178
- }
179
- const UNREPRESENTABLE_WELL_KNOWN_TYPES = /* @__PURE__ */ new Set([
180
- "google.protobuf.Struct",
181
- "google.protobuf.Value",
182
- "google.protobuf.ListValue",
183
- "google.protobuf.NullValue",
184
- "google.protobuf.Any",
185
- "google.protobuf.Empty"
186
- ]);
187
111
  /**
188
- * @param message - The proto message descriptor to inspect
189
- * @returns True when the message is a well-known type that `--field` cannot represent
112
+ * Resolve whether a parsed machine user value came from an explicit CLI option or env fallback.
113
+ * @param machineUser - Parsed machine user value
114
+ * @param argv - Raw CLI argv, excluding the executable and script path
115
+ * @param options - Source resolution options
116
+ * @returns Machine user input source, or undefined when no value was parsed
190
117
  */
191
- function isUnrepresentableWellKnownType(message) {
192
- return UNREPRESENTABLE_WELL_KNOWN_TYPES.has(message.typeName);
118
+ function resolveMachineUserInputSource(machineUser, argv = process.argv.slice(2), options = {}) {
119
+ if (machineUser === void 0) return void 0;
120
+ if (options.valueIsExplicit) return "option";
121
+ if (hasMachineUserFlag(argv)) return "option";
122
+ return process.env.TAILOR_PLATFORM_MACHINE_USER_NAME === machineUser ? "env" : "option";
193
123
  }
194
124
  /**
195
- * Pre-enumerates every `--field` completion candidate for `methodName`,
196
- * walking the input message tree to produce a flat list that the shell can
197
- * prefix-filter at TAB time. Used by the `expand` completion variant so no
198
- * Node process is spawned per keystroke.
199
- *
200
- * For each leaf field, emits `key=` (key completion). Enum leaves additionally
201
- * emit `key=ENUM_VALUE` per value, bool leaves emit `key=true` and
202
- * `key=false`. For nested messages, emits `key.` (drill-down) and recurses.
125
+ * Load env files from parsed arguments.
126
+ * Processes --env-file first, then --env-file-if-exists.
203
127
  *
204
- * Returns an empty array when `methodName` is unknown so the `expand`
205
- * generator stays exception-free at script-generation time.
206
- * @param methodName - Name of the unary RPC whose input message is being walked
207
- * @returns Flat list of `{ value, description }` candidates
208
- */
209
- function enumerateAllFieldCompletions(methodName) {
210
- const method = getMethodDescriptor(methodName);
211
- if (!method) return [];
212
- const candidates = [];
213
- const visited = /* @__PURE__ */ new Set();
214
- function walk(message, prefix) {
215
- visited.add(message);
216
- for (const field of message.fields) {
217
- if (field.fieldKind === "list" || field.fieldKind === "map") continue;
218
- const fullKey = prefix + field.localName;
219
- if (field.fieldKind === "message") {
220
- if (isUnrepresentableWellKnownType(field.message)) continue;
221
- if (!isWellKnownType(field.message)) {
222
- const nested = field.message;
223
- candidates.push({
224
- value: `${fullKey}.`,
225
- description: `${fullKey} (message)`
226
- });
227
- if (!visited.has(nested)) walk(nested, `${fullKey}.`);
228
- continue;
229
- }
128
+ * Follows Node.js --env-file behavior:
129
+ * - Variables already set in the environment are NOT overwritten
130
+ * - Variables from later files override those from earlier files
131
+ * @param envFiles - Required env file path(s) that must exist
132
+ * @param envFilesIfExists - Optional env file path(s) that are loaded if they exist
133
+ */
134
+ function loadEnvFiles(envFiles, envFilesIfExists) {
135
+ const originalEnvKeys = new Set(Object.keys(process.env));
136
+ const load = (files, required) => {
137
+ for (const file of [files ?? []].flat()) {
138
+ const envPath = path.resolve(process.cwd(), file);
139
+ if (!fs$2.existsSync(envPath)) {
140
+ if (required) throw new Error(`Environment file not found: ${envPath}`);
141
+ continue;
230
142
  }
231
- candidates.push({
232
- value: `${fullKey}=`,
233
- description: `Set ${fullKey}`
234
- });
235
- if (field.fieldKind === "enum") for (const v of field.enum.values) candidates.push({
236
- value: `${fullKey}=${v.name}`,
237
- description: v.name
238
- });
239
- else if (field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
240
- candidates.push({
241
- value: `${fullKey}=true`,
242
- description: "true"
243
- });
244
- candidates.push({
245
- value: `${fullKey}=false`,
246
- description: "false"
247
- });
143
+ const parsed = parseEnv(fs$2.readFileSync(envPath, "utf-8"));
144
+ for (const [key, value] of Object.entries(parsed)) {
145
+ if (originalEnvKeys.has(key)) continue;
146
+ process.env[key] = value;
248
147
  }
249
148
  }
250
- visited.delete(message);
251
- }
252
- walk(method.input, "");
253
- return candidates;
254
- }
255
- /**
256
- * Resolves the leaf `DescField` at a dotted `--field` path within an input
257
- * message tree. Returns undefined when any segment is missing or when a
258
- * non-leaf segment isn't a singular message — callers fall back to treating
259
- * the value as a string in that case.
260
- * @param input - The RPC input message descriptor
261
- * @param path - Dot-split path segments to follow (e.g. ["application", "name"])
262
- * @returns The leaf field descriptor, or undefined when the path doesn't resolve
263
- */
264
- function resolveLeafField(input, path) {
265
- let message = input;
266
- for (let i = 0; i < path.length; i++) {
267
- const field = message.fields.find((f) => f.localName === path[i]);
268
- if (!field) return void 0;
269
- if (i === path.length - 1) {
270
- if (field.fieldKind === "message" && isUnrepresentableWellKnownType(field.message)) return;
271
- return field;
272
- }
273
- if (field.fieldKind !== "message") return void 0;
274
- if (isWellKnownType(field.message)) return void 0;
275
- message = field.message;
276
- }
149
+ };
150
+ load(envFiles, true);
151
+ load(envFilesIfExists, false);
277
152
  }
278
-
279
- //#endregion
280
- //#region src/cli/commands/api/render.ts
281
- const SCALAR_LABEL = {
282
- [ScalarType.DOUBLE]: "double",
283
- [ScalarType.FLOAT]: "float",
284
- [ScalarType.INT64]: "int64",
285
- [ScalarType.UINT64]: "uint64",
286
- [ScalarType.INT32]: "int32",
287
- [ScalarType.FIXED64]: "fixed64",
288
- [ScalarType.FIXED32]: "fixed32",
289
- [ScalarType.BOOL]: "bool",
290
- [ScalarType.STRING]: "string",
291
- [ScalarType.BYTES]: "bytes",
292
- [ScalarType.UINT32]: "uint32",
293
- [ScalarType.SFIXED32]: "sfixed32",
294
- [ScalarType.SFIXED64]: "sfixed64",
295
- [ScalarType.SINT32]: "sint32",
296
- [ScalarType.SINT64]: "sint64"
297
- };
298
- function shortName(typeName) {
299
- const dot = typeName.lastIndexOf(".");
300
- return dot < 0 ? typeName : typeName.slice(dot + 1);
301
- }
302
- function scalarLabel(scalar) {
303
- return SCALAR_LABEL[scalar] ?? `scalar(${scalar})`;
304
- }
305
- function enumLabel(enumDesc) {
306
- return `enum ${shortName(enumDesc.typeName)}`;
307
- }
308
- function enumOf(field) {
309
- if (field.fieldKind === "enum") return field.enum;
310
- if (field.fieldKind === "list" && field.listKind === "enum") return field.enum;
311
- }
312
- function describeFieldType(field) {
313
- switch (field.fieldKind) {
314
- case "scalar": return scalarLabel(field.scalar);
315
- case "enum": return enumLabel(field.enum);
316
- case "message": return shortName(field.message.typeName);
317
- case "list": {
318
- let inner;
319
- if (field.listKind === "scalar") inner = scalarLabel(field.scalar);
320
- else if (field.listKind === "enum") inner = enumLabel(field.enum);
321
- else inner = shortName(field.message.typeName);
322
- return `repeated ${inner}`;
153
+ /**
154
+ * Common arguments for all CLI commands
155
+ *
156
+ * NOTE: --env-file and --env-file-if-exists collide with Node.js flags due to a bug
157
+ * (https://github.com/nodejs/node/issues/54232). Node.js parses these even after the
158
+ * script path, causing warnings (twice due to tsx loader).
159
+ */
160
+ const commonArgs = {
161
+ "env-file": arg(z.string().optional(), {
162
+ alias: "e",
163
+ description: "Path to the environment file (error if not found)",
164
+ completion: {
165
+ type: "file",
166
+ matcher: [".env.*", ".env"]
167
+ }
168
+ }),
169
+ "env-file-if-exists": arg(z.string().optional(), {
170
+ description: "Path to the environment file (ignored if not found)",
171
+ completion: {
172
+ type: "file",
173
+ matcher: [".env.*", ".env"]
174
+ },
175
+ effect: (_value, { args }) => {
176
+ loadEnvFiles(args["env-file"], args["env-file-if-exists"]);
177
+ }
178
+ }),
179
+ verbose: arg(z.boolean().default(false), {
180
+ description: "Enable verbose logging",
181
+ effect: (value) => {
182
+ verboseMode = value;
323
183
  }
324
- case "map": {
325
- const keyType = scalarLabel(field.mapKey);
326
- let valType;
327
- if (field.mapKind === "scalar") valType = scalarLabel(field.scalar);
328
- else if (field.mapKind === "enum") valType = enumLabel(field.enum);
329
- else valType = shortName(field.message.typeName);
330
- return `map<${keyType}, ${valType}>`;
184
+ }),
185
+ json: arg(z.boolean().default(false), {
186
+ alias: "j",
187
+ description: "Output as JSON",
188
+ effect: (value) => {
189
+ logger.jsonMode = value;
331
190
  }
332
- default: return "<unknown>";
191
+ })
192
+ };
193
+ /**
194
+ * Arguments for commands that require workspace context
195
+ */
196
+ const workspaceArgs = {
197
+ "workspace-id": arg(z.string().optional(), {
198
+ alias: "w",
199
+ description: "Workspace ID",
200
+ env: "TAILOR_PLATFORM_WORKSPACE_ID",
201
+ completion: { type: "none" }
202
+ }),
203
+ profile: arg(z.string().optional(), {
204
+ alias: "p",
205
+ description: "Workspace profile",
206
+ env: "TAILOR_PLATFORM_PROFILE",
207
+ completion: { type: "none" }
208
+ })
209
+ };
210
+ /**
211
+ * Shared config arg for commands that accept a config file path
212
+ */
213
+ const configArg = { config: arg(z.string().default("tailor.config.ts"), {
214
+ alias: "c",
215
+ description: "Path to Tailor config file",
216
+ env: "TAILOR_CONFIG_PATH",
217
+ completion: {
218
+ type: "file",
219
+ extensions: ["ts"]
333
220
  }
334
- }
335
- function fieldToJson(field, visited) {
336
- const json = {
337
- name: field.localName,
338
- protoName: field.name,
339
- type: describeFieldType(field),
340
- fieldKind: field.fieldKind,
341
- repeated: field.fieldKind === "list"
342
- };
343
- if (field.oneof) json.oneof = field.oneof.name;
344
- const fieldEnum = enumOf(field);
345
- if (fieldEnum) json.enumValues = fieldEnum.values.map((v) => v.name);
346
- const nested = nestedMessage(field);
347
- if (nested && !visited.has(nested)) {
348
- visited.add(nested);
349
- json.message = {
350
- typeName: nested.typeName,
351
- fields: nested.fields.map((f) => fieldToJson(f, visited))
352
- };
353
- visited.delete(nested);
354
- } else if (nested) json.message = {
355
- typeName: nested.typeName,
356
- fields: [],
357
- recursive: true
358
- };
359
- return json;
360
- }
361
- function renderInspectJson(method) {
362
- const visited = /* @__PURE__ */ new Set([method.input]);
363
- return {
364
- method: method.name,
365
- input: {
366
- typeName: method.input.typeName,
367
- fields: method.input.fields.map((f) => fieldToJson(f, visited))
368
- },
369
- output: { typeName: method.output.typeName }
370
- };
371
- }
372
- function renderFieldText(field, indent, visited) {
373
- const lines = [];
374
- const oneofTag = field.oneof ? ` (oneof ${field.oneof.name})` : "";
375
- lines.push(`${indent}${field.localName}: ${describeFieldType(field)}${oneofTag}`);
376
- const fieldEnum = enumOf(field);
377
- if (fieldEnum) {
378
- const values = fieldEnum.values.map((v) => v.name).join(", ");
379
- lines.push(`${indent} values: ${values}`);
380
- }
381
- const nested = nestedMessage(field);
382
- if (nested && !visited.has(nested)) {
383
- visited.add(nested);
384
- for (const sub of nested.fields) lines.push(...renderFieldText(sub, `${indent} `, visited));
385
- visited.delete(nested);
386
- } else if (nested) lines.push(`${indent} …(recursive ${nested.typeName})`);
387
- return lines;
388
- }
389
- function renderInspectText(method) {
390
- const lines = [];
391
- lines.push(`${method.name}`);
392
- lines.push(` request: ${method.input.typeName}`);
393
- const visited = /* @__PURE__ */ new Set([method.input]);
394
- for (const f of method.input.fields) lines.push(...renderFieldText(f, " ", visited));
395
- lines.push(` response: ${method.output.typeName}`);
396
- return lines.join("\n");
221
+ }) };
222
+ /**
223
+ * Shared config arg for commands that accept one or more comma-separated config file paths
224
+ */
225
+ const multiConfigArg = { config: arg(z.string().default("tailor.config.ts"), {
226
+ alias: "c",
227
+ description: "Path to SDK config file. Use comma-separated paths to deploy multiple apps together.",
228
+ env: "TAILOR_PLATFORM_SDK_CONFIG_PATH",
229
+ completion: {
230
+ type: "file",
231
+ extensions: ["ts"]
232
+ }
233
+ }) };
234
+ /**
235
+ * Arguments for commands that interact with deployed resources (includes config)
236
+ */
237
+ const deploymentArgs = {
238
+ ...workspaceArgs,
239
+ ...configArg
240
+ };
241
+ /**
242
+ * Arguments for commands that require confirmation
243
+ */
244
+ const confirmationArgs = { yes: arg(z.boolean().default(false), {
245
+ alias: "y",
246
+ description: "Skip confirmation prompts"
247
+ }) };
248
+ /**
249
+ * Arguments for commands that require organization context
250
+ */
251
+ const organizationArgs = { "organization-id": arg(z.string(), {
252
+ alias: "o",
253
+ description: "Organization ID",
254
+ env: "TAILOR_PLATFORM_ORGANIZATION_ID",
255
+ completion: { type: "none" }
256
+ }) };
257
+ /**
258
+ * Arguments for list commands that accept `--order` / `--limit`. Sort
259
+ * order defaults to `desc` (newest first) because most callers want the
260
+ * latest items; pass `--order asc` to opt in to ascending order. The
261
+ * limit is unbounded by default so existing invocations keep returning
262
+ * every item; pass `--limit N` to cap the result size.
263
+ * @param defaultOrder - Default value for `--order` (defaults to `"desc"`)
264
+ * @returns Argument shape suitable for spreading into a command schema
265
+ */
266
+ const paginationArgs = (defaultOrder = "desc") => ({
267
+ order: arg(orderArg.default(defaultOrder), { description: "Sort order (asc or desc)" }),
268
+ limit: arg(nonNegativeIntArg.optional(), {
269
+ alias: "l",
270
+ description: "Maximum number of items to return (0 or omit: unlimited)"
271
+ })
272
+ });
273
+ /**
274
+ * Arguments for time-series log list commands. Defaults to newest-first
275
+ * (`desc`) and a 50-item cap so that listing stays responsive on busy
276
+ * workspaces. Pass `--limit 0` to disable the cap and fetch all entries.
277
+ */
278
+ const pagedLogArgs = {
279
+ order: arg(orderArg.default("desc"), { description: "Sort order (asc or desc)" }),
280
+ limit: arg(nonNegativeIntArg.default(50), {
281
+ alias: "l",
282
+ description: "Maximum number of items to return (0: unlimited)"
283
+ })
284
+ };
285
+ /**
286
+ * Arguments for commands that require folder context
287
+ */
288
+ const folderArgs = { "folder-id": arg(z.string(), {
289
+ alias: "f",
290
+ description: "Folder ID",
291
+ env: "TAILOR_PLATFORM_FOLDER_ID",
292
+ completion: { type: "none" }
293
+ }) };
294
+ let verboseMode = false;
295
+ /**
296
+ * Returns whether verbose mode is enabled.
297
+ * Used by the global cleanup handler which doesn't have access to parsed args.
298
+ * @returns Whether verbose mode is enabled
299
+ */
300
+ function isVerbose() {
301
+ return verboseMode;
397
302
  }
398
303
 
399
304
  //#endregion
400
- //#region src/cli/commands/api/inspect.ts
401
- const inspectCommand = defineAppCommand({
402
- name: "inspect",
403
- description: "Print the input message tree of an OperatorService endpoint.",
404
- notes: "Combine with the global `--json` flag for a machine-readable descriptor. Recursive type references and `oneof` membership are annotated. Use `tailor api list` to discover endpoint names.",
405
- examples: [{
406
- cmd: "GetApplication",
407
- desc: "Show fields of GetApplicationRequest."
408
- }, {
409
- cmd: "CreateExecutorExecutor",
410
- desc: "Inspect a deeply nested input with `(oneof config)` annotations."
411
- }],
412
- args: z.strictObject({ endpoint: arg(z.string(), {
413
- positional: true,
414
- description: "API endpoint to inspect (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
415
- completion: { custom: { choices: listMethodNames() } }
416
- }) }),
417
- run: (args) => {
418
- const methodName = extractMethodName(args.endpoint);
419
- const method = getMethodDescriptor(methodName);
420
- if (!method) throw createCLIError({
421
- message: `unknown method: ${methodName}`,
422
- suggestion: "Run `tailor api list` to see available methods.",
423
- command: "api inspect"
424
- });
425
- if (logger.jsonMode) logger.out(renderInspectJson(method));
426
- else logger.out(renderInspectText(method));
427
- }
428
- });
305
+ //#region src/cli/shared/command.ts
306
+ /**
307
+ * defineCommand with global args type (CommonArgsType).
308
+ * Use this for leaf commands with `run` to get type-safe access to global args.
309
+ * Parent commands with only `subCommands` can use `defineCommand` from politty directly.
310
+ */
311
+ const defineAppCommand = createDefineCommand();
429
312
 
430
313
  //#endregion
431
- //#region src/cli/commands/api/list.ts
432
- const listCommand$10 = defineAppCommand({
433
- name: "list",
434
- description: "List all invocable OperatorService methods.",
435
- notes: "Only single-request (non-streaming) methods are listed, because the CLI issues a single JSON request and reads one JSON response.",
436
- args: z.strictObject({}),
437
- run: () => {
438
- const names = listMethodNames();
439
- if (logger.jsonMode) logger.out(names);
440
- else for (const name of names) logger.out(name);
441
- }
314
+ //#region src/parser/app-config/schema.ts
315
+ const envValueSchema = z.union([
316
+ z.string(),
317
+ z.number(),
318
+ z.boolean()
319
+ ]);
320
+ const LogLevelSchema = z.enum(LOG_LEVELS);
321
+ const logLevelSchema = z.string().refine((value) => LogLevelSchema.safeParse(value.trim().toUpperCase()).success, { message: `'logLevel' must be one of: ${LOG_LEVELS.join(", ")}.` });
322
+ /**
323
+ * Structural validation schema for `defineConfig({...})`. Validates only
324
+ * top-level fields with platform-side constraints (notably `id`); fields
325
+ * that carry SDK builder objects (`auth`, `idp`, `db`, ...) are accepted
326
+ * as opaque values, since their internal shapes are validated by their
327
+ * own factory functions and parser-level schemas.
328
+ *
329
+ * The `id` is auto-managed by `deploy` and stored as a plain UUID. A
330
+ * label-compatible prefix is added at the metadata boundary, so user-facing
331
+ * configs only need to carry a UUID.
332
+ */
333
+ const AppConfigSchema = z.strictObject({
334
+ id: z.uuid({ message: "'id' must be a UUID." }).optional(),
335
+ name: z.string().min(1, { message: "'name' must be a non-empty string." }),
336
+ env: z.record(z.string(), envValueSchema).optional(),
337
+ cors: z.array(z.string()).optional(),
338
+ allowedIpAddresses: z.array(z.string()).optional(),
339
+ disableIntrospection: z.boolean().optional(),
340
+ inlineSourcemap: z.boolean().optional(),
341
+ logLevel: logLevelSchema.optional(),
342
+ db: z.unknown().optional(),
343
+ resolver: z.unknown().optional(),
344
+ idp: z.unknown().optional(),
345
+ auth: z.unknown().optional(),
346
+ executor: z.unknown().optional(),
347
+ workflow: z.unknown().optional(),
348
+ httpAdapter: z.unknown().optional(),
349
+ staticWebsites: z.unknown().optional(),
350
+ aiGateways: z.unknown().optional(),
351
+ secrets: z.unknown().optional()
442
352
  });
443
353
 
444
354
  //#endregion
445
- //#region src/cli/commands/api/index.ts
446
- function resolveNamespaceName(methodName, config) {
447
- if (/Auth|Tenant|UserProfile/.test(methodName)) return config.auth?.name;
448
- if (/IdP/.test(methodName)) {
449
- if (config.idp?.length === 1) return assertDefined(config.idp[0], "idp config missing").name;
450
- return;
451
- }
452
- if (/TailorDB/.test(methodName)) {
453
- const keys = Object.keys(config.db ?? {});
454
- if (keys.length === 1) return keys[0];
455
- return;
456
- }
457
- if (/Pipeline/.test(methodName)) {
458
- const keys = Object.keys(config.resolver ?? {});
459
- if (keys.length === 1) return keys[0];
460
- return;
355
+ //#region src/parser/plugin-config/schema.ts
356
+ const PluginConfigSchema = z.looseObject({
357
+ id: z.string(),
358
+ description: z.string(),
359
+ importPath: z.string().optional(),
360
+ pluginConfig: z.unknown().optional(),
361
+ typeConfigRequired: z.union([z.boolean(), functionSchema]).optional(),
362
+ onTypeLoaded: functionSchema.optional(),
363
+ onNamespaceLoaded: functionSchema.optional(),
364
+ onTailorDBReady: functionSchema.optional(),
365
+ onResolverReady: functionSchema.optional(),
366
+ onExecutorReady: functionSchema.optional()
367
+ }).refine((p) => {
368
+ return !(p.onTypeLoaded || p.onNamespaceLoaded) || !!p.importPath;
369
+ }, { message: "importPath is required when plugin has definition-time hooks (onTypeLoaded/onNamespaceLoaded)" }).transform((plugin) => plugin);
370
+
371
+ //#endregion
372
+ //#region src/cli/shared/token-store.ts
373
+ const SERVICE_NAME = "tailor-platform-cli";
374
+ let entryClass;
375
+ async function getEntryClass() {
376
+ if (entryClass !== void 0) return entryClass;
377
+ try {
378
+ const mod = await import("@napi-rs/keyring");
379
+ const probe = new mod.Entry(SERVICE_NAME, "__probe__");
380
+ probe.setPassword("probe");
381
+ probe.deletePassword();
382
+ entryClass = mod.Entry;
383
+ } catch {
384
+ logger.warn("System keyring is not available. Tokens will be stored in the config file. Set TAILOR_PLATFORM_TOKEN environment variable for CI environments.");
385
+ entryClass = false;
461
386
  }
387
+ return entryClass;
388
+ }
389
+ /**
390
+ * Check whether the OS keyring is available and functional.
391
+ * @returns true if keyring is available
392
+ */
393
+ async function isKeyringAvailable() {
394
+ return await getEntryClass() !== false;
462
395
  }
463
396
  /**
464
- * Parse a JSON body string as a plain object for field injection.
465
- * Returns undefined for invalid JSON or non-object values (null, arrays, primitives),
466
- * so callers can fall back to sending the raw string unchanged.
467
- * @param body - Raw body string
468
- * @returns Parsed object or undefined
397
+ * Load tokens from the OS keyring for a given account.
398
+ * @param account - User identifier (e.g. email or client ID)
399
+ * @returns Token data or undefined if not found or keyring unavailable
469
400
  */
470
- function parseBodyAsObject(body) {
471
- let parsed;
401
+ async function loadKeyringTokens(account) {
402
+ const Entry = await getEntryClass();
403
+ if (!Entry) return void 0;
472
404
  try {
473
- parsed = JSON.parse(body);
405
+ const raw = new Entry(SERVICE_NAME, account).getPassword();
406
+ if (raw === null) return void 0;
407
+ return JSON.parse(raw);
474
408
  } catch {
475
409
  return;
476
410
  }
477
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
478
- return parsed;
479
- }
480
- /**
481
- * Set a dotted path on a body object, replacing non-object intermediates as
482
- * needed. `--field` takes precedence over `--body`, so collisions overwrite.
483
- * @param obj - The body object to mutate
484
- * @param path - Dot-split path segments (e.g. ["application", "name"])
485
- * @param value - Value to assign at the leaf
486
- */
487
- function setNestedPath(obj, path, value) {
488
- let cursor = obj;
489
- for (let i = 0; i < path.length - 1; i++) {
490
- const key = assertDefined(path[i], "path segment missing");
491
- const next = cursor[key];
492
- if (typeof next !== "object" || next === null || Array.isArray(next)) cursor[key] = {};
493
- cursor = cursor[key];
494
- }
495
- cursor[assertDefined(path[path.length - 1], "path last segment missing")] = value;
496
- }
497
- /**
498
- * Coerces a raw `--field` string value to match the leaf proto type so the
499
- * JSON body sends a properly typed value. Today we only coerce bool — the
500
- * completion candidates explicitly suggest `=true`/`=false`, so sending the
501
- * literal string "true" would be a visible mismatch. Other scalars are left
502
- * as strings: proto JSON accepts string forms for ints/floats/int64/etc.,
503
- * and we'd rather pass through what the user typed than silently coerce.
504
- * @param field - The resolved leaf field descriptor, or undefined when the path didn't resolve
505
- * @param raw - The raw string value after `=`
506
- * @returns The value to write into the body object
507
- */
508
- function coerceFieldValue(field, raw) {
509
- if (field && field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
510
- if (raw === "true") return true;
511
- if (raw === "false") return false;
512
- throw new Error(`Invalid value for bool field: '${raw}'. Expected 'true' or 'false'.`);
513
- }
514
- return raw;
515
- }
516
- /**
517
- * Collapse top-level body keys that address a known input field by its
518
- * protobuf name (snake_case) or JSON name to the field's `localName`. protojson
519
- * accepts every alias, so converging them here lets the presence checks below
520
- * reason in a single namespace — otherwise a `--body` written as
521
- * `{"workspace_id": ...}` slips past the camelCase check and we inject a second
522
- * `workspaceId`, which the server rejects as a duplicate field. When both forms
523
- * are present the canonical key wins and the alias is dropped.
524
- * @param body - The parsed body object to mutate
525
- * @param fields - The target endpoint's input fields
526
- * @returns Whether any key was rewritten
527
- */
528
- function normalizeBodyFieldKeys(body, fields) {
529
- const aliasToLocal = /* @__PURE__ */ new Map();
530
- for (const f of fields) {
531
- aliasToLocal.set(f.name, f.localName);
532
- aliasToLocal.set(f.jsonName, f.localName);
533
- }
534
- let changed = false;
535
- for (const key of Object.keys(body)) {
536
- const local = aliasToLocal.get(key);
537
- if (!local || local === key) continue;
538
- if (!Object.hasOwn(body, local)) body[local] = body[key];
539
- delete body[key];
540
- changed = true;
541
- }
542
- return changed;
543
- }
544
- const FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set([
545
- "__proto__",
546
- "constructor",
547
- "prototype"
411
+ }
412
+ /**
413
+ * Save tokens to the OS keyring for a given account.
414
+ * @param account - User identifier (e.g. email or client ID)
415
+ * @param tokens - Token data to store
416
+ */
417
+ async function saveKeyringTokens(account, tokens) {
418
+ const Entry = await getEntryClass();
419
+ if (!Entry) return;
420
+ new Entry(SERVICE_NAME, account).setPassword(JSON.stringify(tokens));
421
+ }
422
+ /**
423
+ * Delete tokens from the OS keyring for a given account.
424
+ * @param account - User identifier (e.g. email or client ID)
425
+ */
426
+ async function deleteKeyringTokens(account) {
427
+ const Entry = await getEntryClass();
428
+ if (!Entry) return;
429
+ try {
430
+ new Entry(SERVICE_NAME, account).deletePassword();
431
+ } catch {}
432
+ }
433
+
434
+ //#endregion
435
+ //#region src/cli/shared/context.ts
436
+ const pfProfileSchema = z.object({
437
+ user: z.string(),
438
+ workspace_id: z.string(),
439
+ readonly: z.boolean().optional(),
440
+ machine_user: z.string().optional(),
441
+ machine_user_override: z.enum(["allow", "deny"]).optional(),
442
+ platform_url: z.url().optional(),
443
+ oauth2_client_id: z.string().optional(),
444
+ console_url: z.url().optional()
445
+ });
446
+ const pfUserSchemaV1 = z.object({
447
+ access_token: z.string(),
448
+ refresh_token: z.string().optional(),
449
+ token_expires_at: z.string()
450
+ });
451
+ const pfUserKeyringSchema = z.object({
452
+ storage: z.literal("keyring"),
453
+ token_expires_at: z.string()
454
+ });
455
+ const pfUserFileSchema = z.object({
456
+ storage: z.literal("file"),
457
+ token_expires_at: z.string(),
458
+ access_token: z.string(),
459
+ refresh_token: z.string().optional()
460
+ });
461
+ const pfUserSchemaV2 = z.discriminatedUnion("storage", [pfUserKeyringSchema, pfUserFileSchema]);
462
+ const pfUserKeyringSchemaV3 = pfUserKeyringSchema.extend({ email: z.string().optional() });
463
+ const pfUserFileSchemaV3 = pfUserFileSchema.extend({ email: z.string().optional() });
464
+ const pfUserSchemaV3 = z.discriminatedUnion("storage", [pfUserKeyringSchemaV3, pfUserFileSchemaV3]);
465
+ const pfConfigSchemaV1 = z.object({
466
+ version: z.literal(1),
467
+ users: z.partialRecord(z.string(), pfUserSchemaV1),
468
+ profiles: z.partialRecord(z.string(), pfProfileSchema),
469
+ current_user: z.string().nullable()
470
+ });
471
+ const V2_CONFIG_VERSION = 2;
472
+ const LATEST_CONFIG_VERSION = 3;
473
+ const V2_MIN_SDK_VERSION = "1.29.0";
474
+ const V3_MIN_SDK_VERSION = "2.0.0";
475
+ const semverSchema = z.templateLiteral([
476
+ z.number().int(),
477
+ ".",
478
+ z.number().int(),
479
+ ".",
480
+ z.number().int()
548
481
  ]);
549
- const fieldArg = z.string().transform((val, ctx) => {
550
- const eq = val.indexOf("=");
551
- if (eq < 0) {
552
- ctx.addIssue({
553
- code: z.ZodIssueCode.custom,
554
- message: `Invalid field format: '${val}'. Expected format: 'key=value' or 'a.b.c=value'`
555
- });
556
- return z.NEVER;
557
- }
558
- const key = val.slice(0, eq);
559
- if (key.length === 0) {
560
- ctx.addIssue({
561
- code: z.ZodIssueCode.custom,
562
- message: "Field key cannot be empty"
563
- });
564
- return z.NEVER;
482
+ const pfConfigSchemaV2 = z.object({
483
+ version: z.literal(V2_CONFIG_VERSION),
484
+ min_sdk_version: semverSchema,
485
+ latest_version: z.number().int().optional(),
486
+ latest_min_sdk_version: semverSchema.optional(),
487
+ users: z.partialRecord(z.string(), pfUserSchemaV2),
488
+ profiles: z.partialRecord(z.string(), pfProfileSchema),
489
+ current_user: z.string().nullable()
490
+ });
491
+ const pfConfigSchemaV3 = z.object({
492
+ version: z.literal(LATEST_CONFIG_VERSION),
493
+ min_sdk_version: semverSchema,
494
+ latest_version: z.number().int().optional(),
495
+ latest_min_sdk_version: semverSchema.optional(),
496
+ users: z.partialRecord(z.string(), pfUserSchemaV3),
497
+ profiles: z.partialRecord(z.string(), pfProfileSchema),
498
+ current_user: z.string().nullable()
499
+ });
500
+ function platformConfigPath() {
501
+ if (!xdgConfig) throw new Error("User home directory not found");
502
+ return path.join(xdgConfig, "tailor-platform", "config.yaml");
503
+ }
504
+ /**
505
+ * Convert stored profile platform fields to platform client settings.
506
+ * @param profile - Profile platform settings
507
+ * @returns Platform client settings
508
+ */
509
+ function platformConfigFromProfile(profile) {
510
+ const config = {
511
+ ...profile?.platform_url ? { platformUrl: profile.platform_url } : {},
512
+ ...profile?.oauth2_client_id ? { oauth2ClientId: profile.oauth2_client_id } : {},
513
+ ...profile?.console_url ? { consoleUrl: profile.console_url } : {}
514
+ };
515
+ return Object.keys(config).length > 0 ? config : void 0;
516
+ }
517
+ function platformUserKey(user, config) {
518
+ const platformUrl = getPlatformBaseUrl(config);
519
+ if (platformUrl === normalizeBaseUrl("https://api.tailor.tech")) return user;
520
+ return `${platformUrl}|${user}`;
521
+ }
522
+ function userFromPlatformUserKey(userKey, config) {
523
+ const platformPrefix = `${getPlatformBaseUrl(config)}|`;
524
+ return userKey.startsWith(platformPrefix) ? userKey.slice(platformPrefix.length) : userKey;
525
+ }
526
+ function canUseLegacyUserKey(platformUrl) {
527
+ try {
528
+ return getPlatformBaseUrl() === platformUrl;
529
+ } catch {
530
+ return false;
565
531
  }
566
- const segments = key.split(".");
567
- if (segments.some((seg) => seg.length === 0)) {
568
- ctx.addIssue({
569
- code: z.ZodIssueCode.custom,
570
- message: `Invalid field key: '${key}'. Dotted segments cannot be empty`
571
- });
572
- return z.NEVER;
532
+ }
533
+ function findUserByEmail(users, email, platformConfig) {
534
+ const platformUrl = getPlatformBaseUrl(platformConfig);
535
+ const platformPrefix = `${platformUrl}|`;
536
+ const defaultPlatform = platformUrl === normalizeBaseUrl(defaultPlatformBaseUrl);
537
+ return Object.entries(users).find(([key, entry]) => {
538
+ if (entry?.email !== email) return false;
539
+ return defaultPlatform ? !key.includes("|") : key.startsWith(platformPrefix);
540
+ });
541
+ }
542
+ function findUserEntry(config, user, platformConfig, opts = {}) {
543
+ const userKey = platformUserKey(user, platformConfig);
544
+ const userEntry = config.users[userKey];
545
+ if (userEntry) return {
546
+ userKey,
547
+ userEntry
548
+ };
549
+ const emailMatch = findUserByEmail(config.users, user, platformConfig);
550
+ if (emailMatch?.[1]) return {
551
+ userKey: emailMatch[0],
552
+ userEntry: emailMatch[1]
553
+ };
554
+ const platformUrl = getPlatformBaseUrl(platformConfig);
555
+ if (userKey !== user && (opts.allowLegacyUserKey === false || !canUseLegacyUserKey(platformUrl))) return {
556
+ userKey,
557
+ userEntry
558
+ };
559
+ const legacyEntry = config.users[user];
560
+ return legacyEntry ? {
561
+ userKey: user,
562
+ userEntry: legacyEntry
563
+ } : {
564
+ userKey,
565
+ userEntry
566
+ };
567
+ }
568
+ /**
569
+ * Resolve the config user key that would be used for a user on the selected platform.
570
+ * @param config - Platform config
571
+ * @param user - User name
572
+ * @param platformConfig - Optional platform connection settings
573
+ * @param opts - Token lookup options
574
+ * @returns Resolved config user key
575
+ */
576
+ function resolveUserTokenKey(config, user, platformConfig, opts) {
577
+ return findUserEntry(config, user, platformConfig, opts).userKey;
578
+ }
579
+ function resolveConfigUser(config, user, platformConfig, opts) {
580
+ const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
581
+ return userEntry ? userFromPlatformUserKey(userKey, platformConfig) : void 0;
582
+ }
583
+ /**
584
+ * Check whether tokens are registered for a user on the selected platform.
585
+ * @param config - Platform config
586
+ * @param user - User name
587
+ * @param platformConfig - Optional platform connection settings
588
+ * @param opts - Token lookup options
589
+ * @returns True when the user has a registered token entry
590
+ */
591
+ function hasUserTokenEntry(config, user, platformConfig, opts) {
592
+ return findUserEntry(config, user, platformConfig, opts).userEntry !== void 0;
593
+ }
594
+ function hasUserKeyForName(users, user) {
595
+ return users[user] !== void 0 || Object.keys(users).some((key) => key.endsWith(`|${user}`));
596
+ }
597
+ function hasUserEmailEntry(users, user) {
598
+ return Object.values(users).some((entry) => entry?.email === user);
599
+ }
600
+ /**
601
+ * Check whether any platform has tokens registered for a user.
602
+ * @param config - Platform config
603
+ * @param user - User name
604
+ * @returns True when the user has a token entry for any platform
605
+ */
606
+ function hasAnyUserTokenEntry(config, user) {
607
+ return hasUserKeyForName(config.users, user) || hasUserEmailEntry(config.users, user);
608
+ }
609
+ function hasCurrentUserEntry(users, currentUser) {
610
+ return hasUserKeyForName(users, currentUser);
611
+ }
612
+ /**
613
+ * Migrate a v1 config to v2.
614
+ * Tokens are kept in the config file (storage: "file") during migration.
615
+ * They will be moved to the OS keyring on next login or token refresh.
616
+ * @param v1Config - v1 configuration to migrate
617
+ * @returns Migrated v2 configuration
618
+ */
619
+ function migrateV1ToV2(v1Config) {
620
+ const users = {};
621
+ for (const [name, v1User] of Object.entries(v1Config.users)) {
622
+ if (!v1User) continue;
623
+ users[name] = {
624
+ access_token: v1User.access_token,
625
+ refresh_token: v1User.refresh_token,
626
+ token_expires_at: v1User.token_expires_at,
627
+ storage: "file"
628
+ };
573
629
  }
574
- const forbidden = segments.find((seg) => FORBIDDEN_SEGMENTS.has(seg));
575
- if (forbidden) {
576
- ctx.addIssue({
577
- code: z.ZodIssueCode.custom,
578
- message: `Invalid field key: '${key}'. Segment '${forbidden}' is not allowed.`
579
- });
580
- return z.NEVER;
630
+ return {
631
+ version: V2_CONFIG_VERSION,
632
+ min_sdk_version: V2_MIN_SDK_VERSION,
633
+ users,
634
+ profiles: v1Config.profiles,
635
+ current_user: v1Config.current_user
636
+ };
637
+ }
638
+ function inferEmailFromUserId(user) {
639
+ return z.email().safeParse(user).success ? user : void 0;
640
+ }
641
+ function migrateV2ToV3(v2Config) {
642
+ const users = {};
643
+ for (const [user, entry] of Object.entries(v2Config.users)) {
644
+ if (!entry) continue;
645
+ const email = inferEmailFromUserId(user);
646
+ users[user] = {
647
+ ...entry,
648
+ ...email ? { email } : {}
649
+ };
581
650
  }
582
651
  return {
583
- path: segments,
584
- value: val.slice(eq + 1)
652
+ version: LATEST_CONFIG_VERSION,
653
+ min_sdk_version: V3_MIN_SDK_VERSION,
654
+ users,
655
+ profiles: v2Config.profiles,
656
+ current_user: v2Config.current_user
585
657
  };
586
- });
587
- const apiCommand = defineAppCommand({
588
- name: "api",
589
- description: "Call Tailor Platform API endpoints directly.",
590
- notes: `Use \`tailor api list\` to enumerate invocable methods and \`tailor api inspect <endpoint>\` to print an endpoint's input message tree (combine with \`--json\` for machine-readable output).
591
-
592
- 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\`:
593
-
594
- - \`workspaceId\` resolved from \`-w\` / \`TAILOR_PLATFORM_WORKSPACE_ID\` / the selected profile.
595
- - \`namespaceName\` — resolved from \`tailor.config.ts\` based on the endpoint's service:
596
- - Auth / Tenant / UserProfile endpoints use \`auth.name\`.
597
- - IdP / TailorDB / Pipeline endpoints use the sole configured namespace when exactly one is defined.
598
-
599
- 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.
600
-
601
- 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.`,
602
- examples: [
603
- {
604
- cmd: "GetApplication -b '{\"applicationName\":\"app-1\"}'",
605
- desc: "Call an endpoint; workspaceId is auto-injected."
606
- },
607
- {
608
- cmd: "GetApplication -f applicationName=app-1",
609
- desc: "Same as above, using --field instead of --body."
610
- },
611
- {
612
- cmd: "list",
613
- desc: "List all invocable OperatorService methods."
614
- },
615
- {
616
- cmd: "inspect GetApplication",
617
- desc: "Show the input message tree for an endpoint."
618
- }
619
- ],
620
- subCommands: {
621
- list: listCommand$10,
622
- inspect: inspectCommand
623
- },
624
- args: z.strictObject({
625
- ...workspaceArgs,
626
- ...configArg,
627
- body: arg(z.string().default("{}"), {
628
- alias: "b",
629
- description: "Request body as JSON."
630
- }),
631
- field: arg(fieldArg.array().optional(), {
632
- alias: "f",
633
- description: "Set a body field as `key=value` (repeatable; dotted keys nest). Overrides --body.",
634
- completion: { custom: { expand: {
635
- dependsOn: ["endpoint"],
636
- enumerate: ({ endpoint }) => enumerateAllFieldCompletions(extractMethodName(endpoint ?? ""))
637
- } } }
638
- }),
639
- endpoint: arg(z.string(), {
640
- positional: true,
641
- description: "API endpoint to call (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
642
- completion: { custom: { choices: listMethodChoices() } }
643
- })
644
- }),
645
- run: async (args) => {
646
- await assertWritable({ profile: args.profile });
647
- const methodName = extractMethodName(args.endpoint);
648
- const method = getMethodDescriptor(methodName);
649
- const parsedBody = parseBodyAsObject(args.body);
650
- let mutated = false;
651
- if (args.field && args.field.length > 0) {
652
- if (!parsedBody) throw new Error("--field requires --body to be a JSON object (or omitted).");
653
- for (const f of args.field) {
654
- const leaf = method ? resolveLeafField(method.input, f.path) : void 0;
655
- setNestedPath(parsedBody, f.path, coerceFieldValue(leaf, f.value));
656
- }
657
- mutated = true;
658
+ }
659
+ function migrateV1ToV3(v1Config) {
660
+ return migrateV2ToV3(migrateV1ToV2(v1Config));
661
+ }
662
+ function formatUnknownError(error) {
663
+ return error instanceof Error ? error.message : String(error);
664
+ }
665
+ async function trySaveTokensInKeyring(user, tokens) {
666
+ if (!await isKeyringAvailable()) return false;
667
+ try {
668
+ await saveKeyringTokens(user, tokens);
669
+ return true;
670
+ } catch (error) {
671
+ logger.warn(`System keyring failed to store credentials. Tokens will be stored in the config file. ${formatUnknownError(error)}`);
672
+ return false;
673
+ }
674
+ }
675
+ async function warnIfNewerConfigAvailable(config) {
676
+ if (!config.latest_min_sdk_version) return;
677
+ if (lt((await readPackageJson()).version ?? "0.0.0", config.latest_min_sdk_version)) logger.warn(multiline`
678
+ A newer config version (${String(config.latest_version)}) is available.
679
+ Please update your SDK to >= ${config.latest_min_sdk_version}: pnpm update @tailor-platform/sdk
680
+ `);
681
+ }
682
+ /**
683
+ * Read Tailor Platform CLI configuration, migrating from v1 if necessary.
684
+ * @returns Parsed platform configuration
685
+ */
686
+ async function readPlatformConfig() {
687
+ const configPath = platformConfigPath();
688
+ if (!fs$2.existsSync(configPath)) {
689
+ const config = {
690
+ version: LATEST_CONFIG_VERSION,
691
+ min_sdk_version: V3_MIN_SDK_VERSION,
692
+ users: {},
693
+ profiles: {},
694
+ current_user: null
695
+ };
696
+ writePlatformConfig(config);
697
+ return config;
698
+ }
699
+ const rawConfig = parseYAML(fs$2.readFileSync(configPath, "utf-8"));
700
+ tightenSecretFilePermissions(configPath);
701
+ const version = rawConfig != null && typeof rawConfig === "object" && "version" in rawConfig ? rawConfig.version : void 0;
702
+ if (typeof version === "number" && version > LATEST_CONFIG_VERSION) {
703
+ const minSdk = "min_sdk_version" in rawConfig ? String(rawConfig.min_sdk_version) : void 0;
704
+ const updateHint = minSdk ? `Please update your SDK to >= ${minSdk}: pnpm update @tailor-platform/sdk` : "Please update your SDK: pnpm update @tailor-platform/sdk";
705
+ throw new Error(multiline`
706
+ Config file uses version ${String(version)}, but this SDK only supports up to version ${String(LATEST_CONFIG_VERSION)}.
707
+ ${updateHint}
708
+ `);
709
+ }
710
+ const v3Result = pfConfigSchemaV3.safeParse(rawConfig);
711
+ if (v3Result.success) {
712
+ await warnIfNewerConfigAvailable(v3Result.data);
713
+ return v3Result.data;
714
+ }
715
+ const v2Result = pfConfigSchemaV2.safeParse(rawConfig);
716
+ if (v2Result.success) {
717
+ await warnIfNewerConfigAvailable(v2Result.data);
718
+ return migrateV2ToV3(v2Result.data);
719
+ }
720
+ const v1Result = pfConfigSchemaV1.safeParse(rawConfig);
721
+ if (v1Result.success) return migrateV1ToV3(v1Result.data);
722
+ throw new Error(multiline`
723
+ Failed to parse config file at ${configPath}.
724
+ The file may be corrupted or created by an incompatible SDK version.
725
+ `);
726
+ }
727
+ function toV1ForDisk(config) {
728
+ const users = {};
729
+ for (const [name, entry] of Object.entries(config.users)) {
730
+ if (!entry || entry.storage === "keyring") continue;
731
+ users[name] = {
732
+ access_token: entry.access_token,
733
+ refresh_token: entry.refresh_token,
734
+ token_expires_at: entry.token_expires_at
735
+ };
736
+ }
737
+ const currentUser = config.current_user && hasCurrentUserEntry(users, config.current_user) ? config.current_user : null;
738
+ return {
739
+ version: 1,
740
+ users,
741
+ profiles: config.profiles,
742
+ current_user: currentUser
743
+ };
744
+ }
745
+ function hasProfilePlatformSettings(config) {
746
+ return Object.values(config.profiles).some((profile) => profile?.platform_url !== void 0 || profile?.oauth2_client_id !== void 0 || profile?.console_url !== void 0);
747
+ }
748
+ function hasScopedUserKeys(config) {
749
+ return Object.keys(config.users).some((userKey) => userKey.includes("|"));
750
+ }
751
+ function hasUserEmailMetadata(config) {
752
+ return Object.values(config.users).some((user) => user != null && "email" in user && user.email !== void 0);
753
+ }
754
+ function toLatestForDisk(config) {
755
+ return {
756
+ ...config.version === 1 ? migrateV1ToV2(config) : config,
757
+ version: LATEST_CONFIG_VERSION,
758
+ min_sdk_version: V3_MIN_SDK_VERSION
759
+ };
760
+ }
761
+ /**
762
+ * Write Tailor Platform CLI configuration to disk.
763
+ * By default, file-backed configs without newer fields are converted to V1 for
764
+ * backward compatibility, so an older SDK can still read the file. Configs
765
+ * containing a keyring user are kept in V2 or later because the keyring storage
766
+ * variant is not representable in V1. Configs containing profile-level Platform
767
+ * settings, platform-scoped user tokens, canonical user IDs, or email metadata
768
+ * are written in the latest min-SDK-gated format because older SDKs would
769
+ * silently drop or misread those settings.
770
+ *
771
+ * The config file may contain access/refresh tokens when the OS keyring is
772
+ * unavailable, so it is written via {@link writeSecretFile} so other users
773
+ * on the host cannot read it.
774
+ * @param config - Platform configuration to write
775
+ */
776
+ function writePlatformConfig(config) {
777
+ const configPath = platformConfigPath();
778
+ const hasKeyringUser = config.version !== 1 && Object.values(config.users).some((u) => u?.storage === "keyring");
779
+ writeSecretFile(configPath, stringifyYAML(config.version === LATEST_CONFIG_VERSION || hasProfilePlatformSettings(config) || hasScopedUserKeys(config) || hasUserEmailMetadata(config) ? toLatestForDisk(config) : config.version === V2_CONFIG_VERSION && !hasKeyringUser ? toV1ForDisk(config) : config));
780
+ }
781
+ function validateUUID(value, source) {
782
+ const result = z.uuid().safeParse(value);
783
+ if (!result.success) throw new Error(`Invalid value from ${source}: must be a valid UUID`);
784
+ return result.data;
785
+ }
786
+ /**
787
+ * Load workspace ID from command options, environment variables, or platform config.
788
+ * In CLI context, env fallback is also handled by politty's arg env option.
789
+ * Priority: opts/workspaceId > env/workspaceId > opts/profile > error
790
+ * @param opts - Workspace and profile options
791
+ * @returns Resolved workspace ID
792
+ */
793
+ async function loadWorkspaceId(opts) {
794
+ const workspaceId = await tryLoadWorkspaceId(opts);
795
+ if (workspaceId) return workspaceId;
796
+ throw new Error(multiline`
797
+ Workspace ID not found.
798
+ Please specify workspace ID via --workspace-id option or TAILOR_PLATFORM_WORKSPACE_ID environment variable.
799
+ `);
800
+ }
801
+ /**
802
+ * Load a workspace ID when one is explicitly configured.
803
+ * @param opts - Workspace and profile options
804
+ * @returns Resolved workspace ID, or undefined when no source is configured
805
+ */
806
+ async function tryLoadWorkspaceId(opts) {
807
+ const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
808
+ if (opts?.workspaceId !== void 0) return validateUUID(opts.workspaceId, "--workspace-id option");
809
+ if (process.env.TAILOR_PLATFORM_WORKSPACE_ID) return validateUUID(process.env.TAILOR_PLATFORM_WORKSPACE_ID, "TAILOR_PLATFORM_WORKSPACE_ID environment variable");
810
+ if (profile) {
811
+ const wsId = (await readPlatformConfig()).profiles[profile]?.workspace_id;
812
+ if (!wsId) throw new Error(`Profile "${profile}" not found`);
813
+ return validateUUID(wsId, `profile "${profile}"`);
814
+ }
815
+ }
816
+ /**
817
+ * Load machine user name from command options, environment variables, or platform config.
818
+ * In CLI context, env fallback is also handled by politty's arg env option.
819
+ * Priority: opts/machineUser > env/TAILOR_PLATFORM_MACHINE_USER_NAME > opts/profile (profile default) > undefined.
820
+ * 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.
821
+ * 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`.
822
+ * @param opts - Machine user and profile options
823
+ * @returns Resolved machine user name, or undefined if not set
824
+ */
825
+ async function loadMachineUserName(opts) {
826
+ if (opts?.machineUser === "") throw createCLIError({
827
+ code: "MACHINE_USER_NAME_EMPTY",
828
+ message: "Machine user name cannot be empty.",
829
+ suggestion: "Pass a non-empty machine user name, or omit the option to use the environment variable or profile default."
830
+ });
831
+ const envMachineUser = process.env.TAILOR_PLATFORM_MACHINE_USER_NAME || void 0;
832
+ const explicitMachineUser = opts?.machineUser ? {
833
+ source: opts.machineUserSource ?? "option",
834
+ value: opts.machineUser
835
+ } : envMachineUser ? {
836
+ source: "env",
837
+ value: envMachineUser
838
+ } : void 0;
839
+ const explicit = explicitMachineUser?.value;
840
+ const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
841
+ if (!profile) return explicit;
842
+ const entry = (await readPlatformConfig()).profiles[profile];
843
+ if (!entry) {
844
+ if (explicit) return explicit;
845
+ throw new Error(`Profile "${profile}" not found`);
846
+ }
847
+ if (entry.machine_user && entry.machine_user_override === "deny") {
848
+ if (explicit && explicit !== entry.machine_user) {
849
+ 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.`;
850
+ throw createCLIError({
851
+ code: "PROFILE_MACHINE_USER_OVERRIDE_DENIED",
852
+ message: `Profile "${profile}" denies overriding the machine user.`,
853
+ details,
854
+ suggestion: `Omit the machine user option, unset TAILOR_PLATFORM_MACHINE_USER_NAME, or run 'tailor profile update ${profile} --machine-user-override allow'.`
855
+ });
658
856
  }
659
- if (parsedBody && method) {
660
- if (normalizeBodyFieldKeys(parsedBody, method.input.fields)) mutated = true;
661
- const fieldNames = method.input.fields.map((f) => f.localName);
662
- if (fieldNames.includes("workspaceId") && !Object.hasOwn(parsedBody, "workspaceId")) try {
663
- parsedBody.workspaceId = await loadWorkspaceId({
664
- workspaceId: args["workspace-id"],
665
- profile: args.profile
666
- });
667
- mutated = true;
668
- } catch {}
669
- if (fieldNames.includes("namespaceName") && !Object.hasOwn(parsedBody, "namespaceName")) try {
670
- const { config } = await loadConfig(args.config);
671
- const ns = resolveNamespaceName(methodName, config);
672
- if (ns) {
673
- parsedBody.namespaceName = ns;
674
- mutated = true;
675
- }
676
- } catch {}
857
+ return entry.machine_user;
858
+ }
859
+ return explicit || entry.machine_user;
860
+ }
861
+ /**
862
+ * Load access token from environment variables, command options, or platform config.
863
+ * In CLI context, profile env fallback is also handled by politty's arg env option.
864
+ * Priority: env/TAILOR_PLATFORM_TOKEN > env/TAILOR_TOKEN (deprecated) > opts/profile > env/profile > config/currentUser > error
865
+ * @param opts - Profile options
866
+ * @returns Resolved access token
867
+ */
868
+ async function loadAccessToken(opts) {
869
+ const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
870
+ const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
871
+ if (envToken && !process.env.TAILOR_PLATFORM_TOKEN) logger.warn("TAILOR_TOKEN is deprecated. Please use TAILOR_PLATFORM_TOKEN instead.");
872
+ if (envToken) {
873
+ rememberPlatformConfigForToken(envToken, await loadPlatformClientConfig({
874
+ profile,
875
+ allowMissingProfile: true
876
+ }));
877
+ return envToken;
878
+ }
879
+ const pfConfig = await readPlatformConfig();
880
+ const profileEntry = profile ? pfConfig.profiles[profile] : void 0;
881
+ if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
882
+ const user = profileEntry?.user ?? pfConfig.current_user;
883
+ if (!user) throw new Error(multiline`
884
+ Tailor Platform token not found.
885
+ Please specify token via TAILOR_PLATFORM_TOKEN environment variable or login using 'tailor login' command.
886
+ `);
887
+ return (await fetchLatestToken(pfConfig, user, profileEntry ? platformConfigFromProfile(profileEntry) : void 0)).accessToken;
888
+ }
889
+ /**
890
+ * Load platform connection settings from the active profile.
891
+ * @param opts - Profile options
892
+ * @returns Resolved platform connection settings, or undefined for the default environment
893
+ */
894
+ async function loadPlatformClientConfig(opts) {
895
+ const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
896
+ if (!profile) return;
897
+ let pfConfig;
898
+ try {
899
+ pfConfig = await readPlatformConfig();
900
+ } catch (error) {
901
+ if (opts?.allowMissingProfile) return;
902
+ throw error;
903
+ }
904
+ const profileEntry = pfConfig.profiles[profile];
905
+ if (!profileEntry) {
906
+ if (opts?.allowMissingProfile) return;
907
+ throw new Error(`Profile "${profile}" not found`);
908
+ }
909
+ return platformConfigFromProfile(profileEntry);
910
+ }
911
+ /**
912
+ * Load the Tailor Platform Console base URL from environment variables or the active profile.
913
+ * @param opts - Profile options
914
+ * @returns Resolved Console base URL
915
+ */
916
+ async function loadConsoleBaseUrl(opts) {
917
+ return getConsoleBaseUrl(await loadPlatformClientConfig(opts));
918
+ }
919
+ /**
920
+ * Resolve the actual token values for a user, reading from keyring or config as appropriate.
921
+ * @param userEntry - User entry from the config
922
+ * @param user - User identifier
923
+ * @param label - User-facing identifier used in error messages
924
+ * @returns Access token and optional refresh token
925
+ */
926
+ async function resolveTokens(userEntry, user, label = user) {
927
+ if (userEntry.storage === "keyring") {
928
+ const tokens = await loadKeyringTokens(user);
929
+ if (!tokens) throw new Error(multiline`
930
+ Credentials not found in OS keyring for "${label}".
931
+ Please run 'tailor login' and try again.
932
+ `);
933
+ return tokens;
934
+ }
935
+ return {
936
+ accessToken: userEntry.access_token,
937
+ refreshToken: userEntry.refresh_token
938
+ };
939
+ }
940
+ /**
941
+ * Save tokens for a user, writing to keyring by default when available.
942
+ * @param config - Platform config
943
+ * @param user - User identifier
944
+ * @param tokens - Token data to save
945
+ * @param tokens.accessToken - Access token to save
946
+ * @param tokens.refreshToken - Optional refresh token to save
947
+ * @param expiresAt - Token expiration date
948
+ * @param opts - Optional platform and user metadata
949
+ */
950
+ async function saveUserTokens(config, user, tokens, expiresAt, opts = {}) {
951
+ const userKey = platformUserKey(user, opts.platformConfig);
952
+ const email = opts.email ?? config.users[userKey]?.email;
953
+ if (await trySaveTokensInKeyring(userKey, tokens)) config.users[userKey] = {
954
+ token_expires_at: expiresAt,
955
+ storage: "keyring",
956
+ ...email ? { email } : {}
957
+ };
958
+ else {
959
+ if (config.users[userKey]?.storage === "keyring") await deleteKeyringTokens(userKey);
960
+ config.users[userKey] = {
961
+ access_token: tokens.accessToken,
962
+ refresh_token: tokens.refreshToken,
963
+ token_expires_at: expiresAt,
964
+ storage: "file",
965
+ ...email ? { email } : {}
966
+ };
967
+ }
968
+ }
969
+ /**
970
+ * Delete tokens for a user from keyring if applicable.
971
+ * @param config - Platform config
972
+ * @param user - User identifier
973
+ * @param platformConfig - Optional platform connection settings
974
+ * @param opts - Token lookup options
975
+ */
976
+ async function deleteUserTokens(config, user, platformConfig, opts) {
977
+ const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
978
+ if (userEntry?.storage === "keyring") await deleteKeyringTokens(userKey);
979
+ delete config.users[userKey];
980
+ }
981
+ /**
982
+ * Resolve stored tokens for a user on the selected platform.
983
+ * @param config - Platform config
984
+ * @param user - User name
985
+ * @param platformConfig - Optional platform connection settings
986
+ * @param opts - Token lookup options
987
+ * @returns Stored user entry and token values, or undefined when the user is not logged in
988
+ */
989
+ async function loadStoredUserTokens(config, user, platformConfig, opts) {
990
+ const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
991
+ if (!userEntry) return void 0;
992
+ return {
993
+ userEntry,
994
+ ...await resolveTokens(userEntry, userKey, user)
995
+ };
996
+ }
997
+ function updateUserReferences(config, fromUser, toUser) {
998
+ if (fromUser === toUser) return;
999
+ if (config.current_user === fromUser) config.current_user = toUser;
1000
+ for (const profile of Object.values(config.profiles)) if (profile?.user === fromUser) profile.user = toUser;
1001
+ }
1002
+ /**
1003
+ * Remove a legacy alias after a canonical user ID has been written.
1004
+ * @param config - Platform config
1005
+ * @param legacyUser - Previous user key
1006
+ * @param canonicalUser - Canonical user key
1007
+ * @param platformConfig - Platform settings for scoped token storage
1008
+ */
1009
+ async function removeLegacyUserAlias(config, legacyUser, canonicalUser, platformConfig) {
1010
+ if (legacyUser === canonicalUser) return;
1011
+ updateUserReferences(config, legacyUser, canonicalUser);
1012
+ const canonicalKey = platformUserKey(canonicalUser, platformConfig);
1013
+ const legacyKeys = /* @__PURE__ */ new Set([legacyUser, platformUserKey(legacyUser, platformConfig)]);
1014
+ for (const legacyKey of legacyKeys) {
1015
+ if (legacyKey === canonicalKey) continue;
1016
+ if (config.users[legacyKey]?.storage === "keyring") await deleteKeyringTokens(legacyKey);
1017
+ delete config.users[legacyKey];
1018
+ }
1019
+ }
1020
+ function shouldResolveSubjectOnRefresh(user, userEntry) {
1021
+ return Boolean(userEntry.email || inferEmailFromUserId(user));
1022
+ }
1023
+ /**
1024
+ * Fetch the latest access token, refreshing if necessary.
1025
+ * @param config - Platform config
1026
+ * @param user - User identifier
1027
+ * @param platformConfig - Optional platform connection settings
1028
+ * @returns Latest access token and the canonical user ID it is stored under
1029
+ * (the resolved subject when a legacy email key was migrated during refresh,
1030
+ * otherwise the matching config user)
1031
+ */
1032
+ async function fetchLatestToken(config, user, platformConfig) {
1033
+ const { userKey: storedUser, userEntry } = findUserEntry(config, user, platformConfig);
1034
+ if (!userEntry) throw new Error(multiline`
1035
+ User "${user}" not found.
1036
+ Please verify your user name and login using 'tailor login' command.
1037
+ `);
1038
+ const storedConfigUser = userFromPlatformUserKey(storedUser, platformConfig);
1039
+ const tokens = await resolveTokens(userEntry, storedUser, user);
1040
+ if (new Date(userEntry.token_expires_at) > /* @__PURE__ */ new Date()) {
1041
+ rememberPlatformConfigForToken(tokens.accessToken, platformConfig);
1042
+ return {
1043
+ accessToken: tokens.accessToken,
1044
+ user: storedConfigUser
1045
+ };
1046
+ }
1047
+ if (!tokens.refreshToken) throw new Error(multiline`
1048
+ Token expired.
1049
+ Please run 'tailor login' and try again.
1050
+ `);
1051
+ const client = initOAuth2Client(platformConfig);
1052
+ let resp;
1053
+ try {
1054
+ resp = await client.refreshToken({
1055
+ accessToken: tokens.accessToken,
1056
+ refreshToken: tokens.refreshToken,
1057
+ expiresAt: Date.parse(userEntry.token_expires_at)
1058
+ });
1059
+ } catch {
1060
+ throw new Error(multiline`
1061
+ Failed to refresh token. Your session may have expired.
1062
+ Please run 'tailor login' and try again.
1063
+ `);
1064
+ }
1065
+ const newExpiresAt = new Date(assertDefined(resp.expiresAt, "token refresh response missing expiresAt")).toISOString();
1066
+ let resolvedUser = storedConfigUser;
1067
+ const previousEmail = userEntry.email ?? inferEmailFromUserId(user) ?? inferEmailFromUserId(storedConfigUser);
1068
+ let email = previousEmail;
1069
+ if (shouldResolveSubjectOnRefresh(user, userEntry) || shouldResolveSubjectOnRefresh(storedConfigUser, userEntry)) try {
1070
+ const userInfo = await fetchUserInfo(resp.accessToken, platformConfig);
1071
+ resolvedUser = userInfo.sub;
1072
+ email = userInfo.email;
1073
+ } catch (error) {
1074
+ logger.debug(`Failed to resolve refreshed token user info: ${String(error)}`);
1075
+ }
1076
+ await saveUserTokens(config, resolvedUser, {
1077
+ accessToken: resp.accessToken,
1078
+ refreshToken: resp.refreshToken ?? void 0
1079
+ }, newExpiresAt, {
1080
+ platformConfig,
1081
+ email
1082
+ });
1083
+ await removeLegacyUserAlias(config, user, resolvedUser, platformConfig);
1084
+ if (storedUser !== platformUserKey(resolvedUser, platformConfig)) {
1085
+ if (config.users[storedUser]?.storage === "keyring") await deleteKeyringTokens(storedUser);
1086
+ delete config.users[storedUser];
1087
+ }
1088
+ if (previousEmail && email && previousEmail !== email) logger.info(`Updated local user email from "${previousEmail}" to "${email}".`);
1089
+ writePlatformConfig(config);
1090
+ rememberPlatformConfigForToken(resp.accessToken, platformConfig);
1091
+ return {
1092
+ accessToken: resp.accessToken,
1093
+ user: resolvedUser
1094
+ };
1095
+ }
1096
+ const DEFAULT_CONFIG_FILENAME = "tailor.config.ts";
1097
+ /**
1098
+ * Load config path from command options, environment variables, or search parent directories.
1099
+ * In CLI context, env fallback is also handled by politty's arg env option.
1100
+ * Priority: opts/config > env/config > search parent directories
1101
+ * @param configPath - Optional explicit config path
1102
+ * @returns Resolved config path or undefined
1103
+ */
1104
+ function loadConfigPath(configPath) {
1105
+ if (configPath) return configPath;
1106
+ if (process.env.TAILOR_CONFIG_PATH) return process.env.TAILOR_CONFIG_PATH;
1107
+ return findUpSync(DEFAULT_CONFIG_FILENAME);
1108
+ }
1109
+
1110
+ //#endregion
1111
+ //#region src/cli/shared/mock.ts
1112
+ /**
1113
+ * Install a stub `globalThis.tailordb` so that user code loaded by the CLI
1114
+ * (e.g. via `createGetDB` in `@tailor-platform/sdk/kysely`) can reference
1115
+ * `tailordb.Client` without hitting a `ReferenceError`. The CLI never
1116
+ * actually executes the user code paths that issue queries, so a no-op
1117
+ * client suffices.
1118
+ *
1119
+ * Exposed as a function (rather than a top-level statement) so that
1120
+ * `package.json#sideEffects` can keep the file marked side-effect-free
1121
+ * without bundlers eliminating the install step.
1122
+ */
1123
+ function installCliTailordbStub() {
1124
+ globalThis.tailordb = { Client: class {
1125
+ constructor(_config) {}
1126
+ async connect() {}
1127
+ async end() {}
1128
+ async queryObject() {
1129
+ return {};
677
1130
  }
678
- const result = await apiCall({
679
- profile: args.profile,
680
- endpoint: args.endpoint,
681
- body: mutated && parsedBody ? JSON.stringify(parsedBody) : args.body
1131
+ } };
1132
+ }
1133
+
1134
+ //#endregion
1135
+ //#region src/cli/shared/config-loader.ts
1136
+ /**
1137
+ * Load Tailor configuration file and associated plugins.
1138
+ * @param configPath - Optional explicit config path
1139
+ * @param options - Optional module import behavior.
1140
+ * @returns Loaded config, plugins, and config path
1141
+ */
1142
+ async function loadConfig(configPath, options = {}) {
1143
+ installCliTailordbStub();
1144
+ const foundPath = loadConfigPath(configPath);
1145
+ if (!foundPath) throw new Error("Configuration file not found: tailor.config.ts not found in current or parent directories");
1146
+ const resolvedPath = path.resolve(process.cwd(), foundPath);
1147
+ if (!fs$2.existsSync(resolvedPath)) throw new Error(`Configuration file not found: ${configPath}`);
1148
+ const configUrl = pathToFileURL(resolvedPath);
1149
+ if (options.importNonce) configUrl.searchParams.set("tailorImportNonce", options.importNonce);
1150
+ const configModule = await import(configUrl.href);
1151
+ if (!configModule || !configModule.default) throw new Error("Invalid Tailor config module: default export not found");
1152
+ const validated = AppConfigSchema.safeParse(configModule.default);
1153
+ if (!validated.success) {
1154
+ const issues = validated.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
1155
+ throw new Error(`Invalid Tailor config in ${resolvedPath}:\n${issues}`);
1156
+ }
1157
+ const allPlugins = [];
1158
+ for (const value of Object.values(configModule)) if (Array.isArray(value)) {
1159
+ const pluginParsed = value.reduce((acc, item) => {
1160
+ if (!acc.success) return acc;
1161
+ const result = PluginConfigSchema.safeParse(item);
1162
+ if (result.success) acc.items.push(result.data);
1163
+ else acc.success = false;
1164
+ return acc;
1165
+ }, {
1166
+ success: true,
1167
+ items: []
682
1168
  });
683
- logger.out(JSON.stringify(result.data, null, 2));
1169
+ if (pluginParsed.success && pluginParsed.items.length > 0) allPlugins.push(...pluginParsed.items);
684
1170
  }
685
- });
1171
+ return {
1172
+ config: {
1173
+ ...configModule.default,
1174
+ path: resolvedPath
1175
+ },
1176
+ plugins: allPlugins
1177
+ };
1178
+ }
1179
+
1180
+ //#endregion
1181
+ //#region src/cli/shared/readonly-guard.ts
1182
+ /**
1183
+ * Throw a CLIError if the active profile has `readonly: true`.
1184
+ *
1185
+ * Resolves the active profile in this order:
1186
+ * 1. `opts.profile` (CLI flag)
1187
+ * 2. `process.env.TAILOR_PLATFORM_PROFILE`
1188
+ *
1189
+ * If neither is set, no profile is in scope so the call is allowed. This is
1190
+ * intentional: `TAILOR_PLATFORM_TOKEN` direct access (CI / machine user) and
1191
+ * `--workspace-id` without a profile are out-of-band paths whose authorization
1192
+ * is governed by the bearer token itself, not by the local profile flag.
1193
+ *
1194
+ * If the resolved profile cannot be found in the config, this function returns
1195
+ * silently and lets downstream loaders surface the not-found error.
1196
+ * @param opts - Options
1197
+ * @param opts.profile - Optional explicit profile name from command args
1198
+ */
1199
+ async function assertWritable(opts) {
1200
+ const profileName = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
1201
+ if (!profileName) return;
1202
+ const profile = (await readPlatformConfig()).profiles[profileName];
1203
+ if (!profile || profile.readonly !== true) return;
1204
+ throw createCLIError({
1205
+ code: "PROFILE_READONLY",
1206
+ message: `Profile "${profileName}" is read-only.`,
1207
+ 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.",
1208
+ suggestion: `Use a different profile, unset TAILOR_PLATFORM_PROFILE, or run 'tailor profile update ${profileName} --permission write'.`
1209
+ });
1210
+ }
1211
+
1212
+ //#endregion
1213
+ //#region src/cli/commands/api/api-call.ts
1214
+ function hasEnvAccessToken() {
1215
+ const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
1216
+ return Boolean(envToken);
1217
+ }
1218
+ /**
1219
+ * Call Tailor Platform API endpoints directly.
1220
+ * If the endpoint doesn't contain "/", it defaults to `tailor.v1.OperatorService/{endpoint}`.
1221
+ * @param options - API call options (profile, endpoint, body)
1222
+ * @returns Response status and data
1223
+ */
1224
+ async function apiCall(options) {
1225
+ const accessToken = await loadAccessToken({ profile: options.profile });
1226
+ let platformConfig;
1227
+ try {
1228
+ platformConfig = await loadPlatformClientConfig({ profile: options.profile });
1229
+ } catch (error) {
1230
+ if (!hasEnvAccessToken()) throw error;
1231
+ }
1232
+ let endpointPath;
1233
+ if (options.endpoint.includes("/")) endpointPath = options.endpoint;
1234
+ else endpointPath = `tailor.v1.OperatorService/${options.endpoint}`;
1235
+ const url = new URL(endpointPath, getPlatformBaseUrl(platformConfig));
1236
+ const response = await fetch(url.toString(), {
1237
+ method: "POST",
1238
+ headers: {
1239
+ "Content-Type": "application/json",
1240
+ Authorization: `Bearer ${accessToken}`,
1241
+ "User-Agent": await userAgent()
1242
+ },
1243
+ body: options.body ?? "{}"
1244
+ });
1245
+ const data = await response.json();
1246
+ if (!response.ok) throw new Error(`API call failed (${response.status}): ${JSON.stringify(data)}`);
1247
+ return {
1248
+ status: response.status,
1249
+ data
1250
+ };
1251
+ }
686
1252
 
687
1253
  //#endregion
688
1254
  //#region src/cli/shared/prompt.ts
1255
+ function canPrompt() {
1256
+ return !isCI && process.stdin.isTTY === true && process.stdout.isTTY === true && !logger.jsonMode;
1257
+ }
689
1258
  /**
690
1259
  * Wraps a prompt function with CI guard and cancellation handling.
691
1260
  * @param fn - A prompt function from `@inquirer/prompts`
1261
+ * @param unavailableMessage - Message used when interactive input is unavailable
692
1262
  * @returns A wrapped function that throws in CI and exits on cancel
693
1263
  */
694
- function withGuard(fn) {
1264
+ function withGuard(fn, unavailableMessage) {
695
1265
  return async (...args) => {
696
- if (isCI) throw new CIPromptError();
1266
+ if (!canPrompt()) throw new CIPromptError(unavailableMessage);
697
1267
  try {
698
1268
  return await fn(...args);
699
1269
  } catch (error) {
@@ -703,9 +1273,10 @@ function withGuard(fn) {
703
1273
  };
704
1274
  }
705
1275
  const prompt = {
706
- confirm: withGuard(confirm),
1276
+ confirm: withGuard(confirm, "Interactive confirmations are not available in this environment. Use --yes to skip confirmation prompts, or provide the required options explicitly."),
707
1277
  text: withGuard(input),
708
- password: withGuard(password)
1278
+ password: withGuard(password),
1279
+ select: withGuard(select)
709
1280
  };
710
1281
 
711
1282
  //#endregion
@@ -750,7 +1321,7 @@ function createCacheStore(config) {
750
1321
  }
751
1322
  function loadManifest() {
752
1323
  try {
753
- const raw = fs$1.readFileSync(manifestPath(), "utf-8");
1324
+ const raw = fs$2.readFileSync(manifestPath(), "utf-8");
754
1325
  const result = cacheManifestSchema.safeParse(JSON.parse(raw));
755
1326
  if (!result.success) {
756
1327
  cachedManifest = void 0;
@@ -777,15 +1348,15 @@ function createCacheStore(config) {
777
1348
  return cachedManifest;
778
1349
  }
779
1350
  function saveManifest(manifest) {
780
- fs$1.mkdirSync(config.cacheDir, { recursive: true });
1351
+ fs$2.mkdirSync(config.cacheDir, { recursive: true });
781
1352
  const target = manifestPath();
782
1353
  const tmpFile = path.join(config.cacheDir, `.manifest.${process.pid}.tmp`);
783
1354
  try {
784
- fs$1.writeFileSync(tmpFile, JSON.stringify(manifest, null, 2), "utf-8");
785
- fs$1.renameSync(tmpFile, target);
1355
+ fs$2.writeFileSync(tmpFile, JSON.stringify(manifest, null, 2), "utf-8");
1356
+ fs$2.renameSync(tmpFile, target);
786
1357
  } catch (e) {
787
1358
  try {
788
- fs$1.rmSync(tmpFile, { force: true });
1359
+ fs$2.rmSync(tmpFile, { force: true });
789
1360
  } catch {}
790
1361
  throw e;
791
1362
  }
@@ -804,19 +1375,19 @@ function createCacheStore(config) {
804
1375
  }
805
1376
  function storeBundleContent(cacheKey, content) {
806
1377
  const dir = bundlesDir();
807
- fs$1.mkdirSync(dir, { recursive: true });
808
- fs$1.writeFileSync(bundlePath(cacheKey), content, "utf-8");
1378
+ fs$2.mkdirSync(dir, { recursive: true });
1379
+ fs$2.writeFileSync(bundlePath(cacheKey), content, "utf-8");
809
1380
  }
810
1381
  function restoreBundleContent(cacheKey) {
811
1382
  try {
812
- return fs$1.readFileSync(bundlePath(cacheKey), "utf-8");
1383
+ return fs$2.readFileSync(bundlePath(cacheKey), "utf-8");
813
1384
  } catch (e) {
814
1385
  if (e.code === "ENOENT") return void 0;
815
1386
  throw e;
816
1387
  }
817
1388
  }
818
1389
  function clean() {
819
- fs$1.rmSync(config.cacheDir, {
1390
+ fs$2.rmSync(config.cacheDir, {
820
1391
  recursive: true,
821
1392
  force: true
822
1393
  });
@@ -910,7 +1481,7 @@ function extractAttributesFromConfig(config) {
910
1481
  * @returns Generated type definition source
911
1482
  */
912
1483
  function generateTypeDefinition(attributes, attributeList, env, machineUserNames, idpNames, connectionNames, aiGatewayNames) {
913
- const attributeFields = attributes ? Object.entries(attributes).map(([key, value]) => ` ${key}: ${value};`).join("\n") : "";
1484
+ const attributeFields = attributes ? Object.entries(attributes).map(([key, { type, optional }]) => ` ${key}${optional ? "?" : ""}: ${type};`).join("\n") : "";
914
1485
  const attributesBody = !attributes || Object.keys(attributes).length === 0 ? "{}" : `{
915
1486
  ${attributeFields}
916
1487
  }`;
@@ -973,12 +1544,15 @@ function collectAttributesFromConfig(config) {
973
1544
  const inferAttributeType = (field) => {
974
1545
  const type = field?.type;
975
1546
  const metadata = field?.metadata;
976
- if (!metadata) return "string";
1547
+ if (!metadata) return { type: "string" };
977
1548
  let typeStr = "string";
978
1549
  if (type === "boolean") typeStr = "boolean";
979
1550
  else if (type === "enum" && metadata.allowedValues) typeStr = metadata.allowedValues.map((v) => `"${v.value}"`).join(" | ");
980
1551
  if (metadata.array) typeStr = typeStr.includes(" | ") ? `(${typeStr})[]` : `${typeStr}[]`;
981
- return typeStr;
1552
+ return {
1553
+ type: typeStr,
1554
+ optional: metadata.required === false
1555
+ };
982
1556
  };
983
1557
  if ("userProfile" in auth) {
984
1558
  const userProfile = auth.userProfile;
@@ -1057,8 +1631,8 @@ async function generateUserTypes(options) {
1057
1631
  if (env) logger.debug(`Extracted Env: ${JSON.stringify(env)}`);
1058
1632
  const typeDefContent = generateTypeDefinition(attributes, attributeList, env, machineUserNames, idpNames, connectionNames, aiGatewayNames);
1059
1633
  const outputPath = resolveTypeDefinitionPath(configPath);
1060
- fs$1.mkdirSync(path.dirname(outputPath), { recursive: true });
1061
- fs$1.writeFileSync(outputPath, typeDefContent);
1634
+ fs$2.mkdirSync(path.dirname(outputPath), { recursive: true });
1635
+ fs$2.writeFileSync(outputPath, typeDefContent);
1062
1636
  const relativePath = path.relative(process.cwd(), outputPath);
1063
1637
  logger.newline();
1064
1638
  logger.success(`Generated type definitions: ${relativePath}`, { mode: "plain" });
@@ -2559,7 +3133,7 @@ async function applyAuthConnections(client, result, phase) {
2559
3133
 
2560
3134
  //#endregion
2561
3135
  //#region src/cli/commands/deploy/function-registry.ts
2562
- const CHUNK_SIZE = 64 * 1024;
3136
+ const CHUNK_SIZE$1 = 64 * 1024;
2563
3137
  /**
2564
3138
  * Compute SHA-256 content hash for a script string.
2565
3139
  * @param content - Script content to hash
@@ -2836,9 +3410,9 @@ async function uploadFunctionScript(client, workspaceId, entry, isCreate) {
2836
3410
  case: "info",
2837
3411
  value: info
2838
3412
  } };
2839
- for (let i = 0; i < buffer.length; i += CHUNK_SIZE) yield { payload: {
3413
+ for (let i = 0; i < buffer.length; i += CHUNK_SIZE$1) yield { payload: {
2840
3414
  case: "chunk",
2841
- value: buffer.subarray(i, Math.min(i + CHUNK_SIZE, buffer.length))
3415
+ value: buffer.subarray(i, Math.min(i + CHUNK_SIZE$1, buffer.length))
2842
3416
  } };
2843
3417
  }
2844
3418
  await client.createFunctionRegistry(createStream());
@@ -2849,9 +3423,9 @@ async function uploadFunctionScript(client, workspaceId, entry, isCreate) {
2849
3423
  case: "info",
2850
3424
  value: info
2851
3425
  } };
2852
- for (let i = 0; i < buffer.length; i += CHUNK_SIZE) yield { payload: {
3426
+ for (let i = 0; i < buffer.length; i += CHUNK_SIZE$1) yield { payload: {
2853
3427
  case: "chunk",
2854
- value: buffer.subarray(i, Math.min(i + CHUNK_SIZE, buffer.length))
3428
+ value: buffer.subarray(i, Math.min(i + CHUNK_SIZE$1, buffer.length))
2855
3429
  } };
2856
3430
  }
2857
3431
  await client.updateFunctionRegistry(updateStream());
@@ -5239,7 +5813,7 @@ function protoExecutor(context, executor) {
5239
5813
  case: "workflow",
5240
5814
  value: {
5241
5815
  workflowName: target.workflowName,
5242
- variables: target.args ? typeof target.args === "function" ? { expr: `(${stringifyFunction(target.args)})(${argsExpr})` } : { expr: JSON.stringify(target.args) } : void 0,
5816
+ variables: target.args !== void 0 ? typeof target.args === "function" ? { expr: `(${stringifyFunction(target.args)})(${argsExpr})` } : { expr: JSON.stringify(target.args) } : void 0,
5243
5817
  invoker: normalizeInvoker(target.invoker, authNamespace, invokerContext)
5244
5818
  }
5245
5819
  } };
@@ -6928,7 +7502,7 @@ function createSnapshotFromLocalTypes(types, namespace) {
6928
7502
  * @returns {NormalizedSchemaSnapshot} Loaded normalized schema snapshot
6929
7503
  */
6930
7504
  function loadSnapshot(filePath) {
6931
- const content = fs$1.readFileSync(filePath, "utf-8");
7505
+ const content = fs$2.readFileSync(filePath, "utf-8");
6932
7506
  let raw;
6933
7507
  try {
6934
7508
  raw = JSON.parse(content);
@@ -6946,7 +7520,7 @@ function loadSnapshot(filePath) {
6946
7520
  * @returns {MigrationDiff} Loaded migration diff
6947
7521
  */
6948
7522
  function loadDiff(filePath) {
6949
- const content = fs$1.readFileSync(filePath, "utf-8");
7523
+ const content = fs$2.readFileSync(filePath, "utf-8");
6950
7524
  let raw;
6951
7525
  try {
6952
7526
  raw = JSON.parse(content);
@@ -6969,8 +7543,8 @@ function loadDiff(filePath) {
6969
7543
  * @returns {Array<{number: number, type: "schema" | "diff", path: string}>} Migration files sorted by number
6970
7544
  */
6971
7545
  function getMigrationFiles(migrationsDir) {
6972
- if (!fs$1.existsSync(migrationsDir)) return [];
6973
- const entries = fs$1.readdirSync(migrationsDir, { withFileTypes: true });
7546
+ if (!fs$2.existsSync(migrationsDir)) return [];
7547
+ const entries = fs$2.readdirSync(migrationsDir, { withFileTypes: true });
6974
7548
  const migrations = [];
6975
7549
  for (const entry of entries) {
6976
7550
  if (!entry.isDirectory()) continue;
@@ -6978,13 +7552,13 @@ function getMigrationFiles(migrationsDir) {
6978
7552
  const num = parseInt(entry.name, 10);
6979
7553
  const migrationDir = path.join(migrationsDir, entry.name);
6980
7554
  const schemaPath = path.join(migrationDir, SCHEMA_FILE_NAME);
6981
- if (fs$1.existsSync(schemaPath)) migrations.push({
7555
+ if (fs$2.existsSync(schemaPath)) migrations.push({
6982
7556
  number: num,
6983
7557
  type: "schema",
6984
7558
  path: schemaPath
6985
7559
  });
6986
7560
  const diffPath = path.join(migrationDir, DIFF_FILE_NAME);
6987
- if (fs$1.existsSync(diffPath)) migrations.push({
7561
+ if (fs$2.existsSync(diffPath)) migrations.push({
6988
7562
  number: num,
6989
7563
  type: "diff",
6990
7564
  path: diffPath
@@ -7750,7 +8324,7 @@ function compareLocalTypesWithSnapshot(snapshot, localTypes, namespace) {
7750
8324
  */
7751
8325
  function validateMigrationFiles(migrationsDir) {
7752
8326
  const errors = [];
7753
- if (!fs$1.existsSync(migrationsDir)) return errors;
8327
+ if (!fs$2.existsSync(migrationsDir)) return errors;
7754
8328
  const migrationFiles = getMigrationFiles(migrationsDir);
7755
8329
  if (migrationFiles.length === 0) return errors;
7756
8330
  const schemaFiles = [];
@@ -8987,7 +9561,7 @@ function handleOptionalToRequiredError(error, messages) {
8987
9561
  */
8988
9562
  async function bundleMigrationScript(sourceFile, namespace, migrationNumber, env = {}) {
8989
9563
  const outputDir = path.resolve(getDistDir(), "migrations");
8990
- fs$1.mkdirSync(outputDir, { recursive: true });
9564
+ fs$2.mkdirSync(outputDir, { recursive: true });
8991
9565
  const entryPath = path.join(outputDir, `migration_${namespace}_${migrationNumber}.entry.js`);
8992
9566
  const entryContent = multiline`
8993
9567
  import { main as _migrationMain } from "${path.resolve(sourceFile).replace(/\\/g, "/")}";
@@ -9009,7 +9583,7 @@ async function bundleMigrationScript(sourceFile, namespace, migrationNumber, env
9009
9583
  return { success: true };
9010
9584
  }
9011
9585
  `;
9012
- fs$1.writeFileSync(entryPath, entryContent);
9586
+ fs$2.writeFileSync(entryPath, entryContent);
9013
9587
  let tsconfig;
9014
9588
  try {
9015
9589
  tsconfig = await resolveTSConfig();
@@ -9320,10 +9894,10 @@ async function detectPendingMigrations(client, workspaceId, namespacesWithMigrat
9320
9894
  for (const file of migrationFiles) {
9321
9895
  if (file.number <= currentMigration) continue;
9322
9896
  const diffPath = getMigrationFilePath(migrationsDir, file.number, "diff");
9323
- if (!fs$1.existsSync(diffPath)) continue;
9897
+ if (!fs$2.existsSync(diffPath)) continue;
9324
9898
  const diff = loadDiff(diffPath);
9325
9899
  const scriptPath = getMigrationFilePath(migrationsDir, file.number, "migrate");
9326
- const hasScript = fs$1.existsSync(scriptPath);
9900
+ const hasScript = fs$2.existsSync(scriptPath);
9327
9901
  if (diff.requiresMigrationScript && !hasScript) {
9328
9902
  logger.warn(`Migration ${namespace}/${file.number} requires a script but migrate.ts not found`);
9329
9903
  continue;
@@ -11205,7 +11779,7 @@ function findIdProperty(obj) {
11205
11779
  * @returns Resolved id and whether it was newly injected, or null if skipped
11206
11780
  */
11207
11781
  async function ensureConfigId(configPath) {
11208
- const source = await fs$1.promises.readFile(configPath, "utf-8");
11782
+ const source = await fs$2.promises.readFile(configPath, "utf-8");
11209
11783
  const { program } = parseSync(configPath, source);
11210
11784
  const calls = [];
11211
11785
  findDefineConfigCalls(program, calls);
@@ -11227,7 +11801,7 @@ async function ensureConfigId(configPath) {
11227
11801
  }
11228
11802
  const id = crypto.randomUUID();
11229
11803
  const newSource = insertIdProperty(source, configObj, id);
11230
- await fs$1.promises.writeFile(configPath, newSource, "utf-8");
11804
+ await fs$2.promises.writeFile(configPath, newSource, "utf-8");
11231
11805
  logger.info(`Generated app id and wrote to ${configPath}: ${id}`);
11232
11806
  return {
11233
11807
  id,
@@ -11243,7 +11817,7 @@ async function ensureConfigId(configPath) {
11243
11817
  * @returns The existing id (or null when absent), or null for wrapper configs
11244
11818
  */
11245
11819
  async function readConfigId(configPath) {
11246
- const { program } = parseSync(configPath, await fs$1.promises.readFile(configPath, "utf-8"));
11820
+ const { program } = parseSync(configPath, await fs$2.promises.readFile(configPath, "utf-8"));
11247
11821
  const calls = [];
11248
11822
  findDefineConfigCalls(program, calls);
11249
11823
  if (calls.length === 0) return null;
@@ -11497,188 +12071,1014 @@ async function validatePlan(input) {
11497
12071
  violations
11498
12072
  });
11499
12073
  }
11500
- function replaces(schema, kind, items) {
11501
- validateItems({
11502
- validator,
11503
- schema,
11504
- kind,
11505
- action: "replace",
11506
- items,
11507
- requestKey: "createRequest",
11508
- violations
12074
+ function replaces(schema, kind, items) {
12075
+ validateItems({
12076
+ validator,
12077
+ schema,
12078
+ kind,
12079
+ action: "replace",
12080
+ items,
12081
+ requestKey: "createRequest",
12082
+ violations
12083
+ });
12084
+ }
12085
+ function inPlaceReplaces(schema, kind, items) {
12086
+ validateItems({
12087
+ validator,
12088
+ schema,
12089
+ kind,
12090
+ action: "replace",
12091
+ items,
12092
+ requestKey: "updateRequest",
12093
+ violations
12094
+ });
12095
+ }
12096
+ creates(CreateTailorDBServiceRequestSchema, "TailorDB service", tailorDB.changeSet.service.creates);
12097
+ creates(CreateTailorDBTypeRequestSchema, "TailorDB type", tailorDB.changeSet.type.creates);
12098
+ updates(UpdateTailorDBTypeRequestSchema, "TailorDB type", tailorDB.changeSet.type.updates);
12099
+ creates(CreateStaticWebsiteRequestSchema, "StaticWebsite", staticWebsite.changeSet.creates);
12100
+ updates(UpdateStaticWebsiteRequestSchema, "StaticWebsite", staticWebsite.changeSet.updates);
12101
+ creates(AddCustomDomainRequestSchema, "StaticWebsite custom domain", staticWebsite.customDomainChangeSet.creates);
12102
+ creates(CreateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.creates.map((item) => ({
12103
+ name: item.name,
12104
+ request: {
12105
+ ...item.request,
12106
+ cors: void 0
12107
+ }
12108
+ })));
12109
+ updates(UpdateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.updates.map((item) => ({
12110
+ name: item.name,
12111
+ request: {
12112
+ ...item.request,
12113
+ cors: void 0
12114
+ }
12115
+ })));
12116
+ const placeholderOriginReplacement = "https://placeholder.invalid";
12117
+ const substituteIdpReturnOrigins = (item) => {
12118
+ const request = item.request;
12119
+ const origins = request.userAuthPolicy?.allowedReturnOrigins;
12120
+ if (!Array.isArray(origins) || origins.length === 0) return item;
12121
+ const substituted = origins.map((origin) => typeof origin === "string" && /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]:url$/.test(origin) ? placeholderOriginReplacement : origin);
12122
+ return {
12123
+ ...item,
12124
+ request: {
12125
+ ...request,
12126
+ userAuthPolicy: {
12127
+ ...request.userAuthPolicy,
12128
+ allowedReturnOrigins: substituted
12129
+ }
12130
+ }
12131
+ };
12132
+ };
12133
+ creates(CreateIdPServiceRequestSchema, "IdP service", idp.changeSet.service.creates.map(substituteIdpReturnOrigins));
12134
+ updates(UpdateIdPServiceRequestSchema, "IdP service", idp.changeSet.service.updates.map(substituteIdpReturnOrigins));
12135
+ const idpClientVaultItems = [...idp.changeSet.client.creates.map((c) => ({
12136
+ clientName: c.request.client?.name ?? "",
12137
+ namespaceName: c.request.namespaceName ?? "",
12138
+ workspaceId: c.request.workspaceId ?? ""
12139
+ })), ...idp.changeSet.client.updates.map((u) => ({
12140
+ clientName: u.name,
12141
+ namespaceName: u.namespaceName,
12142
+ workspaceId: u.workspaceId
12143
+ }))];
12144
+ creates(CreateSecretManagerVaultRequestSchema, "IdP client secret", idpClientVaultItems.map((item) => ({
12145
+ name: item.clientName,
12146
+ request: {
12147
+ workspaceId: item.workspaceId,
12148
+ secretmanagerVaultName: idpClientVaultName(item.namespaceName, item.clientName)
12149
+ }
12150
+ })));
12151
+ creates(CreateSecretManagerSecretRequestSchema, "IdP client secret", idpClientVaultItems.map((item) => ({
12152
+ name: item.clientName,
12153
+ request: {
12154
+ workspaceId: item.workspaceId,
12155
+ secretmanagerVaultName: idpClientVaultName(item.namespaceName, item.clientName),
12156
+ secretmanagerSecretName: idpClientSecretName(item.namespaceName, item.clientName)
12157
+ }
12158
+ })));
12159
+ creates(CreateAuthServiceRequestSchema, "Auth service", auth.changeSet.service.creates);
12160
+ updates(UpdateAuthServiceRequestSchema, "Auth service", auth.changeSet.service.updates);
12161
+ creates(CreateAuthIDPConfigRequestSchema, "Auth IDP config", auth.changeSet.idpConfig.creates);
12162
+ updates(UpdateAuthIDPConfigRequestSchema, "Auth IDP config", auth.changeSet.idpConfig.updates);
12163
+ creates(CreateUserProfileConfigRequestSchema, "Auth user profile config", auth.changeSet.userProfileConfig.creates);
12164
+ updates(UpdateUserProfileConfigRequestSchema, "Auth user profile config", auth.changeSet.userProfileConfig.updates);
12165
+ creates(CreateTenantConfigRequestSchema, "Auth tenant config", auth.changeSet.tenantConfig.creates);
12166
+ updates(UpdateTenantConfigRequestSchema, "Auth tenant config", auth.changeSet.tenantConfig.updates);
12167
+ creates(CreateAuthMachineUserRequestSchema, "Auth machine user", auth.changeSet.machineUser.creates);
12168
+ updates(UpdateAuthMachineUserRequestSchema, "Auth machine user", auth.changeSet.machineUser.updates);
12169
+ creates(CreateAuthHookRequestSchema, "Auth hook", auth.changeSet.authHook.creates);
12170
+ updates(UpdateAuthHookRequestSchema, "Auth hook", auth.changeSet.authHook.updates);
12171
+ creates(CreateAuthSCIMConfigRequestSchema, "Auth SCIM config", auth.changeSet.scim.creates);
12172
+ updates(UpdateAuthSCIMConfigRequestSchema, "Auth SCIM config", auth.changeSet.scim.updates);
12173
+ creates(CreateAuthSCIMResourceRequestSchema, "Auth SCIM resource", auth.changeSet.scimResource.creates);
12174
+ updates(UpdateAuthSCIMResourceRequestSchema, "Auth SCIM resource", auth.changeSet.scimResource.updates);
12175
+ creates(CreateAuthOAuth2ClientRequestSchema, "OAuth2 client", auth.changeSet.oauth2Client.creates);
12176
+ updates(UpdateAuthOAuth2ClientRequestSchema, "OAuth2 client", auth.changeSet.oauth2Client.updates);
12177
+ replaces(CreateAuthOAuth2ClientRequestSchema, "OAuth2 client", auth.changeSet.oauth2Client.replaces);
12178
+ creates(CreatePipelineServiceRequestSchema, "Pipeline service", pipeline.changeSet.service.creates);
12179
+ updates(UpdatePipelineServiceRequestSchema, "Pipeline service", pipeline.changeSet.service.updates);
12180
+ creates(CreatePipelineResolverRequestSchema, "Resolver", pipeline.changeSet.resolver.creates);
12181
+ updates(UpdatePipelineResolverRequestSchema, "Resolver", pipeline.changeSet.resolver.updates);
12182
+ creates(CreateExecutorExecutorRequestSchema, "Executor", executor.changeSet.creates);
12183
+ updates(UpdateExecutorExecutorRequestSchema, "Executor", executor.changeSet.updates);
12184
+ creates(CreateWorkflowRequestSchema, "Workflow", workflow.changeSet.creates.map((item) => ({
12185
+ name: item.name,
12186
+ request: buildWorkflowValidationShape(item.workspaceId, item.workflow)
12187
+ })));
12188
+ updates(UpdateWorkflowRequestSchema, "Workflow", workflow.changeSet.updates.map((item) => ({
12189
+ name: item.name,
12190
+ request: buildWorkflowValidationShape(item.workspaceId, item.workflow)
12191
+ })));
12192
+ const workflowJobNameWorkspaceId = workflow.changeSet.creates[0]?.workspaceId ?? workflow.changeSet.updates[0]?.workspaceId ?? "";
12193
+ if (workflowJobNameWorkspaceId) {
12194
+ const allJobNames = /* @__PURE__ */ new Set();
12195
+ for (const item of [...workflow.changeSet.creates, ...workflow.changeSet.updates]) for (const jobName of item.usedJobNames) allJobNames.add(jobName);
12196
+ for (const jobName of workflow.unchangedWorkflowJobNames) allJobNames.add(jobName);
12197
+ creates(CreateWorkflowJobFunctionRequestSchema, "Workflow job function", [...allJobNames].map((jobName) => ({
12198
+ name: jobName,
12199
+ request: {
12200
+ workspaceId: workflowJobNameWorkspaceId,
12201
+ jobFunctionName: jobName,
12202
+ scriptRef: jobName
12203
+ }
12204
+ })));
12205
+ }
12206
+ creates(CreateWorkflowJobFunctionExecutionPolicyRequestSchema, "Workflow execution policy", [...workflowExecutionPolicy.changeSet.creates, ...workflowExecutionPolicy.changeSet.replaces].map((item) => ({
12207
+ name: item.name,
12208
+ request: {
12209
+ workspaceId: item.workspaceId,
12210
+ executionPolicyName: item.policy.name,
12211
+ executionPolicyKey: toPlatformExecutionPolicyKey(item.policy),
12212
+ ...item.policy.concurrencyPolicy && { concurrencyPolicy: { maxConcurrentExecutions: item.policy.concurrencyPolicy.maxConcurrentExecutions } }
12213
+ }
12214
+ })));
12215
+ updates(UpdateWorkflowJobFunctionExecutionPolicyRequestSchema, "Workflow execution policy", workflowExecutionPolicy.changeSet.updates.map((item) => ({
12216
+ name: item.name,
12217
+ request: {
12218
+ workspaceId: item.workspaceId,
12219
+ executionPolicyName: item.policy.name,
12220
+ ...item.policy.concurrencyPolicy && { concurrencyPolicy: { maxConcurrentExecutions: item.policy.concurrencyPolicy.maxConcurrentExecutions } }
12221
+ }
12222
+ })));
12223
+ creates(CreateSecretManagerVaultRequestSchema, "Secret Manager vault", secretManager.vaultChangeSet.creates.map((item) => ({
12224
+ name: item.name,
12225
+ request: vaultCreateRequest(item)
12226
+ })));
12227
+ creates(CreateSecretManagerSecretRequestSchema, "Secret Manager secret", secretManager.secretChangeSet.creates.map((item) => ({
12228
+ name: item.name,
12229
+ request: secretCreateRequest(item)
12230
+ })));
12231
+ updates(UpdateSecretManagerSecretRequestSchema, "Secret Manager secret", secretManager.secretChangeSet.updates.map((item) => ({
12232
+ name: item.name,
12233
+ request: secretUpdateRequest(item)
12234
+ })));
12235
+ creates(CreateApplicationRequestSchema, "Application", app.creates.map((item) => ({
12236
+ name: item.name,
12237
+ request: {
12238
+ ...item.request,
12239
+ cors: void 0
12240
+ }
12241
+ })));
12242
+ updates(UpdateApplicationRequestSchema, "Application", [...app.updates, ...app.unchanged].map((item) => ({
12243
+ name: item.name,
12244
+ request: {
12245
+ ...item.request,
12246
+ cors: void 0
12247
+ }
12248
+ })));
12249
+ creates(CreateAuthConnectionRequestSchema, "Auth connection", auth.changeSet.connection.creates);
12250
+ inPlaceReplaces(UpdateAuthConnectionRequestSchema, "Auth connection", auth.changeSet.connection.replaces);
12251
+ if (violations.length === 0) return;
12252
+ const resourceNames = new Set(violations.map((v) => `${v.kind}:${v.name}`));
12253
+ logger.error(`Pre-flight validation found ${violations.length} violation(s) across ${resourceNames.size} resource(s):`);
12254
+ for (const v of violations) logger.log(` ${styles.resourceType(v.kind)} ${styles.resourceName(v.name)} (${v.action}) — ${styles.bold(v.fieldPath)}: ${v.message}`);
12255
+ throw new Error(`${violations.length} validation error(s) found in ${resourceNames.size} resource(s)`);
12256
+ }
12257
+
12258
+ //#endregion
12259
+ //#region src/cli/shared/profile-name.ts
12260
+ const profileNameSchema = z.string().min(1, "Profile must not be empty");
12261
+
12262
+ //#endregion
12263
+ //#region src/cli/shared/workspace-name.ts
12264
+ 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");
12265
+ /**
12266
+ * Validate a workspace name for use in an interactive prompt.
12267
+ * @param name - Candidate workspace name
12268
+ * @returns True when valid, otherwise a validation message
12269
+ */
12270
+ function validateWorkspaceName(name) {
12271
+ const result = workspaceNameSchema.safeParse(name);
12272
+ return result.success ? true : assertDefined(result.error.issues[0], "Zod returned no issues").message;
12273
+ }
12274
+
12275
+ //#endregion
12276
+ //#region src/cli/shared/format.ts
12277
+ /**
12278
+ * Format a protobuf Timestamp to ISO string.
12279
+ * @param timestamp - Protobuf timestamp
12280
+ * @returns Date object or null if invalid
12281
+ */
12282
+ function formatTimestamp(timestamp) {
12283
+ if (!timestamp) return null;
12284
+ const date = timestampDate(timestamp);
12285
+ if (Number.isNaN(date.getTime())) return null;
12286
+ return date;
12287
+ }
12288
+ /**
12289
+ * Formats a table with consistent single-line border style.
12290
+ * Use this instead of importing `table` directly.
12291
+ * @param data - Table data
12292
+ * @param config - Table configuration
12293
+ * @returns Formatted table string
12294
+ */
12295
+ function formatTable(data, config) {
12296
+ return table(data, {
12297
+ ...config,
12298
+ border: getBorderCharacters("norc")
12299
+ });
12300
+ }
12301
+ /**
12302
+ * Formats a key-value table with single-line border style.
12303
+ * @param data - Key-value pairs
12304
+ * @returns Formatted key-value table string
12305
+ */
12306
+ function formatKeyValueTable(data) {
12307
+ return formatTable(data, { singleLine: true });
12308
+ }
12309
+ /**
12310
+ * Format a Date or ISO timestamp string as a human-readable relative time.
12311
+ * @param value - Date object, ISO date string, or null
12312
+ * @returns Relative time (e.g., "5 minutes ago") or "N/A" for null/invalid
12313
+ */
12314
+ function humanizeRelativeTime(value) {
12315
+ if (value === null) return "N/A";
12316
+ const date = value instanceof Date ? value : new Date(value);
12317
+ if (Number.isNaN(date.getTime())) return typeof value === "string" ? value : "N/A";
12318
+ return formatDistanceToNowStrict(date, { addSuffix: true });
12319
+ }
12320
+
12321
+ //#endregion
12322
+ //#region src/cli/commands/workspace/transform.ts
12323
+ const workspaceInfo = (workspace, folderName) => {
12324
+ const info = {
12325
+ id: workspace.id,
12326
+ name: workspace.name,
12327
+ region: workspace.region,
12328
+ ...workspace.organizationId ? { organizationId: workspace.organizationId } : {},
12329
+ ...workspace.folderId ? { folderId: workspace.folderId } : {},
12330
+ createdAt: formatTimestamp(workspace.createTime),
12331
+ updatedAt: formatTimestamp(workspace.updateTime)
12332
+ };
12333
+ return folderName ? {
12334
+ ...info,
12335
+ folderName
12336
+ } : info;
12337
+ };
12338
+ const workspaceDetails = (workspace, folderName) => {
12339
+ return {
12340
+ ...workspaceInfo(workspace, folderName),
12341
+ deleteProtection: workspace.deleteProtection,
12342
+ organizationId: workspace.organizationId,
12343
+ folderId: workspace.folderId
12344
+ };
12345
+ };
12346
+ async function resolveWorkspaceFolderName(client, workspace) {
12347
+ if (!workspace.organizationId || !workspace.folderId) return void 0;
12348
+ try {
12349
+ return (await client.getOrganizationFolder({
12350
+ organizationId: workspace.organizationId,
12351
+ folderId: workspace.folderId
12352
+ })).folder?.name || void 0;
12353
+ } catch (error) {
12354
+ if (error instanceof ConnectError && (error.code === Code.NotFound || error.code === Code.PermissionDenied)) return;
12355
+ logger.warn(`Failed to resolve workspace folder name: ${error}`);
12356
+ return;
12357
+ }
12358
+ }
12359
+ function createWorkspaceFolderNameResolver(client) {
12360
+ const cache = /* @__PURE__ */ new Map();
12361
+ return (workspace) => {
12362
+ if (!workspace.organizationId || !workspace.folderId) return Promise.resolve(void 0);
12363
+ const cacheKey = `${workspace.organizationId}/${workspace.folderId}`;
12364
+ const cached = cache.get(cacheKey);
12365
+ if (cached) return cached;
12366
+ const promise = resolveWorkspaceFolderName(client, workspace);
12367
+ cache.set(cacheKey, promise);
12368
+ return promise;
12369
+ };
12370
+ }
12371
+ async function workspaceInfoWithFolderName(client, workspace) {
12372
+ const folderName = await resolveWorkspaceFolderName(client, workspace);
12373
+ return workspaceInfo(workspace, folderName);
12374
+ }
12375
+ async function workspaceDetailsWithFolderName(client, workspace) {
12376
+ const folderName = await resolveWorkspaceFolderName(client, workspace);
12377
+ return workspaceDetails(workspace, folderName);
12378
+ }
12379
+ async function workspaceInfosWithFolderNames(client, workspaces) {
12380
+ const resolveFolderName = createWorkspaceFolderNameResolver(client);
12381
+ const limit = pLimit(5);
12382
+ return Promise.all(workspaces.map((workspace) => limit(async () => workspaceInfo(workspace, await resolveFolderName(workspace)))));
12383
+ }
12384
+ function workspaceDisplayName(workspace) {
12385
+ return workspace.folderName ? `${workspace.folderName}/${workspace.name}` : workspace.name;
12386
+ }
12387
+ function createWorkspaceNameTransformer(nameKey, folderNameKey) {
12388
+ return (value, item) => {
12389
+ const workspace = item;
12390
+ const name = workspace[nameKey];
12391
+ const folderName = workspace[folderNameKey];
12392
+ if (typeof name === "string" && typeof folderName === "string") return workspaceDisplayName({
12393
+ name,
12394
+ folderName
12395
+ });
12396
+ return String(value ?? "");
12397
+ };
12398
+ }
12399
+ const workspaceNameTransformer = createWorkspaceNameTransformer("name", "folderName");
12400
+
12401
+ //#endregion
12402
+ //#region src/cli/commands/workspace/create.ts
12403
+ /**
12404
+ * Schema for workspace creation options
12405
+ * - name: 3-63 chars, lowercase alphanumeric and hyphens, cannot start/end with hyphen
12406
+ * - organizationId, folderId: optional UUIDs
12407
+ */
12408
+ const createWorkspaceOptionsSchema = z.object({
12409
+ name: workspaceNameSchema,
12410
+ region: z.string(),
12411
+ deleteProtection: z.boolean().optional(),
12412
+ organizationId: z.uuid().optional(),
12413
+ folderId: z.uuid().optional(),
12414
+ profile: profileNameSchema.optional()
12415
+ });
12416
+ const validateRegion = async (region, client) => {
12417
+ const availableRegions = await client.listAvailableWorkspaceRegions({});
12418
+ if (!availableRegions.regions.includes(region)) throw new Error(`Region must be one of: ${availableRegions.regions.join(", ")}.`);
12419
+ };
12420
+ function profilePlatformSettings(platformConfig) {
12421
+ const hasOAuth2ClientId = platformConfig?.oauth2ClientId || process.env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID;
12422
+ const hasConsoleUrl = platformConfig?.consoleUrl || process.env.TAILOR_PLATFORM_CONSOLE_URL;
12423
+ return {
12424
+ ...isDefaultPlatform(platformConfig) ? {} : { platform_url: getPlatformBaseUrl(platformConfig) },
12425
+ ...hasOAuth2ClientId ? { oauth2_client_id: getOAuth2ClientId(platformConfig) } : {},
12426
+ ...hasConsoleUrl ? { console_url: getConsoleBaseUrl(platformConfig) } : {}
12427
+ };
12428
+ }
12429
+ /**
12430
+ * Create a new workspace with the given options.
12431
+ * @param options - Workspace creation options
12432
+ * @returns Created workspace info
12433
+ */
12434
+ async function createWorkspace$1(options) {
12435
+ const validated = validateCreateWorkspaceOptions(options);
12436
+ const client = await initOperatorClient(await loadAccessToken({ profile: validated.profile }), await loadPlatformClientConfig({ profile: validated.profile }));
12437
+ await validateRegion(validated.region, client);
12438
+ return createValidatedWorkspaceWithClient(client, validated);
12439
+ }
12440
+ /**
12441
+ * Create a workspace after its local options and region have been validated.
12442
+ * @param client - Authenticated Operator client
12443
+ * @param options - Validated workspace creation options
12444
+ * @returns Created workspace info
12445
+ */
12446
+ async function createValidatedWorkspaceWithClient(client, options) {
12447
+ return workspaceInfoWithFolderName(client, assertDefined((await client.createWorkspace({
12448
+ workspaceName: options.name,
12449
+ workspaceRegion: options.region,
12450
+ deleteProtection: options.deleteProtection ?? false,
12451
+ organizationId: options.organizationId,
12452
+ folderId: options.folderId
12453
+ })).workspace, "createWorkspace response missing workspace"));
12454
+ }
12455
+ /**
12456
+ * Validate workspace creation options without making API calls.
12457
+ * @param options - Workspace creation options
12458
+ * @returns Validated workspace creation options
12459
+ */
12460
+ function validateCreateWorkspaceOptions(options) {
12461
+ const result = createWorkspaceOptionsSchema.safeParse(options);
12462
+ if (!result.success) throw new Error(assertDefined(result.error.issues[0], "Zod returned no issues").message);
12463
+ return result.data;
12464
+ }
12465
+ const createCommand$1 = defineAppCommand({
12466
+ name: "create",
12467
+ description: "Create a new Tailor Platform workspace.",
12468
+ args: z.strictObject({
12469
+ name: arg(z.string(), {
12470
+ alias: "n",
12471
+ description: "Workspace name"
12472
+ }),
12473
+ region: arg(z.string(), {
12474
+ alias: "r",
12475
+ description: "Workspace region (us-west, asia-northeast)"
12476
+ }),
12477
+ "delete-protection": arg(z.boolean().default(false), {
12478
+ alias: "d",
12479
+ description: "Enable delete protection"
12480
+ }),
12481
+ "organization-id": arg(z.string().optional(), {
12482
+ alias: "o",
12483
+ description: "Organization ID to workspace associate with",
12484
+ env: "TAILOR_PLATFORM_ORGANIZATION_ID"
12485
+ }),
12486
+ "folder-id": arg(z.string().optional(), {
12487
+ alias: "f",
12488
+ description: "Folder ID to workspace associate with",
12489
+ env: "TAILOR_PLATFORM_FOLDER_ID"
12490
+ }),
12491
+ "profile-name": arg(z.string().optional(), {
12492
+ alias: "p",
12493
+ description: "Profile name to create"
12494
+ }),
12495
+ profile: arg(profileNameSchema.optional(), {
12496
+ description: "Workspace profile used for authentication and Platform selection",
12497
+ env: "TAILOR_PLATFORM_PROFILE"
12498
+ }),
12499
+ "profile-user": arg(z.string().optional(), { description: "User email address or machine user client ID for the profile (defaults to current user)" }),
12500
+ permission: arg(z.enum(["write", "read"]).default("write"), { description: "Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active." })
12501
+ }),
12502
+ run: async (args) => {
12503
+ await assertWritable({ profile: args.profile });
12504
+ const profileName = args["profile-name"];
12505
+ let profileSetup;
12506
+ if (profileName) {
12507
+ const config = await readPlatformConfig();
12508
+ if (config.profiles[profileName]) throw new Error(`Profile "${profileName}" already exists.`);
12509
+ const activeProfileName = args.profile;
12510
+ const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
12511
+ const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
12512
+ const profileUser = args["profile-user"] || activeProfileEntry?.user || config.current_user;
12513
+ if (!profileUser) throw new Error("Current user not found. Please login or specify --profile-user to create a profile.");
12514
+ const resolvedProfileUser = resolveConfigUser(config, profileUser, platformConfig);
12515
+ if (!resolvedProfileUser) throw new Error(`User "${profileUser}" not found.\nPlease verify your user name and login using 'tailor login' command.`);
12516
+ profileSetup = {
12517
+ name: profileName,
12518
+ user: resolvedProfileUser,
12519
+ platformSettings: profilePlatformSettings(platformConfig)
12520
+ };
12521
+ }
12522
+ const workspace = await createWorkspace$1({
12523
+ name: args.name,
12524
+ region: args.region,
12525
+ deleteProtection: args["delete-protection"],
12526
+ organizationId: args["organization-id"],
12527
+ folderId: args["folder-id"],
12528
+ profile: args.profile
12529
+ });
12530
+ let profileInfo;
12531
+ if (profileSetup) {
12532
+ const config = await readPlatformConfig();
12533
+ const platformSettings = profileSetup.platformSettings;
12534
+ config.profiles[profileSetup.name] = {
12535
+ user: profileSetup.user,
12536
+ workspace_id: workspace.id,
12537
+ ...args.permission === "read" ? { readonly: true } : {},
12538
+ ...platformSettings
12539
+ };
12540
+ writePlatformConfig(config);
12541
+ profileInfo = {
12542
+ name: profileSetup.name,
12543
+ user: profileSetup.user,
12544
+ workspaceId: workspace.id,
12545
+ permission: args.permission,
12546
+ ...platformSettings.platform_url ? { platformUrl: platformSettings.platform_url } : {},
12547
+ ...platformSettings.oauth2_client_id ? { oauth2ClientId: platformSettings.oauth2_client_id } : {},
12548
+ ...platformSettings.console_url ? { consoleUrl: platformSettings.console_url } : {}
12549
+ };
12550
+ if (!args.json) logger.success(`Profile "${profileSetup.name}" created successfully.`);
12551
+ }
12552
+ if (!args.json) logger.success(`Workspace "${workspaceDisplayName(workspace)}" created successfully.`);
12553
+ if (args.json && profileInfo) {
12554
+ logger.out({
12555
+ ...workspace,
12556
+ profile: profileInfo
12557
+ });
12558
+ return;
12559
+ }
12560
+ logger.out(workspace, { display: {
12561
+ name: workspaceNameTransformer,
12562
+ folderName: null
12563
+ } });
12564
+ if (profileInfo) {
12565
+ logger.out("Profile:");
12566
+ logger.out(profileInfo);
12567
+ }
12568
+ }
12569
+ });
12570
+
12571
+ //#endregion
12572
+ //#region src/cli/commands/workspace/list.ts
12573
+ /**
12574
+ * List workspaces with an optional order and limit.
12575
+ * @param options - Workspace listing options
12576
+ * @returns List of workspaces
12577
+ */
12578
+ async function listWorkspaces(options) {
12579
+ const profile = profileNameSchema.optional().parse(options?.profile);
12580
+ return listWorkspacesWithClient(await initOperatorClient(await loadAccessToken({ profile }), await loadPlatformClientConfig({ profile })), options);
12581
+ }
12582
+ /**
12583
+ * List workspaces using an existing Operator client.
12584
+ * @param client - Authenticated Operator client
12585
+ * @param options - Workspace listing options
12586
+ * @returns List of workspaces
12587
+ */
12588
+ async function listWorkspacesWithClient(client, options) {
12589
+ const pageDirection = toPageDirection(options?.order);
12590
+ return workspaceInfosWithFolderNames(client, await fetchPaged(async (pageToken, pageSize) => {
12591
+ const { workspaces, nextPageToken } = await client.listWorkspaces({
12592
+ pageToken,
12593
+ pageSize,
12594
+ pageDirection
12595
+ });
12596
+ return [workspaces, nextPageToken];
12597
+ }, { limit: options?.limit }));
12598
+ }
12599
+ const listCommand$9 = defineAppCommand({
12600
+ name: "list",
12601
+ description: "List all Tailor Platform workspaces.",
12602
+ args: z.strictObject({
12603
+ ...paginationArgs(),
12604
+ profile: arg(profileNameSchema.optional(), {
12605
+ description: "Workspace profile used for authentication and Platform selection",
12606
+ env: "TAILOR_PLATFORM_PROFILE"
12607
+ })
12608
+ }),
12609
+ run: async (args) => {
12610
+ const workspaces = await listWorkspaces({
12611
+ order: args.order,
12612
+ limit: args.limit,
12613
+ profile: args.profile
12614
+ });
12615
+ logger.out(workspaces, { display: {
12616
+ name: workspaceNameTransformer,
12617
+ folderName: null,
12618
+ organizationId: null,
12619
+ folderId: null,
12620
+ updatedAt: null
12621
+ } });
12622
+ }
12623
+ });
12624
+
12625
+ //#endregion
12626
+ //#region src/cli/commands/deploy/workspace-context.ts
12627
+ const workspaceContextSchema = z.object({
12628
+ version: z.literal(1),
12629
+ platformUrl: z.url(),
12630
+ applicationId: z.string().min(1).optional(),
12631
+ workspaceId: z.uuid()
12632
+ });
12633
+ function defaultConfigPath() {
12634
+ return resolve(process.cwd(), "tailor.config.ts");
12635
+ }
12636
+ function contextPath(configPath) {
12637
+ return join(dirname(configPath), getDistDir(), `${basename(configPath)}.context.json`);
12638
+ }
12639
+ /**
12640
+ * Load the project workspace context when it belongs to the current platform.
12641
+ * Missing, malformed, and cross-platform state is ignored.
12642
+ * @param platformUrl - Current Platform API base URL
12643
+ * @param configPath - Configuration file whose workspace selection is loaded
12644
+ * @param applicationId - Application identity that must match the saved context
12645
+ * @returns Valid context for the current platform, or undefined
12646
+ */
12647
+ async function loadWorkspaceContext(platformUrl, configPath = defaultConfigPath(), applicationId) {
12648
+ let contents;
12649
+ try {
12650
+ contents = await readFile(contextPath(configPath), "utf8");
12651
+ } catch (error) {
12652
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
12653
+ throw error;
12654
+ }
12655
+ let value;
12656
+ try {
12657
+ value = JSON.parse(contents);
12658
+ } catch {
12659
+ return;
12660
+ }
12661
+ const result = workspaceContextSchema.safeParse(value);
12662
+ if (!result.success || result.data.platformUrl !== platformUrl || applicationId !== void 0 && result.data.applicationId !== applicationId) return;
12663
+ return result.data;
12664
+ }
12665
+ /**
12666
+ * Persist the selected workspace as project-local SDK state.
12667
+ * @param context - Workspace context to persist
12668
+ * @param configPath - Configuration file whose workspace selection is persisted
12669
+ * @param applicationId - Application identity stored with the workspace selection
12670
+ */
12671
+ async function saveWorkspaceContext(context, configPath = defaultConfigPath(), applicationId) {
12672
+ const validated = workspaceContextSchema.parse({
12673
+ ...context,
12674
+ ...applicationId === void 0 ? {} : { applicationId }
12675
+ });
12676
+ const stateDirectory = join(dirname(configPath), getDistDir());
12677
+ const targetPath = contextPath(configPath);
12678
+ const serialized = `${JSON.stringify(validated, null, 2)}\n`;
12679
+ try {
12680
+ if (await readFile(targetPath, "utf8") === serialized) return;
12681
+ } catch {}
12682
+ const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;
12683
+ await mkdir(stateDirectory, { recursive: true });
12684
+ try {
12685
+ await writeFile(temporaryPath, serialized, { mode: 384 });
12686
+ await rename(temporaryPath, targetPath);
12687
+ } catch (error) {
12688
+ await rm(temporaryPath, { force: true }).catch(() => void 0);
12689
+ throw error;
12690
+ }
12691
+ }
12692
+
12693
+ //#endregion
12694
+ //#region src/cli/commands/deploy/workspace.ts
12695
+ function suggestedWorkspaceName() {
12696
+ const name = basename(process.cwd()).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, 63).replace(/-+$/, "");
12697
+ return validateWorkspaceName(name) === true ? name : "my-workspace";
12698
+ }
12699
+ function executableAction(args) {
12700
+ return {
12701
+ command: "tailor-sdk",
12702
+ args
12703
+ };
12704
+ }
12705
+ function deployArgs(options) {
12706
+ return options.deployArgs ?? ["deploy"];
12707
+ }
12708
+ function workspaceCommandArgs(options) {
12709
+ return options.workspaceCommandArgs ?? [];
12710
+ }
12711
+ function createDeployArgs(options, name, region) {
12712
+ return [
12713
+ ...deployArgs(options),
12714
+ "--create-workspace",
12715
+ "--workspace-name",
12716
+ name,
12717
+ "--workspace-region",
12718
+ region,
12719
+ ...options.organizationId ? ["--organization-id", options.organizationId] : [],
12720
+ ...options.folderId ? ["--folder-id", options.folderId] : []
12721
+ ];
12722
+ }
12723
+ function selectDeployArgs(options) {
12724
+ return [
12725
+ ...deployArgs(options),
12726
+ "--workspace-id",
12727
+ "<workspace-id>"
12728
+ ];
12729
+ }
12730
+ function workspaceLabel(workspace) {
12731
+ const organization = workspace.organizationId ?? "personal";
12732
+ return `${workspaceDisplayName(workspace)} (${workspace.region}, org: ${organization}, id: ${workspace.id})`;
12733
+ }
12734
+ function workspaceMatchesRequestedIdentity(workspace, options) {
12735
+ return options.workspaceName === workspace.name && options.workspaceRegion === workspace.region && options.organizationId === workspace.organizationId && options.folderId === workspace.folderId;
12736
+ }
12737
+ function workspaceIdentity(workspace) {
12738
+ const { id, name, region, organizationId, folderId } = workspace;
12739
+ return {
12740
+ id,
12741
+ name,
12742
+ region,
12743
+ organizationId,
12744
+ folderId
12745
+ };
12746
+ }
12747
+ function projectContextTargets(contextTargets) {
12748
+ if (!contextTargets) return void 0;
12749
+ return [...new Map(contextTargets.map((target) => [target.configPath, target])).values()];
12750
+ }
12751
+ async function loadProjectContexts(platformUrl, contextTargets) {
12752
+ const targets = projectContextTargets(contextTargets);
12753
+ if (!targets || targets.length === 0) {
12754
+ const context = await loadWorkspaceContext(platformUrl);
12755
+ return context ? [context] : [];
12756
+ }
12757
+ return (await Promise.all(targets.map(({ configPath, applicationId }) => loadWorkspaceContext(platformUrl, configPath, applicationId)))).filter((context) => context !== void 0);
12758
+ }
12759
+ async function persistWorkspaceContext(context, contextTargets) {
12760
+ const targets = projectContextTargets(contextTargets);
12761
+ if (!targets || targets.length === 0) {
12762
+ await saveWorkspaceContext(context);
12763
+ return;
12764
+ }
12765
+ const failures = (await Promise.allSettled(targets.map(({ configPath, applicationId }) => saveWorkspaceContext(context, configPath, applicationId)))).filter((result) => result.status === "rejected");
12766
+ if (failures.length > 0) throw new Error(failures.map(({ reason }) => reason instanceof Error ? reason.message : String(reason)).join("; "));
12767
+ }
12768
+ async function rememberWorkspaceContext(context, options, failurePolicy = "error-on-partial") {
12769
+ if (options.dryRun) return;
12770
+ try {
12771
+ await persistWorkspaceContext(context, options.contextTargets);
12772
+ } catch (error) {
12773
+ if ((projectContextTargets(options.contextTargets)?.length ?? 0) > 1 && failurePolicy === "error-on-partial") throw createCLIError({
12774
+ code: "WORKSPACE_CONTEXT_SAVE_FAILED",
12775
+ message: "The workspace selection could not be saved for every configuration file.",
12776
+ details: error instanceof Error ? error.message : String(error),
12777
+ suggestion: "Fix project state permissions, then rerun deploy with the workspace ID.",
12778
+ next: executableAction([
12779
+ ...deployArgs(options),
12780
+ "--workspace-id",
12781
+ context.workspaceId
12782
+ ]),
12783
+ context: {
12784
+ workspaceId: context.workspaceId,
12785
+ configPaths: options.contextTargets?.map(({ configPath }) => configPath)
12786
+ }
12787
+ });
12788
+ 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.`);
12789
+ }
12790
+ }
12791
+ async function useWorkspace(client, platformUrl, workspace, options, failurePolicy = "error-on-partial") {
12792
+ await rememberWorkspaceContext({
12793
+ version: 1,
12794
+ platformUrl,
12795
+ workspaceId: workspace.id
12796
+ }, options, failurePolicy);
12797
+ logger.info(`Using workspace: ${workspaceLabel(workspace)}`);
12798
+ return {
12799
+ client,
12800
+ workspaceId: workspace.id
12801
+ };
12802
+ }
12803
+ function useRememberedWorkspace(client, workspace) {
12804
+ logger.warn(`Using saved workspace selection: ${workspaceLabel(workspace)}`);
12805
+ return {
12806
+ client,
12807
+ workspaceId: workspace.id
12808
+ };
12809
+ }
12810
+ const createNewWorkspaceSelection = "create-new-workspace";
12811
+ async function chooseWorkspace(client, platformUrl, workspaces, options) {
12812
+ const workspaceId = await prompt.select({
12813
+ message: "Select a workspace",
12814
+ choices: [...workspaces.map((workspace) => ({
12815
+ name: workspaceLabel(workspace),
12816
+ value: workspace.id
12817
+ })), {
12818
+ name: "Create new workspace",
12819
+ value: createNewWorkspaceSelection
12820
+ }]
12821
+ });
12822
+ if (workspaceId === createNewWorkspaceSelection) return createWorkspaceForDeploy(client, platformUrl, options);
12823
+ const workspace = workspaces.find(({ id }) => id === workspaceId);
12824
+ if (!workspace) throw new Error("Selected workspace was not found");
12825
+ return useWorkspace(client, platformUrl, workspace, options);
12826
+ }
12827
+ function invalidCreateOptionsError(options, name, region, details) {
12828
+ return createCLIError({
12829
+ code: "WORKSPACE_CREATE_OPTIONS_INVALID",
12830
+ message: "Workspace creation options are invalid.",
12831
+ details,
12832
+ suggestion: "Correct the workspace creation options and rerun deploy.",
12833
+ next: executableAction(createDeployArgs(options, name, region))
12834
+ });
12835
+ }
12836
+ async function createWorkspace(client, platformUrl, options, availableRegions, validatedOptions) {
12837
+ let name = options.workspaceName;
12838
+ let region = options.workspaceRegion;
12839
+ const interactive = canPrompt();
12840
+ if (interactive) {
12841
+ name ??= await prompt.text({
12842
+ message: "Workspace name",
12843
+ default: suggestedWorkspaceName(),
12844
+ validate: validateWorkspaceName
12845
+ });
12846
+ region ??= await prompt.select({
12847
+ message: "Workspace region",
12848
+ choices: availableRegions.map((value) => ({
12849
+ name: value,
12850
+ value
12851
+ }))
12852
+ });
12853
+ } else {
12854
+ const missingOptions = [...name === void 0 ? ["--workspace-name"] : [], ...region === void 0 ? ["--workspace-region"] : []];
12855
+ if (missingOptions.length > 0) throw createCLIError({
12856
+ code: "WORKSPACE_CREATE_OPTIONS_REQUIRED",
12857
+ message: "Workspace creation requires a name and region in non-interactive mode.",
12858
+ suggestion: "Provide every workspace creation option and rerun deploy.",
12859
+ next: executableAction(createDeployArgs(options, name ?? "<name>", region ?? "<region>")),
12860
+ context: { missingOptions }
12861
+ });
12862
+ }
12863
+ if (!name || !region) throw new Error("Workspace creation options were not resolved");
12864
+ let validated = validatedOptions;
12865
+ if (!validated) {
12866
+ try {
12867
+ validated = validateCreateWorkspaceOptions({
12868
+ name,
12869
+ region,
12870
+ organizationId: options.organizationId,
12871
+ folderId: options.folderId
12872
+ });
12873
+ } catch (error) {
12874
+ throw invalidCreateOptionsError(options, name, region, error instanceof Error ? error.message : String(error));
12875
+ }
12876
+ if (!availableRegions.includes(region)) throw invalidCreateOptionsError(options, name, region, `Region must be one of: ${availableRegions.join(", ")}.`);
12877
+ }
12878
+ if (interactive) {
12879
+ const scope = [options.organizationId ? `organization: ${options.organizationId}` : void 0, options.folderId ? `folder: ${options.folderId}` : void 0].filter((value) => value !== void 0);
12880
+ if (!await prompt.confirm({
12881
+ message: `Create workspace "${name}" in ${region}${scope.length > 0 ? ` (${scope.join(", ")})` : ""}?`,
12882
+ default: true
12883
+ })) throw createCLIError({
12884
+ code: "WORKSPACE_CREATION_CANCELLED",
12885
+ message: "Workspace creation was cancelled."
11509
12886
  });
11510
12887
  }
11511
- function inPlaceReplaces(schema, kind, items) {
11512
- validateItems({
11513
- validator,
11514
- schema,
11515
- kind,
11516
- action: "replace",
11517
- items,
11518
- requestKey: "updateRequest",
11519
- violations
12888
+ let workspace;
12889
+ try {
12890
+ workspace = await createValidatedWorkspaceWithClient(client, validated);
12891
+ } catch (error) {
12892
+ if (error instanceof ConnectError && ![
12893
+ Code.Canceled,
12894
+ Code.Unknown,
12895
+ Code.DeadlineExceeded,
12896
+ Code.Aborted,
12897
+ Code.Internal,
12898
+ Code.Unavailable,
12899
+ Code.DataLoss
12900
+ ].includes(error.code)) throw error;
12901
+ throw createCLIError({
12902
+ code: "WORKSPACE_CREATION_FAILED",
12903
+ message: "Workspace creation did not complete successfully.",
12904
+ details: error instanceof Error ? error.message : String(error),
12905
+ suggestion: "The outcome may be uncertain. List workspaces before retrying creation to avoid duplicates.",
12906
+ next: executableAction([
12907
+ "workspace",
12908
+ "list",
12909
+ ...workspaceCommandArgs(options),
12910
+ "--json"
12911
+ ])
11520
12912
  });
11521
12913
  }
11522
- creates(CreateTailorDBServiceRequestSchema, "TailorDB service", tailorDB.changeSet.service.creates);
11523
- creates(CreateTailorDBTypeRequestSchema, "TailorDB type", tailorDB.changeSet.type.creates);
11524
- updates(UpdateTailorDBTypeRequestSchema, "TailorDB type", tailorDB.changeSet.type.updates);
11525
- creates(CreateStaticWebsiteRequestSchema, "StaticWebsite", staticWebsite.changeSet.creates);
11526
- updates(UpdateStaticWebsiteRequestSchema, "StaticWebsite", staticWebsite.changeSet.updates);
11527
- creates(AddCustomDomainRequestSchema, "StaticWebsite custom domain", staticWebsite.customDomainChangeSet.creates);
11528
- creates(CreateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.creates.map((item) => ({
11529
- name: item.name,
11530
- request: {
11531
- ...item.request,
11532
- cors: void 0
11533
- }
11534
- })));
11535
- updates(UpdateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.updates.map((item) => ({
11536
- name: item.name,
11537
- request: {
11538
- ...item.request,
11539
- cors: void 0
11540
- }
11541
- })));
11542
- const placeholderOriginReplacement = "https://placeholder.invalid";
11543
- const substituteIdpReturnOrigins = (item) => {
11544
- const request = item.request;
11545
- const origins = request.userAuthPolicy?.allowedReturnOrigins;
11546
- if (!Array.isArray(origins) || origins.length === 0) return item;
11547
- const substituted = origins.map((origin) => typeof origin === "string" && /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]:url$/.test(origin) ? placeholderOriginReplacement : origin);
11548
- return {
11549
- ...item,
11550
- request: {
11551
- ...request,
11552
- userAuthPolicy: {
11553
- ...request.userAuthPolicy,
11554
- allowedReturnOrigins: substituted
11555
- }
11556
- }
11557
- };
12914
+ await rememberWorkspaceContext({
12915
+ version: 1,
12916
+ platformUrl,
12917
+ workspaceId: workspace.id
12918
+ }, options);
12919
+ logger.success(`Created workspace: ${workspaceLabel(workspace)}`);
12920
+ logger.info(`Reuse this workspace with: tailor-sdk deploy --workspace-id ${workspace.id}`);
12921
+ logger.info(`Or set TAILOR_PLATFORM_WORKSPACE_ID=${workspace.id}.`);
12922
+ return {
12923
+ client,
12924
+ workspaceId: workspace.id
11558
12925
  };
11559
- creates(CreateIdPServiceRequestSchema, "IdP service", idp.changeSet.service.creates.map(substituteIdpReturnOrigins));
11560
- updates(UpdateIdPServiceRequestSchema, "IdP service", idp.changeSet.service.updates.map(substituteIdpReturnOrigins));
11561
- const idpClientVaultItems = [...idp.changeSet.client.creates.map((c) => ({
11562
- clientName: c.request.client?.name ?? "",
11563
- namespaceName: c.request.namespaceName ?? "",
11564
- workspaceId: c.request.workspaceId ?? ""
11565
- })), ...idp.changeSet.client.updates.map((u) => ({
11566
- clientName: u.name,
11567
- namespaceName: u.namespaceName,
11568
- workspaceId: u.workspaceId
11569
- }))];
11570
- creates(CreateSecretManagerVaultRequestSchema, "IdP client secret", idpClientVaultItems.map((item) => ({
11571
- name: item.clientName,
11572
- request: {
11573
- workspaceId: item.workspaceId,
11574
- secretmanagerVaultName: idpClientVaultName(item.namespaceName, item.clientName)
11575
- }
11576
- })));
11577
- creates(CreateSecretManagerSecretRequestSchema, "IdP client secret", idpClientVaultItems.map((item) => ({
11578
- name: item.clientName,
11579
- request: {
11580
- workspaceId: item.workspaceId,
11581
- secretmanagerVaultName: idpClientVaultName(item.namespaceName, item.clientName),
11582
- secretmanagerSecretName: idpClientSecretName(item.namespaceName, item.clientName)
12926
+ }
12927
+ async function createWorkspaceForDeploy(client, platformUrl, options, requestedRegions, validatedOptions) {
12928
+ const regions = requestedRegions ?? (await client.listAvailableWorkspaceRegions({})).regions;
12929
+ if (options.dryRun) throw createCLIError({
12930
+ code: "WORKSPACE_CREATION_DISABLED_IN_DRY_RUN",
12931
+ message: "Dry-run cannot create the workspace required to build a deployment plan.",
12932
+ suggestion: "Create a workspace explicitly, then rerun the same dry-run with its workspace ID.",
12933
+ context: { availableRegions: regions }
12934
+ });
12935
+ return createWorkspace(client, platformUrl, options, regions, validatedOptions);
12936
+ }
12937
+ /**
12938
+ * Resolve or provision the workspace used by deploy.
12939
+ * Explicit configuration wins over project context and account discovery.
12940
+ * @param options - Deploy workspace selection and creation options
12941
+ * @returns Authenticated client and resolved workspace ID
12942
+ */
12943
+ async function resolveDeployWorkspace(options = {}) {
12944
+ const createOptionNames = [options.workspaceName !== void 0 ? "--workspace-name" : void 0, options.workspaceRegion !== void 0 ? "--workspace-region" : void 0].filter((name) => name !== void 0);
12945
+ if (!options.createWorkspace && createOptionNames.length > 0) throw createCLIError({
12946
+ code: "WORKSPACE_CREATE_FLAG_REQUIRED",
12947
+ message: "Workspace creation options require --create-workspace.",
12948
+ suggestion: "Add --create-workspace or remove the workspace creation options.",
12949
+ next: executableAction(createDeployArgs(options, options.workspaceName ?? "<name>", options.workspaceRegion ?? "<region>")),
12950
+ context: { options: createOptionNames }
12951
+ });
12952
+ if (options.createWorkspace && options.workspaceName !== void 0) {
12953
+ const nameValidation = validateWorkspaceName(options.workspaceName);
12954
+ if (nameValidation !== true) throw invalidCreateOptionsError(options, options.workspaceName, options.workspaceRegion ?? "<region>", nameValidation);
12955
+ }
12956
+ if (options.createWorkspace && options.workspaceRegion === "") throw invalidCreateOptionsError(options, options.workspaceName ?? "<name>", options.workspaceRegion, "Region must not be empty.");
12957
+ const explicitWorkspaceId = await tryLoadWorkspaceId({
12958
+ workspaceId: options.workspaceId,
12959
+ profile: options.profile
12960
+ });
12961
+ const accessToken = await loadAccessToken({ profile: options.profile });
12962
+ const platformConfig = await loadPlatformClientConfig({
12963
+ profile: options.profile,
12964
+ allowMissingProfile: explicitWorkspaceId !== void 0
12965
+ });
12966
+ const platformUrl = getPlatformBaseUrl(platformConfig);
12967
+ const client = await initOperatorClient(accessToken, platformConfig);
12968
+ if (explicitWorkspaceId) {
12969
+ let response;
12970
+ try {
12971
+ response = await client.getWorkspace({ workspaceId: explicitWorkspaceId });
12972
+ } catch (error) {
12973
+ if (error instanceof ConnectError && error.code === Code.NotFound) throw createCLIError({
12974
+ code: "WORKSPACE_NOT_FOUND",
12975
+ message: `Workspace "${explicitWorkspaceId}" was not found.`
12976
+ });
12977
+ throw error;
11583
12978
  }
11584
- })));
11585
- creates(CreateAuthServiceRequestSchema, "Auth service", auth.changeSet.service.creates);
11586
- updates(UpdateAuthServiceRequestSchema, "Auth service", auth.changeSet.service.updates);
11587
- creates(CreateAuthIDPConfigRequestSchema, "Auth IDP config", auth.changeSet.idpConfig.creates);
11588
- updates(UpdateAuthIDPConfigRequestSchema, "Auth IDP config", auth.changeSet.idpConfig.updates);
11589
- creates(CreateUserProfileConfigRequestSchema, "Auth user profile config", auth.changeSet.userProfileConfig.creates);
11590
- updates(UpdateUserProfileConfigRequestSchema, "Auth user profile config", auth.changeSet.userProfileConfig.updates);
11591
- creates(CreateTenantConfigRequestSchema, "Auth tenant config", auth.changeSet.tenantConfig.creates);
11592
- updates(UpdateTenantConfigRequestSchema, "Auth tenant config", auth.changeSet.tenantConfig.updates);
11593
- creates(CreateAuthMachineUserRequestSchema, "Auth machine user", auth.changeSet.machineUser.creates);
11594
- updates(UpdateAuthMachineUserRequestSchema, "Auth machine user", auth.changeSet.machineUser.updates);
11595
- creates(CreateAuthHookRequestSchema, "Auth hook", auth.changeSet.authHook.creates);
11596
- updates(UpdateAuthHookRequestSchema, "Auth hook", auth.changeSet.authHook.updates);
11597
- creates(CreateAuthSCIMConfigRequestSchema, "Auth SCIM config", auth.changeSet.scim.creates);
11598
- updates(UpdateAuthSCIMConfigRequestSchema, "Auth SCIM config", auth.changeSet.scim.updates);
11599
- creates(CreateAuthSCIMResourceRequestSchema, "Auth SCIM resource", auth.changeSet.scimResource.creates);
11600
- updates(UpdateAuthSCIMResourceRequestSchema, "Auth SCIM resource", auth.changeSet.scimResource.updates);
11601
- creates(CreateAuthOAuth2ClientRequestSchema, "OAuth2 client", auth.changeSet.oauth2Client.creates);
11602
- updates(UpdateAuthOAuth2ClientRequestSchema, "OAuth2 client", auth.changeSet.oauth2Client.updates);
11603
- replaces(CreateAuthOAuth2ClientRequestSchema, "OAuth2 client", auth.changeSet.oauth2Client.replaces);
11604
- creates(CreatePipelineServiceRequestSchema, "Pipeline service", pipeline.changeSet.service.creates);
11605
- updates(UpdatePipelineServiceRequestSchema, "Pipeline service", pipeline.changeSet.service.updates);
11606
- creates(CreatePipelineResolverRequestSchema, "Resolver", pipeline.changeSet.resolver.creates);
11607
- updates(UpdatePipelineResolverRequestSchema, "Resolver", pipeline.changeSet.resolver.updates);
11608
- creates(CreateExecutorExecutorRequestSchema, "Executor", executor.changeSet.creates);
11609
- updates(UpdateExecutorExecutorRequestSchema, "Executor", executor.changeSet.updates);
11610
- creates(CreateWorkflowRequestSchema, "Workflow", workflow.changeSet.creates.map((item) => ({
11611
- name: item.name,
11612
- request: buildWorkflowValidationShape(item.workspaceId, item.workflow)
11613
- })));
11614
- updates(UpdateWorkflowRequestSchema, "Workflow", workflow.changeSet.updates.map((item) => ({
11615
- name: item.name,
11616
- request: buildWorkflowValidationShape(item.workspaceId, item.workflow)
11617
- })));
11618
- const workflowJobNameWorkspaceId = workflow.changeSet.creates[0]?.workspaceId ?? workflow.changeSet.updates[0]?.workspaceId ?? "";
11619
- if (workflowJobNameWorkspaceId) {
11620
- const allJobNames = /* @__PURE__ */ new Set();
11621
- for (const item of [...workflow.changeSet.creates, ...workflow.changeSet.updates]) for (const jobName of item.usedJobNames) allJobNames.add(jobName);
11622
- for (const jobName of workflow.unchangedWorkflowJobNames) allJobNames.add(jobName);
11623
- creates(CreateWorkflowJobFunctionRequestSchema, "Workflow job function", [...allJobNames].map((jobName) => ({
11624
- name: jobName,
11625
- request: {
11626
- workspaceId: workflowJobNameWorkspaceId,
11627
- jobFunctionName: jobName,
11628
- scriptRef: jobName
12979
+ if (!response.workspace) throw createCLIError({
12980
+ code: "WORKSPACE_NOT_FOUND",
12981
+ message: `Workspace "${explicitWorkspaceId}" was not found.`
12982
+ });
12983
+ return useWorkspace(client, platformUrl, workspaceInfo(response.workspace), options, "warn");
12984
+ }
12985
+ let requestedCreateOptions;
12986
+ if (options.createWorkspace && options.workspaceName !== void 0 && options.workspaceRegion !== void 0) try {
12987
+ requestedCreateOptions = validateCreateWorkspaceOptions({
12988
+ name: options.workspaceName,
12989
+ region: options.workspaceRegion,
12990
+ organizationId: options.organizationId,
12991
+ folderId: options.folderId
12992
+ });
12993
+ } catch (error) {
12994
+ throw invalidCreateOptionsError(options, options.workspaceName, options.workspaceRegion, error instanceof Error ? error.message : String(error));
12995
+ }
12996
+ const [contexts, workspaces] = await Promise.all([loadProjectContexts(platformUrl, options.contextTargets), listWorkspacesWithClient(client)]);
12997
+ const interactive = canPrompt();
12998
+ const contextWorkspaceIds = new Set(contexts.map(({ workspaceId }) => workspaceId));
12999
+ const linkedWorkspace = contextWorkspaceIds.size === 1 ? workspaces.find(({ id }) => contextWorkspaceIds.has(id)) : void 0;
13000
+ if (linkedWorkspace && (!options.createWorkspace || workspaceMatchesRequestedIdentity(linkedWorkspace, options))) {
13001
+ const contextTargetCount = projectContextTargets(options.contextTargets)?.length ?? 1;
13002
+ return contexts.length === contextTargetCount ? useRememberedWorkspace(client, linkedWorkspace) : useWorkspace(client, platformUrl, linkedWorkspace, options);
13003
+ }
13004
+ const onlyWorkspace = workspaces.length === 1 ? workspaces[0] : void 0;
13005
+ const canReuseOnlyWorkspace = onlyWorkspace !== void 0 && requestedCreateOptions !== void 0 && workspaceMatchesRequestedIdentity(onlyWorkspace, options);
13006
+ let requestedRegions;
13007
+ if (requestedCreateOptions && !canReuseOnlyWorkspace) {
13008
+ requestedRegions = await client.listAvailableWorkspaceRegions({});
13009
+ if (!requestedRegions.regions.includes(requestedCreateOptions.region)) throw invalidCreateOptionsError(options, requestedCreateOptions.name, requestedCreateOptions.region, `Region must be one of: ${requestedRegions.regions.join(", ")}.`);
13010
+ }
13011
+ if (workspaces.length === 0 && (interactive || options.createWorkspace || contexts.length === 0)) {
13012
+ const regions = requestedRegions?.regions ?? (await client.listAvailableWorkspaceRegions({})).regions;
13013
+ if (options.dryRun || interactive || options.createWorkspace) return createWorkspaceForDeploy(client, platformUrl, options, regions, requestedCreateOptions);
13014
+ throw createCLIError({
13015
+ code: "WORKSPACE_NOT_FOUND",
13016
+ message: "No workspaces are available for this account.",
13017
+ suggestion: "Create one during deploy by providing the workspace name and one of the available regions.",
13018
+ next: executableAction(createDeployArgs(options, "<name>", "<region>")),
13019
+ context: { availableRegions: regions }
13020
+ });
13021
+ }
13022
+ if (contextWorkspaceIds.size > 1) {
13023
+ const canReplaceStaleContexts = options.createWorkspace === true && options.workspaceName !== void 0 && options.workspaceRegion !== void 0 && workspaces.length === 0;
13024
+ if (!interactive && !canReplaceStaleContexts) throw createCLIError({
13025
+ code: "WORKSPACE_CONTEXT_CONFLICT",
13026
+ message: "The deployed configuration files are linked to different workspaces.",
13027
+ suggestion: "Choose one workspace explicitly for the combined deployment.",
13028
+ next: executableAction(selectDeployArgs(options)),
13029
+ context: { savedWorkspaceIds: [...contextWorkspaceIds] }
13030
+ });
13031
+ if (workspaces.length > 0 && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
13032
+ }
13033
+ if (!linkedWorkspace && contexts.length > 0) {
13034
+ const explicitlyEnsuringSingleTarget = options.createWorkspace === true && options.workspaceName !== void 0 && options.workspaceRegion !== void 0 && workspaces.length <= 1;
13035
+ if (!interactive && !explicitlyEnsuringSingleTarget) throw createCLIError({
13036
+ code: "WORKSPACE_CONTEXT_STALE",
13037
+ message: "The saved project workspace is no longer available.",
13038
+ suggestion: "Choose an available workspace explicitly before deploying.",
13039
+ next: executableAction(selectDeployArgs(options)),
13040
+ context: {
13041
+ savedWorkspaceIds: [...contextWorkspaceIds],
13042
+ workspaces: workspaces.map(workspaceIdentity)
11629
13043
  }
11630
- })));
13044
+ });
13045
+ if (workspaces.length > 0 && interactive && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
13046
+ }
13047
+ if (workspaces.length === 1) {
13048
+ const [workspace] = workspaces;
13049
+ if (!workspace) throw new Error("Workspace discovery returned an invalid result");
13050
+ if (options.createWorkspace && !workspaceMatchesRequestedIdentity(workspace, options)) throw createCLIError({
13051
+ code: "WORKSPACE_CREATE_CONFLICT",
13052
+ message: "The existing workspace does not match the requested workspace.",
13053
+ suggestion: "Use the existing workspace, or run the explicit workspace create command to create another one.",
13054
+ next: executableAction([
13055
+ "workspace",
13056
+ "create",
13057
+ ...workspaceCommandArgs(options),
13058
+ ...options.workspaceCommandJson ? ["--json"] : [],
13059
+ "--name",
13060
+ options.workspaceName ?? "<name>",
13061
+ "--region",
13062
+ options.workspaceRegion ?? "<region>",
13063
+ ...options.organizationId ? ["--organization-id", options.organizationId] : [],
13064
+ ...options.folderId ? ["--folder-id", options.folderId] : []
13065
+ ]),
13066
+ context: { existingWorkspace: workspaceIdentity(workspace) }
13067
+ });
13068
+ if (interactive && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
13069
+ return useWorkspace(client, platformUrl, workspace, options);
13070
+ }
13071
+ if (workspaces.length > 1) {
13072
+ if (!interactive) throw createCLIError({
13073
+ code: "WORKSPACE_SELECTION_REQUIRED",
13074
+ message: "Multiple workspaces are available.",
13075
+ suggestion: "Choose one explicitly for non-interactive deployment.",
13076
+ next: executableAction(selectDeployArgs(options)),
13077
+ context: { workspaces: workspaces.map(workspaceIdentity) }
13078
+ });
13079
+ return chooseWorkspace(client, platformUrl, workspaces, options);
11631
13080
  }
11632
- creates(CreateWorkflowJobFunctionExecutionPolicyRequestSchema, "Workflow execution policy", [...workflowExecutionPolicy.changeSet.creates, ...workflowExecutionPolicy.changeSet.replaces].map((item) => ({
11633
- name: item.name,
11634
- request: {
11635
- workspaceId: item.workspaceId,
11636
- executionPolicyName: item.policy.name,
11637
- executionPolicyKey: toPlatformExecutionPolicyKey(item.policy),
11638
- ...item.policy.concurrencyPolicy && { concurrencyPolicy: { maxConcurrentExecutions: item.policy.concurrencyPolicy.maxConcurrentExecutions } }
11639
- }
11640
- })));
11641
- updates(UpdateWorkflowJobFunctionExecutionPolicyRequestSchema, "Workflow execution policy", workflowExecutionPolicy.changeSet.updates.map((item) => ({
11642
- name: item.name,
11643
- request: {
11644
- workspaceId: item.workspaceId,
11645
- executionPolicyName: item.policy.name,
11646
- ...item.policy.concurrencyPolicy && { concurrencyPolicy: { maxConcurrentExecutions: item.policy.concurrencyPolicy.maxConcurrentExecutions } }
11647
- }
11648
- })));
11649
- creates(CreateSecretManagerVaultRequestSchema, "Secret Manager vault", secretManager.vaultChangeSet.creates.map((item) => ({
11650
- name: item.name,
11651
- request: vaultCreateRequest(item)
11652
- })));
11653
- creates(CreateSecretManagerSecretRequestSchema, "Secret Manager secret", secretManager.secretChangeSet.creates.map((item) => ({
11654
- name: item.name,
11655
- request: secretCreateRequest(item)
11656
- })));
11657
- updates(UpdateSecretManagerSecretRequestSchema, "Secret Manager secret", secretManager.secretChangeSet.updates.map((item) => ({
11658
- name: item.name,
11659
- request: secretUpdateRequest(item)
11660
- })));
11661
- creates(CreateApplicationRequestSchema, "Application", app.creates.map((item) => ({
11662
- name: item.name,
11663
- request: {
11664
- ...item.request,
11665
- cors: void 0
11666
- }
11667
- })));
11668
- updates(UpdateApplicationRequestSchema, "Application", [...app.updates, ...app.unchanged].map((item) => ({
11669
- name: item.name,
11670
- request: {
11671
- ...item.request,
11672
- cors: void 0
11673
- }
11674
- })));
11675
- creates(CreateAuthConnectionRequestSchema, "Auth connection", auth.changeSet.connection.creates);
11676
- inPlaceReplaces(UpdateAuthConnectionRequestSchema, "Auth connection", auth.changeSet.connection.replaces);
11677
- if (violations.length === 0) return;
11678
- const resourceNames = new Set(violations.map((v) => `${v.kind}:${v.name}`));
11679
- logger.error(`Pre-flight validation found ${violations.length} violation(s) across ${resourceNames.size} resource(s):`);
11680
- for (const v of violations) logger.log(` ${styles.resourceType(v.kind)} ${styles.resourceName(v.name)} (${v.action}) — ${styles.bold(v.fieldPath)}: ${v.message}`);
11681
- throw new Error(`${violations.length} validation error(s) found in ${resourceNames.size} resource(s)`);
13081
+ throw new Error("Workspace discovery returned an invalid result");
11682
13082
  }
11683
13083
 
11684
13084
  //#endregion
@@ -12098,19 +13498,40 @@ function parseDeployConfigPaths(configPath) {
12098
13498
  if (configPaths.some((entry) => entry.length === 0)) throw new Error("--config must contain one or more non-empty config paths.");
12099
13499
  return configPaths;
12100
13500
  }
13501
+ function recoveryEnvironmentArgs(options, cliContext) {
13502
+ return [
13503
+ ...cliContext?.envFile ? ["--env-file", path.resolve(process.cwd(), cliContext.envFile)] : [],
13504
+ ...cliContext?.envFileIfExists ? ["--env-file-if-exists", path.resolve(process.cwd(), cliContext.envFileIfExists)] : [],
13505
+ ...options?.profile ? ["--profile", options.profile] : []
13506
+ ];
13507
+ }
13508
+ function recoveryOutputArgs(cliContext) {
13509
+ return [...cliContext?.verbose ? ["--verbose"] : [], ...cliContext?.json || logger.jsonMode ? ["--json"] : []];
13510
+ }
13511
+ function retryDeployArgs(options, configPaths, cliContext) {
13512
+ return [
13513
+ "deploy",
13514
+ "--config",
13515
+ configPaths.join(","),
13516
+ ...recoveryEnvironmentArgs(options, cliContext),
13517
+ ...options?.dryRun ? ["--dry-run"] : [],
13518
+ ...options?.yes ? ["--yes"] : [],
13519
+ ...options?.noSchemaCheck ? ["--no-schema-check"] : [],
13520
+ ...options?.noValidate ? ["--no-validate"] : [],
13521
+ ...options?.noCache ? ["--no-cache"] : [],
13522
+ ...options?.cleanCache ? ["--clean-cache"] : [],
13523
+ ...recoveryOutputArgs(cliContext)
13524
+ ];
13525
+ }
13526
+ function workspaceRecoveryArgs(options, cliContext) {
13527
+ return [...recoveryEnvironmentArgs(options, cliContext), ...cliContext?.verbose ? ["--verbose"] : []];
13528
+ }
12101
13529
  async function buildDeploymentTarget(params) {
12102
- const { configPath, dryRun, buildOnly, noCache, packageVersion, cacheDir } = params;
12103
- const { config, plugins } = await withSpan("build.loadConfig", async () => {
12104
- const foundPath = loadConfigPath(configPath);
12105
- if (foundPath) {
12106
- const resolvedPath = path.resolve(process.cwd(), foundPath);
12107
- if (fs$1.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
12108
- configPath: resolvedPath,
12109
- dryRun,
12110
- buildOnly
12111
- });
12112
- }
12113
- return loadConfig(configPath);
13530
+ const { configPath, loadedConfig, dryRun, buildOnly, noCache, packageVersion, cacheDir } = params;
13531
+ const { config, plugins } = loadedConfig ?? await loadDeployConfig({
13532
+ configPath,
13533
+ dryRun,
13534
+ buildOnly
12114
13535
  });
12115
13536
  const configDir = path.dirname(config.path);
12116
13537
  const lockfilePath = findUpSync("pnpm-lock.yaml", { cwd: configDir }) ?? findUpSync("package-lock.json", { cwd: configDir }) ?? findUpSync("yarn.lock", { cwd: configDir }) ?? findUpSync("bun.lock", { cwd: configDir });
@@ -12151,11 +13572,27 @@ async function buildDeploymentTarget(params) {
12151
13572
  bundledScripts
12152
13573
  };
12153
13574
  }
13575
+ async function loadDeployConfig(params) {
13576
+ const { configPath, dryRun, buildOnly } = params;
13577
+ return withSpan("build.loadConfig", async () => {
13578
+ const foundPath = loadConfigPath(configPath);
13579
+ if (foundPath) {
13580
+ const resolvedPath = path.resolve(process.cwd(), foundPath);
13581
+ if (fs$2.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
13582
+ configPath: resolvedPath,
13583
+ dryRun,
13584
+ buildOnly
13585
+ });
13586
+ }
13587
+ return loadConfig(configPath);
13588
+ });
13589
+ }
12154
13590
  async function buildDeploymentTargets(params) {
12155
- const { configPaths, buildTarget = buildDeploymentTarget, ...targetParams } = params;
12156
- return Promise.all(configPaths.map((configPath) => buildTarget({
13591
+ const { configPaths, loadedConfigs, buildTarget = buildDeploymentTarget, ...targetParams } = params;
13592
+ return Promise.all(configPaths.map((configPath, index) => buildTarget({
12157
13593
  ...targetParams,
12158
- configPath
13594
+ configPath,
13595
+ loadedConfig: loadedConfigs?.[index]
12159
13596
  })));
12160
13597
  }
12161
13598
  function setBundledScript(target, name, code, kind) {
@@ -12734,45 +14171,65 @@ async function validateDeploymentPlans(deployments) {
12734
14171
  }
12735
14172
  /**
12736
14173
  * Deploy the configured application to the Tailor platform.
12737
- * @param options - Options for deploy execution
14174
+ * @param options - Deploy execution options
14175
+ * @param cliContext - Global CLI arguments to preserve in recovery actions
12738
14176
  * @returns Promise that resolves to `{ bundledScripts }` when `buildOnly` is true, otherwise void
12739
14177
  */
12740
- async function deploy(options) {
14178
+ async function deployInternal(options, cliContext) {
12741
14179
  return withSpan("deploy", async (rootSpan) => {
12742
14180
  rootSpan.setAttribute("deploy.dry_run", options?.dryRun ?? false);
12743
14181
  const configPaths = parseDeployConfigPaths(options?.configPath);
12744
- const { targets, buildOnly } = await withSpan("build", async () => {
12745
- const dryRun = options?.dryRun ?? false;
12746
- const buildOnly = options?.buildOnly ?? parseBoolean(process.env.TAILOR_DEPLOY_BUILD_ONLY) === true;
14182
+ const dryRun = options?.dryRun ?? false;
14183
+ const buildOnly = options?.buildOnly ?? parseBoolean(process.env.TAILOR_DEPLOY_BUILD_ONLY) === true;
14184
+ const preflightConfigs = buildOnly ? [] : await withSpan("config.preflight", () => Promise.all(configPaths.map((configPath) => loadDeployConfig({
14185
+ configPath,
14186
+ dryRun,
14187
+ buildOnly
14188
+ }))));
14189
+ const resolvedConfigPaths = preflightConfigs.map(({ config }) => config.path);
14190
+ const workspaceContextTargets = preflightConfigs.map(({ config }) => ({
14191
+ configPath: config.path,
14192
+ applicationId: config.id ?? `name:${config.name}`
14193
+ }));
14194
+ const workspace = buildOnly ? void 0 : await resolveDeployWorkspace({
14195
+ workspaceId: options?.workspaceId,
14196
+ profile: options?.profile,
14197
+ createWorkspace: options?.createWorkspace,
14198
+ workspaceName: options?.workspaceName,
14199
+ workspaceRegion: options?.workspaceRegion,
14200
+ organizationId: options?.organizationId,
14201
+ folderId: options?.folderId,
14202
+ dryRun,
14203
+ contextTargets: workspaceContextTargets,
14204
+ deployArgs: retryDeployArgs(options, resolvedConfigPaths, cliContext),
14205
+ workspaceCommandArgs: workspaceRecoveryArgs(options, cliContext),
14206
+ workspaceCommandJson: cliContext?.json || logger.jsonMode
14207
+ });
14208
+ const targets = await withSpan("build", async () => {
12747
14209
  const noCache = options?.noCache ?? false;
12748
14210
  const packageJson = await readPackageJson();
12749
14211
  const cacheDir = path.resolve(getDistDir(), "cache");
12750
14212
  if (options?.cleanCache) {
12751
- fs$1.rmSync(cacheDir, {
14213
+ fs$2.rmSync(cacheDir, {
12752
14214
  recursive: true,
12753
14215
  force: true
12754
14216
  });
12755
14217
  logger.info("Bundle cache cleaned");
12756
14218
  }
12757
- return {
12758
- targets: await buildDeploymentTargets({
12759
- configPaths,
12760
- dryRun,
12761
- buildOnly,
12762
- noCache,
12763
- packageVersion: packageJson.version ?? "unknown",
12764
- cacheDir
12765
- }),
12766
- buildOnly
12767
- };
14219
+ return await buildDeploymentTargets({
14220
+ configPaths,
14221
+ loadedConfigs: preflightConfigs,
14222
+ dryRun,
14223
+ buildOnly,
14224
+ noCache,
14225
+ packageVersion: packageJson.version ?? "unknown",
14226
+ cacheDir
14227
+ });
12768
14228
  });
12769
14229
  if (buildOnly) return { bundledScripts: mergeBundledScripts(targets) };
12770
- assertUniqueGlobalResourceNames(targets);
12771
- const client = await initOperatorClient(await loadAccessToken({ profile: options?.profile }));
12772
- const workspaceId = await loadWorkspaceId({
12773
- workspaceId: options?.workspaceId,
12774
- profile: options?.profile
12775
- });
14230
+ assertUniqueGlobalResourceNames(targets);
14231
+ if (!workspace) throw new Error("Workspace was not resolved");
14232
+ const { client, workspaceId } = workspace;
12776
14233
  rootSpan.setAttribute("app.name", targets.map((target) => target.application.name).join(","));
12777
14234
  rootSpan.setAttribute("workspace.id", workspaceId);
12778
14235
  const deployments = await planDeploymentTargets({
@@ -12782,7 +14239,6 @@ async function deploy(options) {
12782
14239
  workspaceId,
12783
14240
  noSchemaCheck: options?.noSchemaCheck
12784
14241
  });
12785
- const dryRun = options?.dryRun ?? false;
12786
14242
  const yes = options?.yes ?? false;
12787
14243
  dropCrossDeploymentManagedDeletes(deployments);
12788
14244
  await withSpan("confirm", async () => {
@@ -12806,6 +14262,23 @@ async function deploy(options) {
12806
14262
  else logger.success("Successfully applied changes.");
12807
14263
  });
12808
14264
  }
14265
+ /**
14266
+ * Deploy using the programmatic CLI API.
14267
+ * @param options - Deploy execution options
14268
+ * @returns Deploy result
14269
+ */
14270
+ function deploy(options) {
14271
+ return deployInternal(options);
14272
+ }
14273
+ /**
14274
+ * Deploy from the command adapter while preserving global CLI arguments in recovery actions.
14275
+ * @param options - Deploy execution options
14276
+ * @param cliContext - Global CLI arguments already applied by the command runner
14277
+ * @returns Deploy result
14278
+ */
14279
+ function deployFromCLI(options, cliContext) {
14280
+ return deployInternal(options, cliContext);
14281
+ }
12809
14282
 
12810
14283
  //#endregion
12811
14284
  //#region src/cli/commands/executor/status.ts
@@ -13195,52 +14668,6 @@ const getCommand$6 = defineAppCommand({
13195
14668
  }
13196
14669
  });
13197
14670
 
13198
- //#endregion
13199
- //#region src/cli/shared/format.ts
13200
- /**
13201
- * Format a protobuf Timestamp to ISO string.
13202
- * @param timestamp - Protobuf timestamp
13203
- * @returns Date object or null if invalid
13204
- */
13205
- function formatTimestamp(timestamp) {
13206
- if (!timestamp) return null;
13207
- const date = timestampDate(timestamp);
13208
- if (Number.isNaN(date.getTime())) return null;
13209
- return date;
13210
- }
13211
- /**
13212
- * Formats a table with consistent single-line border style.
13213
- * Use this instead of importing `table` directly.
13214
- * @param data - Table data
13215
- * @param config - Table configuration
13216
- * @returns Formatted table string
13217
- */
13218
- function formatTable(data, config) {
13219
- return table(data, {
13220
- ...config,
13221
- border: getBorderCharacters("norc")
13222
- });
13223
- }
13224
- /**
13225
- * Formats a key-value table with single-line border style.
13226
- * @param data - Key-value pairs
13227
- * @returns Formatted key-value table string
13228
- */
13229
- function formatKeyValueTable(data) {
13230
- return formatTable(data, { singleLine: true });
13231
- }
13232
- /**
13233
- * Format a Date or ISO timestamp string as a human-readable relative time.
13234
- * @param value - Date object, ISO date string, or null
13235
- * @returns Relative time (e.g., "5 minutes ago") or "N/A" for null/invalid
13236
- */
13237
- function humanizeRelativeTime(value) {
13238
- if (value === null) return "N/A";
13239
- const date = value instanceof Date ? value : new Date(value);
13240
- if (Number.isNaN(date.getTime())) return typeof value === "string" ? value : "N/A";
13241
- return formatDistanceToNowStrict(date, { addSuffix: true });
13242
- }
13243
-
13244
14671
  //#endregion
13245
14672
  //#region src/cli/shared/function-execution.ts
13246
14673
  /**
@@ -14657,7 +16084,7 @@ async function listExecutors(options) {
14657
16084
  return [executors, nextPageToken];
14658
16085
  }, { limit: options?.limit })).map((e) => toExecutorListInfo(e));
14659
16086
  }
14660
- const listCommand$9 = defineAppCommand({
16087
+ const listCommand$8 = defineAppCommand({
14661
16088
  name: "list",
14662
16089
  description: "List all executors",
14663
16090
  args: z.strictObject({
@@ -15082,7 +16509,7 @@ async function listFunctionRegistries(options) {
15082
16509
  }
15083
16510
  }, { limit })).map(functionRegistryInfo);
15084
16511
  }
15085
- const listCommand$8 = defineAppCommand({
16512
+ const listCommand$7 = defineAppCommand({
15086
16513
  name: "list",
15087
16514
  description: "List function registries in a workspace",
15088
16515
  args: z.strictObject({
@@ -15106,387 +16533,6 @@ const listCommand$8 = defineAppCommand({
15106
16533
  }
15107
16534
  });
15108
16535
 
15109
- //#endregion
15110
- //#region src/cli/commands/generate/watch/index.ts
15111
- /**
15112
- * Error codes.
15113
- */
15114
- const WatcherErrorCode = {
15115
- DEPENDENCY_ANALYSIS_FAILED: "DEPENDENCY_ANALYSIS_FAILED",
15116
- FILE_WATCH_FAILED: "FILE_WATCH_FAILED",
15117
- CIRCULAR_DEPENDENCY_DETECTED: "CIRCULAR_DEPENDENCY_DETECTED",
15118
- INVALID_WATCH_GROUP: "INVALID_WATCH_GROUP",
15119
- MADGE_INITIALIZATION_FAILED: "MADGE_INITIALIZATION_FAILED"
15120
- };
15121
- /**
15122
- * Watcher-specific error.
15123
- */
15124
- var WatcherError = class extends Error {
15125
- code;
15126
- filePath;
15127
- originalError;
15128
- constructor(message, code, filePath, originalError) {
15129
- super(message);
15130
- this.code = code;
15131
- this.filePath = filePath;
15132
- this.originalError = originalError;
15133
- this.name = "WatcherError";
15134
- }
15135
- };
15136
- /**
15137
- * Creates a dependency graph manager.
15138
- * @param options - Options for madge
15139
- * @returns DependencyGraphManager instance
15140
- */
15141
- function createDependencyGraphManager(options = {}) {
15142
- const graph = /* @__PURE__ */ new Map();
15143
- let madgeInstance = null;
15144
- let madgeLoader = null;
15145
- function getMadgeLoader() {
15146
- if (madgeLoader) return madgeLoader;
15147
- const defaultExport = madgeModule.default;
15148
- if (typeof defaultExport === "function") {
15149
- madgeLoader = defaultExport;
15150
- return madgeLoader;
15151
- }
15152
- if (typeof madgeModule === "function") {
15153
- madgeLoader = madgeModule;
15154
- return madgeLoader;
15155
- }
15156
- throw new WatcherError("Failed to initialize madge analyzer: module did not export a callable function.", WatcherErrorCode.MADGE_INITIALIZATION_FAILED);
15157
- }
15158
- function traverseDependents(filePath, visited) {
15159
- if (visited.has(filePath)) return [];
15160
- visited.add(filePath);
15161
- const node = graph.get(filePath);
15162
- if (!node) return [];
15163
- const result = [];
15164
- for (const dependent of node.dependents) {
15165
- result.push(dependent);
15166
- result.push(...traverseDependents(dependent, visited));
15167
- }
15168
- return result;
15169
- }
15170
- function traverseDependencies(filePath, visited) {
15171
- if (visited.has(filePath)) return [];
15172
- visited.add(filePath);
15173
- const node = graph.get(filePath);
15174
- if (!node) return [];
15175
- const result = [];
15176
- for (const dependency of node.dependencies) {
15177
- result.push(dependency);
15178
- result.push(...traverseDependencies(dependency, visited));
15179
- }
15180
- return result;
15181
- }
15182
- function addNode(filePath) {
15183
- const absolutePath = path.resolve(filePath);
15184
- if (!graph.has(absolutePath)) graph.set(absolutePath, {
15185
- filePath: absolutePath,
15186
- dependencies: /* @__PURE__ */ new Set(),
15187
- dependents: /* @__PURE__ */ new Set()
15188
- });
15189
- }
15190
- function removeNode(filePath) {
15191
- const absolutePath = path.resolve(filePath);
15192
- const node = graph.get(absolutePath);
15193
- if (!node) return;
15194
- for (const dep of node.dependencies) {
15195
- const depNode = graph.get(dep);
15196
- if (depNode) depNode.dependents.delete(absolutePath);
15197
- }
15198
- for (const dependent of node.dependents) {
15199
- const dependentNode = graph.get(dependent);
15200
- if (dependentNode) dependentNode.dependencies.delete(absolutePath);
15201
- }
15202
- graph.delete(absolutePath);
15203
- }
15204
- function findCircularDependencies() {
15205
- if (!madgeInstance) return [];
15206
- try {
15207
- return madgeInstance.circular();
15208
- } catch (error) {
15209
- logger.warn(`Failed to detect circular dependencies: ${String(error)}`);
15210
- return [];
15211
- }
15212
- }
15213
- return {
15214
- async buildGraph(filePaths) {
15215
- try {
15216
- if (filePaths.length === 0) return;
15217
- madgeInstance = await getMadgeLoader()(filePaths, {
15218
- fileExtensions: ["ts", "js"],
15219
- excludeRegExp: [/node_modules/],
15220
- baseDir: ".",
15221
- ...options
15222
- });
15223
- const dependencyObj = madgeInstance.obj();
15224
- graph.clear();
15225
- for (const filePath of filePaths) addNode(filePath);
15226
- for (const [filePath, dependencies] of Object.entries(dependencyObj)) {
15227
- const absoluteFilePath = path.resolve(".", filePath);
15228
- const node = graph.get(absoluteFilePath);
15229
- if (!node) continue;
15230
- for (const dep of dependencies) {
15231
- const absoluteDepPath = path.resolve(".", dep);
15232
- node.dependencies.add(absoluteDepPath);
15233
- const depNode = graph.get(absoluteDepPath);
15234
- if (depNode) depNode.dependents.add(absoluteFilePath);
15235
- }
15236
- }
15237
- } catch (error) {
15238
- if (error instanceof WatcherError) throw error;
15239
- throw new WatcherError(`Failed to build dependency graph: ${error instanceof Error ? error.message : String(error)}`, WatcherErrorCode.DEPENDENCY_ANALYSIS_FAILED, void 0, error instanceof Error ? error : void 0);
15240
- }
15241
- },
15242
- getDependents(filePath) {
15243
- const visited = /* @__PURE__ */ new Set();
15244
- return traverseDependents(path.resolve(filePath), visited);
15245
- },
15246
- getDependencies(filePath) {
15247
- const visited = /* @__PURE__ */ new Set();
15248
- return traverseDependencies(path.resolve(filePath), visited);
15249
- },
15250
- findCircularDependencies,
15251
- addNode,
15252
- removeNode,
15253
- getGraphStats() {
15254
- let edgeCount = 0;
15255
- for (const node of graph.values()) edgeCount += node.dependencies.size;
15256
- return {
15257
- nodeCount: graph.size,
15258
- edgeCount,
15259
- circularDependencyCount: findCircularDependencies().length
15260
- };
15261
- }
15262
- };
15263
- }
15264
- /**
15265
- * Creates a dependency watcher.
15266
- * @param options - Watcher options
15267
- * @returns DependencyWatcher instance
15268
- */
15269
- function createDependencyWatcher(options = {}) {
15270
- let chokidarWatcher = null;
15271
- const watchGroups = /* @__PURE__ */ new Map();
15272
- const dependencyGraphManager = createDependencyGraphManager(options.madgeOptions);
15273
- let errorCallback = null;
15274
- const debounceTimers = /* @__PURE__ */ new Map();
15275
- let isInitialized = false;
15276
- const dependencyCache = /* @__PURE__ */ new Map();
15277
- const maxCacheSize = 1e3;
15278
- let signalHandlersRegistered = false;
15279
- let restartCallback = null;
15280
- function validateWatchGroup(groupId, patterns) {
15281
- if (!groupId || typeof groupId !== "string") throw new WatcherError("Group ID must be a non-empty string", WatcherErrorCode.INVALID_WATCH_GROUP);
15282
- if (!Array.isArray(patterns) || patterns.length === 0) throw new WatcherError("Patterns must be a non-empty array", WatcherErrorCode.INVALID_WATCH_GROUP);
15283
- if (watchGroups.has(groupId)) throw new WatcherError(`Watch group with ID '${groupId}' already exists`, WatcherErrorCode.INVALID_WATCH_GROUP);
15284
- }
15285
- function handleError(error) {
15286
- logger.error(`[DependencyWatcher] ${error.message} (code: ${error.code}, filePath: ${error.filePath})`);
15287
- if (errorCallback) errorCallback(error);
15288
- }
15289
- function setCacheValue(key, value) {
15290
- if (dependencyCache.size >= maxCacheSize) {
15291
- const firstKey = dependencyCache.keys().next().value;
15292
- if (firstKey) dependencyCache.delete(firstKey);
15293
- }
15294
- dependencyCache.set(key, value);
15295
- }
15296
- function findAffectedFiles(changedFile) {
15297
- return dependencyGraphManager.getDependents(changedFile);
15298
- }
15299
- function findAffectedGroups(affectedFiles) {
15300
- logger.debug(`Finding affected groups for files: ${affectedFiles.join(", ")}`);
15301
- const affectedGroupsSet = /* @__PURE__ */ new Set();
15302
- for (const [groupId, group] of watchGroups) for (const affectedFile of affectedFiles) if (group.files.has(affectedFile)) {
15303
- logger.debug(`Group ${groupId} is affected by file: ${affectedFile}`);
15304
- affectedGroupsSet.add(groupId);
15305
- break;
15306
- }
15307
- return Array.from(affectedGroupsSet);
15308
- }
15309
- function calculateImpact(filePath) {
15310
- const cacheKey = `impact:${filePath}`;
15311
- let affectedFiles = dependencyCache.get(cacheKey);
15312
- if (!affectedFiles) {
15313
- affectedFiles = findAffectedFiles(filePath);
15314
- setCacheValue(cacheKey, affectedFiles);
15315
- }
15316
- const allAffectedFiles = [filePath, ...affectedFiles];
15317
- return {
15318
- changedFile: filePath,
15319
- affectedFiles: allAffectedFiles,
15320
- affectedGroups: findAffectedGroups(allAffectedFiles)
15321
- };
15322
- }
15323
- async function updateDependencyGraph() {
15324
- const allFiles = [];
15325
- for (const group of watchGroups.values()) allFiles.push(...Array.from(group.files));
15326
- await dependencyGraphManager.buildGraph(allFiles);
15327
- dependencyCache.clear();
15328
- if (options.detectCircularDependencies) {
15329
- const circularDeps = dependencyGraphManager.findCircularDependencies();
15330
- if (circularDeps.length > 0) logger.warn(`Circular dependencies detected: ${JSON.stringify(circularDeps)}`);
15331
- }
15332
- }
15333
- async function handleFileChange(event, filePath) {
15334
- try {
15335
- const absolutePath = path.resolve(filePath);
15336
- if (event === "unlink") dependencyGraphManager.removeNode(absolutePath);
15337
- else {
15338
- dependencyGraphManager.addNode(absolutePath);
15339
- if (event === "change") await updateDependencyGraph();
15340
- }
15341
- dependencyCache.clear();
15342
- const impactResult = calculateImpact(absolutePath);
15343
- if (impactResult.affectedGroups.length > 0) {
15344
- logger.info("File change detected, restarting watch process...", { mode: "stream" });
15345
- logger.info(`Changed file: ${absolutePath}`, { mode: "stream" });
15346
- logger.info(`Affected groups: ${impactResult.affectedGroups.join(", ")}`, { mode: "stream" });
15347
- if (restartCallback) restartCallback();
15348
- } else logger.debug(`No affected groups found for file: ${absolutePath}`);
15349
- } catch (error) {
15350
- handleError(new WatcherError(`Failed to handle file change: ${error instanceof Error ? error.message : String(error)}`, WatcherErrorCode.DEPENDENCY_ANALYSIS_FAILED, filePath, error instanceof Error ? error : void 0));
15351
- }
15352
- }
15353
- function debounceFileChange(event, filePath) {
15354
- const key = `${event}:${filePath}`;
15355
- if (debounceTimers.has(key)) clearTimeout(debounceTimers.get(key));
15356
- const timer = setTimeout(() => {
15357
- handleFileChange(event, filePath);
15358
- debounceTimers.delete(key);
15359
- }, options.debounceTime || 100);
15360
- debounceTimers.set(key, timer);
15361
- }
15362
- async function stop() {
15363
- if (chokidarWatcher) {
15364
- await chokidarWatcher.close();
15365
- chokidarWatcher = null;
15366
- }
15367
- for (const timer of debounceTimers.values()) clearTimeout(timer);
15368
- debounceTimers.clear();
15369
- removeSignalHandlers();
15370
- isInitialized = false;
15371
- }
15372
- function setupSignalHandlers() {
15373
- if (signalHandlersRegistered) return;
15374
- const handleSignal = async () => {
15375
- try {
15376
- await stop();
15377
- logger.info("Watcher stopped successfully");
15378
- process.exit(0);
15379
- } catch (error) {
15380
- logger.error(`Error during shutdown: ${String(error)}`);
15381
- process.exit(0);
15382
- }
15383
- };
15384
- process.on("SIGINT", () => handleSignal());
15385
- process.on("SIGTERM", () => handleSignal());
15386
- signalHandlersRegistered = true;
15387
- }
15388
- function removeSignalHandlers() {
15389
- if (!signalHandlersRegistered) return;
15390
- process.removeAllListeners("SIGINT");
15391
- process.removeAllListeners("SIGTERM");
15392
- signalHandlersRegistered = false;
15393
- }
15394
- async function initialize() {
15395
- if (isInitialized) return;
15396
- try {
15397
- chokidarWatcher = watch([], {
15398
- ignored: /node_modules/,
15399
- persistent: true,
15400
- ignoreInitial: true,
15401
- usePolling: false,
15402
- awaitWriteFinish: {
15403
- stabilityThreshold: 100,
15404
- pollInterval: 100
15405
- },
15406
- ...options.chokidarOptions
15407
- });
15408
- chokidarWatcher.on("add", (filePath) => {
15409
- logger.debug(`File added: ${filePath}`);
15410
- debounceFileChange("add", filePath);
15411
- });
15412
- chokidarWatcher.on("change", (filePath) => {
15413
- logger.debug(`File changed: ${filePath}`);
15414
- debounceFileChange("change", filePath);
15415
- });
15416
- chokidarWatcher.on("unlink", (filePath) => {
15417
- logger.debug(`File removed: ${filePath}`);
15418
- debounceFileChange("unlink", filePath);
15419
- });
15420
- chokidarWatcher.on("error", (error) => {
15421
- logger.error(`Watcher error: ${error instanceof Error ? error.message : String(error)}`, { mode: "stream" });
15422
- handleError(new WatcherError(`File watcher error: ${error instanceof Error ? error.message : String(error)}`, WatcherErrorCode.FILE_WATCH_FAILED, void 0, error instanceof Error ? error : void 0));
15423
- });
15424
- setupSignalHandlers();
15425
- isInitialized = true;
15426
- } catch (error) {
15427
- throw new WatcherError(`Failed to initialize watcher: ${error instanceof Error ? error.message : String(error)}`, WatcherErrorCode.FILE_WATCH_FAILED, void 0, error instanceof Error ? error : void 0);
15428
- }
15429
- }
15430
- return {
15431
- initialize,
15432
- async addWatchGroup(groupId, patterns) {
15433
- validateWatchGroup(groupId, patterns);
15434
- if (!isInitialized) await initialize();
15435
- const files = /* @__PURE__ */ new Set();
15436
- for (const pattern of patterns) {
15437
- logger.log(`${styles.dim(`Watch pattern for`)} ${styles.dim(groupId + ":")} ${path.relative(process.cwd(), pattern)}`);
15438
- for await (const file of glob(pattern)) files.add(path.resolve(file));
15439
- }
15440
- const watchGroup = {
15441
- id: groupId,
15442
- patterns,
15443
- files
15444
- };
15445
- watchGroups.set(groupId, watchGroup);
15446
- if (chokidarWatcher) {
15447
- const filePaths = Array.from(files);
15448
- chokidarWatcher.add(filePaths);
15449
- }
15450
- await updateDependencyGraph();
15451
- },
15452
- async removeWatchGroup(groupId) {
15453
- const watchGroup = watchGroups.get(groupId);
15454
- if (!watchGroup) return;
15455
- if (chokidarWatcher) chokidarWatcher.unwatch(watchGroup.patterns);
15456
- for (const filePath of watchGroup.files) dependencyGraphManager.removeNode(filePath);
15457
- watchGroups.delete(groupId);
15458
- dependencyCache.clear();
15459
- },
15460
- async start() {
15461
- if (!isInitialized) await initialize();
15462
- await updateDependencyGraph();
15463
- },
15464
- stop,
15465
- onError(callback) {
15466
- errorCallback = callback;
15467
- },
15468
- updateDependencyGraph,
15469
- calculateImpact,
15470
- detectCircularDependencies() {
15471
- return dependencyGraphManager.findCircularDependencies();
15472
- },
15473
- getWatchStatus() {
15474
- let fileCount = 0;
15475
- for (const group of watchGroups.values()) fileCount += group.files.size;
15476
- const stats = dependencyGraphManager.getGraphStats();
15477
- return {
15478
- isWatching: isInitialized && chokidarWatcher !== null,
15479
- groupCount: watchGroups.size,
15480
- fileCount,
15481
- dependencyNodeCount: stats.nodeCount
15482
- };
15483
- },
15484
- setRestartCallback(callback) {
15485
- restartCallback = callback;
15486
- }
15487
- };
15488
- }
15489
-
15490
16536
  //#endregion
15491
16537
  //#region src/cli/commands/generate/service.ts
15492
16538
  /**
@@ -15500,13 +16546,12 @@ function createDependencyWatcher(options = {}) {
15500
16546
  function createGenerationManager(params) {
15501
16547
  const { application, config, pluginManager } = params;
15502
16548
  const baseDir = path.join(getDistDir(), "generated");
15503
- fs$1.mkdirSync(baseDir, { recursive: true });
16549
+ fs$2.mkdirSync(baseDir, { recursive: true });
15504
16550
  const services = {
15505
16551
  tailordb: {},
15506
16552
  resolver: {},
15507
16553
  executor: {}
15508
16554
  };
15509
- let watcher = null;
15510
16555
  const generationPlugins = pluginManager?.getPluginsWithGenerationHooks() ?? [];
15511
16556
  function getAuthInput() {
15512
16557
  const authService = application.authService;
@@ -15598,24 +16643,20 @@ function createGenerationManager(params) {
15598
16643
  * Each hook runs at its natural pipeline phase, ensuring outputs from earlier
15599
16644
  * phases are available when later phases load resolvers/executors.
15600
16645
  * @param hookName - Name of the hook to call
15601
- * @param watch - Whether running in watch mode (suppresses throws)
15602
16646
  */
15603
- async function runPluginHook(hookName, watch) {
16647
+ async function runPluginHook(hookName) {
15604
16648
  const plugins = generationPlugins.filter((p) => p[hookName] != null);
15605
16649
  if (plugins.length === 0) return;
15606
- const results = await Promise.allSettled(plugins.map(async (plugin) => {
16650
+ const failures = (await Promise.allSettled(plugins.map(async (plugin) => {
15607
16651
  try {
15608
16652
  await runPluginPhaseHook(plugin, hookName);
15609
16653
  } catch (error) {
15610
16654
  logger.error(`Error processing plugin ${styles.bold(plugin.id)} (${hookName})`);
15611
16655
  logger.error(String(error));
15612
- if (!watch) throw error;
16656
+ throw error;
15613
16657
  }
15614
- }));
15615
- if (!watch) {
15616
- const failures = results.filter((r) => r.status === "rejected");
15617
- if (failures.length > 0) throw new AggregateError(failures.map((f) => f.reason));
15618
- }
16658
+ }))).filter((r) => r.status === "rejected");
16659
+ if (failures.length > 0) throw new AggregateError(failures.map((f) => f.reason));
15619
16660
  }
15620
16661
  /**
15621
16662
  * Write generated files to disk.
@@ -15624,14 +16665,14 @@ function createGenerationManager(params) {
15624
16665
  */
15625
16666
  async function writeGeneratedFiles(sourceId, result) {
15626
16667
  await Promise.all(result.files.map(async (file) => {
15627
- fs$1.mkdirSync(path.dirname(file.path), { recursive: true });
16668
+ fs$2.mkdirSync(path.dirname(file.path), { recursive: true });
15628
16669
  return new Promise((resolve, reject) => {
15629
- if (file.skipIfExists && fs$1.existsSync(file.path)) {
16670
+ if (file.skipIfExists && fs$2.existsSync(file.path)) {
15630
16671
  const relativePath = path.relative(process.cwd(), file.path);
15631
16672
  logger.debug(`${sourceId} | skip existing: ${relativePath}`);
15632
16673
  return resolve();
15633
16674
  }
15634
- fs$1.writeFile(file.path, file.content, (err) => {
16675
+ fs$2.writeFile(file.path, file.content, (err) => {
15635
16676
  if (err) {
15636
16677
  const relativePath = path.relative(process.cwd(), file.path);
15637
16678
  logger.error(`Error writing file ${styles.bold(relativePath)}`);
@@ -15640,7 +16681,7 @@ function createGenerationManager(params) {
15640
16681
  } else {
15641
16682
  const relativePath = path.relative(process.cwd(), file.path);
15642
16683
  logger.log(`${sourceId} | generate: ${styles.success(relativePath)}`);
15643
- if (file.executable) fs$1.chmod(file.path, 493, (chmodErr) => {
16684
+ if (file.executable) fs$2.chmod(file.path, 493, (chmodErr) => {
15644
16685
  if (chmodErr) {
15645
16686
  const relativePath = path.relative(process.cwd(), file.path);
15646
16687
  logger.error(`Error setting executable permission on ${styles.bold(relativePath)}`);
@@ -15654,35 +16695,11 @@ function createGenerationManager(params) {
15654
16695
  });
15655
16696
  }));
15656
16697
  }
15657
- async function restartWatchProcess() {
15658
- logger.newline();
15659
- logger.info("Restarting watch process to clear module cache...", { mode: "stream" });
15660
- logger.newline();
15661
- if (watcher) await watcher.stop();
15662
- const args = process.argv.slice(2);
15663
- const env = {
15664
- ...process.env,
15665
- __TAILOR_WATCH_GENERATION: (parseInt(process.env.__TAILOR_WATCH_GENERATION || "0", 10) + 1).toString()
15666
- };
15667
- const child = spawn(assertDefined(process.argv[0], "argv[0] missing"), [assertDefined(process.argv[1], "argv[1] missing"), ...args], {
15668
- stdio: "inherit",
15669
- env,
15670
- detached: false
15671
- });
15672
- const forwardSignal = (signal) => {
15673
- child.kill(signal);
15674
- };
15675
- process.on("SIGINT", forwardSignal);
15676
- process.on("SIGTERM", forwardSignal);
15677
- child.on("exit", (code) => {
15678
- process.exit(code || 0);
15679
- });
15680
- }
15681
16698
  return {
15682
16699
  application,
15683
16700
  baseDir,
15684
16701
  services,
15685
- async generate(watch) {
16702
+ async generate() {
15686
16703
  logger.newline();
15687
16704
  logger.log(`Generation for application: ${styles.highlight(application.config.name)}`);
15688
16705
  const app = application;
@@ -15702,7 +16719,7 @@ function createGenerationManager(params) {
15702
16719
  } catch (error) {
15703
16720
  logger.error(`Error loading types for TailorDB service ${styles.bold(namespace)}`);
15704
16721
  logger.error(String(error));
15705
- if (!watch) throw error;
16722
+ throw error;
15706
16723
  }
15707
16724
  });
15708
16725
  }
@@ -15711,14 +16728,17 @@ function createGenerationManager(params) {
15711
16728
  } catch (error) {
15712
16729
  logger.error("Error validating TailorDB type names");
15713
16730
  logger.error(String(error));
15714
- if (!watch) throw error;
16731
+ throw error;
15715
16732
  }
15716
16733
  });
15717
16734
  const { pluginExecutorFiles, executorService } = await withSpan("generate.pluginFiles", async () => {
15718
16735
  const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, app.tailorDBServices, config.path);
15719
16736
  return {
15720
16737
  pluginExecutorFiles,
15721
- executorService: app.executorService ?? (pluginExecutorFiles.length > 0 ? createExecutorService({ config: { files: [] } }) : void 0)
16738
+ executorService: app.executorService ?? (pluginExecutorFiles.length > 0 ? createExecutorService({
16739
+ config: { files: [] },
16740
+ baseDir: path.dirname(config.path)
16741
+ }) : void 0)
15722
16742
  };
15723
16743
  });
15724
16744
  if (app.authService) {
@@ -15728,7 +16748,7 @@ function createGenerationManager(params) {
15728
16748
  if (app.tailorDBServices.length > 0 || pluginExecutorFiles.length > 0) logger.newline();
15729
16749
  if (generationPlugins.some((p) => p.onTailorDBReady != null)) {
15730
16750
  await withSpan("generate.onTailorDBReady", async () => {
15731
- await runPluginHook("onTailorDBReady", watch);
16751
+ await runPluginHook("onTailorDBReady");
15732
16752
  });
15733
16753
  logger.newline();
15734
16754
  }
@@ -15746,14 +16766,14 @@ function createGenerationManager(params) {
15746
16766
  } catch (error) {
15747
16767
  logger.error(`Error loading resolvers for Resolver service ${styles.bold(namespace)}`);
15748
16768
  logger.error(String(error));
15749
- if (!watch) throw error;
16769
+ throw error;
15750
16770
  }
15751
16771
  });
15752
16772
  }
15753
16773
  });
15754
16774
  if (generationPlugins.some((p) => p.onResolverReady != null)) {
15755
16775
  await withSpan("generate.onResolversReady", async () => {
15756
- await runPluginHook("onResolverReady", watch);
16776
+ await runPluginHook("onResolverReady");
15757
16777
  });
15758
16778
  logger.newline();
15759
16779
  }
@@ -15769,42 +16789,23 @@ function createGenerationManager(params) {
15769
16789
  });
15770
16790
  if (generationPlugins.some((p) => p.onExecutorReady != null)) {
15771
16791
  await withSpan("generate.onExecutorsReady", async () => {
15772
- await runPluginHook("onExecutorReady", watch);
16792
+ await runPluginHook("onExecutorReady");
15773
16793
  });
15774
16794
  logger.newline();
15775
16795
  }
15776
- },
15777
- async watch() {
15778
- watcher = createDependencyWatcher();
15779
- watcher.setRestartCallback(() => {
15780
- restartWatchProcess();
15781
- });
15782
- await watcher.addWatchGroup("Config", [config.path]);
15783
- const app = application;
15784
- for (const db of app.tailorDBServices) {
15785
- const dbNamespace = db.namespace;
15786
- await watcher.addWatchGroup(`TailorDB/${dbNamespace}`, db.config.files);
15787
- }
15788
- for (const resolverService of app.resolverServices) {
15789
- const resolverNamespace = resolverService.namespace;
15790
- await watcher.addWatchGroup(`Resolver/${resolverNamespace}`, resolverService["config"].files);
15791
- }
15792
- await new Promise(() => {});
15793
16796
  }
15794
16797
  };
15795
16798
  }
15796
16799
  /**
15797
16800
  * Run code generation using the Tailor configuration.
15798
16801
  * @param options - Generation options
15799
- * @returns Promise that resolves when generation (and watch, if enabled) completes
16802
+ * @returns Promise that resolves when generation completes
15800
16803
  */
15801
16804
  async function generate$1(options) {
15802
16805
  return withSpan("generate", async (rootSpan) => {
15803
16806
  const { config, plugins } = await withSpan("generate.loadConfig", async () => {
15804
16807
  return loadConfig(options?.configPath);
15805
16808
  });
15806
- const watch = options?.watch ?? false;
15807
- rootSpan.setAttribute("generate.watch", watch);
15808
16809
  await withSpan("generate.generateUserTypes", async () => generateUserTypes({
15809
16810
  config,
15810
16811
  configPath: config.path
@@ -15816,13 +16817,11 @@ async function generate$1(options) {
15816
16817
  pluginManager
15817
16818
  });
15818
16819
  rootSpan.setAttribute("app.name", application.config.name);
15819
- const manager = createGenerationManager({
16820
+ await createGenerationManager({
15820
16821
  application,
15821
16822
  config,
15822
16823
  pluginManager
15823
- });
15824
- await manager.generate(watch);
15825
- if (watch) await manager.watch();
16824
+ }).generate();
15826
16825
  });
15827
16826
  }
15828
16827
 
@@ -15872,7 +16871,7 @@ async function listMachineUsers(options) {
15872
16871
  return [machineUsers, nextPageToken];
15873
16872
  }, { limit: options?.limit })).map(machineUserInfo);
15874
16873
  }
15875
- const listCommand$7 = defineAppCommand({
16874
+ const listCommand$6 = defineAppCommand({
15876
16875
  name: "list",
15877
16876
  description: "List all machine users in the application.",
15878
16877
  args: z.strictObject({
@@ -16088,7 +17087,7 @@ async function listOAuth2Clients(options) {
16088
17087
  return [oauth2Clients, nextPageToken];
16089
17088
  }, { limit: options?.limit })).map(toOAuth2ClientInfo);
16090
17089
  }
16091
- const listCommand$6 = defineAppCommand({
17090
+ const listCommand$5 = defineAppCommand({
16092
17091
  name: "list",
16093
17092
  description: "List all OAuth2 clients in the application.",
16094
17093
  args: z.strictObject({
@@ -16158,7 +17157,7 @@ async function createFolder(options) {
16158
17157
  if (!response.folder) throw new Error("Failed to create folder.");
16159
17158
  return folderInfo(response.folder);
16160
17159
  }
16161
- const createCommand$1 = defineAppCommand({
17160
+ const createCommand = defineAppCommand({
16162
17161
  name: "create",
16163
17162
  description: "Create a new folder in an organization.",
16164
17163
  args: z.strictObject({
@@ -16306,7 +17305,7 @@ async function listFolders(options) {
16306
17305
  return [response.folders, response.nextPageToken];
16307
17306
  }, { limit })).map(folderListInfo);
16308
17307
  }
16309
- const listCommand$5 = defineAppCommand({
17308
+ const listCommand$4 = defineAppCommand({
16310
17309
  name: "list",
16311
17310
  description: "List folders in an organization.",
16312
17311
  args: z.strictObject({
@@ -16415,7 +17414,7 @@ async function listOrganizations(options) {
16415
17414
  if (limit !== void 0) return results.slice(0, limit);
16416
17415
  return results;
16417
17416
  }
16418
- const listCommand$4 = defineAppCommand({
17417
+ const listCommand$3 = defineAppCommand({
16419
17418
  name: "list",
16420
17419
  description: "List organizations you belong to.",
16421
17420
  args: z.strictObject({ limit: arg(positiveIntArg.optional(), {
@@ -16726,7 +17725,7 @@ const removeCommand$1 = defineAppCommand({
16726
17725
  //#region src/cli/shared/beta.ts
16727
17726
  /**
16728
17727
  * Warn that a feature is in beta.
16729
- * @param {string} featureName - Name of the beta feature (e.g., "tailordb erd", "tailordb migration")
17728
+ * @param {string} featureName - Name of the beta feature (e.g., "tailordb migration")
16730
17729
  */
16731
17730
  function logBetaWarning(featureName) {
16732
17731
  logger.warn(`The '${featureName}' command is a beta feature and may introduce breaking changes in future releases.`);
@@ -16757,89 +17756,11 @@ function extractAllNamespaces(config) {
16757
17756
  function extractOwnedNamespaces(config) {
16758
17757
  const namespaces = /* @__PURE__ */ new Set();
16759
17758
  if (config.db) for (const [namespaceName, nsConfig] of Object.entries(config.db)) {
16760
- if ("external" in nsConfig) continue;
16761
- namespaces.add(namespaceName);
16762
- }
16763
- return Array.from(namespaces);
16764
- }
16765
-
16766
- //#endregion
16767
- //#region src/cli/commands/workspace/transform.ts
16768
- const workspaceInfo = (workspace, folderName) => {
16769
- const info = {
16770
- id: workspace.id,
16771
- name: workspace.name,
16772
- region: workspace.region,
16773
- createdAt: formatTimestamp(workspace.createTime),
16774
- updatedAt: formatTimestamp(workspace.updateTime)
16775
- };
16776
- return folderName ? {
16777
- ...info,
16778
- folderName
16779
- } : info;
16780
- };
16781
- const workspaceDetails = (workspace, folderName) => {
16782
- return {
16783
- ...workspaceInfo(workspace, folderName),
16784
- deleteProtection: workspace.deleteProtection,
16785
- organizationId: workspace.organizationId,
16786
- folderId: workspace.folderId
16787
- };
16788
- };
16789
- async function resolveWorkspaceFolderName(client, workspace) {
16790
- if (!workspace.organizationId || !workspace.folderId) return void 0;
16791
- try {
16792
- return (await client.getOrganizationFolder({
16793
- organizationId: workspace.organizationId,
16794
- folderId: workspace.folderId
16795
- })).folder?.name || void 0;
16796
- } catch (error) {
16797
- if (error instanceof ConnectError && (error.code === Code.NotFound || error.code === Code.PermissionDenied)) return;
16798
- logger.warn(`Failed to resolve workspace folder name: ${error}`);
16799
- return;
16800
- }
16801
- }
16802
- function createWorkspaceFolderNameResolver(client) {
16803
- const cache = /* @__PURE__ */ new Map();
16804
- return (workspace) => {
16805
- if (!workspace.organizationId || !workspace.folderId) return Promise.resolve(void 0);
16806
- const cacheKey = `${workspace.organizationId}/${workspace.folderId}`;
16807
- const cached = cache.get(cacheKey);
16808
- if (cached) return cached;
16809
- const promise = resolveWorkspaceFolderName(client, workspace);
16810
- cache.set(cacheKey, promise);
16811
- return promise;
16812
- };
16813
- }
16814
- async function workspaceInfoWithFolderName(client, workspace) {
16815
- const folderName = await resolveWorkspaceFolderName(client, workspace);
16816
- return workspaceInfo(workspace, folderName);
16817
- }
16818
- async function workspaceDetailsWithFolderName(client, workspace) {
16819
- const folderName = await resolveWorkspaceFolderName(client, workspace);
16820
- return workspaceDetails(workspace, folderName);
16821
- }
16822
- async function workspaceInfosWithFolderNames(client, workspaces) {
16823
- const resolveFolderName = createWorkspaceFolderNameResolver(client);
16824
- const limit = pLimit(5);
16825
- return Promise.all(workspaces.map((workspace) => limit(async () => workspaceInfo(workspace, await resolveFolderName(workspace)))));
16826
- }
16827
- function workspaceDisplayName(workspace) {
16828
- return workspace.folderName ? `${workspace.folderName}/${workspace.name}` : workspace.name;
16829
- }
16830
- function createWorkspaceNameTransformer(nameKey, folderNameKey) {
16831
- return (value, item) => {
16832
- const workspace = item;
16833
- const name = workspace[nameKey];
16834
- const folderName = workspace[folderNameKey];
16835
- if (typeof name === "string" && typeof folderName === "string") return workspaceDisplayName({
16836
- name,
16837
- folderName
16838
- });
16839
- return String(value ?? "");
16840
- };
17759
+ if ("external" in nsConfig) continue;
17760
+ namespaces.add(namespaceName);
17761
+ }
17762
+ return Array.from(namespaces);
16841
17763
  }
16842
- const workspaceNameTransformer = createWorkspaceNameTransformer("name", "folderName");
16843
17764
 
16844
17765
  //#endregion
16845
17766
  //#region src/cli/commands/show.ts
@@ -16925,6 +17846,203 @@ const showCommand = defineAppCommand({
16925
17846
  }
16926
17847
  });
16927
17848
 
17849
+ //#endregion
17850
+ //#region src/cli/shared/progress.ts
17851
+ /**
17852
+ * Create a simple progress reporter that writes updates to stderr.
17853
+ * @param label - Label to prefix progress output
17854
+ * @param total - Total number of steps
17855
+ * @returns Progress helpers
17856
+ */
17857
+ function createProgress(label, total) {
17858
+ let current = 0;
17859
+ const update = () => {
17860
+ current += 1;
17861
+ const percent = Math.round(current / total * 100);
17862
+ process.stderr.write(`\r${label} ${current}/${total} (${percent}%)`);
17863
+ };
17864
+ const finish = () => {
17865
+ process.stderr.write("\n");
17866
+ };
17867
+ return {
17868
+ update,
17869
+ finish
17870
+ };
17871
+ }
17872
+ /**
17873
+ * Wrap a promise with a timeout, rejecting if the timeout elapses first.
17874
+ * @template T
17875
+ * @param p - Promise to await
17876
+ * @param ms - Timeout in milliseconds
17877
+ * @param message - Error message on timeout
17878
+ * @returns Result of the original promise if it completes in time
17879
+ */
17880
+ async function withTimeout(p, ms, message) {
17881
+ return await Promise.race([p, setTimeout$2(ms).then(() => {
17882
+ throw new Error(message);
17883
+ })]);
17884
+ }
17885
+
17886
+ //#endregion
17887
+ //#region src/cli/commands/staticwebsite/deploy.ts
17888
+ const CHUNK_SIZE = 64 * 1024;
17889
+ const IGNORED_FILES = /* @__PURE__ */ new Set([
17890
+ ".DS_Store",
17891
+ "thumbs.db",
17892
+ "desktop.ini"
17893
+ ]);
17894
+ function shouldIgnoreFile(filePath) {
17895
+ const fileName = path.basename(filePath).toLowerCase();
17896
+ return IGNORED_FILES.has(fileName);
17897
+ }
17898
+ /**
17899
+ * Deploy a static website by creating a deployment, uploading files, and publishing it.
17900
+ * @param client - Operator client instance
17901
+ * @param workspaceId - Workspace ID
17902
+ * @param name - Static website name
17903
+ * @param distDir - Directory containing static site files
17904
+ * @param showProgress - Whether to show upload progress
17905
+ * @returns Deployment result with URL and skipped files
17906
+ */
17907
+ async function deployStaticWebsite(client, workspaceId, name, distDir, showProgress = true) {
17908
+ const { deploymentId } = await client.createDeployment({
17909
+ workspaceId,
17910
+ name
17911
+ });
17912
+ if (!deploymentId) throw new Error("createDeployment returned empty deploymentId");
17913
+ const skippedFiles = await uploadDirectory(client, workspaceId, deploymentId, distDir, showProgress);
17914
+ const { url } = await client.publishDeployment({
17915
+ workspaceId,
17916
+ deploymentId
17917
+ });
17918
+ if (!url) throw new Error("publishDeployment returned empty url");
17919
+ return {
17920
+ url,
17921
+ skippedFiles
17922
+ };
17923
+ }
17924
+ async function uploadDirectory(client, workspaceId, deploymentId, rootDir, showProgress) {
17925
+ const files = await collectFiles(rootDir);
17926
+ if (files.length === 0) {
17927
+ logger.warn(`No files found under ${rootDir}`);
17928
+ return [];
17929
+ }
17930
+ const limit = pLimit(5);
17931
+ const total = files.length;
17932
+ const progress = showProgress ? createProgress("Uploading files", total) : void 0;
17933
+ const skippedFiles = [];
17934
+ await Promise.all(files.map((relativePath) => limit(async () => {
17935
+ await uploadSingleFile(client, workspaceId, deploymentId, rootDir, relativePath, skippedFiles);
17936
+ if (progress) progress.update();
17937
+ })));
17938
+ if (progress) progress.finish();
17939
+ return skippedFiles;
17940
+ }
17941
+ /**
17942
+ * Recursively collect all deployable files under the given directory.
17943
+ * @param rootDir - Root directory to scan
17944
+ * @param currentDir - Current relative directory (for recursion)
17945
+ * @returns List of file paths relative to rootDir
17946
+ */
17947
+ async function collectFiles(rootDir, currentDir = "") {
17948
+ const dirPath = path.join(rootDir, currentDir);
17949
+ const entries = await fs$1.promises.readdir(dirPath, { withFileTypes: true });
17950
+ const files = [];
17951
+ for (const entry of entries) {
17952
+ const rel = path.join(currentDir, entry.name);
17953
+ if (entry.isDirectory()) {
17954
+ const sub = await collectFiles(rootDir, rel);
17955
+ files.push(...sub);
17956
+ } else if (entry.isFile() && !entry.isSymbolicLink() && !shouldIgnoreFile(rel)) files.push(rel);
17957
+ }
17958
+ return files;
17959
+ }
17960
+ async function uploadSingleFile(client, workspaceId, deploymentId, rootDir, filePath, skippedFiles) {
17961
+ const absPath = path.join(rootDir, filePath);
17962
+ const mime = lookup(filePath);
17963
+ if (!mime) {
17964
+ skippedFiles.push(`${filePath} (unsupported content type; no MIME mapping found)`);
17965
+ return;
17966
+ }
17967
+ const contentType = mime;
17968
+ const readStream = fs$1.createReadStream(absPath, { highWaterMark: CHUNK_SIZE });
17969
+ async function* requestStream() {
17970
+ yield { payload: {
17971
+ case: "initialMetadata",
17972
+ value: {
17973
+ workspaceId,
17974
+ deploymentId,
17975
+ filePath,
17976
+ contentType
17977
+ }
17978
+ } };
17979
+ for await (const chunk of readStream) yield { payload: {
17980
+ case: "chunkData",
17981
+ value: chunk
17982
+ } };
17983
+ }
17984
+ async function uploadWithLogging() {
17985
+ try {
17986
+ await client.uploadFile(requestStream());
17987
+ } catch (error) {
17988
+ if (error instanceof ConnectError && error.code === Code.InvalidArgument) {
17989
+ skippedFiles.push(`${filePath} (server rejected file as invalid: ${error.message})`);
17990
+ return;
17991
+ }
17992
+ throw error;
17993
+ }
17994
+ }
17995
+ await withTimeout(uploadWithLogging(), 2 * 6e4, `Upload timed out for "${filePath}"`);
17996
+ }
17997
+ /**
17998
+ * Log skipped files after a deployment, including reasons for skipping.
17999
+ * @param skippedFiles - List of skipped file descriptions
18000
+ */
18001
+ function logSkippedFiles(skippedFiles) {
18002
+ if (skippedFiles.length === 0) return;
18003
+ logger.warn("Deployment completed, but some files failed to upload. These files may have unsupported content types or other validation issues. Please review the list below:");
18004
+ for (const file of skippedFiles) logger.log(` - ${file}`);
18005
+ }
18006
+ const deployCommand = defineAppCommand({
18007
+ name: "deploy",
18008
+ description: "Deploy a static website from a local build directory.",
18009
+ args: z.strictObject({
18010
+ ...workspaceArgs,
18011
+ name: arg(z.string(), {
18012
+ alias: "n",
18013
+ description: "Static website name"
18014
+ }),
18015
+ dir: arg(z.string(), {
18016
+ alias: "d",
18017
+ description: "Path to the static website files",
18018
+ completion: { type: "directory" }
18019
+ })
18020
+ }),
18021
+ run: async (args) => {
18022
+ await assertWritable({ profile: args.profile });
18023
+ logger.info(`Deploying static website "${args.name}" from directory: ${args.dir}`);
18024
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
18025
+ const name = args.name;
18026
+ const dir = path.resolve(process.cwd(), args.dir);
18027
+ const workspaceId = await loadWorkspaceId({
18028
+ workspaceId: args["workspace-id"],
18029
+ profile: args.profile
18030
+ });
18031
+ if (!fs$1.existsSync(dir) || !fs$1.statSync(dir).isDirectory()) throw new Error(`Directory not found or not a directory: ${dir}`);
18032
+ const { url, skippedFiles } = await withTimeout(deployStaticWebsite(client, workspaceId, name, dir, !args.json), 10 * 6e4, "Deployment timed out after 10 minutes.");
18033
+ if (args.json) logger.out({
18034
+ name,
18035
+ workspaceId,
18036
+ url,
18037
+ skippedFiles
18038
+ });
18039
+ else {
18040
+ logger.success(`Static website "${name}" deployed successfully. URL: ${url}`);
18041
+ logSkippedFiles(skippedFiles);
18042
+ }
18043
+ }
18044
+ });
18045
+
16928
18046
  //#endregion
16929
18047
  //#region src/cli/shared/editor.ts
16930
18048
  const DEFAULT_EDITOR = "editor";
@@ -17481,7 +18599,7 @@ function generateChangeScript(change) {
17481
18599
  * @returns {Promise<void>}
17482
18600
  */
17483
18601
  async function handleInitOption(namespaces, skipConfirmation) {
17484
- const existingDirs = namespaces.filter(({ migrationsDir }) => fs$1.existsSync(migrationsDir));
18602
+ const existingDirs = namespaces.filter(({ migrationsDir }) => fs$2.existsSync(migrationsDir));
17485
18603
  if (existingDirs.length === 0) {
17486
18604
  logger.info("No existing migration directories found.");
17487
18605
  return;
@@ -17531,7 +18649,7 @@ async function generate(options) {
17531
18649
  if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
17532
18650
  let pluginManager;
17533
18651
  if (plugins.length > 0) pluginManager = new PluginManager(plugins);
17534
- const { defineApplication } = await import("./application-CUkjzfij.mjs");
18652
+ const { defineApplication } = await import("./application-OM0taSPn.mjs");
17535
18653
  const application = defineApplication({
17536
18654
  config,
17537
18655
  pluginManager
@@ -17891,7 +19009,7 @@ async function listWorkflows(options) {
17891
19009
  return [workflows, nextPageToken];
17892
19010
  }, { limit: options?.limit })).map(toWorkflowListInfo);
17893
19011
  }
17894
- const listCommand$3 = defineAppCommand({
19012
+ const listCommand$2 = defineAppCommand({
17895
19013
  name: "list",
17896
19014
  description: "List all workflows in the workspace.",
17897
19015
  args: z.strictObject({
@@ -18224,7 +19342,7 @@ async function listApps(options) {
18224
19342
  return [applications, nextPageToken];
18225
19343
  }, { limit })).map(appInfo);
18226
19344
  }
18227
- const listCommand$2 = defineAppCommand({
19345
+ const listCommand$1 = defineAppCommand({
18228
19346
  name: "list",
18229
19347
  description: "List applications in a workspace",
18230
19348
  args: z.strictObject({
@@ -18247,153 +19365,6 @@ const listCommand$2 = defineAppCommand({
18247
19365
  }
18248
19366
  });
18249
19367
 
18250
- //#endregion
18251
- //#region src/cli/commands/workspace/create.ts
18252
- /**
18253
- * Schema for workspace creation options
18254
- * - name: 3-63 chars, lowercase alphanumeric and hyphens, cannot start/end with hyphen
18255
- * - organizationId, folderId: optional UUIDs
18256
- */
18257
- const createWorkspaceOptionsSchema = z.object({
18258
- 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"),
18259
- region: z.string(),
18260
- deleteProtection: z.boolean().optional(),
18261
- organizationId: z.uuid().optional(),
18262
- folderId: z.uuid().optional()
18263
- });
18264
- const validateRegion = async (region, client) => {
18265
- const availableRegions = await client.listAvailableWorkspaceRegions({});
18266
- if (!availableRegions.regions.includes(region)) throw new Error(`Region must be one of: ${availableRegions.regions.join(", ")}.`);
18267
- };
18268
- function profilePlatformSettings(platformConfig) {
18269
- const hasOAuth2ClientId = platformConfig?.oauth2ClientId || process.env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID;
18270
- const hasConsoleUrl = platformConfig?.consoleUrl || process.env.TAILOR_PLATFORM_CONSOLE_URL;
18271
- return {
18272
- ...isDefaultPlatform(platformConfig) ? {} : { platform_url: getPlatformBaseUrl(platformConfig) },
18273
- ...hasOAuth2ClientId ? { oauth2_client_id: getOAuth2ClientId(platformConfig) } : {},
18274
- ...hasConsoleUrl ? { console_url: getConsoleBaseUrl(platformConfig) } : {}
18275
- };
18276
- }
18277
- /**
18278
- * Create a new workspace with the given options.
18279
- * @param options - Workspace creation options
18280
- * @returns Created workspace info
18281
- */
18282
- async function createWorkspace(options) {
18283
- const result = createWorkspaceOptionsSchema.safeParse(options);
18284
- if (!result.success) throw new Error(assertDefined(result.error.issues[0], "Zod returned no issues").message);
18285
- const validated = result.data;
18286
- const client = await initOperatorClient(await loadAccessToken());
18287
- await validateRegion(validated.region, client);
18288
- return workspaceInfoWithFolderName(client, assertDefined((await client.createWorkspace({
18289
- workspaceName: validated.name,
18290
- workspaceRegion: validated.region,
18291
- deleteProtection: validated.deleteProtection ?? false,
18292
- organizationId: validated.organizationId,
18293
- folderId: validated.folderId
18294
- })).workspace, "createWorkspace response missing workspace"));
18295
- }
18296
- const createCommand = defineAppCommand({
18297
- name: "create",
18298
- description: "Create a new Tailor Platform workspace.",
18299
- args: z.strictObject({
18300
- name: arg(z.string(), {
18301
- alias: "n",
18302
- description: "Workspace name"
18303
- }),
18304
- region: arg(z.string(), {
18305
- alias: "r",
18306
- description: "Workspace region (us-west, asia-northeast)"
18307
- }),
18308
- "delete-protection": arg(z.boolean().default(false), {
18309
- alias: "d",
18310
- description: "Enable delete protection"
18311
- }),
18312
- "organization-id": arg(z.string().optional(), {
18313
- alias: "o",
18314
- description: "Organization ID to workspace associate with",
18315
- env: "TAILOR_PLATFORM_ORGANIZATION_ID"
18316
- }),
18317
- "folder-id": arg(z.string().optional(), {
18318
- alias: "f",
18319
- description: "Folder ID to workspace associate with",
18320
- env: "TAILOR_PLATFORM_FOLDER_ID"
18321
- }),
18322
- "profile-name": arg(z.string().optional(), {
18323
- alias: "p",
18324
- description: "Profile name to create"
18325
- }),
18326
- "profile-user": arg(z.string().optional(), { description: "User email address or machine user client ID for the profile (defaults to current user)" }),
18327
- permission: arg(z.enum(["write", "read"]).default("write"), { description: "Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active." })
18328
- }),
18329
- run: async (args) => {
18330
- await assertWritable();
18331
- const profileName = args["profile-name"];
18332
- let profileSetup;
18333
- if (profileName) {
18334
- const config = await readPlatformConfig();
18335
- if (config.profiles[profileName]) throw new Error(`Profile "${profileName}" already exists.`);
18336
- const activeProfileName = process.env.TAILOR_PLATFORM_PROFILE;
18337
- const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
18338
- const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
18339
- const profileUser = args["profile-user"] || activeProfileEntry?.user || config.current_user;
18340
- if (!profileUser) throw new Error("Current user not found. Please login or specify --profile-user to create a profile.");
18341
- const resolvedProfileUser = resolveConfigUser(config, profileUser, platformConfig);
18342
- if (!resolvedProfileUser) throw new Error(`User "${profileUser}" not found.\nPlease verify your user name and login using 'tailor login' command.`);
18343
- profileSetup = {
18344
- name: profileName,
18345
- user: resolvedProfileUser,
18346
- platformSettings: profilePlatformSettings(platformConfig)
18347
- };
18348
- }
18349
- const workspace = await createWorkspace({
18350
- name: args.name,
18351
- region: args.region,
18352
- deleteProtection: args["delete-protection"],
18353
- organizationId: args["organization-id"],
18354
- folderId: args["folder-id"]
18355
- });
18356
- let profileInfo;
18357
- if (profileSetup) {
18358
- const config = await readPlatformConfig();
18359
- const platformSettings = profileSetup.platformSettings;
18360
- config.profiles[profileSetup.name] = {
18361
- user: profileSetup.user,
18362
- workspace_id: workspace.id,
18363
- ...args.permission === "read" ? { readonly: true } : {},
18364
- ...platformSettings
18365
- };
18366
- writePlatformConfig(config);
18367
- profileInfo = {
18368
- name: profileSetup.name,
18369
- user: profileSetup.user,
18370
- workspaceId: workspace.id,
18371
- permission: args.permission,
18372
- ...platformSettings.platform_url ? { platformUrl: platformSettings.platform_url } : {},
18373
- ...platformSettings.oauth2_client_id ? { oauth2ClientId: platformSettings.oauth2_client_id } : {},
18374
- ...platformSettings.console_url ? { consoleUrl: platformSettings.console_url } : {}
18375
- };
18376
- if (!args.json) logger.success(`Profile "${profileSetup.name}" created successfully.`);
18377
- }
18378
- if (!args.json) logger.success(`Workspace "${workspaceDisplayName(workspace)}" created successfully.`);
18379
- if (args.json && profileInfo) {
18380
- logger.out({
18381
- ...workspace,
18382
- profile: profileInfo
18383
- });
18384
- return;
18385
- }
18386
- logger.out(workspace, { display: {
18387
- name: workspaceNameTransformer,
18388
- folderName: null
18389
- } });
18390
- if (profileInfo) {
18391
- logger.out("Profile:");
18392
- logger.out(profileInfo);
18393
- }
18394
- }
18395
- });
18396
-
18397
19368
  //#endregion
18398
19369
  //#region src/cli/commands/workspace/delete.ts
18399
19370
  const deleteWorkspaceOptionsSchema = z.object({ workspaceId: z.uuid({ message: "workspace-id must be a valid UUID" }) });
@@ -18507,42 +19478,6 @@ const getCommand = defineAppCommand({
18507
19478
  }
18508
19479
  });
18509
19480
 
18510
- //#endregion
18511
- //#region src/cli/commands/workspace/list.ts
18512
- /**
18513
- * List workspaces with an optional order and limit.
18514
- * @param options - Workspace listing options
18515
- * @returns List of workspaces
18516
- */
18517
- async function listWorkspaces(options) {
18518
- const client = await initOperatorClient(await loadAccessToken());
18519
- const pageDirection = toPageDirection(options?.order);
18520
- return workspaceInfosWithFolderNames(client, await fetchPaged(async (pageToken, pageSize) => {
18521
- const { workspaces, nextPageToken } = await client.listWorkspaces({
18522
- pageToken,
18523
- pageSize,
18524
- pageDirection
18525
- });
18526
- return [workspaces, nextPageToken];
18527
- }, { limit: options?.limit }));
18528
- }
18529
- const listCommand$1 = defineAppCommand({
18530
- name: "list",
18531
- description: "List all Tailor Platform workspaces.",
18532
- args: z.strictObject({ ...paginationArgs() }),
18533
- run: async (args) => {
18534
- const workspaces = await listWorkspaces({
18535
- order: args.order,
18536
- limit: args.limit
18537
- });
18538
- logger.out(workspaces, { display: {
18539
- name: workspaceNameTransformer,
18540
- folderName: null,
18541
- updatedAt: null
18542
- } });
18543
- }
18544
- });
18545
-
18546
19481
  //#endregion
18547
19482
  //#region src/cli/commands/workspace/restore.ts
18548
19483
  const restoreWorkspaceOptionsSchema = z.object({ workspaceId: z.uuid({ message: "workspace-id must be a valid UUID" }) });
@@ -18922,10 +19857,10 @@ function createGqlEntry() {
18922
19857
  */
18923
19858
  async function bundleQueryScript(engine) {
18924
19859
  const outputDir = path.resolve(getDistDir(), "query");
18925
- fs$1.mkdirSync(outputDir, { recursive: true });
19860
+ fs$2.mkdirSync(outputDir, { recursive: true });
18926
19861
  const entryPath = path.join(outputDir, `query_${engine}.entry.ts`);
18927
19862
  const entryContent = engine === "sql" ? createSqlEntry() : createGqlEntry();
18928
- fs$1.writeFileSync(entryPath, entryContent);
19863
+ fs$2.writeFileSync(entryPath, entryContent);
18929
19864
  let tsconfig;
18930
19865
  try {
18931
19866
  tsconfig = await resolveTSConfig();
@@ -19193,7 +20128,7 @@ async function loadTypeFieldOrder(config, namespace) {
19193
20128
  const fieldOrder = /* @__PURE__ */ new Map();
19194
20129
  const dbConfig = config.db?.[namespace];
19195
20130
  if (!dbConfig || !("files" in dbConfig) || dbConfig.files.length === 0) return fieldOrder;
19196
- const typeFiles = loadFilesWithIgnores(dbConfig);
20131
+ const typeFiles = loadFilesWithIgnores(dbConfig, path.dirname(config.path));
19197
20132
  await Promise.all(typeFiles.map(async (typeFile) => {
19198
20133
  try {
19199
20134
  const module = await import(pathToFileURL(typeFile).href);
@@ -19809,5 +20744,5 @@ async function registerTsHook(tsHookUrl) {
19809
20744
  }
19810
20745
 
19811
20746
  //#endregion
19812
- 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, registerTsHook 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 };
19813
- //# sourceMappingURL=register-ts-hook-BhzVkej0.mjs.map
20747
+ export { updateFolder as $, saveUserTokens as $n, bundleMigrationScript as $t, generate as A, sdkNameLabelKey as An, listExecutorJobs as At, extractOwnedNamespaces as B, hasUserTokenEntry as Bn, getCommand$6 as Bt, waitWorkflowExecution as C, formatMigrationNumber as Cn, triggerCommand as Ct, listWorkflows as D, hasChanges as Dn, getExecutorJob as Dt, listCommand$2 as E, formatMigrationDiff as En, listExecutors as Et, openInConfiguredEditor as F, assertWritable as Fn, getWorkflow as Ft, updateOrganization as G, loadPlatformClientConfig as Gn, listWorkspaces as Gt, remove as H, loadConfigPath as Hn, deploy as Ht, deployCommand as I, loadConfig as In, executionsCommand as It, listCommand$3 as J, platformConfigFromProfile as Jn, formatKeyValueTable as Jt, organizationTree as K, loadStoredUserTokens as Kn, createCommand$1 as Kt, deployStaticWebsite as L, deleteUserTokens as Ln, getWorkflowExecution as Lt, generateMigrationScript as M, generateUserTypes as Mn, startCommand as Mt, writeDbTypesFile as N, prompt as Nn, startWorkflow as Nt, truncate as O, getNamespacesWithMigrations as On, getExecutorWaitFailureMessage as Ot, getConfiguredEditorCommand as P, apiCall as Pn, getCommand$5 as Pt, updateCommand$2 as Q, resolveUserTokenKey as Qn, waitForExecution as Qt, show as R, fetchLatestToken as Rn, listWorkflowExecutions as Rt, waitCommand as S, reconstructSnapshotFromMigrations as Sn, webhookCommand as St, resumeWorkflow as T, formatDiffSummary as Tn, listCommand$8 as Tt, removeCommand$1 as U, loadConsoleBaseUrl as Un, deployFromCLI as Ut, logBetaWarning as V, loadAccessToken as Vn, getExecutor as Vt, updateCommand$1 as W, loadMachineUserName as Wn, listCommand$9 as Wt, getCommand$1 as X, removeLegacyUserAlias as Xn, ensureConfigId as Xt, listOrganizations as Y, readPlatformConfig as Yn, workspaceNameSchema as Yt, getOrganization as Z, resolveConfigUser as Zn, executeScript as Zt, deleteWorkspace as _, getMigrationFilePath as _n, listCommand$7 as _t, updateUser as a, protoGqlPermission as an, deploymentArgs as ar, deleteFolder as at, getAppHealth as b, isValidMigrationNumber as bn, getFunctionRegistry as bt, listCommand as c, INITIAL_SCHEMA_NUMBER as cn, pagedLogArgs as cr, listCommand$5 as ct, inviteUser as d, assertValidMigrationFiles as dn, toPageDirection as dr, getOAuth2Client as dt, MIGRATION_LABEL_KEY as en, writePlatformConfig as er, listCommand$4 as et, restoreCommand as f, compareLocalTypesWithSnapshot as fn, workspaceArgs as fr, getMachineUserToken as ft, deleteCommand as g, getMigrationDirPath as gn, generate$1 as gt, getWorkspace as h, getLatestMigrationNumber as hn, listMachineUsers as ht, updateCommand as i, generateAllTypeManifestsFromSnapshot as in, confirmationArgs as ir, deleteCommand$1 as it, generateCommand as j, PluginManager as jn, watchExecutorJob as jt, truncateCommand as k, resourceTrn as kn, jobsCommand as kt, listUsers as l, MIGRATE_FILE_NAME as ln, paginationArgs as lr, listOAuth2Clients as lt, getCommand as m, createSnapshotFromLocalTypes as mn, listCommand$6 as mt, query as n, parseMigrationLabelNumber as nn, commonArgs as nr, getCommand$2 as nt, removeCommand as o, DB_TYPES_FILE_NAME as on, isVerbose as or, createCommand as ot, restoreWorkspace as p, compareSnapshots as pn, tokenCommand as pt, treeCommand as q, loadWorkspaceId as qn, createWorkspace$1 as qt, queryCommand as r, compareSnapshotWithRemote as rn, configArg as rr, getFolder as rt, removeUser as s, DIFF_FILE_NAME as sn, multiConfigArg as sr, createFolder as st, registerTsHook as t, handleOptionalToRequiredError as tn, defineAppCommand as tr, listFolders as tt, inviteCommand as u, SCHEMA_FILE_NAME as un, resolveMachineUserInputSource as ur, getCommand$3 as ut, listApps as v, getMigrationFiles as vn, listFunctionRegistries as vt, resumeCommand as w, parseMigrationNumberArg as wn, triggerExecutor as wt, healthCommand as x, loadDiff as xn, listWebhookExecutors as xt, listCommand$1 as y, getNextMigrationNumber as yn, getCommand$4 as yt, showCommand as z, hasAnyUserTokenEntry as zn, functionExecutionStatusToString as zt };
20748
+ //# sourceMappingURL=register-ts-hook-DvEs6YsL.mjs.map