From a5c27996e8cc04b9a939653d28db492b92ab5ec2 Mon Sep 17 00:00:00 2001 From: Joey Houtenbos Date: Fri, 2 Oct 2026 12:06:13 +0200 Subject: [PATCH 1/9] fix(release): publish the release through the REST API, not the gh CLI The Release workflow runs on vars.CI_RUNNER when set, and a self-hosted runner image cannot be assumed to ship the GitHub CLI. On such a runner the job pushed the annotated tag and then failed at gh release create with 'gh: command not found', leaving a tag without a release; a rerun then refuses, because the commit is already tagged. The step now POSTs to ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases with curl and the same fields (tag name as title, generated notes, target = the dispatched commit), using the job's GITHUB_TOKEN with contents: write. That works on GitHub-hosted and self-hosted runners, public and private repositories alike, and the job needs only git and curl. gh was used nowhere else in the workflow. shared:github 3.1.0 -> 3.1.1. New test: the emitted release.yml calls no gh command and posts to the releases endpoint (fails on the old template). --- packages/preset-nuxt4/src/features/github.ts | 2 +- .../templates/.github/workflows/release.yml | 11 ++++++++++- packages/preset-nuxt4/test/github.test.ts | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/preset-nuxt4/src/features/github.ts b/packages/preset-nuxt4/src/features/github.ts index c12fa8a..f70464c 100644 --- a/packages/preset-nuxt4/src/features/github.ts +++ b/packages/preset-nuxt4/src/features/github.ts @@ -12,7 +12,7 @@ const FEATURE_ID = 'shared:github' /** GitHub Actions gate: lint, typecheck, coverage, dependency audit. ` audit` is advisory. */ export const githubFeature: Feature = { id: 'shared:github', - version: '3.1.0', + version: '3.1.1', label: 'GitHub Actions workflows', stage: STAGE.GITIGNORE, failureIsNonFatal: true, diff --git a/packages/preset-nuxt4/templates/.github/workflows/release.yml b/packages/preset-nuxt4/templates/.github/workflows/release.yml index 8b7ae45..7f2184b 100644 --- a/packages/preset-nuxt4/templates/.github/workflows/release.yml +++ b/packages/preset-nuxt4/templates/.github/workflows/release.yml @@ -96,7 +96,16 @@ jobs: # tagger date is what dates the release when someone reads the history later. git tag -a "$TAG" -m "$TAG" git push origin "refs/tags/${TAG}" - gh release create "$TAG" --title "$TAG" --generate-notes --target "$GITHUB_SHA" + # The REST API, not `gh release create`: a self-hosted runner set through + # `CI_RUNNER` cannot be assumed to ship the GitHub CLI; curl is near universal. + # TAG is vMAJOR.MINOR.PATCH and GITHUB_SHA is hex, so printf builds safe JSON. + curl --fail-with-body --silent --show-error \ + -X POST "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + -d "$(printf '{"tag_name":"%s","name":"%s","target_commitish":"%s","generate_release_notes":true}' "$TAG" "$TAG" "$GITHUB_SHA")" \ + > /dev/null - name: Say what shipped if: always() && steps.version.outputs.tag != '' diff --git a/packages/preset-nuxt4/test/github.test.ts b/packages/preset-nuxt4/test/github.test.ts index d244bf6..43832a9 100644 --- a/packages/preset-nuxt4/test/github.test.ts +++ b/packages/preset-nuxt4/test/github.test.ts @@ -202,6 +202,22 @@ describe('githubFeature', () => { expect(workflow).not.toMatch(/^\s{2}schedule:/m) }) + /** + * The job runs on `CI_RUNNER` when set, and a self-hosted image cannot be assumed to ship the + * GitHub CLI: a run there pushed the tag, then died at `gh release create`, leaving a tag + * without a release. Publishing goes through the REST API with curl, so the only tools the + * job needs are git and curl, on GitHub-hosted and self-hosted runners alike. + */ + it('publishes the release through the REST API, never the gh CLI', async () => { + await githubFeature.execute!(ctx()) + const workflow = await readFile(path.join(projectDir, '.github/workflows/release.yml'), 'utf8') + const commands = workflow.split('\n').filter((line) => !line.trim().startsWith('#')) + + expect(commands.some((line) => /(^|\s|\$\()gh\s/.test(line))).toBe(false) + expect(workflow).toContain('${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases') + expect(workflow).toContain('"generate_release_notes":true') + }) + it('collectDocs documents the quality gate under a single GitHub Actions heading', () => { const docs = githubFeature.collectDocs!(ctx()) expect(docs).toHaveLength(1) From fe54c02a586ad6277629874699f3a769e6c2041a Mon Sep 17 00:00:00 2001 From: Christian Stroet Date: Mon, 5 Oct 2026 11:40:27 +0200 Subject: [PATCH 2/9] fix(release): survive old curl and transient API errors when publishing - Drop `--fail-with-body` (curl >= 7.76): Ubuntu 20.04 and Debian bullseye ship older curl and would fail after the tag push. Check the HTTP status with `-w '%{http_code}'` instead, and print the API's error body on failure rather than sending it to /dev/null. - `--retry 3` for transient errors, since any failure here leaves a pushed tag without a release and a rerun refuses the already-tagged commit. - Add the `CI_RUNNER_APT_PACKAGES` step from lint-test.yml, before checkout so it can also provide git. - Comments: drop the gh tool list from the emitter, and move the incident narrative out of the source per the comment policy. - Test: match any `gh` word on command lines, and pin the printf format and its argument order. --- packages/preset-nuxt4/src/features/github.ts | 4 ++-- .../templates/.github/workflows/release.yml | 20 ++++++++++++++----- packages/preset-nuxt4/test/github.test.ts | 13 +++++------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/preset-nuxt4/src/features/github.ts b/packages/preset-nuxt4/src/features/github.ts index f70464c..acb0260 100644 --- a/packages/preset-nuxt4/src/features/github.ts +++ b/packages/preset-nuxt4/src/features/github.ts @@ -86,8 +86,8 @@ async function emit(ctx: RunContext): Promise { ) written.push(workflowRel) - // Copied verbatim: it runs git and gh, never the project's package manager, so there is - // nothing in it to render per manager. + // Copied verbatim: it never runs the project's package manager, so there is nothing in it + // to render per manager. const releaseRel = '.github/workflows/release.yml' await writeRecorded( ctx, diff --git a/packages/preset-nuxt4/templates/.github/workflows/release.yml b/packages/preset-nuxt4/templates/.github/workflows/release.yml index 7f2184b..a0a8f5f 100644 --- a/packages/preset-nuxt4/templates/.github/workflows/release.yml +++ b/packages/preset-nuxt4/templates/.github/workflows/release.yml @@ -35,6 +35,12 @@ jobs: # Set the repo/org variable `CI_RUNNER` to a self-hosted runner label to point this job at your own fleet, no template edit needed. runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} steps: + # Same knob as lint-test.yml: a minimal self-hosted image may lack git or curl. + # Before checkout: without git it downloads a tarball with no tags. + - name: Install extra runner packages + if: vars.CI_RUNNER_APT_PACKAGES != '' + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ${{ vars.CI_RUNNER_APT_PACKAGES }} + - uses: actions/checkout@v7 with: # Tags *are* the version history here, and a shallow clone fetches none of them. @@ -96,16 +102,20 @@ jobs: # tagger date is what dates the release when someone reads the history later. git tag -a "$TAG" -m "$TAG" git push origin "refs/tags/${TAG}" - # The REST API, not `gh release create`: a self-hosted runner set through - # `CI_RUNNER` cannot be assumed to ship the GitHub CLI; curl is near universal. + # curl, not gh: a self-hosted `CI_RUNNER` image may lack the GitHub CLI. # TAG is vMAJOR.MINOR.PATCH and GITHUB_SHA is hex, so printf builds safe JSON. - curl --fail-with-body --silent --show-error \ + body="${RUNNER_TEMP}/release.json" + status=$(curl --silent --show-error --retry 3 -o "$body" -w '%{http_code}' \ -X POST "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" \ -H "Authorization: Bearer ${GH_TOKEN}" \ -H 'Accept: application/vnd.github+json' \ -H 'X-GitHub-Api-Version: 2022-11-28' \ - -d "$(printf '{"tag_name":"%s","name":"%s","target_commitish":"%s","generate_release_notes":true}' "$TAG" "$TAG" "$GITHUB_SHA")" \ - > /dev/null + -d "$(printf '{"tag_name":"%s","name":"%s","target_commitish":"%s","generate_release_notes":true}' "$TAG" "$TAG" "$GITHUB_SHA")") + if [ "$status" != 201 ]; then + echo "::error::creating the ${TAG} release returned HTTP ${status}; the tag is pushed, create the release from it by hand" + cat "$body" + exit 1 + fi - name: Say what shipped if: always() && steps.version.outputs.tag != '' diff --git a/packages/preset-nuxt4/test/github.test.ts b/packages/preset-nuxt4/test/github.test.ts index 43832a9..1b6bef9 100644 --- a/packages/preset-nuxt4/test/github.test.ts +++ b/packages/preset-nuxt4/test/github.test.ts @@ -202,20 +202,17 @@ describe('githubFeature', () => { expect(workflow).not.toMatch(/^\s{2}schedule:/m) }) - /** - * The job runs on `CI_RUNNER` when set, and a self-hosted image cannot be assumed to ship the - * GitHub CLI: a run there pushed the tag, then died at `gh release create`, leaving a tag - * without a release. Publishing goes through the REST API with curl, so the only tools the - * job needs are git and curl, on GitHub-hosted and self-hosted runners alike. - */ + /** A self-hosted `CI_RUNNER` image may lack the GitHub CLI. */ it('publishes the release through the REST API, never the gh CLI', async () => { await githubFeature.execute!(ctx()) const workflow = await readFile(path.join(projectDir, '.github/workflows/release.yml'), 'utf8') const commands = workflow.split('\n').filter((line) => !line.trim().startsWith('#')) - expect(commands.some((line) => /(^|\s|\$\()gh\s/.test(line))).toBe(false) + expect(commands.some((line) => /\bgh\b/.test(line))).toBe(false) expect(workflow).toContain('${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases') - expect(workflow).toContain('"generate_release_notes":true') + expect(workflow).toContain( + `'{"tag_name":"%s","name":"%s","target_commitish":"%s","generate_release_notes":true}' "$TAG" "$TAG" "$GITHUB_SHA"`, + ) }) it('collectDocs documents the quality gate under a single GitHub Actions heading', () => { From 6a677d347bb190b65b3b1ae09dc4c13913efda5c Mon Sep 17 00:00:00 2001 From: Christian Stroet Date: Mon, 5 Oct 2026 12:18:34 +0200 Subject: [PATCH 3/9] fix(release): keep the token out of argv and report every publish failure - Pass the Authorization header to curl on stdin (`-H @-`): argv shows in `ps` for every user on a shared self-hosted runner. - `|| status=000` so a transport failure (DNS, refused, TLS) still reaches the error branch instead of `set -e` ending the step silently. - Word the error for a retried POST whose first attempt already created the release: "if the release is missing, create it from the tag". - Docs: `CI_RUNNER_APT_PACKAGES` now applies to both workflows. --- packages/preset-nuxt4/src/features/github.ts | 2 +- .../preset-nuxt4/templates/.github/workflows/release.yml | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/preset-nuxt4/src/features/github.ts b/packages/preset-nuxt4/src/features/github.ts index acb0260..15d2a53 100644 --- a/packages/preset-nuxt4/src/features/github.ts +++ b/packages/preset-nuxt4/src/features/github.ts @@ -38,7 +38,7 @@ export const githubFeature: Feature = { '', 'A freshly scaffolded project\'s `' + audit + '` will report findings in transitive dependencies of the AI and framework stack. That is the npm-ecosystem baseline for an unpinned dependency tree, not a defect the scaffold introduced, and not something this project can unilaterally clear. The CI gate is shaped around that fact: `dependency-review-action` blocks vulnerabilities your changes *introduce*, while the audit step reports the standing baseline without failing the build.', '', - 'To run it on a self-hosted runner instead, set the repo/org variable `CI_RUNNER` to your runner\'s label; no template edit needed. If that runner\'s image is missing packages `ubuntu-latest` already has, set `CI_RUNNER_APT_PACKAGES` (space-separated) to have the workflow `apt-get install` them first; leave it unset to skip that step entirely.', + 'To run it on a self-hosted runner instead, set the repo/org variable `CI_RUNNER` to your runner\'s label; no template edit needed. If that runner\'s image is missing packages `ubuntu-latest` already has, set `CI_RUNNER_APT_PACKAGES` (space-separated) to have both workflows `apt-get install` them first; leave it unset to skip that step entirely.', '', 'Secrets: any Docker build secrets declared by enabled features (see the Docker section for the exact env var names). This workflow itself needs none.', '', diff --git a/packages/preset-nuxt4/templates/.github/workflows/release.yml b/packages/preset-nuxt4/templates/.github/workflows/release.yml index a0a8f5f..6420955 100644 --- a/packages/preset-nuxt4/templates/.github/workflows/release.yml +++ b/packages/preset-nuxt4/templates/.github/workflows/release.yml @@ -105,15 +105,16 @@ jobs: # curl, not gh: a self-hosted `CI_RUNNER` image may lack the GitHub CLI. # TAG is vMAJOR.MINOR.PATCH and GITHUB_SHA is hex, so printf builds safe JSON. body="${RUNNER_TEMP}/release.json" + # The auth header goes in on stdin: argv is visible to every user on a shared runner. status=$(curl --silent --show-error --retry 3 -o "$body" -w '%{http_code}' \ -X POST "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" \ - -H "Authorization: Bearer ${GH_TOKEN}" \ + -H @- <<< "Authorization: Bearer ${GH_TOKEN}" \ -H 'Accept: application/vnd.github+json' \ -H 'X-GitHub-Api-Version: 2022-11-28' \ - -d "$(printf '{"tag_name":"%s","name":"%s","target_commitish":"%s","generate_release_notes":true}' "$TAG" "$TAG" "$GITHUB_SHA")") + -d "$(printf '{"tag_name":"%s","name":"%s","target_commitish":"%s","generate_release_notes":true}' "$TAG" "$TAG" "$GITHUB_SHA")") || status=000 if [ "$status" != 201 ]; then - echo "::error::creating the ${TAG} release returned HTTP ${status}; the tag is pushed, create the release from it by hand" - cat "$body" + echo "::error::creating the ${TAG} release returned HTTP ${status}; the tag is pushed, so if the release is missing, create it from the tag by hand" + cat "$body" 2>/dev/null exit 1 fi From cd2c879200c540c2edb3b39df5ca7947b17a68b1 Mon Sep 17 00:00:00 2001 From: Christian Stroet Date: Mon, 5 Oct 2026 15:56:49 +0200 Subject: [PATCH 4/9] fix(release): pass when a retried POST finds the release already created - On any non-201, check `GET /releases/tags/$TAG` before failing: a retried POST whose first attempt created the release gets 422 `already_exists`, which used to fail the run and then block the rerun on the existing tag. - `|| true` instead of `|| status=000`: `-w` already prints 000 on a transport failure, and the override could mask a real status. --- .../templates/.github/workflows/release.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/preset-nuxt4/templates/.github/workflows/release.yml b/packages/preset-nuxt4/templates/.github/workflows/release.yml index 6420955..82bf5fc 100644 --- a/packages/preset-nuxt4/templates/.github/workflows/release.yml +++ b/packages/preset-nuxt4/templates/.github/workflows/release.yml @@ -111,8 +111,18 @@ jobs: -H @- <<< "Authorization: Bearer ${GH_TOKEN}" \ -H 'Accept: application/vnd.github+json' \ -H 'X-GitHub-Api-Version: 2022-11-28' \ - -d "$(printf '{"tag_name":"%s","name":"%s","target_commitish":"%s","generate_release_notes":true}' "$TAG" "$TAG" "$GITHUB_SHA")") || status=000 + -d "$(printf '{"tag_name":"%s","name":"%s","target_commitish":"%s","generate_release_notes":true}' "$TAG" "$TAG" "$GITHUB_SHA")") || true if [ "$status" != 201 ]; then + # A retried POST fails with 422 when the first attempt already created the release. + exists=$(curl --silent --retry 3 -o /dev/null -w '%{http_code}' \ + -H @- <<< "Authorization: Bearer ${GH_TOKEN}" \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}") || true + if [ "$exists" = 200 ]; then + echo "::warning::creating the ${TAG} release returned HTTP ${status}, but the release exists" + exit 0 + fi echo "::error::creating the ${TAG} release returned HTTP ${status}; the tag is pushed, so if the release is missing, create it from the tag by hand" cat "$body" 2>/dev/null exit 1 From b30dd65f4588fff5080a92d539b3cbd06ec45731 Mon Sep 17 00:00:00 2001 From: Joey Houtenbos Date: Wed, 7 Oct 2026 09:42:41 +0000 Subject: [PATCH 5/9] fix(docs): bump writer for release workflow documentation update --- packages/preset-nuxt4/src/features/docs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/preset-nuxt4/src/features/docs.ts b/packages/preset-nuxt4/src/features/docs.ts index 782fc5b..a5a5112 100644 --- a/packages/preset-nuxt4/src/features/docs.ts +++ b/packages/preset-nuxt4/src/features/docs.ts @@ -15,7 +15,7 @@ import { /** Generates AGENTS.md, CLAUDE.md and README.md from `collectDocs()`. Always rewritten. */ export const docsFeature: Feature = { id: 'nuxt4:docs', - version: '1.0.24', + version: '1.0.25', label: 'Generate AGENTS.md + CLAUDE.md + README.md', frameworks: ['nuxt4'], stage: STAGE.DOCS, From eac4964c7d200f562fba821faa40d2f229b1b67b Mon Sep 17 00:00:00 2001 From: Joey Houtenbos Date: Wed, 7 Oct 2026 10:47:40 +0000 Subject: [PATCH 6/9] fix(auth-2fa): type fetch responses to avoid recursive Nuxt route inference --- packages/preset-nuxt4/src/features/auth-2fa.ts | 2 +- .../templates/auth-2fa/app/composables/use2fa.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/preset-nuxt4/src/features/auth-2fa.ts b/packages/preset-nuxt4/src/features/auth-2fa.ts index 3daad1b..ca28bd2 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.1', + version: '1.2.2', label: 'Two-factor auth (TOTP)', description: 'Authenticator-app 2FA; TOTP secrets encrypted at rest.', frameworks: ['nuxt4'], 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 fd4710b..3e8195a 100644 --- a/packages/preset-nuxt4/templates/auth-2fa/app/composables/use2fa.ts +++ b/packages/preset-nuxt4/templates/auth-2fa/app/composables/use2fa.ts @@ -1,32 +1,32 @@ /** Client wrapper around the 2FA endpoints. */ export function use2fa() { async function status(): Promise<{ enabled: boolean; enabledAt: string | null }> { - return $fetch('/api/auth/2fa/status') + return $fetch<{ enabled: boolean; enabledAt: string | null }>('/api/auth/2fa/status') } async function setup(): Promise<{ secret: string; otpauthUrl: string }> { - return $fetch('/api/auth/2fa/setup', { method: 'POST' }) + 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('/api/auth/2fa/verify', { method: 'POST', body: { code } }) + return $fetch<{ ok: true; backupCodes: string[] }>('/api/auth/2fa/verify', { method: 'POST', body: { code } }) } async function disable(code: string): Promise<{ ok: true }> { - return $fetch('/api/auth/2fa/disable', { method: 'POST', body: { code } }) + return $fetch<{ ok: true }>('/api/auth/2fa/disable', { method: 'POST', body: { code } }) } async function backupCodesStatus(): Promise<{ unused: number }> { - return $fetch('/api/auth/2fa/backup-codes') + return $fetch<{ unused: number }>('/api/auth/2fa/backup-codes') } async function generateBackupCodes(): Promise<{ codes: string[] }> { - return $fetch('/api/auth/2fa/backup-codes/generate', { method: 'POST' }) + return $fetch<{ codes: string[] }>('/api/auth/2fa/backup-codes/generate', { method: 'POST' }) } async function redeemBackupCode(code: string): Promise<{ ok: true }> { - return $fetch('/api/auth/2fa/backup-codes/redeem', { + return $fetch<{ ok: true }>('/api/auth/2fa/backup-codes/redeem', { method: 'POST', body: { code }, }) From f6d2aff24b6026b7ecc64c1b6b0cb6e05e2b8311 Mon Sep 17 00:00:00 2001 From: Joey Houtenbos Date: Wed, 7 Oct 2026 13:16:27 +0000 Subject: [PATCH 7/9] fix(mastra): type admin fetch responses to avoid recursive route inference --- packages/preset-nuxt4/src/features/mastra.ts | 2 +- .../mastra-admin/app/pages/dashboard/settings/ai.vue | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/preset-nuxt4/src/features/mastra.ts b/packages/preset-nuxt4/src/features/mastra.ts index c7b4882..25a9db7 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.1', + version: '2.1.2', label: 'Mastra AI runtime', frameworks: ['nuxt4'], stage: STAGE.AI_CORE, diff --git a/packages/preset-nuxt4/templates/mastra-admin/app/pages/dashboard/settings/ai.vue b/packages/preset-nuxt4/templates/mastra-admin/app/pages/dashboard/settings/ai.vue index 3923527..5e9acde 100644 --- a/packages/preset-nuxt4/templates/mastra-admin/app/pages/dashboard/settings/ai.vue +++ b/packages/preset-nuxt4/templates/mastra-admin/app/pages/dashboard/settings/ai.vue @@ -16,7 +16,7 @@ const toast = useToast() const headers = useRequestHeaders(['cookie']) const { data: modelConfigs, refresh: refreshConfigs } = await useAsyncData( 'ai-model-configs', - () => $fetch('/api/ai/configs', { headers }), + () => $fetch('/api/ai/configs', { headers }), ) interface ProxyModel { id: string @@ -76,7 +76,7 @@ interface PromptRow { const { data: agentRows, refresh: refreshAgents } = await useAsyncData( 'ai-agents', - () => $fetch('/api/ai/agents', { headers }), + () => $fetch('/api/ai/agents', { headers }), { default: () => [] }, ) From 2afd8c24d262975a286ee02ed6b794d8d3d9ed51 Mon Sep 17 00:00:00 2001 From: Joey Houtenbos Date: Wed, 7 Oct 2026 13:17:07 +0000 Subject: [PATCH 8/9] fix(mastra): bump past current main feature version --- packages/preset-nuxt4/src/features/mastra.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/preset-nuxt4/src/features/mastra.ts b/packages/preset-nuxt4/src/features/mastra.ts index 25a9db7..2c03f17 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.2', + version: '2.1.3', label: 'Mastra AI runtime', frameworks: ['nuxt4'], stage: STAGE.AI_CORE, From dddd0ac339129a959855107d0845b94cd703c0c2 Mon Sep 17 00:00:00 2001 From: Joey Houtenbos Date: Wed, 7 Oct 2026 13:18:04 +0000 Subject: [PATCH 9/9] fix(admin): type async-data fetch responses explicitly --- packages/preset-nuxt4/src/features/prompts.ts | 2 +- packages/preset-nuxt4/src/features/user-admin.ts | 2 +- .../templates/prompts/app/pages/dashboard/prompts.vue | 2 +- .../templates/user-admin/app/pages/dashboard/users/[id].vue | 2 +- .../templates/user-admin/app/pages/dashboard/users/index.vue | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/preset-nuxt4/src/features/prompts.ts b/packages/preset-nuxt4/src/features/prompts.ts index fe1fbc5..6ea5761 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.0', + version: '1.2.1', label: 'AI prompt management', description: 'Admin-editable agent prompts with shipped registry defaults.', frameworks: ['nuxt4'], diff --git a/packages/preset-nuxt4/src/features/user-admin.ts b/packages/preset-nuxt4/src/features/user-admin.ts index a0233d9..8566b5b 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.3', + version: '1.0.4', 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/templates/prompts/app/pages/dashboard/prompts.vue b/packages/preset-nuxt4/templates/prompts/app/pages/dashboard/prompts.vue index 37f48e9..d7f3106 100644 --- a/packages/preset-nuxt4/templates/prompts/app/pages/dashboard/prompts.vue +++ b/packages/preset-nuxt4/templates/prompts/app/pages/dashboard/prompts.vue @@ -23,7 +23,7 @@ const { t } = useI18n() const headers = useRequestHeaders(['cookie']) const toast = useToast() const { data, pending, refresh } = await useAsyncData('prompts-admin-list', () => - $fetch('/api/prompts', { headers }), + $fetch('/api/prompts', { headers }), ) const editing = ref>({}) diff --git a/packages/preset-nuxt4/templates/user-admin/app/pages/dashboard/users/[id].vue b/packages/preset-nuxt4/templates/user-admin/app/pages/dashboard/users/[id].vue index b191714..33efea4 100644 --- a/packages/preset-nuxt4/templates/user-admin/app/pages/dashboard/users/[id].vue +++ b/packages/preset-nuxt4/templates/user-admin/app/pages/dashboard/users/[id].vue @@ -20,7 +20,7 @@ const headers = useRequestHeaders(['cookie']) const { data: user, refresh } = await useAsyncData( () => `user-${userId.value}`, - () => $fetch(`/api/users/${userId.value}`, { headers }), + () => $fetch(`/api/users/${userId.value}`, { headers }), ) const form = reactive({ diff --git a/packages/preset-nuxt4/templates/user-admin/app/pages/dashboard/users/index.vue b/packages/preset-nuxt4/templates/user-admin/app/pages/dashboard/users/index.vue index e330e85..79f3810 100644 --- a/packages/preset-nuxt4/templates/user-admin/app/pages/dashboard/users/index.vue +++ b/packages/preset-nuxt4/templates/user-admin/app/pages/dashboard/users/index.vue @@ -27,7 +27,7 @@ const headers = useRequestHeaders(['cookie']) const { data, status, refresh } = await useAsyncData( 'users-admin-list', () => - $fetch('/api/users', { + $fetch('/api/users', { query: { search: search.value || undefined, limit: PAGE_SIZE,