@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.
- package/README.md +62 -0
- package/dist/commands/clone.d.ts +6 -0
- package/dist/commands/clone.js +60 -0
- package/dist/commands/deploy.d.ts +8 -0
- package/dist/commands/deploy.js +409 -0
- package/dist/commands/destroy.d.ts +8 -0
- package/dist/commands/destroy.js +298 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +201 -0
- package/dist/commands/logs.d.ts +9 -0
- package/dist/commands/logs.js +222 -0
- package/dist/commands/open.d.ts +7 -0
- package/dist/commands/open.js +139 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.js +125 -0
- package/dist/commands/upgrade.d.ts +7 -0
- package/dist/commands/upgrade.js +239 -0
- package/dist/components/DNSWaitScreen.d.ts +9 -0
- package/dist/components/DNSWaitScreen.js +73 -0
- package/dist/components/Wizard/WizardContext.d.ts +176 -0
- package/dist/components/Wizard/WizardContext.js +346 -0
- package/dist/components/Wizard/index.d.ts +2 -0
- package/dist/components/Wizard/index.js +2 -0
- package/dist/components/Wizard/steps/CloudProviderStep.d.ts +6 -0
- package/dist/components/Wizard/steps/CloudProviderStep.js +210 -0
- package/dist/components/Wizard/steps/CredentialsStep.d.ts +6 -0
- package/dist/components/Wizard/steps/CredentialsStep.js +22 -0
- package/dist/components/Wizard/steps/DatabaseStep.d.ts +6 -0
- package/dist/components/Wizard/steps/DatabaseStep.js +80 -0
- package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +5 -0
- package/dist/components/Wizard/steps/DeploymentModeStep.js +26 -0
- package/dist/components/Wizard/steps/DomainStep.d.ts +6 -0
- package/dist/components/Wizard/steps/DomainStep.js +126 -0
- package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +6 -0
- package/dist/components/Wizard/steps/FeatureConfigStep.js +765 -0
- package/dist/components/Wizard/steps/FeaturesStep.d.ts +6 -0
- package/dist/components/Wizard/steps/FeaturesStep.js +119 -0
- package/dist/components/Wizard/steps/ReviewStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ReviewStep.js +56 -0
- package/dist/components/Wizard/steps/SMTPStep.d.ts +6 -0
- package/dist/components/Wizard/steps/SMTPStep.js +191 -0
- package/dist/components/Wizard/steps/SupabaseCredentialsStep.d.ts +6 -0
- package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +76 -0
- package/dist/components/Wizard/steps/TierStep.d.ts +6 -0
- package/dist/components/Wizard/steps/TierStep.js +29 -0
- package/dist/components/Wizard/steps/VersionStep.d.ts +6 -0
- package/dist/components/Wizard/steps/VersionStep.js +113 -0
- package/dist/components/Wizard/steps/index.d.ts +12 -0
- package/dist/components/Wizard/steps/index.js +12 -0
- package/dist/components/common/AppShell.d.ts +31 -0
- package/dist/components/common/AppShell.js +31 -0
- package/dist/components/common/Box.d.ts +20 -0
- package/dist/components/common/Box.js +20 -0
- package/dist/components/common/Logo.d.ts +7 -0
- package/dist/components/common/Logo.js +22 -0
- package/dist/components/common/Spinner.d.ts +12 -0
- package/dist/components/common/Spinner.js +28 -0
- package/dist/components/common/index.d.ts +6 -0
- package/dist/components/common/index.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +202 -0
- package/dist/lib/cloudCli.d.ts +156 -0
- package/dist/lib/cloudCli.js +691 -0
- package/dist/lib/config.d.ts +91 -0
- package/dist/lib/config.js +278 -0
- package/dist/lib/dns.d.ts +41 -0
- package/dist/lib/dns.js +235 -0
- package/dist/lib/dockerHub.d.ts +57 -0
- package/dist/lib/dockerHub.js +128 -0
- package/dist/lib/helm.d.ts +53 -0
- package/dist/lib/helm.js +209 -0
- package/dist/lib/helmValues.d.ts +17 -0
- package/dist/lib/helmValues.js +693 -0
- package/dist/lib/kubernetes.d.ts +161 -0
- package/dist/lib/kubernetes.js +755 -0
- package/dist/lib/terraform.d.ts +44 -0
- package/dist/lib/terraform.js +230 -0
- package/dist/lib/theme.d.ts +81 -0
- package/dist/lib/theme.js +115 -0
- package/dist/lib/validation.d.ts +47 -0
- package/dist/lib/validation.js +164 -0
- package/dist/lib/versions.d.ts +69 -0
- package/dist/lib/versions.js +139 -0
- package/dist/types/index.d.ts +718 -0
- package/dist/types/index.js +556 -0
- package/email-templates/email_change.html +325 -0
- package/email-templates/invite.html +383 -0
- package/email-templates/password_change.html +414 -0
- package/email-templates/verify.html +396 -0
- package/package.json +78 -0
- package/terraform/aws/main.tf +327 -0
- package/terraform/azure/main.tf +326 -0
- 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,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,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
|
+
}
|