@tailor-platform/sdk 1.35.2 → 1.37.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 (79) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/application-ILhZq_oW.mjs +4 -0
  3. package/dist/{application-BnJRroGX.mjs → application-qRGMV8Tr.mjs} +150 -35
  4. package/dist/application-qRGMV8Tr.mjs.map +1 -0
  5. package/dist/{brand-0SscafcY.mjs → brand-D-d15jx3.mjs} +1 -1
  6. package/dist/{brand-0SscafcY.mjs.map → brand-D-d15jx3.mjs.map} +1 -1
  7. package/dist/cli/index.mjs +247 -12
  8. package/dist/cli/index.mjs.map +1 -1
  9. package/dist/cli/lib.d.mts +190 -6
  10. package/dist/cli/lib.mjs +7 -7
  11. package/dist/{client-BmQP4kKS.mjs → client-424n_3T9.mjs} +1 -1
  12. package/dist/{client-CA2NM_4R.mjs → client-DllDLYmZ.mjs} +28 -11
  13. package/dist/client-DllDLYmZ.mjs.map +1 -0
  14. package/dist/configure/index.d.mts +5 -4
  15. package/dist/configure/index.mjs +42 -23
  16. package/dist/configure/index.mjs.map +1 -1
  17. package/dist/{crash-report-CPkI2-cp.mjs → crash-report-CDQ2JvgR.mjs} +4 -4
  18. package/dist/{crash-report-CPkI2-cp.mjs.map → crash-report-CDQ2JvgR.mjs.map} +1 -1
  19. package/dist/{crash-report-Bd2T8BhU.mjs → crash-report-aHnky_xH.mjs} +1 -1
  20. package/dist/{enum-constants-DI85-fPE.mjs → enum-constants-Dx82rSjf.mjs} +1 -1
  21. package/dist/{enum-constants-DI85-fPE.mjs.map → enum-constants-Dx82rSjf.mjs.map} +1 -1
  22. package/dist/env-04IQXqsl.d.mts +30 -0
  23. package/dist/{file-utils-C4rXlOVt.mjs → file-utils-DeWpvq3T.mjs} +1 -1
  24. package/dist/{file-utils-C4rXlOVt.mjs.map → file-utils-DeWpvq3T.mjs.map} +1 -1
  25. package/dist/{index-DTJkkO-t.d.mts → index-BUT18Kak.d.mts} +2 -2
  26. package/dist/{index--9iVDOXn.d.mts → index-BVJQLjyN.d.mts} +98 -12
  27. package/dist/{index-D4pBPp65.d.mts → index-C3kcXHXJ.d.mts} +2 -2
  28. package/dist/{index-niQ9Qblw.d.mts → index-CeS4FA9o.d.mts} +2 -2
  29. package/dist/{index-qVqjEYnr.d.mts → index-DnIg_LfT.d.mts} +2 -2
  30. package/dist/{interceptor-f7slMkCC.mjs → interceptor-dSNiQq71.mjs} +1 -1
  31. package/dist/{interceptor-f7slMkCC.mjs.map → interceptor-dSNiQq71.mjs.map} +1 -1
  32. package/dist/{job-CPKYCk_e.mjs → job-DkAklmE4.mjs} +2 -2
  33. package/dist/{job-CPKYCk_e.mjs.map → job-DkAklmE4.mjs.map} +1 -1
  34. package/dist/{kysely-type-DtnNdHn3.mjs → kysely-type-CwtvQuxh.mjs} +1 -1
  35. package/dist/{kysely-type-DtnNdHn3.mjs.map → kysely-type-CwtvQuxh.mjs.map} +1 -1
  36. package/dist/{logger-qz-Y4sBV.mjs → logger-C8qBDCKO.mjs} +1 -1
  37. package/dist/{logger-qz-Y4sBV.mjs.map → logger-C8qBDCKO.mjs.map} +1 -1
  38. package/dist/package-json--6dmp6-h.mjs +4 -0
  39. package/dist/{package-json-CfUqjJaQ.mjs → package-json-BHViVisJ.mjs} +1 -1
  40. package/dist/{package-json-CfUqjJaQ.mjs.map → package-json-BHViVisJ.mjs.map} +1 -1
  41. package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
  42. package/dist/plugin/builtin/enum-constants/index.mjs +1 -1
  43. package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
  44. package/dist/plugin/builtin/file-utils/index.mjs +1 -1
  45. package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
  46. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  47. package/dist/plugin/builtin/seed/index.d.mts +1 -1
  48. package/dist/plugin/builtin/seed/index.mjs +1 -1
  49. package/dist/plugin/index.d.mts +2 -1
  50. package/dist/{plugin-D8hKE6rZ.d.mts → plugin-D6P4g_2L.d.mts} +17 -36
  51. package/dist/{runtime-D4O-RfcH.mjs → runtime-D9ejnCm6.mjs} +788 -109
  52. package/dist/runtime-D9ejnCm6.mjs.map +1 -0
  53. package/dist/{schema-D27cW0Ca.mjs → schema-CnwUqPyM.mjs} +4 -361
  54. package/dist/schema-CnwUqPyM.mjs.map +1 -0
  55. package/dist/{seed-BZIFDG27.mjs → seed-DrbB1VXd.mjs} +1 -1
  56. package/dist/{seed-BZIFDG27.mjs.map → seed-DrbB1VXd.mjs.map} +1 -1
  57. package/dist/telemetry-4IOPW6wE.mjs +4 -0
  58. package/dist/{telemetry-CREcGK8y.mjs → telemetry-DwHuiNiR.mjs} +2 -2
  59. package/dist/{telemetry-CREcGK8y.mjs.map → telemetry-DwHuiNiR.mjs.map} +1 -1
  60. package/dist/types-B9ZMosul.mjs +372 -0
  61. package/dist/types-B9ZMosul.mjs.map +1 -0
  62. package/dist/types-C45jRrCM.mjs +4 -0
  63. package/dist/utils/test/index.d.mts +2 -2
  64. package/dist/utils/test/index.mjs +1 -1
  65. package/dist/{workflow.generated-DMt8PNVd.d.mts → workflow.generated-Bj_DVqGh.d.mts} +212 -4
  66. package/docs/services/auth.md +6 -5
  67. package/docs/services/executor.md +5 -12
  68. package/docs/services/idp.md +50 -0
  69. package/docs/services/resolver.md +6 -13
  70. package/docs/services/secret.md +25 -0
  71. package/docs/services/workflow.md +4 -3
  72. package/package.json +7 -6
  73. package/dist/application-BnJRroGX.mjs.map +0 -1
  74. package/dist/application-mGasp_EX.mjs +0 -4
  75. package/dist/client-CA2NM_4R.mjs.map +0 -1
  76. package/dist/package-json-D5Km1jjt.mjs +0 -4
  77. package/dist/runtime-D4O-RfcH.mjs.map +0 -1
  78. package/dist/schema-D27cW0Ca.mjs.map +0 -1
  79. package/dist/telemetry-C508zIi1.mjs +0 -4
@@ -1,10 +1,10 @@
1
1
 
2
- import { A as ExecutorTriggerType, B as AuthSCIMConfig_AuthorizationType, C as TailorDBType_PermitAction, E as FunctionExecution_Status, F as AuthOAuth2Client_ClientType, G as ConditionSchema, H as TenantProviderConfig_TenantProviderType, I as AuthOAuth2Client_GrantType, J as PageDirection, K as Condition_Operator, L as AuthSCIMAttribute_Mutability, M as AuthHookPoint, N as AuthIDPConfig_AuthType, O as ExecutorJobStatus, P as AuthInvokerSchema, R as AuthSCIMAttribute_Type, S as TailorDBType_Permission_Permit, T as IdPLang, U as UserProfileProviderConfig_UserProfileProviderType, W as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, X as Subgraph_ServiceType, Y as ApplicationSchemaUpdateAttemptStatus, _ 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 AuthConnection_Type, k as ExecutorTargetType, m as userAgent, p as resolveStaticWebsiteUrls, q as FilterSchema, u as initOperatorClient, v as TailorDBGQLPermission_Action, w as PipelineResolver_OperationType, x as TailorDBType_Permission_Operator, y as TailorDBGQLPermission_Operator, z as AuthSCIMAttribute_Uniqueness } from "./client-CA2NM_4R.mjs";
3
- import { t as db } from "./schema-D27cW0Ca.mjs";
4
- import { i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-qz-Y4sBV.mjs";
5
- import { t as readPackageJson } from "./package-json-CfUqjJaQ.mjs";
6
- import { S as readPlatformConfig, T as writePlatformConfig, _ as hashFile, a as loadConfig, b as loadAccessToken, c as createExecutorService, d as TailorDBTypeSchema, f as stringifyFunction, g as getDistDir, h as createBundleCache, m as loadFilesWithIgnores, n as generatePluginFilesIfNeeded, p as tailorUserMap, r as loadApplication, t as defineApplication, u as OAuth2ClientSchema, x as loadWorkspaceId } from "./application-BnJRroGX.mjs";
7
- import { r as withSpan } from "./telemetry-CREcGK8y.mjs";
2
+ import { A as ExecutorJobStatus, B as AuthSCIMAttribute_Type, C as TailorDBType_PermitAction, D as IdPPermissionPermit, E as IdPPermissionOperator, F as AuthIDPConfig_AuthType, G as UserProfileProviderConfig_UserProfileProviderType, H as AuthSCIMConfig_AuthorizationType, I as AuthInvokerSchema, J as Condition_Operator, K as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, L as AuthOAuth2Client_ClientType, M as ExecutorTriggerType, N as AuthConnection_Type, O as FunctionExecution_Status, P as AuthHookPoint, Q as Subgraph_ServiceType, R as AuthOAuth2Client_GrantType, S as TailorDBType_Permission_Permit, T as IdPLang, V as AuthSCIMAttribute_Uniqueness, W as TenantProviderConfig_TenantProviderType, X as PageDirection, Y as FilterSchema, Z as ApplicationSchemaUpdateAttemptStatus, _ 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 ExecutorTargetType, m as userAgent, p as resolveStaticWebsiteUrls, q as ConditionSchema, u as initOperatorClient, v as TailorDBGQLPermission_Action, w as PipelineResolver_OperationType, x as TailorDBType_Permission_Operator, y as TailorDBGQLPermission_Operator, z as AuthSCIMAttribute_Mutability } from "./client-DllDLYmZ.mjs";
3
+ import { t as db } from "./schema-CnwUqPyM.mjs";
4
+ import { i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-C8qBDCKO.mjs";
5
+ import { t as readPackageJson } from "./package-json-BHViVisJ.mjs";
6
+ import { S as readPlatformConfig, T as writePlatformConfig, _ as hashFile, a as loadConfig, b as loadAccessToken, c as createExecutorService, d as TailorDBTypeSchema, f as stringifyFunction, g as getDistDir, h as createBundleCache, m as loadFilesWithIgnores, n as generatePluginFilesIfNeeded, p as tailorUserMap, r as loadApplication, t as defineApplication, u as OAuth2ClientSchema, x as loadWorkspaceId } from "./application-qRGMV8Tr.mjs";
7
+ import { r as withSpan } from "./telemetry-DwHuiNiR.mjs";
8
8
  import { arg, createDefineCommand, defineCommand, runCommand } from "politty";
9
9
  import { z } from "zod";
10
10
  import * as fs$1 from "node:fs";
@@ -463,9 +463,10 @@ function extractAttributesFromConfig(config) {
463
463
  * @param attributeMap - Attribute map configuration
464
464
  * @param attributeList - Attribute list configuration
465
465
  * @param env - Environment configuration
466
+ * @param machineUserNames - Registered machine user names (used to narrow `authInvoker` strings)
466
467
  * @returns Generated type definition source
467
468
  */
468
- function generateTypeDefinition(attributeMap, attributeList, env) {
469
+ function generateTypeDefinition(attributeMap, attributeList, env, machineUserNames) {
469
470
  const mapFields = attributeMap ? Object.entries(attributeMap).map(([key, value]) => ` ${key}: ${value};`).join("\n") : "";
470
471
  const mapBody = !attributeMap || Object.keys(attributeMap).length === 0 ? "{}" : `{
471
472
  ${mapFields}
@@ -476,6 +477,11 @@ ${mapFields}
476
477
  const envFields = env ? Object.entries(env).map(([key, value]) => {
477
478
  return ` ${key}: ${typeof value === "string" ? `"${value}"` : String(value)};`;
478
479
  }).join("\n") : "";
480
+ const envBody = !env || Object.keys(env).length === 0 ? "{}" : `{
481
+ ${envFields}
482
+ }`;
483
+ const isValidIdentifier = (s) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(s);
484
+ const machineUserFields = machineUserNames?.length ? machineUserNames.map((name) => ` ${isValidIdentifier(name) ? name : JSON.stringify(name)}: true;`).join("\n") : "";
479
485
  return ml`
480
486
  // This file is auto-generated by @tailor-platform/sdk
481
487
  // Do not edit this file manually
@@ -484,8 +490,9 @@ ${mapFields}
484
490
  declare module "@tailor-platform/sdk" {
485
491
  interface AttributeMap ${mapBody}
486
492
  interface AttributeList ${listBody}
487
- interface Env ${!env || Object.keys(env).length === 0 ? "{}" : `{
488
- ${envFields}
493
+ interface Env ${envBody}
494
+ interface MachineUserNameRegistry ${!machineUserNames || machineUserNames.length === 0 ? "{}" : `{
495
+ ${machineUserFields}
489
496
  }`}
490
497
  }
491
498
 
@@ -496,6 +503,8 @@ export {};
496
503
  function collectAttributesFromConfig(config) {
497
504
  const auth = config.auth;
498
505
  if (!auth || typeof auth !== "object") return {};
506
+ const machineUsersObj = auth.machineUsers;
507
+ const machineUserNames = machineUsersObj && typeof machineUsersObj === "object" ? Object.keys(machineUsersObj) : void 0;
499
508
  const inferAttributeType = (field) => {
500
509
  const type = field?.type;
501
510
  const metadata = field?.metadata;
@@ -516,18 +525,22 @@ function collectAttributesFromConfig(config) {
516
525
  acc[key] = inferAttributeType(fields?.[key]);
517
526
  return acc;
518
527
  }, {}) : void 0,
519
- attributeList
528
+ attributeList,
529
+ machineUserNames
520
530
  };
521
531
  }
522
532
  if ("machineUserAttributes" in auth) {
523
533
  const machineUserAttributes = auth.machineUserAttributes;
524
- if (!machineUserAttributes) return {};
525
- return { attributeMap: Object.entries(machineUserAttributes).reduce((acc, [key, field]) => {
526
- acc[key] = inferAttributeType(field);
527
- return acc;
528
- }, {}) };
534
+ if (!machineUserAttributes) return { machineUserNames };
535
+ return {
536
+ attributeMap: Object.entries(machineUserAttributes).reduce((acc, [key, field]) => {
537
+ acc[key] = inferAttributeType(field);
538
+ return acc;
539
+ }, {}),
540
+ machineUserNames
541
+ };
529
542
  }
530
- return {};
543
+ return { machineUserNames };
531
544
  }
532
545
  /**
533
546
  * Resolve the output path for the generated type definition file.
@@ -545,13 +558,14 @@ function resolveTypeDefinitionPath(configPath) {
545
558
  async function generateUserTypes(options) {
546
559
  const { config, configPath } = options;
547
560
  try {
548
- const { attributeMap, attributeList } = extractAttributesFromConfig(config);
561
+ const { attributeMap, attributeList, machineUserNames } = extractAttributesFromConfig(config);
549
562
  if (!attributeMap && !attributeList) logger.info("No attributes found in configuration", { mode: "plain" });
550
563
  if (attributeMap) logger.debug(`Extracted AttributeMap: ${JSON.stringify(attributeMap)}`);
551
564
  if (attributeList) logger.debug(`Extracted AttributeList: ${JSON.stringify(attributeList)}`);
565
+ if (machineUserNames?.length) logger.debug(`Extracted MachineUserNames: ${JSON.stringify(machineUserNames)}`);
552
566
  const env = config.env;
553
567
  if (env) logger.debug(`Extracted Env: ${JSON.stringify(env)}`);
554
- const typeDefContent = generateTypeDefinition(attributeMap, attributeList, env);
568
+ const typeDefContent = generateTypeDefinition(attributeMap, attributeList, env, machineUserNames);
555
569
  const outputPath = resolveTypeDefinitionPath(configPath);
556
570
  fs$1.mkdirSync(path.dirname(outputPath), { recursive: true });
557
571
  fs$1.writeFileSync(outputPath, typeDefContent);
@@ -945,7 +959,6 @@ function formatPlanSummary(summary) {
945
959
  `${summary.delete} to delete`
946
960
  ];
947
961
  if (summary.replace > 0) parts.push(`${summary.replace} to replace`);
948
- parts.push(`${summary.unchanged} unchanged`);
949
962
  return `Plan: ${parts.join(", ")}`;
950
963
  }
951
964
 
@@ -1133,13 +1146,9 @@ async function planApplication(context) {
1133
1146
  applicationName: application.name
1134
1147
  }
1135
1148
  });
1136
- changeSet.print();
1137
- return changeSet;
1138
- }
1139
- if (application.subgraphs.length === 0) {
1140
- changeSet.print();
1141
1149
  return changeSet;
1142
1150
  }
1151
+ if (application.subgraphs.length === 0) return changeSet;
1143
1152
  let authNamespace;
1144
1153
  let authIdpConfigName;
1145
1154
  if (application.authService && application.authService.config) {
@@ -1191,7 +1200,6 @@ async function planApplication(context) {
1191
1200
  request,
1192
1201
  metaRequest
1193
1202
  });
1194
- changeSet.print();
1195
1203
  return changeSet;
1196
1204
  }
1197
1205
  function protoSubgraph(subgraph) {
@@ -1501,6 +1509,10 @@ function computeContentHash(content) {
1501
1509
  function functionRegistryTrn$1(workspaceId, name) {
1502
1510
  return `trn:v1:workspace:${workspaceId}:function_registry:${name}`;
1503
1511
  }
1512
+ const RESOLVER_PREFIX = "resolver--";
1513
+ const EXECUTOR_PREFIX = "executor--";
1514
+ const WORKFLOW_PREFIX = "workflow--";
1515
+ const AUTH_HOOK_PREFIX = "auth-hook--";
1504
1516
  /**
1505
1517
  * Build a function registry name for a resolver.
1506
1518
  * @param namespace - Resolver namespace
@@ -1508,7 +1520,7 @@ function functionRegistryTrn$1(workspaceId, name) {
1508
1520
  * @returns Function registry name
1509
1521
  */
1510
1522
  function resolverFunctionName(namespace, resolverName) {
1511
- return `resolver--${namespace}--${resolverName}`;
1523
+ return `${RESOLVER_PREFIX}${namespace}--${resolverName}`;
1512
1524
  }
1513
1525
  /**
1514
1526
  * Build a function registry name for an executor.
@@ -1516,7 +1528,7 @@ function resolverFunctionName(namespace, resolverName) {
1516
1528
  * @returns Function registry name
1517
1529
  */
1518
1530
  function executorFunctionName(executorName) {
1519
- return `executor--${executorName}`;
1531
+ return `${EXECUTOR_PREFIX}${executorName}`;
1520
1532
  }
1521
1533
  /**
1522
1534
  * Build a function registry name for a workflow job.
@@ -1524,7 +1536,50 @@ function executorFunctionName(executorName) {
1524
1536
  * @returns Function registry name
1525
1537
  */
1526
1538
  function workflowJobFunctionName(jobName) {
1527
- return `workflow--${jobName}`;
1539
+ return `${WORKFLOW_PREFIX}${jobName}`;
1540
+ }
1541
+ /**
1542
+ * Split function registry changes into grouped buckets by resource-name prefix.
1543
+ * @param changeSet - Function registry change set
1544
+ * @returns Grouped function registry changes by resource kind
1545
+ */
1546
+ function splitFunctionRegistryChanges(changeSet) {
1547
+ function partition(items) {
1548
+ const buckets = {
1549
+ workflowJob: [],
1550
+ resolver: [],
1551
+ executor: [],
1552
+ authHook: [],
1553
+ other: []
1554
+ };
1555
+ for (const item of items) if (item.name.startsWith("workflow--")) buckets.workflowJob.push(item);
1556
+ else if (item.name.startsWith("resolver--")) buckets.resolver.push(item);
1557
+ else if (item.name.startsWith("executor--")) buckets.executor.push(item);
1558
+ else if (item.name.startsWith("auth-hook--")) buckets.authHook.push(item);
1559
+ else buckets.other.push(item);
1560
+ return buckets;
1561
+ }
1562
+ const creates = partition(changeSet.creates);
1563
+ const updates = partition(changeSet.updates);
1564
+ const deletes = partition(changeSet.deletes);
1565
+ const replaces = partition(changeSet.replaces);
1566
+ const unchanged = partition(changeSet.unchanged);
1567
+ function collect(key) {
1568
+ return {
1569
+ creates: creates[key],
1570
+ updates: updates[key],
1571
+ deletes: deletes[key],
1572
+ replaces: replaces[key],
1573
+ unchanged: unchanged[key]
1574
+ };
1575
+ }
1576
+ return {
1577
+ workflowJobChanges: collect("workflowJob"),
1578
+ resolverFunctionChanges: collect("resolver"),
1579
+ executorFunctionChanges: collect("executor"),
1580
+ authHookFunctionChanges: collect("authHook"),
1581
+ otherChanges: collect("other")
1582
+ };
1528
1583
  }
1529
1584
  /**
1530
1585
  * Build a function registry name for an auth hook.
@@ -1687,9 +1742,13 @@ async function planFunctionRegistry(client, workspaceId, appName, entries) {
1687
1742
  workspaceId
1688
1743
  });
1689
1744
  }
1690
- changeSet.print();
1745
+ const { workflowJobChanges, resolverFunctionChanges, executorFunctionChanges, authHookFunctionChanges } = splitFunctionRegistryChanges(changeSet);
1691
1746
  return {
1692
1747
  changeSet,
1748
+ workflowJobChanges,
1749
+ resolverFunctionChanges,
1750
+ executorFunctionChanges,
1751
+ authHookFunctionChanges,
1693
1752
  conflicts,
1694
1753
  unmanaged,
1695
1754
  resourceOwners
@@ -1763,6 +1822,321 @@ async function applyFunctionRegistry(client, workspaceId, result, phase = "creat
1763
1822
  })));
1764
1823
  }
1765
1824
 
1825
+ //#endregion
1826
+ //#region src/cli/commands/apply/grouped-display.ts
1827
+ /**
1828
+ * Convert grouped function registry changes into mutable name sets.
1829
+ * @param changes - Grouped function registry changes
1830
+ * @returns Mutable name sets keyed by action
1831
+ */
1832
+ function createRelatedFunctionRegistryNameSets(changes) {
1833
+ return {
1834
+ creates: new Set(changes?.creates.map((item) => item.name) ?? []),
1835
+ updates: new Set(changes?.updates.map((item) => item.name) ?? []),
1836
+ deletes: new Set(changes?.deletes.map((item) => item.name) ?? []),
1837
+ replaces: new Set(changes?.replaces.map((item) => item.name) ?? [])
1838
+ };
1839
+ }
1840
+ const ACTION_SYMBOLS = {
1841
+ create: symbols.create,
1842
+ update: symbols.update,
1843
+ delete: symbols.delete,
1844
+ replace: symbols.replace
1845
+ };
1846
+ /**
1847
+ * Convert a plain change set into grouped display entries.
1848
+ * @param changeSet - Change set to convert
1849
+ * @param labels - Labels to attach to each entry
1850
+ * @param getNamespace - Optional callback to extract namespace from an item
1851
+ * @returns Display entries in CLI print order
1852
+ */
1853
+ function formatChangeSetEntries(changeSet, labels = [], getNamespace) {
1854
+ function toEntry(action, item) {
1855
+ return {
1856
+ action,
1857
+ symbol: ACTION_SYMBOLS[action],
1858
+ name: item.name,
1859
+ labels: [...labels],
1860
+ namespace: getNamespace?.(item)
1861
+ };
1862
+ }
1863
+ return [
1864
+ ...changeSet.creates.map((item) => toEntry("create", item)),
1865
+ ...changeSet.deletes.map((item) => toEntry("delete", item)),
1866
+ ...changeSet.updates.map((item) => toEntry("update", item)),
1867
+ ...changeSet.replaces.map((item) => toEntry("replace", item))
1868
+ ];
1869
+ }
1870
+ function formatGroupedDisplayLine(entry) {
1871
+ return entry.labels.length > 0 ? `${entry.symbol} ${entry.name} (${entry.labels.join(", ")})` : `${entry.symbol} ${entry.name}`;
1872
+ }
1873
+ function parseFunctionRegistryName(name) {
1874
+ if (name.startsWith("resolver--")) {
1875
+ const [, namespace, resolverName] = name.split("--");
1876
+ if (namespace && resolverName) return {
1877
+ displayName: resolverName,
1878
+ namespace
1879
+ };
1880
+ }
1881
+ if (name.startsWith("workflow--")) return { displayName: name.slice(WORKFLOW_PREFIX.length) };
1882
+ if (name.startsWith("executor--")) return { displayName: name.slice(EXECUTOR_PREFIX.length) };
1883
+ if (name.startsWith("auth-hook--")) {
1884
+ const [, namespace, hookPoint] = name.split("--");
1885
+ if (namespace && hookPoint) return {
1886
+ displayName: hookPoint,
1887
+ namespace
1888
+ };
1889
+ }
1890
+ return { displayName: name };
1891
+ }
1892
+ /**
1893
+ * Build function-registry-only entries that were not grouped with a parent resource.
1894
+ * @param names - Related function registry names keyed by action
1895
+ * @param consumed - Function registry names already grouped with parent resources
1896
+ * @returns Display entries for ungrouped function registry changes
1897
+ */
1898
+ function buildRemainingFunctionRegistryEntries(names, consumed = createRelatedFunctionRegistryNameSets()) {
1899
+ return [
1900
+ [
1901
+ "create",
1902
+ names.creates,
1903
+ consumed.creates
1904
+ ],
1905
+ [
1906
+ "delete",
1907
+ names.deletes,
1908
+ consumed.deletes
1909
+ ],
1910
+ [
1911
+ "update",
1912
+ names.updates,
1913
+ consumed.updates
1914
+ ],
1915
+ [
1916
+ "replace",
1917
+ names.replaces,
1918
+ consumed.replaces
1919
+ ]
1920
+ ].flatMap(([action, nameSet, consumedSet]) => [...nameSet].filter((name) => !consumedSet.has(name)).map((name) => {
1921
+ const { displayName, namespace } = parseFunctionRegistryName(name);
1922
+ return {
1923
+ action,
1924
+ symbol: ACTION_SYMBOLS[action],
1925
+ name: displayName,
1926
+ labels: ["function"],
1927
+ namespace
1928
+ };
1929
+ }));
1930
+ }
1931
+ /**
1932
+ * Format change set entries with function registry grouping.
1933
+ *
1934
+ * For each item in creates/updates/deletes, calls `getFunctionRegistryNames` to
1935
+ * derive zero or more function registry names. When a matching function registry
1936
+ * change exists for the same action, the item is displayed with both the resource
1937
+ * label and "functionRegistry". Ungrouped function registry changes are appended.
1938
+ * @param resourceLabel - Label for the resource kind (e.g. "executor", "resolver")
1939
+ * @param changeSet - Resource change set with creates/updates/deletes/replaces
1940
+ * @param changeSet.creates - Created resources
1941
+ * @param changeSet.updates - Updated resources
1942
+ * @param changeSet.deletes - Deleted resources
1943
+ * @param changeSet.replaces - Replaced resources
1944
+ * @param functionRegistryChanges - Related function registry changes
1945
+ * @param getFunctionRegistryNames - Derives function registry names from a resource item
1946
+ * @param options - Optional display callbacks
1947
+ * @param options.getNamespace - Extract namespace from an item for nested display
1948
+ * @param options.getDisplayName - Override display name for an item
1949
+ * @returns Display entries for CLI output
1950
+ */
1951
+ function formatChangeEntriesWithFunctionRegistry(resourceLabel, changeSet, functionRegistryChanges, getFunctionRegistryNames, options) {
1952
+ const { getNamespace, getDisplayName } = options ?? {};
1953
+ const functionNames = createRelatedFunctionRegistryNameSets(functionRegistryChanges);
1954
+ const consumed = createRelatedFunctionRegistryNameSets();
1955
+ function processItems(items, action, fnNameSet, consumedSet) {
1956
+ return items.map((item) => {
1957
+ const names = getFunctionRegistryNames(item, action);
1958
+ const hasMatch = names.some((name) => fnNameSet.has(name));
1959
+ if (hasMatch) {
1960
+ for (const name of names) if (fnNameSet.has(name)) consumedSet.add(name);
1961
+ }
1962
+ return {
1963
+ action,
1964
+ symbol: ACTION_SYMBOLS[action],
1965
+ name: getDisplayName?.(item) ?? item.name,
1966
+ labels: hasMatch ? [resourceLabel, "function"] : [resourceLabel],
1967
+ namespace: getNamespace?.(item)
1968
+ };
1969
+ });
1970
+ }
1971
+ return [
1972
+ ...processItems(changeSet.creates, "create", functionNames.creates, consumed.creates),
1973
+ ...processItems(changeSet.deletes, "delete", functionNames.deletes, consumed.deletes),
1974
+ ...processItems(changeSet.updates, "update", functionNames.updates, consumed.updates),
1975
+ ...changeSet.replaces.map((item) => ({
1976
+ action: "replace",
1977
+ symbol: ACTION_SYMBOLS["replace"],
1978
+ name: getDisplayName?.(item) ?? item.name,
1979
+ labels: [resourceLabel],
1980
+ namespace: getNamespace?.(item)
1981
+ })),
1982
+ ...buildRemainingFunctionRegistryEntries(functionNames, consumed)
1983
+ ];
1984
+ }
1985
+ /**
1986
+ * Extract service-level actions from a change set for namespace header display.
1987
+ * @param changeSet - Service change set
1988
+ * @returns Array of namespace actions
1989
+ */
1990
+ function extractServiceActions(changeSet) {
1991
+ return [
1992
+ ...changeSet.creates.map((item) => ({
1993
+ name: item.name,
1994
+ action: "create"
1995
+ })),
1996
+ ...changeSet.deletes.map((item) => ({
1997
+ name: item.name,
1998
+ action: "delete"
1999
+ })),
2000
+ ...changeSet.updates.map((item) => ({
2001
+ name: item.name,
2002
+ action: "update"
2003
+ })),
2004
+ ...changeSet.replaces.map((item) => ({
2005
+ name: item.name,
2006
+ action: "replace"
2007
+ }))
2008
+ ];
2009
+ }
2010
+ /**
2011
+ * Print a titled section of grouped display entries, nesting by namespace.
2012
+ * Service-level changes are shown as the namespace header symbol.
2013
+ * Services without child entries are shown as flat entries.
2014
+ * @param title - Section title
2015
+ * @param entries - Entries to print (should NOT include service entries)
2016
+ * @param serviceActions - Optional service-level actions to merge into namespace headers
2017
+ */
2018
+ function printGroupedDisplaySection(title, entries, serviceActions) {
2019
+ const serviceMap = /* @__PURE__ */ new Map();
2020
+ if (serviceActions) for (const sa of serviceActions) serviceMap.set(sa.name, sa.action);
2021
+ if (entries.length === 0 && serviceMap.size === 0) return;
2022
+ logger.log(styles.bold(`${title}:`));
2023
+ const namespaceOrder = [];
2024
+ const byNamespace = /* @__PURE__ */ new Map();
2025
+ for (const entry of entries) {
2026
+ const ns = entry.namespace;
2027
+ if (!byNamespace.has(ns)) {
2028
+ namespaceOrder.push(ns);
2029
+ byNamespace.set(ns, []);
2030
+ }
2031
+ byNamespace.get(ns).push(entry);
2032
+ }
2033
+ const printedServices = /* @__PURE__ */ new Set();
2034
+ for (const ns of namespaceOrder) {
2035
+ const group = byNamespace.get(ns);
2036
+ if (ns) {
2037
+ const svcAction = serviceMap.get(ns);
2038
+ const prefix = svcAction ? `${ACTION_SYMBOLS[svcAction]} ` : "";
2039
+ logger.log(` ${prefix}${styles.bold(`${ns}:`)}`);
2040
+ printedServices.add(ns);
2041
+ for (const entry of group) logger.log(` ${formatGroupedDisplayLine(entry)}`);
2042
+ } else for (const entry of group) logger.log(` ${formatGroupedDisplayLine(entry)}`);
2043
+ }
2044
+ for (const [name, action] of serviceMap) if (!printedServices.has(name)) logger.log(` ${ACTION_SYMBOLS[action]} ${name}`);
2045
+ }
2046
+
2047
+ //#endregion
2048
+ //#region src/parser/service/idp/permission.ts
2049
+ const operatorMap = {
2050
+ "=": "eq",
2051
+ "!=": "ne",
2052
+ in: "in",
2053
+ "not in": "nin"
2054
+ };
2055
+ function normalizeOperand(operand) {
2056
+ if (typeof operand === "object" && !Array.isArray(operand) && "user" in operand) return { user: operand.user === "id" ? "_id" : operand.user };
2057
+ return operand;
2058
+ }
2059
+ function normalizeConditions(conditions) {
2060
+ return conditions.map((cond) => {
2061
+ const [left, operator, right] = cond;
2062
+ return [
2063
+ normalizeOperand(left),
2064
+ operatorMap[operator],
2065
+ normalizeOperand(right)
2066
+ ];
2067
+ });
2068
+ }
2069
+ function isObjectFormat(p) {
2070
+ return typeof p === "object" && p !== null && "conditions" in p;
2071
+ }
2072
+ function isSingleArrayConditionFormat(cond) {
2073
+ return cond.length >= 2 && typeof cond[1] === "string";
2074
+ }
2075
+ /**
2076
+ * Normalize a single IdP action permission into the standard format.
2077
+ * @param permission - Raw permission definition
2078
+ * @returns Normalized action permission
2079
+ */
2080
+ function normalizeIdPActionPermission(permission) {
2081
+ if (isObjectFormat(permission)) {
2082
+ const conditions = permission.conditions;
2083
+ return {
2084
+ conditions: normalizeConditions(isSingleArrayConditionFormat(conditions) ? [conditions] : conditions),
2085
+ permit: permission.permit ? "allow" : "deny",
2086
+ description: permission.description
2087
+ };
2088
+ }
2089
+ if (!Array.isArray(permission)) throw new Error("Invalid permission format");
2090
+ if (isSingleArrayConditionFormat(permission)) {
2091
+ const [op1, operator, op2, permit] = [...permission, true];
2092
+ return {
2093
+ conditions: normalizeConditions([[
2094
+ op1,
2095
+ operator,
2096
+ op2
2097
+ ]]),
2098
+ permit: permit ? "allow" : "deny"
2099
+ };
2100
+ }
2101
+ const conditions = [];
2102
+ const conditionArray = permission;
2103
+ let conditionArrayPermit = true;
2104
+ for (const item of conditionArray) {
2105
+ if (typeof item === "boolean") {
2106
+ conditionArrayPermit = item;
2107
+ continue;
2108
+ }
2109
+ conditions.push(item);
2110
+ }
2111
+ return {
2112
+ conditions: normalizeConditions(conditions),
2113
+ permit: conditionArrayPermit ? "allow" : "deny"
2114
+ };
2115
+ }
2116
+ /**
2117
+ * Normalize raw IdP permission into standard form.
2118
+ * @param permission - Raw IdP permission from user config
2119
+ * @returns Normalized IdP permission
2120
+ */
2121
+ function normalizeIdPPermission(permission) {
2122
+ return {
2123
+ create: permission.create.map((p) => normalizeIdPActionPermission(p)),
2124
+ read: permission.read.map((p) => normalizeIdPActionPermission(p)),
2125
+ update: permission.update.map((p) => normalizeIdPActionPermission(p)),
2126
+ delete: permission.delete.map((p) => normalizeIdPActionPermission(p)),
2127
+ sendPasswordResetEmail: permission.sendPasswordResetEmail.map((p) => normalizeIdPActionPermission(p))
2128
+ };
2129
+ }
2130
+ /**
2131
+ * Parse raw IdP permission, returning undefined if not set.
2132
+ * @param rawPermission - Raw permission from parsed config
2133
+ * @returns Normalized permission or undefined
2134
+ */
2135
+ function parseIdPPermission(rawPermission) {
2136
+ if (!rawPermission) return;
2137
+ return normalizeIdPPermission(rawPermission);
2138
+ }
2139
+
1766
2140
  //#endregion
1767
2141
  //#region src/cli/commands/apply/idp.ts
1768
2142
  /**
@@ -1856,13 +2230,10 @@ async function planIdP(context) {
1856
2230
  const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
1857
2231
  const idps = forRemoval ? [] : application.idpServices;
1858
2232
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$3(client, workspaceId, application.name, idps);
1859
- const clientChangeSet = await planClients(client, workspaceId, idps, serviceChangeSet.deletes.map((del) => del.name), forceApplyAll);
1860
- serviceChangeSet.print();
1861
- clientChangeSet.print();
1862
2233
  return {
1863
2234
  changeSet: {
1864
2235
  service: serviceChangeSet,
1865
- client: clientChangeSet
2236
+ client: await planClients(client, workspaceId, idps, serviceChangeSet.deletes.map((del) => del.name), forceApplyAll)
1866
2237
  },
1867
2238
  conflicts,
1868
2239
  unmanaged,
@@ -1910,7 +2281,27 @@ function normalizeComparableIdPService(input) {
1910
2281
  userAuthPolicy: input.userAuthPolicy,
1911
2282
  publishUserEvents: input.publishUserEvents,
1912
2283
  disableGqlOperations: input.disableGqlOperations,
1913
- emailConfig: input.emailConfig
2284
+ emailConfig: input.emailConfig,
2285
+ permission: input.permission
2286
+ };
2287
+ }
2288
+ function normalizeComparablePermission(permission) {
2289
+ if (!permission) return;
2290
+ const normalizePolicy = (policy) => ({
2291
+ conditions: policy.conditions.map((c) => ({
2292
+ left: c.left ? { kind: c.left.kind } : void 0,
2293
+ operator: c.operator,
2294
+ right: c.right ? { kind: c.right.kind } : void 0
2295
+ })),
2296
+ permit: policy.permit,
2297
+ description: policy.description
2298
+ });
2299
+ return {
2300
+ create: permission.create.map(normalizePolicy),
2301
+ read: permission.read.map(normalizePolicy),
2302
+ update: permission.update.map(normalizePolicy),
2303
+ delete: permission.delete.map(normalizePolicy),
2304
+ sendPasswordResetEmail: permission.sendPasswordResetEmail.map(normalizePolicy)
1914
2305
  };
1915
2306
  }
1916
2307
  function areIdPServicesEqual(existing, desired) {
@@ -1920,7 +2311,8 @@ function areIdPServicesEqual(existing, desired) {
1920
2311
  userAuthPolicy: normalizeComparableUserAuthPolicy(existing.userAuthPolicy),
1921
2312
  publishUserEvents: existing.publishUserEvents,
1922
2313
  disableGqlOperations: normalizeComparableDisableGqlOperations(existing.disableGqlOperations),
1923
- emailConfig: normalizeComparableEmailConfig(existing.emailConfig)
2314
+ emailConfig: normalizeComparableEmailConfig(existing.emailConfig),
2315
+ permission: normalizeComparablePermission(existing.permission)
1924
2316
  }), desired);
1925
2317
  }
1926
2318
  async function planServices$3(client, workspaceId, appName, idps) {
@@ -1971,13 +2363,17 @@ async function planServices$3(client, workspaceId, appName, idps) {
1971
2363
  const userAuthPolicy = idp.userAuthPolicy;
1972
2364
  const publishUserEvents = idp.publishUserEvents ?? false;
1973
2365
  const emailConfig = idp.emailConfig;
2366
+ if (!idp.permission) logger.warn(`IdP service "${namespaceName}" has no permission configured.`);
2367
+ const parsedPermission = parseIdPPermission(idp.permission);
2368
+ const protoPermission = parsedPermission ? protoIdPPermission(parsedPermission) : void 0;
1974
2369
  const desired = normalizeComparableIdPService({
1975
2370
  authorization,
1976
2371
  lang,
1977
2372
  userAuthPolicy: normalizeComparableUserAuthPolicy(userAuthPolicy),
1978
2373
  publishUserEvents,
1979
2374
  disableGqlOperations: normalizeComparableDisableGqlOperations(convertGqlOperationsToDisable(idp.gqlOperations)),
1980
- emailConfig: normalizeComparableEmailConfig(emailConfig)
2375
+ emailConfig: normalizeComparableEmailConfig(emailConfig),
2376
+ permission: protoPermission
1981
2377
  });
1982
2378
  const request = {
1983
2379
  workspaceId,
@@ -1987,7 +2383,8 @@ async function planServices$3(client, workspaceId, appName, idps) {
1987
2383
  userAuthPolicy,
1988
2384
  publishUserEvents,
1989
2385
  disableGqlOperations: convertGqlOperationsToDisable(idp.gqlOperations),
1990
- emailConfig
2386
+ emailConfig,
2387
+ permission: protoPermission
1991
2388
  };
1992
2389
  if (existing) {
1993
2390
  const isManagedByApp = existing.label === appName;
@@ -2119,6 +2516,81 @@ function convertGqlOperationsToDisable(gqlOperations) {
2119
2516
  sendPasswordResetEmail: gqlOperations.sendPasswordResetEmail === false
2120
2517
  };
2121
2518
  }
2519
+ function protoIdPPermission(permission) {
2520
+ return {
2521
+ create: permission.create.map((p) => protoIdPPolicy(p)),
2522
+ read: permission.read.map((p) => protoIdPPolicy(p)),
2523
+ update: permission.update.map((p) => protoIdPPolicy(p)),
2524
+ delete: permission.delete.map((p) => protoIdPPolicy(p)),
2525
+ sendPasswordResetEmail: permission.sendPasswordResetEmail.map((p) => protoIdPPolicy(p))
2526
+ };
2527
+ }
2528
+ function protoIdPPolicy(policy) {
2529
+ let permit;
2530
+ switch (policy.permit) {
2531
+ case "allow":
2532
+ permit = IdPPermissionPermit.ALLOW;
2533
+ break;
2534
+ case "deny":
2535
+ permit = IdPPermissionPermit.DENY;
2536
+ break;
2537
+ default: throw new Error(`Unknown permission: ${policy.permit}`);
2538
+ }
2539
+ return {
2540
+ conditions: policy.conditions.map((cond) => protoIdPCondition(cond)),
2541
+ permit,
2542
+ description: policy.description
2543
+ };
2544
+ }
2545
+ function protoIdPCondition(condition) {
2546
+ const [left, operator, right] = condition;
2547
+ const l = protoIdPOperand(left);
2548
+ const r = protoIdPOperand(right);
2549
+ let op;
2550
+ switch (operator) {
2551
+ case "eq":
2552
+ op = IdPPermissionOperator.EQ;
2553
+ break;
2554
+ case "ne":
2555
+ op = IdPPermissionOperator.NE;
2556
+ break;
2557
+ case "in":
2558
+ op = IdPPermissionOperator.IN;
2559
+ break;
2560
+ case "nin":
2561
+ op = IdPPermissionOperator.NIN;
2562
+ break;
2563
+ default: throw new Error(`Unknown operator: ${operator}`);
2564
+ }
2565
+ return {
2566
+ left: l,
2567
+ operator: op,
2568
+ right: r
2569
+ };
2570
+ }
2571
+ function protoIdPOperand(operand) {
2572
+ if (typeof operand === "object" && !Array.isArray(operand)) if ("user" in operand) return { kind: {
2573
+ case: "userField",
2574
+ value: operand.user
2575
+ } };
2576
+ else if ("idpUser" in operand) return { kind: {
2577
+ case: "idpUserField",
2578
+ value: operand.idpUser
2579
+ } };
2580
+ else if ("newIdpUser" in operand) return { kind: {
2581
+ case: "newIdpUserField",
2582
+ value: operand.newIdpUser
2583
+ } };
2584
+ else if ("oldIdpUser" in operand) return { kind: {
2585
+ case: "oldIdpUserField",
2586
+ value: operand.oldIdpUser
2587
+ } };
2588
+ else throw new Error(`Unknown operand: ${JSON.stringify(operand)}`);
2589
+ return { kind: {
2590
+ case: "value",
2591
+ value: fromJson(ValueSchema, operand)
2592
+ } };
2593
+ }
2122
2594
 
2123
2595
  //#endregion
2124
2596
  //#region src/cli/commands/apply/auth.ts
@@ -2202,16 +2674,6 @@ async function planAuth(context) {
2202
2674
  planSCIMResources(client, workspaceId, auths, deletedServices),
2203
2675
  planAuthConnections(client, workspaceId, application.name, auths)
2204
2676
  ]);
2205
- serviceChangeSet.print();
2206
- idpConfigChangeSet.print();
2207
- userProfileConfigChangeSet.print();
2208
- tenantConfigChangeSet.print();
2209
- machineUserChangeSet.print();
2210
- authHookChangeSet.print();
2211
- oauth2ClientChangeSet.print();
2212
- scimChangeSet.print();
2213
- scimResourceChangeSet.print();
2214
- connectionResult.changeSet.print();
2215
2677
  return {
2216
2678
  changeSet: {
2217
2679
  service: serviceChangeSet,
@@ -2447,7 +2909,8 @@ function protoIdPConfig(idpConfig) {
2447
2909
  case: "saml",
2448
2910
  value: {
2449
2911
  ...idpConfig.metadataURL !== void 0 ? { metadataUrl: idpConfig.metadataURL } : { rawMetadata: idpConfig.rawMetadata },
2450
- enableSignRequest: idpConfig.enableSignRequest
2912
+ enableSignRequest: idpConfig.enableSignRequest,
2913
+ defaultRedirectUrl: idpConfig.defaultRedirectURL
2451
2914
  }
2452
2915
  } }
2453
2916
  };
@@ -3176,6 +3639,21 @@ function areAuthHooksEqual(existing, desired) {
3176
3639
  } : void 0
3177
3640
  });
3178
3641
  }
3642
+ /**
3643
+ * Format auth hook changes for grouped dry-run display.
3644
+ * @param changeSet - Auth hook changes
3645
+ * @param functionRegistryAuthHookChanges - Related function registry changes for auth hooks
3646
+ * @returns Display entries for auth hook output
3647
+ */
3648
+ function formatAuthHookChangeEntries(changeSet, functionRegistryAuthHookChanges) {
3649
+ return formatChangeEntriesWithFunctionRegistry("authHook", changeSet, functionRegistryAuthHookChanges, (item) => {
3650
+ const [namespace, hookPoint] = item.name.split("/");
3651
+ return namespace && hookPoint ? [authHookFunctionName(namespace, hookPoint)] : [];
3652
+ }, {
3653
+ getNamespace: (item) => item.name.split("/")[0],
3654
+ getDisplayName: (item) => item.name.split("/")[1] ?? item.name
3655
+ });
3656
+ }
3179
3657
  async function planAuthHooks(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
3180
3658
  const changeSet = createChangeSet("Auth hooks");
3181
3659
  for (const auth of auths) {
@@ -3411,6 +3889,32 @@ function buildResolverOperationHookExpr(env) {
3411
3889
  return `({ ...context.pipeline, input: context.args, user: ${tailorUserMap}, env: ${JSON.stringify(env)} });`;
3412
3890
  }
3413
3891
 
3892
+ //#endregion
3893
+ //#region src/cli/commands/apply/auth-invoker.ts
3894
+ /**
3895
+ * Normalize an authInvoker value to the object form required by the proto payload.
3896
+ *
3897
+ * Accepts either:
3898
+ * - `undefined` — returns undefined
3899
+ * - a plain string (machine user name) — expands to `{ namespace, machineUserName }` using `authNamespace`
3900
+ * - an object `{ namespace, machineUserName }` — returned as-is
3901
+ * @param authInvoker - String machine user name or object form
3902
+ * @param authNamespace - Auth service namespace (required when authInvoker is a string)
3903
+ * @param context - Contextual label used in error messages (e.g. `resolver "foo"`)
3904
+ * @returns Object form of auth invoker, or undefined
3905
+ */
3906
+ function normalizeAuthInvoker(authInvoker, authNamespace, context) {
3907
+ if (authInvoker === void 0) return void 0;
3908
+ if (typeof authInvoker === "string") {
3909
+ if (!authNamespace) throw new Error(`${context} uses a string authInvoker ("${authInvoker}"), but no Auth service is configured. Configure an Auth service or use the object form { namespace, machineUserName }.`);
3910
+ return {
3911
+ namespace: authNamespace,
3912
+ machineUserName: authInvoker
3913
+ };
3914
+ }
3915
+ return authInvoker;
3916
+ }
3917
+
3414
3918
  //#endregion
3415
3919
  //#region src/cli/commands/apply/executor.ts
3416
3920
  /**
@@ -3512,7 +4016,6 @@ async function planExecutor(context) {
3512
4016
  }
3513
4017
  });
3514
4018
  });
3515
- changeSet.print();
3516
4019
  return {
3517
4020
  changeSet,
3518
4021
  conflicts,
@@ -3520,6 +4023,31 @@ async function planExecutor(context) {
3520
4023
  resourceOwners
3521
4024
  };
3522
4025
  }
4026
+ function isFunctionBackedExecutor(executor) {
4027
+ return executor?.targetType === ExecutorTargetType.FUNCTION || executor?.targetType === ExecutorTargetType.JOB_FUNCTION;
4028
+ }
4029
+ /**
4030
+ * Build desired executor configs keyed by executor name from create/update changes.
4031
+ * @param changeSet - Executor create/update changes
4032
+ * @returns Executor configs keyed by name
4033
+ */
4034
+ function buildPlannedExecutorsByName(changeSet) {
4035
+ return Object.fromEntries([...changeSet.creates, ...changeSet.updates].map((item) => [item.name, item.request.executor]));
4036
+ }
4037
+ /**
4038
+ * Format executor changes for grouped dry-run display.
4039
+ * @param changeSet - Executor changes
4040
+ * @param executors - Desired executor configs keyed by name
4041
+ * @param functionRegistryExecutorChanges - Related function registry changes for executors
4042
+ * @returns Display entries for executor output
4043
+ */
4044
+ function formatExecutorChangeEntries(changeSet, executors, functionRegistryExecutorChanges) {
4045
+ return formatChangeEntriesWithFunctionRegistry("executor", changeSet, functionRegistryExecutorChanges, (item, action) => {
4046
+ if (action === "delete") return [executorFunctionName(item.name)];
4047
+ const executor = executors[item.name];
4048
+ return executor && isFunctionBackedExecutor(executor) ? [executorFunctionName(item.name)] : [];
4049
+ });
4050
+ }
3523
4051
  function normalizeComparableExecutor(executor) {
3524
4052
  const normalized = normalizeProtoConfig(executor) ?? {};
3525
4053
  const webhookHeaders = normalized.targetConfig?.config?.case === "webhook" ? [...normalized.targetConfig.config.value.headers ?? []].sort((left, right) => (left.key ?? "").localeCompare(right.key ?? "")) : void 0;
@@ -3667,6 +4195,8 @@ function protoExecutor(application, executor) {
3667
4195
  const target = executor.operation;
3668
4196
  let targetType;
3669
4197
  let targetConfig;
4198
+ const authNamespace = application.authService?.parsedConfig.name;
4199
+ const invokerContext = `Executor "${executor.name}"`;
3670
4200
  switch (target.kind) {
3671
4201
  case "webhook":
3672
4202
  targetType = ExecutorTargetType.WEBHOOK;
@@ -3704,7 +4234,7 @@ function protoExecutor(application, executor) {
3704
4234
  appName: target.appName ?? appName,
3705
4235
  query: target.query,
3706
4236
  variables: target.variables ? { expr: `(${stringifyFunction(target.variables)})(${argsExpr})` } : void 0,
3707
- invoker: target.authInvoker ?? void 0
4237
+ invoker: normalizeAuthInvoker(target.authInvoker, authNamespace, invokerContext)
3708
4238
  }
3709
4239
  } };
3710
4240
  break;
@@ -3718,7 +4248,7 @@ function protoExecutor(application, executor) {
3718
4248
  name: "operation",
3719
4249
  scriptRef: executorFunctionName(executor.name),
3720
4250
  variables: { expr: argsExpr },
3721
- invoker: target.authInvoker ?? void 0
4251
+ invoker: normalizeAuthInvoker(target.authInvoker, authNamespace, invokerContext)
3722
4252
  }
3723
4253
  } };
3724
4254
  break;
@@ -3729,7 +4259,7 @@ function protoExecutor(application, executor) {
3729
4259
  value: {
3730
4260
  workflowName: target.workflowName,
3731
4261
  variables: target.args ? typeof target.args === "function" ? { expr: `(${stringifyFunction(target.args)})(${argsExpr})` } : { expr: JSON.stringify(target.args) } : void 0,
3732
- invoker: target.authInvoker ?? void 0
4262
+ invoker: normalizeAuthInvoker(target.authInvoker, authNamespace, invokerContext)
3733
4263
  }
3734
4264
  } };
3735
4265
  break;
@@ -3821,9 +4351,7 @@ async function planPipeline(context) {
3821
4351
  }
3822
4352
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
3823
4353
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$1(client, workspaceId, application.name, pipelines);
3824
- const resolverChangeSet = await planResolvers(client, workspaceId, pipelines, executors, serviceChangeSet.deletes.map((del) => del.name), application.env, forceApplyAll);
3825
- serviceChangeSet.print();
3826
- resolverChangeSet.print();
4354
+ const { changeSet: resolverChangeSet } = await planResolvers(client, workspaceId, pipelines, executors, serviceChangeSet.deletes.map((del) => del.name), application.env, application.authService?.config.name, forceApplyAll);
3827
4355
  return {
3828
4356
  changeSet: {
3829
4357
  service: serviceChangeSet,
@@ -3915,7 +4443,7 @@ async function planServices$1(client, workspaceId, appName, pipelines) {
3915
4443
  resourceOwners
3916
4444
  };
3917
4445
  }
3918
- async function planResolvers(client, workspaceId, pipelines, executors, deletedServices, env, forceApplyAll = false) {
4446
+ async function planResolvers(client, workspaceId, pipelines, executors, deletedServices, env, authNamespace, forceApplyAll = false) {
3919
4447
  const changeSet = createChangeSet("Pipeline resolvers");
3920
4448
  const fetchResolvers = (namespaceName) => {
3921
4449
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -3940,7 +4468,7 @@ async function planResolvers(client, workspaceId, pipelines, executors, deletedS
3940
4468
  const existingResolvers = await fetchResolvers(pipeline.namespace);
3941
4469
  const existingResolversMap = new Map(existingResolvers.map((resolver) => [resolver.name, resolver]));
3942
4470
  for (const resolver of Object.values(pipeline.resolvers)) {
3943
- const desiredResolver = processResolver(pipeline.namespace, resolver, executorUsedResolvers, env);
4471
+ const desiredResolver = processResolver(pipeline.namespace, resolver, executorUsedResolvers, env, authNamespace);
3944
4472
  if (existingResolversMap.get(resolver.name)) {
3945
4473
  const { pipelineResolver: existingResolverDetail } = await client.getPipelineResolver({
3946
4474
  workspaceId,
@@ -3987,7 +4515,19 @@ async function planResolvers(client, workspaceId, pipelines, executors, deletedS
3987
4515
  }
3988
4516
  });
3989
4517
  });
3990
- return changeSet;
4518
+ return { changeSet };
4519
+ }
4520
+ /**
4521
+ * Format resolver changes for grouped dry-run display.
4522
+ * @param changeSet - Resolver changes
4523
+ * @param resolverFunctionChanges - Related function registry changes for resolvers
4524
+ * @returns Display entries for resolver output
4525
+ */
4526
+ function formatResolverChangeEntries(changeSet, resolverFunctionChanges) {
4527
+ return formatChangeEntriesWithFunctionRegistry("resolver", changeSet, resolverFunctionChanges, (item) => {
4528
+ const namespace = item.request.namespaceName;
4529
+ return namespace ? [resolverFunctionName(namespace, item.name)] : [];
4530
+ }, { getNamespace: (item) => item.request.namespaceName });
3991
4531
  }
3992
4532
  function normalizeComparableResolver(resolver) {
3993
4533
  const normalized = normalizeProtoConfig(resolver) ?? {};
@@ -4042,7 +4582,7 @@ function normalizeComparableType(type) {
4042
4582
  fields: (type.fields ?? []).map((field) => normalizeComparableField(field))
4043
4583
  };
4044
4584
  }
4045
- function processResolver(namespace, resolver, executorUsedResolvers, env) {
4585
+ function processResolver(namespace, resolver, executorUsedResolvers, env, authNamespace) {
4046
4586
  const pipelines = [{
4047
4587
  name: "body",
4048
4588
  operationName: "body",
@@ -4051,7 +4591,7 @@ function processResolver(namespace, resolver, executorUsedResolvers, env) {
4051
4591
  operationSourceRef: resolverFunctionName(namespace, resolver.name),
4052
4592
  operationHook: { expr: buildResolverOperationHookExpr(env) },
4053
4593
  postScript: `args.body`,
4054
- invoker: resolver.authInvoker
4594
+ invoker: normalizeAuthInvoker(resolver.authInvoker, authNamespace, `Resolver "${resolver.name}"`)
4055
4595
  }];
4056
4596
  const typeBaseName = inflection.camelize(resolver.name);
4057
4597
  const inputs = resolver.input ? protoFields(resolver.input, `${typeBaseName}Input`, true) : [];
@@ -4145,6 +4685,7 @@ async function planSecretManager(context) {
4145
4685
  };
4146
4686
  }));
4147
4687
  const state = loadSecretsState();
4688
+ const skippedSecrets = [];
4148
4689
  await Promise.all(secretVaults.map(async (vault) => {
4149
4690
  const vaultName = vault.vaultName;
4150
4691
  const existing = existingVaults[vaultName];
@@ -4185,24 +4726,31 @@ async function planSecretManager(context) {
4185
4726
  }
4186
4727
  })).map((s) => s.name);
4187
4728
  const existingSet = new Set(existingSecrets);
4188
- for (const secret of vault.secrets) if (existingSet.has(secret.name)) {
4189
- const currentHash = hashValue(secret.value);
4190
- const storedHash = state.vaults[vaultName]?.[secret.name];
4191
- if (forceApplyAll || currentHash !== storedHash) secretChangeSet.updates.push({
4729
+ for (const secret of vault.secrets) {
4730
+ if (secret.value == null) {
4731
+ existingSet.delete(secret.name);
4732
+ skippedSecrets.push(`${vaultName}/${secret.name}`);
4733
+ continue;
4734
+ }
4735
+ if (existingSet.has(secret.name)) {
4736
+ const currentHash = hashValue(secret.value);
4737
+ const storedHash = state.vaults[vaultName]?.[secret.name];
4738
+ if (forceApplyAll || currentHash !== storedHash) secretChangeSet.updates.push({
4739
+ name: `${vaultName}/${secret.name}`,
4740
+ secretName: secret.name,
4741
+ workspaceId,
4742
+ vaultName,
4743
+ value: secret.value
4744
+ });
4745
+ existingSet.delete(secret.name);
4746
+ } else secretChangeSet.creates.push({
4192
4747
  name: `${vaultName}/${secret.name}`,
4193
4748
  secretName: secret.name,
4194
4749
  workspaceId,
4195
4750
  vaultName,
4196
4751
  value: secret.value
4197
4752
  });
4198
- existingSet.delete(secret.name);
4199
- } else secretChangeSet.creates.push({
4200
- name: `${vaultName}/${secret.name}`,
4201
- secretName: secret.name,
4202
- workspaceId,
4203
- vaultName,
4204
- value: secret.value
4205
- });
4753
+ }
4206
4754
  for (const orphanName of existingSet) secretChangeSet.deletes.push({
4207
4755
  name: `${vaultName}/${orphanName}`,
4208
4756
  secretName: orphanName,
@@ -4241,11 +4789,10 @@ async function planSecretManager(context) {
4241
4789
  });
4242
4790
  }
4243
4791
  }
4244
- vaultChangeSet.print();
4245
- secretChangeSet.print();
4246
4792
  return {
4247
4793
  vaultChangeSet,
4248
4794
  secretChangeSet,
4795
+ skippedSecrets,
4249
4796
  conflicts,
4250
4797
  unmanaged,
4251
4798
  resourceOwners
@@ -4295,7 +4842,7 @@ async function applySecretManager(client, result, phase = "create-update", appli
4295
4842
  const state = loadSecretsState();
4296
4843
  for (const vault of application.secrets) {
4297
4844
  if (!state.vaults[vault.vaultName]) state.vaults[vault.vaultName] = {};
4298
- for (const secret of vault.secrets) state.vaults[vault.vaultName][secret.name] = hashValue(secret.value);
4845
+ for (const secret of vault.secrets) if (secret.value != null) state.vaults[vault.vaultName][secret.name] = hashValue(secret.value);
4299
4846
  }
4300
4847
  saveSecretsState(state);
4301
4848
  }
@@ -4442,7 +4989,6 @@ async function planStaticWebsite(context) {
4442
4989
  }
4443
4990
  });
4444
4991
  });
4445
- changeSet.print();
4446
4992
  return {
4447
4993
  changeSet,
4448
4994
  conflicts,
@@ -6563,9 +7109,6 @@ async function planTailorDB(context) {
6563
7109
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices(client, workspaceId, application.name, tailordbs);
6564
7110
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
6565
7111
  const [typeChangeSet, gqlPermissionChangeSet] = await Promise.all([planTypes(client, workspaceId, tailordbs, executors, deletedServices, void 0, forceApplyAll), planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll)]);
6566
- serviceChangeSet.print();
6567
- typeChangeSet.print();
6568
- gqlPermissionChangeSet.print();
6569
7112
  return {
6570
7113
  changeSet: {
6571
7114
  service: serviceChangeSet,
@@ -6583,6 +7126,42 @@ async function planTailorDB(context) {
6583
7126
  }
6584
7127
  };
6585
7128
  }
7129
+ function itemKey(item) {
7130
+ return `${item.request?.namespaceName ?? ""}/${item.name}`;
7131
+ }
7132
+ function collectTailorDBDisplayEntries(action, typeItems, gqlPermissionItems) {
7133
+ const typeKeys = new Set(typeItems.map(itemKey));
7134
+ const gqlPermissionKeys = new Set(gqlPermissionItems.map(itemKey));
7135
+ const typeEntries = typeItems.map((item) => ({
7136
+ action,
7137
+ symbol: ACTION_SYMBOLS[action],
7138
+ name: item.name,
7139
+ labels: gqlPermissionKeys.has(itemKey(item)) ? ["type", "gqlPermission"] : ["type"],
7140
+ namespace: item.request?.namespaceName
7141
+ }));
7142
+ const gqlPermissionOnlyEntries = gqlPermissionItems.filter((item) => !typeKeys.has(itemKey(item))).map((item) => ({
7143
+ action,
7144
+ symbol: ACTION_SYMBOLS[action],
7145
+ name: item.name,
7146
+ labels: ["gqlPermission"],
7147
+ namespace: item.request?.namespaceName
7148
+ }));
7149
+ return [...typeEntries, ...gqlPermissionOnlyEntries];
7150
+ }
7151
+ /**
7152
+ * Format TailorDB type and gqlPermission changes as grouped dry-run entries.
7153
+ * @param typeChangeSet - TailorDB type changes
7154
+ * @param gqlPermissionChangeSet - TailorDB gqlPermission changes
7155
+ * @returns Display entries for TailorDB resource output
7156
+ */
7157
+ function formatTailorDBResourceChangeEntries(typeChangeSet, gqlPermissionChangeSet) {
7158
+ return [
7159
+ ...collectTailorDBDisplayEntries("create", typeChangeSet.creates, gqlPermissionChangeSet.creates),
7160
+ ...collectTailorDBDisplayEntries("delete", typeChangeSet.deletes, gqlPermissionChangeSet.deletes),
7161
+ ...collectTailorDBDisplayEntries("update", typeChangeSet.updates, gqlPermissionChangeSet.updates),
7162
+ ...collectTailorDBDisplayEntries("replace", typeChangeSet.replaces, gqlPermissionChangeSet.replaces)
7163
+ ];
7164
+ }
6586
7165
  function trn(workspaceId, name) {
6587
7166
  return `${trnPrefix(workspaceId)}:tailordb:${name}`;
6588
7167
  }
@@ -7480,15 +8059,16 @@ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps
7480
8059
  });
7481
8060
  }
7482
8061
  Object.values(existingWorkflows).forEach((existing) => {
7483
- const label = existing?.label;
8062
+ if (!existing) return;
8063
+ const label = existing.label;
7484
8064
  if (label && label !== appName) resourceOwners.add(label);
7485
8065
  if (label === appName) changeSet.deletes.push({
7486
8066
  name: existing.resource.name,
7487
8067
  workspaceId,
7488
- workflowId: existing.resource.id
8068
+ workflowId: existing.resource.id,
8069
+ usedJobNames: getExistingWorkflowJobNames(existing.resource)
7489
8070
  });
7490
8071
  });
7491
- changeSet.print();
7492
8072
  return {
7493
8073
  changeSet,
7494
8074
  conflicts,
@@ -7498,6 +8078,15 @@ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps
7498
8078
  unchangedWorkflowJobNames
7499
8079
  };
7500
8080
  }
8081
+ /**
8082
+ * Format workflow changes for grouped dry-run display.
8083
+ * @param changeSet - Workflow changes
8084
+ * @param workflowJobFunctionChanges - Related function registry changes for workflow jobs
8085
+ * @returns Display entries for workflow output
8086
+ */
8087
+ function formatWorkflowChangeEntries(changeSet, workflowJobFunctionChanges) {
8088
+ return formatChangeEntriesWithFunctionRegistry("workflow", changeSet, workflowJobFunctionChanges, (item) => "usedJobNames" in item ? item.usedJobNames.map((jobName) => workflowJobFunctionName(jobName)) : []);
8089
+ }
7501
8090
  function canTreatWorkflowAsUnchanged(existing, workflow, usedJobNames, unchangedJobFunctions) {
7502
8091
  if (!usedJobNames.every((jobName) => unchangedJobFunctions.has(jobName))) return false;
7503
8092
  return areWorkflowsEqual(existing, workflow, usedJobNames);
@@ -7532,6 +8121,11 @@ function normalizeComparableWorkflowRetryPolicy(policy) {
7532
8121
  function normalizeComparableWorkflowJobNames(jobFunctions) {
7533
8122
  return Array.isArray(jobFunctions) ? [...jobFunctions].sort() : Object.keys(jobFunctions ?? {}).sort();
7534
8123
  }
8124
+ function getExistingWorkflowJobNames(existing) {
8125
+ const jobNames = new Set(Object.keys(existing.jobFunctions ?? {}));
8126
+ if (existing.mainJobFunctionName) jobNames.add(existing.mainJobFunctionName);
8127
+ return [...jobNames].sort();
8128
+ }
7535
8129
  function normalizeRetryPolicyForCompare(policy) {
7536
8130
  return {
7537
8131
  maxRetries: policy.maxRetries,
@@ -7618,34 +8212,95 @@ async function shouldForceApplyAll(client, workspaceId, application, functionEnt
7618
8212
  }
7619
8213
  return false;
7620
8214
  }
7621
- function printPlanSummary(results) {
7622
- const summary = summarizeChangeSets([
7623
- results.functionRegistry.changeSet,
7624
- results.tailorDB.changeSet.service,
7625
- results.tailorDB.changeSet.type,
7626
- results.tailorDB.changeSet.gqlPermission,
8215
+ function printPlanResults(results) {
8216
+ const executorEntries = formatExecutorChangeEntries(results.executor.changeSet, buildPlannedExecutorsByName(results.executor.changeSet), results.functionRegistry.executorFunctionChanges);
8217
+ const resolverEntries = formatResolverChangeEntries(results.pipeline.changeSet.resolver, results.functionRegistry.resolverFunctionChanges);
8218
+ const workflowEntries = formatWorkflowChangeEntries(results.workflow.changeSet, results.functionRegistry.workflowJobChanges);
8219
+ const authHookEntries = formatAuthHookChangeEntries(results.auth.changeSet.authHook, results.functionRegistry.authHookFunctionChanges);
8220
+ const tailorDBEntries = [...formatTailorDBResourceChangeEntries(results.tailorDB.changeSet.type, results.tailorDB.changeSet.gqlPermission)];
8221
+ const pipelineEntries = [...resolverEntries];
8222
+ const namespaceOf = (item) => {
8223
+ if ("request" in item && item.request && typeof item.request === "object" && "namespaceName" in item.request) return item.request.namespaceName;
8224
+ if ("namespaceName" in item) return item.namespaceName;
8225
+ };
8226
+ const authNamespaceOf = (item) => "request" in item && item.request && typeof item.request === "object" && "authNamespace" in item.request ? item.request.authNamespace : void 0;
8227
+ const idpEntries = [...formatChangeSetEntries(results.idp.changeSet.client, ["client"], namespaceOf)];
8228
+ const authEntries = [
8229
+ ...formatChangeSetEntries(results.auth.changeSet.idpConfig, ["idpConfig"], namespaceOf),
8230
+ ...formatChangeSetEntries(results.auth.changeSet.userProfileConfig, ["userProfileConfig"], namespaceOf),
8231
+ ...formatChangeSetEntries(results.auth.changeSet.tenantConfig, ["tenantConfig"], namespaceOf),
8232
+ ...formatChangeSetEntries(results.auth.changeSet.machineUser, ["machineUser"], authNamespaceOf),
8233
+ ...authHookEntries,
8234
+ ...formatChangeSetEntries(results.auth.changeSet.oauth2Client, ["oauth2Client"], namespaceOf),
8235
+ ...formatChangeSetEntries(results.auth.changeSet.scim, ["scimConfig"], namespaceOf),
8236
+ ...formatChangeSetEntries(results.auth.changeSet.scimResource, ["scimResource"], namespaceOf),
8237
+ ...results.auth.changeSet.connection ? formatChangeSetEntries(results.auth.changeSet.connection, ["connection"], namespaceOf) : []
8238
+ ];
8239
+ const { otherChanges: otherFunctionRegistryChanges } = splitFunctionRegistryChanges(results.functionRegistry.changeSet);
8240
+ printGroupedDisplaySection(results.functionRegistry.changeSet.title, formatChangeSetEntries(otherFunctionRegistryChanges));
8241
+ const tailorDBServiceActions = extractServiceActions(results.tailorDB.changeSet.service);
8242
+ const pipelineServiceActions = extractServiceActions(results.pipeline.changeSet.service);
8243
+ const idpServiceActions = extractServiceActions(results.idp.changeSet.service);
8244
+ const authServiceActions = extractServiceActions(results.auth.changeSet.service);
8245
+ results.staticWebsite.changeSet.print();
8246
+ results.app.print();
8247
+ printGroupedDisplaySection("TailorDB", tailorDBEntries, tailorDBServiceActions);
8248
+ printGroupedDisplaySection("Resolver", pipelineEntries, pipelineServiceActions);
8249
+ printGroupedDisplaySection("Executor", executorEntries);
8250
+ printGroupedDisplaySection("Workflow", workflowEntries);
8251
+ printGroupedDisplaySection("IdP", idpEntries, idpServiceActions);
8252
+ printGroupedDisplaySection("Auth", authEntries, authServiceActions);
8253
+ results.secretManager.vaultChangeSet.print();
8254
+ results.secretManager.secretChangeSet.print();
8255
+ if (results.secretManager.skippedSecrets.length > 0) {
8256
+ logger.log(styles.bold("Secret Manager secrets (skipped - no value provided):"));
8257
+ for (const name of results.secretManager.skippedSecrets) logger.log(` ${styles.dim("○")} ${name}`);
8258
+ }
8259
+ const summary = summarizePlanResults(results, [
8260
+ ...tailorDBEntries,
8261
+ ...pipelineEntries,
8262
+ ...executorEntries,
8263
+ ...workflowEntries,
8264
+ ...idpEntries,
8265
+ ...authEntries
8266
+ ], [
8267
+ ...tailorDBServiceActions,
8268
+ ...pipelineServiceActions,
8269
+ ...idpServiceActions,
8270
+ ...authServiceActions
8271
+ ]);
8272
+ logger.log(formatPlanSummary(summary));
8273
+ }
8274
+ /**
8275
+ * Summarize plan counts from display entries, service actions, and non-grouped changesets.
8276
+ * @param results - Planned apply results
8277
+ * @param displayEntries - All grouped display entries across sections
8278
+ * @param serviceActions - All service-level namespace actions
8279
+ * @returns Aggregated plan summary
8280
+ */
8281
+ function summarizePlanResults(results, displayEntries, serviceActions) {
8282
+ const summary = {
8283
+ create: 0,
8284
+ update: 0,
8285
+ delete: 0,
8286
+ replace: 0,
8287
+ unchanged: 0
8288
+ };
8289
+ for (const entry of displayEntries) summary[entry.action] += 1;
8290
+ for (const sa of serviceActions) summary[sa.action] += 1;
8291
+ const { otherChanges } = splitFunctionRegistryChanges(results.functionRegistry.changeSet);
8292
+ const nonGrouped = summarizeChangeSets([
8293
+ otherChanges,
7627
8294
  results.staticWebsite.changeSet,
7628
- results.idp.changeSet.service,
7629
- results.idp.changeSet.client,
7630
- results.auth.changeSet.service,
7631
- results.auth.changeSet.idpConfig,
7632
- results.auth.changeSet.userProfileConfig,
7633
- results.auth.changeSet.tenantConfig,
7634
- results.auth.changeSet.machineUser,
7635
- results.auth.changeSet.oauth2Client,
7636
- results.auth.changeSet.authHook,
7637
- results.auth.changeSet.scim,
7638
- results.auth.changeSet.scimResource,
7639
- ...results.auth.changeSet.connection ? [results.auth.changeSet.connection] : [],
7640
- results.pipeline.changeSet.service,
7641
- results.pipeline.changeSet.resolver,
7642
8295
  results.app,
7643
- results.executor.changeSet,
7644
- results.workflow.changeSet,
7645
8296
  results.secretManager.vaultChangeSet,
7646
8297
  results.secretManager.secretChangeSet
7647
8298
  ]);
7648
- logger.log(formatPlanSummary(summary));
8299
+ summary.create += nonGrouped.create;
8300
+ summary.update += nonGrouped.update;
8301
+ summary.delete += nonGrouped.delete;
8302
+ summary.replace += nonGrouped.replace;
8303
+ return summary;
7649
8304
  }
7650
8305
  /**
7651
8306
  * Apply the configured application to the Tailor platform.
@@ -7735,7 +8390,7 @@ async function apply(options) {
7735
8390
  forceApplyAll
7736
8391
  };
7737
8392
  const functionRegistry = await withSpan("plan.functionRegistry", () => planFunctionRegistry(client, workspaceId, application.name, functionEntries));
7738
- const unchangedWorkflowJobs = new Set(functionRegistry.changeSet.unchanged.map((entry) => entry.name).filter((name) => name.startsWith("workflow--")).map((name) => name.slice(10)));
8393
+ const unchangedWorkflowJobs = new Set(functionRegistry.changeSet.unchanged.filter((entry) => entry.name.startsWith(WORKFLOW_PREFIX)).map((entry) => entry.name.slice(WORKFLOW_PREFIX.length)));
7739
8394
  const [tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, secretManager] = await Promise.all([
7740
8395
  withSpan("plan.tailorDB", () => planTailorDB(ctx)),
7741
8396
  withSpan("plan.staticWebsite", () => planStaticWebsite(ctx)),
@@ -7830,7 +8485,7 @@ async function apply(options) {
7830
8485
  }
7831
8486
  });
7832
8487
  });
7833
- printPlanSummary({
8488
+ printPlanResults({
7834
8489
  functionRegistry,
7835
8490
  tailorDB,
7836
8491
  staticWebsite,
@@ -11228,6 +11883,30 @@ async function execRemove(client, workspaceId, application, config, confirm) {
11228
11883
  const workflow = await planWorkflow(client, workspaceId, application.name, {}, {});
11229
11884
  const functionRegistry = await planFunctionRegistry(client, workspaceId, application.name, []);
11230
11885
  const secretManager = await planSecretManager(ctx);
11886
+ functionRegistry.changeSet.print();
11887
+ staticWebsite.changeSet.print();
11888
+ app.print();
11889
+ tailorDB.changeSet.service.print();
11890
+ tailorDB.changeSet.type.print();
11891
+ tailorDB.changeSet.gqlPermission.print();
11892
+ pipeline.changeSet.service.print();
11893
+ pipeline.changeSet.resolver.print();
11894
+ executor.changeSet.print();
11895
+ workflow.changeSet.print();
11896
+ idp.changeSet.service.print();
11897
+ idp.changeSet.client.print();
11898
+ auth.changeSet.service.print();
11899
+ auth.changeSet.idpConfig.print();
11900
+ auth.changeSet.userProfileConfig.print();
11901
+ auth.changeSet.tenantConfig.print();
11902
+ auth.changeSet.machineUser.print();
11903
+ auth.changeSet.oauth2Client.print();
11904
+ auth.changeSet.authHook.print();
11905
+ auth.changeSet.scim.print();
11906
+ auth.changeSet.scimResource.print();
11907
+ auth.changeSet.connection?.print();
11908
+ secretManager.vaultChangeSet.print();
11909
+ secretManager.secretChangeSet.print();
11231
11910
  if (tailorDB.changeSet.service.deletes.length === 0 && staticWebsite.changeSet.deletes.length === 0 && idp.changeSet.service.deletes.length === 0 && auth.changeSet.service.deletes.length === 0 && pipeline.changeSet.service.deletes.length === 0 && app.deletes.length === 0 && executor.changeSet.deletes.length === 0 && workflow.changeSet.deletes.length === 0 && functionRegistry.changeSet.deletes.length === 0 && secretManager.vaultChangeSet.deletes.length === 0 && secretManager.secretChangeSet.deletes.length === 0) return;
11232
11911
  if (confirm) await confirm();
11233
11912
  await applyWorkflow(client, workflow, "delete");
@@ -11925,7 +12604,7 @@ async function generate(options) {
11925
12604
  if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
11926
12605
  let pluginManager;
11927
12606
  if (plugins.length > 0) pluginManager = new PluginManager(plugins);
11928
- const { defineApplication } = await import("./application-mGasp_EX.mjs");
12607
+ const { defineApplication } = await import("./application-ILhZq_oW.mjs");
11929
12608
  const application = defineApplication({
11930
12609
  config,
11931
12610
  pluginManager
@@ -14128,4 +14807,4 @@ function isDeno() {
14128
14807
 
14129
14808
  //#endregion
14130
14809
  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 };
14131
- //# sourceMappingURL=runtime-D4O-RfcH.mjs.map
14810
+ //# sourceMappingURL=runtime-D9ejnCm6.mjs.map