mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
ci: build on the native runners, and make the tree buildable again
The search fix (be64271) has been sitting on main unpublished because CI on
this forge could not produce an image. Three independent faults, each hidden
behind the one in front of it.
1. The credential. .github/workflows/build-lux.yml logged in to ghcr.io with
secrets.GITHUB_TOKEN. On Hanzo Git that is a Hanzo Git token and ghcr.io has
never heard of it, so every run died in about a minute with
Get "https://ghcr.io/v2/": denied: denied
The build was not queueing forever — it ran, and it failed. The pair that
works here is GHCR_USER / GHCR_TOKEN, the org secrets luxfi/node already
publishes with.
2. The lockfile. d319792b took react-icons out and put @hanzogui/lucide-icons-2
in, and moved @luxfi/ui to 7.4.9, but pnpm-lock.yaml was never regenerated —
it still specified react-icons and @luxfi/ui 7.3.2. The Dockerfile installs
with --frozen-lockfile, so the image build could not have succeeded even with
a valid registry credential:
ERR_PNPM_OUTDATED_LOCKFILE ... don't match specs in package.json
Regenerated against the committed package.json; `pnpm install
--frozen-lockfile` is green.
3. The stub. f3833dfc pointed four Turbopack resolve aliases at
./stubs/empty.js and the file was never added. It exists on the author's
disk, so it builds there and nowhere else. Added.
2 and 3 are the same failure twice: a commit landed with a piece missing, and
because CI could not run, nothing said so. That is the actual cost of a broken
pipeline — not the delay, the silence.
CI moves to .hanzo/workflows, the one path this forge reads, mirroring
luxfi/node:
release.yml gates on the unit suite, builds, proves the image is a runnable
build whose baked NEXT_PUBLIC_GIT_TAG equals the tag being
published, pushes, then proves the manifest resolves. v1.1.14 and
v1.1.15 are package.json bumps with no image behind them; the
inverse — an image whose footer misreports its own version — is
the one that misleads in production, and both are now unpublishable.
ci.yml the unit suite on push and PR.
build-lux.yml is deleted rather than repaired. Besides the dead credential its
second job deployed by driving kubectl straight at the cluster, and those
objects are reconciled by hanzo-cd — an imperative apply is drift by
construction. Deployment is a tag bump in luxfi/universe plus a CD sync; CI's
job ends at a published, resolvable image.
lint:tsc is deliberately not gated: it is red on main on its own account
(TS2304 across ui/**/*.pw.tsx, plus the unrelated vite/ scratch app). It needs
a fix, not a permanently-red job standing in for one.
Version 1.1.15 -> 1.1.16, the patch above the last published tag v1.1.13.
Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -1,173 +0,0 @@
|
||||
name: Build and Deploy LUX Explorer
|
||||
|
||||
# Builds the Lux-branded explorer image only. The source supports
|
||||
# white-label brand selection at runtime (lib/white-label.ts inspects
|
||||
# hostname or NEXT_PUBLIC_BRAND), so a single image serves any brand
|
||||
# at deploy time. Per-brand images for hanzo / zoo / pars are built
|
||||
# from each brand's own org repo (ghcr.io/hanzoai, ghcr.io/zooai, …) —
|
||||
# never from this repo.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "v*"
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
- "**/*.mdx"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: lux-build-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: lux-build
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/luxfi/explore
|
||||
flavor: |
|
||||
latest=auto
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
type=sha,prefix=sha-
|
||||
|
||||
- name: Resolve app version
|
||||
id: appversion
|
||||
# Footer shows NEXT_PUBLIC_GIT_TAG. On a branch push there is no git tag,
|
||||
# so bake the package.json version (single source) as v<version>.
|
||||
run: |
|
||||
v=$(grep -m1 '"version"' package.json | sed -E 's/.*"version": *"([^"]+)".*/\1/')
|
||||
echo "tag=v${v}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
cache-from: type=gha,scope=lux
|
||||
cache-to: type=gha,mode=max,scope=lux
|
||||
provenance: false
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
build-args: |
|
||||
GIT_COMMIT_SHA=${{ github.sha }}
|
||||
GIT_TAG=${{ github.ref_type == 'tag' && github.ref_name || steps.appversion.outputs.tag }}
|
||||
NEXT_PUBLIC_BRAND=lux
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
runs-on: lux-build
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
|
||||
- name: Fetch deploy credentials from KMS
|
||||
id: kms
|
||||
env:
|
||||
KMS_CLIENT_ID: ${{ secrets.KMS_CLIENT_ID }}
|
||||
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}
|
||||
HANZO_API_KEY: ${{ secrets.HANZO_API_KEY }}
|
||||
KMS_ENDPOINT: ${{ vars.KMS_ENDPOINT || 'https://kms.hanzo.ai' }}
|
||||
DO_FALLBACK_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
run: |
|
||||
if [ -n "${KMS_CLIENT_ID}" ] && [ -n "${KMS_CLIENT_SECRET}" ]; then
|
||||
# Must not abort the step under `bash -eo pipefail` when KMS login
|
||||
# fails — swallow curl/jq errors so the DO_FALLBACK_TOKEN path runs.
|
||||
HANZO_API_KEY=$(curl -sf "${KMS_ENDPOINT}/api/v1/auth/universal-auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"clientId\":\"${KMS_CLIENT_ID}\",\"clientSecret\":\"${KMS_CLIENT_SECRET}\"}" 2>/dev/null | jq -r '.accessToken' 2>/dev/null || echo "")
|
||||
fi
|
||||
response=$(curl -sf "${KMS_ENDPOINT}/api/v3/secrets/raw?workspaceId=e1359bf4-31b4-4dfa-bb90-323e2c298ad8&secretPath=/deploy&environment=prod" \
|
||||
-H "Authorization: Bearer ${HANZO_API_KEY}" 2>/dev/null || echo "")
|
||||
if [ -n "$response" ]; then
|
||||
token=$(echo "$response" | jq -r '.secrets[] | select(.secretKey=="DO_API_TOKEN") | .secretValue // empty' 2>/dev/null || echo "")
|
||||
fi
|
||||
# KMS is the source of truth for the DO token; fall back to the repo's
|
||||
# DigitalOcean token secret when KMS has no /deploy DO_API_TOKEN entry.
|
||||
if [ -z "${token:-}" ]; then
|
||||
token="${DO_FALLBACK_TOKEN}"
|
||||
fi
|
||||
if [ -z "${token:-}" ]; then
|
||||
echo "::warning::Deploy credentials not found. Image was pushed but deployment skipped."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "::add-mask::${token}"
|
||||
echo "token=${token}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Deploy to production
|
||||
if: steps.kms.outputs.skip != 'true'
|
||||
env:
|
||||
DO_TOKEN: ${{ steps.kms.outputs.token }}
|
||||
run: |
|
||||
INSTALL_DIR="$HOME/.local/bin"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
export PATH="$INSTALL_DIR:$PATH"
|
||||
if ! command -v doctl &>/dev/null; then
|
||||
DOCTL_VERSION=$(curl -sI https://github.com/digitalocean/doctl/releases/latest | grep -i '^location:' | sed 's|.*/v||;s/\r//')
|
||||
curl -sL "https://github.com/digitalocean/doctl/releases/download/v${DOCTL_VERSION}/doctl-${DOCTL_VERSION}-linux-amd64.tar.gz" | tar xz -C "$INSTALL_DIR"
|
||||
fi
|
||||
if ! command -v kubectl &>/dev/null; then
|
||||
curl -sLO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
|
||||
chmod +x kubectl && mv kubectl "$INSTALL_DIR/kubectl"
|
||||
fi
|
||||
doctl auth init -t "$DO_TOKEN"
|
||||
doctl kubernetes cluster kubeconfig save lux-k8s
|
||||
|
||||
for ns in lux-mainnet lux-testnet lux-devnet; do
|
||||
kubectl create namespace $ns 2>/dev/null || true
|
||||
done
|
||||
|
||||
kubectl apply -f deploy/k8s/stats.yaml -n lux-mainnet || true
|
||||
kubectl rollout restart deployment/lux-stats -n lux-mainnet 2>/dev/null || true
|
||||
|
||||
# The Lux-branded FE image built by this run. metadata-action tags it
|
||||
# sha-<7-char-short> (see the build job). IfNotPresent + a fresh
|
||||
# immutable sha tag guarantees each rollout pulls the new bits.
|
||||
IMAGE="${REGISTRY}/luxfi/explore:sha-$(echo "${GITHUB_SHA}" | cut -c1-7)"
|
||||
echo "Rolling explore-fe-lux -> ${IMAGE}"
|
||||
|
||||
# explore-fe-lux is the Deployment that serves explore.lux.network (see
|
||||
# deploy/k8s/explore-fe/deployments-mainnet.yaml + the mainnet
|
||||
# ingressroute). Roll it across every network; testnet/devnet best-effort.
|
||||
kubectl -n lux-testnet set image deployment/explore-fe-lux explore="${IMAGE}" 2>/dev/null || true
|
||||
kubectl -n lux-devnet set image deployment/explore-fe-lux explore="${IMAGE}" 2>/dev/null || true
|
||||
kubectl -n lux-mainnet set image deployment/explore-fe-lux explore="${IMAGE}"
|
||||
|
||||
kubectl -n lux-mainnet rollout status deployment/explore-fe-lux --timeout=300s
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Tests
|
||||
|
||||
# The repo's unit suite, on the native runners (git.hanzo.ai → git-runner).
|
||||
#
|
||||
# This replaces the runnable half of .github/workflows/checks.yml. Every job
|
||||
# here names a label the fleet actually carries, and every job here is green on
|
||||
# main — a job no runner can claim, or one that is perpetually red, is worse
|
||||
# than no job at all, because it trains everyone to ignore the colour.
|
||||
#
|
||||
# Deliberately NOT gated here:
|
||||
#
|
||||
# lint:tsc — `tsc -p tsconfig.json` is red on main independently of any change
|
||||
# in this workflow: ~dozens of TS2304 "Cannot find name 'Box'" in
|
||||
# ui/**/*.pw.tsx (Playwright fixtures the app tsconfig pulls in but
|
||||
# does not give React/Chakra globals to) plus the unrelated vite/
|
||||
# scratch app (missing `wouter`, ImportMeta.env). Gating on it
|
||||
# would make this workflow red on arrival and prove nothing. It is
|
||||
# a real defect and it needs its own fix, not a permanently-failing
|
||||
# job standing in for one.
|
||||
#
|
||||
# test:pw — Playwright needs a browser image and a built app; it belongs in
|
||||
# its own workflow with its own timeout, not bolted onto the unit
|
||||
# gate.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# A newer commit makes the older run irrelevant.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
vitest:
|
||||
runs-on: [lux-build-linux-amd64]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.14.0'
|
||||
|
||||
- name: Install pnpm
|
||||
run: |
|
||||
set -euo pipefail
|
||||
corepack enable
|
||||
corepack prepare [email protected] --activate
|
||||
pnpm --version
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Unit tests
|
||||
run: pnpm vitest run
|
||||
@@ -0,0 +1,184 @@
|
||||
name: Release
|
||||
|
||||
# Builds ghcr.io/luxfi/explore on the native runners (git.hanzo.ai → git-runner).
|
||||
#
|
||||
# This replaces .github/workflows/build-lux.yml, which could never publish from
|
||||
# this forge for two independent reasons:
|
||||
#
|
||||
# 1. It authenticated to ghcr.io with `secrets.GITHUB_TOKEN`. On Hanzo Git that
|
||||
# token is a Hanzo Git token; ghcr.io has never heard of it. Every run died
|
||||
# at the login step with `Get "https://ghcr.io/v2/": denied: denied` — the
|
||||
# build ran, took a minute, and failed. The credential that works here is the
|
||||
# GHCR_USER / GHCR_TOKEN org secret pair, sourced from KMS, exactly as
|
||||
# luxfi/node uses.
|
||||
#
|
||||
# 2. Its second job deployed by driving kubectl straight at the cluster. Those
|
||||
# objects are reconciled by hanzo-cd (the live Deployment carries
|
||||
# apps.hanzo.ai/installation-id: hanzo-cd), so an imperative apply is drift
|
||||
# by construction — it wins until the next sync and then silently loses.
|
||||
# Deployment is a tag bump in luxfi/universe
|
||||
# (deploy/lux-mainnet/explore-fe-lux.yaml) plus a CD sync. CI's job ends at a
|
||||
# published, resolvable image.
|
||||
#
|
||||
# A tag push vX.Y.Z is a release: the image carries the semver tags plus the sha
|
||||
# tag; a main push carries only the sha tag.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [lux-build-linux-amd64]
|
||||
# Next.js production build of the full explorer. Measured runs sit well
|
||||
# under this; the ceiling is here so a hung build releases the runner.
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.14.0'
|
||||
|
||||
- name: Install pnpm
|
||||
run: |
|
||||
set -euo pipefail
|
||||
corepack enable
|
||||
corepack prepare [email protected] --activate
|
||||
pnpm --version
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# The gate. The search crash this repo exists to fix is a unit-testable
|
||||
# contract — /search/quick answers with a paginated envelope, the frontend
|
||||
# used to iterate it as a bare list — and the suite covers it directly
|
||||
# (lib/api/unwrapItems.spec.ts, ui/snippets/searchBar/useQuickSearchQuery.spec.tsx).
|
||||
# An image that cannot pass this must not become a tag.
|
||||
- name: Unit tests
|
||||
run: pnpm vitest run
|
||||
|
||||
- name: Compute tags
|
||||
id: meta
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA_TAG="ghcr.io/luxfi/explore:sha-${GITHUB_SHA:0:7}"
|
||||
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
||||
{
|
||||
echo "tags<<EOF"
|
||||
echo "ghcr.io/luxfi/explore:v${VERSION}"
|
||||
echo "$SHA_TAG"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "probe=ghcr.io/luxfi/explore:v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "git_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
{
|
||||
echo "tags<<EOF"
|
||||
echo "$SHA_TAG"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "probe=$SHA_TAG" >> "$GITHUB_OUTPUT"
|
||||
# No tag on a branch push, so the footer's version string comes from
|
||||
# package.json — the single source for "which release is this".
|
||||
echo "git_tag=v$(node -p 'require("./package.json").version')" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver: docker-container
|
||||
driver-opts: network=host
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.GHCR_USER }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
||||
- name: Build (load for smoke, not yet pushed)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64
|
||||
load: true
|
||||
push: false
|
||||
provenance: false
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
GIT_COMMIT_SHA=${{ github.sha }}
|
||||
GIT_TAG=${{ steps.meta.outputs.git_tag }}
|
||||
NEXT_PUBLIC_BRAND=lux
|
||||
# Which code is actually in this artifact, answerable from the registry
|
||||
# alone via `docker buildx imagetools inspect` — without pulling it.
|
||||
labels: |
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.source=https://git.hanzo.ai/luxfi/explore
|
||||
|
||||
- name: Smoke — the image is a runnable build, and its stamp is not a lie
|
||||
# v1.1.14 and v1.1.15 exist as package.json bumps with no image behind
|
||||
# them, because CI has been unable to publish since July. The inverse
|
||||
# failure — an image whose footer reports a version other than its own
|
||||
# tag — is the one that misleads in production, since the footer is how
|
||||
# anyone reads which build they are looking at. Both are checkable here
|
||||
# from the image config, without booting: the entrypoint downloads
|
||||
# external assets and validates runtime envs, so a bare `docker run`
|
||||
# proves nothing about the build and fails for reasons of its own.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
img="${{ steps.meta.outputs.probe }}"
|
||||
|
||||
# The standalone server the CMD names must exist.
|
||||
docker run --rm --entrypoint sh "$img" -c 'test -f server.js' \
|
||||
|| { echo "::error::server.js missing — not a usable standalone build"; exit 1; }
|
||||
|
||||
# Client chunks must have been emitted; an empty .next/static means the
|
||||
# browser bundle never built and every page would render blank.
|
||||
chunks=$(docker run --rm --entrypoint sh "$img" -c 'ls -1 .next/static/chunks 2>/dev/null | wc -l')
|
||||
echo "client chunks: ${chunks}"
|
||||
if [ "${chunks}" -lt 1 ]; then
|
||||
echo "::error::no client chunks in .next/static — the browser bundle is missing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The version baked at build time must be the version being published.
|
||||
baked=$(docker run --rm --entrypoint sh "$img" -c 'printenv NEXT_PUBLIC_GIT_TAG' || true)
|
||||
want='${{ steps.meta.outputs.git_tag }}'
|
||||
echo "baked NEXT_PUBLIC_GIT_TAG=${baked:-<unset>} want=${want}"
|
||||
if [ "${baked}" != "${want}" ]; then
|
||||
echo "::error::image stamps ${baked:-<unset>} but is published as ${want} — the footer would misreport the build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Push
|
||||
run: |
|
||||
set -euo pipefail
|
||||
while IFS= read -r t; do
|
||||
[ -n "$t" ] && docker push "$t"
|
||||
done <<< "${{ steps.meta.outputs.tags }}"
|
||||
|
||||
- name: Verify the pushed manifest resolves
|
||||
# A tag that names nothing pullable is how a Deployment ends up pinned to
|
||||
# a phantom: the pin applies, the pod never starts, and the tag looks like
|
||||
# a release. Prove it before the job is green.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
img="${{ steps.meta.outputs.probe }}"
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if docker manifest inspect "$img" >/dev/null 2>&1; then
|
||||
echo "$img is resolvable"; exit 0
|
||||
fi
|
||||
echo "manifest not visible yet (attempt ${i}/6) — retrying"; sleep 5
|
||||
done
|
||||
echo "::error::$img not pullable after push"; exit 1
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@luxfi/explore",
|
||||
"version": "1.1.15",
|
||||
"version": "1.1.16",
|
||||
"private": false,
|
||||
"homepage": "https://github.com/luxfi/explore#readme",
|
||||
"engines": {
|
||||
|
||||
Generated
+5416
-491
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
// The browser's answer to a Node builtin.
|
||||
//
|
||||
// `configs/multichain/config.nodejs.ts` reaches for `fs`/`path` behind a
|
||||
// `NEXT_RUNTIME === 'nodejs'` guard, and MetaMask's SDK reaches for a React
|
||||
// Native storage module. Webpack was told to answer those with nothing
|
||||
// (`resolve.fallback: { fs: false }`); Turbopack has no `fallback`, so it needs
|
||||
// a module to point at. This is that module — same answer, both bundlers.
|
||||
module.exports = {};
|
||||
Reference in New Issue
Block a user