Build the web console on our own runners from a lockfile that resolves

hanzo.yml declares the one image, ghcr.io/hanzoai/bootnode:<version>-web,
built from the repository root with console/Dockerfile and named by the
console's own package version, and gates it on the console's unit tests
and typecheck. .hanzo/workflows/cicd.yml runs hanzoai/ci build.yml@v1 on
push to main.

The console manifest names every dependency from the pnpm catalog, and the
catalog carries the versions this code builds against: @luxfi/ui 7.4.3 with
@hanzo/gui 7.3, next 16.2, TypeScript 5.9 and the Zen faces from
@hanzo/font 1.8.0. `pnpm install --frozen-lockfile` and `next build` both
pass. The unused geist and lucide-react entries and the npm lockfile are
gone, tests report in TAP so the pipeline can count them, and the README no
longer calls this repository a retired copy.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
zooqueen
2026-09-15 12:01:02 -07:00
co-authored by Hanzo Dev
parent cf106de1da
commit a479ffa0db
11 changed files with 7735 additions and 4611 deletions
+11
View File
@@ -0,0 +1,11 @@
name: CI/CD
on:
push:
branches: [main]
workflow_dispatch:
jobs:
cicd:
uses: hanzoai/ci/.github/workflows/build.yml@v1
secrets: inherit
-7
View File
@@ -1,10 +1,3 @@
> **Retired — this is a stale copy of `hanzoai/bootnode`.**
>
> Its 3 unique commits are carried into `hanzoai/bootnode` first — merged onto the default branch or pushed there as `carry/*` refs — so nothing here is lost. This copy has no push mirror, so those commits had reached nothing.
>
> It also declared `ghcr.io/hanzoai/bootnode`, the tag `hanzoai/bootnode` owns, so a push here
> could have published over it. That declaration is removed.
<p align="center"><img src=".github/hero.svg" alt="bootnode" width="880"></p>
# Bootnode
-26
View File
@@ -1,26 +0,0 @@
# Build outputs and caches — regenerated inside the Dockerfile (pnpm install + next build).
# Excluding them keeps the build context small so the buildx kubernetes-driver
# context transfer is fast and reliable. Does NOT alter the produced artifact.
node_modules
.next
.turbo
out
dist
# VCS / editor / OS
.git
.gitignore
.vscode
.idea
.DS_Store
# Logs / local env / test artifacts
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.env*.local
coverage
playwright-report
test-results
*.tsbuildinfo
+8 -19
View File
@@ -1,22 +1,12 @@
# Bootnode web console — image ghcr.io/hanzoai/bootnode:<tag>-web.
# Bootnode web console, image ghcr.io/hanzoai/bootnode:<version>-web.
#
# Serves the Next.js STATIC EXPORT via ghcr.io/hanzoai/static, matching the
# house pattern (hanzo.ai/Dockerfile.production). No nginx, no caddy, no Node
# server in the runtime image.
#
# This file previously ended in a `node server.js` runner copying
# /app/.next/standalone, but next.config.ts declares `output: "export"`, which
# emits ./out and NEVER emits .next/standalone. The two deploy models are
# mutually exclusive, so the image build had been failing at
# COPY /app/.next/standalone: not found
# and the deployed image stayed frozen at its last pre-`export` build. One
# model now: static export, served by hanzoai/static.
#
# Build (BuildKit, on-cluster — no GHA). The build context is this directory,
# so pass the subdir in the git ref:
# --opt=context=https://github.com/hanzoai/bootnode.git#<SHA>:web
# --opt=filename=Dockerfile
# --output=type=image,name=ghcr.io/hanzoai/bootnode:<tag>-web,push=true
# The build context is the REPOSITORY ROOT and this file is named by path, which
# is how the build door takes it (hanzo.yml `images:`, context `.`):
# docker build -f console/Dockerfile .
# ---- build stage: Next.js static export ----------------------------------
# Node 24 = Active LTS (26 is Current, not LTS). Digest-pinned so the base
@@ -30,10 +20,10 @@ RUN apk add --no-cache libc6-compat
# must land before install or sharp's build script is skipped and the install
# fails ERR_PNPM_IGNORED_BUILDS. package.json's `packageManager` pins which pnpm
# corepack fetches, so a newly published pnpm can never change this build.
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
COPY console/package.json console/pnpm-lock.yaml console/pnpm-workspace.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile
COPY . .
COPY console/ .
# ONE image, every brand — and therefore NO brand build args.
#
@@ -64,9 +54,8 @@ RUN cd out && for f in $(find . -type f -name '*.html' ! -name 'index.html' ! -n
done
# ---- serve stage: hanzoai/static (scratch + single Go binary) -------------
# Port 3001 keeps the existing contract: the bootnode-web App CR declares
# containerPort/servicePort 3001 and the hand-made bootnode-web-ingress routes
# to bootnode-web:3001 by number.
# Port 3001 is the contract the bootnode-web values in universe declare
# (containerPort/servicePort 3001), and the host routes to it by number.
FROM ghcr.io/hanzoai/static:0.5.15
COPY --from=build /app/out /srv
EXPOSE 3001
+22
View File
@@ -0,0 +1,22 @@
# The build context is the repository root (see Dockerfile), so every pattern
# here is relative to it. Build outputs and caches are regenerated inside the
# image, and nothing outside console/ is copied in.
**/node_modules
**/.next
**/.turbo
**/out
**/dist
.git
# VCS / editor / OS
**/.vscode
**/.idea
**/.DS_Store
# Logs / local env / test artifacts
**/*.log
**/.env*.local
**/coverage
**/playwright-report
**/test-results
**/*.tsbuildinfo
-4371
View File
File diff suppressed because it is too large Load Diff
+6 -15
View File
@@ -1,6 +1,6 @@
{
"name": "@bootnode/web",
"version": "2.0.0",
"version": "2.0.1",
"packageManager": "[email protected]",
"private": true,
"type": "module",
@@ -10,12 +10,14 @@
"start": "next start -p 3001",
"lint": "next lint",
"typecheck": "tsc --noEmit",
"test": "node --test --experimental-strip-types \"lib/**/*.test.ts\""
"test": "node --test --test-reporter=tap --experimental-strip-types \"lib/**/*.test.ts\""
},
"dependencies": {
"@hanzo/font": "catalog:",
"@hanzo/gui": "catalog:",
"@hanzo/iam": "catalog:",
"@luxfi/logo": "catalog:",
"@luxfi/ui": "catalog:",
"@radix-ui/react-accordion": "catalog:",
"@radix-ui/react-alert-dialog": "catalog:",
"@radix-ui/react-avatar": "catalog:",
@@ -43,21 +45,17 @@
"cmdk": "catalog:",
"d3": "catalog:",
"date-fns": "catalog:",
"lucide-react": "catalog:",
"next": "catalog:",
"next-themes": "catalog:",
"nuqs": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"react-native-web": "catalog:",
"recharts": "catalog:",
"sonner": "catalog:",
"tailwind-merge": "catalog:",
"tailwindcss-animate": "catalog:",
"zod": "catalog:",
"@hanzo/gui": "catalog:",
"@luxfi/ui": "catalog:",
"geist": "catalog:",
"react-native-web": "catalog:"
"zod": "catalog:"
},
"devDependencies": {
"@tailwindcss/postcss": "catalog:",
@@ -67,12 +65,5 @@
"postcss": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:"
},
"pnpm": {
"onlyBuiltDependencies": [
"@tailwindcss/oxide",
"esbuild",
"sharp"
]
}
}
+7651 -63
View File
File diff suppressed because it is too large Load Diff
+20 -110
View File
@@ -15,103 +15,13 @@ allowBuilds:
sharp: true
minimumReleaseAgeExclude:
- '@hanzo/[email protected]'
overrides:
'@noble/hashes': '1.8.0'
'zod': '^4.5.2'
'@hanzogui/adapt': '8.3.1'
'@hanzogui/animate': '8.3.1'
'@hanzogui/animate-presence': '8.3.1'
'@hanzogui/animations-css': '8.3.1'
'@hanzogui/animations-moti': '8.3.1'
'@hanzogui/animations-react-native': '8.3.1'
'@hanzogui/babel-plugin': '8.3.1'
'@hanzogui/build': '8.3.1'
'@hanzogui/button': '8.3.1'
'@hanzogui/checkbox-headless': '8.3.1'
'@hanzogui/cli': '8.3.1'
'@hanzogui/collapsible': '8.3.1'
'@hanzogui/collection': '8.3.1'
'@hanzogui/colors': '8.3.1'
'@hanzogui/component-helpers': '8.3.1'
'@hanzogui/compose-refs': '8.3.1'
'@hanzogui/config': '8.3.1'
'@hanzogui/config-default': '8.3.1'
'@hanzogui/constants': '8.3.1'
'@hanzogui/core': '8.3.1'
'@hanzogui/create-context': '8.3.1'
'@hanzogui/create-menu': '8.3.1'
'@hanzogui/dialog': '8.3.1'
'@hanzogui/dismissable': '8.3.1'
'@hanzogui/element': '8.3.1'
'@hanzogui/floating': '8.3.1'
'@hanzogui/focus-guard': '8.3.1'
'@hanzogui/focus-scope': '8.3.1'
'@hanzogui/focusable': '8.3.1'
'@hanzogui/font-size': '8.3.1'
'@hanzogui/get-button-sized': '8.3.1'
'@hanzogui/get-font-sized': '8.3.1'
'@hanzogui/get-token': '8.3.1'
'@hanzogui/group': '8.3.1'
'@hanzogui/helpers': '8.3.1'
'@hanzogui/helpers-icon': '8.3.1'
'@hanzogui/image': '8.3.1'
'@hanzogui/input': '8.3.1'
'@hanzogui/label': '8.3.1'
'@hanzogui/list-item': '8.3.1'
'@hanzogui/loader': '8.3.1'
'@hanzogui/lucide-icons-2': '8.3.1'
'@hanzogui/native': '8.3.1'
'@hanzogui/next-plugin': '8.3.1'
'@hanzogui/next-theme': '8.3.1'
'@hanzogui/polyfill-dev': '8.3.1'
'@hanzogui/popover': '8.3.1'
'@hanzogui/popper': '8.3.1'
'@hanzogui/portal': '8.3.1'
'@hanzogui/radio-headless': '8.3.1'
'@hanzogui/react-native-media-driver': '8.3.1'
'@hanzogui/react-native-svg': '8.3.1'
'@hanzogui/react-native-use-pressable': '8.3.1'
'@hanzogui/react-native-use-responder-events': '8.3.1'
'@hanzogui/react-native-web-lite': '8.3.1'
'@hanzogui/remove-scroll': '8.3.1'
'@hanzogui/roving-focus': '8.3.1'
'@hanzogui/scroll-view': '8.3.1'
'@hanzogui/separator': '8.3.1'
'@hanzogui/shapes': '8.3.1'
'@hanzogui/sheet': '8.3.1'
'@hanzogui/shell': '8.3.1'
'@hanzogui/sizable-context': '8.3.1'
'@hanzogui/spacer': '8.3.1'
'@hanzogui/stacks': '8.3.1'
'@hanzogui/start-transition': '8.3.1'
'@hanzogui/switch-headless': '8.3.1'
'@hanzogui/telemetry': '8.3.1'
'@hanzogui/text': '8.3.1'
'@hanzogui/theme': '8.3.1'
'@hanzogui/theme-base': '8.3.1'
'@hanzogui/themes': '8.3.1'
'@hanzogui/toast': '8.3.1'
'@hanzogui/use-async': '8.3.1'
'@hanzogui/use-callback-ref': '8.3.1'
'@hanzogui/use-constant': '8.3.1'
'@hanzogui/use-controllable-state': '8.3.1'
'@hanzogui/use-debounce': '8.3.1'
'@hanzogui/use-did-finish-ssr': '8.3.1'
'@hanzogui/use-direction': '8.3.1'
'@hanzogui/use-escape-keydown': '8.3.1'
'@hanzogui/use-event': '8.3.1'
'@hanzogui/use-force-update': '8.3.1'
'@hanzogui/use-keyboard-visible': '8.3.1'
'@hanzogui/use-presence': '8.3.1'
'@hanzogui/use-previous': '8.3.1'
'@hanzogui/visually-hidden': '8.3.1'
'@hanzogui/vite-plugin': '8.3.1'
'@hanzogui/web': '8.3.1'
'@hanzogui/z-index-stack': '8.3.1'
- '@luxfi/[email protected] || 7.4.3'
catalog:
'@hanzo/font': '1.9.2'
'@hanzo/iam': '0.21.17'
'@luxfi/logo': '1.0.10'
'@hanzo/font': '1.8.0'
'@hanzo/gui': '^7.3.0'
'@hanzo/iam': '^0.13.8'
'@luxfi/logo': '^1.0.7'
'@luxfi/ui': '7.4.3'
'@radix-ui/react-accordion': '^1.2.20'
'@radix-ui/react-alert-dialog': '^1.1.23'
'@radix-ui/react-avatar': '^1.2.6'
@@ -132,28 +42,28 @@ catalog:
'@radix-ui/react-toast': '^1.2.23'
'@radix-ui/react-tooltip': '^1.2.16'
'@tailwindcss/postcss': '^4.3.3'
'@tanstack/react-query': '^5.102.8'
'@tanstack/react-table': '^9.2.4'
'@tanstack/react-query': '^5.101.4'
'@tanstack/react-table': '^8.21.3'
'@types/d3': '^7.4.3'
'@types/node': '26.4.0'
'@types/react': '19.2.18'
'@types/react-dom': '19.2.5'
'@types/node': '^24.13.3'
'@types/react': '^19.2.17'
'@types/react-dom': '^19.2.3'
'class-variance-authority': '^0.7.1'
'clsx': '^2.1.1'
'cmdk': '^1.1.1'
'd3': '^7.9.0'
'date-fns': '^4.4.0'
'lucide-react': '^1.33.0'
'next': '16.3.3'
'next': '^16.2.11'
'next-themes': '^0.4.6'
'nuqs': '^2.10.1'
'postcss': '^8.5.26'
'react': '19.2.8'
'react-dom': '19.2.8'
'nuqs': '^2.9.2'
'postcss': '^8.5.23'
'react': '^19.2.8'
'react-dom': '^19.2.8'
'react-native-web': '^0.21.0'
'recharts': '^3.10.1'
'sonner': '^2.0.8'
'sonner': '^2.0.7'
'tailwind-merge': '^3.6.0'
'tailwindcss': '^4.3.3'
'tailwindcss-animate': '^1.0.7'
'typescript': '7.0.2'
'zod': '^4.5.2'
'typescript': '^5.9.3'
'zod': '^4.4.3'
+1
View File
@@ -9,6 +9,7 @@
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"allowImportingTsExtensions": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
+16
View File
@@ -0,0 +1,16 @@
# Build and test, read by hanzoai/ci (.hanzo/workflows/cicd.yml) on our own runners.
#
# One image, the web console. It serves every white-label host, and the brand is
# chosen in the browser from the hostname (console/lib/brand.ts).
images:
- name: web
context: .
dockerfile: console/Dockerfile
repo: ghcr.io/hanzoai/bootnode
tag-suffix: web
# The console's own version names the image.
version: "sh:jq -r .version console/package.json"
test:
- name: console
run: cd console && pnpm install --frozen-lockfile && pnpm test && pnpm typecheck