mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-13 09:33:54 +00:00
feat(nuxi): scaffold files with nuxi add
(#3841)
Co-authored-by: pooya parsa <pyapar@gmail.com> Co-authored-by: Clément Ollivier <clement.o2p@gmail.com>
This commit is contained in:
parent
5fa9d79303
commit
19842ce471
@ -102,3 +102,32 @@ The `upgrade` command upgrades Nuxt 3 to the latest version.
|
|||||||
Option | Default | Description
|
Option | Default | Description
|
||||||
-------------------------|-----------------|------------------
|
-------------------------|-----------------|------------------
|
||||||
`--force, -f` | `false` | Removes `node_modules` and lock files before upgrade.
|
`--force, -f` | `false` | Removes `node_modules` and lock files before upgrade.
|
||||||
|
|
||||||
|
## Add
|
||||||
|
|
||||||
|
```{bash}
|
||||||
|
npx nuxi add [--cwd] [--force] <TEMPLATE> <NAME>
|
||||||
|
```
|
||||||
|
|
||||||
|
Option | Default | Description
|
||||||
|
-------------------------|-----------------|------------------
|
||||||
|
`TEMPLATE` | - | Specify a template of the file to be generated.
|
||||||
|
`NAME` | - | Specify a name of the file that will be created.
|
||||||
|
`--cwd` | `.` | The directory of the target application.
|
||||||
|
`--force` | `false` | Force override file if already exsits.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```{bash}
|
||||||
|
npx nuxi add component TheHeader
|
||||||
|
```
|
||||||
|
|
||||||
|
The `add` command generates new elements:
|
||||||
|
|
||||||
|
* **component**: `npx nuxi add component TheHeader`
|
||||||
|
* **composable**: `npx nuxi add composable foo`
|
||||||
|
* **layout**: `npx nuxi add layout custom`
|
||||||
|
* **plugin**: `npx nuxi add plugin analytics`
|
||||||
|
* **page**: `npx nuxi add page about` or `npx nuxi add page "category/[id]"`
|
||||||
|
* **middleware**: `npx nuxi add middleware auth`
|
||||||
|
* **api**: `npx nuxi add api hello` (CLI will generate file under `/server/api`)
|
||||||
|
62
packages/nuxi/src/commands/add.ts
Normal file
62
packages/nuxi/src/commands/add.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { existsSync, promises as fsp } from 'fs'
|
||||||
|
import { resolve, dirname } from 'pathe'
|
||||||
|
import consola from 'consola'
|
||||||
|
import { loadKit } from '../utils/kit'
|
||||||
|
import { templates } from '../utils/templates'
|
||||||
|
import { defineNuxtCommand } from './index'
|
||||||
|
|
||||||
|
export default defineNuxtCommand({
|
||||||
|
meta: {
|
||||||
|
name: 'add',
|
||||||
|
usage: `npx nuxi add [--cwd] [--force] ${Object.keys(templates).join('|')} <name>`,
|
||||||
|
description: 'Create a new template file.'
|
||||||
|
},
|
||||||
|
async invoke (args) {
|
||||||
|
const cwd = resolve(args.cwd || '.')
|
||||||
|
|
||||||
|
const template = args._[0]
|
||||||
|
const name = args._[1]
|
||||||
|
|
||||||
|
// Validate template name
|
||||||
|
if (!templates[template]) {
|
||||||
|
consola.error(`Template ${template} is not supported. Possible values: ${Object.keys(templates).join(', ')}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate options
|
||||||
|
if (!name) {
|
||||||
|
consola.error('name argument is missing!')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load config in order to respect srcDir
|
||||||
|
const kit = await loadKit(cwd)
|
||||||
|
const config = await kit.loadNuxtConfig({ cwd })
|
||||||
|
|
||||||
|
// Resolve template
|
||||||
|
const res = templates[template]({ name })
|
||||||
|
|
||||||
|
// Resolve full path to generated file
|
||||||
|
const path = resolve(config.srcDir, res.path)
|
||||||
|
|
||||||
|
// Ensure not overriding user code
|
||||||
|
if (!args.force && existsSync(path)) {
|
||||||
|
consola.error(`File exists: ${path} . Use --force to override or use a different name.`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure parent directory exists
|
||||||
|
const parentDir = dirname(path)
|
||||||
|
if (!existsSync(parentDir)) {
|
||||||
|
consola.info('Creating directory', parentDir)
|
||||||
|
if (template === 'page') {
|
||||||
|
consola.info('This enables vue-router functionality!')
|
||||||
|
}
|
||||||
|
await fsp.mkdir(parentDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write file
|
||||||
|
await fsp.writeFile(path, res.contents.trim() + '\n')
|
||||||
|
consola.info(`🪄 Generated a new ${template} in ${path}`)
|
||||||
|
}
|
||||||
|
})
|
@ -16,7 +16,9 @@ export const commands = {
|
|||||||
init: () => import('./init').then(_rDefault),
|
init: () => import('./init').then(_rDefault),
|
||||||
create: () => import('./init').then(_rDefault),
|
create: () => import('./init').then(_rDefault),
|
||||||
upgrade: () => import('./upgrade').then(_rDefault),
|
upgrade: () => import('./upgrade').then(_rDefault),
|
||||||
test: () => import('./test').then(_rDefault)
|
test: () => import('./test').then(_rDefault),
|
||||||
|
add: () => import('./add').then(_rDefault),
|
||||||
|
new: () => import('./add').then(_rDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Command = keyof typeof commands
|
export type Command = keyof typeof commands
|
||||||
|
98
packages/nuxi/src/utils/templates.ts
Normal file
98
packages/nuxi/src/utils/templates.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { upperFirst } from 'scule'
|
||||||
|
|
||||||
|
interface Template {
|
||||||
|
(options: { name: string }): { path: string, contents: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
const api: Template = ({ name }) => ({
|
||||||
|
path: `server/api/${name}.ts`,
|
||||||
|
contents: `
|
||||||
|
import { defineHandle } from 'h3'
|
||||||
|
|
||||||
|
export default defineHandle((req, res) => {
|
||||||
|
return 'Hello ${name}'
|
||||||
|
})
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const plugin: Template = ({ name }) => ({
|
||||||
|
path: `plugins/${name}.ts`,
|
||||||
|
contents: `
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {})
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const component: Template = ({ name }) => ({
|
||||||
|
path: `components/${name}.vue`,
|
||||||
|
contents: `
|
||||||
|
<script lang="ts" setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Component: ${name}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const composable: Template = ({ name }) => {
|
||||||
|
const nameWithUsePrefix = name.startsWith('use') ? name : `use${upperFirst(name)}`
|
||||||
|
return {
|
||||||
|
path: `composables/${name}.ts`,
|
||||||
|
contents: `
|
||||||
|
export const ${nameWithUsePrefix} = () => {
|
||||||
|
return ref()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const middleware: Template = ({ name }) => ({
|
||||||
|
path: `middleware/${name}.ts`,
|
||||||
|
contents: `
|
||||||
|
export default defineNuxtRouteMiddleware((to, from) => {})
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const layout: Template = ({ name }) => ({
|
||||||
|
path: `layouts/${name}.vue`,
|
||||||
|
contents: `
|
||||||
|
<script lang="ts" setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Layout: ${name}
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const page: Template = ({ name }) => ({
|
||||||
|
path: `pages/${name}.vue`,
|
||||||
|
contents: `
|
||||||
|
<script lang="ts" setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Page: foo
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
export const templates = {
|
||||||
|
api,
|
||||||
|
plugin,
|
||||||
|
component,
|
||||||
|
composable,
|
||||||
|
middleware,
|
||||||
|
layout,
|
||||||
|
page
|
||||||
|
} as Record<string, Template>
|
Loading…
Reference in New Issue
Block a user