diff --git a/README.md b/README.md index 12f4d491..588a92db 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,10 @@ nuxt add nuxt-shiki nuxt add nuxt-seo-kit ``` +## Custom templates + +To override a built-in `add-template` template, add a matching file to the project `stubs` directory. For example, `stubs/component.vue` is used instead of the default component contents when running `nuxt add-template component `. + ## Update Notifications Once a day, `nuxt/cli` checks in the background whether a newer version of Nuxt has been released and, if so, prints a short nudge to run `nuxt upgrade` after the command finishes. The `latest` dist-tag is read from the registry configured in your `.npmrc` and cached in your user-level `.nuxtrc`. diff --git a/packages/nuxt-cli/src/commands/add-template.ts b/packages/nuxt-cli/src/commands/add-template.ts index bdb3c19d..077c5f73 100644 --- a/packages/nuxt-cli/src/commands/add-template.ts +++ b/packages/nuxt-cli/src/commands/add-template.ts @@ -6,7 +6,7 @@ import { styleText } from 'node:util' import { intro, outro } from '@clack/prompts' import { defineCommand } from 'citty' -import { dirname, resolve } from 'pathe' +import { dirname, extname, resolve } from 'pathe' import { loadKit } from '../utils/kit' import { logger } from '../utils/logger' @@ -99,6 +99,16 @@ export default defineCommand({ const kit = await loadKit(cwd) const config = await kit.loadNuxtConfig({ cwd }) const res = templates[templateName]({ name, args: ctx.args, nuxtOptions: config }) + const stubPath = resolve(config.rootDir, 'stubs', `${templateName}${extname(res.path)}`) + let contents = `${res.contents.trim()}\n` + try { + contents = await fsp.readFile(stubPath, 'utf8') + } + catch (error) { + if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) { + throw error + } + } const parentDir = dirname(res.path) const createdDir = await fsp.mkdir(parentDir, { recursive: true }) if (createdDir) { @@ -109,7 +119,7 @@ export default defineCommand({ } try { - await fsp.writeFile(res.path, `${res.contents.trim()}\n`, { flag: ctx.args.force ? 'w' : 'wx' }) + await fsp.writeFile(res.path, contents, { flag: ctx.args.force ? 'w' : 'wx' }) } catch (error) { if (!ctx.args.force && (error as NodeJS.ErrnoException).code === 'EEXIST') { diff --git a/packages/nuxt-cli/test/unit/commands/add-template.spec.ts b/packages/nuxt-cli/test/unit/commands/add-template.spec.ts index 2ccd2042..03b77e5c 100644 --- a/packages/nuxt-cli/test/unit/commands/add-template.spec.ts +++ b/packages/nuxt-cli/test/unit/commands/add-template.spec.ts @@ -1,4 +1,4 @@ -import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -33,6 +33,24 @@ describe('add-template command', () => { expect(await readFile(path, 'utf8')).toMatch(/\n$/) }) + it('preserves project stub contents exactly', async () => { + await mkdir(join(cwd, 'stubs')) + await writeFile(join(cwd, 'stubs/component.vue'), ' \n\n') + + await run('component', 'user-card') + + await expect(readFile(join(cwd, 'components/user-card.vue'), 'utf8')).resolves.toBe(' \n\n') + }) + + it('preserves an empty project stub', async () => { + await mkdir(join(cwd, 'stubs')) + await writeFile(join(cwd, 'stubs/component.vue'), '') + + await run('component', 'empty-card') + + await expect(readFile(join(cwd, 'components/empty-card.vue'), 'utf8')).resolves.toBe('') + }) + it('exposes template-specific options', async () => { await run('api', 'users', '--method', 'get') await run('component', 'island', '--mode', 'client')