@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,40 +1,33 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from "react";
3
- import { Box, Text, useInput } from "ink";
3
+ import { Box, Text } from "ink";
4
4
  import SelectInput from "ink-select-input";
5
5
  import TextInput from "ink-text-input";
6
6
  import { useWizard } from "../WizardContext.js";
7
- import { BorderBox, useTheme } from "../../common/index.js";
7
+ import { BorderBox, useGatedInput, useTheme } from "../../common/index.js";
8
8
  import { Spinner } from "../../common/Spinner.js";
9
- import { CLOUD_REGIONS } from "../../../types/index.js";
10
- import { checkAllCloudClis, checkTerraform, listRegions, listClusters, getGcpProjectId, CLI_INSTALL_URLS, CLI_LOGIN_COMMANDS, TERRAFORM_INSTALL_INFO, } from "../../../lib/cloudCli.js";
9
+ import { CLOUD_PROVIDER_NAMES, CLOUD_REGIONS, } from "../../../types/index.js";
10
+ import { checkAllCloudClis, discoverClustersInRegion, listRegions, getGcpProjectId, updateKubeconfig, CLI_INSTALL_URLS, CLI_LOGIN_COMMANDS, } from "../../../lib/cloudCli.js";
11
11
  export function CloudProviderStep({ onComplete, onBack, }) {
12
12
  const { state, dispatch } = useWizard();
13
13
  const { colors } = useTheme();
14
14
  const [subStep, setSubStep] = useState("checking");
15
15
  const [cliStatus, setCliStatus] = useState(null);
16
- const [terraformStatus, setTerraformStatus] = useState(null);
17
16
  const [clusterName, setClusterName] = useState(state.clusterName || "rulebricks-cluster");
18
- const [gcpProject, setGcpProject] = useState(state.gcpProjectId || "");
19
- const [azureRg, setAzureRg] = useState(state.azureResourceGroup || "");
20
- const [regions, setRegions] = useState([]);
21
- const [regionsLoading, setRegionsLoading] = useState(false);
22
17
  const [clusters, setClusters] = useState([]);
23
- const [clustersLoading, setClustersLoading] = useState(false);
24
- // Whether we need infrastructure provisioning (Terraform required)
25
- const needsTerraform = state.infrastructureMode === "provision";
18
+ const [selectedProvider, setSelectedProvider] = useState(state.provider);
19
+ // Manual-entry path (no clusters discovered): region and, on Azure, the
20
+ // resource group still need to be captured so downstream steps (kubeconfig,
21
+ // storage/monitoring discovery) aren't left blind.
22
+ const [manualRegion, setManualRegion] = useState(state.region || "");
23
+ const [manualRegions, setManualRegions] = useState([]);
24
+ const [manualResourceGroup, setManualResourceGroup] = useState(state.azureResourceGroup || "");
25
+ const [rgError, setRgError] = useState(null);
26
26
  // Check CLIs on mount
27
27
  useEffect(() => {
28
28
  async function checkClis() {
29
- // Check cloud CLIs and Terraform in parallel
30
- const [status, tfStatus] = await Promise.all([
31
- checkAllCloudClis(),
32
- needsTerraform
33
- ? checkTerraform()
34
- : Promise.resolve({ installed: true }),
35
- ]);
29
+ const status = await checkAllCloudClis();
36
30
  setCliStatus(status);
37
- setTerraformStatus(tfStatus);
38
31
  if (!status.anyInstalled) {
39
32
  setSubStep("no-cli");
40
33
  }
@@ -43,8 +36,8 @@ export function CloudProviderStep({ onComplete, onBack, }) {
43
36
  }
44
37
  }
45
38
  checkClis();
46
- }, [needsTerraform]);
47
- useInput((input, key) => {
39
+ }, []);
40
+ useGatedInput((input, key) => {
48
41
  if (key.escape) {
49
42
  if (subStep === "provider" ||
50
43
  subStep === "no-cli" ||
@@ -54,17 +47,20 @@ export function CloudProviderStep({ onComplete, onBack, }) {
54
47
  else if (subStep === "region" || subStep === "region-loading") {
55
48
  setSubStep("provider");
56
49
  }
50
+ else if (subStep === "manual-region" ||
51
+ subStep === "manual-region-loading") {
52
+ setSubStep("cluster");
53
+ }
54
+ else if (subStep === "manual-rg") {
55
+ setRgError(null);
56
+ setSubStep("manual-region");
57
+ }
57
58
  else if (subStep === "cluster" ||
58
59
  subStep === "cluster-loading" ||
59
- subStep === "cluster-select") {
60
+ subStep === "cluster-select" ||
61
+ subStep === "kubeconfig-loading") {
60
62
  setSubStep("region");
61
63
  }
62
- else if (subStep === "gcp-project") {
63
- setSubStep("provider");
64
- }
65
- else if (subStep === "azure-rg") {
66
- setSubStep("provider");
67
- }
68
64
  }
69
65
  });
70
66
  // Build provider items with status
@@ -78,74 +74,37 @@ export function CloudProviderStep({ onComplete, onBack, }) {
78
74
  ];
79
75
  return providers.map((p) => ({
80
76
  ...p,
81
- disabled: !p.status.installed || !p.status.authenticated,
77
+ disabled: !p.status.authenticated,
82
78
  }));
83
79
  };
84
80
  const providerItems = getProviderItems();
85
- const handleProviderSelect = async (item) => {
81
+ const handleProviderSelect = (item) => {
86
82
  const selectedItem = providerItems.find((p) => p.value === item.value);
87
83
  if (!selectedItem || selectedItem.disabled)
88
84
  return;
89
85
  const provider = item.value;
86
+ setSelectedProvider(provider);
87
+ setClusterName("rulebricks-cluster");
88
+ setManualRegion("");
89
+ setManualResourceGroup("");
90
90
  dispatch({ type: "SET_PROVIDER", provider });
91
- if (provider === "gcp") {
92
- // GCP requires project to be pre-configured, so use the detected project
93
- const detectedProject = await getGcpProjectId();
94
- if (detectedProject) {
95
- dispatch({ type: "SET_GCP_PROJECT", projectId: detectedProject });
96
- // Skip project input and go directly to regions
97
- loadRegions(provider);
98
- }
99
- else {
100
- // Fallback to project input if somehow not detected (shouldn't happen with new auth check)
101
- setSubStep("gcp-project");
102
- }
103
- }
104
- else if (provider === "azure") {
105
- setSubStep("azure-rg");
106
- }
107
- else {
108
- loadRegions(provider);
109
- }
91
+ loadProviderRegions(provider);
110
92
  };
111
- const loadRegions = async (provider) => {
93
+ const loadProviderRegions = async (provider) => {
112
94
  setSubStep("region-loading");
113
- setRegionsLoading(true);
114
95
  try {
115
- const dynamicRegions = await listRegions(provider);
116
- if (dynamicRegions.length > 0) {
117
- setRegions(dynamicRegions);
118
- }
119
- else {
120
- // Fall back to static regions
121
- setRegions(CLOUD_REGIONS[provider]);
122
- }
96
+ const regions = await listRegions(provider);
97
+ setManualRegions(regions.length > 0 ? regions : CLOUD_REGIONS[provider]);
123
98
  }
124
99
  catch {
125
- // Fall back to static regions on error
126
- setRegions(CLOUD_REGIONS[provider]);
100
+ setManualRegions(CLOUD_REGIONS[provider]);
127
101
  }
128
- setRegionsLoading(false);
129
102
  setSubStep("region");
130
103
  };
131
- const handleRegionSelect = (item) => {
132
- dispatch({ type: "SET_REGION", region: item.value });
133
- // For existing infrastructure, load available clusters
134
- if (state.infrastructureMode === "existing" && state.provider) {
135
- loadClusters(state.provider, item.value);
136
- }
137
- else {
138
- // For provisioning, go directly to cluster name input
139
- setSubStep("cluster");
140
- }
141
- };
142
104
  const loadClusters = async (provider, region) => {
143
105
  setSubStep("cluster-loading");
144
- setClustersLoading(true);
145
106
  try {
146
- const availableClusters = await listClusters(provider, region, {
147
- azureResourceGroup: state.azureResourceGroup || undefined,
148
- });
107
+ const availableClusters = await discoverClustersInRegion(provider, region);
149
108
  setClusters(availableClusters);
150
109
  if (availableClusters.length > 0) {
151
110
  setSubStep("cluster-select");
@@ -160,27 +119,130 @@ export function CloudProviderStep({ onComplete, onBack, }) {
160
119
  setClusters([]);
161
120
  setSubStep("cluster");
162
121
  }
163
- setClustersLoading(false);
164
122
  };
165
- const handleClusterSelect = (item) => {
166
- setClusterName(item.value);
167
- dispatch({ type: "SET_CLUSTER_NAME", clusterName: item.value });
123
+ const completeWithCluster = async (cluster) => {
124
+ dispatch({ type: "SET_REGION", region: cluster.region });
125
+ dispatch({ type: "SET_CLUSTER_NAME", clusterName: cluster.name });
126
+ dispatch({ type: "SET_AZURE_RG", resourceGroup: cluster.resourceGroup || "" });
127
+ dispatch({ type: "SET_GCP_PROJECT", projectId: cluster.projectId || "" });
128
+ if (cluster.provider && cluster.region) {
129
+ setSubStep("kubeconfig-loading");
130
+ try {
131
+ await updateKubeconfig(cluster.provider, cluster.name, cluster.region, {
132
+ gcpProjectId: cluster.projectId,
133
+ azureResourceGroup: cluster.resourceGroup,
134
+ });
135
+ }
136
+ catch {
137
+ // The next step performs a direct kubectl scan and will show a concrete
138
+ // access error if kubeconfig still points at the wrong cluster.
139
+ }
140
+ }
168
141
  onComplete();
169
142
  };
143
+ const handleClusterSelect = (item) => {
144
+ const cluster = clusters.find((c) => getClusterKey(c) === item.value);
145
+ if (!cluster)
146
+ return;
147
+ setClusterName(cluster.name);
148
+ completeWithCluster(cluster);
149
+ };
170
150
  const handleClusterSubmit = () => {
171
151
  dispatch({ type: "SET_CLUSTER_NAME", clusterName });
172
- onComplete();
152
+ const provider = selectedProvider || state.provider;
153
+ if (!manualRegion) {
154
+ loadManualRegions();
155
+ return;
156
+ }
157
+ if (provider === "azure" && !manualResourceGroup.trim()) {
158
+ setSubStep("manual-rg");
159
+ return;
160
+ }
161
+ completeManual(manualRegion, manualResourceGroup.trim());
162
+ };
163
+ // Manual path: the cluster wasn't discovered, so the region (and Azure
164
+ // resource group) are collected explicitly before refreshing kubeconfig
165
+ // otherwise the cluster capability scan runs against whatever kubectl happens
166
+ // to point at and storage/monitoring discovery start without a region.
167
+ const loadManualRegions = async () => {
168
+ const provider = selectedProvider || state.provider;
169
+ if (!provider) {
170
+ onComplete();
171
+ return;
172
+ }
173
+ setSubStep("manual-region-loading");
174
+ try {
175
+ const regions = await listRegions(provider);
176
+ setManualRegions(regions.length > 0 ? regions : CLOUD_REGIONS[provider]);
177
+ }
178
+ catch {
179
+ setManualRegions(CLOUD_REGIONS[provider]);
180
+ }
181
+ setSubStep("manual-region");
182
+ };
183
+ const handleManualRegionSelect = (item) => {
184
+ setManualRegion(item.value);
185
+ dispatch({ type: "SET_REGION", region: item.value });
186
+ const provider = selectedProvider || state.provider;
187
+ if (provider === "azure") {
188
+ setSubStep("manual-rg");
189
+ }
190
+ else {
191
+ completeManual(item.value, "");
192
+ }
173
193
  };
174
- const handleGcpProjectSubmit = async () => {
175
- dispatch({ type: "SET_GCP_PROJECT", projectId: gcpProject });
176
- // ADC is now checked upfront in checkGcloudCli(), so proceed directly to regions
177
- loadRegions("gcp");
194
+ const handleRegionSelect = (item) => {
195
+ const provider = selectedProvider || state.provider;
196
+ if (!provider)
197
+ return;
198
+ setManualRegion(item.value);
199
+ dispatch({ type: "SET_REGION", region: item.value });
200
+ loadClusters(provider, item.value);
201
+ };
202
+ const handleManualResourceGroupSubmit = () => {
203
+ if (!manualResourceGroup.trim()) {
204
+ setRgError("Resource group is required for AKS clusters");
205
+ return;
206
+ }
207
+ setRgError(null);
208
+ dispatch({
209
+ type: "SET_AZURE_RG",
210
+ resourceGroup: manualResourceGroup.trim(),
211
+ });
212
+ completeManual(manualRegion, manualResourceGroup.trim());
178
213
  };
179
- const handleAzureRgSubmit = () => {
180
- dispatch({ type: "SET_AZURE_RG", resourceGroup: azureRg });
181
- loadRegions("azure");
214
+ const completeManual = async (region, resourceGroup) => {
215
+ const provider = selectedProvider || state.provider;
216
+ if (!provider) {
217
+ onComplete();
218
+ return;
219
+ }
220
+ setSubStep("kubeconfig-loading");
221
+ // GCP: derive the project from the active gcloud config instead of asking.
222
+ let gcpProjectId;
223
+ if (provider === "gcp") {
224
+ try {
225
+ gcpProjectId = (await getGcpProjectId()) || undefined;
226
+ }
227
+ catch {
228
+ gcpProjectId = undefined;
229
+ }
230
+ if (gcpProjectId) {
231
+ dispatch({ type: "SET_GCP_PROJECT", projectId: gcpProjectId });
232
+ }
233
+ }
234
+ try {
235
+ await updateKubeconfig(provider, clusterName, region, {
236
+ gcpProjectId,
237
+ azureResourceGroup: resourceGroup || undefined,
238
+ });
239
+ }
240
+ catch {
241
+ // The next step performs a direct kubectl scan and will show a concrete
242
+ // access error if kubeconfig still points at the wrong cluster.
243
+ }
244
+ onComplete();
182
245
  };
183
- const regionItems = regions.map((r) => ({ label: r, value: r }));
184
246
  // Render status indicator for a provider
185
247
  const renderStatusIndicator = (status) => {
186
248
  if (!status.installed) {
@@ -198,11 +260,15 @@ export function CloudProviderStep({ onComplete, onBack, }) {
198
260
  }
199
261
  return _jsx(Text, { color: "green", children: " \u2713" });
200
262
  };
201
- return (_jsxs(BorderBox, { title: "Cloud Provider", children: [subStep === "checking" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Spinner, { label: "Checking cloud CLI tools..." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Detecting AWS, GCP, and Azure CLIs..." }) })] })), subStep === "no-cli" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "red", bold: true, children: "No cloud CLI tools detected" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Text, { children: "To provision infrastructure, you need to install and authenticate with at least one cloud CLI:" }) }), _jsx(Box, { marginTop: 1, flexDirection: "column", marginLeft: 2, children: Object.entries(CLI_INSTALL_URLS).map(([provider, info]) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { bold: true, children: [info.name, ":"] }), _jsxs(Text, { color: "gray", children: [" ", info.installCmd] }), _jsxs(Text, { color: "gray", children: [" ", info.url] })] }, provider))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "After installing, authenticate:" }) }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: Object.entries(CLI_LOGIN_COMMANDS).map(([provider, cmd]) => (_jsx(Box, { flexDirection: "column", children: Array.isArray(cmd) ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "gray", children: [" ", provider, ":"] }), cmd.map((c, i) => (_jsxs(Text, { color: "gray", children: [" ", c] }, i)))] })) : (_jsxs(Text, { color: "gray", children: [" ", provider, ": ", cmd] })) }, provider))) })] })), subStep === "provider" && cliStatus && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select your cloud provider:" }), !cliStatus.anyAvailable && cliStatus.anyInstalled && (_jsx(Text, { color: "yellow", dimColor: true, children: "\u26A0 Some CLIs are installed but not authenticated" })), needsTerraform &&
202
- terraformStatus &&
203
- !terraformStatus.installed && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", bold: true, children: "\u26A0 Terraform not installed" }), _jsx(Text, { color: "gray", children: "You'll need Terraform to provision infrastructure." }), _jsxs(Text, { color: "gray", children: ["Install: ", TERRAFORM_INSTALL_INFO.installCmd] }), _jsx(Text, { color: "gray", dimColor: true, children: TERRAFORM_INSTALL_INFO.url })] })), needsTerraform && terraformStatus?.installed && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", "Terraform", " ", terraformStatus.version
204
- ? `v${terraformStatus.version}`
205
- : "", " ", "detected"] })] }))] }), _jsx(SelectInput, { items: providerItems.map((p) => ({
263
+ const clusterItems = clusters.map((cluster) => ({
264
+ label: formatClusterRow(cluster),
265
+ value: getClusterKey(cluster),
266
+ }));
267
+ const activeProvider = selectedProvider || state.provider;
268
+ const providerName = activeProvider
269
+ ? CLOUD_PROVIDER_NAMES[activeProvider]
270
+ : "";
271
+ return (_jsxs(BorderBox, { title: "Cloud Provider", children: [subStep === "checking" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Spinner, { label: "Checking cloud CLI tools..." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Detecting AWS, GCP, and Azure CLIs..." }) })] })), subStep === "no-cli" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "red", bold: true, children: "No cloud CLI tools detected" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Text, { children: "To discover clusters, install at least one cloud CLI:" }) }), _jsx(Box, { marginTop: 1, flexDirection: "column", marginLeft: 2, children: Object.entries(CLI_INSTALL_URLS).map(([provider, info]) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { bold: true, children: [info.name, ":"] }), _jsxs(Text, { color: "gray", children: [" ", info.installCmd] }), _jsxs(Text, { color: "gray", children: [" ", info.url] })] }, provider))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "After installing, authenticate:" }) }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: Object.entries(CLI_LOGIN_COMMANDS).map(([provider, cmd]) => (_jsx(Box, { flexDirection: "column", children: Array.isArray(cmd) ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "gray", children: [" ", provider, ":"] }), cmd.map((c, i) => (_jsxs(Text, { color: "gray", children: [" ", c] }, i)))] })) : (_jsxs(Text, { color: "gray", children: [" ", provider, ": ", cmd] })) }, provider))) })] })), subStep === "provider" && cliStatus && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select your cloud provider:" }), !cliStatus.anyAvailable && cliStatus.anyInstalled && (_jsx(Text, { color: "yellow", dimColor: true, children: "Some CLIs are installed but not authenticated" }))] }), _jsx(SelectInput, { items: providerItems.map((p) => ({
206
272
  label: p.label,
207
273
  value: p.value,
208
274
  })), onSelect: handleProviderSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => {
@@ -214,10 +280,34 @@ export function CloudProviderStep({ onComplete, onBack, }) {
214
280
  : isSelected
215
281
  ? colors.accent
216
282
  : undefined;
217
- return (_jsxs(Box, { children: [_jsxs(Text, { color: textColor, dimColor: item.disabled, children: [isSelected && !item.disabled ? " " : " ", label] }), renderStatusIndicator(item.status), item.status.identity && item.status.authenticated && (_jsxs(Text, { color: "gray", dimColor: true, children: [" ", "(", item.status.identity, ")"] }))] }));
218
- } })] })), subStep === "gcp-project" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your GCP Project ID:" }), gcpProject && (_jsxs(Text, { color: "gray", dimColor: true, children: ["Detected project: ", gcpProject] })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: gcpProject, onChange: setGcpProject, onSubmit: handleGcpProjectSubmit, placeholder: "my-gcp-project" })] })] })), subStep === "azure-rg" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Azure Resource Group name:" }), _jsx(Text, { color: "gray", dimColor: true, children: "This resource group will contain all Rulebricks resources" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: azureRg, onChange: setAzureRg, onSubmit: handleAzureRgSubmit, placeholder: "rulebricks-rg" })] })] })), subStep === "region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: `Fetching ${state.provider?.toUpperCase()} regions...` }) })), subStep === "region" && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { children: ["Select a region for ", state.provider?.toUpperCase(), ":"] }), _jsxs(Text, { color: "gray", dimColor: true, children: [regions.length, " regions available"] })] }), _jsx(Box, { height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: regionItems, onSelect: handleRegionSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? " " : " ", label] })) }) })] })), subStep === "cluster-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: `Fetching ${state.provider?.toUpperCase()} clusters in ${state.region}...` }) })), subStep === "cluster-select" && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select your Kubernetes cluster:" }), _jsxs(Text, { color: "gray", dimColor: true, children: [clusters.length, " cluster", clusters.length !== 1 ? "s" : "", " found in ", state.region] })] }), _jsx(Box, { height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: clusters.map((c) => ({ label: c, value: c })), onSelect: handleClusterSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? " " : " ", label] })) }) }), state.provider && state.region && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", state.provider?.toUpperCase(), " \u2022 ", state.region] })] }))] })), subStep === "cluster" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the Kubernetes cluster name:" }), _jsx(Text, { color: "gray", dimColor: true, children: state.infrastructureMode === "provision"
219
- ? "This cluster will be created"
220
- : clusters.length === 0 && state.infrastructureMode === "existing"
221
- ? "No clusters found in this region - enter the name manually"
222
- : "Enter the name of your existing cluster" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: clusterName, onChange: setClusterName, onSubmit: handleClusterSubmit, placeholder: "rulebricks-cluster" })] }), state.provider && state.region && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", state.provider?.toUpperCase(), " \u2022 ", state.region] })] }))] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
283
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: textColor, dimColor: item.disabled, children: [isSelected && !item.disabled ? "> " : " ", label] }), renderStatusIndicator(item.status), item.status.identity && item.status.authenticated && (_jsxs(Text, { color: "gray", dimColor: true, children: [" ", "(", item.status.identity, ")"] }))] }));
284
+ } })] })), subStep === "region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Loading available regions..." }) })), subStep === "region" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select the cluster's region:" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Rulebricks will only search ", providerName, " clusters in this region."] }), _jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: manualRegions.map((r) => ({ label: r, value: r })), onSelect: handleRegionSelect, limit: 8, initialIndex: Math.max(0, manualRegions.indexOf(manualRegion)), indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "> " : " ", label] })) }) })] })), subStep === "cluster-loading" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Spinner, { label: `Fetching ${providerName} clusters in ${manualRegion}...` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "This keeps discovery targeted to the selected region." }) })] })), subStep === "cluster-select" && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select your Kubernetes cluster:" }), _jsxs(Text, { color: "gray", dimColor: true, children: [clusters.length, " cluster", clusters.length !== 1 ? "s" : "", " found for ", providerName, " in ", manualRegion] })] }), _jsx(Text, { color: "gray", dimColor: true, children: ` ${formatClusterColumns("Name", "Location", "Details", "Nodes")}` }), _jsx(Box, { height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: clusterItems, onSelect: handleClusterSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "> " : " ", label] })) }) })] })), subStep === "cluster" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the Kubernetes cluster name:" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["No clusters were discovered for ", providerName, ". If kubectl already points at the cluster, you can enter its name manually."] }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Text, { color: "yellow", children: "Need a basic cluster? See cluster-setup/ for minimum Rulebricks examples." }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: " > " }), _jsx(TextInput, { value: clusterName, onChange: setClusterName, onSubmit: handleClusterSubmit, placeholder: "rulebricks-cluster" })] })] })), subStep === "manual-region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Loading available regions..." }) })), subStep === "manual-region" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select the cluster's region:" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Used to refresh kubeconfig and discover storage and monitoring resources for ", clusterName, "."] }), _jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: manualRegions.map((r) => ({ label: r, value: r })), onSelect: handleManualRegionSelect, limit: 8, initialIndex: Math.max(0, manualRegions.indexOf(manualRegion)), indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "> " : " ", label] })) }) })] })), subStep === "manual-rg" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the cluster's resource group:" }), _jsx(Text, { color: "gray", dimColor: true, children: "The Azure resource group containing the AKS cluster (needed for kubeconfig access)." }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: " > " }), _jsx(TextInput, { value: manualResourceGroup, onChange: setManualResourceGroup, onSubmit: handleManualResourceGroupSubmit, placeholder: "my-resource-group" })] }), rgError && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", rgError] }) }))] })), subStep === "kubeconfig-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Refreshing kubeconfig for selected cluster..." }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
285
+ }
286
+ function getClusterKey(cluster) {
287
+ return [
288
+ cluster.provider,
289
+ cluster.region,
290
+ cluster.resourceGroup || cluster.projectId || "",
291
+ cluster.name,
292
+ ].join(":");
293
+ }
294
+ function formatClusterRow(cluster) {
295
+ const details = cluster.resourceGroup || cluster.projectId || cluster.version || "-";
296
+ const nodes = cluster.nodeCount === undefined || cluster.nodeCount === null
297
+ ? "-"
298
+ : String(cluster.nodeCount);
299
+ return formatClusterColumns(cluster.name, cluster.region, details, nodes);
300
+ }
301
+ function formatClusterColumns(name, location, details, nodes) {
302
+ return [
303
+ fit(name, 26),
304
+ fit(location, 16),
305
+ fit(details, 24),
306
+ fit(nodes, 5),
307
+ ].join(" ");
308
+ }
309
+ function fit(value, width) {
310
+ const text = value || "-";
311
+ const clipped = text.length > width ? `${text.slice(0, Math.max(width - 1, 0))}~` : text;
312
+ return clipped.padEnd(width, " ");
223
313
  }
@@ -25,7 +25,6 @@ export function DomainStep({ onComplete, onBack }) {
25
25
  const [subStep, setSubStep] = useState('domain');
26
26
  const [domain, setDomain] = useState(state.domain || '');
27
27
  const [adminEmail, setAdminEmail] = useState(state.adminEmail || '');
28
- const [tlsEmail, setTlsEmail] = useState(state.tlsEmail || '');
29
28
  const [error, setError] = useState(null);
30
29
  useInput((input, key) => {
31
30
  if (key.escape) {
@@ -36,11 +35,8 @@ export function DomainStep({ onComplete, onBack }) {
36
35
  else if (subStep === 'admin-email') {
37
36
  setSubStep('domain');
38
37
  }
39
- else if (subStep === 'tls-email') {
40
- setSubStep('admin-email');
41
- }
42
38
  else if (subStep === 'dns-provider') {
43
- setSubStep('tls-email');
39
+ setSubStep('admin-email');
44
40
  }
45
41
  else if (subStep === 'dns-auto-manage') {
46
42
  setSubStep('dns-provider');
@@ -85,23 +81,8 @@ export function DomainStep({ onComplete, onBack }) {
85
81
  }
86
82
  setError(null);
87
83
  dispatch({ type: 'SET_ADMIN_EMAIL', email: adminEmail });
88
- // Default TLS email to admin email if not set
89
- if (!tlsEmail) {
90
- setTlsEmail(adminEmail);
91
- }
92
- setSubStep('tls-email');
93
- };
94
- const handleTlsEmailSubmit = () => {
95
- if (!tlsEmail) {
96
- setError('TLS email is required');
97
- return;
98
- }
99
- if (!isValidEmail(tlsEmail)) {
100
- setError('Invalid email format');
101
- return;
102
- }
103
- setError(null);
104
- dispatch({ type: 'SET_TLS_EMAIL', email: tlsEmail });
84
+ // The TLS (Let's Encrypt) email defaults to the admin email in toConfig;
85
+ // advanced users can override `tlsEmail` in config.yaml.
105
86
  setSubStep('dns-provider');
106
87
  };
107
88
  const handleDnsProviderSelect = (item) => {
@@ -121,6 +102,6 @@ export function DomainStep({ onComplete, onBack }) {
121
102
  onComplete();
122
103
  };
123
104
  // Progress summary component
124
- const ProgressSummary = () => (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [domain && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Domain: ", domain] })] })), adminEmail && subStep !== 'admin-email' && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Admin: ", adminEmail] })] })), tlsEmail && subStep !== 'tls-email' && subStep !== 'admin-email' && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" TLS email: ", tlsEmail] })] }))] }));
125
- return (_jsxs(BorderBox, { title: "Domain & DNS", children: [subStep === 'domain' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Rulebricks domain:" }), _jsx(Text, { color: "gray", dimColor: true, children: "This is where Rulebricks will be accessible (e.g., rulebricks.example.com)" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: domain, onChange: setDomain, onSubmit: handleDomainSubmit, placeholder: "rulebricks.example.com" }) }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) }))] })), subStep === 'validating' && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Validating domain..." }) })), subStep === 'admin-email' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the admin email address:" }), _jsx(Text, { color: "gray", dimColor: true, children: "This email will be used for Rulebricks administration and notifications" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: adminEmail, onChange: setAdminEmail, onSubmit: handleAdminEmailSubmit, placeholder: "admin@example.com" }) }), _jsx(ProgressSummary, {}), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) }))] })), subStep === 'tls-email' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the email for TLS certificates:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Let's Encrypt will send certificate expiration notices here" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: tlsEmail, onChange: setTlsEmail, onSubmit: handleTlsEmailSubmit, placeholder: adminEmail || 'admin@example.com' }) }), _jsx(ProgressSummary, {}), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) }))] })), subStep === 'dns-provider' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Where is your domain's DNS hosted?" }), _jsx(Text, { color: "gray", dimColor: true, children: "This determines whether we can automatically manage DNS records for you" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: DNS_PROVIDER_OPTIONS, onSelect: handleDnsProviderSelect, itemComponent: ({ isSelected, label }) => (_jsx(Text, { color: isSelected ? colors.accent : undefined, children: label })) }) }), _jsx(ProgressSummary, {})] })), subStep === 'dns-auto-manage' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Automatic DNS Management" }), _jsx(Text, { color: "gray", dimColor: true, children: "Would you like Rulebricks to automatically create and manage DNS records?" }), _jsx(Text, { color: "gray", dimColor: true, children: "This enables single-step deployment without manual DNS configuration." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: AUTO_MANAGE_OPTIONS, onSelect: handleAutoManageSelect, itemComponent: ({ isSelected, label }) => (_jsx(Text, { color: isSelected ? colors.accent : undefined, children: label })) }) }), _jsx(ProgressSummary, {}), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" DNS Provider: ", DNS_PROVIDER_NAMES[state.dnsProvider]] })] }), state.infrastructureMode === 'existing' && (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: _jsx(Text, { color: "yellow", children: "Note: Auto-DNS requires external-dns with proper IAM credentials in your cluster." }) }))] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
105
+ const ProgressSummary = () => (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [domain && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Domain: ", domain] })] })), adminEmail && subStep !== 'admin-email' && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Admin: ", adminEmail] })] }))] }));
106
+ return (_jsxs(BorderBox, { title: "Domain & DNS", children: [subStep === 'domain' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Rulebricks domain:" }), _jsx(Text, { color: "gray", dimColor: true, children: "This is where Rulebricks will be accessible (e.g., rulebricks.example.com)" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: domain, onChange: setDomain, onSubmit: handleDomainSubmit, placeholder: "rulebricks.example.com" }) }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) }))] })), subStep === 'validating' && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Validating domain..." }) })), subStep === 'admin-email' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the admin email address:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Used for Rulebricks administration, notifications, and TLS certificate (Let's Encrypt) notices" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: adminEmail, onChange: setAdminEmail, onSubmit: handleAdminEmailSubmit, placeholder: "admin@example.com" }) }), _jsx(ProgressSummary, {}), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) }))] })), subStep === 'dns-provider' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Where is your domain's DNS hosted?" }), _jsx(Text, { color: "gray", dimColor: true, children: "This determines whether we can automatically manage DNS records for you" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: DNS_PROVIDER_OPTIONS, onSelect: handleDnsProviderSelect, itemComponent: ({ isSelected, label }) => (_jsx(Text, { color: isSelected ? colors.accent : undefined, children: label })) }) }), _jsx(ProgressSummary, {})] })), subStep === 'dns-auto-manage' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Automatic DNS Management" }), _jsx(Text, { color: "gray", dimColor: true, children: "Would you like Rulebricks to automatically create and manage DNS records?" }), _jsx(Text, { color: "gray", dimColor: true, children: "This enables single-step deployment without manual DNS configuration." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: AUTO_MANAGE_OPTIONS, onSelect: handleAutoManageSelect, itemComponent: ({ isSelected, label }) => (_jsx(Text, { color: isSelected ? colors.accent : undefined, children: label })) }) }), _jsx(ProgressSummary, {}), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" DNS Provider: ", DNS_PROVIDER_NAMES[state.dnsProvider]] })] }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: _jsx(Text, { color: "yellow", children: "Note: Auto-DNS requires external-dns with proper IAM credentials in your cluster." }) })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
126
107
  }
@@ -0,0 +1,6 @@
1
+ interface ExternalServicesStepProps {
2
+ onComplete: () => void;
3
+ onBack: () => void;
4
+ }
5
+ export declare function ExternalServicesStep({ onComplete, onBack, }: ExternalServicesStepProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};