@tailor-platform/sdk 1.32.1 → 1.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/{application-p2GujXmg.mjs → application-CfAom-vi.mjs} +33 -25
  3. package/dist/application-CfAom-vi.mjs.map +1 -0
  4. package/dist/{application-Cwt_ifTT.mjs → application-ChVyhwe-.mjs} +4 -4
  5. package/dist/cli/index.mjs +9 -9
  6. package/dist/cli/index.mjs.map +1 -1
  7. package/dist/cli/lib.d.mts +9 -6
  8. package/dist/cli/lib.mjs +7 -7
  9. package/dist/configure/index.d.mts +5 -5
  10. package/dist/configure/index.mjs +81 -11
  11. package/dist/configure/index.mjs.map +1 -1
  12. package/dist/{enum-constants-CkKARYb7.mjs → enum-constants-Piv_E-2M.mjs} +2 -1
  13. package/dist/{enum-constants-CkKARYb7.mjs.map → enum-constants-Piv_E-2M.mjs.map} +1 -1
  14. package/dist/{env-BZLTIlIo.d.mts → env-CSZ9CKg7.d.mts} +2 -2
  15. package/dist/{file-utils-D2TxR_kj.mjs → file-utils-B7xME5IK.mjs} +2 -1
  16. package/dist/{file-utils-D2TxR_kj.mjs.map → file-utils-B7xME5IK.mjs.map} +1 -1
  17. package/dist/{index-BYk_9R3S.d.mts → index-BdlrrjvD.d.mts} +133 -24
  18. package/dist/{index-BQKAzTPA.d.mts → index-Dlpe_4Nd.d.mts} +3 -2
  19. package/dist/{index-vVGamLOw.d.mts → index-IHl7P_9I.d.mts} +3 -2
  20. package/dist/{index-CgMytw2A.d.mts → index-dg3Sf-No.d.mts} +3 -2
  21. package/dist/{index-CoReoodF.d.mts → index-uNv9YJgx.d.mts} +3 -2
  22. package/dist/{kysely-type-BK0b4Rqt.mjs → kysely-type-t5MbP7iJ.mjs} +2 -1
  23. package/dist/{kysely-type-BK0b4Rqt.mjs.map → kysely-type-t5MbP7iJ.mjs.map} +1 -1
  24. package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
  25. package/dist/plugin/builtin/enum-constants/index.mjs +1 -1
  26. package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
  27. package/dist/plugin/builtin/file-utils/index.mjs +1 -1
  28. package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
  29. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  30. package/dist/plugin/builtin/seed/index.d.mts +1 -1
  31. package/dist/plugin/builtin/seed/index.mjs +1 -1
  32. package/dist/plugin/index.d.mts +2 -2
  33. package/dist/{plugin-IIDZW9GG.d.mts → plugin-BC7WQrjm.d.mts} +24 -13
  34. package/dist/{runtime-7DOyTTxR.mjs → runtime-3P9JFpe9.mjs} +1018 -303
  35. package/dist/runtime-3P9JFpe9.mjs.map +1 -0
  36. package/dist/{schema-BITbkmq3.mjs → schema-D27cW0Ca.mjs} +2 -1
  37. package/dist/{schema-BITbkmq3.mjs.map → schema-D27cW0Ca.mjs.map} +1 -1
  38. package/dist/{seed-BXrSEJbv.mjs → seed-DtYgudLq.mjs} +11 -3
  39. package/dist/seed-DtYgudLq.mjs.map +1 -0
  40. package/dist/utils/test/index.d.mts +2 -2
  41. package/dist/{workflow.generated-C2A5Ye0m.d.mts → workflow.generated-Cd5dsFnf.d.mts} +2 -2
  42. package/docs/cli/application.md +17 -0
  43. package/docs/services/executor.md +54 -0
  44. package/package.json +8 -8
  45. package/dist/application-p2GujXmg.mjs.map +0 -1
  46. package/dist/runtime-7DOyTTxR.mjs.map +0 -1
  47. package/dist/seed-BXrSEJbv.mjs.map +0 -1
@@ -1,9 +1,9 @@
1
1
 
2
- import { t as db } from "./schema-BITbkmq3.mjs";
2
+ import { t as db } from "./schema-D27cW0Ca.mjs";
3
3
  import { i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-qz-Y4sBV.mjs";
4
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
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";
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-CfAom-vi.mjs";
7
7
  import { r as withSpan } from "./telemetry-B4sp-dD2.mjs";
8
8
  import { arg, createDefineCommand, defineCommand, runCommand } from "politty";
9
9
  import { z } from "zod";
@@ -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({
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({
1032
1190
  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({
1046
- 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,49 @@ 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 normalizeComparableIdPService(input) {
1631
+ return {
1632
+ authorization: input.authorization,
1633
+ lang: input.lang === IdPLang.UNSPECIFIED ? IdPLang.EN : input.lang,
1634
+ userAuthPolicy: input.userAuthPolicy,
1635
+ publishUserEvents: input.publishUserEvents,
1636
+ disableGqlOperations: input.disableGqlOperations
1637
+ };
1638
+ }
1639
+ function areIdPServicesEqual(existing, desired) {
1640
+ return areNormalizedEqual(normalizeComparableIdPService({
1641
+ authorization: existing.authorization,
1642
+ lang: existing.lang,
1643
+ userAuthPolicy: normalizeComparableUserAuthPolicy(existing.userAuthPolicy),
1644
+ publishUserEvents: existing.publishUserEvents,
1645
+ disableGqlOperations: normalizeComparableDisableGqlOperations(existing.disableGqlOperations)
1646
+ }), desired);
1647
+ }
1457
1648
  async function planServices$3(client, workspaceId, appName, idps) {
1458
1649
  const changeSet = createChangeSet("IdP services");
1459
1650
  const conflicts = [];
@@ -1478,7 +1669,8 @@ async function planServices$3(client, workspaceId, appName, idps) {
1478
1669
  const { metadata } = await client.getMetadata({ trn: trn$5(workspaceId, resource.namespace.name) });
1479
1670
  existingServices[resource.namespace.name] = {
1480
1671
  resource,
1481
- label: metadata?.labels[sdkNameLabelKey]
1672
+ label: metadata?.labels[sdkNameLabelKey],
1673
+ allLabels: metadata?.labels
1482
1674
  };
1483
1675
  }));
1484
1676
  for (const idp of idps) {
@@ -1499,7 +1691,25 @@ async function planServices$3(client, workspaceId, appName, idps) {
1499
1691
  }
1500
1692
  const lang = convertLang(idp.lang);
1501
1693
  const userAuthPolicy = idp.userAuthPolicy;
1694
+ const publishUserEvents = idp.publishUserEvents ?? false;
1695
+ const desired = normalizeComparableIdPService({
1696
+ authorization,
1697
+ lang,
1698
+ userAuthPolicy: normalizeComparableUserAuthPolicy(userAuthPolicy),
1699
+ publishUserEvents,
1700
+ disableGqlOperations: normalizeComparableDisableGqlOperations(convertGqlOperationsToDisable(idp.gqlOperations))
1701
+ });
1702
+ const request = {
1703
+ workspaceId,
1704
+ namespaceName,
1705
+ authorization,
1706
+ lang,
1707
+ userAuthPolicy,
1708
+ publishUserEvents,
1709
+ disableGqlOperations: convertGqlOperationsToDisable(idp.gqlOperations)
1710
+ };
1502
1711
  if (existing) {
1712
+ const isManagedByApp = existing.label === appName;
1503
1713
  if (!existing.label) unmanaged.push({
1504
1714
  resourceType: "IdP service",
1505
1715
  resourceName: idp.name
@@ -1509,31 +1719,16 @@ async function planServices$3(client, workspaceId, appName, idps) {
1509
1719
  resourceName: idp.name,
1510
1720
  currentOwner: existing.label
1511
1721
  });
1512
- changeSet.updates.push({
1722
+ if (isManagedByApp && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areIdPServicesEqual(existing.resource, desired)) changeSet.unchanged.push({ name: namespaceName });
1723
+ else changeSet.updates.push({
1513
1724
  name: namespaceName,
1514
- request: {
1515
- workspaceId,
1516
- namespaceName,
1517
- authorization,
1518
- lang,
1519
- userAuthPolicy,
1520
- publishUserEvents: idp.publishUserEvents,
1521
- disableGqlOperations: convertGqlOperationsToDisable(idp.gqlOperations)
1522
- },
1725
+ request,
1523
1726
  metaRequest
1524
1727
  });
1525
1728
  delete existingServices[namespaceName];
1526
1729
  } else changeSet.creates.push({
1527
1730
  name: namespaceName,
1528
- request: {
1529
- workspaceId,
1530
- namespaceName,
1531
- authorization,
1532
- lang,
1533
- userAuthPolicy,
1534
- publishUserEvents: idp.publishUserEvents,
1535
- disableGqlOperations: convertGqlOperationsToDisable(idp.gqlOperations)
1536
- },
1731
+ request,
1537
1732
  metaRequest
1538
1733
  });
1539
1734
  }
@@ -1555,7 +1750,7 @@ async function planServices$3(client, workspaceId, appName, idps) {
1555
1750
  resourceOwners
1556
1751
  };
1557
1752
  }
1558
- async function planClients(client, workspaceId, idps, deletedServices) {
1753
+ async function planClients(client, workspaceId, idps, deletedServices, forceApplyAll = false) {
1559
1754
  const changeSet = createChangeSet("IdP clients");
1560
1755
  const fetchClients = (namespaceName) => {
1561
1756
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -1583,12 +1778,13 @@ async function planClients(client, workspaceId, idps, deletedServices) {
1583
1778
  existingNameMap.set(client.name, client.clientSecret);
1584
1779
  });
1585
1780
  for (const name of idp.clients) if (existingNameMap.has(name)) {
1586
- changeSet.updates.push({
1781
+ if (forceApplyAll) changeSet.updates.push({
1587
1782
  name,
1588
1783
  workspaceId,
1589
1784
  namespaceName,
1590
- clientSecret: existingNameMap.get(name)
1785
+ clientSecret: existingNameMap.get(name) ?? ""
1591
1786
  });
1787
+ else changeSet.unchanged.push({ name });
1592
1788
  existingNameMap.delete(name);
1593
1789
  } else changeSet.creates.push({
1594
1790
  name,
@@ -1598,7 +1794,7 @@ async function planClients(client, workspaceId, idps, deletedServices) {
1598
1794
  client: { name }
1599
1795
  }
1600
1796
  });
1601
- existingNameMap.forEach((name) => {
1797
+ existingNameMap.forEach((_clientSecret, name) => {
1602
1798
  changeSet.deletes.push({
1603
1799
  name,
1604
1800
  request: {
@@ -1704,21 +1900,21 @@ async function applyAuth(client, result, phase = "create-update") {
1704
1900
  * @returns Planned auth changes and metadata
1705
1901
  */
1706
1902
  async function planAuth(context) {
1707
- const { client, workspaceId, application, forRemoval } = context;
1903
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
1708
1904
  const auths = [];
1709
1905
  if (!forRemoval && application.authService) {
1710
1906
  await application.authService.resolveNamespaces();
1711
1907
  auths.push(application.authService);
1712
1908
  }
1713
- const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$2(client, workspaceId, application.name, auths);
1909
+ const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$2(client, workspaceId, application.name, auths, forceApplyAll);
1714
1910
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
1715
1911
  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),
1912
+ planIdPConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
1913
+ planUserProfileConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
1914
+ planTenantConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
1915
+ planMachineUsers(client, workspaceId, auths, deletedServices, forceApplyAll),
1916
+ planAuthHooks(client, workspaceId, auths, deletedServices, forceApplyAll),
1917
+ planOAuth2Clients(client, workspaceId, auths, deletedServices, forceApplyAll),
1722
1918
  planSCIMConfigs(client, workspaceId, auths, deletedServices),
1723
1919
  planSCIMResources(client, workspaceId, auths, deletedServices)
1724
1920
  ]);
@@ -1751,7 +1947,7 @@ async function planAuth(context) {
1751
1947
  function trn$4(workspaceId, name) {
1752
1948
  return `trn:v1:workspace:${workspaceId}:auth:${name}`;
1753
1949
  }
1754
- async function planServices$2(client, workspaceId, appName, auths) {
1950
+ async function planServices$2(client, workspaceId, appName, auths, forceApplyAll = false) {
1755
1951
  const changeSet = createChangeSet("Auth services");
1756
1952
  const conflicts = [];
1757
1953
  const unmanaged = [];
@@ -1782,7 +1978,13 @@ async function planServices$2(client, workspaceId, appName, auths) {
1782
1978
  const { parsedConfig: config } = auth;
1783
1979
  const existing = existingServices[config.name];
1784
1980
  const metaRequest = await buildMetaRequest(trn$4(workspaceId, config.name), appName);
1981
+ const request = {
1982
+ workspaceId,
1983
+ namespaceName: config.name,
1984
+ publishSessionEvents: config.publishSessionEvents
1985
+ };
1785
1986
  if (existing) {
1987
+ const isManagedByApp = existing.label === appName;
1786
1988
  if (!existing.label) unmanaged.push({
1787
1989
  resourceType: "Auth service",
1788
1990
  resourceName: config.name
@@ -1792,23 +1994,16 @@ async function planServices$2(client, workspaceId, appName, auths) {
1792
1994
  resourceName: config.name,
1793
1995
  currentOwner: existing.label
1794
1996
  });
1795
- changeSet.updates.push({
1997
+ if (!forceApplyAll && existing.resource.publishSessionEvents === (config.publishSessionEvents ?? false) && isManagedByApp) changeSet.unchanged.push({ name: config.name });
1998
+ else changeSet.updates.push({
1796
1999
  name: config.name,
1797
- request: {
1798
- workspaceId,
1799
- namespaceName: config.name,
1800
- publishSessionEvents: config.publishSessionEvents
1801
- },
2000
+ request,
1802
2001
  metaRequest
1803
2002
  });
1804
2003
  delete existingServices[config.name];
1805
2004
  } else changeSet.creates.push({
1806
2005
  name: config.name,
1807
- request: {
1808
- workspaceId,
1809
- namespaceName: config.name,
1810
- publishSessionEvents: config.publishSessionEvents
1811
- },
2006
+ request,
1812
2007
  metaRequest
1813
2008
  });
1814
2009
  }
@@ -1830,7 +2025,7 @@ async function planServices$2(client, workspaceId, appName, auths) {
1830
2025
  resourceOwners
1831
2026
  };
1832
2027
  }
1833
- async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
2028
+ async function planIdPConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
1834
2029
  const changeSet = createChangeSet("Auth idpConfigs");
1835
2030
  const fetchIdPConfigs = (namespaceName) => {
1836
2031
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -1851,32 +2046,51 @@ async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
1851
2046
  for (const authService of auths) {
1852
2047
  const { parsedConfig: config } = authService;
1853
2048
  const existingIdPConfigs = await fetchIdPConfigs(config.name);
1854
- const existingNameSet = /* @__PURE__ */ new Set();
2049
+ const existingMap = /* @__PURE__ */ new Map();
1855
2050
  existingIdPConfigs.forEach((idpConfig) => {
1856
- existingNameSet.add(idpConfig.name);
2051
+ existingMap.set(idpConfig.name, idpConfig);
1857
2052
  });
1858
2053
  const idpConfig = config.idProvider;
1859
- if (idpConfig) if (existingNameSet.has(idpConfig.name)) {
1860
- changeSet.updates.push({
2054
+ if (idpConfig) {
2055
+ const desired = protoIdPConfig(idpConfig);
2056
+ const existing = existingMap.get(idpConfig.name);
2057
+ if (existing) {
2058
+ const desiredComparable = await protoIdPConfigForComparison(client, workspaceId, idpConfig, desired);
2059
+ if (!desiredComparable) {
2060
+ changeSet.updates.push({
2061
+ name: idpConfig.name,
2062
+ idpConfig,
2063
+ request: {
2064
+ workspaceId,
2065
+ namespaceName: config.name,
2066
+ idpConfig: desired
2067
+ }
2068
+ });
2069
+ existingMap.delete(idpConfig.name);
2070
+ continue;
2071
+ }
2072
+ if (!forceApplyAll && areAuthIdPConfigsEqual(existing, desiredComparable)) changeSet.unchanged.push({ name: idpConfig.name });
2073
+ else changeSet.updates.push({
2074
+ name: idpConfig.name,
2075
+ idpConfig,
2076
+ request: {
2077
+ workspaceId,
2078
+ namespaceName: config.name,
2079
+ idpConfig: desired
2080
+ }
2081
+ });
2082
+ existingMap.delete(idpConfig.name);
2083
+ } else changeSet.creates.push({
1861
2084
  name: idpConfig.name,
1862
2085
  idpConfig,
1863
2086
  request: {
1864
2087
  workspaceId,
1865
2088
  namespaceName: config.name,
1866
- idpConfig: protoIdPConfig(idpConfig)
2089
+ idpConfig: desired
1867
2090
  }
1868
2091
  });
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) => {
2092
+ }
2093
+ existingMap.forEach((_, name) => {
1880
2094
  changeSet.deletes.push({
1881
2095
  name,
1882
2096
  request: {
@@ -1899,6 +2113,32 @@ async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
1899
2113
  });
1900
2114
  return changeSet;
1901
2115
  }
2116
+ async function protoIdPConfigForComparison(client, workspaceId, idpConfig, desired) {
2117
+ if (idpConfig.kind !== "BuiltInIdP") return desired;
2118
+ const config = await tryProtoBuiltinIdPConfig(client, workspaceId, idpConfig);
2119
+ return config ? {
2120
+ ...desired,
2121
+ config
2122
+ } : void 0;
2123
+ }
2124
+ function normalizeComparableAuthIdPConfig(idpConfig) {
2125
+ const configCase = idpConfig.config?.config?.case;
2126
+ const oidcValue = configCase === "oidc" && typeof idpConfig.config?.config?.value === "object" && idpConfig.config.config.value !== null ? idpConfig.config.config.value : void 0;
2127
+ return normalizeProtoConfig({
2128
+ name: idpConfig.name,
2129
+ authType: idpConfig.authType,
2130
+ config: configCase === "oidc" ? { config: {
2131
+ case: "oidc",
2132
+ value: {
2133
+ ...oidcValue ?? {},
2134
+ issuerUrl: oidcValue && "issuerUrl" in oidcValue ? oidcValue.issuerUrl || void 0 : void 0
2135
+ }
2136
+ } } : idpConfig.config
2137
+ });
2138
+ }
2139
+ function areAuthIdPConfigsEqual(existing, desired) {
2140
+ return areNormalizedEqual(normalizeComparableAuthIdPConfig(existing), normalizeComparableAuthIdPConfig(desired));
2141
+ }
1902
2142
  function protoIdPConfig(idpConfig) {
1903
2143
  switch (idpConfig.kind) {
1904
2144
  case "IDToken": return {
@@ -1951,6 +2191,11 @@ function protoIdPConfig(idpConfig) {
1951
2191
  }
1952
2192
  }
1953
2193
  async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
2194
+ const config = await tryProtoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig);
2195
+ if (!config) throw new Error(`Built-in IdP "${builtinIdPConfig.namespace}" not found. Please ensure that idp is configured correctly.`);
2196
+ return config;
2197
+ }
2198
+ async function tryProtoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
1954
2199
  let idpService;
1955
2200
  try {
1956
2201
  idpService = await client.getIdPService({
@@ -1958,14 +2203,20 @@ async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
1958
2203
  namespaceName: builtinIdPConfig.namespace
1959
2204
  });
1960
2205
  } 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 });
2206
+ if (error instanceof ConnectError && error.code === Code.NotFound) return;
2207
+ throw error;
2208
+ }
2209
+ let idpClient;
2210
+ try {
2211
+ idpClient = await client.getIdPClient({
2212
+ workspaceId,
2213
+ namespaceName: builtinIdPConfig.namespace,
2214
+ name: builtinIdPConfig.clientName
2215
+ });
2216
+ } catch (error) {
2217
+ if (error instanceof ConnectError && error.code === Code.NotFound) return;
1962
2218
  throw error;
1963
2219
  }
1964
- const idpClient = await client.getIdPClient({
1965
- workspaceId,
1966
- namespaceName: builtinIdPConfig.namespace,
1967
- name: builtinIdPConfig.clientName
1968
- });
1969
2220
  const vaultName = idpClientVaultName(builtinIdPConfig.namespace, builtinIdPConfig.clientName);
1970
2221
  const secretKey = idpClientSecretName(builtinIdPConfig.namespace, builtinIdPConfig.clientName);
1971
2222
  return { config: {
@@ -1981,16 +2232,35 @@ async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
1981
2232
  }
1982
2233
  } };
1983
2234
  }
1984
- async function planUserProfileConfigs(client, workspaceId, auths, deletedServices) {
2235
+ async function planUserProfileConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
1985
2236
  const changeSet = createChangeSet("Auth userProfileConfigs");
1986
2237
  for (const auth of auths) {
1987
2238
  const { parsedConfig: config } = auth;
1988
2239
  const name = `${config.name}-user-profile-config`;
1989
2240
  try {
1990
- await client.getUserProfileConfig({
2241
+ const { userProfileProviderConfig } = await client.getUserProfileConfig({
1991
2242
  workspaceId,
1992
2243
  namespaceName: config.name
1993
2244
  });
2245
+ const userProfileForUpdate = auth.userProfile;
2246
+ if (userProfileForUpdate) {
2247
+ const desired = protoUserProfileConfig(userProfileForUpdate);
2248
+ if (!forceApplyAll && areUserProfileConfigsEqual(userProfileProviderConfig ?? {}, desired)) changeSet.unchanged.push({ name });
2249
+ else changeSet.updates.push({
2250
+ name,
2251
+ request: {
2252
+ workspaceId,
2253
+ namespaceName: config.name,
2254
+ userProfileProviderConfig: desired
2255
+ }
2256
+ });
2257
+ } else changeSet.deletes.push({
2258
+ name,
2259
+ request: {
2260
+ workspaceId,
2261
+ namespaceName: config.name
2262
+ }
2263
+ });
1994
2264
  } catch (error) {
1995
2265
  if (error instanceof ConnectError && error.code === Code.NotFound) {
1996
2266
  const userProfileForCreate = auth.userProfile;
@@ -2006,22 +2276,6 @@ async function planUserProfileConfigs(client, workspaceId, auths, deletedService
2006
2276
  }
2007
2277
  throw error;
2008
2278
  }
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
2279
  }
2026
2280
  for (const namespaceName of deletedServices) {
2027
2281
  try {
@@ -2061,16 +2315,34 @@ function protoUserProfileConfig(userProfile) {
2061
2315
  } }
2062
2316
  };
2063
2317
  }
2064
- async function planTenantConfigs(client, workspaceId, auths, deletedServices) {
2318
+ async function planTenantConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2065
2319
  const changeSet = createChangeSet("Auth tenantConfigs");
2066
2320
  for (const auth of auths) {
2067
2321
  const { parsedConfig: config } = auth;
2068
2322
  const name = `${config.name}-tenant-config`;
2069
2323
  try {
2070
- await client.getTenantConfig({
2324
+ const { tenantProviderConfig } = await client.getTenantConfig({
2071
2325
  workspaceId,
2072
2326
  namespaceName: config.name
2073
2327
  });
2328
+ if (config.tenantProvider) {
2329
+ const desired = protoTenantConfig(config.tenantProvider);
2330
+ if (!forceApplyAll && areTenantProviderConfigsEqual(tenantProviderConfig, desired)) changeSet.unchanged.push({ name });
2331
+ else changeSet.updates.push({
2332
+ name,
2333
+ request: {
2334
+ workspaceId,
2335
+ namespaceName: config.name,
2336
+ tenantProviderConfig: desired
2337
+ }
2338
+ });
2339
+ } else changeSet.deletes.push({
2340
+ name,
2341
+ request: {
2342
+ workspaceId,
2343
+ namespaceName: config.name
2344
+ }
2345
+ });
2074
2346
  } catch (error) {
2075
2347
  if (error instanceof ConnectError && error.code === Code.NotFound) {
2076
2348
  if (config.tenantProvider) changeSet.creates.push({
@@ -2085,21 +2357,6 @@ async function planTenantConfigs(client, workspaceId, auths, deletedServices) {
2085
2357
  }
2086
2358
  throw error;
2087
2359
  }
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
2360
  }
2104
2361
  for (const namespaceName of deletedServices) {
2105
2362
  try {
@@ -2134,7 +2391,7 @@ function protoTenantConfig(tenantConfig) {
2134
2391
  } }
2135
2392
  };
2136
2393
  }
2137
- async function planMachineUsers(client, workspaceId, auths, deletedServices) {
2394
+ async function planMachineUsers(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2138
2395
  const changeSet = createChangeSet("Auth machineUsers");
2139
2396
  const fetchMachineUsers = (authNamespace) => {
2140
2397
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -2155,25 +2412,31 @@ async function planMachineUsers(client, workspaceId, auths, deletedServices) {
2155
2412
  for (const auth of auths) {
2156
2413
  const { parsedConfig: config } = auth;
2157
2414
  const existingMachineUsers = await fetchMachineUsers(config.name);
2158
- const existingNameSet = /* @__PURE__ */ new Set();
2415
+ const existingMap = /* @__PURE__ */ new Map();
2159
2416
  existingMachineUsers.forEach((machineUser) => {
2160
- existingNameSet.add(machineUser.name);
2417
+ existingMap.set(machineUser.name, machineUser);
2161
2418
  });
2162
2419
  for (const machineUsername of Object.keys(config.machineUsers ?? {})) {
2163
2420
  const machineUser = config.machineUsers?.[machineUsername];
2164
2421
  if (!machineUser) continue;
2165
- if (existingNameSet.has(machineUsername)) {
2166
- changeSet.updates.push({
2422
+ const desiredMachineUser = {
2423
+ attributes: machineUser.attributeList,
2424
+ attributeMap: machineUser.attributes ? protoMachineUserAttributeMap(machineUser.attributes) : void 0
2425
+ };
2426
+ const existing = existingMap.get(machineUsername);
2427
+ if (existing) {
2428
+ if (!forceApplyAll && areMachineUsersEqual(existing, desiredMachineUser)) changeSet.unchanged.push({ name: machineUsername });
2429
+ else changeSet.updates.push({
2167
2430
  name: machineUsername,
2168
2431
  request: {
2169
2432
  workspaceId,
2170
2433
  authNamespace: config.name,
2171
2434
  name: machineUsername,
2172
2435
  attributes: machineUser.attributeList,
2173
- attributeMap: machineUser.attributes ? protoMachineUserAttributeMap(machineUser.attributes) : void 0
2436
+ attributeMap: desiredMachineUser.attributeMap
2174
2437
  }
2175
2438
  });
2176
- existingNameSet.delete(machineUsername);
2439
+ existingMap.delete(machineUsername);
2177
2440
  } else changeSet.creates.push({
2178
2441
  name: machineUsername,
2179
2442
  request: {
@@ -2181,11 +2444,11 @@ async function planMachineUsers(client, workspaceId, auths, deletedServices) {
2181
2444
  authNamespace: config.name,
2182
2445
  name: machineUsername,
2183
2446
  attributes: machineUser.attributeList,
2184
- attributeMap: machineUser.attributes ? protoMachineUserAttributeMap(machineUser.attributes) : void 0
2447
+ attributeMap: desiredMachineUser.attributeMap
2185
2448
  }
2186
2449
  });
2187
2450
  }
2188
- existingNameSet.forEach((name) => {
2451
+ existingMap.forEach((_, name) => {
2189
2452
  changeSet.deletes.push({
2190
2453
  name,
2191
2454
  request: {
@@ -2213,7 +2476,60 @@ function protoMachineUserAttributeMap(attributeMap) {
2213
2476
  for (const [key, value] of Object.entries(attributeMap)) ret[key] = fromJson(ValueSchema, value ?? null);
2214
2477
  return ret;
2215
2478
  }
2216
- async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
2479
+ function normalizeComparableUserProfileConfig(config) {
2480
+ const comparableConfig = config.config?.config;
2481
+ const tailorDBConfig = comparableConfig?.case === "tailordb" ? comparableConfig.value : void 0;
2482
+ return normalizeProtoConfig({
2483
+ providerType: config.providerType,
2484
+ config: { config: {
2485
+ case: comparableConfig?.case,
2486
+ value: tailorDBConfig ? {
2487
+ ...tailorDBConfig,
2488
+ tenantIdField: tailorDBConfig.tenantIdField || void 0,
2489
+ attributesFields: normalizeStringArray(tailorDBConfig.attributesFields),
2490
+ attributeMap: normalizeProtoConfig(tailorDBConfig.attributeMap)
2491
+ } : comparableConfig?.value
2492
+ } }
2493
+ });
2494
+ }
2495
+ function areUserProfileConfigsEqual(existing, desired) {
2496
+ return areNormalizedEqual(normalizeComparableUserProfileConfig(existing), normalizeComparableUserProfileConfig(desired));
2497
+ }
2498
+ function normalizeComparableTenantProviderConfig(config) {
2499
+ return normalizeProtoConfig(config);
2500
+ }
2501
+ function areTenantProviderConfigsEqual(existing, desired) {
2502
+ return areNormalizedEqual(normalizeComparableTenantProviderConfig(existing), normalizeComparableTenantProviderConfig(desired));
2503
+ }
2504
+ function normalizeComparableMachineUser(input) {
2505
+ return normalizeProtoConfig({
2506
+ attributes: normalizeStringArray(input.attributes),
2507
+ attributeMap: normalizeProtoConfig(input.attributeMap ?? {})
2508
+ });
2509
+ }
2510
+ function areMachineUsersEqual(existing, desired) {
2511
+ return areNormalizedEqual(normalizeComparableMachineUser(existing), normalizeComparableMachineUser(desired));
2512
+ }
2513
+ function normalizeComparableOAuth2Client(client) {
2514
+ const accessTokenLifetime = oauth2LifetimeToSeconds(client.accessTokenLifetime);
2515
+ const refreshTokenLifetime = oauth2LifetimeToSeconds(client.refreshTokenLifetime);
2516
+ return normalizeProtoConfig({
2517
+ ...client,
2518
+ redirectUris: normalizeStringArray(client.redirectUris),
2519
+ grantTypes: [...client.grantTypes ?? []].sort((left, right) => left - right),
2520
+ accessTokenLifetime: accessTokenLifetime ?? 86400,
2521
+ refreshTokenLifetime: refreshTokenLifetime ?? 604800,
2522
+ requireDpop: client.requireDpop ?? false
2523
+ });
2524
+ }
2525
+ function oauth2LifetimeToSeconds(lifetime) {
2526
+ if (typeof lifetime === "number") return lifetime;
2527
+ if (lifetime?.seconds != null) return Number(lifetime.seconds);
2528
+ }
2529
+ function areOAuth2ClientsEqual(existing, desired) {
2530
+ return areNormalizedEqual(normalizeComparableOAuth2Client(existing), normalizeComparableOAuth2Client(desired));
2531
+ }
2532
+ async function planOAuth2Clients(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2217
2533
  const changeSet = createChangeSet("Auth oauth2Clients");
2218
2534
  const fetchOAuth2Clients = (namespaceName) => {
2219
2535
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -2236,14 +2552,16 @@ async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
2236
2552
  const existingOAuth2Clients = await fetchOAuth2Clients(config.name);
2237
2553
  const existingClientsMap = /* @__PURE__ */ new Map();
2238
2554
  existingOAuth2Clients.forEach((oauth2Client) => {
2239
- existingClientsMap.set(oauth2Client.name, oauth2Client.clientType);
2555
+ existingClientsMap.set(oauth2Client.name, oauth2Client);
2240
2556
  });
2241
2557
  for (const oauth2ClientName of Object.keys(config.oauth2Clients ?? {})) {
2242
2558
  const oauth2Client = config.oauth2Clients?.[oauth2ClientName];
2243
2559
  if (!oauth2Client) continue;
2244
2560
  const newOAuth2Client = protoOAuth2Client(oauth2ClientName, oauth2Client);
2561
+ const resolvedRedirectUris = await resolveStaticWebsiteUrls(client, workspaceId, newOAuth2Client.redirectUris ?? [], "OAuth2 redirect URIs");
2245
2562
  if (existingClientsMap.has(oauth2ClientName)) {
2246
- if (existingClientsMap.get(oauth2ClientName) !== newOAuth2Client.clientType) changeSet.replaces.push({
2563
+ const existingClient = existingClientsMap.get(oauth2ClientName);
2564
+ if (existingClient.clientType !== newOAuth2Client.clientType) changeSet.replaces.push({
2247
2565
  name: oauth2ClientName,
2248
2566
  deleteRequest: {
2249
2567
  workspaceId,
@@ -2256,14 +2574,33 @@ async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
2256
2574
  oauth2Client: newOAuth2Client
2257
2575
  }
2258
2576
  });
2259
- else changeSet.updates.push({
2260
- name: oauth2ClientName,
2261
- request: {
2262
- workspaceId,
2263
- namespaceName: config.name,
2264
- oauth2Client: newOAuth2Client
2265
- }
2266
- });
2577
+ else {
2578
+ const desiredComparable = {
2579
+ ...newOAuth2Client,
2580
+ redirectUris: resolvedRedirectUris,
2581
+ accessTokenLifetime: oauth2LifetimeToSeconds(newOAuth2Client.accessTokenLifetime),
2582
+ refreshTokenLifetime: oauth2LifetimeToSeconds(newOAuth2Client.refreshTokenLifetime)
2583
+ };
2584
+ const existingComparable = {
2585
+ name: existingClient.name,
2586
+ description: existingClient.description,
2587
+ grantTypes: existingClient.grantTypes,
2588
+ redirectUris: existingClient.redirectUris,
2589
+ clientType: existingClient.clientType,
2590
+ accessTokenLifetime: oauth2LifetimeToSeconds(existingClient.accessTokenLifetime),
2591
+ refreshTokenLifetime: oauth2LifetimeToSeconds(existingClient.refreshTokenLifetime),
2592
+ requireDpop: existingClient.requireDpop
2593
+ };
2594
+ if (!forceApplyAll && areOAuth2ClientsEqual(existingComparable, desiredComparable)) changeSet.unchanged.push({ name: oauth2ClientName });
2595
+ else changeSet.updates.push({
2596
+ name: oauth2ClientName,
2597
+ request: {
2598
+ workspaceId,
2599
+ namespaceName: config.name,
2600
+ oauth2Client: newOAuth2Client
2601
+ }
2602
+ });
2603
+ }
2267
2604
  existingClientsMap.delete(oauth2ClientName);
2268
2605
  } else changeSet.creates.push({
2269
2606
  name: oauth2ClientName,
@@ -2538,21 +2875,36 @@ function protoSCIMAttribute(attr) {
2538
2875
  subAttributes: attr.subAttributes?.map((attr) => protoSCIMAttribute(attr))
2539
2876
  };
2540
2877
  }
2541
- async function planAuthHooks(client, workspaceId, auths, deletedServices) {
2878
+ function areAuthHooksEqual(existing, desired) {
2879
+ return areNormalizedEqual({
2880
+ scriptRef: existing.scriptRef ?? "",
2881
+ invoker: existing.invoker ? {
2882
+ namespace: existing.invoker.namespace ?? "",
2883
+ machineUserName: existing.invoker.machineUserName ?? ""
2884
+ } : void 0
2885
+ }, {
2886
+ scriptRef: desired.scriptRef ?? "",
2887
+ invoker: desired.invoker ? {
2888
+ namespace: desired.invoker.namespace ?? "",
2889
+ machineUserName: desired.invoker.machineUserName ?? ""
2890
+ } : void 0
2891
+ });
2892
+ }
2893
+ async function planAuthHooks(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2542
2894
  const changeSet = createChangeSet("Auth hooks");
2543
2895
  for (const auth of auths) {
2544
2896
  const { parsedConfig: config } = auth;
2545
2897
  const beforeLogin = config.hooks?.beforeLogin;
2546
2898
  let existingHook;
2547
2899
  try {
2548
- await client.getAuthHook({
2900
+ const { hook } = await client.getAuthHook({
2549
2901
  workspaceId,
2550
2902
  namespaceName: config.name,
2551
2903
  hookPoint: AuthHookPoint.BEFORE_LOGIN
2552
2904
  });
2553
- existingHook = true;
2905
+ existingHook = hook;
2554
2906
  } catch (error) {
2555
- if (error instanceof ConnectError && error.code === Code.NotFound) existingHook = false;
2907
+ if (error instanceof ConnectError && error.code === Code.NotFound) existingHook = void 0;
2556
2908
  else throw error;
2557
2909
  }
2558
2910
  if (beforeLogin) {
@@ -2568,7 +2920,8 @@ async function planAuthHooks(client, workspaceId, auths, deletedServices) {
2568
2920
  }
2569
2921
  }
2570
2922
  };
2571
- if (existingHook) changeSet.updates.push({
2923
+ if (existingHook) if (!forceApplyAll && areAuthHooksEqual(existingHook, hookRequest.hook)) changeSet.unchanged.push({ name: `${config.name}/before-login` });
2924
+ else changeSet.updates.push({
2572
2925
  name: `${config.name}/before-login`,
2573
2926
  request: hookRequest
2574
2927
  });
@@ -2751,19 +3104,10 @@ const ACTOR_TRANSFORM_EXPR = "actor: args.actor ? (({ attributeMap, attributes:
2751
3104
  function buildExecutorArgsExpr(triggerKind, env) {
2752
3105
  const envExpr = `env: ${JSON.stringify(env)}`;
2753
3106
  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} })`;
3107
+ case "schedule": return `({ ...args, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, ${envExpr} })`;
2764
3108
  case "resolverExecuted": return `({ ...args, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, success: !!args.succeeded, result: args.succeeded?.result.resolver, error: args.failed?.error, ${envExpr} })`;
2765
3109
  case "incomingWebhook": return `({ ...args, appNamespace: args.namespaceName, rawBody: args.raw_body, ${envExpr} })`;
2766
- default: throw new Error(`Unknown trigger kind for args expression: ${triggerKind}`);
3110
+ default: return `({ ...args, event: args.eventType?.split(".").pop(), rawEvent: args.eventType, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, ${envExpr} })`;
2767
3111
  }
2768
3112
  }
2769
3113
  /**
@@ -2833,13 +3177,15 @@ async function planExecutor(context) {
2833
3177
  const { metadata } = await client.getMetadata({ trn: trn$3(workspaceId, resource.name) });
2834
3178
  existingExecutors[resource.name] = {
2835
3179
  resource,
2836
- label: metadata?.labels[sdkNameLabelKey]
3180
+ label: metadata?.labels[sdkNameLabelKey],
3181
+ allLabels: metadata?.labels
2837
3182
  };
2838
3183
  }));
2839
3184
  const executors = forRemoval ? {} : await application.executorService?.loadExecutors() ?? {};
2840
3185
  for (const executor of Object.values(executors)) {
2841
3186
  const existing = existingExecutors[executor.name];
2842
3187
  const metaRequest = await buildMetaRequest(trn$3(workspaceId, executor.name), application.name);
3188
+ const desiredExecutor = protoExecutor(application, executor);
2843
3189
  if (existing) {
2844
3190
  if (!existing.label) unmanaged.push({
2845
3191
  resourceType: "Executor",
@@ -2850,11 +3196,12 @@ async function planExecutor(context) {
2850
3196
  resourceName: executor.name,
2851
3197
  currentOwner: existing.label
2852
3198
  });
2853
- changeSet.updates.push({
3199
+ if (existing.label === application.name && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areExecutorsEqual(existing.resource, desiredExecutor)) changeSet.unchanged.push({ name: executor.name });
3200
+ else changeSet.updates.push({
2854
3201
  name: executor.name,
2855
3202
  request: {
2856
3203
  workspaceId,
2857
- executor: protoExecutor(application, executor)
3204
+ executor: desiredExecutor
2858
3205
  },
2859
3206
  metaRequest
2860
3207
  });
@@ -2863,7 +3210,7 @@ async function planExecutor(context) {
2863
3210
  name: executor.name,
2864
3211
  request: {
2865
3212
  workspaceId,
2866
- executor: protoExecutor(application, executor)
3213
+ executor: desiredExecutor
2867
3214
  },
2868
3215
  metaRequest
2869
3216
  });
@@ -2887,6 +3234,56 @@ async function planExecutor(context) {
2887
3234
  resourceOwners
2888
3235
  };
2889
3236
  }
3237
+ function normalizeComparableExecutor(executor) {
3238
+ const normalized = normalizeProtoConfig(executor) ?? {};
3239
+ const webhookHeaders = normalized.targetConfig?.config?.case === "webhook" ? [...normalized.targetConfig.config.value.headers ?? []].sort((left, right) => (left.key ?? "").localeCompare(right.key ?? "")) : void 0;
3240
+ const triggerConfig = normalized.triggerConfig?.config?.case === "incomingWebhook" ? {
3241
+ ...normalized.triggerConfig,
3242
+ config: {
3243
+ ...normalized.triggerConfig.config,
3244
+ value: {}
3245
+ }
3246
+ } : normalized.triggerConfig?.config?.case === "event" ? {
3247
+ ...normalized.triggerConfig,
3248
+ config: {
3249
+ ...normalized.triggerConfig.config,
3250
+ value: {
3251
+ ...normalized.triggerConfig.config.value,
3252
+ eventType: void 0
3253
+ }
3254
+ }
3255
+ } : normalized.triggerConfig;
3256
+ return {
3257
+ name: normalized.name,
3258
+ description: normalized.description ?? "",
3259
+ disabled: normalized.disabled ?? false,
3260
+ triggerType: normalized.triggerType,
3261
+ triggerConfig,
3262
+ targetType: normalized.targetType,
3263
+ targetConfig: normalized.targetConfig?.config?.case === "webhook" ? {
3264
+ ...normalized.targetConfig,
3265
+ config: {
3266
+ ...normalized.targetConfig.config,
3267
+ value: {
3268
+ ...normalized.targetConfig.config.value,
3269
+ headers: webhookHeaders
3270
+ }
3271
+ }
3272
+ } : normalized.targetConfig?.config?.case === "function" ? {
3273
+ ...normalized.targetConfig,
3274
+ config: {
3275
+ ...normalized.targetConfig.config,
3276
+ value: {
3277
+ ...normalized.targetConfig.config.value,
3278
+ script: void 0
3279
+ }
3280
+ }
3281
+ } : normalized.targetConfig
3282
+ };
3283
+ }
3284
+ function areExecutorsEqual(existing, desired) {
3285
+ return areNormalizedEqual(normalizeComparableExecutor(existing), normalizeComparableExecutor(desired));
3286
+ }
2890
3287
  function resolveTailorDBNamespace(application, typeName) {
2891
3288
  for (const service of application.tailorDBServices) if (service.types[typeName]) return service.namespace;
2892
3289
  throw new Error(`TailorDB type "${typeName}" not found in any namespace. Available namespaces: ${application.tailorDBServices.map((s) => s.namespace).join(", ")}`);
@@ -2911,18 +3308,6 @@ function protoExecutor(application, executor) {
2911
3308
  let triggerType;
2912
3309
  let triggerConfig;
2913
3310
  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
3311
  function typedEventTrigger(typedConfig) {
2927
3312
  return { config: {
2928
3313
  case: "event",
@@ -2940,14 +3325,12 @@ function protoExecutor(application, executor) {
2940
3325
  }
2941
3326
  } };
2942
3327
  break;
2943
- case "recordCreated":
2944
- case "recordUpdated":
2945
- case "recordDeleted":
3328
+ case "tailordb":
2946
3329
  triggerType = ExecutorTriggerType.EVENT;
2947
3330
  triggerConfig = typedEventTrigger({
2948
3331
  case: "tailordb",
2949
3332
  value: {
2950
- eventTypes: [eventType[trigger.kind]],
3333
+ eventTypes: trigger.events,
2951
3334
  namespaceName: resolveTailorDBNamespace(application, trigger.typeName),
2952
3335
  typeName: trigger.typeName,
2953
3336
  ...trigger.condition ? { condition: { expr: `(${stringifyFunction(trigger.condition)})(${argsExpr})` } } : {}
@@ -2959,7 +3342,7 @@ function protoExecutor(application, executor) {
2959
3342
  triggerConfig = typedEventTrigger({
2960
3343
  case: "pipeline",
2961
3344
  value: {
2962
- eventTypes: [eventType[trigger.kind]],
3345
+ eventTypes: ["pipeline.resolver.executed"],
2963
3346
  namespaceName: resolveResolverNamespace(application, trigger.resolverName),
2964
3347
  resolverName: trigger.resolverName,
2965
3348
  ...trigger.condition ? { condition: { expr: `(${stringifyFunction(trigger.condition)})(${argsExpr})` } } : {}
@@ -2973,26 +3356,22 @@ function protoExecutor(application, executor) {
2973
3356
  value: {}
2974
3357
  } };
2975
3358
  break;
2976
- case "idpUserCreated":
2977
- case "idpUserUpdated":
2978
- case "idpUserDeleted":
3359
+ case "idpUser":
2979
3360
  triggerType = ExecutorTriggerType.EVENT;
2980
3361
  triggerConfig = typedEventTrigger({
2981
3362
  case: "idp",
2982
3363
  value: {
2983
- eventTypes: [eventType[trigger.kind]],
3364
+ eventTypes: trigger.events,
2984
3365
  namespaceName: resolveIdpNamespace(application)
2985
3366
  }
2986
3367
  });
2987
3368
  break;
2988
- case "authAccessTokenIssued":
2989
- case "authAccessTokenRefreshed":
2990
- case "authAccessTokenRevoked":
3369
+ case "authAccessToken":
2991
3370
  triggerType = ExecutorTriggerType.EVENT;
2992
3371
  triggerConfig = typedEventTrigger({
2993
3372
  case: "auth",
2994
3373
  value: {
2995
- eventTypes: [eventType[trigger.kind]],
3374
+ eventTypes: trigger.events,
2996
3375
  namespaceName: resolveAuthNamespace(application)
2997
3376
  }
2998
3377
  });
@@ -3148,7 +3527,7 @@ async function applyPipeline(client, result, phase = "create-update") {
3148
3527
  * @returns Planned changes
3149
3528
  */
3150
3529
  async function planPipeline(context) {
3151
- const { client, workspaceId, application, forRemoval } = context;
3530
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
3152
3531
  const pipelines = [];
3153
3532
  if (!forRemoval) for (const pipeline of application.resolverServices) {
3154
3533
  await pipeline.loadResolvers();
@@ -3156,7 +3535,7 @@ async function planPipeline(context) {
3156
3535
  }
3157
3536
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
3158
3537
  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);
3538
+ const resolverChangeSet = await planResolvers(client, workspaceId, pipelines, executors, serviceChangeSet.deletes.map((del) => del.name), application.env, forceApplyAll);
3160
3539
  serviceChangeSet.print();
3161
3540
  resolverChangeSet.print();
3162
3541
  return {
@@ -3196,7 +3575,8 @@ async function planServices$1(client, workspaceId, appName, pipelines) {
3196
3575
  const { metadata } = await client.getMetadata({ trn: trn$2(workspaceId, resource.namespace.name) });
3197
3576
  existingServices[resource.namespace.name] = {
3198
3577
  resource,
3199
- label: metadata?.labels[sdkNameLabelKey]
3578
+ label: metadata?.labels[sdkNameLabelKey],
3579
+ allLabels: metadata?.labels
3200
3580
  };
3201
3581
  }));
3202
3582
  for (const pipeline of pipelines) {
@@ -3212,7 +3592,8 @@ async function planServices$1(client, workspaceId, appName, pipelines) {
3212
3592
  resourceName: pipeline.namespace,
3213
3593
  currentOwner: existing.label
3214
3594
  });
3215
- changeSet.updates.push({
3595
+ if (existing.label === appName && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) changeSet.unchanged.push({ name: pipeline.namespace });
3596
+ else changeSet.updates.push({
3216
3597
  name: pipeline.namespace,
3217
3598
  request: {
3218
3599
  workspaceId,
@@ -3248,7 +3629,7 @@ async function planServices$1(client, workspaceId, appName, pipelines) {
3248
3629
  resourceOwners
3249
3630
  };
3250
3631
  }
3251
- async function planResolvers(client, workspaceId, pipelines, executors, deletedServices, env) {
3632
+ async function planResolvers(client, workspaceId, pipelines, executors, deletedServices, env, forceApplyAll = false) {
3252
3633
  const changeSet = createChangeSet("Pipeline resolvers");
3253
3634
  const fetchResolvers = (namespaceName) => {
3254
3635
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -3271,29 +3652,35 @@ async function planResolvers(client, workspaceId, pipelines, executors, deletedS
3271
3652
  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
3653
  for (const pipeline of pipelines) {
3273
3654
  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({
3655
+ const existingResolversMap = new Map(existingResolvers.map((resolver) => [resolver.name, resolver]));
3656
+ for (const resolver of Object.values(pipeline.resolvers)) {
3657
+ const desiredResolver = processResolver(pipeline.namespace, resolver, executorUsedResolvers, env);
3658
+ if (existingResolversMap.get(resolver.name)) {
3659
+ const { pipelineResolver: existingResolverDetail } = await client.getPipelineResolver({
3660
+ workspaceId,
3661
+ namespaceName: pipeline.namespace,
3662
+ resolverName: resolver.name
3663
+ });
3664
+ if (!forceApplyAll && existingResolverDetail && areResolversEqual(existingResolverDetail, desiredResolver)) changeSet.unchanged.push({ name: resolver.name });
3665
+ else changeSet.updates.push({
3666
+ name: resolver.name,
3667
+ request: {
3668
+ workspaceId,
3669
+ namespaceName: pipeline.namespace,
3670
+ pipelineResolver: desiredResolver
3671
+ }
3672
+ });
3673
+ existingResolversMap.delete(resolver.name);
3674
+ } else changeSet.creates.push({
3280
3675
  name: resolver.name,
3281
3676
  request: {
3282
3677
  workspaceId,
3283
3678
  namespaceName: pipeline.namespace,
3284
- pipelineResolver: processResolver(pipeline.namespace, resolver, executorUsedResolvers, env)
3679
+ pipelineResolver: desiredResolver
3285
3680
  }
3286
3681
  });
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) => {
3682
+ }
3683
+ existingResolversMap.forEach((_resolver, name) => {
3297
3684
  changeSet.deletes.push({
3298
3685
  name,
3299
3686
  request: {
@@ -3316,6 +3703,59 @@ async function planResolvers(client, workspaceId, pipelines, executors, deletedS
3316
3703
  });
3317
3704
  return changeSet;
3318
3705
  }
3706
+ function normalizeComparableResolver(resolver) {
3707
+ const normalized = normalizeProtoConfig(resolver) ?? {};
3708
+ return {
3709
+ name: normalized.name,
3710
+ description: normalized.description ?? "",
3711
+ authorization: normalized.authorization ?? "",
3712
+ operationType: normalized.operationType,
3713
+ publishExecutionEvents: normalized.publishExecutionEvents ?? false,
3714
+ inputs: normalizeComparableFields(normalized.inputs),
3715
+ response: normalizeComparableField(normalized.response),
3716
+ pipelines: normalizeComparablePipelines(normalized.pipelines)
3717
+ };
3718
+ }
3719
+ function areResolversEqual(existing, desired) {
3720
+ return areNormalizedEqual(normalizeComparableResolver(existing), normalizeComparableResolver(desired));
3721
+ }
3722
+ function normalizeComparablePipelines(pipelines) {
3723
+ return (pipelines ?? []).map((pipeline) => ({
3724
+ name: pipeline.name ?? "",
3725
+ operationName: pipeline.operationName ?? "",
3726
+ description: pipeline.description ?? "",
3727
+ operationType: pipeline.operationType,
3728
+ operationSourceRef: pipeline.operationSourceRef ?? "",
3729
+ operationHook: pipeline.operationHook?.expr ?? "",
3730
+ postScript: pipeline.postScript ?? "",
3731
+ skipOperationOnError: pipeline.skipOperationOnError ?? false,
3732
+ invoker: pipeline.invoker ?? void 0
3733
+ }));
3734
+ }
3735
+ function normalizeComparableFields(fields) {
3736
+ return (fields ?? []).map((field) => normalizeComparableField(field));
3737
+ }
3738
+ function normalizeComparableField(field) {
3739
+ if (!field) return;
3740
+ return {
3741
+ name: field.name ?? "",
3742
+ array: field.array ?? false,
3743
+ required: field.required ?? true,
3744
+ description: field.description ?? "",
3745
+ type: normalizeComparableType(field.type)
3746
+ };
3747
+ }
3748
+ function normalizeComparableType(type) {
3749
+ if (!type) return;
3750
+ return {
3751
+ kind: type.kind ?? "",
3752
+ name: type.name ?? "",
3753
+ required: type.required ?? true,
3754
+ description: type.description ?? "",
3755
+ allowedValues: type.allowedValues ?? [],
3756
+ fields: (type.fields ?? []).map((field) => normalizeComparableField(field))
3757
+ };
3758
+ }
3319
3759
  function processResolver(namespace, resolver, executorUsedResolvers, env) {
3320
3760
  const pipelines = [{
3321
3761
  name: "body",
@@ -3431,7 +3871,7 @@ function hashValue(value) {
3431
3871
  * @returns Planned changes for vaults and secrets
3432
3872
  */
3433
3873
  async function planSecretManager(context) {
3434
- const { client, workspaceId, application, forRemoval } = context;
3874
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
3435
3875
  const secretVaults = forRemoval ? [] : application.secrets;
3436
3876
  const vaultChangeSet = createChangeSet("Secret Manager vaults");
3437
3877
  const secretChangeSet = createChangeSet("Secret Manager secrets");
@@ -3453,10 +3893,11 @@ async function planSecretManager(context) {
3453
3893
  });
3454
3894
  const existingVaults = {};
3455
3895
  await Promise.all(existingVaultList.map(async (resource) => {
3456
- const { metadata } = await client.getMetadata({ trn: vaultTrn(workspaceId, resource.name) });
3896
+ const { metadata } = await client.getMetadata({ trn: vaultTrn$1(workspaceId, resource.name) });
3457
3897
  existingVaults[resource.name] = {
3458
3898
  resource,
3459
- label: metadata?.labels[sdkNameLabelKey]
3899
+ label: metadata?.labels[sdkNameLabelKey],
3900
+ allLabels: metadata?.labels
3460
3901
  };
3461
3902
  }));
3462
3903
  const state = loadSecretsState();
@@ -3464,6 +3905,7 @@ async function planSecretManager(context) {
3464
3905
  const vaultName = vault.vaultName;
3465
3906
  const existing = existingVaults[vaultName];
3466
3907
  if (existing) {
3908
+ const metaRequest = await buildMetaRequest(vaultTrn$1(workspaceId, vaultName), application.name);
3467
3909
  if (!existing.label) unmanaged.push({
3468
3910
  resourceType: "Secret Manager vault",
3469
3911
  resourceName: vaultName
@@ -3473,7 +3915,8 @@ async function planSecretManager(context) {
3473
3915
  resourceName: vaultName,
3474
3916
  currentOwner: existing.label
3475
3917
  });
3476
- vaultChangeSet.updates.push({
3918
+ if (existing.label === application.name && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) vaultChangeSet.unchanged.push({ name: vaultName });
3919
+ else vaultChangeSet.updates.push({
3477
3920
  name: vaultName,
3478
3921
  workspaceId
3479
3922
  });
@@ -3499,7 +3942,9 @@ async function planSecretManager(context) {
3499
3942
  })).map((s) => s.name);
3500
3943
  const existingSet = new Set(existingSecrets);
3501
3944
  for (const secret of vault.secrets) if (existingSet.has(secret.name)) {
3502
- if (hashValue(secret.value) !== state.vaults[vaultName]?.[secret.name]) secretChangeSet.updates.push({
3945
+ const currentHash = hashValue(secret.value);
3946
+ const storedHash = state.vaults[vaultName]?.[secret.name];
3947
+ if (forceApplyAll || currentHash !== storedHash) secretChangeSet.updates.push({
3503
3948
  name: `${vaultName}/${secret.name}`,
3504
3949
  secretName: secret.name,
3505
3950
  workspaceId,
@@ -3562,7 +4007,7 @@ async function planSecretManager(context) {
3562
4007
  resourceOwners
3563
4008
  };
3564
4009
  }
3565
- function vaultTrn(workspaceId, name) {
4010
+ function vaultTrn$1(workspaceId, name) {
3566
4011
  return `trn:v1:workspace:${workspaceId}:vault:${name}`;
3567
4012
  }
3568
4013
  /**
@@ -3582,12 +4027,12 @@ async function applySecretManager(client, result, phase = "create-update", appli
3582
4027
  secretmanagerVaultName: create.name
3583
4028
  });
3584
4029
  if (application) {
3585
- const metaRequest = await buildMetaRequest(vaultTrn(create.workspaceId, create.name), application.name);
4030
+ const metaRequest = await buildMetaRequest(vaultTrn$1(create.workspaceId, create.name), application.name);
3586
4031
  await client.setMetadata(metaRequest);
3587
4032
  }
3588
4033
  }));
3589
4034
  if (application) await Promise.all(vaultChangeSet.updates.map(async (update) => {
3590
- const metaRequest = await buildMetaRequest(vaultTrn(update.workspaceId, update.name), application.name);
4035
+ const metaRequest = await buildMetaRequest(vaultTrn$1(update.workspaceId, update.name), application.name);
3591
4036
  await client.setMetadata(metaRequest);
3592
4037
  }));
3593
4038
  await Promise.all(secretChangeSet.creates.map((create) => client.createSecretManagerSecret({
@@ -3655,6 +4100,21 @@ async function applyStaticWebsite(client, result, phase = "create-update") {
3655
4100
  function trn$1(workspaceId, name) {
3656
4101
  return `trn:v1:workspace:${workspaceId}:staticwebsite:${name}`;
3657
4102
  }
4103
+ function normalizeComparableStaticWebsiteShape(input) {
4104
+ return {
4105
+ description: input.description,
4106
+ allowedIpAddresses: [...input.allowedIpAddresses].sort()
4107
+ };
4108
+ }
4109
+ function normalizeComparableStaticWebsite(input) {
4110
+ return normalizeComparableStaticWebsiteShape({
4111
+ description: input.description || "",
4112
+ allowedIpAddresses: [...input.allowedIpAddresses || []]
4113
+ });
4114
+ }
4115
+ function areStaticWebsitesEqual(existing, desired) {
4116
+ return areNormalizedEqual(normalizeComparableStaticWebsite(existing), normalizeComparableStaticWebsite(desired));
4117
+ }
3658
4118
  /**
3659
4119
  * Plan static website changes based on current and desired state.
3660
4120
  * @param context - Planning context
@@ -3684,7 +4144,8 @@ async function planStaticWebsite(context) {
3684
4144
  const { metadata } = await client.getMetadata({ trn: trn$1(workspaceId, resource.name) });
3685
4145
  existingWebsites[resource.name] = {
3686
4146
  resource,
3687
- label: metadata?.labels[sdkNameLabelKey]
4147
+ label: metadata?.labels[sdkNameLabelKey],
4148
+ allLabels: metadata?.labels
3688
4149
  };
3689
4150
  }));
3690
4151
  const staticWebsiteServices = forRemoval ? [] : application.staticWebsiteServices;
@@ -3693,7 +4154,17 @@ async function planStaticWebsite(context) {
3693
4154
  const name = websiteService.name;
3694
4155
  const existing = existingWebsites[name];
3695
4156
  const metaRequest = await buildMetaRequest(trn$1(workspaceId, name), application.name);
4157
+ const desired = normalizeComparableStaticWebsite(config);
4158
+ const request = {
4159
+ workspaceId,
4160
+ staticwebsite: {
4161
+ name,
4162
+ description: config.description || "",
4163
+ allowedIpAddresses: config.allowedIpAddresses || []
4164
+ }
4165
+ };
3696
4166
  if (existing) {
4167
+ const isManagedByApp = existing.label === application.name;
3697
4168
  if (!existing.label) unmanaged.push({
3698
4169
  resourceType: "StaticWebsite",
3699
4170
  resourceName: name
@@ -3703,29 +4174,16 @@ async function planStaticWebsite(context) {
3703
4174
  resourceName: name,
3704
4175
  currentOwner: existing.label
3705
4176
  });
3706
- changeSet.updates.push({
4177
+ if (isManagedByApp && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areStaticWebsitesEqual(existing.resource, desired)) changeSet.unchanged.push({ name });
4178
+ else changeSet.updates.push({
3707
4179
  name,
3708
- request: {
3709
- workspaceId,
3710
- staticwebsite: {
3711
- name,
3712
- description: config.description || "",
3713
- allowedIpAddresses: config.allowedIpAddresses || []
3714
- }
3715
- },
4180
+ request,
3716
4181
  metaRequest
3717
4182
  });
3718
4183
  delete existingWebsites[name];
3719
4184
  } else changeSet.creates.push({
3720
4185
  name,
3721
- request: {
3722
- workspaceId,
3723
- staticwebsite: {
3724
- name,
3725
- description: config.description || "",
3726
- allowedIpAddresses: config.allowedIpAddresses || []
3727
- }
3728
- },
4186
+ request,
3729
4187
  metaRequest
3730
4188
  });
3731
4189
  }
@@ -3949,8 +4407,11 @@ const INITIAL_SCHEMA_NUMBER = 0;
3949
4407
  * Migration file names (used within migration directories)
3950
4408
  */
3951
4409
  const SCHEMA_FILE_NAME = "schema.json";
4410
+ /** File name for migration diff metadata. */
3952
4411
  const DIFF_FILE_NAME = "diff.json";
4412
+ /** File name for migration script. */
3953
4413
  const MIGRATE_FILE_NAME = "migrate.ts";
4414
+ /** File name for generated DB type definitions. */
3954
4415
  const DB_TYPES_FILE_NAME = "db.ts";
3955
4416
  /**
3956
4417
  * Pattern for validating migration number format (4-digit sequential number)
@@ -5848,7 +6309,7 @@ async function executeSingleMigrationPostPhase(client, changeSet, migration) {
5848
6309
  * @returns Planned changes
5849
6310
  */
5850
6311
  async function planTailorDB(context) {
5851
- const { client, workspaceId, application, forRemoval, config, noSchemaCheck } = context;
6312
+ const { client, workspaceId, application, forRemoval, config, noSchemaCheck, forceApplyAll = false } = context;
5852
6313
  const tailordbs = [];
5853
6314
  if (!forRemoval) for (const tailordb of application.tailorDBServices) {
5854
6315
  await tailordb.loadTypes();
@@ -5857,7 +6318,7 @@ async function planTailorDB(context) {
5857
6318
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
5858
6319
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices(client, workspaceId, application.name, tailordbs);
5859
6320
  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)]);
6321
+ const [typeChangeSet, gqlPermissionChangeSet] = await Promise.all([planTypes(client, workspaceId, tailordbs, executors, deletedServices, void 0, forceApplyAll), planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll)]);
5861
6322
  serviceChangeSet.print();
5862
6323
  typeChangeSet.print();
5863
6324
  gqlPermissionChangeSet.print();
@@ -5881,6 +6342,21 @@ async function planTailorDB(context) {
5881
6342
  function trn(workspaceId, name) {
5882
6343
  return `${trnPrefix(workspaceId)}:tailordb:${name}`;
5883
6344
  }
6345
+ function normalizeComparableTailorDBService(service) {
6346
+ return normalizeProtoConfig({
6347
+ namespace: service.namespace,
6348
+ defaultTimezone: service.defaultTimezone || "UTC"
6349
+ });
6350
+ }
6351
+ function areTailorDBServicesEqual(existing, desired) {
6352
+ return areNormalizedEqual(normalizeComparableTailorDBService({
6353
+ namespace: existing.namespace?.name,
6354
+ defaultTimezone: existing.defaultTimezone
6355
+ }), normalizeComparableTailorDBService({
6356
+ namespace: desired.namespace,
6357
+ defaultTimezone: "UTC"
6358
+ }));
6359
+ }
5884
6360
  async function planServices(client, workspaceId, appName, tailordbs) {
5885
6361
  const changeSet = createChangeSet("TailorDB services");
5886
6362
  const conflicts = [];
@@ -5922,7 +6398,8 @@ async function planServices(client, workspaceId, appName, tailordbs) {
5922
6398
  resourceName: tailordb.namespace,
5923
6399
  currentOwner: existing.label
5924
6400
  });
5925
- changeSet.updates.push({
6401
+ if (existing.label === appName && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areTailorDBServicesEqual(existing.resource, tailordb)) changeSet.unchanged.push({ name: tailordb.namespace });
6402
+ else changeSet.updates.push({
5926
6403
  name: tailordb.namespace,
5927
6404
  metaRequest
5928
6405
  });
@@ -5955,7 +6432,7 @@ async function planServices(client, workspaceId, appName, tailordbs) {
5955
6432
  resourceOwners
5956
6433
  };
5957
6434
  }
5958
- async function planTypes(client, workspaceId, tailordbs, executors, deletedServices, filteredTypesByNamespace) {
6435
+ async function planTypes(client, workspaceId, tailordbs, executors, deletedServices, filteredTypesByNamespace, forceApplyAll = false) {
5959
6436
  const changeSet = createChangeSet("TailorDB types");
5960
6437
  const fetchTypes = (namespaceName) => {
5961
6438
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -5974,7 +6451,7 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
5974
6451
  });
5975
6452
  };
5976
6453
  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);
6454
+ for (const executor of executors) if (executor.trigger.kind === "tailordb") executorUsedTypes.add(executor.trigger.typeName);
5978
6455
  for (const tailordb of tailordbs) {
5979
6456
  const types = filteredTypesByNamespace?.get(tailordb.namespace) ?? tailordb.types;
5980
6457
  for (const typeName of Object.keys(types)) {
@@ -5984,13 +6461,14 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
5984
6461
  }
5985
6462
  for (const tailordb of tailordbs) {
5986
6463
  const existingTypes = await fetchTypes(tailordb.namespace);
5987
- const existingNameSet = /* @__PURE__ */ new Set();
5988
- existingTypes.forEach((type) => existingNameSet.add(type.name));
6464
+ const existingTypesMap = new Map(existingTypes.map((type) => [type.name, type]));
5989
6465
  const types = filteredTypesByNamespace?.get(tailordb.namespace) ?? tailordb.types;
5990
6466
  for (const typeName of Object.keys(types)) {
5991
6467
  const tailordbType = generateTailorDBTypeManifest(types[typeName], executorUsedTypes, tailordb.config.gqlOperations);
5992
- if (existingNameSet.has(typeName)) {
5993
- changeSet.updates.push({
6468
+ const existingType = existingTypesMap.get(typeName);
6469
+ if (existingType) {
6470
+ if (!forceApplyAll && areNormalizedEqual(normalizeComparableTailorDBType(existingType), normalizeComparableTailorDBType(tailordbType))) changeSet.unchanged.push({ name: typeName });
6471
+ else changeSet.updates.push({
5994
6472
  name: typeName,
5995
6473
  request: {
5996
6474
  workspaceId,
@@ -5998,7 +6476,7 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
5998
6476
  tailordbType
5999
6477
  }
6000
6478
  });
6001
- existingNameSet.delete(typeName);
6479
+ existingTypesMap.delete(typeName);
6002
6480
  } else changeSet.creates.push({
6003
6481
  name: typeName,
6004
6482
  request: {
@@ -6008,7 +6486,7 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
6008
6486
  }
6009
6487
  });
6010
6488
  }
6011
- existingNameSet.forEach((name) => {
6489
+ existingTypesMap.forEach((_type, name) => {
6012
6490
  changeSet.deletes.push({
6013
6491
  name,
6014
6492
  request: {
@@ -6031,6 +6509,66 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
6031
6509
  });
6032
6510
  return changeSet;
6033
6511
  }
6512
+ function isPlainObject(value) {
6513
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6514
+ }
6515
+ const tailordbCompareKnownDefaults = {
6516
+ disableGqlOperations: {
6517
+ create: false,
6518
+ update: false,
6519
+ delete: false,
6520
+ read: false
6521
+ },
6522
+ emptyExpression: "",
6523
+ numericStringPaths: new Set([
6524
+ "schema.fields.*.serial.start",
6525
+ "schema.fields.*.serial.maxValue",
6526
+ "schema.settings.defaultQueryLimitSize",
6527
+ "schema.settings.maxBulkUpsertSize"
6528
+ ])
6529
+ };
6530
+ function normalizeComparableTailorDBType(type) {
6531
+ const normalized = normalizeProtoConfig(type);
6532
+ return normalizeTailorDBCompareValue({
6533
+ name: normalized?.name ?? "",
6534
+ schema: {
6535
+ description: normalized?.schema?.description ?? "",
6536
+ fields: normalized?.schema?.fields ?? {},
6537
+ relationships: normalized?.schema?.relationships ?? {},
6538
+ settings: normalized?.schema?.settings ?? {},
6539
+ indexes: normalized?.schema?.indexes ?? {},
6540
+ files: normalized?.schema?.files ?? {},
6541
+ permission: normalized?.schema?.permission ?? {}
6542
+ }
6543
+ }, []);
6544
+ }
6545
+ function normalizeTailorDBCompareValue(value, path) {
6546
+ if (value === void 0 || value === null) return value;
6547
+ if (typeof value === "number" || typeof value === "bigint" || typeof value === "string") {
6548
+ if (matchesNumericStringPath(path) && isNumericLikeValue(value)) return String(value);
6549
+ if (path.at(-1) === "expr" && value === tailordbCompareKnownDefaults.emptyExpression) return;
6550
+ return value;
6551
+ }
6552
+ if (Array.isArray(value)) return value.map((item, index) => normalizeTailorDBCompareValue(item, [...path, index])).filter((item) => item !== void 0);
6553
+ if (!isPlainObject(value)) return value;
6554
+ const normalizedEntries = Object.entries(value).map(([key, entryValue]) => [key, normalizeTailorDBCompareValue(entryValue, [...path, key])]).filter(([, entryValue]) => entryValue !== void 0);
6555
+ const normalizedObject = Object.fromEntries(normalizedEntries);
6556
+ if (path.at(-1) === "fields" && Object.keys(normalizedObject).length === 0) return;
6557
+ if (path.at(-1) === "disableGqlOperations" && areNormalizedEqual(normalizedObject, tailordbCompareKnownDefaults.disableGqlOperations)) return;
6558
+ return normalizedObject;
6559
+ }
6560
+ function matchesNumericStringPath(path) {
6561
+ const pathKey = path.map((segment) => String(segment)).join(".");
6562
+ return [...tailordbCompareKnownDefaults.numericStringPaths].some((pattern) => {
6563
+ const patternParts = pattern.split(".");
6564
+ const pathParts = pathKey.split(".");
6565
+ if (patternParts.length !== pathParts.length) return false;
6566
+ return patternParts.every((part, index) => part === "*" || part === pathParts[index]);
6567
+ });
6568
+ }
6569
+ function isNumericLikeValue(value) {
6570
+ return typeof value === "number" || typeof value === "bigint" || /^-?\d+$/.test(value);
6571
+ }
6034
6572
  /**
6035
6573
  * Generate a TailorDB type manifest from parsed type
6036
6574
  * @param {TailorDBType} type - Parsed TailorDB type
@@ -6267,7 +6805,7 @@ function protoOperand(operand) {
6267
6805
  value: fromJson(ValueSchema, operand)
6268
6806
  } };
6269
6807
  }
6270
- async function planGqlPermissions(client, workspaceId, tailordbs, deletedServices) {
6808
+ async function planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll = false) {
6271
6809
  const changeSet = createChangeSet("TailorDB gqlPermissions");
6272
6810
  const fetchGqlPermissions = (namespaceName) => {
6273
6811
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -6295,14 +6833,17 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
6295
6833
  for (const typeName of Object.keys(types)) {
6296
6834
  const gqlPermission = types[typeName].permissions.gql;
6297
6835
  if (!gqlPermission) continue;
6836
+ const desiredPermission = protoGqlPermission(gqlPermission);
6837
+ const existingPermission = existingGqlPermissions.find((entry) => entry.typeName === typeName);
6298
6838
  if (existingNameSet.has(typeName)) {
6299
- changeSet.updates.push({
6839
+ if (!forceApplyAll && existingPermission && areNormalizedEqual(normalizeComparableGqlPermission(existingPermission.permission), normalizeComparableGqlPermission(desiredPermission))) changeSet.unchanged.push({ name: typeName });
6840
+ else changeSet.updates.push({
6300
6841
  name: typeName,
6301
6842
  request: {
6302
6843
  workspaceId,
6303
6844
  namespaceName: tailordb.namespace,
6304
6845
  typeName,
6305
- permission: protoGqlPermission(gqlPermission)
6846
+ permission: desiredPermission
6306
6847
  }
6307
6848
  });
6308
6849
  existingNameSet.delete(typeName);
@@ -6312,7 +6853,7 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
6312
6853
  workspaceId,
6313
6854
  namespaceName: tailordb.namespace,
6314
6855
  typeName,
6315
- permission: protoGqlPermission(gqlPermission)
6856
+ permission: desiredPermission
6316
6857
  }
6317
6858
  });
6318
6859
  }
@@ -6339,6 +6880,12 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
6339
6880
  });
6340
6881
  return changeSet;
6341
6882
  }
6883
+ function normalizeComparableGqlPermission(permission) {
6884
+ return { policies: (normalizeProtoConfig(permission)?.policies ?? []).map((policy) => ({
6885
+ ...policy,
6886
+ actions: [...policy.actions ?? []].sort((left, right) => left - right)
6887
+ })) };
6888
+ }
6342
6889
  function protoGqlPermission(permission) {
6343
6890
  return { policies: permission.map((policy) => protoGqlPolicy(policy)) };
6344
6891
  }
@@ -6503,7 +7050,7 @@ function formatMigrationCheckResults(results) {
6503
7050
  async function applyWorkflow(client, result, phase = "create-update") {
6504
7051
  const { changeSet, appName } = result;
6505
7052
  if (phase === "create-update") {
6506
- const jobFunctionVersions = await registerJobFunctions(client, changeSet, appName);
7053
+ const jobFunctionVersions = await registerJobFunctions(client, changeSet, appName, result.unchangedWorkflowJobNames);
6507
7054
  await Promise.all([...changeSet.creates.map(async (create) => {
6508
7055
  const filteredVersions = filterJobFunctionVersions(jobFunctionVersions, create.usedJobNames);
6509
7056
  await client.createWorkflow({
@@ -6549,14 +7096,16 @@ function filterJobFunctionVersions(allVersions, usedJobNames) {
6549
7096
  * @param client - Operator client instance
6550
7097
  * @param changeSet - Workflow change set
6551
7098
  * @param appName - Application name
7099
+ * @param unchangedWorkflowJobNames - Job function names used by unchanged workflows
6552
7100
  * @returns Map of job function names to versions
6553
7101
  */
6554
- async function registerJobFunctions(client, changeSet, appName) {
7102
+ async function registerJobFunctions(client, changeSet, appName, unchangedWorkflowJobNames = /* @__PURE__ */ new Set()) {
6555
7103
  const jobFunctionVersions = {};
6556
- const firstWorkflow = changeSet.creates[0] || changeSet.updates[0];
7104
+ const firstWorkflow = changeSet.creates[0] || changeSet.updates[0] || changeSet.deletes[0];
6557
7105
  if (!firstWorkflow) return jobFunctionVersions;
6558
7106
  const { workspaceId } = firstWorkflow;
6559
7107
  const allUsedJobNames = /* @__PURE__ */ new Set();
7108
+ unchangedWorkflowJobNames.forEach((jobName) => allUsedJobNames.add(jobName));
6560
7109
  for (const item of [...changeSet.creates, ...changeSet.updates]) for (const jobName of item.usedJobNames) allUsedJobNames.add(jobName);
6561
7110
  const existingJobFunctions = await fetchAll(async (pageToken, maxPageSize) => {
6562
7111
  const response = await client.listWorkflowJobFunctions({
@@ -6567,23 +7116,25 @@ async function registerJobFunctions(client, changeSet, appName) {
6567
7116
  return [response.jobFunctions.map((j) => j.name), response.nextPageToken];
6568
7117
  });
6569
7118
  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;
7119
+ if (changeSet.creates.length > 0 || changeSet.updates.length > 0) {
7120
+ const results = await Promise.all(Array.from(allUsedJobNames).map(async (jobName) => {
7121
+ const response = existingJobNamesSet.has(jobName) ? await client.updateWorkflowJobFunction({
7122
+ workspaceId,
7123
+ jobFunctionName: jobName,
7124
+ scriptRef: workflowJobFunctionName(jobName)
7125
+ }) : await client.createWorkflowJobFunction({
7126
+ workspaceId,
7127
+ jobFunctionName: jobName,
7128
+ scriptRef: workflowJobFunctionName(jobName)
7129
+ });
7130
+ await client.setMetadata(await buildMetaRequest(jobFunctionTrn(workspaceId, jobName), appName));
7131
+ return {
7132
+ jobName,
7133
+ version: response.jobFunction?.version
7134
+ };
7135
+ }));
7136
+ for (const { jobName, version } of results) if (version) jobFunctionVersions[jobName] = version;
7137
+ }
6587
7138
  const unusedJobFunctions = existingJobFunctions.filter((jobName) => !allUsedJobNames.has(jobName));
6588
7139
  await Promise.all(unusedJobFunctions.map(async (jobName) => {
6589
7140
  const { metadata } = await client.getMetadata({ trn: jobFunctionTrn(workspaceId, jobName) });
@@ -6611,7 +7162,7 @@ function toRetryPolicy(policy) {
6611
7162
  backoffMultiplier: policy.backoffMultiplier
6612
7163
  };
6613
7164
  }
6614
- function workflowTrn(workspaceId, name) {
7165
+ function workflowTrn$1(workspaceId, name) {
6615
7166
  return `trn:v1:workspace:${workspaceId}:workflow:${name}`;
6616
7167
  }
6617
7168
  function jobFunctionTrn(workspaceId, name) {
@@ -6624,35 +7175,35 @@ function jobFunctionTrn(workspaceId, name) {
6624
7175
  * @param appName - Application name
6625
7176
  * @param workflows - Parsed workflows
6626
7177
  * @param mainJobDeps - Main job dependencies by workflow
7178
+ * @param unchangedJobFunctions - Job functions already proven unchanged by function registry plan
6627
7179
  * @returns Planned workflow changes
6628
7180
  */
6629
- async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps) {
7181
+ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps, unchangedJobFunctions = /* @__PURE__ */ new Set()) {
6630
7182
  const changeSet = createChangeSet("Workflows");
6631
7183
  const conflicts = [];
6632
7184
  const unmanaged = [];
6633
7185
  const resourceOwners = /* @__PURE__ */ new Set();
7186
+ const unchangedWorkflowJobNames = /* @__PURE__ */ new Set();
6634
7187
  const withoutLabel = await fetchAll(async (pageToken, maxPageSize) => {
6635
7188
  const response = await client.listWorkflows({
6636
7189
  workspaceId,
6637
7190
  pageToken,
6638
7191
  pageSize: maxPageSize
6639
7192
  });
6640
- return [response.workflows.map((w) => ({
6641
- id: w.id,
6642
- name: w.name
6643
- })), response.nextPageToken];
7193
+ return [response.workflows, response.nextPageToken];
6644
7194
  });
6645
7195
  const existingWorkflows = {};
6646
7196
  await Promise.all(withoutLabel.map(async (resource) => {
6647
- const { metadata } = await client.getMetadata({ trn: workflowTrn(workspaceId, resource.name) });
7197
+ const { metadata } = await client.getMetadata({ trn: workflowTrn$1(workspaceId, resource.name) });
6648
7198
  existingWorkflows[resource.name] = {
6649
7199
  resource,
6650
- label: metadata?.labels[sdkNameLabelKey]
7200
+ label: metadata?.labels[sdkNameLabelKey],
7201
+ allLabels: metadata?.labels
6651
7202
  };
6652
7203
  }));
6653
7204
  for (const workflow of Object.values(workflows)) {
6654
7205
  const existing = existingWorkflows[workflow.name];
6655
- const metaRequest = await buildMetaRequest(workflowTrn(workspaceId, workflow.name), appName);
7206
+ const metaRequest = await buildMetaRequest(workflowTrn$1(workspaceId, workflow.name), appName);
6656
7207
  const usedJobNames = mainJobDeps[workflow.mainJob.name];
6657
7208
  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
7209
  if (existing) {
@@ -6665,7 +7216,10 @@ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps
6665
7216
  resourceName: workflow.name,
6666
7217
  currentOwner: existing.label
6667
7218
  });
6668
- changeSet.updates.push({
7219
+ if (existing.label === appName && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && canTreatWorkflowAsUnchanged(existing.resource, workflow, usedJobNames, unchangedJobFunctions)) {
7220
+ changeSet.unchanged.push({ name: workflow.name });
7221
+ for (const jobName of usedJobNames) unchangedWorkflowJobNames.add(jobName);
7222
+ } else changeSet.updates.push({
6669
7223
  name: workflow.name,
6670
7224
  workspaceId,
6671
7225
  workflow,
@@ -6696,12 +7250,158 @@ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps
6696
7250
  conflicts,
6697
7251
  unmanaged,
6698
7252
  resourceOwners,
6699
- appName
7253
+ appName,
7254
+ unchangedWorkflowJobNames
7255
+ };
7256
+ }
7257
+ function canTreatWorkflowAsUnchanged(existing, workflow, usedJobNames, unchangedJobFunctions) {
7258
+ if (!usedJobNames.every((jobName) => unchangedJobFunctions.has(jobName))) return false;
7259
+ return areWorkflowsEqual(existing, workflow, usedJobNames);
7260
+ }
7261
+ function areWorkflowsEqual(existing, workflow, usedJobNames) {
7262
+ return existing.mainJobFunctionName === workflow.mainJob.name && areNormalizedEqual(normalizeComparableExistingWorkflowRetryPolicy(existing.retryPolicy), normalizeComparableWorkflowRetryPolicy(workflow.retryPolicy)) && areNormalizedEqual(normalizeComparableWorkflowJobNames(existing.jobFunctions), normalizeComparableWorkflowJobNames(usedJobNames));
7263
+ }
7264
+ function normalizeComparableExistingWorkflowRetryPolicy(policy) {
7265
+ if (!policy) return;
7266
+ return normalizeRetryPolicyForCompare({
7267
+ maxRetries: policy.maxRetries ?? 0,
7268
+ backoffMultiplier: policy.backoffMultiplier ?? 0,
7269
+ initialBackoff: {
7270
+ seconds: policy.initialBackoff?.seconds ?? 0n,
7271
+ nanos: policy.initialBackoff?.nanos ?? 0
7272
+ },
7273
+ maxBackoff: {
7274
+ seconds: policy.maxBackoff?.seconds ?? 0n,
7275
+ nanos: policy.maxBackoff?.nanos ?? 0
7276
+ }
7277
+ });
7278
+ }
7279
+ function normalizeComparableWorkflowRetryPolicy(policy) {
7280
+ if (!policy) return;
7281
+ return normalizeRetryPolicyForCompare({
7282
+ maxRetries: policy.maxRetries,
7283
+ backoffMultiplier: policy.backoffMultiplier,
7284
+ initialBackoff: parseDurationToProto(policy.initialBackoff),
7285
+ maxBackoff: parseDurationToProto(policy.maxBackoff)
7286
+ });
7287
+ }
7288
+ function normalizeComparableWorkflowJobNames(jobFunctions) {
7289
+ return Array.isArray(jobFunctions) ? [...jobFunctions].sort() : Object.keys(jobFunctions ?? {}).sort();
7290
+ }
7291
+ function normalizeRetryPolicyForCompare(policy) {
7292
+ return {
7293
+ maxRetries: policy.maxRetries,
7294
+ backoffMultiplier: policy.backoffMultiplier,
7295
+ initialBackoff: {
7296
+ seconds: String(policy.initialBackoff.seconds),
7297
+ nanos: policy.initialBackoff.nanos
7298
+ },
7299
+ maxBackoff: {
7300
+ seconds: String(policy.maxBackoff.seconds),
7301
+ nanos: policy.maxBackoff.nanos
7302
+ }
6700
7303
  };
6701
7304
  }
6702
7305
 
6703
7306
  //#endregion
6704
7307
  //#region src/cli/commands/apply/apply.ts
7308
+ function applicationTrn(workspaceId, name) {
7309
+ return `trn:v1:workspace:${workspaceId}:application:${name}`;
7310
+ }
7311
+ function functionRegistryTrn(workspaceId, name) {
7312
+ return `trn:v1:workspace:${workspaceId}:function_registry:${name}`;
7313
+ }
7314
+ function pipelineTrn(workspaceId, name) {
7315
+ return `trn:v1:workspace:${workspaceId}:pipeline:${name}`;
7316
+ }
7317
+ function idpTrn(workspaceId, name) {
7318
+ return `trn:v1:workspace:${workspaceId}:idp:${name}`;
7319
+ }
7320
+ function authTrn(workspaceId, name) {
7321
+ return `trn:v1:workspace:${workspaceId}:auth:${name}`;
7322
+ }
7323
+ function executorTrn(workspaceId, name) {
7324
+ return `trn:v1:workspace:${workspaceId}:executor:${name}`;
7325
+ }
7326
+ function workflowTrn(workspaceId, name) {
7327
+ return `trn:v1:workspace:${workspaceId}:workflow:${name}`;
7328
+ }
7329
+ function staticWebsiteTrn(workspaceId, name) {
7330
+ return `trn:v1:workspace:${workspaceId}:staticwebsite:${name}`;
7331
+ }
7332
+ function tailorDBTrn(workspaceId, name) {
7333
+ return `trn:v1:workspace:${workspaceId}:tailordb:${name}`;
7334
+ }
7335
+ function vaultTrn(workspaceId, name) {
7336
+ return `trn:v1:workspace:${workspaceId}:vault:${name}`;
7337
+ }
7338
+ async function shouldForceApplyAll(client, workspaceId, application, functionEntries) {
7339
+ const desiredLabels = (await buildMetaRequest(applicationTrn(workspaceId, application.name), application.name)).labels;
7340
+ const candidateTrns = /* @__PURE__ */ new Set();
7341
+ if (application.subgraphs.length > 0) candidateTrns.add(applicationTrn(workspaceId, application.name));
7342
+ application.staticWebsiteServices.forEach((website) => {
7343
+ candidateTrns.add(staticWebsiteTrn(workspaceId, website.name));
7344
+ });
7345
+ application.resolverServices.forEach((pipeline) => {
7346
+ candidateTrns.add(pipelineTrn(workspaceId, pipeline.namespace));
7347
+ });
7348
+ application.idpServices.forEach((idp) => {
7349
+ candidateTrns.add(idpTrn(workspaceId, idp.name));
7350
+ });
7351
+ if (application.authService) candidateTrns.add(authTrn(workspaceId, application.authService.config.name));
7352
+ Object.values(application.executorService?.executors ?? {}).forEach((executor) => {
7353
+ candidateTrns.add(executorTrn(workspaceId, executor.name));
7354
+ });
7355
+ Object.values(application.workflowService?.workflows ?? {}).forEach((workflow) => {
7356
+ candidateTrns.add(workflowTrn(workspaceId, workflow.name));
7357
+ });
7358
+ application.tailorDBServices.forEach((service) => {
7359
+ candidateTrns.add(tailorDBTrn(workspaceId, service.namespace));
7360
+ });
7361
+ application.secrets.forEach((vault) => {
7362
+ candidateTrns.add(vaultTrn(workspaceId, vault.vaultName));
7363
+ });
7364
+ functionEntries.forEach((entry) => {
7365
+ candidateTrns.add(functionRegistryTrn(workspaceId, entry.name));
7366
+ });
7367
+ for (const trn of candidateTrns) try {
7368
+ const { metadata } = await client.getMetadata({ trn });
7369
+ if (metadata?.labels?.["sdk-name"] !== application.name) continue;
7370
+ if (!hasMatchingSdkVersion(metadata.labels, desiredLabels)) return true;
7371
+ } catch (error) {
7372
+ if (error instanceof ConnectError && error.code === Code.NotFound) continue;
7373
+ throw error;
7374
+ }
7375
+ return false;
7376
+ }
7377
+ function printPlanSummary(results) {
7378
+ const summary = summarizeChangeSets([
7379
+ results.functionRegistry.changeSet,
7380
+ results.tailorDB.changeSet.service,
7381
+ results.tailorDB.changeSet.type,
7382
+ results.tailorDB.changeSet.gqlPermission,
7383
+ results.staticWebsite.changeSet,
7384
+ results.idp.changeSet.service,
7385
+ results.idp.changeSet.client,
7386
+ results.auth.changeSet.service,
7387
+ results.auth.changeSet.idpConfig,
7388
+ results.auth.changeSet.userProfileConfig,
7389
+ results.auth.changeSet.tenantConfig,
7390
+ results.auth.changeSet.machineUser,
7391
+ results.auth.changeSet.oauth2Client,
7392
+ results.auth.changeSet.authHook,
7393
+ results.auth.changeSet.scim,
7394
+ results.auth.changeSet.scimResource,
7395
+ results.pipeline.changeSet.service,
7396
+ results.pipeline.changeSet.resolver,
7397
+ results.app,
7398
+ results.executor.changeSet,
7399
+ results.workflow.changeSet,
7400
+ results.secretManager.vaultChangeSet,
7401
+ results.secretManager.secretChangeSet
7402
+ ]);
7403
+ logger.log(formatPlanSummary(summary));
7404
+ }
6705
7405
  /**
6706
7406
  * Apply the configured application to the Tailor platform.
6707
7407
  * @param options - Options for apply execution
@@ -6775,9 +7475,10 @@ async function apply(options) {
6775
7475
  rootSpan.setAttribute("app.name", application.name);
6776
7476
  rootSpan.setAttribute("workspace.id", workspaceId);
6777
7477
  const workflowService = application.workflowService;
6778
- const functionEntries = collectFunctionEntries(application, workflowService?.jobs ?? [], bundledScripts);
7478
+ const functionEntries = collectFunctionEntries(application, filterBundledWorkflowJobs(workflowService?.jobs ?? [], workflowBuildResult?.usedJobNames ?? []), bundledScripts);
6779
7479
  const dryRun = options?.dryRun ?? false;
6780
7480
  const yes = options?.yes ?? false;
7481
+ const forceApplyAll = await withSpan("plan.detectSdkVersionChange", () => shouldForceApplyAll(client, workspaceId, application, functionEntries));
6781
7482
  const { functionRegistry, tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, secretManager } = await withSpan("plan", async () => {
6782
7483
  const ctx = {
6783
7484
  client,
@@ -6785,10 +7486,12 @@ async function apply(options) {
6785
7486
  application,
6786
7487
  forRemoval: false,
6787
7488
  config,
6788
- noSchemaCheck: options?.noSchemaCheck
7489
+ noSchemaCheck: options?.noSchemaCheck,
7490
+ forceApplyAll
6789
7491
  };
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)),
7492
+ const functionRegistry = await withSpan("plan.functionRegistry", () => planFunctionRegistry(client, workspaceId, application.name, functionEntries));
7493
+ const unchangedWorkflowJobs = new Set(functionRegistry.changeSet.unchanged.map((entry) => entry.name).filter((name) => name.startsWith("workflow--")).map((name) => name.slice(10)));
7494
+ const [tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, secretManager] = await Promise.all([
6792
7495
  withSpan("plan.tailorDB", () => planTailorDB(ctx)),
6793
7496
  withSpan("plan.staticWebsite", () => planStaticWebsite(ctx)),
6794
7497
  withSpan("plan.idp", () => planIdP(ctx)),
@@ -6796,7 +7499,7 @@ async function apply(options) {
6796
7499
  withSpan("plan.pipeline", () => planPipeline(ctx)),
6797
7500
  withSpan("plan.application", () => planApplication(ctx)),
6798
7501
  withSpan("plan.executor", () => planExecutor(ctx)),
6799
- withSpan("plan.workflow", () => planWorkflow(client, workspaceId, application.name, workflowService?.workflows ?? {}, workflowBuildResult?.mainJobDeps ?? {})),
7502
+ withSpan("plan.workflow", () => planWorkflow(client, workspaceId, application.name, workflowService?.workflows ?? {}, workflowBuildResult?.mainJobDeps ?? {}, unchangedWorkflowJobs)),
6800
7503
  withSpan("plan.secretManager", () => planSecretManager(ctx))
6801
7504
  ]);
6802
7505
  return {
@@ -6882,6 +7585,18 @@ async function apply(options) {
6882
7585
  }
6883
7586
  });
6884
7587
  });
7588
+ printPlanSummary({
7589
+ functionRegistry,
7590
+ tailorDB,
7591
+ staticWebsite,
7592
+ idp,
7593
+ auth,
7594
+ pipeline,
7595
+ app,
7596
+ executor,
7597
+ workflow,
7598
+ secretManager
7599
+ });
6885
7600
  if (dryRun) {
6886
7601
  logger.info("Dry run enabled. No changes applied.");
6887
7602
  return;
@@ -10965,7 +11680,7 @@ async function generate(options) {
10965
11680
  if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
10966
11681
  let pluginManager;
10967
11682
  if (plugins.length > 0) pluginManager = new PluginManager(plugins);
10968
- const { defineApplication } = await import("./application-Cwt_ifTT.mjs");
11683
+ const { defineApplication } = await import("./application-ChVyhwe-.mjs");
10969
11684
  const application = defineApplication({
10970
11685
  config,
10971
11686
  pluginManager
@@ -13170,4 +13885,4 @@ function isDeno() {
13170
13885
 
13171
13886
  //#endregion
13172
13887
  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
13888
+ //# sourceMappingURL=runtime-3P9JFpe9.mjs.map