@rulebricks/cli 2.1.7 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +51 -16
  2. package/cluster-setup/aws/README.md +96 -47
  3. package/cluster-setup/aws/check-aws-access.sh +216 -52
  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 +103 -55
  7. package/cluster-setup/azure/check-aks-prereqs.sh +236 -56
  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 +51 -34
  11. package/cluster-setup/gcp/check-gke-prereqs.sh +222 -60
  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 -54
  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 +157 -36
  33. package/dist/components/Wizard/WizardContext.js +872 -160
  34. package/dist/components/Wizard/steps/CloudProviderStep.js +192 -107
  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 +739 -425
  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 -12
  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 +1841 -289
  80. package/dist/lib/helmValues.test.d.ts +1 -0
  81. package/dist/lib/helmValues.test.js +1012 -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 +124 -17
  85. package/dist/lib/kubernetes.js +576 -145
  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 +1860 -164
  101. package/dist/types/index.js +518 -295
  102. package/package.json +9 -4
  103. package/schema/values.schema.json +1934 -0
  104. package/cluster-setup/aws/cluster.yaml +0 -33
  105. package/cluster-setup/azure/main.bicep +0 -282
  106. package/cluster-setup/azure/main.parameters.json +0 -21
  107. package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
  108. package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
  109. package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
  110. package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
  111. package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
  112. package/dist/components/Wizard/steps/TierStep.js +0 -29
  113. package/dist/lib/terraform.d.ts +0 -66
  114. package/dist/lib/terraform.js +0 -754
  115. package/terraform/aws/main.tf +0 -355
  116. package/terraform/azure/main.tf +0 -371
  117. 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,79 +74,37 @@ export function CloudProviderStep({ onComplete, onBack, }) {
78
74
  ];
79
75
  return providers.map((p) => ({
80
76
  ...p,
81
- // Existing clusters only need a selectable provider so we can record
82
- // kubeconfig refresh details. Strict auth/quota checks are only required
83
- // when the CLI will provision infrastructure.
84
- disabled: needsTerraform
85
- ? !p.status.installed || !p.status.authenticated
86
- : !p.status.installed,
77
+ disabled: !p.status.authenticated,
87
78
  }));
88
79
  };
89
80
  const providerItems = getProviderItems();
90
- const handleProviderSelect = async (item) => {
81
+ const handleProviderSelect = (item) => {
91
82
  const selectedItem = providerItems.find((p) => p.value === item.value);
92
83
  if (!selectedItem || selectedItem.disabled)
93
84
  return;
94
85
  const provider = item.value;
86
+ setSelectedProvider(provider);
87
+ setClusterName("rulebricks-cluster");
88
+ setManualRegion("");
89
+ setManualResourceGroup("");
95
90
  dispatch({ type: "SET_PROVIDER", provider });
96
- if (provider === "gcp") {
97
- // GCP requires project to be pre-configured, so use the detected project
98
- const detectedProject = await getGcpProjectId();
99
- if (detectedProject) {
100
- dispatch({ type: "SET_GCP_PROJECT", projectId: detectedProject });
101
- // Skip project input and go directly to regions
102
- loadRegions(provider);
103
- }
104
- else {
105
- // Fallback to project input if somehow not detected (shouldn't happen with new auth check)
106
- setSubStep("gcp-project");
107
- }
108
- }
109
- else if (provider === "azure") {
110
- setSubStep("azure-rg");
111
- }
112
- else {
113
- loadRegions(provider);
114
- }
91
+ loadProviderRegions(provider);
115
92
  };
116
- const loadRegions = async (provider) => {
93
+ const loadProviderRegions = async (provider) => {
117
94
  setSubStep("region-loading");
118
- setRegionsLoading(true);
119
95
  try {
120
- const dynamicRegions = await listRegions(provider);
121
- if (dynamicRegions.length > 0) {
122
- setRegions(dynamicRegions);
123
- }
124
- else {
125
- // Fall back to static regions
126
- setRegions(CLOUD_REGIONS[provider]);
127
- }
96
+ const regions = await listRegions(provider);
97
+ setManualRegions(regions.length > 0 ? regions : CLOUD_REGIONS[provider]);
128
98
  }
129
99
  catch {
130
- // Fall back to static regions on error
131
- setRegions(CLOUD_REGIONS[provider]);
100
+ setManualRegions(CLOUD_REGIONS[provider]);
132
101
  }
133
- setRegionsLoading(false);
134
102
  setSubStep("region");
135
103
  };
136
- const handleRegionSelect = (item) => {
137
- dispatch({ type: "SET_REGION", region: item.value });
138
- // For existing infrastructure, load available clusters
139
- if (state.infrastructureMode === "existing" && state.provider) {
140
- loadClusters(state.provider, item.value);
141
- }
142
- else {
143
- // For provisioning, go directly to cluster name input
144
- setSubStep("cluster");
145
- }
146
- };
147
104
  const loadClusters = async (provider, region) => {
148
105
  setSubStep("cluster-loading");
149
- setClustersLoading(true);
150
106
  try {
151
- const availableClusters = await listClusters(provider, region, {
152
- azureResourceGroup: state.azureResourceGroup || undefined,
153
- });
107
+ const availableClusters = await discoverClustersInRegion(provider, region);
154
108
  setClusters(availableClusters);
155
109
  if (availableClusters.length > 0) {
156
110
  setSubStep("cluster-select");
@@ -165,27 +119,130 @@ export function CloudProviderStep({ onComplete, onBack, }) {
165
119
  setClusters([]);
166
120
  setSubStep("cluster");
167
121
  }
168
- setClustersLoading(false);
169
122
  };
170
- const handleClusterSelect = (item) => {
171
- setClusterName(item.value);
172
- 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
+ }
173
141
  onComplete();
174
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
+ };
175
150
  const handleClusterSubmit = () => {
176
151
  dispatch({ type: "SET_CLUSTER_NAME", clusterName });
177
- 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
+ }
178
193
  };
179
- const handleGcpProjectSubmit = async () => {
180
- dispatch({ type: "SET_GCP_PROJECT", projectId: gcpProject });
181
- // ADC is now checked upfront in checkGcloudCli(), so proceed directly to regions
182
- 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());
183
213
  };
184
- const handleAzureRgSubmit = () => {
185
- dispatch({ type: "SET_AZURE_RG", resourceGroup: azureRg });
186
- 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();
187
245
  };
188
- const regionItems = regions.map((r) => ({ label: r, value: r }));
189
246
  // Render status indicator for a provider
190
247
  const renderStatusIndicator = (status) => {
191
248
  if (!status.installed) {
@@ -203,11 +260,15 @@ export function CloudProviderStep({ onComplete, onBack, }) {
203
260
  }
204
261
  return _jsx(Text, { color: "green", children: " \u2713" });
205
262
  };
206
- 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 &&
207
- terraformStatus &&
208
- !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
209
- ? `v${terraformStatus.version}`
210
- : "", " ", "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) => ({
211
272
  label: p.label,
212
273
  value: p.value,
213
274
  })), onSelect: handleProviderSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => {
@@ -219,10 +280,34 @@ export function CloudProviderStep({ onComplete, onBack, }) {
219
280
  : isSelected
220
281
  ? colors.accent
221
282
  : undefined;
222
- 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, ")"] }))] }));
223
- } })] })), 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 should be the resource group containing your AKS cluster" }), _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"
224
- ? "This cluster will be created"
225
- : clusters.length === 0 && state.infrastructureMode === "existing"
226
- ? "No clusters found in this region - enter the name manually"
227
- : "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, " ");
228
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 {};