@rizom/ops 0.2.0-alpha.170 → 0.2.0-alpha.171

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rizom/ops",
3
- "version": "0.2.0-alpha.170",
3
+ "version": "0.2.0-alpha.171",
4
4
  "description": "Operator CLI for managing private brain fleet registry repos",
5
5
  "keywords": [
6
6
  "brains",
@@ -4,17 +4,22 @@ on:
4
4
  workflow_dispatch:
5
5
  inputs:
6
6
  brain_version:
7
- description: Brain version to build; defaults to pilot.yaml brainVersion
7
+ description: Force one explicit build at this brain version (skips the registry-derived resolve)
8
+ required: false
9
+ type: string
10
+ site_packages:
11
+ description: 'Space-separated site packages for a per-instance site image (e.g. "@rizom/site-rizom-ai@0.2.0-alpha.148"). Empty builds the default fleet image.'
8
12
  required: false
9
13
  type: string
10
14
  push:
11
15
  branches: ["main"]
12
16
  paths:
17
+ # The image set is a pure function of the declared fleet state: a push
18
+ # that changes it builds exactly what it declares, so the deploy (fired
19
+ # off the same push via Reconcile) finds its tag ready.
13
20
  - pilot.yaml
14
- - cohorts/*.yaml
21
+ - cohorts/**
15
22
  - users/*.yaml
16
- - package.json
17
- - bun.lock
18
23
  - deploy/**
19
24
  - .github/workflows/build.yml
20
25
 
@@ -23,8 +28,10 @@ permissions:
23
28
  packages: write
24
29
 
25
30
  jobs:
26
- build:
31
+ resolve:
27
32
  runs-on: ubuntu-latest
33
+ outputs:
34
+ images_json: ${{ steps.resolve.outputs.images_json }}
28
35
  steps:
29
36
  - uses: actions/checkout@v5
30
37
  with:
@@ -35,12 +42,40 @@ jobs:
35
42
  - name: Install operator tooling
36
43
  run: bun install
37
44
 
38
- - name: Resolve image metadata inputs
45
+ - name: Log in to GHCR
46
+ uses: docker/login-action@v4
47
+ with:
48
+ registry: ghcr.io
49
+ username: ${{ github.actor }}
50
+ password: ${{ secrets.GITHUB_TOKEN }}
51
+
52
+ - name: Resolve images to build
53
+ id: resolve
39
54
  env:
40
55
  BRAIN_VERSION_INPUT: ${{ inputs.brain_version || '' }}
41
- run: |
42
- bun deploy/scripts/resolve-build-config.ts
43
- echo "IMAGE_REPOSITORY=ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}" >> "$GITHUB_ENV"
56
+ SITE_PACKAGES_INPUT: ${{ inputs.site_packages || '' }}
57
+ run: bun deploy/scripts/resolve-missing-images.ts
58
+
59
+ no_builds:
60
+ needs: resolve
61
+ if: needs.resolve.outputs.images_json == '[]'
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - name: Skip when every declared image exists
65
+ run: echo "All declared images already exist; nothing to build."
66
+
67
+ build:
68
+ needs: resolve
69
+ if: needs.resolve.outputs.images_json != '[]'
70
+ runs-on: ubuntu-latest
71
+ strategy:
72
+ fail-fast: false
73
+ matrix:
74
+ image: ${{ fromJson(needs.resolve.outputs.images_json) }}
75
+ steps:
76
+ - uses: actions/checkout@v5
77
+ with:
78
+ ref: ${{ github.sha }}
44
79
 
45
80
  - name: Set up Docker Buildx
46
81
  uses: docker/setup-buildx-action@v4
@@ -49,9 +84,9 @@ jobs:
49
84
  id: meta
50
85
  uses: docker/metadata-action@v6
51
86
  with:
52
- images: ${{ env.IMAGE_REPOSITORY }}
87
+ images: ghcr.io/${{ github.repository }}
53
88
  tags: |
54
- type=raw,value=${{ env.IMAGE_TAG }}
89
+ type=raw,value=${{ matrix.image.tag }}
55
90
 
56
91
  - name: Log in to GHCR
57
92
  uses: docker/login-action@v4
@@ -68,8 +103,8 @@ jobs:
68
103
  target: fleet
69
104
  push: true
70
105
  build-args: |
71
- BRAIN_VERSION=${{ env.BRAIN_VERSION }}
72
- SITE_PACKAGES=${{ env.SITE_PACKAGES }}
106
+ BRAIN_VERSION=${{ matrix.image.brain_version }}
107
+ SITE_PACKAGES=${{ matrix.image.site_packages }}
73
108
  tags: ${{ steps.meta.outputs.tags }}
74
109
  labels: |
75
110
  ${{ steps.meta.outputs.labels }}
@@ -10,7 +10,6 @@ on:
10
10
  push:
11
11
  branches: ["main"]
12
12
  paths:
13
- - users/*.yaml
14
13
  - users/*/.env
15
14
  - users/*/brain.yaml
16
15
  - users/*/content/**
@@ -83,72 +82,99 @@ jobs:
83
82
  - name: Install operator tooling
84
83
  run: bun install
85
84
 
85
+ - name: Load shared env via varlock
86
+ env:
87
+ BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }}
88
+ run: |
89
+ for attempt in 1 2 3; do
90
+ if bunx varlock@1.1.0 load --path .env.schema --format json --compact > /tmp/varlock-env.json; then
91
+ break
92
+ fi
93
+ if [ "$attempt" = "3" ]; then
94
+ exit 1
95
+ fi
96
+ echo "Varlock load failed (attempt $attempt/3); retrying in 5s..."
97
+ sleep 5
98
+ done
99
+ node <<'NODE'
100
+ import { appendFileSync, readFileSync } from "node:fs";
101
+ const env = JSON.parse(readFileSync('/tmp/varlock-env.json', 'utf8'));
102
+ const githubEnvPath = process.env.GITHUB_ENV;
103
+ if (!githubEnvPath) {
104
+ throw new Error('Missing GITHUB_ENV');
105
+ }
106
+
107
+ const newline = String.fromCharCode(10);
108
+ const carriageReturn = String.fromCharCode(13);
109
+ const chunks = [];
110
+ for (const [key, value] of Object.entries(env)) {
111
+ if (key === 'BWS_ACCESS_TOKEN' || value === null || value === undefined) {
112
+ continue;
113
+ }
114
+
115
+ const text = String(value)
116
+ .split(carriageReturn + newline)
117
+ .join(newline);
118
+ const escapedMask = text
119
+ .replace(/%/g, '%25')
120
+ .replace(/\r/g, '%0D')
121
+ .replace(/\n/g, '%0A');
122
+ if (escapedMask.length > 0) {
123
+ process.stdout.write('::add-mask::' + escapedMask + newline);
124
+ }
125
+
126
+ if (text.includes(newline)) {
127
+ let delimiter = `VARLOCK_${key}_EOF`;
128
+ while (text.includes(delimiter)) {
129
+ delimiter += '_X';
130
+ }
131
+ chunks.push(`${key}<<${delimiter}${newline}${text}${newline}${delimiter}`);
132
+ } else {
133
+ chunks.push(`${key}=${text}`);
134
+ }
135
+ }
136
+
137
+ appendFileSync(githubEnvPath, chunks.join(newline) + newline);
138
+ NODE
139
+
86
140
  - name: Decrypt user secrets
87
141
  id: user_secrets
88
- env:
89
- AGE_SECRET_KEY: ${{ secrets.AGE_SECRET_KEY }}
90
142
  run: bun deploy/scripts/decrypt-user-secrets.ts "$HANDLE"
91
143
 
92
144
  - name: Reconcile selected user config
93
- env:
94
- SHARED_GIT_SYNC_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_git_sync_token_secret_name] }}
95
- run: |
96
- export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
97
- bunx brains-ops onboard "$GITHUB_WORKSPACE" "$HANDLE"
145
+ run: bunx brains-ops onboard "$GITHUB_WORKSPACE" "$HANDLE"
98
146
 
99
147
  - name: Resolve generated user config
100
148
  id: user_config
101
149
  run: bun deploy/scripts/resolve-user-config.ts
102
150
 
103
151
  - name: Validate selected secrets
104
- env:
105
- SHARED_AI_API_KEY: ${{ secrets[steps.user_secrets.outputs.shared_ai_api_key_secret_name] }}
106
- SHARED_GIT_SYNC_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_git_sync_token_secret_name] }}
107
- SETUP_EMAIL_API_KEY: ${{ secrets.SETUP_EMAIL_API_KEY }}
108
- SETUP_EMAIL_FROM: ${{ secrets.SETUP_EMAIL_FROM }}
109
- HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
110
- HCLOUD_SSH_KEY_NAME: ${{ secrets.HCLOUD_SSH_KEY_NAME }}
111
- HCLOUD_SERVER_TYPE: ${{ secrets.HCLOUD_SERVER_TYPE }}
112
- HCLOUD_LOCATION: ${{ secrets.HCLOUD_LOCATION }}
113
- KAMAL_SSH_PRIVATE_KEY: ${{ secrets.KAMAL_SSH_PRIVATE_KEY }}
114
- KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
115
- CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
116
- CONFIG_CF_ZONE_ID: ${{ steps.user_config.outputs.cloudflare_zone_id }}
117
- SHARED_CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
118
- SHARED_CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
119
- SHARED_PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
120
- run: |
121
- export AI_API_KEY="${AI_API_KEY:-$SHARED_AI_API_KEY}"
122
- export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
123
- export CF_ZONE_ID="${CONFIG_CF_ZONE_ID:-${CF_ZONE_ID:-$SHARED_CF_ZONE_ID}}"
124
- export CERTIFICATE_PEM="${CERTIFICATE_PEM:-$SHARED_CERTIFICATE_PEM}"
125
- export PRIVATE_KEY_PEM="${PRIVATE_KEY_PEM:-$SHARED_PRIVATE_KEY_PEM}"
126
- bun deploy/scripts/validate-secrets.ts
152
+ run: bun deploy/scripts/validate-secrets.ts
127
153
 
128
154
  - name: Seed content repo
129
155
  env:
130
156
  CONTENT_REPO: ${{ steps.user_config.outputs.content_repo }}
131
- SHARED_GIT_SYNC_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_git_sync_token_secret_name] }}
132
- run: |
133
- export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
134
- bun deploy/scripts/sync-content-repo.ts
157
+ run: bun deploy/scripts/sync-content-repo.ts
135
158
 
136
159
  - name: Log in to GHCR
137
- uses: docker/login-action@v4
160
+ uses: docker/login-action@v3
138
161
  with:
139
162
  registry: ghcr.io
140
163
  username: ${{ github.actor }}
141
164
  password: ${{ secrets.GITHUB_TOKEN }}
142
165
 
143
- - name: Wait for shared image tag
166
+ - name: Wait for image tag
144
167
  run: |
168
+ # The Build workflow fires off the same config push (it derives the
169
+ # declared image set from the registry), so this wait must cover a
170
+ # full image build, not just registry propagation.
145
171
  IMAGE_TAG="${{ steps.user_config.outputs.image_repository }}:${{ steps.user_config.outputs.image_tag }}"
146
- for attempt in $(seq 1 24); do
172
+ for attempt in $(seq 1 60); do
147
173
  if docker manifest inspect "$IMAGE_TAG" >/dev/null 2>&1; then
148
174
  exit 0
149
175
  fi
150
- echo "Shared image tag not ready yet ($attempt/24): $IMAGE_TAG"
151
- sleep 10
176
+ echo "Image tag not ready yet ($attempt/60): $IMAGE_TAG"
177
+ sleep 20
152
178
  done
153
179
  echo "Timed out waiting for $IMAGE_TAG" >&2
154
180
  exit 1
@@ -159,8 +185,6 @@ jobs:
159
185
  ruby -r rubygems -e 'puts Gem.user_dir + "/bin"' >> "$GITHUB_PATH"
160
186
 
161
187
  - name: Write Kamal SSH key
162
- env:
163
- KAMAL_SSH_PRIVATE_KEY: ${{ secrets.KAMAL_SSH_PRIVATE_KEY }}
164
188
  run: bun deploy/scripts/write-ssh-key.ts
165
189
 
166
190
  - name: Configure SSH client
@@ -177,46 +201,21 @@ jobs:
177
201
  chmod 600 ~/.ssh/config
178
202
 
179
203
  - name: Write .kamal/secrets
180
- env:
181
- SHARED_AI_API_KEY: ${{ secrets[steps.user_secrets.outputs.shared_ai_api_key_secret_name] }}
182
- SHARED_GIT_SYNC_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_git_sync_token_secret_name] }}
183
- SETUP_EMAIL_API_KEY: ${{ secrets.SETUP_EMAIL_API_KEY }}
184
- SETUP_EMAIL_FROM: ${{ secrets.SETUP_EMAIL_FROM }}
185
- KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
186
- SHARED_CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
187
- SHARED_PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
188
- run: |
189
- export AI_API_KEY="${AI_API_KEY:-$SHARED_AI_API_KEY}"
190
- export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
191
- export CERTIFICATE_PEM="${CERTIFICATE_PEM:-$SHARED_CERTIFICATE_PEM}"
192
- export PRIVATE_KEY_PEM="${PRIVATE_KEY_PEM:-$SHARED_PRIVATE_KEY_PEM}"
193
- bun deploy/scripts/write-kamal-secrets.ts
204
+ run: bun deploy/scripts/write-kamal-secrets.ts
194
205
 
195
206
  - name: Provision server
196
207
  id: provision
197
208
  env:
198
- HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
199
- HCLOUD_SSH_KEY_NAME: ${{ secrets.HCLOUD_SSH_KEY_NAME }}
200
- HCLOUD_SERVER_TYPE: ${{ secrets.HCLOUD_SERVER_TYPE }}
201
- HCLOUD_LOCATION: ${{ secrets.HCLOUD_LOCATION }}
202
209
  INSTANCE_NAME: ${{ steps.user_config.outputs.instance_name }}
203
210
  run: bun deploy/scripts/provision-server.ts
204
211
 
205
212
  - name: Update Cloudflare DNS
206
213
  env:
207
- CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
208
- CONFIG_CF_ZONE_ID: ${{ steps.user_config.outputs.cloudflare_zone_id }}
209
- SHARED_CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
210
214
  BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
211
- WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
212
215
  PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
213
216
  SERVER_IP: ${{ steps.provision.outputs.server_ip }}
214
217
  run: |
215
- export CF_ZONE_ID="${CONFIG_CF_ZONE_ID:-${CF_ZONE_ID:-$SHARED_CF_ZONE_ID}}"
216
218
  bun deploy/scripts/update-dns.ts
217
- if [ -n "$WWW_DOMAIN" ]; then
218
- BRAIN_DOMAIN="$WWW_DOMAIN" bun deploy/scripts/update-dns.ts
219
- fi
220
219
  BRAIN_DOMAIN="$PREVIEW_DOMAIN" bun deploy/scripts/update-dns.ts
221
220
 
222
221
  - name: Validate SSH key
@@ -244,7 +243,6 @@ jobs:
244
243
  IMAGE_REPOSITORY: ${{ steps.user_config.outputs.image_repository }}
245
244
  REGISTRY_USERNAME: ${{ steps.user_config.outputs.registry_username }}
246
245
  BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
247
- WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
248
246
  PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
249
247
  BRAIN_YAML_PATH: ${{ steps.user_config.outputs.brain_yaml_path }}
250
248
  run: kamal setup --skip-push -c deploy/kamal/deploy.yml
@@ -253,13 +251,9 @@ jobs:
253
251
  env:
254
252
  SERVER_IP: ${{ steps.provision.outputs.server_ip }}
255
253
  BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
256
- WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
257
254
  PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
258
255
  run: |
259
256
  curl -I -k --max-time 20 --resolve "$BRAIN_DOMAIN:443:$SERVER_IP" "https://$BRAIN_DOMAIN/health"
260
- if [ -n "$WWW_DOMAIN" ]; then
261
- curl -I -k --max-time 20 --resolve "$WWW_DOMAIN:443:$SERVER_IP" "https://$WWW_DOMAIN/"
262
- fi
263
257
  curl -I -k --max-time 20 --resolve "$PREVIEW_DOMAIN:443:$SERVER_IP" "https://$PREVIEW_DOMAIN/"
264
258
 
265
259
  - name: Upload generated config
@@ -6,5 +6,8 @@ export {
6
6
  requireEnv,
7
7
  writeGitHubOutput,
8
8
  writeGitHubEnv,
9
+ siteImageTag,
10
+ sitePackagesFor,
11
+ runResolveMissingImages,
9
12
  } from "@rizom/ops/deploy";
10
13
  export type { EnvSchemaEntry } from "@rizom/ops/deploy";
@@ -0,0 +1,13 @@
1
+ import {
2
+ requireEnv,
3
+ runResolveMissingImages,
4
+ writeGitHubOutput,
5
+ } from "./helpers";
6
+
7
+ // The Build workflow's resolve step — the logic lives in @rizom/ops; this
8
+ // pilot repo only supplies its own paths and repository.
9
+ await runResolveMissingImages({
10
+ rootDir: process.cwd(),
11
+ imageRepository: `ghcr.io/${requireEnv("GITHUB_REPOSITORY")}`,
12
+ writeOutput: writeGitHubOutput,
13
+ });
@@ -1,22 +1,20 @@
1
- import { createHash } from "node:crypto";
2
1
  import { readFileSync } from "node:fs";
2
+
3
3
  import { loadPilotRegistry } from "@rizom/ops";
4
4
 
5
- import { parseEnvFile, requireEnv, writeGitHubOutput } from "./helpers";
5
+ import {
6
+ parseEnvFile,
7
+ requireEnv,
8
+ sitePackagesFor,
9
+ siteImageTag,
10
+ writeGitHubOutput,
11
+ } from "./helpers";
6
12
 
7
13
  const handle = requireEnv("HANDLE");
8
14
  const envPath = `users/${handle}/.env`;
9
15
  const brainYamlPath = `users/${handle}/brain.yaml`;
10
16
 
11
17
  const envEntries = parseEnvFile(envPath);
12
- const registry = await loadPilotRegistry(process.cwd());
13
- const user = registry.users.find((candidate) => candidate.handle === handle);
14
- if (!user) {
15
- throw new Error(`Unknown user handle: ${handle}`);
16
- }
17
-
18
- const brainVersion = envEntries["BRAIN_VERSION"] ?? "";
19
- const imageTag = resolveImageTag(registry, brainVersion);
20
18
  const repository = process.env["GITHUB_REPOSITORY"] ?? "";
21
19
  const repositoryOwner = repository.split("/")[0] ?? "";
22
20
 
@@ -27,24 +25,31 @@ if (!brainDomain) {
27
25
  throw new Error(`Missing domain in ${brainYamlPath}`);
28
26
  }
29
27
 
30
- const pilotSubdomainPrefix = `${handle}.`;
31
- const pilotZone =
32
- brainDomain.startsWith(pilotSubdomainPrefix) &&
33
- brainDomain.length > pilotSubdomainPrefix.length
34
- ? brainDomain.slice(pilotSubdomainPrefix.length)
28
+ const zone =
29
+ brainDomain.startsWith(`${handle}.`) && brainDomain.length > handle.length + 1
30
+ ? brainDomain.slice(handle.length + 1)
35
31
  : "";
36
- const previewDomain = pilotZone
37
- ? `${handle}-preview.${pilotZone}`
38
- : `preview.${brainDomain}`;
39
- const wwwDomain = pilotZone ? "" : `www.${brainDomain}`;
32
+ if (!zone) {
33
+ throw new Error(`Could not derive preview domain from ${brainDomain}`);
34
+ }
35
+ const previewDomain = `${handle}-preview.${zone}`;
36
+
37
+ const brainVersion = envEntries["BRAIN_VERSION"] ?? "";
38
+
39
+ // The image tag is a pure function of this instance's own config: plain
40
+ // `brain-{version}` for a default instance, or its own `brain-{version}-sites-
41
+ // {hash}` when it declares a siteOverride. Resolved through the same helper the
42
+ // build uses so the tag we wait for and run matches exactly what was pushed.
43
+ const registry = await loadPilotRegistry(process.cwd());
44
+ const user = registry.users.find((entry) => entry.handle === handle);
45
+ const sitePackages = sitePackagesFor(user?.siteOverride);
46
+ const imageTag = siteImageTag(brainVersion, sitePackages);
40
47
 
41
48
  const outputs: Record<string, string> = {
42
49
  brain_version: brainVersion,
43
50
  content_repo: envEntries["CONTENT_REPO"] ?? "",
44
51
  brain_domain: brainDomain,
45
52
  preview_domain: previewDomain,
46
- www_domain: wwwDomain,
47
- cloudflare_zone_id: user.cloudflareZoneId ?? "",
48
53
  brain_yaml_path: brainYamlPath,
49
54
  instance_name: `rover-${handle}`,
50
55
  image_repository: `ghcr.io/${repository}`,
@@ -62,35 +67,3 @@ for (const key of required) {
62
67
  for (const [key, value] of Object.entries(outputs)) {
63
68
  writeGitHubOutput(key, value);
64
69
  }
65
-
66
- function resolveImageTag(
67
- registry: Awaited<ReturnType<typeof loadPilotRegistry>>,
68
- brainVersion: string,
69
- ): string {
70
- if (!brainVersion) {
71
- return "";
72
- }
73
-
74
- const sitePackages = [
75
- ...new Set(
76
- registry.users
77
- .filter((user) => user.brainVersion === brainVersion)
78
- .flatMap((user) =>
79
- user.siteOverride
80
- ? [`${user.siteOverride.package}@${user.siteOverride.version}`]
81
- : [],
82
- ),
83
- ),
84
- ].sort();
85
-
86
- if (sitePackages.length === 0) {
87
- return `brain-${brainVersion}`;
88
- }
89
-
90
- const siteHash = createHash("sha256")
91
- .update(sitePackages.join("\n"))
92
- .digest("hex")
93
- .slice(0, 12);
94
-
95
- return `brain-${brainVersion}-sites-${siteHash}`;
96
- }
@@ -1,53 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { createHash } from "node:crypto";
3
- import { loadPilotRegistry } from "@rizom/ops";
4
- import { writeGitHubEnv, writeGitHubOutput } from "./helpers";
5
-
6
- const requestedBrainVersion = process.env["BRAIN_VERSION_INPUT"]?.trim();
7
- const registry = await loadPilotRegistry(process.cwd());
8
- const brainVersion =
9
- requestedBrainVersion && requestedBrainVersion.length > 0
10
- ? requestedBrainVersion
11
- : registry.pilot.brainVersion;
12
-
13
- const sitePackages = [
14
- ...new Set(
15
- registry.users
16
- .filter((user) => user.brainVersion === brainVersion)
17
- .flatMap((user) =>
18
- user.siteOverride
19
- ? [`${user.siteOverride.package}@${user.siteOverride.version}`]
20
- : [],
21
- ),
22
- ),
23
- ].sort();
24
- const sitePackagesValue = sitePackages.join(" ");
25
- const imageTag = buildImageTag(brainVersion, sitePackages);
26
-
27
- writeGitHubEnv("BRAIN_VERSION", brainVersion);
28
- writeGitHubEnv("SITE_PACKAGES", sitePackagesValue);
29
- writeGitHubEnv("IMAGE_TAG", imageTag);
30
- writeGitHubOutput("brain_version", brainVersion);
31
- writeGitHubOutput("site_packages", sitePackagesValue);
32
- writeGitHubOutput("image_tag", imageTag);
33
-
34
- console.log(`BRAIN_VERSION=${brainVersion}`);
35
- console.log(`IMAGE_TAG=${imageTag}`);
36
- console.log(
37
- sitePackages.length > 0
38
- ? `SITE_PACKAGES=${sitePackagesValue}`
39
- : "SITE_PACKAGES=(none)",
40
- );
41
-
42
- function buildImageTag(brainVersion: string, sitePackages: string[]): string {
43
- if (sitePackages.length === 0) {
44
- return `brain-${brainVersion}`;
45
- }
46
-
47
- const siteHash = createHash("sha256")
48
- .update(sitePackages.join("\n"))
49
- .digest("hex")
50
- .slice(0, 12);
51
-
52
- return `brain-${brainVersion}-sites-${siteHash}`;
53
- }