@onozaty/growi-uploader 1.5.0 → 1.6.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.ja.md ADDED
@@ -0,0 +1,341 @@
1
+ # growi-uploader
2
+
3
+ 日本語 | [English](README.md)
4
+
5
+ [![Test](https://github.com/onozaty/growi-uploader/actions/workflows/test.yaml/badge.svg)](https://github.com/onozaty/growi-uploader/actions/workflows/test.yaml)
6
+ [![codecov](https://codecov.io/gh/onozaty/growi-uploader/graph/badge.svg?token=X0YN1OP5PB)](https://codecov.io/gh/onozaty/growi-uploader)
7
+ [![npm version](https://badge.fury.io/js/@onozaty%2Fgrowi-uploader.svg)](https://www.npmjs.com/package/@onozaty/growi-uploader)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ローカルのMarkdownファイルと添付ファイルを[GROWI](https://growi.org/) Wikiに一括アップロードするCLIツールです。
11
+
12
+ ## 機能
13
+
14
+ - 📁 **ディレクトリ構造の維持** - ローカルのフォルダ階層がGROWIのページ階層になります
15
+ - 📝 **Markdownファイルのアップロード** - `.md`ファイルからGROWIページを作成・更新
16
+ - 📎 **添付ファイルの自動検出** - `<ページ名>_attachment_<ファイル名>`パターンのファイルを自動的に添付ファイルとしてアップロード
17
+ - 🔗 **リンクの自動置換** - ローカルの添付ファイルへのリンクをGROWI形式(`/attachment/{id}`)に自動変換
18
+ - 🖼️ **画像の埋め込み** - 画像リンク(`![alt](image.png)`)を自動変換
19
+ - ⚙️ **柔軟な設定** - ベースパス、更新動作などを制御可能
20
+
21
+ ## クイックスタート
22
+
23
+ 1. 設定ファイル`growi-uploader.json`を作成:
24
+
25
+ ```json
26
+ {
27
+ "url": "https://your-growi-instance.com",
28
+ "token": "your-api-token",
29
+ "basePath": "/",
30
+ "update": false
31
+ }
32
+ ```
33
+
34
+ 2. npxで実行(インストール不要):
35
+
36
+ ```bash
37
+ npx @onozaty/growi-uploader ./docs
38
+ ```
39
+
40
+ これだけです!ローカルの`./docs`ディレクトリがGROWIにアップロードされます。
41
+
42
+ ## インストール
43
+
44
+ ### npxを使用(推奨)
45
+
46
+ インストール不要で実行できます:
47
+
48
+ ```bash
49
+ npx @onozaty/growi-uploader <source-dir>
50
+ ```
51
+
52
+ ### グローバルインストール
53
+
54
+ 頻繁に使用する場合は、グローバルインストールできます:
55
+
56
+ ```bash
57
+ npm install -g @onozaty/growi-uploader
58
+ growi-uploader <source-dir>
59
+ ```
60
+
61
+ ## 使い方
62
+
63
+ ### 基本コマンド
64
+
65
+ ```bash
66
+ growi-uploader <source-dir> [options]
67
+ ```
68
+
69
+ **引数:**
70
+ - `<source-dir>`: Markdownファイルを含むディレクトリのパス
71
+
72
+ **オプション:**
73
+ - `-c, --config <path>`: 設定ファイルのパス(デフォルト: `growi-uploader.json`)
74
+ - `-v, --verbose`: 詳細なエラー出力を有効化
75
+ - `-V, --version`: バージョン番号を表示
76
+ - `-h, --help`: ヘルプ情報を表示
77
+
78
+ ### 実行例
79
+
80
+ ```bash
81
+ # Upload with default config file
82
+ npx @onozaty/growi-uploader ./docs
83
+
84
+ # Upload with custom config file
85
+ npx @onozaty/growi-uploader ./docs -c my-config.json
86
+
87
+ # Upload with verbose error output
88
+ npx @onozaty/growi-uploader ./docs --verbose
89
+ ```
90
+
91
+ ## ディレクトリ構造の例
92
+
93
+ ### ローカルディレクトリ
94
+
95
+ ```
96
+ docs/
97
+ guide.md
98
+ guide_attachment_diagram.svg
99
+ guide_attachment_sample.txt
100
+ api/
101
+ overview.md
102
+ overview_attachment_example.json
103
+ authentication.md
104
+ ```
105
+
106
+ ### アップロード後のGROWIページ
107
+
108
+ ```
109
+ /docs/guide (from guide.md)
110
+ └─ diagram.svg (attachment)
111
+ └─ sample.txt (attachment)
112
+ /docs/api/overview (from api/overview.md)
113
+ └─ example.json (attachment)
114
+ /docs/api/authentication (from api/authentication.md)
115
+ ```
116
+
117
+ ## ページ名の正規化
118
+
119
+ APIエラーを防ぐため、ページ名は以下のルールで自動的に正規化されます:
120
+
121
+ ### 正規化ルール
122
+
123
+ 1. **スラッシュ前後のスペース** → アンダースコアに置換
124
+ - `a / b.md` → `/a_/_b`
125
+
126
+ 2. **特殊文字** → 安全な文字列に置換:
127
+ - `+` → `-plus-`
128
+ - `?` → `-question-`
129
+ - `*` → `-asterisk-`
130
+ - `$` → `-dollar-`
131
+ - `^` → `-caret-`
132
+ - `%` → `-percent-`
133
+
134
+ 3. **予約済みページ名** → アンダースコアを末尾に追加:
135
+ - `edit` → `edit_` (パスの最後のセグメントの場合のみ)
136
+
137
+ ### 例
138
+
139
+ ```
140
+ Local file GROWI page path
141
+ ──────────────────────────────────────────────────
142
+ C++.md → /C-plus--plus-
143
+ What?.md → /What-question-
144
+ C++ / Python?.md → /C-plus--plus-_/_Python-question-
145
+ edit.md → /edit_
146
+ docs/edit.md → /docs/edit_
147
+ docs/normal-page.md → /docs/normal-page (no change)
148
+ ```
149
+
150
+ この正規化により、GROWIのページ名要件との互換性を確保しつつ、ファイル名の可読性を維持します。
151
+
152
+ ## 設定ファイル
153
+
154
+ プロジェクトルートに`growi-uploader.json`ファイルを作成:
155
+
156
+ ```json
157
+ {
158
+ "url": "https://your-growi-instance.com",
159
+ "token": "your-api-token",
160
+ "basePath": "/imported",
161
+ "update": true,
162
+ "verbose": false
163
+ }
164
+ ```
165
+
166
+ ### 設定オプション
167
+
168
+ | オプション | 型 | 必須 | デフォルト | 説明 |
169
+ |--------|------|----------|---------|-------------|
170
+ | `url` | string | ✅ | - | GROWIインスタンスのURL |
171
+ | `token` | string | ✅ | - | GROWI APIアクセストークン |
172
+ | `basePath` | string | ❌ | `/` | インポートされるページのベースパス |
173
+ | `update` | boolean | ❌ | `false` | trueの場合は既存ページを更新、falseの場合はスキップ |
174
+ | `verbose` | boolean | ❌ | `false` | 詳細なエラー出力を有効化 |
175
+
176
+ ### APIトークンの取得方法
177
+
178
+ 1. GROWIインスタンスにログイン
179
+ 2. **ユーザー設定** → **API設定** に移動
180
+ 3. **新しいトークンを発行** をクリック
181
+ 4. 生成されたトークンを設定ファイルにコピー
182
+
183
+ ## 添付ファイル
184
+
185
+ 添付ファイルは2つの方法で自動検出されます:
186
+
187
+ ### 方法1: 命名規則
188
+
189
+ 以下の命名パターンに従うファイルが添付ファイルとして検出されます:
190
+
191
+ ```
192
+ <ページ名>_attachment_<ファイル名>
193
+ ```
194
+
195
+ **例:**
196
+ ```
197
+ guide.md → GROWI page: /guide
198
+ guide_attachment_image.png → Attached to /guide
199
+ guide_attachment_document.pdf → Attached to /guide
200
+ ```
201
+
202
+ ### 方法2: リンクベースの検出
203
+
204
+ Markdownリンクで参照されているファイルが自動的に添付ファイルとして検出されます:
205
+
206
+ **ローカルディレクトリ:**
207
+ ```
208
+ guide.md
209
+ assets/
210
+ banner.png
211
+ images/
212
+ logo.png
213
+ screenshot.png
214
+ ```
215
+
216
+ **guide.mdの内容:**
217
+ ```markdown
218
+ ![Logo](./images/logo.png)
219
+ ![Screenshot](images/screenshot.png)
220
+ ![Banner](/assets/banner.png)
221
+ ```
222
+
223
+ 参照されているすべてのファイル(`logo.png`、`screenshot.png`、`banner.png`)は、`_attachment_`命名規則に従っていなくても、`/guide`ページに添付ファイルとしてアップロードされます。
224
+
225
+ **パスの解決:**
226
+ - Markdownエスケープシーケンスは解除されます(`\(` → `(`)
227
+ - URLエンコーディング(パーセントエンコーディング)はデコードされます(`%20` → スペース、`%E7%94%BB%E5%83%8F` → `画像`)
228
+ - 相対パス(`./`、`../`、またはプレフィックスなし): Markdownファイルのディレクトリからの相対パス
229
+ - 絶対パス(`/`で始まる): ソースディレクトリのルートからの絶対パス
230
+ - 例: `/assets/banner.png` → `<source-dir>/assets/banner.png`
231
+
232
+ **サポートされるリンク形式:**
233
+ ```markdown
234
+ ![Logo](./images/logo.png) # Standard relative path
235
+ ![Logo](images/logo.png) # Relative path without ./
236
+ ![Image](./images/%E7%94%BB%E5%83%8F.png) # URL-encoded Japanese filename
237
+ [File](./docs/my%20file.pdf) # URL-encoded space
238
+ [File](<./path/file (1).png>) # Special chars with angle brackets
239
+ ![Image](./path/file\\(1\\).png) # Special chars with escaping
240
+ <img src="./images/logo.png" alt="Logo"> # HTML img tag (double quotes)
241
+ <img src='./images/logo.png' alt='Logo'> # HTML img tag (single quotes)
242
+ ```
243
+
244
+ **検出から除外されるもの:**
245
+ - `.md`ファイル(ページリンクとして扱われます)
246
+ - 外部URL(`http://`、`https://`)
247
+ - 存在しないファイル
248
+
249
+ ### リンクの自動置換
250
+
251
+ 添付ファイルへのMarkdownリンクは自動的にGROWI形式(`/attachment/{id}`)に変換されます。
252
+
253
+ **例(命名規則):**
254
+
255
+ ```markdown
256
+ # Before upload
257
+ ![Diagram](./guide_attachment_diagram.png)
258
+ Download the [documentation](guide_attachment_document.pdf).
259
+
260
+ # After upload (on GROWI)
261
+ ![Diagram](/attachment/68f3a41c794f665ad2c0d322)
262
+ Download the [documentation](/attachment/68f3a3fa794f665ad2c0d2b3).
263
+ ```
264
+
265
+ **例(リンクベース):**
266
+
267
+ ```markdown
268
+ # Before upload
269
+ ![Logo](./images/logo.png)
270
+
271
+ # After upload (on GROWI)
272
+ ![Logo](/attachment/68f3a41c794f665ad2c0d322)
273
+ ```
274
+
275
+ 両方の検出方法で、複数のリンク形式(`./`あり・なし)がサポートされています。
276
+
277
+ ## 高度な使い方
278
+
279
+ ### 既存ページの更新
280
+
281
+ 設定ファイルで`update: true`を設定すると、既存ページを更新できます:
282
+
283
+ ```json
284
+ {
285
+ "url": "https://your-growi-instance.com",
286
+ "token": "your-api-token",
287
+ "update": true
288
+ }
289
+ ```
290
+
291
+ ### 特定のパスへのインポート
292
+
293
+ `basePath`を使用して、すべてのページを特定のパス配下にインポートできます:
294
+
295
+ ```json
296
+ {
297
+ "url": "https://your-growi-instance.com",
298
+ "token": "your-api-token",
299
+ "basePath": "/imported"
300
+ }
301
+ ```
302
+
303
+ **結果:**
304
+ ```
305
+ docs/guide.md → /imported/docs/guide
306
+ ```
307
+
308
+ ### 出力例
309
+
310
+ ```
311
+ Found 5 Markdown file(s) and 3 attachment(s)
312
+
313
+ [SUCCESS] docs/guide.md → /docs/guide (created)
314
+ [SUCCESS] docs/guide_attachment_diagram.svg → /docs/guide (attachment)
315
+ [SUCCESS] docs/guide.md → /docs/guide (attachment links replaced)
316
+ [SUCCESS] docs/api/overview.md → /docs/api/overview (created)
317
+ [SKIP] docs/api/auth.md → /docs/api/auth (page already exists)
318
+
319
+ Completed:
320
+ - Pages created: 2
321
+ - Pages updated: 0
322
+ - Pages skipped: 1
323
+ - Page errors: 0
324
+ - Attachments uploaded: 2
325
+ - Attachments skipped: 0
326
+ - Attachment errors: 0
327
+ - Link replacement errors: 0
328
+ ```
329
+
330
+ ## 必要環境
331
+
332
+ - Node.js 18以降
333
+ - REST API v3をサポートするGROWIインスタンス
334
+
335
+ ## ライセンス
336
+
337
+ MIT
338
+
339
+ ## 作者
340
+
341
+ [onozaty](https://github.com/onozaty)
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # growi-uploader
2
2
 
3
+ English | [日本語](README.ja.md)
4
+
3
5
  [![Test](https://github.com/onozaty/growi-uploader/actions/workflows/test.yaml/badge.svg)](https://github.com/onozaty/growi-uploader/actions/workflows/test.yaml)
4
6
  [![codecov](https://codecov.io/gh/onozaty/growi-uploader/graph/badge.svg?token=X0YN1OP5PB)](https://codecov.io/gh/onozaty/growi-uploader)
5
7
  [![npm version](https://badge.fury.io/js/@onozaty%2Fgrowi-uploader.svg)](https://www.npmjs.com/package/@onozaty/growi-uploader)
@@ -112,6 +114,41 @@ docs/
112
114
  /docs/api/authentication (from api/authentication.md)
113
115
  ```
114
116
 
117
+ ## Page Name Normalization
118
+
119
+ To prevent API errors, page names are automatically normalized using the following rules:
120
+
121
+ ### Normalization Rules
122
+
123
+ 1. **Spaces around slashes** → Replaced with underscores
124
+ - `a / b.md` → `/a_/_b`
125
+
126
+ 2. **Special characters** → Replaced with safe alternatives:
127
+ - `+` → `-plus-`
128
+ - `?` → `-question-`
129
+ - `*` → `-asterisk-`
130
+ - `$` → `-dollar-`
131
+ - `^` → `-caret-`
132
+ - `%` → `-percent-`
133
+
134
+ 3. **Reserved page names** → Suffixed with underscore:
135
+ - `edit` → `edit_` (only when it's the last path segment)
136
+
137
+ ### Examples
138
+
139
+ ```
140
+ Local file GROWI page path
141
+ ──────────────────────────────────────────────────
142
+ C++.md → /C-plus--plus-
143
+ What?.md → /What-question-
144
+ C++ / Python?.md → /C-plus--plus-_/_Python-question-
145
+ edit.md → /edit_
146
+ docs/edit.md → /docs/edit_
147
+ docs/normal-page.md → /docs/normal-page (no change)
148
+ ```
149
+
150
+ This normalization ensures compatibility with GROWI's page naming requirements while preserving the readability of your file names.
151
+
115
152
  ## Configuration File
116
153
 
117
154
  Create a `growi-uploader.json` file in your project root:
package/dist/index.mjs CHANGED
@@ -8,7 +8,7 @@ import { lookup } from "mime-types";
8
8
  import { glob } from "glob";
9
9
 
10
10
  //#region package.json
11
- var version = "1.5.0";
11
+ var version = "1.6.0";
12
12
 
13
13
  //#endregion
14
14
  //#region src/config.ts
@@ -363,6 +363,19 @@ const mergeAttachments = (namingAttachments, linkAttachments) => {
363
363
  }
364
364
  return Array.from(map.values());
365
365
  };
366
+ /**
367
+ * Normalize GROWI page path to avoid API errors
368
+ *
369
+ * @param path Raw GROWI page path
370
+ * @returns Normalized page path
371
+ */
372
+ const normalizeGrowiPath = (path) => {
373
+ let normalized = path;
374
+ normalized = normalized.replace(/\s+\/\s+/g, "_/_");
375
+ normalized = normalized.replace(/\+/g, "-plus-").replace(/\?/g, "-question-").replace(/\*/g, "-asterisk-").replace(/\$/g, "-dollar-").replace(/\^/g, "-caret-").replace(/%/g, "-percent-");
376
+ normalized = normalized.replace(/\/edit$/g, "/edit_");
377
+ return normalized;
378
+ };
366
379
  const scanMarkdownFiles = async (sourceDir, basePath = "/") => {
367
380
  const pageFiles = (await glob("**/*.md", {
368
381
  cwd: sourceDir,
@@ -371,7 +384,8 @@ const scanMarkdownFiles = async (sourceDir, basePath = "/") => {
371
384
  pageFiles.sort();
372
385
  return await Promise.all(pageFiles.map(async (file) => {
373
386
  const content = readFileSync(join(sourceDir, file), "utf-8");
374
- const growiPath = join(basePath, file.replace(/\.md$/, "")).replace(/\\/g, "/");
387
+ let growiPath = join(basePath, file.replace(/\.md$/, "")).replace(/\\/g, "/");
388
+ growiPath = normalizeGrowiPath(growiPath);
375
389
  const dir = dirname(file);
376
390
  const pageName = basename(file, ".md");
377
391
  const attachments = mergeAttachments((await glob(dir === "." ? `${pageName}_attachment_*` : `${dir}/${pageName}_attachment_*`, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onozaty/growi-uploader",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "A content uploader for GROWI",
5
5
  "type": "module",
6
6
  "bin": {