@rulebricks/cli 2.1.7 → 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.
- package/README.md +51 -16
- package/cluster-setup/aws/README.md +96 -47
- package/cluster-setup/aws/check-aws-access.sh +216 -52
- package/cluster-setup/aws/parameters.json +13 -0
- package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
- package/cluster-setup/azure/README.md +103 -55
- package/cluster-setup/azure/check-aks-prereqs.sh +236 -56
- package/cluster-setup/azure/parameters.json +30 -0
- package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
- package/cluster-setup/gcp/README.md +51 -34
- package/cluster-setup/gcp/check-gke-prereqs.sh +222 -60
- package/dist/commands/backup.d.ts +5 -0
- package/dist/commands/backup.js +104 -0
- package/dist/commands/deploy.d.ts +3 -1
- package/dist/commands/deploy.js +226 -326
- package/dist/commands/destroy.d.ts +1 -1
- package/dist/commands/destroy.js +73 -123
- package/dist/commands/init.d.ts +5 -1
- package/dist/commands/init.js +78 -54
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +74 -0
- package/dist/commands/open.d.ts +1 -1
- package/dist/commands/open.js +4 -12
- package/dist/commands/redeploy.d.ts +6 -0
- package/dist/commands/redeploy.js +310 -0
- package/dist/commands/restore.d.ts +5 -0
- package/dist/commands/restore.js +338 -0
- package/dist/commands/status.js +62 -49
- package/dist/commands/upgrade.js +74 -51
- package/dist/components/DNSWaitScreen.d.ts +5 -1
- package/dist/components/DNSWaitScreen.js +47 -41
- package/dist/components/Wizard/WizardContext.d.ts +157 -36
- package/dist/components/Wizard/WizardContext.js +872 -160
- package/dist/components/Wizard/steps/CloudProviderStep.js +192 -107
- package/dist/components/Wizard/steps/DomainStep.js +5 -24
- package/dist/components/Wizard/steps/ExternalServicesStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ExternalServicesStep.js +645 -0
- package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +2 -1
- package/dist/components/Wizard/steps/FeatureConfigStep.js +739 -425
- package/dist/components/Wizard/steps/FeaturesStep.js +31 -35
- package/dist/components/Wizard/steps/ObservabilityStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ObservabilityStep.js +137 -0
- package/dist/components/Wizard/steps/ReviewStep.d.ts +2 -1
- package/dist/components/Wizard/steps/ReviewStep.js +56 -12
- package/dist/components/Wizard/steps/StorageStep.d.ts +9 -0
- package/dist/components/Wizard/steps/StorageStep.js +592 -0
- package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +20 -21
- package/dist/components/Wizard/steps/VersionStep.js +45 -23
- package/dist/components/Wizard/steps/index.d.ts +3 -3
- package/dist/components/Wizard/steps/index.js +3 -3
- package/dist/components/common/CommandApproval.d.ts +12 -0
- package/dist/components/common/CommandApproval.js +91 -0
- package/dist/components/common/DeploymentPicker.d.ts +14 -0
- package/dist/components/common/DeploymentPicker.js +16 -0
- package/dist/components/common/index.d.ts +2 -0
- package/dist/components/common/index.js +2 -0
- package/dist/index.js +94 -62
- package/dist/lib/cloudCli.d.ts +134 -63
- package/dist/lib/cloudCli.js +512 -220
- package/dist/lib/clusterSetupDefaults.d.ts +30 -0
- package/dist/lib/clusterSetupDefaults.js +64 -0
- package/dist/lib/commandApproval.d.ts +26 -0
- package/dist/lib/commandApproval.js +114 -0
- package/dist/lib/config.d.ts +12 -10
- package/dist/lib/config.js +91 -33
- package/dist/lib/configFixtures.d.ts +5 -0
- package/dist/lib/configFixtures.js +513 -0
- package/dist/lib/deploymentHealth.d.ts +32 -0
- package/dist/lib/deploymentHealth.js +157 -0
- package/dist/lib/dns.d.ts +1 -1
- package/dist/lib/dns.js +19 -1
- package/dist/lib/dns.test.d.ts +1 -0
- package/dist/lib/dns.test.js +27 -0
- package/dist/lib/dockerHub.d.ts +12 -1
- package/dist/lib/dockerHub.js +18 -8
- package/dist/lib/helm.d.ts +4 -0
- package/dist/lib/helm.js +16 -0
- package/dist/lib/helmValues.d.ts +25 -0
- package/dist/lib/helmValues.js +1762 -289
- package/dist/lib/helmValues.test.d.ts +1 -0
- package/dist/lib/helmValues.test.js +966 -0
- package/dist/lib/htpasswd.d.ts +1 -0
- package/dist/lib/htpasswd.js +15 -0
- package/dist/lib/kubernetes.d.ts +124 -17
- package/dist/lib/kubernetes.js +576 -145
- package/dist/lib/secrets.d.ts +23 -0
- package/dist/lib/secrets.js +158 -0
- package/dist/lib/validateValues.d.ts +31 -0
- package/dist/lib/validateValues.js +253 -0
- package/dist/lib/versions.d.ts +82 -11
- package/dist/lib/versions.js +131 -31
- package/dist/lib/versions.test.d.ts +1 -0
- package/dist/lib/versions.test.js +81 -0
- package/dist/lib/wizardSteps.d.ts +14 -0
- package/dist/lib/wizardSteps.js +23 -0
- package/dist/lib/workloadIdentity.d.ts +26 -0
- package/dist/lib/workloadIdentity.js +323 -0
- package/dist/lib/workloadIdentity.test.d.ts +1 -0
- package/dist/lib/workloadIdentity.test.js +57 -0
- package/dist/types/index.d.ts +1860 -164
- package/dist/types/index.js +518 -295
- package/package.json +9 -4
- package/schema/values.schema.json +1934 -0
- package/cluster-setup/aws/cluster.yaml +0 -33
- package/cluster-setup/azure/main.bicep +0 -282
- package/cluster-setup/azure/main.parameters.json +0 -21
- package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
- package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
- package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
- package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
- package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
- package/dist/components/Wizard/steps/TierStep.js +0 -29
- package/dist/lib/terraform.d.ts +0 -66
- package/dist/lib/terraform.js +0 -754
- package/terraform/aws/main.tf +0 -355
- package/terraform/azure/main.tf +0 -371
- package/terraform/gcp/main.tf +0 -407
package/dist/commands/destroy.js
CHANGED
|
@@ -1,58 +1,48 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React, {
|
|
2
|
+
import React, { useCallback, useState } from "react";
|
|
3
3
|
import { Box, Text, useApp, useInput } from "ink";
|
|
4
4
|
import { BorderBox, Spinner, StatusLine, ThemeProvider, useTheme, Logo, } from "../components/common/index.js";
|
|
5
|
-
import { loadDeploymentConfig, loadDeploymentState, deleteDeployment, deploymentExists, } from "../lib/config.js";
|
|
5
|
+
import { loadDeploymentConfig, loadDeploymentState, deleteDeployment, deploymentExists, updateDeploymentStatus, } from "../lib/config.js";
|
|
6
6
|
import { uninstallChart, getInstalledVersion } from "../lib/helm.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
function DestroyCommandInner({ name, cluster, config, force, }) {
|
|
7
|
+
import { cleanupKubeSystemLeftovers, cleanupNamespaceAPIServices, deleteNamespace, deletePVCs, deleteRulebricksCRDs, isClusterAccessible, isLastRulebricksDeployment, namespaceExists, removeBlockingFinalizers, } from "../lib/kubernetes.js";
|
|
8
|
+
import { getNamespace, getReleaseName } from "../types/index.js";
|
|
9
|
+
function DestroyCommandInner({ name, config, force, purge, }) {
|
|
11
10
|
const { exit } = useApp();
|
|
12
11
|
const { colors } = useTheme();
|
|
13
12
|
const [step, setStep] = useState("loading");
|
|
14
|
-
const [deploymentConfig, setDeploymentConfig] = useState(null);
|
|
15
13
|
const [state, setState] = useState(null);
|
|
16
14
|
const [scope, setScope] = useState(null);
|
|
17
15
|
const [error, setError] = useState(null);
|
|
18
|
-
const [confirmText, setConfirmText] = useState("");
|
|
19
|
-
const [infraError, setInfraError] = useState(null);
|
|
20
16
|
const [status, setStatus] = useState({
|
|
21
17
|
helm: "pending",
|
|
22
18
|
pvc: "pending",
|
|
23
19
|
namespace: "pending",
|
|
24
|
-
|
|
20
|
+
kubeSystem: "pending",
|
|
21
|
+
crds: "pending",
|
|
25
22
|
cleanup: "pending",
|
|
26
23
|
});
|
|
27
|
-
// Load config and determine scope on mount
|
|
28
24
|
React.useEffect(() => {
|
|
29
25
|
(async () => {
|
|
30
26
|
try {
|
|
31
|
-
// Check if deployment exists
|
|
32
27
|
const exists = await deploymentExists(name);
|
|
33
28
|
if (!exists) {
|
|
34
29
|
setError(`Deployment "${name}" not found`);
|
|
35
30
|
setStep("error");
|
|
36
31
|
return;
|
|
37
32
|
}
|
|
38
|
-
// Load config (may throw if corrupted)
|
|
39
|
-
let cfg = null;
|
|
40
33
|
try {
|
|
41
|
-
|
|
42
|
-
setDeploymentConfig(cfg);
|
|
34
|
+
await loadDeploymentConfig(name);
|
|
43
35
|
}
|
|
44
36
|
catch {
|
|
45
|
-
// Config might be corrupted or missing
|
|
37
|
+
// Config might be corrupted or missing; cluster cleanup can still use state/name.
|
|
46
38
|
}
|
|
47
|
-
// Load state
|
|
48
39
|
const st = await loadDeploymentState(name);
|
|
49
40
|
setState(st);
|
|
50
|
-
|
|
51
|
-
const deploymentScope = await determineScope(name, cfg, st);
|
|
41
|
+
const deploymentScope = await determineScope(name, st);
|
|
52
42
|
setScope(deploymentScope);
|
|
53
43
|
if (force) {
|
|
54
44
|
setStep("destroying");
|
|
55
|
-
runDestroy(
|
|
45
|
+
runDestroy(st, deploymentScope);
|
|
56
46
|
}
|
|
57
47
|
else {
|
|
58
48
|
setStep("confirm");
|
|
@@ -67,41 +57,22 @@ function DestroyCommandInner({ name, cluster, config, force, }) {
|
|
|
67
57
|
useInput((input, key) => {
|
|
68
58
|
if (step === "confirm") {
|
|
69
59
|
if (key.return) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
setStep("destroying");
|
|
73
|
-
runDestroy(deploymentConfig, state, scope);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
setStep("destroying");
|
|
78
|
-
runDestroy(deploymentConfig, state, scope);
|
|
79
|
-
}
|
|
60
|
+
setStep("destroying");
|
|
61
|
+
runDestroy(state, scope);
|
|
80
62
|
}
|
|
81
63
|
else if (key.escape) {
|
|
82
64
|
exit();
|
|
83
65
|
}
|
|
84
|
-
else if (key.backspace || key.delete) {
|
|
85
|
-
setConfirmText((t) => t.slice(0, -1));
|
|
86
|
-
}
|
|
87
|
-
else if (input && !key.ctrl && !key.meta) {
|
|
88
|
-
setConfirmText((t) => t + input);
|
|
89
|
-
}
|
|
90
66
|
}
|
|
91
|
-
else if (step === "error") {
|
|
92
|
-
|
|
93
|
-
exit();
|
|
94
|
-
}
|
|
67
|
+
else if (step === "error" && (key.escape || key.return)) {
|
|
68
|
+
exit();
|
|
95
69
|
}
|
|
96
70
|
});
|
|
97
|
-
const runDestroy = useCallback(async (
|
|
71
|
+
const runDestroy = useCallback(async (st, deploymentScope) => {
|
|
98
72
|
try {
|
|
99
|
-
// Use namespace from state if available (backwards compat), otherwise compute from deployment name
|
|
100
73
|
const namespace = st?.application?.namespace || getNamespace(name);
|
|
101
74
|
const releaseName = getReleaseName(name);
|
|
102
|
-
// Run cluster cleanup if cluster is accessible
|
|
103
75
|
if (deploymentScope.clusterAccessible) {
|
|
104
|
-
// Step 1: Uninstall Helm release (only if namespace exists - helm data is stored there)
|
|
105
76
|
if (deploymentScope.hasHelmRelease && deploymentScope.hasNamespace) {
|
|
106
77
|
setStatus((s) => ({ ...s, helm: "running" }));
|
|
107
78
|
try {
|
|
@@ -109,15 +80,12 @@ function DestroyCommandInner({ name, cluster, config, force, }) {
|
|
|
109
80
|
setStatus((s) => ({ ...s, helm: "success" }));
|
|
110
81
|
}
|
|
111
82
|
catch {
|
|
112
|
-
// Helm release might already be gone, continue anyway
|
|
113
83
|
setStatus((s) => ({ ...s, helm: "error" }));
|
|
114
84
|
}
|
|
115
85
|
}
|
|
116
86
|
else {
|
|
117
|
-
// Skip if no helm release OR namespace is already gone
|
|
118
87
|
setStatus((s) => ({ ...s, helm: "skipped" }));
|
|
119
88
|
}
|
|
120
|
-
// Step 2: Delete all PVCs in the namespace
|
|
121
89
|
if (deploymentScope.hasNamespace) {
|
|
122
90
|
setStatus((s) => ({ ...s, pvc: "running" }));
|
|
123
91
|
try {
|
|
@@ -125,64 +93,72 @@ function DestroyCommandInner({ name, cluster, config, force, }) {
|
|
|
125
93
|
setStatus((s) => ({ ...s, pvc: "success" }));
|
|
126
94
|
}
|
|
127
95
|
catch {
|
|
128
|
-
// PVCs might not exist, continue anyway
|
|
129
96
|
setStatus((s) => ({ ...s, pvc: "error" }));
|
|
130
97
|
}
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
setStatus((s) => ({ ...s, pvc: "skipped" }));
|
|
134
|
-
}
|
|
135
|
-
// Step 3: Delete namespace
|
|
136
|
-
if (deploymentScope.hasNamespace) {
|
|
137
98
|
setStatus((s) => ({ ...s, namespace: "running" }));
|
|
138
99
|
try {
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
100
|
+
// Clear teardown deadlocks BEFORE deleting the namespace:
|
|
101
|
+
// - Custom-resource finalizers (KEDA ScaledObjects, cert-manager
|
|
102
|
+
// ACME Challenges/Orders, Strimzi Kafka) wait on controllers
|
|
103
|
+
// removed with the release, so they're never cleared and wedge
|
|
104
|
+
// the namespace (and the CRD) in Terminating.
|
|
105
|
+
// - Aggregated APIServices backed by this namespace's services
|
|
106
|
+
// (KEDA external.metrics, metrics adapters, etc.) go
|
|
107
|
+
// Unavailable as the namespace tears down and break the
|
|
108
|
+
// namespace controller's discovery, wedging it in Terminating.
|
|
109
|
+
await removeBlockingFinalizers(namespace);
|
|
110
|
+
await cleanupNamespaceAPIServices(namespace);
|
|
142
111
|
await deleteNamespace(namespace);
|
|
143
112
|
setStatus((s) => ({ ...s, namespace: "success" }));
|
|
144
113
|
}
|
|
145
114
|
catch {
|
|
146
|
-
// Namespace might already be gone
|
|
147
115
|
setStatus((s) => ({ ...s, namespace: "error" }));
|
|
148
116
|
}
|
|
149
117
|
}
|
|
150
118
|
else {
|
|
151
|
-
setStatus((s) => ({ ...s, namespace: "skipped" }));
|
|
119
|
+
setStatus((s) => ({ ...s, pvc: "skipped", namespace: "skipped" }));
|
|
120
|
+
}
|
|
121
|
+
// Leftovers `helm uninstall` does NOT remove. The prometheus-operator's
|
|
122
|
+
// kube-system kubelet Service is per-release and operator-created, so
|
|
123
|
+
// always clean it (safe; scoped to this release only).
|
|
124
|
+
setStatus((s) => ({ ...s, kubeSystem: "running" }));
|
|
125
|
+
try {
|
|
126
|
+
await cleanupKubeSystemLeftovers(releaseName);
|
|
127
|
+
setStatus((s) => ({ ...s, kubeSystem: "success" }));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
setStatus((s) => ({ ...s, kubeSystem: "error" }));
|
|
131
|
+
}
|
|
132
|
+
// CRDs (cert-manager/keda/strimzi/kube-prometheus-stack) ship in crds/
|
|
133
|
+
// dirs and are never removed by helm. They are cluster-SHARED, so only
|
|
134
|
+
// purge them when this is the last Rulebricks deployment on the cluster
|
|
135
|
+
// (or the operator forces --purge) — otherwise deleting a CRD would
|
|
136
|
+
// cascade-delete other deployments' custom resources.
|
|
137
|
+
const purgeCRDs = purge === true || (await isLastRulebricksDeployment(releaseName));
|
|
138
|
+
if (purgeCRDs) {
|
|
139
|
+
setStatus((s) => ({ ...s, crds: "running" }));
|
|
140
|
+
try {
|
|
141
|
+
await deleteRulebricksCRDs();
|
|
142
|
+
setStatus((s) => ({ ...s, crds: "success" }));
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
setStatus((s) => ({ ...s, crds: "error" }));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
setStatus((s) => ({ ...s, crds: "skipped" }));
|
|
152
150
|
}
|
|
153
151
|
}
|
|
154
152
|
else {
|
|
155
|
-
// Cluster not accessible - skip all cluster operations
|
|
156
153
|
setStatus((s) => ({
|
|
157
154
|
...s,
|
|
158
155
|
helm: "skipped",
|
|
159
156
|
pvc: "skipped",
|
|
160
157
|
namespace: "skipped",
|
|
158
|
+
kubeSystem: "skipped",
|
|
159
|
+
crds: "skipped",
|
|
161
160
|
}));
|
|
162
161
|
}
|
|
163
|
-
// Destroy infrastructure if requested and it exists
|
|
164
|
-
if (cluster && deploymentScope.hasInfrastructure) {
|
|
165
|
-
setStatus((s) => ({ ...s, infrastructure: "running" }));
|
|
166
|
-
try {
|
|
167
|
-
const cloudContext = cfg?.infrastructure.provider && cfg?.infrastructure.region
|
|
168
|
-
? {
|
|
169
|
-
provider: cfg.infrastructure.provider,
|
|
170
|
-
clusterName: cfg.infrastructure.clusterName || `${name}-cluster`,
|
|
171
|
-
region: cfg.infrastructure.region,
|
|
172
|
-
}
|
|
173
|
-
: undefined;
|
|
174
|
-
await terraformDestroy(name, cloudContext);
|
|
175
|
-
setStatus((s) => ({ ...s, infrastructure: "success" }));
|
|
176
|
-
}
|
|
177
|
-
catch (infraErr) {
|
|
178
|
-
setInfraError(infraErr instanceof Error ? infraErr.message : "Infrastructure destroy failed");
|
|
179
|
-
setStatus((s) => ({ ...s, infrastructure: "error" }));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
setStatus((s) => ({ ...s, infrastructure: "skipped" }));
|
|
184
|
-
}
|
|
185
|
-
// Clean up local files (only if --config flag is passed)
|
|
186
162
|
if (config && deploymentScope.hasLocalFiles) {
|
|
187
163
|
setStatus((s) => ({ ...s, cleanup: "running" }));
|
|
188
164
|
try {
|
|
@@ -196,6 +172,9 @@ function DestroyCommandInner({ name, cluster, config, force, }) {
|
|
|
196
172
|
else {
|
|
197
173
|
setStatus((s) => ({ ...s, cleanup: "skipped" }));
|
|
198
174
|
}
|
|
175
|
+
if (!config && deploymentScope.clusterAccessible) {
|
|
176
|
+
await updateDeploymentStatus(name, "destroyed");
|
|
177
|
+
}
|
|
199
178
|
setStep("complete");
|
|
200
179
|
setTimeout(() => exit(), 3000);
|
|
201
180
|
}
|
|
@@ -203,16 +182,13 @@ function DestroyCommandInner({ name, cluster, config, force, }) {
|
|
|
203
182
|
setError(err instanceof Error ? err.message : "Destruction failed");
|
|
204
183
|
setStep("error");
|
|
205
184
|
}
|
|
206
|
-
}, [name,
|
|
207
|
-
// Loading screen
|
|
185
|
+
}, [name, config, purge, exit]);
|
|
208
186
|
if (step === "loading") {
|
|
209
187
|
return (_jsx(BorderBox, { title: `Destroying ${name}`, children: _jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Checking deployment state..." }) }) }));
|
|
210
188
|
}
|
|
211
|
-
// Error screen
|
|
212
189
|
if (step === "error") {
|
|
213
190
|
return (_jsx(BorderBox, { title: "Destruction Failed", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.error, bold: true, children: "\u2717 Error" }), _jsx(Text, { color: colors.error, children: error }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Press Enter or Esc to exit" }) })] }) }));
|
|
214
191
|
}
|
|
215
|
-
// Complete screen
|
|
216
192
|
if (step === "complete") {
|
|
217
193
|
const cleanedItems = [];
|
|
218
194
|
if (status.helm === "success")
|
|
@@ -221,57 +197,35 @@ function DestroyCommandInner({ name, cluster, config, force, }) {
|
|
|
221
197
|
cleanedItems.push("Persistent volume claims");
|
|
222
198
|
if (status.namespace === "success")
|
|
223
199
|
cleanedItems.push("Kubernetes namespace");
|
|
224
|
-
if (status.
|
|
225
|
-
cleanedItems.push("
|
|
200
|
+
if (status.kubeSystem === "success")
|
|
201
|
+
cleanedItems.push("kube-system leftovers (kubelet service)");
|
|
202
|
+
if (status.crds === "success")
|
|
203
|
+
cleanedItems.push("Shared CRDs");
|
|
226
204
|
if (status.cleanup === "success")
|
|
227
205
|
cleanedItems.push("Local configuration files");
|
|
228
|
-
// Check if nothing was cleaned in cluster (no helm, no pvc, no namespace)
|
|
229
206
|
const noClusterCleanup = status.helm === "skipped" &&
|
|
230
207
|
status.pvc === "skipped" &&
|
|
231
208
|
status.namespace === "skipped";
|
|
232
|
-
|
|
233
|
-
const title = hasInfraFailure ? "Destruction Partially Complete" : "Destruction Complete";
|
|
234
|
-
return (_jsx(BorderBox, { title: title, children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [hasInfraFailure ? (_jsxs(Text, { color: colors.warning, bold: true, children: ["\u26A0 Deployment \"", name, "\" was partially destroyed"] })) : (_jsxs(Text, { color: colors.success, bold: true, children: ["\u2713 Deployment \"", name, "\" has been destroyed"] })), cleanedItems.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, children: "Cleaned up:" }), cleanedItems.map((item) => (_jsxs(Text, { color: colors.muted, children: [" ", "\u2022 ", item] }, item)))] })), hasInfraFailure && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.error, bold: true, children: "\u2717 Infrastructure destroy failed" }), _jsx(Text, { color: colors.error, children: infraError }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, children: ["Cloud resources may still exist. Run `rulebricks destroy ", name, " ", "--cluster` to retry."] }) })] })), noClusterCleanup && status.cleanup === "success" && !hasInfraFailure && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Note: No cluster resources found, only local files were cleaned up." }) })), status.cleanup === "skipped" && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, dimColor: true, children: ["Local configuration files preserved in ~/.rulebricks/deployments/", name, "/"] }) }))] }) }));
|
|
209
|
+
return (_jsx(BorderBox, { title: "Destruction Complete", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { color: colors.success, bold: true, children: ["\u2713 Deployment \"", name, "\" has been destroyed"] }), cleanedItems.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, children: "Cleaned up:" }), cleanedItems.map((item) => (_jsxs(Text, { color: colors.muted, children: [" ", "\u2022 ", item] }, item)))] })), noClusterCleanup && status.cleanup === "success" && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Note: No cluster resources found, only local files were cleaned up." }) })), status.cleanup === "skipped" && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, dimColor: true, children: ["Local configuration files preserved in ~/.rulebricks/deployments/", name, "/"] }) }))] }) }));
|
|
235
210
|
}
|
|
236
|
-
// Destroying screen
|
|
237
211
|
if (step === "destroying") {
|
|
238
|
-
|
|
239
|
-
const showClusterOps = scope?.clusterAccessible;
|
|
240
|
-
const showInfra = cluster && scope?.hasInfrastructure;
|
|
241
|
-
return (_jsx(BorderBox, { title: `Destroying ${name}`, children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [showClusterOps && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: status.helm, label: "Uninstalling Helm release" }), _jsx(StatusLine, { status: status.pvc, label: "Deleting persistent volumes" }), _jsx(StatusLine, { status: status.namespace, label: "Deleting namespace" })] })), showInfra && (_jsx(StatusLine, { status: status.infrastructure, label: "Destroying infrastructure" })), config && (_jsx(StatusLine, { status: status.cleanup, label: "Cleaning up local files" })), !scope?.clusterAccessible && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.warning, dimColor: true, children: "Skipping cluster operations (cluster not accessible)" }) })), _jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: "Destroying deployment..." }) })] }) }));
|
|
212
|
+
return (_jsx(BorderBox, { title: `Destroying ${name}`, children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [scope?.clusterAccessible && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: status.helm, label: "Uninstalling Helm release" }), _jsx(StatusLine, { status: status.pvc, label: "Deleting persistent volumes" }), _jsx(StatusLine, { status: status.namespace, label: "Deleting namespace" }), _jsx(StatusLine, { status: status.kubeSystem, label: "Removing kube-system leftovers" }), _jsx(StatusLine, { status: status.crds, label: "Removing shared CRDs" })] })), config && (_jsx(StatusLine, { status: status.cleanup, label: "Cleaning up local files" })), !scope?.clusterAccessible && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.warning, dimColor: true, children: "Skipping cluster operations (cluster not accessible)" }) })), _jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: "Destroying deployment..." }) })] }) }));
|
|
242
213
|
}
|
|
243
|
-
// Confirmation screen
|
|
244
|
-
// Check if there's nothing in the cluster to clean up
|
|
245
214
|
const hasClusterResources = scope?.hasHelmRelease || scope?.hasNamespace;
|
|
246
|
-
const onlyLocalFiles = !hasClusterResources
|
|
247
|
-
const needsInfraConfirm = cluster && scope?.hasInfrastructure;
|
|
215
|
+
const onlyLocalFiles = !hasClusterResources;
|
|
248
216
|
const willDeleteConfig = config && scope?.hasLocalFiles;
|
|
249
|
-
// Nothing to do if only local files exist but --config not passed
|
|
250
217
|
if (onlyLocalFiles && !config) {
|
|
251
218
|
return (_jsx(BorderBox, { title: "Nothing to Destroy", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.muted, children: "No cluster resources found to clean up." }), _jsx(Text, { color: colors.muted, children: "Local configuration files will be preserved." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, dimColor: true, children: ["Use ", _jsx(Text, { color: colors.accent, children: "--config" }), " to also remove local files."] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Press Esc to exit" }) })] }) }));
|
|
252
219
|
}
|
|
253
|
-
return (_jsx(BorderBox, { title: "Confirm Destruction", children:
|
|
254
|
-
// Only cleaning local files (with --config)
|
|
255
|
-
_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.warning, bold: true, children: "\u2139 Local Cleanup" }), _jsxs(Box, { marginY: 1, flexDirection: "column", children: [_jsx(Text, { children: "No cluster resources found to clean up." }), _jsx(Text, { children: "This will delete local configuration files." })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.warning, children: "Press Enter to confirm, Esc to cancel" }) })] })) : (
|
|
256
|
-
// Full destruction
|
|
257
|
-
_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.accent, bold: true, children: "\u26A0 WARNING" }), _jsxs(Box, { marginY: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, children: "This will permanently delete:" }), (scope?.hasHelmRelease || scope?.hasNamespace) && (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.muted, children: " \u2022 Rulebricks application" }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 All databases and stored data"] }), _jsx(Text, { color: colors.muted, children: " \u2022 All persistent volumes" }), _jsx(Text, { color: colors.muted, children: " \u2022 Monitoring stack" }), _jsx(Text, { color: colors.muted, children: " \u2022 Kubernetes namespace" })] })), needsInfraConfirm && (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.accent, children: " \u2022 Kubernetes cluster" }), _jsx(Text, { color: colors.accent, children: " \u2022 All cloud infrastructure" })] })), willDeleteConfig && (_jsx(Text, { color: colors.muted, children: " \u2022 Local configuration files" })), !cluster && scope?.hasInfrastructure && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Cloud infrastructure will be preserved. Use --cluster to remove it." }) })), cluster && !scope?.hasInfrastructure && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "No CLI managed infrastructure found for this deployment." }) })), !willDeleteConfig && (_jsx(Box, { marginTop: !needsInfraConfirm && !cluster ? 0 : 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Local config files will be preserved. Use --config to remove them." }) })), !scope?.clusterAccessible && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.warning, dimColor: true, children: "\u26A0 Cluster is not accessible. Some cluster resources may need manual cleanup." }) }))] }), needsInfraConfirm ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["Type", " ", _jsx(Text, { color: colors.accent, bold: true, children: "destroy-all" }), " ", "to confirm:"] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(Text, { children: confirmText }), _jsx(Text, { color: colors.muted, children: "\u2588" })] })] })) : (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.warning, children: "Press Enter to confirm, Esc to cancel" }) }))] })) }) }));
|
|
220
|
+
return (_jsx(BorderBox, { title: "Confirm Destruction", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [onlyLocalFiles && config ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.warning, bold: true, children: "Local Cleanup" }), _jsxs(Box, { marginY: 1, flexDirection: "column", children: [_jsx(Text, { children: "No cluster resources found to clean up." }), _jsx(Text, { children: "This will delete local configuration files." })] })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.accent, bold: true, children: "WARNING" }), _jsxs(Box, { marginY: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, children: "This will permanently delete:" }), (scope?.hasHelmRelease || scope?.hasNamespace) && (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.muted, children: " \u2022 Rulebricks application" }), _jsx(Text, { color: colors.muted, children: " \u2022 All databases and stored data" }), _jsx(Text, { color: colors.muted, children: " \u2022 All persistent volumes" }), _jsx(Text, { color: colors.muted, children: " \u2022 Monitoring stack" }), _jsx(Text, { color: colors.muted, children: " \u2022 Kubernetes namespace" })] })), willDeleteConfig && (_jsx(Text, { color: colors.muted, children: " \u2022 Local configuration files" })), !willDeleteConfig && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Local config files will be preserved. Use --config to remove them." }) })), !scope?.clusterAccessible && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.warning, dimColor: true, children: "Cluster is not accessible. Some cluster resources may need manual cleanup." }) }))] })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.warning, children: "Press Enter to confirm, Esc to cancel" }) })] }) }));
|
|
258
221
|
}
|
|
259
222
|
export function DestroyCommand(props) {
|
|
260
223
|
return (_jsxs(ThemeProvider, { theme: "destroy", children: [_jsx(Logo, {}), _jsx(DestroyCommandInner, { ...props })] }));
|
|
261
224
|
}
|
|
262
|
-
|
|
263
|
-
* Determines what actually exists by checking cluster state directly.
|
|
264
|
-
* This ensures cleanup works even if local state is out of sync.
|
|
265
|
-
*/
|
|
266
|
-
async function determineScope(name, config, state) {
|
|
267
|
-
// Check if we have local files (we do, since we loaded the deployment)
|
|
225
|
+
async function determineScope(name, state) {
|
|
268
226
|
const hasLocalFiles = true;
|
|
269
|
-
// Check if infrastructure was provisioned (from local terraform state)
|
|
270
|
-
const hasInfrastructure = await hasTerraformState(name);
|
|
271
|
-
// Use namespace from state if available (backwards compat), otherwise compute from deployment name
|
|
272
227
|
const namespace = state?.application?.namespace || getNamespace(name);
|
|
273
228
|
const releaseName = getReleaseName(name);
|
|
274
|
-
// Check if cluster is accessible
|
|
275
229
|
let clusterAccessible = false;
|
|
276
230
|
try {
|
|
277
231
|
clusterAccessible = await isClusterAccessible();
|
|
@@ -279,11 +233,9 @@ async function determineScope(name, config, state) {
|
|
|
279
233
|
catch {
|
|
280
234
|
clusterAccessible = false;
|
|
281
235
|
}
|
|
282
|
-
// If cluster is accessible, check what actually exists in the cluster
|
|
283
236
|
let hasHelmRelease = false;
|
|
284
237
|
let hasNamespace = false;
|
|
285
238
|
if (clusterAccessible) {
|
|
286
|
-
// Check if Helm release actually exists in the cluster
|
|
287
239
|
try {
|
|
288
240
|
const installedVersion = await getInstalledVersion(releaseName, namespace);
|
|
289
241
|
hasHelmRelease = installedVersion !== null;
|
|
@@ -291,7 +243,6 @@ async function determineScope(name, config, state) {
|
|
|
291
243
|
catch {
|
|
292
244
|
hasHelmRelease = false;
|
|
293
245
|
}
|
|
294
|
-
// Check if namespace exists
|
|
295
246
|
try {
|
|
296
247
|
hasNamespace = await namespaceExists(namespace);
|
|
297
248
|
}
|
|
@@ -303,7 +254,6 @@ async function determineScope(name, config, state) {
|
|
|
303
254
|
hasLocalFiles,
|
|
304
255
|
hasHelmRelease,
|
|
305
256
|
hasNamespace,
|
|
306
|
-
hasInfrastructure,
|
|
307
257
|
clusterAccessible,
|
|
308
258
|
};
|
|
309
259
|
}
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { WizardState } from "../components/Wizard/WizardContext.js";
|
|
1
2
|
import { ProfileConfig } from "../types/index.js";
|
|
2
3
|
interface InitWizardProps {
|
|
3
4
|
initialName?: string;
|
|
5
|
+
initialState?: WizardState;
|
|
6
|
+
mode?: "create" | "redeploy";
|
|
7
|
+
onSaveComplete?: () => void;
|
|
4
8
|
profile?: ProfileConfig | null;
|
|
5
9
|
}
|
|
6
|
-
export declare function InitWizard({ initialName, profile: providedProfile, }: InitWizardProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function InitWizard({ initialName, initialState, mode, onSaveComplete, profile: providedProfile, }: InitWizardProps): import("react/jsx-runtime").JSX.Element;
|
|
7
11
|
export {};
|
package/dist/commands/init.js
CHANGED
|
@@ -2,29 +2,37 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState, useCallback, useEffect } from "react";
|
|
3
3
|
import { Box, Text, useApp, useStdout } from "ink";
|
|
4
4
|
import { WizardProvider, useWizard, } from "../components/Wizard/WizardContext.js";
|
|
5
|
-
import {
|
|
6
|
-
import { AppShell, ProgressHeader, ThemeProvider, useTheme, Logo, LOGO_LINES, } from "../components/common/index.js";
|
|
7
|
-
import { saveDeploymentConfig, deploymentExists, loadProfile, updateProfile, extractProfileFromConfig, } from "../lib/config.js";
|
|
8
|
-
import { generateHelmValues } from "../lib/helmValues.js";
|
|
9
|
-
import {
|
|
5
|
+
import { CloudProviderStep, DomainStep, SMTPStep, DatabaseStep, SupabaseCredentialsStep, FeaturesStep, StorageStep, ObservabilityStep, ExternalServicesStep, FeatureConfigStep, VersionStep, ReviewStep, } from "../components/Wizard/steps/index.js";
|
|
6
|
+
import { AppShell, ProgressHeader, ThemeProvider, useTheme, Logo, LOGO_LINES, CommandApprovalProvider, } from "../components/common/index.js";
|
|
7
|
+
import { saveDeploymentConfig, deploymentExists, loadHelmValues, loadProfile, saveHelmValues, updateProfile, extractProfileFromConfig, } from "../lib/config.js";
|
|
8
|
+
import { buildHelmValues, generateHelmValues, mergeHelmValues, } from "../lib/helmValues.js";
|
|
9
|
+
import { assertValidHelmValues } from "../lib/validateValues.js";
|
|
10
|
+
import { getActiveWizardSteps, } from "../lib/wizardSteps.js";
|
|
10
11
|
const STEP_INFO = {
|
|
11
|
-
mode: { title: "Deployment Mode", description: "Choose how to deploy" },
|
|
12
12
|
cloud: { title: "Cloud Provider", description: "Select your cloud provider" },
|
|
13
|
-
domain: { title: "Domain &
|
|
13
|
+
domain: { title: "Domain & DNS", description: "Configure your domain and DNS" },
|
|
14
14
|
smtp: { title: "Email (SMTP)", description: "Configure email delivery" },
|
|
15
15
|
database: { title: "Database", description: "Choose your database setup" },
|
|
16
16
|
"database-creds": {
|
|
17
17
|
title: "Database Credentials",
|
|
18
18
|
description: "Configure database access",
|
|
19
19
|
},
|
|
20
|
-
|
|
21
|
-
title: "
|
|
22
|
-
description: "
|
|
20
|
+
"external-services": {
|
|
21
|
+
title: "External Services",
|
|
22
|
+
description: "Use managed Redis/Kafka (optional)",
|
|
23
23
|
},
|
|
24
24
|
features: {
|
|
25
25
|
title: "Optional Features",
|
|
26
26
|
description: "Enable additional features",
|
|
27
27
|
},
|
|
28
|
+
storage: {
|
|
29
|
+
title: "Storage & Backups",
|
|
30
|
+
description: "Configure object storage and database backups",
|
|
31
|
+
},
|
|
32
|
+
observability: {
|
|
33
|
+
title: "Observability",
|
|
34
|
+
description: "Choose built-in ClickStack or export to your own systems",
|
|
35
|
+
},
|
|
28
36
|
"feature-config": {
|
|
29
37
|
title: "Feature Settings",
|
|
30
38
|
description: "Configure enabled features",
|
|
@@ -35,12 +43,12 @@ const STEP_INFO = {
|
|
|
35
43
|
},
|
|
36
44
|
review: { title: "Review & Save", description: "Review your configuration" },
|
|
37
45
|
};
|
|
38
|
-
function WizardStepController({ onSaveComplete }) {
|
|
39
|
-
const { state, dispatch, toConfig } = useWizard();
|
|
46
|
+
function WizardStepController({ mode, onSaveComplete, }) {
|
|
47
|
+
const { state, dispatch, toConfig, configIssues } = useWizard();
|
|
40
48
|
const { exit } = useApp();
|
|
41
49
|
const { write } = useStdout();
|
|
42
50
|
const { colors } = useTheme();
|
|
43
|
-
const [currentStep, setCurrentStep] = useState(
|
|
51
|
+
const [currentStep, setCurrentStep] = useState(mode === "redeploy" ? "domain" : "cloud");
|
|
44
52
|
const [saving, setSaving] = useState(false);
|
|
45
53
|
const [complete, setComplete] = useState(false);
|
|
46
54
|
const [error, setError] = useState(null);
|
|
@@ -53,39 +61,22 @@ function WizardStepController({ onSaveComplete }) {
|
|
|
53
61
|
}, [complete, write]);
|
|
54
62
|
// Track pending navigation to handle React's async state updates
|
|
55
63
|
const [pendingNav, setPendingNav] = useState(null);
|
|
64
|
+
// Direction of the last navigation, so multi-substep steps can resume at their
|
|
65
|
+
// end when the user navigates back into them (e.g. Esc from the Storage step).
|
|
66
|
+
const [navDirection, setNavDirection] = useState("forward");
|
|
56
67
|
// Get list of active steps based on config
|
|
57
68
|
const getActiveSteps = useCallback(() => {
|
|
58
|
-
|
|
59
|
-
// Cloud provider step for both provision and existing modes
|
|
60
|
-
if (state.infrastructureMode === "provision" ||
|
|
61
|
-
state.infrastructureMode === "existing") {
|
|
62
|
-
steps.push("cloud");
|
|
63
|
-
}
|
|
64
|
-
steps.push("domain", "smtp", "database");
|
|
65
|
-
// Database credentials only for self-hosted
|
|
66
|
-
if (state.databaseType === "self-hosted") {
|
|
67
|
-
steps.push("database-creds");
|
|
68
|
-
}
|
|
69
|
-
if (state.infrastructureMode === "provision") {
|
|
70
|
-
steps.push("tier");
|
|
71
|
-
}
|
|
72
|
-
steps.push("features");
|
|
73
|
-
// Feature config only if AI, SSO, monitoring, external logging, or custom emails enabled
|
|
74
|
-
if (state.aiEnabled ||
|
|
75
|
-
state.ssoEnabled ||
|
|
76
|
-
state.monitoringEnabled ||
|
|
77
|
-
state.loggingSink !== "console" ||
|
|
78
|
-
state.customEmailsEnabled) {
|
|
79
|
-
steps.push("feature-config");
|
|
80
|
-
}
|
|
81
|
-
steps.push("version", "review");
|
|
82
|
-
return steps;
|
|
69
|
+
return getActiveWizardSteps(state, mode);
|
|
83
70
|
}, [
|
|
84
|
-
|
|
71
|
+
mode,
|
|
85
72
|
state.databaseType,
|
|
86
73
|
state.aiEnabled,
|
|
87
74
|
state.ssoEnabled,
|
|
88
|
-
state.
|
|
75
|
+
state.clickStackEnabled,
|
|
76
|
+
state.metricsExportEnabled,
|
|
77
|
+
state.tracingEnabled,
|
|
78
|
+
state.appLogsEnabled,
|
|
79
|
+
state.valkeyAdminEnabled,
|
|
89
80
|
state.loggingSink,
|
|
90
81
|
state.customEmailsEnabled,
|
|
91
82
|
]);
|
|
@@ -95,9 +86,11 @@ function WizardStepController({ onSaveComplete }) {
|
|
|
95
86
|
const steps = getActiveSteps();
|
|
96
87
|
const currentIndex = steps.indexOf(currentStep);
|
|
97
88
|
if (pendingNav === "next" && currentIndex < steps.length - 1) {
|
|
89
|
+
setNavDirection("forward");
|
|
98
90
|
setCurrentStep(steps[currentIndex + 1]);
|
|
99
91
|
}
|
|
100
92
|
else if (pendingNav === "back" && currentIndex > 0) {
|
|
93
|
+
setNavDirection("back");
|
|
101
94
|
setCurrentStep(steps[currentIndex - 1]);
|
|
102
95
|
}
|
|
103
96
|
setPendingNav(null);
|
|
@@ -111,17 +104,36 @@ function WizardStepController({ onSaveComplete }) {
|
|
|
111
104
|
setPendingNav("back");
|
|
112
105
|
}, []);
|
|
113
106
|
const handleSave = useCallback(async () => {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
: state.
|
|
117
|
-
|
|
107
|
+
const config = toConfig({
|
|
108
|
+
nodeArchitecture: state.nodeArchitecture || undefined,
|
|
109
|
+
arm64TolerationRequired: state.arm64TolerationRequired,
|
|
110
|
+
storageClass: state.storageClass || undefined,
|
|
111
|
+
storageProvisioner: state.storageProvisioner || undefined,
|
|
112
|
+
schedulableNodeCount: state.schedulableNodeCount || undefined,
|
|
113
|
+
totalCpuCores: state.totalCpuCores || undefined,
|
|
114
|
+
totalMemoryGi: state.totalMemoryGi || undefined,
|
|
115
|
+
eligibleCpuCores: state.eligibleCpuCores || undefined,
|
|
116
|
+
eligibleMemoryGi: state.eligibleMemoryGi || undefined,
|
|
117
|
+
totalPersistentStorageGi: state.totalPersistentStorageGi || undefined,
|
|
118
|
+
});
|
|
118
119
|
if (!config) {
|
|
119
|
-
|
|
120
|
+
const issues = configIssues();
|
|
121
|
+
setError(issues.length > 0
|
|
122
|
+
? `Configuration is incomplete:\n${issues.map((i) => ` • ${i}`).join("\n")}`
|
|
123
|
+
: "Invalid configuration - please check all required fields");
|
|
120
124
|
return;
|
|
121
125
|
}
|
|
122
126
|
setSaving(true);
|
|
123
127
|
try {
|
|
124
128
|
if (await deploymentExists(config.name)) {
|
|
129
|
+
if (mode === "redeploy") {
|
|
130
|
+
await saveRedeployValues(config);
|
|
131
|
+
await saveDeploymentConfig(config);
|
|
132
|
+
const profileData = extractProfileFromConfig(config);
|
|
133
|
+
await updateProfile(profileData);
|
|
134
|
+
onSaveComplete?.();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
125
137
|
setError(`Deployment "${config.name}" already exists. Choose a different name.`);
|
|
126
138
|
setSaving(false);
|
|
127
139
|
return;
|
|
@@ -139,7 +151,17 @@ function WizardStepController({ onSaveComplete }) {
|
|
|
139
151
|
setError(err instanceof Error ? err.message : "Failed to save configuration");
|
|
140
152
|
setSaving(false);
|
|
141
153
|
}
|
|
142
|
-
}, [toConfig, exit, onSaveComplete]);
|
|
154
|
+
}, [toConfig, configIssues, state, exit, onSaveComplete, mode]);
|
|
155
|
+
async function saveRedeployValues(config) {
|
|
156
|
+
if (!config)
|
|
157
|
+
return;
|
|
158
|
+
const existingValues = (await loadHelmValues(config.name)) ?? {};
|
|
159
|
+
const generatedValues = buildHelmValues(config);
|
|
160
|
+
const mergedValues = mergeHelmValues(existingValues, generatedValues);
|
|
161
|
+
// Guardrail: a merge with stale manual edits must still satisfy the chart.
|
|
162
|
+
assertValidHelmValues(mergedValues);
|
|
163
|
+
await saveHelmValues(config.name, mergedValues);
|
|
164
|
+
}
|
|
143
165
|
// Get step progress
|
|
144
166
|
const steps = getActiveSteps();
|
|
145
167
|
const stepNumber = steps.indexOf(currentStep) + 1;
|
|
@@ -160,8 +182,6 @@ function WizardStepController({ onSaveComplete }) {
|
|
|
160
182
|
// Render current step
|
|
161
183
|
const renderStep = () => {
|
|
162
184
|
switch (currentStep) {
|
|
163
|
-
case "mode":
|
|
164
|
-
return _jsx(DeploymentModeStep, { onComplete: goNext });
|
|
165
185
|
case "cloud":
|
|
166
186
|
return _jsx(CloudProviderStep, { onComplete: goNext, onBack: goBack });
|
|
167
187
|
case "domain":
|
|
@@ -172,23 +192,27 @@ function WizardStepController({ onSaveComplete }) {
|
|
|
172
192
|
return _jsx(DatabaseStep, { onComplete: goNext, onBack: goBack });
|
|
173
193
|
case "database-creds":
|
|
174
194
|
return _jsx(SupabaseCredentialsStep, { onComplete: goNext, onBack: goBack });
|
|
175
|
-
case "
|
|
176
|
-
return _jsx(
|
|
195
|
+
case "external-services":
|
|
196
|
+
return _jsx(ExternalServicesStep, { onComplete: goNext, onBack: goBack });
|
|
177
197
|
case "features":
|
|
178
198
|
return _jsx(FeaturesStep, { onComplete: goNext, onBack: goBack });
|
|
199
|
+
case "storage":
|
|
200
|
+
return _jsx(StorageStep, { onComplete: goNext, onBack: goBack });
|
|
201
|
+
case "observability":
|
|
202
|
+
return _jsx(ObservabilityStep, { onComplete: goNext, onBack: goBack });
|
|
179
203
|
case "feature-config":
|
|
180
|
-
return _jsx(FeatureConfigStep, { onComplete: goNext, onBack: goBack });
|
|
204
|
+
return (_jsx(FeatureConfigStep, { onComplete: goNext, onBack: goBack, entryDirection: navDirection }));
|
|
181
205
|
case "version":
|
|
182
206
|
return _jsx(VersionStep, { onComplete: goNext, onBack: goBack });
|
|
183
207
|
case "review":
|
|
184
|
-
return _jsx(ReviewStep, { onComplete: handleSave, onBack: goBack });
|
|
208
|
+
return (_jsx(ReviewStep, { onComplete: handleSave, onBack: goBack, allowEditName: mode === "create" }));
|
|
185
209
|
default:
|
|
186
210
|
return null;
|
|
187
211
|
}
|
|
188
212
|
};
|
|
189
213
|
return (_jsxs(AppShell, { title: "Rulebricks Configuration", children: [_jsx(ProgressHeader, { currentStep: stepNumber, totalSteps: totalSteps, stepTitle: stepInfo?.title || "Complete" }), _jsx(Box, { marginTop: 1, children: renderStep() })] }));
|
|
190
214
|
}
|
|
191
|
-
export function InitWizard({ initialName, profile: providedProfile, }) {
|
|
215
|
+
export function InitWizard({ initialName, initialState, mode = "create", onSaveComplete, profile: providedProfile, }) {
|
|
192
216
|
const [profile, setProfile] = useState(providedProfile ?? null);
|
|
193
217
|
const [profileLoaded, setProfileLoaded] = useState(!!providedProfile);
|
|
194
218
|
// Load profile on mount if not provided
|
|
@@ -204,5 +228,5 @@ export function InitWizard({ initialName, profile: providedProfile, }) {
|
|
|
204
228
|
if (!profileLoaded) {
|
|
205
229
|
return (_jsxs(ThemeProvider, { theme: "init", children: [_jsx(Logo, {}), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { children: "Loading..." }) })] }));
|
|
206
230
|
}
|
|
207
|
-
return (_jsxs(ThemeProvider, { theme: "init", children: [_jsx(Logo, {}), _jsx(WizardProvider, { initialName: initialName, profile: profile, children: _jsx(WizardStepController, {}) })] }));
|
|
231
|
+
return (_jsxs(ThemeProvider, { theme: "init", children: [_jsx(Logo, {}), _jsx(CommandApprovalProvider, { children: _jsx(WizardProvider, { initialName: initialName, initialState: initialState, profile: profile, children: _jsx(WizardStepController, { mode: mode, onSaveComplete: onSaveComplete }) }) })] }));
|
|
208
232
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ListCommand(): import("react/jsx-runtime").JSX.Element;
|