@rulebricks/cli 2.1.6 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +75 -14
  2. package/cluster-setup/aws/README.md +123 -0
  3. package/cluster-setup/aws/check-aws-access.sh +242 -0
  4. package/cluster-setup/aws/parameters.json +13 -0
  5. package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
  6. package/cluster-setup/azure/README.md +141 -0
  7. package/cluster-setup/azure/check-aks-prereqs.sh +276 -0
  8. package/cluster-setup/azure/parameters.json +30 -0
  9. package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
  10. package/cluster-setup/gcp/README.md +189 -0
  11. package/cluster-setup/gcp/check-gke-prereqs.sh +260 -0
  12. package/dist/commands/backup.d.ts +5 -0
  13. package/dist/commands/backup.js +104 -0
  14. package/dist/commands/deploy.d.ts +3 -1
  15. package/dist/commands/deploy.js +226 -326
  16. package/dist/commands/destroy.d.ts +1 -1
  17. package/dist/commands/destroy.js +73 -123
  18. package/dist/commands/init.d.ts +5 -1
  19. package/dist/commands/init.js +78 -47
  20. package/dist/commands/list.d.ts +1 -0
  21. package/dist/commands/list.js +74 -0
  22. package/dist/commands/open.d.ts +1 -1
  23. package/dist/commands/open.js +4 -12
  24. package/dist/commands/redeploy.d.ts +6 -0
  25. package/dist/commands/redeploy.js +310 -0
  26. package/dist/commands/restore.d.ts +5 -0
  27. package/dist/commands/restore.js +338 -0
  28. package/dist/commands/status.js +62 -49
  29. package/dist/commands/upgrade.js +74 -51
  30. package/dist/components/DNSWaitScreen.d.ts +5 -1
  31. package/dist/components/DNSWaitScreen.js +47 -41
  32. package/dist/components/Wizard/WizardContext.d.ts +174 -29
  33. package/dist/components/Wizard/WizardContext.js +896 -91
  34. package/dist/components/Wizard/steps/CloudProviderStep.js +192 -102
  35. package/dist/components/Wizard/steps/DomainStep.js +5 -24
  36. package/dist/components/Wizard/steps/ExternalServicesStep.d.ts +6 -0
  37. package/dist/components/Wizard/steps/ExternalServicesStep.js +645 -0
  38. package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +2 -1
  39. package/dist/components/Wizard/steps/FeatureConfigStep.js +959 -248
  40. package/dist/components/Wizard/steps/FeaturesStep.js +31 -35
  41. package/dist/components/Wizard/steps/ObservabilityStep.d.ts +6 -0
  42. package/dist/components/Wizard/steps/ObservabilityStep.js +137 -0
  43. package/dist/components/Wizard/steps/ReviewStep.d.ts +2 -1
  44. package/dist/components/Wizard/steps/ReviewStep.js +56 -7
  45. package/dist/components/Wizard/steps/StorageStep.d.ts +9 -0
  46. package/dist/components/Wizard/steps/StorageStep.js +592 -0
  47. package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +20 -21
  48. package/dist/components/Wizard/steps/VersionStep.js +45 -23
  49. package/dist/components/Wizard/steps/index.d.ts +3 -3
  50. package/dist/components/Wizard/steps/index.js +3 -3
  51. package/dist/components/common/CommandApproval.d.ts +12 -0
  52. package/dist/components/common/CommandApproval.js +91 -0
  53. package/dist/components/common/DeploymentPicker.d.ts +14 -0
  54. package/dist/components/common/DeploymentPicker.js +16 -0
  55. package/dist/components/common/index.d.ts +2 -0
  56. package/dist/components/common/index.js +2 -0
  57. package/dist/index.js +94 -62
  58. package/dist/lib/cloudCli.d.ts +134 -63
  59. package/dist/lib/cloudCli.js +512 -220
  60. package/dist/lib/clusterSetupDefaults.d.ts +30 -0
  61. package/dist/lib/clusterSetupDefaults.js +64 -0
  62. package/dist/lib/commandApproval.d.ts +26 -0
  63. package/dist/lib/commandApproval.js +114 -0
  64. package/dist/lib/config.d.ts +12 -10
  65. package/dist/lib/config.js +91 -33
  66. package/dist/lib/configFixtures.d.ts +5 -0
  67. package/dist/lib/configFixtures.js +513 -0
  68. package/dist/lib/deploymentHealth.d.ts +32 -0
  69. package/dist/lib/deploymentHealth.js +157 -0
  70. package/dist/lib/dns.d.ts +1 -1
  71. package/dist/lib/dns.js +19 -1
  72. package/dist/lib/dns.test.d.ts +1 -0
  73. package/dist/lib/dns.test.js +27 -0
  74. package/dist/lib/dockerHub.d.ts +12 -1
  75. package/dist/lib/dockerHub.js +18 -8
  76. package/dist/lib/helm.d.ts +4 -0
  77. package/dist/lib/helm.js +16 -0
  78. package/dist/lib/helmValues.d.ts +25 -0
  79. package/dist/lib/helmValues.js +1937 -259
  80. package/dist/lib/helmValues.test.d.ts +1 -0
  81. package/dist/lib/helmValues.test.js +966 -0
  82. package/dist/lib/htpasswd.d.ts +1 -0
  83. package/dist/lib/htpasswd.js +15 -0
  84. package/dist/lib/kubernetes.d.ts +126 -13
  85. package/dist/lib/kubernetes.js +624 -134
  86. package/dist/lib/secrets.d.ts +23 -0
  87. package/dist/lib/secrets.js +158 -0
  88. package/dist/lib/validateValues.d.ts +31 -0
  89. package/dist/lib/validateValues.js +253 -0
  90. package/dist/lib/versions.d.ts +82 -11
  91. package/dist/lib/versions.js +131 -31
  92. package/dist/lib/versions.test.d.ts +1 -0
  93. package/dist/lib/versions.test.js +81 -0
  94. package/dist/lib/wizardSteps.d.ts +14 -0
  95. package/dist/lib/wizardSteps.js +23 -0
  96. package/dist/lib/workloadIdentity.d.ts +26 -0
  97. package/dist/lib/workloadIdentity.js +323 -0
  98. package/dist/lib/workloadIdentity.test.d.ts +1 -0
  99. package/dist/lib/workloadIdentity.test.js +57 -0
  100. package/dist/types/index.d.ts +2152 -95
  101. package/dist/types/index.js +554 -286
  102. package/package.json +10 -4
  103. package/schema/values.schema.json +1934 -0
  104. package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
  105. package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
  106. package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
  107. package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
  108. package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
  109. package/dist/components/Wizard/steps/TierStep.js +0 -29
  110. package/dist/lib/terraform.d.ts +0 -66
  111. package/dist/lib/terraform.js +0 -754
  112. package/terraform/aws/main.tf +0 -355
  113. package/terraform/azure/main.tf +0 -371
  114. package/terraform/gcp/main.tf +0 -407
@@ -0,0 +1,30 @@
1
+ import { CloudProvider } from "../types/index.js";
2
+ /**
3
+ * Detects the IAM roles / managed identities / buckets that the bundled
4
+ * cluster-setup templates (cluster-setup/aws|azure|gcp) provision, so the wizard
5
+ * can preselect them as sensible defaults. The user can always arrow to a
6
+ * different option, and when nothing matches the behavior is unchanged.
7
+ *
8
+ * Naming conventions (parameterized by cluster name, default rulebricks-cluster):
9
+ * AWS (CloudFormation): roles `${cluster}-metrics`, `${cluster}-decision-logs`,
10
+ * `${cluster}-backups`; buckets `${cluster}-decision-logs-*`,
11
+ * `${cluster}-backups-*`.
12
+ * Azure (Bicep): UAMIs `${cluster}-metrics`, `${cluster}-decision-logs`,
13
+ * `${cluster}-backups`; blob containers `decision-logs`, `backups`.
14
+ * GCP (docs): service account `rulebricks-vector` (decision logs only).
15
+ */
16
+ export type ClusterSetupCategory = "metrics-identity" | "decision-logs-identity" | "backups-identity" | "decision-logs-bucket" | "backups-bucket" | "decision-logs-container" | "backups-container";
17
+ export interface ClusterSetupDetectOptions {
18
+ provider?: CloudProvider | null;
19
+ /** Cluster name used to build the most specific match. */
20
+ clusterName?: string;
21
+ }
22
+ /**
23
+ * Returns the index of the best cluster-setup match in `candidates`, or -1 when
24
+ * none match. Matching is case-insensitive and prefers more specific patterns.
25
+ */
26
+ export declare function findClusterSetupDefaultIndex(candidates: string[], category: ClusterSetupCategory, options?: ClusterSetupDetectOptions): number;
27
+ /**
28
+ * Convenience wrapper returning the matched candidate string (or undefined).
29
+ */
30
+ export declare function findClusterSetupDefault(candidates: string[], category: ClusterSetupCategory, options?: ClusterSetupDetectOptions): string | undefined;
@@ -0,0 +1,64 @@
1
+ const DEFAULT_CLUSTER = "rulebricks-cluster";
2
+ /**
3
+ * Substrings to look for, ordered most-specific to least-specific. The first
4
+ * candidate matching the highest-priority pattern wins.
5
+ */
6
+ function patternsFor(category, cluster) {
7
+ // The consolidated cluster-setup provisions a single `${cluster}-rulebricks`
8
+ // identity and a single `${cluster}-data` bucket/container. Older split
9
+ // resources (`-decision-logs`, `-backups`, `-metrics`) are kept as lower-
10
+ // priority fallbacks so the wizard still preselects sensibly on legacy infra.
11
+ switch (category) {
12
+ case "metrics-identity":
13
+ return [`${cluster}-rulebricks`, `${cluster}-metrics`, "-rulebricks", "-metrics"];
14
+ case "decision-logs-identity":
15
+ case "backups-identity":
16
+ return [
17
+ `${cluster}-rulebricks`,
18
+ "-rulebricks",
19
+ `${cluster}-decision-logs`,
20
+ "-decision-logs",
21
+ `${cluster}-backups`,
22
+ "-backups",
23
+ ];
24
+ case "decision-logs-bucket":
25
+ case "backups-bucket":
26
+ return [
27
+ `${cluster}-data`,
28
+ "-data",
29
+ `${cluster}-decision-logs`,
30
+ "-decision-logs",
31
+ `${cluster}-backups`,
32
+ "-backups",
33
+ ];
34
+ case "decision-logs-container":
35
+ case "backups-container":
36
+ return [`${cluster}-data`, "-data", "rulebricks", "decision-logs", "backups"];
37
+ default:
38
+ return [];
39
+ }
40
+ }
41
+ /**
42
+ * Returns the index of the best cluster-setup match in `candidates`, or -1 when
43
+ * none match. Matching is case-insensitive and prefers more specific patterns.
44
+ */
45
+ export function findClusterSetupDefaultIndex(candidates, category, options = {}) {
46
+ if (candidates.length === 0)
47
+ return -1;
48
+ const cluster = (options.clusterName || DEFAULT_CLUSTER).trim() || DEFAULT_CLUSTER;
49
+ const lowered = candidates.map((c) => c.toLowerCase());
50
+ for (const pattern of patternsFor(category, cluster)) {
51
+ const needle = pattern.toLowerCase();
52
+ const index = lowered.findIndex((c) => c.includes(needle));
53
+ if (index >= 0)
54
+ return index;
55
+ }
56
+ return -1;
57
+ }
58
+ /**
59
+ * Convenience wrapper returning the matched candidate string (or undefined).
60
+ */
61
+ export function findClusterSetupDefault(candidates, category, options = {}) {
62
+ const index = findClusterSetupDefaultIndex(candidates, category, options);
63
+ return index >= 0 ? candidates[index] : undefined;
64
+ }
@@ -0,0 +1,26 @@
1
+ import { CloudProvider } from "../types/index.js";
2
+ export type CommandApprovalDecision = "approve" | "deny";
3
+ export type CommandApprovalScope = "once" | "all-like" | "all";
4
+ export interface CommandApprovalRequest {
5
+ intent: string;
6
+ command: string;
7
+ description?: string;
8
+ provider?: CloudProvider;
9
+ mutating?: boolean;
10
+ }
11
+ export interface PendingCommandApproval extends CommandApprovalRequest {
12
+ id: number;
13
+ }
14
+ type Listener = () => void;
15
+ export declare class CommandDeniedError extends Error {
16
+ readonly command: string;
17
+ readonly intent: string;
18
+ constructor(command: string, intent: string);
19
+ }
20
+ export declare function setCommandApprovalInteractive(value: boolean): void;
21
+ export declare function subscribeCommandApprovals(listener: Listener): () => void;
22
+ export declare function getCurrentCommandApproval(): PendingCommandApproval | null;
23
+ export declare function respondToCommandApproval(id: number, decision: CommandApprovalDecision, scope?: CommandApprovalScope): void;
24
+ export declare function requestCommandApproval(req: CommandApprovalRequest): Promise<CommandApprovalDecision>;
25
+ export declare function approveCloudCommandOrThrow(req: CommandApprovalRequest): Promise<void>;
26
+ export {};
@@ -0,0 +1,114 @@
1
+ export class CommandDeniedError extends Error {
2
+ command;
3
+ intent;
4
+ constructor(command, intent) {
5
+ super(`User denied cloud CLI command: ${command}`);
6
+ this.name = "CommandDeniedError";
7
+ this.command = command;
8
+ this.intent = intent;
9
+ }
10
+ }
11
+ let nextId = 1;
12
+ let interactive = false;
13
+ let approveAll = false;
14
+ const approvedIntents = new Set();
15
+ const queue = [];
16
+ const inFlightByCommand = new Map();
17
+ const listeners = new Set();
18
+ let current = null;
19
+ function isTty() {
20
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
21
+ }
22
+ function shouldPrompt(req) {
23
+ if (!interactive || !isTty())
24
+ return false;
25
+ if (approveAll || approvedIntents.has(req.intent))
26
+ return false;
27
+ return true;
28
+ }
29
+ function notify() {
30
+ for (const listener of listeners) {
31
+ listener();
32
+ }
33
+ }
34
+ function pumpQueue() {
35
+ if (current || queue.length === 0)
36
+ return;
37
+ current = queue.shift() || null;
38
+ notify();
39
+ }
40
+ function complete(approval, decision) {
41
+ inFlightByCommand.delete(approval.command);
42
+ for (const resolve of approval.resolvers) {
43
+ resolve(decision);
44
+ }
45
+ }
46
+ function completeQueuedByIntent(intent) {
47
+ for (let i = queue.length - 1; i >= 0; i -= 1) {
48
+ const approval = queue[i];
49
+ if (approval.intent !== intent)
50
+ continue;
51
+ queue.splice(i, 1);
52
+ complete(approval, "approve");
53
+ }
54
+ }
55
+ export function setCommandApprovalInteractive(value) {
56
+ interactive = value;
57
+ }
58
+ export function subscribeCommandApprovals(listener) {
59
+ listeners.add(listener);
60
+ return () => {
61
+ listeners.delete(listener);
62
+ };
63
+ }
64
+ export function getCurrentCommandApproval() {
65
+ if (!current)
66
+ return null;
67
+ const { resolvers: _resolvers, ...pending } = current;
68
+ return pending;
69
+ }
70
+ export function respondToCommandApproval(id, decision, scope = "once") {
71
+ if (!current || current.id !== id)
72
+ return;
73
+ const approval = current;
74
+ current = null;
75
+ if (decision === "approve") {
76
+ if (scope === "all") {
77
+ approveAll = true;
78
+ }
79
+ else if (scope === "all-like") {
80
+ approvedIntents.add(approval.intent);
81
+ completeQueuedByIntent(approval.intent);
82
+ }
83
+ }
84
+ complete(approval, decision);
85
+ notify();
86
+ pumpQueue();
87
+ }
88
+ export async function requestCommandApproval(req) {
89
+ if (!shouldPrompt(req)) {
90
+ return "approve";
91
+ }
92
+ const existing = inFlightByCommand.get(req.command);
93
+ if (existing) {
94
+ return new Promise((resolve) => {
95
+ existing.resolvers.push(resolve);
96
+ });
97
+ }
98
+ return new Promise((resolve) => {
99
+ const approval = {
100
+ id: nextId++,
101
+ ...req,
102
+ resolvers: [resolve],
103
+ };
104
+ inFlightByCommand.set(req.command, approval);
105
+ queue.push(approval);
106
+ pumpQueue();
107
+ });
108
+ }
109
+ export async function approveCloudCommandOrThrow(req) {
110
+ const decision = await requestCommandApproval(req);
111
+ if (decision === "deny") {
112
+ throw new CommandDeniedError(req.command, req.intent);
113
+ }
114
+ }
@@ -15,6 +15,16 @@ export declare function listDeployments(): Promise<string[]>;
15
15
  * Checks if a deployment exists
16
16
  */
17
17
  export declare function deploymentExists(name: string): Promise<boolean>;
18
+ export declare function resolveDeploymentConfigVersion(parsed: Record<string, unknown>, values?: {
19
+ global?: {
20
+ version?: unknown;
21
+ };
22
+ }, state?: {
23
+ version?: unknown;
24
+ application?: {
25
+ version?: unknown;
26
+ };
27
+ }): string;
18
28
  /**
19
29
  * Saves a deployment configuration
20
30
  */
@@ -24,8 +34,8 @@ export declare function saveDeploymentConfig(config: DeploymentConfig): Promise<
24
34
  */
25
35
  export declare function loadDeploymentConfig(name: string): Promise<DeploymentConfig>;
26
36
  /**
27
- * Clones a deployment configuration to a new name
28
- * Only copies config.yaml with the new name - state and terraform are not copied
37
+ * Clones a deployment configuration to a new name.
38
+ * Only copies config.yaml with the new name - state is not copied.
29
39
  */
30
40
  export declare function cloneDeploymentConfig(sourceName: string, targetName: string): Promise<DeploymentConfig>;
31
41
  /**
@@ -48,14 +58,6 @@ export declare function loadHelmValues(name: string): Promise<Record<string, unk
48
58
  * Gets the Helm values file path
49
59
  */
50
60
  export declare function getHelmValuesPath(name: string): string;
51
- /**
52
- * Saves Terraform variables
53
- */
54
- export declare function saveTerraformVars(name: string, vars: Record<string, unknown>): Promise<string>;
55
- /**
56
- * Gets the Terraform working directory
57
- */
58
- export declare function getTerraformDir(name: string): string;
59
61
  /**
60
62
  * Deletes a deployment and all its files
61
63
  */
@@ -44,6 +44,83 @@ export async function deploymentExists(name) {
44
44
  return false;
45
45
  }
46
46
  }
47
+ function migrateStorageConfig(parsed) {
48
+ if (!parsed || typeof parsed !== "object")
49
+ return;
50
+ // Collapse the older per-purpose storage shape (separate decisionLogs/dbBackups
51
+ // buckets, containers, and identities) into a single bucket/container with key
52
+ // prefixes. The decision-logs location is treated as canonical; the dbBackups
53
+ // path becomes a prefix in the same bucket/container.
54
+ const storage = parsed.storage;
55
+ if (storage && typeof storage === "object" && storage.decisionLogs) {
56
+ const dl = storage.decisionLogs || {};
57
+ const db = storage.dbBackups || {};
58
+ parsed.storage = {
59
+ provider: storage.provider,
60
+ cloudAuthMode: storage.cloudAuthMode,
61
+ bucket: dl.bucket ?? db.bucket,
62
+ region: dl.region ?? db.region,
63
+ awsIamRoleArn: storage.awsIamRoleArn,
64
+ azureBlobClientId: storage.azureBlobClientId,
65
+ azureBlobTenantId: storage.azureBlobTenantId,
66
+ azureBlobConnectionStringSecretRef: storage.azureBlobConnectionStringSecretRef,
67
+ azureBlobContainer: dl.azureBlobContainer ?? db.azureBlobContainer,
68
+ gcpServiceAccountEmail: storage.gcpServiceAccountEmail,
69
+ paths: {
70
+ decisionLogs: dl.path || "decision-logs",
71
+ dbBackups: db.path || "db-backups",
72
+ },
73
+ };
74
+ }
75
+ if (parsed.features?.decisionLogQuery) {
76
+ delete parsed.features.decisionLogQuery;
77
+ }
78
+ }
79
+ export function resolveDeploymentConfigVersion(parsed, values, state) {
80
+ if (typeof values?.global?.version === "string" && values.global.version) {
81
+ return values.global.version;
82
+ }
83
+ if (typeof state?.application?.version === "string" &&
84
+ state.application.version) {
85
+ return state.application.version;
86
+ }
87
+ if (typeof state?.version === "string" && state.version) {
88
+ return state.version;
89
+ }
90
+ if (typeof parsed.chartVersion === "string" && parsed.chartVersion) {
91
+ return parsed.chartVersion;
92
+ }
93
+ return "latest";
94
+ }
95
+ async function inferMissingVersion(name, parsed) {
96
+ const dir = getDeploymentDir(name);
97
+ let values;
98
+ let state;
99
+ try {
100
+ const valuesContent = await fs.readFile(path.join(dir, "values.yaml"), "utf-8");
101
+ values = yaml.parse(valuesContent);
102
+ }
103
+ catch {
104
+ // values.yaml is optional for config-only deployments.
105
+ }
106
+ try {
107
+ const stateContent = await fs.readFile(path.join(dir, "state.yaml"), "utf-8");
108
+ state = yaml.parse(stateContent);
109
+ }
110
+ catch {
111
+ // state.yaml may not exist yet.
112
+ }
113
+ return resolveDeploymentConfigVersion(parsed, values, state);
114
+ }
115
+ async function migrateConfig(name, parsed) {
116
+ if (!parsed || typeof parsed !== "object")
117
+ return;
118
+ const config = parsed;
119
+ migrateStorageConfig(config);
120
+ if (typeof config.version !== "string" || !config.version) {
121
+ config.version = await inferMissingVersion(name, config);
122
+ }
123
+ }
47
124
  /**
48
125
  * Saves a deployment configuration
49
126
  */
@@ -60,11 +137,21 @@ export async function loadDeploymentConfig(name) {
60
137
  const configPath = path.join(getDeploymentDir(name), "config.yaml");
61
138
  const content = await fs.readFile(configPath, "utf-8");
62
139
  const parsed = yaml.parse(content);
140
+ if (parsed &&
141
+ typeof parsed === "object" &&
142
+ "infrastructure" in parsed &&
143
+ parsed.infrastructure &&
144
+ typeof parsed.infrastructure === "object" &&
145
+ "mode" in parsed.infrastructure &&
146
+ parsed.infrastructure.mode === "provision") {
147
+ throw new Error(`Deployment "${name}" was created with CLI-managed infrastructure, which is no longer supported. Use an existing Kubernetes cluster and create a new deployment config.`);
148
+ }
149
+ await migrateConfig(name, parsed);
63
150
  return DeploymentConfigSchema.parse(parsed);
64
151
  }
65
152
  /**
66
- * Clones a deployment configuration to a new name
67
- * Only copies config.yaml with the new name - state and terraform are not copied
153
+ * Clones a deployment configuration to a new name.
154
+ * Only copies config.yaml with the new name - state is not copied.
68
155
  */
69
156
  export async function cloneDeploymentConfig(sourceName, targetName) {
70
157
  const sourceConfig = await loadDeploymentConfig(sourceName);
@@ -126,35 +213,6 @@ export async function loadHelmValues(name) {
126
213
  export function getHelmValuesPath(name) {
127
214
  return path.join(getDeploymentDir(name), "values.yaml");
128
215
  }
129
- /**
130
- * Saves Terraform variables
131
- */
132
- export async function saveTerraformVars(name, vars) {
133
- const dir = path.join(getDeploymentDir(name), "terraform");
134
- await fs.mkdir(dir, { recursive: true });
135
- // Convert to HCL-compatible format
136
- let content = "";
137
- for (const [key, value] of Object.entries(vars)) {
138
- if (typeof value === "string") {
139
- content += `${key} = "${value}"\n`;
140
- }
141
- else if (typeof value === "number" || typeof value === "boolean") {
142
- content += `${key} = ${value}\n`;
143
- }
144
- else {
145
- content += `${key} = ${JSON.stringify(value)}\n`;
146
- }
147
- }
148
- const varsPath = path.join(dir, "terraform.tfvars");
149
- await fs.writeFile(varsPath, content, "utf-8");
150
- return varsPath;
151
- }
152
- /**
153
- * Gets the Terraform working directory
154
- */
155
- export function getTerraformDir(name) {
156
- return path.join(getDeploymentDir(name), "terraform");
157
- }
158
216
  /**
159
217
  * Deletes a deployment and all its files
160
218
  */
@@ -212,6 +270,7 @@ export async function loadProfile() {
212
270
  try {
213
271
  const content = await fs.readFile(profilePath, "utf-8");
214
272
  const data = yaml.parse(content);
273
+ migrateStorageConfig(data);
215
274
  return ProfileConfigSchema.parse(data);
216
275
  }
217
276
  catch {
@@ -238,7 +297,6 @@ export function extractProfileFromConfig(config) {
238
297
  provider: config.infrastructure.provider,
239
298
  region: config.infrastructure.region,
240
299
  clusterName: config.infrastructure.clusterName,
241
- infrastructureMode: config.infrastructure.mode,
242
300
  // Domain - store suffix for suggesting new domains
243
301
  domainSuffix: extractDomainSuffix(config.domain),
244
302
  adminEmail: config.adminEmail,
@@ -255,8 +313,8 @@ export function extractProfileFromConfig(config) {
255
313
  openaiApiKey: config.features.ai.openaiApiKey,
256
314
  licenseKey: config.licenseKey,
257
315
  // Preferences
258
- tier: config.tier,
259
316
  databaseType: config.database.type,
317
+ storage: config.storage,
260
318
  // SSO
261
319
  ssoProvider: config.features.sso.provider,
262
320
  ssoUrl: config.features.sso.url,
@@ -0,0 +1,5 @@
1
+ import { DeploymentConfig } from "../types/index.js";
2
+ export declare function buildConfigMatrix(): {
3
+ name: string;
4
+ config: DeploymentConfig;
5
+ }[];