@rulebricks/cli 2.0.0 → 2.0.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.
- package/README.md +3 -3
- package/benchmarks/README.md +98 -0
- package/benchmarks/Test Flow.rbf +4088 -0
- package/benchmarks/benchmark-flow.json +26 -0
- package/benchmarks/lib/payload.js +101 -0
- package/benchmarks/lib/report.js +929 -0
- package/benchmarks/qps-test.js +136 -0
- package/benchmarks/run-qps-test.sh +115 -0
- package/benchmarks/run-throughput-test.sh +123 -0
- package/benchmarks/throughput-report.html +632 -0
- package/benchmarks/throughput-results.json +298 -0
- package/benchmarks/throughput-test.js +159 -0
- package/dist/commands/benchmark.d.ts +11 -0
- package/dist/commands/benchmark.js +173 -0
- package/dist/commands/deploy.js +15 -4
- package/dist/commands/destroy.js +2 -2
- package/dist/commands/logs.js +1 -0
- package/dist/components/Wizard/steps/BenchmarkSteps.d.ts +31 -0
- package/dist/components/Wizard/steps/BenchmarkSteps.js +304 -0
- package/dist/components/Wizard/steps/DatabaseStep.js +49 -35
- package/dist/index.js +42 -6
- package/dist/lib/benchmark.d.ts +63 -0
- package/dist/lib/benchmark.js +466 -0
- package/dist/lib/dns.d.ts +3 -1
- package/dist/lib/dns.js +138 -56
- package/dist/lib/helm.d.ts +14 -1
- package/dist/lib/helm.js +36 -1
- package/dist/lib/kubernetes.js +2 -0
- package/dist/types/index.d.ts +90 -0
- package/dist/types/index.js +51 -0
- package/package.json +8 -6
- package/terraform/aws/main.tf +22 -0
- package/terraform/azure/main.tf +45 -0
- package/terraform/gcp/main.tf +34 -0
- /package/{email-templates → templates}/email_change.html +0 -0
- /package/{email-templates → templates}/invite.html +0 -0
- /package/{email-templates → templates}/password_change.html +0 -0
- /package/{email-templates → templates}/verify.html +0 -0
package/dist/commands/deploy.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
3
3
|
import { Box, Text, useApp, useInput } from "ink";
|
|
4
|
+
import { platform } from "os";
|
|
4
5
|
import { BorderBox, Spinner, StatusLine, ThemeProvider, useTheme, Logo, } from "../components/common/index.js";
|
|
5
6
|
import { DNSWaitScreen } from "../components/DNSWaitScreen.js";
|
|
6
7
|
import { loadDeploymentConfig, loadDeploymentState, saveDeploymentState, updateDeploymentStatus, } from "../lib/config.js";
|
|
7
8
|
import { setupTerraformWorkspace, terraformInit, terraformPlan, terraformApply, terraformDestroy, updateKubeconfig, hasTerraformState, isTerraformInstalled, } from "../lib/terraform.js";
|
|
8
|
-
import {
|
|
9
|
+
import { installOrUpgradeChart, upgradeChart, isHelmInstalled, } from "../lib/helm.js";
|
|
9
10
|
import { isKubectlInstalled, checkClusterAccessible, } from "../lib/kubernetes.js";
|
|
10
11
|
import { generateHelmValues, updateHelmValuesForTLS, } from "../lib/helmValues.js";
|
|
11
12
|
import { isSupportedDnsProvider, getNamespace, getReleaseName, } from "../types/index.js";
|
|
@@ -194,7 +195,7 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
|
|
|
194
195
|
// SINGLE-PHASE DEPLOYMENT (External DNS)
|
|
195
196
|
// Install with TLS enabled from the start - external-dns handles DNS records
|
|
196
197
|
await generateHelmValues(cfg, { tlsEnabled: true });
|
|
197
|
-
await
|
|
198
|
+
await installOrUpgradeChart(name, {
|
|
198
199
|
releaseName,
|
|
199
200
|
namespace,
|
|
200
201
|
version,
|
|
@@ -223,7 +224,7 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
|
|
|
223
224
|
// TWO-PHASE DEPLOYMENT (Manual DNS)
|
|
224
225
|
// Phase 1: Install without TLS
|
|
225
226
|
await generateHelmValues(cfg, { tlsEnabled: false });
|
|
226
|
-
await
|
|
227
|
+
await installOrUpgradeChart(name, {
|
|
227
228
|
releaseName,
|
|
228
229
|
namespace,
|
|
229
230
|
version,
|
|
@@ -360,7 +361,7 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
|
|
|
360
361
|
// Complete screen
|
|
361
362
|
if (step === "complete") {
|
|
362
363
|
const tlsSkipped = status.helmUpgradeTls === "skipped" && !useExternalDns;
|
|
363
|
-
return (_jsx(BorderBox, { title: "Deployment Complete", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.success, bold: true, children: "\u2713 Rulebricks deployed successfully!" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: ["URL:", " ", _jsxs(Text, { color: colors.accent, children: ["https://", config?.domain, "/auth/signup"] })] }), useExternalDns && (_jsx(Text, { color: colors.muted, children: "DNS records will be created automatically by external-dns" })), tlsSkipped && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.warning, children: ["\u26A0 TLS not configured. Run `rulebricks deploy ", name, "` again after DNS setup."] }) }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Next steps:" }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Visit the URL to complete initial setup"] }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Run `rulebricks status ", name, "` to check deployment health"] }), tlsSkipped && (_jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Configure DNS and re-run deploy for TLS"] }))] })] }) }));
|
|
364
|
+
return (_jsx(BorderBox, { title: "Deployment Complete", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.success, bold: true, children: "\u2713 Rulebricks deployed successfully!" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: ["URL:", " ", _jsxs(Text, { color: colors.accent, children: ["https://", config?.domain, "/auth/signup"] })] }), useExternalDns && (_jsx(Text, { color: colors.muted, children: "DNS records will be created automatically by external-dns" })), tlsSkipped && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.warning, children: ["\u26A0 TLS not configured. Run `rulebricks deploy ", name, "` again after DNS setup."] }) }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Next steps:" }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Visit the URL to complete initial setup"] }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Run `rulebricks status ", name, "` to check deployment health"] }), tlsSkipped && (_jsxs(Text, { color: colors.muted, children: [" ", "\u2022 Configure DNS and re-run deploy for TLS"] }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, dimColor: true, children: "Tip: If the URL isn't accessible yet, your local DNS may need time to propagate." }), _jsxs(Text, { color: colors.muted, dimColor: true, children: ["Flush DNS cache: ", getDnsFlushCommand()] })] })] }) }));
|
|
364
365
|
}
|
|
365
366
|
// Progress screen
|
|
366
367
|
const helmInstallLabel = useExternalDns
|
|
@@ -376,6 +377,16 @@ function DeployCommandInner({ name, skipInfra, skipDns, version, }) {
|
|
|
376
377
|
? "Applying infrastructure"
|
|
377
378
|
: undefined }), _jsx(StatusLine, { status: status.kubeconfig, label: "Kubernetes configuration" }), _jsx(StatusLine, { status: status.helmInstall, label: helmInstallLabel }), !useExternalDns && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: status.dnsConfig, label: "DNS configuration" }), _jsx(StatusLine, { status: status.helmUpgradeTls, label: "TLS configuration" })] })), step !== "dns-wait" && (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: getStepLabel(step, useExternalDns) }) }))] }) }));
|
|
378
379
|
}
|
|
380
|
+
function getDnsFlushCommand() {
|
|
381
|
+
switch (platform()) {
|
|
382
|
+
case "darwin":
|
|
383
|
+
return "sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder";
|
|
384
|
+
case "win32":
|
|
385
|
+
return "ipconfig /flushdns";
|
|
386
|
+
default:
|
|
387
|
+
return "sudo systemd-resolve --flush-caches";
|
|
388
|
+
}
|
|
389
|
+
}
|
|
379
390
|
function getStepLabel(step, useExternalDns) {
|
|
380
391
|
switch (step) {
|
|
381
392
|
case "loading":
|
package/dist/commands/destroy.js
CHANGED
|
@@ -194,7 +194,7 @@ function DestroyCommandInner({ name, cluster, config, force, }) {
|
|
|
194
194
|
setError(err instanceof Error ? err.message : "Destruction failed");
|
|
195
195
|
setStep("error");
|
|
196
196
|
}
|
|
197
|
-
}, [name, cluster, exit]);
|
|
197
|
+
}, [name, cluster, config, exit]);
|
|
198
198
|
// Loading screen
|
|
199
199
|
if (step === "loading") {
|
|
200
200
|
return (_jsx(BorderBox, { title: `Destroying ${name}`, children: _jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Checking deployment state..." }) }) }));
|
|
@@ -243,7 +243,7 @@ function DestroyCommandInner({ name, cluster, config, force, }) {
|
|
|
243
243
|
// Only cleaning local files (with --config)
|
|
244
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
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" })), !
|
|
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" })), !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. 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
247
|
}
|
|
248
248
|
export function DestroyCommand(props) {
|
|
249
249
|
return (_jsxs(ThemeProvider, { theme: "destroy", children: [_jsx(Logo, {}), _jsx(DestroyCommandInner, { ...props })] }));
|
package/dist/commands/logs.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark Wizard Steps
|
|
3
|
+
*
|
|
4
|
+
* These components provide the interactive wizard flow for configuring
|
|
5
|
+
* and running benchmark tests against Rulebricks deployments.
|
|
6
|
+
*/
|
|
7
|
+
import { BenchmarkTestMode, BenchmarkPreset } from "../../../types/index.js";
|
|
8
|
+
export interface BenchmarkWizardState {
|
|
9
|
+
deploymentName: string;
|
|
10
|
+
deploymentUrl: string;
|
|
11
|
+
apiKey: string;
|
|
12
|
+
flowSlug: string;
|
|
13
|
+
testMode: BenchmarkTestMode;
|
|
14
|
+
preset: BenchmarkPreset;
|
|
15
|
+
targetRps: number;
|
|
16
|
+
testDuration: string;
|
|
17
|
+
bulkSize: number;
|
|
18
|
+
}
|
|
19
|
+
interface StepProps {
|
|
20
|
+
onComplete: (data: Partial<BenchmarkWizardState>) => void;
|
|
21
|
+
onBack: () => void;
|
|
22
|
+
state: BenchmarkWizardState;
|
|
23
|
+
}
|
|
24
|
+
export declare function DeploymentSelectStep({ onComplete, onBack, state }: StepProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export declare function ApiKeyStep({ onComplete, onBack, state }: StepProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export declare function FlowSlugStep({ onComplete, onBack, state }: StepProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export declare function TestModeStep({ onComplete, onBack, state }: StepProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare function PresetsStep({ onComplete, onBack, state }: StepProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export declare function ReviewStep({ onComplete, onBack, state }: StepProps): import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
export declare function createInitialBenchmarkState(): BenchmarkWizardState;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Benchmark Wizard Steps
|
|
4
|
+
*
|
|
5
|
+
* These components provide the interactive wizard flow for configuring
|
|
6
|
+
* and running benchmark tests against Rulebricks deployments.
|
|
7
|
+
*/
|
|
8
|
+
import { useState, useEffect } from "react";
|
|
9
|
+
import { Box, Text, useInput } from "ink";
|
|
10
|
+
import SelectInput from "ink-select-input";
|
|
11
|
+
import TextInput from "ink-text-input";
|
|
12
|
+
import { BorderBox, useTheme, Spinner } from "../../common/index.js";
|
|
13
|
+
import { QPS_PRESETS, THROUGHPUT_PRESETS, } from "../../../types/index.js";
|
|
14
|
+
import { listDeployments, loadDeploymentState } from "../../../lib/config.js";
|
|
15
|
+
import { buildApiUrl, checkDeploymentHealth } from "../../../lib/benchmark.js";
|
|
16
|
+
export function DeploymentSelectStep({ onComplete, onBack, state }) {
|
|
17
|
+
const { colors } = useTheme();
|
|
18
|
+
const [deployments, setDeployments] = useState([]);
|
|
19
|
+
const [loading, setLoading] = useState(true);
|
|
20
|
+
const [loadingStatus, setLoadingStatus] = useState("Loading deployments...");
|
|
21
|
+
const [error, setError] = useState(null);
|
|
22
|
+
useInput((input, key) => {
|
|
23
|
+
if (key.escape) {
|
|
24
|
+
onBack();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
(async () => {
|
|
29
|
+
try {
|
|
30
|
+
const names = await listDeployments();
|
|
31
|
+
const candidates = [];
|
|
32
|
+
// First, collect all deployments that have a URL
|
|
33
|
+
for (const name of names) {
|
|
34
|
+
try {
|
|
35
|
+
const deploymentState = await loadDeploymentState(name);
|
|
36
|
+
if (deploymentState?.application?.url) {
|
|
37
|
+
candidates.push({
|
|
38
|
+
name,
|
|
39
|
+
url: deploymentState.application.url,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Skip deployments without state
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (candidates.length === 0) {
|
|
48
|
+
setError("No deployments found. Deploy a Rulebricks instance first with 'rulebricks deploy'.");
|
|
49
|
+
setLoading(false);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Now check health of each candidate
|
|
53
|
+
setLoadingStatus(`Checking health of ${candidates.length} deployment(s)...`);
|
|
54
|
+
const healthyDeployments = [];
|
|
55
|
+
for (const candidate of candidates) {
|
|
56
|
+
setLoadingStatus(`Checking ${candidate.name}...`);
|
|
57
|
+
const isHealthy = await checkDeploymentHealth(candidate.url);
|
|
58
|
+
if (isHealthy) {
|
|
59
|
+
healthyDeployments.push({
|
|
60
|
+
name: candidate.name,
|
|
61
|
+
url: candidate.url,
|
|
62
|
+
healthy: true,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
setDeployments(healthyDeployments);
|
|
67
|
+
if (healthyDeployments.length === 0) {
|
|
68
|
+
setError("No healthy deployments found.\n\nAll configured deployments failed the health check (/api/health).\nMake sure your deployment is running and accessible.");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
setError("Failed to load deployments");
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
setLoading(false);
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
}, []);
|
|
79
|
+
if (loading) {
|
|
80
|
+
return (_jsx(BorderBox, { title: "Select Deployment", children: _jsx(Box, { marginY: 1, children: _jsx(Spinner, { label: loadingStatus }) }) }));
|
|
81
|
+
}
|
|
82
|
+
if (error) {
|
|
83
|
+
return (_jsx(BorderBox, { title: "Select Deployment", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.error, children: error }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Esc to go back" }) })] }) }));
|
|
84
|
+
}
|
|
85
|
+
const items = deployments.map((d) => ({
|
|
86
|
+
label: d.name,
|
|
87
|
+
value: d.name,
|
|
88
|
+
url: d.url,
|
|
89
|
+
}));
|
|
90
|
+
const handleSelect = (item) => {
|
|
91
|
+
const deployment = deployments.find((d) => d.name === item.value);
|
|
92
|
+
onComplete({
|
|
93
|
+
deploymentName: item.value,
|
|
94
|
+
deploymentUrl: deployment?.url || "",
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
return (_jsxs(BorderBox, { title: "Select Deployment", children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Choose a deployment to benchmark:" }), _jsx(Text, { color: colors.muted, dimColor: true, children: "Only healthy, accessible deployments are shown" })] }), _jsx(SelectInput, { items: items, onSelect: handleSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => {
|
|
98
|
+
const deployment = deployments.find((d) => d.name === label);
|
|
99
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: isSelected ? 1 : 0, children: [_jsxs(Text, { color: isSelected ? colors.accent : undefined, bold: isSelected, children: [isSelected ? "❯ " : " ", label] }), isSelected && deployment?.url && (_jsxs(Text, { color: colors.muted, dimColor: true, children: [" ", deployment.url] }))] }));
|
|
100
|
+
} }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Esc to go back \u2022 Enter to select" }) })] }));
|
|
101
|
+
}
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Step 2: API Key Input
|
|
104
|
+
// ============================================================================
|
|
105
|
+
export function ApiKeyStep({ onComplete, onBack, state }) {
|
|
106
|
+
const { colors } = useTheme();
|
|
107
|
+
const [apiKey, setApiKey] = useState(state.apiKey || "");
|
|
108
|
+
useInput((input, key) => {
|
|
109
|
+
if (key.escape) {
|
|
110
|
+
onBack();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const handleSubmit = () => {
|
|
114
|
+
if (!apiKey.trim())
|
|
115
|
+
return;
|
|
116
|
+
onComplete({ apiKey: apiKey.trim() });
|
|
117
|
+
};
|
|
118
|
+
return (_jsxs(BorderBox, { title: "API Key", children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Rulebricks API key:" }), _jsx(Text, { color: colors.muted, dimColor: true, children: "This key is used to authenticate benchmark requests" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: apiKey, onChange: setApiKey, onSubmit: handleSubmit, placeholder: "Enter your API key", mask: "*" })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
|
|
119
|
+
}
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// Step 3: Flow Slug Input
|
|
122
|
+
// ============================================================================
|
|
123
|
+
export function FlowSlugStep({ onComplete, onBack, state }) {
|
|
124
|
+
const { colors } = useTheme();
|
|
125
|
+
const [flowSlug, setFlowSlug] = useState(state.flowSlug || "");
|
|
126
|
+
useInput((input, key) => {
|
|
127
|
+
if (key.escape) {
|
|
128
|
+
onBack();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
const handleSubmit = () => {
|
|
132
|
+
if (!flowSlug.trim())
|
|
133
|
+
return;
|
|
134
|
+
onComplete({ flowSlug: flowSlug.trim() });
|
|
135
|
+
};
|
|
136
|
+
return (_jsxs(BorderBox, { title: "Benchmarking Flow", children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the slug of your benchmarking flow:" }), _jsx(Text, { color: colors.muted, dimColor: true, children: "Create a flow in Rulebricks using the \"Benchmarking Flow\" template, then find its slug (ex: \"oryoRqvOV1\")" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, children: "Expected payload schema:" }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 req_id: string (auto-generated)"] }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 alpha: number (0-100)"] }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 beta: string"] }), _jsxs(Text, { color: colors.muted, children: [" ", "\u2022 charlie: boolean"] })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: flowSlug, onChange: setFlowSlug, onSubmit: handleSubmit, placeholder: "e.g., benchmark-flow" })] }), state.deploymentUrl && flowSlug && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, dimColor: true, children: ["Will test: ", buildApiUrl(state.deploymentUrl, flowSlug)] }) }))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
|
|
137
|
+
}
|
|
138
|
+
// ============================================================================
|
|
139
|
+
// Step 4: Test Mode Selection
|
|
140
|
+
// ============================================================================
|
|
141
|
+
export function TestModeStep({ onComplete, onBack, state }) {
|
|
142
|
+
const { colors } = useTheme();
|
|
143
|
+
useInput((input, key) => {
|
|
144
|
+
if (key.escape) {
|
|
145
|
+
onBack();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
const items = [
|
|
149
|
+
{
|
|
150
|
+
label: "QPS Test",
|
|
151
|
+
value: "qps",
|
|
152
|
+
description: "Measures requests per second - tests API responsiveness",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
label: "Throughput Test",
|
|
156
|
+
value: "throughput",
|
|
157
|
+
description: "Measures solutions per second with bulk requests - tests engine capacity",
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
const handleSelect = (item) => {
|
|
161
|
+
// Set default presets based on mode
|
|
162
|
+
const defaults = item.value === "qps"
|
|
163
|
+
? {
|
|
164
|
+
targetRps: QPS_PRESETS.medium.targetRps,
|
|
165
|
+
testDuration: QPS_PRESETS.medium.testDuration,
|
|
166
|
+
}
|
|
167
|
+
: {
|
|
168
|
+
targetRps: THROUGHPUT_PRESETS.medium.targetRps,
|
|
169
|
+
testDuration: THROUGHPUT_PRESETS.medium.testDuration,
|
|
170
|
+
bulkSize: THROUGHPUT_PRESETS.medium.bulkSize,
|
|
171
|
+
};
|
|
172
|
+
onComplete({
|
|
173
|
+
testMode: item.value,
|
|
174
|
+
preset: "medium",
|
|
175
|
+
...defaults,
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
return (_jsxs(BorderBox, { title: "Test Mode", children: [_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Text, { children: "Select the type of benchmark to run:" }) }), _jsx(SelectInput, { items: items, onSelect: handleSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => {
|
|
179
|
+
const item = items.find((i) => i.label === label);
|
|
180
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: isSelected ? 1 : 0, children: [_jsxs(Text, { color: isSelected ? colors.accent : undefined, bold: isSelected, children: [isSelected ? "❯ " : " ", label] }), isSelected && item && (_jsxs(Text, { color: colors.muted, dimColor: true, children: [" ", item.description] }))] }));
|
|
181
|
+
} }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Esc to go back \u2022 Enter to select" }) })] }));
|
|
182
|
+
}
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// Step 5: Presets Selection
|
|
185
|
+
// ============================================================================
|
|
186
|
+
export function PresetsStep({ onComplete, onBack, state }) {
|
|
187
|
+
const { colors } = useTheme();
|
|
188
|
+
const [customMode, setCustomMode] = useState(false);
|
|
189
|
+
const [customRps, setCustomRps] = useState(state.targetRps.toString());
|
|
190
|
+
const [customDuration, setCustomDuration] = useState(state.testDuration);
|
|
191
|
+
const [customBulkSize, setCustomBulkSize] = useState(state.bulkSize?.toString() || "50");
|
|
192
|
+
const [activeField, setActiveField] = useState("rps");
|
|
193
|
+
const presets = state.testMode === "qps" ? QPS_PRESETS : THROUGHPUT_PRESETS;
|
|
194
|
+
useInput((input, key) => {
|
|
195
|
+
if (key.escape) {
|
|
196
|
+
if (customMode) {
|
|
197
|
+
setCustomMode(false);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
onBack();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (customMode && key.tab) {
|
|
204
|
+
// Cycle through fields
|
|
205
|
+
if (state.testMode === "throughput") {
|
|
206
|
+
setActiveField((prev) => prev === "rps"
|
|
207
|
+
? "duration"
|
|
208
|
+
: prev === "duration"
|
|
209
|
+
? "bulkSize"
|
|
210
|
+
: "rps");
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
setActiveField((prev) => (prev === "rps" ? "duration" : "rps"));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
const items = [
|
|
218
|
+
...Object.entries(presets).map(([key, value]) => ({
|
|
219
|
+
label: value.label,
|
|
220
|
+
value: key,
|
|
221
|
+
description: value.description,
|
|
222
|
+
})),
|
|
223
|
+
{
|
|
224
|
+
label: "Custom",
|
|
225
|
+
value: "custom",
|
|
226
|
+
description: "Define your own test parameters",
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
const handleSelect = (item) => {
|
|
230
|
+
if (item.value === "custom") {
|
|
231
|
+
setCustomMode(true);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const preset = presets[item.value];
|
|
235
|
+
const data = {
|
|
236
|
+
preset: item.value,
|
|
237
|
+
targetRps: preset.targetRps,
|
|
238
|
+
testDuration: preset.testDuration,
|
|
239
|
+
};
|
|
240
|
+
if (state.testMode === "throughput" && "bulkSize" in preset) {
|
|
241
|
+
data.bulkSize = preset.bulkSize;
|
|
242
|
+
}
|
|
243
|
+
onComplete(data);
|
|
244
|
+
};
|
|
245
|
+
const handleCustomSubmit = () => {
|
|
246
|
+
const rps = parseInt(customRps, 10);
|
|
247
|
+
const bulkSize = parseInt(customBulkSize, 10);
|
|
248
|
+
if (isNaN(rps) || rps < 1)
|
|
249
|
+
return;
|
|
250
|
+
if (state.testMode === "throughput" && (isNaN(bulkSize) || bulkSize < 1))
|
|
251
|
+
return;
|
|
252
|
+
const data = {
|
|
253
|
+
preset: "custom",
|
|
254
|
+
targetRps: rps,
|
|
255
|
+
testDuration: customDuration,
|
|
256
|
+
};
|
|
257
|
+
if (state.testMode === "throughput") {
|
|
258
|
+
data.bulkSize = bulkSize;
|
|
259
|
+
}
|
|
260
|
+
onComplete(data);
|
|
261
|
+
};
|
|
262
|
+
if (customMode) {
|
|
263
|
+
return (_jsxs(BorderBox, { title: "Custom Configuration", children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Configure your custom benchmark parameters:" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: activeField === "rps" ? colors.accent : undefined, children: [activeField === "rps" ? "❯ " : " ", "Target RPS:", " "] }), activeField === "rps" ? (_jsx(TextInput, { value: customRps, onChange: setCustomRps, onSubmit: handleCustomSubmit, placeholder: "e.g., 500" })) : (_jsx(Text, { children: customRps }))] }), _jsxs(Box, { children: [_jsxs(Text, { color: activeField === "duration" ? colors.accent : undefined, children: [activeField === "duration" ? "❯ " : " ", "Duration:", " "] }), activeField === "duration" ? (_jsx(TextInput, { value: customDuration, onChange: setCustomDuration, onSubmit: handleCustomSubmit, placeholder: "e.g., 4m" })) : (_jsx(Text, { children: customDuration }))] }), state.testMode === "throughput" && (_jsxs(Box, { children: [_jsxs(Text, { color: activeField === "bulkSize" ? colors.accent : undefined, children: [activeField === "bulkSize" ? "❯ " : " ", "Bulk Size:", " "] }), activeField === "bulkSize" ? (_jsx(TextInput, { value: customBulkSize, onChange: setCustomBulkSize, onSubmit: handleCustomSubmit, placeholder: "e.g., 50" })) : (_jsx(Text, { children: customBulkSize }))] }))] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Tab to switch fields \u2022 Esc to go back \u2022 Enter to continue" }) })] }));
|
|
264
|
+
}
|
|
265
|
+
return (_jsxs(BorderBox, { title: "Test Presets", children: [_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsxs(Text, { children: ["Select a preset for your ", state.testMode.toUpperCase(), " test:"] }) }), _jsx(SelectInput, { items: items, onSelect: handleSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => {
|
|
266
|
+
const item = items.find((i) => i.label === label);
|
|
267
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: isSelected ? 1 : 0, children: [_jsxs(Text, { color: isSelected ? colors.accent : undefined, bold: isSelected, children: [isSelected ? "❯ " : " ", label] }), isSelected && item && (_jsxs(Text, { color: colors.muted, dimColor: true, children: [" ", item.description] }))] }));
|
|
268
|
+
} }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Esc to go back \u2022 Enter to select" }) })] }));
|
|
269
|
+
}
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Step 6: Review and Confirm
|
|
272
|
+
// ============================================================================
|
|
273
|
+
export function ReviewStep({ onComplete, onBack, state }) {
|
|
274
|
+
const { colors } = useTheme();
|
|
275
|
+
useInput((input, key) => {
|
|
276
|
+
if (key.escape) {
|
|
277
|
+
onBack();
|
|
278
|
+
}
|
|
279
|
+
if (key.return) {
|
|
280
|
+
onComplete({});
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
const apiUrl = buildApiUrl(state.deploymentUrl, state.flowSlug);
|
|
284
|
+
const expectedThroughput = state.testMode === "throughput"
|
|
285
|
+
? state.targetRps * state.bulkSize
|
|
286
|
+
: state.targetRps;
|
|
287
|
+
return (_jsxs(BorderBox, { title: "Review Configuration", children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Ready to run benchmark" }), _jsx(Text, { color: colors.muted, dimColor: true, children: "Review your configuration before starting the test" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Deployment: " }), _jsx(Text, { bold: true, children: state.deploymentName })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Target URL: " }), _jsx(Text, { children: apiUrl })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Test Mode: " }), _jsx(Text, { bold: true, children: state.testMode.toUpperCase() })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Preset: " }), _jsx(Text, { children: state.preset })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Target RPS: " }), _jsxs(Text, { children: [state.targetRps, " requests/sec"] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Duration: " }), _jsxs(Text, { children: [state.testDuration, " (+ 1m warm-up)"] })] }), state.testMode === "throughput" && (_jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Bulk Size: " }), _jsxs(Text, { children: [state.bulkSize, " payloads/request"] })] })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.accent, children: ["Expected ", state.testMode === "throughput" ? "throughput" : "load", ": ~", expectedThroughput.toLocaleString(), " ", state.testMode === "throughput" ? "solutions" : "requests", "/sec"] }) })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: "Esc to go back \u2022 Enter to start benchmark" }) })] }));
|
|
288
|
+
}
|
|
289
|
+
// ============================================================================
|
|
290
|
+
// Initial State Factory
|
|
291
|
+
// ============================================================================
|
|
292
|
+
export function createInitialBenchmarkState() {
|
|
293
|
+
return {
|
|
294
|
+
deploymentName: "",
|
|
295
|
+
deploymentUrl: "",
|
|
296
|
+
apiKey: "",
|
|
297
|
+
flowSlug: "",
|
|
298
|
+
testMode: "qps",
|
|
299
|
+
preset: "medium",
|
|
300
|
+
targetRps: QPS_PRESETS.medium.targetRps,
|
|
301
|
+
testDuration: QPS_PRESETS.medium.testDuration,
|
|
302
|
+
bulkSize: THROUGHPUT_PRESETS.medium.bulkSize,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
@@ -1,53 +1,58 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from
|
|
3
|
-
import { Box, Text, useInput } from
|
|
4
|
-
import SelectInput from
|
|
5
|
-
import TextInput from
|
|
6
|
-
import { useWizard } from
|
|
7
|
-
import { BorderBox, useTheme } from
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import SelectInput from "ink-select-input";
|
|
5
|
+
import TextInput from "ink-text-input";
|
|
6
|
+
import { useWizard } from "../WizardContext.js";
|
|
7
|
+
import { BorderBox, useTheme } from "../../common/index.js";
|
|
8
8
|
export function DatabaseStep({ onComplete, onBack }) {
|
|
9
9
|
const { state, dispatch } = useWizard();
|
|
10
10
|
const { colors } = useTheme();
|
|
11
|
-
const [subStep, setSubStep] = useState(
|
|
12
|
-
const [supabaseUrl, setSupabaseUrl] = useState(state.supabaseUrl ||
|
|
13
|
-
const [anonKey, setAnonKey] = useState(state.supabaseAnonKey ||
|
|
14
|
-
const [serviceKey, setServiceKey] = useState(state.supabaseServiceKey ||
|
|
15
|
-
const [
|
|
11
|
+
const [subStep, setSubStep] = useState("type");
|
|
12
|
+
const [supabaseUrl, setSupabaseUrl] = useState(state.supabaseUrl || "");
|
|
13
|
+
const [anonKey, setAnonKey] = useState(state.supabaseAnonKey || "");
|
|
14
|
+
const [serviceKey, setServiceKey] = useState(state.supabaseServiceKey || "");
|
|
15
|
+
const [accessToken, setAccessToken] = useState(state.supabaseAccessToken || "");
|
|
16
|
+
const [currentField, setCurrentField] = useState("anon");
|
|
16
17
|
useInput((input, key) => {
|
|
17
18
|
if (key.escape) {
|
|
18
|
-
if (subStep ===
|
|
19
|
+
if (subStep === "type") {
|
|
19
20
|
onBack();
|
|
20
21
|
}
|
|
21
|
-
else if (subStep ===
|
|
22
|
-
setSubStep(
|
|
22
|
+
else if (subStep === "supabase-url") {
|
|
23
|
+
setSubStep("type");
|
|
23
24
|
}
|
|
24
|
-
else if (subStep ===
|
|
25
|
-
if (currentField ===
|
|
26
|
-
setCurrentField(
|
|
25
|
+
else if (subStep === "supabase-keys") {
|
|
26
|
+
if (currentField === "service") {
|
|
27
|
+
setCurrentField("anon");
|
|
27
28
|
}
|
|
28
29
|
else {
|
|
29
|
-
setSubStep(
|
|
30
|
+
setSubStep("supabase-url");
|
|
30
31
|
}
|
|
31
32
|
}
|
|
33
|
+
else if (subStep === "access-token") {
|
|
34
|
+
setSubStep("supabase-keys");
|
|
35
|
+
setCurrentField("service");
|
|
36
|
+
}
|
|
32
37
|
}
|
|
33
38
|
});
|
|
34
39
|
const items = [
|
|
35
40
|
{
|
|
36
|
-
label:
|
|
37
|
-
value:
|
|
38
|
-
description:
|
|
41
|
+
label: "Self-hosted Supabase",
|
|
42
|
+
value: "self-hosted",
|
|
43
|
+
description: "Deploy Supabase as part of the Helm chart",
|
|
39
44
|
},
|
|
40
45
|
{
|
|
41
|
-
label:
|
|
42
|
-
value:
|
|
43
|
-
description:
|
|
44
|
-
}
|
|
46
|
+
label: "Supabase Cloud",
|
|
47
|
+
value: "supabase-cloud",
|
|
48
|
+
description: "Use your existing Supabase Cloud project",
|
|
49
|
+
},
|
|
45
50
|
];
|
|
46
51
|
const handleTypeSelect = (item) => {
|
|
47
52
|
const dbType = item.value;
|
|
48
|
-
dispatch({ type:
|
|
49
|
-
if (dbType ===
|
|
50
|
-
setSubStep(
|
|
53
|
+
dispatch({ type: "SET_DATABASE_TYPE", dbType });
|
|
54
|
+
if (dbType === "supabase-cloud") {
|
|
55
|
+
setSubStep("supabase-url");
|
|
51
56
|
}
|
|
52
57
|
else {
|
|
53
58
|
onComplete();
|
|
@@ -56,25 +61,34 @@ export function DatabaseStep({ onComplete, onBack }) {
|
|
|
56
61
|
const handleUrlSubmit = () => {
|
|
57
62
|
if (!supabaseUrl)
|
|
58
63
|
return;
|
|
59
|
-
dispatch({ type:
|
|
60
|
-
setSubStep(
|
|
64
|
+
dispatch({ type: "SET_SUPABASE_CONFIG", config: { supabaseUrl } });
|
|
65
|
+
setSubStep("supabase-keys");
|
|
61
66
|
};
|
|
62
67
|
const handleAnonKeySubmit = () => {
|
|
63
68
|
if (!anonKey)
|
|
64
69
|
return;
|
|
65
|
-
setCurrentField(
|
|
70
|
+
setCurrentField("service");
|
|
66
71
|
};
|
|
67
72
|
const handleServiceKeySubmit = () => {
|
|
68
73
|
if (!serviceKey)
|
|
69
74
|
return;
|
|
70
75
|
dispatch({
|
|
71
|
-
type:
|
|
76
|
+
type: "SET_SUPABASE_CONFIG",
|
|
72
77
|
config: {
|
|
73
78
|
supabaseAnonKey: anonKey,
|
|
74
|
-
supabaseServiceKey: serviceKey
|
|
75
|
-
}
|
|
79
|
+
supabaseServiceKey: serviceKey,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
setSubStep("access-token");
|
|
83
|
+
};
|
|
84
|
+
const handleAccessTokenSubmit = () => {
|
|
85
|
+
if (!accessToken)
|
|
86
|
+
return;
|
|
87
|
+
dispatch({
|
|
88
|
+
type: "SET_SUPABASE_CONFIG",
|
|
89
|
+
config: { supabaseAccessToken: accessToken },
|
|
76
90
|
});
|
|
77
91
|
onComplete();
|
|
78
92
|
};
|
|
79
|
-
return (_jsxs(BorderBox, { title: "Database", children: [subStep ===
|
|
93
|
+
return (_jsxs(BorderBox, { title: "Database", children: [subStep === "type" && (_jsxs(_Fragment, { children: [_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Text, { children: "Choose your database setup:" }) }), _jsx(SelectInput, { items: items, onSelect: handleTypeSelect, itemComponent: ({ isSelected, label }) => (_jsx(Text, { color: isSelected ? colors.accent : undefined, children: label })) })] })), subStep === "supabase-url" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Supabase project URL:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Find this in your Supabase Dashboard \u2192 Project Settings" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: supabaseUrl, onChange: setSupabaseUrl, onSubmit: handleUrlSubmit, placeholder: "https://xxxxx.supabase.co" })] })] })), subStep === "supabase-keys" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Supabase API keys:" }), currentField === "anon" ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Anon (public) key:" }), _jsxs(Box, { children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: anonKey, onChange: setAnonKey, onSubmit: handleAnonKeySubmit, placeholder: "eyJhbGciOiJIUzI1NiIs..." })] })] })) : (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { children: [" Anon key: ", anonKey.substring(0, 20), "..."] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Service role key:" }) }), _jsxs(Box, { children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: serviceKey, onChange: setServiceKey, onSubmit: handleServiceKeySubmit, placeholder: "eyJhbGciOiJIUzI1NiIs..." })] })] }))] })), subStep === "access-token" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Supabase Access Token:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Find this in Supabase Dashboard \u2192 Account Settings \u2192 Access Tokens" }), _jsx(Text, { color: "gray", dimColor: true, children: "This is required for managing your Supabase project." }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: accessToken, onChange: setAccessToken, onSubmit: handleAccessTokenSubmit, placeholder: "sbp_...", mask: "*" })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsx(Text, { color: "gray", children: " Supabase URL configured" })] }), _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsx(Text, { color: "gray", children: " API keys configured" })] })] })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
|
|
80
94
|
}
|