From 99624501fcc8a2b8d594e6ffd002a2b4ade14dc6 Mon Sep 17 00:00:00 2001 From: Pascal Klesse <54418919+pascal-klesse@users.noreply.github.com> Date: Sun, 3 May 2026 17:23:36 +0200 Subject: [PATCH 1/2] feat(fullstack init --next): default frontend branch to 'next' on nuxt-base-starter The `next` branch on lenneTech/nuxt-base-starter ships an auth basePath aligned with nest-base (`/api/auth`) instead of nest-server-starter's `/iam`. With `--next`, that's now the implicit default; explicit `--frontend-branch` still wins. Refs: nest-base LLM-test 2026-05-03 friction #5 (auth path blocker). Co-Authored-By: Claude Opus 4.7 (1M context) --- ...ullstack-init-next-frontend-branch.test.ts | 92 +++++++++++++++++++ src/commands/fullstack/init.ts | 9 +- 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 __tests__/fullstack-init-next-frontend-branch.test.ts diff --git a/__tests__/fullstack-init-next-frontend-branch.test.ts b/__tests__/fullstack-init-next-frontend-branch.test.ts new file mode 100644 index 0000000..baaef37 --- /dev/null +++ b/__tests__/fullstack-init-next-frontend-branch.test.ts @@ -0,0 +1,92 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * `--next` defaults the frontend git ref to `nuxt-base-starter#next`. + * + * The `next` branch on `lenneTech/nuxt-base-starter` ships an auth + * `basePath` aligned with `nest-base` (`/api/auth`) instead of the + * `nest-server-starter` default (`/iam`). Without this default, the + * Better-Auth handshake between the experimental `--next` API and the + * cloned frontend lands on a 404 and the user has to discover + * `--frontend-branch next` themselves (LLM-test 2026-05-03 friction #5, + * blocker). + * + * Two invariants need to hold: + * + * 1. When `--next` is set and no explicit `--frontend-branch` is + * supplied, init.ts derives `frontendBranch === 'next'` and + * passes it through to `frontendHelper.setupNuxt`. + * 2. An explicit `--frontend-branch ` always wins, including + * under `--next`, so consumers can target a custom branch on + * either the legacy or `next`-derived line. + * + * Without `--next`, behaviour is unchanged: `frontendBranch` falls + * back to `cliFrontendBranch || configFrontendBranch` and is + * `undefined` if neither is provided (clones the default branch). + * + * We assert against the source string (same style as + * `fullstack-init-next-rename.test.ts`) — running the full gluegun + * command in-process would require mocking out the entire frontend + * helper, git, and prompts surface, which adds maintenance cost + * without buying meaningful coverage for a one-liner derivation. + */ +describe('Fullstack init --next frontend branch default', () => { + const initSource = fs.readFileSync( + path.join(__dirname, '..', 'src', 'commands', 'fullstack', 'init.ts'), + 'utf8', + ); + + test('frontendBranch derivation prefers explicit --frontend-branch over the --next default', () => { + // The derivation must explicitly check `cliFrontendBranch` first so + // an explicit `--frontend-branch ` overrides the experimental + // default. We allow either a direct truthy/length check; what we + // care about is that `cliFrontendBranch` appears before the + // `experimental` ternary that supplies `'next'`. + const derivation = initSource.match(/const frontendBranch =[^;]*;/); + expect(derivation).not.toBeNull(); + const expr = derivation![0]; + + // Explicit CLI flag is checked first. + expect(expr).toMatch(/cliFrontendBranch/); + // Experimental default falls through to 'next'. + expect(expr).toMatch(/experimental/); + expect(expr).toMatch(/'next'/); + + // Sanity: `cliFrontendBranch` is referenced before `'next'` in the + // expression, so the explicit value wins. + const cliIdx = expr.indexOf('cliFrontendBranch'); + const nextIdx = expr.indexOf("'next'"); + expect(cliIdx).toBeGreaterThanOrEqual(0); + expect(nextIdx).toBeGreaterThan(cliIdx); + }); + + test('no --next flag preserves the legacy fallback (cli > config > undefined)', () => { + // The legacy `cliFrontendBranch || configFrontendBranch` chain must + // remain inside the new derivation so non-experimental users still + // get the default branch when neither flag nor config sets one. + const derivation = initSource.match(/const frontendBranch =[^;]*;/); + expect(derivation).not.toBeNull(); + expect(derivation![0]).toMatch(/configFrontendBranch/); + }); + + test('frontendBranch is forwarded into frontendHelper.setupNuxt unchanged', () => { + // The single-line derivation only matters if it's the value that + // setupNuxt actually receives. Guard against a future refactor + // that re-derives a separate branch for the nuxt path and + // accidentally drops the experimental default. + const setupNuxtCall = initSource.match(/frontendHelper\.setupNuxt\([^)]*,\s*\{[\s\S]*?\}\)/); + expect(setupNuxtCall).not.toBeNull(); + expect(setupNuxtCall![0]).toMatch(/branch:\s*frontendBranch/); + }); + + test('CLI usage hint mentions that --next implies the nuxt-base-starter next branch', () => { + // The non-interactive hint is the only documentation surface + // Claude Code / scripted consumers see for `--next`, so it must + // surface this implicit default. `--frontend-branch` overriding it + // is the well-known precedence rule and doesn't need to be + // restated here, but the hint must at least name the implicit + // default so users searching for "next branch" find it. + expect(initSource).toMatch(/--next[^]*?nuxt-base-starter[^]*?next/); + }); +}); diff --git a/src/commands/fullstack/init.ts b/src/commands/fullstack/init.ts index fb7f8ec..f4385f4 100644 --- a/src/commands/fullstack/init.ts +++ b/src/commands/fullstack/init.ts @@ -37,7 +37,7 @@ const NewCommand: GluegunCommand = { // Hint for non-interactive callers (e.g. Claude Code) toolbox.tools.nonInteractiveHint( - 'lt fullstack init --name --frontend --api-mode --framework-mode [--framework-upstream-branch ] [--next] [--dry-run] --noConfirm', + 'lt fullstack init --name --frontend --api-mode --framework-mode [--framework-upstream-branch ] [--next: implies nuxt-base-starter#next unless --frontend-branch overrides] [--dry-run] --noConfirm', ); // Check git @@ -297,7 +297,12 @@ const NewCommand: GluegunCommand = { // Determine branches and copy/link paths with priority: CLI > config const apiBranch = cliApiBranch || configApiBranch; - const frontendBranch = cliFrontendBranch || configFrontendBranch; + // Under `--next`, default the nuxt-base-starter ref to the `next` + // branch — that branch ships an auth basePath (`/api/auth`) that + // matches the experimental nest-base API. Explicit + // `--frontend-branch` (or lt.config) still wins so consumers can + // target a custom branch on either line. + const frontendBranch = cliFrontendBranch || configFrontendBranch || (experimental ? 'next' : undefined); const apiCopy = cliApiCopy || configApiCopy; const apiLink = cliApiLink || configApiLink; const frontendCopy = cliFrontendCopy || configFrontendCopy; From 8ee8d1bf80ec30f025ccb29749f6d0da08406794 Mon Sep 17 00:00:00 2001 From: Pascal Klesse <54418919+pascal-klesse@users.noreply.github.com> Date: Sun, 3 May 2026 17:31:26 +0200 Subject: [PATCH 2/2] fix(test): scope require('fs')/'path' inside describe to avoid TS2451 with sibling test file ts-jest treats every test file as part of one TypeScript program, so two files with top-level `const fs = require('fs')` collide. The neighbouring `fullstack-init-next-rename.test.ts` already owns the unprefixed `fs` / `path` names; this commit moves this file's requires inside the describe block (matching how that file already scopes `gluegun` / `filesystem` / `patching`). Co-Authored-By: Claude Opus 4.7 (1M context) --- ...ullstack-init-next-frontend-branch.test.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/__tests__/fullstack-init-next-frontend-branch.test.ts b/__tests__/fullstack-init-next-frontend-branch.test.ts index baaef37..b52fd1c 100644 --- a/__tests__/fullstack-init-next-frontend-branch.test.ts +++ b/__tests__/fullstack-init-next-frontend-branch.test.ts @@ -1,5 +1,11 @@ -const fs = require('fs'); -const path = require('path'); +// Test file shares one TypeScript program with the rest of `__tests__/` +// under ts-jest, so any top-level `const fs = require('fs')` clashes +// with other files' globals (TS2451). The neighbouring +// `fullstack-init-next-rename.test.ts` already owns the unprefixed +// `fs` / `path` names, so this file requires Node's built-ins inside +// the describe block where their scope is local. See the header +// comment in `fullstack-init-next-rename.test.ts` for the same +// rationale around `filesystem` / `patching`. /** * `--next` defaults the frontend git ref to `nuxt-base-starter#next`. @@ -32,8 +38,14 @@ const path = require('path'); * without buying meaningful coverage for a one-liner derivation. */ describe('Fullstack init --next frontend branch default', () => { - const initSource = fs.readFileSync( - path.join(__dirname, '..', 'src', 'commands', 'fullstack', 'init.ts'), + // Lazy require to avoid colliding with the top-level `fs` / `path` + // declarations in other test files (see header comment). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const nodeFs = require('fs'); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const nodePath = require('path'); + const initSource: string = nodeFs.readFileSync( + nodePath.join(__dirname, '..', 'src', 'commands', 'fullstack', 'init.ts'), 'utf8', );