vue-feat-cli
v26.9.1
Published
An opinionated CLI for scaffolding feature-based Vue 3 projects. Generates consistent, typed code following a layered architecture: service → composable → store → view.
Readme
vue-feat-cli
An opinionated CLI for scaffolding feature-based Vue 3 projects. Generates consistent, typed code following a layered architecture: service → composable → store → view.
Install
npm install -g vue-feat-cliOr use directly with npx:
npx vue-feat-cli initQuick start
# 1. Initialize — detects your stack and creates vf.config.json
vf init
# 2. Generate your first feature
vf g:feat Product
# Get help at any time
vf help
vf help generate:featCommands
help
Shows an overview of all commands and their descriptions. Pass a command name (or its alias) to get detailed docs with options, examples, and related commands.
vf help # overview interativo de todos os comandos
vf help generate:feat # docs detalhados de generate:feat
vf help g:feat # aliases também funcionaminit
Detects the project's installed dependencies (Pinia, Vue Router, Axios) and the TypeScript path alias, then saves vf.config.json at the project root and creates src/shared/http/client.ts.
vf initProduces vf.config.json:
{
"srcDir": "src",
"featuresDir": "src/features",
"sharedDir": "src/shared",
"alias": "@",
"httpClient": "fetch",
"usesPinia": true,
"usesVueRouter": true,
"usesTanstackQuery": false
}All subsequent generators read this file. If it doesn't exist, safe defaults are used (fetch, no Pinia, no Router).
g:feat <name>
Scaffolds a complete feature module under featuresDir/<name>/.
vf g:feat ProductGenerated structure (with Pinia + Vue Router enabled):
src/features/product/
├── composables/
│ ├── useProductService.ts # business logic (data refs, CRUD)
│ └── useProductPage.ts # UI state (loading, error) — delegates to service
├── services/
│ └── product.service.ts # HTTP layer using httpClient
├── stores/
│ └── product.store.ts # Pinia store (Composition API) or reactive() fallback
├── types/
│ └── product.types.ts # entity interface + CreateDto + UpdateDto
├── views/
│ └── ProductView.vue # page component wired to useProductPage (Vue Router only)
├── routes.ts # lazy-loaded route record (Vue Router only)
├── components/ # feature-local components (empty)
└── index.ts # public barrel exportWithout Vue Router, routes.ts and views/ are omitted and a blank views/ folder is created instead.
g:composable <name>
Creates a standalone composable inside a feature or in sharedDir/composables/.
vf g:composable useFilters --feature product
vf g:composable useTheme # → src/shared/composables/useTheme.tsg:component <name>
Creates a Vue component. Supports nested paths.
vf g:component ProductCard --feature product
vf g:component cards/ProductCardg:service <name>
Adds a service and its types file to an existing feature.
vf g:service product --feature productg:store <name>
Adds a store to an existing feature. Uses Pinia (defineStore) when usesPinia: true, falls back to reactive() otherwise.
vf g:store product --feature productArchitecture
Each feature follows a strict layered separation:
service.ts
└── useXxxService.ts ← business composable: data refs + CRUD, no UI state
└── useXxxPage.ts ← UI composable: loading + error, delegates to service
└── XxxView.vue ← page component, calls load() on mountWhy two composables?
useXxxServiceowns domain data. Can be reused (e.g. in a modal) without triggering loading spinners.useXxxPageowns UI concerns. Keeps views thin and independently testable.
The store is optional and intended for state that must be shared across features.
HTTP client
vf init generates src/shared/http/client.ts with a unified httpClient interface:
httpClient.get<T>(url)
httpClient.post<T>(url, body)
httpClient.put<T>(url, body)
httpClient.patch<T>(url, body)
httpClient.delete<T>(url)Both the fetch and axios implementations expose the same interface, so generated services work unchanged regardless of which you choose.
The base URL is read from the VITE_API_BASE_URL environment variable.
Expected project structure
src/
├── features/ # generated modules
├── shared/
│ ├── http/
│ │ └── client.ts # generated by vf init
│ └── composables/
└── main.tsThe path alias (@ by default) must point to src/ in your tsconfig.json:
{
"compilerOptions": {
"paths": { "@/*": ["./src/*"] }
}
}Custom Templates
You can override any scaffold template on a per-project basis by creating .hbs files in a local folder and pointing vf.config.json to it.
Setup
Option A — via vf init:
Answer y when asked "Use custom templates?". This sets templatesDir: ".vf/templates" in vf.config.json and creates the empty folder.
Option B — copy all defaults for editing:
vf templates:initThis copies every built-in template to .vf/templates/, ready to edit. It also adds "templatesDir": ".vf/templates" to vf.config.json if not already set.
Option C — manual:
Add "templatesDir": ".vf/templates" to your vf.config.json and create only the templates you want to override.
File structure
Your local templates must mirror the built-in structure:
.vf/templates/
├── component/
│ └── Component.vue.hbs
├── composable/
│ └── composable.ts.hbs
└── feature/
├── service.ts.hbs
├── service-crud.ts.hbs
├── service-composable.ts.hbs
├── service-composable-crud.ts.hbs
├── page-composable.ts.hbs
├── page-composable-crud.ts.hbs
├── store.ts.hbs
├── store-composable.ts.hbs
├── types.ts.hbs
├── types-crud.ts.hbs
├── index.ts.hbs
├── routes.ts.hbs
└── View.vue.hbsYou only need to include the files you want to override — missing files fall back to the built-in defaults automatically.
Handlebars context variables
| Variable | Type | Available in |
|---|---|---|
| name | string | all templates — kebab-case feature name |
| Name | string | all templates — PascalCase feature name |
| nameCamel | string | feature templates — camelCase feature name |
| alias | string | feature templates — import alias from config (e.g. @) |
| usesVueRouter | boolean | feature templates |
.gitignore recommendation
Commit .vf/templates/ to your repo so the whole team uses the same overrides:
# .gitignore — do NOT ignore .vf/templates if you want team-wide overrides
# .vf/ ← remove this line if presentIf you want the overrides to be personal only, add .vf/ to .gitignore.
Development
npm run dev # run CLI with tsx (no build step)
npm run build # compile to dist/License
MIT
