@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
@@ -1,74 +1,50 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState, useEffect, useCallback, useRef } from "react";
3
- import { Box, Text, useApp, useInput } from "ink";
2
+ import { useCallback, useEffect, useState } from "react";
3
+ import { Box, Text, useApp } from "ink";
4
4
  import { platform } from "os";
5
- import { BorderBox, Spinner, StatusLine, ThemeProvider, useTheme, Logo, } from "../components/common/index.js";
5
+ import { BorderBox, Spinner, StatusLine, ThemeProvider, useTheme, Logo, CommandApprovalProvider, } from "../components/common/index.js";
6
6
  import { DNSWaitScreen } from "../components/DNSWaitScreen.js";
7
- import { loadDeploymentConfig, loadDeploymentState, saveDeploymentState, updateDeploymentStatus, saveTerraformVars, } from "../lib/config.js";
8
- import { setupTerraformWorkspace, terraformInit, terraformPlan, terraformApply, terraformDestroy, cleanupOrphanedResources, updateKubeconfig, hasTerraformState, isTerraformInstalled, generateTerraformVars, } from "../lib/terraform.js";
9
- import { checkGcpApplicationDefaultCredentials, checkAzureResourceProviders, checkAzureVmQuota, AZURE_TIER_CORES, } from "../lib/cloudCli.js";
7
+ import { loadDeploymentConfig, loadDeploymentState, loadHelmValues, saveDeploymentState, updateDeploymentStatus, } from "../lib/config.js";
10
8
  import { installOrUpgradeChart, upgradeChart, isHelmInstalled, } from "../lib/helm.js";
9
+ import { assertValidHelmValues } from "../lib/validateValues.js";
11
10
  import { isKubectlInstalled, checkClusterAccessible, waitForCertificatesReady, } from "../lib/kubernetes.js";
11
+ import { updateKubeconfig, checkAuroraLogicalReplication, } from "../lib/cloudCli.js";
12
+ import { ensureWorkloadIdentityFederation } from "../lib/workloadIdentity.js";
12
13
  import { generateHelmValues, updateHelmValuesForTLS, } from "../lib/helmValues.js";
14
+ import { ensureNamespace, applyDeploymentSecrets } from "../lib/secrets.js";
15
+ import { CommandDeniedError } from "../lib/commandApproval.js";
13
16
  import { isSupportedDnsProvider, getNamespace, getReleaseName, } from "../types/index.js";
14
- function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
17
+ function getConfigProductVersion(config) {
18
+ return config.version;
19
+ }
20
+ function DeployCommandInner({ name, skipDns, version, regenerateValues = true, assumeDnsConfigured = false, inlineSecrets = false, }) {
21
+ const secretMode = inlineSecrets ? "inline" : "k8s";
15
22
  const { exit } = useApp();
16
23
  const { colors } = useTheme();
17
24
  const [step, setStep] = useState("loading");
18
25
  const [config, setConfig] = useState(null);
19
26
  const [error, setError] = useState(null);
20
27
  const [useExternalDns, setUseExternalDns] = useState(false);
21
- const infraStartedRef = useRef(false); // Track if we started infra provisioning (ref for sync access)
22
- const [cleanupError, setCleanupError] = useState(null);
23
28
  const [tlsWarning, setTlsWarning] = useState(null);
29
+ const [federationWarning, setFederationWarning] = useState(null);
24
30
  const [status, setStatus] = useState({
25
31
  preflight: "pending",
26
- infrastructure: "pending",
32
+ federation: "pending",
27
33
  kubeconfig: "pending",
28
34
  helmInstall: "pending",
29
35
  certCheck: "pending",
30
36
  dnsConfig: "pending",
31
37
  helmUpgradeTls: "pending",
32
38
  });
33
- // Handle cleanup prompt responses
34
- const handleCleanup = useCallback(async () => {
35
- setStep("cleanup-running");
36
- try {
37
- const cloudContext = config?.infrastructure.provider && config?.infrastructure.region
38
- ? {
39
- provider: config.infrastructure.provider,
40
- clusterName: config.infrastructure.clusterName || `${name}-cluster`,
41
- region: config.infrastructure.region,
42
- }
43
- : undefined;
44
- await terraformDestroy(name, cloudContext);
45
- setStep("cleanup-complete");
46
- setTimeout(() => exit(), 3000);
47
- }
48
- catch (err) {
49
- setCleanupError(err instanceof Error ? err.message : "Cleanup failed");
50
- setStep("cleanup-complete");
51
- setTimeout(() => exit(), 5000);
52
- }
53
- }, [name, config, exit]);
54
- const skipCleanup = useCallback(() => {
55
- setStep("error");
39
+ useEffect(() => {
40
+ runDeployment();
56
41
  }, []);
57
- useInput((input, key) => {
58
- if (step === "cleanup-prompt") {
59
- if (input === "y" || input === "Y") {
60
- handleCleanup();
61
- }
62
- else if (input === "n" || input === "N" || key.escape) {
63
- skipCleanup();
64
- }
65
- }
66
- else if (key.escape &&
67
- (step === "error" || step === "cleanup-complete")) {
68
- exit();
69
- }
70
- });
71
- // Resume after DNS wait (manual DNS flow)
42
+ const markRunning = (key) => {
43
+ setStatus((s) => ({ ...s, [key]: "running" }));
44
+ };
45
+ const markSuccess = (key) => {
46
+ setStatus((s) => ({ ...s, [key]: "success" }));
47
+ };
72
48
  const handleDnsComplete = useCallback(async () => {
73
49
  if (!config)
74
50
  return;
@@ -79,45 +55,21 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
79
55
  dnsConfig: "success",
80
56
  helmUpgradeTls: "running",
81
57
  }));
82
- // Update helm values to enable TLS
83
58
  await updateHelmValuesForTLS(name, true);
84
59
  const namespace = getNamespace(config.name);
85
60
  const releaseName = getReleaseName(config.name);
86
- // Upgrade the chart with TLS enabled
87
61
  await upgradeChart(name, { releaseName, namespace, version, wait: true });
88
62
  setStatus((s) => ({ ...s, helmUpgradeTls: "success", certCheck: "running" }));
89
63
  setStep("cert-check");
90
- try {
91
- await waitForCertificatesReady(namespace);
92
- setStatus((s) => ({ ...s, certCheck: "success" }));
93
- }
94
- catch (certErr) {
95
- setStatus((s) => ({ ...s, certCheck: "error" }));
96
- setTlsWarning("TLS certificates are still being issued. " +
97
- "HTTPS may not be available yet.");
98
- }
99
- // Update state
100
- await updateDeploymentStatus(name, "running", {
101
- application: {
102
- appVersion: config.appVersion || "latest",
103
- hpsVersion: config.hpsVersion || config.appVersion || "latest",
104
- chartVersion: version || "latest",
105
- namespace,
106
- url: `https://${config.domain}`,
107
- },
108
- });
64
+ await verifyCertificates(namespace);
65
+ await markRunningState(config, namespace);
109
66
  setStep("complete");
110
67
  setTimeout(() => exit(), 5000);
111
68
  }
112
69
  catch (err) {
113
- const message = err instanceof Error ? err.message : "TLS upgrade failed";
114
- setError(message);
115
- setStep("error");
116
- setStatus((s) => ({ ...s, helmUpgradeTls: "error" }));
117
- await updateDeploymentStatus(name, "failed");
70
+ await failDeployment(err, "TLS upgrade failed");
118
71
  }
119
72
  }, [config, name, version, exit]);
120
- // Skip DNS validation (manual DNS flow)
121
73
  const handleDnsSkip = useCallback(async () => {
122
74
  if (!config)
123
75
  return;
@@ -128,11 +80,10 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
128
80
  certCheck: "skipped",
129
81
  }));
130
82
  const namespace = getNamespace(config.name);
131
- // Mark as running without TLS upgrade
132
- await updateDeploymentStatus(name, "running", {
83
+ const productVersion = getConfigProductVersion(config);
84
+ await updateDeploymentStatus(name, "waiting-dns", {
133
85
  application: {
134
- appVersion: config.appVersion || "latest",
135
- hpsVersion: config.hpsVersion || config.appVersion || "latest",
86
+ version: productVersion,
136
87
  chartVersion: version || "latest",
137
88
  namespace,
138
89
  url: `https://${config.domain}`,
@@ -141,19 +92,12 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
141
92
  setStep("complete");
142
93
  setTimeout(() => exit(), 5000);
143
94
  }, [config, name, version, exit]);
144
- useEffect(() => {
145
- runDeployment();
146
- }, []);
147
95
  async function runDeployment() {
148
96
  try {
149
- // Load configuration
150
97
  const cfg = await loadDeploymentConfig(name);
151
98
  setConfig(cfg);
152
- // Determine if External DNS is enabled
153
- // External DNS = supported provider + auto-manage enabled
154
99
  const externalDnsEnabled = cfg.dns.autoManage && isSupportedDnsProvider(cfg.dns.provider);
155
100
  setUseExternalDns(externalDnsEnabled);
156
- // Initialize deployment state
157
101
  const existingState = await loadDeploymentState(name);
158
102
  const state = existingState || {
159
103
  name,
@@ -163,68 +107,41 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
163
107
  status: "deploying",
164
108
  };
165
109
  await saveDeploymentState(name, { ...state, status: "deploying" });
166
- // Preflight checks
167
110
  setStep("preflight");
168
- setStatus((s) => ({ ...s, preflight: "running" }));
111
+ markRunning("preflight");
169
112
  await runPreflightChecks(cfg);
170
- setStatus((s) => ({ ...s, preflight: "success" }));
171
- // Infrastructure provisioning
172
- const needsInfra = cfg.infrastructure.mode === "provision" && !skipInfra;
173
- if (needsInfra) {
174
- setStatus((s) => ({ ...s, infrastructure: "running" }));
175
- infraStartedRef.current = true; // Mark that we're doing infrastructure work
176
- // Check if already provisioned
177
- const hasState = await hasTerraformState(name);
178
- if (!hasState) {
179
- setStep("infra-setup");
180
- await setupTerraformWorkspace(name, cfg.infrastructure.provider);
181
- }
182
- // Generate and save terraform variables (always do this before plan,
183
- // even if state exists, in case config changed)
184
- const terraformVars = generateTerraformVars(cfg);
185
- await saveTerraformVars(name, terraformVars);
186
- setStep("infra-init");
187
- await terraformInit(name);
188
- setStep("infra-plan");
189
- await terraformPlan(name);
190
- // Clean up orphaned cloud resources from prior failed deployments
191
- // (e.g. CloudWatch log groups that survived an incomplete destroy)
192
- if (cfg.infrastructure.provider && cfg.infrastructure.region) {
193
- await cleanupOrphanedResources(cfg.infrastructure.provider, cfg.infrastructure.clusterName || `${name}-cluster`, cfg.infrastructure.region);
194
- }
195
- setStep("infra-apply");
196
- await terraformApply(name);
197
- setStatus((s) => ({ ...s, infrastructure: "success" }));
198
- // Update kubeconfig
199
- setStep("kubeconfig");
200
- setStatus((s) => ({ ...s, kubeconfig: "running" }));
201
- await updateKubeconfig(cfg.infrastructure.provider, cfg.infrastructure.clusterName || `${name}-cluster`, cfg.infrastructure.region, {
202
- gcpProjectId: cfg.infrastructure.gcpProjectId,
203
- azureResourceGroup: cfg.infrastructure.azureResourceGroup,
204
- });
205
- // Note: StorageClass is managed by the Helm chart, not the CLI
206
- // This avoids conflicts where kubectl-created resources lack Helm ownership labels
207
- setStatus((s) => ({ ...s, kubeconfig: "success" }));
113
+ markSuccess("preflight");
114
+ // Ensure the per-namespace workload-identity trust exists. cluster-setup
115
+ // creates the deployment-independent identity; this wires it to this
116
+ // deployment's ServiceAccounts so one cluster can host many deployments.
117
+ setStep("federation");
118
+ markRunning("federation");
119
+ try {
120
+ const federation = await ensureWorkloadIdentityFederation(cfg);
121
+ setStatus((s) => ({
122
+ ...s,
123
+ federation: federation.skipped ? "skipped" : "success",
124
+ }));
208
125
  }
209
- else {
210
- // For existing infrastructure, infrastructure is always skipped
211
- // kubeconfig may have been updated during preflight if cluster wasn't accessible
212
- // (in that case, it's already set to 'success'), otherwise mark as skipped
126
+ catch (federationError) {
127
+ if (!(federationError instanceof CommandDeniedError)) {
128
+ throw federationError;
129
+ }
130
+ setFederationWarning("Workload identity setup was skipped because a cloud CLI command was denied. Continuing assumes you created the trust manually.");
213
131
  setStatus((s) => ({
214
132
  ...s,
215
- infrastructure: "skipped",
216
- kubeconfig: s.kubeconfig === "success" ? "success" : "skipped",
133
+ federation: "skipped",
217
134
  }));
218
135
  }
219
- // Helm Chart Installation
220
136
  setStep("helm-install");
221
- setStatus((s) => ({ ...s, helmInstall: "running" }));
137
+ markRunning("helmInstall");
222
138
  const namespace = getNamespace(cfg.name);
223
139
  const releaseName = getReleaseName(cfg.name);
224
140
  if (externalDnsEnabled) {
225
- // SINGLE-PHASE DEPLOYMENT (External DNS)
226
- // Install with TLS enabled from the start - external-dns handles DNS records
227
- await generateHelmValues(cfg, { tlsEnabled: true });
141
+ if (regenerateValues) {
142
+ await generateHelmValues(cfg, { tlsEnabled: true, secretMode });
143
+ }
144
+ await ensureGeneratedValuesValid();
228
145
  await installOrUpgradeChart(name, {
229
146
  releaseName,
230
147
  namespace,
@@ -234,25 +151,59 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
234
151
  setStatus((s) => ({
235
152
  ...s,
236
153
  helmInstall: "success",
237
- dnsConfig: "skipped", // External DNS handles this
238
- helmUpgradeTls: "skipped", // TLS enabled from start
154
+ dnsConfig: "skipped",
155
+ helmUpgradeTls: "skipped",
239
156
  certCheck: "running",
240
157
  }));
241
158
  setStep("cert-check");
242
- try {
243
- await waitForCertificatesReady(namespace);
244
- setStatus((s) => ({ ...s, certCheck: "success" }));
245
- }
246
- catch (certErr) {
247
- setStatus((s) => ({ ...s, certCheck: "error" }));
248
- setTlsWarning("TLS certificates are still being issued. " +
249
- "HTTPS may not be available yet.");
250
- }
251
- // Update state to running
252
- await updateDeploymentStatus(name, "running", {
159
+ await verifyCertificates(namespace);
160
+ await markRunningState(cfg, namespace);
161
+ setStep("complete");
162
+ setTimeout(() => exit(), 5000);
163
+ return;
164
+ }
165
+ if (regenerateValues) {
166
+ await generateHelmValues(cfg, { tlsEnabled: false, secretMode });
167
+ }
168
+ await ensureGeneratedValuesValid();
169
+ // k8s secret mode: create the namespace + Secrets (idempotent upsert) so
170
+ // they exist before Helm runs and the chart's secretRef seams resolve.
171
+ if (secretMode === "k8s") {
172
+ await ensureNamespace(namespace);
173
+ await applyDeploymentSecrets(cfg, namespace);
174
+ }
175
+ await installOrUpgradeChart(name, {
176
+ releaseName,
177
+ namespace,
178
+ version,
179
+ wait: true,
180
+ });
181
+ markSuccess("helmInstall");
182
+ if (assumeDnsConfigured) {
183
+ setStatus((s) => ({
184
+ ...s,
185
+ dnsConfig: "skipped",
186
+ helmUpgradeTls: "skipped",
187
+ certCheck: "running",
188
+ }));
189
+ setStep("cert-check");
190
+ await verifyCertificates(namespace);
191
+ await markRunningState(cfg, namespace);
192
+ setStep("complete");
193
+ setTimeout(() => exit(), 5000);
194
+ return;
195
+ }
196
+ if (skipDns) {
197
+ setStatus((s) => ({
198
+ ...s,
199
+ dnsConfig: "skipped",
200
+ helmUpgradeTls: "skipped",
201
+ certCheck: "skipped",
202
+ }));
203
+ const productVersion = getConfigProductVersion(cfg);
204
+ await updateDeploymentStatus(name, "waiting-dns", {
253
205
  application: {
254
- appVersion: cfg.appVersion || "latest",
255
- hpsVersion: cfg.hpsVersion || cfg.appVersion || "latest",
206
+ version: productVersion,
256
207
  chartVersion: version || "latest",
257
208
  namespace,
258
209
  url: `https://${cfg.domain}`,
@@ -260,65 +211,28 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
260
211
  });
261
212
  setStep("complete");
262
213
  setTimeout(() => exit(), 5000);
214
+ return;
263
215
  }
264
- else {
265
- // TWO-PHASE DEPLOYMENT (Manual DNS)
266
- // Phase 1: Install without TLS
267
- await generateHelmValues(cfg, { tlsEnabled: false });
268
- await installOrUpgradeChart(name, {
269
- releaseName,
270
- namespace,
271
- version,
272
- wait: true,
273
- });
274
- setStatus((s) => ({ ...s, helmInstall: "success" }));
275
- // If skipping DNS, go straight to complete
276
- if (skipDns) {
277
- setStatus((s) => ({
278
- ...s,
279
- dnsConfig: "skipped",
280
- helmUpgradeTls: "skipped",
281
- certCheck: "skipped",
282
- }));
283
- await updateDeploymentStatus(name, "waiting-dns", {
284
- application: {
285
- appVersion: cfg.appVersion || "latest",
286
- hpsVersion: cfg.hpsVersion || cfg.appVersion || "latest",
287
- chartVersion: version || "latest",
288
- namespace,
289
- url: `https://${cfg.domain}`,
290
- },
291
- });
292
- setStep("complete");
293
- setTimeout(() => exit(), 5000);
294
- return;
295
- }
296
- // Update state to waiting for DNS
297
- await updateDeploymentStatus(name, "waiting-dns");
298
- // Phase 2: DNS configuration wait
299
- setStep("dns-wait");
300
- setStatus((s) => ({ ...s, dnsConfig: "running" }));
301
- }
216
+ await updateDeploymentStatus(name, "waiting-dns");
217
+ setStep("dns-wait");
218
+ markRunning("dnsConfig");
302
219
  }
303
220
  catch (err) {
304
- const message = err instanceof Error ? err.message : "Unknown error";
305
- setError(message);
306
- await updateDeploymentStatus(name, "failed");
307
- // If we started infrastructure provisioning but failed, offer cleanup
308
- if (infraStartedRef.current) {
309
- setStep("cleanup-prompt");
310
- }
311
- else {
312
- setStep("error");
313
- }
221
+ await failDeployment(err, "Unknown error");
222
+ }
223
+ }
224
+ // Guardrail: validate the values we're about to install against the chart's
225
+ // bundled schema. Catches reused/hand-edited values too (regenerateValues=false).
226
+ async function ensureGeneratedValuesValid() {
227
+ const values = await loadHelmValues(name);
228
+ if (values) {
229
+ assertValidHelmValues(values);
314
230
  }
315
231
  }
316
232
  async function runPreflightChecks(cfg) {
317
- // Check required tools
318
- const [helm, kubectl, terraform] = await Promise.all([
233
+ const [helm, kubectl] = await Promise.all([
319
234
  isHelmInstalled(),
320
235
  isKubectlInstalled(),
321
- isTerraformInstalled(),
322
236
  ]);
323
237
  if (!helm) {
324
238
  throw new Error("Helm is not installed. Please install Helm first.");
@@ -326,141 +240,135 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
326
240
  if (!kubectl) {
327
241
  throw new Error("kubectl is not installed. Please install kubectl first.");
328
242
  }
329
- if (cfg.infrastructure.mode === "provision" && !terraform) {
330
- throw new Error("Terraform is not installed. Required for infrastructure provisioning.");
331
- }
332
- // Check GCP Application Default Credentials if provisioning GCP infrastructure
333
- if (cfg.infrastructure.mode === "provision" &&
334
- cfg.infrastructure.provider === "gcp") {
335
- const adcCheck = await checkGcpApplicationDefaultCredentials();
336
- if (!adcCheck.configured) {
337
- throw new Error("GCP Application Default Credentials (ADC) not configured.\n\n" +
338
- "Terraform requires ADC to authenticate with Google Cloud.\n\n" +
339
- "To fix this:\n" +
340
- " • Run: gcloud auth login\n" +
341
- " • Run: gcloud auth application-default login\n" +
342
- " • Verify: gcloud auth application-default print-access-token\n\n" +
343
- "For more information: https://cloud.google.com/docs/authentication/application-default-credentials");
344
- }
345
- }
346
- // Check Azure prerequisites if provisioning Azure infrastructure
347
- if (cfg.infrastructure.mode === "provision" &&
348
- cfg.infrastructure.provider === "azure") {
349
- // 1. Verify resource providers are registered
350
- const providerCheck = await checkAzureResourceProviders();
351
- if (!providerCheck.allRegistered) {
352
- throw new Error(`Azure resource providers not registered: ${providerCheck.missing.join(", ")}\n\n` +
353
- "To register:\n" +
354
- providerCheck.missing
355
- .map((p) => ` • az provider register --namespace ${p}`)
356
- .join("\n") +
357
- "\n\nNote: Registration may take a few minutes to complete.");
358
- }
359
- // 2. Check VM quota for the selected tier
360
- const tier = cfg.tier || "small";
361
- const region = cfg.infrastructure.region;
362
- if (!region) {
363
- throw new Error("Azure region is required for infrastructure provisioning");
364
- }
365
- const requiredCores = AZURE_TIER_CORES[tier] || AZURE_TIER_CORES.small;
366
- const quotaCheck = await checkAzureVmQuota(region, requiredCores);
367
- if (!quotaCheck.sufficient) {
368
- throw new Error(`Insufficient Azure vCPU quota in ${region}.\n` +
369
- `Required: ${requiredCores} cores (${tier} tier), Available: ${quotaCheck.available}/${quotaCheck.limit}\n\n` +
370
- "Request a quota increase in the Azure portal:\n" +
371
- " • Go to: Subscriptions > Usage + quotas\n" +
372
- " • Request increase for 'Total Regional vCPUs' in your region");
243
+ let clusterError = await checkClusterAccessible();
244
+ if (clusterError &&
245
+ cfg.infrastructure.provider &&
246
+ cfg.infrastructure.region &&
247
+ cfg.infrastructure.clusterName) {
248
+ try {
249
+ setStep("kubeconfig");
250
+ setStatus((s) => ({
251
+ ...s,
252
+ preflight: "success",
253
+ kubeconfig: "running",
254
+ }));
255
+ await updateKubeconfig(cfg.infrastructure.provider, cfg.infrastructure.clusterName, cfg.infrastructure.region, {
256
+ gcpProjectId: cfg.infrastructure.gcpProjectId,
257
+ azureResourceGroup: cfg.infrastructure.azureResourceGroup,
258
+ });
259
+ clusterError = await checkClusterAccessible();
260
+ if (!clusterError) {
261
+ markSuccess("kubeconfig");
262
+ }
373
263
  }
374
- }
375
- // Check cluster access if using existing infrastructure
376
- if (cfg.infrastructure.mode === "existing") {
377
- let clusterError = await checkClusterAccessible();
378
- // If cluster not accessible but we have provider details, try updating kubeconfig
379
- if (clusterError &&
380
- cfg.infrastructure.provider &&
381
- cfg.infrastructure.region &&
382
- cfg.infrastructure.clusterName) {
383
- try {
384
- // Show visual feedback for kubeconfig update
385
- setStep("kubeconfig");
386
- setStatus((s) => ({
387
- ...s,
388
- preflight: "success",
389
- kubeconfig: "running",
390
- }));
391
- await updateKubeconfig(cfg.infrastructure.provider, cfg.infrastructure.clusterName, cfg.infrastructure.region, {
392
- gcpProjectId: cfg.infrastructure.gcpProjectId,
393
- azureResourceGroup: cfg.infrastructure.azureResourceGroup,
394
- });
395
- // Retry cluster access check
264
+ catch (kubeconfigError) {
265
+ if (kubeconfigError instanceof CommandDeniedError) {
396
266
  clusterError = await checkClusterAccessible();
397
267
  if (!clusterError) {
398
- setStatus((s) => ({ ...s, kubeconfig: "success" }));
268
+ markSuccess("kubeconfig");
269
+ return;
399
270
  }
400
271
  }
401
- catch (kubeconfigError) {
402
- // Kubeconfig update failed, include both errors
403
- const kubeconfigMsg = kubeconfigError instanceof Error
404
- ? kubeconfigError.message
405
- : "Unknown error";
406
- throw new Error(`Cannot access Kubernetes cluster and kubeconfig update failed:\n` +
407
- `Cluster error: ${clusterError}\n` +
408
- `Kubeconfig update error: ${kubeconfigMsg}`);
409
- }
272
+ const kubeconfigMsg = kubeconfigError instanceof Error
273
+ ? kubeconfigError.message
274
+ : "Unknown error";
275
+ throw new Error(`Cannot access Kubernetes cluster and kubeconfig refresh failed:\n` +
276
+ `Cluster error: ${clusterError}\n` +
277
+ `Kubeconfig error: ${kubeconfigMsg}`);
410
278
  }
411
- if (clusterError) {
412
- // Provide helpful message based on whether provider details are missing
413
- if (!cfg.infrastructure.provider ||
414
- !cfg.infrastructure.region ||
415
- !cfg.infrastructure.clusterName) {
416
- throw new Error(`Cannot access Kubernetes cluster:\n${clusterError}\n\n` +
417
- `Tip: Re-run 'rulebricks init' and provide your cloud provider, region, and cluster name ` +
418
- `to enable automatic kubeconfig updates.`);
419
- }
420
- throw new Error(`Cannot access Kubernetes cluster:\n${clusterError}`);
279
+ }
280
+ if (clusterError) {
281
+ throw new Error(`Cannot access Kubernetes cluster:\n${clusterError}`);
282
+ }
283
+ setStatus((s) => ({
284
+ ...s,
285
+ kubeconfig: s.kubeconfig === "success" ? "success" : "skipped",
286
+ }));
287
+ // External AWS Aurora needs logical replication for Supabase Realtime - a
288
+ // static cluster parameter bootstrap.sql can't set - so catch it here before
289
+ // a long deploy ends in a Realtime crashloop. Fail-open: the check returns
290
+ // "unknown" (and we proceed) on any ambiguity; we only block when the
291
+ // parameter is definitively off.
292
+ const pg = cfg.externalServices?.postgres;
293
+ if (pg?.mode === "external" &&
294
+ pg.external?.provider === "aws" &&
295
+ pg.external.host) {
296
+ const lr = await checkAuroraLogicalReplication(pg.external.host, cfg.infrastructure.region);
297
+ if (lr.status === "disabled") {
298
+ const pgName = lr.parameterGroup ?? "<db-cluster-parameter-group>";
299
+ throw new Error("External Aurora Postgres has logical replication DISABLED" +
300
+ (lr.parameterGroup ? ` (parameter group ${lr.parameterGroup})` : "") +
301
+ ". Supabase Realtime requires it, and rds.logical_replication is a " +
302
+ "static parameter the chart's bootstrap cannot set. Enable it, then " +
303
+ "reboot the writer, before deploying:\n" +
304
+ ` aws rds modify-db-cluster-parameter-group --db-cluster-parameter-group-name ${pgName} \\\n` +
305
+ ' --parameters "ParameterName=rds.logical_replication,ParameterValue=1,ApplyMethod=pending-reboot"\n' +
306
+ " aws rds reboot-db-instance --db-instance-identifier <writer-instance>\n" +
307
+ "(If the cluster uses a default parameter group, create a custom one first and attach it.)");
421
308
  }
422
309
  }
423
310
  }
424
- // Cleanup prompt screen
425
- if (step === "cleanup-prompt") {
426
- return (_jsx(BorderBox, { title: "Deployment Failed", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.error, bold: true, children: "\u2717 Infrastructure provisioning failed" }), _jsx(Text, { color: colors.error, children: error }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.warning, bold: true, children: "Partial infrastructure may have been created." }), _jsx(Text, { color: colors.muted, children: "Would you like to clean up to avoid orphaned resources?" })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, bold: true, children: "[Y]" }), _jsxs(Text, { color: colors.muted, children: [" ", "Yes, destroy partial infrastructure"] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.accent, bold: true, children: "[N]" }), _jsxs(Text, { color: colors.muted, children: [" ", "No, keep for debugging (you can run `rulebricks destroy --cluster` later)"] })] })] }) }));
311
+ async function verifyCertificates(namespace) {
312
+ try {
313
+ await waitForCertificatesReady(namespace);
314
+ markSuccess("certCheck");
315
+ }
316
+ catch {
317
+ setStatus((s) => ({ ...s, certCheck: "error" }));
318
+ setTlsWarning("TLS certificates are still being issued. HTTPS may not be available yet.");
319
+ }
427
320
  }
428
- // Cleanup running screen
429
- if (step === "cleanup-running") {
430
- return (_jsx(BorderBox, { title: "Cleaning Up", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Spinner, { label: "Destroying partial infrastructure..." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, children: "This may take several minutes..." }) })] }) }));
321
+ async function markRunningState(cfg, namespace) {
322
+ const productVersion = getConfigProductVersion(cfg);
323
+ await updateDeploymentStatus(name, "running", {
324
+ application: {
325
+ version: productVersion,
326
+ chartVersion: version || "latest",
327
+ namespace,
328
+ url: `https://${cfg.domain}`,
329
+ },
330
+ });
431
331
  }
432
- // Cleanup complete screen
433
- if (step === "cleanup-complete") {
434
- return (_jsx(BorderBox, { title: "Cleanup Complete", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [cleanupError ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.warning, bold: true, children: "\u26A0 Cleanup encountered issues" }), _jsx(Text, { color: colors.warning, children: cleanupError }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, children: ["Some resources may remain. Run `rulebricks destroy ", name, " ", "--cluster` to retry."] }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.success, bold: true, children: "\u2713 Infrastructure cleaned up successfully" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, children: "All partial resources have been destroyed. You can try deploying again." }) })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Press Esc to exit" }) })] }) }));
332
+ async function failDeployment(err, fallback) {
333
+ const message = err instanceof Error ? err.message : fallback;
334
+ setError(message);
335
+ setStep("error");
336
+ setStatus((s) => ({
337
+ ...s,
338
+ preflight: step === "preflight" ? "error" : s.preflight,
339
+ federation: step === "federation" ? "error" : s.federation,
340
+ helmInstall: step === "helm-install" ? "error" : s.helmInstall,
341
+ helmUpgradeTls: step === "helm-upgrade-tls" ? "error" : s.helmUpgradeTls,
342
+ }));
343
+ await updateDeploymentStatus(name, "failed");
435
344
  }
436
- // Error screen (non-infra failures or when user skips cleanup)
437
345
  if (step === "error") {
438
- // Format error message, preserving newlines for multi-line errors
439
346
  const errorLines = error?.split("\n") || ["Unknown error"];
440
- return (_jsx(BorderBox, { title: "Deployment Failed", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.error, bold: true, children: "\u2717 Error" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: errorLines.map((line, i) => (_jsx(Text, { color: line.startsWith(" •") ? colors.muted : colors.error, children: line }, i))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Press Esc to exit" }) })] }) }));
347
+ return (_jsx(BorderBox, { title: "Deployment Failed", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.error, bold: true, children: "\u2717 Error" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: errorLines.map((line, i) => (_jsx(Text, { color: line.startsWith(" •") ? colors.muted : colors.error, children: line }, i))) })] }) }));
441
348
  }
442
- // DNS wait screen (only for manual DNS flow)
443
349
  if (step === "dns-wait" && config) {
444
- return (_jsx(DNSWaitScreen, { domain: config.domain, selfHostedSupabase: config.database.type === "self-hosted", namespace: getNamespace(config.name), onComplete: handleDnsComplete, onSkip: handleDnsSkip }));
350
+ return (_jsx(DNSWaitScreen, { domain: config.domain, selfHostedSupabase: config.database.type === "self-hosted", builtInObservability: config.features.observability?.clickstack?.enabled ?? true, valkeyAdminIngress: config.features.cache?.valkeyAdmin?.enabled === true &&
351
+ config.features.cache.valkeyAdmin.exposure === "ingress", valkeyAdminHostname: config.features.cache?.valkeyAdmin?.hostname, namespace: getNamespace(config.name), onComplete: handleDnsComplete, onSkip: handleDnsSkip }));
445
352
  }
446
- // Complete screen
447
353
  if (step === "complete") {
448
- const tlsSkipped = status.helmUpgradeTls === "skipped" && !useExternalDns;
449
- return (_jsx(BorderBox, { title: "Deployment Complete", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.success, bold: true, children: "\u2713 Rulebricks deployed successfully!" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: ["URL:", " ", _jsxs(Text, { color: colors.accent, children: ["https://", config?.domain, "/auth/signup"] })] }), useExternalDns && (_jsx(Text, { color: colors.muted, children: "DNS records will be created automatically by external-dns" })), tlsSkipped && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.warning, children: ["\u26A0 TLS not configured. Run `rulebricks deploy ", name, "` again after DNS setup."] }) })), tlsWarning && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.warning, children: ["\u26A0 ", tlsWarning] }) }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Next steps:" }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Visit the URL to complete initial setup"] }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Run `rulebricks status ", name, "` to check deployment health"] }), tlsSkipped && (_jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Configure DNS and re-run deploy for TLS"] })), tlsWarning && (_jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Run `rulebricks status ", name, "` to check TLS certificate status"] }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, dimColor: true, children: "Tip: If the URL isn't accessible yet, your local DNS may need time to propagate." }), _jsxs(Text, { color: colors.muted, dimColor: true, children: ["Flush DNS cache: ", getDnsFlushCommand()] })] })] }) }));
354
+ const tlsSkipped = status.helmUpgradeTls === "skipped" &&
355
+ !useExternalDns &&
356
+ !assumeDnsConfigured;
357
+ return (_jsx(BorderBox, { title: "Deployment Complete", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.success, bold: true, children: "\u2713 Rulebricks deployed successfully!" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: ["URL:", " ", _jsxs(Text, { color: colors.accent, children: ["https://", config?.domain, "/auth/signup"] })] }), useExternalDns && (_jsx(Text, { color: colors.muted, children: "DNS records will be created automatically by external-dns" })), tlsSkipped && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.warning, children: ["\u26A0 TLS not configured. Run `rulebricks deploy ", name, "` again after DNS setup."] }) })), tlsWarning && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.warning, children: ["\u26A0 ", tlsWarning] }) })), federationWarning && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.warning, children: ["\u26A0 ", federationWarning] }) }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Next steps:" }), _jsx(Text, { color: colors.muted, children: " \u2022 Visit the URL to complete setup" }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Run `rulebricks status ", name, "` to check deployment health"] }), tlsSkipped && (_jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Configure DNS and re-run deploy for TLS"] }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, dimColor: true, children: "Tip: If the URL isn't accessible yet, your local DNS may need time to propagate." }), _jsxs(Text, { color: colors.muted, dimColor: true, children: ["Flush DNS cache: ", getDnsFlushCommand()] })] })] }) }));
450
358
  }
451
- // Progress screen
452
359
  const helmInstallLabel = useExternalDns
453
360
  ? "Helm chart installation (with TLS)"
454
361
  : "Helm chart installation";
455
- return (_jsx(BorderBox, { title: `Deploying ${name}`, children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(StatusLine, { status: status.preflight, label: "Preflight checks" }), _jsx(StatusLine, { status: status.infrastructure, label: "Infrastructure provisioning", detail: step === "infra-setup"
456
- ? "Setting up workspace"
457
- : step === "infra-init"
458
- ? "Initializing Terraform"
459
- : step === "infra-plan"
460
- ? "Planning changes"
461
- : step === "infra-apply"
462
- ? "Applying infrastructure"
463
- : undefined }), _jsx(StatusLine, { status: status.kubeconfig, label: "Kubernetes configuration" }), _jsx(StatusLine, { status: status.helmInstall, label: helmInstallLabel }), !useExternalDns && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: status.dnsConfig, label: "DNS configuration" }), _jsx(StatusLine, { status: status.helmUpgradeTls, label: "TLS configuration" })] })), _jsx(StatusLine, { status: status.certCheck, label: "TLS certificate verification" }), step !== "dns-wait" && (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: getStepLabel(step, useExternalDns) }) }))] }) }));
362
+ // The federation step does the cloud-appropriate per-namespace identity wiring;
363
+ // label it for the cluster's cloud so it's clear what's happening.
364
+ const federationLabel = config?.infrastructure.provider === "aws"
365
+ ? "EKS Pod Identity associations"
366
+ : config?.infrastructure.provider === "gcp"
367
+ ? "Workload Identity bindings"
368
+ : config?.infrastructure.provider === "azure"
369
+ ? "Azure federated identity credentials"
370
+ : "Workload identity setup";
371
+ return (_jsx(BorderBox, { title: `Deploying ${name}`, children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(StatusLine, { status: status.preflight, label: "Preflight checks" }), _jsx(StatusLine, { status: status.kubeconfig, label: "Kubernetes configuration" }), _jsx(StatusLine, { status: status.federation, label: federationLabel }), federationWarning && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.warning, children: federationWarning }) })), _jsx(StatusLine, { status: status.helmInstall, label: helmInstallLabel }), !useExternalDns && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: status.dnsConfig, label: "DNS configuration" }), _jsx(StatusLine, { status: status.helmUpgradeTls, label: "TLS configuration" })] })), _jsx(StatusLine, { status: status.certCheck, label: "TLS certificate verification" }), _jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: getStepLabel(step, useExternalDns) }) })] }) }));
464
372
  }
465
373
  function getDnsFlushCommand() {
466
374
  switch (platform()) {
@@ -478,16 +386,8 @@ function getStepLabel(step, useExternalDns) {
478
386
  return "Loading configuration...";
479
387
  case "preflight":
480
388
  return "Running preflight checks...";
481
- case "infra-setup":
482
- return "Setting up Terraform workspace...";
483
- case "infra-init":
484
- return "Initializing Terraform...";
485
- case "infra-plan":
486
- return "Planning infrastructure changes...";
487
- case "infra-apply":
488
- return "Creating infrastructure (may take up to 15 minutes)...";
489
389
  case "kubeconfig":
490
- return "Updating kubeconfig...";
390
+ return "Refreshing kubeconfig...";
491
391
  case "helm-install":
492
392
  return useExternalDns
493
393
  ? "Installing Helm chart with TLS..."
@@ -503,5 +403,5 @@ function getStepLabel(step, useExternalDns) {
503
403
  }
504
404
  }
505
405
  export function DeployCommand(props) {
506
- return (_jsxs(ThemeProvider, { theme: "deploy", children: [_jsx(Logo, {}), _jsx(DeployCommandInner, { ...props })] }));
406
+ return (_jsxs(ThemeProvider, { theme: "deploy", children: [_jsx(Logo, {}), _jsx(CommandApprovalProvider, { children: _jsx(DeployCommandInner, { ...props }) })] }));
507
407
  }