@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
@@ -4,13 +4,29 @@ import { Box, Text, useApp, useInput } from "ink";
4
4
  import SelectInput from "ink-select-input";
5
5
  import { BorderBox, Spinner, ThemeProvider, useTheme, Logo, } from "../components/common/index.js";
6
6
  import { loadDeploymentConfig, loadDeploymentState, updateDeploymentStatus, getHelmValuesPath, } from "../lib/config.js";
7
- import { upgradeChart, dryRunUpgrade } from "../lib/helm.js";
8
- import { formatDate, getAppVersionInfo, } from "../lib/versions.js";
7
+ import { upgradeChart, dryRunUpgrade, getInstalledChartVersion, } from "../lib/helm.js";
8
+ import { formatDate, getAppVersionInfo, hasRegistryDigestMismatch, } from "../lib/versions.js";
9
9
  import { formatVersionDisplay, normalizeVersion } from "../lib/dockerHub.js";
10
10
  import { CHANGELOG_URL, getNamespace, getReleaseName, } from "../types/index.js";
11
- import { getDeployedImageVersions, rolloutRestart } from "../lib/kubernetes.js";
11
+ import { getDeployedImageVersions, rolloutRestart, } from "../lib/kubernetes.js";
12
12
  import fs from "fs/promises";
13
13
  import YAML from "yaml";
14
+ function hasSameVersionHpsPatch(version, deployedVersions) {
15
+ if (!deployedVersions) {
16
+ return false;
17
+ }
18
+ const hpsVersionMatches = deployedVersions.hpsVersion &&
19
+ normalizeVersion(deployedVersions.hpsVersion) ===
20
+ normalizeVersion(version.version);
21
+ const workerVersionMatches = deployedVersions.hpsWorkerVersion &&
22
+ normalizeVersion(deployedVersions.hpsWorkerVersion) ===
23
+ normalizeVersion(version.version);
24
+ if (!hpsVersionMatches && !workerVersionMatches) {
25
+ return false;
26
+ }
27
+ return (hasRegistryDigestMismatch(deployedVersions.hpsDigests, version.hpsDigests) ||
28
+ hasRegistryDigestMismatch(deployedVersions.hpsWorkerDigests, version.hpsWorkerDigests));
29
+ }
14
30
  function UpgradeCommandInner({ name, targetVersion, dryRun, }) {
15
31
  const { exit } = useApp();
16
32
  const { colors } = useTheme();
@@ -22,6 +38,15 @@ function UpgradeCommandInner({ name, targetVersion, dryRun, }) {
22
38
  const [dryRunOutput, setDryRunOutput] = useState(null);
23
39
  // Store actual deployed HPS version separately (may differ from expected)
24
40
  const [deployedHpsVersion, setDeployedHpsVersion] = useState(null);
41
+ const [deployedVersions, setDeployedVersions] = useState(null);
42
+ async function resolvePinnedChartVersion(namespace, releaseName) {
43
+ const state = await loadDeploymentState(name);
44
+ const stateChartVersion = state?.application?.chartVersion;
45
+ if (stateChartVersion && stateChartVersion !== "latest") {
46
+ return stateChartVersion;
47
+ }
48
+ return (await getInstalledChartVersion(releaseName, namespace)) || undefined;
49
+ }
25
50
  useEffect(() => {
26
51
  loadVersions();
27
52
  }, []);
@@ -34,11 +59,14 @@ function UpgradeCommandInner({ name, targetVersion, dryRun, }) {
34
59
  const namespace = state?.application?.namespace || getNamespace(name);
35
60
  const releaseName = getReleaseName(name);
36
61
  const deployedVersions = await getDeployedImageVersions(releaseName, namespace);
62
+ setDeployedVersions(deployedVersions);
37
63
  // Use deployed version from K8s, fall back to state file if K8s query fails
38
- const currentAppVersion = deployedVersions.appVersion || state?.application?.appVersion || null;
64
+ const currentVersion = deployedVersions.appVersion ||
65
+ state?.application?.version ||
66
+ null;
39
67
  // Store actual deployed HPS version for display
40
- setDeployedHpsVersion(deployedVersions.hpsVersion || state?.application?.hpsVersion || null);
41
- const info = await getAppVersionInfo(cfg.licenseKey, currentAppVersion);
68
+ setDeployedHpsVersion(deployedVersions.hpsVersion || null);
69
+ const info = await getAppVersionInfo(cfg.licenseKey, currentVersion);
42
70
  setVersionInfo(info);
43
71
  if (targetVersion) {
44
72
  // Find the version in available list
@@ -68,13 +96,18 @@ function UpgradeCommandInner({ name, targetVersion, dryRun, }) {
68
96
  }
69
97
  async function performDryRun(version) {
70
98
  try {
71
- // Update helm values with new image tags before dry run
99
+ // Update Helm values with the unified product version before dry run
72
100
  await updateHelmValuesWithVersion(version);
73
101
  const state = await loadDeploymentState(name);
74
102
  // Use namespace from state if available (backwards compat), otherwise compute from deployment name
75
103
  const namespace = state?.application?.namespace || getNamespace(name);
76
104
  const releaseName = getReleaseName(name);
77
- const output = await dryRunUpgrade(name, { releaseName, namespace });
105
+ const chartVersion = await resolvePinnedChartVersion(namespace, releaseName);
106
+ const output = await dryRunUpgrade(name, {
107
+ releaseName,
108
+ namespace,
109
+ version: chartVersion,
110
+ });
78
111
  setDryRunOutput(output);
79
112
  setStep("complete");
80
113
  }
@@ -88,30 +121,10 @@ function UpgradeCommandInner({ name, targetVersion, dryRun, }) {
88
121
  try {
89
122
  const content = await fs.readFile(valuesPath, "utf8");
90
123
  const values = YAML.parse(content);
91
- // Update image tags
92
- if (!values.rulebricks) {
93
- values.rulebricks = {};
124
+ if (!values.global) {
125
+ values.global = {};
94
126
  }
95
- const rulebricks = values.rulebricks;
96
- // Update app image tag
97
- if (!rulebricks.app) {
98
- rulebricks.app = {};
99
- }
100
- const app = rulebricks.app;
101
- if (!app.image) {
102
- app.image = {};
103
- }
104
- app.image.tag = version.version;
105
- // Update HPS image tag
106
- if (!rulebricks.hps) {
107
- rulebricks.hps = {};
108
- }
109
- const hps = rulebricks.hps;
110
- if (!hps.image) {
111
- hps.image = {};
112
- }
113
- hps.image.tag =
114
- version.hpsVersion || version.version;
127
+ values.global.version = version.version;
115
128
  // Save updated values
116
129
  await fs.writeFile(valuesPath, YAML.stringify(values), "utf8");
117
130
  }
@@ -124,14 +137,20 @@ function UpgradeCommandInner({ name, targetVersion, dryRun, }) {
124
137
  return;
125
138
  setStep("upgrading");
126
139
  try {
127
- // Update helm values with new image tags
140
+ // Update Helm values with the unified product version
128
141
  await updateHelmValuesWithVersion(selectedVersion);
129
142
  const state = await loadDeploymentState(name);
130
143
  // Use namespace from state if available (backwards compat), otherwise compute from deployment name
131
144
  const namespace = state?.application?.namespace || getNamespace(name);
132
145
  const releaseName = getReleaseName(name);
133
146
  // Perform the upgrade
134
- await upgradeChart(name, { releaseName, namespace, wait: true });
147
+ const chartVersion = await resolvePinnedChartVersion(namespace, releaseName);
148
+ await upgradeChart(name, {
149
+ releaseName,
150
+ namespace,
151
+ version: chartVersion,
152
+ wait: true,
153
+ });
135
154
  // Force restart HPS statefulsets to ensure fresh images are pulled
136
155
  // (pullPolicy: Always only pulls on pod restart, not on unchanged spec)
137
156
  await rolloutRestart("statefulset", `${releaseName}-hps`, namespace);
@@ -139,8 +158,8 @@ function UpgradeCommandInner({ name, targetVersion, dryRun, }) {
139
158
  // Update deployment state
140
159
  await updateDeploymentStatus(name, "running", {
141
160
  application: {
142
- appVersion: selectedVersion.version,
143
- hpsVersion: selectedVersion.hpsVersion || selectedVersion.version,
161
+ version: selectedVersion.version,
162
+ chartVersion: chartVersion || state?.application?.chartVersion,
144
163
  namespace,
145
164
  url: `https://${config.domain}`,
146
165
  },
@@ -185,7 +204,7 @@ function UpgradeCommandInner({ name, targetVersion, dryRun, }) {
185
204
  if (dryRun && dryRunOutput) {
186
205
  return (_jsx(BorderBox, { title: "Dry Run Results", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.accent, children: "Preview of changes (no changes made):" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, children: [dryRunOutput.substring(0, 500), "..."] }) })] }) }));
187
206
  }
188
- return (_jsx(BorderBox, { title: "Upgrade Complete", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { color: colors.success, bold: true, children: ["\u2713 Upgraded to ", formatVersionDisplay(selectedVersion?.version || "")] }), selectedVersion?.hpsVersion && (_jsxs(Text, { color: colors.muted, children: ["HPS version: ", formatVersionDisplay(selectedVersion.hpsVersion)] })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Run `rulebricks status ", name, "` to verify the deployment"] }) })] }) }));
207
+ return (_jsx(BorderBox, { title: "Upgrade Complete", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { color: colors.success, bold: true, children: ["\u2713 Upgraded to ", formatVersionDisplay(selectedVersion?.version || "")] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Run `rulebricks status ", name, "` to verify the deployment"] }) })] }) }));
189
208
  }
190
209
  if (step === "upgrading") {
191
210
  return (_jsx(BorderBox, { title: "Upgrading", children: _jsx(Box, { marginY: 1, children: _jsx(Spinner, { label: `Installing ${formatVersionDisplay(selectedVersion?.version || "")}...` }) }) }));
@@ -193,36 +212,40 @@ function UpgradeCommandInner({ name, targetVersion, dryRun, }) {
193
212
  if (step === "confirm") {
194
213
  return (_jsx(BorderBox, { title: "Confirm Upgrade", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { children: ["Current:", " ", _jsx(Text, { color: colors.accent, children: versionInfo?.current
195
214
  ? formatVersionDisplay(versionInfo.current.version)
196
- : "Not installed" }), deployedHpsVersion && (_jsxs(Text, { color: colors.muted, children: [" ", "(Solver: ", formatVersionDisplay(deployedHpsVersion), ")"] }))] }), _jsxs(Text, { children: ["Target:", " ", _jsx(Text, { color: colors.success, children: formatVersionDisplay(selectedVersion?.version || "") }), selectedVersion?.hpsVersion && (_jsxs(Text, { color: colors.muted, children: [" ", "(Solver: ", formatVersionDisplay(selectedVersion.hpsVersion), ")"] }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.warning, children: "\u26A0 This will upgrade your Rulebricks deployment." }), _jsx(Text, { color: colors.muted, children: "Pods will be restarted and there may be brief downtime." })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.success, bold: true, children: "Press Enter to continue, Esc to go back" }) })] }) }));
215
+ : "Not installed" })] }), _jsxs(Text, { children: ["Target:", " ", _jsx(Text, { color: colors.success, children: formatVersionDisplay(selectedVersion?.version || "") })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.warning, children: "\u26A0 This will upgrade your Rulebricks deployment." }), _jsx(Text, { color: colors.muted, children: "Pods will be restarted and there may be brief downtime." })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.success, bold: true, children: "Press Enter to continue, Esc to go back" }) })] }) }));
197
216
  }
198
217
  // Version selection screen
199
218
  const versionItems = versionInfo?.available.map((v) => ({
200
219
  label: formatVersionDisplay(v.version),
201
220
  value: v.version,
202
- hpsVersion: v.hpsVersion,
203
221
  date: v.releaseDate,
204
- // Only mark as "current" if both app AND HPS versions match what's deployed
222
+ hasSameVersionPatch: hasSameVersionHpsPatch(v, deployedVersions),
223
+ // Only mark as "current" if app and HPS versions match what's deployed
205
224
  isCurrent: versionInfo.current?.version === v.version &&
206
225
  (!deployedHpsVersion ||
207
- !v.hpsVersion ||
208
- normalizeVersion(deployedHpsVersion) ===
209
- normalizeVersion(v.hpsVersion)),
226
+ normalizeVersion(deployedHpsVersion) === normalizeVersion(v.version)) &&
227
+ !hasSameVersionHpsPatch(v, deployedVersions),
210
228
  isLatest: versionInfo.latest?.version === v.version,
211
229
  })) || [];
212
230
  return (_jsx(BorderBox, { title: "Rulebricks Version Manager", children: _jsxs(Box, { flexDirection: "column", marginY: 1, children: [(() => {
213
231
  // Check if HPS has an update available (even if app version is current)
214
- const latestHps = versionInfo?.latest?.hpsVersion;
215
- const hasHpsUpdate = deployedHpsVersion &&
216
- latestHps &&
217
- normalizeVersion(deployedHpsVersion) !==
218
- normalizeVersion(latestHps);
232
+ const hasHpsDigestUpdate = versionInfo?.latest
233
+ ? hasSameVersionHpsPatch(versionInfo.latest, deployedVersions)
234
+ : false;
235
+ const hasHpsUpdate = hasHpsDigestUpdate ||
236
+ !!(deployedHpsVersion &&
237
+ versionInfo?.latest &&
238
+ normalizeVersion(deployedHpsVersion) !==
239
+ normalizeVersion(versionInfo.latest.version));
219
240
  const hasAnyUpdate = versionInfo?.hasUpdate || hasHpsUpdate;
220
241
  return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: ["Current:", " ", _jsx(Text, { color: colors.accent, children: versionInfo?.current
221
242
  ? formatVersionDisplay(versionInfo.current.version)
222
- : "Not installed" }), deployedHpsVersion && (_jsxs(Text, { color: hasHpsUpdate ? colors.accent : colors.muted, children: [" ", "(Solver: ", formatVersionDisplay(deployedHpsVersion), ")"] }))] }), _jsxs(Text, { children: ["Latest:", " ", _jsx(Text, { color: hasAnyUpdate ? colors.success : colors.accent, children: versionInfo?.latest
243
+ : "Not installed" })] }), _jsxs(Text, { children: ["Latest:", " ", _jsx(Text, { color: hasAnyUpdate ? colors.success : colors.accent, children: versionInfo?.latest
223
244
  ? formatVersionDisplay(versionInfo.latest.version)
224
- : "Unknown" }), versionInfo?.latest?.hpsVersion && (_jsxs(Text, { color: hasHpsUpdate ? colors.success : colors.muted, children: [" ", "(Solver:", " ", formatVersionDisplay(versionInfo.latest.hpsVersion), ")"] }))] }), hasAnyUpdate && (_jsx(Text, { color: colors.muted, dimColor: true, children: "Update available" }))] }));
225
- })(), _jsxs(Box, { marginBottom: 1, paddingX: 1, borderStyle: "single", borderColor: colors.accent, alignSelf: "flex-start", children: [_jsx(Text, { children: "\uD83D\uDCDA What's new: " }), _jsx(Text, { color: colors.accent, underline: true, children: CHANGELOG_URL })] }), _jsx(Text, { bold: true, children: "Select app version to install:" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: versionItems, onSelect: handleVersionSelect, limit: 8, itemComponent: ({ isSelected, label }) => {
245
+ : "Unknown" })] }), hasAnyUpdate && (_jsx(Text, { color: colors.muted, dimColor: true, children: hasHpsDigestUpdate
246
+ ? "HPS patch available for the installed version"
247
+ : "Update available" }))] }));
248
+ })(), _jsxs(Box, { marginBottom: 1, paddingX: 1, borderStyle: "single", borderColor: colors.accent, alignSelf: "flex-start", children: [_jsx(Text, { children: "\uD83D\uDCDA What's new: " }), _jsx(Text, { color: colors.accent, underline: true, children: CHANGELOG_URL })] }), _jsx(Text, { bold: true, children: "Select Rulebricks version to install:" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: versionItems, onSelect: handleVersionSelect, limit: 8, itemComponent: ({ isSelected, label }) => {
226
249
  const vItem = versionItems.find((v) => v.label === label) || versionItems[0];
227
250
  // Highlight latest in green when there's an update available
228
251
  const isLatestWithUpdate = vItem.isLatest && !vItem.isCurrent;
@@ -231,7 +254,7 @@ function UpgradeCommandInner({ name, targetVersion, dryRun, }) {
231
254
  : isLatestWithUpdate
232
255
  ? colors.success
233
256
  : undefined;
234
- return (_jsxs(Box, { children: [_jsx(Text, { color: labelColor, children: label }), vItem.hpsVersion && (_jsxs(Text, { color: isLatestWithUpdate ? colors.success : colors.muted, children: [" ", "(Solver: ", formatVersionDisplay(vItem.hpsVersion), ")"] })), vItem.isCurrent && (_jsx(Text, { color: colors.warning, children: " current" })), _jsxs(Text, { color: colors.muted, children: [" ", formatDate(vItem.date)] })] }));
257
+ return (_jsxs(Box, { children: [_jsx(Text, { color: labelColor, children: label }), vItem.isCurrent && (_jsx(Text, { color: colors.warning, children: " current" })), vItem.hasSameVersionPatch && (_jsx(Text, { color: colors.success, children: " patch available" })), _jsxs(Text, { color: colors.muted, children: [" ", formatDate(vItem.date)] })] }));
235
258
  } }) })] }) }));
236
259
  }
237
260
  export function UpgradeCommand(props) {
@@ -1,9 +1,13 @@
1
1
  interface DNSWaitScreenProps {
2
2
  domain: string;
3
3
  selfHostedSupabase: boolean;
4
+ builtInObservability?: boolean;
5
+ observabilityHostname?: string;
6
+ valkeyAdminIngress?: boolean;
7
+ valkeyAdminHostname?: string;
4
8
  namespace: string;
5
9
  onComplete: () => void;
6
10
  onSkip?: () => void;
7
11
  }
8
- export declare function DNSWaitScreen({ domain, selfHostedSupabase, namespace, onComplete, onSkip, }: DNSWaitScreenProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function DNSWaitScreen({ domain, selfHostedSupabase, builtInObservability, observabilityHostname, valkeyAdminIngress, valkeyAdminHostname, namespace, onComplete, onSkip, }: DNSWaitScreenProps): import("react/jsx-runtime").JSX.Element;
9
13
  export {};
@@ -1,19 +1,32 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect, useRef } from "react";
2
+ import { useState, useEffect, useCallback } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
- import InkSpinner from "ink-spinner";
5
4
  import { BorderBox, Spinner, useTheme } from "./common/index.js";
6
5
  import { getLoadBalancerAddress, getRequiredDNSRecords, checkDNSRecord, isDNSComplete, } from "../lib/dns.js";
7
- export function DNSWaitScreen({ domain, selfHostedSupabase, namespace, onComplete, onSkip, }) {
6
+ export function DNSWaitScreen({ domain, selfHostedSupabase, builtInObservability = false, observabilityHostname, valkeyAdminIngress = false, valkeyAdminHostname, namespace, onComplete, onSkip, }) {
8
7
  const { colors } = useTheme();
9
8
  const [status, setStatus] = useState("loading-lb");
10
9
  const [loadBalancer, setLoadBalancer] = useState(null);
11
10
  const [records, setRecords] = useState([]);
12
11
  const [error, setError] = useState(null);
13
- const [pollCount, setPollCount] = useState(0);
14
- // Use ref to avoid stale closure in polling interval
15
- const recordsRef = useRef(records);
16
- recordsRef.current = records;
12
+ const [hasChecked, setHasChecked] = useState(false);
13
+ const checkRecords = useCallback(async () => {
14
+ if (status !== "idle" || records.length === 0)
15
+ return;
16
+ setStatus("checking");
17
+ setHasChecked(true);
18
+ const updatedRecords = await Promise.all(records.map(async (record) => {
19
+ if (record.verified)
20
+ return record;
21
+ const result = await checkDNSRecord(record.hostname, record.target);
22
+ return {
23
+ ...record,
24
+ verified: result.resolved && result.matchesTarget,
25
+ };
26
+ }));
27
+ setRecords(updatedRecords);
28
+ setStatus(isDNSComplete(updatedRecords) ? "complete" : "idle");
29
+ }, [records, status]);
17
30
  useInput((input, key) => {
18
31
  if (key.escape || input.toLowerCase() === "s") {
19
32
  onSkip?.();
@@ -21,6 +34,9 @@ export function DNSWaitScreen({ domain, selfHostedSupabase, namespace, onComplet
21
34
  if (key.return && status === "complete") {
22
35
  onComplete();
23
36
  }
37
+ else if (key.return && status === "idle") {
38
+ void checkRecords();
39
+ }
24
40
  });
25
41
  // Fetch load balancer address
26
42
  useEffect(() => {
@@ -32,42 +48,32 @@ export function DNSWaitScreen({ domain, selfHostedSupabase, namespace, onComplet
32
48
  return;
33
49
  }
34
50
  setLoadBalancer({ address: result.address, type: result.type });
35
- const dnsRecords = getRequiredDNSRecords(domain, result.address, result.type, selfHostedSupabase);
51
+ const dnsRecords = getRequiredDNSRecords(domain, result.address, result.type, selfHostedSupabase, builtInObservability, observabilityHostname, valkeyAdminIngress, valkeyAdminHostname);
36
52
  setRecords(dnsRecords);
37
- setStatus("waiting-dns");
53
+ setStatus("idle");
38
54
  };
39
55
  fetchLB();
40
- }, [domain, selfHostedSupabase, namespace]);
41
- // Poll DNS records
42
- useEffect(() => {
43
- if (status !== "waiting-dns")
44
- return;
45
- const pollDNS = async () => {
46
- // Use ref to get current records, avoiding stale closure
47
- const currentRecords = recordsRef.current;
48
- const updatedRecords = await Promise.all(currentRecords.map(async (record) => {
49
- if (record.verified)
50
- return record;
51
- const result = await checkDNSRecord(record.hostname, record.target);
52
- return {
53
- ...record,
54
- verified: result.resolved && result.matchesTarget,
55
- };
56
- }));
57
- setRecords(updatedRecords);
58
- setPollCount((c) => c + 1);
59
- if (isDNSComplete(updatedRecords)) {
60
- setStatus("complete");
61
- }
62
- };
63
- // Initial check
64
- pollDNS();
65
- // Poll every 5 seconds
66
- const interval = setInterval(pollDNS, 5000);
67
- return () => clearInterval(interval);
68
- }, [status]);
56
+ }, [
57
+ domain,
58
+ selfHostedSupabase,
59
+ builtInObservability,
60
+ observabilityHostname,
61
+ valkeyAdminIngress,
62
+ valkeyAdminHostname,
63
+ namespace,
64
+ ]);
69
65
  const verifiedCount = records.filter((r) => r.verified).length;
70
- return (_jsxs(BorderBox, { title: "Configure DNS Records", children: [status === "loading-lb" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Getting load balancer address..." }) })), status === "error" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.error, bold: true, children: "\u2717 Error" }), _jsx(Text, { color: colors.error, children: error }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, children: "Press Esc to skip DNS validation" }) })] })), (status === "waiting-dns" || status === "complete") && loadBalancer && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Your load balancer address:" }), _jsx(Box, { marginY: 1, children: _jsx(Text, { color: colors.accent, bold: true, children: loadBalancer.address }) }), _jsx(Text, { children: "Please add the following DNS records:" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: records.map((record, idx) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [record.verified ? (_jsx(Text, { color: colors.success, children: "\u2713" })) : (_jsx(Text, { color: colors.accent, children: _jsx(InkSpinner, { type: "dots" }) })), _jsx(Text, { children: " " }), _jsx(Text, { color: record.verified ? colors.success : undefined, children: record.hostname })] }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: colors.accent, children: record.type }), _jsx(Text, { color: colors.muted, children: " \u2192 " }), _jsx(Text, { color: colors.accent, children: record.target })] })] }, idx))) }), _jsx(Box, { marginTop: 2, children: status === "waiting-dns" ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Spinner, { label: `Checking DNS propagation... (${verifiedCount}/${records.length} complete)` }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, dimColor: true, children: ["Poll #", pollCount, " \u2022 DNS changes can take up to 48 hours to propagate"] }) })] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.success, bold: true, children: "\u2713 All DNS records verified!" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, children: "Press Enter to continue with TLS setup" }) })] })) })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: status === "complete"
71
- ? "Enter to continue"
72
- : "S or Esc to skip DNS validation (not recommended)" }) })] }));
66
+ const footerText = status === "complete"
67
+ ? "Enter to continue"
68
+ : status === "checking"
69
+ ? "Checking DNS records..."
70
+ : hasChecked
71
+ ? "We couldn't find one or more DNS records. Please verify they exist and press Enter to try again."
72
+ : "Press Enter once you've created the DNS records • S or Esc to skip DNS validation";
73
+ return (_jsxs(BorderBox, { title: "Configure DNS Records", children: [status === "loading-lb" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Getting load balancer address..." }) })), status === "error" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: colors.error, bold: true, children: "\u2717 Error" }), _jsx(Text, { color: colors.error, children: error }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, children: "Press Esc to skip DNS validation" }) })] })), (status === "idle" || status === "checking" || status === "complete") &&
74
+ loadBalancer && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Your load balancer address:" }), _jsx(Box, { marginY: 1, children: _jsx(Text, { color: colors.accent, bold: true, children: loadBalancer.address }) }), _jsx(Text, { children: "Please add the following DNS records:" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: records.map((record, idx) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [record.verified ? (_jsx(Text, { color: colors.success, children: "\u2713" })) : hasChecked ? (_jsx(Text, { color: colors.warning, children: "\u25CB" })) : (_jsx(Text, { color: colors.muted, children: "\u25CB" })), _jsx(Text, { children: " " }), _jsx(Text, { color: record.verified
75
+ ? colors.success
76
+ : hasChecked
77
+ ? colors.warning
78
+ : undefined, children: record.hostname })] }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: colors.accent, children: record.type }), _jsx(Text, { color: colors.muted, children: " \u2192 " }), _jsx(Text, { color: colors.accent, children: record.target })] })] }, idx))) }), _jsx(Box, { marginTop: 2, children: status === "complete" ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.success, bold: true, children: "\u2713 All DNS records verified!" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, children: "Press Enter to continue with TLS setup" }) })] })) : status === "checking" ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Spinner, { label: "Checking DNS records..." }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.muted, dimColor: true, children: [verifiedCount, "/", records.length, " records verified"] }) })] })) : hasChecked ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.warning, children: "We couldn't find one or more DNS records." }), _jsx(Text, { color: colors.muted, children: "Please verify they exist and press Enter to try again." })] })) : (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: colors.muted, children: "Press Enter once you've created the DNS records." }) })) })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, dimColor: true, children: footerText }) })] }));
73
79
  }