@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,346 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useReducer } from "react";
3
+ import { DEFAULT_EMAIL_SUBJECTS, } from "../../types/index.js";
4
+ /**
5
+ * Creates the initial wizard state, optionally pre-populated from a user profile.
6
+ * Profile values are used as defaults that the user can still modify.
7
+ */
8
+ function getInitialState(profile) {
9
+ return {
10
+ step: 0,
11
+ name: "",
12
+ // Infrastructure - pre-populate from profile
13
+ infrastructureMode: profile?.infrastructureMode ?? null,
14
+ provider: profile?.provider ?? null,
15
+ region: profile?.region ?? "",
16
+ clusterName: profile?.clusterName ?? "",
17
+ gcpProjectId: "",
18
+ azureResourceGroup: "",
19
+ // Domain & Email - pre-populate from profile
20
+ domain: "", // Domain is intentionally left empty - user should enter unique domain per deployment
21
+ adminEmail: profile?.adminEmail ?? "",
22
+ tlsEmail: profile?.tlsEmail ?? "",
23
+ // DNS Configuration - pre-populate from profile
24
+ dnsProvider: profile?.dnsProvider ?? "other",
25
+ dnsAutoManage: false,
26
+ existingExternalDns: false,
27
+ // SMTP - pre-populate from profile
28
+ smtpHost: profile?.smtpHost ?? "",
29
+ smtpPort: profile?.smtpPort ?? 587,
30
+ smtpUser: profile?.smtpUser ?? "",
31
+ smtpPass: profile?.smtpPass ?? "",
32
+ smtpFrom: profile?.smtpFrom ?? "",
33
+ smtpFromName: profile?.smtpFromName ?? "Rulebricks",
34
+ // Database - pre-populate from profile
35
+ databaseType: profile?.databaseType ?? null,
36
+ supabaseUrl: "",
37
+ supabaseAnonKey: "",
38
+ supabaseServiceKey: "",
39
+ supabaseAccessToken: "",
40
+ supabaseProjectRef: "",
41
+ supabaseJwtSecret: "",
42
+ supabaseDbPassword: "",
43
+ supabaseDashboardUser: "supabase",
44
+ supabaseDashboardPass: "",
45
+ // Performance - pre-populate from profile
46
+ tier: profile?.tier ?? null,
47
+ // Features - AI - pre-populate from profile
48
+ aiEnabled: !!profile?.openaiApiKey,
49
+ openaiApiKey: profile?.openaiApiKey ?? "",
50
+ // Features - SSO - pre-populate from profile
51
+ ssoEnabled: !!profile?.ssoProvider,
52
+ ssoProvider: profile?.ssoProvider ?? null,
53
+ ssoUrl: profile?.ssoUrl ?? "",
54
+ ssoClientId: profile?.ssoClientId ?? "",
55
+ ssoClientSecret: profile?.ssoClientSecret ?? "",
56
+ // Features - Monitoring
57
+ monitoringEnabled: false,
58
+ prometheusRemoteWriteUrl: "",
59
+ // Features - Logging
60
+ loggingSink: "console", // Default to console only
61
+ loggingBucket: "",
62
+ loggingRegion: "",
63
+ // Features - Custom Email Templates
64
+ customEmailsEnabled: false,
65
+ emailSubjects: { ...DEFAULT_EMAIL_SUBJECTS },
66
+ emailTemplates: {
67
+ invite: "",
68
+ confirmation: "",
69
+ recovery: "",
70
+ emailChange: "",
71
+ },
72
+ // Credentials - pre-populate from profile
73
+ licenseKey: profile?.licenseKey ?? "",
74
+ // Version
75
+ appVersion: "",
76
+ hpsVersion: "",
77
+ chartVersion: "",
78
+ };
79
+ }
80
+ // Default initial state (for backwards compatibility)
81
+ const initialState = getInitialState();
82
+ function wizardReducer(state, action) {
83
+ switch (action.type) {
84
+ case "SET_STEP":
85
+ return { ...state, step: action.step };
86
+ case "SET_NAME":
87
+ return { ...state, name: action.name };
88
+ case "SET_INFRA_MODE":
89
+ return { ...state, infrastructureMode: action.mode };
90
+ case "SET_PROVIDER":
91
+ return { ...state, provider: action.provider, region: "" };
92
+ case "SET_REGION":
93
+ return { ...state, region: action.region };
94
+ case "SET_CLUSTER_NAME":
95
+ return { ...state, clusterName: action.clusterName };
96
+ case "SET_GCP_PROJECT":
97
+ return { ...state, gcpProjectId: action.projectId };
98
+ case "SET_AZURE_RG":
99
+ return { ...state, azureResourceGroup: action.resourceGroup };
100
+ case "SET_DOMAIN":
101
+ return { ...state, domain: action.domain };
102
+ case "SET_ADMIN_EMAIL":
103
+ return { ...state, adminEmail: action.email };
104
+ case "SET_TLS_EMAIL":
105
+ return { ...state, tlsEmail: action.email };
106
+ case "SET_DNS_PROVIDER":
107
+ // Reset auto-manage if switching to unsupported provider
108
+ return {
109
+ ...state,
110
+ dnsProvider: action.provider,
111
+ dnsAutoManage: action.provider === "other" ? false : state.dnsAutoManage,
112
+ };
113
+ case "SET_DNS_AUTO_MANAGE":
114
+ return { ...state, dnsAutoManage: action.autoManage };
115
+ case "SET_EXISTING_EXTERNAL_DNS":
116
+ return { ...state, existingExternalDns: action.exists };
117
+ case "SET_SMTP":
118
+ return { ...state, ...action.config };
119
+ case "SET_DATABASE_TYPE":
120
+ return { ...state, databaseType: action.dbType };
121
+ case "SET_SUPABASE_CONFIG":
122
+ return { ...state, ...action.config };
123
+ case "SET_SUPABASE_SELF_HOSTED":
124
+ return { ...state, ...action.config };
125
+ case "SET_TIER":
126
+ return { ...state, tier: action.tier };
127
+ case "SET_AI_ENABLED":
128
+ return { ...state, aiEnabled: action.enabled };
129
+ case "SET_OPENAI_KEY":
130
+ return { ...state, openaiApiKey: action.key };
131
+ case "SET_SSO_ENABLED":
132
+ return { ...state, ssoEnabled: action.enabled };
133
+ case "SET_SSO_CONFIG":
134
+ return { ...state, ...action.config };
135
+ case "SET_MONITORING":
136
+ return { ...state, monitoringEnabled: action.enabled };
137
+ case "SET_PROMETHEUS_REMOTE_WRITE":
138
+ return { ...state, prometheusRemoteWriteUrl: action.url };
139
+ case "SET_LOGGING_SINK":
140
+ // Reset bucket/region if switching to console
141
+ return {
142
+ ...state,
143
+ loggingSink: action.sink,
144
+ loggingBucket: action.sink === "console" ? "" : state.loggingBucket,
145
+ loggingRegion: action.sink === "console" ? "" : state.loggingRegion,
146
+ };
147
+ case "SET_LOGGING_CONFIG":
148
+ return { ...state, ...action.config };
149
+ case "SET_CUSTOM_EMAILS_ENABLED":
150
+ return { ...state, customEmailsEnabled: action.enabled };
151
+ case "SET_EMAIL_SUBJECTS":
152
+ return {
153
+ ...state,
154
+ emailSubjects: { ...state.emailSubjects, ...action.subjects },
155
+ };
156
+ case "SET_EMAIL_TEMPLATES":
157
+ return {
158
+ ...state,
159
+ emailTemplates: { ...state.emailTemplates, ...action.templates },
160
+ };
161
+ case "SET_LICENSE_KEY":
162
+ return { ...state, licenseKey: action.key };
163
+ case "SET_APP_VERSION":
164
+ return {
165
+ ...state,
166
+ appVersion: action.appVersion,
167
+ hpsVersion: action.hpsVersion,
168
+ };
169
+ case "SET_CHART_VERSION":
170
+ return { ...state, chartVersion: action.version };
171
+ case "NEXT_STEP":
172
+ return { ...state, step: state.step + 1 };
173
+ case "PREV_STEP":
174
+ return { ...state, step: Math.max(0, state.step - 1) };
175
+ default:
176
+ return state;
177
+ }
178
+ }
179
+ const WizardContext = createContext(null);
180
+ export function WizardProvider({ children, initialName, profile, }) {
181
+ // Initialize state with profile values for pre-population
182
+ const [state, dispatch] = useReducer(wizardReducer, {
183
+ ...getInitialState(profile),
184
+ name: initialName || "",
185
+ });
186
+ const toConfig = () => {
187
+ // Validate required fields
188
+ if (!state.name ||
189
+ !state.domain ||
190
+ !state.adminEmail ||
191
+ !state.tlsEmail ||
192
+ !state.licenseKey) {
193
+ return null;
194
+ }
195
+ // Validate SMTP
196
+ if (!state.smtpHost ||
197
+ !state.smtpUser ||
198
+ !state.smtpPass ||
199
+ !state.smtpFrom) {
200
+ return null;
201
+ }
202
+ // Validate database config
203
+ if (state.databaseType === "supabase-cloud") {
204
+ if (!state.supabaseUrl ||
205
+ !state.supabaseAnonKey ||
206
+ !state.supabaseServiceKey) {
207
+ return null;
208
+ }
209
+ }
210
+ else if (state.databaseType === "self-hosted") {
211
+ if (!state.supabaseDbPassword) {
212
+ return null;
213
+ }
214
+ }
215
+ // Validate logging sink config
216
+ if (state.loggingSink !== "console" && !state.loggingBucket) {
217
+ return null;
218
+ }
219
+ return {
220
+ name: state.name,
221
+ infrastructure: {
222
+ mode: state.infrastructureMode || "existing",
223
+ provider: state.provider || undefined,
224
+ region: state.region || undefined,
225
+ clusterName: state.clusterName || undefined,
226
+ gcpProjectId: state.gcpProjectId || undefined,
227
+ azureResourceGroup: state.azureResourceGroup || undefined,
228
+ },
229
+ domain: state.domain,
230
+ adminEmail: state.adminEmail,
231
+ tlsEmail: state.tlsEmail,
232
+ dns: {
233
+ provider: state.dnsProvider,
234
+ autoManage: state.dnsAutoManage,
235
+ existingExternalDns: state.existingExternalDns || undefined,
236
+ },
237
+ smtp: {
238
+ host: state.smtpHost,
239
+ port: state.smtpPort,
240
+ user: state.smtpUser,
241
+ pass: state.smtpPass,
242
+ from: state.smtpFrom,
243
+ fromName: state.smtpFromName,
244
+ },
245
+ database: {
246
+ type: state.databaseType || "self-hosted",
247
+ supabaseUrl: state.supabaseUrl || undefined,
248
+ supabaseAnonKey: state.supabaseAnonKey || undefined,
249
+ supabaseServiceKey: state.supabaseServiceKey || undefined,
250
+ supabaseAccessToken: state.supabaseAccessToken || undefined,
251
+ supabaseProjectRef: state.supabaseProjectRef || undefined,
252
+ supabaseJwtSecret: state.supabaseJwtSecret || undefined,
253
+ supabaseDbPassword: state.supabaseDbPassword || undefined,
254
+ supabaseDashboardUser: state.supabaseDashboardUser || undefined,
255
+ supabaseDashboardPass: state.supabaseDashboardPass || undefined,
256
+ },
257
+ tier: state.tier || "small",
258
+ features: {
259
+ ai: {
260
+ enabled: state.aiEnabled,
261
+ openaiApiKey: state.openaiApiKey || undefined,
262
+ },
263
+ sso: {
264
+ enabled: state.ssoEnabled,
265
+ provider: state.ssoProvider || undefined,
266
+ url: state.ssoUrl || undefined,
267
+ clientId: state.ssoClientId || undefined,
268
+ clientSecret: state.ssoClientSecret || undefined,
269
+ },
270
+ monitoring: {
271
+ enabled: state.monitoringEnabled,
272
+ remoteWriteUrl: state.prometheusRemoteWriteUrl || undefined,
273
+ },
274
+ logging: {
275
+ sink: state.loggingSink,
276
+ bucket: state.loggingBucket || undefined,
277
+ region: state.loggingRegion || undefined,
278
+ },
279
+ customEmails: state.customEmailsEnabled
280
+ ? {
281
+ enabled: true,
282
+ subjects: state.emailSubjects,
283
+ templates: {
284
+ invite: state.emailTemplates.invite,
285
+ confirmation: state.emailTemplates.confirmation,
286
+ recovery: state.emailTemplates.recovery,
287
+ emailChange: state.emailTemplates.emailChange,
288
+ },
289
+ }
290
+ : undefined,
291
+ },
292
+ licenseKey: state.licenseKey,
293
+ appVersion: state.appVersion || undefined,
294
+ hpsVersion: state.hpsVersion || undefined,
295
+ chartVersion: state.chartVersion || undefined,
296
+ };
297
+ };
298
+ const skipToStep = (stepId) => {
299
+ // For conditional step skipping
300
+ const stepIndex = [
301
+ "mode",
302
+ "cloud",
303
+ "domain",
304
+ "smtp",
305
+ "database",
306
+ "database-creds",
307
+ "tier",
308
+ "features",
309
+ "feature-config",
310
+ "credentials",
311
+ "review",
312
+ ].indexOf(stepId);
313
+ if (stepIndex >= 0) {
314
+ dispatch({ type: "SET_STEP", step: stepIndex });
315
+ }
316
+ };
317
+ /**
318
+ * Suggests a domain based on the profile's domain suffix and a deployment name.
319
+ * e.g., if profile has domainSuffix ".example.com" and name is "staging",
320
+ * suggests "staging.example.com"
321
+ */
322
+ const suggestDomain = (name) => {
323
+ if (!profile?.domainSuffix || !name)
324
+ return "";
325
+ // Remove leading dot if present and combine with name
326
+ const suffix = profile.domainSuffix.startsWith(".")
327
+ ? profile.domainSuffix.slice(1)
328
+ : profile.domainSuffix;
329
+ return `${name}.${suffix}`;
330
+ };
331
+ return (_jsx(WizardContext.Provider, { value: {
332
+ state,
333
+ dispatch,
334
+ toConfig,
335
+ skipToStep,
336
+ profile: profile ?? null,
337
+ suggestDomain,
338
+ }, children: children }));
339
+ }
340
+ export function useWizard() {
341
+ const context = useContext(WizardContext);
342
+ if (!context) {
343
+ throw new Error("useWizard must be used within WizardProvider");
344
+ }
345
+ return context;
346
+ }
@@ -0,0 +1,2 @@
1
+ export { WizardProvider, useWizard, type WizardState } from './WizardContext.js';
2
+ export * from './steps/index.js';
@@ -0,0 +1,2 @@
1
+ export { WizardProvider, useWizard } from './WizardContext.js';
2
+ export * from './steps/index.js';
@@ -0,0 +1,6 @@
1
+ interface CloudProviderStepProps {
2
+ onComplete: () => void;
3
+ onBack: () => void;
4
+ }
5
+ export declare function CloudProviderStep({ onComplete, onBack, }: CloudProviderStepProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,210 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect } 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
+ import { Spinner } from "../../common/Spinner.js";
9
+ import { CLOUD_REGIONS } from "../../../types/index.js";
10
+ import { checkAllCloudClis, checkTerraform, listRegions, listClusters, getGcpProjectId, CLI_INSTALL_URLS, CLI_LOGIN_COMMANDS, TERRAFORM_INSTALL_INFO, } from "../../../lib/cloudCli.js";
11
+ export function CloudProviderStep({ onComplete, onBack, }) {
12
+ const { state, dispatch } = useWizard();
13
+ const { colors } = useTheme();
14
+ const [subStep, setSubStep] = useState("checking");
15
+ const [cliStatus, setCliStatus] = useState(null);
16
+ const [terraformStatus, setTerraformStatus] = useState(null);
17
+ const [clusterName, setClusterName] = useState(state.clusterName || "rulebricks-cluster");
18
+ const [gcpProject, setGcpProject] = useState(state.gcpProjectId || "");
19
+ const [azureRg, setAzureRg] = useState(state.azureResourceGroup || "");
20
+ const [regions, setRegions] = useState([]);
21
+ const [regionsLoading, setRegionsLoading] = useState(false);
22
+ const [clusters, setClusters] = useState([]);
23
+ const [clustersLoading, setClustersLoading] = useState(false);
24
+ // Whether we need infrastructure provisioning (Terraform required)
25
+ const needsTerraform = state.infrastructureMode === "provision";
26
+ // Check CLIs on mount
27
+ useEffect(() => {
28
+ async function checkClis() {
29
+ // Check cloud CLIs and Terraform in parallel
30
+ const [status, tfStatus] = await Promise.all([
31
+ checkAllCloudClis(),
32
+ needsTerraform
33
+ ? checkTerraform()
34
+ : Promise.resolve({ installed: true }),
35
+ ]);
36
+ setCliStatus(status);
37
+ setTerraformStatus(tfStatus);
38
+ if (!status.anyInstalled) {
39
+ setSubStep("no-cli");
40
+ }
41
+ else {
42
+ setSubStep("provider");
43
+ }
44
+ }
45
+ checkClis();
46
+ }, [needsTerraform]);
47
+ useInput((input, key) => {
48
+ if (key.escape) {
49
+ if (subStep === "provider" ||
50
+ subStep === "no-cli" ||
51
+ subStep === "checking") {
52
+ onBack();
53
+ }
54
+ else if (subStep === "region" || subStep === "region-loading") {
55
+ setSubStep("provider");
56
+ }
57
+ else if (subStep === "cluster" ||
58
+ subStep === "cluster-loading" ||
59
+ subStep === "cluster-select") {
60
+ setSubStep("region");
61
+ }
62
+ else if (subStep === "gcp-project") {
63
+ setSubStep("provider");
64
+ }
65
+ else if (subStep === "azure-rg") {
66
+ setSubStep("provider");
67
+ }
68
+ }
69
+ });
70
+ // Build provider items with status
71
+ const getProviderItems = () => {
72
+ if (!cliStatus)
73
+ return [];
74
+ const providers = [
75
+ { label: "AWS (EKS)", value: "aws", status: cliStatus.aws },
76
+ { label: "Google Cloud (GKE)", value: "gcp", status: cliStatus.gcp },
77
+ { label: "Azure (AKS)", value: "azure", status: cliStatus.azure },
78
+ ];
79
+ return providers.map((p) => ({
80
+ ...p,
81
+ disabled: !p.status.installed || !p.status.authenticated,
82
+ }));
83
+ };
84
+ const providerItems = getProviderItems();
85
+ const handleProviderSelect = async (item) => {
86
+ const selectedItem = providerItems.find((p) => p.value === item.value);
87
+ if (!selectedItem || selectedItem.disabled)
88
+ return;
89
+ const provider = item.value;
90
+ dispatch({ type: "SET_PROVIDER", provider });
91
+ if (provider === "gcp") {
92
+ // Try to auto-fill GCP project ID
93
+ const detectedProject = await getGcpProjectId();
94
+ if (detectedProject) {
95
+ setGcpProject(detectedProject);
96
+ }
97
+ setSubStep("gcp-project");
98
+ }
99
+ else if (provider === "azure") {
100
+ setSubStep("azure-rg");
101
+ }
102
+ else {
103
+ loadRegions(provider);
104
+ }
105
+ };
106
+ const loadRegions = async (provider) => {
107
+ setSubStep("region-loading");
108
+ setRegionsLoading(true);
109
+ try {
110
+ const dynamicRegions = await listRegions(provider);
111
+ if (dynamicRegions.length > 0) {
112
+ setRegions(dynamicRegions);
113
+ }
114
+ else {
115
+ // Fall back to static regions
116
+ setRegions(CLOUD_REGIONS[provider]);
117
+ }
118
+ }
119
+ catch {
120
+ // Fall back to static regions on error
121
+ setRegions(CLOUD_REGIONS[provider]);
122
+ }
123
+ setRegionsLoading(false);
124
+ setSubStep("region");
125
+ };
126
+ const handleRegionSelect = (item) => {
127
+ dispatch({ type: "SET_REGION", region: item.value });
128
+ // For existing infrastructure, load available clusters
129
+ if (state.infrastructureMode === "existing" && state.provider) {
130
+ loadClusters(state.provider, item.value);
131
+ }
132
+ else {
133
+ // For provisioning, go directly to cluster name input
134
+ setSubStep("cluster");
135
+ }
136
+ };
137
+ const loadClusters = async (provider, region) => {
138
+ setSubStep("cluster-loading");
139
+ setClustersLoading(true);
140
+ try {
141
+ const availableClusters = await listClusters(provider, region, {
142
+ azureResourceGroup: state.azureResourceGroup || undefined,
143
+ });
144
+ setClusters(availableClusters);
145
+ if (availableClusters.length > 0) {
146
+ setSubStep("cluster-select");
147
+ }
148
+ else {
149
+ // No clusters found, fall back to manual input
150
+ setSubStep("cluster");
151
+ }
152
+ }
153
+ catch {
154
+ // On error, fall back to manual input
155
+ setClusters([]);
156
+ setSubStep("cluster");
157
+ }
158
+ setClustersLoading(false);
159
+ };
160
+ const handleClusterSelect = (item) => {
161
+ setClusterName(item.value);
162
+ dispatch({ type: "SET_CLUSTER_NAME", clusterName: item.value });
163
+ onComplete();
164
+ };
165
+ const handleClusterSubmit = () => {
166
+ dispatch({ type: "SET_CLUSTER_NAME", clusterName });
167
+ onComplete();
168
+ };
169
+ const handleGcpProjectSubmit = () => {
170
+ dispatch({ type: "SET_GCP_PROJECT", projectId: gcpProject });
171
+ loadRegions("gcp");
172
+ };
173
+ const handleAzureRgSubmit = () => {
174
+ dispatch({ type: "SET_AZURE_RG", resourceGroup: azureRg });
175
+ loadRegions("azure");
176
+ };
177
+ const regionItems = regions.map((r) => ({ label: r, value: r }));
178
+ // Render status indicator for a provider
179
+ const renderStatusIndicator = (status) => {
180
+ if (!status.installed) {
181
+ return _jsx(Text, { color: "gray", children: " (not installed)" });
182
+ }
183
+ if (!status.authenticated) {
184
+ return _jsx(Text, { color: "yellow", children: " (log in required)" });
185
+ }
186
+ return _jsx(Text, { color: "green", children: " \u2713" });
187
+ };
188
+ return (_jsxs(BorderBox, { title: "Cloud Provider", children: [subStep === "checking" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Spinner, { label: "Checking cloud CLI tools..." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Detecting AWS, GCP, and Azure CLIs..." }) })] })), subStep === "no-cli" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "red", bold: true, children: "No cloud CLI tools detected" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Text, { children: "To provision infrastructure, you need to install and authenticate with at least one cloud CLI:" }) }), _jsx(Box, { marginTop: 1, flexDirection: "column", marginLeft: 2, children: Object.entries(CLI_INSTALL_URLS).map(([provider, info]) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { bold: true, children: [info.name, ":"] }), _jsxs(Text, { color: "gray", children: [" ", info.installCmd] }), _jsxs(Text, { color: "gray", children: [" ", info.url] })] }, provider))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "After installing, authenticate:" }) }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: Object.entries(CLI_LOGIN_COMMANDS).map(([provider, cmd]) => (_jsxs(Text, { color: "gray", children: [" ", provider, ": ", cmd] }, provider))) })] })), subStep === "provider" && cliStatus && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select your cloud provider:" }), !cliStatus.anyAvailable && cliStatus.anyInstalled && (_jsx(Text, { color: "yellow", dimColor: true, children: "\u26A0 Some CLIs are installed but not authenticated" })), needsTerraform &&
189
+ terraformStatus &&
190
+ !terraformStatus.installed && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", bold: true, children: "\u26A0 Terraform not installed" }), _jsx(Text, { color: "gray", children: "You'll need Terraform to provision infrastructure." }), _jsxs(Text, { color: "gray", children: ["Install: ", TERRAFORM_INSTALL_INFO.installCmd] }), _jsx(Text, { color: "gray", dimColor: true, children: TERRAFORM_INSTALL_INFO.url })] })), needsTerraform && terraformStatus?.installed && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", "Terraform", " ", terraformStatus.version
191
+ ? `v${terraformStatus.version}`
192
+ : "", " ", "detected"] })] }))] }), _jsx(SelectInput, { items: providerItems.map((p) => ({
193
+ label: p.label,
194
+ value: p.value,
195
+ })), onSelect: handleProviderSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => {
196
+ const item = providerItems.find((p) => p.label === label);
197
+ if (!item)
198
+ return _jsx(Text, { children: label });
199
+ const textColor = item.disabled
200
+ ? "gray"
201
+ : isSelected
202
+ ? colors.accent
203
+ : undefined;
204
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: textColor, dimColor: item.disabled, children: [isSelected && !item.disabled ? "❯ " : " ", label] }), renderStatusIndicator(item.status), item.status.identity && item.status.authenticated && (_jsxs(Text, { color: "gray", dimColor: true, children: [" ", "(", item.status.identity, ")"] }))] }));
205
+ } })] })), subStep === "gcp-project" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your GCP Project ID:" }), gcpProject && (_jsxs(Text, { color: "gray", dimColor: true, children: ["Detected project: ", gcpProject] })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: gcpProject, onChange: setGcpProject, onSubmit: handleGcpProjectSubmit, placeholder: "my-gcp-project" })] })] })), subStep === "azure-rg" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Azure Resource Group name:" }), _jsx(Text, { color: "gray", dimColor: true, children: "This resource group will contain all Rulebricks resources" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: azureRg, onChange: setAzureRg, onSubmit: handleAzureRgSubmit, placeholder: "rulebricks-rg" })] })] })), subStep === "region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: `Fetching ${state.provider?.toUpperCase()} regions...` }) })), subStep === "region" && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { children: ["Select a region for ", state.provider?.toUpperCase(), ":"] }), _jsxs(Text, { color: "gray", dimColor: true, children: [regions.length, " regions available"] })] }), _jsx(Box, { height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: regionItems, onSelect: handleRegionSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) })] })), subStep === "cluster-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: `Fetching ${state.provider?.toUpperCase()} clusters in ${state.region}...` }) })), subStep === "cluster-select" && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select your Kubernetes cluster:" }), _jsxs(Text, { color: "gray", dimColor: true, children: [clusters.length, " cluster", clusters.length !== 1 ? "s" : "", " found in ", state.region] })] }), _jsx(Box, { height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: clusters.map((c) => ({ label: c, value: c })), onSelect: handleClusterSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), state.provider && state.region && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", state.provider?.toUpperCase(), " \u2022 ", state.region] })] }))] })), subStep === "cluster" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the Kubernetes cluster name:" }), _jsx(Text, { color: "gray", dimColor: true, children: state.infrastructureMode === "provision"
206
+ ? "This cluster will be created"
207
+ : clusters.length === 0 && state.infrastructureMode === "existing"
208
+ ? "No clusters found in this region - enter the name manually"
209
+ : "Enter the name of your existing cluster" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: "\u276F " }), _jsx(TextInput, { value: clusterName, onChange: setClusterName, onSubmit: handleClusterSubmit, placeholder: "rulebricks-cluster" })] }), state.provider && state.region && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", state.provider?.toUpperCase(), " \u2022 ", state.region] })] }))] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
210
+ }
@@ -0,0 +1,6 @@
1
+ interface CredentialsStepProps {
2
+ onComplete: () => void;
3
+ onBack: () => void;
4
+ }
5
+ export declare function CredentialsStep({ onComplete, onBack }: CredentialsStepProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import TextInput from 'ink-text-input';
5
+ import { useWizard } from '../WizardContext.js';
6
+ import { BorderBox } from '../../common/index.js';
7
+ export function CredentialsStep({ onComplete, onBack }) {
8
+ const { state, dispatch } = useWizard();
9
+ const [licenseKey, setLicenseKey] = useState(state.licenseKey || '');
10
+ useInput((input, key) => {
11
+ if (key.escape) {
12
+ onBack();
13
+ }
14
+ });
15
+ const handleSubmit = () => {
16
+ if (!licenseKey)
17
+ return;
18
+ dispatch({ type: 'SET_LICENSE_KEY', key: licenseKey });
19
+ onComplete();
20
+ };
21
+ return (_jsxs(BorderBox, { title: "Credentials", children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Rulebricks license key:" }), _jsx(Text, { color: "gray", dimColor: true, children: "This is required to pull the Rulebricks Docker images" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "cyan", children: "\u276F " }), _jsx(TextInput, { value: licenseKey, onChange: setLicenseKey, onSubmit: handleSubmit, placeholder: "Enter your license key", mask: "*" })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
22
+ }
@@ -0,0 +1,6 @@
1
+ interface DatabaseStepProps {
2
+ onComplete: () => void;
3
+ onBack: () => void;
4
+ }
5
+ export declare function DatabaseStep({ onComplete, onBack }: DatabaseStepProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};