@tailor-platform/sdk 1.32.1 → 1.33.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 (92) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/dist/{application-p2GujXmg.mjs → application-CYPU-WIc.mjs} +58 -43
  3. package/dist/application-CYPU-WIc.mjs.map +1 -0
  4. package/dist/application-dnB8CQiT.mjs +4 -0
  5. package/dist/cli/index.d.mts +2 -2
  6. package/dist/cli/index.mjs +28 -35
  7. package/dist/cli/index.mjs.map +1 -1
  8. package/dist/cli/lib.d.mts +104 -12
  9. package/dist/cli/lib.mjs +7 -13
  10. package/dist/cli/lib.mjs.map +1 -1
  11. package/dist/cli/skills.mjs +0 -1
  12. package/dist/cli/skills.mjs.map +1 -1
  13. package/dist/{client-D5P1myz0.mjs → client-CYGsf3Zl.mjs} +91 -27
  14. package/dist/client-CYGsf3Zl.mjs.map +1 -0
  15. package/dist/{client-gpRgZl1b.mjs → client-ea1w8SmG.mjs} +1 -4
  16. package/dist/configure/index.d.mts +5 -5
  17. package/dist/configure/index.mjs +81 -12
  18. package/dist/configure/index.mjs.map +1 -1
  19. package/dist/crash-report-CbueUPaP.mjs +4 -0
  20. package/dist/{crash-report-DnwITWeV.mjs → crash-report-OXafT1iS.mjs} +3 -3
  21. package/dist/{crash-report-DnwITWeV.mjs.map → crash-report-OXafT1iS.mjs.map} +1 -1
  22. package/dist/{enum-constants-CkKARYb7.mjs → enum-constants-DI85-fPE.mjs} +2 -1
  23. package/dist/{enum-constants-CkKARYb7.mjs.map → enum-constants-DI85-fPE.mjs.map} +1 -1
  24. package/dist/{env-BZLTIlIo.d.mts → env-BvIWsJxg.d.mts} +2 -2
  25. package/dist/{file-utils-D2TxR_kj.mjs → file-utils-C4rXlOVt.mjs} +2 -1
  26. package/dist/{file-utils-D2TxR_kj.mjs.map → file-utils-C4rXlOVt.mjs.map} +1 -1
  27. package/dist/{index-CgMytw2A.d.mts → index-0H-YH8Ya.d.mts} +3 -2
  28. package/dist/{index-vVGamLOw.d.mts → index-BM2SqNfO.d.mts} +3 -2
  29. package/dist/{index-CoReoodF.d.mts → index-BU6fmwJC.d.mts} +3 -2
  30. package/dist/{index-BYk_9R3S.d.mts → index-DlivLpTN.d.mts} +145 -32
  31. package/dist/{index-BQKAzTPA.d.mts → index-mAV9kYJA.d.mts} +3 -2
  32. package/dist/{interceptor-1LL1gBsF.mjs → interceptor-f7slMkCC.mjs} +1 -2
  33. package/dist/{interceptor-1LL1gBsF.mjs.map → interceptor-f7slMkCC.mjs.map} +1 -1
  34. package/dist/kysely/index.d.mts +5 -1
  35. package/dist/kysely/index.mjs +0 -1
  36. package/dist/kysely/index.mjs.map +1 -1
  37. package/dist/{kysely-type-BK0b4Rqt.mjs → kysely-type-BwMqaL3z.mjs} +18 -7
  38. package/dist/kysely-type-BwMqaL3z.mjs.map +1 -0
  39. package/dist/{package-json-VqyFvGiP.mjs → package-json-CfUqjJaQ.mjs} +1 -1
  40. package/dist/{package-json-VqyFvGiP.mjs.map → package-json-CfUqjJaQ.mjs.map} +1 -1
  41. package/dist/package-json-D5Km1jjt.mjs +4 -0
  42. package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
  43. package/dist/plugin/builtin/enum-constants/index.mjs +1 -2
  44. package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
  45. package/dist/plugin/builtin/file-utils/index.mjs +1 -2
  46. package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
  47. package/dist/plugin/builtin/kysely-type/index.mjs +1 -2
  48. package/dist/plugin/builtin/seed/index.d.mts +1 -1
  49. package/dist/plugin/builtin/seed/index.mjs +1 -2
  50. package/dist/plugin/index.d.mts +2 -2
  51. package/dist/plugin/index.mjs +0 -1
  52. package/dist/plugin/index.mjs.map +1 -1
  53. package/dist/{plugin-IIDZW9GG.d.mts → plugin-DQqzlulP.d.mts} +46 -33
  54. package/dist/{runtime-7DOyTTxR.mjs → runtime-CDvdBV66.mjs} +1032 -306
  55. package/dist/runtime-CDvdBV66.mjs.map +1 -0
  56. package/dist/{schema-BITbkmq3.mjs → schema-D27cW0Ca.mjs} +2 -1
  57. package/dist/schema-D27cW0Ca.mjs.map +1 -0
  58. package/dist/seed/index.mjs +0 -1
  59. package/dist/seed/index.mjs.map +1 -1
  60. package/dist/{seed-BXrSEJbv.mjs → seed-BZIFDG27.mjs} +11 -3
  61. package/dist/seed-BZIFDG27.mjs.map +1 -0
  62. package/dist/telemetry-C508zIi1.mjs +4 -0
  63. package/dist/{telemetry-B4sp-dD2.mjs → telemetry-CREcGK8y.mjs} +2 -2
  64. package/dist/{telemetry-B4sp-dD2.mjs.map → telemetry-CREcGK8y.mjs.map} +1 -1
  65. package/dist/utils/test/index.d.mts +2 -2
  66. package/dist/utils/test/index.mjs +0 -2
  67. package/dist/utils/test/index.mjs.map +1 -1
  68. package/dist/{workflow.generated-C2A5Ye0m.d.mts → workflow.generated-u9MgzqbM.d.mts} +14 -3
  69. package/docs/cli/application.md +17 -0
  70. package/docs/cli/auth.md +1 -1
  71. package/docs/cli/crash-report.md +1 -1
  72. package/docs/cli/executor.md +2 -2
  73. package/docs/cli/secret.md +3 -3
  74. package/docs/cli/staticwebsite.md +1 -1
  75. package/docs/cli/tailordb.md +4 -2
  76. package/docs/cli/user.md +2 -2
  77. package/docs/cli-reference.md +116 -100
  78. package/docs/generator/builtin.md +1 -1
  79. package/docs/services/executor.md +54 -0
  80. package/docs/services/idp.md +22 -0
  81. package/package.json +20 -20
  82. package/dist/application-Cwt_ifTT.mjs +0 -12
  83. package/dist/application-p2GujXmg.mjs.map +0 -1
  84. package/dist/chunk-COzJYswC.mjs +0 -9
  85. package/dist/client-D5P1myz0.mjs.map +0 -1
  86. package/dist/crash-report-Cm9vFmTN.mjs +0 -7
  87. package/dist/kysely-type-BK0b4Rqt.mjs.map +0 -1
  88. package/dist/package-json-88x7bPKC.mjs +0 -5
  89. package/dist/runtime-7DOyTTxR.mjs.map +0 -1
  90. package/dist/schema-BITbkmq3.mjs.map +0 -1
  91. package/dist/seed-BXrSEJbv.mjs.map +0 -1
  92. package/dist/telemetry-DXIGGa6A.mjs +0 -5
@@ -1,10 +1,10 @@
1
1
 
2
- import { t as db } from "./schema-BITbkmq3.mjs";
2
+ import { A as ExecutorTriggerType, C as TailorDBType_PermitAction, E as FunctionExecution_Status, F as AuthOAuth2Client_GrantType, G as Condition_Operator, H as UserProfileProviderConfig_UserProfileProviderType, I as AuthSCIMAttribute_Mutability, J as ApplicationSchemaUpdateAttemptStatus, K as FilterSchema, L as AuthSCIMAttribute_Type, M as AuthIDPConfig_AuthType, N as AuthInvokerSchema, O as ExecutorJobStatus, P as AuthOAuth2Client_ClientType, R as AuthSCIMAttribute_Uniqueness, S as TailorDBType_Permission_Permit, T as IdPLang, U as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, V as TenantProviderConfig_TenantProviderType, W as ConditionSchema, Y as Subgraph_ServiceType, _ as WorkflowJobExecution_Status, a as fetchMachineUserToken, b as TailorDBGQLPermission_Permit, f as platformBaseUrl, g as WorkflowExecution_Status, h as WorkspacePlatformUserRole, i as fetchAll, j as AuthHookPoint, k as ExecutorTargetType, m as userAgent, p as resolveStaticWebsiteUrls, q as PageDirection, u as initOperatorClient, v as TailorDBGQLPermission_Action, w as PipelineResolver_OperationType, x as TailorDBType_Permission_Operator, y as TailorDBGQLPermission_Operator, z as AuthSCIMConfig_AuthorizationType } from "./client-CYGsf3Zl.mjs";
3
+ import { t as db } from "./schema-D27cW0Ca.mjs";
3
4
  import { i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-qz-Y4sBV.mjs";
4
- import { A as ExecutorTriggerType, C as TailorDBType_PermitAction, E as FunctionExecution_Status, F as AuthOAuth2Client_GrantType, G as Condition_Operator, H as UserProfileProviderConfig_UserProfileProviderType, I as AuthSCIMAttribute_Mutability, J as ApplicationSchemaUpdateAttemptStatus, K as FilterSchema, L as AuthSCIMAttribute_Type, M as AuthIDPConfig_AuthType, N as AuthInvokerSchema, O as ExecutorJobStatus, P as AuthOAuth2Client_ClientType, R as AuthSCIMAttribute_Uniqueness, S as TailorDBType_Permission_Permit, T as IdPLang, U as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, V as TenantProviderConfig_TenantProviderType, W as ConditionSchema, Y as Subgraph_ServiceType, _ as WorkflowJobExecution_Status, a as fetchMachineUserToken, b as TailorDBGQLPermission_Permit, f as platformBaseUrl, g as WorkflowExecution_Status, h as WorkspacePlatformUserRole, i as fetchAll, j as AuthHookPoint, k as ExecutorTargetType, m as userAgent, p as resolveStaticWebsiteUrls, q as PageDirection, u as initOperatorClient, v as TailorDBGQLPermission_Action, w as PipelineResolver_OperationType, x as TailorDBType_Permission_Operator, y as TailorDBGQLPermission_Operator, z as AuthSCIMConfig_AuthorizationType } from "./client-D5P1myz0.mjs";
5
- import { t as readPackageJson } from "./package-json-VqyFvGiP.mjs";
6
- import { S as readPlatformConfig, T as writePlatformConfig, _ as hashFile, a as loadConfig, b as loadAccessToken, d as TailorDBTypeSchema, f as stringifyFunction, g as getDistDir, h as createBundleCache, l as OAuth2ClientSchema, m as loadFilesWithIgnores, n as generatePluginFilesIfNeeded, p as tailorUserMap, r as loadApplication, s as createExecutorService, t as defineApplication, x as loadWorkspaceId } from "./application-p2GujXmg.mjs";
7
- import { r as withSpan } from "./telemetry-B4sp-dD2.mjs";
5
+ import { t as readPackageJson } from "./package-json-CfUqjJaQ.mjs";
6
+ import { S as readPlatformConfig, T as writePlatformConfig, _ as hashFile, a as loadConfig, b as loadAccessToken, c as createExecutorService, d as TailorDBTypeSchema, f as stringifyFunction, g as getDistDir, h as createBundleCache, m as loadFilesWithIgnores, n as generatePluginFilesIfNeeded, p as tailorUserMap, r as loadApplication, t as defineApplication, u as OAuth2ClientSchema, x as loadWorkspaceId } from "./application-CYPU-WIc.mjs";
7
+ import { r as withSpan } from "./telemetry-CREcGK8y.mjs";
8
8
  import { arg, createDefineCommand, defineCommand, runCommand } from "politty";
9
9
  import { z } from "zod";
10
10
  import * as fs$1 from "node:fs";
@@ -891,6 +891,7 @@ function createChangeSet(title) {
891
891
  const updates = [];
892
892
  const deletes = [];
893
893
  const replaces = [];
894
+ const unchanged = [];
894
895
  const isEmpty = () => creates.length === 0 && updates.length === 0 && deletes.length === 0 && replaces.length === 0;
895
896
  return {
896
897
  title,
@@ -898,6 +899,7 @@ function createChangeSet(title) {
898
899
  updates,
899
900
  deletes,
900
901
  replaces,
902
+ unchanged,
901
903
  isEmpty,
902
904
  print: () => {
903
905
  if (isEmpty()) return;
@@ -909,6 +911,83 @@ function createChangeSet(title) {
909
911
  }
910
912
  };
911
913
  }
914
+ /**
915
+ * Summarize resource counts across multiple change sets.
916
+ * @param changeSets - Change sets to aggregate
917
+ * @returns Aggregated plan counts by action
918
+ */
919
+ function summarizeChangeSets(changeSets) {
920
+ const summary = {
921
+ create: 0,
922
+ update: 0,
923
+ delete: 0,
924
+ replace: 0,
925
+ unchanged: 0
926
+ };
927
+ for (const changeSet of changeSets) {
928
+ summary.create += changeSet.creates.length;
929
+ summary.update += changeSet.updates.length;
930
+ summary.delete += changeSet.deletes.length;
931
+ summary.replace += changeSet.replaces.length;
932
+ summary.unchanged += changeSet.unchanged.length;
933
+ }
934
+ return summary;
935
+ }
936
+ /**
937
+ * Format an aggregated plan summary for CLI output.
938
+ * @param summary - Aggregated plan counts
939
+ * @returns Human-readable plan summary line
940
+ */
941
+ function formatPlanSummary(summary) {
942
+ const parts = [
943
+ `${summary.create} to create`,
944
+ `${summary.update} to update`,
945
+ `${summary.delete} to delete`
946
+ ];
947
+ if (summary.replace > 0) parts.push(`${summary.replace} to replace`);
948
+ parts.push(`${summary.unchanged} unchanged`);
949
+ return `Plan: ${parts.join(", ")}`;
950
+ }
951
+
952
+ //#endregion
953
+ //#region src/cli/commands/apply/compare.ts
954
+ /**
955
+ * Stable JSON-like serialization that sorts object keys and ignores proto runtime metadata.
956
+ * @param value - Value to serialize
957
+ * @returns Stable serialized string
958
+ */
959
+ function stableStringify(value) {
960
+ if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "null" : stableStringify(item)).join(",")}]`;
961
+ if (value && typeof value === "object") return `{${Object.entries(value).filter(([key, entryValue]) => key !== "$typeName" && entryValue !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`).join(",")}}`;
962
+ if (typeof value === "bigint") return JSON.stringify(value.toString());
963
+ return JSON.stringify(value);
964
+ }
965
+ /**
966
+ * Normalize a proto-ish object into a plain JSON-compatible structure for comparison.
967
+ * @param value - Value to normalize
968
+ * @returns Normalized value
969
+ */
970
+ function normalizeProtoConfig(value) {
971
+ if (value === void 0 || value === null) return value;
972
+ return JSON.parse(stableStringify(value));
973
+ }
974
+ /**
975
+ * Sort a string array for order-insensitive comparison.
976
+ * @param values - Values to sort
977
+ * @returns Sorted values
978
+ */
979
+ function normalizeStringArray(values) {
980
+ return [...values ?? []].sort();
981
+ }
982
+ /**
983
+ * Compare two values after proto normalization.
984
+ * @param left - Left value
985
+ * @param right - Right value
986
+ * @returns True when normalized values are equal
987
+ */
988
+ function areNormalizedEqual(left, right) {
989
+ return stableStringify(normalizeProtoConfig(left)) === stableStringify(normalizeProtoConfig(right));
990
+ }
912
991
 
913
992
  //#endregion
914
993
  //#region src/cli/commands/apply/label.ts
@@ -921,6 +1000,16 @@ function trnPrefix(workspaceId) {
921
1000
  return `trn:v1:workspace:${workspaceId}`;
922
1001
  }
923
1002
  const sdkNameLabelKey = "sdk-name";
1003
+ const sdkVersionLabelKey = "sdk-version";
1004
+ /**
1005
+ * Check whether existing metadata was produced by the current SDK version.
1006
+ * @param existingLabels - Labels currently stored on the remote resource
1007
+ * @param desiredLabels - Labels that will be written by the current apply run
1008
+ * @returns True when sdk-version matches
1009
+ */
1010
+ function hasMatchingSdkVersion(existingLabels, desiredLabels) {
1011
+ return existingLabels?.[sdkVersionLabelKey] === desiredLabels?.[sdkVersionLabelKey];
1012
+ }
924
1013
  /**
925
1014
  * Build metadata request with SDK labels.
926
1015
  * @param trn - Target TRN
@@ -936,7 +1025,7 @@ async function buildMetaRequest(trn, appName, existingLabels) {
936
1025
  labels: {
937
1026
  ...existingLabels ?? {},
938
1027
  [sdkNameLabelKey]: appName,
939
- "sdk-version": sdkVersion
1028
+ [sdkVersionLabelKey]: sdkVersion
940
1029
  }
941
1030
  };
942
1031
  }
@@ -967,6 +1056,54 @@ async function applyApplication(client, changeSet, phase = "create-update") {
967
1056
  function trn$6(workspaceId, name) {
968
1057
  return `trn:v1:workspace:${workspaceId}:application:${name}`;
969
1058
  }
1059
+ function sortStrings(values) {
1060
+ return [...values ?? []].sort();
1061
+ }
1062
+ function normalizeSubgraphs(subgraphs) {
1063
+ return [...subgraphs ?? []].map((subgraph) => ({
1064
+ serviceType: subgraph.serviceType,
1065
+ serviceNamespace: subgraph.serviceNamespace ?? ""
1066
+ })).sort((left, right) => {
1067
+ if (left.serviceType !== right.serviceType) return left.serviceType - right.serviceType;
1068
+ return left.serviceNamespace.localeCompare(right.serviceNamespace);
1069
+ });
1070
+ }
1071
+ function toComparableApplication(input) {
1072
+ return {
1073
+ authNamespace: input.authNamespace,
1074
+ authIdpConfigName: input.authIdpConfigName,
1075
+ cors: sortStrings(input.cors),
1076
+ subgraphs: [...input.subgraphs],
1077
+ allowedIpAddresses: sortStrings(input.allowedIpAddresses),
1078
+ disableIntrospection: input.disableIntrospection,
1079
+ disabled: input.disabled
1080
+ };
1081
+ }
1082
+ function normalizeComparableApplication(application, authNamespace, authIdpConfigName, cors) {
1083
+ return toComparableApplication({
1084
+ authNamespace: authNamespace ?? "",
1085
+ authIdpConfigName: authIdpConfigName ?? "",
1086
+ cors,
1087
+ subgraphs: normalizeSubgraphs(application.subgraphs.map((subgraph) => protoSubgraph(subgraph))),
1088
+ allowedIpAddresses: application.config.allowedIpAddresses ?? [],
1089
+ disableIntrospection: application.config.disableIntrospection ?? false,
1090
+ disabled: false
1091
+ });
1092
+ }
1093
+ function normalizeComparableExistingApplication(app) {
1094
+ return toComparableApplication({
1095
+ authNamespace: app.authNamespace,
1096
+ authIdpConfigName: app.authIdpConfigName,
1097
+ cors: app.cors,
1098
+ subgraphs: normalizeSubgraphs(app.subgraphs),
1099
+ allowedIpAddresses: app.allowedIpAddresses,
1100
+ disableIntrospection: app.disableIntrospection,
1101
+ disabled: app.disabled
1102
+ });
1103
+ }
1104
+ function areApplicationsEqual(existing, desired) {
1105
+ return areNormalizedEqual(normalizeComparableExistingApplication(existing), desired);
1106
+ }
970
1107
  /**
971
1108
  * Plan application changes based on current and desired state.
972
1109
  * @param context - Planning context
@@ -1028,32 +1165,30 @@ async function planApplication(context) {
1028
1165
  if (idpConfigs.length > 0) authIdpConfigName = idpConfigs[0].name;
1029
1166
  }
1030
1167
  const metaRequest = await buildMetaRequest(trn$6(workspaceId, application.name), application.name);
1031
- if (existingApplications.some((app) => app.name === application.name)) changeSet.updates.push({
1032
- name: application.name,
1033
- request: {
1034
- workspaceId,
1035
- applicationName: application.name,
1036
- authNamespace,
1037
- authIdpConfigName,
1038
- cors: application.config.cors,
1039
- subgraphs: application.subgraphs.map((subgraph) => protoSubgraph(subgraph)),
1040
- allowedIpAddresses: application.config.allowedIpAddresses,
1041
- disableIntrospection: application.config.disableIntrospection
1042
- },
1043
- metaRequest
1044
- });
1045
- else changeSet.creates.push({
1168
+ const resolvedCors = await resolveStaticWebsiteUrls(client, workspaceId, application.config.cors, "CORS");
1169
+ const desired = normalizeComparableApplication(application, authNamespace, authIdpConfigName, resolvedCors);
1170
+ const request = {
1171
+ workspaceId,
1172
+ applicationName: application.name,
1173
+ authNamespace,
1174
+ authIdpConfigName,
1175
+ cors: application.config.cors,
1176
+ subgraphs: application.subgraphs.map((subgraph) => protoSubgraph(subgraph)),
1177
+ allowedIpAddresses: application.config.allowedIpAddresses,
1178
+ disableIntrospection: application.config.disableIntrospection
1179
+ };
1180
+ const existing = existingApplications.find((app) => app.name === application.name);
1181
+ if (existing) {
1182
+ const { metadata } = await client.getMetadata({ trn: trn$6(workspaceId, application.name) });
1183
+ if (metadata?.labels?.["sdk-name"] === application.name && hasMatchingSdkVersion(metadata?.labels, metaRequest.labels) && areApplicationsEqual(existing, desired)) changeSet.unchanged.push({ name: application.name });
1184
+ else changeSet.updates.push({
1185
+ name: application.name,
1186
+ request,
1187
+ metaRequest
1188
+ });
1189
+ } else changeSet.creates.push({
1046
1190
  name: application.name,
1047
- request: {
1048
- workspaceId,
1049
- applicationName: application.name,
1050
- authNamespace,
1051
- authIdpConfigName,
1052
- cors: application.config.cors,
1053
- subgraphs: application.subgraphs.map((subgraph) => protoSubgraph(subgraph)),
1054
- allowedIpAddresses: application.config.allowedIpAddresses,
1055
- disableIntrospection: application.config.disableIntrospection
1056
- },
1191
+ request,
1057
1192
  metaRequest
1058
1193
  });
1059
1194
  changeSet.print();
@@ -1093,7 +1228,7 @@ const CHUNK_SIZE = 64 * 1024;
1093
1228
  function computeContentHash(content) {
1094
1229
  return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
1095
1230
  }
1096
- function functionRegistryTrn(workspaceId, name) {
1231
+ function functionRegistryTrn$1(workspaceId, name) {
1097
1232
  return `trn:v1:workspace:${workspaceId}:function_registry:${name}`;
1098
1233
  }
1099
1234
  /**
@@ -1199,6 +1334,16 @@ function collectFunctionEntries(application, workflowJobs, bundledScripts) {
1199
1334
  return entries;
1200
1335
  }
1201
1336
  /**
1337
+ * Filter collected workflow jobs down to the ones actually bundled.
1338
+ * @param jobs - All collected workflow jobs
1339
+ * @param usedJobNames - Job names that were bundled
1340
+ * @returns Bundled workflow jobs only
1341
+ */
1342
+ function filterBundledWorkflowJobs(jobs, usedJobNames) {
1343
+ const used = new Set(usedJobNames);
1344
+ return jobs.filter((job) => used.has(job.name));
1345
+ }
1346
+ /**
1202
1347
  * Plan function registry changes based on current and desired state.
1203
1348
  * @param client - Operator client instance
1204
1349
  * @param workspaceId - Workspace ID
@@ -1229,16 +1374,18 @@ async function planFunctionRegistry(client, workspaceId, appName, entries) {
1229
1374
  });
1230
1375
  const existingMap = {};
1231
1376
  await Promise.all(existingFunctions.map(async (func) => {
1232
- const { metadata } = await client.getMetadata({ trn: functionRegistryTrn(workspaceId, func.name) });
1377
+ const { metadata } = await client.getMetadata({ trn: functionRegistryTrn$1(workspaceId, func.name) });
1233
1378
  existingMap[func.name] = {
1234
1379
  resource: func,
1235
- label: metadata?.labels[sdkNameLabelKey]
1380
+ label: metadata?.labels[sdkNameLabelKey],
1381
+ allLabels: metadata?.labels
1236
1382
  };
1237
1383
  }));
1238
1384
  for (const entry of entries) {
1239
1385
  const existing = existingMap[entry.name];
1240
- const metaRequest = await buildMetaRequest(functionRegistryTrn(workspaceId, entry.name), appName);
1386
+ const metaRequest = await buildMetaRequest(functionRegistryTrn$1(workspaceId, entry.name), appName);
1241
1387
  if (existing) {
1388
+ const isManagedByApp = existing.label === appName;
1242
1389
  if (!existing.label) unmanaged.push({
1243
1390
  resourceType: "Function registry",
1244
1391
  resourceName: entry.name
@@ -1248,7 +1395,8 @@ async function planFunctionRegistry(client, workspaceId, appName, entries) {
1248
1395
  resourceName: entry.name,
1249
1396
  currentOwner: existing.label
1250
1397
  });
1251
- changeSet.updates.push({
1398
+ if (existing.resource.contentHash === entry.contentHash && isManagedByApp && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) changeSet.unchanged.push({ name: entry.name });
1399
+ else changeSet.updates.push({
1252
1400
  name: entry.name,
1253
1401
  entry,
1254
1402
  metaRequest
@@ -1435,10 +1583,10 @@ async function applyIdP(client, result, phase = "create-update") {
1435
1583
  * @returns Planned changes and metadata
1436
1584
  */
1437
1585
  async function planIdP(context) {
1438
- const { client, workspaceId, application, forRemoval } = context;
1586
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
1439
1587
  const idps = forRemoval ? [] : application.idpServices;
1440
1588
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$3(client, workspaceId, application.name, idps);
1441
- const clientChangeSet = await planClients(client, workspaceId, idps, serviceChangeSet.deletes.map((del) => del.name));
1589
+ const clientChangeSet = await planClients(client, workspaceId, idps, serviceChangeSet.deletes.map((del) => del.name), forceApplyAll);
1442
1590
  serviceChangeSet.print();
1443
1591
  clientChangeSet.print();
1444
1592
  return {
@@ -1454,6 +1602,57 @@ async function planIdP(context) {
1454
1602
  function trn$5(workspaceId, name) {
1455
1603
  return `trn:v1:workspace:${workspaceId}:idp:${name}`;
1456
1604
  }
1605
+ function normalizeComparableUserAuthPolicy(policy) {
1606
+ return {
1607
+ useNonEmailIdentifier: policy?.useNonEmailIdentifier ?? false,
1608
+ allowSelfPasswordReset: policy?.allowSelfPasswordReset ?? false,
1609
+ passwordRequireUppercase: policy?.passwordRequireUppercase ?? false,
1610
+ passwordRequireLowercase: policy?.passwordRequireLowercase ?? false,
1611
+ passwordRequireNonAlphanumeric: policy?.passwordRequireNonAlphanumeric ?? false,
1612
+ passwordRequireNumeric: policy?.passwordRequireNumeric ?? false,
1613
+ passwordMinLength: policy?.passwordMinLength ?? 0,
1614
+ passwordMaxLength: policy?.passwordMaxLength ?? 0,
1615
+ allowedEmailDomains: [...policy?.allowedEmailDomains ?? []].sort(),
1616
+ allowGoogleOauth: policy?.allowGoogleOauth ?? false,
1617
+ disablePasswordAuth: policy?.disablePasswordAuth ?? false,
1618
+ allowMicrosoftOauth: policy?.allowMicrosoftOauth ?? false
1619
+ };
1620
+ }
1621
+ function normalizeComparableDisableGqlOperations(value) {
1622
+ return {
1623
+ create: value?.create ?? false,
1624
+ update: value?.update ?? false,
1625
+ delete: value?.delete ?? false,
1626
+ read: value?.read ?? false,
1627
+ sendPasswordResetEmail: value?.sendPasswordResetEmail ?? false
1628
+ };
1629
+ }
1630
+ function normalizeComparableEmailConfig(value) {
1631
+ return {
1632
+ fromName: value?.fromName ?? "",
1633
+ passwordResetSubject: value?.passwordResetSubject ?? ""
1634
+ };
1635
+ }
1636
+ function normalizeComparableIdPService(input) {
1637
+ return {
1638
+ authorization: input.authorization,
1639
+ lang: input.lang === IdPLang.UNSPECIFIED ? IdPLang.EN : input.lang,
1640
+ userAuthPolicy: input.userAuthPolicy,
1641
+ publishUserEvents: input.publishUserEvents,
1642
+ disableGqlOperations: input.disableGqlOperations,
1643
+ emailConfig: input.emailConfig
1644
+ };
1645
+ }
1646
+ function areIdPServicesEqual(existing, desired) {
1647
+ return areNormalizedEqual(normalizeComparableIdPService({
1648
+ authorization: existing.authorization,
1649
+ lang: existing.lang,
1650
+ userAuthPolicy: normalizeComparableUserAuthPolicy(existing.userAuthPolicy),
1651
+ publishUserEvents: existing.publishUserEvents,
1652
+ disableGqlOperations: normalizeComparableDisableGqlOperations(existing.disableGqlOperations),
1653
+ emailConfig: normalizeComparableEmailConfig(existing.emailConfig)
1654
+ }), desired);
1655
+ }
1457
1656
  async function planServices$3(client, workspaceId, appName, idps) {
1458
1657
  const changeSet = createChangeSet("IdP services");
1459
1658
  const conflicts = [];
@@ -1478,7 +1677,8 @@ async function planServices$3(client, workspaceId, appName, idps) {
1478
1677
  const { metadata } = await client.getMetadata({ trn: trn$5(workspaceId, resource.namespace.name) });
1479
1678
  existingServices[resource.namespace.name] = {
1480
1679
  resource,
1481
- label: metadata?.labels[sdkNameLabelKey]
1680
+ label: metadata?.labels[sdkNameLabelKey],
1681
+ allLabels: metadata?.labels
1482
1682
  };
1483
1683
  }));
1484
1684
  for (const idp of idps) {
@@ -1499,7 +1699,28 @@ async function planServices$3(client, workspaceId, appName, idps) {
1499
1699
  }
1500
1700
  const lang = convertLang(idp.lang);
1501
1701
  const userAuthPolicy = idp.userAuthPolicy;
1702
+ const publishUserEvents = idp.publishUserEvents ?? false;
1703
+ const emailConfig = idp.emailConfig;
1704
+ const desired = normalizeComparableIdPService({
1705
+ authorization,
1706
+ lang,
1707
+ userAuthPolicy: normalizeComparableUserAuthPolicy(userAuthPolicy),
1708
+ publishUserEvents,
1709
+ disableGqlOperations: normalizeComparableDisableGqlOperations(convertGqlOperationsToDisable(idp.gqlOperations)),
1710
+ emailConfig: normalizeComparableEmailConfig(emailConfig)
1711
+ });
1712
+ const request = {
1713
+ workspaceId,
1714
+ namespaceName,
1715
+ authorization,
1716
+ lang,
1717
+ userAuthPolicy,
1718
+ publishUserEvents,
1719
+ disableGqlOperations: convertGqlOperationsToDisable(idp.gqlOperations),
1720
+ emailConfig
1721
+ };
1502
1722
  if (existing) {
1723
+ const isManagedByApp = existing.label === appName;
1503
1724
  if (!existing.label) unmanaged.push({
1504
1725
  resourceType: "IdP service",
1505
1726
  resourceName: idp.name
@@ -1509,31 +1730,16 @@ async function planServices$3(client, workspaceId, appName, idps) {
1509
1730
  resourceName: idp.name,
1510
1731
  currentOwner: existing.label
1511
1732
  });
1512
- changeSet.updates.push({
1733
+ if (isManagedByApp && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areIdPServicesEqual(existing.resource, desired)) changeSet.unchanged.push({ name: namespaceName });
1734
+ else changeSet.updates.push({
1513
1735
  name: namespaceName,
1514
- request: {
1515
- workspaceId,
1516
- namespaceName,
1517
- authorization,
1518
- lang,
1519
- userAuthPolicy,
1520
- publishUserEvents: idp.publishUserEvents,
1521
- disableGqlOperations: convertGqlOperationsToDisable(idp.gqlOperations)
1522
- },
1736
+ request,
1523
1737
  metaRequest
1524
1738
  });
1525
1739
  delete existingServices[namespaceName];
1526
1740
  } else changeSet.creates.push({
1527
1741
  name: namespaceName,
1528
- request: {
1529
- workspaceId,
1530
- namespaceName,
1531
- authorization,
1532
- lang,
1533
- userAuthPolicy,
1534
- publishUserEvents: idp.publishUserEvents,
1535
- disableGqlOperations: convertGqlOperationsToDisable(idp.gqlOperations)
1536
- },
1742
+ request,
1537
1743
  metaRequest
1538
1744
  });
1539
1745
  }
@@ -1555,7 +1761,7 @@ async function planServices$3(client, workspaceId, appName, idps) {
1555
1761
  resourceOwners
1556
1762
  };
1557
1763
  }
1558
- async function planClients(client, workspaceId, idps, deletedServices) {
1764
+ async function planClients(client, workspaceId, idps, deletedServices, forceApplyAll = false) {
1559
1765
  const changeSet = createChangeSet("IdP clients");
1560
1766
  const fetchClients = (namespaceName) => {
1561
1767
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -1583,12 +1789,13 @@ async function planClients(client, workspaceId, idps, deletedServices) {
1583
1789
  existingNameMap.set(client.name, client.clientSecret);
1584
1790
  });
1585
1791
  for (const name of idp.clients) if (existingNameMap.has(name)) {
1586
- changeSet.updates.push({
1792
+ if (forceApplyAll) changeSet.updates.push({
1587
1793
  name,
1588
1794
  workspaceId,
1589
1795
  namespaceName,
1590
- clientSecret: existingNameMap.get(name)
1796
+ clientSecret: existingNameMap.get(name) ?? ""
1591
1797
  });
1798
+ else changeSet.unchanged.push({ name });
1592
1799
  existingNameMap.delete(name);
1593
1800
  } else changeSet.creates.push({
1594
1801
  name,
@@ -1598,7 +1805,7 @@ async function planClients(client, workspaceId, idps, deletedServices) {
1598
1805
  client: { name }
1599
1806
  }
1600
1807
  });
1601
- existingNameMap.forEach((name) => {
1808
+ existingNameMap.forEach((_clientSecret, name) => {
1602
1809
  changeSet.deletes.push({
1603
1810
  name,
1604
1811
  request: {
@@ -1704,21 +1911,21 @@ async function applyAuth(client, result, phase = "create-update") {
1704
1911
  * @returns Planned auth changes and metadata
1705
1912
  */
1706
1913
  async function planAuth(context) {
1707
- const { client, workspaceId, application, forRemoval } = context;
1914
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
1708
1915
  const auths = [];
1709
1916
  if (!forRemoval && application.authService) {
1710
1917
  await application.authService.resolveNamespaces();
1711
1918
  auths.push(application.authService);
1712
1919
  }
1713
- const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$2(client, workspaceId, application.name, auths);
1920
+ const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$2(client, workspaceId, application.name, auths, forceApplyAll);
1714
1921
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
1715
1922
  const [idpConfigChangeSet, userProfileConfigChangeSet, tenantConfigChangeSet, machineUserChangeSet, authHookChangeSet, oauth2ClientChangeSet, scimChangeSet, scimResourceChangeSet] = await Promise.all([
1716
- planIdPConfigs(client, workspaceId, auths, deletedServices),
1717
- planUserProfileConfigs(client, workspaceId, auths, deletedServices),
1718
- planTenantConfigs(client, workspaceId, auths, deletedServices),
1719
- planMachineUsers(client, workspaceId, auths, deletedServices),
1720
- planAuthHooks(client, workspaceId, auths, deletedServices),
1721
- planOAuth2Clients(client, workspaceId, auths, deletedServices),
1923
+ planIdPConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
1924
+ planUserProfileConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
1925
+ planTenantConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
1926
+ planMachineUsers(client, workspaceId, auths, deletedServices, forceApplyAll),
1927
+ planAuthHooks(client, workspaceId, auths, deletedServices, forceApplyAll),
1928
+ planOAuth2Clients(client, workspaceId, auths, deletedServices, forceApplyAll),
1722
1929
  planSCIMConfigs(client, workspaceId, auths, deletedServices),
1723
1930
  planSCIMResources(client, workspaceId, auths, deletedServices)
1724
1931
  ]);
@@ -1751,7 +1958,7 @@ async function planAuth(context) {
1751
1958
  function trn$4(workspaceId, name) {
1752
1959
  return `trn:v1:workspace:${workspaceId}:auth:${name}`;
1753
1960
  }
1754
- async function planServices$2(client, workspaceId, appName, auths) {
1961
+ async function planServices$2(client, workspaceId, appName, auths, forceApplyAll = false) {
1755
1962
  const changeSet = createChangeSet("Auth services");
1756
1963
  const conflicts = [];
1757
1964
  const unmanaged = [];
@@ -1782,7 +1989,13 @@ async function planServices$2(client, workspaceId, appName, auths) {
1782
1989
  const { parsedConfig: config } = auth;
1783
1990
  const existing = existingServices[config.name];
1784
1991
  const metaRequest = await buildMetaRequest(trn$4(workspaceId, config.name), appName);
1992
+ const request = {
1993
+ workspaceId,
1994
+ namespaceName: config.name,
1995
+ publishSessionEvents: config.publishSessionEvents
1996
+ };
1785
1997
  if (existing) {
1998
+ const isManagedByApp = existing.label === appName;
1786
1999
  if (!existing.label) unmanaged.push({
1787
2000
  resourceType: "Auth service",
1788
2001
  resourceName: config.name
@@ -1792,23 +2005,16 @@ async function planServices$2(client, workspaceId, appName, auths) {
1792
2005
  resourceName: config.name,
1793
2006
  currentOwner: existing.label
1794
2007
  });
1795
- changeSet.updates.push({
2008
+ if (!forceApplyAll && existing.resource.publishSessionEvents === (config.publishSessionEvents ?? false) && isManagedByApp) changeSet.unchanged.push({ name: config.name });
2009
+ else changeSet.updates.push({
1796
2010
  name: config.name,
1797
- request: {
1798
- workspaceId,
1799
- namespaceName: config.name,
1800
- publishSessionEvents: config.publishSessionEvents
1801
- },
2011
+ request,
1802
2012
  metaRequest
1803
2013
  });
1804
2014
  delete existingServices[config.name];
1805
2015
  } else changeSet.creates.push({
1806
2016
  name: config.name,
1807
- request: {
1808
- workspaceId,
1809
- namespaceName: config.name,
1810
- publishSessionEvents: config.publishSessionEvents
1811
- },
2017
+ request,
1812
2018
  metaRequest
1813
2019
  });
1814
2020
  }
@@ -1830,7 +2036,7 @@ async function planServices$2(client, workspaceId, appName, auths) {
1830
2036
  resourceOwners
1831
2037
  };
1832
2038
  }
1833
- async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
2039
+ async function planIdPConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
1834
2040
  const changeSet = createChangeSet("Auth idpConfigs");
1835
2041
  const fetchIdPConfigs = (namespaceName) => {
1836
2042
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -1851,32 +2057,51 @@ async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
1851
2057
  for (const authService of auths) {
1852
2058
  const { parsedConfig: config } = authService;
1853
2059
  const existingIdPConfigs = await fetchIdPConfigs(config.name);
1854
- const existingNameSet = /* @__PURE__ */ new Set();
2060
+ const existingMap = /* @__PURE__ */ new Map();
1855
2061
  existingIdPConfigs.forEach((idpConfig) => {
1856
- existingNameSet.add(idpConfig.name);
2062
+ existingMap.set(idpConfig.name, idpConfig);
1857
2063
  });
1858
2064
  const idpConfig = config.idProvider;
1859
- if (idpConfig) if (existingNameSet.has(idpConfig.name)) {
1860
- changeSet.updates.push({
2065
+ if (idpConfig) {
2066
+ const desired = protoIdPConfig(idpConfig);
2067
+ const existing = existingMap.get(idpConfig.name);
2068
+ if (existing) {
2069
+ const desiredComparable = await protoIdPConfigForComparison(client, workspaceId, idpConfig, desired);
2070
+ if (!desiredComparable) {
2071
+ changeSet.updates.push({
2072
+ name: idpConfig.name,
2073
+ idpConfig,
2074
+ request: {
2075
+ workspaceId,
2076
+ namespaceName: config.name,
2077
+ idpConfig: desired
2078
+ }
2079
+ });
2080
+ existingMap.delete(idpConfig.name);
2081
+ continue;
2082
+ }
2083
+ if (!forceApplyAll && areAuthIdPConfigsEqual(existing, desiredComparable)) changeSet.unchanged.push({ name: idpConfig.name });
2084
+ else changeSet.updates.push({
2085
+ name: idpConfig.name,
2086
+ idpConfig,
2087
+ request: {
2088
+ workspaceId,
2089
+ namespaceName: config.name,
2090
+ idpConfig: desired
2091
+ }
2092
+ });
2093
+ existingMap.delete(idpConfig.name);
2094
+ } else changeSet.creates.push({
1861
2095
  name: idpConfig.name,
1862
2096
  idpConfig,
1863
2097
  request: {
1864
2098
  workspaceId,
1865
2099
  namespaceName: config.name,
1866
- idpConfig: protoIdPConfig(idpConfig)
2100
+ idpConfig: desired
1867
2101
  }
1868
2102
  });
1869
- existingNameSet.delete(idpConfig.name);
1870
- } else changeSet.creates.push({
1871
- name: idpConfig.name,
1872
- idpConfig,
1873
- request: {
1874
- workspaceId,
1875
- namespaceName: config.name,
1876
- idpConfig: protoIdPConfig(idpConfig)
1877
- }
1878
- });
1879
- existingNameSet.forEach((name) => {
2103
+ }
2104
+ existingMap.forEach((_, name) => {
1880
2105
  changeSet.deletes.push({
1881
2106
  name,
1882
2107
  request: {
@@ -1899,6 +2124,32 @@ async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
1899
2124
  });
1900
2125
  return changeSet;
1901
2126
  }
2127
+ async function protoIdPConfigForComparison(client, workspaceId, idpConfig, desired) {
2128
+ if (idpConfig.kind !== "BuiltInIdP") return desired;
2129
+ const config = await tryProtoBuiltinIdPConfig(client, workspaceId, idpConfig);
2130
+ return config ? {
2131
+ ...desired,
2132
+ config
2133
+ } : void 0;
2134
+ }
2135
+ function normalizeComparableAuthIdPConfig(idpConfig) {
2136
+ const configCase = idpConfig.config?.config?.case;
2137
+ const oidcValue = configCase === "oidc" && typeof idpConfig.config?.config?.value === "object" && idpConfig.config.config.value !== null ? idpConfig.config.config.value : void 0;
2138
+ return normalizeProtoConfig({
2139
+ name: idpConfig.name,
2140
+ authType: idpConfig.authType,
2141
+ config: configCase === "oidc" ? { config: {
2142
+ case: "oidc",
2143
+ value: {
2144
+ ...oidcValue ?? {},
2145
+ issuerUrl: oidcValue && "issuerUrl" in oidcValue ? oidcValue.issuerUrl || void 0 : void 0
2146
+ }
2147
+ } } : idpConfig.config
2148
+ });
2149
+ }
2150
+ function areAuthIdPConfigsEqual(existing, desired) {
2151
+ return areNormalizedEqual(normalizeComparableAuthIdPConfig(existing), normalizeComparableAuthIdPConfig(desired));
2152
+ }
1902
2153
  function protoIdPConfig(idpConfig) {
1903
2154
  switch (idpConfig.kind) {
1904
2155
  case "IDToken": return {
@@ -1951,6 +2202,11 @@ function protoIdPConfig(idpConfig) {
1951
2202
  }
1952
2203
  }
1953
2204
  async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
2205
+ const config = await tryProtoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig);
2206
+ if (!config) throw new Error(`Built-in IdP "${builtinIdPConfig.namespace}" not found. Please ensure that idp is configured correctly.`);
2207
+ return config;
2208
+ }
2209
+ async function tryProtoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
1954
2210
  let idpService;
1955
2211
  try {
1956
2212
  idpService = await client.getIdPService({
@@ -1958,14 +2214,20 @@ async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
1958
2214
  namespaceName: builtinIdPConfig.namespace
1959
2215
  });
1960
2216
  } catch (error) {
1961
- if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Built-in IdP "${builtinIdPConfig.namespace}" not found. Please ensure that idp is configured correctly.`, { cause: error });
2217
+ if (error instanceof ConnectError && error.code === Code.NotFound) return;
2218
+ throw error;
2219
+ }
2220
+ let idpClient;
2221
+ try {
2222
+ idpClient = await client.getIdPClient({
2223
+ workspaceId,
2224
+ namespaceName: builtinIdPConfig.namespace,
2225
+ name: builtinIdPConfig.clientName
2226
+ });
2227
+ } catch (error) {
2228
+ if (error instanceof ConnectError && error.code === Code.NotFound) return;
1962
2229
  throw error;
1963
2230
  }
1964
- const idpClient = await client.getIdPClient({
1965
- workspaceId,
1966
- namespaceName: builtinIdPConfig.namespace,
1967
- name: builtinIdPConfig.clientName
1968
- });
1969
2231
  const vaultName = idpClientVaultName(builtinIdPConfig.namespace, builtinIdPConfig.clientName);
1970
2232
  const secretKey = idpClientSecretName(builtinIdPConfig.namespace, builtinIdPConfig.clientName);
1971
2233
  return { config: {
@@ -1981,16 +2243,35 @@ async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
1981
2243
  }
1982
2244
  } };
1983
2245
  }
1984
- async function planUserProfileConfigs(client, workspaceId, auths, deletedServices) {
2246
+ async function planUserProfileConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
1985
2247
  const changeSet = createChangeSet("Auth userProfileConfigs");
1986
2248
  for (const auth of auths) {
1987
2249
  const { parsedConfig: config } = auth;
1988
2250
  const name = `${config.name}-user-profile-config`;
1989
2251
  try {
1990
- await client.getUserProfileConfig({
2252
+ const { userProfileProviderConfig } = await client.getUserProfileConfig({
1991
2253
  workspaceId,
1992
2254
  namespaceName: config.name
1993
2255
  });
2256
+ const userProfileForUpdate = auth.userProfile;
2257
+ if (userProfileForUpdate) {
2258
+ const desired = protoUserProfileConfig(userProfileForUpdate);
2259
+ if (!forceApplyAll && areUserProfileConfigsEqual(userProfileProviderConfig ?? {}, desired)) changeSet.unchanged.push({ name });
2260
+ else changeSet.updates.push({
2261
+ name,
2262
+ request: {
2263
+ workspaceId,
2264
+ namespaceName: config.name,
2265
+ userProfileProviderConfig: desired
2266
+ }
2267
+ });
2268
+ } else changeSet.deletes.push({
2269
+ name,
2270
+ request: {
2271
+ workspaceId,
2272
+ namespaceName: config.name
2273
+ }
2274
+ });
1994
2275
  } catch (error) {
1995
2276
  if (error instanceof ConnectError && error.code === Code.NotFound) {
1996
2277
  const userProfileForCreate = auth.userProfile;
@@ -2006,22 +2287,6 @@ async function planUserProfileConfigs(client, workspaceId, auths, deletedService
2006
2287
  }
2007
2288
  throw error;
2008
2289
  }
2009
- const userProfileForUpdate = auth.userProfile;
2010
- if (userProfileForUpdate) changeSet.updates.push({
2011
- name,
2012
- request: {
2013
- workspaceId,
2014
- namespaceName: config.name,
2015
- userProfileProviderConfig: protoUserProfileConfig(userProfileForUpdate)
2016
- }
2017
- });
2018
- else changeSet.deletes.push({
2019
- name,
2020
- request: {
2021
- workspaceId,
2022
- namespaceName: config.name
2023
- }
2024
- });
2025
2290
  }
2026
2291
  for (const namespaceName of deletedServices) {
2027
2292
  try {
@@ -2061,16 +2326,34 @@ function protoUserProfileConfig(userProfile) {
2061
2326
  } }
2062
2327
  };
2063
2328
  }
2064
- async function planTenantConfigs(client, workspaceId, auths, deletedServices) {
2329
+ async function planTenantConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2065
2330
  const changeSet = createChangeSet("Auth tenantConfigs");
2066
2331
  for (const auth of auths) {
2067
2332
  const { parsedConfig: config } = auth;
2068
2333
  const name = `${config.name}-tenant-config`;
2069
2334
  try {
2070
- await client.getTenantConfig({
2335
+ const { tenantProviderConfig } = await client.getTenantConfig({
2071
2336
  workspaceId,
2072
2337
  namespaceName: config.name
2073
2338
  });
2339
+ if (config.tenantProvider) {
2340
+ const desired = protoTenantConfig(config.tenantProvider);
2341
+ if (!forceApplyAll && areTenantProviderConfigsEqual(tenantProviderConfig, desired)) changeSet.unchanged.push({ name });
2342
+ else changeSet.updates.push({
2343
+ name,
2344
+ request: {
2345
+ workspaceId,
2346
+ namespaceName: config.name,
2347
+ tenantProviderConfig: desired
2348
+ }
2349
+ });
2350
+ } else changeSet.deletes.push({
2351
+ name,
2352
+ request: {
2353
+ workspaceId,
2354
+ namespaceName: config.name
2355
+ }
2356
+ });
2074
2357
  } catch (error) {
2075
2358
  if (error instanceof ConnectError && error.code === Code.NotFound) {
2076
2359
  if (config.tenantProvider) changeSet.creates.push({
@@ -2085,21 +2368,6 @@ async function planTenantConfigs(client, workspaceId, auths, deletedServices) {
2085
2368
  }
2086
2369
  throw error;
2087
2370
  }
2088
- if (config.tenantProvider) changeSet.updates.push({
2089
- name,
2090
- request: {
2091
- workspaceId,
2092
- namespaceName: config.name,
2093
- tenantProviderConfig: protoTenantConfig(config.tenantProvider)
2094
- }
2095
- });
2096
- else changeSet.deletes.push({
2097
- name,
2098
- request: {
2099
- workspaceId,
2100
- namespaceName: config.name
2101
- }
2102
- });
2103
2371
  }
2104
2372
  for (const namespaceName of deletedServices) {
2105
2373
  try {
@@ -2134,7 +2402,7 @@ function protoTenantConfig(tenantConfig) {
2134
2402
  } }
2135
2403
  };
2136
2404
  }
2137
- async function planMachineUsers(client, workspaceId, auths, deletedServices) {
2405
+ async function planMachineUsers(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2138
2406
  const changeSet = createChangeSet("Auth machineUsers");
2139
2407
  const fetchMachineUsers = (authNamespace) => {
2140
2408
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -2155,25 +2423,31 @@ async function planMachineUsers(client, workspaceId, auths, deletedServices) {
2155
2423
  for (const auth of auths) {
2156
2424
  const { parsedConfig: config } = auth;
2157
2425
  const existingMachineUsers = await fetchMachineUsers(config.name);
2158
- const existingNameSet = /* @__PURE__ */ new Set();
2426
+ const existingMap = /* @__PURE__ */ new Map();
2159
2427
  existingMachineUsers.forEach((machineUser) => {
2160
- existingNameSet.add(machineUser.name);
2428
+ existingMap.set(machineUser.name, machineUser);
2161
2429
  });
2162
2430
  for (const machineUsername of Object.keys(config.machineUsers ?? {})) {
2163
2431
  const machineUser = config.machineUsers?.[machineUsername];
2164
2432
  if (!machineUser) continue;
2165
- if (existingNameSet.has(machineUsername)) {
2166
- changeSet.updates.push({
2433
+ const desiredMachineUser = {
2434
+ attributes: machineUser.attributeList,
2435
+ attributeMap: machineUser.attributes ? protoMachineUserAttributeMap(machineUser.attributes) : void 0
2436
+ };
2437
+ const existing = existingMap.get(machineUsername);
2438
+ if (existing) {
2439
+ if (!forceApplyAll && areMachineUsersEqual(existing, desiredMachineUser)) changeSet.unchanged.push({ name: machineUsername });
2440
+ else changeSet.updates.push({
2167
2441
  name: machineUsername,
2168
2442
  request: {
2169
2443
  workspaceId,
2170
2444
  authNamespace: config.name,
2171
2445
  name: machineUsername,
2172
2446
  attributes: machineUser.attributeList,
2173
- attributeMap: machineUser.attributes ? protoMachineUserAttributeMap(machineUser.attributes) : void 0
2447
+ attributeMap: desiredMachineUser.attributeMap
2174
2448
  }
2175
2449
  });
2176
- existingNameSet.delete(machineUsername);
2450
+ existingMap.delete(machineUsername);
2177
2451
  } else changeSet.creates.push({
2178
2452
  name: machineUsername,
2179
2453
  request: {
@@ -2181,11 +2455,11 @@ async function planMachineUsers(client, workspaceId, auths, deletedServices) {
2181
2455
  authNamespace: config.name,
2182
2456
  name: machineUsername,
2183
2457
  attributes: machineUser.attributeList,
2184
- attributeMap: machineUser.attributes ? protoMachineUserAttributeMap(machineUser.attributes) : void 0
2458
+ attributeMap: desiredMachineUser.attributeMap
2185
2459
  }
2186
2460
  });
2187
2461
  }
2188
- existingNameSet.forEach((name) => {
2462
+ existingMap.forEach((_, name) => {
2189
2463
  changeSet.deletes.push({
2190
2464
  name,
2191
2465
  request: {
@@ -2213,7 +2487,60 @@ function protoMachineUserAttributeMap(attributeMap) {
2213
2487
  for (const [key, value] of Object.entries(attributeMap)) ret[key] = fromJson(ValueSchema, value ?? null);
2214
2488
  return ret;
2215
2489
  }
2216
- async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
2490
+ function normalizeComparableUserProfileConfig(config) {
2491
+ const comparableConfig = config.config?.config;
2492
+ const tailorDBConfig = comparableConfig?.case === "tailordb" ? comparableConfig.value : void 0;
2493
+ return normalizeProtoConfig({
2494
+ providerType: config.providerType,
2495
+ config: { config: {
2496
+ case: comparableConfig?.case,
2497
+ value: tailorDBConfig ? {
2498
+ ...tailorDBConfig,
2499
+ tenantIdField: tailorDBConfig.tenantIdField || void 0,
2500
+ attributesFields: normalizeStringArray(tailorDBConfig.attributesFields),
2501
+ attributeMap: normalizeProtoConfig(tailorDBConfig.attributeMap)
2502
+ } : comparableConfig?.value
2503
+ } }
2504
+ });
2505
+ }
2506
+ function areUserProfileConfigsEqual(existing, desired) {
2507
+ return areNormalizedEqual(normalizeComparableUserProfileConfig(existing), normalizeComparableUserProfileConfig(desired));
2508
+ }
2509
+ function normalizeComparableTenantProviderConfig(config) {
2510
+ return normalizeProtoConfig(config);
2511
+ }
2512
+ function areTenantProviderConfigsEqual(existing, desired) {
2513
+ return areNormalizedEqual(normalizeComparableTenantProviderConfig(existing), normalizeComparableTenantProviderConfig(desired));
2514
+ }
2515
+ function normalizeComparableMachineUser(input) {
2516
+ return normalizeProtoConfig({
2517
+ attributes: normalizeStringArray(input.attributes),
2518
+ attributeMap: normalizeProtoConfig(input.attributeMap ?? {})
2519
+ });
2520
+ }
2521
+ function areMachineUsersEqual(existing, desired) {
2522
+ return areNormalizedEqual(normalizeComparableMachineUser(existing), normalizeComparableMachineUser(desired));
2523
+ }
2524
+ function normalizeComparableOAuth2Client(client) {
2525
+ const accessTokenLifetime = oauth2LifetimeToSeconds(client.accessTokenLifetime);
2526
+ const refreshTokenLifetime = oauth2LifetimeToSeconds(client.refreshTokenLifetime);
2527
+ return normalizeProtoConfig({
2528
+ ...client,
2529
+ redirectUris: normalizeStringArray(client.redirectUris),
2530
+ grantTypes: [...client.grantTypes ?? []].sort((left, right) => left - right),
2531
+ accessTokenLifetime: accessTokenLifetime ?? 86400,
2532
+ refreshTokenLifetime: refreshTokenLifetime ?? 604800,
2533
+ requireDpop: client.requireDpop ?? false
2534
+ });
2535
+ }
2536
+ function oauth2LifetimeToSeconds(lifetime) {
2537
+ if (typeof lifetime === "number") return lifetime;
2538
+ if (lifetime?.seconds != null) return Number(lifetime.seconds);
2539
+ }
2540
+ function areOAuth2ClientsEqual(existing, desired) {
2541
+ return areNormalizedEqual(normalizeComparableOAuth2Client(existing), normalizeComparableOAuth2Client(desired));
2542
+ }
2543
+ async function planOAuth2Clients(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2217
2544
  const changeSet = createChangeSet("Auth oauth2Clients");
2218
2545
  const fetchOAuth2Clients = (namespaceName) => {
2219
2546
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -2236,14 +2563,16 @@ async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
2236
2563
  const existingOAuth2Clients = await fetchOAuth2Clients(config.name);
2237
2564
  const existingClientsMap = /* @__PURE__ */ new Map();
2238
2565
  existingOAuth2Clients.forEach((oauth2Client) => {
2239
- existingClientsMap.set(oauth2Client.name, oauth2Client.clientType);
2566
+ existingClientsMap.set(oauth2Client.name, oauth2Client);
2240
2567
  });
2241
2568
  for (const oauth2ClientName of Object.keys(config.oauth2Clients ?? {})) {
2242
2569
  const oauth2Client = config.oauth2Clients?.[oauth2ClientName];
2243
2570
  if (!oauth2Client) continue;
2244
2571
  const newOAuth2Client = protoOAuth2Client(oauth2ClientName, oauth2Client);
2572
+ const resolvedRedirectUris = await resolveStaticWebsiteUrls(client, workspaceId, newOAuth2Client.redirectUris ?? [], "OAuth2 redirect URIs");
2245
2573
  if (existingClientsMap.has(oauth2ClientName)) {
2246
- if (existingClientsMap.get(oauth2ClientName) !== newOAuth2Client.clientType) changeSet.replaces.push({
2574
+ const existingClient = existingClientsMap.get(oauth2ClientName);
2575
+ if (existingClient.clientType !== newOAuth2Client.clientType) changeSet.replaces.push({
2247
2576
  name: oauth2ClientName,
2248
2577
  deleteRequest: {
2249
2578
  workspaceId,
@@ -2256,14 +2585,33 @@ async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
2256
2585
  oauth2Client: newOAuth2Client
2257
2586
  }
2258
2587
  });
2259
- else changeSet.updates.push({
2260
- name: oauth2ClientName,
2261
- request: {
2262
- workspaceId,
2263
- namespaceName: config.name,
2264
- oauth2Client: newOAuth2Client
2265
- }
2266
- });
2588
+ else {
2589
+ const desiredComparable = {
2590
+ ...newOAuth2Client,
2591
+ redirectUris: resolvedRedirectUris,
2592
+ accessTokenLifetime: oauth2LifetimeToSeconds(newOAuth2Client.accessTokenLifetime),
2593
+ refreshTokenLifetime: oauth2LifetimeToSeconds(newOAuth2Client.refreshTokenLifetime)
2594
+ };
2595
+ const existingComparable = {
2596
+ name: existingClient.name,
2597
+ description: existingClient.description,
2598
+ grantTypes: existingClient.grantTypes,
2599
+ redirectUris: existingClient.redirectUris,
2600
+ clientType: existingClient.clientType,
2601
+ accessTokenLifetime: oauth2LifetimeToSeconds(existingClient.accessTokenLifetime),
2602
+ refreshTokenLifetime: oauth2LifetimeToSeconds(existingClient.refreshTokenLifetime),
2603
+ requireDpop: existingClient.requireDpop
2604
+ };
2605
+ if (!forceApplyAll && areOAuth2ClientsEqual(existingComparable, desiredComparable)) changeSet.unchanged.push({ name: oauth2ClientName });
2606
+ else changeSet.updates.push({
2607
+ name: oauth2ClientName,
2608
+ request: {
2609
+ workspaceId,
2610
+ namespaceName: config.name,
2611
+ oauth2Client: newOAuth2Client
2612
+ }
2613
+ });
2614
+ }
2267
2615
  existingClientsMap.delete(oauth2ClientName);
2268
2616
  } else changeSet.creates.push({
2269
2617
  name: oauth2ClientName,
@@ -2538,21 +2886,36 @@ function protoSCIMAttribute(attr) {
2538
2886
  subAttributes: attr.subAttributes?.map((attr) => protoSCIMAttribute(attr))
2539
2887
  };
2540
2888
  }
2541
- async function planAuthHooks(client, workspaceId, auths, deletedServices) {
2889
+ function areAuthHooksEqual(existing, desired) {
2890
+ return areNormalizedEqual({
2891
+ scriptRef: existing.scriptRef ?? "",
2892
+ invoker: existing.invoker ? {
2893
+ namespace: existing.invoker.namespace ?? "",
2894
+ machineUserName: existing.invoker.machineUserName ?? ""
2895
+ } : void 0
2896
+ }, {
2897
+ scriptRef: desired.scriptRef ?? "",
2898
+ invoker: desired.invoker ? {
2899
+ namespace: desired.invoker.namespace ?? "",
2900
+ machineUserName: desired.invoker.machineUserName ?? ""
2901
+ } : void 0
2902
+ });
2903
+ }
2904
+ async function planAuthHooks(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2542
2905
  const changeSet = createChangeSet("Auth hooks");
2543
2906
  for (const auth of auths) {
2544
2907
  const { parsedConfig: config } = auth;
2545
2908
  const beforeLogin = config.hooks?.beforeLogin;
2546
2909
  let existingHook;
2547
2910
  try {
2548
- await client.getAuthHook({
2911
+ const { hook } = await client.getAuthHook({
2549
2912
  workspaceId,
2550
2913
  namespaceName: config.name,
2551
2914
  hookPoint: AuthHookPoint.BEFORE_LOGIN
2552
2915
  });
2553
- existingHook = true;
2916
+ existingHook = hook;
2554
2917
  } catch (error) {
2555
- if (error instanceof ConnectError && error.code === Code.NotFound) existingHook = false;
2918
+ if (error instanceof ConnectError && error.code === Code.NotFound) existingHook = void 0;
2556
2919
  else throw error;
2557
2920
  }
2558
2921
  if (beforeLogin) {
@@ -2568,7 +2931,8 @@ async function planAuthHooks(client, workspaceId, auths, deletedServices) {
2568
2931
  }
2569
2932
  }
2570
2933
  };
2571
- if (existingHook) changeSet.updates.push({
2934
+ if (existingHook) if (!forceApplyAll && areAuthHooksEqual(existingHook, hookRequest.hook)) changeSet.unchanged.push({ name: `${config.name}/before-login` });
2935
+ else changeSet.updates.push({
2572
2936
  name: `${config.name}/before-login`,
2573
2937
  request: hookRequest
2574
2938
  });
@@ -2751,19 +3115,10 @@ const ACTOR_TRANSFORM_EXPR = "actor: args.actor ? (({ attributeMap, attributes:
2751
3115
  function buildExecutorArgsExpr(triggerKind, env) {
2752
3116
  const envExpr = `env: ${JSON.stringify(env)}`;
2753
3117
  switch (triggerKind) {
2754
- case "schedule":
2755
- case "recordCreated":
2756
- case "recordUpdated":
2757
- case "recordDeleted":
2758
- case "idpUserCreated":
2759
- case "idpUserUpdated":
2760
- case "idpUserDeleted":
2761
- case "authAccessTokenIssued":
2762
- case "authAccessTokenRefreshed":
2763
- case "authAccessTokenRevoked": return `({ ...args, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, ${envExpr} })`;
3118
+ case "schedule": return `({ ...args, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, ${envExpr} })`;
2764
3119
  case "resolverExecuted": return `({ ...args, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, success: !!args.succeeded, result: args.succeeded?.result.resolver, error: args.failed?.error, ${envExpr} })`;
2765
3120
  case "incomingWebhook": return `({ ...args, appNamespace: args.namespaceName, rawBody: args.raw_body, ${envExpr} })`;
2766
- default: throw new Error(`Unknown trigger kind for args expression: ${triggerKind}`);
3121
+ default: return `({ ...args, event: args.eventType?.split(".").pop(), rawEvent: args.eventType, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, ${envExpr} })`;
2767
3122
  }
2768
3123
  }
2769
3124
  /**
@@ -2833,13 +3188,15 @@ async function planExecutor(context) {
2833
3188
  const { metadata } = await client.getMetadata({ trn: trn$3(workspaceId, resource.name) });
2834
3189
  existingExecutors[resource.name] = {
2835
3190
  resource,
2836
- label: metadata?.labels[sdkNameLabelKey]
3191
+ label: metadata?.labels[sdkNameLabelKey],
3192
+ allLabels: metadata?.labels
2837
3193
  };
2838
3194
  }));
2839
3195
  const executors = forRemoval ? {} : await application.executorService?.loadExecutors() ?? {};
2840
3196
  for (const executor of Object.values(executors)) {
2841
3197
  const existing = existingExecutors[executor.name];
2842
3198
  const metaRequest = await buildMetaRequest(trn$3(workspaceId, executor.name), application.name);
3199
+ const desiredExecutor = protoExecutor(application, executor);
2843
3200
  if (existing) {
2844
3201
  if (!existing.label) unmanaged.push({
2845
3202
  resourceType: "Executor",
@@ -2850,11 +3207,12 @@ async function planExecutor(context) {
2850
3207
  resourceName: executor.name,
2851
3208
  currentOwner: existing.label
2852
3209
  });
2853
- changeSet.updates.push({
3210
+ if (existing.label === application.name && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areExecutorsEqual(existing.resource, desiredExecutor)) changeSet.unchanged.push({ name: executor.name });
3211
+ else changeSet.updates.push({
2854
3212
  name: executor.name,
2855
3213
  request: {
2856
3214
  workspaceId,
2857
- executor: protoExecutor(application, executor)
3215
+ executor: desiredExecutor
2858
3216
  },
2859
3217
  metaRequest
2860
3218
  });
@@ -2863,7 +3221,7 @@ async function planExecutor(context) {
2863
3221
  name: executor.name,
2864
3222
  request: {
2865
3223
  workspaceId,
2866
- executor: protoExecutor(application, executor)
3224
+ executor: desiredExecutor
2867
3225
  },
2868
3226
  metaRequest
2869
3227
  });
@@ -2887,6 +3245,56 @@ async function planExecutor(context) {
2887
3245
  resourceOwners
2888
3246
  };
2889
3247
  }
3248
+ function normalizeComparableExecutor(executor) {
3249
+ const normalized = normalizeProtoConfig(executor) ?? {};
3250
+ const webhookHeaders = normalized.targetConfig?.config?.case === "webhook" ? [...normalized.targetConfig.config.value.headers ?? []].sort((left, right) => (left.key ?? "").localeCompare(right.key ?? "")) : void 0;
3251
+ const triggerConfig = normalized.triggerConfig?.config?.case === "incomingWebhook" ? {
3252
+ ...normalized.triggerConfig,
3253
+ config: {
3254
+ ...normalized.triggerConfig.config,
3255
+ value: {}
3256
+ }
3257
+ } : normalized.triggerConfig?.config?.case === "event" ? {
3258
+ ...normalized.triggerConfig,
3259
+ config: {
3260
+ ...normalized.triggerConfig.config,
3261
+ value: {
3262
+ ...normalized.triggerConfig.config.value,
3263
+ eventType: void 0
3264
+ }
3265
+ }
3266
+ } : normalized.triggerConfig;
3267
+ return {
3268
+ name: normalized.name,
3269
+ description: normalized.description ?? "",
3270
+ disabled: normalized.disabled ?? false,
3271
+ triggerType: normalized.triggerType,
3272
+ triggerConfig,
3273
+ targetType: normalized.targetType,
3274
+ targetConfig: normalized.targetConfig?.config?.case === "webhook" ? {
3275
+ ...normalized.targetConfig,
3276
+ config: {
3277
+ ...normalized.targetConfig.config,
3278
+ value: {
3279
+ ...normalized.targetConfig.config.value,
3280
+ headers: webhookHeaders
3281
+ }
3282
+ }
3283
+ } : normalized.targetConfig?.config?.case === "function" ? {
3284
+ ...normalized.targetConfig,
3285
+ config: {
3286
+ ...normalized.targetConfig.config,
3287
+ value: {
3288
+ ...normalized.targetConfig.config.value,
3289
+ script: void 0
3290
+ }
3291
+ }
3292
+ } : normalized.targetConfig
3293
+ };
3294
+ }
3295
+ function areExecutorsEqual(existing, desired) {
3296
+ return areNormalizedEqual(normalizeComparableExecutor(existing), normalizeComparableExecutor(desired));
3297
+ }
2890
3298
  function resolveTailorDBNamespace(application, typeName) {
2891
3299
  for (const service of application.tailorDBServices) if (service.types[typeName]) return service.namespace;
2892
3300
  throw new Error(`TailorDB type "${typeName}" not found in any namespace. Available namespaces: ${application.tailorDBServices.map((s) => s.namespace).join(", ")}`);
@@ -2911,18 +3319,6 @@ function protoExecutor(application, executor) {
2911
3319
  let triggerType;
2912
3320
  let triggerConfig;
2913
3321
  const argsExpr = buildExecutorArgsExpr(trigger.kind, env);
2914
- const eventType = {
2915
- recordCreated: "tailordb.type_record.created",
2916
- recordUpdated: "tailordb.type_record.updated",
2917
- recordDeleted: "tailordb.type_record.deleted",
2918
- resolverExecuted: "pipeline.resolver.executed",
2919
- idpUserCreated: "idp.user.created",
2920
- idpUserUpdated: "idp.user.updated",
2921
- idpUserDeleted: "idp.user.deleted",
2922
- authAccessTokenIssued: "auth.access_token.issued",
2923
- authAccessTokenRefreshed: "auth.access_token.refreshed",
2924
- authAccessTokenRevoked: "auth.access_token.revoked"
2925
- };
2926
3322
  function typedEventTrigger(typedConfig) {
2927
3323
  return { config: {
2928
3324
  case: "event",
@@ -2940,14 +3336,12 @@ function protoExecutor(application, executor) {
2940
3336
  }
2941
3337
  } };
2942
3338
  break;
2943
- case "recordCreated":
2944
- case "recordUpdated":
2945
- case "recordDeleted":
3339
+ case "tailordb":
2946
3340
  triggerType = ExecutorTriggerType.EVENT;
2947
3341
  triggerConfig = typedEventTrigger({
2948
3342
  case: "tailordb",
2949
3343
  value: {
2950
- eventTypes: [eventType[trigger.kind]],
3344
+ eventTypes: trigger.events,
2951
3345
  namespaceName: resolveTailorDBNamespace(application, trigger.typeName),
2952
3346
  typeName: trigger.typeName,
2953
3347
  ...trigger.condition ? { condition: { expr: `(${stringifyFunction(trigger.condition)})(${argsExpr})` } } : {}
@@ -2959,7 +3353,7 @@ function protoExecutor(application, executor) {
2959
3353
  triggerConfig = typedEventTrigger({
2960
3354
  case: "pipeline",
2961
3355
  value: {
2962
- eventTypes: [eventType[trigger.kind]],
3356
+ eventTypes: ["pipeline.resolver.executed"],
2963
3357
  namespaceName: resolveResolverNamespace(application, trigger.resolverName),
2964
3358
  resolverName: trigger.resolverName,
2965
3359
  ...trigger.condition ? { condition: { expr: `(${stringifyFunction(trigger.condition)})(${argsExpr})` } } : {}
@@ -2973,26 +3367,22 @@ function protoExecutor(application, executor) {
2973
3367
  value: {}
2974
3368
  } };
2975
3369
  break;
2976
- case "idpUserCreated":
2977
- case "idpUserUpdated":
2978
- case "idpUserDeleted":
3370
+ case "idpUser":
2979
3371
  triggerType = ExecutorTriggerType.EVENT;
2980
3372
  triggerConfig = typedEventTrigger({
2981
3373
  case: "idp",
2982
3374
  value: {
2983
- eventTypes: [eventType[trigger.kind]],
3375
+ eventTypes: trigger.events,
2984
3376
  namespaceName: resolveIdpNamespace(application)
2985
3377
  }
2986
3378
  });
2987
3379
  break;
2988
- case "authAccessTokenIssued":
2989
- case "authAccessTokenRefreshed":
2990
- case "authAccessTokenRevoked":
3380
+ case "authAccessToken":
2991
3381
  triggerType = ExecutorTriggerType.EVENT;
2992
3382
  triggerConfig = typedEventTrigger({
2993
3383
  case: "auth",
2994
3384
  value: {
2995
- eventTypes: [eventType[trigger.kind]],
3385
+ eventTypes: trigger.events,
2996
3386
  namespaceName: resolveAuthNamespace(application)
2997
3387
  }
2998
3388
  });
@@ -3148,7 +3538,7 @@ async function applyPipeline(client, result, phase = "create-update") {
3148
3538
  * @returns Planned changes
3149
3539
  */
3150
3540
  async function planPipeline(context) {
3151
- const { client, workspaceId, application, forRemoval } = context;
3541
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
3152
3542
  const pipelines = [];
3153
3543
  if (!forRemoval) for (const pipeline of application.resolverServices) {
3154
3544
  await pipeline.loadResolvers();
@@ -3156,7 +3546,7 @@ async function planPipeline(context) {
3156
3546
  }
3157
3547
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
3158
3548
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$1(client, workspaceId, application.name, pipelines);
3159
- const resolverChangeSet = await planResolvers(client, workspaceId, pipelines, executors, serviceChangeSet.deletes.map((del) => del.name), application.env);
3549
+ const resolverChangeSet = await planResolvers(client, workspaceId, pipelines, executors, serviceChangeSet.deletes.map((del) => del.name), application.env, forceApplyAll);
3160
3550
  serviceChangeSet.print();
3161
3551
  resolverChangeSet.print();
3162
3552
  return {
@@ -3196,7 +3586,8 @@ async function planServices$1(client, workspaceId, appName, pipelines) {
3196
3586
  const { metadata } = await client.getMetadata({ trn: trn$2(workspaceId, resource.namespace.name) });
3197
3587
  existingServices[resource.namespace.name] = {
3198
3588
  resource,
3199
- label: metadata?.labels[sdkNameLabelKey]
3589
+ label: metadata?.labels[sdkNameLabelKey],
3590
+ allLabels: metadata?.labels
3200
3591
  };
3201
3592
  }));
3202
3593
  for (const pipeline of pipelines) {
@@ -3212,7 +3603,8 @@ async function planServices$1(client, workspaceId, appName, pipelines) {
3212
3603
  resourceName: pipeline.namespace,
3213
3604
  currentOwner: existing.label
3214
3605
  });
3215
- changeSet.updates.push({
3606
+ if (existing.label === appName && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) changeSet.unchanged.push({ name: pipeline.namespace });
3607
+ else changeSet.updates.push({
3216
3608
  name: pipeline.namespace,
3217
3609
  request: {
3218
3610
  workspaceId,
@@ -3248,7 +3640,7 @@ async function planServices$1(client, workspaceId, appName, pipelines) {
3248
3640
  resourceOwners
3249
3641
  };
3250
3642
  }
3251
- async function planResolvers(client, workspaceId, pipelines, executors, deletedServices, env) {
3643
+ async function planResolvers(client, workspaceId, pipelines, executors, deletedServices, env, forceApplyAll = false) {
3252
3644
  const changeSet = createChangeSet("Pipeline resolvers");
3253
3645
  const fetchResolvers = (namespaceName) => {
3254
3646
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -3271,29 +3663,35 @@ async function planResolvers(client, workspaceId, pipelines, executors, deletedS
3271
3663
  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.`);
3272
3664
  for (const pipeline of pipelines) {
3273
3665
  const existingResolvers = await fetchResolvers(pipeline.namespace);
3274
- const existingNameSet = /* @__PURE__ */ new Set();
3275
- existingResolvers.forEach((resolver) => {
3276
- existingNameSet.add(resolver.name);
3277
- });
3278
- for (const resolver of Object.values(pipeline.resolvers)) if (existingNameSet.has(resolver.name)) {
3279
- changeSet.updates.push({
3666
+ const existingResolversMap = new Map(existingResolvers.map((resolver) => [resolver.name, resolver]));
3667
+ for (const resolver of Object.values(pipeline.resolvers)) {
3668
+ const desiredResolver = processResolver(pipeline.namespace, resolver, executorUsedResolvers, env);
3669
+ if (existingResolversMap.get(resolver.name)) {
3670
+ const { pipelineResolver: existingResolverDetail } = await client.getPipelineResolver({
3671
+ workspaceId,
3672
+ namespaceName: pipeline.namespace,
3673
+ resolverName: resolver.name
3674
+ });
3675
+ if (!forceApplyAll && existingResolverDetail && areResolversEqual(existingResolverDetail, desiredResolver)) changeSet.unchanged.push({ name: resolver.name });
3676
+ else changeSet.updates.push({
3677
+ name: resolver.name,
3678
+ request: {
3679
+ workspaceId,
3680
+ namespaceName: pipeline.namespace,
3681
+ pipelineResolver: desiredResolver
3682
+ }
3683
+ });
3684
+ existingResolversMap.delete(resolver.name);
3685
+ } else changeSet.creates.push({
3280
3686
  name: resolver.name,
3281
3687
  request: {
3282
3688
  workspaceId,
3283
3689
  namespaceName: pipeline.namespace,
3284
- pipelineResolver: processResolver(pipeline.namespace, resolver, executorUsedResolvers, env)
3690
+ pipelineResolver: desiredResolver
3285
3691
  }
3286
3692
  });
3287
- existingNameSet.delete(resolver.name);
3288
- } else changeSet.creates.push({
3289
- name: resolver.name,
3290
- request: {
3291
- workspaceId,
3292
- namespaceName: pipeline.namespace,
3293
- pipelineResolver: processResolver(pipeline.namespace, resolver, executorUsedResolvers, env)
3294
- }
3295
- });
3296
- existingNameSet.forEach((name) => {
3693
+ }
3694
+ existingResolversMap.forEach((_resolver, name) => {
3297
3695
  changeSet.deletes.push({
3298
3696
  name,
3299
3697
  request: {
@@ -3316,6 +3714,59 @@ async function planResolvers(client, workspaceId, pipelines, executors, deletedS
3316
3714
  });
3317
3715
  return changeSet;
3318
3716
  }
3717
+ function normalizeComparableResolver(resolver) {
3718
+ const normalized = normalizeProtoConfig(resolver) ?? {};
3719
+ return {
3720
+ name: normalized.name,
3721
+ description: normalized.description ?? "",
3722
+ authorization: normalized.authorization ?? "",
3723
+ operationType: normalized.operationType,
3724
+ publishExecutionEvents: normalized.publishExecutionEvents ?? false,
3725
+ inputs: normalizeComparableFields(normalized.inputs),
3726
+ response: normalizeComparableField(normalized.response),
3727
+ pipelines: normalizeComparablePipelines(normalized.pipelines)
3728
+ };
3729
+ }
3730
+ function areResolversEqual(existing, desired) {
3731
+ return areNormalizedEqual(normalizeComparableResolver(existing), normalizeComparableResolver(desired));
3732
+ }
3733
+ function normalizeComparablePipelines(pipelines) {
3734
+ return (pipelines ?? []).map((pipeline) => ({
3735
+ name: pipeline.name ?? "",
3736
+ operationName: pipeline.operationName ?? "",
3737
+ description: pipeline.description ?? "",
3738
+ operationType: pipeline.operationType,
3739
+ operationSourceRef: pipeline.operationSourceRef ?? "",
3740
+ operationHook: pipeline.operationHook?.expr ?? "",
3741
+ postScript: pipeline.postScript ?? "",
3742
+ skipOperationOnError: pipeline.skipOperationOnError ?? false,
3743
+ invoker: pipeline.invoker ?? void 0
3744
+ }));
3745
+ }
3746
+ function normalizeComparableFields(fields) {
3747
+ return (fields ?? []).map((field) => normalizeComparableField(field));
3748
+ }
3749
+ function normalizeComparableField(field) {
3750
+ if (!field) return;
3751
+ return {
3752
+ name: field.name ?? "",
3753
+ array: field.array ?? false,
3754
+ required: field.required ?? true,
3755
+ description: field.description ?? "",
3756
+ type: normalizeComparableType(field.type)
3757
+ };
3758
+ }
3759
+ function normalizeComparableType(type) {
3760
+ if (!type) return;
3761
+ return {
3762
+ kind: type.kind ?? "",
3763
+ name: type.name ?? "",
3764
+ required: type.required ?? true,
3765
+ description: type.description ?? "",
3766
+ allowedValues: type.allowedValues ?? [],
3767
+ fields: (type.fields ?? []).map((field) => normalizeComparableField(field))
3768
+ };
3769
+ }
3319
3770
  function processResolver(namespace, resolver, executorUsedResolvers, env) {
3320
3771
  const pipelines = [{
3321
3772
  name: "body",
@@ -3431,7 +3882,7 @@ function hashValue(value) {
3431
3882
  * @returns Planned changes for vaults and secrets
3432
3883
  */
3433
3884
  async function planSecretManager(context) {
3434
- const { client, workspaceId, application, forRemoval } = context;
3885
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
3435
3886
  const secretVaults = forRemoval ? [] : application.secrets;
3436
3887
  const vaultChangeSet = createChangeSet("Secret Manager vaults");
3437
3888
  const secretChangeSet = createChangeSet("Secret Manager secrets");
@@ -3453,10 +3904,11 @@ async function planSecretManager(context) {
3453
3904
  });
3454
3905
  const existingVaults = {};
3455
3906
  await Promise.all(existingVaultList.map(async (resource) => {
3456
- const { metadata } = await client.getMetadata({ trn: vaultTrn(workspaceId, resource.name) });
3907
+ const { metadata } = await client.getMetadata({ trn: vaultTrn$1(workspaceId, resource.name) });
3457
3908
  existingVaults[resource.name] = {
3458
3909
  resource,
3459
- label: metadata?.labels[sdkNameLabelKey]
3910
+ label: metadata?.labels[sdkNameLabelKey],
3911
+ allLabels: metadata?.labels
3460
3912
  };
3461
3913
  }));
3462
3914
  const state = loadSecretsState();
@@ -3464,6 +3916,7 @@ async function planSecretManager(context) {
3464
3916
  const vaultName = vault.vaultName;
3465
3917
  const existing = existingVaults[vaultName];
3466
3918
  if (existing) {
3919
+ const metaRequest = await buildMetaRequest(vaultTrn$1(workspaceId, vaultName), application.name);
3467
3920
  if (!existing.label) unmanaged.push({
3468
3921
  resourceType: "Secret Manager vault",
3469
3922
  resourceName: vaultName
@@ -3473,7 +3926,8 @@ async function planSecretManager(context) {
3473
3926
  resourceName: vaultName,
3474
3927
  currentOwner: existing.label
3475
3928
  });
3476
- vaultChangeSet.updates.push({
3929
+ if (existing.label === application.name && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) vaultChangeSet.unchanged.push({ name: vaultName });
3930
+ else vaultChangeSet.updates.push({
3477
3931
  name: vaultName,
3478
3932
  workspaceId
3479
3933
  });
@@ -3499,7 +3953,9 @@ async function planSecretManager(context) {
3499
3953
  })).map((s) => s.name);
3500
3954
  const existingSet = new Set(existingSecrets);
3501
3955
  for (const secret of vault.secrets) if (existingSet.has(secret.name)) {
3502
- if (hashValue(secret.value) !== state.vaults[vaultName]?.[secret.name]) secretChangeSet.updates.push({
3956
+ const currentHash = hashValue(secret.value);
3957
+ const storedHash = state.vaults[vaultName]?.[secret.name];
3958
+ if (forceApplyAll || currentHash !== storedHash) secretChangeSet.updates.push({
3503
3959
  name: `${vaultName}/${secret.name}`,
3504
3960
  secretName: secret.name,
3505
3961
  workspaceId,
@@ -3562,7 +4018,7 @@ async function planSecretManager(context) {
3562
4018
  resourceOwners
3563
4019
  };
3564
4020
  }
3565
- function vaultTrn(workspaceId, name) {
4021
+ function vaultTrn$1(workspaceId, name) {
3566
4022
  return `trn:v1:workspace:${workspaceId}:vault:${name}`;
3567
4023
  }
3568
4024
  /**
@@ -3582,12 +4038,12 @@ async function applySecretManager(client, result, phase = "create-update", appli
3582
4038
  secretmanagerVaultName: create.name
3583
4039
  });
3584
4040
  if (application) {
3585
- const metaRequest = await buildMetaRequest(vaultTrn(create.workspaceId, create.name), application.name);
4041
+ const metaRequest = await buildMetaRequest(vaultTrn$1(create.workspaceId, create.name), application.name);
3586
4042
  await client.setMetadata(metaRequest);
3587
4043
  }
3588
4044
  }));
3589
4045
  if (application) await Promise.all(vaultChangeSet.updates.map(async (update) => {
3590
- const metaRequest = await buildMetaRequest(vaultTrn(update.workspaceId, update.name), application.name);
4046
+ const metaRequest = await buildMetaRequest(vaultTrn$1(update.workspaceId, update.name), application.name);
3591
4047
  await client.setMetadata(metaRequest);
3592
4048
  }));
3593
4049
  await Promise.all(secretChangeSet.creates.map((create) => client.createSecretManagerSecret({
@@ -3655,6 +4111,21 @@ async function applyStaticWebsite(client, result, phase = "create-update") {
3655
4111
  function trn$1(workspaceId, name) {
3656
4112
  return `trn:v1:workspace:${workspaceId}:staticwebsite:${name}`;
3657
4113
  }
4114
+ function normalizeComparableStaticWebsiteShape(input) {
4115
+ return {
4116
+ description: input.description,
4117
+ allowedIpAddresses: [...input.allowedIpAddresses].sort()
4118
+ };
4119
+ }
4120
+ function normalizeComparableStaticWebsite(input) {
4121
+ return normalizeComparableStaticWebsiteShape({
4122
+ description: input.description || "",
4123
+ allowedIpAddresses: [...input.allowedIpAddresses || []]
4124
+ });
4125
+ }
4126
+ function areStaticWebsitesEqual(existing, desired) {
4127
+ return areNormalizedEqual(normalizeComparableStaticWebsite(existing), normalizeComparableStaticWebsite(desired));
4128
+ }
3658
4129
  /**
3659
4130
  * Plan static website changes based on current and desired state.
3660
4131
  * @param context - Planning context
@@ -3684,7 +4155,8 @@ async function planStaticWebsite(context) {
3684
4155
  const { metadata } = await client.getMetadata({ trn: trn$1(workspaceId, resource.name) });
3685
4156
  existingWebsites[resource.name] = {
3686
4157
  resource,
3687
- label: metadata?.labels[sdkNameLabelKey]
4158
+ label: metadata?.labels[sdkNameLabelKey],
4159
+ allLabels: metadata?.labels
3688
4160
  };
3689
4161
  }));
3690
4162
  const staticWebsiteServices = forRemoval ? [] : application.staticWebsiteServices;
@@ -3693,7 +4165,17 @@ async function planStaticWebsite(context) {
3693
4165
  const name = websiteService.name;
3694
4166
  const existing = existingWebsites[name];
3695
4167
  const metaRequest = await buildMetaRequest(trn$1(workspaceId, name), application.name);
4168
+ const desired = normalizeComparableStaticWebsite(config);
4169
+ const request = {
4170
+ workspaceId,
4171
+ staticwebsite: {
4172
+ name,
4173
+ description: config.description || "",
4174
+ allowedIpAddresses: config.allowedIpAddresses || []
4175
+ }
4176
+ };
3696
4177
  if (existing) {
4178
+ const isManagedByApp = existing.label === application.name;
3697
4179
  if (!existing.label) unmanaged.push({
3698
4180
  resourceType: "StaticWebsite",
3699
4181
  resourceName: name
@@ -3703,29 +4185,16 @@ async function planStaticWebsite(context) {
3703
4185
  resourceName: name,
3704
4186
  currentOwner: existing.label
3705
4187
  });
3706
- changeSet.updates.push({
4188
+ if (isManagedByApp && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areStaticWebsitesEqual(existing.resource, desired)) changeSet.unchanged.push({ name });
4189
+ else changeSet.updates.push({
3707
4190
  name,
3708
- request: {
3709
- workspaceId,
3710
- staticwebsite: {
3711
- name,
3712
- description: config.description || "",
3713
- allowedIpAddresses: config.allowedIpAddresses || []
3714
- }
3715
- },
4191
+ request,
3716
4192
  metaRequest
3717
4193
  });
3718
4194
  delete existingWebsites[name];
3719
4195
  } else changeSet.creates.push({
3720
4196
  name,
3721
- request: {
3722
- workspaceId,
3723
- staticwebsite: {
3724
- name,
3725
- description: config.description || "",
3726
- allowedIpAddresses: config.allowedIpAddresses || []
3727
- }
3728
- },
4197
+ request,
3729
4198
  metaRequest
3730
4199
  });
3731
4200
  }
@@ -3949,8 +4418,11 @@ const INITIAL_SCHEMA_NUMBER = 0;
3949
4418
  * Migration file names (used within migration directories)
3950
4419
  */
3951
4420
  const SCHEMA_FILE_NAME = "schema.json";
4421
+ /** File name for migration diff metadata. */
3952
4422
  const DIFF_FILE_NAME = "diff.json";
4423
+ /** File name for migration script. */
3953
4424
  const MIGRATE_FILE_NAME = "migrate.ts";
4425
+ /** File name for generated DB type definitions. */
3954
4426
  const DB_TYPES_FILE_NAME = "db.ts";
3955
4427
  /**
3956
4428
  * Pattern for validating migration number format (4-digit sequential number)
@@ -5848,7 +6320,7 @@ async function executeSingleMigrationPostPhase(client, changeSet, migration) {
5848
6320
  * @returns Planned changes
5849
6321
  */
5850
6322
  async function planTailorDB(context) {
5851
- const { client, workspaceId, application, forRemoval, config, noSchemaCheck } = context;
6323
+ const { client, workspaceId, application, forRemoval, config, noSchemaCheck, forceApplyAll = false } = context;
5852
6324
  const tailordbs = [];
5853
6325
  if (!forRemoval) for (const tailordb of application.tailorDBServices) {
5854
6326
  await tailordb.loadTypes();
@@ -5857,7 +6329,7 @@ async function planTailorDB(context) {
5857
6329
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
5858
6330
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices(client, workspaceId, application.name, tailordbs);
5859
6331
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
5860
- const [typeChangeSet, gqlPermissionChangeSet] = await Promise.all([planTypes(client, workspaceId, tailordbs, executors, deletedServices), planGqlPermissions(client, workspaceId, tailordbs, deletedServices)]);
6332
+ const [typeChangeSet, gqlPermissionChangeSet] = await Promise.all([planTypes(client, workspaceId, tailordbs, executors, deletedServices, void 0, forceApplyAll), planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll)]);
5861
6333
  serviceChangeSet.print();
5862
6334
  typeChangeSet.print();
5863
6335
  gqlPermissionChangeSet.print();
@@ -5881,6 +6353,21 @@ async function planTailorDB(context) {
5881
6353
  function trn(workspaceId, name) {
5882
6354
  return `${trnPrefix(workspaceId)}:tailordb:${name}`;
5883
6355
  }
6356
+ function normalizeComparableTailorDBService(service) {
6357
+ return normalizeProtoConfig({
6358
+ namespace: service.namespace,
6359
+ defaultTimezone: service.defaultTimezone || "UTC"
6360
+ });
6361
+ }
6362
+ function areTailorDBServicesEqual(existing, desired) {
6363
+ return areNormalizedEqual(normalizeComparableTailorDBService({
6364
+ namespace: existing.namespace?.name,
6365
+ defaultTimezone: existing.defaultTimezone
6366
+ }), normalizeComparableTailorDBService({
6367
+ namespace: desired.namespace,
6368
+ defaultTimezone: "UTC"
6369
+ }));
6370
+ }
5884
6371
  async function planServices(client, workspaceId, appName, tailordbs) {
5885
6372
  const changeSet = createChangeSet("TailorDB services");
5886
6373
  const conflicts = [];
@@ -5922,7 +6409,8 @@ async function planServices(client, workspaceId, appName, tailordbs) {
5922
6409
  resourceName: tailordb.namespace,
5923
6410
  currentOwner: existing.label
5924
6411
  });
5925
- changeSet.updates.push({
6412
+ if (existing.label === appName && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areTailorDBServicesEqual(existing.resource, tailordb)) changeSet.unchanged.push({ name: tailordb.namespace });
6413
+ else changeSet.updates.push({
5926
6414
  name: tailordb.namespace,
5927
6415
  metaRequest
5928
6416
  });
@@ -5955,7 +6443,7 @@ async function planServices(client, workspaceId, appName, tailordbs) {
5955
6443
  resourceOwners
5956
6444
  };
5957
6445
  }
5958
- async function planTypes(client, workspaceId, tailordbs, executors, deletedServices, filteredTypesByNamespace) {
6446
+ async function planTypes(client, workspaceId, tailordbs, executors, deletedServices, filteredTypesByNamespace, forceApplyAll = false) {
5959
6447
  const changeSet = createChangeSet("TailorDB types");
5960
6448
  const fetchTypes = (namespaceName) => {
5961
6449
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -5974,7 +6462,7 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
5974
6462
  });
5975
6463
  };
5976
6464
  const executorUsedTypes = /* @__PURE__ */ new Set();
5977
- for (const executor of executors) if (executor.trigger.kind === "recordCreated" || executor.trigger.kind === "recordUpdated" || executor.trigger.kind === "recordDeleted") executorUsedTypes.add(executor.trigger.typeName);
6465
+ for (const executor of executors) if (executor.trigger.kind === "tailordb") executorUsedTypes.add(executor.trigger.typeName);
5978
6466
  for (const tailordb of tailordbs) {
5979
6467
  const types = filteredTypesByNamespace?.get(tailordb.namespace) ?? tailordb.types;
5980
6468
  for (const typeName of Object.keys(types)) {
@@ -5984,13 +6472,14 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
5984
6472
  }
5985
6473
  for (const tailordb of tailordbs) {
5986
6474
  const existingTypes = await fetchTypes(tailordb.namespace);
5987
- const existingNameSet = /* @__PURE__ */ new Set();
5988
- existingTypes.forEach((type) => existingNameSet.add(type.name));
6475
+ const existingTypesMap = new Map(existingTypes.map((type) => [type.name, type]));
5989
6476
  const types = filteredTypesByNamespace?.get(tailordb.namespace) ?? tailordb.types;
5990
6477
  for (const typeName of Object.keys(types)) {
5991
6478
  const tailordbType = generateTailorDBTypeManifest(types[typeName], executorUsedTypes, tailordb.config.gqlOperations);
5992
- if (existingNameSet.has(typeName)) {
5993
- changeSet.updates.push({
6479
+ const existingType = existingTypesMap.get(typeName);
6480
+ if (existingType) {
6481
+ if (!forceApplyAll && areNormalizedEqual(normalizeComparableTailorDBType(existingType), normalizeComparableTailorDBType(tailordbType))) changeSet.unchanged.push({ name: typeName });
6482
+ else changeSet.updates.push({
5994
6483
  name: typeName,
5995
6484
  request: {
5996
6485
  workspaceId,
@@ -5998,7 +6487,7 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
5998
6487
  tailordbType
5999
6488
  }
6000
6489
  });
6001
- existingNameSet.delete(typeName);
6490
+ existingTypesMap.delete(typeName);
6002
6491
  } else changeSet.creates.push({
6003
6492
  name: typeName,
6004
6493
  request: {
@@ -6008,7 +6497,7 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
6008
6497
  }
6009
6498
  });
6010
6499
  }
6011
- existingNameSet.forEach((name) => {
6500
+ existingTypesMap.forEach((_type, name) => {
6012
6501
  changeSet.deletes.push({
6013
6502
  name,
6014
6503
  request: {
@@ -6031,6 +6520,66 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
6031
6520
  });
6032
6521
  return changeSet;
6033
6522
  }
6523
+ function isPlainObject(value) {
6524
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6525
+ }
6526
+ const tailordbCompareKnownDefaults = {
6527
+ disableGqlOperations: {
6528
+ create: false,
6529
+ update: false,
6530
+ delete: false,
6531
+ read: false
6532
+ },
6533
+ emptyExpression: "",
6534
+ numericStringPaths: new Set([
6535
+ "schema.fields.*.serial.start",
6536
+ "schema.fields.*.serial.maxValue",
6537
+ "schema.settings.defaultQueryLimitSize",
6538
+ "schema.settings.maxBulkUpsertSize"
6539
+ ])
6540
+ };
6541
+ function normalizeComparableTailorDBType(type) {
6542
+ const normalized = normalizeProtoConfig(type);
6543
+ return normalizeTailorDBCompareValue({
6544
+ name: normalized?.name ?? "",
6545
+ schema: {
6546
+ description: normalized?.schema?.description ?? "",
6547
+ fields: normalized?.schema?.fields ?? {},
6548
+ relationships: normalized?.schema?.relationships ?? {},
6549
+ settings: normalized?.schema?.settings ?? {},
6550
+ indexes: normalized?.schema?.indexes ?? {},
6551
+ files: normalized?.schema?.files ?? {},
6552
+ permission: normalized?.schema?.permission ?? {}
6553
+ }
6554
+ }, []);
6555
+ }
6556
+ function normalizeTailorDBCompareValue(value, path) {
6557
+ if (value === void 0 || value === null) return value;
6558
+ if (typeof value === "number" || typeof value === "bigint" || typeof value === "string") {
6559
+ if (matchesNumericStringPath(path) && isNumericLikeValue(value)) return String(value);
6560
+ if (path.at(-1) === "expr" && value === tailordbCompareKnownDefaults.emptyExpression) return;
6561
+ return value;
6562
+ }
6563
+ if (Array.isArray(value)) return value.map((item, index) => normalizeTailorDBCompareValue(item, [...path, index])).filter((item) => item !== void 0);
6564
+ if (!isPlainObject(value)) return value;
6565
+ const normalizedEntries = Object.entries(value).map(([key, entryValue]) => [key, normalizeTailorDBCompareValue(entryValue, [...path, key])]).filter(([, entryValue]) => entryValue !== void 0);
6566
+ const normalizedObject = Object.fromEntries(normalizedEntries);
6567
+ if (path.at(-1) === "fields" && Object.keys(normalizedObject).length === 0) return;
6568
+ if (path.at(-1) === "disableGqlOperations" && areNormalizedEqual(normalizedObject, tailordbCompareKnownDefaults.disableGqlOperations)) return;
6569
+ return normalizedObject;
6570
+ }
6571
+ function matchesNumericStringPath(path) {
6572
+ const pathKey = path.map((segment) => String(segment)).join(".");
6573
+ return [...tailordbCompareKnownDefaults.numericStringPaths].some((pattern) => {
6574
+ const patternParts = pattern.split(".");
6575
+ const pathParts = pathKey.split(".");
6576
+ if (patternParts.length !== pathParts.length) return false;
6577
+ return patternParts.every((part, index) => part === "*" || part === pathParts[index]);
6578
+ });
6579
+ }
6580
+ function isNumericLikeValue(value) {
6581
+ return typeof value === "number" || typeof value === "bigint" || /^-?\d+$/.test(value);
6582
+ }
6034
6583
  /**
6035
6584
  * Generate a TailorDB type manifest from parsed type
6036
6585
  * @param {TailorDBType} type - Parsed TailorDB type
@@ -6267,7 +6816,7 @@ function protoOperand(operand) {
6267
6816
  value: fromJson(ValueSchema, operand)
6268
6817
  } };
6269
6818
  }
6270
- async function planGqlPermissions(client, workspaceId, tailordbs, deletedServices) {
6819
+ async function planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll = false) {
6271
6820
  const changeSet = createChangeSet("TailorDB gqlPermissions");
6272
6821
  const fetchGqlPermissions = (namespaceName) => {
6273
6822
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -6295,14 +6844,17 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
6295
6844
  for (const typeName of Object.keys(types)) {
6296
6845
  const gqlPermission = types[typeName].permissions.gql;
6297
6846
  if (!gqlPermission) continue;
6847
+ const desiredPermission = protoGqlPermission(gqlPermission);
6848
+ const existingPermission = existingGqlPermissions.find((entry) => entry.typeName === typeName);
6298
6849
  if (existingNameSet.has(typeName)) {
6299
- changeSet.updates.push({
6850
+ if (!forceApplyAll && existingPermission && areNormalizedEqual(normalizeComparableGqlPermission(existingPermission.permission), normalizeComparableGqlPermission(desiredPermission))) changeSet.unchanged.push({ name: typeName });
6851
+ else changeSet.updates.push({
6300
6852
  name: typeName,
6301
6853
  request: {
6302
6854
  workspaceId,
6303
6855
  namespaceName: tailordb.namespace,
6304
6856
  typeName,
6305
- permission: protoGqlPermission(gqlPermission)
6857
+ permission: desiredPermission
6306
6858
  }
6307
6859
  });
6308
6860
  existingNameSet.delete(typeName);
@@ -6312,7 +6864,7 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
6312
6864
  workspaceId,
6313
6865
  namespaceName: tailordb.namespace,
6314
6866
  typeName,
6315
- permission: protoGqlPermission(gqlPermission)
6867
+ permission: desiredPermission
6316
6868
  }
6317
6869
  });
6318
6870
  }
@@ -6339,6 +6891,12 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
6339
6891
  });
6340
6892
  return changeSet;
6341
6893
  }
6894
+ function normalizeComparableGqlPermission(permission) {
6895
+ return { policies: (normalizeProtoConfig(permission)?.policies ?? []).map((policy) => ({
6896
+ ...policy,
6897
+ actions: [...policy.actions ?? []].sort((left, right) => left - right)
6898
+ })) };
6899
+ }
6342
6900
  function protoGqlPermission(permission) {
6343
6901
  return { policies: permission.map((policy) => protoGqlPolicy(policy)) };
6344
6902
  }
@@ -6503,7 +7061,7 @@ function formatMigrationCheckResults(results) {
6503
7061
  async function applyWorkflow(client, result, phase = "create-update") {
6504
7062
  const { changeSet, appName } = result;
6505
7063
  if (phase === "create-update") {
6506
- const jobFunctionVersions = await registerJobFunctions(client, changeSet, appName);
7064
+ const jobFunctionVersions = await registerJobFunctions(client, changeSet, appName, result.unchangedWorkflowJobNames);
6507
7065
  await Promise.all([...changeSet.creates.map(async (create) => {
6508
7066
  const filteredVersions = filterJobFunctionVersions(jobFunctionVersions, create.usedJobNames);
6509
7067
  await client.createWorkflow({
@@ -6549,14 +7107,16 @@ function filterJobFunctionVersions(allVersions, usedJobNames) {
6549
7107
  * @param client - Operator client instance
6550
7108
  * @param changeSet - Workflow change set
6551
7109
  * @param appName - Application name
7110
+ * @param unchangedWorkflowJobNames - Job function names used by unchanged workflows
6552
7111
  * @returns Map of job function names to versions
6553
7112
  */
6554
- async function registerJobFunctions(client, changeSet, appName) {
7113
+ async function registerJobFunctions(client, changeSet, appName, unchangedWorkflowJobNames = /* @__PURE__ */ new Set()) {
6555
7114
  const jobFunctionVersions = {};
6556
- const firstWorkflow = changeSet.creates[0] || changeSet.updates[0];
7115
+ const firstWorkflow = changeSet.creates[0] || changeSet.updates[0] || changeSet.deletes[0];
6557
7116
  if (!firstWorkflow) return jobFunctionVersions;
6558
7117
  const { workspaceId } = firstWorkflow;
6559
7118
  const allUsedJobNames = /* @__PURE__ */ new Set();
7119
+ unchangedWorkflowJobNames.forEach((jobName) => allUsedJobNames.add(jobName));
6560
7120
  for (const item of [...changeSet.creates, ...changeSet.updates]) for (const jobName of item.usedJobNames) allUsedJobNames.add(jobName);
6561
7121
  const existingJobFunctions = await fetchAll(async (pageToken, maxPageSize) => {
6562
7122
  const response = await client.listWorkflowJobFunctions({
@@ -6567,23 +7127,25 @@ async function registerJobFunctions(client, changeSet, appName) {
6567
7127
  return [response.jobFunctions.map((j) => j.name), response.nextPageToken];
6568
7128
  });
6569
7129
  const existingJobNamesSet = new Set(existingJobFunctions);
6570
- const results = await Promise.all(Array.from(allUsedJobNames).map(async (jobName) => {
6571
- const response = existingJobNamesSet.has(jobName) ? await client.updateWorkflowJobFunction({
6572
- workspaceId,
6573
- jobFunctionName: jobName,
6574
- scriptRef: workflowJobFunctionName(jobName)
6575
- }) : await client.createWorkflowJobFunction({
6576
- workspaceId,
6577
- jobFunctionName: jobName,
6578
- scriptRef: workflowJobFunctionName(jobName)
6579
- });
6580
- await client.setMetadata(await buildMetaRequest(jobFunctionTrn(workspaceId, jobName), appName));
6581
- return {
6582
- jobName,
6583
- version: response.jobFunction?.version
6584
- };
6585
- }));
6586
- for (const { jobName, version } of results) if (version) jobFunctionVersions[jobName] = version;
7130
+ if (changeSet.creates.length > 0 || changeSet.updates.length > 0) {
7131
+ const results = await Promise.all(Array.from(allUsedJobNames).map(async (jobName) => {
7132
+ const response = existingJobNamesSet.has(jobName) ? await client.updateWorkflowJobFunction({
7133
+ workspaceId,
7134
+ jobFunctionName: jobName,
7135
+ scriptRef: workflowJobFunctionName(jobName)
7136
+ }) : await client.createWorkflowJobFunction({
7137
+ workspaceId,
7138
+ jobFunctionName: jobName,
7139
+ scriptRef: workflowJobFunctionName(jobName)
7140
+ });
7141
+ await client.setMetadata(await buildMetaRequest(jobFunctionTrn(workspaceId, jobName), appName));
7142
+ return {
7143
+ jobName,
7144
+ version: response.jobFunction?.version
7145
+ };
7146
+ }));
7147
+ for (const { jobName, version } of results) if (version) jobFunctionVersions[jobName] = version;
7148
+ }
6587
7149
  const unusedJobFunctions = existingJobFunctions.filter((jobName) => !allUsedJobNames.has(jobName));
6588
7150
  await Promise.all(unusedJobFunctions.map(async (jobName) => {
6589
7151
  const { metadata } = await client.getMetadata({ trn: jobFunctionTrn(workspaceId, jobName) });
@@ -6611,7 +7173,7 @@ function toRetryPolicy(policy) {
6611
7173
  backoffMultiplier: policy.backoffMultiplier
6612
7174
  };
6613
7175
  }
6614
- function workflowTrn(workspaceId, name) {
7176
+ function workflowTrn$1(workspaceId, name) {
6615
7177
  return `trn:v1:workspace:${workspaceId}:workflow:${name}`;
6616
7178
  }
6617
7179
  function jobFunctionTrn(workspaceId, name) {
@@ -6624,35 +7186,35 @@ function jobFunctionTrn(workspaceId, name) {
6624
7186
  * @param appName - Application name
6625
7187
  * @param workflows - Parsed workflows
6626
7188
  * @param mainJobDeps - Main job dependencies by workflow
7189
+ * @param unchangedJobFunctions - Job functions already proven unchanged by function registry plan
6627
7190
  * @returns Planned workflow changes
6628
7191
  */
6629
- async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps) {
7192
+ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps, unchangedJobFunctions = /* @__PURE__ */ new Set()) {
6630
7193
  const changeSet = createChangeSet("Workflows");
6631
7194
  const conflicts = [];
6632
7195
  const unmanaged = [];
6633
7196
  const resourceOwners = /* @__PURE__ */ new Set();
7197
+ const unchangedWorkflowJobNames = /* @__PURE__ */ new Set();
6634
7198
  const withoutLabel = await fetchAll(async (pageToken, maxPageSize) => {
6635
7199
  const response = await client.listWorkflows({
6636
7200
  workspaceId,
6637
7201
  pageToken,
6638
7202
  pageSize: maxPageSize
6639
7203
  });
6640
- return [response.workflows.map((w) => ({
6641
- id: w.id,
6642
- name: w.name
6643
- })), response.nextPageToken];
7204
+ return [response.workflows, response.nextPageToken];
6644
7205
  });
6645
7206
  const existingWorkflows = {};
6646
7207
  await Promise.all(withoutLabel.map(async (resource) => {
6647
- const { metadata } = await client.getMetadata({ trn: workflowTrn(workspaceId, resource.name) });
7208
+ const { metadata } = await client.getMetadata({ trn: workflowTrn$1(workspaceId, resource.name) });
6648
7209
  existingWorkflows[resource.name] = {
6649
7210
  resource,
6650
- label: metadata?.labels[sdkNameLabelKey]
7211
+ label: metadata?.labels[sdkNameLabelKey],
7212
+ allLabels: metadata?.labels
6651
7213
  };
6652
7214
  }));
6653
7215
  for (const workflow of Object.values(workflows)) {
6654
7216
  const existing = existingWorkflows[workflow.name];
6655
- const metaRequest = await buildMetaRequest(workflowTrn(workspaceId, workflow.name), appName);
7217
+ const metaRequest = await buildMetaRequest(workflowTrn$1(workspaceId, workflow.name), appName);
6656
7218
  const usedJobNames = mainJobDeps[workflow.mainJob.name];
6657
7219
  if (!usedJobNames) throw new Error(`Job "${workflow.mainJob.name}" (mainJob of workflow "${workflow.name}") was not found.\n\nPossible causes:\n - The job is not exported as a named export\n - The file containing the job is not included in workflow.files glob pattern\n\nSolution:\n export const ${workflow.mainJob.name} = createWorkflowJob({ name: "${workflow.mainJob.name}", ... })`);
6658
7220
  if (existing) {
@@ -6665,7 +7227,10 @@ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps
6665
7227
  resourceName: workflow.name,
6666
7228
  currentOwner: existing.label
6667
7229
  });
6668
- changeSet.updates.push({
7230
+ if (existing.label === appName && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && canTreatWorkflowAsUnchanged(existing.resource, workflow, usedJobNames, unchangedJobFunctions)) {
7231
+ changeSet.unchanged.push({ name: workflow.name });
7232
+ for (const jobName of usedJobNames) unchangedWorkflowJobNames.add(jobName);
7233
+ } else changeSet.updates.push({
6669
7234
  name: workflow.name,
6670
7235
  workspaceId,
6671
7236
  workflow,
@@ -6696,12 +7261,158 @@ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps
6696
7261
  conflicts,
6697
7262
  unmanaged,
6698
7263
  resourceOwners,
6699
- appName
7264
+ appName,
7265
+ unchangedWorkflowJobNames
7266
+ };
7267
+ }
7268
+ function canTreatWorkflowAsUnchanged(existing, workflow, usedJobNames, unchangedJobFunctions) {
7269
+ if (!usedJobNames.every((jobName) => unchangedJobFunctions.has(jobName))) return false;
7270
+ return areWorkflowsEqual(existing, workflow, usedJobNames);
7271
+ }
7272
+ function areWorkflowsEqual(existing, workflow, usedJobNames) {
7273
+ return existing.mainJobFunctionName === workflow.mainJob.name && areNormalizedEqual(normalizeComparableExistingWorkflowRetryPolicy(existing.retryPolicy), normalizeComparableWorkflowRetryPolicy(workflow.retryPolicy)) && areNormalizedEqual(normalizeComparableWorkflowJobNames(existing.jobFunctions), normalizeComparableWorkflowJobNames(usedJobNames));
7274
+ }
7275
+ function normalizeComparableExistingWorkflowRetryPolicy(policy) {
7276
+ if (!policy) return;
7277
+ return normalizeRetryPolicyForCompare({
7278
+ maxRetries: policy.maxRetries ?? 0,
7279
+ backoffMultiplier: policy.backoffMultiplier ?? 0,
7280
+ initialBackoff: {
7281
+ seconds: policy.initialBackoff?.seconds ?? 0n,
7282
+ nanos: policy.initialBackoff?.nanos ?? 0
7283
+ },
7284
+ maxBackoff: {
7285
+ seconds: policy.maxBackoff?.seconds ?? 0n,
7286
+ nanos: policy.maxBackoff?.nanos ?? 0
7287
+ }
7288
+ });
7289
+ }
7290
+ function normalizeComparableWorkflowRetryPolicy(policy) {
7291
+ if (!policy) return;
7292
+ return normalizeRetryPolicyForCompare({
7293
+ maxRetries: policy.maxRetries,
7294
+ backoffMultiplier: policy.backoffMultiplier,
7295
+ initialBackoff: parseDurationToProto(policy.initialBackoff),
7296
+ maxBackoff: parseDurationToProto(policy.maxBackoff)
7297
+ });
7298
+ }
7299
+ function normalizeComparableWorkflowJobNames(jobFunctions) {
7300
+ return Array.isArray(jobFunctions) ? [...jobFunctions].sort() : Object.keys(jobFunctions ?? {}).sort();
7301
+ }
7302
+ function normalizeRetryPolicyForCompare(policy) {
7303
+ return {
7304
+ maxRetries: policy.maxRetries,
7305
+ backoffMultiplier: policy.backoffMultiplier,
7306
+ initialBackoff: {
7307
+ seconds: String(policy.initialBackoff.seconds),
7308
+ nanos: policy.initialBackoff.nanos
7309
+ },
7310
+ maxBackoff: {
7311
+ seconds: String(policy.maxBackoff.seconds),
7312
+ nanos: policy.maxBackoff.nanos
7313
+ }
6700
7314
  };
6701
7315
  }
6702
7316
 
6703
7317
  //#endregion
6704
7318
  //#region src/cli/commands/apply/apply.ts
7319
+ function applicationTrn(workspaceId, name) {
7320
+ return `trn:v1:workspace:${workspaceId}:application:${name}`;
7321
+ }
7322
+ function functionRegistryTrn(workspaceId, name) {
7323
+ return `trn:v1:workspace:${workspaceId}:function_registry:${name}`;
7324
+ }
7325
+ function pipelineTrn(workspaceId, name) {
7326
+ return `trn:v1:workspace:${workspaceId}:pipeline:${name}`;
7327
+ }
7328
+ function idpTrn(workspaceId, name) {
7329
+ return `trn:v1:workspace:${workspaceId}:idp:${name}`;
7330
+ }
7331
+ function authTrn(workspaceId, name) {
7332
+ return `trn:v1:workspace:${workspaceId}:auth:${name}`;
7333
+ }
7334
+ function executorTrn(workspaceId, name) {
7335
+ return `trn:v1:workspace:${workspaceId}:executor:${name}`;
7336
+ }
7337
+ function workflowTrn(workspaceId, name) {
7338
+ return `trn:v1:workspace:${workspaceId}:workflow:${name}`;
7339
+ }
7340
+ function staticWebsiteTrn(workspaceId, name) {
7341
+ return `trn:v1:workspace:${workspaceId}:staticwebsite:${name}`;
7342
+ }
7343
+ function tailorDBTrn(workspaceId, name) {
7344
+ return `trn:v1:workspace:${workspaceId}:tailordb:${name}`;
7345
+ }
7346
+ function vaultTrn(workspaceId, name) {
7347
+ return `trn:v1:workspace:${workspaceId}:vault:${name}`;
7348
+ }
7349
+ async function shouldForceApplyAll(client, workspaceId, application, functionEntries) {
7350
+ const desiredLabels = (await buildMetaRequest(applicationTrn(workspaceId, application.name), application.name)).labels;
7351
+ const candidateTrns = /* @__PURE__ */ new Set();
7352
+ if (application.subgraphs.length > 0) candidateTrns.add(applicationTrn(workspaceId, application.name));
7353
+ application.staticWebsiteServices.forEach((website) => {
7354
+ candidateTrns.add(staticWebsiteTrn(workspaceId, website.name));
7355
+ });
7356
+ application.resolverServices.forEach((pipeline) => {
7357
+ candidateTrns.add(pipelineTrn(workspaceId, pipeline.namespace));
7358
+ });
7359
+ application.idpServices.forEach((idp) => {
7360
+ candidateTrns.add(idpTrn(workspaceId, idp.name));
7361
+ });
7362
+ if (application.authService) candidateTrns.add(authTrn(workspaceId, application.authService.config.name));
7363
+ Object.values(application.executorService?.executors ?? {}).forEach((executor) => {
7364
+ candidateTrns.add(executorTrn(workspaceId, executor.name));
7365
+ });
7366
+ Object.values(application.workflowService?.workflows ?? {}).forEach((workflow) => {
7367
+ candidateTrns.add(workflowTrn(workspaceId, workflow.name));
7368
+ });
7369
+ application.tailorDBServices.forEach((service) => {
7370
+ candidateTrns.add(tailorDBTrn(workspaceId, service.namespace));
7371
+ });
7372
+ application.secrets.forEach((vault) => {
7373
+ candidateTrns.add(vaultTrn(workspaceId, vault.vaultName));
7374
+ });
7375
+ functionEntries.forEach((entry) => {
7376
+ candidateTrns.add(functionRegistryTrn(workspaceId, entry.name));
7377
+ });
7378
+ for (const trn of candidateTrns) try {
7379
+ const { metadata } = await client.getMetadata({ trn });
7380
+ if (metadata?.labels?.["sdk-name"] !== application.name) continue;
7381
+ if (!hasMatchingSdkVersion(metadata.labels, desiredLabels)) return true;
7382
+ } catch (error) {
7383
+ if (error instanceof ConnectError && error.code === Code.NotFound) continue;
7384
+ throw error;
7385
+ }
7386
+ return false;
7387
+ }
7388
+ function printPlanSummary(results) {
7389
+ const summary = summarizeChangeSets([
7390
+ results.functionRegistry.changeSet,
7391
+ results.tailorDB.changeSet.service,
7392
+ results.tailorDB.changeSet.type,
7393
+ results.tailorDB.changeSet.gqlPermission,
7394
+ results.staticWebsite.changeSet,
7395
+ results.idp.changeSet.service,
7396
+ results.idp.changeSet.client,
7397
+ results.auth.changeSet.service,
7398
+ results.auth.changeSet.idpConfig,
7399
+ results.auth.changeSet.userProfileConfig,
7400
+ results.auth.changeSet.tenantConfig,
7401
+ results.auth.changeSet.machineUser,
7402
+ results.auth.changeSet.oauth2Client,
7403
+ results.auth.changeSet.authHook,
7404
+ results.auth.changeSet.scim,
7405
+ results.auth.changeSet.scimResource,
7406
+ results.pipeline.changeSet.service,
7407
+ results.pipeline.changeSet.resolver,
7408
+ results.app,
7409
+ results.executor.changeSet,
7410
+ results.workflow.changeSet,
7411
+ results.secretManager.vaultChangeSet,
7412
+ results.secretManager.secretChangeSet
7413
+ ]);
7414
+ logger.log(formatPlanSummary(summary));
7415
+ }
6705
7416
  /**
6706
7417
  * Apply the configured application to the Tailor platform.
6707
7418
  * @param options - Options for apply execution
@@ -6775,9 +7486,10 @@ async function apply(options) {
6775
7486
  rootSpan.setAttribute("app.name", application.name);
6776
7487
  rootSpan.setAttribute("workspace.id", workspaceId);
6777
7488
  const workflowService = application.workflowService;
6778
- const functionEntries = collectFunctionEntries(application, workflowService?.jobs ?? [], bundledScripts);
7489
+ const functionEntries = collectFunctionEntries(application, filterBundledWorkflowJobs(workflowService?.jobs ?? [], workflowBuildResult?.usedJobNames ?? []), bundledScripts);
6779
7490
  const dryRun = options?.dryRun ?? false;
6780
7491
  const yes = options?.yes ?? false;
7492
+ const forceApplyAll = await withSpan("plan.detectSdkVersionChange", () => shouldForceApplyAll(client, workspaceId, application, functionEntries));
6781
7493
  const { functionRegistry, tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, secretManager } = await withSpan("plan", async () => {
6782
7494
  const ctx = {
6783
7495
  client,
@@ -6785,10 +7497,12 @@ async function apply(options) {
6785
7497
  application,
6786
7498
  forRemoval: false,
6787
7499
  config,
6788
- noSchemaCheck: options?.noSchemaCheck
7500
+ noSchemaCheck: options?.noSchemaCheck,
7501
+ forceApplyAll
6789
7502
  };
6790
- const [functionRegistry, tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, secretManager] = await Promise.all([
6791
- withSpan("plan.functionRegistry", () => planFunctionRegistry(client, workspaceId, application.name, functionEntries)),
7503
+ const functionRegistry = await withSpan("plan.functionRegistry", () => planFunctionRegistry(client, workspaceId, application.name, functionEntries));
7504
+ const unchangedWorkflowJobs = new Set(functionRegistry.changeSet.unchanged.map((entry) => entry.name).filter((name) => name.startsWith("workflow--")).map((name) => name.slice(10)));
7505
+ const [tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, secretManager] = await Promise.all([
6792
7506
  withSpan("plan.tailorDB", () => planTailorDB(ctx)),
6793
7507
  withSpan("plan.staticWebsite", () => planStaticWebsite(ctx)),
6794
7508
  withSpan("plan.idp", () => planIdP(ctx)),
@@ -6796,7 +7510,7 @@ async function apply(options) {
6796
7510
  withSpan("plan.pipeline", () => planPipeline(ctx)),
6797
7511
  withSpan("plan.application", () => planApplication(ctx)),
6798
7512
  withSpan("plan.executor", () => planExecutor(ctx)),
6799
- withSpan("plan.workflow", () => planWorkflow(client, workspaceId, application.name, workflowService?.workflows ?? {}, workflowBuildResult?.mainJobDeps ?? {})),
7513
+ withSpan("plan.workflow", () => planWorkflow(client, workspaceId, application.name, workflowService?.workflows ?? {}, workflowBuildResult?.mainJobDeps ?? {}, unchangedWorkflowJobs)),
6800
7514
  withSpan("plan.secretManager", () => planSecretManager(ctx))
6801
7515
  ]);
6802
7516
  return {
@@ -6882,6 +7596,18 @@ async function apply(options) {
6882
7596
  }
6883
7597
  });
6884
7598
  });
7599
+ printPlanSummary({
7600
+ functionRegistry,
7601
+ tailorDB,
7602
+ staticWebsite,
7603
+ idp,
7604
+ auth,
7605
+ pipeline,
7606
+ app,
7607
+ executor,
7608
+ workflow,
7609
+ secretManager
7610
+ });
6885
7611
  if (dryRun) {
6886
7612
  logger.info("Dry run enabled. No changes applied.");
6887
7613
  return;
@@ -10965,7 +11691,7 @@ async function generate(options) {
10965
11691
  if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
10966
11692
  let pluginManager;
10967
11693
  if (plugins.length > 0) pluginManager = new PluginManager(plugins);
10968
- const { defineApplication } = await import("./application-Cwt_ifTT.mjs");
11694
+ const { defineApplication } = await import("./application-dnB8CQiT.mjs");
10969
11695
  const application = defineApplication({
10970
11696
  config,
10971
11697
  pluginManager
@@ -13170,4 +13896,4 @@ function isDeno() {
13170
13896
 
13171
13897
  //#endregion
13172
13898
  export { getFolder as $, getNextMigrationNumber as $t, listWorkflows as A, functionExecutionStatusToString as At, updateCommand$1 as B, DB_TYPES_FILE_NAME as Bt, listApps as C, startCommand as Ct, resumeCommand as D, executionsCommand as Dt, healthCommand as E, getWorkflow as Et, show as F, executeScript as Ft, listOrganizations as G, compareLocalTypesWithSnapshot as Gt, organizationTree as H, INITIAL_SCHEMA_NUMBER as Ht, showCommand as I, waitForExecution$1 as It, updateCommand$2 as J, formatMigrationNumber as Jt, getCommand$1 as K, compareSnapshots as Kt, logBetaWarning as L, MIGRATION_LABEL_KEY as Lt, truncateCommand as M, getCommand$5 as Mt, generate as N, getExecutor as Nt, resumeWorkflow as O, getWorkflowExecution as Ot, generateCommand as P, apply as Pt, getCommand$2 as Q, getMigrationFiles as Qt, remove as R, parseMigrationLabelNumber as Rt, createWorkspace as S, watchExecutorJob as St, getAppHealth as T, getCommand$4 as Tt, treeCommand as U, MIGRATE_FILE_NAME as Ut, updateOrganization as V, DIFF_FILE_NAME as Vt, listCommand$4 as W, SCHEMA_FILE_NAME as Wt, listCommand$5 as X, getMigrationDirPath as Xt, updateFolder as Y, getLatestMigrationNumber as Yt, listFolders as Z, getMigrationFilePath as Zt, getCommand as _, isVerbose as _n, listCommand$8 as _t, updateCommand as a, hasChanges as an, listOAuth2Clients as at, deleteWorkspace as b, jobsCommand as bt, removeUser as c, sdkNameLabelKey as cn, getMachineUserToken as ct, inviteCommand as d, apiCall as dn, listMachineUsers as dt, isValidMigrationNumber as en, deleteCommand$1 as et, inviteUser as f, apiCommand as fn, generate$1 as ft, listWorkspaces as g, deploymentArgs as gn, triggerExecutor as gt, listCommand$1 as h, confirmationArgs as hn, triggerCommand as ht, isCLIError as i, formatMigrationDiff as in, listCommand$6 as it, truncate as j, formatKeyValueTable as jt, listCommand$3 as k, listWorkflowExecutions as kt, listCommand as l, trnPrefix as ln, tokenCommand as lt, restoreWorkspace as m, commonArgs as mn, webhookCommand as mt, query as n, reconstructSnapshotFromMigrations as nn, createCommand$1 as nt, updateUser as o, getNamespacesWithMigrations as on, getCommand$3 as ot, restoreCommand as p, defineAppCommand as pn, listWebhookExecutors as pt, getOrganization as q, createSnapshotFromLocalTypes as qt, queryCommand as r, formatDiffSummary as rn, createFolder as rt, removeCommand as s, prompt as sn, getOAuth2Client as st, isNativeTypeScriptRuntime as t, loadDiff as tn, deleteFolder as tt, listUsers as u, generateUserTypes as un, listCommand$7 as ut, getWorkspace as v, workspaceArgs as vn, listExecutors as vt, listCommand$2 as w, startWorkflow as wt, createCommand as x, listExecutorJobs as xt, deleteCommand as y, getExecutorJob as yt, removeCommand$1 as z, bundleMigrationScript as zt };
13173
- //# sourceMappingURL=runtime-7DOyTTxR.mjs.map
13899
+ //# sourceMappingURL=runtime-CDvdBV66.mjs.map