vue-feat-cli 26.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/bin/vf.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +948 -0
- package/dist/templates/component/Component.vue.hbs +18 -0
- package/dist/templates/composable/composable.ts.hbs +22 -0
- package/dist/templates/feature/View.vue.hbs +29 -0
- package/dist/templates/feature/composable.ts.hbs +28 -0
- package/dist/templates/feature/index.ts.hbs +8 -0
- package/dist/templates/feature/page-composable-crud.ts.hbs +45 -0
- package/dist/templates/feature/page-composable.ts.hbs +28 -0
- package/dist/templates/feature/routes.ts.hbs +9 -0
- package/dist/templates/feature/service-composable-crud.ts.hbs +44 -0
- package/dist/templates/feature/service-composable.ts.hbs +16 -0
- package/dist/templates/feature/service-crud.ts.hbs +26 -0
- package/dist/templates/feature/service.ts.hbs +15 -0
- package/dist/templates/feature/store-composable.ts.hbs +28 -0
- package/dist/templates/feature/store.ts.hbs +26 -0
- package/dist/templates/feature/types-crud.ts.hbs +7 -0
- package/dist/templates/feature/types.ts.hbs +4 -0
- package/dist/templates/shared/http-client-axios.ts.hbs +25 -0
- package/dist/templates/shared/http-client-fetch.ts.hbs +58 -0
- package/package.json +41 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
// TODO: define component props
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
defineProps<Props>()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<div class="{{name}}">
|
|
11
|
+
<!-- {{Name}} -->
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<style scoped>
|
|
16
|
+
.{{name}} {
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ref, computed } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function use{{Name}}() {
|
|
4
|
+
const state = ref<unknown>(null)
|
|
5
|
+
|
|
6
|
+
const isReady = computed(() => state.value !== null)
|
|
7
|
+
|
|
8
|
+
function set{{Name}}(value: unknown) {
|
|
9
|
+
state.value = value
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function reset() {
|
|
13
|
+
state.value = null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
state,
|
|
18
|
+
isReady,
|
|
19
|
+
set{{Name}},
|
|
20
|
+
reset,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted } from 'vue'
|
|
3
|
+
import { use{{Name}}Page } from '../composables/use{{Name}}Page'
|
|
4
|
+
|
|
5
|
+
const { items, loading, error, load } = use{{Name}}Page()
|
|
6
|
+
|
|
7
|
+
onMounted(load)
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<section class="{{name}}-view">
|
|
12
|
+
<h1>{{Name}}</h1>
|
|
13
|
+
|
|
14
|
+
<p v-if="loading">Loading...</p>
|
|
15
|
+
<p v-else-if="error">\{{ error }}</p>
|
|
16
|
+
|
|
17
|
+
<ul v-else>
|
|
18
|
+
<li v-for="item in items" :key="item.id">
|
|
19
|
+
\{{ item.id }}
|
|
20
|
+
</li>
|
|
21
|
+
</ul>
|
|
22
|
+
</section>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<style scoped>
|
|
26
|
+
.{{name}}-view {
|
|
27
|
+
padding: 1rem;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import { {{nameCamel}}Service } from '../services/{{name}}.service'
|
|
3
|
+
import type { {{Name}} } from '../types/{{name}}.types'
|
|
4
|
+
|
|
5
|
+
export function use{{Name}}() {
|
|
6
|
+
const items = ref<{{Name}}[]>([])
|
|
7
|
+
const loading = ref(false)
|
|
8
|
+
const error = ref<string | null>(null)
|
|
9
|
+
|
|
10
|
+
async function fetchAll() {
|
|
11
|
+
loading.value = true
|
|
12
|
+
error.value = null
|
|
13
|
+
try {
|
|
14
|
+
items.value = await {{nameCamel}}Service.getAll()
|
|
15
|
+
} catch (e) {
|
|
16
|
+
error.value = (e as Error).message
|
|
17
|
+
} finally {
|
|
18
|
+
loading.value = false
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
items,
|
|
24
|
+
loading,
|
|
25
|
+
error,
|
|
26
|
+
fetchAll,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './services/{{name}}.service'
|
|
2
|
+
export * from './composables/use{{Name}}Service'
|
|
3
|
+
export * from './composables/use{{Name}}Page'
|
|
4
|
+
export * from './stores/{{name}}.store'
|
|
5
|
+
export * from './types/{{name}}.types'
|
|
6
|
+
{{#if usesVueRouter}}
|
|
7
|
+
export * from './routes'
|
|
8
|
+
{{/if}}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import { use{{Name}}Service } from './use{{Name}}Service'
|
|
3
|
+
|
|
4
|
+
export function use{{Name}}Page() {
|
|
5
|
+
const loading = ref(false)
|
|
6
|
+
const error = ref<string | null>(null)
|
|
7
|
+
|
|
8
|
+
const { items, selected, fetchAll, fetchById, create, update, remove } = use{{Name}}Service()
|
|
9
|
+
|
|
10
|
+
async function load() {
|
|
11
|
+
loading.value = true
|
|
12
|
+
error.value = null
|
|
13
|
+
try {
|
|
14
|
+
await fetchAll()
|
|
15
|
+
} catch (e) {
|
|
16
|
+
error.value = (e as Error).message
|
|
17
|
+
} finally {
|
|
18
|
+
loading.value = false
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function loadById(id: string) {
|
|
23
|
+
loading.value = true
|
|
24
|
+
error.value = null
|
|
25
|
+
try {
|
|
26
|
+
await fetchById(id)
|
|
27
|
+
} catch (e) {
|
|
28
|
+
error.value = (e as Error).message
|
|
29
|
+
} finally {
|
|
30
|
+
loading.value = false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
items,
|
|
36
|
+
selected,
|
|
37
|
+
loading,
|
|
38
|
+
error,
|
|
39
|
+
load,
|
|
40
|
+
loadById,
|
|
41
|
+
create,
|
|
42
|
+
update,
|
|
43
|
+
remove,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import { use{{Name}}Service } from './use{{Name}}Service'
|
|
3
|
+
|
|
4
|
+
export function use{{Name}}Page() {
|
|
5
|
+
const loading = ref(false)
|
|
6
|
+
const error = ref<string | null>(null)
|
|
7
|
+
|
|
8
|
+
const { items, fetchAll } = use{{Name}}Service()
|
|
9
|
+
|
|
10
|
+
async function load() {
|
|
11
|
+
loading.value = true
|
|
12
|
+
error.value = null
|
|
13
|
+
try {
|
|
14
|
+
await fetchAll()
|
|
15
|
+
} catch (e) {
|
|
16
|
+
error.value = (e as Error).message
|
|
17
|
+
} finally {
|
|
18
|
+
loading.value = false
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
items,
|
|
24
|
+
loading,
|
|
25
|
+
error,
|
|
26
|
+
load,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import { {{nameCamel}}Service } from '../services/{{name}}.service'
|
|
3
|
+
import type { {{Name}}, Create{{Name}}Dto, Update{{Name}}Dto } from '../types/{{name}}.types'
|
|
4
|
+
|
|
5
|
+
export function use{{Name}}Service() {
|
|
6
|
+
const items = ref<{{Name}}[]>([])
|
|
7
|
+
const selected = ref<{{Name}} | null>(null)
|
|
8
|
+
|
|
9
|
+
async function fetchAll() {
|
|
10
|
+
items.value = await {{nameCamel}}Service.getAll()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function fetchById(id: string) {
|
|
14
|
+
selected.value = await {{nameCamel}}Service.getById(id)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function create(payload: Create{{Name}}Dto) {
|
|
18
|
+
const created = await {{nameCamel}}Service.create(payload)
|
|
19
|
+
items.value.push(created)
|
|
20
|
+
return created
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function update(id: string, payload: Update{{Name}}Dto) {
|
|
24
|
+
const updated = await {{nameCamel}}Service.update(id, payload)
|
|
25
|
+
const index = items.value.findIndex((i) => i.id === id)
|
|
26
|
+
if (index !== -1) items.value[index] = updated
|
|
27
|
+
return updated
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function remove(id: string) {
|
|
31
|
+
await {{nameCamel}}Service.remove(id)
|
|
32
|
+
items.value = items.value.filter((i) => i.id !== id)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
items,
|
|
37
|
+
selected,
|
|
38
|
+
fetchAll,
|
|
39
|
+
fetchById,
|
|
40
|
+
create,
|
|
41
|
+
update,
|
|
42
|
+
remove,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import { {{nameCamel}}Service } from '../services/{{name}}.service'
|
|
3
|
+
import type { {{Name}} } from '../types/{{name}}.types'
|
|
4
|
+
|
|
5
|
+
export function use{{Name}}Service() {
|
|
6
|
+
const items = ref<{{Name}}[]>([])
|
|
7
|
+
|
|
8
|
+
async function fetchAll() {
|
|
9
|
+
items.value = await {{nameCamel}}Service.getAll()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
items,
|
|
14
|
+
fetchAll,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { httpClient } from '{{alias}}/shared/http/client'
|
|
2
|
+
import type { {{Name}}, Create{{Name}}Dto, Update{{Name}}Dto } from '../types/{{name}}.types'
|
|
3
|
+
|
|
4
|
+
const RESOURCE = '/{{name}}'
|
|
5
|
+
|
|
6
|
+
export const {{nameCamel}}Service = {
|
|
7
|
+
async getAll(): Promise<{{Name}}[]> {
|
|
8
|
+
return httpClient.get<{{Name}}[]>(RESOURCE)
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
async getById(id: string): Promise<{{Name}}> {
|
|
12
|
+
return httpClient.get<{{Name}}>(`${RESOURCE}/${id}`)
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
async create(payload: Create{{Name}}Dto): Promise<{{Name}}> {
|
|
16
|
+
return httpClient.post<{{Name}}>(RESOURCE, payload)
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async update(id: string, payload: Update{{Name}}Dto): Promise<{{Name}}> {
|
|
20
|
+
return httpClient.put<{{Name}}>(`${RESOURCE}/${id}`, payload)
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async remove(id: string): Promise<void> {
|
|
24
|
+
return httpClient.delete<void>(`${RESOURCE}/${id}`)
|
|
25
|
+
},
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { httpClient } from '{{alias}}/shared/http/client'
|
|
2
|
+
import type { {{Name}} } from '../types/{{name}}.types'
|
|
3
|
+
|
|
4
|
+
const RESOURCE = '/{{name}}'
|
|
5
|
+
|
|
6
|
+
export const {{nameCamel}}Service = {
|
|
7
|
+
async getAll(): Promise<{{Name}}[]> {
|
|
8
|
+
return httpClient.get<{{Name}}[]>(RESOURCE)
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
// Example with query params:
|
|
12
|
+
// async search(query: string): Promise<{{Name}}[]> {
|
|
13
|
+
// return httpClient.get<{{Name}}[]>(RESOURCE, { params: { q: query } })
|
|
14
|
+
// },
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { reactive, readonly } from 'vue'
|
|
2
|
+
import type { {{Name}} } from '../types/{{name}}.types'
|
|
3
|
+
|
|
4
|
+
interface {{Name}}State {
|
|
5
|
+
items: {{Name}}[]
|
|
6
|
+
selected: {{Name}} | null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const state = reactive<{{Name}}State>({
|
|
10
|
+
items: [],
|
|
11
|
+
selected: null,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export function use{{Name}}Store() {
|
|
15
|
+
function setItems(items: {{Name}}[]) {
|
|
16
|
+
state.items = items
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function select(item: {{Name}}) {
|
|
20
|
+
state.selected = item
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
state: readonly(state),
|
|
25
|
+
setItems,
|
|
26
|
+
select,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
import type { {{Name}} } from '../types/{{name}}.types'
|
|
4
|
+
|
|
5
|
+
export const use{{Name}}Store = defineStore('{{nameCamel}}', () => {
|
|
6
|
+
const items = ref<{{Name}}[]>([])
|
|
7
|
+
const selected = ref<{{Name}} | null>(null)
|
|
8
|
+
|
|
9
|
+
const count = computed(() => items.value.length)
|
|
10
|
+
|
|
11
|
+
function setItems(newItems: {{Name}}[]) {
|
|
12
|
+
items.value = newItems
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function select(item: {{Name}}) {
|
|
16
|
+
selected.value = item
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
items,
|
|
21
|
+
selected,
|
|
22
|
+
count,
|
|
23
|
+
setItems,
|
|
24
|
+
select,
|
|
25
|
+
}
|
|
26
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
|
|
3
|
+
const instance = axios.create({
|
|
4
|
+
baseURL: import.meta.env.VITE_API_BASE_URL ?? '',
|
|
5
|
+
headers: {
|
|
6
|
+
'Content-Type': 'application/json',
|
|
7
|
+
},
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export const httpClient = {
|
|
11
|
+
get: async <T>(url: string, config?: Parameters<typeof instance.get>[1]) =>
|
|
12
|
+
(await instance.get<T>(url, config)).data,
|
|
13
|
+
|
|
14
|
+
post: async <T>(url: string, body?: unknown, config?: Parameters<typeof instance.post>[2]) =>
|
|
15
|
+
(await instance.post<T>(url, body, config)).data,
|
|
16
|
+
|
|
17
|
+
put: async <T>(url: string, body?: unknown, config?: Parameters<typeof instance.put>[2]) =>
|
|
18
|
+
(await instance.put<T>(url, body, config)).data,
|
|
19
|
+
|
|
20
|
+
patch: async <T>(url: string, body?: unknown, config?: Parameters<typeof instance.patch>[2]) =>
|
|
21
|
+
(await instance.patch<T>(url, body, config)).data,
|
|
22
|
+
|
|
23
|
+
delete: async <T>(url: string, config?: Parameters<typeof instance.delete>[1]) =>
|
|
24
|
+
(await instance.delete<T>(url, config)).data,
|
|
25
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? ''
|
|
2
|
+
|
|
3
|
+
interface RequestOptions extends RequestInit {
|
|
4
|
+
params?: Record<string, string | number | boolean>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function buildUrl(path: string, params?: RequestOptions['params']) {
|
|
8
|
+
const full = path.startsWith('http') ? path : `${BASE_URL}${path}`
|
|
9
|
+
if (!params) return full
|
|
10
|
+
|
|
11
|
+
// Avoid `new URL()` here: it throws on a relative `full` (e.g. "/job") when
|
|
12
|
+
// BASE_URL is unset, which is the default right after scaffolding.
|
|
13
|
+
const query = new URLSearchParams(
|
|
14
|
+
Object.entries(params).map(([key, value]) => [key, String(value)]),
|
|
15
|
+
).toString()
|
|
16
|
+
|
|
17
|
+
if (!query) return full
|
|
18
|
+
return `${full}${full.includes('?') ? '&' : '?'}${query}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
|
22
|
+
const { params, ...init } = options
|
|
23
|
+
|
|
24
|
+
const response = await fetch(buildUrl(path, params), {
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
...init.headers,
|
|
28
|
+
},
|
|
29
|
+
...init,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (response.status === 204) {
|
|
37
|
+
return undefined as T
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return response.json() as Promise<T>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const httpClient = {
|
|
44
|
+
get: <T>(path: string, options?: RequestOptions) =>
|
|
45
|
+
request<T>(path, { ...options, method: 'GET' }),
|
|
46
|
+
|
|
47
|
+
post: <T>(path: string, body?: unknown, options?: RequestOptions) =>
|
|
48
|
+
request<T>(path, { ...options, method: 'POST', body: JSON.stringify(body) }),
|
|
49
|
+
|
|
50
|
+
put: <T>(path: string, body?: unknown, options?: RequestOptions) =>
|
|
51
|
+
request<T>(path, { ...options, method: 'PUT', body: JSON.stringify(body) }),
|
|
52
|
+
|
|
53
|
+
patch: <T>(path: string, body?: unknown, options?: RequestOptions) =>
|
|
54
|
+
request<T>(path, { ...options, method: 'PATCH', body: JSON.stringify(body) }),
|
|
55
|
+
|
|
56
|
+
delete: <T>(path: string, options?: RequestOptions) =>
|
|
57
|
+
request<T>(path, { ...options, method: 'DELETE' }),
|
|
58
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-feat-cli",
|
|
3
|
+
"version": "26.9.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/cli.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"bin"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"test:coverage": "vitest run --coverage",
|
|
14
|
+
"dev": "tsx src/cli.ts",
|
|
15
|
+
"build": "tsup src/cli.ts --format esm --dts",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"bin": {
|
|
23
|
+
"vf": "bin/vf.js"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@clack/prompts": "^1.5.1",
|
|
27
|
+
"cac": "^7.0.0",
|
|
28
|
+
"fs-extra": "^11.3.5",
|
|
29
|
+
"handlebars": "^4.7.9",
|
|
30
|
+
"prettier": "^3.8.4"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/fs-extra": "^11.0.4",
|
|
34
|
+
"@types/node": "^25.9.3",
|
|
35
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
36
|
+
"tsup": "^8.5.1",
|
|
37
|
+
"tsx": "^4.22.4",
|
|
38
|
+
"typescript": "^6.0.3",
|
|
39
|
+
"vitest": "^5.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|