@stacksjs/ts-cloud 0.9.1 → 0.9.2

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 (63) hide show
  1. package/dist/aws/index.js +10 -2
  2. package/dist/bin/cli.js +540 -540
  3. package/dist/bin/dashboard-server.js +279 -279
  4. package/dist/{chunk-5nr95wpj.js → chunk-728jvrr0.js} +3 -3
  5. package/dist/{chunk-ws47smxr.js → chunk-k4px0bnv.js} +2 -2
  6. package/dist/{chunk-atbgd8wz.js → chunk-qr803fye.js} +112 -1
  7. package/dist/{chunk-hygwq9y2.js → chunk-rxgchb08.js} +1 -1
  8. package/dist/deploy/index.js +3 -3
  9. package/dist/dns/declared-records.d.ts +87 -0
  10. package/dist/dns/declared-records.test.d.ts +1 -0
  11. package/dist/dns/index.d.ts +2 -0
  12. package/dist/dns/index.js +10 -2
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.js +12 -4
  15. package/dist/ui/access-denied.html +2 -2
  16. package/dist/ui/account/automation.html +3 -3
  17. package/dist/ui/account/security.html +2 -2
  18. package/dist/ui/applications/compose.html +4 -4
  19. package/dist/ui/applications/new.html +2 -2
  20. package/dist/ui/data/backups.html +4 -4
  21. package/dist/ui/data/services.html +4 -4
  22. package/dist/ui/data/volumes.html +4 -4
  23. package/dist/ui/index.html +4 -4
  24. package/dist/ui/infrastructure/topology.html +4 -4
  25. package/dist/ui/operations/alerts.html +4 -4
  26. package/dist/ui/operations/configuration.html +4 -4
  27. package/dist/ui/operations/jobs.html +4 -4
  28. package/dist/ui/operations/maintenance.html +4 -4
  29. package/dist/ui/operations/observability.html +4 -4
  30. package/dist/ui/operations/previews.html +4 -4
  31. package/dist/ui/operations/queue.html +4 -4
  32. package/dist/ui/operations/regions.html +4 -4
  33. package/dist/ui/operations/releases.html +4 -4
  34. package/dist/ui/operations/spend.html +4 -4
  35. package/dist/ui/operations/workloads.html +4 -4
  36. package/dist/ui/security.html +2 -2
  37. package/dist/ui/server/actions.html +4 -4
  38. package/dist/ui/server/activity.html +2 -2
  39. package/dist/ui/server/capacity.html +3 -3
  40. package/dist/ui/server/database.html +4 -4
  41. package/dist/ui/server/deployments.html +3 -3
  42. package/dist/ui/server/diagnostics.html +2 -2
  43. package/dist/ui/server/firewall.html +4 -4
  44. package/dist/ui/server/fleet.html +4 -4
  45. package/dist/ui/server/logs.html +3 -3
  46. package/dist/ui/server/metrics.html +4 -4
  47. package/dist/ui/server/security.html +2 -2
  48. package/dist/ui/server/services.html +2 -2
  49. package/dist/ui/server/sites.html +4 -4
  50. package/dist/ui/server/ssh-keys.html +4 -4
  51. package/dist/ui/server/team.html +3 -3
  52. package/dist/ui/serverless/alarms.html +4 -4
  53. package/dist/ui/serverless/assets.html +2 -2
  54. package/dist/ui/serverless/data.html +4 -4
  55. package/dist/ui/serverless/deployments.html +2 -2
  56. package/dist/ui/serverless/firewall.html +2 -2
  57. package/dist/ui/serverless/functions.html +3 -3
  58. package/dist/ui/serverless/logs.html +4 -4
  59. package/dist/ui/serverless/queues.html +4 -4
  60. package/dist/ui/serverless/secrets.html +4 -4
  61. package/dist/ui/serverless/traces.html +4 -4
  62. package/dist/ui/serverless.html +4 -4
  63. package/package.json +3 -3
@@ -7,7 +7,7 @@ import {
7
7
  Route53Provider,
8
8
  UnifiedDnsValidator,
9
9
  createDnsProvider
10
- } from "./chunk-atbgd8wz.js";
10
+ } from "./chunk-qr803fye.js";
11
11
  import {
12
12
  Route53Client
13
13
  } from "./chunk-32e7ya18.js";
@@ -815,7 +815,7 @@ async function deployStaticSiteWithExternalDnsFull(config) {
815
815
  }
816
816
  }
817
817
  onProgress?.("upload", "Uploading files to S3...");
818
- const { uploadStaticFiles } = await import("./chunk-ws47smxr.js");
818
+ const { uploadStaticFiles } = await import("./chunk-k4px0bnv.js");
819
819
  const uploadResult = await uploadStaticFiles({
820
820
  sourceDir,
821
821
  bucket: infraResult.bucket,
@@ -835,7 +835,7 @@ async function deployStaticSiteWithExternalDnsFull(config) {
835
835
  }
836
836
  if (infraResult.distributionId && uploadResult.uploaded > 0) {
837
837
  onProgress?.("invalidate", "Invalidating CloudFront cache...");
838
- const { invalidateCache } = await import("./chunk-ws47smxr.js");
838
+ const { invalidateCache } = await import("./chunk-k4px0bnv.js");
839
839
  await invalidateCache(infraResult.distributionId);
840
840
  }
841
841
  onProgress?.("complete", "Deployment complete!");
@@ -5,9 +5,9 @@ import {
5
5
  generateStaticSiteTemplate,
6
6
  invalidateCache,
7
7
  uploadStaticFiles
8
- } from "./chunk-5nr95wpj.js";
8
+ } from "./chunk-728jvrr0.js";
9
9
  import"./chunk-01d86gt1.js";
10
- import"./chunk-atbgd8wz.js";
10
+ import"./chunk-qr803fye.js";
11
11
  import"./chunk-32e7ya18.js";
12
12
  import"./chunk-4cjrg98a.js";
13
13
  import"./chunk-rds0sy87.js";
@@ -956,6 +956,117 @@ function deepEqual(a, b) {
956
956
  const bRecord = b;
957
957
  return Object.keys(bRecord).every((key) => deepEqual(aRecord[key], bRecord[key]));
958
958
  }
959
+ // src/dns/declared-records.ts
960
+ var SINGLETON_TYPES = new Set(["A", "AAAA", "CNAME", "ALIAS"]);
961
+ function policyTag(content) {
962
+ const normalized = content.trim().replace(/^"|"$/g, "").toLowerCase();
963
+ if (normalized.startsWith("v=spf1"))
964
+ return "v=spf1";
965
+ if (normalized.startsWith("v=dmarc1"))
966
+ return "v=dmarc1";
967
+ return;
968
+ }
969
+ function unquote(value) {
970
+ return value.trim().replace(/^"(.*)"$/s, "$1");
971
+ }
972
+ function normalizeName(name) {
973
+ return name.replace(/\.$/, "").toLowerCase();
974
+ }
975
+ function qualifyName(name, zone) {
976
+ const z = normalizeName(zone);
977
+ const raw = (name ?? "@").trim();
978
+ if (!raw || raw === "@")
979
+ return z;
980
+ const n = normalizeName(raw);
981
+ if (n === z || n.endsWith(`.${z}`))
982
+ return n;
983
+ return `${n}.${z}`;
984
+ }
985
+ function resolveDeclaredRecords(records, zone) {
986
+ return records.map((record) => ({
987
+ ...record,
988
+ name: qualifyName(record.name, zone),
989
+ proxied: record.proxied ?? false
990
+ }));
991
+ }
992
+ function matches(existing, record) {
993
+ if (existing.type !== record.type)
994
+ return false;
995
+ if (normalizeName(existing.name) !== normalizeName(record.name))
996
+ return false;
997
+ if (SINGLETON_TYPES.has(record.type))
998
+ return true;
999
+ if (record.type === "TXT") {
1000
+ const tag = policyTag(record.content);
1001
+ if (tag)
1002
+ return policyTag(unquote(existing.content)) === tag;
1003
+ return unquote(existing.content) === unquote(record.content);
1004
+ }
1005
+ return unquote(existing.content) === unquote(record.content);
1006
+ }
1007
+ function isCurrent(existing, record) {
1008
+ if (unquote(existing.content) !== unquote(record.content))
1009
+ return false;
1010
+ if (record.priority !== undefined && existing.priority !== record.priority)
1011
+ return false;
1012
+ if (record.proxied !== undefined && existing.proxied !== undefined && existing.proxied !== record.proxied)
1013
+ return false;
1014
+ return true;
1015
+ }
1016
+ async function reconcileDeclaredRecords(options) {
1017
+ const { provider, zone, records } = options;
1018
+ const report = { outcomes: [], warnings: [] };
1019
+ if (records.length === 0)
1020
+ return report;
1021
+ const listed = await provider.listRecords(zone);
1022
+ if (!listed.success) {
1023
+ report.warnings.push(`could not list ${zone}: ${listed.message || "unknown provider error"} — records not reconciled`);
1024
+ return report;
1025
+ }
1026
+ const existing = listed.records;
1027
+ for (const record of records) {
1028
+ const current = existing.filter((e) => matches(e, record));
1029
+ if (current.length > 0 && current.every((e) => isCurrent(e, record))) {
1030
+ report.outcomes.push({ record, action: "unchanged" });
1031
+ continue;
1032
+ }
1033
+ const wire = {
1034
+ name: record.name,
1035
+ type: record.type,
1036
+ content: record.content,
1037
+ ttl: record.ttl ?? 3600,
1038
+ ...record.priority !== undefined ? { priority: record.priority } : {},
1039
+ ...record.proxied !== undefined ? { proxied: record.proxied } : {}
1040
+ };
1041
+ const supersedes = record.type === "TXT" && policyTag(record.content) !== undefined;
1042
+ if (supersedes) {
1043
+ for (const stale of current) {
1044
+ const removed = await provider.deleteRecord(zone, {
1045
+ name: stale.name,
1046
+ type: stale.type,
1047
+ content: stale.content
1048
+ });
1049
+ if (!removed.success)
1050
+ report.warnings.push(`could not remove superseded ${stale.type} at ${stale.name}: ${removed.message || "unknown error"}`);
1051
+ }
1052
+ }
1053
+ const result = supersedes || current.length === 0 ? await provider.createRecord(zone, wire) : await provider.upsertRecord(zone, wire);
1054
+ if (result.success)
1055
+ report.outcomes.push({ record, action: current.length > 0 ? "updated" : "created" });
1056
+ else
1057
+ report.outcomes.push({ record, action: "failed", message: result.message });
1058
+ }
1059
+ for (const type of ["MX"]) {
1060
+ const declaredNames = new Set(records.filter((r) => r.type === type).map((r) => normalizeName(r.name)));
1061
+ for (const name of declaredNames) {
1062
+ const declaredValues = new Set(records.filter((r) => r.type === type && normalizeName(r.name) === name).map((r) => unquote(r.content).toLowerCase()));
1063
+ const strays = existing.filter((e) => e.type === type && normalizeName(e.name) === name && !declaredValues.has(unquote(e.content).toLowerCase()));
1064
+ for (const stray of strays)
1065
+ report.warnings.push(`${name} has an undeclared ${type} record (${stray.content}) alongside the declared one — mail will be split between them. Remove it at the provider if it is stale.`);
1066
+ }
1067
+ }
1068
+ return report;
1069
+ }
959
1070
  // src/dns/route53-adapter.ts
960
1071
  class Route53Provider {
961
1072
  name = "route53";
@@ -2008,4 +2119,4 @@ class DnsProviderFactory {
2008
2119
  }
2009
2120
  var dnsProviders = new DnsProviderFactory;
2010
2121
 
2011
- export { PROXIABLE_RECORD_TYPES, PorkbunProvider, GoDaddyProvider, CLOUDFLARE_MANAGED_RULE_PREFIX, CloudflareProvider, Route53Provider, UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, ACMClient, ACMDnsValidator };
2122
+ export { PROXIABLE_RECORD_TYPES, PorkbunProvider, GoDaddyProvider, CLOUDFLARE_MANAGED_RULE_PREFIX, CloudflareProvider, policyTag, qualifyName, resolveDeclaredRecords, reconcileDeclaredRecords, Route53Provider, UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, ACMClient, ACMDnsValidator };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  deployStaticSiteWithExternalDnsFull
3
- } from "./chunk-5nr95wpj.js";
3
+ } from "./chunk-728jvrr0.js";
4
4
  import {
5
5
  box,
6
6
  error,
@@ -36,7 +36,7 @@ import {
36
36
  synchronizeDashboardUsers,
37
37
  trackDashboardOperation,
38
38
  verifyStaticApiOrigin
39
- } from "../chunk-hygwq9y2.js";
39
+ } from "../chunk-rxgchb08.js";
40
40
  import {
41
41
  deleteStaticSite,
42
42
  deployStaticSite,
@@ -47,7 +47,7 @@ import {
47
47
  generateStaticSiteTemplate,
48
48
  invalidateCache,
49
49
  uploadStaticFiles
50
- } from "../chunk-5nr95wpj.js";
50
+ } from "../chunk-728jvrr0.js";
51
51
  import {
52
52
  buildAndPushServerlessImage
53
53
  } from "../chunk-fgk8cqew.js";
@@ -56,7 +56,7 @@ import"../chunk-nsgqsmrh.js";
56
56
  import"../chunk-01d86gt1.js";
57
57
  import"../chunk-50jpda9q.js";
58
58
  import"../chunk-he9a874b.js";
59
- import"../chunk-atbgd8wz.js";
59
+ import"../chunk-qr803fye.js";
60
60
  import"../chunk-32e7ya18.js";
61
61
  import {
62
62
  IPV6_EXCLUDED_HOST_LABELS,
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Publish the records a deploy cannot infer: mail, verification tokens,
3
+ * third-party CNAMEs — everything in `infrastructure.dns.records`.
4
+ *
5
+ * The address records for `sites` are derived from the deploy itself and live in
6
+ * {@link import('../deploy/server-dns')}. This module covers the other half of a
7
+ * zone, which is precisely the half that silently disappears in a nameserver
8
+ * migration: nothing in a normal deploy reads or writes MX or SPF, so their
9
+ * absence is invisible until someone reports that mail stopped days later.
10
+ *
11
+ * The whole module is upsert-only. A real zone holds records owned by other
12
+ * tools and other people, so "not declared here" never implies "safe to
13
+ * delete". The single deliberate exception is a policy TXT record — see
14
+ * {@link policyTag}.
15
+ */
16
+ import type { DnsProvider, DnsRecordType } from './types';
17
+ /** A record to publish, already resolved to a fully-qualified name. */
18
+ export interface DeclaredRecord {
19
+ type: DnsRecordType;
20
+ /** Fully-qualified record name. */
21
+ name: string;
22
+ content: string;
23
+ ttl?: number;
24
+ priority?: number;
25
+ proxied?: boolean;
26
+ comment?: string;
27
+ }
28
+ /** The config shape, before names are qualified against the zone. */
29
+ export interface DeclaredRecordInput {
30
+ type: DnsRecordType;
31
+ name?: string;
32
+ content: string;
33
+ ttl?: number;
34
+ priority?: number;
35
+ proxied?: boolean;
36
+ comment?: string;
37
+ }
38
+ /**
39
+ * The policy tag opening a TXT record whose duplicates are harmful, or
40
+ * `undefined` for an ordinary TXT.
41
+ *
42
+ * This is the one case where publishing a second record is not additive but
43
+ * destructive. Two `v=spf1` records at a name are a permerror: receivers do not
44
+ * merge them, they conclude the domain has no usable SPF, and mail that would
45
+ * have passed starts failing. Two `v=DMARC1` records are discarded wholesale,
46
+ * silently disabling the policy. So a policy record must REPLACE its
47
+ * predecessor, while every other TXT at that name is left alone.
48
+ */
49
+ export declare function policyTag(content: string): string | undefined;
50
+ /**
51
+ * Qualify a configured record name against the zone.
52
+ *
53
+ * `'@'`, `''` and an omitted name all mean the apex; a bare label is joined to
54
+ * the zone; an already-qualified name is left alone.
55
+ */
56
+ export declare function qualifyName(name: string | undefined, zone: string): string;
57
+ /** Resolve config records into fully-qualified {@link DeclaredRecord}s. */
58
+ export declare function resolveDeclaredRecords(records: DeclaredRecordInput[], zone: string): DeclaredRecord[];
59
+ /** What the reconcile did with one record. */
60
+ export interface DeclaredRecordOutcome {
61
+ record: DeclaredRecord;
62
+ action: 'created' | 'updated' | 'unchanged' | 'failed';
63
+ message?: string;
64
+ }
65
+ export interface DeclaredRecordsReport {
66
+ outcomes: DeclaredRecordOutcome[];
67
+ /**
68
+ * Undeclared values sitting beside a declared multi-value record — a second
69
+ * MX from a previous provider, say. Reported and never removed: a stray MX
70
+ * splits mail delivery and the operator must see it, but silently deleting a
71
+ * record on a shared zone is the worse failure.
72
+ */
73
+ warnings: string[];
74
+ }
75
+ /**
76
+ * Publish `records` into `zone`, updating what exists and creating what does
77
+ * not.
78
+ *
79
+ * Never throws: this runs at the tail of a deploy that has already shipped, so a
80
+ * provider rejecting one record is reported per-record rather than failing the
81
+ * whole run — and the outcome list says exactly which ones landed.
82
+ */
83
+ export declare function reconcileDeclaredRecords(options: {
84
+ provider: DnsProvider;
85
+ zone: string;
86
+ records: DeclaredRecord[];
87
+ }): Promise<DeclaredRecordsReport>;
@@ -0,0 +1 @@
1
+ export {};
@@ -6,6 +6,8 @@ export * from './types';
6
6
  export { PorkbunProvider } from './porkbun';
7
7
  export { GoDaddyProvider } from './godaddy';
8
8
  export { CloudflareProvider } from './cloudflare';
9
+ export type { DeclaredRecord, DeclaredRecordInput, DeclaredRecordOutcome, DeclaredRecordsReport, } from './declared-records';
10
+ export { policyTag, qualifyName, reconcileDeclaredRecords, resolveDeclaredRecords, } from './declared-records';
9
11
  export { Route53Provider } from './route53-adapter';
10
12
  export { UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, } from './validator';
11
13
  import type { DnsProvider, DnsProviderConfig } from './types';
package/dist/dns/index.js CHANGED
@@ -11,12 +11,20 @@ import {
11
11
  createPorkbunValidator,
12
12
  createRoute53Validator,
13
13
  detectDnsProvider,
14
- dnsProviders
15
- } from "../chunk-atbgd8wz.js";
14
+ dnsProviders,
15
+ policyTag,
16
+ qualifyName,
17
+ reconcileDeclaredRecords,
18
+ resolveDeclaredRecords
19
+ } from "../chunk-qr803fye.js";
16
20
  import"../chunk-32e7ya18.js";
17
21
  import"../chunk-zqtpg06c.js";
18
22
  import"../chunk-v0bahtg2.js";
19
23
  export {
24
+ resolveDeclaredRecords,
25
+ reconcileDeclaredRecords,
26
+ qualifyName,
27
+ policyTag,
20
28
  dnsProviders,
21
29
  detectDnsProvider,
22
30
  createRoute53Validator,
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@ export { createCloudDriver, CloudDriverFactory, cloudDrivers, AwsDriver, Hetzner
41
41
  export type { CreateCloudDriverOptions } from './drivers/factory';
42
42
  export { dashboardActions, resolveDashboardAction, sanitizeCloudConfig, startLocalDashboardServer, } from './deploy/local-dashboard-server';
43
43
  export type { DashboardAction, LocalDashboardServer, LocalDashboardServerOptions, } from './deploy/local-dashboard-server';
44
- export { createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, PorkbunProvider, GoDaddyProvider, Route53Provider, CloudflareProvider, UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, } from './dns';
44
+ export { createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, PorkbunProvider, GoDaddyProvider, Route53Provider, CloudflareProvider, policyTag, qualifyName, reconcileDeclaredRecords, resolveDeclaredRecords, UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, } from './dns';
45
45
  export type { CloudflareProviderOptions, CloudflareRule, CloudflareRulesetPhase, CloudflareZoneSetting, } from './dns/cloudflare';
46
46
  export { CLOUDFLARE_MANAGED_RULE_PREFIX } from './dns/cloudflare';
47
47
  export * from './source';
@@ -50,5 +50,6 @@ export * from './backups';
50
50
  export * from './configuration';
51
51
  export type { DnsProvider, DnsProviderConfig, DnsRecord, DnsRecordType, DnsRecordResult, CreateRecordResult, DeleteRecordResult, ListRecordsResult, } from './dns';
52
52
  export { PROXIABLE_RECORD_TYPES } from './dns';
53
+ export type { DeclaredRecord, DeclaredRecordInput, DeclaredRecordOutcome, DeclaredRecordsReport, } from './dns';
53
54
  export * from '@ts-cloud/core';
54
55
  export type { S3Bucket, S3BucketPolicy, CloudFrontDistribution, CloudFrontOriginAccessControl, CloudFrontCacheBehavior, CloudFrontOrigin, } from '@ts-cloud/aws-types';
package/dist/index.js CHANGED
@@ -208,7 +208,7 @@ import {
208
208
  verifyUpdateSignature,
209
209
  webhookEndpoint,
210
210
  zeroCapacity
211
- } from "./chunk-hygwq9y2.js";
211
+ } from "./chunk-rxgchb08.js";
212
212
  import {
213
213
  deleteStaticSite,
214
214
  deployStaticSite,
@@ -219,7 +219,7 @@ import {
219
219
  generateStaticSiteTemplate,
220
220
  invalidateCache,
221
221
  uploadStaticFiles
222
- } from "./chunk-5nr95wpj.js";
222
+ } from "./chunk-728jvrr0.js";
223
223
  import {
224
224
  buildAndPushServerlessImage
225
225
  } from "./chunk-fgk8cqew.js";
@@ -291,8 +291,12 @@ import {
291
291
  createPorkbunValidator,
292
292
  createRoute53Validator,
293
293
  detectDnsProvider,
294
- dnsProviders
295
- } from "./chunk-atbgd8wz.js";
294
+ dnsProviders,
295
+ policyTag,
296
+ qualifyName,
297
+ reconcileDeclaredRecords,
298
+ resolveDeclaredRecords
299
+ } from "./chunk-qr803fye.js";
296
300
  import {
297
301
  Route53Client
298
302
  } from "./chunk-32e7ya18.js";
@@ -5795,6 +5799,7 @@ export {
5795
5799
  resolveExecStart,
5796
5800
  resolveDeploymentMode,
5797
5801
  resolveDeployBucketName,
5802
+ resolveDeclaredRecords,
5798
5803
  resolveDashboardDomains,
5799
5804
  resolveDashboardDomain,
5800
5805
  resolveDashboardAuth,
@@ -5834,12 +5839,14 @@ export {
5834
5839
  recordDashboardHostPosture,
5835
5840
  reconcileSourceWebhook,
5836
5841
  reconcileJobObservation,
5842
+ reconcileDeclaredRecords,
5837
5843
  reconcileCloudflareCdn,
5838
5844
  reconcileAddressRecords,
5839
5845
  rateLimitKey,
5840
5846
  rateLimitHeaders,
5841
5847
  quickHash,
5842
5848
  queueManagementManager,
5849
+ qualifyName,
5843
5850
  publicKeyFingerprint,
5844
5851
  providerEndpoint,
5845
5852
  propagationHeaders,
@@ -5855,6 +5862,7 @@ export {
5855
5862
  previewSchedule,
5856
5863
  previewNotifications,
5857
5864
  previewManager,
5865
+ policyTag,
5858
5866
  planMitigation,
5859
5867
  planEnforcement,
5860
5868
  planComposeTemplateUpgrade,