@sidebase/base-config
@sidebase/base-config
Shared setup for sidebase Nuxt 4 apps. @sidebase/streamctl
syncs this package's config files (CI, Dockerfile, tsconfig, lint config, editor settings, ...) into your
repo and keeps them up to date. The package also exports ESLint, Prisma and tsconfig helpers.
Quick start
Requires Nuxt 4, Node >= 24.21 and pnpm.
pnpm add -D @sidebase/streamctl
pnpm streamctl init --package @sidebase/base-config
init adds @sidebase/base-config, writes streamctl.config.ts, and runs the first sync.
Review the changes and commit them.
Coming from @sidestream-tech/nuxt-config? Follow docs/migration.md
instead.
What you get
| File | Managed as | What it is |
|---|---|---|
.github/workflows/ci.yaml |
owned | Lint, typecheck and build, plus optional test and e2e jobs |
.github/workflows/pr-preview-cleanup.yaml |
owned | Removes the preview-deployed label when preview is removed |
.github/workflows/streamctl-upgrade.yaml |
owned, opt-in | Weekly upgrade PRs (see Upgrades) |
Dockerfile |
owned | Production image, runs prisma migrate deploy and then the server |
tsconfig.json |
owned | Extends the shared strict base and Nuxt's generated config |
pnpm-workspace.yaml |
owned | 7-day release cooldown and the install-script allowlist |
.oxlintrc.json |
owned | oxlint rules, run before ESLint |
AGENTS.md |
owned | Conventions for coding agents |
.editorconfig, .gitignore, .dockerignore |
block | A marked block; the rest of the file is yours |
.vscode/settings.json, .vscode/extensions.json |
merged | Only the keys the payload sets; your other keys stay |
eslint.config.ts, prisma.config.ts |
scaffolded | Written once, then yours |
- owned: streamctl writes the whole file. Local edits show up as drift in
check. - block: only the region between the
BEGIN/END streamctl MANAGED BLOCKmarkers is managed.
sync also updates package.json:
engines,packageManager, and the version ranges of nuxt, typescript, eslint, oxlint, prisma,@prisma/client, jiti, json-sort-cli and vitest. A range is only raised, never lowered.scripts.postinstall(nuxt prepare) andscripts.lint(oxlint, then ESLint).
Everyday use
pnpm streamctl sync # apply the payload after changing the config
pnpm streamctl check # CI gate: fails when managed files drifted
pnpm streamctl upgrade # move to the latest payload version and re-sync
sync stops on conflicts, e.g. a file you edited by hand. Resolve them with
pnpm streamctl sync --interactive.
Upgrades
Upgrade by hand with pnpm streamctl upgrade, or let a scheduled workflow open the PRs:
automation: { upgradePr: true },
It runs Mondays at 06:00 UTC. Pick your own time with automation.schedule (a
five-field cron) and automation.scheduleTimezone (an IANA name):
automation: { upgradePr: true, schedule: "30 14 * * 5", scheduleTimezone: "Europe/Berlin" },
Day-of-month and day-of-week combine with OR: "0 16 1-7 * 5" is not "first Friday".
The workflow runs streamctl check, lint, typecheck, test and build before it opens the PR.
Files that need manual work end up in a draft PR with instructions. When a PR for a newer
version opens, older upgrade PRs are closed with a link to it.
By default it uses GITHUB_TOKEN, which may not change .github/workflows/**, so
workflow changes are left out of the PR. To include them, set
automation.allowWorkflowUpdates: true and add a STREAMCTL_PR_TOKEN secret (a
fine-grained PAT or GitHub App token with Contents, Pull requests and Workflows write).
That token also makes the PR trigger your normal CI.
Configuration
Everything is configured in streamctl.config.ts. defineNuxtBaseConfig gives you
completion and type checking for every option:
import { defineNuxtBaseConfig } from "@sidebase/base-config";
export default defineNuxtBaseConfig({
package: "@sidebase/base-config",
base: "nuxt-app",
version: "0.3.0",
profile: "nuxt-4",
ci: { unitTests: true, e2e: true },
docker: { aptPackages: ["ffmpeg"] },
automation: { upgradePr: true },
editor: { i18nSourceLanguage: "en" },
});
Options
| Option | Default | Effect |
|---|---|---|
ci.unitTests |
false |
Add a test job (pnpm test) |
ci.e2e |
false |
Add an e2e job with a postgres service |
ci.aptPackages |
[] |
apt packages for the test/e2e jobs and upgrade validation |
docker.aptPackages |
[] |
apt packages for the image, on top of openssl |
docker.preInstall |
"" |
Dockerfile lines before pnpm install |
docker.buildArgs |
"" |
Dockerfile lines before COPY . . |
docker.buildSteps |
nuxi prepare, prisma generate, build |
The build commands |
docker.finalStageSetup |
"" |
Dockerfile lines in the final stage, before the app is copied. Cached across app changes, so use it for installs that do not need the app |
docker.finalStage |
"" |
Dockerfile lines in the final stage, before CMD. The image runs as root unless this adds USER node |
docker.prismaRuntime |
copy schema, install Prisma CLI | Prisma setup in the final stage |
docker.prismaVersion |
your prisma version |
Prisma CLI version in the image |
docker.startCommand |
npm exec prisma migrate deploy && exec node ... |
The CMD, one line |
versions.node |
24.21.0 |
Node in CI, the Dockerfile and the upgrade workflow |
versions.pnpm |
10.34.5 |
pnpm in the Dockerfile (CI uses packageManager) |
automation.upgradePr |
false |
Add the scheduled upgrade workflow |
automation.allowWorkflowUpdates |
false |
Include workflow changes in upgrade PRs (needs a token) |
automation.prTokenSecret |
"STREAMCTL_PR_TOKEN" |
Secret name for that token |
automation.prepare |
pnpm prisma generate if Prisma is installed |
Command run before validating an upgrade; "" skips |
automation.schedule |
"0 6 * * 1" |
Cron for the upgrade workflow |
automation.scheduleTimezone |
"UTC" |
IANA time zone for that cron |
security.minimumReleaseAge |
"10080" |
Minutes a release must be public before pnpm installs it; "0" disables |
pnpm.onlyBuiltDependencies |
[] |
Extra packages allowed to run install scripts |
editor.i18nSourceLanguage |
"de" |
i18n-ally source language |
Things to know:
- A
docker.*text option replaces the default. It does not append, so abuildStepsoverride must list every build command you still need. docker.prismaRuntimeanddocker.startCommandgo together. Without Prisma, setprismaRuntime: ""and astartCommandwithoutmigrate deploy, e.g.exec node .output/server/index.mjs. Keep theexec, so node gets SIGTERM.- Repos with English source strings must set
editor.i18nSourceLanguage: "en". Otherwise every sync writes"de"back. - Adopting
pnpm-workspace.yamlreplaces your existing file, including anyonlyBuiltDependencies. Move those entries topnpm.onlyBuiltDependenciesinstead of runningpnpm approve-builds, which the next sync reverts.
Customizing managed files
Block files: put your own settings below the END marker. For .editorconfig,
later sections win, so an override below the block takes precedence. On the first sync,
the block is appended after your existing content, so move that content below the
block once.
Opt a file out when the options are not enough:
files: { "Dockerfile": "off" },
streamctl then leaves the file alone, including all future fixes to it.
Keep your own scripts.lint (or any other synced package.json field):
versionSyncExclude: ["scripts.lint"],
Helpers
ESLint
// eslint.config.ts
import { createSidebaseEslint } from "@sidebase/base-config/eslint";
export default createSidebaseEslint({ zod: "full", trpcGuard: true })
.append({ rules: { "vue/multi-word-component-names": "off" } });
Wraps @antfu/eslint-config, which is a
dependency of this package; don't install it yourself. Needs eslint and jiti (for the
TypeScript config file) in your devDependencies; sync adds both.
| Option | Default | Effect |
|---|---|---|
zod |
"none" |
"import-style": enforce import * as z. "full": also ban .extend(), .merge(), .passthrough() |
console |
"error" |
Ban all console.*, or pass allowed methods, e.g. ["warn", "error"] |
trpcGuard |
false |
Ban publicProcedure in server/trpc/routers/** |
prismaImportGuard |
false |
Ban ~~/prisma/ client imports outside server code |
typeDefStyle |
"interface" |
Prefer interface or type |
autoImportPaths |
["utils/", "composables/", "~~/shared/types/"] |
Paths Nuxt auto-imports, so direct imports are banned in app code. The defaults cover app/utils, shared/utils, app/composables and shared/types under every Nuxt alias. Files in the root (or layers/*) server/ and shared/ dirs and relative imports are never matched |
autoImportTypeOnly |
[] |
Of those, paths where import type stays allowed |
ignoresTypeAware |
config files | Files excluded from type-aware linting |
testFilePattern |
*.test, *.spec, *.stories |
Files where the process.env and auto-import bans are off |
Type-aware linting is off by default; run with LINT_TYPEAWARE=true to enable it.
To silence a single line, use the rule ID:
| Ban | Rule ID |
|---|---|
process.env.X |
no-restricted-properties |
import { env } from "node:process" and similar |
ts/no-restricted-imports |
| Auto-import paths, app-side Prisma client | ts/no-restricted-imports |
Zod import style and methods, publicProcedure |
no-restricted-syntax |
Prisma
// prisma.config.ts
import "dotenv/config";
import { applyPrismaDevEnv, buildPrismaConfig } from "@sidebase/base-config/prisma";
import { defineConfig } from "prisma/config";
applyPrismaDevEnv(); // optional: localhost defaults for unset DB URLs
export default defineConfig(buildPrismaConfig({ views: true, typedSql: true }));
buildPrismaConfigreadsDATABASE_URL,DIRECT_DATABASE_URLandSHADOW_DATABASE_URL. Migrations useDIRECT_DATABASE_URL, orDATABASE_URLwith the pooler parameters removed.viewsandtypedSqlenable those Prisma features.applyPrismaDevEnvsets localhost defaults for the URLs that are unset, soprisma migrate devworks on a fresh checkout without a.env.
Prisma 6.19 only; prisma and @prisma/client are optional peer dependencies.
tsconfig
The managed tsconfig.json extends @sidebase/base-config/tsconfig.base, the shared
strict config, before Nuxt's generated one. You can extend it directly as well.
Troubleshooting
No version profile could be detected: the repo is not on Nuxt 4. Nuxt 3 is not supported.pnpm approve-buildschanges disappear: usepnpm.onlyBuiltDependencies..editorconfigsettings ignored after the first sync: your old content sits above the managed block. Move it below.eslint.config.tsfails to load: addjitito devDependencies.- Repo with private npm dependencies: the upgrade workflow installs without registry credentials. Opt it out and use your own.
Developing this package
pnpm lint && pnpm typecheck && pnpm test
pnpm validate:presets # manifests and cross-file invariants
pnpm e2e:dry-run # pack, install into a scratch consumer, sync and check
pnpm e2e:docker # build and start the rendered Dockerfile (needs Docker)
- The payload lives in
presets/(base, andnuxt-appon top of it). The manifests inpreset.jsondefine files, options and version baselines. - Tests check contracts derived from the manifests, so changing a preset file rarely needs a test change.
- Actions in managed workflows are pinned by commit SHA; the tests enforce it.
- Requires streamctl >= 0.4.0 (manifest schema version 4).
- Releases:
docs/release.md. Agent conventions:AGENTS.md.
Related Packages
The productive way to build fullstack Nuxt 3 applications, like create-t3-app bu...
ESLint plugin with rules that help validate proper imports.
This ESLint plugin enforces adding `.value` when accessing reactive values in th...
Version History
| Version | Released | License |
|---|---|---|
| 0.4.0 | MIT | |
| 0.3.7 | MIT | |
| 0.3.6 | MIT | |
| 0.3.5 | MIT | |
| 0.3.4 | MIT | |
| 0.3.3 | MIT | |
| 0.3.2 | MIT | |
| 0.3.1 | MIT | |
| 0.3.0 | MIT | |
| 0.2.2 | MIT | |
| 0.2.1 | MIT | |
| 0.2.0 | MIT | |
| 0.1.0 | MIT |