@gaonjs/cli 0.24.0 → 0.26.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/dist/commands/test.js +70 -0
- package/dist/db/migrate.d.ts +3 -0
- package/dist/db/migrate.js +1 -0
- package/dist/db/resolve.d.ts +7 -1
- package/dist/db/resolve.js +5 -1
- package/dist/doctor/page-layout-breakpoint.d.ts +8 -0
- package/dist/doctor/page-layout-breakpoint.js +94 -0
- package/dist/doctor/types.d.ts +1 -1
- package/dist/doctor/ui-kit-wiring.js +11 -7
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +6 -1
- package/dist/index.js +2 -2
- package/dist/templates/auth/Dashboard.vue.tpl +7 -7
- package/dist/templates/auth/Login.vue.tpl +11 -11
- package/dist/templates/auth/Signup.vue.tpl +11 -11
- package/dist/templates/auth/app.ts.tpl +29 -0
- package/dist/templates/auth/server.ts.tpl +14 -0
- package/dist/templates/project/AGENTS.md.tpl +3 -2
- package/dist/templates/project/agents/data.md.tpl +39 -2
- package/dist/templates/project/agents/frontend.md.tpl +73 -26
- package/dist/templates/project/agents/testing.md.tpl +53 -6
- package/dist/templates/project/agents/web.md.tpl +42 -3
- package/dist/templates/project/apps/web/pages/Home/Index.vue.tpl +3 -3
- package/dist/templates/project/apps/web/style.css.tpl +62 -41
- package/dist/templates/project/test/setup.ts.tpl +24 -0
- package/dist/templates/project/tsconfig.json.tpl +5 -1
- package/dist/templates/project/vite.config.ts.tpl +16 -0
- package/dist/templates/project/vitest.config.ts.tpl +23 -0
- package/dist/templates/ui-kit/EmptyState.vue.tpl +23 -0
- package/dist/templates/ui-kit/PageHeader.vue.tpl +25 -0
- package/dist/templates/ui-kit/PageShell.vue.tpl +27 -0
- package/dist/templates/ui-kit/Pagination.vue.tpl +60 -0
- package/dist/templates/ui-kit/utils.ts.tpl +1 -1
- package/dist/uikit.d.ts +4 -4
- package/dist/uikit.js +56 -22
- package/package.json +5 -5
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// UI 킷 블록 · Pagination (결정 106). 페이지 이동 컨트롤. 라우트를 모르는 순수 UI —
|
|
3
|
+
// 현재 페이지를 v-model(update:page)로 올려보내고, 실제 이동(Link·router)은 페이지가
|
|
4
|
+
// 정한다(shared 순수성 · 결정 25). 모바일은 압축형(이전/다음 + "n / m"), sm 이상에선
|
|
5
|
+
// 현재 주변 번호를 함께 보인다(반응형은 블록 책임 · 결정 107). 버튼은 모바일 44px·
|
|
6
|
+
// 데스크톱 40px 로 터치 타깃을 확보한다(결정 107).
|
|
7
|
+
import { computed } from 'vue'
|
|
8
|
+
import { cn } from '../../lib/utils.js'
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(
|
|
11
|
+
defineProps<{ page: number; pageCount: number; siblings?: number }>(),
|
|
12
|
+
{ siblings: 1 },
|
|
13
|
+
)
|
|
14
|
+
const emit = defineEmits<{ (e: 'update:page', page: number): void }>()
|
|
15
|
+
|
|
16
|
+
const total = computed(() => Math.max(1, props.pageCount))
|
|
17
|
+
const clamped = computed(() => Math.min(Math.max(1, props.page), total.value))
|
|
18
|
+
const canPrev = computed(() => clamped.value > 1)
|
|
19
|
+
const canNext = computed(() => clamped.value < total.value)
|
|
20
|
+
|
|
21
|
+
// 현재 주변 번호 창(sm 이상): [현재-siblings, 현재+siblings]를 1..total 로 자른다.
|
|
22
|
+
const windowPages = computed<number[]>(() => {
|
|
23
|
+
const from = Math.max(1, clamped.value - props.siblings)
|
|
24
|
+
const to = Math.min(total.value, clamped.value + props.siblings)
|
|
25
|
+
const out: number[] = []
|
|
26
|
+
for (let p = from; p <= to; p++) out.push(p)
|
|
27
|
+
return out
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
function go(p: number): void {
|
|
31
|
+
const next = Math.min(Math.max(1, p), total.value)
|
|
32
|
+
if (next !== clamped.value) emit('update:page', next)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const BTN =
|
|
36
|
+
'inline-flex h-11 min-w-11 sm:h-10 sm:min-w-10 items-center justify-center rounded-md border ' +
|
|
37
|
+
'px-3 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground ' +
|
|
38
|
+
'disabled:pointer-events-none disabled:opacity-50'
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<nav class="flex items-center justify-between gap-2 sm:justify-center" aria-label="페이지 이동">
|
|
43
|
+
<button type="button" :class="BTN" :disabled="!canPrev" @click="go(clamped - 1)">이전</button>
|
|
44
|
+
|
|
45
|
+
<span class="text-sm text-muted-foreground sm:hidden">{{ clamped }} / {{ total }}</span>
|
|
46
|
+
|
|
47
|
+
<span class="hidden items-center gap-1 sm:inline-flex">
|
|
48
|
+
<button
|
|
49
|
+
v-for="p in windowPages"
|
|
50
|
+
:key="p"
|
|
51
|
+
type="button"
|
|
52
|
+
:class="cn(BTN, p === clamped && 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground')"
|
|
53
|
+
:aria-current="p === clamped ? 'page' : undefined"
|
|
54
|
+
@click="go(p)"
|
|
55
|
+
>{{ p }}</button>
|
|
56
|
+
</span>
|
|
57
|
+
|
|
58
|
+
<button type="button" :class="BTN" :disabled="!canNext" @click="go(clamped + 1)">다음</button>
|
|
59
|
+
</nav>
|
|
60
|
+
</template>
|
package/dist/uikit.d.ts
CHANGED
|
@@ -11,10 +11,10 @@ export interface UiKitScaffoldResult {
|
|
|
11
11
|
readonly created: string[];
|
|
12
12
|
readonly skipped: string[];
|
|
13
13
|
}
|
|
14
|
-
/** UI 킷 전체가 생성하는 파일
|
|
15
|
-
export declare function uiKitScaffoldFiles(
|
|
16
|
-
/** `gaon g auth` 가 필요로 하는 UI 킷 최소 세트 파일
|
|
17
|
-
export declare function authUiKitFiles(
|
|
14
|
+
/** UI 킷 전체가 생성하는 파일 목록(shared/ · 프로젝트당 한 벌). */
|
|
15
|
+
export declare function uiKitScaffoldFiles(_opts?: UiKitScaffoldOptions): UiKitScaffoldFile[];
|
|
16
|
+
/** `gaon g auth` 가 필요로 하는 UI 킷 최소 세트 파일 목록(shared/). */
|
|
17
|
+
export declare function authUiKitFiles(_app?: string): UiKitScaffoldFile[];
|
|
18
18
|
/** 파일 목록을 디스크에 쓴다. 기존 파일은 덮어쓰지 않고 skip(멱등 · 복사-소유). */
|
|
19
19
|
export declare function writeUiKitFiles(cwd: string, files: readonly UiKitScaffoldFile[]): UiKitScaffoldResult;
|
|
20
20
|
/** `gaon g ui-kit` — UI 킷 전체를 쓴다. */
|
package/dist/uikit.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @gaonjs/cli · `gaon g ui-kit` — shadcn 식 UI 킷 제너레이터 (결정 75)
|
|
2
|
+
* @gaonjs/cli · `gaon g ui-kit` — shadcn 식 UI 킷 제너레이터 (결정 75 · 결정 105 개정)
|
|
3
3
|
*
|
|
4
4
|
* shadcn 을 참조해 Vue 3 로 새로 쓴 UI 컴포넌트를 프로젝트에 **복사**한다
|
|
5
5
|
* (복사-소유 — 생성된 뒤엔 사용자 코드다). npm 의존이 아니라 파일로 심으므로
|
|
6
6
|
* 자유롭게 고칠 수 있고, 외부 런타임 의존이 전혀 없다(cn 은 자작 · reka-ui·
|
|
7
7
|
* clsx·cva 미도입 · 결정 75). Tailwind(결정 74)를 전제로 한다.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* 배치(결정 105 — 결정 75 의 "앱마다 복사" 개정):
|
|
10
|
+
* shared/lib/utils.ts — cn() 헬퍼 (프로젝트당 한 벌)
|
|
11
|
+
* shared/components/ui/*.vue — Button·Input·Card·Form·Dialog … (전 앱 공용 순수 UI)
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* shared 에 두는 이유(결정 105): UI 킷은 성격 중립 순수 UI 라 앱마다 복제하지
|
|
14
|
+
* 않고 프로젝트당 한 벌만 둔다 — 앱 컴포넌트는 이 킷을 조합·확장한다. 앱 페이지는
|
|
15
|
+
* `@shared/components/ui/…` alias 로 참조한다(vite.config.ts·tsconfig.json 에 배선).
|
|
16
|
+
* 킷 내부 import(cn·하위 컴포넌트)는 상대 경로(`../../lib/utils.js`·`./Label.vue`)라
|
|
17
|
+
* 위치가 apps/<app> 에서 shared 로 옮겨져도 그대로 유효하다.
|
|
18
|
+
*
|
|
19
|
+
* --app 은 이제 킷 위치가 아니라 대상 앱의 Tailwind 배선(style.css·main.ts)을
|
|
20
|
+
* 멱등 보정하는 데만 쓰인다 — 킷은 앱과 무관하게 shared 로 간다.
|
|
16
21
|
*/
|
|
17
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
18
23
|
import { dirname, join, resolve } from 'node:path';
|
|
19
24
|
import { fileURLToPath } from 'node:url';
|
|
20
25
|
import { appWiringFiles, readProjectName } from './scaffold/app-wiring.js';
|
|
@@ -39,6 +44,11 @@ const COMPONENTS = [
|
|
|
39
44
|
'FormMessage',
|
|
40
45
|
'Dialog',
|
|
41
46
|
'Sheet',
|
|
47
|
+
// 블록(결정 106) — 성격 중립 페이지 조각. 원자를 조합해 페이지 골격을 만든다.
|
|
48
|
+
'PageShell',
|
|
49
|
+
'PageHeader',
|
|
50
|
+
'EmptyState',
|
|
51
|
+
'Pagination',
|
|
42
52
|
];
|
|
43
53
|
/** `gaon g auth` 가 스캐폴드하는 로그인·가입·대시보드 페이지가 쓰는 최소 세트.
|
|
44
54
|
* auth 생성 시 이 컴포넌트가 없으면 페이지가 컴파일되지 않으므로 함께 보장한다. */
|
|
@@ -62,26 +72,26 @@ const AUTH_SUBSET = [
|
|
|
62
72
|
function read(name) {
|
|
63
73
|
return readFileSync(join(TEMPLATE_DIR, name), 'utf8');
|
|
64
74
|
}
|
|
65
|
-
/** 컴포넌트명 목록 → 생성 파일 목록(utils 포함). */
|
|
66
|
-
function filesFor(components
|
|
75
|
+
/** 컴포넌트명 목록 → 생성 파일 목록(utils 포함). 위치는 shared/ 고정(결정 105). */
|
|
76
|
+
function filesFor(components) {
|
|
67
77
|
const files = [
|
|
68
|
-
{ path: `
|
|
78
|
+
{ path: `shared/lib/utils.ts`, contents: read('utils.ts.tpl') },
|
|
69
79
|
];
|
|
70
80
|
for (const name of components) {
|
|
71
81
|
files.push({
|
|
72
|
-
path: `
|
|
82
|
+
path: `shared/components/ui/${name}.vue`,
|
|
73
83
|
contents: read(`${name}.vue.tpl`),
|
|
74
84
|
});
|
|
75
85
|
}
|
|
76
86
|
return files;
|
|
77
87
|
}
|
|
78
|
-
/** UI 킷 전체가 생성하는 파일
|
|
79
|
-
export function uiKitScaffoldFiles(
|
|
80
|
-
return filesFor(COMPONENTS
|
|
88
|
+
/** UI 킷 전체가 생성하는 파일 목록(shared/ · 프로젝트당 한 벌). */
|
|
89
|
+
export function uiKitScaffoldFiles(_opts = {}) {
|
|
90
|
+
return filesFor(COMPONENTS);
|
|
81
91
|
}
|
|
82
|
-
/** `gaon g auth` 가 필요로 하는 UI 킷 최소 세트 파일
|
|
83
|
-
export function authUiKitFiles(
|
|
84
|
-
return filesFor(AUTH_SUBSET
|
|
92
|
+
/** `gaon g auth` 가 필요로 하는 UI 킷 최소 세트 파일 목록(shared/). */
|
|
93
|
+
export function authUiKitFiles(_app = 'web') {
|
|
94
|
+
return filesFor(AUTH_SUBSET);
|
|
85
95
|
}
|
|
86
96
|
/** 파일 목록을 디스크에 쓴다. 기존 파일은 덮어쓰지 않고 skip(멱등 · 복사-소유). */
|
|
87
97
|
export function writeUiKitFiles(cwd, files) {
|
|
@@ -118,11 +128,12 @@ export function runGenerateUiKitCommand(opts = {}) {
|
|
|
118
128
|
skipped: [...wiring.skipped, ...kit.skipped].sort(),
|
|
119
129
|
};
|
|
120
130
|
if (opts.json) {
|
|
121
|
-
|
|
131
|
+
const legacy = legacyUiKitApps(cwd);
|
|
132
|
+
process.stdout.write(JSON.stringify({ command: 'g ui-kit', app, ...result, legacyUiKitApps: legacy }, null, 2) + '\n');
|
|
122
133
|
return 0;
|
|
123
134
|
}
|
|
124
135
|
const lines = [''];
|
|
125
|
-
lines.push(` gaon g ui-kit — UI 킷 (
|
|
136
|
+
lines.push(` gaon g ui-kit — UI 킷 (shared/ · shadcn 참조 · 복사-소유)`);
|
|
126
137
|
lines.push('');
|
|
127
138
|
for (const f of result.created)
|
|
128
139
|
lines.push(` + ${f}`);
|
|
@@ -130,9 +141,32 @@ export function runGenerateUiKitCommand(opts = {}) {
|
|
|
130
141
|
lines.push(` · ${f} (이미 있음 — 건너뜀)`);
|
|
131
142
|
lines.push('');
|
|
132
143
|
lines.push(' Tailwind 배선(tailwind.config.ts·postcss.config.js·apps/' + app + '/style.css)이 함께 보정됩니다.');
|
|
133
|
-
lines.push(' 컴포넌트는 이제 여러분 코드입니다 —
|
|
134
|
-
lines.push(' 예) import Button from \'
|
|
144
|
+
lines.push(' 컴포넌트는 이제 여러분 코드입니다 — shared/components/ui/ 에서 자유롭게 고치세요.');
|
|
145
|
+
lines.push(' 예) import Button from \'@shared/components/ui/Button.vue\'');
|
|
146
|
+
const legacy = legacyUiKitApps(cwd);
|
|
147
|
+
if (legacy.length > 0) {
|
|
148
|
+
lines.push('');
|
|
149
|
+
lines.push(' ⚠ 구버전 UI 킷 사본이 남아 있습니다(결정 105 이전 · 앱별 배치):');
|
|
150
|
+
for (const app of legacy)
|
|
151
|
+
lines.push(` apps/${app}/components/ui/ · apps/${app}/lib/utils.ts`);
|
|
152
|
+
lines.push(' → shared/ 로 옮겼으니 앱 사본을 지우고 import 를 @shared 로 바꾸세요:');
|
|
153
|
+
lines.push(" import X from '../../components/ui/X.vue' → import X from '@shared/components/ui/X.vue'");
|
|
154
|
+
}
|
|
135
155
|
lines.push('');
|
|
136
156
|
process.stdout.write(lines.join('\n') + '\n');
|
|
137
157
|
return 0;
|
|
138
158
|
}
|
|
159
|
+
/** 결정 105 이전 배치(apps/<app>/components/ui)를 아직 가진 앱 목록 — 마이그레이션 안내용. */
|
|
160
|
+
function legacyUiKitApps(cwd) {
|
|
161
|
+
const appsDir = join(cwd, 'apps');
|
|
162
|
+
let apps;
|
|
163
|
+
try {
|
|
164
|
+
apps = readdirSync(appsDir, { withFileTypes: true })
|
|
165
|
+
.filter((e) => e.isDirectory())
|
|
166
|
+
.map((e) => e.name);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
return apps.filter((app) => existsSync(join(appsDir, app, 'components', 'ui'))).sort();
|
|
172
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaonjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Gaon CLI 구현: 제너레이터·스캐폴딩·로드맵 출력 (M1 스텁)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"typescript": "^5.9.0",
|
|
29
29
|
"vite": "^7.0.0",
|
|
30
30
|
"@gaonjs/async": "0.6.1",
|
|
31
|
-
"@gaonjs/
|
|
32
|
-
"@gaonjs/config": "0.5.3",
|
|
31
|
+
"@gaonjs/config": "0.6.0",
|
|
33
32
|
"@gaonjs/core": "0.2.1",
|
|
34
|
-
"@gaonjs/
|
|
35
|
-
"@gaonjs/data": "0.
|
|
33
|
+
"@gaonjs/web": "0.8.0",
|
|
34
|
+
"@gaonjs/data": "0.11.0",
|
|
35
|
+
"@gaonjs/mail": "0.1.3"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json && node -e \"require('fs').cpSync('src/templates','dist/templates',{recursive:true})\""
|