From 4f6cfa07f5cfe2f28ccb0b290afefa7751ad93d9 Mon Sep 17 00:00:00 2001 From: Baptiste Leproux Date: Wed, 24 Jun 2026 15:20:40 +0200 Subject: [PATCH 1/3] chore(starters): upgrade deps --- .starters/default/package.json | 4 ++-- .starters/i18n/package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.starters/default/package.json b/.starters/default/package.json index 21af780ab..10ebbdd1b 100644 --- a/.starters/default/package.json +++ b/.starters/default/package.json @@ -6,7 +6,7 @@ }, "dependencies": { "docus": "latest", - "better-sqlite3": "^12.6.2", - "nuxt": "^4.3.1" + "better-sqlite3": "^12.11.1", + "nuxt": "^4.4.8" } } \ No newline at end of file diff --git a/.starters/i18n/package.json b/.starters/i18n/package.json index 21af780ab..10ebbdd1b 100644 --- a/.starters/i18n/package.json +++ b/.starters/i18n/package.json @@ -6,7 +6,7 @@ }, "dependencies": { "docus": "latest", - "better-sqlite3": "^12.6.2", - "nuxt": "^4.3.1" + "better-sqlite3": "^12.11.1", + "nuxt": "^4.4.8" } } \ No newline at end of file From b3b2e4866fd91b7d689d280d555fb2f7b08d7742 Mon Sep 17 00:00:00 2001 From: Baptiste Leproux Date: Wed, 24 Jun 2026 21:45:43 +0200 Subject: [PATCH 2/3] test --- .github/workflows/ci.yml | 3 + package.json | 1 + test/starters.mjs | 135 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 test/starters.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 462d6ca25..d16511290 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,5 +53,8 @@ jobs: run: pnpm run build working-directory: ./cli + - name: Test starters + run: pnpm run test:starters + - name: Publish CLI and Layer run: npx pkg-pr-new publish --compact './cli' './layer' diff --git a/package.json b/package.json index 40b48293d..75095913d 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dev:prepare": "nuxt prepare layer && nuxt prepare docs --extends ../layer && nuxt prepare playground --extends ../layer", "playground:dev": "cd playground && nuxt dev --extends ../layer", "lint": "eslint .", + "test:starters": "node test/starters.mjs", "typecheck": "nuxt typecheck layer", "verify": "pnpm run dev:prepare && pnpm run lint && pnpm run typecheck && pnpm run docs:build", "layer:release": "release-it --config .release-it.layer.json && cd layer && npm publish", diff --git a/test/starters.mjs b/test/starters.mjs new file mode 100644 index 000000000..6b33f6560 --- /dev/null +++ b/test/starters.mjs @@ -0,0 +1,135 @@ +// Smoke test for the published starters against the local `layer`. +// +// For each starter in `.starters/*` it: +// 1. copies the starter into a temp dir (so the repo stays clean), +// 2. rewrites its `docus` dependency to a `link:` to the local `layer`, +// so we test the current branch's layer exactly as a user extends it, +// 3. installs standalone (`--ignore-workspace`), +// 4. runs `nuxt build` (production server bundle) and `nuxt generate` +// (static prerender), then asserts the generated home page rendered the +// docus hero. If the layer fails to extend, that markup is absent. +// +// Usage: `node test/starters.mjs [default] [i18n]` (defaults to all). + +import { execFileSync } from 'node:child_process' +import { cpSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const here = dirname(fileURLToPath(import.meta.url)) +const repoRoot = resolve(here, '..') +const layerDir = join(repoRoot, 'layer') +const startersDir = join(repoRoot, '.starters') + +// Stable marker rendered by the starters' `content/index.md` hero. It only +// appears if the docus layer extended and Content + MDC + Nuxt UI rendered. +const HERO_MARKER = 'Write beautiful docs with Markdown' + +// Paths we never want to copy from the source starter. +const SKIP_COPY = /(?:^|\/)(?:node_modules|\.nuxt|\.output|\.data|\.cache|dist)(?:\/|$)|\.tgz$/ + +const requested = process.argv.slice(2) +const all = readdirSync(startersDir, { withFileTypes: true }) + .filter(d => d.isDirectory()) + .map(d => d.name) +const starters = requested.length ? requested : all + +for (const name of starters) { + if (!all.includes(name)) { + console.error(`✗ unknown starter "${name}" (available: ${all.join(', ')})`) + process.exit(1) + } +} + +function run(cmd, args, cwd, env) { + console.log(`\n $ ${cmd} ${args.join(' ')}`) + execFileSync(cmd, args, { cwd, stdio: 'inherit', env: { ...process.env, ...env } }) +} + +// The docus production build (sqlite wasm, og-image, takumi, full UI) is heavy +// enough to exhaust the default V8 heap, so give the Nuxt steps more headroom. +const buildEnv = { NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ''} --max-old-space-size=8192`.trim() } + +/** Recursively collect the contents of every prerendered .html file. */ +function readGeneratedHtml(dir) { + let html = '' + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = join(dir, entry.name) + if (entry.isDirectory()) html += readGeneratedHtml(full) + else if (entry.name.endsWith('.html')) html += readFileSync(full, 'utf8') + } + return html +} + +const failures = [] + +for (const name of starters) { + const src = join(startersDir, name) + const work = mkdtempSync(join(tmpdir(), `docus-starter-${name}-`)) + console.log(`\n=== starter: ${name} ===\n workdir: ${work}`) + + try { + cpSync(src, work, { recursive: true, filter: s => !SKIP_COPY.test(s) }) + + // Point `docus` at the local layer so we test the current branch's layer + // exactly as a user extends it from npm. + const pkgPath = join(work, 'package.json') + const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) + pkg.dependencies.docus = `link:${layerDir}` + writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`) + + // A dedicated pnpm-workspace.yaml makes the temp dir its own workspace root + // (decoupled from this repo) and pre-decides which native build scripts may + // run — pnpm errors on undecided builds in CI. The keys mirror the repo's + // own pnpm-workspace.yaml: `allowBuilds` is honoured by pnpm 11.1.x while + // `onlyBuiltDependencies`/`ignoredBuiltDependencies` cover newer pnpm. + writeFileSync(join(work, 'pnpm-workspace.yaml'), [ + 'packages: []', + 'onlyBuiltDependencies:', + ' - better-sqlite3', + ' - sharp', + 'ignoredBuiltDependencies:', + ' - \'@parcel/watcher\'', + ' - \'@tailwindcss/oxide\'', + ' - esbuild', + ' - unrs-resolver', + ' - vue-demi', + 'allowBuilds:', + ' \'@parcel/watcher\': false', + ' \'@tailwindcss/oxide\': false', + ' better-sqlite3: true', + ' esbuild: false', + ' sharp: true', + ' unrs-resolver: false', + ' vue-demi: false', + '', + ].join('\n')) + + run('pnpm', ['install', '--no-frozen-lockfile'], work) + run('pnpm', ['exec', 'nuxt', 'build'], work, buildEnv) + run('pnpm', ['exec', 'nuxt', 'generate'], work, buildEnv) + + const html = readGeneratedHtml(join(work, '.output', 'public')) + if (!html.includes(HERO_MARKER)) { + throw new Error(`generated HTML does not contain the docus hero ("${HERO_MARKER}") — the layer likely failed to extend`) + } + + console.log(`\n✓ starter "${name}" built, generated, and rendered the docus home`) + } + catch (error) { + failures.push(`${name}: ${error.message}`) + console.error(`\n✗ starter "${name}" failed: ${error.message}`) + } + finally { + rmSync(work, { recursive: true, force: true }) + } +} + +console.log(`\n${'='.repeat(48)}`) +if (failures.length) { + console.error(`✗ ${failures.length}/${starters.length} starter(s) failed:`) + for (const f of failures) console.error(` - ${f}`) + process.exit(1) +} +console.log(`✓ all ${starters.length} starter(s) passed`) From c748871f4593fd5fd84957507b42a1ae57d95061 Mon Sep 17 00:00:00 2001 From: Baptiste Leproux Date: Thu, 25 Jun 2026 09:38:16 +0200 Subject: [PATCH 3/3] Revert "test" This reverts commit b3b2e4866fd91b7d689d280d555fb2f7b08d7742. --- .github/workflows/ci.yml | 3 - package.json | 1 - test/starters.mjs | 135 --------------------------------------- 3 files changed, 139 deletions(-) delete mode 100644 test/starters.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d16511290..462d6ca25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,8 +53,5 @@ jobs: run: pnpm run build working-directory: ./cli - - name: Test starters - run: pnpm run test:starters - - name: Publish CLI and Layer run: npx pkg-pr-new publish --compact './cli' './layer' diff --git a/package.json b/package.json index 75095913d..40b48293d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "dev:prepare": "nuxt prepare layer && nuxt prepare docs --extends ../layer && nuxt prepare playground --extends ../layer", "playground:dev": "cd playground && nuxt dev --extends ../layer", "lint": "eslint .", - "test:starters": "node test/starters.mjs", "typecheck": "nuxt typecheck layer", "verify": "pnpm run dev:prepare && pnpm run lint && pnpm run typecheck && pnpm run docs:build", "layer:release": "release-it --config .release-it.layer.json && cd layer && npm publish", diff --git a/test/starters.mjs b/test/starters.mjs deleted file mode 100644 index 6b33f6560..000000000 --- a/test/starters.mjs +++ /dev/null @@ -1,135 +0,0 @@ -// Smoke test for the published starters against the local `layer`. -// -// For each starter in `.starters/*` it: -// 1. copies the starter into a temp dir (so the repo stays clean), -// 2. rewrites its `docus` dependency to a `link:` to the local `layer`, -// so we test the current branch's layer exactly as a user extends it, -// 3. installs standalone (`--ignore-workspace`), -// 4. runs `nuxt build` (production server bundle) and `nuxt generate` -// (static prerender), then asserts the generated home page rendered the -// docus hero. If the layer fails to extend, that markup is absent. -// -// Usage: `node test/starters.mjs [default] [i18n]` (defaults to all). - -import { execFileSync } from 'node:child_process' -import { cpSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { dirname, join, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' - -const here = dirname(fileURLToPath(import.meta.url)) -const repoRoot = resolve(here, '..') -const layerDir = join(repoRoot, 'layer') -const startersDir = join(repoRoot, '.starters') - -// Stable marker rendered by the starters' `content/index.md` hero. It only -// appears if the docus layer extended and Content + MDC + Nuxt UI rendered. -const HERO_MARKER = 'Write beautiful docs with Markdown' - -// Paths we never want to copy from the source starter. -const SKIP_COPY = /(?:^|\/)(?:node_modules|\.nuxt|\.output|\.data|\.cache|dist)(?:\/|$)|\.tgz$/ - -const requested = process.argv.slice(2) -const all = readdirSync(startersDir, { withFileTypes: true }) - .filter(d => d.isDirectory()) - .map(d => d.name) -const starters = requested.length ? requested : all - -for (const name of starters) { - if (!all.includes(name)) { - console.error(`✗ unknown starter "${name}" (available: ${all.join(', ')})`) - process.exit(1) - } -} - -function run(cmd, args, cwd, env) { - console.log(`\n $ ${cmd} ${args.join(' ')}`) - execFileSync(cmd, args, { cwd, stdio: 'inherit', env: { ...process.env, ...env } }) -} - -// The docus production build (sqlite wasm, og-image, takumi, full UI) is heavy -// enough to exhaust the default V8 heap, so give the Nuxt steps more headroom. -const buildEnv = { NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ''} --max-old-space-size=8192`.trim() } - -/** Recursively collect the contents of every prerendered .html file. */ -function readGeneratedHtml(dir) { - let html = '' - for (const entry of readdirSync(dir, { withFileTypes: true })) { - const full = join(dir, entry.name) - if (entry.isDirectory()) html += readGeneratedHtml(full) - else if (entry.name.endsWith('.html')) html += readFileSync(full, 'utf8') - } - return html -} - -const failures = [] - -for (const name of starters) { - const src = join(startersDir, name) - const work = mkdtempSync(join(tmpdir(), `docus-starter-${name}-`)) - console.log(`\n=== starter: ${name} ===\n workdir: ${work}`) - - try { - cpSync(src, work, { recursive: true, filter: s => !SKIP_COPY.test(s) }) - - // Point `docus` at the local layer so we test the current branch's layer - // exactly as a user extends it from npm. - const pkgPath = join(work, 'package.json') - const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) - pkg.dependencies.docus = `link:${layerDir}` - writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`) - - // A dedicated pnpm-workspace.yaml makes the temp dir its own workspace root - // (decoupled from this repo) and pre-decides which native build scripts may - // run — pnpm errors on undecided builds in CI. The keys mirror the repo's - // own pnpm-workspace.yaml: `allowBuilds` is honoured by pnpm 11.1.x while - // `onlyBuiltDependencies`/`ignoredBuiltDependencies` cover newer pnpm. - writeFileSync(join(work, 'pnpm-workspace.yaml'), [ - 'packages: []', - 'onlyBuiltDependencies:', - ' - better-sqlite3', - ' - sharp', - 'ignoredBuiltDependencies:', - ' - \'@parcel/watcher\'', - ' - \'@tailwindcss/oxide\'', - ' - esbuild', - ' - unrs-resolver', - ' - vue-demi', - 'allowBuilds:', - ' \'@parcel/watcher\': false', - ' \'@tailwindcss/oxide\': false', - ' better-sqlite3: true', - ' esbuild: false', - ' sharp: true', - ' unrs-resolver: false', - ' vue-demi: false', - '', - ].join('\n')) - - run('pnpm', ['install', '--no-frozen-lockfile'], work) - run('pnpm', ['exec', 'nuxt', 'build'], work, buildEnv) - run('pnpm', ['exec', 'nuxt', 'generate'], work, buildEnv) - - const html = readGeneratedHtml(join(work, '.output', 'public')) - if (!html.includes(HERO_MARKER)) { - throw new Error(`generated HTML does not contain the docus hero ("${HERO_MARKER}") — the layer likely failed to extend`) - } - - console.log(`\n✓ starter "${name}" built, generated, and rendered the docus home`) - } - catch (error) { - failures.push(`${name}: ${error.message}`) - console.error(`\n✗ starter "${name}" failed: ${error.message}`) - } - finally { - rmSync(work, { recursive: true, force: true }) - } -} - -console.log(`\n${'='.repeat(48)}`) -if (failures.length) { - console.error(`✗ ${failures.length}/${starters.length} starter(s) failed:`) - for (const f of failures) console.error(` - ${f}`) - process.exit(1) -} -console.log(`✓ all ${starters.length} starter(s) passed`)