@tailor-platform/sdk 0.8.6 → 0.9.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.
@@ -21,6 +21,7 @@ import * as os from "node:os";
21
21
  import { parseTOML, parseYAML, stringifyYAML } from "confbox";
22
22
  import { xdgConfig } from "xdg-basedir";
23
23
  import { fromJson } from "@bufbuild/protobuf";
24
+ import chalk from "chalk";
24
25
  import { spawn } from "node:child_process";
25
26
  import { glob } from "node:fs/promises";
26
27
  import chokidar from "chokidar";
@@ -190,6 +191,7 @@ var ExecutorService = class {
190
191
  this.config = config;
191
192
  }
192
193
  async loadExecutors() {
194
+ if (Object.keys(this.executors).length > 0) return this.executors;
193
195
  if (!this.config.files || this.config.files.length === 0) return;
194
196
  const executorFiles = loadFilesWithIgnores(this.config);
195
197
  console.log("");
@@ -275,6 +277,7 @@ var ResolverService = class {
275
277
  this.config = config;
276
278
  }
277
279
  async loadResolvers() {
280
+ if (Object.keys(this.resolvers).length > 0) return;
278
281
  if (!this.config.files || this.config.files.length === 0) return;
279
282
  const resolverFiles = loadFilesWithIgnores(this.config);
280
283
  console.log("");
@@ -2254,6 +2257,15 @@ const file_tailor_v1_service = /* @__PURE__ */ fileDesc("Chd0YWlsb3IvdjEvc2Vydml
2254
2257
  */
2255
2258
  const OperatorService = /* @__PURE__ */ serviceDesc(file_tailor_v1_service, 0);
2256
2259
 
2260
+ //#endregion
2261
+ //#region src/cli/package-json.ts
2262
+ let packageJson = null;
2263
+ async function readPackageJson() {
2264
+ if (packageJson) return packageJson;
2265
+ packageJson = await readPackageJSON(import.meta.url);
2266
+ return packageJson;
2267
+ }
2268
+
2257
2269
  //#endregion
2258
2270
  //#region src/cli/client.ts
2259
2271
  const baseUrl = process.env.PLATFORM_URL ?? "https://api.tailor.tech";
@@ -2278,7 +2290,7 @@ async function userAgentInterceptor() {
2278
2290
  };
2279
2291
  }
2280
2292
  async function userAgent() {
2281
- return `tailor-sdk/${(await readPackageJSON(import.meta.url)).version ?? "unknown"}`;
2293
+ return `tailor-sdk/${(await readPackageJson()).version ?? "unknown"}`;
2282
2294
  }
2283
2295
  async function bearerTokenInterceptor(accessToken) {
2284
2296
  return (next) => async (req) => {
@@ -2540,6 +2552,24 @@ function loadConfigPath(configPath) {
2540
2552
  return "tailor.config.ts";
2541
2553
  }
2542
2554
 
2555
+ //#endregion
2556
+ //#region src/cli/apply/services/label.ts
2557
+ function trnPrefix(workspaceId) {
2558
+ return `trn:v1:workspace:${workspaceId}`;
2559
+ }
2560
+ const sdkNameLabelKey = "sdk-name";
2561
+ async function buildMetaRequest(trn$7, appName) {
2562
+ const packageJson$1 = await readPackageJson();
2563
+ const sdkVersion = packageJson$1.version ? `v${packageJson$1.version.replace(/\./g, "-")}` : "unknown";
2564
+ return {
2565
+ trn: trn$7,
2566
+ labels: {
2567
+ [sdkNameLabelKey]: appName,
2568
+ "sdk-version": sdkVersion
2569
+ }
2570
+ };
2571
+ }
2572
+
2543
2573
  //#endregion
2544
2574
  //#region src/cli/apply/services/index.ts
2545
2575
  var ChangeSet = class {
@@ -2569,14 +2599,21 @@ var ChangeSet = class {
2569
2599
  async function applyApplication(client, changeSet, phase = "create-update") {
2570
2600
  if (phase === "create-update") await Promise.all([...changeSet.creates.map(async (create) => {
2571
2601
  create.request.cors = await resolveStaticWebsiteUrls(client, create.request.workspaceId, create.request.cors, "CORS");
2572
- return client.createApplication(create.request);
2602
+ await client.createApplication(create.request);
2603
+ await client.setMetadata(create.metaRequest);
2573
2604
  }), ...changeSet.updates.map(async (update) => {
2574
2605
  update.request.cors = await resolveStaticWebsiteUrls(client, update.request.workspaceId, update.request.cors, "CORS");
2575
- return client.updateApplication(update.request);
2606
+ await client.updateApplication(update.request);
2607
+ await client.setMetadata(update.metaRequest);
2576
2608
  })]);
2577
- else if (phase === "delete") await Promise.all(changeSet.deletes.map((del) => client.deleteApplication(del.request)));
2609
+ else if (phase === "delete") await Promise.all(changeSet.deletes.map(async (del) => {
2610
+ await client.deleteApplication(del.request);
2611
+ }));
2578
2612
  }
2579
- async function planApplication(client, workspaceId, application) {
2613
+ function trn$6(workspaceId, name) {
2614
+ return `trn:v1:workspace:${workspaceId}:application:${name}`;
2615
+ }
2616
+ async function planApplication({ client, workspaceId, application }) {
2580
2617
  const changeSet = new ChangeSet("Applications");
2581
2618
  const existingApplications = await fetchAll(async (pageToken) => {
2582
2619
  try {
@@ -2590,55 +2627,41 @@ async function planApplication(client, workspaceId, application) {
2590
2627
  throw error;
2591
2628
  }
2592
2629
  });
2593
- const existingNameSet = /* @__PURE__ */ new Set();
2594
- existingApplications.forEach((application$1) => {
2595
- existingNameSet.add(application$1.name);
2630
+ let authNamespace;
2631
+ let authIdpConfigName;
2632
+ if (application.authService && application.authService.config) {
2633
+ authNamespace = application.authService.config.name;
2634
+ const idProvider = application.authService.config.idProvider;
2635
+ if (idProvider) authIdpConfigName = idProvider.name;
2636
+ }
2637
+ const metaRequest = await buildMetaRequest(trn$6(workspaceId, application.name), application.name);
2638
+ if (existingApplications.some((app) => app.name === application.name)) changeSet.updates.push({
2639
+ name: application.name,
2640
+ request: {
2641
+ workspaceId,
2642
+ applicationName: application.name,
2643
+ authNamespace,
2644
+ authIdpConfigName,
2645
+ cors: application.config.cors,
2646
+ subgraphs: application.subgraphs.map((subgraph) => protoSubgraph(subgraph)),
2647
+ allowedIpAddresses: application.config.allowedIPAddresses,
2648
+ disableIntrospection: application.config.disableIntrospection
2649
+ },
2650
+ metaRequest
2596
2651
  });
2597
- for (const app of application.applications) {
2598
- let authNamespace;
2599
- let authIdpConfigName;
2600
- if (app.authService && app.authService.config) {
2601
- authNamespace = app.authService.config.name;
2602
- const idProvider = app.authService.config.idProvider;
2603
- if (idProvider) authIdpConfigName = idProvider.name;
2604
- }
2605
- if (existingNameSet.has(app.name)) {
2606
- changeSet.updates.push({
2607
- name: app.name,
2608
- request: {
2609
- workspaceId,
2610
- applicationName: app.name,
2611
- authNamespace,
2612
- authIdpConfigName,
2613
- cors: app.config.cors,
2614
- subgraphs: app.subgraphs.map((subgraph) => protoSubgraph(subgraph)),
2615
- allowedIpAddresses: app.config.allowedIPAddresses,
2616
- disableIntrospection: app.config.disableIntrospection
2617
- }
2618
- });
2619
- existingNameSet.delete(app.name);
2620
- } else changeSet.creates.push({
2621
- name: app.name,
2622
- request: {
2623
- workspaceId,
2624
- applicationName: app.name,
2625
- authNamespace,
2626
- authIdpConfigName,
2627
- cors: app.config.cors,
2628
- subgraphs: app.subgraphs.map((subgraph) => protoSubgraph(subgraph)),
2629
- allowedIpAddresses: app.config.allowedIPAddresses,
2630
- disableIntrospection: app.config.disableIntrospection
2631
- }
2632
- });
2633
- }
2634
- existingNameSet.forEach((name) => {
2635
- changeSet.deletes.push({
2636
- name,
2637
- request: {
2638
- workspaceId,
2639
- applicationName: name
2640
- }
2641
- });
2652
+ else changeSet.creates.push({
2653
+ name: application.name,
2654
+ request: {
2655
+ workspaceId,
2656
+ applicationName: application.name,
2657
+ authNamespace,
2658
+ authIdpConfigName,
2659
+ cors: application.config.cors,
2660
+ subgraphs: application.subgraphs.map((subgraph) => protoSubgraph(subgraph)),
2661
+ allowedIpAddresses: application.config.allowedIPAddresses,
2662
+ disableIntrospection: application.config.disableIntrospection
2663
+ },
2664
+ metaRequest
2642
2665
  });
2643
2666
  changeSet.print();
2644
2667
  return changeSet;
@@ -2674,9 +2697,16 @@ function idpClientVaultName(namespaceName, clientName) {
2674
2697
  function idpClientSecretName(namespaceName, clientName) {
2675
2698
  return `client-secret-${namespaceName}-${clientName}`;
2676
2699
  }
2677
- async function applyIdP(client, changeSet, phase = "create-update") {
2700
+ async function applyIdP(client, result, phase = "create-update") {
2701
+ const { changeSet } = result;
2678
2702
  if (phase === "create-update") {
2679
- await Promise.all([...changeSet.service.creates.map((create) => client.createIdPService(create.request)), ...changeSet.service.updates.map((update) => client.updateIdPService(update.request))]);
2703
+ await Promise.all([...changeSet.service.creates.map(async (create) => {
2704
+ await client.createIdPService(create.request);
2705
+ await client.setMetadata(create.metaRequest);
2706
+ }), ...changeSet.service.updates.map(async (update) => {
2707
+ await client.updateIdPService(update.request);
2708
+ await client.setMetadata(update.metaRequest);
2709
+ })]);
2680
2710
  await Promise.all([...changeSet.client.creates.map(async (create) => {
2681
2711
  const resp = await client.createIdPClient(create.request);
2682
2712
  const vaultName = idpClientVaultName(create.request.namespaceName, create.request.client?.name || "");
@@ -2715,7 +2745,7 @@ async function applyIdP(client, changeSet, phase = "create-update") {
2715
2745
  });
2716
2746
  })]);
2717
2747
  } else if (phase === "delete") {
2718
- await Promise.all(changeSet.client.deletes.filter((del) => del.tag === "client-deleted").map(async (del) => {
2748
+ await Promise.all(changeSet.client.deletes.map(async (del) => {
2719
2749
  await client.deleteIdPClient(del.request);
2720
2750
  const vaultName = `idp-${del.request.namespaceName}-${del.request.name}`;
2721
2751
  await client.deleteSecretManagerVault({
@@ -2726,22 +2756,32 @@ async function applyIdP(client, changeSet, phase = "create-update") {
2726
2756
  await Promise.all(changeSet.service.deletes.map((del) => client.deleteIdPService(del.request)));
2727
2757
  }
2728
2758
  }
2729
- async function planIdP(client, workspaceId, application) {
2730
- const idps = [];
2731
- for (const app of application.applications) idps.push(...app.idpServices);
2732
- const serviceChangeSet = await planServices$3(client, workspaceId, idps);
2759
+ async function planIdP({ client, workspaceId, application }) {
2760
+ const idps = application.idpServices;
2761
+ const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$3(client, workspaceId, application.name, idps);
2733
2762
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
2734
2763
  const clientChangeSet = await planClients(client, workspaceId, idps, deletedServices);
2735
2764
  serviceChangeSet.print();
2736
2765
  clientChangeSet.print();
2737
2766
  return {
2738
- service: serviceChangeSet,
2739
- client: clientChangeSet
2767
+ changeSet: {
2768
+ service: serviceChangeSet,
2769
+ client: clientChangeSet
2770
+ },
2771
+ conflicts,
2772
+ unmanaged,
2773
+ resourceOwners
2740
2774
  };
2741
2775
  }
2742
- async function planServices$3(client, workspaceId, idps) {
2776
+ function trn$5(workspaceId, name) {
2777
+ return `trn:v1:workspace:${workspaceId}:idp:${name}`;
2778
+ }
2779
+ async function planServices$3(client, workspaceId, appName, idps) {
2743
2780
  const changeSet = new ChangeSet("IdP services");
2744
- const existingServices = await fetchAll(async (pageToken) => {
2781
+ const conflicts = [];
2782
+ const unmanaged = [];
2783
+ const resourceOwners = /* @__PURE__ */ new Set();
2784
+ const withoutLabel = await fetchAll(async (pageToken) => {
2745
2785
  try {
2746
2786
  const { idpServices, nextPageToken } = await client.listIdPServices({
2747
2787
  workspaceId,
@@ -2753,13 +2793,19 @@ async function planServices$3(client, workspaceId, idps) {
2753
2793
  throw error;
2754
2794
  }
2755
2795
  });
2756
- const existingNameSet = /* @__PURE__ */ new Set();
2757
- existingServices.forEach((service) => {
2758
- const name = service.namespace?.name;
2759
- if (name) existingNameSet.add(name);
2760
- });
2796
+ const existingServices = {};
2797
+ await Promise.all(withoutLabel.map(async (resource) => {
2798
+ if (!resource.namespace?.name) return;
2799
+ const { metadata } = await client.getMetadata({ trn: trn$5(workspaceId, resource.namespace.name) });
2800
+ existingServices[resource.namespace.name] = {
2801
+ resource,
2802
+ label: metadata?.labels[sdkNameLabelKey]
2803
+ };
2804
+ }));
2761
2805
  for (const idp of idps) {
2762
2806
  const namespaceName = idp.name;
2807
+ const existing = existingServices[namespaceName];
2808
+ const metaRequest = await buildMetaRequest(trn$5(workspaceId, namespaceName), appName);
2763
2809
  let authorization;
2764
2810
  switch (idp.authorization) {
2765
2811
  case "insecure":
@@ -2772,27 +2818,40 @@ async function planServices$3(client, workspaceId, idps) {
2772
2818
  authorization = idp.authorization.cel;
2773
2819
  break;
2774
2820
  }
2775
- if (existingNameSet.has(namespaceName)) {
2821
+ if (existing) {
2822
+ if (!existing.label) unmanaged.push({
2823
+ resourceType: "IdP service",
2824
+ resourceName: idp.name
2825
+ });
2826
+ else if (existing.label !== appName) conflicts.push({
2827
+ resourceType: "IdP service",
2828
+ resourceName: idp.name,
2829
+ currentOwner: existing.label
2830
+ });
2776
2831
  changeSet.updates.push({
2777
2832
  name: namespaceName,
2778
2833
  request: {
2779
2834
  workspaceId,
2780
2835
  namespaceName,
2781
2836
  authorization
2782
- }
2837
+ },
2838
+ metaRequest
2783
2839
  });
2784
- existingNameSet.delete(namespaceName);
2840
+ delete existingServices[namespaceName];
2785
2841
  } else changeSet.creates.push({
2786
2842
  name: namespaceName,
2787
2843
  request: {
2788
2844
  workspaceId,
2789
2845
  namespaceName,
2790
2846
  authorization
2791
- }
2847
+ },
2848
+ metaRequest
2792
2849
  });
2793
2850
  }
2794
- existingNameSet.forEach((namespaceName) => {
2795
- changeSet.deletes.push({
2851
+ Object.entries(existingServices).forEach(([namespaceName]) => {
2852
+ const label = existingServices[namespaceName]?.label;
2853
+ if (label && label !== appName) resourceOwners.add(label);
2854
+ if (label === appName) changeSet.deletes.push({
2796
2855
  name: namespaceName,
2797
2856
  request: {
2798
2857
  workspaceId,
@@ -2800,7 +2859,12 @@ async function planServices$3(client, workspaceId, idps) {
2800
2859
  }
2801
2860
  });
2802
2861
  });
2803
- return changeSet;
2862
+ return {
2863
+ changeSet,
2864
+ conflicts,
2865
+ unmanaged,
2866
+ resourceOwners
2867
+ };
2804
2868
  }
2805
2869
  async function planClients(client, workspaceId, idps, deletedServices) {
2806
2870
  const changeSet = new ChangeSet("IdP clients");
@@ -2844,7 +2908,6 @@ async function planClients(client, workspaceId, idps, deletedServices) {
2844
2908
  });
2845
2909
  existingNameMap.forEach((name) => {
2846
2910
  changeSet.deletes.push({
2847
- tag: "client-deleted",
2848
2911
  name,
2849
2912
  request: {
2850
2913
  workspaceId,
@@ -2856,8 +2919,12 @@ async function planClients(client, workspaceId, idps, deletedServices) {
2856
2919
  }
2857
2920
  for (const namespaceName of deletedServices) (await fetchClients(namespaceName)).forEach((client$1) => {
2858
2921
  changeSet.deletes.push({
2859
- tag: "service-deleted",
2860
- name: client$1.name
2922
+ name: client$1.name,
2923
+ request: {
2924
+ workspaceId,
2925
+ namespaceName,
2926
+ name: client$1.name
2927
+ }
2861
2928
  });
2862
2929
  });
2863
2930
  return changeSet;
@@ -2865,9 +2932,13 @@ async function planClients(client, workspaceId, idps, deletedServices) {
2865
2932
 
2866
2933
  //#endregion
2867
2934
  //#region src/cli/apply/services/auth.ts
2868
- async function applyAuth(client, changeSet, phase = "create-update") {
2935
+ async function applyAuth(client, result, phase = "create-update") {
2936
+ const { changeSet } = result;
2869
2937
  if (phase === "create-update") {
2870
- await Promise.all(changeSet.service.creates.map((create) => client.createAuthService(create.request)));
2938
+ await Promise.all([...changeSet.service.creates.map(async (create) => {
2939
+ await client.createAuthService(create.request);
2940
+ await client.setMetadata(create.metaRequest);
2941
+ }), ...changeSet.service.updates.map((update) => client.setMetadata(update.metaRequest))]);
2871
2942
  await Promise.all([...changeSet.idpConfig.creates.map(async (create) => {
2872
2943
  if (create.idpConfig.kind === "BuiltInIdP") create.request.idpConfig.config = await protoBuiltinIdPConfig(client, create.request.workspaceId, create.idpConfig);
2873
2944
  return client.createAuthIDPConfig(create.request);
@@ -2888,23 +2959,23 @@ async function applyAuth(client, changeSet, phase = "create-update") {
2888
2959
  await Promise.all([...changeSet.scim.creates.map((create) => client.createAuthSCIMConfig(create.request)), ...changeSet.scim.updates.map((update) => client.updateAuthSCIMConfig(update.request))]);
2889
2960
  await Promise.all([...changeSet.scimResource.creates.map((create) => client.createAuthSCIMResource(create.request)), ...changeSet.scimResource.updates.map((update) => client.updateAuthSCIMResource(update.request))]);
2890
2961
  } else if (phase === "delete") {
2891
- await Promise.all(changeSet.scimResource.deletes.filter((del) => del.tag === "scim-resource-deleted").map((del) => client.deleteAuthSCIMResource(del.request)));
2892
- await Promise.all(changeSet.scim.deletes.filter((del) => del.tag === "scim-config-deleted").map((del) => client.deleteAuthSCIMConfig(del.request)));
2893
- await Promise.all(changeSet.oauth2Client.deletes.filter((del) => del.tag === "oauth2-client-deleted").map((del) => client.deleteAuthOAuth2Client(del.request)));
2894
- await Promise.all(changeSet.machineUser.deletes.filter((del) => del.tag === "machine-user-deleted").map((del) => client.deleteAuthMachineUser(del.request)));
2895
- await Promise.all(changeSet.tenantConfig.deletes.filter((del) => del.tag === "tenant-config-deleted").map((del) => client.deleteTenantConfig(del.request)));
2896
- await Promise.all(changeSet.userProfileConfig.deletes.filter((del) => del.tag === "user-profile-config-deleted").map((del) => client.deleteUserProfileConfig(del.request)));
2897
- await Promise.all(changeSet.idpConfig.deletes.filter((del) => del.tag === "idp-config-deleted").map((del) => client.deleteAuthIDPConfig(del.request)));
2962
+ await Promise.all(changeSet.scimResource.deletes.map((del) => client.deleteAuthSCIMResource(del.request)));
2963
+ await Promise.all(changeSet.scim.deletes.map((del) => client.deleteAuthSCIMConfig(del.request)));
2964
+ await Promise.all(changeSet.oauth2Client.deletes.map((del) => client.deleteAuthOAuth2Client(del.request)));
2965
+ await Promise.all(changeSet.machineUser.deletes.map((del) => client.deleteAuthMachineUser(del.request)));
2966
+ await Promise.all(changeSet.tenantConfig.deletes.map((del) => client.deleteTenantConfig(del.request)));
2967
+ await Promise.all(changeSet.userProfileConfig.deletes.map((del) => client.deleteUserProfileConfig(del.request)));
2968
+ await Promise.all(changeSet.idpConfig.deletes.map((del) => client.deleteAuthIDPConfig(del.request)));
2898
2969
  await Promise.all(changeSet.service.deletes.map((del) => client.deleteAuthService(del.request)));
2899
2970
  }
2900
2971
  }
2901
- async function planAuth(client, workspaceId, application) {
2972
+ async function planAuth({ client, workspaceId, application }) {
2902
2973
  const auths = [];
2903
- for (const app of application.applications) if (app.authService) {
2904
- await app.authService.resolveNamespaces();
2905
- auths.push(app.authService);
2974
+ if (application.authService) {
2975
+ await application.authService.resolveNamespaces();
2976
+ auths.push(application.authService);
2906
2977
  }
2907
- const serviceChangeSet = await planServices$2(client, workspaceId, auths);
2978
+ const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$2(client, workspaceId, application.name, auths);
2908
2979
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
2909
2980
  const idpConfigChangeSet = await planIdPConfigs(client, workspaceId, auths, deletedServices);
2910
2981
  const userProfileConfigChangeSet = await planUserProfileConfigs(client, workspaceId, auths, deletedServices);
@@ -2922,19 +2993,30 @@ async function planAuth(client, workspaceId, application) {
2922
2993
  scimChangeSet.print();
2923
2994
  scimResourceChangeSet.print();
2924
2995
  return {
2925
- service: serviceChangeSet,
2926
- idpConfig: idpConfigChangeSet,
2927
- userProfileConfig: userProfileConfigChangeSet,
2928
- tenantConfig: tenantConfigChangeSet,
2929
- machineUser: machineUserChangeSet,
2930
- oauth2Client: oauth2ClientChangeSet,
2931
- scim: scimChangeSet,
2932
- scimResource: scimResourceChangeSet
2996
+ changeSet: {
2997
+ service: serviceChangeSet,
2998
+ idpConfig: idpConfigChangeSet,
2999
+ userProfileConfig: userProfileConfigChangeSet,
3000
+ tenantConfig: tenantConfigChangeSet,
3001
+ machineUser: machineUserChangeSet,
3002
+ oauth2Client: oauth2ClientChangeSet,
3003
+ scim: scimChangeSet,
3004
+ scimResource: scimResourceChangeSet
3005
+ },
3006
+ conflicts,
3007
+ unmanaged,
3008
+ resourceOwners
2933
3009
  };
2934
3010
  }
2935
- async function planServices$2(client, workspaceId, auths) {
3011
+ function trn$4(workspaceId, name) {
3012
+ return `trn:v1:workspace:${workspaceId}:auth:${name}`;
3013
+ }
3014
+ async function planServices$2(client, workspaceId, appName, auths) {
2936
3015
  const changeSet = new ChangeSet("Auth services");
2937
- const existingServices = await fetchAll(async (pageToken) => {
3016
+ const conflicts = [];
3017
+ const unmanaged = [];
3018
+ const resourceOwners = /* @__PURE__ */ new Set();
3019
+ const withoutLabel = await fetchAll(async (pageToken) => {
2938
3020
  try {
2939
3021
  const { authServices, nextPageToken } = await client.listAuthServices({
2940
3022
  workspaceId,
@@ -2946,23 +3028,46 @@ async function planServices$2(client, workspaceId, auths) {
2946
3028
  throw error;
2947
3029
  }
2948
3030
  });
2949
- const existingNameSet = /* @__PURE__ */ new Set();
2950
- existingServices.forEach((service) => {
2951
- const name = service.namespace?.name;
2952
- if (name) existingNameSet.add(name);
2953
- });
2954
- for (const { config } of auths) if (existingNameSet.has(config.name)) {
2955
- changeSet.updates.push({ name: config.name });
2956
- existingNameSet.delete(config.name);
2957
- } else changeSet.creates.push({
2958
- name: config.name,
2959
- request: {
2960
- workspaceId,
2961
- namespaceName: config.name
2962
- }
2963
- });
2964
- existingNameSet.forEach((namespaceName) => {
2965
- changeSet.deletes.push({
3031
+ const existingServices = {};
3032
+ await Promise.all(withoutLabel.map(async (resource) => {
3033
+ if (!resource.namespace?.name) return;
3034
+ const { metadata } = await client.getMetadata({ trn: trn$4(workspaceId, resource.namespace.name) });
3035
+ existingServices[resource.namespace.name] = {
3036
+ resource,
3037
+ label: metadata?.labels[sdkNameLabelKey]
3038
+ };
3039
+ }));
3040
+ for (const { config } of auths) {
3041
+ const existing = existingServices[config.name];
3042
+ const metaRequest = await buildMetaRequest(trn$4(workspaceId, config.name), appName);
3043
+ if (existing) {
3044
+ if (!existing.label) unmanaged.push({
3045
+ resourceType: "Auth service",
3046
+ resourceName: config.name
3047
+ });
3048
+ else if (existing.label !== appName) conflicts.push({
3049
+ resourceType: "Auth service",
3050
+ resourceName: config.name,
3051
+ currentOwner: existing.label
3052
+ });
3053
+ changeSet.updates.push({
3054
+ name: config.name,
3055
+ metaRequest
3056
+ });
3057
+ delete existingServices[config.name];
3058
+ } else changeSet.creates.push({
3059
+ name: config.name,
3060
+ request: {
3061
+ workspaceId,
3062
+ namespaceName: config.name
3063
+ },
3064
+ metaRequest
3065
+ });
3066
+ }
3067
+ Object.entries(existingServices).forEach(([namespaceName]) => {
3068
+ const label = existingServices[namespaceName]?.label;
3069
+ if (label && label !== appName) resourceOwners.add(label);
3070
+ if (label === appName) changeSet.deletes.push({
2966
3071
  name: namespaceName,
2967
3072
  request: {
2968
3073
  workspaceId,
@@ -2970,7 +3075,12 @@ async function planServices$2(client, workspaceId, auths) {
2970
3075
  }
2971
3076
  });
2972
3077
  });
2973
- return changeSet;
3078
+ return {
3079
+ changeSet,
3080
+ conflicts,
3081
+ unmanaged,
3082
+ resourceOwners
3083
+ };
2974
3084
  }
2975
3085
  async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
2976
3086
  const changeSet = new ChangeSet("Auth idpConfigs");
@@ -3018,7 +3128,6 @@ async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
3018
3128
  });
3019
3129
  existingNameSet.forEach((name) => {
3020
3130
  changeSet.deletes.push({
3021
- tag: "idp-config-deleted",
3022
3131
  name,
3023
3132
  request: {
3024
3133
  workspaceId,
@@ -3030,8 +3139,12 @@ async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
3030
3139
  }
3031
3140
  for (const namespaceName of deletedServices) (await fetchIdPConfigs(namespaceName)).forEach((idpConfig) => {
3032
3141
  changeSet.deletes.push({
3033
- tag: "service-deleted",
3034
- name: idpConfig.name
3142
+ name: idpConfig.name,
3143
+ request: {
3144
+ workspaceId,
3145
+ namespaceName,
3146
+ name: idpConfig.name
3147
+ }
3035
3148
  });
3036
3149
  });
3037
3150
  return changeSet;
@@ -3157,7 +3270,6 @@ async function planUserProfileConfigs(client, workspaceId, auths, deletedService
3157
3270
  }
3158
3271
  });
3159
3272
  else changeSet.deletes.push({
3160
- tag: "user-profile-config-deleted",
3161
3273
  name,
3162
3274
  request: {
3163
3275
  workspaceId,
@@ -3176,8 +3288,11 @@ async function planUserProfileConfigs(client, workspaceId, auths, deletedService
3176
3288
  throw error;
3177
3289
  }
3178
3290
  changeSet.deletes.push({
3179
- tag: "service-deleted",
3180
- name: `${namespaceName}-user-profile-config`
3291
+ name: `${namespaceName}-user-profile-config`,
3292
+ request: {
3293
+ workspaceId,
3294
+ namespaceName
3295
+ }
3181
3296
  });
3182
3297
  }
3183
3298
  return changeSet;
@@ -3232,7 +3347,6 @@ async function planTenantConfigs(client, workspaceId, auths, deletedServices) {
3232
3347
  }
3233
3348
  });
3234
3349
  else changeSet.deletes.push({
3235
- tag: "tenant-config-deleted",
3236
3350
  name,
3237
3351
  request: {
3238
3352
  workspaceId,
@@ -3251,8 +3365,11 @@ async function planTenantConfigs(client, workspaceId, auths, deletedServices) {
3251
3365
  throw error;
3252
3366
  }
3253
3367
  changeSet.deletes.push({
3254
- tag: "service-deleted",
3255
- name: `${namespaceName}-tenant-config`
3368
+ name: `${namespaceName}-tenant-config`,
3369
+ request: {
3370
+ workspaceId,
3371
+ namespaceName
3372
+ }
3256
3373
  });
3257
3374
  }
3258
3375
  return changeSet;
@@ -3321,7 +3438,6 @@ async function planMachineUsers(client, workspaceId, auths, deletedServices) {
3321
3438
  }
3322
3439
  existingNameSet.forEach((name) => {
3323
3440
  changeSet.deletes.push({
3324
- tag: "machine-user-deleted",
3325
3441
  name,
3326
3442
  request: {
3327
3443
  workspaceId,
@@ -3333,8 +3449,12 @@ async function planMachineUsers(client, workspaceId, auths, deletedServices) {
3333
3449
  }
3334
3450
  for (const namespaceName of deletedServices) (await fetchMachineUsers(namespaceName)).forEach((machineUser) => {
3335
3451
  changeSet.deletes.push({
3336
- tag: "service-deleted",
3337
- name: machineUser.name
3452
+ name: machineUser.name,
3453
+ request: {
3454
+ workspaceId,
3455
+ authNamespace: namespaceName,
3456
+ name: machineUser.name
3457
+ }
3338
3458
  });
3339
3459
  });
3340
3460
  return changeSet;
@@ -3391,7 +3511,6 @@ async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
3391
3511
  }
3392
3512
  existingNameSet.forEach((name) => {
3393
3513
  changeSet.deletes.push({
3394
- tag: "oauth2-client-deleted",
3395
3514
  name,
3396
3515
  request: {
3397
3516
  workspaceId,
@@ -3403,8 +3522,12 @@ async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
3403
3522
  }
3404
3523
  for (const namespaceName of deletedServices) (await fetchOAuth2Clients(namespaceName)).forEach((oauth2Client) => {
3405
3524
  changeSet.deletes.push({
3406
- tag: "service-deleted",
3407
- name: oauth2Client.name
3525
+ name: oauth2Client.name,
3526
+ request: {
3527
+ workspaceId,
3528
+ namespaceName,
3529
+ name: oauth2Client.name
3530
+ }
3408
3531
  });
3409
3532
  });
3410
3533
  return changeSet;
@@ -3460,7 +3583,6 @@ async function planSCIMConfigs(client, workspaceId, auths, deletedServices) {
3460
3583
  }
3461
3584
  });
3462
3585
  else changeSet.deletes.push({
3463
- tag: "scim-config-deleted",
3464
3586
  name,
3465
3587
  request: {
3466
3588
  workspaceId,
@@ -3479,8 +3601,11 @@ async function planSCIMConfigs(client, workspaceId, auths, deletedServices) {
3479
3601
  throw error;
3480
3602
  }
3481
3603
  changeSet.deletes.push({
3482
- tag: "service-deleted",
3483
- name: `${namespaceName}-scim-config`
3604
+ name: `${namespaceName}-scim-config`,
3605
+ request: {
3606
+ workspaceId,
3607
+ namespaceName
3608
+ }
3484
3609
  });
3485
3610
  }
3486
3611
  return changeSet;
@@ -3548,7 +3673,6 @@ async function planSCIMResources(client, workspaceId, auths, deletedServices) {
3548
3673
  });
3549
3674
  existingNameSet.forEach((name) => {
3550
3675
  changeSet.deletes.push({
3551
- tag: "scim-resource-deleted",
3552
3676
  name,
3553
3677
  request: {
3554
3678
  workspaceId,
@@ -3560,8 +3684,12 @@ async function planSCIMResources(client, workspaceId, auths, deletedServices) {
3560
3684
  }
3561
3685
  for (const namespaceName of deletedServices) (await fetchSCIMResources(namespaceName)).forEach((scimResource) => {
3562
3686
  changeSet.deletes.push({
3563
- tag: "service-deleted",
3564
- name: scimResource.name
3687
+ name: scimResource.name,
3688
+ request: {
3689
+ workspaceId,
3690
+ namespaceName,
3691
+ name: scimResource.name
3692
+ }
3565
3693
  });
3566
3694
  });
3567
3695
  return changeSet;
@@ -3640,15 +3768,72 @@ function protoSCIMAttribute(attr) {
3640
3768
  };
3641
3769
  }
3642
3770
 
3771
+ //#endregion
3772
+ //#region src/cli/apply/services/confirm.ts
3773
+ async function confirmOwnerConflict(conflicts, appName, yes) {
3774
+ if (conflicts.length === 0) return;
3775
+ const currentOwners = [...new Set(conflicts.map((c) => c.currentOwner))];
3776
+ consola.warn("Application name mismatch detected:");
3777
+ console.log(` ${chalk.yellow("Current application(s)")}: ${currentOwners.map((o) => chalk.bold(`"${o}"`)).join(", ")}`);
3778
+ console.log(` ${chalk.green("New application")}: ${chalk.bold(`"${appName}"`)}`);
3779
+ console.log("");
3780
+ console.log(` ${chalk.cyan("Resources")}:`);
3781
+ for (const c of conflicts) console.log(` • ${chalk.bold(c.resourceType)} ${chalk.cyan(`"${c.resourceName}"`)}`);
3782
+ if (yes) {
3783
+ consola.success("Updating resources (--yes flag specified)...");
3784
+ return;
3785
+ }
3786
+ const promptMessage = currentOwners.length === 1 ? `Update these resources to be managed by "${appName}"?\n${chalk.gray("(Common when renaming your application)")}` : `Update these resources to be managed by "${appName}"?`;
3787
+ if (!await consola.prompt(promptMessage, {
3788
+ type: "confirm",
3789
+ initial: false
3790
+ })) throw new Error(ml`
3791
+ Apply cancelled. Resources remain managed by their current applications.
3792
+ To override, run again and confirm, or use --yes flag.
3793
+ `);
3794
+ }
3795
+ async function confirmUnmanagedResources(resources, appName, yes) {
3796
+ if (resources.length === 0) return;
3797
+ consola.warn("Unmanaged resources detected:");
3798
+ console.log(` ${chalk.cyan("Resources")}:`);
3799
+ for (const r of resources) console.log(` • ${chalk.bold(r.resourceType)} ${chalk.cyan(`"${r.resourceName}"`)}`);
3800
+ console.log("");
3801
+ console.log(" These resources are not managed by any application.");
3802
+ if (yes) {
3803
+ consola.success(`Adding to "${appName}" (--yes flag specified)...`);
3804
+ return;
3805
+ }
3806
+ if (!await consola.prompt(`Add these resources to "${appName}"?`, {
3807
+ type: "confirm",
3808
+ initial: false
3809
+ })) throw new Error(ml`
3810
+ Apply cancelled. Resources remain unmanaged.
3811
+ To override, run again and confirm, or use --yes flag.
3812
+ `);
3813
+ }
3814
+
3643
3815
  //#endregion
3644
3816
  //#region src/cli/apply/services/executor.ts
3645
- async function applyExecutor(client, changeSet, phase = "create-update") {
3646
- if (phase === "create-update") await Promise.all([...changeSet.creates.map((create) => client.createExecutorExecutor(create.request)), ...changeSet.updates.map((update) => client.updateExecutorExecutor(update.request))]);
3817
+ async function applyExecutor(client, result, phase = "create-update") {
3818
+ const { changeSet } = result;
3819
+ if (phase === "create-update") await Promise.all([...changeSet.creates.map(async (create) => {
3820
+ await client.createExecutorExecutor(create.request);
3821
+ await client.setMetadata(create.metaRequest);
3822
+ }), ...changeSet.updates.map(async (update) => {
3823
+ await client.updateExecutorExecutor(update.request);
3824
+ await client.setMetadata(update.metaRequest);
3825
+ })]);
3647
3826
  else if (phase === "delete") await Promise.all(changeSet.deletes.map((del) => client.deleteExecutorExecutor(del.request)));
3648
3827
  }
3649
- async function planExecutor(client, workspaceId, application) {
3828
+ function trn$3(workspaceId, name) {
3829
+ return `trn:v1:workspace:${workspaceId}:executor:${name}`;
3830
+ }
3831
+ async function planExecutor({ client, workspaceId, application }) {
3650
3832
  const changeSet = new ChangeSet("Executors");
3651
- const existingExecutors = await fetchAll(async (pageToken) => {
3833
+ const conflicts = [];
3834
+ const unmanaged = [];
3835
+ const resourceOwners = /* @__PURE__ */ new Set();
3836
+ const withoutLabel = await fetchAll(async (pageToken) => {
3652
3837
  try {
3653
3838
  const { executors: executors$1, nextPageToken } = await client.listExecutorExecutors({
3654
3839
  workspaceId,
@@ -3660,29 +3845,50 @@ async function planExecutor(client, workspaceId, application) {
3660
3845
  throw error;
3661
3846
  }
3662
3847
  });
3663
- const existingNameSet = /* @__PURE__ */ new Set();
3664
- existingExecutors.forEach((executor) => {
3665
- existingNameSet.add(executor.name);
3666
- });
3848
+ const existingExecutors = {};
3849
+ await Promise.all(withoutLabel.map(async (resource) => {
3850
+ const { metadata } = await client.getMetadata({ trn: trn$3(workspaceId, resource.name) });
3851
+ existingExecutors[resource.name] = {
3852
+ resource,
3853
+ label: metadata?.labels[sdkNameLabelKey]
3854
+ };
3855
+ }));
3667
3856
  const executors = await application.executorService?.loadExecutors() ?? {};
3668
- for (const executor of Object.values(executors)) if (existingNameSet.has(executor.name)) {
3669
- changeSet.updates.push({
3857
+ for (const executor of Object.values(executors)) {
3858
+ const existing = existingExecutors[executor.name];
3859
+ const metaRequest = await buildMetaRequest(trn$3(workspaceId, executor.name), application.name);
3860
+ if (existing) {
3861
+ if (!existing.label) unmanaged.push({
3862
+ resourceType: "Executor",
3863
+ resourceName: executor.name
3864
+ });
3865
+ else if (existing.label !== application.name) conflicts.push({
3866
+ resourceType: "Executor",
3867
+ resourceName: executor.name,
3868
+ currentOwner: existing.label
3869
+ });
3870
+ changeSet.updates.push({
3871
+ name: executor.name,
3872
+ request: {
3873
+ workspaceId,
3874
+ executor: protoExecutor(executor)
3875
+ },
3876
+ metaRequest
3877
+ });
3878
+ delete existingExecutors[executor.name];
3879
+ } else changeSet.creates.push({
3670
3880
  name: executor.name,
3671
3881
  request: {
3672
3882
  workspaceId,
3673
3883
  executor: protoExecutor(executor)
3674
- }
3884
+ },
3885
+ metaRequest
3675
3886
  });
3676
- existingNameSet.delete(executor.name);
3677
- } else changeSet.creates.push({
3678
- name: executor.name,
3679
- request: {
3680
- workspaceId,
3681
- executor: protoExecutor(executor)
3682
- }
3683
- });
3684
- existingNameSet.forEach((name) => {
3685
- changeSet.deletes.push({
3887
+ }
3888
+ Object.entries(existingExecutors).forEach(([name]) => {
3889
+ const label = existingExecutors[name]?.label;
3890
+ if (label && label !== application.name) resourceOwners.add(label);
3891
+ if (label === application.name) changeSet.deletes.push({
3686
3892
  name,
3687
3893
  request: {
3688
3894
  workspaceId,
@@ -3691,7 +3897,12 @@ async function planExecutor(client, workspaceId, application) {
3691
3897
  });
3692
3898
  });
3693
3899
  changeSet.print();
3694
- return changeSet;
3900
+ return {
3901
+ changeSet,
3902
+ conflicts,
3903
+ unmanaged,
3904
+ resourceOwners
3905
+ };
3695
3906
  }
3696
3907
  function protoExecutor(executor) {
3697
3908
  const trigger = executor.trigger;
@@ -3861,35 +4072,53 @@ const SCALAR_TYPE_MAP = {
3861
4072
  name: "Time"
3862
4073
  }
3863
4074
  };
3864
- async function applyPipeline(client, changeSet, phase = "create-update") {
4075
+ async function applyPipeline(client, result, phase = "create-update") {
4076
+ const { changeSet } = result;
3865
4077
  if (phase === "create-update") {
3866
- await Promise.all([...changeSet.service.creates.map((create) => client.createPipelineService(create.request)), ...changeSet.service.updates.map((update) => client.updatePipelineService(update.request))]);
4078
+ await Promise.all([...changeSet.service.creates.map(async (create) => {
4079
+ await client.createPipelineService(create.request);
4080
+ await client.setMetadata(create.metaRequest);
4081
+ }), ...changeSet.service.updates.map(async (update) => {
4082
+ await client.updatePipelineService(update.request);
4083
+ await client.setMetadata(update.metaRequest);
4084
+ })]);
3867
4085
  await Promise.all([...changeSet.resolver.creates.map((create) => client.createPipelineResolver(create.request)), ...changeSet.resolver.updates.map((update) => client.updatePipelineResolver(update.request))]);
3868
4086
  } else if (phase === "delete") {
3869
- await Promise.all(changeSet.resolver.deletes.filter((del) => del.tag === "resolver-deleted").map((del) => client.deletePipelineResolver(del.request)));
4087
+ await Promise.all(changeSet.resolver.deletes.map((del) => client.deletePipelineResolver(del.request)));
3870
4088
  await Promise.all(changeSet.service.deletes.map((del) => client.deletePipelineService(del.request)));
3871
4089
  }
3872
4090
  }
3873
- async function planPipeline(client, workspaceId, application) {
4091
+ async function planPipeline({ client, workspaceId, application }) {
3874
4092
  const pipelines = [];
3875
- for (const app of application.applications) for (const pipeline of app.resolverServices) {
4093
+ for (const pipeline of application.resolverServices) {
3876
4094
  await pipeline.loadResolvers();
3877
4095
  pipelines.push(pipeline);
3878
4096
  }
3879
4097
  const executors = Object.values(await application.executorService?.loadExecutors() ?? {});
3880
- const serviceChangeSet = await planServices$1(client, workspaceId, pipelines);
4098
+ const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$1(client, workspaceId, application.name, pipelines);
3881
4099
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
3882
4100
  const resolverChangeSet = await planResolvers(client, workspaceId, pipelines, executors, deletedServices);
3883
4101
  serviceChangeSet.print();
3884
4102
  resolverChangeSet.print();
3885
4103
  return {
3886
- service: serviceChangeSet,
3887
- resolver: resolverChangeSet
4104
+ changeSet: {
4105
+ service: serviceChangeSet,
4106
+ resolver: resolverChangeSet
4107
+ },
4108
+ conflicts,
4109
+ unmanaged,
4110
+ resourceOwners
3888
4111
  };
3889
4112
  }
3890
- async function planServices$1(client, workspaceId, pipelines) {
4113
+ function trn$2(workspaceId, name) {
4114
+ return `trn:v1:workspace:${workspaceId}:pipeline:${name}`;
4115
+ }
4116
+ async function planServices$1(client, workspaceId, appName, pipelines) {
3891
4117
  const changeSet = new ChangeSet("Pipeline services");
3892
- const existingServices = await fetchAll(async (pageToken) => {
4118
+ const conflicts = [];
4119
+ const unmanaged = [];
4120
+ const resourceOwners = /* @__PURE__ */ new Set();
4121
+ const withoutLabel = await fetchAll(async (pageToken) => {
3893
4122
  try {
3894
4123
  const { pipelineServices, nextPageToken } = await client.listPipelineServices({
3895
4124
  workspaceId,
@@ -3901,29 +4130,50 @@ async function planServices$1(client, workspaceId, pipelines) {
3901
4130
  throw error;
3902
4131
  }
3903
4132
  });
3904
- const existingNameSet = /* @__PURE__ */ new Set();
3905
- existingServices.forEach((service) => {
3906
- const name = service.namespace?.name;
3907
- if (name) existingNameSet.add(name);
3908
- });
3909
- for (const pipeline of pipelines) if (existingNameSet.has(pipeline.namespace)) {
3910
- changeSet.updates.push({
4133
+ const existingServices = {};
4134
+ await Promise.all(withoutLabel.map(async (resource) => {
4135
+ if (!resource.namespace?.name) return;
4136
+ const { metadata } = await client.getMetadata({ trn: trn$2(workspaceId, resource.namespace.name) });
4137
+ existingServices[resource.namespace.name] = {
4138
+ resource,
4139
+ label: metadata?.labels[sdkNameLabelKey]
4140
+ };
4141
+ }));
4142
+ for (const pipeline of pipelines) {
4143
+ const existing = existingServices[pipeline.namespace];
4144
+ const metaRequest = await buildMetaRequest(trn$2(workspaceId, pipeline.namespace), appName);
4145
+ if (existing) {
4146
+ if (!existing.label) unmanaged.push({
4147
+ resourceType: "Pipeline service",
4148
+ resourceName: pipeline.namespace
4149
+ });
4150
+ else if (existing.label !== appName) conflicts.push({
4151
+ resourceType: "Pipeline service",
4152
+ resourceName: pipeline.namespace,
4153
+ currentOwner: existing.label
4154
+ });
4155
+ changeSet.updates.push({
4156
+ name: pipeline.namespace,
4157
+ request: {
4158
+ workspaceId,
4159
+ namespaceName: pipeline.namespace
4160
+ },
4161
+ metaRequest
4162
+ });
4163
+ delete existingServices[pipeline.namespace];
4164
+ } else changeSet.creates.push({
3911
4165
  name: pipeline.namespace,
3912
4166
  request: {
3913
4167
  workspaceId,
3914
4168
  namespaceName: pipeline.namespace
3915
- }
4169
+ },
4170
+ metaRequest
3916
4171
  });
3917
- existingNameSet.delete(pipeline.namespace);
3918
- } else changeSet.creates.push({
3919
- name: pipeline.namespace,
3920
- request: {
3921
- workspaceId,
3922
- namespaceName: pipeline.namespace
3923
- }
3924
- });
3925
- existingNameSet.forEach((namespaceName) => {
3926
- changeSet.deletes.push({
4172
+ }
4173
+ Object.entries(existingServices).forEach(([namespaceName]) => {
4174
+ const label = existingServices[namespaceName]?.label;
4175
+ if (label && label !== appName) resourceOwners.add(label);
4176
+ if (label === appName) changeSet.deletes.push({
3927
4177
  name: namespaceName,
3928
4178
  request: {
3929
4179
  workspaceId,
@@ -3931,7 +4181,12 @@ async function planServices$1(client, workspaceId, pipelines) {
3931
4181
  }
3932
4182
  });
3933
4183
  });
3934
- return changeSet;
4184
+ return {
4185
+ changeSet,
4186
+ conflicts,
4187
+ unmanaged,
4188
+ resourceOwners
4189
+ };
3935
4190
  }
3936
4191
  async function planResolvers(client, workspaceId, pipelines, executors, deletedServices) {
3937
4192
  const changeSet = new ChangeSet("Pipeline resolvers");
@@ -3978,7 +4233,6 @@ async function planResolvers(client, workspaceId, pipelines, executors, deletedS
3978
4233
  });
3979
4234
  existingNameSet.forEach((name) => {
3980
4235
  changeSet.deletes.push({
3981
- tag: "resolver-deleted",
3982
4236
  name,
3983
4237
  request: {
3984
4238
  workspaceId,
@@ -3990,8 +4244,12 @@ async function planResolvers(client, workspaceId, pipelines, executors, deletedS
3990
4244
  }
3991
4245
  for (const namespaceName of deletedServices) (await fetchResolvers(namespaceName)).forEach((resolver) => {
3992
4246
  changeSet.deletes.push({
3993
- tag: "service-deleted",
3994
- name: resolver.name
4247
+ name: resolver.name,
4248
+ request: {
4249
+ workspaceId,
4250
+ namespaceName,
4251
+ resolverName: resolver.name
4252
+ }
3995
4253
  });
3996
4254
  });
3997
4255
  return changeSet;
@@ -4065,13 +4323,26 @@ function protoFields(fields, baseName, isInput) {
4065
4323
 
4066
4324
  //#endregion
4067
4325
  //#region src/cli/apply/services/staticwebsite.ts
4068
- async function applyStaticWebsite(client, changeSet, phase = "create-update") {
4069
- if (phase === "create-update") await Promise.all([...changeSet.creates.map((create) => client.createStaticWebsite(create.request)), ...changeSet.updates.map((update) => client.updateStaticWebsite(update.request))]);
4326
+ async function applyStaticWebsite(client, result, phase = "create-update") {
4327
+ const { changeSet } = result;
4328
+ if (phase === "create-update") await Promise.all([...changeSet.creates.map(async (create) => {
4329
+ await client.createStaticWebsite(create.request);
4330
+ await client.setMetadata(create.metaRequest);
4331
+ }), ...changeSet.updates.map(async (update) => {
4332
+ await client.updateStaticWebsite(update.request);
4333
+ await client.setMetadata(update.metaRequest);
4334
+ })]);
4070
4335
  else if (phase === "delete") await Promise.all(changeSet.deletes.map((del) => client.deleteStaticWebsite(del.request)));
4071
4336
  }
4072
- async function planStaticWebsite(client, workspaceId, application) {
4337
+ function trn$1(workspaceId, name) {
4338
+ return `trn:v1:workspace:${workspaceId}:staticwebsite:${name}`;
4339
+ }
4340
+ async function planStaticWebsite({ client, workspaceId, application }) {
4073
4341
  const changeSet = new ChangeSet("StaticWebsites");
4074
- const existingWebsites = await fetchAll(async (pageToken) => {
4342
+ const conflicts = [];
4343
+ const unmanaged = [];
4344
+ const resourceOwners = /* @__PURE__ */ new Set();
4345
+ const withoutLabel = await fetchAll(async (pageToken) => {
4075
4346
  try {
4076
4347
  const { staticwebsites, nextPageToken } = await client.listStaticWebsites({
4077
4348
  workspaceId,
@@ -4083,14 +4354,29 @@ async function planStaticWebsite(client, workspaceId, application) {
4083
4354
  throw error;
4084
4355
  }
4085
4356
  });
4086
- const existingNameSet = /* @__PURE__ */ new Set();
4087
- existingWebsites.forEach((website) => {
4088
- existingNameSet.add(website.name);
4089
- });
4357
+ const existingWebsites = {};
4358
+ await Promise.all(withoutLabel.map(async (resource) => {
4359
+ const { metadata } = await client.getMetadata({ trn: trn$1(workspaceId, resource.name) });
4360
+ existingWebsites[resource.name] = {
4361
+ resource,
4362
+ label: metadata?.labels[sdkNameLabelKey]
4363
+ };
4364
+ }));
4090
4365
  for (const websiteService of application.staticWebsiteServices) {
4091
4366
  const config = websiteService;
4092
4367
  const name = websiteService.name;
4093
- if (existingNameSet.has(name)) {
4368
+ const existing = existingWebsites[name];
4369
+ const metaRequest = await buildMetaRequest(trn$1(workspaceId, name), application.name);
4370
+ if (existing) {
4371
+ if (!existing.label) unmanaged.push({
4372
+ resourceType: "StaticWebsite",
4373
+ resourceName: name
4374
+ });
4375
+ else if (existing.label !== application.name) conflicts.push({
4376
+ resourceType: "StaticWebsite",
4377
+ resourceName: name,
4378
+ currentOwner: existing.label
4379
+ });
4094
4380
  changeSet.updates.push({
4095
4381
  name,
4096
4382
  request: {
@@ -4100,9 +4386,10 @@ async function planStaticWebsite(client, workspaceId, application) {
4100
4386
  description: config.description || "",
4101
4387
  allowedIpAddresses: config.allowedIpAddresses || []
4102
4388
  }
4103
- }
4389
+ },
4390
+ metaRequest
4104
4391
  });
4105
- existingNameSet.delete(name);
4392
+ delete existingWebsites[name];
4106
4393
  } else changeSet.creates.push({
4107
4394
  name,
4108
4395
  request: {
@@ -4112,11 +4399,14 @@ async function planStaticWebsite(client, workspaceId, application) {
4112
4399
  description: config.description || "",
4113
4400
  allowedIpAddresses: config.allowedIpAddresses || []
4114
4401
  }
4115
- }
4402
+ },
4403
+ metaRequest
4116
4404
  });
4117
4405
  }
4118
- existingNameSet.forEach((name) => {
4119
- changeSet.deletes.push({
4406
+ Object.entries(existingWebsites).forEach(([name]) => {
4407
+ const label = existingWebsites[name]?.label;
4408
+ if (label && label !== application.name) resourceOwners.add(label);
4409
+ if (label === application.name) changeSet.deletes.push({
4120
4410
  name,
4121
4411
  request: {
4122
4412
  workspaceId,
@@ -4125,30 +4415,39 @@ async function planStaticWebsite(client, workspaceId, application) {
4125
4415
  });
4126
4416
  });
4127
4417
  changeSet.print();
4128
- return changeSet;
4418
+ return {
4419
+ changeSet,
4420
+ conflicts,
4421
+ unmanaged,
4422
+ resourceOwners
4423
+ };
4129
4424
  }
4130
4425
 
4131
4426
  //#endregion
4132
4427
  //#region src/cli/apply/services/tailordb.ts
4133
- async function applyTailorDB(client, changeSet, phase = "create-update") {
4428
+ async function applyTailorDB(client, result, phase = "create-update") {
4429
+ const { changeSet } = result;
4134
4430
  if (phase === "create-update") {
4135
- await Promise.all(changeSet.service.creates.map((create) => client.createTailorDBService(create.request)));
4431
+ await Promise.all([...changeSet.service.creates.map(async (create) => {
4432
+ await client.createTailorDBService(create.request);
4433
+ await client.setMetadata(create.metaRequest);
4434
+ }), ...changeSet.service.updates.map((update) => client.setMetadata(update.metaRequest))]);
4136
4435
  await Promise.all([...changeSet.type.creates.map((create) => client.createTailorDBType(create.request)), ...changeSet.type.updates.map((update) => client.updateTailorDBType(update.request))]);
4137
4436
  await Promise.all([...changeSet.gqlPermission.creates.map((create) => client.createTailorDBGQLPermission(create.request)), ...changeSet.gqlPermission.updates.map((update) => client.updateTailorDBGQLPermission(update.request))]);
4138
4437
  } else if (phase === "delete") {
4139
- await Promise.all(changeSet.gqlPermission.deletes.filter((del) => del.tag === "gql-permission-deleted").map((del) => client.deleteTailorDBGQLPermission(del.request)));
4140
- await Promise.all(changeSet.type.deletes.filter((del) => del.tag === "type-deleted").map((del) => client.deleteTailorDBType(del.request)));
4438
+ await Promise.all(changeSet.gqlPermission.deletes.map((del) => client.deleteTailorDBGQLPermission(del.request)));
4439
+ await Promise.all(changeSet.type.deletes.map((del) => client.deleteTailorDBType(del.request)));
4141
4440
  await Promise.all(changeSet.service.deletes.map((del) => client.deleteTailorDBService(del.request)));
4142
4441
  }
4143
4442
  }
4144
- async function planTailorDB(client, workspaceId, application) {
4443
+ async function planTailorDB({ client, workspaceId, application }) {
4145
4444
  const tailordbs = [];
4146
- for (const app of application.applications) for (const tailordb of app.tailorDBServices) {
4445
+ for (const tailordb of application.tailorDBServices) {
4147
4446
  await tailordb.loadTypes();
4148
4447
  tailordbs.push(tailordb);
4149
4448
  }
4150
4449
  const executors = Object.values(await application.executorService?.loadExecutors() ?? {});
4151
- const serviceChangeSet = await planServices(client, workspaceId, tailordbs);
4450
+ const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices(client, workspaceId, application.name, tailordbs);
4152
4451
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
4153
4452
  const typeChangeSet = await planTypes(client, workspaceId, tailordbs, executors, deletedServices);
4154
4453
  const gqlPermissionChangeSet = await planGqlPermissions(client, workspaceId, tailordbs, deletedServices);
@@ -4156,14 +4455,25 @@ async function planTailorDB(client, workspaceId, application) {
4156
4455
  typeChangeSet.print();
4157
4456
  gqlPermissionChangeSet.print();
4158
4457
  return {
4159
- service: serviceChangeSet,
4160
- type: typeChangeSet,
4161
- gqlPermission: gqlPermissionChangeSet
4458
+ changeSet: {
4459
+ service: serviceChangeSet,
4460
+ type: typeChangeSet,
4461
+ gqlPermission: gqlPermissionChangeSet
4462
+ },
4463
+ conflicts,
4464
+ unmanaged,
4465
+ resourceOwners
4162
4466
  };
4163
4467
  }
4164
- async function planServices(client, workspaceId, tailordbs) {
4468
+ function trn(workspaceId, name) {
4469
+ return `${trnPrefix(workspaceId)}:tailordb:${name}`;
4470
+ }
4471
+ async function planServices(client, workspaceId, appName, tailordbs) {
4165
4472
  const changeSet = new ChangeSet("TailorDB services");
4166
- const existingServices = await fetchAll(async (pageToken) => {
4473
+ const conflicts = [];
4474
+ const unmanaged = [];
4475
+ const resourceOwners = /* @__PURE__ */ new Set();
4476
+ const withoutLabel = await fetchAll(async (pageToken) => {
4167
4477
  try {
4168
4478
  const { tailordbServices, nextPageToken } = await client.listTailorDBServices({
4169
4479
  workspaceId,
@@ -4175,24 +4485,47 @@ async function planServices(client, workspaceId, tailordbs) {
4175
4485
  throw error;
4176
4486
  }
4177
4487
  });
4178
- const existingNameSet = /* @__PURE__ */ new Set();
4179
- existingServices.forEach((service) => {
4180
- const name = service.namespace?.name;
4181
- if (name) existingNameSet.add(name);
4182
- });
4183
- for (const tailordb of tailordbs) if (existingNameSet.has(tailordb.namespace)) {
4184
- changeSet.updates.push({ name: tailordb.namespace });
4185
- existingNameSet.delete(tailordb.namespace);
4186
- } else changeSet.creates.push({
4187
- name: tailordb.namespace,
4188
- request: {
4189
- workspaceId,
4190
- namespaceName: tailordb.namespace,
4191
- defaultTimezone: "UTC"
4192
- }
4193
- });
4194
- existingNameSet.forEach((namespaceName) => {
4195
- changeSet.deletes.push({
4488
+ const existingServices = {};
4489
+ await Promise.all(withoutLabel.map(async (resource) => {
4490
+ if (!resource.namespace?.name) return;
4491
+ const { metadata } = await client.getMetadata({ trn: trn(workspaceId, resource.namespace.name) });
4492
+ existingServices[resource.namespace.name] = {
4493
+ resource,
4494
+ label: metadata?.labels[sdkNameLabelKey]
4495
+ };
4496
+ }));
4497
+ for (const tailordb of tailordbs) {
4498
+ const existing = existingServices[tailordb.namespace];
4499
+ const metaRequest = await buildMetaRequest(trn(workspaceId, tailordb.namespace), appName);
4500
+ if (existing) {
4501
+ if (!existing.label) unmanaged.push({
4502
+ resourceType: "TailorDB service",
4503
+ resourceName: tailordb.namespace
4504
+ });
4505
+ else if (existing.label !== appName) conflicts.push({
4506
+ resourceType: "TailorDB service",
4507
+ resourceName: tailordb.namespace,
4508
+ currentOwner: existing.label
4509
+ });
4510
+ changeSet.updates.push({
4511
+ name: tailordb.namespace,
4512
+ metaRequest
4513
+ });
4514
+ delete existingServices[tailordb.namespace];
4515
+ } else changeSet.creates.push({
4516
+ name: tailordb.namespace,
4517
+ request: {
4518
+ workspaceId,
4519
+ namespaceName: tailordb.namespace,
4520
+ defaultTimezone: "UTC"
4521
+ },
4522
+ metaRequest
4523
+ });
4524
+ }
4525
+ Object.entries(existingServices).forEach(([namespaceName]) => {
4526
+ const label = existingServices[namespaceName]?.label;
4527
+ if (label && label !== appName) resourceOwners.add(label);
4528
+ if (label === appName) changeSet.deletes.push({
4196
4529
  name: namespaceName,
4197
4530
  request: {
4198
4531
  workspaceId,
@@ -4200,7 +4533,12 @@ async function planServices(client, workspaceId, tailordbs) {
4200
4533
  }
4201
4534
  });
4202
4535
  });
4203
- return changeSet;
4536
+ return {
4537
+ changeSet,
4538
+ conflicts,
4539
+ unmanaged,
4540
+ resourceOwners
4541
+ };
4204
4542
  }
4205
4543
  async function planTypes(client, workspaceId, tailordbs, executors, deletedServices) {
4206
4544
  const changeSet = new ChangeSet("TailorDB types");
@@ -4249,7 +4587,6 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
4249
4587
  }
4250
4588
  existingNameSet.forEach((name) => {
4251
4589
  changeSet.deletes.push({
4252
- tag: "type-deleted",
4253
4590
  name,
4254
4591
  request: {
4255
4592
  workspaceId,
@@ -4261,8 +4598,12 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
4261
4598
  }
4262
4599
  for (const namespaceName of deletedServices) (await fetchTypes(namespaceName)).forEach((typ) => {
4263
4600
  changeSet.deletes.push({
4264
- tag: "service-deleted",
4265
- name: typ.name
4601
+ name: typ.name,
4602
+ request: {
4603
+ workspaceId,
4604
+ namespaceName,
4605
+ tailordbTypeName: typ.name
4606
+ }
4266
4607
  });
4267
4608
  });
4268
4609
  return changeSet;
@@ -4520,7 +4861,6 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
4520
4861
  }
4521
4862
  existingNameSet.forEach((name) => {
4522
4863
  changeSet.deletes.push({
4523
- tag: "gql-permission-deleted",
4524
4864
  name,
4525
4865
  request: {
4526
4866
  workspaceId,
@@ -4532,8 +4872,12 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
4532
4872
  }
4533
4873
  for (const namespaceName of deletedServices) (await fetchGqlPermissions(namespaceName)).forEach((gqlPermission) => {
4534
4874
  changeSet.deletes.push({
4535
- tag: "service-deleted",
4536
- name: gqlPermission.typeName
4875
+ name: gqlPermission.typeName,
4876
+ request: {
4877
+ workspaceId,
4878
+ namespaceName,
4879
+ typeName: gqlPermission.typeName
4880
+ }
4537
4881
  });
4538
4882
  });
4539
4883
  return changeSet;
@@ -4628,6 +4972,7 @@ async function apply(options) {
4628
4972
  const configPath = loadConfigPath(options?.configPath);
4629
4973
  const { config } = await loadConfig(configPath);
4630
4974
  const dryRun = options?.dryRun ?? false;
4975
+ const yes = options?.yes ?? false;
4631
4976
  const buildOnly = options?.buildOnly ?? false;
4632
4977
  await generateUserTypes(config, configPath);
4633
4978
  const application = defineApplication(config);
@@ -4643,13 +4988,56 @@ async function apply(options) {
4643
4988
  workspaceId: options?.workspaceId,
4644
4989
  profile: options?.profile
4645
4990
  });
4646
- const tailorDB = await planTailorDB(client, workspaceId, application);
4647
- const staticWebsite = await planStaticWebsite(client, workspaceId, application);
4648
- const idp = await planIdP(client, workspaceId, application);
4649
- const auth = await planAuth(client, workspaceId, application);
4650
- const pipeline = await planPipeline(client, workspaceId, application);
4651
- const app = await planApplication(client, workspaceId, application);
4652
- const executor = await planExecutor(client, workspaceId, application);
4991
+ for (const tailordb of application.tailorDBServices) await tailordb.loadTypes();
4992
+ for (const pipeline$1 of application.resolverServices) await pipeline$1.loadResolvers();
4993
+ if (application.executorService) await application.executorService.loadExecutors();
4994
+ console.log("");
4995
+ const ctx = {
4996
+ client,
4997
+ workspaceId,
4998
+ application
4999
+ };
5000
+ const tailorDB = await planTailorDB(ctx);
5001
+ const staticWebsite = await planStaticWebsite(ctx);
5002
+ const idp = await planIdP(ctx);
5003
+ const auth = await planAuth(ctx);
5004
+ const pipeline = await planPipeline(ctx);
5005
+ const app = await planApplication(ctx);
5006
+ const executor = await planExecutor(ctx);
5007
+ const allConflicts = [
5008
+ ...tailorDB.conflicts,
5009
+ ...staticWebsite.conflicts,
5010
+ ...idp.conflicts,
5011
+ ...auth.conflicts,
5012
+ ...pipeline.conflicts,
5013
+ ...executor.conflicts
5014
+ ];
5015
+ const allUnmanaged = [
5016
+ ...tailorDB.unmanaged,
5017
+ ...staticWebsite.unmanaged,
5018
+ ...idp.unmanaged,
5019
+ ...auth.unmanaged,
5020
+ ...pipeline.unmanaged,
5021
+ ...executor.unmanaged
5022
+ ];
5023
+ await confirmOwnerConflict(allConflicts, application.name, yes);
5024
+ await confirmUnmanagedResources(allUnmanaged, application.name, yes);
5025
+ const resourceOwners = new Set([
5026
+ ...tailorDB.resourceOwners,
5027
+ ...staticWebsite.resourceOwners,
5028
+ ...idp.resourceOwners,
5029
+ ...auth.resourceOwners,
5030
+ ...pipeline.resourceOwners,
5031
+ ...executor.resourceOwners
5032
+ ]);
5033
+ const emptyApps = [...new Set(allConflicts.map((c) => c.currentOwner))].filter((owner) => !resourceOwners.has(owner));
5034
+ for (const emptyApp of emptyApps) app.deletes.push({
5035
+ name: emptyApp,
5036
+ request: {
5037
+ workspaceId,
5038
+ applicationName: emptyApp
5039
+ }
5040
+ });
4653
5041
  if (dryRun) {
4654
5042
  console.log("Dry run enabled. No changes applied.");
4655
5043
  return;
@@ -4724,6 +5112,11 @@ const applyCommand = defineCommand({
4724
5112
  type: "boolean",
4725
5113
  description: "Run the command without making any changes",
4726
5114
  alias: "d"
5115
+ },
5116
+ yes: {
5117
+ type: "boolean",
5118
+ description: "Skip all confirmation prompts",
5119
+ alias: "y"
4727
5120
  }
4728
5121
  },
4729
5122
  run: withCommonArgs(async (args) => {
@@ -4731,7 +5124,8 @@ const applyCommand = defineCommand({
4731
5124
  workspaceId: args["workspace-id"],
4732
5125
  profile: args.profile,
4733
5126
  configPath: args.config,
4734
- dryRun: args.dryRun
5127
+ dryRun: args.dryRun,
5128
+ yes: args.yes
4735
5129
  });
4736
5130
  })
4737
5131
  });
@@ -5855,4 +6249,4 @@ const tokenCommand = defineCommand({
5855
6249
  });
5856
6250
 
5857
6251
  //#endregion
5858
- export { PATScope, apply, applyCommand, commonArgs, createCommand, deleteCommand, fetchAll, fetchLatestToken, formatArgs, generate, generateCommand, generateUserTypes, initOperatorClient, listCommand, listCommand$1, loadAccessToken, loadConfig, loadConfigPath, loadWorkspaceId, machineUserList, machineUserToken, parseFormat, printWithFormat, readPlatformConfig, show, showCommand, tokenCommand, userAgent, withCommonArgs, workspaceCreate, workspaceDelete, workspaceList, writePlatformConfig };
6252
+ export { PATScope, apply, applyCommand, commonArgs, createCommand, deleteCommand, fetchAll, fetchLatestToken, formatArgs, generate, generateCommand, generateUserTypes, initOperatorClient, listCommand, listCommand$1, loadAccessToken, loadConfig, loadConfigPath, loadWorkspaceId, machineUserList, machineUserToken, parseFormat, printWithFormat, readPackageJson, readPlatformConfig, show, showCommand, tokenCommand, userAgent, withCommonArgs, workspaceCreate, workspaceDelete, workspaceList, writePlatformConfig };