@rulebricks/cli 2.1.7 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +51 -16
  2. package/cluster-setup/aws/README.md +96 -47
  3. package/cluster-setup/aws/check-aws-access.sh +216 -52
  4. package/cluster-setup/aws/parameters.json +13 -0
  5. package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
  6. package/cluster-setup/azure/README.md +103 -55
  7. package/cluster-setup/azure/check-aks-prereqs.sh +236 -56
  8. package/cluster-setup/azure/parameters.json +30 -0
  9. package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
  10. package/cluster-setup/gcp/README.md +51 -34
  11. package/cluster-setup/gcp/check-gke-prereqs.sh +222 -60
  12. package/dist/commands/backup.d.ts +5 -0
  13. package/dist/commands/backup.js +104 -0
  14. package/dist/commands/deploy.d.ts +3 -1
  15. package/dist/commands/deploy.js +226 -326
  16. package/dist/commands/destroy.d.ts +1 -1
  17. package/dist/commands/destroy.js +73 -123
  18. package/dist/commands/init.d.ts +5 -1
  19. package/dist/commands/init.js +78 -54
  20. package/dist/commands/list.d.ts +1 -0
  21. package/dist/commands/list.js +74 -0
  22. package/dist/commands/open.d.ts +1 -1
  23. package/dist/commands/open.js +4 -12
  24. package/dist/commands/redeploy.d.ts +6 -0
  25. package/dist/commands/redeploy.js +310 -0
  26. package/dist/commands/restore.d.ts +5 -0
  27. package/dist/commands/restore.js +338 -0
  28. package/dist/commands/status.js +62 -49
  29. package/dist/commands/upgrade.js +74 -51
  30. package/dist/components/DNSWaitScreen.d.ts +5 -1
  31. package/dist/components/DNSWaitScreen.js +47 -41
  32. package/dist/components/Wizard/WizardContext.d.ts +157 -36
  33. package/dist/components/Wizard/WizardContext.js +872 -160
  34. package/dist/components/Wizard/steps/CloudProviderStep.js +192 -107
  35. package/dist/components/Wizard/steps/DomainStep.js +5 -24
  36. package/dist/components/Wizard/steps/ExternalServicesStep.d.ts +6 -0
  37. package/dist/components/Wizard/steps/ExternalServicesStep.js +645 -0
  38. package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +2 -1
  39. package/dist/components/Wizard/steps/FeatureConfigStep.js +739 -425
  40. package/dist/components/Wizard/steps/FeaturesStep.js +31 -35
  41. package/dist/components/Wizard/steps/ObservabilityStep.d.ts +6 -0
  42. package/dist/components/Wizard/steps/ObservabilityStep.js +137 -0
  43. package/dist/components/Wizard/steps/ReviewStep.d.ts +2 -1
  44. package/dist/components/Wizard/steps/ReviewStep.js +56 -12
  45. package/dist/components/Wizard/steps/StorageStep.d.ts +9 -0
  46. package/dist/components/Wizard/steps/StorageStep.js +592 -0
  47. package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +20 -21
  48. package/dist/components/Wizard/steps/VersionStep.js +45 -23
  49. package/dist/components/Wizard/steps/index.d.ts +3 -3
  50. package/dist/components/Wizard/steps/index.js +3 -3
  51. package/dist/components/common/CommandApproval.d.ts +12 -0
  52. package/dist/components/common/CommandApproval.js +91 -0
  53. package/dist/components/common/DeploymentPicker.d.ts +14 -0
  54. package/dist/components/common/DeploymentPicker.js +16 -0
  55. package/dist/components/common/index.d.ts +2 -0
  56. package/dist/components/common/index.js +2 -0
  57. package/dist/index.js +94 -62
  58. package/dist/lib/cloudCli.d.ts +134 -63
  59. package/dist/lib/cloudCli.js +512 -220
  60. package/dist/lib/clusterSetupDefaults.d.ts +30 -0
  61. package/dist/lib/clusterSetupDefaults.js +64 -0
  62. package/dist/lib/commandApproval.d.ts +26 -0
  63. package/dist/lib/commandApproval.js +114 -0
  64. package/dist/lib/config.d.ts +12 -10
  65. package/dist/lib/config.js +91 -33
  66. package/dist/lib/configFixtures.d.ts +5 -0
  67. package/dist/lib/configFixtures.js +513 -0
  68. package/dist/lib/deploymentHealth.d.ts +32 -0
  69. package/dist/lib/deploymentHealth.js +157 -0
  70. package/dist/lib/dns.d.ts +1 -1
  71. package/dist/lib/dns.js +19 -1
  72. package/dist/lib/dns.test.d.ts +1 -0
  73. package/dist/lib/dns.test.js +27 -0
  74. package/dist/lib/dockerHub.d.ts +12 -1
  75. package/dist/lib/dockerHub.js +18 -8
  76. package/dist/lib/helm.d.ts +4 -0
  77. package/dist/lib/helm.js +16 -0
  78. package/dist/lib/helmValues.d.ts +25 -0
  79. package/dist/lib/helmValues.js +1762 -289
  80. package/dist/lib/helmValues.test.d.ts +1 -0
  81. package/dist/lib/helmValues.test.js +966 -0
  82. package/dist/lib/htpasswd.d.ts +1 -0
  83. package/dist/lib/htpasswd.js +15 -0
  84. package/dist/lib/kubernetes.d.ts +124 -17
  85. package/dist/lib/kubernetes.js +576 -145
  86. package/dist/lib/secrets.d.ts +23 -0
  87. package/dist/lib/secrets.js +158 -0
  88. package/dist/lib/validateValues.d.ts +31 -0
  89. package/dist/lib/validateValues.js +253 -0
  90. package/dist/lib/versions.d.ts +82 -11
  91. package/dist/lib/versions.js +131 -31
  92. package/dist/lib/versions.test.d.ts +1 -0
  93. package/dist/lib/versions.test.js +81 -0
  94. package/dist/lib/wizardSteps.d.ts +14 -0
  95. package/dist/lib/wizardSteps.js +23 -0
  96. package/dist/lib/workloadIdentity.d.ts +26 -0
  97. package/dist/lib/workloadIdentity.js +323 -0
  98. package/dist/lib/workloadIdentity.test.d.ts +1 -0
  99. package/dist/lib/workloadIdentity.test.js +57 -0
  100. package/dist/types/index.d.ts +1860 -164
  101. package/dist/types/index.js +518 -295
  102. package/package.json +9 -4
  103. package/schema/values.schema.json +1934 -0
  104. package/cluster-setup/aws/cluster.yaml +0 -33
  105. package/cluster-setup/azure/main.bicep +0 -282
  106. package/cluster-setup/azure/main.parameters.json +0 -21
  107. package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
  108. package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
  109. package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
  110. package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
  111. package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
  112. package/dist/components/Wizard/steps/TierStep.js +0 -29
  113. package/dist/lib/terraform.d.ts +0 -66
  114. package/dist/lib/terraform.js +0 -754
  115. package/terraform/aws/main.tf +0 -355
  116. package/terraform/azure/main.tf +0 -371
  117. package/terraform/gcp/main.tf +0 -407
@@ -5,17 +5,19 @@ import TextInput from "ink-text-input";
5
5
  import { useWizard } from "../WizardContext.js";
6
6
  import { BorderBox } from "../../common/index.js";
7
7
  import { generateSecureSecret } from "../../../lib/validation.js";
8
- // Fixed JWT secret - users can edit this in the raw config if needed
9
- const JWT_SECRET = "your-super-secret-jwt-token-with-at-least-32-characters-long";
10
8
  export function SupabaseCredentialsStep({ onComplete, onBack, }) {
11
9
  const { state, dispatch } = useWizard();
12
- // Generate defaults if not already set
13
- const defaultDbPass = state.supabaseDbPassword || generateSecureSecret(24);
14
- const defaultDashboardPass = state.supabaseDashboardPass || generateSecureSecret(16);
10
+ // Secure fallbacks used only when the user leaves a field empty. Generated
11
+ // once so they stay stable across renders. The inputs themselves start empty
12
+ // (or prefilled with an existing value when editing) so there's no confusing
13
+ // pre-filled secret to clear.
14
+ const [defaultDbPass] = useState(() => generateSecureSecret(24));
15
+ const [defaultDashboardPass] = useState(() => generateSecureSecret(16));
16
+ const [defaultJwtSecret] = useState(() => state.supabaseJwtSecret || generateSecureSecret(64));
15
17
  const [subStep, setSubStep] = useState("db-password");
16
- const [dbPassword, setDbPassword] = useState(defaultDbPass);
18
+ const [dbPassword, setDbPassword] = useState(state.supabaseDbPassword || "");
17
19
  const [dashboardUser, setDashboardUser] = useState(state.supabaseDashboardUser || "supabase");
18
- const [dashboardPass, setDashboardPass] = useState(defaultDashboardPass);
20
+ const [dashboardPass, setDashboardPass] = useState(state.supabaseDashboardPass || "");
19
21
  const [error, setError] = useState(null);
20
22
  useInput((input, key) => {
21
23
  if (key.escape) {
@@ -32,10 +34,13 @@ export function SupabaseCredentialsStep({ onComplete, onBack, }) {
32
34
  }
33
35
  });
34
36
  const handleDbPasswordSubmit = () => {
35
- if (!dbPassword || dbPassword.length < 8) {
37
+ // Empty means "use a generated secure value".
38
+ const effective = dbPassword.trim() || defaultDbPass;
39
+ if (effective.length < 8) {
36
40
  setError("Database password must be at least 8 characters");
37
41
  return;
38
42
  }
43
+ setDbPassword(effective);
39
44
  setError(null);
40
45
  setSubStep("dashboard-user");
41
46
  };
@@ -48,7 +53,9 @@ export function SupabaseCredentialsStep({ onComplete, onBack, }) {
48
53
  setSubStep("dashboard-pass");
49
54
  };
50
55
  const handleDashboardPassSubmit = () => {
51
- if (!dashboardPass || dashboardPass.length < 8) {
56
+ // Empty means "use a generated secure value".
57
+ const effectivePass = dashboardPass.trim() || defaultDashboardPass;
58
+ if (effectivePass.length < 8) {
52
59
  setError("Dashboard password must be at least 8 characters");
53
60
  return;
54
61
  }
@@ -56,21 +63,13 @@ export function SupabaseCredentialsStep({ onComplete, onBack, }) {
56
63
  dispatch({
57
64
  type: "SET_SUPABASE_SELF_HOSTED",
58
65
  config: {
59
- supabaseJwtSecret: JWT_SECRET,
60
- supabaseDbPassword: dbPassword,
66
+ supabaseJwtSecret: defaultJwtSecret,
67
+ supabaseDbPassword: dbPassword.trim() || defaultDbPass,
61
68
  supabaseDashboardUser: dashboardUser,
62
- supabaseDashboardPass: dashboardPass,
69
+ supabaseDashboardPass: effectivePass,
63
70
  },
64
71
  });
65
72
  onComplete();
66
73
  };
67
- const regenerateSecret = (field) => {
68
- if (field === "db") {
69
- setDbPassword(generateSecureSecret(24));
70
- }
71
- else {
72
- setDashboardPass(generateSecureSecret(16));
73
- }
74
- };
75
- return (_jsxs(BorderBox, { title: "Supabase Credentials", children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "gray", dimColor: true, children: "Configure credentials for your self-hosted Supabase instance" }), _jsx(Text, { color: "yellow", dimColor: true, children: "\u26A0 Save these credentials securely - you'll need them to access Supabase" })] }), subStep === "db-password" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Database Password:" }), _jsx(Text, { color: "gray", dimColor: true, children: "PostgreSQL database password" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Default (press Enter to use): ", defaultDbPass] }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: dbPassword, onChange: setDbPassword, onSubmit: handleDbPasswordSubmit, placeholder: "Database password (min 8 chars)", mask: "*" }) })] })), subStep === "dashboard-user" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Supabase Studio Username:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Username for accessing the Supabase dashboard" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: dashboardUser, onChange: setDashboardUser, onSubmit: handleDashboardUserSubmit, placeholder: "supabase" }) }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsx(Text, { color: "gray", children: " Database password configured" })] }) })] })), subStep === "dashboard-pass" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Supabase Studio Password:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Password for accessing the Supabase dashboard" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Default (press Enter to use): ", defaultDashboardPass] }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: dashboardPass, onChange: setDashboardPass, onSubmit: handleDashboardPassSubmit, placeholder: "Dashboard password (min 8 chars)", mask: "*" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsx(Text, { color: "gray", children: " Database password configured" })] }), _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Dashboard user: ", dashboardUser] })] })] })] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
74
+ return (_jsxs(BorderBox, { title: "Supabase Credentials", children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "gray", dimColor: true, children: "Configure credentials for your self-hosted Supabase instance" }), _jsx(Text, { color: "yellow", dimColor: true, children: "\u26A0 Save these credentials securely - you'll need them to access Supabase" })] }), subStep === "db-password" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Database Password:" }), _jsx(Text, { color: "gray", dimColor: true, children: "PostgreSQL database password. Leave empty to generate a secure value." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: dbPassword, onChange: setDbPassword, onSubmit: handleDbPasswordSubmit, placeholder: "Leave empty to generate a secure value", mask: "*" }) })] })), subStep === "dashboard-user" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Supabase Studio Username:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Username for accessing the Supabase dashboard" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: dashboardUser, onChange: setDashboardUser, onSubmit: handleDashboardUserSubmit, placeholder: "supabase" }) }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsx(Text, { color: "gray", children: " Database password configured" })] }) })] })), subStep === "dashboard-pass" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Supabase Studio Password:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Password for accessing the Supabase dashboard. Leave empty to generate a secure value." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: dashboardPass, onChange: setDashboardPass, onSubmit: handleDashboardPassSubmit, placeholder: "Leave empty to generate a secure value", mask: "*" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsx(Text, { color: "gray", children: " Database password configured" })] }), _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Dashboard user: ", dashboardUser] })] })] })] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
76
75
  }
@@ -7,8 +7,9 @@ import { useWizard } from "../WizardContext.js";
7
7
  import { BorderBox, useTheme } from "../../common/index.js";
8
8
  import { Spinner } from "../../common/Spinner.js";
9
9
  import { fetchAppVersions, formatDate } from "../../../lib/versions.js";
10
- import { CHANGELOG_URL } from "../../../types/index.js";
10
+ import { CHANGELOG_URL, } from "../../../types/index.js";
11
11
  import { formatVersionDisplay } from "../../../lib/dockerHub.js";
12
+ import { inferClusterCapabilities } from "../../../lib/kubernetes.js";
12
13
  export function VersionStep({ onComplete, onBack }) {
13
14
  const { state, dispatch } = useWizard();
14
15
  const { colors } = useTheme();
@@ -32,10 +33,44 @@ export function VersionStep({ onComplete, onBack }) {
32
33
  setSubStep("loading-versions");
33
34
  setLoadError(null);
34
35
  try {
35
- const appVersions = await fetchAppVersions(licenseKey);
36
+ // Scan the cluster for its capabilities (node architecture, storage
37
+ // class, ARM tolerations) and store them in wizard state, unless an
38
+ // earlier scan already populated them. The architecture is needed to
39
+ // fetch the matching image versions below.
40
+ let architecture;
41
+ if (state.nodeArchitecture === "amd64" ||
42
+ state.nodeArchitecture === "arm64") {
43
+ architecture = state.nodeArchitecture;
44
+ }
45
+ else {
46
+ const capabilities = await inferClusterCapabilities();
47
+ if (capabilities) {
48
+ dispatch({
49
+ type: "SET_CLUSTER_CAPABILITIES",
50
+ nodeArchitecture: capabilities.nodeArchitecture,
51
+ arm64TolerationRequired: capabilities.arm64TolerationRequired,
52
+ storageClass: capabilities.storageClass,
53
+ storageProvisioner: capabilities.storageProvisioner,
54
+ schedulableNodeCount: capabilities.schedulableNodeCount,
55
+ totalCpuCores: capabilities.totalCpuCores,
56
+ totalMemoryGi: capabilities.totalMemoryGi,
57
+ eligibleCpuCores: capabilities.eligibleCpuCores,
58
+ eligibleMemoryGi: capabilities.eligibleMemoryGi,
59
+ totalPersistentStorageGi: capabilities.totalPersistentStorageGi ?? 0,
60
+ });
61
+ if (capabilities.nodeArchitecture === "amd64" ||
62
+ capabilities.nodeArchitecture === "arm64") {
63
+ architecture = capabilities.nodeArchitecture;
64
+ }
65
+ }
66
+ }
67
+ const appVersions = await fetchAppVersions(licenseKey, architecture);
36
68
  setVersions(appVersions);
37
- if (appVersions.length === 0) {
38
- setLoadError("No versions found. Using latest.");
69
+ if (appVersions.length === 0 && architecture) {
70
+ setLoadError(`No compatible Rulebricks version found for ${architecture} nodes.`);
71
+ }
72
+ else if (appVersions.length === 0) {
73
+ setLoadError("No Rulebricks versions found.");
39
74
  }
40
75
  setSubStep("version");
41
76
  }
@@ -67,9 +102,8 @@ export function VersionStep({ onComplete, onBack }) {
67
102
  };
68
103
  const handleVersionSelect = (item) => {
69
104
  dispatch({
70
- type: "SET_APP_VERSION",
71
- appVersion: item.value,
72
- hpsVersion: item.hpsVersion || item.value,
105
+ type: "SET_VERSION",
106
+ version: item.value,
73
107
  });
74
108
  onComplete();
75
109
  };
@@ -82,32 +116,20 @@ export function VersionStep({ onComplete, onBack }) {
82
116
  {
83
117
  label: `✨ Latest (${formatVersionDisplay(latestVersion.version)})`,
84
118
  value: latestVersion.version,
85
- hpsVersion: latestVersion.hpsVersion || latestVersion.version,
86
119
  releaseDate: latestVersion.releaseDate,
87
120
  },
88
121
  // Skip the first version since it's shown as "Latest"
89
122
  ...versions.slice(1, 15).map((v) => ({
90
123
  label: formatVersionDisplay(v.version),
91
124
  value: v.version,
92
- hpsVersion: v.hpsVersion || v.version,
93
125
  releaseDate: v.releaseDate,
94
126
  })),
95
127
  ]
96
- : [
97
- {
98
- label: "✨ Latest (recommended)",
99
- value: "latest",
100
- hpsVersion: "latest",
101
- releaseDate: null,
102
- },
103
- ];
104
- return (_jsxs(BorderBox, { title: "License & Version", children: [subStep === "license" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Rulebricks license key:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Get a license at https://rulebricks.com/pricing" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: licenseKey, onChange: setLicenseKey, onSubmit: handleLicenseSubmit, placeholder: "vd67aveCHr1G..." }) })] })), subStep === "loading-versions" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Spinner, { label: "Authenticating and fetching versions..." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Connecting to Docker Hub..." }) })] })), subStep === "version" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select app version to deploy:" }), loadError && (_jsxs(Text, { color: colors.warning, dimColor: true, children: ["\u26A0 ", loadError] })), _jsx(Box, { marginTop: 1, height: 12, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: versionItems, onSelect: handleVersionSelect, limit: 10, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => {
128
+ : [];
129
+ return (_jsxs(BorderBox, { title: "License & Version", children: [subStep === "license" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Rulebricks license key:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Get a license at https://rulebricks.com/pricing" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: licenseKey, onChange: setLicenseKey, onSubmit: handleLicenseSubmit, placeholder: "vd67aveCHr1G..." }) })] })), subStep === "loading-versions" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Spinner, { label: "Authenticating and fetching versions..." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Connecting to Docker Hub..." }) })] })), subStep === "version" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select Rulebricks version to deploy:" }), loadError && (_jsxs(Text, { color: colors.warning, dimColor: true, children: ["\u26A0 ", loadError] })), _jsx(Box, { marginTop: 1, height: 12, flexDirection: "column", overflowY: "hidden", children: versionItems.length > 0 ? (_jsx(SelectInput, { items: versionItems, onSelect: handleVersionSelect, limit: 10, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => {
105
130
  // Find the version item from the list using the label
106
131
  const versionItem = versionItems.find((v) => v.label === label);
107
- const hasHps = versionItem?.value &&
108
- versionItem?.hpsVersion &&
109
- versionItem.value !== "latest";
110
132
  const hasDate = versionItem?.releaseDate;
111
- return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] }), hasDate && (_jsxs(Text, { color: colors.muted, children: [" ", "(", formatDate(versionItem.releaseDate), ")"] })), hasHps && (_jsxs(Text, { color: colors.muted, children: [" ", "Solver:", " ", formatVersionDisplay(versionItem.hpsVersion)] }))] }));
112
- } }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", "License key: ", licenseKey.substring(0, 8), "..."] })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", dimColor: true, children: ["View changelog: ", CHANGELOG_URL] }) })] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.error, children: ["\u2717 ", error] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
133
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] }), hasDate && (_jsxs(Text, { color: colors.muted, children: [" ", "(", formatDate(versionItem.releaseDate), ")"] }))] }));
134
+ } })) : (_jsx(Text, { color: colors.warning, children: "No compatible image versions are available for this cluster." })) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", "License key: ", licenseKey.substring(0, 8), "..."] })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", dimColor: true, children: ["View changelog: ", CHANGELOG_URL] }) })] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.error, children: ["\u2717 ", error] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
113
135
  }
@@ -1,12 +1,12 @@
1
- export { DeploymentModeStep } from './DeploymentModeStep.js';
2
1
  export { CloudProviderStep } from './CloudProviderStep.js';
3
2
  export { DomainStep } from './DomainStep.js';
4
3
  export { SMTPStep } from './SMTPStep.js';
5
4
  export { DatabaseStep } from './DatabaseStep.js';
6
5
  export { SupabaseCredentialsStep } from './SupabaseCredentialsStep.js';
7
- export { TierStep } from './TierStep.js';
8
6
  export { FeaturesStep } from './FeaturesStep.js';
7
+ export { StorageStep } from './StorageStep.js';
8
+ export { ObservabilityStep } from './ObservabilityStep.js';
9
+ export { ExternalServicesStep } from './ExternalServicesStep.js';
9
10
  export { FeatureConfigStep } from './FeatureConfigStep.js';
10
11
  export { VersionStep } from './VersionStep.js';
11
- export { CredentialsStep } from './CredentialsStep.js';
12
12
  export { ReviewStep } from './ReviewStep.js';
@@ -1,12 +1,12 @@
1
- export { DeploymentModeStep } from './DeploymentModeStep.js';
2
1
  export { CloudProviderStep } from './CloudProviderStep.js';
3
2
  export { DomainStep } from './DomainStep.js';
4
3
  export { SMTPStep } from './SMTPStep.js';
5
4
  export { DatabaseStep } from './DatabaseStep.js';
6
5
  export { SupabaseCredentialsStep } from './SupabaseCredentialsStep.js';
7
- export { TierStep } from './TierStep.js';
8
6
  export { FeaturesStep } from './FeaturesStep.js';
7
+ export { StorageStep } from './StorageStep.js';
8
+ export { ObservabilityStep } from './ObservabilityStep.js';
9
+ export { ExternalServicesStep } from './ExternalServicesStep.js';
9
10
  export { FeatureConfigStep } from './FeatureConfigStep.js';
10
11
  export { VersionStep } from './VersionStep.js';
11
- export { CredentialsStep } from './CredentialsStep.js';
12
12
  export { ReviewStep } from './ReviewStep.js';
@@ -0,0 +1,12 @@
1
+ import { ReactNode } from "react";
2
+ import { useInput } from "ink";
3
+ interface CommandApprovalContextValue {
4
+ pending: boolean;
5
+ }
6
+ interface CommandApprovalProviderProps {
7
+ children: ReactNode;
8
+ }
9
+ export declare function CommandApprovalProvider({ children, }: CommandApprovalProviderProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function useCommandApproval(): CommandApprovalContextValue;
11
+ export declare function useGatedInput(inputHandler: Parameters<typeof useInput>[0], options?: Parameters<typeof useInput>[1]): void;
12
+ export {};
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import SelectInput from "ink-select-input";
5
+ import { useTheme } from "../../lib/theme.js";
6
+ import { getCurrentCommandApproval, respondToCommandApproval, setCommandApprovalInteractive, subscribeCommandApprovals, } from "../../lib/commandApproval.js";
7
+ const CommandApprovalContext = createContext({
8
+ pending: false,
9
+ });
10
+ export function CommandApprovalProvider({ children, }) {
11
+ const [pending, setPending] = useState(getCurrentCommandApproval());
12
+ useEffect(() => {
13
+ setCommandApprovalInteractive(true);
14
+ const unsubscribe = subscribeCommandApprovals(() => {
15
+ setPending(getCurrentCommandApproval());
16
+ });
17
+ setPending(getCurrentCommandApproval());
18
+ return () => {
19
+ unsubscribe();
20
+ setCommandApprovalInteractive(false);
21
+ };
22
+ }, []);
23
+ return (_jsx(CommandApprovalContext.Provider, { value: { pending: pending !== null }, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { display: pending ? "none" : "flex", flexDirection: "column", children: children }), pending && _jsx(CommandApprovalScreen, { request: pending })] }) }));
24
+ }
25
+ export function useCommandApproval() {
26
+ return useContext(CommandApprovalContext);
27
+ }
28
+ export function useGatedInput(inputHandler, options = {}) {
29
+ const { pending } = useCommandApproval();
30
+ useInput(inputHandler, {
31
+ ...options,
32
+ isActive: (options?.isActive ?? true) && !pending,
33
+ });
34
+ }
35
+ function CommandApprovalScreen({ request, }) {
36
+ const { colors } = useTheme();
37
+ const tagColor = request.mutating ? colors.warning : colors.success;
38
+ const tag = request.mutating ? "modifies cloud resources" : "read-only";
39
+ const provider = request.provider ? request.provider.toUpperCase() : "CLOUD CLI";
40
+ const approve = (scope) => {
41
+ respondToCommandApproval(request.id, "approve", scope);
42
+ };
43
+ const deny = () => {
44
+ respondToCommandApproval(request.id, "deny", "once");
45
+ };
46
+ useInput((input) => {
47
+ if (input === "y" || input === "Y") {
48
+ approve("once");
49
+ }
50
+ else if (input === "a" || input === "A") {
51
+ approve("all-like");
52
+ }
53
+ else if (input === "n" || input === "N") {
54
+ deny();
55
+ }
56
+ });
57
+ const items = [
58
+ { label: "Approve", value: "once" },
59
+ { label: `Approve all "${request.intent}" commands`, value: "all-like" },
60
+ { label: "Deny", value: "deny" },
61
+ ];
62
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Text, { color: colors.accent, children: ["\u250C\u2500 Cloud CLI Approval ", "─".repeat(51), "\u2510"] }), _jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, width: 76, children: [_jsx(Box, { children: _jsx(Text, { color: colors.accent, bold: true, children: request.intent }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.muted, children: provider }), _jsx(Text, { color: colors.muted, children: " \u2022 " }), _jsx(Text, { color: tagColor, children: tag })] }), request.description && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, children: request.description }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "The Rulebricks CLI wants to run:" }), _jsx(Box, { flexDirection: "column", marginTop: 1, paddingX: 1, children: wrapCommand(request.command).map((line, index) => (_jsxs(Text, { color: colors.accentBright, children: [index === 0 ? "$ " : " ", line] }, index))) })] }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(SelectInput, { items: items, onSelect: (item) => {
63
+ if (item.value === "deny") {
64
+ deny();
65
+ }
66
+ else {
67
+ approve(item.value);
68
+ }
69
+ }, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "> " : " ", label] })) }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, dimColor: true, children: "y approve \u2022 a approve all like this \u2022 n deny" }), _jsx(Text, { color: colors.muted, dimColor: true, children: "You can run this yourself in another terminal, then choose Deny." })] })] }), _jsxs(Text, { color: colors.accent, children: ["\u2514", "─".repeat(74), "\u2518"] })] }));
70
+ }
71
+ function wrapCommand(command, width = 68) {
72
+ const words = command.split(" ");
73
+ const lines = [];
74
+ let current = "";
75
+ for (const word of words) {
76
+ if (!current) {
77
+ current = word;
78
+ continue;
79
+ }
80
+ if (`${current} ${word}`.length > width) {
81
+ lines.push(current);
82
+ current = word;
83
+ }
84
+ else {
85
+ current = `${current} ${word}`;
86
+ }
87
+ }
88
+ if (current)
89
+ lines.push(current);
90
+ return lines.length > 0 ? lines : [command];
91
+ }
@@ -0,0 +1,14 @@
1
+ interface DeploymentPickerProps {
2
+ deployments: string[];
3
+ /** Verb phrase shown in the prompt, e.g. "deploy" or "view logs for". */
4
+ action?: string;
5
+ onSelect: (name: string) => void;
6
+ onCancel: () => void;
7
+ }
8
+ /**
9
+ * Interactive deployment selector shown when a command is run without a
10
+ * deployment name and more than one deployment exists. Kept standalone (no
11
+ * ThemeProvider) so it can render before a command's own Ink tree mounts.
12
+ */
13
+ export declare function DeploymentPicker({ deployments, action, onSelect, onCancel, }: DeploymentPickerProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
@@ -0,0 +1,16 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from "ink";
3
+ import SelectInput from "ink-select-input";
4
+ /**
5
+ * Interactive deployment selector shown when a command is run without a
6
+ * deployment name and more than one deployment exists. Kept standalone (no
7
+ * ThemeProvider) so it can render before a command's own Ink tree mounts.
8
+ */
9
+ export function DeploymentPicker({ deployments, action, onSelect, onCancel, }) {
10
+ useInput((input, key) => {
11
+ if (key.escape) {
12
+ onCancel();
13
+ }
14
+ });
15
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { bold: true, children: ["Select a deployment", action ? ` to ${action}` : "", ":"] }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(SelectInput, { items: deployments.map((d) => ({ label: d, value: d })), onSelect: (item) => onSelect(item.value), limit: 10, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? "cyan" : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "\u2191/\u2193 to navigate \u2022 Enter to select \u2022 Esc to cancel" }) })] }));
16
+ }
@@ -1,6 +1,8 @@
1
1
  export { BorderBox, Section, ProgressBar } from "./Box.js";
2
2
  export { Spinner, StatusLine } from "./Spinner.js";
3
3
  export { AppShell, ScreenContainer, ProgressHeader } from "./AppShell.js";
4
+ export { DeploymentPicker } from "./DeploymentPicker.js";
4
5
  export { Logo, LOGO_LINES } from "./Logo.js";
6
+ export { CommandApprovalProvider, useCommandApproval, useGatedInput, } from "./CommandApproval.js";
5
7
  export { ThemeProvider, useTheme, THEMES } from "../../lib/theme.js";
6
8
  export type { CommandTheme, ThemeColors } from "../../lib/theme.js";
@@ -1,5 +1,7 @@
1
1
  export { BorderBox, Section, ProgressBar } from "./Box.js";
2
2
  export { Spinner, StatusLine } from "./Spinner.js";
3
3
  export { AppShell, ScreenContainer, ProgressHeader } from "./AppShell.js";
4
+ export { DeploymentPicker } from "./DeploymentPicker.js";
4
5
  export { Logo, LOGO_LINES } from "./Logo.js";
6
+ export { CommandApprovalProvider, useCommandApproval, useGatedInput, } from "./CommandApproval.js";
5
7
  export { ThemeProvider, useTheme, THEMES } from "../../lib/theme.js";