@tailor-platform/sdk 1.73.3 → 1.74.1

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 (104) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/{application-DgTCDMZY.mjs → application-BsH6tkZC.mjs} +96 -17
  3. package/dist/application-BsH6tkZC.mjs.map +1 -0
  4. package/dist/application-CSUhZMb5.mjs +3 -0
  5. package/dist/cli/commands/deploy/apply-phases.d.mts +1 -0
  6. package/dist/cli/commands/executor/trigger.d.mts +1 -1
  7. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +1 -16
  8. package/dist/cli/index.mjs +121 -43
  9. package/dist/cli/index.mjs.map +1 -1
  10. package/dist/cli/lib.d.mts +4 -4
  11. package/dist/cli/lib.mjs +3 -3
  12. package/dist/cli/lib.mjs.map +1 -1
  13. package/dist/cli/shared/client.d.mts +1 -1
  14. package/dist/completion/zsh-worker.zsh +28 -3
  15. package/dist/configure/index.mjs +2 -2
  16. package/dist/configure/services/tailordb/schema.d.mts +5 -19
  17. package/dist/configure/types/field-runtime.d.mts +17 -0
  18. package/dist/configure/types/type.d.mts +1 -18
  19. package/dist/field-runtime-9bfXBAOK.mjs +183 -0
  20. package/dist/field-runtime-9bfXBAOK.mjs.map +1 -0
  21. package/dist/plugin/builtin/seed/index.mjs +1 -1
  22. package/dist/{repl-editor-BmQOyZJd.mjs → repl-editor-BCozyiNq.mjs} +3 -2
  23. package/dist/{repl-editor-BmQOyZJd.mjs.map → repl-editor-BCozyiNq.mjs.map} +1 -1
  24. package/dist/{runtime-SxHHEzVb.mjs → runtime-DV1EnfMs.mjs} +2130 -1538
  25. package/dist/runtime-DV1EnfMs.mjs.map +1 -0
  26. package/dist/{schema-DAdZSFOd.mjs → schema-Dl_y0m9e.mjs} +8 -167
  27. package/dist/schema-Dl_y0m9e.mjs.map +1 -0
  28. package/dist/secretmanager-Bd45j7an.mjs +98 -0
  29. package/dist/secretmanager-Bd45j7an.mjs.map +1 -0
  30. package/dist/{seed-DoMTMLcm.mjs → seed-fm0LeYP2.mjs} +14 -3
  31. package/dist/seed-fm0LeYP2.mjs.map +1 -0
  32. package/dist/{service-DiEtAsvc.mjs → service-BCRJ-3NV.mjs} +18 -1
  33. package/dist/{service-DiEtAsvc.mjs.map → service-BCRJ-3NV.mjs.map} +1 -1
  34. package/dist/service-CPMlIg3X.mjs +3 -0
  35. package/dist/tailor-proto/src/tailor/v1/aigateway_pb.d.mts +2 -2
  36. package/dist/tailor-proto/src/tailor/v1/aigateway_resource_pb.d.mts +1 -1
  37. package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +1 -1
  38. package/dist/tailor-proto/src/tailor/v1/application_resource_pb.d.mts +1 -1
  39. package/dist/tailor-proto/src/tailor/v1/auth_pb.d.mts +1 -1
  40. package/dist/tailor-proto/src/tailor/v1/auth_resource_pb.d.mts +1 -1
  41. package/dist/tailor-proto/src/tailor/v1/events_pb.d.mts +1 -1
  42. package/dist/tailor-proto/src/tailor/v1/events_resource_pb.d.mts +1 -1
  43. package/dist/tailor-proto/src/tailor/v1/executor_pb.d.mts +1 -1
  44. package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +1 -1
  45. package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +1 -1
  46. package/dist/tailor-proto/src/tailor/v1/function_registry_pb.d.mts +1 -1
  47. package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +1 -1
  48. package/dist/tailor-proto/src/tailor/v1/http_adapter_resource_pb.d.mts +1 -1
  49. package/dist/tailor-proto/src/tailor/v1/idp_pb.d.mts +1 -1
  50. package/dist/tailor-proto/src/tailor/v1/idp_resource_pb.d.mts +1 -1
  51. package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +1 -1
  52. package/dist/tailor-proto/src/tailor/v1/metadata_resource_pb.d.mts +1 -1
  53. package/dist/tailor-proto/src/tailor/v1/meter_pb.d.mts +1 -1
  54. package/dist/tailor-proto/src/tailor/v1/pipeline_pb.d.mts +1 -1
  55. package/dist/tailor-proto/src/tailor/v1/pipeline_resource_pb.d.mts +1 -1
  56. package/dist/tailor-proto/src/tailor/v1/resource_pb.d.mts +1 -1
  57. package/dist/tailor-proto/src/tailor/v1/secret_manager_pb.d.mts +1 -1
  58. package/dist/tailor-proto/src/tailor/v1/secret_manager_resource_pb.d.mts +1 -1
  59. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +2 -2
  60. package/dist/tailor-proto/src/tailor/v1/stateflow_pb.d.mts +1 -1
  61. package/dist/tailor-proto/src/tailor/v1/stateflow_resource_pb.d.mts +1 -1
  62. package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +1 -1
  63. package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +1 -1
  64. package/dist/tailor-proto/src/tailor/v1/tailordb_pb.d.mts +1 -1
  65. package/dist/tailor-proto/src/tailor/v1/tailordb_resource_pb.d.mts +1 -1
  66. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_pb.d.mts +1 -1
  67. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +1 -1
  68. package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +1 -1
  69. package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +1 -1
  70. package/dist/tailor-proto/src/tailor/v1/workspace_pb.d.mts +2 -2
  71. package/dist/tailor-proto/src/tailor/v1/workspace_resource_pb.d.mts +1 -1
  72. package/dist/types-BwzB2okW.mjs +199 -0
  73. package/dist/types-BwzB2okW.mjs.map +1 -0
  74. package/dist/types-KlawuVKa.mjs +4 -0
  75. package/dist/vitest/index.d.mts +8 -1
  76. package/dist/vitest/index.mjs +778 -2
  77. package/dist/vitest/index.mjs.map +1 -1
  78. package/dist/vitest/mock.d.mts +8 -363
  79. package/dist/vitest/mocks/aigateway.d.mts +30 -0
  80. package/dist/vitest/mocks/authconnection.d.mts +28 -0
  81. package/dist/vitest/mocks/file.d.mts +41 -0
  82. package/dist/vitest/mocks/iconv.d.mts +27 -0
  83. package/dist/vitest/mocks/idp.d.mts +44 -0
  84. package/dist/vitest/mocks/secretmanager.d.mts +33 -0
  85. package/dist/vitest/mocks/tailordb.d.mts +71 -0
  86. package/dist/vitest/mocks/workflow.d.mts +115 -0
  87. package/dist/vitest/setup.mjs +1 -1
  88. package/docs/cli/application.md +21 -11
  89. package/docs/cli/setup.md +25 -0
  90. package/docs/cli-reference.md +1 -0
  91. package/package.json +15 -15
  92. package/dist/application-BAo_zFA4.mjs +0 -3
  93. package/dist/application-DgTCDMZY.mjs.map +0 -1
  94. package/dist/field-DOsJCPFa.mjs +0 -22
  95. package/dist/field-DOsJCPFa.mjs.map +0 -1
  96. package/dist/mock-Ce1GCi4-.mjs +0 -871
  97. package/dist/mock-Ce1GCi4-.mjs.map +0 -1
  98. package/dist/runtime-SxHHEzVb.mjs.map +0 -1
  99. package/dist/schema-DAdZSFOd.mjs.map +0 -1
  100. package/dist/seed-DoMTMLcm.mjs.map +0 -1
  101. package/dist/service-BXmdFwSH.mjs +0 -3
  102. package/dist/types-qhFZ8y9B.mjs +0 -4
  103. package/dist/types-ywCGpzDZ.mjs +0 -360
  104. package/dist/types-ywCGpzDZ.mjs.map +0 -1
@@ -1,9 +1,9 @@
1
- import { t as db } from "./schema-DAdZSFOd.mjs";
1
+ import { t as db } from "./schema-Dl_y0m9e.mjs";
2
2
  import { $ as CreateUserProfileConfigRequestSchema, A as UpdatePipelineServiceRequestSchema, At as Condition_Operator, B as UpdateExecutorExecutorRequestSchema, Ct as UserProfileProviderConfig_UserProfileProviderType, D as CreatePipelineResolverRequestSchema, Dt as ApplicationSchemaUpdateAttemptStatus, E as UpdateSecretManagerSecretRequestSchema, Et as UpdateApplicationRequestSchema, F as IdPPermissionOperator, G as CreateAuthHookRequestSchema, H as ExecutorTargetType, I as IdPPermissionPermit, J as CreateAuthOAuth2ClientRequestSchema, K as CreateAuthIDPConfigRequestSchema, L as FunctionExecution_Status, M as CreateIdPServiceRequestSchema, Mt as PageDirection, N as UpdateIdPServiceRequestSchema, O as CreatePipelineServiceRequestSchema, Ot as Subgraph_ServiceType, P as IdPLang, Q as CreateTenantConfigRequestSchema, S as UpdateStaticWebsiteRequestSchema, St as TenantProviderConfig_TenantProviderType, T as CreateSecretManagerVaultRequestSchema, Tt as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, U as ExecutorTriggerType, V as ExecutorJobStatus, W as CreateAuthConnectionRequestSchema, X as CreateAuthSCIMResourceRequestSchema, Y as CreateAuthSCIMConfigRequestSchema, Z as CreateAuthServiceRequestSchema, _ as TailorDBType_Permission_Operator, _t as AuthSCIMAttribute_Mutability, a as WorkspacePlatformUserRole, at as UpdateAuthSCIMConfigRequestSchema, b as AddCustomDomainRequestSchema, bt as AuthSCIMConfig_AuthorizationType, c as UpdateWorkflowRequestSchema, ct as UpdateTenantConfigRequestSchema, d as CreateTailorDBServiceRequestSchema, dt as AuthConnection_Type, et as UpdateAuthConnectionRequestSchema, f as CreateTailorDBTypeRequestSchema, ft as AuthHookPoint, g as TailorDBGQLPermission_Permit, gt as AuthOAuth2Client_GrantType, h as TailorDBGQLPermission_Operator, ht as AuthOAuth2Client_ClientType, it as UpdateAuthOAuth2ClientRequestSchema, j as PipelineResolver_OperationType, jt as FilterSchema, k as UpdatePipelineResolverRequestSchema, kt as ConditionSchema, l as WorkflowExecution_Status, lt as UpdateUserProfileConfigRequestSchema, m as TailorDBGQLPermission_Action, mt as AuthInvokerSchema, nt as UpdateAuthIDPConfigRequestSchema, o as CreateWorkflowJobFunctionRequestSchema, ot as UpdateAuthSCIMResourceRequestSchema, p as UpdateTailorDBTypeRequestSchema, pt as AuthIDPConfig_AuthType, q as CreateAuthMachineUserRequestSchema, rt as UpdateAuthMachineUserRequestSchema, s as CreateWorkflowRequestSchema, st as UpdateAuthServiceRequestSchema, t as OperatorService, tt as UpdateAuthHookRequestSchema, u as WorkflowJobExecution_Status, ut as AuthConnection_Status, v as TailorDBType_Permission_Permit, vt as AuthSCIMAttribute_Type, w as CreateSecretManagerSecretRequestSchema, wt as CreateApplicationRequestSchema, x as CreateStaticWebsiteRequestSchema, y as TailorDBType_PermitAction, yt as AuthSCIMAttribute_Uniqueness, z as CreateExecutorExecutorRequestSchema } from "./service_pb-CIrhGwHk.mjs";
3
3
  import { t as assertDefined } from "./assert-DBxo8jPo.mjs";
4
4
  import { a as parseBoolean, i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-BEiZZ3qT.mjs";
5
- import { $ as initOperatorClient, A as loadAccessToken, G as fetchMachineUserToken, I as loadWorkspaceId, K as fetchPaged, L as platformConfigFromProfile, N as loadMachineUserName, P as loadPlatformClientConfig, R as readPlatformConfig, S as getDistDir, T as loadConfig, V as writePlatformConfig, W as fetchAll, X as getOAuth2ClientId, Y as getConsoleBaseUrl, Z as getPlatformBaseUrl, b as hasGenerationHooks, d as assertUniqueLocalTailorDBTypeNames, et as isDefaultPlatform, f as assertUniqueTailorDBTypeNamesWithExternal, h as platformBundleDefinePlugin, j as loadConfigPath, k as hasUserTokenEntry, l as buildExecutorArgsExpr, m as stringifyFunction, n as generatePluginFilesIfNeeded, nt as byName, p as TailorDBTypeSchema, r as loadApplication, s as HTTP_METHODS, t as defineApplication, tt as resolveStaticWebsiteUrls, u as buildResolverOperationHookExpr, w as hashFile, x as createBundleCache, y as getPluginGenerationDependencies } from "./application-DgTCDMZY.mjs";
6
- import { o as loadFilesWithIgnores, t as createExecutorService } from "./service-DiEtAsvc.mjs";
5
+ import { $ as getOrNull, A as hasUserTokenEntry, C as getDistDir, E as loadConfig, F as loadPlatformClientConfig, G as fetchAll, H as writePlatformConfig, J as fetchPaged, K as fetchAllTolerant, L as loadWorkspaceId, M as loadConfigPath, P as loadMachineUserName, Q as getOAuth2ClientId, R as platformConfigFromProfile, S as createBundleCache, T as hashFile, Z as getConsoleBaseUrl, at as byName, b as getPluginGenerationDependencies, c as HTTP_METHODS, d as buildResolverOperationHookExpr, et as getPlatformBaseUrl, f as assertUniqueLocalTailorDBTypeNames, g as platformBundleDefinePlugin, h as stringifyFunction, i as resolverBundleKey, it as resolveStaticWebsiteUrls, j as loadAccessToken, m as TailorDBTypeSchema, n as generatePluginFilesIfNeeded, nt as initOperatorClient, p as assertUniqueTailorDBTypeNamesWithExternal, q as fetchMachineUserToken, r as loadApplication, rt as isDefaultPlatform, t as defineApplication, u as buildExecutorArgsExpr, w as hashContent, x as hasGenerationHooks, z as readPlatformConfig } from "./application-BsH6tkZC.mjs";
6
+ import { o as loadFilesWithIgnores, t as createExecutorService } from "./service-BCRJ-3NV.mjs";
7
7
  import { t as multiline } from "./multiline-sfHpTZZK.mjs";
8
8
  import { t as readPackageJson } from "./package-json-8b0O9TlX.mjs";
9
9
  import { i as userAgent } from "./secret-file-VSVGy1V0.mjs";
@@ -71,7 +71,9 @@ const durationArg = z.string().refine((val) => durationPattern.test(val), { mess
71
71
  */
72
72
  function parseDuration(duration) {
73
73
  const match = assertDefined(duration.match(durationPattern), `invalid duration format: ${duration}`);
74
- return parseInt(assertDefined(match[1], "duration digits group missing"), 10) * unitToMs[assertDefined(match[2], "duration unit group missing")];
74
+ const value = parseInt(assertDefined(match[1], "duration digits group missing"), 10);
75
+ const unit = assertDefined(match[2], "duration unit group missing");
76
+ return value * unitToMs[unit];
75
77
  }
76
78
  /**
77
79
  * Schema for positive integer validation (from string input)
@@ -197,6 +199,18 @@ const configArg = { config: arg(z.string().default("tailor.config.ts"), {
197
199
  }
198
200
  }) };
199
201
  /**
202
+ * Shared config arg for commands that accept one or more comma-separated config file paths
203
+ */
204
+ const multiConfigArg = { config: arg(z.string().default("tailor.config.ts"), {
205
+ alias: "c",
206
+ description: "Path to SDK config file. Use comma-separated paths to deploy multiple apps together.",
207
+ env: "TAILOR_PLATFORM_SDK_CONFIG_PATH",
208
+ completion: {
209
+ type: "file",
210
+ extensions: ["ts"]
211
+ }
212
+ }) };
213
+ /**
200
214
  * Arguments for commands that interact with deployed resources (includes config)
201
215
  */
202
216
  const deploymentArgs = {
@@ -965,7 +979,7 @@ function createCacheStore(config) {
965
979
  return path.join(config.cacheDir, BUNDLES_DIR);
966
980
  }
967
981
  function bundlePath(cacheKey) {
968
- return path.join(bundlesDir(), `${cacheKey.replaceAll(":", "_")}.js`);
982
+ return path.join(bundlesDir(), `${hashContent(cacheKey)}.js`);
969
983
  }
970
984
  function loadManifest() {
971
985
  try {
@@ -1085,12 +1099,20 @@ function createCacheManager(options) {
1085
1099
  enabled: true,
1086
1100
  bundleCache: createBundleCache(store),
1087
1101
  finalize() {
1088
- const manifest = store.getCurrentManifest() ?? {
1102
+ const currentManifest = store.getCurrentManifest() ?? {
1089
1103
  version: 1,
1090
1104
  sdkVersion: options.sdkVersion,
1091
1105
  lockfileHash: options.lockfileHash,
1092
1106
  entries: {}
1093
1107
  };
1108
+ const latestManifest = store.loadManifest();
1109
+ const manifest = latestManifest?.sdkVersion === options.sdkVersion && latestManifest.lockfileHash === options.lockfileHash ? {
1110
+ ...latestManifest,
1111
+ entries: {
1112
+ ...latestManifest.entries,
1113
+ ...currentManifest.entries
1114
+ }
1115
+ } : currentManifest;
1094
1116
  manifest.sdkVersion = options.sdkVersion;
1095
1117
  manifest.lockfileHash = options.lockfileHash;
1096
1118
  store.saveManifest(manifest);
@@ -1827,14 +1849,7 @@ async function buildMetaRequest(params) {
1827
1849
  */
1828
1850
  async function fetchExistingResourcesWithLabels(params) {
1829
1851
  const { client, fetchPage, getName, getTrn } = params;
1830
- const withoutLabel = await fetchAll(async (pageToken, maxPageSize) => {
1831
- try {
1832
- return await fetchPage(pageToken, maxPageSize);
1833
- } catch (error) {
1834
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
1835
- throw error;
1836
- }
1837
- });
1852
+ const withoutLabel = await fetchAllTolerant(fetchPage);
1838
1853
  const existingResources = {};
1839
1854
  await Promise.all(withoutLabel.map(async (resource) => {
1840
1855
  const name = getName(resource);
@@ -1894,6 +1909,207 @@ function trackRemainingResourceOwner(params) {
1894
1909
  return owned;
1895
1910
  }
1896
1911
 
1912
+ //#endregion
1913
+ //#region src/cli/commands/deploy/staticwebsite.ts
1914
+ /**
1915
+ * Apply static website changes for the given phase.
1916
+ * @param client - Operator client instance
1917
+ * @param result - Planned static website changes
1918
+ * @param phase - Apply phase
1919
+ * @returns Promise that resolves when static websites are applied
1920
+ */
1921
+ async function applyStaticWebsite(client, result, phase = "create-update") {
1922
+ const { changeSet, customDomainChangeSet } = result;
1923
+ if (phase === "create-update") {
1924
+ await Promise.all([...changeSet.creates.map(async (create) => {
1925
+ await client.createStaticWebsite(create.request);
1926
+ await client.setMetadata(create.metaRequest);
1927
+ }), ...changeSet.updates.map(async (update) => {
1928
+ await client.updateStaticWebsite(update.request);
1929
+ await client.setMetadata(update.metaRequest);
1930
+ })]);
1931
+ await Promise.all([...customDomainChangeSet.creates.map(async (add) => {
1932
+ await client.addCustomDomain(add.request);
1933
+ await client.setMetadata(add.metaRequest);
1934
+ }), ...customDomainChangeSet.deletes.map((del) => client.removeCustomDomain(del.request))]);
1935
+ } else await Promise.all(changeSet.deletes.map((del) => client.deleteStaticWebsite(del.request)));
1936
+ }
1937
+ function customDomainTrn(workspaceId, websiteName, domain) {
1938
+ return `trn:v1:workspace:${workspaceId}:staticwebsite:${websiteName}:custom_domain:${domain}`;
1939
+ }
1940
+ function normalizeComparableStaticWebsiteShape(input) {
1941
+ return {
1942
+ description: input.description,
1943
+ allowedIpAddresses: input.allowedIpAddresses.toSorted()
1944
+ };
1945
+ }
1946
+ function normalizeComparableStaticWebsite(input) {
1947
+ return normalizeComparableStaticWebsiteShape({
1948
+ description: input.description || "",
1949
+ allowedIpAddresses: [...input.allowedIpAddresses || []]
1950
+ });
1951
+ }
1952
+ function areStaticWebsitesEqual(existing, desired) {
1953
+ return areNormalizedEqual(normalizeComparableStaticWebsite(existing), normalizeComparableStaticWebsite(desired));
1954
+ }
1955
+ /**
1956
+ * Static website names expected to exist after this deploy run.
1957
+ * @param context - Planning context
1958
+ * @returns Names from the deploy-run scope when set, otherwise the application's own websites
1959
+ */
1960
+ function expectedLocalStaticWebsiteNames(context) {
1961
+ return context.expectedLocalStaticWebsiteNames ?? new Set(context.application.staticWebsiteServices.map((website) => website.name));
1962
+ }
1963
+ /**
1964
+ * Plan static website changes based on current and desired state.
1965
+ * @param context - Planning context
1966
+ * @returns Planned changes
1967
+ */
1968
+ async function planStaticWebsite(context) {
1969
+ const { client, workspaceId, application, forRemoval } = context;
1970
+ const changeSet = createChangeSet("StaticWebsites");
1971
+ const customDomainChangeSet = createChangeSet("CustomDomains");
1972
+ const conflicts = [];
1973
+ const unmanaged = [];
1974
+ const resourceOwners = /* @__PURE__ */ new Set();
1975
+ const existingWebsites = await fetchExistingResourcesWithLabels({
1976
+ client,
1977
+ fetchPage: async (pageToken, pageSize) => {
1978
+ const { staticwebsites, nextPageToken } = await client.listStaticWebsites({
1979
+ workspaceId,
1980
+ pageToken,
1981
+ pageSize
1982
+ });
1983
+ return [staticwebsites, nextPageToken];
1984
+ },
1985
+ getName: (resource) => resource.name,
1986
+ getTrn: (name) => resourceTrn(workspaceId, "staticwebsite", name)
1987
+ });
1988
+ const ownedWebsiteNames = /* @__PURE__ */ new Set();
1989
+ const staticWebsiteServices = forRemoval ? [] : application.staticWebsiteServices;
1990
+ for (const websiteService of staticWebsiteServices) {
1991
+ const config = websiteService;
1992
+ const name = websiteService.name;
1993
+ const existing = existingWebsites[name];
1994
+ const metaRequest = await buildMetaRequest({
1995
+ trn: resourceTrn(workspaceId, "staticwebsite", name),
1996
+ appName: application.name,
1997
+ appId: application.id
1998
+ });
1999
+ const desired = normalizeComparableStaticWebsite(config);
2000
+ const request = {
2001
+ workspaceId,
2002
+ staticwebsite: {
2003
+ name,
2004
+ description: config.description || "",
2005
+ allowedIpAddresses: config.allowedIpAddresses || []
2006
+ }
2007
+ };
2008
+ if (existing) {
2009
+ const owned = trackDesiredResourceOwnership({
2010
+ labels: existing.allLabels,
2011
+ ownerLabel: existing.label,
2012
+ appName: application.name,
2013
+ appId: application.id,
2014
+ resourceType: "StaticWebsite",
2015
+ resourceName: name,
2016
+ conflicts,
2017
+ unmanaged
2018
+ });
2019
+ if (owned && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areStaticWebsitesEqual(existing.resource, desired)) changeSet.unchanged.push({ name });
2020
+ else changeSet.updates.push({
2021
+ name,
2022
+ request,
2023
+ metaRequest
2024
+ });
2025
+ if (owned) ownedWebsiteNames.add(name);
2026
+ delete existingWebsites[name];
2027
+ } else {
2028
+ changeSet.creates.push({
2029
+ name,
2030
+ request,
2031
+ metaRequest
2032
+ });
2033
+ ownedWebsiteNames.add(name);
2034
+ }
2035
+ }
2036
+ Object.entries(existingWebsites).forEach(([name]) => {
2037
+ const entry = existingWebsites[name];
2038
+ const label = entry?.label;
2039
+ if (trackRemainingResourceOwner({
2040
+ labels: entry?.allLabels,
2041
+ ownerLabel: label,
2042
+ appName: application.name,
2043
+ appId: application.id,
2044
+ resourceOwners
2045
+ })) changeSet.deletes.push({
2046
+ name,
2047
+ request: {
2048
+ workspaceId,
2049
+ name
2050
+ }
2051
+ });
2052
+ });
2053
+ const desiredDomainsByWebsite = /* @__PURE__ */ new Map();
2054
+ for (const service of staticWebsiteServices) if (service.customDomains !== void 0 && ownedWebsiteNames.has(service.name)) desiredDomainsByWebsite.set(service.name, service.customDomains);
2055
+ const existingDomainsByWebsite = /* @__PURE__ */ new Map();
2056
+ const websitesToFetchDomains = [...ownedWebsiteNames].filter((name) => !changeSet.creates.some((c) => c.name === name));
2057
+ await Promise.all(websitesToFetchDomains.map(async (name) => {
2058
+ const domainsWithLabels = await getOrNull(async () => {
2059
+ const { customDomains } = await client.listCustomDomains({
2060
+ workspaceId,
2061
+ staticWebsiteName: name
2062
+ });
2063
+ return await Promise.all(customDomains.map(async (d) => {
2064
+ const { metadata } = await client.getMetadata({ trn: customDomainTrn(workspaceId, name, d.domain) });
2065
+ return {
2066
+ domain: d.domain,
2067
+ allLabels: metadata?.labels
2068
+ };
2069
+ }));
2070
+ });
2071
+ if (domainsWithLabels) existingDomainsByWebsite.set(name, domainsWithLabels);
2072
+ }));
2073
+ for (const name of ownedWebsiteNames) {
2074
+ const desired = new Set(desiredDomainsByWebsite.get(name) ?? []);
2075
+ const existingDomains = existingDomainsByWebsite.get(name) ?? [];
2076
+ const existingSet = new Set(existingDomains.map((d) => d.domain));
2077
+ const sdkOwnedDomains = new Set(existingDomains.filter((d) => isOwnedByApp(d.allLabels, application.name, application.id)).map((d) => d.domain));
2078
+ for (const domain of desired) if (!existingSet.has(domain)) {
2079
+ const metaRequest = await buildMetaRequest({
2080
+ trn: customDomainTrn(workspaceId, name, domain),
2081
+ appName: application.name,
2082
+ appId: application.id
2083
+ });
2084
+ customDomainChangeSet.creates.push({
2085
+ name: domain,
2086
+ request: {
2087
+ workspaceId,
2088
+ staticWebsiteName: name,
2089
+ domain
2090
+ },
2091
+ metaRequest
2092
+ });
2093
+ } else customDomainChangeSet.unchanged.push({ name: domain });
2094
+ if (desiredDomainsByWebsite.has(name)) {
2095
+ for (const domain of sdkOwnedDomains) if (!desired.has(domain)) customDomainChangeSet.deletes.push({
2096
+ name: domain,
2097
+ request: {
2098
+ workspaceId,
2099
+ domain
2100
+ }
2101
+ });
2102
+ }
2103
+ }
2104
+ return {
2105
+ changeSet,
2106
+ customDomainChangeSet,
2107
+ conflicts,
2108
+ unmanaged,
2109
+ resourceOwners
2110
+ };
2111
+ }
2112
+
1897
2113
  //#endregion
1898
2114
  //#region src/cli/commands/deploy/aigateway.ts
1899
2115
  /**
@@ -1956,7 +2172,7 @@ async function planAIGateway(context) {
1956
2172
  getTrn: (name) => resourceTrn(workspaceId, "aigateway", name)
1957
2173
  });
1958
2174
  const aiGatewayServices = forRemoval ? [] : application.aiGatewayServices;
1959
- const expectedLocalWebsites = new Set(application.staticWebsiteServices.map((website) => website.name));
2175
+ const expectedLocalWebsites = expectedLocalStaticWebsiteNames(context);
1960
2176
  for (const gatewayService of aiGatewayServices) {
1961
2177
  const config = gatewayService;
1962
2178
  const name = gatewayService.name;
@@ -2120,18 +2336,13 @@ function areApplicationsEqual(existing, desired) {
2120
2336
  async function planApplication(context, httpAdapterBuildResult) {
2121
2337
  const { client, workspaceId, application, forRemoval } = context;
2122
2338
  const changeSet = createChangeSet("Applications");
2123
- const existingApplications = await fetchAll(async (pageToken, maxPageSize) => {
2124
- try {
2125
- const { applications, nextPageToken } = await client.listApplications({
2126
- workspaceId,
2127
- pageToken,
2128
- pageSize: maxPageSize
2129
- });
2130
- return [applications, nextPageToken];
2131
- } catch (error) {
2132
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
2133
- throw error;
2134
- }
2339
+ const existingApplications = await fetchAllTolerant(async (pageToken, maxPageSize) => {
2340
+ const { applications, nextPageToken } = await client.listApplications({
2341
+ workspaceId,
2342
+ pageToken,
2343
+ pageSize: maxPageSize
2344
+ });
2345
+ return [applications, nextPageToken];
2135
2346
  });
2136
2347
  if (forRemoval) {
2137
2348
  const candidates = application.id ? existingApplications : existingApplications.filter((app) => app.name === application.name);
@@ -2156,8 +2367,9 @@ async function planApplication(context, httpAdapterBuildResult) {
2156
2367
  if (idProvider) authIdpConfigName = idProvider.name;
2157
2368
  } else if (application.config.auth) {
2158
2369
  authNamespace = application.config.auth.name;
2159
- const idpConfigs = await fetchAll(async (pageToken, maxPageSize) => {
2160
- try {
2370
+ if (context.externalAuthIdpConfigNames?.has(authNamespace)) authIdpConfigName = context.externalAuthIdpConfigNames.get(authNamespace);
2371
+ else {
2372
+ const idpConfigs = await fetchAllTolerant(async (pageToken, maxPageSize) => {
2161
2373
  const { idpConfigs, nextPageToken } = await client.listAuthIDPConfigs({
2162
2374
  workspaceId,
2163
2375
  namespaceName: assertDefined(authNamespace, "authNamespace must be set before listing IDP configs"),
@@ -2165,14 +2377,11 @@ async function planApplication(context, httpAdapterBuildResult) {
2165
2377
  pageSize: maxPageSize
2166
2378
  });
2167
2379
  return [idpConfigs, nextPageToken];
2168
- } catch (error) {
2169
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
2170
- throw error;
2380
+ });
2381
+ if (idpConfigs.length > 0) {
2382
+ const [firstConfig] = idpConfigs;
2383
+ if (firstConfig) authIdpConfigName = firstConfig.name;
2171
2384
  }
2172
- });
2173
- if (idpConfigs.length > 0) {
2174
- const [firstConfig] = idpConfigs;
2175
- if (firstConfig) authIdpConfigName = firstConfig.name;
2176
2385
  }
2177
2386
  }
2178
2387
  const metaRequest = await buildMetaRequest({
@@ -2180,7 +2389,7 @@ async function planApplication(context, httpAdapterBuildResult) {
2180
2389
  appName: application.name,
2181
2390
  appId: application.id
2182
2391
  });
2183
- const expectedLocalWebsites = new Set(application.staticWebsiteServices.map((website) => website.name));
2392
+ const expectedLocalWebsites = expectedLocalStaticWebsiteNames(context);
2184
2393
  const resolvedCors = await resolveStaticWebsiteUrls(client, workspaceId, application.config.cors, "CORS", { expectedLocalNames: expectedLocalWebsites });
2185
2394
  const httpAdapters = buildHttpAdapters(application, httpAdapterBuildResult);
2186
2395
  const desired = normalizeComparableApplication(application, authNamespace, authIdpConfigName, resolvedCors, httpAdapters);
@@ -2234,13 +2443,10 @@ async function planApplication(context, httpAdapterBuildResult) {
2234
2443
  return changeSet;
2235
2444
  }
2236
2445
  async function fetchAppLabels(client, workspaceId, appName) {
2237
- try {
2446
+ return (await getOrNull(async () => {
2238
2447
  const { metadata } = await client.getMetadata({ trn: resourceTrn(workspaceId, "application", appName) });
2239
- return metadata?.labels;
2240
- } catch (error) {
2241
- if (error instanceof ConnectError && error.code === Code.NotFound) return;
2242
- throw error;
2243
- }
2448
+ return metadata;
2449
+ }))?.labels;
2244
2450
  }
2245
2451
  /**
2246
2452
  * Build per-adapter diff lines for the application plan display. The platform
@@ -2424,18 +2630,13 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
2424
2630
  const resourceOwners = /* @__PURE__ */ new Set();
2425
2631
  const desiredConnections = {};
2426
2632
  for (const auth of auths) for (const [name, config] of Object.entries(auth.connections)) desiredConnections[name] = config;
2427
- const existingList = await fetchAll(async (pageToken, maxPageSize) => {
2428
- try {
2429
- const { connections, nextPageToken } = await client.listAuthConnections({
2430
- workspaceId,
2431
- pageToken,
2432
- pageSize: maxPageSize
2433
- });
2434
- return [connections, nextPageToken];
2435
- } catch (error) {
2436
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
2437
- throw error;
2438
- }
2633
+ const existingList = await fetchAllTolerant(async (pageToken, maxPageSize) => {
2634
+ const { connections, nextPageToken } = await client.listAuthConnections({
2635
+ workspaceId,
2636
+ pageToken,
2637
+ pageSize: maxPageSize
2638
+ });
2639
+ return [connections, nextPageToken];
2439
2640
  });
2440
2641
  const existingConnections = {};
2441
2642
  await Promise.all(existingList.map(async (resource) => {
@@ -2455,14 +2656,15 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
2455
2656
  appId
2456
2657
  });
2457
2658
  if (existing) {
2458
- if (!isOwnedByApp(existing.allLabels, appName, appId)) if (existing.label) conflicts.push({
2659
+ trackDesiredResourceOwnership({
2660
+ labels: existing.allLabels,
2661
+ ownerLabel: existing.label,
2662
+ appName,
2663
+ appId,
2459
2664
  resourceType: "Auth connection",
2460
2665
  resourceName: name,
2461
- currentOwner: existing.label
2462
- });
2463
- else unmanaged.push({
2464
- resourceType: "Auth connection",
2465
- resourceName: name
2666
+ conflicts,
2667
+ unmanaged
2466
2668
  });
2467
2669
  const secretChanged = hashValue(config.clientSecret) !== state.connections?.[name];
2468
2670
  const updateMask = buildUpdateMask(existing.resource, config, secretChanged);
@@ -2488,12 +2690,13 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
2488
2690
  }
2489
2691
  for (const [name, entry] of Object.entries(existingConnections)) {
2490
2692
  if (!entry) continue;
2491
- const owned = isOwnedByApp(entry.allLabels, appName, appId);
2492
- if (entry.label && !owned) {
2493
- resourceOwners.add(entry.label);
2494
- continue;
2495
- }
2496
- if (owned) changeSet.deletes.push({
2693
+ if (trackRemainingResourceOwner({
2694
+ labels: entry.allLabels,
2695
+ ownerLabel: entry.label,
2696
+ appName,
2697
+ appId,
2698
+ resourceOwners
2699
+ })) changeSet.deletes.push({
2497
2700
  name,
2498
2701
  request: {
2499
2702
  workspaceId,
@@ -2656,9 +2859,9 @@ function authHookFunctionName(authName, hookPoint) {
2656
2859
  function collectFunctionEntries(application, workflowJobs, bundledScripts) {
2657
2860
  const entries = [];
2658
2861
  for (const app of application.applications) for (const pipeline of app.resolverServices) for (const resolver of Object.values(pipeline.resolvers)) {
2659
- const content = bundledScripts.resolvers.get(resolver.name);
2862
+ const content = bundledScripts.resolvers.get(resolverBundleKey(pipeline.namespace, resolver.name));
2660
2863
  if (!content) {
2661
- logger.warn(`Bundled code not found for resolver: ${resolver.name}`);
2864
+ logger.warn(`Bundled code not found for resolver: ${pipeline.namespace}/${resolver.name}`);
2662
2865
  continue;
2663
2866
  }
2664
2867
  entries.push({
@@ -2762,15 +2965,15 @@ async function planFunctionRegistry(client, workspaceId, appName, appId, entries
2762
2965
  appId
2763
2966
  });
2764
2967
  if (existing) {
2765
- const owned = isOwnedByApp(existing.allLabels, appName, appId);
2766
- if (!owned) if (!existing.label) unmanaged.push({
2767
- resourceType: "Function registry",
2768
- resourceName: entry.name
2769
- });
2770
- else conflicts.push({
2968
+ const owned = trackDesiredResourceOwnership({
2969
+ labels: existing.allLabels,
2970
+ ownerLabel: existing.label,
2971
+ appName,
2972
+ appId,
2771
2973
  resourceType: "Function registry",
2772
2974
  resourceName: entry.name,
2773
- currentOwner: existing.label
2975
+ conflicts,
2976
+ unmanaged
2774
2977
  });
2775
2978
  if (existing.resource.contentHash === entry.contentHash && owned && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) changeSet.unchanged.push({ name: entry.name });
2776
2979
  else changeSet.updates.push({
@@ -2787,10 +2990,13 @@ async function planFunctionRegistry(client, workspaceId, appName, appId, entries
2787
2990
  }
2788
2991
  for (const [name, existing] of Object.entries(existingMap)) {
2789
2992
  if (!existing) continue;
2790
- const label = existing.label;
2791
- const owned = isOwnedByApp(existing.allLabels, appName, appId);
2792
- if (label && !owned) resourceOwners.add(label);
2793
- if (owned) changeSet.deletes.push({
2993
+ if (trackRemainingResourceOwner({
2994
+ labels: existing.allLabels,
2995
+ ownerLabel: existing.label,
2996
+ appName,
2997
+ appId,
2998
+ resourceOwners
2999
+ })) changeSet.deletes.push({
2794
3000
  name,
2795
3001
  workspaceId
2796
3002
  });
@@ -3276,15 +3482,12 @@ async function applyIdP(client, result, phase = "create-update") {
3276
3482
  }), ...changeSet.client.updates.map(async (update) => {
3277
3483
  const vaultName = idpClientVaultName(update.namespaceName, update.name);
3278
3484
  const secretName = idpClientSecretName(update.namespaceName, update.name);
3279
- try {
3280
- await client.getSecretManagerVault({
3485
+ if (await getOrNull(async () => {
3486
+ return await client.getSecretManagerVault({
3281
3487
  workspaceId: update.workspaceId,
3282
3488
  secretmanagerVaultName: vaultName
3283
3489
  });
3284
- return;
3285
- } catch (error) {
3286
- if (!(error instanceof ConnectError && error.code === Code.NotFound)) throw error;
3287
- }
3490
+ })) return;
3288
3491
  await client.createSecretManagerVault({
3289
3492
  workspaceId: update.workspaceId,
3290
3493
  secretmanagerVaultName: vaultName
@@ -3314,7 +3517,7 @@ async function applyIdP(client, result, phase = "create-update") {
3314
3517
  async function planIdP(context) {
3315
3518
  const { client, workspaceId, application, forRemoval, forceApplyAll = false, idpUserTriggerTargets } = context;
3316
3519
  const idps = forRemoval ? [] : application.idpServices;
3317
- const expectedLocalWebsites = new Set(application.staticWebsiteServices.map((website) => website.name));
3520
+ const expectedLocalWebsites = expectedLocalStaticWebsiteNames(context);
3318
3521
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$3(client, workspaceId, application.name, application.id, idps, idpUserTriggerTargets ?? /* @__PURE__ */ new Set(), expectedLocalWebsites);
3319
3522
  return {
3320
3523
  changeSet: {
@@ -3484,17 +3687,16 @@ async function planServices$3(client, workspaceId, appName, appId, idps, idpUser
3484
3687
  permission: protoPermission
3485
3688
  };
3486
3689
  if (existing) {
3487
- const owned = isOwnedByApp(existing.allLabels, appName, appId);
3488
- if (!owned) if (!existing.label) unmanaged.push({
3489
- resourceType: "IdP service",
3490
- resourceName: idp.name
3491
- });
3492
- else conflicts.push({
3690
+ if (trackDesiredResourceOwnership({
3691
+ labels: existing.allLabels,
3692
+ ownerLabel: existing.label,
3693
+ appName,
3694
+ appId,
3493
3695
  resourceType: "IdP service",
3494
3696
  resourceName: idp.name,
3495
- currentOwner: existing.label
3496
- });
3497
- if (owned && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areIdPServicesEqual(existing.resource, desired)) changeSet.unchanged.push({ name: namespaceName });
3697
+ conflicts,
3698
+ unmanaged
3699
+ }) && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areIdPServicesEqual(existing.resource, desired)) changeSet.unchanged.push({ name: namespaceName });
3498
3700
  else changeSet.updates.push({
3499
3701
  name: namespaceName,
3500
3702
  request,
@@ -3509,10 +3711,13 @@ async function planServices$3(client, workspaceId, appName, appId, idps, idpUser
3509
3711
  }
3510
3712
  Object.entries(existingServices).forEach(([namespaceName]) => {
3511
3713
  const entry = existingServices[namespaceName];
3512
- const label = entry?.label;
3513
- const owned = isOwnedByApp(entry?.allLabels, appName, appId);
3514
- if (label && !owned) resourceOwners.add(label);
3515
- if (owned) changeSet.deletes.push({
3714
+ if (trackRemainingResourceOwner({
3715
+ labels: entry?.allLabels,
3716
+ ownerLabel: entry?.label,
3717
+ appName,
3718
+ appId,
3719
+ resourceOwners
3720
+ })) changeSet.deletes.push({
3516
3721
  name: namespaceName,
3517
3722
  request: {
3518
3723
  workspaceId,
@@ -3530,19 +3735,14 @@ async function planServices$3(client, workspaceId, appName, appId, idps, idpUser
3530
3735
  async function planClients(client, workspaceId, idps, deletedServices, forceApplyAll = false) {
3531
3736
  const changeSet = createChangeSet("IdP clients");
3532
3737
  const fetchClients = (namespaceName) => {
3533
- return fetchAll(async (pageToken, maxPageSize) => {
3534
- try {
3535
- const { clients, nextPageToken } = await client.listIdPClients({
3536
- workspaceId,
3537
- namespaceName,
3538
- pageToken,
3539
- pageSize: maxPageSize
3540
- });
3541
- return [clients, nextPageToken];
3542
- } catch (error) {
3543
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
3544
- throw error;
3545
- }
3738
+ return fetchAllTolerant(async (pageToken, maxPageSize) => {
3739
+ const { clients, nextPageToken } = await client.listIdPClients({
3740
+ workspaceId,
3741
+ namespaceName,
3742
+ pageToken,
3743
+ pageSize: maxPageSize
3744
+ });
3745
+ return [clients, nextPageToken];
3546
3746
  });
3547
3747
  };
3548
3748
  const clientsByIdp = await Promise.all(idps.map((idp) => fetchClients(idp.name)));
@@ -3701,7 +3901,7 @@ function protoIdPOperand(operand) {
3701
3901
  */
3702
3902
  async function applyAuth(client, result, phase = "create-update") {
3703
3903
  const { changeSet } = result;
3704
- if (phase === "create-update") {
3904
+ const applyServices = async () => {
3705
3905
  await Promise.all([...changeSet.service.creates.map(async (create) => {
3706
3906
  await client.createAuthService(create.request);
3707
3907
  await client.setMetadata(create.metaRequest);
@@ -3709,6 +3909,11 @@ async function applyAuth(client, result, phase = "create-update") {
3709
3909
  await client.updateAuthService(update.request);
3710
3910
  await client.setMetadata(update.metaRequest);
3711
3911
  })]);
3912
+ };
3913
+ const applyMachineUsers = async () => {
3914
+ await Promise.all([...changeSet.machineUser.creates.map((create) => client.createAuthMachineUser(create.request)), ...changeSet.machineUser.updates.map((update) => client.updateAuthMachineUser(update.request))]);
3915
+ };
3916
+ const applyCreateUpdateDependents = async () => {
3712
3917
  await applyAuthConnections(client, { changeSet: changeSet.connection }, "create-update");
3713
3918
  await Promise.all([...changeSet.idpConfig.creates.map(async (create) => {
3714
3919
  if (create.idpConfig.kind === "BuiltInIdP") assertDefined(create.request.idpConfig, "request missing idpConfig").config = await protoBuiltinIdPConfig(client, assertDefined(create.request.workspaceId, "request missing workspaceId"), create.idpConfig);
@@ -3719,7 +3924,6 @@ async function applyAuth(client, result, phase = "create-update") {
3719
3924
  })]);
3720
3925
  await Promise.all([...changeSet.userProfileConfig.creates.map((create) => client.createUserProfileConfig(create.request)), ...changeSet.userProfileConfig.updates.map((update) => client.updateUserProfileConfig(update.request))]);
3721
3926
  await Promise.all([...changeSet.tenantConfig.creates.map((create) => client.createTenantConfig(create.request)), ...changeSet.tenantConfig.updates.map((update) => client.updateTenantConfig(update.request))]);
3722
- await Promise.all([...changeSet.machineUser.creates.map((create) => client.createAuthMachineUser(create.request)), ...changeSet.machineUser.updates.map((update) => client.updateAuthMachineUser(update.request))]);
3723
3927
  await Promise.all([...changeSet.authHook.creates.map((create) => client.createAuthHook(create.request)), ...changeSet.authHook.updates.map((update) => client.updateAuthHook(update.request))]);
3724
3928
  await Promise.all([...changeSet.oauth2Client.creates.map(async (create) => {
3725
3929
  const oauth2Client = assertDefined(create.request.oauth2Client, "request missing oauth2Client");
@@ -3738,7 +3942,13 @@ async function applyAuth(client, result, phase = "create-update") {
3738
3942
  }
3739
3943
  await Promise.all([...changeSet.scim.creates.map((create) => client.createAuthSCIMConfig(create.request)), ...changeSet.scim.updates.map((update) => client.updateAuthSCIMConfig(update.request))]);
3740
3944
  await Promise.all([...changeSet.scimResource.creates.map((create) => client.createAuthSCIMResource(create.request)), ...changeSet.scimResource.updates.map((update) => client.updateAuthSCIMResource(update.request))]);
3741
- } else if (phase === "delete-resources") {
3945
+ };
3946
+ if (phase === "create-update-prerequisites" || phase === "create-update") {
3947
+ await applyServices();
3948
+ await applyMachineUsers();
3949
+ }
3950
+ if (phase === "create-update-dependents" || phase === "create-update") await applyCreateUpdateDependents();
3951
+ if (phase === "delete-resources") {
3742
3952
  await Promise.all(changeSet.scimResource.deletes.map((del) => client.deleteAuthSCIMResource(del.request)));
3743
3953
  await Promise.all(changeSet.scim.deletes.map((del) => client.deleteAuthSCIMConfig(del.request)));
3744
3954
  await Promise.all(changeSet.oauth2Client.deletes.map((del) => client.deleteAuthOAuth2Client(del.request)));
@@ -3748,7 +3958,7 @@ async function applyAuth(client, result, phase = "create-update") {
3748
3958
  await Promise.all(changeSet.userProfileConfig.deletes.map((del) => client.deleteUserProfileConfig(del.request)));
3749
3959
  await Promise.all(changeSet.idpConfig.deletes.map((del) => client.deleteAuthIDPConfig(del.request)));
3750
3960
  await applyAuthConnections(client, { changeSet: changeSet.connection }, "delete-resources");
3751
- } else await Promise.all(changeSet.service.deletes.map((del) => client.deleteAuthService(del.request)));
3961
+ } else if (phase === "delete-services") await Promise.all(changeSet.service.deletes.map((del) => client.deleteAuthService(del.request)));
3752
3962
  }
3753
3963
  /**
3754
3964
  * Plan auth-related changes based on current and desired state.
@@ -3764,7 +3974,7 @@ async function planAuth(context) {
3764
3974
  }
3765
3975
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$2(client, workspaceId, application.name, application.id, auths, forceApplyAll);
3766
3976
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
3767
- const expectedLocalWebsites = new Set(application.staticWebsiteServices.map((website) => website.name));
3977
+ const expectedLocalWebsites = expectedLocalStaticWebsiteNames(context);
3768
3978
  const [idpConfigChangeSet, userProfileConfigChangeSet, tenantConfigChangeSet, machineUserChangeSet, authHookChangeSet, oauth2ClientChangeSet, scimChangeSet, scimResourceChangeSet, connectionResult] = await Promise.all([
3769
3979
  planIdPConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
3770
3980
  planUserProfileConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
@@ -3826,15 +4036,15 @@ async function planServices$2(client, workspaceId, appName, appId, auths, forceA
3826
4036
  publishSessionEvents: config.publishSessionEvents
3827
4037
  };
3828
4038
  if (existing) {
3829
- const owned = isOwnedByApp(existing.allLabels, appName, appId);
3830
- if (!owned) if (!existing.label) unmanaged.push({
3831
- resourceType: "Auth service",
3832
- resourceName: config.name
3833
- });
3834
- else conflicts.push({
4039
+ const owned = trackDesiredResourceOwnership({
4040
+ labels: existing.allLabels,
4041
+ ownerLabel: existing.label,
4042
+ appName,
4043
+ appId,
3835
4044
  resourceType: "Auth service",
3836
4045
  resourceName: config.name,
3837
- currentOwner: existing.label
4046
+ conflicts,
4047
+ unmanaged
3838
4048
  });
3839
4049
  if (!forceApplyAll && existing.resource.publishSessionEvents === (config.publishSessionEvents ?? false) && owned) changeSet.unchanged.push({ name: config.name });
3840
4050
  else changeSet.updates.push({
@@ -3851,10 +4061,13 @@ async function planServices$2(client, workspaceId, appName, appId, auths, forceA
3851
4061
  }
3852
4062
  Object.entries(existingServices).forEach(([namespaceName]) => {
3853
4063
  const entry = existingServices[namespaceName];
3854
- const label = entry?.label;
3855
- const owned = isOwnedByApp(entry?.allLabels, appName, appId);
3856
- if (label && !owned) resourceOwners.add(label);
3857
- if (owned) changeSet.deletes.push({
4064
+ if (trackRemainingResourceOwner({
4065
+ labels: entry?.allLabels,
4066
+ ownerLabel: entry?.label,
4067
+ appName,
4068
+ appId,
4069
+ resourceOwners
4070
+ })) changeSet.deletes.push({
3858
4071
  name: namespaceName,
3859
4072
  request: {
3860
4073
  workspaceId,
@@ -3872,19 +4085,14 @@ async function planServices$2(client, workspaceId, appName, appId, auths, forceA
3872
4085
  async function planIdPConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
3873
4086
  const changeSet = createChangeSet("Auth idpConfigs");
3874
4087
  const fetchIdPConfigs = (namespaceName) => {
3875
- return fetchAll(async (pageToken, maxPageSize) => {
3876
- try {
3877
- const { idpConfigs, nextPageToken } = await client.listAuthIDPConfigs({
3878
- workspaceId,
3879
- namespaceName,
3880
- pageToken,
3881
- pageSize: maxPageSize
3882
- });
3883
- return [idpConfigs, nextPageToken];
3884
- } catch (error) {
3885
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
3886
- throw error;
3887
- }
4088
+ return fetchAllTolerant(async (pageToken, maxPageSize) => {
4089
+ const { idpConfigs, nextPageToken } = await client.listAuthIDPConfigs({
4090
+ workspaceId,
4091
+ namespaceName,
4092
+ pageToken,
4093
+ pageSize: maxPageSize
4094
+ });
4095
+ return [idpConfigs, nextPageToken];
3888
4096
  });
3889
4097
  };
3890
4098
  for (const authService of auths) {
@@ -4041,27 +4249,21 @@ async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
4041
4249
  return config;
4042
4250
  }
4043
4251
  async function tryProtoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
4044
- let idpService;
4045
- try {
4046
- idpService = await client.getIdPService({
4252
+ const idpService = await getOrNull(async () => {
4253
+ return await client.getIdPService({
4047
4254
  workspaceId,
4048
4255
  namespaceName: builtinIdPConfig.namespace
4049
4256
  });
4050
- } catch (error) {
4051
- if (error instanceof ConnectError && error.code === Code.NotFound) return;
4052
- throw error;
4053
- }
4054
- let idpClient;
4055
- try {
4056
- idpClient = await client.getIdPClient({
4257
+ });
4258
+ if (!idpService) return void 0;
4259
+ const idpClient = await getOrNull(async () => {
4260
+ return await client.getIdPClient({
4057
4261
  workspaceId,
4058
4262
  namespaceName: builtinIdPConfig.namespace,
4059
4263
  name: builtinIdPConfig.clientName
4060
4264
  });
4061
- } catch (error) {
4062
- if (error instanceof ConnectError && error.code === Code.NotFound) return;
4063
- throw error;
4064
- }
4265
+ });
4266
+ if (!idpClient) return void 0;
4065
4267
  const vaultName = idpClientVaultName(builtinIdPConfig.namespace, builtinIdPConfig.clientName);
4066
4268
  const secretKey = idpClientSecretName(builtinIdPConfig.namespace, builtinIdPConfig.clientName);
4067
4269
  return { config: {
@@ -4082,56 +4284,51 @@ async function planUserProfileConfigs(client, workspaceId, auths, deletedService
4082
4284
  for (const auth of auths) {
4083
4285
  const { config } = auth;
4084
4286
  const name = `${config.name}-user-profile-config`;
4085
- try {
4086
- const { userProfileProviderConfig } = await client.getUserProfileConfig({
4287
+ const existing = await getOrNull(async () => {
4288
+ return await client.getUserProfileConfig({
4087
4289
  workspaceId,
4088
4290
  namespaceName: config.name
4089
4291
  });
4292
+ });
4293
+ if (!existing) {
4090
4294
  const userProfileForUpdate = auth.userProfile;
4091
- if (userProfileForUpdate) {
4092
- const desired = protoUserProfileConfig(userProfileForUpdate);
4093
- if (!forceApplyAll && areUserProfileConfigsEqual(userProfileProviderConfig ?? {}, desired)) changeSet.unchanged.push({ name });
4094
- else changeSet.updates.push({
4095
- name,
4096
- request: {
4097
- workspaceId,
4098
- namespaceName: config.name,
4099
- userProfileProviderConfig: desired
4100
- }
4101
- });
4102
- } else changeSet.deletes.push({
4295
+ if (userProfileForUpdate) changeSet.creates.push({
4103
4296
  name,
4104
4297
  request: {
4105
4298
  workspaceId,
4106
- namespaceName: config.name
4299
+ namespaceName: config.name,
4300
+ userProfileProviderConfig: protoUserProfileConfig(userProfileForUpdate)
4107
4301
  }
4108
4302
  });
4109
- } catch (error) {
4110
- if (error instanceof ConnectError && error.code === Code.NotFound) {
4111
- const userProfileForCreate = auth.userProfile;
4112
- if (userProfileForCreate) changeSet.creates.push({
4113
- name,
4114
- request: {
4115
- workspaceId,
4116
- namespaceName: config.name,
4117
- userProfileProviderConfig: protoUserProfileConfig(userProfileForCreate)
4118
- }
4119
- });
4120
- continue;
4121
- }
4122
- throw error;
4303
+ continue;
4123
4304
  }
4305
+ const userProfileForUpdate = auth.userProfile;
4306
+ if (userProfileForUpdate) {
4307
+ const desired = protoUserProfileConfig(userProfileForUpdate);
4308
+ if (!forceApplyAll && areUserProfileConfigsEqual(existing.userProfileProviderConfig ?? {}, desired)) changeSet.unchanged.push({ name });
4309
+ else changeSet.updates.push({
4310
+ name,
4311
+ request: {
4312
+ workspaceId,
4313
+ namespaceName: config.name,
4314
+ userProfileProviderConfig: desired
4315
+ }
4316
+ });
4317
+ } else changeSet.deletes.push({
4318
+ name,
4319
+ request: {
4320
+ workspaceId,
4321
+ namespaceName: config.name
4322
+ }
4323
+ });
4124
4324
  }
4125
4325
  for (const namespaceName of deletedServices) {
4126
- try {
4127
- await client.getUserProfileConfig({
4326
+ if (!await getOrNull(async () => {
4327
+ return await client.getUserProfileConfig({
4128
4328
  workspaceId,
4129
4329
  namespaceName
4130
4330
  });
4131
- } catch (error) {
4132
- if (error instanceof ConnectError && error.code === Code.NotFound) continue;
4133
- throw error;
4134
- }
4331
+ })) continue;
4135
4332
  changeSet.deletes.push({
4136
4333
  name: `${namespaceName}-user-profile-config`,
4137
4334
  request: {
@@ -4165,54 +4362,49 @@ async function planTenantConfigs(client, workspaceId, auths, deletedServices, fo
4165
4362
  for (const auth of auths) {
4166
4363
  const { config } = auth;
4167
4364
  const name = `${config.name}-tenant-config`;
4168
- try {
4169
- const { tenantProviderConfig } = await client.getTenantConfig({
4365
+ const existing = await getOrNull(async () => {
4366
+ return await client.getTenantConfig({
4170
4367
  workspaceId,
4171
4368
  namespaceName: config.name
4172
4369
  });
4173
- if (config.tenantProvider) {
4174
- const desired = protoTenantConfig(config.tenantProvider);
4175
- if (!forceApplyAll && areTenantProviderConfigsEqual(tenantProviderConfig, desired)) changeSet.unchanged.push({ name });
4176
- else changeSet.updates.push({
4177
- name,
4178
- request: {
4179
- workspaceId,
4180
- namespaceName: config.name,
4181
- tenantProviderConfig: desired
4182
- }
4183
- });
4184
- } else changeSet.deletes.push({
4370
+ });
4371
+ if (!existing) {
4372
+ if (config.tenantProvider) changeSet.creates.push({
4185
4373
  name,
4186
4374
  request: {
4187
4375
  workspaceId,
4188
- namespaceName: config.name
4376
+ namespaceName: config.name,
4377
+ tenantProviderConfig: protoTenantConfig(config.tenantProvider)
4189
4378
  }
4190
4379
  });
4191
- } catch (error) {
4192
- if (error instanceof ConnectError && error.code === Code.NotFound) {
4193
- if (config.tenantProvider) changeSet.creates.push({
4194
- name,
4195
- request: {
4196
- workspaceId,
4197
- namespaceName: config.name,
4198
- tenantProviderConfig: protoTenantConfig(config.tenantProvider)
4199
- }
4200
- });
4201
- continue;
4202
- }
4203
- throw error;
4380
+ continue;
4204
4381
  }
4382
+ if (config.tenantProvider) {
4383
+ const desired = protoTenantConfig(config.tenantProvider);
4384
+ if (!forceApplyAll && areTenantProviderConfigsEqual(existing.tenantProviderConfig, desired)) changeSet.unchanged.push({ name });
4385
+ else changeSet.updates.push({
4386
+ name,
4387
+ request: {
4388
+ workspaceId,
4389
+ namespaceName: config.name,
4390
+ tenantProviderConfig: desired
4391
+ }
4392
+ });
4393
+ } else changeSet.deletes.push({
4394
+ name,
4395
+ request: {
4396
+ workspaceId,
4397
+ namespaceName: config.name
4398
+ }
4399
+ });
4205
4400
  }
4206
4401
  for (const namespaceName of deletedServices) {
4207
- try {
4208
- await client.getTenantConfig({
4402
+ if (!await getOrNull(async () => {
4403
+ return await client.getTenantConfig({
4209
4404
  workspaceId,
4210
4405
  namespaceName
4211
4406
  });
4212
- } catch (error) {
4213
- if (error instanceof ConnectError && error.code === Code.NotFound) continue;
4214
- throw error;
4215
- }
4407
+ })) continue;
4216
4408
  changeSet.deletes.push({
4217
4409
  name: `${namespaceName}-tenant-config`,
4218
4410
  request: {
@@ -4239,19 +4431,14 @@ function protoTenantConfig(tenantConfig) {
4239
4431
  async function planMachineUsers(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
4240
4432
  const changeSet = createChangeSet("Auth machineUsers");
4241
4433
  const fetchMachineUsers = (authNamespace) => {
4242
- return fetchAll(async (pageToken, maxPageSize) => {
4243
- try {
4244
- const { machineUsers, nextPageToken } = await client.listAuthMachineUsers({
4245
- workspaceId,
4246
- authNamespace,
4247
- pageToken,
4248
- pageSize: maxPageSize
4249
- });
4250
- return [machineUsers, nextPageToken];
4251
- } catch (error) {
4252
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
4253
- throw error;
4254
- }
4434
+ return fetchAllTolerant(async (pageToken, maxPageSize) => {
4435
+ const { machineUsers, nextPageToken } = await client.listAuthMachineUsers({
4436
+ workspaceId,
4437
+ authNamespace,
4438
+ pageToken,
4439
+ pageSize: maxPageSize
4440
+ });
4441
+ return [machineUsers, nextPageToken];
4255
4442
  });
4256
4443
  };
4257
4444
  for (const auth of auths) {
@@ -4378,19 +4565,14 @@ function areOAuth2ClientsEqual(existing, desired) {
4378
4565
  async function planOAuth2Clients(client, workspaceId, auths, deletedServices, expectedLocalWebsites, forceApplyAll = false) {
4379
4566
  const changeSet = createChangeSet("Auth oauth2Clients");
4380
4567
  const fetchOAuth2Clients = (namespaceName) => {
4381
- return fetchAll(async (pageToken, maxPageSize) => {
4382
- try {
4383
- const { oauth2Clients, nextPageToken } = await client.listAuthOAuth2Clients({
4384
- workspaceId,
4385
- namespaceName,
4386
- pageToken,
4387
- pageSize: maxPageSize
4388
- });
4389
- return [oauth2Clients, nextPageToken];
4390
- } catch (error) {
4391
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
4392
- throw error;
4393
- }
4568
+ return fetchAllTolerant(async (pageToken, maxPageSize) => {
4569
+ const { oauth2Clients, nextPageToken } = await client.listAuthOAuth2Clients({
4570
+ workspaceId,
4571
+ namespaceName,
4572
+ pageToken,
4573
+ pageSize: maxPageSize
4574
+ });
4575
+ return [oauth2Clients, nextPageToken];
4394
4576
  });
4395
4577
  };
4396
4578
  for (const auth of auths) {
@@ -4507,26 +4689,21 @@ async function planSCIMConfigs(client, workspaceId, auths, deletedServices) {
4507
4689
  for (const auth of auths) {
4508
4690
  const { config } = auth;
4509
4691
  const name = `${config.name}-scim-config`;
4510
- try {
4511
- await client.getAuthSCIMConfig({
4692
+ if (!await getOrNull(async () => {
4693
+ return await client.getAuthSCIMConfig({
4512
4694
  workspaceId,
4513
4695
  namespaceName: config.name
4514
4696
  });
4515
- } catch (error) {
4516
- if (error instanceof ConnectError && error.code === Code.NotFound) {
4517
- if (config.scim) changeSet.creates.push({
4518
- name,
4519
- request: {
4520
- workspaceId,
4521
- namespaceName: config.name,
4522
- scimConfig: protoSCIMConfig(config.scim)
4523
- }
4524
- });
4525
- continue;
4526
- }
4527
- throw error;
4528
- }
4529
- if (config.scim) changeSet.updates.push({
4697
+ })) {
4698
+ if (config.scim) changeSet.creates.push({
4699
+ name,
4700
+ request: {
4701
+ workspaceId,
4702
+ namespaceName: config.name,
4703
+ scimConfig: protoSCIMConfig(config.scim)
4704
+ }
4705
+ });
4706
+ } else if (config.scim) changeSet.updates.push({
4530
4707
  name,
4531
4708
  request: {
4532
4709
  workspaceId,
@@ -4543,15 +4720,12 @@ async function planSCIMConfigs(client, workspaceId, auths, deletedServices) {
4543
4720
  });
4544
4721
  }
4545
4722
  for (const namespaceName of deletedServices) {
4546
- try {
4547
- await client.getAuthSCIMConfig({
4723
+ if (!await getOrNull(async () => {
4724
+ return await client.getAuthSCIMConfig({
4548
4725
  workspaceId,
4549
4726
  namespaceName
4550
4727
  });
4551
- } catch (error) {
4552
- if (error instanceof ConnectError && error.code === Code.NotFound) continue;
4553
- throw error;
4554
- }
4728
+ })) continue;
4555
4729
  changeSet.deletes.push({
4556
4730
  name: `${namespaceName}-scim-config`,
4557
4731
  request: {
@@ -4588,16 +4762,13 @@ function protoSCIMConfig(scimConfig) {
4588
4762
  async function planSCIMResources(client, workspaceId, auths, deletedServices) {
4589
4763
  const changeSet = createChangeSet("Auth scimResources");
4590
4764
  const fetchSCIMResources = async (namespaceName) => {
4591
- try {
4765
+ return await getOrNull(async () => {
4592
4766
  const { scimResources } = await client.getAuthSCIMResources({
4593
4767
  workspaceId,
4594
4768
  namespaceName
4595
4769
  });
4596
4770
  return scimResources;
4597
- } catch (error) {
4598
- if (error instanceof ConnectError && error.code === Code.NotFound) return [];
4599
- throw error;
4600
- }
4771
+ }) ?? [];
4601
4772
  };
4602
4773
  for (const auth of auths) {
4603
4774
  const { config } = auth;
@@ -4755,18 +4926,14 @@ async function planAuthHooks(client, workspaceId, auths, deletedServices, forceA
4755
4926
  for (const auth of auths) {
4756
4927
  const { config } = auth;
4757
4928
  const beforeLogin = config.hooks?.beforeLogin;
4758
- let existingHook;
4759
- try {
4929
+ const existingHook = await getOrNull(async () => {
4760
4930
  const { hook } = await client.getAuthHook({
4761
4931
  workspaceId,
4762
4932
  namespaceName: config.name,
4763
4933
  hookPoint: AuthHookPoint.BEFORE_LOGIN
4764
4934
  });
4765
- existingHook = hook;
4766
- } catch (error) {
4767
- if (error instanceof ConnectError && error.code === Code.NotFound) existingHook = void 0;
4768
- else throw error;
4769
- }
4935
+ return hook;
4936
+ });
4770
4937
  if (beforeLogin) {
4771
4938
  const hookRequest = {
4772
4939
  workspaceId,
@@ -4798,291 +4965,23 @@ async function planAuthHooks(client, workspaceId, auths, deletedServices, forceA
4798
4965
  }
4799
4966
  });
4800
4967
  }
4801
- for (const namespaceName of deletedServices) try {
4802
- await client.getAuthHook({
4968
+ for (const namespaceName of deletedServices) if (await getOrNull(async () => {
4969
+ return await client.getAuthHook({
4803
4970
  workspaceId,
4804
4971
  namespaceName,
4805
4972
  hookPoint: AuthHookPoint.BEFORE_LOGIN
4806
4973
  });
4807
- changeSet.deletes.push({
4808
- name: `${namespaceName}/before-login`,
4809
- request: {
4810
- workspaceId,
4811
- namespaceName,
4812
- hookPoint: AuthHookPoint.BEFORE_LOGIN
4813
- }
4814
- });
4815
- } catch (error) {
4816
- if (error instanceof ConnectError && error.code === Code.NotFound) {} else throw error;
4817
- }
4974
+ })) changeSet.deletes.push({
4975
+ name: `${namespaceName}/before-login`,
4976
+ request: {
4977
+ workspaceId,
4978
+ namespaceName,
4979
+ hookPoint: AuthHookPoint.BEFORE_LOGIN
4980
+ }
4981
+ });
4818
4982
  return changeSet;
4819
4983
  }
4820
4984
 
4821
- //#endregion
4822
- //#region src/cli/commands/deploy/config-id-injector.ts
4823
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4824
- function findDefineConfigCalls(node, results) {
4825
- if (!node || typeof node !== "object") return;
4826
- const n = node;
4827
- if (n.type === "CallExpression") {
4828
- const ce = n;
4829
- if (ce.callee.type === "Identifier" && ce.callee.name === "defineConfig") {
4830
- const arg = ce.arguments[0];
4831
- const configObj = arg && arg.type === "ObjectExpression" ? arg : null;
4832
- results.push({
4833
- callExpr: ce,
4834
- configObj
4835
- });
4836
- }
4837
- }
4838
- for (const key of Object.keys(n)) {
4839
- const child = n[key];
4840
- if (Array.isArray(child)) for (const c of child) findDefineConfigCalls(c, results);
4841
- else if (child && typeof child === "object") findDefineConfigCalls(child, results);
4842
- }
4843
- }
4844
- function findIdProperty(obj) {
4845
- for (const prop of obj.properties) {
4846
- if (prop.type !== "Property") continue;
4847
- if ((prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" ? prop.key.value : null) === "id") return prop;
4848
- }
4849
- return null;
4850
- }
4851
- /**
4852
- * Ensure `tailor.config.ts` has an `id` property on the `defineConfig({...})`
4853
- * argument. Generates a UUID when missing and writes it back to the file.
4854
- * Returns null when the file does not contain a `defineConfig()` call (e.g.
4855
- * a wrapper that re-exports another config).
4856
- * @param configPath - Absolute path to the config file
4857
- * @returns Resolved id and whether it was newly injected, or null if skipped
4858
- */
4859
- async function ensureConfigId(configPath) {
4860
- const source = await fs$1.promises.readFile(configPath, "utf-8");
4861
- const { program } = parseSync(configPath, source);
4862
- const calls = [];
4863
- findDefineConfigCalls(program, calls);
4864
- if (calls.length === 0) return null;
4865
- if (calls.length > 1) throw new Error(`Multiple defineConfig() calls found in ${configPath}. Only one is supported.`);
4866
- const { configObj } = assertDefined(calls[0], "defineConfig call site missing");
4867
- if (!configObj) throw new Error(`defineConfig() argument must be an inline object literal in ${configPath} so the SDK can manage the 'id' field.`);
4868
- const idProp = findIdProperty(configObj);
4869
- if (idProp) {
4870
- const value = idProp.value;
4871
- if (value.type !== "Literal") throw new Error(`'id' field in ${configPath} must be a string literal. To use this config for a separate app, delete it.`);
4872
- const literalValue = value.value;
4873
- if (typeof literalValue !== "string" || literalValue === "") throw new Error(`'id' field in ${configPath} must be a non-empty string literal. To use this config for a separate app, delete it.`);
4874
- if (!uuidRegex.test(literalValue)) throw new Error(`'id' field in ${configPath} must be a UUID. To use this config for a separate app, delete it.`);
4875
- return {
4876
- id: literalValue,
4877
- injected: false
4878
- };
4879
- }
4880
- const id = crypto.randomUUID();
4881
- const newSource = insertIdProperty(source, configObj, id);
4882
- await fs$1.promises.writeFile(configPath, newSource, "utf-8");
4883
- logger.info(`Generated app id and wrote to ${configPath}: ${id}`);
4884
- return {
4885
- id,
4886
- injected: true
4887
- };
4888
- }
4889
- /**
4890
- * Read the resolved `defineConfig({...})` `id` from a config file without
4891
- * mutating it. Returns null when the file has no inline `defineConfig()` call
4892
- * (wrapper/re-export config), and `{ id: null }` when the call exists but has
4893
- * no usable `id` property.
4894
- * @param configPath - Absolute path to the config file
4895
- * @returns The existing id (or null when absent), or null for wrapper configs
4896
- */
4897
- async function readConfigId(configPath) {
4898
- const { program } = parseSync(configPath, await fs$1.promises.readFile(configPath, "utf-8"));
4899
- const calls = [];
4900
- findDefineConfigCalls(program, calls);
4901
- if (calls.length === 0) return null;
4902
- if (calls.length > 1) throw new Error(`Multiple defineConfig() calls found in ${configPath}. Only one is supported.`);
4903
- const { configObj } = assertDefined(calls[0], "defineConfig call site missing");
4904
- if (!configObj) throw new Error(`defineConfig() argument must be an inline object literal in ${configPath} so the SDK can manage the 'id' field.`);
4905
- const idProp = findIdProperty(configObj);
4906
- if (!idProp || idProp.value.type !== "Literal") return { id: null };
4907
- const value = idProp.value.value;
4908
- return { id: typeof value === "string" && value !== "" ? value : null };
4909
- }
4910
- /**
4911
- * Read-only CI check: the config must already carry a valid app id.
4912
- * Wrapper/re-export configs (no inline defineConfig call) are exempt,
4913
- * mirroring the local behavior where {@link ensureConfigId} no-ops.
4914
- * @param configPath - Absolute path to the config file
4915
- */
4916
- async function assertConfigIdInCI(configPath) {
4917
- const result = await readConfigId(configPath);
4918
- if (result === null) return;
4919
- if (!result.id) throw new Error("tailor.config.ts is missing an 'id'. CI does not auto-generate one (each run would be treated as a separate app and break resource ownership). Run 'tailor-sdk setup' or 'tailor-sdk apply' locally and commit the injected id.");
4920
- if (!uuidRegex.test(result.id)) throw new Error(`'id' in ${configPath} must be a UUID. To use this config for a separate app, delete it.`);
4921
- }
4922
- /**
4923
- * Ensure the config has an app id for a deploy run.
4924
- *
4925
- * Locally, the id is auto-injected when missing (via {@link ensureConfigId}).
4926
- * In CI, the id is never auto-injected — a missing id is a hard error, because
4927
- * generating one per run would create a fresh app each time and break resource
4928
- * ownership. CI dry-runs (plan) perform the same check read-only, so a
4929
- * forgotten id fails at PR time instead of at deploy. Ephemeral pipelines that
4930
- * intentionally deploy a fresh app per run (such as e2e harnesses) can opt
4931
- * back into injection with `TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION=true`.
4932
- * Local dry-run and build-only flows skip both injection and the check (no
4933
- * on-disk side effects are expected, and build-only never talks to the
4934
- * platform).
4935
- * @param obj - Inputs
4936
- * @param obj.configPath - Absolute path to the config file
4937
- * @param obj.dryRun - Whether this is a dry-run
4938
- * @param obj.buildOnly - Whether this is a build-only run
4939
- */
4940
- async function ensureConfigIdForDeploy(obj) {
4941
- const { configPath, dryRun, buildOnly } = obj;
4942
- if (buildOnly) return;
4943
- const allowCIInjection = parseBoolean(process.env.TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION) === true;
4944
- const strictCI = isCI && !allowCIInjection;
4945
- if (dryRun) {
4946
- if (strictCI) await assertConfigIdInCI(configPath);
4947
- return;
4948
- }
4949
- if (strictCI) {
4950
- await assertConfigIdInCI(configPath);
4951
- return;
4952
- }
4953
- await ensureConfigId(configPath);
4954
- }
4955
- const idComment = "// SDK-managed app id — do not edit, except when copying this config to a separate app.";
4956
- function insertIdProperty(source, configObj, id) {
4957
- const idLiteral = `id: ${JSON.stringify(id)}`;
4958
- if (configObj.properties.length > 0) {
4959
- const firstProp = assertDefined(configObj.properties[0], "first property missing");
4960
- const lineStart = source.lastIndexOf("\n", firstProp.start - 1) + 1;
4961
- const indent = source.slice(lineStart, firstProp.start);
4962
- if (!/^[\t ]*$/.test(indent)) return source.slice(0, firstProp.start) + `${idLiteral}, ` + source.slice(firstProp.start);
4963
- const insertion = `${idComment}\n${indent}${idLiteral},\n${indent}`;
4964
- return source.slice(0, firstProp.start) + insertion + source.slice(firstProp.start);
4965
- }
4966
- const openBracePos = configObj.start + 1;
4967
- const braceLineStart = source.lastIndexOf("\n", configObj.start) + 1;
4968
- const baseIndent = source.slice(braceLineStart).match(/^[\t ]*/)?.[0] ?? "";
4969
- const innerIndent = `${baseIndent} `;
4970
- const insertion = `\n${innerIndent}${idComment}\n${innerIndent}${idLiteral},\n${baseIndent}`;
4971
- return source.slice(0, openBracePos) + insertion + source.slice(openBracePos);
4972
- }
4973
-
4974
- //#endregion
4975
- //#region src/cli/commands/deploy/confirm.ts
4976
- /**
4977
- * Confirm reassignment of resources when owner conflicts are detected.
4978
- * Splits into two scenarios: id regeneration (same sdk-name, different
4979
- * sdk-app-id) and name mismatch (different sdk-name). Each gets its own
4980
- * prompt because the user-facing meaning is different.
4981
- * @param conflicts - Detected owner conflicts
4982
- * @param appName - Target application name
4983
- * @param yes - Whether to auto-confirm without prompting
4984
- * @returns Promise that resolves when confirmation completes
4985
- */
4986
- async function confirmOwnerConflict(conflicts, appName, yes) {
4987
- if (conflicts.length === 0) return;
4988
- const idRegenerated = conflicts.filter((c) => c.currentOwner === appName);
4989
- const nameMismatches = conflicts.filter((c) => c.currentOwner !== appName);
4990
- if (idRegenerated.length > 0) await confirmIdRegeneration(idRegenerated, appName, yes);
4991
- if (nameMismatches.length > 0) await confirmNameMismatch(nameMismatches, appName, yes);
4992
- }
4993
- async function confirmIdRegeneration(conflicts, appName, yes) {
4994
- logger.warn(`Application id was regenerated for "${appName}":`);
4995
- logger.log(" These resources still carry the previous id.");
4996
- logger.newline();
4997
- logger.log(` ${styles.info("Resources")}:`);
4998
- for (const c of conflicts) logger.log(` • ${styles.bold(c.resourceType)} ${styles.info(`"${c.resourceName}"`)}`);
4999
- if (yes) {
5000
- logger.success("Re-tagging resources with the new id (--yes flag specified)...", { mode: "plain" });
5001
- return;
5002
- }
5003
- if (!await prompt.confirm({
5004
- message: `Re-tag these resources with the new id for "${appName}"?\n${styles.dim("(The id in tailor.config.ts was removed since the previous deploy, so a new one was generated)")}`,
5005
- default: false
5006
- })) throw new Error(multiline`
5007
- Apply cancelled. Resources remain tagged with the previous id.
5008
- To override, run again and confirm, or use --yes flag.
5009
- `);
5010
- }
5011
- async function confirmNameMismatch(conflicts, appName, yes) {
5012
- const currentOwners = [...new Set(conflicts.map((c) => c.currentOwner))];
5013
- logger.warn("Application name mismatch detected:");
5014
- logger.log(` ${styles.warning("Current application(s)")}: ${currentOwners.map((o) => styles.bold(`"${o}"`)).join(", ")}`);
5015
- logger.log(` ${styles.success("New application")}: ${styles.bold(`"${appName}"`)}`);
5016
- logger.newline();
5017
- logger.log(` ${styles.info("Resources")}:`);
5018
- for (const c of conflicts) logger.log(` • ${styles.bold(c.resourceType)} ${styles.info(`"${c.resourceName}"`)}`);
5019
- if (yes) {
5020
- logger.success("Updating resources (--yes flag specified)...", { mode: "plain" });
5021
- return;
5022
- }
5023
- const promptMessage = currentOwners.length === 1 ? `Update these resources to be managed by "${appName}"?\n${styles.dim("(Common when renaming your application)")}` : `Update these resources to be managed by "${appName}"?`;
5024
- if (!await prompt.confirm({
5025
- message: promptMessage,
5026
- default: false
5027
- })) throw new Error(multiline`
5028
- Apply cancelled. Resources remain managed by their current applications.
5029
- To override, run again and confirm, or use --yes flag.
5030
- `);
5031
- }
5032
- /**
5033
- * Confirm allowing tailor-sdk to manage previously unmanaged resources.
5034
- * @param resources - Unmanaged resources
5035
- * @param appName - Target application name
5036
- * @param yes - Whether to auto-confirm without prompting
5037
- * @returns Promise that resolves when confirmation completes
5038
- */
5039
- async function confirmUnmanagedResources(resources, appName, yes) {
5040
- if (resources.length === 0) return;
5041
- logger.warn("Existing resources not tracked by tailor-sdk were found:");
5042
- logger.log(` ${styles.info("Resources")}:`);
5043
- for (const r of resources) logger.log(` • ${styles.bold(r.resourceType)} ${styles.info(`"${r.resourceName}"`)}`);
5044
- logger.newline();
5045
- logger.log(" These resources may have been created by older SDK versions, Terraform, or CUE.");
5046
- logger.log(" To continue, confirm that tailor-sdk should manage them.");
5047
- logger.log(" If they are managed by another tool (e.g., Terraform), cancel and manage them there instead.");
5048
- if (yes) {
5049
- logger.success(`Adding to "${appName}" (--yes flag specified)...`, { mode: "plain" });
5050
- return;
5051
- }
5052
- if (!await prompt.confirm({
5053
- message: `Allow tailor-sdk to manage these resources for "${appName}"?`,
5054
- default: false
5055
- })) throw new Error(multiline`
5056
- Apply cancelled. Resources remain unmanaged.
5057
- To override, run again and confirm, or use --yes flag.
5058
- `);
5059
- }
5060
- /**
5061
- * Confirm deletion of important resources.
5062
- * @param resources - Resources scheduled for deletion
5063
- * @param yes - Whether to auto-confirm without prompting
5064
- * @returns Promise that resolves when confirmation completes
5065
- */
5066
- async function confirmImportantResourceDeletion(resources, yes) {
5067
- if (resources.length === 0) return;
5068
- logger.warn("The following resources will be deleted:");
5069
- logger.log(` ${styles.info("Resources")}:`);
5070
- for (const r of resources) logger.log(` • ${styles.bold(r.resourceType)} ${styles.error(`"${r.resourceName}"`)}`);
5071
- logger.newline();
5072
- logger.log(styles.warning(" Deleting these resources will permanently remove all associated data."));
5073
- if (yes) {
5074
- logger.success("Deleting resources (--yes flag specified)...", { mode: "plain" });
5075
- return;
5076
- }
5077
- if (!await prompt.confirm({
5078
- message: "Are you sure you want to delete these resources?",
5079
- default: false
5080
- })) throw new Error(multiline`
5081
- Apply cancelled. Resources will not be deleted.
5082
- To override, run again and confirm, or use --yes flag.
5083
- `);
5084
- }
5085
-
5086
4985
  //#endregion
5087
4986
  //#region src/cli/commands/deploy/auth-invoker.ts
5088
4987
  /**
@@ -5161,7 +5060,7 @@ async function planExecutor(context) {
5161
5060
  appName: application.name,
5162
5061
  appId: application.id
5163
5062
  });
5164
- const desiredExecutor = protoExecutor(application, executor);
5063
+ const desiredExecutor = protoExecutor(context, executor);
5165
5064
  if (existing) {
5166
5065
  if (trackDesiredResourceOwnership({
5167
5066
  labels: existing.allLabels,
@@ -5293,35 +5192,101 @@ function normalizeComparableExecutor(executor) {
5293
5192
  function areExecutorsEqual(existing, desired) {
5294
5193
  return areNormalizedEqual(normalizeComparableExecutor(existing), normalizeComparableExecutor(desired));
5295
5194
  }
5296
- function resolveTailorDBNamespace(application, typeName) {
5297
- for (const service of application.tailorDBServices) if (Object.hasOwn(service.types, typeName)) return service.namespace;
5298
- throw new Error(`TailorDB type "${typeName}" not found in any namespace. Available namespaces: ${application.tailorDBServices.map((s) => s.namespace).join(", ")}`);
5195
+ /**
5196
+ * Resolve a resource's namespace from same-run peer configs, throwing when the
5197
+ * name is claimed by more than one namespace in the deploy run.
5198
+ * @param sameRunNamespaces - Namespaces keyed by resource name across the deploy run
5199
+ * @param resourceName - Resource name to resolve
5200
+ * @param resourceLabel - Resource label used in error messages
5201
+ * @returns The owning namespace, or undefined when the name is unknown
5202
+ */
5203
+ function resolveSameRunNamespace(sameRunNamespaces, resourceName, resourceLabel) {
5204
+ if (!sameRunNamespaces?.has(resourceName)) return;
5205
+ const namespace = sameRunNamespaces.get(resourceName);
5206
+ if (!namespace) throw new Error(`${resourceLabel} "${resourceName}" is defined in multiple namespaces in this deploy run. Move the trigger to the application that owns it or use unique names.`);
5207
+ return namespace;
5208
+ }
5209
+ /**
5210
+ * Find the local TailorDB namespace that declares the given type.
5211
+ * @param application - Loaded application
5212
+ * @param typeName - TailorDB type name to look up
5213
+ * @returns The declaring namespace, or undefined when no local service has it
5214
+ */
5215
+ function findTailorDBNamespace(application, typeName) {
5216
+ return application.tailorDBServices.find((service) => Object.hasOwn(service.types, typeName))?.namespace;
5217
+ }
5218
+ /**
5219
+ * Find the local resolver namespace that declares the given resolver.
5220
+ * @param application - Loaded application
5221
+ * @param resolverName - Resolver name to look up
5222
+ * @returns The declaring namespace, or undefined when no local service has it
5223
+ */
5224
+ function findResolverNamespace(application, resolverName) {
5225
+ return application.resolverServices.find((service) => Object.values(service.resolvers).some((resolver) => resolver.name === resolverName))?.namespace;
5226
+ }
5227
+ function resolveNamespace(params) {
5228
+ const { resourceLabel, resourceName, localNamespaces, findLocalNamespace, sameRunNamespaces } = params;
5229
+ const localNamespace = findLocalNamespace();
5230
+ if (localNamespace !== void 0) return localNamespace;
5231
+ const sameRunNamespace = resolveSameRunNamespace(sameRunNamespaces, resourceName, resourceLabel);
5232
+ if (sameRunNamespace !== void 0) return sameRunNamespace;
5233
+ const sameRunAvailableNamespaces = sameRunNamespaces ? [...new Set([...sameRunNamespaces.values()].filter((value) => value !== void 0))] : [];
5234
+ const availableNamespaces = [...localNamespaces, ...sameRunAvailableNamespaces];
5235
+ throw new Error(`${resourceLabel} "${resourceName}" not found in any namespace. Available namespaces: ${availableNamespaces.join(", ")}`);
5236
+ }
5237
+ function resolveTailorDBNamespace(application, typeName, sameRunNamespaces) {
5238
+ return resolveNamespace({
5239
+ resourceLabel: "TailorDB type",
5240
+ resourceName: typeName,
5241
+ localNamespaces: application.tailorDBServices.map((service) => service.namespace),
5242
+ findLocalNamespace: () => findTailorDBNamespace(application, typeName),
5243
+ sameRunNamespaces
5244
+ });
5299
5245
  }
5300
- function resolveResolverNamespace(application, resolverName) {
5301
- for (const service of application.resolverServices) if (Object.values(service.resolvers).some((r) => r.name === resolverName)) return service.namespace;
5302
- throw new Error(`Resolver "${resolverName}" not found in any namespace. Available namespaces: ${application.resolverServices.map((s) => s.namespace).join(", ")}`);
5246
+ function resolveResolverNamespace(application, resolverName, sameRunNamespaces) {
5247
+ return resolveNamespace({
5248
+ resourceLabel: "Resolver",
5249
+ resourceName: resolverName,
5250
+ localNamespaces: application.resolverServices.map((service) => service.namespace),
5251
+ findLocalNamespace: () => findResolverNamespace(application, resolverName),
5252
+ sameRunNamespaces
5253
+ });
5303
5254
  }
5304
- function resolveIdpNamespace(application, executorName, idpName) {
5305
- if (application.idpServices.length === 0) throw new Error(`Executor "${executorName}" uses an idpUser trigger but no IdP is configured.`);
5255
+ /**
5256
+ * Collect IdP names declared by the application, including external IdP
5257
+ * subgraphs.
5258
+ * @param application - Loaded application
5259
+ * @returns IdP names visible from the application config
5260
+ */
5261
+ function collectApplicationIdpNames(application) {
5262
+ const names = new Set(application.idpServices.map((idp) => idp.name));
5263
+ for (const subgraph of application.subgraphs) if (subgraph.Type === "idp") names.add(subgraph.Name);
5264
+ return names;
5265
+ }
5266
+ function resolveIdpNamespace(application, executorName, idpName, sameRunIdpNames) {
5267
+ const localIdpNames = collectApplicationIdpNames(application);
5306
5268
  if (idpName !== void 0) {
5307
- const found = application.idpServices.find((idp) => idp.name === idpName);
5308
- if (!found) {
5309
- const available = application.idpServices.map((idp) => idp.name).join(", ");
5269
+ const candidateNames = sameRunIdpNames ?? localIdpNames;
5270
+ if (!candidateNames.has(idpName)) {
5271
+ const available = [...candidateNames].join(", ");
5310
5272
  throw new Error(`Executor "${executorName}" specifies IdP "${idpName}" in its idpUser trigger, but no IdP with that name is configured. Available IdPs: ${available}`);
5311
5273
  }
5312
- return found.name;
5274
+ return idpName;
5313
5275
  }
5314
- if (application.idpServices.length > 1) {
5315
- const available = application.idpServices.map((idp) => idp.name).join(", ");
5276
+ if (localIdpNames.size === 0) throw new Error(`Executor "${executorName}" uses an idpUser trigger but no IdP is configured.`);
5277
+ if (localIdpNames.size > 1) {
5278
+ const available = [...localIdpNames].join(", ");
5316
5279
  throw new Error(`Executor "${executorName}" uses an idpUser trigger but the project defines multiple IdPs (${available}). Specify which IdP to subscribe to via the trigger's "idp" option.`);
5317
5280
  }
5318
- return assertDefined(application.idpServices[0], "idp service missing").name;
5281
+ return assertDefined([...localIdpNames][0], "idp service missing");
5319
5282
  }
5320
5283
  function resolveAuthNamespace(application) {
5321
- if (!application.authService) throw new Error("No Auth service configured");
5322
- return application.authService.config.name;
5284
+ const authNamespace = application.authService?.config.name ?? application.config.auth?.name;
5285
+ if (!authNamespace) throw new Error("No Auth service configured");
5286
+ return authNamespace;
5323
5287
  }
5324
- function protoExecutor(application, executor) {
5288
+ function protoExecutor(context, executor) {
5289
+ const { application } = context;
5325
5290
  const appName = application.name;
5326
5291
  const env = application.env;
5327
5292
  const trigger = executor.trigger;
@@ -5351,7 +5316,7 @@ function protoExecutor(application, executor) {
5351
5316
  case: "tailordb",
5352
5317
  value: {
5353
5318
  eventTypes: trigger.events,
5354
- namespaceName: resolveTailorDBNamespace(application, trigger.typeName),
5319
+ namespaceName: resolveTailorDBNamespace(application, trigger.typeName, context.tailorDBTypeNamespaces),
5355
5320
  typeName: trigger.typeName,
5356
5321
  ...trigger.condition ? { condition: { expr: `(${stringifyFunction(trigger.condition)})(${argsExpr})` } } : {}
5357
5322
  }
@@ -5363,7 +5328,7 @@ function protoExecutor(application, executor) {
5363
5328
  case: "pipeline",
5364
5329
  value: {
5365
5330
  eventTypes: ["pipeline.resolver.executed"],
5366
- namespaceName: resolveResolverNamespace(application, trigger.resolverName),
5331
+ namespaceName: resolveResolverNamespace(application, trigger.resolverName, context.resolverNamespaces),
5367
5332
  resolverName: trigger.resolverName,
5368
5333
  ...trigger.condition ? { condition: { expr: `(${stringifyFunction(trigger.condition)})(${argsExpr})` } } : {}
5369
5334
  }
@@ -5385,7 +5350,7 @@ function protoExecutor(application, executor) {
5385
5350
  case: "idp",
5386
5351
  value: {
5387
5352
  eventTypes: trigger.events,
5388
- namespaceName: resolveIdpNamespace(application, executor.name, trigger.idp)
5353
+ namespaceName: resolveIdpNamespace(application, executor.name, trigger.idp, context.idpNames)
5389
5354
  }
5390
5355
  });
5391
5356
  break;
@@ -5560,7 +5525,8 @@ async function planPipeline(context) {
5560
5525
  }
5561
5526
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
5562
5527
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$1(client, workspaceId, application.name, application.id, pipelines);
5563
- const { changeSet: resolverChangeSet } = await planResolvers(client, workspaceId, pipelines, executors, serviceChangeSet.deletes.map((del) => del.name), application.env, application.authService?.config.name, forceApplyAll);
5528
+ const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
5529
+ const { changeSet: resolverChangeSet } = await planResolvers(client, workspaceId, pipelines, executors, context.executorUsedResolvers ?? /* @__PURE__ */ new Set(), deletedServices, application.env, application.authService?.config.name, forceApplyAll);
5564
5530
  return {
5565
5531
  changeSet: {
5566
5532
  service: serviceChangeSet,
@@ -5597,17 +5563,16 @@ async function planServices$1(client, workspaceId, appName, appId, pipelines) {
5597
5563
  appId
5598
5564
  });
5599
5565
  if (existing) {
5600
- const owned = isOwnedByApp(existing.allLabels, appName, appId);
5601
- if (!owned) if (!existing.label) unmanaged.push({
5602
- resourceType: "Pipeline service",
5603
- resourceName: pipeline.namespace
5604
- });
5605
- else conflicts.push({
5566
+ if (trackDesiredResourceOwnership({
5567
+ labels: existing.allLabels,
5568
+ ownerLabel: existing.label,
5569
+ appName,
5570
+ appId,
5606
5571
  resourceType: "Pipeline service",
5607
5572
  resourceName: pipeline.namespace,
5608
- currentOwner: existing.label
5609
- });
5610
- if (owned && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) changeSet.unchanged.push({ name: pipeline.namespace });
5573
+ conflicts,
5574
+ unmanaged
5575
+ }) && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) changeSet.unchanged.push({ name: pipeline.namespace });
5611
5576
  else changeSet.updates.push({
5612
5577
  name: pipeline.namespace,
5613
5578
  request: {
@@ -5628,10 +5593,13 @@ async function planServices$1(client, workspaceId, appName, appId, pipelines) {
5628
5593
  }
5629
5594
  Object.entries(existingServices).forEach(([namespaceName]) => {
5630
5595
  const entry = existingServices[namespaceName];
5631
- const label = entry?.label;
5632
- const owned = isOwnedByApp(entry?.allLabels, appName, appId);
5633
- if (label && !owned) resourceOwners.add(label);
5634
- if (owned) changeSet.deletes.push({
5596
+ if (trackRemainingResourceOwner({
5597
+ labels: entry?.allLabels,
5598
+ ownerLabel: entry?.label,
5599
+ appName,
5600
+ appId,
5601
+ resourceOwners
5602
+ })) changeSet.deletes.push({
5635
5603
  name: namespaceName,
5636
5604
  request: {
5637
5605
  workspaceId,
@@ -5646,25 +5614,20 @@ async function planServices$1(client, workspaceId, appName, appId, pipelines) {
5646
5614
  resourceOwners
5647
5615
  };
5648
5616
  }
5649
- async function planResolvers(client, workspaceId, pipelines, executors, deletedServices, env, authNamespace, forceApplyAll = false) {
5617
+ async function planResolvers(client, workspaceId, pipelines, executors, initialExecutorUsedResolvers, deletedServices, env, authNamespace, forceApplyAll = false) {
5650
5618
  const changeSet = createChangeSet("Pipeline resolvers");
5651
5619
  const fetchResolvers = (namespaceName) => {
5652
- return fetchAll(async (pageToken, maxPageSize) => {
5653
- try {
5654
- const { pipelineResolvers, nextPageToken } = await client.listPipelineResolvers({
5655
- workspaceId,
5656
- namespaceName,
5657
- pageToken,
5658
- pageSize: maxPageSize
5659
- });
5660
- return [pipelineResolvers, nextPageToken];
5661
- } catch (error) {
5662
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
5663
- throw error;
5664
- }
5620
+ return fetchAllTolerant(async (pageToken, maxPageSize) => {
5621
+ const { pipelineResolvers, nextPageToken } = await client.listPipelineResolvers({
5622
+ workspaceId,
5623
+ namespaceName,
5624
+ pageToken,
5625
+ pageSize: maxPageSize
5626
+ });
5627
+ return [pipelineResolvers, nextPageToken];
5665
5628
  });
5666
5629
  };
5667
- const executorUsedResolvers = /* @__PURE__ */ new Set();
5630
+ const executorUsedResolvers = new Set(initialExecutorUsedResolvers);
5668
5631
  for (const executor of executors) if (executor.trigger.kind === "resolverExecuted") executorUsedResolvers.add(executor.trigger.resolverName);
5669
5632
  for (const pipeline of pipelines) for (const resolver of Object.values(pipeline.resolvers)) if (executorUsedResolvers.has(resolver.name) && resolver.publishEvents === false) throw new Error(`Resolver "${resolver.name}" has publishEvents set to false, but it is used by an executor with a resolverExecuted trigger. Either remove the publishEvents: false setting or remove the executor trigger for this resolver.`);
5670
5633
  for (const pipeline of pipelines) {
@@ -5844,453 +5807,249 @@ function protoFields(fields, baseName, isInput) {
5844
5807
  description: field.metadata.description,
5845
5808
  array: field.metadata.array ?? false,
5846
5809
  required,
5847
- type
5848
- };
5849
- });
5850
- }
5851
-
5852
- //#endregion
5853
- //#region src/cli/commands/deploy/secret-manager.ts
5854
- /**
5855
- * Build the CreateSecretManagerVault request for a planned vault create.
5856
- * @param create - Planned vault create
5857
- * @returns Request init shape
5858
- */
5859
- function vaultCreateRequest(create) {
5860
- return {
5861
- workspaceId: create.workspaceId,
5862
- secretmanagerVaultName: create.name
5863
- };
5864
- }
5865
- /**
5866
- * Build the CreateSecretManagerSecret request for a planned secret create.
5867
- * @param create - Planned secret create
5868
- * @returns Request init shape
5869
- */
5870
- function secretCreateRequest(create) {
5871
- return {
5872
- workspaceId: create.workspaceId,
5873
- secretmanagerVaultName: create.vaultName,
5874
- secretmanagerSecretName: create.secretName,
5875
- secretmanagerSecretValue: create.value
5876
- };
5877
- }
5878
- /**
5879
- * Build the UpdateSecretManagerSecret request for a planned secret update.
5880
- * @param update - Planned secret update
5881
- * @returns Request init shape
5882
- */
5883
- function secretUpdateRequest(update) {
5884
- return {
5885
- workspaceId: update.workspaceId,
5886
- secretmanagerVaultName: update.vaultName,
5887
- secretmanagerSecretName: update.secretName,
5888
- secretmanagerSecretValue: update.value
5889
- };
5890
- }
5891
- /**
5892
- * Plan secret manager changes based on current and desired state.
5893
- * @param context - Planning context
5894
- * @returns Planned changes for vaults and secrets
5895
- */
5896
- async function planSecretManager(context) {
5897
- const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
5898
- const secretVaults = forRemoval ? [] : application.secrets;
5899
- const vaultChangeSet = createChangeSet("Secret Manager vaults");
5900
- const secretChangeSet = createChangeSet("Secret Manager secrets");
5901
- const conflicts = [];
5902
- const unmanaged = [];
5903
- const resourceOwners = /* @__PURE__ */ new Set();
5904
- const existingVaults = await fetchExistingResourcesWithLabels({
5905
- client,
5906
- fetchPage: async (pageToken, maxPageSize) => {
5907
- const { vaults, nextPageToken } = await client.listSecretManagerVaults({
5908
- workspaceId,
5909
- pageToken,
5910
- pageSize: maxPageSize
5911
- });
5912
- return [vaults, nextPageToken];
5913
- },
5914
- getName: (resource) => resource.name,
5915
- getTrn: (name) => resourceTrn(workspaceId, "vault", name)
5916
- });
5917
- const state = loadSecretsState();
5918
- const skippedSecrets = [];
5919
- await Promise.all(secretVaults.map(async (vault) => {
5920
- const vaultName = vault.vaultName;
5921
- const existing = existingVaults[vaultName];
5922
- if (existing) {
5923
- const metaRequest = await buildMetaRequest({
5924
- trn: resourceTrn(workspaceId, "vault", vaultName),
5925
- appName: application.name,
5926
- appId: application.id
5927
- });
5928
- const owned = isOwnedByApp(existing.allLabels, application.name, application.id);
5929
- if (!owned) if (!existing.label) unmanaged.push({
5930
- resourceType: "Secret Manager vault",
5931
- resourceName: vaultName
5932
- });
5933
- else conflicts.push({
5934
- resourceType: "Secret Manager vault",
5935
- resourceName: vaultName,
5936
- currentOwner: existing.label
5937
- });
5938
- if (owned && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) vaultChangeSet.unchanged.push({ name: vaultName });
5939
- else vaultChangeSet.updates.push({
5940
- name: vaultName,
5941
- workspaceId
5942
- });
5943
- delete existingVaults[vaultName];
5944
- } else vaultChangeSet.creates.push({
5945
- name: vaultName,
5946
- workspaceId
5947
- });
5948
- let existingSecrets = [];
5949
- if (existing) existingSecrets = (await fetchAll(async (pageToken, maxPageSize) => {
5950
- try {
5951
- const { secrets, nextPageToken } = await client.listSecretManagerSecrets({
5952
- workspaceId,
5953
- secretmanagerVaultName: vaultName,
5954
- pageToken,
5955
- pageSize: maxPageSize
5956
- });
5957
- return [secrets, nextPageToken];
5958
- } catch (error) {
5959
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
5960
- throw error;
5961
- }
5962
- })).map((s) => s.name);
5963
- const existingSet = new Set(existingSecrets);
5964
- for (const secret of vault.secrets) {
5965
- if (secret.value == null) {
5966
- existingSet.delete(secret.name);
5967
- skippedSecrets.push(`${vaultName}/${secret.name}`);
5968
- continue;
5969
- }
5970
- if (existingSet.has(secret.name)) {
5971
- const currentHash = hashValue(secret.value);
5972
- const storedHash = state.vaults[vaultName]?.[secret.name];
5973
- if (forceApplyAll || currentHash !== storedHash) secretChangeSet.updates.push({
5974
- name: `${vaultName}/${secret.name}`,
5975
- secretName: secret.name,
5976
- workspaceId,
5977
- vaultName,
5978
- value: secret.value
5979
- });
5980
- existingSet.delete(secret.name);
5981
- } else secretChangeSet.creates.push({
5982
- name: `${vaultName}/${secret.name}`,
5983
- secretName: secret.name,
5984
- workspaceId,
5985
- vaultName,
5986
- value: secret.value
5987
- });
5988
- }
5989
- for (const orphanName of existingSet) secretChangeSet.deletes.push({
5990
- name: `${vaultName}/${orphanName}`,
5991
- secretName: orphanName,
5992
- workspaceId,
5993
- vaultName
5994
- });
5995
- }));
5996
- for (const [name, entry] of Object.entries(existingVaults)) {
5997
- if (!entry) continue;
5998
- const label = entry.label;
5999
- const owned = isOwnedByApp(entry.allLabels, application.name, application.id);
6000
- if (label && !owned) resourceOwners.add(label);
6001
- if (owned) {
6002
- const secrets = await fetchAll(async (pageToken, maxPageSize) => {
6003
- try {
6004
- const { secrets, nextPageToken } = await client.listSecretManagerSecrets({
6005
- workspaceId,
6006
- secretmanagerVaultName: name,
6007
- pageToken,
6008
- pageSize: maxPageSize
6009
- });
6010
- return [secrets, nextPageToken];
6011
- } catch (error) {
6012
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
6013
- throw error;
6014
- }
6015
- });
6016
- for (const secret of secrets) secretChangeSet.deletes.push({
6017
- name: `${name}/${secret.name}`,
6018
- secretName: secret.name,
6019
- workspaceId,
6020
- vaultName: name
6021
- });
6022
- vaultChangeSet.deletes.push({
6023
- name,
6024
- workspaceId
6025
- });
6026
- }
6027
- }
5810
+ type
5811
+ };
5812
+ });
5813
+ }
5814
+
5815
+ //#endregion
5816
+ //#region src/cli/commands/deploy/secret-manager.ts
5817
+ /**
5818
+ * Build the CreateSecretManagerVault request for a planned vault create.
5819
+ * @param create - Planned vault create
5820
+ * @returns Request init shape
5821
+ */
5822
+ function vaultCreateRequest(create) {
6028
5823
  return {
6029
- vaultChangeSet,
6030
- secretChangeSet,
6031
- skippedSecrets,
6032
- conflicts,
6033
- unmanaged,
6034
- resourceOwners
5824
+ workspaceId: create.workspaceId,
5825
+ secretmanagerVaultName: create.name
6035
5826
  };
6036
5827
  }
6037
5828
  /**
6038
- * Apply secret manager changes for the given phase.
6039
- * @param client - Operator client instance
6040
- * @param result - Planned secret changes
6041
- * @param phase - Apply phase
6042
- * @param application - Application to read secrets from for hash state persistence
6043
- * @returns Promise that resolves when secret changes are applied
5829
+ * Build the CreateSecretManagerSecret request for a planned secret create.
5830
+ * @param create - Planned secret create
5831
+ * @returns Request init shape
6044
5832
  */
6045
- async function applySecretManager(client, result, phase = "create-update", application) {
6046
- const { vaultChangeSet, secretChangeSet } = result;
6047
- if (phase === "create-update") {
6048
- await Promise.all(vaultChangeSet.creates.map(async (create) => {
6049
- await client.createSecretManagerVault(vaultCreateRequest(create));
6050
- if (application) {
6051
- const metaRequest = await buildMetaRequest({
6052
- trn: resourceTrn(create.workspaceId, "vault", create.name),
6053
- appName: application.name,
6054
- appId: application.id
6055
- });
6056
- await client.setMetadata(metaRequest);
6057
- }
6058
- }));
6059
- if (application) await Promise.all(vaultChangeSet.updates.map(async (update) => {
6060
- const metaRequest = await buildMetaRequest({
6061
- trn: resourceTrn(update.workspaceId, "vault", update.name),
6062
- appName: application.name,
6063
- appId: application.id
6064
- });
6065
- await client.setMetadata(metaRequest);
6066
- }));
6067
- await Promise.all(secretChangeSet.creates.map((create) => client.createSecretManagerSecret(secretCreateRequest(create))));
6068
- await Promise.all(secretChangeSet.updates.map((update) => client.updateSecretManagerSecret(secretUpdateRequest(update))));
6069
- if (application) {
6070
- const state = loadSecretsState();
6071
- for (const vault of application.secrets) {
6072
- if (!Object.hasOwn(state.vaults, vault.vaultName)) state.vaults[vault.vaultName] = {};
6073
- for (const secret of vault.secrets) if (secret.value != null) assertDefined(state.vaults[vault.vaultName], "vault state entry missing")[secret.name] = hashValue(secret.value);
6074
- }
6075
- saveSecretsState(state);
6076
- }
6077
- } else {
6078
- await Promise.all(secretChangeSet.deletes.map((del) => client.deleteSecretManagerSecret({
6079
- workspaceId: del.workspaceId,
6080
- secretmanagerVaultName: del.vaultName,
6081
- secretmanagerSecretName: del.secretName
6082
- })));
6083
- await Promise.all(vaultChangeSet.deletes.map((del) => client.deleteSecretManagerVault({
6084
- workspaceId: del.workspaceId,
6085
- secretmanagerVaultName: del.name
6086
- })));
6087
- if (secretChangeSet.deletes.length > 0 || vaultChangeSet.deletes.length > 0) {
6088
- const state = loadSecretsState();
6089
- for (const del of secretChangeSet.deletes) if (Object.hasOwn(state.vaults, del.vaultName)) {
6090
- delete assertDefined(state.vaults[del.vaultName], "vault state entry missing")[del.secretName];
6091
- if (Object.keys(assertDefined(state.vaults[del.vaultName], "vault state entry missing")).length === 0) delete state.vaults[del.vaultName];
6092
- }
6093
- for (const del of vaultChangeSet.deletes) delete state.vaults[del.name];
6094
- saveSecretsState(state);
6095
- }
6096
- }
5833
+ function secretCreateRequest(create) {
5834
+ return {
5835
+ workspaceId: create.workspaceId,
5836
+ secretmanagerVaultName: create.vaultName,
5837
+ secretmanagerSecretName: create.secretName,
5838
+ secretmanagerSecretValue: create.value
5839
+ };
6097
5840
  }
6098
-
6099
- //#endregion
6100
- //#region src/cli/commands/deploy/staticwebsite.ts
6101
5841
  /**
6102
- * Apply static website changes for the given phase.
6103
- * @param client - Operator client instance
6104
- * @param result - Planned static website changes
6105
- * @param phase - Apply phase
6106
- * @returns Promise that resolves when static websites are applied
5842
+ * Build the UpdateSecretManagerSecret request for a planned secret update.
5843
+ * @param update - Planned secret update
5844
+ * @returns Request init shape
6107
5845
  */
6108
- async function applyStaticWebsite(client, result, phase = "create-update") {
6109
- const { changeSet, customDomainChangeSet } = result;
6110
- if (phase === "create-update") {
6111
- await Promise.all([...changeSet.creates.map(async (create) => {
6112
- await client.createStaticWebsite(create.request);
6113
- await client.setMetadata(create.metaRequest);
6114
- }), ...changeSet.updates.map(async (update) => {
6115
- await client.updateStaticWebsite(update.request);
6116
- await client.setMetadata(update.metaRequest);
6117
- })]);
6118
- await Promise.all([...customDomainChangeSet.creates.map(async (add) => {
6119
- await client.addCustomDomain(add.request);
6120
- await client.setMetadata(add.metaRequest);
6121
- }), ...customDomainChangeSet.deletes.map((del) => client.removeCustomDomain(del.request))]);
6122
- } else await Promise.all(changeSet.deletes.map((del) => client.deleteStaticWebsite(del.request)));
6123
- }
6124
- function customDomainTrn(workspaceId, websiteName, domain) {
6125
- return `trn:v1:workspace:${workspaceId}:staticwebsite:${websiteName}:custom_domain:${domain}`;
6126
- }
6127
- function normalizeComparableStaticWebsiteShape(input) {
5846
+ function secretUpdateRequest(update) {
6128
5847
  return {
6129
- description: input.description,
6130
- allowedIpAddresses: input.allowedIpAddresses.toSorted()
5848
+ workspaceId: update.workspaceId,
5849
+ secretmanagerVaultName: update.vaultName,
5850
+ secretmanagerSecretName: update.secretName,
5851
+ secretmanagerSecretValue: update.value
6131
5852
  };
6132
5853
  }
6133
- function normalizeComparableStaticWebsite(input) {
6134
- return normalizeComparableStaticWebsiteShape({
6135
- description: input.description || "",
6136
- allowedIpAddresses: [...input.allowedIpAddresses || []]
6137
- });
6138
- }
6139
- function areStaticWebsitesEqual(existing, desired) {
6140
- return areNormalizedEqual(normalizeComparableStaticWebsite(existing), normalizeComparableStaticWebsite(desired));
6141
- }
6142
5854
  /**
6143
- * Plan static website changes based on current and desired state.
5855
+ * Plan secret manager changes based on current and desired state.
6144
5856
  * @param context - Planning context
6145
- * @returns Planned changes
5857
+ * @returns Planned changes for vaults and secrets
6146
5858
  */
6147
- async function planStaticWebsite(context) {
6148
- const { client, workspaceId, application, forRemoval } = context;
6149
- const changeSet = createChangeSet("StaticWebsites");
6150
- const customDomainChangeSet = createChangeSet("CustomDomains");
5859
+ async function planSecretManager(context) {
5860
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
5861
+ const secretVaults = forRemoval ? [] : application.secrets;
5862
+ const vaultChangeSet = createChangeSet("Secret Manager vaults");
5863
+ const secretChangeSet = createChangeSet("Secret Manager secrets");
6151
5864
  const conflicts = [];
6152
5865
  const unmanaged = [];
6153
5866
  const resourceOwners = /* @__PURE__ */ new Set();
6154
- const existingWebsites = await fetchExistingResourcesWithLabels({
5867
+ const existingVaults = await fetchExistingResourcesWithLabels({
6155
5868
  client,
6156
- fetchPage: async (pageToken, pageSize) => {
6157
- const { staticwebsites, nextPageToken } = await client.listStaticWebsites({
5869
+ fetchPage: async (pageToken, maxPageSize) => {
5870
+ const { vaults, nextPageToken } = await client.listSecretManagerVaults({
6158
5871
  workspaceId,
6159
5872
  pageToken,
6160
- pageSize
5873
+ pageSize: maxPageSize
6161
5874
  });
6162
- return [staticwebsites, nextPageToken];
5875
+ return [vaults, nextPageToken];
6163
5876
  },
6164
5877
  getName: (resource) => resource.name,
6165
- getTrn: (name) => resourceTrn(workspaceId, "staticwebsite", name)
5878
+ getTrn: (name) => resourceTrn(workspaceId, "vault", name)
6166
5879
  });
6167
- const ownedWebsiteNames = /* @__PURE__ */ new Set();
6168
- const staticWebsiteServices = forRemoval ? [] : application.staticWebsiteServices;
6169
- for (const websiteService of staticWebsiteServices) {
6170
- const config = websiteService;
6171
- const name = websiteService.name;
6172
- const existing = existingWebsites[name];
6173
- const metaRequest = await buildMetaRequest({
6174
- trn: resourceTrn(workspaceId, "staticwebsite", name),
6175
- appName: application.name,
6176
- appId: application.id
6177
- });
6178
- const desired = normalizeComparableStaticWebsite(config);
6179
- const request = {
6180
- workspaceId,
6181
- staticwebsite: {
6182
- name,
6183
- description: config.description || "",
6184
- allowedIpAddresses: config.allowedIpAddresses || []
6185
- }
6186
- };
5880
+ const state = loadSecretsState();
5881
+ const skippedSecrets = [];
5882
+ await Promise.all(secretVaults.map(async (vault) => {
5883
+ const vaultName = vault.vaultName;
5884
+ const existing = existingVaults[vaultName];
6187
5885
  if (existing) {
6188
- const owned = trackDesiredResourceOwnership({
5886
+ const metaRequest = await buildMetaRequest({
5887
+ trn: resourceTrn(workspaceId, "vault", vaultName),
5888
+ appName: application.name,
5889
+ appId: application.id
5890
+ });
5891
+ if (trackDesiredResourceOwnership({
6189
5892
  labels: existing.allLabels,
6190
5893
  ownerLabel: existing.label,
6191
5894
  appName: application.name,
6192
5895
  appId: application.id,
6193
- resourceType: "StaticWebsite",
6194
- resourceName: name,
5896
+ resourceType: "Secret Manager vault",
5897
+ resourceName: vaultName,
6195
5898
  conflicts,
6196
5899
  unmanaged
5900
+ }) && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) vaultChangeSet.unchanged.push({ name: vaultName });
5901
+ else vaultChangeSet.updates.push({
5902
+ name: vaultName,
5903
+ workspaceId
6197
5904
  });
6198
- if (owned && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areStaticWebsitesEqual(existing.resource, desired)) changeSet.unchanged.push({ name });
6199
- else changeSet.updates.push({
6200
- name,
6201
- request,
6202
- metaRequest
6203
- });
6204
- if (owned) ownedWebsiteNames.add(name);
6205
- delete existingWebsites[name];
6206
- } else {
6207
- changeSet.creates.push({
6208
- name,
6209
- request,
6210
- metaRequest
6211
- });
6212
- ownedWebsiteNames.add(name);
6213
- }
6214
- }
6215
- Object.entries(existingWebsites).forEach(([name]) => {
6216
- const entry = existingWebsites[name];
6217
- const label = entry?.label;
6218
- if (trackRemainingResourceOwner({
6219
- labels: entry?.allLabels,
6220
- ownerLabel: label,
6221
- appName: application.name,
6222
- appId: application.id,
6223
- resourceOwners
6224
- })) changeSet.deletes.push({
6225
- name,
6226
- request: {
5905
+ delete existingVaults[vaultName];
5906
+ } else vaultChangeSet.creates.push({
5907
+ name: vaultName,
5908
+ workspaceId
5909
+ });
5910
+ let existingSecrets = [];
5911
+ if (existing) existingSecrets = (await fetchAllTolerant(async (pageToken, maxPageSize) => {
5912
+ const { secrets, nextPageToken } = await client.listSecretManagerSecrets({
6227
5913
  workspaceId,
6228
- name
5914
+ secretmanagerVaultName: vaultName,
5915
+ pageToken,
5916
+ pageSize: maxPageSize
5917
+ });
5918
+ return [secrets, nextPageToken];
5919
+ })).map((s) => s.name);
5920
+ const existingSet = new Set(existingSecrets);
5921
+ for (const secret of vault.secrets) {
5922
+ if (secret.value == null) {
5923
+ existingSet.delete(secret.name);
5924
+ skippedSecrets.push(`${vaultName}/${secret.name}`);
5925
+ continue;
6229
5926
  }
6230
- });
6231
- });
6232
- const desiredDomainsByWebsite = /* @__PURE__ */ new Map();
6233
- for (const service of staticWebsiteServices) if (service.customDomains !== void 0 && ownedWebsiteNames.has(service.name)) desiredDomainsByWebsite.set(service.name, service.customDomains);
6234
- const existingDomainsByWebsite = /* @__PURE__ */ new Map();
6235
- const websitesToFetchDomains = [...ownedWebsiteNames].filter((name) => !changeSet.creates.some((c) => c.name === name));
6236
- await Promise.all(websitesToFetchDomains.map(async (name) => {
6237
- try {
6238
- const { customDomains } = await client.listCustomDomains({
5927
+ if (existingSet.has(secret.name)) {
5928
+ const currentHash = hashValue(secret.value);
5929
+ const storedHash = state.vaults[vaultName]?.[secret.name];
5930
+ if (forceApplyAll || currentHash !== storedHash) secretChangeSet.updates.push({
5931
+ name: `${vaultName}/${secret.name}`,
5932
+ secretName: secret.name,
5933
+ workspaceId,
5934
+ vaultName,
5935
+ value: secret.value
5936
+ });
5937
+ existingSet.delete(secret.name);
5938
+ } else secretChangeSet.creates.push({
5939
+ name: `${vaultName}/${secret.name}`,
5940
+ secretName: secret.name,
6239
5941
  workspaceId,
6240
- staticWebsiteName: name
5942
+ vaultName,
5943
+ value: secret.value
6241
5944
  });
6242
- const domainsWithLabels = await Promise.all(customDomains.map(async (d) => {
6243
- const { metadata } = await client.getMetadata({ trn: customDomainTrn(workspaceId, name, d.domain) });
6244
- return {
6245
- domain: d.domain,
6246
- allLabels: metadata?.labels
6247
- };
6248
- }));
6249
- existingDomainsByWebsite.set(name, domainsWithLabels);
6250
- } catch (error) {
6251
- if (error instanceof ConnectError && error.code === Code.NotFound) return;
6252
- throw error;
6253
5945
  }
5946
+ for (const orphanName of existingSet) secretChangeSet.deletes.push({
5947
+ name: `${vaultName}/${orphanName}`,
5948
+ secretName: orphanName,
5949
+ workspaceId,
5950
+ vaultName
5951
+ });
6254
5952
  }));
6255
- for (const name of ownedWebsiteNames) {
6256
- const desired = new Set(desiredDomainsByWebsite.get(name) ?? []);
6257
- const existingDomains = existingDomainsByWebsite.get(name) ?? [];
6258
- const existingSet = new Set(existingDomains.map((d) => d.domain));
6259
- const sdkOwnedDomains = new Set(existingDomains.filter((d) => isOwnedByApp(d.allLabels, application.name, application.id)).map((d) => d.domain));
6260
- for (const domain of desired) if (!existingSet.has(domain)) {
6261
- const metaRequest = await buildMetaRequest({
6262
- trn: customDomainTrn(workspaceId, name, domain),
6263
- appName: application.name,
6264
- appId: application.id
6265
- });
6266
- customDomainChangeSet.creates.push({
6267
- name: domain,
6268
- request: {
5953
+ for (const [name, entry] of Object.entries(existingVaults)) {
5954
+ if (!entry) continue;
5955
+ if (trackRemainingResourceOwner({
5956
+ labels: entry.allLabels,
5957
+ ownerLabel: entry.label,
5958
+ appName: application.name,
5959
+ appId: application.id,
5960
+ resourceOwners
5961
+ })) {
5962
+ const secrets = await fetchAllTolerant(async (pageToken, maxPageSize) => {
5963
+ const { secrets, nextPageToken } = await client.listSecretManagerSecrets({
6269
5964
  workspaceId,
6270
- staticWebsiteName: name,
6271
- domain
6272
- },
6273
- metaRequest
5965
+ secretmanagerVaultName: name,
5966
+ pageToken,
5967
+ pageSize: maxPageSize
5968
+ });
5969
+ return [secrets, nextPageToken];
6274
5970
  });
6275
- } else customDomainChangeSet.unchanged.push({ name: domain });
6276
- if (desiredDomainsByWebsite.has(name)) {
6277
- for (const domain of sdkOwnedDomains) if (!desired.has(domain)) customDomainChangeSet.deletes.push({
6278
- name: domain,
6279
- request: {
6280
- workspaceId,
6281
- domain
6282
- }
5971
+ for (const secret of secrets) secretChangeSet.deletes.push({
5972
+ name: `${name}/${secret.name}`,
5973
+ secretName: secret.name,
5974
+ workspaceId,
5975
+ vaultName: name
5976
+ });
5977
+ vaultChangeSet.deletes.push({
5978
+ name,
5979
+ workspaceId
6283
5980
  });
6284
5981
  }
6285
5982
  }
6286
5983
  return {
6287
- changeSet,
6288
- customDomainChangeSet,
5984
+ vaultChangeSet,
5985
+ secretChangeSet,
5986
+ skippedSecrets,
6289
5987
  conflicts,
6290
5988
  unmanaged,
6291
5989
  resourceOwners
6292
5990
  };
6293
5991
  }
5992
+ /**
5993
+ * Apply secret manager changes for the given phase.
5994
+ * @param client - Operator client instance
5995
+ * @param result - Planned secret changes
5996
+ * @param phase - Apply phase
5997
+ * @param application - Application to read secrets from for hash state persistence
5998
+ * @returns Promise that resolves when secret changes are applied
5999
+ */
6000
+ async function applySecretManager(client, result, phase = "create-update", application) {
6001
+ const { vaultChangeSet, secretChangeSet } = result;
6002
+ if (phase === "create-update") {
6003
+ await Promise.all(vaultChangeSet.creates.map(async (create) => {
6004
+ await client.createSecretManagerVault(vaultCreateRequest(create));
6005
+ if (application) {
6006
+ const metaRequest = await buildMetaRequest({
6007
+ trn: resourceTrn(create.workspaceId, "vault", create.name),
6008
+ appName: application.name,
6009
+ appId: application.id
6010
+ });
6011
+ await client.setMetadata(metaRequest);
6012
+ }
6013
+ }));
6014
+ if (application) await Promise.all(vaultChangeSet.updates.map(async (update) => {
6015
+ const metaRequest = await buildMetaRequest({
6016
+ trn: resourceTrn(update.workspaceId, "vault", update.name),
6017
+ appName: application.name,
6018
+ appId: application.id
6019
+ });
6020
+ await client.setMetadata(metaRequest);
6021
+ }));
6022
+ await Promise.all(secretChangeSet.creates.map((create) => client.createSecretManagerSecret(secretCreateRequest(create))));
6023
+ await Promise.all(secretChangeSet.updates.map((update) => client.updateSecretManagerSecret(secretUpdateRequest(update))));
6024
+ if (application) {
6025
+ const state = loadSecretsState();
6026
+ for (const vault of application.secrets) {
6027
+ if (!Object.hasOwn(state.vaults, vault.vaultName)) state.vaults[vault.vaultName] = {};
6028
+ for (const secret of vault.secrets) if (secret.value != null) assertDefined(state.vaults[vault.vaultName], "vault state entry missing")[secret.name] = hashValue(secret.value);
6029
+ }
6030
+ saveSecretsState(state);
6031
+ }
6032
+ } else {
6033
+ await Promise.all(secretChangeSet.deletes.map((del) => client.deleteSecretManagerSecret({
6034
+ workspaceId: del.workspaceId,
6035
+ secretmanagerVaultName: del.vaultName,
6036
+ secretmanagerSecretName: del.secretName
6037
+ })));
6038
+ await Promise.all(vaultChangeSet.deletes.map((del) => client.deleteSecretManagerVault({
6039
+ workspaceId: del.workspaceId,
6040
+ secretmanagerVaultName: del.name
6041
+ })));
6042
+ if (secretChangeSet.deletes.length > 0 || vaultChangeSet.deletes.length > 0) {
6043
+ const state = loadSecretsState();
6044
+ for (const del of secretChangeSet.deletes) if (Object.hasOwn(state.vaults, del.vaultName)) {
6045
+ delete assertDefined(state.vaults[del.vaultName], "vault state entry missing")[del.secretName];
6046
+ if (Object.keys(assertDefined(state.vaults[del.vaultName], "vault state entry missing")).length === 0) delete state.vaults[del.vaultName];
6047
+ }
6048
+ for (const del of vaultChangeSet.deletes) delete state.vaults[del.name];
6049
+ saveSecretsState(state);
6050
+ }
6051
+ }
6052
+ }
6294
6053
 
6295
6054
  //#endregion
6296
6055
  //#region src/cli/commands/tailordb/migrate/config.ts
@@ -6919,42 +6678,56 @@ function copySnapshotRecord(record) {
6919
6678
  return copy;
6920
6679
  }
6921
6680
  /**
6922
- * Normalize a snapshot field in place so the snapshot becomes the canonical
6923
- * form for comparison. Currently fills in the platform default decimal scale
6924
- * when omitted, which avoids false drift between local schemas (where scale
6925
- * may be omitted) and the platform (which always materializes a scale).
6681
+ * Normalize a snapshot field into the canonical form for comparison, returning
6682
+ * a new object rather than mutating the input. Currently fills in the platform
6683
+ * default decimal scale when omitted, which avoids false drift between local
6684
+ * schemas (where scale may be omitted) and the platform (which always
6685
+ * materializes a scale).
6926
6686
  * @param {SnapshotFieldConfig} field - Field configuration to normalize
6927
- * @returns The same field object after normalization
6687
+ * @returns {SnapshotFieldConfig} A new, normalized field object
6928
6688
  */
6929
6689
  function normalizeSnapshotField(field) {
6930
- if (field.type === "decimal" && field.scale === void 0) field.scale = 6;
6931
- if (field.fields) for (const nested of Object.values(field.fields)) normalizeSnapshotField(nested);
6932
- return field;
6690
+ const fields = field.fields ? Object.fromEntries(Object.entries(field.fields).map(([name, nested]) => [name, normalizeSnapshotField(nested)])) : void 0;
6691
+ return {
6692
+ ...field,
6693
+ ...field.type === "decimal" && field.scale === void 0 && { scale: 6 },
6694
+ ...fields && { fields }
6695
+ };
6933
6696
  }
6934
6697
  /**
6935
- * Normalize a snapshot type in place to the canonical comparison shape.
6936
- * Currently fills:
6698
+ * Normalize a snapshot type into the canonical comparison shape, returning a
6699
+ * new object rather than mutating the input. Currently fills:
6937
6700
  * - `pluralForm` via inflection when missing (legacy snapshots written
6938
6701
  * before `pluralForm` became required may omit it)
6939
6702
  * - per-field `scale` defaults via {@link normalizeSnapshotField}
6940
6703
  *
6941
6704
  * Idempotent — safe to call multiple times on the same input.
6942
6705
  * @param {TailorDBSnapshotType} type - Snapshot type to normalize
6943
- * @returns The same snapshot type object after normalization
6706
+ * @returns {TailorDBSnapshotType} A new, normalized snapshot type object
6944
6707
  */
6945
6708
  function normalizeSnapshotType(type) {
6946
- if (!type.pluralForm) type.pluralForm = inflection.pluralize(type.name);
6947
- for (const field of Object.values(type.fields)) normalizeSnapshotField(field);
6948
- return type;
6709
+ const pluralForm = type.pluralForm || inflection.pluralize(type.name);
6710
+ const fields = createSnapshotRecord();
6711
+ for (const [fieldName, field] of Object.entries(type.fields)) fields[fieldName] = normalizeSnapshotField(field);
6712
+ return {
6713
+ ...type,
6714
+ pluralForm,
6715
+ fields
6716
+ };
6949
6717
  }
6950
6718
  /**
6951
- * Normalize a schema snapshot in place to the canonical comparison shape.
6719
+ * Normalize a schema snapshot into the canonical comparison shape, returning a
6720
+ * new object rather than mutating the input.
6952
6721
  * @param {SchemaSnapshot} snapshot - Schema snapshot to normalize
6953
- * @returns The same schema snapshot object branded as normalized
6722
+ * @returns {NormalizedSchemaSnapshot} A new schema snapshot object branded as normalized
6954
6723
  */
6955
6724
  function normalizeSchemaSnapshot(snapshot) {
6956
- for (const type of Object.values(snapshot.types)) normalizeSnapshotType(type);
6957
- return snapshot;
6725
+ const types = createSnapshotRecord();
6726
+ for (const [typeName, type] of Object.entries(snapshot.types)) types[typeName] = normalizeSnapshotType(type);
6727
+ return {
6728
+ ...snapshot,
6729
+ types
6730
+ };
6958
6731
  }
6959
6732
  /**
6960
6733
  * Validate that a migration number follows the expected format (4-digit number)
@@ -6995,53 +6768,7 @@ function getMigrationFilePath(migrationsDir, num, type) {
6995
6768
  return path.join(migrationDir, MIGRATION_FILE_NAMES[type]);
6996
6769
  }
6997
6770
  /**
6998
- * Create a snapshot field config from a parsed field
6999
- * @param {ParsedField} field - Parsed field definition
7000
- * @returns {SnapshotFieldConfig} Snapshot field configuration
7001
- */
7002
- function createSnapshotFieldConfig(field) {
7003
- const config = {
7004
- type: field.config.type,
7005
- required: field.config.required !== false
7006
- };
7007
- if (field.config.array) config.array = true;
7008
- if (field.config.index) config.index = true;
7009
- if (field.config.unique) config.unique = true;
7010
- if (field.config.allowedValues && field.config.allowedValues.length > 0) config.allowedValues = field.config.allowedValues.map((v) => ({
7011
- value: v.value,
7012
- ...v.description && { description: v.description }
7013
- }));
7014
- if (field.config.foreignKey) {
7015
- config.foreignKey = true;
7016
- if (field.config.foreignKeyType) config.foreignKeyType = field.config.foreignKeyType;
7017
- if (field.config.foreignKeyField) config.foreignKeyField = field.config.foreignKeyField;
7018
- }
7019
- if (field.config.description) config.description = field.config.description;
7020
- if (field.config.vector) config.vector = true;
7021
- if (field.config.hooks) {
7022
- config.hooks = {};
7023
- if (field.config.hooks.create) config.hooks.create = { expr: field.config.hooks.create.expr };
7024
- if (field.config.hooks.update) config.hooks.update = { expr: field.config.hooks.update.expr };
7025
- }
7026
- if (field.config.validate && field.config.validate.length > 0) config.validate = field.config.validate.map((v) => ({
7027
- script: { expr: v.script.expr },
7028
- errorMessage: v.errorMessage
7029
- }));
7030
- if (field.config.serial) config.serial = {
7031
- start: field.config.serial.start,
7032
- ...field.config.serial.maxValue !== void 0 && { maxValue: field.config.serial.maxValue },
7033
- ...field.config.serial.format && { format: field.config.serial.format }
7034
- };
7035
- if (field.config.scale !== void 0) config.scale = field.config.scale;
7036
- if (field.config.fields && Object.keys(field.config.fields).length > 0) {
7037
- config.fields = {};
7038
- for (const [nestedName, nestedConfig] of Object.entries(field.config.fields)) config.fields[nestedName] = createSnapshotFieldConfigFromOperatorConfig(nestedConfig);
7039
- }
7040
- normalizeSnapshotField(config);
7041
- return config;
7042
- }
7043
- /**
7044
- * Create a snapshot field config from an OperatorFieldConfig (for nested fields)
6771
+ * Create a snapshot field config from an OperatorFieldConfig.
7045
6772
  * @param {import("#/parser/service/tailordb/types").OperatorFieldConfig} fieldConfig - Field configuration
7046
6773
  * @returns {SnapshotFieldConfig} Snapshot field configuration
7047
6774
  */
@@ -7083,8 +6810,7 @@ function createSnapshotFieldConfigFromOperatorConfig(fieldConfig) {
7083
6810
  config.fields = {};
7084
6811
  for (const [nestedName, nestedConfig] of Object.entries(fieldConfig.fields)) config.fields[nestedName] = createSnapshotFieldConfigFromOperatorConfig(nestedConfig);
7085
6812
  }
7086
- normalizeSnapshotField(config);
7087
- return config;
6813
+ return normalizeSnapshotField(config);
7088
6814
  }
7089
6815
  /**
7090
6816
  * Create a snapshot type from a parsed type
@@ -7093,7 +6819,7 @@ function createSnapshotFieldConfigFromOperatorConfig(fieldConfig) {
7093
6819
  */
7094
6820
  function createSnapshotType(type) {
7095
6821
  const fields = createSnapshotRecord();
7096
- for (const [fieldName, field] of Object.entries(type.fields)) fields[fieldName] = createSnapshotFieldConfig(field);
6822
+ for (const [fieldName, field] of Object.entries(type.fields)) fields[fieldName] = createSnapshotFieldConfigFromOperatorConfig(field.config);
7097
6823
  const snapshotType = {
7098
6824
  name: type.name,
7099
6825
  pluralForm: type.pluralForm || inflection.pluralize(type.name),
@@ -9640,35 +9366,25 @@ function groupMigrationsByNamespace(migrations) {
9640
9366
  * @returns {Promise<ProtoTailorDBType[]>} Remote TailorDB types
9641
9367
  */
9642
9368
  async function fetchRemoteTypes(client, workspaceId, namespace) {
9643
- return fetchAll(async (pageToken, maxPageSize) => {
9644
- try {
9645
- const { tailordbTypes, nextPageToken } = await client.listTailorDBTypes({
9646
- workspaceId,
9647
- namespaceName: namespace,
9648
- pageToken,
9649
- pageSize: maxPageSize
9650
- });
9651
- return [tailordbTypes, nextPageToken];
9652
- } catch (error) {
9653
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
9654
- throw error;
9655
- }
9369
+ return fetchAllTolerant(async (pageToken, maxPageSize) => {
9370
+ const { tailordbTypes, nextPageToken } = await client.listTailorDBTypes({
9371
+ workspaceId,
9372
+ namespaceName: namespace,
9373
+ pageToken,
9374
+ pageSize: maxPageSize
9375
+ });
9376
+ return [tailordbTypes, nextPageToken];
9656
9377
  });
9657
9378
  }
9658
9379
  async function fetchRemoteGqlPermissions(client, workspaceId, namespace) {
9659
- return fetchAll(async (pageToken, maxPageSize) => {
9660
- try {
9661
- const { permissions, nextPageToken } = await client.listTailorDBGQLPermissions({
9662
- workspaceId,
9663
- namespaceName: namespace,
9664
- pageToken,
9665
- pageSize: maxPageSize
9666
- });
9667
- return [permissions, nextPageToken];
9668
- } catch (error) {
9669
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
9670
- throw error;
9671
- }
9380
+ return fetchAllTolerant(async (pageToken, maxPageSize) => {
9381
+ const { permissions, nextPageToken } = await client.listTailorDBGQLPermissions({
9382
+ workspaceId,
9383
+ namespaceName: namespace,
9384
+ pageToken,
9385
+ pageSize: maxPageSize
9386
+ });
9387
+ return [permissions, nextPageToken];
9672
9388
  });
9673
9389
  }
9674
9390
  function definedWhenNotEmpty(value) {
@@ -10301,7 +10017,7 @@ async function planTailorDB(context) {
10301
10017
  tailordbs.push(toTailorDBDeployInput(tailordb));
10302
10018
  }
10303
10019
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
10304
- const executorUsedTypes = /* @__PURE__ */ new Set();
10020
+ const executorUsedTypes = new Set(context.executorUsedTailorDBTypes ?? []);
10305
10021
  for (const executor of executors) if (executor.trigger.kind === "tailordb") executorUsedTypes.add(executor.trigger.typeName);
10306
10022
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices(client, workspaceId, application.name, application.id, tailordbs);
10307
10023
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
@@ -10406,17 +10122,16 @@ async function planServices(client, workspaceId, appName, appId, tailordbs) {
10406
10122
  existingLabels: existing?.allLabels
10407
10123
  });
10408
10124
  if (existing) {
10409
- const owned = isOwnedByApp(existing.allLabels, appName, appId);
10410
- if (!owned) if (!existing.label) unmanaged.push({
10411
- resourceType: "TailorDB service",
10412
- resourceName: tailordb.namespace
10413
- });
10414
- else conflicts.push({
10125
+ if (trackDesiredResourceOwnership({
10126
+ labels: existing.allLabels,
10127
+ ownerLabel: existing.label,
10128
+ appName,
10129
+ appId,
10415
10130
  resourceType: "TailorDB service",
10416
10131
  resourceName: tailordb.namespace,
10417
- currentOwner: existing.label
10418
- });
10419
- if (owned && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areTailorDBServicesEqual(existing.resource, tailordb)) changeSet.unchanged.push({ name: tailordb.namespace });
10132
+ conflicts,
10133
+ unmanaged
10134
+ }) && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areTailorDBServicesEqual(existing.resource, tailordb)) changeSet.unchanged.push({ name: tailordb.namespace });
10420
10135
  else changeSet.updates.push({
10421
10136
  name: tailordb.namespace,
10422
10137
  metaRequest
@@ -10434,10 +10149,13 @@ async function planServices(client, workspaceId, appName, appId, tailordbs) {
10434
10149
  }
10435
10150
  Object.entries(existingServices).forEach(([namespaceName]) => {
10436
10151
  const entry = existingServices[namespaceName];
10437
- const label = entry?.label;
10438
- const owned = isOwnedByApp(entry?.allLabels, appName, appId);
10439
- if (label && !owned) resourceOwners.add(label);
10440
- if (owned) changeSet.deletes.push({
10152
+ if (trackRemainingResourceOwner({
10153
+ labels: entry?.allLabels,
10154
+ ownerLabel: entry?.label,
10155
+ appName,
10156
+ appId,
10157
+ resourceOwners
10158
+ })) changeSet.deletes.push({
10441
10159
  name: namespaceName,
10442
10160
  request: {
10443
10161
  workspaceId,
@@ -10455,19 +10173,14 @@ async function planServices(client, workspaceId, appName, appId, tailordbs) {
10455
10173
  async function planTypes(client, workspaceId, tailordbs, executorUsedTypes, deletedServices, filteredTypesByNamespace, forceApplyAll = false) {
10456
10174
  const changeSet = createChangeSet("TailorDB types");
10457
10175
  const fetchTypes = (namespaceName) => {
10458
- return fetchAll(async (pageToken, maxPageSize) => {
10459
- try {
10460
- const { tailordbTypes, nextPageToken } = await client.listTailorDBTypes({
10461
- workspaceId,
10462
- namespaceName,
10463
- pageToken,
10464
- pageSize: maxPageSize
10465
- });
10466
- return [tailordbTypes, nextPageToken];
10467
- } catch (error) {
10468
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
10469
- throw error;
10470
- }
10176
+ return fetchAllTolerant(async (pageToken, maxPageSize) => {
10177
+ const { tailordbTypes, nextPageToken } = await client.listTailorDBTypes({
10178
+ workspaceId,
10179
+ namespaceName,
10180
+ pageToken,
10181
+ pageSize: maxPageSize
10182
+ });
10183
+ return [tailordbTypes, nextPageToken];
10471
10184
  });
10472
10185
  };
10473
10186
  for (const tailordb of tailordbs) {
@@ -10597,24 +10310,19 @@ function matchesNumericStringPath(path) {
10597
10310
  });
10598
10311
  }
10599
10312
  function isNumericLikeValue(value) {
10600
- return typeof value === "number" || typeof value === "bigint" || /^-?\d+$/.test(value);
10601
- }
10602
- async function planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll = false) {
10603
- const changeSet = createChangeSet("TailorDB gqlPermissions");
10604
- const fetchGqlPermissions = (namespaceName) => {
10605
- return fetchAll(async (pageToken, maxPageSize) => {
10606
- try {
10607
- const { permissions, nextPageToken } = await client.listTailorDBGQLPermissions({
10608
- workspaceId,
10609
- namespaceName,
10610
- pageToken,
10611
- pageSize: maxPageSize
10612
- });
10613
- return [permissions, nextPageToken];
10614
- } catch (error) {
10615
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
10616
- throw error;
10617
- }
10313
+ return typeof value === "number" || typeof value === "bigint" || /^-?\d+$/.test(value);
10314
+ }
10315
+ async function planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll = false) {
10316
+ const changeSet = createChangeSet("TailorDB gqlPermissions");
10317
+ const fetchGqlPermissions = (namespaceName) => {
10318
+ return fetchAllTolerant(async (pageToken, maxPageSize) => {
10319
+ const { permissions, nextPageToken } = await client.listTailorDBGQLPermissions({
10320
+ workspaceId,
10321
+ namespaceName,
10322
+ pageToken,
10323
+ pageSize: maxPageSize
10324
+ });
10325
+ return [permissions, nextPageToken];
10618
10326
  });
10619
10327
  };
10620
10328
  for (const tailordb of tailordbs) {
@@ -11105,6 +10813,327 @@ function normalizeRetryPolicyForCompare(policy) {
11105
10813
  };
11106
10814
  }
11107
10815
 
10816
+ //#endregion
10817
+ //#region src/cli/commands/deploy/apply-phases.ts
10818
+ /**
10819
+ * Apply planned deploy changes for one or more applications.
10820
+ * @param client - Operator client instance
10821
+ * @param workspaceId - Target workspace ID
10822
+ * @param deployments - Planned deployments to apply
10823
+ */
10824
+ async function applyDeploymentPlans(client, workspaceId, deployments) {
10825
+ const forEachDeployment = async (apply) => {
10826
+ for (const deployment of deployments) await apply(deployment);
10827
+ };
10828
+ await withSpan("apply.createUpdateServices", async () => {
10829
+ await forEachDeployment((d) => applySecretManager(client, d.secretManager, "create-update", d.application));
10830
+ await forEachDeployment((d) => applyFunctionRegistry(client, workspaceId, d.functionRegistry, "create-update"));
10831
+ await forEachDeployment((d) => applyStaticWebsite(client, d.staticWebsite, "create-update"));
10832
+ await forEachDeployment((d) => applyAIGateway(client, d.aiGateway, "create-update"));
10833
+ await forEachDeployment((d) => applyIdP(client, d.idp, "create-update"));
10834
+ await forEachDeployment((d) => applyAuth(client, d.auth, "create-update-prerequisites"));
10835
+ await forEachDeployment((d) => applyTailorDB(client, d.tailorDB, "create-update"));
10836
+ await forEachDeployment((d) => applyAuth(client, d.auth, "create-update-dependents"));
10837
+ await forEachDeployment((d) => applyPipeline(client, d.pipeline, "create-update"));
10838
+ });
10839
+ await withSpan("apply.deleteSubgraphResources", async () => {
10840
+ await forEachDeployment((d) => applyPipeline(client, d.pipeline, "delete-resources"));
10841
+ await forEachDeployment((d) => applyAuth(client, d.auth, "delete-resources"));
10842
+ await forEachDeployment((d) => applyIdP(client, d.idp, "delete-resources"));
10843
+ });
10844
+ await withSpan("apply.createUpdateApplication", async () => {
10845
+ await forEachDeployment((d) => applyApplication(client, d.app, "create-update"));
10846
+ });
10847
+ await withSpan("apply.createUpdateDependentServices", async () => {
10848
+ await forEachDeployment((d) => applyExecutor(client, d.executor, "create-update"));
10849
+ await forEachDeployment((d) => applyWorkflow(client, d.workflow, "create-update"));
10850
+ });
10851
+ await withSpan("apply.deleteDependentServices", async () => {
10852
+ await forEachDeployment((d) => applyWorkflow(client, d.workflow, "delete"));
10853
+ await forEachDeployment((d) => applyExecutor(client, d.executor, "delete"));
10854
+ await forEachDeployment((d) => applyStaticWebsite(client, d.staticWebsite, "delete"));
10855
+ await forEachDeployment((d) => applyAIGateway(client, d.aiGateway, "delete"));
10856
+ await forEachDeployment((d) => applySecretManager(client, d.secretManager, "delete", d.application));
10857
+ });
10858
+ await withSpan("apply.deleteApplication", async () => {
10859
+ await forEachDeployment((d) => applyApplication(client, d.app, "delete"));
10860
+ });
10861
+ await withSpan("apply.deleteSubgraphServices", async () => {
10862
+ await forEachDeployment((d) => applyPipeline(client, d.pipeline, "delete-services"));
10863
+ await forEachDeployment((d) => applyAuth(client, d.auth, "delete-services"));
10864
+ await forEachDeployment((d) => applyIdP(client, d.idp, "delete-services"));
10865
+ await forEachDeployment((d) => applyTailorDB(client, d.tailorDB, "delete-services"));
10866
+ });
10867
+ await withSpan("apply.cleanup", async () => {
10868
+ await forEachDeployment((d) => applyFunctionRegistry(client, workspaceId, d.functionRegistry, "delete"));
10869
+ });
10870
+ }
10871
+
10872
+ //#endregion
10873
+ //#region src/cli/commands/deploy/config-id-injector.ts
10874
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10875
+ function findDefineConfigCalls(node, results) {
10876
+ if (!node || typeof node !== "object") return;
10877
+ const n = node;
10878
+ if (n.type === "CallExpression") {
10879
+ const ce = n;
10880
+ if (ce.callee.type === "Identifier" && ce.callee.name === "defineConfig") {
10881
+ const arg = ce.arguments[0];
10882
+ const configObj = arg && arg.type === "ObjectExpression" ? arg : null;
10883
+ results.push({
10884
+ callExpr: ce,
10885
+ configObj
10886
+ });
10887
+ }
10888
+ }
10889
+ for (const key of Object.keys(n)) {
10890
+ const child = n[key];
10891
+ if (Array.isArray(child)) for (const c of child) findDefineConfigCalls(c, results);
10892
+ else if (child && typeof child === "object") findDefineConfigCalls(child, results);
10893
+ }
10894
+ }
10895
+ function findIdProperty(obj) {
10896
+ for (const prop of obj.properties) {
10897
+ if (prop.type !== "Property") continue;
10898
+ if ((prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" ? prop.key.value : null) === "id") return prop;
10899
+ }
10900
+ return null;
10901
+ }
10902
+ /**
10903
+ * Ensure `tailor.config.ts` has an `id` property on the `defineConfig({...})`
10904
+ * argument. Generates a UUID when missing and writes it back to the file.
10905
+ * Returns null when the file does not contain a `defineConfig()` call (e.g.
10906
+ * a wrapper that re-exports another config).
10907
+ * @param configPath - Absolute path to the config file
10908
+ * @returns Resolved id and whether it was newly injected, or null if skipped
10909
+ */
10910
+ async function ensureConfigId(configPath) {
10911
+ const source = await fs$1.promises.readFile(configPath, "utf-8");
10912
+ const { program } = parseSync(configPath, source);
10913
+ const calls = [];
10914
+ findDefineConfigCalls(program, calls);
10915
+ if (calls.length === 0) return null;
10916
+ if (calls.length > 1) throw new Error(`Multiple defineConfig() calls found in ${configPath}. Only one is supported.`);
10917
+ const { configObj } = assertDefined(calls[0], "defineConfig call site missing");
10918
+ if (!configObj) throw new Error(`defineConfig() argument must be an inline object literal in ${configPath} so the SDK can manage the 'id' field.`);
10919
+ const idProp = findIdProperty(configObj);
10920
+ if (idProp) {
10921
+ const value = idProp.value;
10922
+ if (value.type !== "Literal") throw new Error(`'id' field in ${configPath} must be a string literal. To use this config for a separate app, delete it.`);
10923
+ const literalValue = value.value;
10924
+ if (typeof literalValue !== "string" || literalValue === "") throw new Error(`'id' field in ${configPath} must be a non-empty string literal. To use this config for a separate app, delete it.`);
10925
+ if (!uuidRegex.test(literalValue)) throw new Error(`'id' field in ${configPath} must be a UUID. To use this config for a separate app, delete it.`);
10926
+ return {
10927
+ id: literalValue,
10928
+ injected: false
10929
+ };
10930
+ }
10931
+ const id = crypto.randomUUID();
10932
+ const newSource = insertIdProperty(source, configObj, id);
10933
+ await fs$1.promises.writeFile(configPath, newSource, "utf-8");
10934
+ logger.info(`Generated app id and wrote to ${configPath}: ${id}`);
10935
+ return {
10936
+ id,
10937
+ injected: true
10938
+ };
10939
+ }
10940
+ /**
10941
+ * Read the resolved `defineConfig({...})` `id` from a config file without
10942
+ * mutating it. Returns null when the file has no inline `defineConfig()` call
10943
+ * (wrapper/re-export config), and `{ id: null }` when the call exists but has
10944
+ * no usable `id` property.
10945
+ * @param configPath - Absolute path to the config file
10946
+ * @returns The existing id (or null when absent), or null for wrapper configs
10947
+ */
10948
+ async function readConfigId(configPath) {
10949
+ const { program } = parseSync(configPath, await fs$1.promises.readFile(configPath, "utf-8"));
10950
+ const calls = [];
10951
+ findDefineConfigCalls(program, calls);
10952
+ if (calls.length === 0) return null;
10953
+ if (calls.length > 1) throw new Error(`Multiple defineConfig() calls found in ${configPath}. Only one is supported.`);
10954
+ const { configObj } = assertDefined(calls[0], "defineConfig call site missing");
10955
+ if (!configObj) throw new Error(`defineConfig() argument must be an inline object literal in ${configPath} so the SDK can manage the 'id' field.`);
10956
+ const idProp = findIdProperty(configObj);
10957
+ if (!idProp || idProp.value.type !== "Literal") return { id: null };
10958
+ const value = idProp.value.value;
10959
+ return { id: typeof value === "string" && value !== "" ? value : null };
10960
+ }
10961
+ /**
10962
+ * Read-only CI check: the config must already carry a valid app id.
10963
+ * Wrapper/re-export configs (no inline defineConfig call) are exempt,
10964
+ * mirroring the local behavior where {@link ensureConfigId} no-ops.
10965
+ * @param configPath - Absolute path to the config file
10966
+ */
10967
+ async function assertConfigIdInCI(configPath) {
10968
+ const result = await readConfigId(configPath);
10969
+ if (result === null) return;
10970
+ if (!result.id) throw new Error("tailor.config.ts is missing an 'id'. CI does not auto-generate one (each run would be treated as a separate app and break resource ownership). Run 'tailor-sdk setup' or 'tailor-sdk apply' locally and commit the injected id.");
10971
+ if (!uuidRegex.test(result.id)) throw new Error(`'id' in ${configPath} must be a UUID. To use this config for a separate app, delete it.`);
10972
+ }
10973
+ /**
10974
+ * Ensure the config has an app id for a deploy run.
10975
+ *
10976
+ * Locally, the id is auto-injected when missing (via {@link ensureConfigId}).
10977
+ * In CI, the id is never auto-injected — a missing id is a hard error, because
10978
+ * generating one per run would create a fresh app each time and break resource
10979
+ * ownership. CI dry-runs (plan) perform the same check read-only, so a
10980
+ * forgotten id fails at PR time instead of at deploy. Ephemeral pipelines that
10981
+ * intentionally deploy a fresh app per run (such as e2e harnesses) can opt
10982
+ * back into injection with `TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION=true`.
10983
+ * Local dry-run and build-only flows skip both injection and the check (no
10984
+ * on-disk side effects are expected, and build-only never talks to the
10985
+ * platform).
10986
+ * @param obj - Inputs
10987
+ * @param obj.configPath - Absolute path to the config file
10988
+ * @param obj.dryRun - Whether this is a dry-run
10989
+ * @param obj.buildOnly - Whether this is a build-only run
10990
+ */
10991
+ async function ensureConfigIdForDeploy(obj) {
10992
+ const { configPath, dryRun, buildOnly } = obj;
10993
+ if (buildOnly) return;
10994
+ const allowCIInjection = parseBoolean(process.env.TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION) === true;
10995
+ const strictCI = isCI && !allowCIInjection;
10996
+ if (dryRun) {
10997
+ if (strictCI) await assertConfigIdInCI(configPath);
10998
+ return;
10999
+ }
11000
+ if (strictCI) {
11001
+ await assertConfigIdInCI(configPath);
11002
+ return;
11003
+ }
11004
+ await ensureConfigId(configPath);
11005
+ }
11006
+ const idComment = "// SDK-managed app id — do not edit, except when copying this config to a separate app.";
11007
+ function insertIdProperty(source, configObj, id) {
11008
+ const idLiteral = `id: ${JSON.stringify(id)}`;
11009
+ if (configObj.properties.length > 0) {
11010
+ const firstProp = assertDefined(configObj.properties[0], "first property missing");
11011
+ const lineStart = source.lastIndexOf("\n", firstProp.start - 1) + 1;
11012
+ const indent = source.slice(lineStart, firstProp.start);
11013
+ if (!/^[\t ]*$/.test(indent)) return source.slice(0, firstProp.start) + `${idLiteral}, ` + source.slice(firstProp.start);
11014
+ const insertion = `${idComment}\n${indent}${idLiteral},\n${indent}`;
11015
+ return source.slice(0, firstProp.start) + insertion + source.slice(firstProp.start);
11016
+ }
11017
+ const openBracePos = configObj.start + 1;
11018
+ const braceLineStart = source.lastIndexOf("\n", configObj.start) + 1;
11019
+ const baseIndent = source.slice(braceLineStart).match(/^[\t ]*/)?.[0] ?? "";
11020
+ const innerIndent = `${baseIndent} `;
11021
+ const insertion = `\n${innerIndent}${idComment}\n${innerIndent}${idLiteral},\n${baseIndent}`;
11022
+ return source.slice(0, openBracePos) + insertion + source.slice(openBracePos);
11023
+ }
11024
+
11025
+ //#endregion
11026
+ //#region src/cli/commands/deploy/confirm.ts
11027
+ /**
11028
+ * Confirm reassignment of resources when owner conflicts are detected.
11029
+ * Splits into two scenarios: id regeneration (same sdk-name, different
11030
+ * sdk-app-id) and name mismatch (different sdk-name). Each gets its own
11031
+ * prompt because the user-facing meaning is different.
11032
+ * @param conflicts - Detected owner conflicts
11033
+ * @param appName - Target application name
11034
+ * @param yes - Whether to auto-confirm without prompting
11035
+ * @returns Promise that resolves when confirmation completes
11036
+ */
11037
+ async function confirmOwnerConflict(conflicts, appName, yes) {
11038
+ if (conflicts.length === 0) return;
11039
+ const idRegenerated = conflicts.filter((c) => c.currentOwner === appName);
11040
+ const nameMismatches = conflicts.filter((c) => c.currentOwner !== appName);
11041
+ if (idRegenerated.length > 0) await confirmIdRegeneration(idRegenerated, appName, yes);
11042
+ if (nameMismatches.length > 0) await confirmNameMismatch(nameMismatches, appName, yes);
11043
+ }
11044
+ async function confirmIdRegeneration(conflicts, appName, yes) {
11045
+ logger.warn(`Application id was regenerated for "${appName}":`);
11046
+ logger.log(" These resources still carry the previous id.");
11047
+ logger.newline();
11048
+ logger.log(` ${styles.info("Resources")}:`);
11049
+ for (const c of conflicts) logger.log(` • ${styles.bold(c.resourceType)} ${styles.info(`"${c.resourceName}"`)}`);
11050
+ if (yes) {
11051
+ logger.success("Re-tagging resources with the new id (--yes flag specified)...", { mode: "plain" });
11052
+ return;
11053
+ }
11054
+ if (!await prompt.confirm({
11055
+ message: `Re-tag these resources with the new id for "${appName}"?\n${styles.dim("(The id in tailor.config.ts was removed since the previous deploy, so a new one was generated)")}`,
11056
+ default: false
11057
+ })) throw new Error(multiline`
11058
+ Apply cancelled. Resources remain tagged with the previous id.
11059
+ To override, run again and confirm, or use --yes flag.
11060
+ `);
11061
+ }
11062
+ async function confirmNameMismatch(conflicts, appName, yes) {
11063
+ const currentOwners = [...new Set(conflicts.map((c) => c.currentOwner))];
11064
+ logger.warn("Application name mismatch detected:");
11065
+ logger.log(` ${styles.warning("Current application(s)")}: ${currentOwners.map((o) => styles.bold(`"${o}"`)).join(", ")}`);
11066
+ logger.log(` ${styles.success("New application")}: ${styles.bold(`"${appName}"`)}`);
11067
+ logger.newline();
11068
+ logger.log(` ${styles.info("Resources")}:`);
11069
+ for (const c of conflicts) logger.log(` • ${styles.bold(c.resourceType)} ${styles.info(`"${c.resourceName}"`)}`);
11070
+ if (yes) {
11071
+ logger.success("Updating resources (--yes flag specified)...", { mode: "plain" });
11072
+ return;
11073
+ }
11074
+ const promptMessage = currentOwners.length === 1 ? `Update these resources to be managed by "${appName}"?\n${styles.dim("(Common when renaming your application)")}` : `Update these resources to be managed by "${appName}"?`;
11075
+ if (!await prompt.confirm({
11076
+ message: promptMessage,
11077
+ default: false
11078
+ })) throw new Error(multiline`
11079
+ Apply cancelled. Resources remain managed by their current applications.
11080
+ To override, run again and confirm, or use --yes flag.
11081
+ `);
11082
+ }
11083
+ /**
11084
+ * Confirm allowing tailor-sdk to manage previously unmanaged resources.
11085
+ * @param resources - Unmanaged resources
11086
+ * @param appName - Target application name
11087
+ * @param yes - Whether to auto-confirm without prompting
11088
+ * @returns Promise that resolves when confirmation completes
11089
+ */
11090
+ async function confirmUnmanagedResources(resources, appName, yes) {
11091
+ if (resources.length === 0) return;
11092
+ logger.warn("Existing resources not tracked by tailor-sdk were found:");
11093
+ logger.log(` ${styles.info("Resources")}:`);
11094
+ for (const r of resources) logger.log(` • ${styles.bold(r.resourceType)} ${styles.info(`"${r.resourceName}"`)}`);
11095
+ logger.newline();
11096
+ logger.log(" These resources may have been created by older SDK versions, Terraform, or CUE.");
11097
+ logger.log(" To continue, confirm that tailor-sdk should manage them.");
11098
+ logger.log(" If they are managed by another tool (e.g., Terraform), cancel and manage them there instead.");
11099
+ if (yes) {
11100
+ logger.success(`Adding to "${appName}" (--yes flag specified)...`, { mode: "plain" });
11101
+ return;
11102
+ }
11103
+ if (!await prompt.confirm({
11104
+ message: `Allow tailor-sdk to manage these resources for "${appName}"?`,
11105
+ default: false
11106
+ })) throw new Error(multiline`
11107
+ Apply cancelled. Resources remain unmanaged.
11108
+ To override, run again and confirm, or use --yes flag.
11109
+ `);
11110
+ }
11111
+ /**
11112
+ * Confirm deletion of important resources.
11113
+ * @param resources - Resources scheduled for deletion
11114
+ * @param yes - Whether to auto-confirm without prompting
11115
+ * @returns Promise that resolves when confirmation completes
11116
+ */
11117
+ async function confirmImportantResourceDeletion(resources, yes) {
11118
+ if (resources.length === 0) return;
11119
+ logger.warn("The following resources will be deleted:");
11120
+ logger.log(` ${styles.info("Resources")}:`);
11121
+ for (const r of resources) logger.log(` • ${styles.bold(r.resourceType)} ${styles.error(`"${r.resourceName}"`)}`);
11122
+ logger.newline();
11123
+ logger.log(styles.warning(" Deleting these resources will permanently remove all associated data."));
11124
+ if (yes) {
11125
+ logger.success("Deleting resources (--yes flag specified)...", { mode: "plain" });
11126
+ return;
11127
+ }
11128
+ if (!await prompt.confirm({
11129
+ message: "Are you sure you want to delete these resources?",
11130
+ default: false
11131
+ })) throw new Error(multiline`
11132
+ Apply cancelled. Resources will not be deleted.
11133
+ To override, run again and confirm, or use --yes flag.
11134
+ `);
11135
+ }
11136
+
11108
11137
  //#endregion
11109
11138
  //#region src/cli/commands/deploy/validate-plan.ts
11110
11139
  function validateItems(params) {
@@ -11336,17 +11365,126 @@ async function validatePlan(input) {
11336
11365
  */
11337
11366
  function collectIdpUserTriggerTargets(application) {
11338
11367
  const targets = /* @__PURE__ */ new Set();
11339
- const idps = application.idpServices;
11368
+ const idps = collectApplicationIdpNames(application);
11340
11369
  for (const executor of Object.values(application.executorService?.executors ?? {})) {
11341
11370
  if (executor.trigger.kind !== "idpUser") continue;
11342
11371
  if (executor.trigger.idp != null) targets.add(executor.trigger.idp);
11343
- else if (idps.length === 1) {
11372
+ else if (idps.size === 1) {
11344
11373
  const [idp] = idps;
11345
- if (idp) targets.add(idp.name);
11374
+ if (idp) targets.add(idp);
11346
11375
  }
11347
11376
  }
11348
11377
  return targets;
11349
11378
  }
11379
+ function collectDeployIdpUserTriggerTargets(targets) {
11380
+ const triggerTargets = /* @__PURE__ */ new Set();
11381
+ for (const target of targets) for (const idpName of collectIdpUserTriggerTargets(target.application)) triggerTargets.add(idpName);
11382
+ return triggerTargets;
11383
+ }
11384
+ function collectExecutorUsedTailorDBTypes(owner, targets) {
11385
+ const usedTypes = /* @__PURE__ */ new Set();
11386
+ const ownerNamespaces = new Set(owner.application.tailorDBServices.map((service) => service.namespace));
11387
+ const applications = targets.map((target) => target.application);
11388
+ for (const target of targets) {
11389
+ const visibleNamespaces = collectVisibleTailorDBTypeNamespaces(target.application, applications);
11390
+ for (const executor of Object.values(target.application.executorService?.executors ?? {})) if (executor.trigger.kind === "tailordb") {
11391
+ const namespace = findTailorDBNamespace(target.application, executor.trigger.typeName) ?? resolveSameRunNamespace(visibleNamespaces, executor.trigger.typeName, "TailorDB type");
11392
+ if (namespace && ownerNamespaces.has(namespace)) usedTypes.add(executor.trigger.typeName);
11393
+ }
11394
+ }
11395
+ return usedTypes;
11396
+ }
11397
+ function collectExecutorUsedResolvers(owner, targets) {
11398
+ const usedResolvers = /* @__PURE__ */ new Set();
11399
+ const ownerNamespaces = new Set(owner.application.resolverServices.map((service) => service.namespace));
11400
+ const applications = targets.map((target) => target.application);
11401
+ for (const target of targets) {
11402
+ const visibleNamespaces = collectVisibleResolverNamespaces(target.application, applications);
11403
+ for (const executor of Object.values(target.application.executorService?.executors ?? {})) if (executor.trigger.kind === "resolverExecuted") {
11404
+ const namespace = findResolverNamespace(target.application, executor.trigger.resolverName) ?? resolveSameRunNamespace(visibleNamespaces, executor.trigger.resolverName, "Resolver");
11405
+ if (namespace && ownerNamespaces.has(namespace)) usedResolvers.add(executor.trigger.resolverName);
11406
+ }
11407
+ }
11408
+ return usedResolvers;
11409
+ }
11410
+ function collectExpectedLocalStaticWebsiteNames(targets) {
11411
+ const websiteNames = /* @__PURE__ */ new Set();
11412
+ for (const target of targets) for (const website of target.application.staticWebsiteServices) websiteNames.add(website.name);
11413
+ return websiteNames;
11414
+ }
11415
+ function addPossiblyAmbiguousNamespace(namespaces, key, namespace) {
11416
+ if (namespaces.has(key)) {
11417
+ if (namespaces.get(key) !== namespace) namespaces.set(key, void 0);
11418
+ return;
11419
+ }
11420
+ namespaces.set(key, namespace);
11421
+ }
11422
+ function collectVisibleResources(params) {
11423
+ const { application, applications, visibleKeysOf, resourcesOf, createResult, addResource } = params;
11424
+ const visibleKeys = visibleKeysOf(application);
11425
+ const result = createResult();
11426
+ for (const candidate of applications) for (const resource of resourcesOf(candidate)) {
11427
+ if (!visibleKeys.has(resource.visibilityKey)) continue;
11428
+ addResource(result, resource.resourceKey, resource.visibilityKey);
11429
+ }
11430
+ return result;
11431
+ }
11432
+ function collectApplicationTailorDBNamespaces(application) {
11433
+ return /* @__PURE__ */ new Set([...application.tailorDBServices.map((service) => service.namespace), ...application.externalTailorDBNamespaces]);
11434
+ }
11435
+ function* tailorDBTypeResources(application) {
11436
+ for (const service of application.tailorDBServices) for (const typeName of Object.keys(service.types)) yield {
11437
+ visibilityKey: service.namespace,
11438
+ resourceKey: typeName
11439
+ };
11440
+ }
11441
+ function collectVisibleTailorDBTypeNamespaces(application, applications) {
11442
+ return collectVisibleResources({
11443
+ application,
11444
+ applications,
11445
+ visibleKeysOf: collectApplicationTailorDBNamespaces,
11446
+ resourcesOf: tailorDBTypeResources,
11447
+ createResult: () => /* @__PURE__ */ new Map(),
11448
+ addResource: addPossiblyAmbiguousNamespace
11449
+ });
11450
+ }
11451
+ function collectApplicationResolverNamespaces(application) {
11452
+ return new Set(application.subgraphs.filter((subgraph) => subgraph.Type === "pipeline").map((subgraph) => subgraph.Name));
11453
+ }
11454
+ function* idpNameResources(application) {
11455
+ for (const name of collectApplicationIdpNames(application)) yield {
11456
+ visibilityKey: name,
11457
+ resourceKey: name
11458
+ };
11459
+ }
11460
+ function collectVisibleIdpNames(application, applications) {
11461
+ return collectVisibleResources({
11462
+ application,
11463
+ applications,
11464
+ visibleKeysOf: collectApplicationIdpNames,
11465
+ resourcesOf: idpNameResources,
11466
+ createResult: () => /* @__PURE__ */ new Set(),
11467
+ addResource: (names, name) => {
11468
+ names.add(name);
11469
+ }
11470
+ });
11471
+ }
11472
+ function* resolverResources(application) {
11473
+ for (const service of application.resolverServices) for (const resolver of Object.values(service.resolvers)) yield {
11474
+ visibilityKey: service.namespace,
11475
+ resourceKey: resolver.name
11476
+ };
11477
+ }
11478
+ function collectVisibleResolverNamespaces(application, applications) {
11479
+ return collectVisibleResources({
11480
+ application,
11481
+ applications,
11482
+ visibleKeysOf: collectApplicationResolverNamespaces,
11483
+ resourcesOf: resolverResources,
11484
+ createResult: () => /* @__PURE__ */ new Map(),
11485
+ addResource: addPossiblyAmbiguousNamespace
11486
+ });
11487
+ }
11350
11488
  async function shouldForceApplyAll(client, workspaceId, application, functionEntries) {
11351
11489
  const desiredLabels = (await buildMetaRequest({
11352
11490
  trn: resourceTrn(workspaceId, "application", application.name),
@@ -11383,39 +11521,31 @@ async function shouldForceApplyAll(client, workspaceId, application, functionEnt
11383
11521
  functionEntries.forEach((entry) => {
11384
11522
  candidateTrns.add(resourceTrn(workspaceId, "function_registry", entry.name));
11385
11523
  });
11386
- for (const trn of candidateTrns) try {
11387
- const { metadata } = await client.getMetadata({ trn });
11524
+ for (const trn of candidateTrns) {
11525
+ const metadata = await getOrNull(async () => {
11526
+ const { metadata } = await client.getMetadata({ trn });
11527
+ return metadata;
11528
+ });
11388
11529
  if (metadata?.labels["sdk-name"] !== application.name) continue;
11389
11530
  if (!hasMatchingSdkVersion(metadata.labels, desiredLabels)) return true;
11390
- } catch (error) {
11391
- if (error instanceof ConnectError && error.code === Code.NotFound) continue;
11392
- throw error;
11393
11531
  }
11394
11532
  return false;
11395
11533
  }
11396
11534
  /**
11397
11535
  * Decide which renamed-away applications should be deleted. Excludes the
11398
- * target itself: id regeneration alone keeps the name unchanged, so deleting
11399
- * by name would destroy the live app.
11536
+ * deploy targets themselves: id regeneration alone keeps the name unchanged,
11537
+ * so deleting by name would destroy a live app.
11400
11538
  * @param params - Inputs for the computation
11401
11539
  * @param params.conflicts - Detected owner conflicts across all services
11402
11540
  * @param params.resourceOwners - App names that still own resources we don't manage
11403
- * @param params.targetAppName - The application currently being deployed
11541
+ * @param params.protectedAppNames - App names that must not be deleted
11404
11542
  * @returns Names of empty old applications that should be deleted
11405
11543
  */
11406
11544
  function computeRenamedAppDeletions(params) {
11407
- const { conflicts, resourceOwners, targetAppName } = params;
11408
- return [...new Set(conflicts.map((c) => c.currentOwner))].filter((owner) => !resourceOwners.has(owner) && owner !== targetAppName);
11545
+ const { conflicts, resourceOwners, protectedAppNames } = params;
11546
+ return [...new Set(conflicts.map((c) => c.currentOwner))].filter((owner) => !resourceOwners.has(owner) && !protectedAppNames.has(owner));
11409
11547
  }
11410
- /**
11411
- * Format and output the plan results, then return a summary of change counts.
11412
- * In JSON dry-run mode a JSON payload is written to stdout. In all other modes
11413
- * the human-readable diff goes to stdout (dry-run) or stderr (apply).
11414
- * @param results - Planned results across all services
11415
- * @param opts - Output options (dry-run mode flag)
11416
- * @returns Aggregated plan summary counts
11417
- */
11418
- function printPlanResults(results, opts) {
11548
+ function buildPlanReport(results) {
11419
11549
  const executorEntries = formatExecutorChangeEntries(results.executor.changeSet, buildPlannedExecutorsByName(results.executor.changeSet), results.functionRegistry.executorFunctionChanges);
11420
11550
  const resolverEntries = formatResolverChangeEntries(results.pipeline.changeSet.resolver, results.functionRegistry.resolverFunctionChanges);
11421
11551
  const workflowEntries = formatWorkflowChangeEntries(results.workflow.changeSet, results.functionRegistry.workflowJobChanges);
@@ -11482,132 +11612,808 @@ function printPlanResults(results, opts) {
11482
11612
  ...results.workflow.conflicts,
11483
11613
  ...results.secretManager.conflicts
11484
11614
  ];
11485
- if (logger.jsonMode && opts?.dryRun) {
11486
- const changes = [
11487
- ...allDisplayEntries,
11488
- ...tailorDBServiceActions.map(({ action, name }) => ({
11489
- action,
11490
- name,
11491
- labels: ["tailorDB"],
11492
- namespace: void 0
11493
- })),
11494
- ...pipelineServiceActions.map(({ action, name }) => ({
11495
- action,
11496
- name,
11497
- labels: ["pipeline"],
11498
- namespace: void 0
11499
- })),
11500
- ...idpServiceActions.map(({ action, name }) => ({
11501
- action,
11502
- name,
11503
- labels: ["idp"],
11504
- namespace: void 0
11505
- })),
11506
- ...authServiceActions.map(({ action, name }) => ({
11507
- action,
11508
- name,
11509
- labels: ["auth"],
11510
- namespace: void 0
11511
- })),
11512
- ...formatChangeSetEntries(otherFunctionRegistryChanges),
11513
- ...formatChangeSetEntries(results.staticWebsite.changeSet, ["staticWebsite"]),
11514
- ...formatChangeSetEntries(results.staticWebsite.customDomainChangeSet, ["customDomain"]),
11515
- ...formatChangeSetEntries(results.aiGateway.changeSet, ["aiGateway"]),
11516
- ...formatChangeSetEntries(results.app, ["application"]),
11517
- ...formatChangeSetEntries(results.secretManager.vaultChangeSet, ["vault"]),
11518
- ...formatChangeSetEntries(results.secretManager.secretChangeSet, ["secret"])
11519
- ].map(({ action, name, labels, namespace }) => ({
11520
- action,
11521
- name,
11522
- labels,
11523
- namespace
11524
- }));
11525
- const warnings = [...allUnmanaged.map(({ resourceType, resourceName }) => ({
11526
- type: "unmanaged",
11527
- resourceType,
11528
- name: resourceName
11529
- })), ...results.secretManager.skippedSecrets.map((name) => ({
11530
- type: "skippedSecret",
11531
- resourceType: "secret",
11532
- name
11533
- }))];
11534
- const conflicts = allConflicts.map(({ resourceType, resourceName, currentOwner }) => ({
11535
- resourceType,
11536
- name: resourceName,
11537
- currentOwner
11538
- }));
11539
- logger.out({
11540
- summary,
11541
- changes,
11542
- warnings,
11543
- conflicts
11544
- });
11545
- return summary;
11546
- }
11547
- const allLines = [
11548
- ...buildGroupedDisplayLines(results.functionRegistry.changeSet.title, formatChangeSetEntries(otherFunctionRegistryChanges)),
11549
- ...results.staticWebsite.changeSet.lines(),
11550
- ...results.staticWebsite.customDomainChangeSet.lines(),
11551
- ...results.aiGateway.changeSet.lines(),
11552
- ...results.app.lines(),
11553
- ...buildGroupedDisplayLines("TailorDB", tailorDBEntries, tailorDBServiceActions),
11554
- ...buildGroupedDisplayLines("Resolver", pipelineEntries, pipelineServiceActions),
11555
- ...buildGroupedDisplayLines("Executor", executorEntries),
11556
- ...buildGroupedDisplayLines("Workflow", workflowEntries),
11557
- ...buildGroupedDisplayLines("IdP", idpEntries, idpServiceActions),
11558
- ...buildGroupedDisplayLines("Auth", authEntries, authServiceActions),
11559
- ...results.secretManager.vaultChangeSet.lines(),
11560
- ...results.secretManager.secretChangeSet.lines()
11615
+ const changes = [
11616
+ ...allDisplayEntries,
11617
+ ...tailorDBServiceActions.map(({ action, name }) => ({
11618
+ action,
11619
+ name,
11620
+ labels: ["tailorDB"],
11621
+ namespace: void 0
11622
+ })),
11623
+ ...pipelineServiceActions.map(({ action, name }) => ({
11624
+ action,
11625
+ name,
11626
+ labels: ["pipeline"],
11627
+ namespace: void 0
11628
+ })),
11629
+ ...idpServiceActions.map(({ action, name }) => ({
11630
+ action,
11631
+ name,
11632
+ labels: ["idp"],
11633
+ namespace: void 0
11634
+ })),
11635
+ ...authServiceActions.map(({ action, name }) => ({
11636
+ action,
11637
+ name,
11638
+ labels: ["auth"],
11639
+ namespace: void 0
11640
+ })),
11641
+ ...formatChangeSetEntries(otherFunctionRegistryChanges),
11642
+ ...formatChangeSetEntries(results.staticWebsite.changeSet, ["staticWebsite"]),
11643
+ ...formatChangeSetEntries(results.staticWebsite.customDomainChangeSet, ["customDomain"]),
11644
+ ...formatChangeSetEntries(results.aiGateway.changeSet, ["aiGateway"]),
11645
+ ...formatChangeSetEntries(results.app, ["application"]),
11646
+ ...formatChangeSetEntries(results.secretManager.vaultChangeSet, ["vault"]),
11647
+ ...formatChangeSetEntries(results.secretManager.secretChangeSet, ["secret"])
11648
+ ].map(({ action, name, labels, namespace }) => ({
11649
+ action,
11650
+ name,
11651
+ labels,
11652
+ namespace
11653
+ }));
11654
+ const warnings = [...allUnmanaged.map(({ resourceType, resourceName }) => ({
11655
+ type: "unmanaged",
11656
+ resourceType,
11657
+ name: resourceName
11658
+ })), ...results.secretManager.skippedSecrets.map((name) => ({
11659
+ type: "skippedSecret",
11660
+ resourceType: "secret",
11661
+ name
11662
+ }))];
11663
+ const conflicts = allConflicts.map(({ resourceType, resourceName, currentOwner }) => ({
11664
+ resourceType,
11665
+ name: resourceName,
11666
+ currentOwner
11667
+ }));
11668
+ const allLines = [
11669
+ ...buildGroupedDisplayLines(results.functionRegistry.changeSet.title, formatChangeSetEntries(otherFunctionRegistryChanges)),
11670
+ ...results.staticWebsite.changeSet.lines(),
11671
+ ...results.staticWebsite.customDomainChangeSet.lines(),
11672
+ ...results.aiGateway.changeSet.lines(),
11673
+ ...results.app.lines(),
11674
+ ...buildGroupedDisplayLines("TailorDB", tailorDBEntries, tailorDBServiceActions),
11675
+ ...buildGroupedDisplayLines("Resolver", pipelineEntries, pipelineServiceActions),
11676
+ ...buildGroupedDisplayLines("Executor", executorEntries),
11677
+ ...buildGroupedDisplayLines("Workflow", workflowEntries),
11678
+ ...buildGroupedDisplayLines("IdP", idpEntries, idpServiceActions),
11679
+ ...buildGroupedDisplayLines("Auth", authEntries, authServiceActions),
11680
+ ...results.secretManager.vaultChangeSet.lines(),
11681
+ ...results.secretManager.secretChangeSet.lines()
11682
+ ];
11683
+ if (allUnmanaged.length > 0) {
11684
+ allLines.push(styles.bold("Unmanaged resources (not in config):"));
11685
+ for (const { resourceType, resourceName } of allUnmanaged) allLines.push(` ${styles.warning("")} ${styles.bold(resourceType)} "${resourceName}"`);
11686
+ }
11687
+ if (results.secretManager.skippedSecrets.length > 0) {
11688
+ allLines.push(styles.bold("Secret Manager secrets (skipped - no value provided):"));
11689
+ for (const name of results.secretManager.skippedSecrets) allLines.push(` ${styles.dim("○")} ${name}`);
11690
+ }
11691
+ if (allConflicts.length > 0) {
11692
+ allLines.push(styles.bold("Owner conflicts (will require confirmation on apply):"));
11693
+ for (const { resourceType, resourceName, currentOwner } of allConflicts) allLines.push(` ${styles.warning("!")} ${styles.bold(resourceType)} "${resourceName}" — owned by "${currentOwner}"`);
11694
+ }
11695
+ allLines.push(formatPlanSummary(summary));
11696
+ return {
11697
+ summary,
11698
+ json: {
11699
+ summary,
11700
+ changes,
11701
+ warnings,
11702
+ conflicts
11703
+ },
11704
+ lines: allLines
11705
+ };
11706
+ }
11707
+ /**
11708
+ * Format and output the plan results, then return a summary of change counts.
11709
+ * In JSON dry-run mode a JSON payload is written to stdout. In all other modes
11710
+ * the human-readable diff goes to stdout (dry-run) or stderr (apply).
11711
+ * @param results - Planned results across all services
11712
+ * @param opts - Output options (dry-run mode flag)
11713
+ * @returns Aggregated plan summary counts
11714
+ */
11715
+ function printPlanResults(results, opts) {
11716
+ const report = buildPlanReport(results);
11717
+ if (logger.jsonMode && opts?.dryRun) {
11718
+ logger.out(report.json);
11719
+ return report.summary;
11720
+ }
11721
+ const output = report.lines.join("\n");
11722
+ if (opts?.dryRun) logger.out(output);
11723
+ else logger.log(output);
11724
+ return report.summary;
11725
+ }
11726
+ /**
11727
+ * Summarize plan counts from display entries, service actions, and non-grouped changesets.
11728
+ * @param results - Planned apply results
11729
+ * @param displayEntries - All grouped display entries across sections
11730
+ * @param serviceActions - All service-level namespace actions
11731
+ * @returns Aggregated plan summary
11732
+ */
11733
+ function summarizePlanResults(results, displayEntries, serviceActions) {
11734
+ const summary = {
11735
+ create: 0,
11736
+ update: 0,
11737
+ delete: 0,
11738
+ replace: 0
11739
+ };
11740
+ for (const entry of displayEntries) summary[entry.action] += 1;
11741
+ for (const sa of serviceActions) summary[sa.action] += 1;
11742
+ const { otherChanges } = splitFunctionRegistryChanges(results.functionRegistry.changeSet);
11743
+ const nonGrouped = summarizeChangeSets([
11744
+ otherChanges,
11745
+ results.staticWebsite.changeSet,
11746
+ results.staticWebsite.customDomainChangeSet,
11747
+ results.aiGateway.changeSet,
11748
+ results.app,
11749
+ results.secretManager.vaultChangeSet,
11750
+ results.secretManager.secretChangeSet
11751
+ ]);
11752
+ summary.create += nonGrouped.create;
11753
+ summary.update += nonGrouped.update;
11754
+ summary.delete += nonGrouped.delete;
11755
+ summary.replace += nonGrouped.replace;
11756
+ return summary;
11757
+ }
11758
+ /**
11759
+ * Parse the deploy config option into one or more config paths.
11760
+ * @param configPath - Raw `--config` option value
11761
+ * @returns Config paths, or one undefined entry to preserve default config lookup
11762
+ */
11763
+ function parseDeployConfigPaths(configPath) {
11764
+ const rawConfigPath = configPath ?? process.env.TAILOR_PLATFORM_SDK_CONFIG_PATH;
11765
+ if (rawConfigPath === void 0) return [void 0];
11766
+ const configPaths = rawConfigPath.split(",").map((entry) => entry.trim());
11767
+ if (configPaths.some((entry) => entry.length === 0)) throw new Error("--config must contain one or more non-empty config paths.");
11768
+ return configPaths;
11769
+ }
11770
+ async function buildDeploymentTarget(params) {
11771
+ const { configPath, dryRun, buildOnly, noCache, packageVersion, cacheDir } = params;
11772
+ const { config, plugins } = await withSpan("build.loadConfig", async () => {
11773
+ const foundPath = loadConfigPath(configPath);
11774
+ if (foundPath) {
11775
+ const resolvedPath = path.resolve(process.cwd(), foundPath);
11776
+ if (fs$1.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
11777
+ configPath: resolvedPath,
11778
+ dryRun,
11779
+ buildOnly
11780
+ });
11781
+ }
11782
+ return loadConfig(configPath);
11783
+ });
11784
+ const configDir = path.dirname(config.path);
11785
+ const lockfilePath = findUpSync("pnpm-lock.yaml", { cwd: configDir }) ?? findUpSync("package-lock.json", { cwd: configDir }) ?? findUpSync("yarn.lock", { cwd: configDir }) ?? findUpSync("bun.lock", { cwd: configDir });
11786
+ const cacheManager = createCacheManager({
11787
+ enabled: !noCache,
11788
+ cacheDir,
11789
+ sdkVersion: packageVersion,
11790
+ lockfileHash: lockfilePath ? hashFile(lockfilePath) : void 0
11791
+ });
11792
+ let pluginManager;
11793
+ if (plugins.length > 0) pluginManager = new PluginManager(plugins);
11794
+ await withSpan("build.generateUserTypes", () => generateUserTypes({
11795
+ config,
11796
+ configPath: config.path
11797
+ }));
11798
+ let application;
11799
+ let workflowBuildResult;
11800
+ let httpAdapterBuildResult;
11801
+ let bundledScripts;
11802
+ try {
11803
+ const result = await withSpan("build.loadApplication", () => loadApplication({
11804
+ config,
11805
+ pluginManager,
11806
+ bundleCache: cacheManager.bundleCache
11807
+ }));
11808
+ application = result.application;
11809
+ workflowBuildResult = result.workflowBuildResult;
11810
+ httpAdapterBuildResult = result.httpAdapterBuildResult;
11811
+ bundledScripts = result.bundledScripts;
11812
+ } finally {
11813
+ cacheManager.finalize();
11814
+ }
11815
+ return {
11816
+ config,
11817
+ application,
11818
+ workflowBuildResult,
11819
+ httpAdapterBuildResult,
11820
+ bundledScripts
11821
+ };
11822
+ }
11823
+ async function buildDeploymentTargets(params) {
11824
+ const { configPaths, buildTarget = buildDeploymentTarget, ...targetParams } = params;
11825
+ return Promise.all(configPaths.map((configPath) => buildTarget({
11826
+ ...targetParams,
11827
+ configPath
11828
+ })));
11829
+ }
11830
+ function setBundledScript(target, name, code, kind) {
11831
+ if (target.has(name)) throw new Error(`Duplicate ${kind} bundle name "${name}" across config files.`);
11832
+ target.set(name, code);
11833
+ }
11834
+ function addBundledScripts(target, source, kind) {
11835
+ for (const [name, code] of source) setBundledScript(target, name, code, kind);
11836
+ }
11837
+ function collectDuplicateBundleNames(targets, select) {
11838
+ const seen = /* @__PURE__ */ new Set();
11839
+ const duplicates = /* @__PURE__ */ new Set();
11840
+ for (const target of targets) for (const name of select(target).keys()) {
11841
+ if (seen.has(name)) duplicates.add(name);
11842
+ seen.add(name);
11843
+ }
11844
+ return duplicates;
11845
+ }
11846
+ function addResolverBundledScripts(target, source, duplicateNames) {
11847
+ const consumedNames = /* @__PURE__ */ new Set();
11848
+ for (const service of source.application.resolverServices) for (const resolver of Object.values(service.resolvers)) {
11849
+ const code = source.bundledScripts.resolvers.get(resolver.name);
11850
+ if (code === void 0) continue;
11851
+ setBundledScript(target, duplicateNames.has(resolver.name) ? resolverFunctionName(service.namespace, resolver.name) : resolver.name, code, "resolver");
11852
+ consumedNames.add(resolver.name);
11853
+ }
11854
+ for (const [name, code] of source.bundledScripts.resolvers) if (!consumedNames.has(name)) setBundledScript(target, name, code, "resolver");
11855
+ }
11856
+ /**
11857
+ * Merge per-config bundled scripts into one build-only result.
11858
+ * @param targets - Built deployment targets to merge
11859
+ * @returns Combined bundled scripts across all targets
11860
+ */
11861
+ function mergeBundledScripts(targets) {
11862
+ const bundledScripts = {
11863
+ resolvers: /* @__PURE__ */ new Map(),
11864
+ executors: /* @__PURE__ */ new Map(),
11865
+ workflowJobs: /* @__PURE__ */ new Map(),
11866
+ authHooks: /* @__PURE__ */ new Map()
11867
+ };
11868
+ const duplicateResolverNames = collectDuplicateBundleNames(targets, (target) => target.bundledScripts.resolvers);
11869
+ for (const target of targets) {
11870
+ addResolverBundledScripts(bundledScripts.resolvers, target, duplicateResolverNames);
11871
+ addBundledScripts(bundledScripts.executors, target.bundledScripts.executors, "executor");
11872
+ addBundledScripts(bundledScripts.workflowJobs, target.bundledScripts.workflowJobs, "workflow job");
11873
+ addBundledScripts(bundledScripts.authHooks, target.bundledScripts.authHooks, "auth hook");
11874
+ }
11875
+ return bundledScripts;
11876
+ }
11877
+ /**
11878
+ * Reject resource names that collide across configs. Each checked resource
11879
+ * type is workspace-global (its TRN is not qualified by namespace or app), so
11880
+ * a duplicate name would make two configs target the same platform resource:
11881
+ * one config's apply could overwrite the other's, or a fresh create could be
11882
+ * attempted twice. Resolver and auth-hook function names are namespace- or
11883
+ * app-qualified and are intentionally excluded.
11884
+ * @param targets - Built deployment targets to check
11885
+ */
11886
+ function assertUniqueGlobalResourceNames(targets) {
11887
+ for (const check of DEPLOY_MANAGED_RESOURCE_DEFINITIONS) {
11888
+ const seen = /* @__PURE__ */ new Set();
11889
+ for (const target of targets) for (const name of check.namesOf(target)) {
11890
+ if (seen.has(name)) throw new Error(`Duplicate ${check.resourceLabel} name "${name}" across config files. ${check.resourceLabel} names must be unique across all configs in a single deploy.`);
11891
+ seen.add(name);
11892
+ }
11893
+ }
11894
+ }
11895
+ function collectPlannedExternalTailorDBServices(target, targets) {
11896
+ const externalNamespaces = new Set(target.application.externalTailorDBNamespaces);
11897
+ if (externalNamespaces.size === 0) return [];
11898
+ return targets.flatMap((candidate) => candidate.application.tailorDBServices.filter((service) => externalNamespaces.has(service.namespace)));
11899
+ }
11900
+ function collectExternalAuthIdpConfigNames(targets) {
11901
+ const idpConfigNames = /* @__PURE__ */ new Map();
11902
+ for (const target of targets) {
11903
+ const authService = target.application.authService;
11904
+ if (!authService) continue;
11905
+ const { name } = authService.config;
11906
+ const idpConfigName = authService.config.idProvider?.name;
11907
+ if (idpConfigNames.has(name) && idpConfigNames.get(name) !== idpConfigName) throw new Error(`Auth namespace "${name}" is defined by multiple config files with different IdP configs. Auth namespace names must be unique across all configs in a single deploy.`);
11908
+ idpConfigNames.set(name, idpConfigName);
11909
+ }
11910
+ return idpConfigNames;
11911
+ }
11912
+ function collectDeployRunPlanInputs(targets) {
11913
+ return {
11914
+ idpUserTriggerTargets: collectDeployIdpUserTriggerTargets(targets),
11915
+ expectedLocalStaticWebsiteNames: collectExpectedLocalStaticWebsiteNames(targets),
11916
+ externalAuthIdpConfigNames: collectExternalAuthIdpConfigNames(targets)
11917
+ };
11918
+ }
11919
+ async function planDeploymentTarget(params) {
11920
+ const { target, targets, runInputs, client, workspaceId, noSchemaCheck } = params;
11921
+ const { config, application, workflowBuildResult, httpAdapterBuildResult, bundledScripts } = target;
11922
+ await withSpan("plan.validateTailorDBTypeNames", () => assertUniqueTailorDBTypeNamesWithExternal({
11923
+ client,
11924
+ workspaceId,
11925
+ tailorDBServices: application.tailorDBServices,
11926
+ externalTailorDBNamespaces: application.externalTailorDBNamespaces,
11927
+ plannedExternalTailorDBServices: collectPlannedExternalTailorDBServices(target, targets)
11928
+ }));
11929
+ const workflowService = application.workflowService;
11930
+ const functionEntries = collectFunctionEntries(application, filterBundledWorkflowJobs(workflowService?.jobs ?? [], workflowBuildResult?.usedJobNames ?? []), bundledScripts);
11931
+ const forceApplyAll = await withSpan("plan.detectSdkVersionChange", () => shouldForceApplyAll(client, workspaceId, application, functionEntries));
11932
+ return withSpan("plan", async () => {
11933
+ const applications = targets.map((target) => target.application);
11934
+ const tailorDBTypeNamespaces = collectVisibleTailorDBTypeNamespaces(application, applications);
11935
+ const resolverNamespaces = collectVisibleResolverNamespaces(application, applications);
11936
+ const idpNames = collectVisibleIdpNames(application, applications);
11937
+ const ctx = {
11938
+ client,
11939
+ workspaceId,
11940
+ application,
11941
+ forRemoval: false,
11942
+ config,
11943
+ noSchemaCheck,
11944
+ forceApplyAll,
11945
+ ...runInputs,
11946
+ executorUsedTailorDBTypes: collectExecutorUsedTailorDBTypes(target, targets),
11947
+ executorUsedResolvers: collectExecutorUsedResolvers(target, targets),
11948
+ tailorDBTypeNamespaces,
11949
+ resolverNamespaces,
11950
+ idpNames
11951
+ };
11952
+ const functionRegistry = await withSpan("plan.functionRegistry", () => planFunctionRegistry(client, workspaceId, application.name, application.id, functionEntries));
11953
+ const unchangedWorkflowJobs = new Set(functionRegistry.changeSet.unchanged.filter((entry) => entry.name.startsWith(WORKFLOW_PREFIX)).map((entry) => entry.name.slice(WORKFLOW_PREFIX.length)));
11954
+ const [tailorDB, staticWebsite, aiGateway, idp, auth, pipeline, app, executor, workflow, secretManager] = await Promise.all([
11955
+ withSpan("plan.tailorDB", () => planTailorDB(ctx)),
11956
+ withSpan("plan.staticWebsite", () => planStaticWebsite(ctx)),
11957
+ withSpan("plan.aiGateway", () => planAIGateway(ctx)),
11958
+ withSpan("plan.idp", () => planIdP(ctx)),
11959
+ withSpan("plan.auth", () => planAuth(ctx)),
11960
+ withSpan("plan.pipeline", () => planPipeline(ctx)),
11961
+ withSpan("plan.application", () => planApplication(ctx, httpAdapterBuildResult)),
11962
+ withSpan("plan.executor", () => planExecutor(ctx)),
11963
+ withSpan("plan.workflow", () => planWorkflow(client, workspaceId, application.name, application.id, workflowService?.workflows ?? {}, workflowBuildResult?.mainJobDeps ?? {}, unchangedWorkflowJobs)),
11964
+ withSpan("plan.secretManager", () => planSecretManager(ctx))
11965
+ ]);
11966
+ return {
11967
+ application,
11968
+ functionRegistry,
11969
+ tailorDB,
11970
+ staticWebsite,
11971
+ aiGateway,
11972
+ idp,
11973
+ auth,
11974
+ pipeline,
11975
+ app,
11976
+ executor,
11977
+ workflow,
11978
+ secretManager
11979
+ };
11980
+ });
11981
+ }
11982
+ async function planDeploymentTargets(params) {
11983
+ const { targets, planTarget = planDeploymentTarget, ...planParams } = params;
11984
+ return Promise.all(targets.map((target) => planTarget({
11985
+ ...planParams,
11986
+ target,
11987
+ targets
11988
+ })));
11989
+ }
11990
+ function deploymentPlanResults(deployment) {
11991
+ const { application: _application, ...results } = deployment;
11992
+ return results;
11993
+ }
11994
+ function collectOwnerConflicts(results) {
11995
+ return [
11996
+ ...results.functionRegistry.conflicts,
11997
+ ...results.tailorDB.conflicts,
11998
+ ...results.staticWebsite.conflicts,
11999
+ ...results.aiGateway.conflicts,
12000
+ ...results.idp.conflicts,
12001
+ ...results.auth.conflicts,
12002
+ ...results.pipeline.conflicts,
12003
+ ...results.executor.conflicts,
12004
+ ...results.workflow.conflicts,
12005
+ ...results.secretManager.conflicts
12006
+ ];
12007
+ }
12008
+ function collectUnmanagedResources(results) {
12009
+ return [
12010
+ ...results.functionRegistry.unmanaged,
12011
+ ...results.tailorDB.unmanaged,
12012
+ ...results.staticWebsite.unmanaged,
12013
+ ...results.aiGateway.unmanaged,
12014
+ ...results.idp.unmanaged,
12015
+ ...results.auth.unmanaged,
12016
+ ...results.pipeline.unmanaged,
12017
+ ...results.executor.unmanaged,
12018
+ ...results.workflow.unmanaged,
12019
+ ...results.secretManager.unmanaged
11561
12020
  ];
11562
- if (allUnmanaged.length > 0) {
11563
- allLines.push(styles.bold("Unmanaged resources (not in config):"));
11564
- for (const { resourceType, resourceName } of allUnmanaged) allLines.push(` ${styles.warning("⚠")} ${styles.bold(resourceType)} "${resourceName}"`);
12021
+ }
12022
+ function collectResourceOwners(results) {
12023
+ return /* @__PURE__ */ new Set([
12024
+ ...results.functionRegistry.resourceOwners,
12025
+ ...results.tailorDB.resourceOwners,
12026
+ ...results.staticWebsite.resourceOwners,
12027
+ ...results.aiGateway.resourceOwners,
12028
+ ...results.idp.resourceOwners,
12029
+ ...results.auth.resourceOwners,
12030
+ ...results.pipeline.resourceOwners,
12031
+ ...results.executor.resourceOwners,
12032
+ ...results.workflow.resourceOwners,
12033
+ ...results.secretManager.resourceOwners
12034
+ ]);
12035
+ }
12036
+ function collectImportantResourceDeletions(results) {
12037
+ const importantDeletions = [];
12038
+ for (const del of results.tailorDB.changeSet.type.deletes) importantDeletions.push({
12039
+ resourceType: "TailorDB type",
12040
+ resourceName: del.name
12041
+ });
12042
+ for (const del of results.staticWebsite.changeSet.deletes) importantDeletions.push({
12043
+ resourceType: "StaticWebsite",
12044
+ resourceName: del.name
12045
+ });
12046
+ for (const del of results.aiGateway.changeSet.deletes) importantDeletions.push({
12047
+ resourceType: "AIGateway",
12048
+ resourceName: del.name
12049
+ });
12050
+ for (const del of results.auth.changeSet.oauth2Client.deletes) importantDeletions.push({
12051
+ resourceType: "OAuth2 client",
12052
+ resourceName: del.name
12053
+ });
12054
+ for (const replace of results.auth.changeSet.oauth2Client.replaces) importantDeletions.push({
12055
+ resourceType: "OAuth2 client (client type change)",
12056
+ resourceName: replace.name
12057
+ });
12058
+ for (const del of results.auth.changeSet.connection.deletes) importantDeletions.push({
12059
+ resourceType: "Auth connection",
12060
+ resourceName: del.name
12061
+ });
12062
+ for (const del of results.secretManager.vaultChangeSet.deletes) importantDeletions.push({
12063
+ resourceType: "Secret Manager vault",
12064
+ resourceName: del.name
12065
+ });
12066
+ for (const del of results.secretManager.secretChangeSet.deletes) importantDeletions.push({
12067
+ resourceType: "Secret Manager secret",
12068
+ resourceName: del.name
12069
+ });
12070
+ return importantDeletions;
12071
+ }
12072
+ function readResourceField(item, field) {
12073
+ const itemRecord = item;
12074
+ for (const requestField of [
12075
+ "request",
12076
+ "deleteRequest",
12077
+ "createRequest"
12078
+ ]) {
12079
+ const request = itemRecord[requestField];
12080
+ if (request && typeof request === "object" && field in request) {
12081
+ const value = request[field];
12082
+ return value == null ? void 0 : String(value);
12083
+ }
11565
12084
  }
11566
- if (results.secretManager.skippedSecrets.length > 0) {
11567
- allLines.push(styles.bold("Secret Manager secrets (skipped - no value provided):"));
11568
- for (const name of results.secretManager.skippedSecrets) allLines.push(` ${styles.dim("○")} ${name}`);
12085
+ const value = itemRecord[field];
12086
+ return value == null ? void 0 : String(value);
12087
+ }
12088
+ function managedResourceName(group, item) {
12089
+ if (group.getName) return group.getName(item);
12090
+ return item.name;
12091
+ }
12092
+ function managedResourceKey(group, item) {
12093
+ const namespace = group.namespaceFields?.map((field) => readResourceField(item, field)).find((value) => value !== void 0);
12094
+ const name = managedResourceName(group, item);
12095
+ return namespace !== void 0 ? `${group.resourceType}:${namespace}:${name}` : `${group.resourceType}:${name}`;
12096
+ }
12097
+ function managedNamespaceOwnerKey(group, item) {
12098
+ if (!group.namespaceOwnerResourceType) return;
12099
+ const namespace = group.namespaceFields?.map((field) => readResourceField(item, field)).find((value) => value !== void 0);
12100
+ return namespace !== void 0 ? `${group.namespaceOwnerResourceType}:${namespace}` : void 0;
12101
+ }
12102
+ function addManagedResourceClaims(claims, group, item) {
12103
+ claims.add(managedResourceKey(group, item));
12104
+ const namespaceOwnerKey = managedNamespaceOwnerKey(group, item);
12105
+ if (namespaceOwnerKey) claims.add(namespaceOwnerKey);
12106
+ }
12107
+ function isManagedResourceClaimed(claims, group, item) {
12108
+ if (claims.has(managedResourceKey(group, item))) return true;
12109
+ const namespaceOwnerKey = managedNamespaceOwnerKey(group, item);
12110
+ return namespaceOwnerKey ? claims.has(namespaceOwnerKey) : false;
12111
+ }
12112
+ function retainDeletesNotClaimed(group, otherClaims) {
12113
+ let writeIndex = 0;
12114
+ for (const item of group.changeSet.deletes) {
12115
+ if (isManagedResourceClaimed(otherClaims, group, item)) {
12116
+ logger.debug(`Skipping delete of ${managedResourceKey(group, item)}: still managed by another config in this deploy.`);
12117
+ continue;
12118
+ }
12119
+ group.changeSet.deletes[writeIndex] = item;
12120
+ writeIndex += 1;
11569
12121
  }
11570
- if (allConflicts.length > 0) {
11571
- allLines.push(styles.bold("Owner conflicts (will require confirmation on apply):"));
11572
- for (const { resourceType, resourceName, currentOwner } of allConflicts) allLines.push(` ${styles.warning("!")} ${styles.bold(resourceType)} "${resourceName}" — owned by "${currentOwner}"`);
12122
+ group.changeSet.deletes.length = writeIndex;
12123
+ }
12124
+ function workflowJobFunctionItems(items) {
12125
+ const jobNames = /* @__PURE__ */ new Set();
12126
+ for (const item of items) {
12127
+ const usedJobNames = item.usedJobNames;
12128
+ if (!Array.isArray(usedJobNames)) continue;
12129
+ for (const jobName of usedJobNames) if (typeof jobName === "string") jobNames.add(jobName);
11573
12130
  }
11574
- allLines.push(formatPlanSummary(summary));
11575
- const output = allLines.join("\n");
11576
- if (opts?.dryRun) logger.out(output);
11577
- else logger.log(output);
11578
- return summary;
12131
+ return [...jobNames].map((name) => ({ name }));
11579
12132
  }
11580
- /**
11581
- * Summarize plan counts from display entries, service actions, and non-grouped changesets.
11582
- * @param results - Planned apply results
11583
- * @param displayEntries - All grouped display entries across sections
11584
- * @param serviceActions - All service-level namespace actions
11585
- * @returns Aggregated plan summary
11586
- */
11587
- function summarizePlanResults(results, displayEntries, serviceActions) {
11588
- const summary = {
12133
+ function workflowJobFunctionResourceGroup(results) {
12134
+ return {
12135
+ changeSet: {
12136
+ creates: workflowJobFunctionItems(results.workflow.changeSet.creates),
12137
+ updates: workflowJobFunctionItems(results.workflow.changeSet.updates),
12138
+ replaces: workflowJobFunctionItems(results.workflow.changeSet.replaces),
12139
+ unchanged: [...results.workflow.unchangedWorkflowJobNames].map((name) => ({ name })),
12140
+ deletes: results.workflow.jobFunctionDeletes
12141
+ },
12142
+ resourceType: "workflow_job_function",
12143
+ getName: (item) => "jobFunctionName" in item ? item.jobFunctionName : item.name
12144
+ };
12145
+ }
12146
+ const MANAGED_RESOURCE_NAMESPACE_FIELDS = [
12147
+ "namespaceName",
12148
+ "authNamespace",
12149
+ "vaultName",
12150
+ "staticWebsiteName"
12151
+ ];
12152
+ const DEPLOY_MANAGED_RESOURCE_DEFINITIONS = [
12153
+ {
12154
+ resourceLabel: "Application",
12155
+ resourceType: "application",
12156
+ namesOf: (target) => [target.application.name],
12157
+ changeSetOf: (results) => results.app
12158
+ },
12159
+ {
12160
+ resourceLabel: "Executor",
12161
+ resourceType: "executor",
12162
+ namesOf: (target) => Object.values(target.application.executorService?.executors ?? {}).map((executor) => executor.name),
12163
+ changeSetOf: (results) => results.executor.changeSet
12164
+ },
12165
+ {
12166
+ resourceLabel: "Workflow job",
12167
+ resourceType: "workflow_job_function",
12168
+ namesOf: (target) => target.bundledScripts.workflowJobs.keys(),
12169
+ changeSetOf: (results) => workflowJobFunctionResourceGroup(results).changeSet,
12170
+ getName: (item) => "jobFunctionName" in item ? item.jobFunctionName : item.name
12171
+ },
12172
+ {
12173
+ resourceLabel: "Workflow",
12174
+ resourceType: "workflow",
12175
+ namesOf: (target) => Object.values(target.application.workflowService?.workflows ?? {}).map((workflow) => workflow.name),
12176
+ changeSetOf: (results) => results.workflow.changeSet
12177
+ },
12178
+ {
12179
+ resourceLabel: "Auth connection",
12180
+ resourceType: "auth.connection",
12181
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12182
+ namespaceOwnerResourceType: "auth.service",
12183
+ namesOf: (target) => Object.keys(target.application.authService?.connections ?? {}),
12184
+ changeSetOf: (results) => results.auth.changeSet.connection
12185
+ },
12186
+ {
12187
+ resourceLabel: "StaticWebsite",
12188
+ resourceType: "staticwebsite",
12189
+ namesOf: (target) => target.application.staticWebsiteServices.map((service) => service.name),
12190
+ changeSetOf: (results) => results.staticWebsite.changeSet
12191
+ },
12192
+ {
12193
+ resourceLabel: "TailorDB namespace",
12194
+ resourceType: "tailordb.service",
12195
+ namesOf: (target) => target.application.tailorDBServices.map((service) => service.namespace),
12196
+ changeSetOf: (results) => results.tailorDB.changeSet.service
12197
+ },
12198
+ {
12199
+ resourceLabel: "Auth namespace",
12200
+ resourceType: "auth.service",
12201
+ namesOf: (target) => {
12202
+ const name = target.application.authService?.config.name;
12203
+ return name === void 0 ? [] : [name];
12204
+ },
12205
+ changeSetOf: (results) => results.auth.changeSet.service
12206
+ },
12207
+ {
12208
+ resourceLabel: "IdP namespace",
12209
+ resourceType: "idp.service",
12210
+ namesOf: (target) => target.application.idpServices.map((idp) => idp.name),
12211
+ changeSetOf: (results) => results.idp.changeSet.service
12212
+ },
12213
+ {
12214
+ resourceLabel: "Resolver namespace",
12215
+ resourceType: "pipeline.service",
12216
+ namesOf: (target) => target.application.resolverServices.map((service) => service.namespace),
12217
+ changeSetOf: (results) => results.pipeline.changeSet.service
12218
+ },
12219
+ {
12220
+ resourceLabel: "AIGateway",
12221
+ resourceType: "aigateway",
12222
+ namesOf: (target) => target.application.aiGatewayServices.map((service) => service.name),
12223
+ changeSetOf: (results) => results.aiGateway.changeSet
12224
+ },
12225
+ {
12226
+ resourceLabel: "Secret Manager vault",
12227
+ resourceType: "secret.vault",
12228
+ namesOf: (target) => target.application.secrets.map((vault) => vault.vaultName),
12229
+ changeSetOf: (results) => results.secretManager.vaultChangeSet
12230
+ }
12231
+ ];
12232
+ function managedResourceGroupFromDefinition(definition, results) {
12233
+ const { changeSetOf, namesOf: _namesOf, resourceLabel: _resourceLabel, ...group } = definition;
12234
+ return {
12235
+ ...group,
12236
+ changeSet: changeSetOf(results)
12237
+ };
12238
+ }
12239
+ function managedResourceGroups(results) {
12240
+ return [
12241
+ {
12242
+ changeSet: results.functionRegistry.changeSet,
12243
+ resourceType: "function_registry"
12244
+ },
12245
+ ...DEPLOY_MANAGED_RESOURCE_DEFINITIONS.map((definition) => managedResourceGroupFromDefinition(definition, results)),
12246
+ {
12247
+ changeSet: results.tailorDB.changeSet.type,
12248
+ resourceType: "tailordb.type",
12249
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12250
+ namespaceOwnerResourceType: "tailordb.service"
12251
+ },
12252
+ {
12253
+ changeSet: results.tailorDB.changeSet.gqlPermission,
12254
+ resourceType: "tailordb.gql_permission",
12255
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12256
+ namespaceOwnerResourceType: "tailordb.service"
12257
+ },
12258
+ {
12259
+ changeSet: results.staticWebsite.customDomainChangeSet,
12260
+ resourceType: "staticwebsite.custom_domain",
12261
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12262
+ namespaceOwnerResourceType: "staticwebsite"
12263
+ },
12264
+ {
12265
+ changeSet: results.idp.changeSet.client,
12266
+ resourceType: "idp.client",
12267
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12268
+ namespaceOwnerResourceType: "idp.service"
12269
+ },
12270
+ {
12271
+ changeSet: results.auth.changeSet.idpConfig,
12272
+ resourceType: "auth.idp_config",
12273
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12274
+ namespaceOwnerResourceType: "auth.service"
12275
+ },
12276
+ {
12277
+ changeSet: results.auth.changeSet.userProfileConfig,
12278
+ resourceType: "auth.user_profile_config",
12279
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12280
+ namespaceOwnerResourceType: "auth.service"
12281
+ },
12282
+ {
12283
+ changeSet: results.auth.changeSet.tenantConfig,
12284
+ resourceType: "auth.tenant_config",
12285
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12286
+ namespaceOwnerResourceType: "auth.service"
12287
+ },
12288
+ {
12289
+ changeSet: results.auth.changeSet.machineUser,
12290
+ resourceType: "auth.machine_user",
12291
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12292
+ namespaceOwnerResourceType: "auth.service"
12293
+ },
12294
+ {
12295
+ changeSet: results.auth.changeSet.oauth2Client,
12296
+ resourceType: "auth.oauth2_client",
12297
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12298
+ namespaceOwnerResourceType: "auth.service"
12299
+ },
12300
+ {
12301
+ changeSet: results.auth.changeSet.authHook,
12302
+ resourceType: "auth.hook",
12303
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12304
+ namespaceOwnerResourceType: "auth.service"
12305
+ },
12306
+ {
12307
+ changeSet: results.auth.changeSet.scim,
12308
+ resourceType: "auth.scim",
12309
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12310
+ namespaceOwnerResourceType: "auth.service"
12311
+ },
12312
+ {
12313
+ changeSet: results.auth.changeSet.scimResource,
12314
+ resourceType: "auth.scim_resource",
12315
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12316
+ namespaceOwnerResourceType: "auth.service"
12317
+ },
12318
+ {
12319
+ changeSet: results.pipeline.changeSet.resolver,
12320
+ resourceType: "pipeline.resolver",
12321
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12322
+ namespaceOwnerResourceType: "pipeline.service"
12323
+ },
12324
+ {
12325
+ changeSet: results.secretManager.secretChangeSet,
12326
+ resourceType: "secret.secret",
12327
+ namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
12328
+ namespaceOwnerResourceType: "secret.vault"
12329
+ }
12330
+ ];
12331
+ }
12332
+ function dropCrossDeploymentManagedDeletes(deployments) {
12333
+ const groupsByDeployment = deployments.map((deployment) => managedResourceGroups(deploymentPlanResults(deployment)));
12334
+ const claimsByDeployment = groupsByDeployment.map((groups) => groups.reduce((claims, group) => {
12335
+ for (const item of [
12336
+ ...group.changeSet.creates,
12337
+ ...group.changeSet.updates,
12338
+ ...group.changeSet.replaces,
12339
+ ...group.changeSet.unchanged
12340
+ ]) addManagedResourceClaims(claims, group, item);
12341
+ return claims;
12342
+ }, /* @__PURE__ */ new Set()));
12343
+ groupsByDeployment.forEach((groups, deploymentIndex) => {
12344
+ const otherClaims = /* @__PURE__ */ new Set();
12345
+ claimsByDeployment.forEach((claims, claimIndex) => {
12346
+ if (claimIndex === deploymentIndex) return;
12347
+ for (const claim of claims) otherClaims.add(claim);
12348
+ });
12349
+ for (const group of groups) retainDeletesNotClaimed(group, otherClaims);
12350
+ });
12351
+ }
12352
+ function collectDeploymentResourceOwners(deployments) {
12353
+ const owners = /* @__PURE__ */ new Set();
12354
+ for (const deployment of deployments) for (const owner of collectResourceOwners(deploymentPlanResults(deployment))) owners.add(owner);
12355
+ return owners;
12356
+ }
12357
+ async function confirmDeploymentPlans(params) {
12358
+ const { deployments, yes } = params;
12359
+ const targetAppNames = new Set(deployments.map((deployment) => deployment.application.name));
12360
+ const resourceOwners = collectDeploymentResourceOwners(deployments);
12361
+ const scheduledRenamedAppDeletes = /* @__PURE__ */ new Set();
12362
+ const importantDeletions = [];
12363
+ for (const deployment of deployments) {
12364
+ const results = deploymentPlanResults(deployment);
12365
+ const conflicts = collectOwnerConflicts(results);
12366
+ await confirmOwnerConflict(conflicts, deployment.application.name, yes);
12367
+ await confirmUnmanagedResources(collectUnmanagedResources(results), deployment.application.name, yes);
12368
+ importantDeletions.push(...collectImportantResourceDeletions(results));
12369
+ const emptyApps = computeRenamedAppDeletions({
12370
+ conflicts,
12371
+ resourceOwners,
12372
+ protectedAppNames: targetAppNames
12373
+ });
12374
+ for (const emptyApp of emptyApps) {
12375
+ if (scheduledRenamedAppDeletes.has(emptyApp)) continue;
12376
+ scheduledRenamedAppDeletes.add(emptyApp);
12377
+ deployment.app.deletes.push({
12378
+ name: emptyApp,
12379
+ request: {
12380
+ workspaceId: deployment.tailorDB.context.workspaceId,
12381
+ applicationName: emptyApp
12382
+ }
12383
+ });
12384
+ }
12385
+ }
12386
+ await confirmImportantResourceDeletion(importantDeletions, yes);
12387
+ }
12388
+ function sumPlanSummaries(summaries) {
12389
+ return summaries.reduce((acc, summary) => ({
12390
+ create: acc.create + summary.create,
12391
+ update: acc.update + summary.update,
12392
+ delete: acc.delete + summary.delete,
12393
+ replace: acc.replace + summary.replace
12394
+ }), {
11589
12395
  create: 0,
11590
12396
  update: 0,
11591
12397
  delete: 0,
11592
12398
  replace: 0
11593
- };
11594
- for (const entry of displayEntries) summary[entry.action] += 1;
11595
- for (const sa of serviceActions) summary[sa.action] += 1;
11596
- const { otherChanges } = splitFunctionRegistryChanges(results.functionRegistry.changeSet);
11597
- const nonGrouped = summarizeChangeSets([
11598
- otherChanges,
11599
- results.staticWebsite.changeSet,
11600
- results.staticWebsite.customDomainChangeSet,
11601
- results.aiGateway.changeSet,
11602
- results.app,
11603
- results.secretManager.vaultChangeSet,
11604
- results.secretManager.secretChangeSet
11605
- ]);
11606
- summary.create += nonGrouped.create;
11607
- summary.update += nonGrouped.update;
11608
- summary.delete += nonGrouped.delete;
11609
- summary.replace += nonGrouped.replace;
11610
- return summary;
12399
+ });
12400
+ }
12401
+ function printDeploymentPlans(deployments, opts) {
12402
+ if (logger.jsonMode && opts?.dryRun) {
12403
+ const reports = deployments.map((deployment) => buildPlanReport(deploymentPlanResults(deployment)));
12404
+ const summary = sumPlanSummaries(reports.map((report) => report.summary));
12405
+ logger.out({
12406
+ summary,
12407
+ changes: reports.flatMap((report) => report.json.changes),
12408
+ warnings: reports.flatMap((report) => report.json.warnings),
12409
+ conflicts: reports.flatMap((report) => report.json.conflicts)
12410
+ });
12411
+ return summary;
12412
+ }
12413
+ return sumPlanSummaries(deployments.map((deployment) => printPlanResults(deploymentPlanResults(deployment), opts)));
12414
+ }
12415
+ async function validateDeploymentPlans(deployments) {
12416
+ for (const deployment of deployments) await validatePlan(deploymentPlanResults(deployment));
11611
12417
  }
11612
12418
  /**
11613
12419
  * Deploy the configured application to the Tailor platform.
@@ -11617,21 +12423,10 @@ function summarizePlanResults(results, displayEntries, serviceActions) {
11617
12423
  async function deploy(options) {
11618
12424
  return withSpan("deploy", async (rootSpan) => {
11619
12425
  rootSpan.setAttribute("deploy.dry_run", options?.dryRun ?? false);
11620
- const { config, application, workflowBuildResult, httpAdapterBuildResult, bundledScripts, buildOnly } = await withSpan("build", async () => {
12426
+ const configPaths = parseDeployConfigPaths(options?.configPath);
12427
+ const { targets, buildOnly } = await withSpan("build", async () => {
11621
12428
  const dryRun = options?.dryRun ?? false;
11622
12429
  const buildOnly = options?.buildOnly ?? parseBoolean(process.env.TAILOR_PLATFORM_SDK_BUILD_ONLY) === true;
11623
- const { config, plugins } = await withSpan("build.loadConfig", async () => {
11624
- const foundPath = loadConfigPath(options?.configPath);
11625
- if (foundPath) {
11626
- const resolvedPath = path.resolve(process.cwd(), foundPath);
11627
- if (fs$1.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
11628
- configPath: resolvedPath,
11629
- dryRun,
11630
- buildOnly
11631
- });
11632
- }
11633
- return loadConfig(options?.configPath);
11634
- });
11635
12430
  const noCache = options?.noCache ?? false;
11636
12431
  const packageJson = await readPackageJson();
11637
12432
  const cacheDir = path.resolve(getDistDir(), "cache");
@@ -11642,256 +12437,51 @@ async function deploy(options) {
11642
12437
  });
11643
12438
  logger.info("Bundle cache cleaned");
11644
12439
  }
11645
- const configDir = path.dirname(config.path);
11646
- const lockfilePath = findUpSync("pnpm-lock.yaml", { cwd: configDir }) ?? findUpSync("package-lock.json", { cwd: configDir }) ?? findUpSync("yarn.lock", { cwd: configDir }) ?? findUpSync("bun.lock", { cwd: configDir });
11647
- const cacheManager = createCacheManager({
11648
- enabled: !noCache,
11649
- cacheDir,
11650
- sdkVersion: packageJson.version ?? "unknown",
11651
- lockfileHash: lockfilePath ? hashFile(lockfilePath) : void 0
11652
- });
11653
- let pluginManager;
11654
- if (plugins.length > 0) pluginManager = new PluginManager(plugins);
11655
- await withSpan("build.generateUserTypes", () => generateUserTypes({
11656
- config,
11657
- configPath: config.path
11658
- }));
11659
- let application;
11660
- let workflowBuildResult;
11661
- let httpAdapterBuildResult;
11662
- let bundledScripts;
11663
- try {
11664
- const result = await withSpan("build.loadApplication", () => loadApplication({
11665
- config,
11666
- pluginManager,
11667
- bundleCache: cacheManager.bundleCache
11668
- }));
11669
- application = result.application;
11670
- workflowBuildResult = result.workflowBuildResult;
11671
- httpAdapterBuildResult = result.httpAdapterBuildResult;
11672
- bundledScripts = result.bundledScripts;
11673
- } finally {
11674
- cacheManager.finalize();
11675
- }
11676
12440
  return {
11677
- config,
11678
- plugins,
11679
- application,
11680
- workflowBuildResult,
11681
- httpAdapterBuildResult,
11682
- bundledScripts,
11683
- dryRun,
12441
+ targets: await buildDeploymentTargets({
12442
+ configPaths,
12443
+ dryRun,
12444
+ buildOnly,
12445
+ noCache,
12446
+ packageVersion: packageJson.version ?? "unknown",
12447
+ cacheDir
12448
+ }),
11684
12449
  buildOnly
11685
12450
  };
11686
12451
  });
11687
- if (buildOnly) return { bundledScripts };
12452
+ if (buildOnly) return { bundledScripts: mergeBundledScripts(targets) };
12453
+ assertUniqueGlobalResourceNames(targets);
11688
12454
  const client = await initOperatorClient(await loadAccessToken({ profile: options?.profile }));
11689
12455
  const workspaceId = await loadWorkspaceId({
11690
12456
  workspaceId: options?.workspaceId,
11691
12457
  profile: options?.profile
11692
12458
  });
11693
- rootSpan.setAttribute("app.name", application.name);
12459
+ rootSpan.setAttribute("app.name", targets.map((target) => target.application.name).join(","));
11694
12460
  rootSpan.setAttribute("workspace.id", workspaceId);
11695
- await withSpan("plan.validateTailorDBTypeNames", () => assertUniqueTailorDBTypeNamesWithExternal({
12461
+ const deployments = await planDeploymentTargets({
12462
+ targets,
12463
+ runInputs: collectDeployRunPlanInputs(targets),
11696
12464
  client,
11697
12465
  workspaceId,
11698
- tailorDBServices: application.tailorDBServices,
11699
- externalTailorDBNamespaces: application.externalTailorDBNamespaces
11700
- }));
11701
- const workflowService = application.workflowService;
11702
- const functionEntries = collectFunctionEntries(application, filterBundledWorkflowJobs(workflowService?.jobs ?? [], workflowBuildResult?.usedJobNames ?? []), bundledScripts);
12466
+ noSchemaCheck: options?.noSchemaCheck
12467
+ });
11703
12468
  const dryRun = options?.dryRun ?? false;
11704
12469
  const yes = options?.yes ?? false;
11705
- const forceApplyAll = await withSpan("plan.detectSdkVersionChange", () => shouldForceApplyAll(client, workspaceId, application, functionEntries));
11706
- const { functionRegistry, tailorDB, staticWebsite, aiGateway, idp, auth, pipeline, app, executor, workflow, secretManager } = await withSpan("plan", async () => {
11707
- const idpUserTriggerTargets = collectIdpUserTriggerTargets(application);
11708
- const ctx = {
11709
- client,
11710
- workspaceId,
11711
- application,
11712
- forRemoval: false,
11713
- config,
11714
- noSchemaCheck: options?.noSchemaCheck,
11715
- forceApplyAll,
11716
- idpUserTriggerTargets
11717
- };
11718
- const functionRegistry = await withSpan("plan.functionRegistry", () => planFunctionRegistry(client, workspaceId, application.name, application.id, functionEntries));
11719
- const unchangedWorkflowJobs = new Set(functionRegistry.changeSet.unchanged.filter((entry) => entry.name.startsWith(WORKFLOW_PREFIX)).map((entry) => entry.name.slice(WORKFLOW_PREFIX.length)));
11720
- const [tailorDB, staticWebsite, aiGateway, idp, auth, pipeline, app, executor, workflow, secretManager] = await Promise.all([
11721
- withSpan("plan.tailorDB", () => planTailorDB(ctx)),
11722
- withSpan("plan.staticWebsite", () => planStaticWebsite(ctx)),
11723
- withSpan("plan.aiGateway", () => planAIGateway(ctx)),
11724
- withSpan("plan.idp", () => planIdP(ctx)),
11725
- withSpan("plan.auth", () => planAuth(ctx)),
11726
- withSpan("plan.pipeline", () => planPipeline(ctx)),
11727
- withSpan("plan.application", () => planApplication(ctx, httpAdapterBuildResult)),
11728
- withSpan("plan.executor", () => planExecutor(ctx)),
11729
- withSpan("plan.workflow", () => planWorkflow(client, workspaceId, application.name, application.id, workflowService?.workflows ?? {}, workflowBuildResult?.mainJobDeps ?? {}, unchangedWorkflowJobs)),
11730
- withSpan("plan.secretManager", () => planSecretManager(ctx))
11731
- ]);
11732
- return {
11733
- functionRegistry,
11734
- tailorDB,
11735
- staticWebsite,
11736
- aiGateway,
11737
- idp,
11738
- auth,
11739
- pipeline,
11740
- app,
11741
- executor,
11742
- workflow,
11743
- secretManager
11744
- };
11745
- });
12470
+ dropCrossDeploymentManagedDeletes(deployments);
11746
12471
  await withSpan("confirm", async () => {
11747
- const allConflicts = [
11748
- ...functionRegistry.conflicts,
11749
- ...tailorDB.conflicts,
11750
- ...staticWebsite.conflicts,
11751
- ...aiGateway.conflicts,
11752
- ...idp.conflicts,
11753
- ...auth.conflicts,
11754
- ...pipeline.conflicts,
11755
- ...executor.conflicts,
11756
- ...workflow.conflicts,
11757
- ...secretManager.conflicts
11758
- ];
11759
- await confirmOwnerConflict(allConflicts, application.name, yes);
11760
- await confirmUnmanagedResources([
11761
- ...functionRegistry.unmanaged,
11762
- ...tailorDB.unmanaged,
11763
- ...staticWebsite.unmanaged,
11764
- ...aiGateway.unmanaged,
11765
- ...idp.unmanaged,
11766
- ...auth.unmanaged,
11767
- ...pipeline.unmanaged,
11768
- ...executor.unmanaged,
11769
- ...workflow.unmanaged,
11770
- ...secretManager.unmanaged
11771
- ], application.name, yes);
11772
- const importantDeletions = [];
11773
- for (const del of tailorDB.changeSet.type.deletes) importantDeletions.push({
11774
- resourceType: "TailorDB type",
11775
- resourceName: del.name
11776
- });
11777
- for (const del of staticWebsite.changeSet.deletes) importantDeletions.push({
11778
- resourceType: "StaticWebsite",
11779
- resourceName: del.name
11780
- });
11781
- for (const del of aiGateway.changeSet.deletes) importantDeletions.push({
11782
- resourceType: "AIGateway",
11783
- resourceName: del.name
11784
- });
11785
- for (const del of auth.changeSet.oauth2Client.deletes) importantDeletions.push({
11786
- resourceType: "OAuth2 client",
11787
- resourceName: del.name
11788
- });
11789
- for (const replace of auth.changeSet.oauth2Client.replaces) importantDeletions.push({
11790
- resourceType: "OAuth2 client (client type change)",
11791
- resourceName: replace.name
11792
- });
11793
- for (const del of auth.changeSet.connection.deletes) importantDeletions.push({
11794
- resourceType: "Auth connection",
11795
- resourceName: del.name
11796
- });
11797
- for (const del of secretManager.vaultChangeSet.deletes) importantDeletions.push({
11798
- resourceType: "Secret Manager vault",
11799
- resourceName: del.name
11800
- });
11801
- for (const del of secretManager.secretChangeSet.deletes) importantDeletions.push({
11802
- resourceType: "Secret Manager secret",
11803
- resourceName: del.name
11804
- });
11805
- await confirmImportantResourceDeletion(importantDeletions, yes);
11806
- const emptyApps = computeRenamedAppDeletions({
11807
- conflicts: allConflicts,
11808
- resourceOwners: /* @__PURE__ */ new Set([
11809
- ...functionRegistry.resourceOwners,
11810
- ...tailorDB.resourceOwners,
11811
- ...staticWebsite.resourceOwners,
11812
- ...aiGateway.resourceOwners,
11813
- ...idp.resourceOwners,
11814
- ...auth.resourceOwners,
11815
- ...pipeline.resourceOwners,
11816
- ...executor.resourceOwners,
11817
- ...workflow.resourceOwners,
11818
- ...secretManager.resourceOwners
11819
- ]),
11820
- targetAppName: application.name
11821
- });
11822
- for (const emptyApp of emptyApps) app.deletes.push({
11823
- name: emptyApp,
11824
- request: {
11825
- workspaceId,
11826
- applicationName: emptyApp
11827
- }
12472
+ await confirmDeploymentPlans({
12473
+ deployments,
12474
+ yes
11828
12475
  });
11829
12476
  });
11830
- const planSummary = printPlanResults({
11831
- functionRegistry,
11832
- tailorDB,
11833
- staticWebsite,
11834
- aiGateway,
11835
- idp,
11836
- auth,
11837
- pipeline,
11838
- app,
11839
- executor,
11840
- workflow,
11841
- secretManager
11842
- }, { dryRun: options?.dryRun });
12477
+ const planSummary = printDeploymentPlans(deployments, { dryRun: options?.dryRun });
11843
12478
  if (options?.noValidate) logger.warn("Client-side validation skipped (--no-validate).");
11844
- else await validatePlan({
11845
- functionRegistry,
11846
- tailorDB,
11847
- staticWebsite,
11848
- idp,
11849
- auth,
11850
- pipeline,
11851
- app,
11852
- executor,
11853
- workflow,
11854
- secretManager
11855
- });
12479
+ else await validateDeploymentPlans(deployments);
11856
12480
  if (dryRun) {
11857
12481
  logger.info("Dry run enabled. No changes applied.");
11858
12482
  return;
11859
12483
  }
11860
- await withSpan("apply.createUpdateServices", async () => {
11861
- await applySecretManager(client, secretManager, "create-update", application);
11862
- await applyFunctionRegistry(client, workspaceId, functionRegistry, "create-update");
11863
- await applyStaticWebsite(client, staticWebsite, "create-update");
11864
- await applyAIGateway(client, aiGateway, "create-update");
11865
- await applyIdP(client, idp, "create-update");
11866
- await applyAuth(client, auth, "create-update");
11867
- await applyTailorDB(client, tailorDB, "create-update");
11868
- await applyPipeline(client, pipeline, "create-update");
11869
- });
11870
- await withSpan("apply.deleteSubgraphResources", async () => {
11871
- await applyPipeline(client, pipeline, "delete-resources");
11872
- await applyAuth(client, auth, "delete-resources");
11873
- await applyIdP(client, idp, "delete-resources");
11874
- });
11875
- await withSpan("apply.createUpdateApplication", () => applyApplication(client, app, "create-update"));
11876
- await withSpan("apply.createUpdateDependentServices", async () => {
11877
- await applyExecutor(client, executor, "create-update");
11878
- await applyWorkflow(client, workflow, "create-update");
11879
- });
11880
- await withSpan("apply.deleteDependentServices", async () => {
11881
- await applyWorkflow(client, workflow, "delete");
11882
- await applyExecutor(client, executor, "delete");
11883
- await applyStaticWebsite(client, staticWebsite, "delete");
11884
- await applyAIGateway(client, aiGateway, "delete");
11885
- await applySecretManager(client, secretManager, "delete");
11886
- });
11887
- await withSpan("apply.deleteApplication", () => applyApplication(client, app, "delete"));
11888
- await withSpan("apply.deleteSubgraphServices", async () => {
11889
- await applyPipeline(client, pipeline, "delete-services");
11890
- await applyAuth(client, auth, "delete-services");
11891
- await applyIdP(client, idp, "delete-services");
11892
- await applyTailorDB(client, tailorDB, "delete-services");
11893
- });
11894
- await withSpan("apply.cleanup", () => applyFunctionRegistry(client, workspaceId, functionRegistry, "delete"));
12484
+ await applyDeploymentPlans(client, workspaceId, deployments);
11895
12485
  if (logger.jsonMode) logger.out({
11896
12486
  summary: planSummary,
11897
12487
  status: "applied"
@@ -16031,10 +16621,12 @@ function createWorkspaceFolderNameResolver(client) {
16031
16621
  };
16032
16622
  }
16033
16623
  async function workspaceInfoWithFolderName(client, workspace) {
16034
- return workspaceInfo(workspace, await resolveWorkspaceFolderName(client, workspace));
16624
+ const folderName = await resolveWorkspaceFolderName(client, workspace);
16625
+ return workspaceInfo(workspace, folderName);
16035
16626
  }
16036
16627
  async function workspaceDetailsWithFolderName(client, workspace) {
16037
- return workspaceDetails(workspace, await resolveWorkspaceFolderName(client, workspace));
16628
+ const folderName = await resolveWorkspaceFolderName(client, workspace);
16629
+ return workspaceDetails(workspace, folderName);
16038
16630
  }
16039
16631
  async function workspaceInfosWithFolderNames(client, workspaces) {
16040
16632
  const resolveFolderName = createWorkspaceFolderNameResolver(client);
@@ -16705,7 +17297,7 @@ async function generate(options) {
16705
17297
  if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
16706
17298
  let pluginManager;
16707
17299
  if (plugins.length > 0) pluginManager = new PluginManager(plugins);
16708
- const { defineApplication } = await import("./application-BAo_zFA4.mjs");
17300
+ const { defineApplication } = await import("./application-CSUhZMb5.mjs");
16709
17301
  const application = defineApplication({
16710
17302
  config,
16711
17303
  pluginManager
@@ -18635,7 +19227,7 @@ async function runRepl(options) {
18635
19227
  const execute = await prepareQueryExecutor(options);
18636
19228
  const historyPath = getReplHistoryPath(options.engine, options.profile, options.workspaceId);
18637
19229
  const validate = createReplValidator(options.engine);
18638
- const { highlightSqlLine, highlightGraphqlLine, replTransform } = await import("./repl-editor-BmQOyZJd.mjs");
19230
+ const { highlightSqlLine, highlightGraphqlLine, replTransform } = await import("./repl-editor-BCozyiNq.mjs");
18639
19231
  const highlight = options.engine === "sql" ? highlightSqlLine : highlightGraphqlLine;
18640
19232
  const prompt = createPrompt({
18641
19233
  prefix: "",
@@ -18969,5 +19561,5 @@ function isDeno() {
18969
19561
  }
18970
19562
 
18971
19563
  //#endregion
18972
- export { getOrganization as $, generateAllTypeManifestsFromSnapshot as $t, listCommand$3 as A, apiCommand as An, getExecutorWaitFailureMessage as At, show as B, paginationArgs as Bn, listWorkflowExecutions as Bt, listCommand$2 as C, getNamespacesWithMigrations 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, configArg as Fn, startWorkflow as Ft, removeCommand$1 as G, deploy as Gt, extractOwnedNamespaces as H, workspaceArgs as Hn, formatKeyValueTable as Ht, generateMigrationScript as I, confirmationArgs as In, getCommand$5 as It, organizationTree as J, bundleMigrationScript as Jt, updateCommand$1 as K, executeScript as Kt, writeDbTypesFile as L, deploymentArgs as Ln, 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, commonArgs as Pn, startCommand as Pt, getCommand$1 as Q, compareSnapshotWithRemote as Qt, getConfiguredEditorCommand as R, isVerbose as Rn, executionsCommand as Rt, listApps as S, hasChanges as Sn, getFunctionRegistry as St, healthCommand as T, resourceTrn as Tn, triggerCommand as Tt, logBetaWarning as U, getCommand$6 as Ut, showCommand as V, toPageDirection as Vn, functionExecutionStatusToString as Vt, remove as W, getExecutor as Wt, listCommand$4 as X, handleOptionalToRequiredError as Xt, treeCommand as Y, MIGRATION_LABEL_KEY as Yt, listOrganizations as Z, parseMigrationLabelNumber as Zt, getWorkspace as _, reconstructSnapshotFromMigrations as _n, listMachineUsers as _t, updateUser as a, SCHEMA_FILE_NAME as an, getFolder as at, createCommand as b, formatDiffSummary as bn, listFunctionRegistries as bt, listCommand as c, compareSnapshots as cn, createCommand$1 as ct, inviteUser as d, getMigrationDirPath as dn, listOAuth2Clients as dt, protoGqlPermission as en, updateCommand$2 as et, restoreCommand as f, getMigrationFilePath as fn, getCommand$3 as ft, getCommand as g, loadDiff as gn, listCommand$7 as gt, listWorkspaces as h, isValidMigrationNumber as hn, tokenCommand as ht, updateCommand as i, MIGRATE_FILE_NAME 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, createSnapshotFromLocalTypes as ln, createFolder as lt, listCommand$1 as m, getNextMigrationNumber as mn, getMachineUserToken as mt, query as n, DIFF_FILE_NAME as nn, listCommand$5 as nt, removeCommand as o, assertValidMigrationFiles as on, deleteCommand$1 as ot, restoreWorkspace as p, getMigrationFiles as pn, getOAuth2Client as pt, updateOrganization as q, waitForExecution as qt, queryCommand as r, INITIAL_SCHEMA_NUMBER as rn, listFolders as rt, removeUser as s, compareLocalTypesWithSnapshot as sn, deleteFolder as st, isNativeTypeScriptRuntime as t, DB_TYPES_FILE_NAME as tn, updateFolder as tt, inviteCommand as u, getLatestMigrationNumber as un, listCommand$6 as ut, deleteCommand as v, formatMigrationNumber as vn, generate$1 as vt, getAppHealth as w, ensureConfigId as wn, webhookCommand as wt, createWorkspace as x, formatMigrationDiff as xn, getCommand$4 as xt, deleteWorkspace as y, parseMigrationNumberArg as yn, listCommand$8 as yt, openInConfiguredEditor as z, pagedLogArgs as zn, getWorkflowExecution as zt };
18973
- //# sourceMappingURL=runtime-SxHHEzVb.mjs.map
19564
+ export { getOrganization as $, compareSnapshotWithRemote as $t, listCommand$3 as A, apiCommand as An, getExecutorWaitFailureMessage as At, show as B, pagedLogArgs as Bn, 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, configArg as Fn, startWorkflow as Ft, removeCommand$1 as G, deploy as Gt, extractOwnedNamespaces as H, toPageDirection as Hn, formatKeyValueTable as Ht, generateMigrationScript as I, confirmationArgs as In, getCommand$5 as It, organizationTree as J, waitForExecution as Jt, updateCommand$1 as K, ensureConfigId as Kt, writeDbTypesFile as L, deploymentArgs as Ln, 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, commonArgs as Pn, startCommand as Pt, getCommand$1 as Q, parseMigrationLabelNumber as Qt, getConfiguredEditorCommand as R, isVerbose as Rn, executionsCommand as Rt, listApps as S, formatMigrationDiff as Sn, getFunctionRegistry as St, healthCommand as T, resourceTrn as Tn, triggerCommand as Tt, logBetaWarning as U, workspaceArgs as Un, getCommand$6 as Ut, showCommand as V, paginationArgs as Vn, functionExecutionStatusToString as Vt, remove as W, getExecutor as Wt, listCommand$4 as X, MIGRATION_LABEL_KEY as Xt, treeCommand as Y, bundleMigrationScript as Yt, listOrganizations as Z, handleOptionalToRequiredError as Zt, getWorkspace as _, loadDiff as _n, listMachineUsers as _t, updateUser as a, MIGRATE_FILE_NAME as an, getFolder as at, createCommand as b, parseMigrationNumberArg as bn, listFunctionRegistries as bt, listCommand as c, compareLocalTypesWithSnapshot as cn, createCommand$1 as ct, inviteUser as d, getLatestMigrationNumber as dn, listOAuth2Clients as dt, generateAllTypeManifestsFromSnapshot as en, updateCommand$2 as et, restoreCommand as f, getMigrationDirPath as fn, getCommand$3 as ft, getCommand as g, isValidMigrationNumber as gn, listCommand$7 as gt, listWorkspaces as h, getNextMigrationNumber as hn, tokenCommand as ht, updateCommand as i, INITIAL_SCHEMA_NUMBER as in, getCommand$2 as it, listWorkflows as j, apiCall as jn, jobsCommand as jt, resumeWorkflow as k, prompt as kn, getExecutorJob as kt, listUsers as l, compareSnapshots as ln, createFolder as lt, listCommand$1 as m, getMigrationFiles as mn, getMachineUserToken as mt, query as n, DB_TYPES_FILE_NAME as nn, listCommand$5 as nt, removeCommand as o, SCHEMA_FILE_NAME as on, deleteCommand$1 as ot, restoreWorkspace as p, getMigrationFilePath as pn, getOAuth2Client as pt, updateOrganization as q, executeScript as qt, queryCommand as r, DIFF_FILE_NAME as rn, listFolders as rt, removeUser as s, assertValidMigrationFiles as sn, deleteFolder as st, isNativeTypeScriptRuntime as t, protoGqlPermission as tn, updateFolder as tt, inviteCommand as u, createSnapshotFromLocalTypes as un, listCommand$6 as ut, deleteCommand as v, reconstructSnapshotFromMigrations as vn, generate$1 as vt, getAppHealth as w, getNamespacesWithMigrations as wn, webhookCommand as wt, createWorkspace as x, formatDiffSummary as xn, getCommand$4 as xt, deleteWorkspace as y, formatMigrationNumber as yn, listCommand$8 as yt, openInConfiguredEditor as z, multiConfigArg as zn, getWorkflowExecution as zt };
19565
+ //# sourceMappingURL=runtime-DV1EnfMs.mjs.map