@rulebricks/cli 1.9.0

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 (93) hide show
  1. package/README.md +62 -0
  2. package/dist/commands/clone.d.ts +6 -0
  3. package/dist/commands/clone.js +60 -0
  4. package/dist/commands/deploy.d.ts +8 -0
  5. package/dist/commands/deploy.js +409 -0
  6. package/dist/commands/destroy.d.ts +8 -0
  7. package/dist/commands/destroy.js +298 -0
  8. package/dist/commands/init.d.ts +7 -0
  9. package/dist/commands/init.js +201 -0
  10. package/dist/commands/logs.d.ts +9 -0
  11. package/dist/commands/logs.js +222 -0
  12. package/dist/commands/open.d.ts +7 -0
  13. package/dist/commands/open.js +139 -0
  14. package/dist/commands/status.d.ts +5 -0
  15. package/dist/commands/status.js +125 -0
  16. package/dist/commands/upgrade.d.ts +7 -0
  17. package/dist/commands/upgrade.js +239 -0
  18. package/dist/components/DNSWaitScreen.d.ts +9 -0
  19. package/dist/components/DNSWaitScreen.js +73 -0
  20. package/dist/components/Wizard/WizardContext.d.ts +176 -0
  21. package/dist/components/Wizard/WizardContext.js +346 -0
  22. package/dist/components/Wizard/index.d.ts +2 -0
  23. package/dist/components/Wizard/index.js +2 -0
  24. package/dist/components/Wizard/steps/CloudProviderStep.d.ts +6 -0
  25. package/dist/components/Wizard/steps/CloudProviderStep.js +210 -0
  26. package/dist/components/Wizard/steps/CredentialsStep.d.ts +6 -0
  27. package/dist/components/Wizard/steps/CredentialsStep.js +22 -0
  28. package/dist/components/Wizard/steps/DatabaseStep.d.ts +6 -0
  29. package/dist/components/Wizard/steps/DatabaseStep.js +80 -0
  30. package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +5 -0
  31. package/dist/components/Wizard/steps/DeploymentModeStep.js +26 -0
  32. package/dist/components/Wizard/steps/DomainStep.d.ts +6 -0
  33. package/dist/components/Wizard/steps/DomainStep.js +126 -0
  34. package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +6 -0
  35. package/dist/components/Wizard/steps/FeatureConfigStep.js +765 -0
  36. package/dist/components/Wizard/steps/FeaturesStep.d.ts +6 -0
  37. package/dist/components/Wizard/steps/FeaturesStep.js +119 -0
  38. package/dist/components/Wizard/steps/ReviewStep.d.ts +6 -0
  39. package/dist/components/Wizard/steps/ReviewStep.js +56 -0
  40. package/dist/components/Wizard/steps/SMTPStep.d.ts +6 -0
  41. package/dist/components/Wizard/steps/SMTPStep.js +191 -0
  42. package/dist/components/Wizard/steps/SupabaseCredentialsStep.d.ts +6 -0
  43. package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +76 -0
  44. package/dist/components/Wizard/steps/TierStep.d.ts +6 -0
  45. package/dist/components/Wizard/steps/TierStep.js +29 -0
  46. package/dist/components/Wizard/steps/VersionStep.d.ts +6 -0
  47. package/dist/components/Wizard/steps/VersionStep.js +113 -0
  48. package/dist/components/Wizard/steps/index.d.ts +12 -0
  49. package/dist/components/Wizard/steps/index.js +12 -0
  50. package/dist/components/common/AppShell.d.ts +31 -0
  51. package/dist/components/common/AppShell.js +31 -0
  52. package/dist/components/common/Box.d.ts +20 -0
  53. package/dist/components/common/Box.js +20 -0
  54. package/dist/components/common/Logo.d.ts +7 -0
  55. package/dist/components/common/Logo.js +22 -0
  56. package/dist/components/common/Spinner.d.ts +12 -0
  57. package/dist/components/common/Spinner.js +28 -0
  58. package/dist/components/common/index.d.ts +6 -0
  59. package/dist/components/common/index.js +5 -0
  60. package/dist/index.d.ts +2 -0
  61. package/dist/index.js +202 -0
  62. package/dist/lib/cloudCli.d.ts +156 -0
  63. package/dist/lib/cloudCli.js +691 -0
  64. package/dist/lib/config.d.ts +91 -0
  65. package/dist/lib/config.js +278 -0
  66. package/dist/lib/dns.d.ts +41 -0
  67. package/dist/lib/dns.js +235 -0
  68. package/dist/lib/dockerHub.d.ts +57 -0
  69. package/dist/lib/dockerHub.js +128 -0
  70. package/dist/lib/helm.d.ts +53 -0
  71. package/dist/lib/helm.js +209 -0
  72. package/dist/lib/helmValues.d.ts +17 -0
  73. package/dist/lib/helmValues.js +693 -0
  74. package/dist/lib/kubernetes.d.ts +161 -0
  75. package/dist/lib/kubernetes.js +755 -0
  76. package/dist/lib/terraform.d.ts +44 -0
  77. package/dist/lib/terraform.js +230 -0
  78. package/dist/lib/theme.d.ts +81 -0
  79. package/dist/lib/theme.js +115 -0
  80. package/dist/lib/validation.d.ts +47 -0
  81. package/dist/lib/validation.js +164 -0
  82. package/dist/lib/versions.d.ts +69 -0
  83. package/dist/lib/versions.js +139 -0
  84. package/dist/types/index.d.ts +718 -0
  85. package/dist/types/index.js +556 -0
  86. package/email-templates/email_change.html +325 -0
  87. package/email-templates/invite.html +383 -0
  88. package/email-templates/password_change.html +414 -0
  89. package/email-templates/verify.html +396 -0
  90. package/package.json +78 -0
  91. package/terraform/aws/main.tf +327 -0
  92. package/terraform/azure/main.tf +326 -0
  93. package/terraform/gcp/main.tf +369 -0
@@ -0,0 +1,298 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React, { useState, useCallback } from "react";
3
+ import { Box, Text, useApp, useInput } from "ink";
4
+ import { BorderBox, Spinner, StatusLine, ThemeProvider, useTheme, Logo, } from "../components/common/index.js";
5
+ import { loadDeploymentConfig, loadDeploymentState, deleteDeployment, deploymentExists, } from "../lib/config.js";
6
+ import { uninstallChart, getInstalledVersion } from "../lib/helm.js";
7
+ import { terraformDestroy, hasTerraformState } from "../lib/terraform.js";
8
+ import { deleteNamespace, deletePVCs, isClusterAccessible, namespaceExists, removeKedaFinalizers, } from "../lib/kubernetes.js";
9
+ import { getNamespace, getReleaseName, } from "../types/index.js";
10
+ function DestroyCommandInner({ name, cluster, config, force, }) {
11
+ const { exit } = useApp();
12
+ const { colors } = useTheme();
13
+ const [step, setStep] = useState("loading");
14
+ const [deploymentConfig, setDeploymentConfig] = useState(null);
15
+ const [state, setState] = useState(null);
16
+ const [scope, setScope] = useState(null);
17
+ const [error, setError] = useState(null);
18
+ const [confirmText, setConfirmText] = useState("");
19
+ const [status, setStatus] = useState({
20
+ helm: "pending",
21
+ pvc: "pending",
22
+ namespace: "pending",
23
+ infrastructure: "pending",
24
+ cleanup: "pending",
25
+ });
26
+ // Load config and determine scope on mount
27
+ React.useEffect(() => {
28
+ (async () => {
29
+ try {
30
+ // Check if deployment exists
31
+ const exists = await deploymentExists(name);
32
+ if (!exists) {
33
+ setError(`Deployment "${name}" not found`);
34
+ setStep("error");
35
+ return;
36
+ }
37
+ // Load config (may throw if corrupted)
38
+ let cfg = null;
39
+ try {
40
+ cfg = await loadDeploymentConfig(name);
41
+ setDeploymentConfig(cfg);
42
+ }
43
+ catch {
44
+ // Config might be corrupted or missing, that's OK for destroy
45
+ }
46
+ // Load state
47
+ const st = await loadDeploymentState(name);
48
+ setState(st);
49
+ // Determine what was actually deployed
50
+ const deploymentScope = await determineScope(name, cfg, st);
51
+ setScope(deploymentScope);
52
+ if (force) {
53
+ setStep("destroying");
54
+ runDestroy(cfg, st, deploymentScope);
55
+ }
56
+ else {
57
+ setStep("confirm");
58
+ }
59
+ }
60
+ catch (err) {
61
+ setError(err instanceof Error ? err.message : "Failed to load deployment");
62
+ setStep("error");
63
+ }
64
+ })();
65
+ }, [name, force]);
66
+ useInput((input, key) => {
67
+ if (step === "confirm") {
68
+ if (key.return) {
69
+ if (cluster && scope?.hasInfrastructure) {
70
+ if (confirmText === "destroy-all") {
71
+ setStep("destroying");
72
+ runDestroy(deploymentConfig, state, scope);
73
+ }
74
+ }
75
+ else {
76
+ setStep("destroying");
77
+ runDestroy(deploymentConfig, state, scope);
78
+ }
79
+ }
80
+ else if (key.escape) {
81
+ exit();
82
+ }
83
+ else if (key.backspace || key.delete) {
84
+ setConfirmText((t) => t.slice(0, -1));
85
+ }
86
+ else if (input && !key.ctrl && !key.meta) {
87
+ setConfirmText((t) => t + input);
88
+ }
89
+ }
90
+ else if (step === "error") {
91
+ if (key.escape || key.return) {
92
+ exit();
93
+ }
94
+ }
95
+ });
96
+ const runDestroy = useCallback(async (cfg, st, deploymentScope) => {
97
+ try {
98
+ // Use namespace from state if available (backwards compat), otherwise compute from deployment name
99
+ const namespace = st?.application?.namespace || getNamespace(name);
100
+ const releaseName = getReleaseName(name);
101
+ // Run cluster cleanup if cluster is accessible
102
+ if (deploymentScope.clusterAccessible) {
103
+ // Step 1: Uninstall Helm release (only if namespace exists - helm data is stored there)
104
+ if (deploymentScope.hasHelmRelease && deploymentScope.hasNamespace) {
105
+ setStatus((s) => ({ ...s, helm: "running" }));
106
+ try {
107
+ await uninstallChart(releaseName, namespace, { wait: false });
108
+ setStatus((s) => ({ ...s, helm: "success" }));
109
+ }
110
+ catch {
111
+ // Helm release might already be gone, continue anyway
112
+ setStatus((s) => ({ ...s, helm: "error" }));
113
+ }
114
+ }
115
+ else {
116
+ // Skip if no helm release OR namespace is already gone
117
+ setStatus((s) => ({ ...s, helm: "skipped" }));
118
+ }
119
+ // Step 2: Delete all PVCs in the namespace
120
+ if (deploymentScope.hasNamespace) {
121
+ setStatus((s) => ({ ...s, pvc: "running" }));
122
+ try {
123
+ await deletePVCs(namespace);
124
+ setStatus((s) => ({ ...s, pvc: "success" }));
125
+ }
126
+ catch {
127
+ // PVCs might not exist, continue anyway
128
+ setStatus((s) => ({ ...s, pvc: "error" }));
129
+ }
130
+ }
131
+ else {
132
+ setStatus((s) => ({ ...s, pvc: "skipped" }));
133
+ }
134
+ // Step 3: Delete namespace
135
+ if (deploymentScope.hasNamespace) {
136
+ setStatus((s) => ({ ...s, namespace: "running" }));
137
+ try {
138
+ // Remove KEDA finalizers first to prevent namespace deletion from hanging
139
+ // KEDA finalizers wait for KEDA controller, but it's being deleted too
140
+ await removeKedaFinalizers(namespace);
141
+ await deleteNamespace(namespace);
142
+ setStatus((s) => ({ ...s, namespace: "success" }));
143
+ }
144
+ catch {
145
+ // Namespace might already be gone
146
+ setStatus((s) => ({ ...s, namespace: "error" }));
147
+ }
148
+ }
149
+ else {
150
+ setStatus((s) => ({ ...s, namespace: "skipped" }));
151
+ }
152
+ }
153
+ else {
154
+ // Cluster not accessible - skip all cluster operations
155
+ setStatus((s) => ({
156
+ ...s,
157
+ helm: "skipped",
158
+ pvc: "skipped",
159
+ namespace: "skipped",
160
+ }));
161
+ }
162
+ // Destroy infrastructure if requested and it exists
163
+ if (cluster && deploymentScope.hasInfrastructure) {
164
+ setStatus((s) => ({ ...s, infrastructure: "running" }));
165
+ try {
166
+ await terraformDestroy(name);
167
+ setStatus((s) => ({ ...s, infrastructure: "success" }));
168
+ }
169
+ catch {
170
+ setStatus((s) => ({ ...s, infrastructure: "error" }));
171
+ }
172
+ }
173
+ else {
174
+ setStatus((s) => ({ ...s, infrastructure: "skipped" }));
175
+ }
176
+ // Clean up local files (only if --config flag is passed)
177
+ if (config && deploymentScope.hasLocalFiles) {
178
+ setStatus((s) => ({ ...s, cleanup: "running" }));
179
+ try {
180
+ await deleteDeployment(name);
181
+ setStatus((s) => ({ ...s, cleanup: "success" }));
182
+ }
183
+ catch {
184
+ setStatus((s) => ({ ...s, cleanup: "error" }));
185
+ }
186
+ }
187
+ else {
188
+ setStatus((s) => ({ ...s, cleanup: "skipped" }));
189
+ }
190
+ setStep("complete");
191
+ setTimeout(() => exit(), 3000);
192
+ }
193
+ catch (err) {
194
+ setError(err instanceof Error ? err.message : "Destruction failed");
195
+ setStep("error");
196
+ }
197
+ }, [name, cluster, exit]);
198
+ // Loading screen
199
+ if (step === "loading") {
200
+ return (_jsx(BorderBox, { title: `Destroying ${name}`, children: _jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Checking deployment state..." }) }) }));
201
+ }
202
+ // Error screen
203
+ if (step === "error") {
204
+ 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" }) })] }) }));
205
+ }
206
+ // Complete screen
207
+ if (step === "complete") {
208
+ const cleanedItems = [];
209
+ if (status.helm === "success")
210
+ cleanedItems.push("Helm release");
211
+ if (status.pvc === "success")
212
+ cleanedItems.push("Persistent volume claims");
213
+ if (status.namespace === "success")
214
+ cleanedItems.push("Kubernetes namespace");
215
+ if (status.infrastructure === "success")
216
+ cleanedItems.push("Cloud infrastructure");
217
+ if (status.cleanup === "success")
218
+ cleanedItems.push("Local configuration files");
219
+ // Check if nothing was cleaned in cluster (no helm, no pvc, no namespace)
220
+ const noClusterCleanup = status.helm === "skipped" &&
221
+ status.pvc === "skipped" &&
222
+ status.namespace === "skipped";
223
+ 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, "/"] }) }))] }) }));
224
+ }
225
+ // Destroying screen
226
+ if (step === "destroying") {
227
+ // Show cluster operations if cluster is accessible
228
+ const showClusterOps = scope?.clusterAccessible;
229
+ const showInfra = cluster && scope?.hasInfrastructure;
230
+ 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..." }) })] }) }));
231
+ }
232
+ // Confirmation screen
233
+ // Check if there's nothing in the cluster to clean up
234
+ const hasClusterResources = scope?.hasHelmRelease || scope?.hasNamespace;
235
+ const onlyLocalFiles = !hasClusterResources && !scope?.hasInfrastructure;
236
+ const needsInfraConfirm = cluster && scope?.hasInfrastructure;
237
+ const willDeleteConfig = config && scope?.hasLocalFiles;
238
+ // Nothing to do if only local files exist but --config not passed
239
+ if (onlyLocalFiles && !config) {
240
+ 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" }) })] }) }));
241
+ }
242
+ return (_jsx(BorderBox, { title: "Confirm Destruction", children: _jsx(Box, { flexDirection: "column", marginY: 1, children: onlyLocalFiles && config ? (
243
+ // Only cleaning local files (with --config)
244
+ _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" }) })] })) : (
245
+ // Full destruction
246
+ _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" })), !needsInfraConfirm && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Cloud infrastructure will be preserved. Use --cluster to remove it." }) })), !willDeleteConfig && (_jsx(Box, { marginTop: !needsInfraConfirm ? 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. 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" }) }))] })) }) }));
247
+ }
248
+ export function DestroyCommand(props) {
249
+ return (_jsxs(ThemeProvider, { theme: "destroy", children: [_jsx(Logo, {}), _jsx(DestroyCommandInner, { ...props })] }));
250
+ }
251
+ /**
252
+ * Determines what actually exists by checking cluster state directly.
253
+ * This ensures cleanup works even if local state is out of sync.
254
+ */
255
+ async function determineScope(name, config, state) {
256
+ // Check if we have local files (we do, since we loaded the deployment)
257
+ const hasLocalFiles = true;
258
+ // Check if infrastructure was provisioned (from local terraform state)
259
+ const hasInfrastructure = await hasTerraformState(name);
260
+ // Use namespace from state if available (backwards compat), otherwise compute from deployment name
261
+ const namespace = state?.application?.namespace || getNamespace(name);
262
+ const releaseName = getReleaseName(name);
263
+ // Check if cluster is accessible
264
+ let clusterAccessible = false;
265
+ try {
266
+ clusterAccessible = await isClusterAccessible();
267
+ }
268
+ catch {
269
+ clusterAccessible = false;
270
+ }
271
+ // If cluster is accessible, check what actually exists in the cluster
272
+ let hasHelmRelease = false;
273
+ let hasNamespace = false;
274
+ if (clusterAccessible) {
275
+ // Check if Helm release actually exists in the cluster
276
+ try {
277
+ const installedVersion = await getInstalledVersion(releaseName, namespace);
278
+ hasHelmRelease = installedVersion !== null;
279
+ }
280
+ catch {
281
+ hasHelmRelease = false;
282
+ }
283
+ // Check if namespace exists
284
+ try {
285
+ hasNamespace = await namespaceExists(namespace);
286
+ }
287
+ catch {
288
+ hasNamespace = false;
289
+ }
290
+ }
291
+ return {
292
+ hasLocalFiles,
293
+ hasHelmRelease,
294
+ hasNamespace,
295
+ hasInfrastructure,
296
+ clusterAccessible,
297
+ };
298
+ }
@@ -0,0 +1,7 @@
1
+ import { ProfileConfig } from "../types/index.js";
2
+ interface InitWizardProps {
3
+ initialName?: string;
4
+ profile?: ProfileConfig | null;
5
+ }
6
+ export declare function InitWizard({ initialName, profile: providedProfile, }: InitWizardProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,201 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback, useEffect } from "react";
3
+ import { Box, Text, useApp, useStdout } from "ink";
4
+ import { WizardProvider, useWizard, } from "../components/Wizard/WizardContext.js";
5
+ import { DeploymentModeStep, CloudProviderStep, DomainStep, SMTPStep, DatabaseStep, SupabaseCredentialsStep, TierStep, FeaturesStep, FeatureConfigStep, VersionStep, ReviewStep, } from "../components/Wizard/steps/index.js";
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
+ const STEP_INFO = {
10
+ mode: { title: "Deployment Mode", description: "Choose how to deploy" },
11
+ cloud: { title: "Cloud Provider", description: "Select your cloud provider" },
12
+ domain: { title: "Domain & Email", description: "Configure your domain" },
13
+ smtp: { title: "Email (SMTP)", description: "Configure email delivery" },
14
+ database: { title: "Database", description: "Choose your database setup" },
15
+ "database-creds": {
16
+ title: "Database Credentials",
17
+ description: "Configure database access",
18
+ },
19
+ tier: {
20
+ title: "Performance Tier",
21
+ description: "Select your deployment size",
22
+ },
23
+ features: {
24
+ title: "Optional Features",
25
+ description: "Enable additional features",
26
+ },
27
+ "feature-config": {
28
+ title: "Feature Settings",
29
+ description: "Configure enabled features",
30
+ },
31
+ version: {
32
+ title: "License & Version",
33
+ description: "Enter license and select version",
34
+ },
35
+ review: { title: "Review & Save", description: "Review your configuration" },
36
+ };
37
+ function WizardStepController({ onSaveComplete }) {
38
+ const { state, dispatch, toConfig } = useWizard();
39
+ const { exit } = useApp();
40
+ const { write } = useStdout();
41
+ const { colors } = useTheme();
42
+ const [currentStep, setCurrentStep] = useState("mode");
43
+ const [saving, setSaving] = useState(false);
44
+ const [complete, setComplete] = useState(false);
45
+ const [error, setError] = useState(null);
46
+ // Clear terminal when transitioning to completion screen
47
+ useEffect(() => {
48
+ if (complete) {
49
+ // Clear terminal using ANSI escape codes
50
+ write("\x1B[2J\x1B[0;0H");
51
+ }
52
+ }, [complete, write]);
53
+ // Track pending navigation to handle React's async state updates
54
+ const [pendingNav, setPendingNav] = useState(null);
55
+ // Get list of active steps based on config
56
+ const getActiveSteps = useCallback(() => {
57
+ const steps = ["mode"];
58
+ // Cloud provider step for both provision and existing modes
59
+ if (state.infrastructureMode === "provision" ||
60
+ state.infrastructureMode === "existing") {
61
+ steps.push("cloud");
62
+ }
63
+ steps.push("domain", "smtp", "database");
64
+ // Database credentials only for self-hosted
65
+ if (state.databaseType === "self-hosted") {
66
+ steps.push("database-creds");
67
+ }
68
+ steps.push("tier", "features");
69
+ // Feature config only if AI, SSO, monitoring, external logging, or custom emails enabled
70
+ if (state.aiEnabled ||
71
+ state.ssoEnabled ||
72
+ state.monitoringEnabled ||
73
+ state.loggingSink !== "console" ||
74
+ state.customEmailsEnabled) {
75
+ steps.push("feature-config");
76
+ }
77
+ steps.push("version", "review");
78
+ return steps;
79
+ }, [
80
+ state.infrastructureMode,
81
+ state.databaseType,
82
+ state.aiEnabled,
83
+ state.ssoEnabled,
84
+ state.monitoringEnabled,
85
+ state.loggingSink,
86
+ state.customEmailsEnabled,
87
+ ]);
88
+ // Handle navigation after state updates - this ensures getActiveSteps has the latest state
89
+ useEffect(() => {
90
+ if (pendingNav) {
91
+ const steps = getActiveSteps();
92
+ const currentIndex = steps.indexOf(currentStep);
93
+ if (pendingNav === "next" && currentIndex < steps.length - 1) {
94
+ setCurrentStep(steps[currentIndex + 1]);
95
+ }
96
+ else if (pendingNav === "back" && currentIndex > 0) {
97
+ setCurrentStep(steps[currentIndex - 1]);
98
+ }
99
+ setPendingNav(null);
100
+ }
101
+ }, [pendingNav, currentStep, getActiveSteps]);
102
+ // Request navigation - will be processed after React renders with updated state
103
+ const goNext = useCallback(() => {
104
+ setPendingNav("next");
105
+ }, []);
106
+ const goBack = useCallback(() => {
107
+ setPendingNav("back");
108
+ }, []);
109
+ const handleSave = useCallback(async () => {
110
+ const config = toConfig();
111
+ if (!config) {
112
+ setError("Invalid configuration - please check all required fields");
113
+ return;
114
+ }
115
+ setSaving(true);
116
+ try {
117
+ if (await deploymentExists(config.name)) {
118
+ setError(`Deployment "${config.name}" already exists. Choose a different name.`);
119
+ setSaving(false);
120
+ return;
121
+ }
122
+ await saveDeploymentConfig(config);
123
+ await generateHelmValues(config);
124
+ // Save configuration values to profile for future deployments
125
+ const profileData = extractProfileFromConfig(config);
126
+ await updateProfile(profileData);
127
+ setComplete(true);
128
+ onSaveComplete?.();
129
+ setTimeout(() => exit(), 4000);
130
+ }
131
+ catch (err) {
132
+ setError(err instanceof Error ? err.message : "Failed to save configuration");
133
+ setSaving(false);
134
+ }
135
+ }, [toConfig, exit, onSaveComplete]);
136
+ // Get step progress
137
+ const steps = getActiveSteps();
138
+ const stepNumber = steps.indexOf(currentStep) + 1;
139
+ const totalSteps = steps.length;
140
+ const stepInfo = STEP_INFO[currentStep];
141
+ // Completion screen
142
+ if (complete) {
143
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 2, children: LOGO_LINES.map((line, i) => (_jsx(Text, { color: colors.accent, children: line }, i))) }), _jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.success, bold: true, children: "\u2713 Configuration saved successfully!" }) }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: ["Deployment name:", " ", _jsx(Text, { color: colors.accent, bold: true, children: state.name })] }), _jsxs(Text, { color: colors.muted, children: ["Configuration stored in ~/.rulebricks/deployments/", state.name, "/"] })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Next steps:" }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: colors.muted, children: ["1. Run", " ", _jsxs(Text, { color: colors.accent, children: ["rulebricks deploy ", state.name] }), " ", "to deploy"] }), _jsx(Text, { color: colors.muted, children: "2. Configure your DNS records when prompted" }), _jsxs(Text, { color: colors.muted, children: ["3. Access Rulebricks at", " ", _jsxs(Text, { color: colors.accent, children: ["https://", state.domain] })] })] })] }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Exiting in a moment..." }) })] })] }));
144
+ }
145
+ // Saving state - simple, without wrapper
146
+ if (saving) {
147
+ return (_jsx(Box, { flexDirection: "column", paddingTop: 1, paddingLeft: 2, children: _jsx(Text, { color: colors.accent, children: "\u29D7 Saving configuration..." }) }));
148
+ }
149
+ // Error state - simple, without wrapper
150
+ if (error) {
151
+ return (_jsxs(Box, { flexDirection: "column", paddingTop: 1, paddingLeft: 2, 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, children: "Press Ctrl+C to exit and try again" }) })] }));
152
+ }
153
+ // Render current step
154
+ const renderStep = () => {
155
+ switch (currentStep) {
156
+ case "mode":
157
+ return _jsx(DeploymentModeStep, { onComplete: goNext });
158
+ case "cloud":
159
+ return _jsx(CloudProviderStep, { onComplete: goNext, onBack: goBack });
160
+ case "domain":
161
+ return _jsx(DomainStep, { onComplete: goNext, onBack: goBack });
162
+ case "smtp":
163
+ return _jsx(SMTPStep, { onComplete: goNext, onBack: goBack });
164
+ case "database":
165
+ return _jsx(DatabaseStep, { onComplete: goNext, onBack: goBack });
166
+ case "database-creds":
167
+ return _jsx(SupabaseCredentialsStep, { onComplete: goNext, onBack: goBack });
168
+ case "tier":
169
+ return _jsx(TierStep, { onComplete: goNext, onBack: goBack });
170
+ case "features":
171
+ return _jsx(FeaturesStep, { onComplete: goNext, onBack: goBack });
172
+ case "feature-config":
173
+ return _jsx(FeatureConfigStep, { onComplete: goNext, onBack: goBack });
174
+ case "version":
175
+ return _jsx(VersionStep, { onComplete: goNext, onBack: goBack });
176
+ case "review":
177
+ return _jsx(ReviewStep, { onComplete: handleSave, onBack: goBack });
178
+ default:
179
+ return null;
180
+ }
181
+ };
182
+ return (_jsxs(AppShell, { title: "Rulebricks Configuration", children: [_jsx(ProgressHeader, { currentStep: stepNumber, totalSteps: totalSteps, stepTitle: stepInfo?.title || "Complete" }), _jsx(Box, { marginTop: 1, children: renderStep() })] }));
183
+ }
184
+ export function InitWizard({ initialName, profile: providedProfile, }) {
185
+ const [profile, setProfile] = useState(providedProfile ?? null);
186
+ const [profileLoaded, setProfileLoaded] = useState(!!providedProfile);
187
+ // Load profile on mount if not provided
188
+ useEffect(() => {
189
+ if (!providedProfile) {
190
+ loadProfile().then((loaded) => {
191
+ setProfile(loaded);
192
+ setProfileLoaded(true);
193
+ });
194
+ }
195
+ }, [providedProfile]);
196
+ // Show loading state while profile is being loaded
197
+ if (!profileLoaded) {
198
+ return (_jsxs(ThemeProvider, { theme: "init", children: [_jsx(Logo, {}), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { children: "Loading..." }) })] }));
199
+ }
200
+ return (_jsxs(ThemeProvider, { theme: "init", children: [_jsx(Logo, {}), _jsx(WizardProvider, { initialName: initialName, profile: profile, children: _jsx(WizardStepController, {}) })] }));
201
+ }
@@ -0,0 +1,9 @@
1
+ interface LogsCommandProps {
2
+ name: string;
3
+ component: string;
4
+ follow?: boolean;
5
+ tail?: number;
6
+ split?: boolean;
7
+ }
8
+ export declare function LogsCommand(props: LogsCommandProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};