From 8ddd0aace45b6a133e9f27de74b39ab5edb188e3 Mon Sep 17 00:00:00 2001 From: Christian Stroet Date: Wed, 14 Oct 2026 14:33:42 +0200 Subject: [PATCH 1/6] fix(ci): install the lefthook git hooks from a prepare script Generated projects shipped lefthook.yml but never had .git/hooks/pre-commit: lefthook's own postinstall runs during the scaffold install, before the project has a .git. A `prepare: lefthook install || exit 0` script now runs on every real install; `|| exit 0` keeps the Docker build (no .git) and installs without dev deps from failing. A project's own prepare script is left alone with a note. pull's install takes pnpm's "Already up to date" fast path, which skips prepare, so the ci update() runs it once itself (shared:ci 1.1.2). --- CHANGELOG.md | 2 ++ packages/preset-nuxt4/src/features/ci.ts | 29 ++++++++++++++++++++++-- packages/preset-nuxt4/test/ci.test.ts | 29 ++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b132b8a2..16adfd3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ - Pin generated projects to TypeScript 6: `vue-tsc` and typescript-eslint do not support TypeScript 7 yet. A feature's explicit dependency range now also wins over a peer pin that `nuxi module add` wrote into the other bucket. +- Install the lefthook git hooks: generated projects get a + `prepare: lefthook install || exit 0` script, and `pull` runs it once. ## v1.3.0 (2026-09-24) diff --git a/packages/preset-nuxt4/src/features/ci.ts b/packages/preset-nuxt4/src/features/ci.ts index 0bf57550..de97dd9d 100644 --- a/packages/preset-nuxt4/src/features/ci.ts +++ b/packages/preset-nuxt4/src/features/ci.ts @@ -7,17 +7,23 @@ import { type RunContext, type PackageManager, } from '@battlestack/core' -import { writeFileEnsured } from '@battlestack/core/utils/fs.js' +import { readJson, writeFileEnsured, writeJson } from '@battlestack/core/utils/fs.js' +import { run } from '@battlestack/core/utils/run.js' import { templatesDir } from '@battlestack/core/utils/templates.js' import { applyVars, renderPmVars } from '../utils/pm-template.js' import { STAGE } from '@battlestack/core/constants/stages.js' const FILES = ['lefthook.yml'] as const +// lefthook's own postinstall runs at scaffold time, before the project has a `.git`. +// `|| exit 0` keeps installs without a `.git` (Docker) or without dev deps from failing. +const PREPARE = 'lefthook install || exit 0' +const PREPARE_TAKEN = 'package.json has its own `prepare` script, so `lefthook install` was not added; make sure it runs it.' + /** Lefthook git hooks: pre-commit eslint. Typecheck runs in CI, not on push. */ export const ciFeature: Feature = { id: 'shared:ci', - version: '1.1.1', + version: '1.1.2', label: 'Git hooks (lefthook)', description: 'Pre-commit eslint on staged files via lefthook.', stage: STAGE.GITIGNORE, @@ -43,10 +49,17 @@ export const ciFeature: Feature = { async execute(ctx) { await emit(ctx) + await wirePrepare(ctx) }, async update(ctx, _prev) { await emit(ctx) + if (!await wirePrepare(ctx)) { + return { written: [...FILES], skipped: [], notes: [PREPARE_TAKEN] } + } + // pull's install takes pnpm's up-to-date fast path, which skips `prepare`. + const pm = String(ctx.state.packageManager ?? 'pnpm') as PackageManager + await run(pm, ['run', 'prepare'], { cwd: ctx.projectDir, inherit: ctx.debug }).catch(() => {}) return { written: [...FILES], skipped: [], notes: [] } }, } @@ -64,3 +77,15 @@ async function emit(ctx: RunContext): Promise { recordFile(ctx, 'shared:ci', rel, await hashFile(dest)) } } + +/** Adds the `prepare` hook installer. False when the project's own `prepare` is in the way. */ +async function wirePrepare(ctx: RunContext): Promise { + const pkgPath = path.join(ctx.projectDir, 'package.json') + const pkg = await readJson>(pkgPath) + const scripts = (pkg.scripts as Record | undefined) ?? {} + if (scripts.prepare) return scripts.prepare === PREPARE + scripts.prepare = PREPARE + pkg.scripts = scripts + await writeJson(pkgPath, pkg) + return true +} diff --git a/packages/preset-nuxt4/test/ci.test.ts b/packages/preset-nuxt4/test/ci.test.ts index 92599f09..077792d1 100644 --- a/packages/preset-nuxt4/test/ci.test.ts +++ b/packages/preset-nuxt4/test/ci.test.ts @@ -1,18 +1,23 @@ -import { mkdtemp, readFile, rm } from 'node:fs/promises' +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import os from 'node:os' import path from 'node:path' -import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { run } from '@battlestack/core/utils/run.js' import { ciFeature } from '../src/features/ci.js' import type { PackageManager } from '@battlestack/core' import { mockRunContext } from './test-utils.js' +vi.mock('@battlestack/core/utils/run.js', () => ({ run: vi.fn(async () => ({ stdout: '', stderr: '' })) })) + let projectDir: string beforeEach(async () => { projectDir = await mkdtemp(path.join(os.tmpdir(), 'battlestack-ci-test-')) + await writeFile(path.join(projectDir, 'package.json'), '{}') }) afterEach(async () => { + vi.mocked(run).mockClear() await rm(projectDir, { recursive: true, force: true }) }) @@ -49,6 +54,26 @@ describe('ciFeature', () => { expect(content).toContain('npx') }) + it('installs the hooks from a prepare script', async () => { + await ciFeature.execute(ctx()) + const pkg = JSON.parse(await readFile(path.join(projectDir, 'package.json'), 'utf8')) + expect(pkg.scripts.prepare).toBe('lefthook install || exit 0') + }) + + it('keeps an existing prepare script and notes the missing hook install', async () => { + await writeFile(path.join(projectDir, 'package.json'), JSON.stringify({ scripts: { prepare: 'husky' } })) + const report = await ciFeature.update!(ctx(), null) + const pkg = JSON.parse(await readFile(path.join(projectDir, 'package.json'), 'utf8')) + expect(pkg.scripts.prepare).toBe('husky') + expect(report.notes[0]).toContain('lefthook install') + expect(run).not.toHaveBeenCalled() + }) + + it('update() runs prepare, which pull\'s up-to-date install skips', async () => { + await ciFeature.update!(ctx(), null) + expect(run).toHaveBeenCalledWith('pnpm', ['run', 'prepare'], expect.objectContaining({ cwd: projectDir })) + }) + it('update() reports lefthook.yml as written', async () => { const report = await ciFeature.update!(ctx(), null) expect(report.written).toEqual(['lefthook.yml']) From 27d1e7d74ebd45c5a5f267a74a2de2f6eef34ec7 Mon Sep 17 00:00:00 2001 From: Christian Stroet Date: Wed, 14 Oct 2026 14:33:42 +0200 Subject: [PATCH 2/6] fix(nuxt4): format templates with the generated project's ESLint config The scaffold's trailing `eslint --fix` hid that 49 template files broke the project's own style (operator-linebreak, member-delimiter-style, max-attributes-per-line, ...), so CI's scaffold lint stayed green. pull merges the raw template into the user's formatted copy, which turned every such difference into a conflict; odu-zzs had switched those rules off to cope. Templates are now `eslint --fix`ed with that config, and CI lints the raw templates inside the scaffolded project so this cannot regress. Patch bumps for every feature whose templates changed. --- .github/workflows/ci.yml | 11 + CHANGELOG.md | 3 + .../preset-nuxt4/src/features/audit-log.ts | 2 +- .../preset-nuxt4/src/features/auth-2fa.ts | 2 +- .../src/features/auth-recovery.ts | 2 +- .../src/features/auth-verification.ts | 2 +- packages/preset-nuxt4/src/features/auth.ts | 2 +- .../preset-nuxt4/src/features/build-info.ts | 2 +- packages/preset-nuxt4/src/features/chat.ts | 2 +- .../src/features/dashboard-shell.ts | 2 +- .../preset-nuxt4/src/features/database.ts | 2 +- .../preset-nuxt4/src/features/essentials.ts | 2 +- packages/preset-nuxt4/src/features/health.ts | 2 +- packages/preset-nuxt4/src/features/i18n.ts | 2 +- .../src/features/landing-shell.ts | 2 +- packages/preset-nuxt4/src/features/mastra.ts | 2 +- packages/preset-nuxt4/src/features/nuxt-ui.ts | 2 +- packages/preset-nuxt4/src/features/prompts.ts | 2 +- packages/preset-nuxt4/src/features/rag.ts | 2 +- packages/preset-nuxt4/src/features/storage.ts | 2 +- .../preset-nuxt4/src/features/user-admin.ts | 2 +- packages/preset-nuxt4/src/features/vitest.ts | 2 +- .../templates/audit-log/server/utils/audit.ts | 56 ++--- .../auth-2fa/app/composables/use2fa.ts | 12 +- .../auth/2fa/backup-codes/generate.post.ts | 2 +- .../server/api/auth/2fa/verify.post.ts | 2 +- .../templates/auth-2fa/server/utils/totp.ts | 4 +- .../auth-2fa/test/e2e/api/auth-2fa.test.ts | 2 +- .../app/pages/forgot-password.vue | 35 ++- .../app/pages/reset-password.vue | 30 ++- .../app/pages/verify-email.vue | 9 +- .../auth/app/pages/auth/magic-login.vue | 34 ++- .../templates/auth/app/pages/login.vue | 66 ++++-- .../templates/auth/app/pages/signup.vue | 40 +++- .../templates/auth/app/utils/server-error.ts | 2 +- .../auth/server/api/auth/magic-login.post.ts | 2 +- .../auth/server/utils/email-templates.ts | 4 +- .../auth/shared/utils/password-policy.ts | 6 +- .../app/components/AppBuildInfo.vue | 6 +- .../chat/http/app/components/Chat.vue | 18 +- .../chat/ws-nitro/app/components/Chat.vue | 13 +- .../app/pages/dashboard/index.vue | 25 +- .../app/pages/dashboard/profile.vue | 49 +++- .../app/pages/dashboard/security.vue | 220 ++++++++++++------ .../server/database/seeds/001-admin.ts | 6 +- .../database/server/database/seeds/runner.ts | 4 +- .../server/plugins/00-db-migrate-on-boot.ts | 2 +- .../templates/database/tools/seed.mjs | 10 +- .../app/components/ExampleCalendar.vue | 7 +- .../test/e2e/api/health-split.test.ts | 2 +- .../env-only/test/e2e/api/health.test.ts | 2 +- .../health/with-db/server/api/health.get.ts | 2 +- .../with-db/test/e2e/api/health-split.test.ts | 4 +- .../with-db/test/e2e/api/health.test.ts | 4 +- .../modules/i18n-auto-namespaces/index.ts | 4 +- .../landing-shell/app/layouts/default.vue | 23 +- .../landing-shell/app/pages/index.vue | 4 +- .../app/pages/dashboard/settings/ai.vue | 52 +++-- .../mastra/server/api/ai/models.get.ts | 14 +- .../server/mastra/gateways/openai-compat.ts | 52 ++--- .../mastra/server/mastra/utils/ai-model.ts | 1 - .../templates/nuxt-ui/app/pages/index.vue | 4 +- .../prompts/app/pages/dashboard/prompts.vue | 74 ++++-- .../prompts/test/e2e/api/prompts.test.ts | 6 +- .../templates/rag/app/pages/dashboard/rag.vue | 67 ++++-- .../storage/app/components/FileUpload.vue | 24 +- .../storage/app/pages/dashboard/profile.vue | 49 +++- .../app/pages/dashboard/users/[id].vue | 81 +++++-- .../app/pages/dashboard/users/index.vue | 95 ++++++-- .../vitest/test/e2e/api/auth.test.ts | 4 +- .../templates/vitest/test/helpers/setup.ts | 2 +- 71 files changed, 923 insertions(+), 367 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2edd928c..34620b14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,6 +156,17 @@ jobs: pnpm exec nuxi prepare pnpm run lint + # The scaffold's own `eslint --fix` pass hides a template that breaks the + # project's style, but `pull` merges the raw template into the user's + # formatted copy, so every such difference becomes a conflict. Pages and + # layouts sit outside the paths that exempt them from multi-word names here. + - name: Lint the raw templates with the project's config + working-directory: ${{ runner.temp }}/scaffold/ci-app + run: | + cp -r "$GITHUB_WORKSPACE/packages/preset-nuxt4/templates" __templates + pnpm exec eslint --rule '{"vue/multi-word-component-names":"off"}' __templates + rm -rf __templates + - name: Typecheck working-directory: ${{ runner.temp }}/scaffold/ci-app run: pnpm run typecheck diff --git a/CHANGELOG.md b/CHANGELOG.md index 16adfd3a..ba140725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ over a peer pin that `nuxi module add` wrote into the other bucket. - Install the lefthook git hooks: generated projects get a `prepare: lefthook install || exit 0` script, and `pull` runs it once. +- Format the template files with the generated project's own ESLint config, so + `pull` no longer turns style differences into merge conflicts. CI now lints + the raw templates with that config. ## v1.3.0 (2026-09-24) diff --git a/packages/preset-nuxt4/src/features/audit-log.ts b/packages/preset-nuxt4/src/features/audit-log.ts index 68766147..651a4cb0 100644 --- a/packages/preset-nuxt4/src/features/audit-log.ts +++ b/packages/preset-nuxt4/src/features/audit-log.ts @@ -6,7 +6,7 @@ import { STAGE } from '@battlestack/core' /** Append-only audit trail. Best-effort on insert. */ export const auditLogFeature: Feature = { id: 'nuxt4:audit-log', - version: '1.0.1', + version: '1.0.2', label: 'Audit log (security events)', description: 'Append-only trail of login, signup, role changes, and passkey events.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/auth-2fa.ts b/packages/preset-nuxt4/src/features/auth-2fa.ts index ca28bd2a..0c69ce02 100644 --- a/packages/preset-nuxt4/src/features/auth-2fa.ts +++ b/packages/preset-nuxt4/src/features/auth-2fa.ts @@ -7,7 +7,7 @@ import { STAGE } from '@battlestack/core' /** TOTP-based two-factor auth. Secrets encrypted at rest with AES-256-GCM via `NUXT_TOTP_ENCRYPTION_KEY`. */ export const auth2faFeature: Feature = { id: 'nuxt4:auth-2fa', - version: '1.2.2', + version: '1.2.3', label: 'Two-factor auth (TOTP)', description: 'Authenticator-app 2FA; TOTP secrets encrypted at rest.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/auth-recovery.ts b/packages/preset-nuxt4/src/features/auth-recovery.ts index 747ca365..e9ea9ba8 100644 --- a/packages/preset-nuxt4/src/features/auth-recovery.ts +++ b/packages/preset-nuxt4/src/features/auth-recovery.ts @@ -6,7 +6,7 @@ import { STAGE } from '@battlestack/core' /** Forgot-password and reset-password. Tokens sha256-hashed at rest. */ export const authRecoveryFeature: Feature = { id: 'nuxt4:auth-recovery', - version: '1.1.2', + version: '1.1.3', label: 'Password recovery', description: 'Forgot-password and reset flows with one-time, hashed tokens.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/auth-verification.ts b/packages/preset-nuxt4/src/features/auth-verification.ts index 0c42a4fb..5f9588c2 100644 --- a/packages/preset-nuxt4/src/features/auth-verification.ts +++ b/packages/preset-nuxt4/src/features/auth-verification.ts @@ -7,7 +7,7 @@ import { STAGE } from '@battlestack/core' /** Email verification: a signup token verified via a one-time link, optionally blocking login. */ export const authVerificationFeature: Feature = { id: 'nuxt4:auth-verification', - version: '1.0.2', + version: '1.0.3', label: 'Email verification', frameworks: ['nuxt4'], stage: STAGE.AUTH_EXTRAS, diff --git a/packages/preset-nuxt4/src/features/auth.ts b/packages/preset-nuxt4/src/features/auth.ts index 91f5de50..558d75f3 100644 --- a/packages/preset-nuxt4/src/features/auth.ts +++ b/packages/preset-nuxt4/src/features/auth.ts @@ -30,7 +30,7 @@ function titleCase(slug: string): string { /** Session auth on `nuxt-auth-utils`. The sealed cookie carries an opaque sessionId. */ export const authFeature: Feature = { id: 'nuxt4:auth', - version: '1.10.1', + version: '1.10.2', label: 'Session-based auth (argon2id)', frameworks: ['nuxt4'], stage: STAGE.AUTH, diff --git a/packages/preset-nuxt4/src/features/build-info.ts b/packages/preset-nuxt4/src/features/build-info.ts index 96d7e673..14a5482b 100644 --- a/packages/preset-nuxt4/src/features/build-info.ts +++ b/packages/preset-nuxt4/src/features/build-info.ts @@ -12,7 +12,7 @@ import { patchNuxtConfig } from '../utils/nuxt-config.js' */ export const buildInfoFeature: Feature = { id: 'nuxt4:build-info', - version: '1.0.0', + version: '1.0.1', label: 'Build info (version + commit in the footer)', description: 'Version and commit sha in the app footer, from the image\'s build args.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/chat.ts b/packages/preset-nuxt4/src/features/chat.ts index 92cb27af..43d34a1c 100644 --- a/packages/preset-nuxt4/src/features/chat.ts +++ b/packages/preset-nuxt4/src/features/chat.ts @@ -5,7 +5,7 @@ import { saveFeatureState, STAGE, type Feature, type ChatTransport } from '@batt /** Streaming chat on Mastra's default agent. Transport from `state.chatTransport`. */ export const chatFeature: Feature = { id: 'nuxt4:chat', - version: '1.3.0', + version: '1.3.1', label: 'Streaming chat (Mastra)', description: 'WebSocket streaming chat UI backed by the Mastra default agent.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/dashboard-shell.ts b/packages/preset-nuxt4/src/features/dashboard-shell.ts index 482c80af..557684e4 100644 --- a/packages/preset-nuxt4/src/features/dashboard-shell.ts +++ b/packages/preset-nuxt4/src/features/dashboard-shell.ts @@ -5,7 +5,7 @@ import { patchNuxtConfig } from '../utils/nuxt-config.js' /** Authenticated app shell under `/dashboard/*`. */ export const dashboardShellFeature: Feature = { id: 'nuxt4:dashboard-shell', - version: '1.3.1', + version: '1.3.2', label: 'Authenticated app shell (/dashboard/*)', frameworks: ['nuxt4'], stage: STAGE.AUTH, diff --git a/packages/preset-nuxt4/src/features/database.ts b/packages/preset-nuxt4/src/features/database.ts index a7c1c35f..cb8be16d 100644 --- a/packages/preset-nuxt4/src/features/database.ts +++ b/packages/preset-nuxt4/src/features/database.ts @@ -251,7 +251,7 @@ async function registerRuntimeConfig(projectDir: string): Promise { /** PostgreSQL 18 in Docker, Drizzle ORM, users schema, seed script. */ export const databaseFeature: Feature = { id: 'nuxt4:database', - version: '1.7.1', + version: '1.7.2', label: 'PostgreSQL + Drizzle ORM (Docker)', frameworks: ['nuxt4'], stage: STAGE.DATABASE, diff --git a/packages/preset-nuxt4/src/features/essentials.ts b/packages/preset-nuxt4/src/features/essentials.ts index a0f7f14d..a614a0fd 100644 --- a/packages/preset-nuxt4/src/features/essentials.ts +++ b/packages/preset-nuxt4/src/features/essentials.ts @@ -16,7 +16,7 @@ const ICON_FILES = [ /** Always-on Nuxt essentials: `@nuxt/eslint`, `@nuxt/fonts`, `@nuxt/image`, datepicker, `@iconify/vue`. */ export const essentialsFeature: Feature = { id: 'nuxt4:essentials', - version: '1.2.1', + version: '1.2.2', label: 'Nuxt essentials (eslint, fonts, image, iconify, datepicker, nodemailer)', frameworks: ['nuxt4'], stage: STAGE.BASE_CONFIG, diff --git a/packages/preset-nuxt4/src/features/health.ts b/packages/preset-nuxt4/src/features/health.ts index c602572a..d5406e9d 100644 --- a/packages/preset-nuxt4/src/features/health.ts +++ b/packages/preset-nuxt4/src/features/health.ts @@ -5,7 +5,7 @@ import { patchNuxtConfig } from '../utils/nuxt-config.js' /** `/api/health` + probe split (`/live`, `/ready`) + runtimeConfig knobs. Picks `with-db` or `env-only` variant at scaffold time. */ export const healthFeature: Feature = { id: 'nuxt4:health', - version: '1.5.0', + version: '1.5.1', label: 'Health endpoints (/api/health, /live, /ready)', frameworks: ['nuxt4'], stage: STAGE.BASE_CONFIG, diff --git a/packages/preset-nuxt4/src/features/i18n.ts b/packages/preset-nuxt4/src/features/i18n.ts index b80d889e..3274c93a 100644 --- a/packages/preset-nuxt4/src/features/i18n.ts +++ b/packages/preset-nuxt4/src/features/i18n.ts @@ -6,7 +6,7 @@ import { patchNuxtConfig } from '../utils/nuxt-config.js' /** @nuxtjs/i18n with per-locale namespace JSONs under `i18n/locales//.json`. */ export const i18nFeature: Feature = { id: 'nuxt4:i18n', - version: '1.1.1', + version: '1.1.2', label: 'i18n (Dutch + English)', frameworks: ['nuxt4'], stage: STAGE.I18N, diff --git a/packages/preset-nuxt4/src/features/landing-shell.ts b/packages/preset-nuxt4/src/features/landing-shell.ts index a190345d..86f6eb65 100644 --- a/packages/preset-nuxt4/src/features/landing-shell.ts +++ b/packages/preset-nuxt4/src/features/landing-shell.ts @@ -7,7 +7,7 @@ import { applyColorAliases } from './battlestack-theme.js' /** Frontend shell: `app.vue`, `app.config.ts`, layouts, landing page. All emitted files are structural. */ export const landingShellFeature: Feature = { id: 'nuxt4:landing-shell', - version: '1.4.0', + version: '1.4.1', label: 'Landing shell (layouts, public landing page)', description: 'Public landing page, layouts, and frontend app shell.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/mastra.ts b/packages/preset-nuxt4/src/features/mastra.ts index 687fa03d..b105ccae 100644 --- a/packages/preset-nuxt4/src/features/mastra.ts +++ b/packages/preset-nuxt4/src/features/mastra.ts @@ -29,7 +29,7 @@ import { /** Mastra AI runtime. Talks to an OpenAI-compatible AI gateway (sluis.ai preset, or any compatible URL) via `@ai-sdk/openai-compatible`. */ export const mastraFeature: Feature = { id: 'nuxt4:mastra', - version: '2.1.3', + version: '2.1.4', label: 'Mastra AI runtime', frameworks: ['nuxt4'], stage: STAGE.AI_CORE, diff --git a/packages/preset-nuxt4/src/features/nuxt-ui.ts b/packages/preset-nuxt4/src/features/nuxt-ui.ts index c16cc79a..b18e96af 100644 --- a/packages/preset-nuxt4/src/features/nuxt-ui.ts +++ b/packages/preset-nuxt4/src/features/nuxt-ui.ts @@ -6,7 +6,7 @@ import { patchNuxtConfig } from '../utils/nuxt-config.js' /** Nuxt UI v4 + Tailwind v4. Registers `@nuxt/ui` module and wires `assets/css/main.css`. */ export const nuxtUiFeature: Feature = { id: 'nuxt4:nuxt-ui', - version: '1.2.2', + version: '1.2.3', label: 'Nuxt UI v4 + Tailwind v4', frameworks: ['nuxt4'], stage: STAGE.STYLING, diff --git a/packages/preset-nuxt4/src/features/prompts.ts b/packages/preset-nuxt4/src/features/prompts.ts index 6ea57614..4b10e616 100644 --- a/packages/preset-nuxt4/src/features/prompts.ts +++ b/packages/preset-nuxt4/src/features/prompts.ts @@ -6,7 +6,7 @@ import type { Feature } from '@battlestack/core' /** Admin-editable AI agent prompts. */ export const promptsFeature: Feature = { id: 'nuxt4:prompts', - version: '1.2.1', + version: '1.2.2', label: 'AI prompt management', description: 'Admin-editable agent prompts with shipped registry defaults.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/rag.ts b/packages/preset-nuxt4/src/features/rag.ts index 51a73c1f..05a66c8d 100644 --- a/packages/preset-nuxt4/src/features/rag.ts +++ b/packages/preset-nuxt4/src/features/rag.ts @@ -32,7 +32,7 @@ function defaultDimensionsFor(model: string | undefined): number { /** RAG on Mastra: chunk → embedMany → PgVector.upsert/query. pgvector ships with `nuxt4:database`. */ export const ragFeature: Feature = { id: 'nuxt4:rag', - version: '1.4.1', + version: '1.4.2', label: 'RAG (Mastra + pgvector)', description: 'Ingest, chunk, embed, and query documents with Mastra + pgvector.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/storage.ts b/packages/preset-nuxt4/src/features/storage.ts index 417264ca..3502730d 100644 --- a/packages/preset-nuxt4/src/features/storage.ts +++ b/packages/preset-nuxt4/src/features/storage.ts @@ -10,7 +10,7 @@ import { allocatePort, STAGE, type EnvVar, type Feature } from '@battlestack/cor */ export const storageFeature: Feature = { id: 'nuxt4:storage', - version: '1.3.0', + version: '1.3.1', label: 'Object storage (RustFS dev / S3 prod)', description: 'File uploads and presigned downloads; RustFS locally, S3-compatible provider in prod.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/user-admin.ts b/packages/preset-nuxt4/src/features/user-admin.ts index 4585ae12..b9ba7405 100644 --- a/packages/preset-nuxt4/src/features/user-admin.ts +++ b/packages/preset-nuxt4/src/features/user-admin.ts @@ -6,7 +6,7 @@ import { STAGE } from '@battlestack/core' /** Admin-gated user CRUD + admin pages + admin route middleware. */ export const userAdminFeature: Feature = { id: 'nuxt4:user-admin', - version: '1.0.6', + version: '1.0.7', label: 'User administration (admin-gated CRUD)', description: 'Admin-only /dashboard/users for listing, creating, editing, and deleting users.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/vitest.ts b/packages/preset-nuxt4/src/features/vitest.ts index 48da1742..7b89dff1 100644 --- a/packages/preset-nuxt4/src/features/vitest.ts +++ b/packages/preset-nuxt4/src/features/vitest.ts @@ -19,7 +19,7 @@ import { ui } from '@battlestack/tui' /** Vitest config, a sample unit test and `test` scripts. Three projects: unit, nuxt, e2e. */ export const vitestFeature: Feature = { id: 'nuxt4:vitest', - version: '1.0.7', + version: '1.0.8', label: 'Vitest config + scripts', frameworks: ['nuxt4'], stage: STAGE.NAMING, // run after naming so package.json exists diff --git a/packages/preset-nuxt4/templates/audit-log/server/utils/audit.ts b/packages/preset-nuxt4/templates/audit-log/server/utils/audit.ts index 9afdecbc..2d735c3d 100644 --- a/packages/preset-nuxt4/templates/audit-log/server/utils/audit.ts +++ b/packages/preset-nuxt4/templates/audit-log/server/utils/audit.ts @@ -3,34 +3,34 @@ import { getRequestIP, getRequestHeader } from 'h3' import { db } from '#server/database/client' import { auditEvents } from '#server/database/schema/audit-log' -export type AuditAction = - | 'user.login.success' - | 'user.login.fail' - | 'user.logout' - | 'user.signup' - | 'user.deleted' - | 'user.role.changed' - | 'user.email.changed' - | 'user.password.changed' - | 'user.email.verified' - | 'user.email.verification.resent' - | 'user.password.reset.requested' - | 'user.password.reset.completed' - | 'user.totp.enabled' - | 'user.totp.disabled' - | 'user.backup-codes.generated' - | 'user.backup-codes.redeemed' - | 'user.backup-codes.redeem-failed' - | 'user.passkey.registered' - | 'user.passkey.removed' - | 'user.passkey.login' - | 'user.session.revoked' - | 'auth.oauth.signin' - | 'prompt.updated' - | 'prompt.reset' - | 'ai.model-config.updated' - | 'file.uploaded' - | 'file.deleted' +export type AuditAction + = | 'user.login.success' + | 'user.login.fail' + | 'user.logout' + | 'user.signup' + | 'user.deleted' + | 'user.role.changed' + | 'user.email.changed' + | 'user.password.changed' + | 'user.email.verified' + | 'user.email.verification.resent' + | 'user.password.reset.requested' + | 'user.password.reset.completed' + | 'user.totp.enabled' + | 'user.totp.disabled' + | 'user.backup-codes.generated' + | 'user.backup-codes.redeemed' + | 'user.backup-codes.redeem-failed' + | 'user.passkey.registered' + | 'user.passkey.removed' + | 'user.passkey.login' + | 'user.session.revoked' + | 'auth.oauth.signin' + | 'prompt.updated' + | 'prompt.reset' + | 'ai.model-config.updated' + | 'file.uploaded' + | 'file.deleted' /** Best-effort audit insert; logs to console on failure. */ export async function logAuditEvent( diff --git a/packages/preset-nuxt4/templates/auth-2fa/app/composables/use2fa.ts b/packages/preset-nuxt4/templates/auth-2fa/app/composables/use2fa.ts index 3e8195a1..5e4f5b64 100644 --- a/packages/preset-nuxt4/templates/auth-2fa/app/composables/use2fa.ts +++ b/packages/preset-nuxt4/templates/auth-2fa/app/composables/use2fa.ts @@ -1,16 +1,16 @@ /** Client wrapper around the 2FA endpoints. */ export function use2fa() { - async function status(): Promise<{ enabled: boolean; enabledAt: string | null }> { - return $fetch<{ enabled: boolean; enabledAt: string | null }>('/api/auth/2fa/status') + async function status(): Promise<{ enabled: boolean, enabledAt: string | null }> { + return $fetch<{ enabled: boolean, enabledAt: string | null }>('/api/auth/2fa/status') } - async function setup(): Promise<{ secret: string; otpauthUrl: string }> { - return $fetch<{ secret: string; otpauthUrl: string }>('/api/auth/2fa/setup', { method: 'POST' }) + async function setup(): Promise<{ secret: string, otpauthUrl: string }> { + return $fetch<{ secret: string, otpauthUrl: string }>('/api/auth/2fa/setup', { method: 'POST' }) } // Enabling 2FA also returns the freshly-generated backup codes (shown once). - async function verify(code: string): Promise<{ ok: true; backupCodes: string[] }> { - return $fetch<{ ok: true; backupCodes: string[] }>('/api/auth/2fa/verify', { method: 'POST', body: { code } }) + async function verify(code: string): Promise<{ ok: true, backupCodes: string[] }> { + return $fetch<{ ok: true, backupCodes: string[] }>('/api/auth/2fa/verify', { method: 'POST', body: { code } }) } async function disable(code: string): Promise<{ ok: true }> { diff --git a/packages/preset-nuxt4/templates/auth-2fa/server/api/auth/2fa/backup-codes/generate.post.ts b/packages/preset-nuxt4/templates/auth-2fa/server/api/auth/2fa/backup-codes/generate.post.ts index 874f75ca..7e9d42ed 100644 --- a/packages/preset-nuxt4/templates/auth-2fa/server/api/auth/2fa/backup-codes/generate.post.ts +++ b/packages/preset-nuxt4/templates/auth-2fa/server/api/auth/2fa/backup-codes/generate.post.ts @@ -35,7 +35,7 @@ export default defineEventHandler(async (event) => { setResponseHeaders(event, { 'Cache-Control': 'no-store, no-cache, must-revalidate', - Pragma: 'no-cache', + 'Pragma': 'no-cache', }) return { codes } }) diff --git a/packages/preset-nuxt4/templates/auth-2fa/server/api/auth/2fa/verify.post.ts b/packages/preset-nuxt4/templates/auth-2fa/server/api/auth/2fa/verify.post.ts index 97f10dd2..aa64e7d7 100644 --- a/packages/preset-nuxt4/templates/auth-2fa/server/api/auth/2fa/verify.post.ts +++ b/packages/preset-nuxt4/templates/auth-2fa/server/api/auth/2fa/verify.post.ts @@ -57,7 +57,7 @@ export default defineEventHandler(async (event) => { setResponseHeaders(event, { 'Cache-Control': 'no-store, no-cache, must-revalidate', - Pragma: 'no-cache', + 'Pragma': 'no-cache', }) return { ok: true, backupCodes: codes } }) diff --git a/packages/preset-nuxt4/templates/auth-2fa/server/utils/totp.ts b/packages/preset-nuxt4/templates/auth-2fa/server/utils/totp.ts index 695c9b06..9debe95f 100644 --- a/packages/preset-nuxt4/templates/auth-2fa/server/utils/totp.ts +++ b/packages/preset-nuxt4/templates/auth-2fa/server/utils/totp.ts @@ -13,8 +13,8 @@ function getEncryptionKey(): Buffer { const hex = useRuntimeConfig().totpEncryptionKey as string | undefined if (!hex || hex.length !== 64) { throw new Error( - 'NUXT_TOTP_ENCRYPTION_KEY missing or not 64 hex chars. ' + - "Generate one with `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"` and set it in .env.", + 'NUXT_TOTP_ENCRYPTION_KEY missing or not 64 hex chars. ' + + 'Generate one with `node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"` and set it in .env.', ) } return Buffer.from(hex, 'hex') diff --git a/packages/preset-nuxt4/templates/auth-2fa/test/e2e/api/auth-2fa.test.ts b/packages/preset-nuxt4/templates/auth-2fa/test/e2e/api/auth-2fa.test.ts index b72c92cc..833a9d6c 100644 --- a/packages/preset-nuxt4/templates/auth-2fa/test/e2e/api/auth-2fa.test.ts +++ b/packages/preset-nuxt4/templates/auth-2fa/test/e2e/api/auth-2fa.test.ts @@ -27,7 +27,7 @@ describe('e2e: auth-2fa', () => { it.skipIf(!serverUp)('setup returns a secret + provisioning URL', async () => { const adminCookie = await loginAsAdmin() const user = await createTestUser(adminCookie) - const { status, data } = await apiPost<{ secret: string; otpauthUrl: string }>( + const { status, data } = await apiPost<{ secret: string, otpauthUrl: string }>( '/api/auth/2fa/setup', {}, user.cookie, diff --git a/packages/preset-nuxt4/templates/auth-recovery/app/pages/forgot-password.vue b/packages/preset-nuxt4/templates/auth-recovery/app/pages/forgot-password.vue index ea678a51..a38c776b 100644 --- a/packages/preset-nuxt4/templates/auth-recovery/app/pages/forgot-password.vue +++ b/packages/preset-nuxt4/templates/auth-recovery/app/pages/forgot-password.vue @@ -1,11 +1,22 @@