Consuming the base layer (versioned)
Consuming the base layer (versioned)
This is the recommended way for a new admin project to depend on the base
layer — as a pinned, versioned dependency (Mode B), with a gitignored local
override for day-to-day development. It mirrors how Redelay Go apps consume the
framework via versioned go get + a go.work dev override.
The older approach — a filesystem extends pointing at a sibling clone (shown
in Layered architecture and
Building a custom admin project) — is
Mode A: fine for hacking inside the monorepo, but it means a fresh clone or
CI can't build without the base-layer tree checked out next to it, and there's
no version pin. Prefer the versioned approach below.
fitniac/admin (the GymTracer admin). goshop-pl's
admin is still Mode A and will be reworked to match this.Naming: package vs. repo
| Value | Where it appears | |
|---|---|---|
| npm package name | @redelay/js-admin | the dependency key, node_modules/@redelay/js-admin, the pnpm override, the extends path, Tailwind @source |
| github repo | redelay/js-admin-nuxt4 | the github: URL only |
The two intentionally differ: the package was rebranded to the scoped
@redelay/js-admin (branded, collision-safe, not locked to a Nuxt version)
while the repo URL was kept to avoid breaking existing clones. Convention for
future JS framework packages: scope them under @redelay/….
1. Pin the dependency
In the project admin's package.json, depend on the base layer by its package
name, resolved from the repo at a tag:
{
"dependencies": {
"@redelay/js-admin": "github:redelay/js-admin-nuxt4#v0.4.29"
}
}
Fetching a private repo needs GOPRIVATE-style git access — a git
insteadOf ssh rewrite (host dev) or a token (CI). The dev container never
fetches it (see the override below), so it needs no credentials.
2. Local dev override (the go.work analog)
A gitignored pnpm-workspace.yaml makes pnpm link the sibling checkout
instead of fetching the pinned tag — so you edit base-layer source and HMR
reflects it, with no version bump and no credentials in the container. pnpm 10
reads overrides from this file, and a link: override replaces the spec
before git resolution (it never touches the network):
# pnpm-workspace.yaml — gitignored; commit pnpm-workspace.yaml.example instead
overrides:
"@redelay/js-admin": "link:../../redelay/js-admin-nuxt4"
Commit a pnpm-workspace.yaml.example template and gitignore the real file, so
a fresh clone / CI (which has no override) installs the pinned tag and a make
target seeds the override for local dev:
workspace:
@test -f pnpm-workspace.yaml || cp pnpm-workspace.yaml.example pnpm-workspace.yaml
The committed pnpm-lock.yaml pins the git version (authoritative). A local
install with the override rewrites it to the link: form — don't commit that
churn; have the dev container install with --no-lockfile so it doesn't write
the lockfile back through the bind mount.
3. nuxt.config — extend by REAL path
import { realpathSync } from 'fs'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const baseLayer = realpathSync(resolve(__dirname, 'node_modules/@redelay/js-admin'))
export default defineNuxtConfig({
extends: [baseLayer],
// …
})
Two subtleties this line handles — see the gotchas below.
4. Tailwind @source
Point the globs at the package under node_modules (works in both dev — a
symlink — and versioned/CI — the real package):
@source "../../node_modules/@redelay/js-admin/components/**/*";
@source "../../node_modules/@redelay/js-admin/layouts/**/*";
@source "../../node_modules/@redelay/js-admin/pages/**/*";
@source "../../node_modules/@redelay/js-admin/modules/**/*";
5. Docker (credential-free)
Mount the sibling checkout under /app mirroring the host layout so the
override's relative link: path resolves in-container, and install with
--no-lockfile:
admin:
image: node:22-alpine
working_dir: /app/<project>/admin
command: sh -c "corepack enable && (test -e node_modules/@redelay/js-admin || pnpm install --no-frozen-lockfile --no-lockfile) && pnpm dev --host 0.0.0.0 --port 3002"
volumes:
- ../../admin:/app/<project>/admin
- ../../../redelay/js-admin-nuxt4:/app/redelay/js-admin-nuxt4 # override link target
No GitHub token in the container, no committed secret. If a token is ever genuinely needed (e.g. a build that installs the pinned tag), put it in a gitignored env file the compose auto-loads — never a committed one.
The three gotchas (they broke us; they'll break you)
- Don't use a bare
extendsspecifier.extends: ['@redelay/js-admin']fails — Nuxt resolves a bare string through package-entry resolution, and this private, entry-point-less package errors withCannot extend config from @redelay/js-admin. Extend by the resolvednode_modulespath. realpathSyncis required. The dev entry is a pnpm symlink. Without resolving it, Nuxt registers the layer's~/@aliases against the symlink path while Vite loads the layer's files from their real path — so the layer's own~/config/...imports fail to resolve.- Tailwind
@sourcemust targetnode_modules/@redelay/js-admin, not the sibling tree — otherwise a versioned/CI build (no sibling checkout) has nothing to scan and the admin renders unstyled.
Bumping the base layer
- Tag the
redelay/js-admin-nuxt4repo (the packageversionlives inside the tagged commit, so a rename or change needs a new tag). - In the project admin:
pnpm add github:redelay/js-admin-nuxt4#<newtag>.
Local dev keeps using the linked checkout regardless; the bump only affects fresh clones / CI.