memi-board 0.9.1 → 0.10.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/README.md CHANGED
@@ -14,7 +14,7 @@ Nuxt 4 + Nuxt UI + Firebase 프로젝트에 **클라이언트 전용 게시판**
14
14
 
15
15
  | 항목 | 요구 |
16
16
  |------|------|
17
- | 런타임 | Nuxt **4+** (3.x peer 는 허용하나 playground/검증은 4 기준) |
17
+ | 런타임 | Nuxt **4+** |
18
18
  | UI | `@nuxt/ui` **4+** (컴포넌트가 `UButton` 등 auto-import 전제) |
19
19
  | 데이터 | Firebase **Firestore + Auth + Storage** |
20
20
  | Vue | `vue` 3.5+, `vuefire` 3.2+, `firebase` 12+ |
@@ -22,21 +22,20 @@ Nuxt 4 + Nuxt UI + Firebase 프로젝트에 **클라이언트 전용 게시판**
22
22
 
23
23
  **의도적으로 하지 않는 일:** Firebase 초기화, 라우트/미들웨어 등록, App Check 초기화, 서버 검열.
24
24
 
25
- ## 구조 (0.4+)
25
+ ## 구조
26
26
 
27
27
  | 엔트리 | 역할 |
28
28
  |--------|------|
29
- | `memi-board` | **core** — `configureMemiBoard`, composables, types (**Vue 컴포넌트 export 없음**) |
30
- | `memi-board/nuxt` | **thin Nuxt 모듈** — SFC를 호스트 Vite에 등록 → `MemiBoardList` 등 auto-import |
29
+ | `memi-board` | **Nuxt 모듈** — 설정, SFC auto-import, peer dedupe, `dayjs` 개발 서버 최적화 |
30
+ | `memi-board/runtime` | composables, types, 유틸리티 (**Vue 컴포넌트 export 없음**) |
31
31
 
32
- 런타임 설정은 모듈 options가 아니라 호스트 플러그인의 `configureMemiBoard()` 한곳.
32
+ 일반 사용자는 `nuxt.config.ts`의 `memiBoard`만 설정한다. 별도 Nuxt 플러그인은 필요 없다.
33
33
 
34
34
  ```
35
35
  호스트 Nuxt 앱
36
36
  ├── nuxt-vuefire + firebaseConfig ← 인증·Firestore
37
37
  ├── (선택) App Check 플러그인 ← AI Logic Enforce 시 필수에 가까움
38
- ├── memi-board/nuxt ← UI 등록
39
- ├── plugins/memi-board.ts ← configureMemiBoard
38
+ ├── memi-board ← Nuxt 모듈 + 런타임 설정
40
39
  ├── pages/board/* ← 라우트는 호스트 소유
41
40
  └── firestore.rules + storage.rules ← docs 예시 병합 후 배포
42
41
  ```
@@ -46,10 +45,10 @@ Nuxt 4 + Nuxt UI + Firebase 프로젝트에 **클라이언트 전용 게시판**
46
45
  **사전 준비:** Node + pnpm, Firebase 프로젝트(Firestore·Storage·Auth). 처음부터면 → [docs/firebase-setup.md](docs/firebase-setup.md).
47
46
 
48
47
  ```bash
49
- pnpm add memi-board @nuxt/ui nuxt-vuefire vuefire firebase
48
+ pnpm add memi-board @nuxt/ui nuxt-vuefire vuefire firebase dayjs
50
49
  ```
51
50
 
52
- `vuefire` / `firebase` / `nuxt-vuefire` 는 **호스트에 직접 설치** — peer 이중 인스턴스가 나면 인증·Firestore가 깨진다.
51
+ `vuefire` / `firebase` / `nuxt-vuefire` / `dayjs`는 **호스트에 직접 설치**한다. TipTap은 `@nuxt/ui`와 `memi-board`가 관리하므로 호스트에서 별도로 설치하거나 버전을 맞출 필요가 없다.
53
52
 
54
53
  ### 1. modules + vuefire
55
54
 
@@ -59,8 +58,21 @@ export default defineNuxtConfig({
59
58
  modules: [
60
59
  '@nuxt/ui',
61
60
  'nuxt-vuefire',
62
- 'memi-board/nuxt',
61
+ 'memi-board',
63
62
  ],
63
+ memiBoard: {
64
+ // Firestore: {prefix}Posts, {prefix}Users, {prefix}Settings
65
+ collectionPrefix: 'board',
66
+ auth: { providers: ['google', 'apple'] }, // + 'emailPassword'
67
+ moderation: {
68
+ enabled: true,
69
+ model: 'gemini-3.5-flash-lite',
70
+ onError: 'block', // 'allow' | 'block'
71
+ moderateImages: false,
72
+ useLimitedUseAppCheckTokens: false,
73
+ // localBlocklist: ['금칙어'],
74
+ },
75
+ },
64
76
  vuefire: {
65
77
  // 콘솔 → 프로젝트 설정 → 내 앱 → 웹 앱 SDK snippet 과 동일해야 함
66
78
  // (appId 가 다른 웹 앱이면 App Check / AI Logic 이 실패한다)
@@ -74,33 +86,18 @@ export default defineNuxtConfig({
74
86
  },
75
87
  auth: { enabled: true },
76
88
  },
89
+ routeRules: {
90
+ // 게시판은 Firebase Auth/Firestore를 사용하는 클라이언트 전용 화면이다.
91
+ '/board/**': { ssr: false },
92
+ '/board-settings': { ssr: false },
93
+ '/board-users': { ssr: false },
94
+ },
77
95
  })
78
96
  ```
79
97
 
80
- ### 2. configureMemiBoard
98
+ `memiBoard` 옵션은 Nuxt 모듈이 런타임 플러그인으로 자동 전달한다. `app/plugins/memi-board.ts`를 만들지 않는다.
81
99
 
82
- ```ts
83
- // app/plugins/memi-board.ts
84
- import { configureMemiBoard } from 'memi-board'
85
-
86
- export default defineNuxtPlugin(() => {
87
- configureMemiBoard({
88
- // Firestore: {prefix}Posts, {prefix}Users, {prefix}Settings
89
- collectionPrefix: 'board',
90
- auth: { providers: ['google', 'apple'] }, // + 'emailPassword'
91
- moderation: {
92
- enabled: true,
93
- model: 'gemini-3.5-flash-lite', // 권장. 2.5-flash 는 신규 프로젝트에서 미제공일 수 있음
94
- onError: 'block', // 'allow' | 'block' — AI 실패 시 저장 허용/거부 (기본 allow)
95
- moderateImages: false,
96
- useLimitedUseAppCheckTokens: false, // App Check 세션 토큰으로도 가능
97
- // localBlocklist: ['금칙어'],
98
- },
99
- })
100
- })
101
- ```
102
-
103
- ### 3. Tailwind `@source` (필수)
100
+ ### 2. Tailwind `@source` (필수)
104
101
 
105
102
  패키지 SFC 안의 클래스가 purge되지 않도록:
106
103
 
@@ -111,7 +108,7 @@ export default defineNuxtPlugin(() => {
111
108
  @source "../../../node_modules/memi-board/dist/runtime/components";
112
109
  ```
113
110
 
114
- ### 4. Security Rules + 인덱스 배포
111
+ ### 3. Security Rules + 인덱스 배포
115
112
 
116
113
  1. [docs/firestore.rules.example](docs/firestore.rules.example) → 호스트 `firestore.rules`에 병합
117
114
  (`memiBoard*` 이름을 쓰는 `collectionPrefix`에 맞게 치환)
@@ -145,15 +142,29 @@ export default defineNuxtPlugin(() => {
145
142
 
146
143
  > ⚠️ npm 패키지 tarball에도 `docs/` 가 포함된다(0.4.7+). 구버전이면 GitHub의 `docs/`를 보면 된다.
147
144
 
145
+ ### 4. 최초 관리자와 카테고리 만들기
146
+
147
+ Rules는 일반 사용자가 스스로 관리자가 되는 것을 막는다. 따라서 최초 한 번은 다음 순서가 필요하다.
148
+
149
+ 1. 아래 게시판 페이지를 만든 뒤 Google 또는 Apple로 로그인한다.
150
+ 2. Firebase Console → Firestore에서 `{prefix}Users/{uid}` 문서를 찾는다.
151
+ 3. 해당 문서의 `role`을 문자열 `admin`으로 변경한다.
152
+ - `collectionPrefix: 'board'`라면 `boardUsers/{uid}`
153
+ - UID는 Firebase Console → Authentication → Users에서도 확인할 수 있다.
154
+ 4. `/board-settings`에서 첫 카테고리를 만든다. 카테고리가 없으면 글을 작성할 수 없다.
155
+ 5. 이후 역할 변경은 `/board-users`의 `MemiBoardUsers`에서 처리한다.
156
+
157
+ > 최초 관리자 지정은 신뢰할 수 있는 프로젝트 운영자가 Firebase Console에서만 수행한다. 클라이언트 코드에 관리자 UID나 우회 규칙을 넣지 않는다.
158
+
148
159
  ## 사용법
149
160
 
150
161
  라우팅·미들웨어는 **호스트 소유**. 컴포넌트는 모듈 auto-import (`MemiBoardList` 등).
151
- `import { MemiBoardEditor } from 'memi-board'` 는 **안 된다** — core 엔트리는 composable/types 만 export.
162
+ `import { MemiBoardEditor } from 'memi-board/runtime'`는 **안 된다**. 컴포넌트는 모듈이 auto-import하고, composable과 type만 `memi-board/runtime`에서 명시적으로 가져올 수 있다.
152
163
 
153
164
  ```vue
154
165
  <!-- app/pages/board/index.vue -->
155
166
  <script setup lang="ts">
156
- import { useMemiBoardAuth } from 'memi-board'
167
+ import { useMemiBoardAuth } from 'memi-board/runtime'
157
168
  const { isSignedIn } = useMemiBoardAuth()
158
169
  </script>
159
170
 
@@ -161,7 +172,10 @@ const { isSignedIn } = useMemiBoardAuth()
161
172
  <div>
162
173
  <UButton v-if="isSignedIn" to="/board/new" label="새 글쓰기" />
163
174
  <!-- 단순: 상세 /board/{id} -->
164
- <MemiBoardList post-link-base="/board" />
175
+ <MemiBoardList
176
+ post-link-base="/board"
177
+ introduction="우리 서비스의 소식과 이야기를 나누는 게시판입니다."
178
+ />
165
179
  <!-- 카테고리 path: get-post-link 권장 -->
166
180
  <!--
167
181
  <MemiBoardList
@@ -205,7 +219,7 @@ export default defineNuxtRouteMiddleware(async () => {
205
219
  <script setup lang="ts">
206
220
  definePageMeta({ middleware: 'board-auth' })
207
221
  const router = useRouter()
208
- // MemiBoardEditor 는 auto-import (import from 'memi-board' 불필요)
222
+ // MemiBoardEditor 는 auto-import (import from 'memi-board/runtime' 불필요)
209
223
  </script>
210
224
 
211
225
  <template>
@@ -219,6 +233,26 @@ const router = useRouter()
219
233
 
220
234
  전체 페이지 예시는 `playground/app/pages/board/` 참고.
221
235
 
236
+ ### 관리 페이지
237
+
238
+ 기본 Rules는 게시판의 `{prefix}Users/{uid}.role == 'admin'`을 검사한다. 컴포넌트도 같은 role을 확인하므로 기본 설치에서는 `authorized`를 전달하지 않는다.
239
+
240
+ ```vue
241
+ <!-- app/pages/board-settings.vue -->
242
+ <template>
243
+ <MemiBoardSettings />
244
+ </template>
245
+ ```
246
+
247
+ ```vue
248
+ <!-- app/pages/board-users.vue -->
249
+ <template>
250
+ <MemiBoardUsers />
251
+ </template>
252
+ ```
253
+
254
+ 호스트의 별도 사이트 관리자 체계를 연동할 때만 `authorized`를 사용할 수 있다. 이 prop은 UI 표시만 허용하며 Firestore Rules를 우회하지 않는다. 이 경우에도 해당 사용자를 게시판 admin으로 등록하거나 호스트 Rules를 같은 관리자 체계에 맞게 수정해야 한다. 관리 URL이 일반 사용자에게 노출되지 않도록 호스트 라우트 가드를 추가하는 것을 권장한다.
255
+
222
256
  ### 제공하는 API
223
257
 
224
258
  **컴포넌트 (auto-import):**
@@ -323,13 +357,16 @@ export default defineNuxtPlugin(() => {
323
357
  ## 다른 프로젝트에 붙일 때 체크리스트
324
358
 
325
359
  - [ ] 동일 스택: Nuxt 4 + `@nuxt/ui` 4 + `nuxt-vuefire` + Firebase
326
- - [ ] `pnpm add memi-board` 및 peer 직접 설치
327
- - [ ] `modules`에 `memi-board/nuxt`
328
- - [ ] `configureMemiBoard` 플러그인 (`collectionPrefix` 충돌 없게)
360
+ - [ ] `pnpm add memi-board @nuxt/ui nuxt-vuefire vuefire firebase dayjs`
361
+ - [ ] `modules`에 `@nuxt/ui`, `nuxt-vuefire`, `memi-board`
362
+ - [ ] 게시판·관리 경로에 `ssr: false`
363
+ - [ ] `nuxt.config.ts`의 `memiBoard` 설정 (`collectionPrefix` 충돌 없게)
329
364
  - [ ] CSS `@source` …/memi-board/dist/runtime/components
330
365
  - [ ] Rules 예시 병합 + **deploy**
331
366
  - [ ] 카테고리 필터 사용 시 indexes deploy
332
367
  - [ ] 페이지·미들웨어 직접 작성 (또는 playground 복사)
368
+ - [ ] 최초 로그인 후 Firebase Console에서 `{prefix}Users/{uid}.role = 'admin'`
369
+ - [ ] 설정 페이지에서 첫 카테고리 생성
333
370
  - [ ] (검열) AI Logic 활성화 + 권장 모델 + App Check
334
371
  - [ ] `pnpm why vuefire` 로 이중 인스턴스 없는지 확인
335
372
 
@@ -351,7 +388,25 @@ App Check 토큰, `appId` 일치, 모델명(3.x), AI Logic 활성화, 네트워
351
388
 
352
389
  ### monorepo `link:` / `file:` 로 개발할 때
353
390
 
354
- 패키지 쪽 `node_modules`의 vuefire가 잡히면 이중 인스턴스. 호스트에서 `vite.resolve.dedupe` + 필요 시 alias/optimizeDeps. 배포 전에는 **npm 버전**으로 설치하는 것을 권장.
391
+ 링크하기 전에 패키지 의존성과 `dist`를 준비한다.
392
+
393
+ ```bash
394
+ # memi-board 저장소
395
+ pnpm install
396
+ pnpm build
397
+
398
+ # 호스트 저장소
399
+ pnpm add link:../memi-board
400
+ ```
401
+
402
+ `src/components` 수정은 Nuxt 모듈이 링크 소스를 직접 읽으므로 즉시 반영된다. `src/module.ts` 또는 core composable을 수정하면 `pnpm build`를 다시 실행하고 호스트 개발 서버를 재시작한다. 모듈이 Vue·Vue Router·VueFire·Firebase·TipTap dedupe와 `dayjs` 최적화를 자동 적용한다. 배포 전에는 의존성을 **npm 버전**으로 되돌린다.
403
+
404
+ 링크 상태에서 새 Tailwind 클래스를 바로 시험하려면 호스트 CSS에 소스 경로를 하나 더 둔다.
405
+
406
+ ```css
407
+ @source "../../../node_modules/memi-board/src/components";
408
+ @source "../../../node_modules/memi-board/dist/runtime/components";
409
+ ```
355
410
 
356
411
  ## 로컬 개발 (이 리포)
357
412
 
@@ -370,6 +425,16 @@ firebase emulators:start --project demo-memi-board
370
425
  NUXT_PUBLIC_USE_EMULATORS=1 pnpm dev
371
426
  ```
372
427
 
428
+ ## npm 배포
429
+
430
+ npm의 6자리 OTP를 인자로 전달하면 빌드, 타입 검사, 패키지 dry-run을 모두 통과한 뒤에만 배포한다.
431
+
432
+ ```bash
433
+ pnpm release 123456
434
+ ```
435
+
436
+ `pnpm publish 123456`은 pnpm이 `123456`을 OTP가 아닌 폴더 경로로 해석하므로 사용하지 않는다.
437
+
373
438
  ## 버전 · 라이선스
374
439
 
375
440
  - UI: `<MemiBoardVersionHistory />`
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /** Core API — composables / config / types only. UI SFC 는 memi-board/nuxt 모듈이 등록. */
1
+ /** Runtime API — composables / config / types only. UI SFC는 루트 Nuxt 모듈이 등록. */
2
2
  export { configureMemiBoard, useMemiBoardConfig } from './config';
3
3
  export type { MemiBoardConfig, MemiBoardAuthOptions, MemiBoardModerationOptions } from './config';
4
4
  export { useMemiBoardAuth } from './composables/useMemiBoardAuth';
package/dist/index.js CHANGED
@@ -1058,6 +1058,16 @@ function Be() {
1058
1058
  //#endregion
1059
1059
  //#region src/data/versionHistory.ts
1060
1060
  var Ve = [
1061
+ {
1062
+ version: "0.10.0",
1063
+ date: "2026-08-05",
1064
+ highlights: [
1065
+ "Nuxt 설정 한 곳에서 게시판 설치와 Firebase 연결을 모두 설정할 수 있어요",
1066
+ "설치 프로젝트가 TipTap과 Day.js의 세부 번들 설정을 직접 맞추지 않아도 돼요",
1067
+ "게시글 목록 이동을 NuxtLink로 통일해 SSR과 CSR 환경에서 안정적으로 열려요",
1068
+ "게시판 아래에서 제작 정보와 사용 기술, 지금까지의 버전 이야기를 볼 수 있어요"
1069
+ ]
1070
+ },
1061
1071
  {
1062
1072
  version: "0.9.1",
1063
1073
  date: "2026-08-04",