@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.
Files changed (36) hide show
  1. package/dist/commands/test.js +70 -0
  2. package/dist/db/migrate.d.ts +3 -0
  3. package/dist/db/migrate.js +1 -0
  4. package/dist/db/resolve.d.ts +7 -1
  5. package/dist/db/resolve.js +5 -1
  6. package/dist/doctor/page-layout-breakpoint.d.ts +8 -0
  7. package/dist/doctor/page-layout-breakpoint.js +94 -0
  8. package/dist/doctor/types.d.ts +1 -1
  9. package/dist/doctor/ui-kit-wiring.js +11 -7
  10. package/dist/doctor.d.ts +1 -0
  11. package/dist/doctor.js +6 -1
  12. package/dist/index.js +2 -2
  13. package/dist/templates/auth/Dashboard.vue.tpl +7 -7
  14. package/dist/templates/auth/Login.vue.tpl +11 -11
  15. package/dist/templates/auth/Signup.vue.tpl +11 -11
  16. package/dist/templates/auth/app.ts.tpl +29 -0
  17. package/dist/templates/auth/server.ts.tpl +14 -0
  18. package/dist/templates/project/AGENTS.md.tpl +3 -2
  19. package/dist/templates/project/agents/data.md.tpl +39 -2
  20. package/dist/templates/project/agents/frontend.md.tpl +73 -26
  21. package/dist/templates/project/agents/testing.md.tpl +53 -6
  22. package/dist/templates/project/agents/web.md.tpl +42 -3
  23. package/dist/templates/project/apps/web/pages/Home/Index.vue.tpl +3 -3
  24. package/dist/templates/project/apps/web/style.css.tpl +62 -41
  25. package/dist/templates/project/test/setup.ts.tpl +24 -0
  26. package/dist/templates/project/tsconfig.json.tpl +5 -1
  27. package/dist/templates/project/vite.config.ts.tpl +16 -0
  28. package/dist/templates/project/vitest.config.ts.tpl +23 -0
  29. package/dist/templates/ui-kit/EmptyState.vue.tpl +23 -0
  30. package/dist/templates/ui-kit/PageHeader.vue.tpl +25 -0
  31. package/dist/templates/ui-kit/PageShell.vue.tpl +27 -0
  32. package/dist/templates/ui-kit/Pagination.vue.tpl +60 -0
  33. package/dist/templates/ui-kit/utils.ts.tpl +1 -1
  34. package/dist/uikit.d.ts +4 -4
  35. package/dist/uikit.js +56 -22
  36. 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>
@@ -1,4 +1,4 @@
1
- // apps/<app>/lib/utils.ts — UI 킷 공용 헬퍼 (결정 75 · shadcn 참조 · 복사-소유).
1
+ // shared/lib/utils.ts — UI 킷 공용 헬퍼 (결정 75 · 결정 105 · shadcn 참조 · 복사-소유).
2
2
  //
3
3
  // UI 킷은 외부 런타임 의존 없이 자기완결이다(gaon g ui-kit 로 프로젝트에
4
4
  // 복사된 뒤엔 이 파일도 여러분 코드다 — 자유롭게 고친다).
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(opts?: UiKitScaffoldOptions): UiKitScaffoldFile[];
16
- /** `gaon g auth` 가 필요로 하는 UI 킷 최소 세트 파일 목록. */
17
- export declare function authUiKitFiles(app?: string): UiKitScaffoldFile[];
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
- * apps/<app>/lib/utils.ts — cn() 헬퍼
11
- * apps/<app>/components/ui/*.vue — Button·Input·Card·Form·Dialog … (앱 전용 순수 UI)
9
+ * 배치(결정 105 — 결정 75 의 "앱마다 복사" 개정):
10
+ * shared/lib/utils.ts — cn() 헬퍼 (프로젝트당 한 벌)
11
+ * shared/components/ui/*.vue — Button·Input·Card·Form·Dialog … (공용 순수 UI)
12
12
  *
13
- * 전용에 두는 이유: 컴포넌트에서 lib/utils 가는 import 얕게 유지해
14
- * (`../../lib/utils.js`) AI·사람 모두 실수 없이 참조하게 하고, `gaon g auth`
15
- * 처럼 단위(--app)생성한다. 여러 앱이 각자 소유한다(복사-소유 관례).
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, app) {
75
+ /** 컴포넌트명 목록 → 생성 파일 목록(utils 포함). 위치는 shared/ 고정(결정 105). */
76
+ function filesFor(components) {
67
77
  const files = [
68
- { path: `apps/${app}/lib/utils.ts`, contents: read('utils.ts.tpl') },
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: `apps/${app}/components/ui/${name}.vue`,
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(opts = {}) {
80
- return filesFor(COMPONENTS, opts.app ?? 'web');
88
+ /** UI 킷 전체가 생성하는 파일 목록(shared/ · 프로젝트당 한 벌). */
89
+ export function uiKitScaffoldFiles(_opts = {}) {
90
+ return filesFor(COMPONENTS);
81
91
  }
82
- /** `gaon g auth` 가 필요로 하는 UI 킷 최소 세트 파일 목록. */
83
- export function authUiKitFiles(app = 'web') {
84
- return filesFor(AUTH_SUBSET, app);
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
- process.stdout.write(JSON.stringify({ command: 'g ui-kit', app, ...result }, null, 2) + '\n');
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 킷 (${app} · shadcn 참조 · 복사-소유)`);
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(' 컴포넌트는 이제 여러분 코드입니다 — apps/' + app + '/components/ui/ 에서 자유롭게 고치세요.');
134
- lines.push(' 예) import Button from \'../../components/ui/Button.vue\'');
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.24.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/web": "0.7.2",
32
- "@gaonjs/config": "0.5.3",
31
+ "@gaonjs/config": "0.6.0",
33
32
  "@gaonjs/core": "0.2.1",
34
- "@gaonjs/mail": "0.1.3",
35
- "@gaonjs/data": "0.9.2"
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})\""