@rulebricks/cli 2.1.6 → 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 (114) hide show
  1. package/README.md +75 -14
  2. package/cluster-setup/aws/README.md +123 -0
  3. package/cluster-setup/aws/check-aws-access.sh +242 -0
  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 +141 -0
  7. package/cluster-setup/azure/check-aks-prereqs.sh +276 -0
  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 +189 -0
  11. package/cluster-setup/gcp/check-gke-prereqs.sh +260 -0
  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 -47
  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 +174 -29
  33. package/dist/components/Wizard/WizardContext.js +896 -91
  34. package/dist/components/Wizard/steps/CloudProviderStep.js +192 -102
  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 +959 -248
  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 -7
  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 +1937 -259
  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 +126 -13
  85. package/dist/lib/kubernetes.js +624 -134
  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 +2152 -95
  101. package/dist/types/index.js +554 -286
  102. package/package.json +10 -4
  103. package/schema/values.schema.json +1934 -0
  104. package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
  105. package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
  106. package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
  107. package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
  108. package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
  109. package/dist/components/Wizard/steps/TierStep.js +0 -29
  110. package/dist/lib/terraform.d.ts +0 -66
  111. package/dist/lib/terraform.js +0 -754
  112. package/terraform/aws/main.tf +0 -355
  113. package/terraform/azure/main.tf +0 -371
  114. package/terraform/gcp/main.tf +0 -407
package/dist/lib/dns.d.ts CHANGED
@@ -20,7 +20,7 @@ export declare function getLoadBalancerAddress(namespace?: string): Promise<{
20
20
  /**
21
21
  * Gets all required DNS records for a deployment
22
22
  */
23
- export declare function getRequiredDNSRecords(domain: string, loadBalancerAddress: string, loadBalancerType: "ip" | "hostname", selfHostedSupabase: boolean): DNSRecord[];
23
+ export declare function getRequiredDNSRecords(domain: string, loadBalancerAddress: string, loadBalancerType: "ip" | "hostname", selfHostedSupabase: boolean, builtInObservability?: boolean, observabilityHostname?: string, valkeyAdminIngress?: boolean, valkeyAdminHostname?: string): DNSRecord[];
24
24
  /**
25
25
  * Polls DNS records until they resolve or timeout
26
26
  */
package/dist/lib/dns.js CHANGED
@@ -239,7 +239,7 @@ export async function getLoadBalancerAddress(namespace = DEFAULT_NAMESPACE) {
239
239
  /**
240
240
  * Gets all required DNS records for a deployment
241
241
  */
242
- export function getRequiredDNSRecords(domain, loadBalancerAddress, loadBalancerType, selfHostedSupabase) {
242
+ export function getRequiredDNSRecords(domain, loadBalancerAddress, loadBalancerType, selfHostedSupabase, builtInObservability = false, observabilityHostname, valkeyAdminIngress = false, valkeyAdminHostname) {
243
243
  const records = [
244
244
  {
245
245
  hostname: domain,
@@ -259,6 +259,24 @@ export function getRequiredDNSRecords(domain, loadBalancerAddress, loadBalancerT
259
259
  required: true,
260
260
  });
261
261
  }
262
+ if (builtInObservability) {
263
+ records.push({
264
+ hostname: observabilityHostname || `observability.${domain}`,
265
+ type: loadBalancerType === "ip" ? "A" : "CNAME",
266
+ target: loadBalancerAddress,
267
+ verified: false,
268
+ required: true,
269
+ });
270
+ }
271
+ if (valkeyAdminIngress) {
272
+ records.push({
273
+ hostname: valkeyAdminHostname || `valkey.${domain}`,
274
+ type: loadBalancerType === "ip" ? "A" : "CNAME",
275
+ target: loadBalancerAddress,
276
+ verified: false,
277
+ required: true,
278
+ });
279
+ }
262
280
  return records;
263
281
  }
264
282
  /**
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { getRequiredDNSRecords } from "./dns.js";
4
+ test("manual DNS records include app, Supabase, and built-in observability", () => {
5
+ const records = getRequiredDNSRecords("az-p055.rulebricks.com", "4.236.203.25", "ip", true, true);
6
+ assert.deepEqual(records.map((record) => record.hostname), [
7
+ "az-p055.rulebricks.com",
8
+ "supabase.az-p055.rulebricks.com",
9
+ "observability.az-p055.rulebricks.com",
10
+ ]);
11
+ assert.ok(records.every((record) => record.type === "A"));
12
+ assert.ok(records.every((record) => record.target === "4.236.203.25"));
13
+ });
14
+ test("manual DNS records omit observability when built-in observability is disabled", () => {
15
+ const records = getRequiredDNSRecords("az-p055.rulebricks.com", "example-lb.example.net", "hostname", true, false);
16
+ assert.deepEqual(records.map((record) => record.hostname), ["az-p055.rulebricks.com", "supabase.az-p055.rulebricks.com"]);
17
+ assert.ok(records.every((record) => record.type === "CNAME"));
18
+ });
19
+ test("manual DNS records include Valkey Admin ingress when enabled", () => {
20
+ const records = getRequiredDNSRecords("az-p055.rulebricks.com", "4.236.203.25", "ip", false, false, undefined, true);
21
+ assert.deepEqual(records.map((record) => record.hostname), ["az-p055.rulebricks.com", "valkey.az-p055.rulebricks.com"]);
22
+ });
23
+ test("manual DNS records use custom Valkey Admin hostname", () => {
24
+ const records = getRequiredDNSRecords("az-p055.rulebricks.com", "example-lb.example.net", "hostname", false, false, undefined, true, "redis-tools.example.com");
25
+ assert.deepEqual(records.map((record) => record.hostname), ["az-p055.rulebricks.com", "redis-tools.example.com"]);
26
+ assert.ok(records.every((record) => record.type === "CNAME"));
27
+ });
@@ -14,8 +14,12 @@ export interface ImageTag {
14
14
  lastUpdated: Date;
15
15
  /** Image digest */
16
16
  digest: string;
17
+ /** Manifest/image digests reported for this tag */
18
+ imageDigests: string[];
17
19
  /** Full image size in bytes */
18
20
  fullSize: number;
21
+ /** Architectures available in the tag's manifest list */
22
+ architectures: string[];
19
23
  }
20
24
  /**
21
25
  * Formats the license key as a Docker PAT
@@ -36,7 +40,13 @@ export declare function authenticateDockerHub(licenseKey: string): Promise<strin
36
40
  * @param pageSize - Number of tags to fetch per page (max 100)
37
41
  * @returns Array of image tags sorted by last updated (newest first)
38
42
  */
39
- export declare function fetchImageTags(repo: string, token: string, pageSize?: number): Promise<ImageTag[]>;
43
+ export declare function fetchImageTags(repo: string, token: string, pageSize?: number, tagFilter?: (tag: string) => boolean): Promise<ImageTag[]>;
44
+ /**
45
+ * Checks if a tag looks like a valid semantic version
46
+ * Excludes "latest" and other non-numeric tags
47
+ */
48
+ export declare function isValidVersionTag(tag: string): boolean;
49
+ export declare function isValidWorkerVersionTag(tag: string): boolean;
40
50
  /**
41
51
  * Fetches tags for both app and HPS repositories
42
52
  *
@@ -46,6 +56,7 @@ export declare function fetchImageTags(repo: string, token: string, pageSize?: n
46
56
  export declare function fetchAllImageTags(licenseKey: string): Promise<{
47
57
  appTags: ImageTag[];
48
58
  hpsTags: ImageTag[];
59
+ hpsWorkerTags: ImageTag[];
49
60
  }>;
50
61
  /**
51
62
  * Normalizes a version string by removing leading 'v'
@@ -51,7 +51,7 @@ export async function authenticateDockerHub(licenseKey) {
51
51
  * @param pageSize - Number of tags to fetch per page (max 100)
52
52
  * @returns Array of image tags sorted by last updated (newest first)
53
53
  */
54
- export async function fetchImageTags(repo, token, pageSize = 100) {
54
+ export async function fetchImageTags(repo, token, pageSize = 100, tagFilter = isValidVersionTag) {
55
55
  const allTags = [];
56
56
  let url = `${DOCKER_HUB_API}/repositories/${repo}/tags?page_size=${pageSize}`;
57
57
  while (url) {
@@ -68,19 +68,25 @@ export async function fetchImageTags(repo, token, pageSize = 100) {
68
68
  }
69
69
  const data = await response.json();
70
70
  for (const tag of data.results) {
71
- // Skip non-semver tags like "latest", "dev", etc.
72
- if (!isValidVersionTag(tag.name)) {
71
+ if (!tagFilter(tag.name)) {
73
72
  continue;
74
73
  }
74
+ const imageDigests = Array.from(new Set([
75
+ tag.digest,
76
+ ...(tag.images || []).map((image) => image.digest),
77
+ ].filter(Boolean)));
75
78
  allTags.push({
76
79
  name: tag.name,
77
80
  lastUpdated: new Date(tag.last_updated),
78
81
  digest: tag.digest,
82
+ imageDigests,
79
83
  fullSize: tag.full_size,
84
+ architectures: Array.from(new Set((tag.images || [])
85
+ .map((image) => image.architecture)
86
+ .filter(Boolean))),
80
87
  });
81
88
  }
82
- // Get next page if available (limit to reasonable number of versions)
83
- url = data.next && allTags.length < 50 ? data.next : null;
89
+ url = data.next;
84
90
  }
85
91
  // Sort by last updated, newest first
86
92
  allTags.sort((a, b) => b.lastUpdated.getTime() - a.lastUpdated.getTime());
@@ -90,7 +96,7 @@ export async function fetchImageTags(repo, token, pageSize = 100) {
90
96
  * Checks if a tag looks like a valid semantic version
91
97
  * Excludes "latest" and other non-numeric tags
92
98
  */
93
- function isValidVersionTag(tag) {
99
+ export function isValidVersionTag(tag) {
94
100
  // Exclude "latest" and similar non-versioned tags
95
101
  if (tag === 'latest' || tag === 'dev' || tag === 'main' || tag === 'master') {
96
102
  return false;
@@ -99,6 +105,9 @@ function isValidVersionTag(tag) {
99
105
  const versionPattern = /^v?\d+\.\d+\.\d+(-[\w.]+)?$/;
100
106
  return versionPattern.test(tag);
101
107
  }
108
+ export function isValidWorkerVersionTag(tag) {
109
+ return /^worker-\d+\.\d+\.\d+(-[\w.]+)?$/.test(tag);
110
+ }
102
111
  /**
103
112
  * Fetches tags for both app and HPS repositories
104
113
  *
@@ -107,11 +116,12 @@ function isValidVersionTag(tag) {
107
116
  */
108
117
  export async function fetchAllImageTags(licenseKey) {
109
118
  const token = await authenticateDockerHub(licenseKey);
110
- const [appTags, hpsTags] = await Promise.all([
119
+ const [appTags, hpsTags, hpsWorkerTags] = await Promise.all([
111
120
  fetchImageTags('rulebricks/app', token),
112
121
  fetchImageTags('rulebricks/hps', token),
122
+ fetchImageTags('rulebricks/hps', token, 100, isValidWorkerVersionTag),
113
123
  ]);
114
- return { appTags, hpsTags };
124
+ return { appTags, hpsTags, hpsWorkerTags };
115
125
  }
116
126
  /**
117
127
  * Normalizes a version string by removing leading 'v'
@@ -15,6 +15,10 @@ export declare function fetchChartVersions(): Promise<ChartVersion[]>;
15
15
  * Gets the currently installed chart version for a deployment
16
16
  */
17
17
  export declare function getInstalledVersion(releaseName: string, namespace: string): Promise<string | null>;
18
+ /**
19
+ * Gets the installed Helm chart version for a deployment.
20
+ */
21
+ export declare function getInstalledChartVersion(releaseName: string, namespace: string): Promise<string | null>;
18
22
  /**
19
23
  * Installs the Rulebricks Helm chart (use installOrUpgradeChart for idempotent operations)
20
24
  */
package/dist/lib/helm.js CHANGED
@@ -101,6 +101,22 @@ export async function getInstalledVersion(releaseName, namespace) {
101
101
  return null;
102
102
  }
103
103
  }
104
+ /**
105
+ * Gets the installed Helm chart version for a deployment.
106
+ */
107
+ export async function getInstalledChartVersion(releaseName, namespace) {
108
+ try {
109
+ const { stdout } = await execa("helm", ["list", "-n", namespace, "-f", `^${releaseName}$`, "-o", "json"], { timeout: 15000 });
110
+ const releases = JSON.parse(stdout);
111
+ if (releases.length === 0) {
112
+ return null;
113
+ }
114
+ return releases[0].chart.split("-").pop() || null;
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
104
120
  /**
105
121
  * Installs the Rulebricks Helm chart (use installOrUpgradeChart for idempotent operations)
106
122
  */
@@ -1,7 +1,32 @@
1
1
  import { DeploymentConfig } from "../types/index.js";
2
2
  interface GenerateOptions {
3
3
  tlsEnabled?: boolean;
4
+ secretMode?: "k8s" | "inline";
4
5
  }
6
+ export declare function deploymentSecretNames(config: DeploymentConfig): {
7
+ app: string;
8
+ db: string;
9
+ dbBootstrap: string;
10
+ jwt: string;
11
+ dashboard: string;
12
+ realtime: string;
13
+ smtp: string;
14
+ };
15
+ export declare function signSupabaseJwt(role: "anon" | "service_role", secret: string): string;
16
+ export declare function deriveRealtimeSecrets(jwtSecret: string): {
17
+ secretKeyBase: string;
18
+ dbEncKey: string;
19
+ };
20
+ /**
21
+ * Builds Helm values from the deployment configuration.
22
+ */
23
+ export declare function buildHelmValues(config: DeploymentConfig, options?: GenerateOptions): Record<string, unknown>;
24
+ /**
25
+ * Rewrites generated values for k8s secret mode: sets the chart's *.secretRef
26
+ * seams to the CLI-created Secret names and removes inline plaintext secrets so
27
+ * none are persisted to values.yaml or the Helm release.
28
+ */
29
+ export declare function redactSecretsToRefs(values: Record<string, unknown>, config: DeploymentConfig): Record<string, unknown>;
5
30
  /**
6
31
  * Generates Helm values from the deployment configuration
7
32
  */