@onozaty/growi-uploader 1.4.0 → 1.5.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 +7 -1
- package/dist/index.mjs +71 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,6 +69,7 @@ growi-uploader <source-dir> [options]
|
|
|
69
69
|
|
|
70
70
|
**Options:**
|
|
71
71
|
- `-c, --config <path>`: Path to config file (default: `growi-uploader.json`)
|
|
72
|
+
- `-v, --verbose`: Enable verbose error output with detailed information
|
|
72
73
|
- `-V, --version`: Output the version number
|
|
73
74
|
- `-h, --help`: Display help information
|
|
74
75
|
|
|
@@ -80,6 +81,9 @@ npx @onozaty/growi-uploader ./docs
|
|
|
80
81
|
|
|
81
82
|
# Upload with custom config file
|
|
82
83
|
npx @onozaty/growi-uploader ./docs -c my-config.json
|
|
84
|
+
|
|
85
|
+
# Upload with verbose error output
|
|
86
|
+
npx @onozaty/growi-uploader ./docs --verbose
|
|
83
87
|
```
|
|
84
88
|
|
|
85
89
|
## Directory Structure Example
|
|
@@ -117,7 +121,8 @@ Create a `growi-uploader.json` file in your project root:
|
|
|
117
121
|
"url": "https://your-growi-instance.com",
|
|
118
122
|
"token": "your-api-token",
|
|
119
123
|
"basePath": "/imported",
|
|
120
|
-
"update": true
|
|
124
|
+
"update": true,
|
|
125
|
+
"verbose": false
|
|
121
126
|
}
|
|
122
127
|
```
|
|
123
128
|
|
|
@@ -129,6 +134,7 @@ Create a `growi-uploader.json` file in your project root:
|
|
|
129
134
|
| `token` | string | ✅ | - | GROWI API access token |
|
|
130
135
|
| `basePath` | string | ❌ | `/` | Base path for imported pages |
|
|
131
136
|
| `update` | boolean | ❌ | `false` | Update existing pages if true, skip if false |
|
|
137
|
+
| `verbose` | boolean | ❌ | `false` | Enable verbose error output with detailed information |
|
|
132
138
|
|
|
133
139
|
### Getting an API Token
|
|
134
140
|
|
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.
|
|
11
|
+
var version = "1.5.0";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/config.ts
|
|
@@ -19,6 +19,7 @@ var version = "1.4.0";
|
|
|
19
19
|
* @returns Configuration object with defaults applied:
|
|
20
20
|
* - basePath defaults to "/" if not specified or empty
|
|
21
21
|
* - update defaults to false if not specified
|
|
22
|
+
* - verbose defaults to false if not specified
|
|
22
23
|
* @throws Error if config file is not found or required fields (url, token) are missing
|
|
23
24
|
*/
|
|
24
25
|
const loadConfig = (configPath) => {
|
|
@@ -32,7 +33,8 @@ const loadConfig = (configPath) => {
|
|
|
32
33
|
url: input.url,
|
|
33
34
|
token: input.token,
|
|
34
35
|
basePath: input.basePath || "/",
|
|
35
|
-
update: input.update ?? false
|
|
36
|
+
update: input.update ?? false,
|
|
37
|
+
verbose: input.verbose ?? false
|
|
36
38
|
};
|
|
37
39
|
} catch (error) {
|
|
38
40
|
if (error.code === "ENOENT") throw new Error(`Config file not found: ${fullPath}`);
|
|
@@ -85,7 +87,8 @@ const putPage = (putPageBody, options) => {
|
|
|
85
87
|
* @param token API token for authentication
|
|
86
88
|
*/
|
|
87
89
|
const configureAxios = (growiUrl, token) => {
|
|
88
|
-
|
|
90
|
+
const baseUrl = growiUrl.replace(/\/+$/, "");
|
|
91
|
+
axios.defaults.baseURL = `${baseUrl}/_api/v3`;
|
|
89
92
|
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
|
90
93
|
};
|
|
91
94
|
/**
|
|
@@ -95,11 +98,59 @@ const configureAxios = (growiUrl, token) => {
|
|
|
95
98
|
* @returns Formatted error message string
|
|
96
99
|
*/
|
|
97
100
|
const formatErrorMessage = (error) => {
|
|
98
|
-
if (axios.isAxiosError(error))
|
|
101
|
+
if (axios.isAxiosError(error)) {
|
|
102
|
+
const status = error.response?.status;
|
|
103
|
+
const statusText = error.response?.statusText;
|
|
104
|
+
const data = error.response?.data;
|
|
105
|
+
const parts = [];
|
|
106
|
+
if (status) parts.push(`HTTP ${status}${statusText ? ` ${statusText}` : ""}`);
|
|
107
|
+
if (data && typeof data === "object") {
|
|
108
|
+
const message = data.message || data.error || data.errors;
|
|
109
|
+
if (message) parts.push(message);
|
|
110
|
+
else if (Object.keys(data).length > 0) parts.push(JSON.stringify(data));
|
|
111
|
+
}
|
|
112
|
+
if (error.code) parts.push(`(${error.code})`);
|
|
113
|
+
if (parts.length === 0) return error.message;
|
|
114
|
+
return parts.join(" ");
|
|
115
|
+
}
|
|
99
116
|
if (error instanceof Error) return error.message;
|
|
100
117
|
return String(error);
|
|
101
118
|
};
|
|
102
119
|
/**
|
|
120
|
+
* Format error into a detailed error message for verbose mode
|
|
121
|
+
*
|
|
122
|
+
* @param error Error object from catch block
|
|
123
|
+
* @returns Detailed multi-line error message with all available information
|
|
124
|
+
*/
|
|
125
|
+
const formatDetailedError = (error) => {
|
|
126
|
+
const lines = [];
|
|
127
|
+
if (axios.isAxiosError(error)) {
|
|
128
|
+
const status = error.response?.status;
|
|
129
|
+
const statusText = error.response?.statusText;
|
|
130
|
+
const data = error.response?.data;
|
|
131
|
+
if (status) lines.push(` HTTP Status: ${status}${statusText ? ` ${statusText}` : ""}`);
|
|
132
|
+
if (data) try {
|
|
133
|
+
const responseStr = typeof data === "object" ? JSON.stringify(data, null, 2).split("\n").map((line) => ` ${line}`).join("\n") : String(data);
|
|
134
|
+
lines.push(` Response Body:\n${responseStr}`);
|
|
135
|
+
} catch {
|
|
136
|
+
lines.push(` Response Body: ${String(data)}`);
|
|
137
|
+
}
|
|
138
|
+
if (error.code) lines.push(` Error Code: ${error.code}`);
|
|
139
|
+
if (error.config) lines.push(` Request: ${error.config.method?.toUpperCase()} ${error.config.url}`);
|
|
140
|
+
if (error.stack) {
|
|
141
|
+
const stackLines = error.stack.split("\n").slice(0, 5);
|
|
142
|
+
lines.push(` Stack Trace:\n${stackLines.map((line) => ` ${line}`).join("\n")}`);
|
|
143
|
+
}
|
|
144
|
+
} else if (error instanceof Error) {
|
|
145
|
+
lines.push(` Error: ${error.message}`);
|
|
146
|
+
if (error.stack) {
|
|
147
|
+
const stackLines = error.stack.split("\n").slice(0, 5);
|
|
148
|
+
lines.push(` Stack Trace:\n${stackLines.map((line) => ` ${line}`).join("\n")}`);
|
|
149
|
+
}
|
|
150
|
+
} else lines.push(` Error: ${String(error)}`);
|
|
151
|
+
return lines.length > 0 ? ` Details:\n${lines.join("\n")}` : "";
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
103
154
|
* Create a new page or update an existing page in GROWI
|
|
104
155
|
*
|
|
105
156
|
* @param file Markdown file to upload
|
|
@@ -133,7 +184,8 @@ const createOrUpdatePage = async (file, content, shouldUpdate) => {
|
|
|
133
184
|
pageId: void 0,
|
|
134
185
|
revisionId: void 0,
|
|
135
186
|
action: "error",
|
|
136
|
-
errorMessage: formatErrorMessage(error)
|
|
187
|
+
errorMessage: formatErrorMessage(error),
|
|
188
|
+
error
|
|
137
189
|
};
|
|
138
190
|
}
|
|
139
191
|
try {
|
|
@@ -151,7 +203,8 @@ const createOrUpdatePage = async (file, content, shouldUpdate) => {
|
|
|
151
203
|
pageId: void 0,
|
|
152
204
|
revisionId: void 0,
|
|
153
205
|
action: "error",
|
|
154
|
-
errorMessage: formatErrorMessage(error)
|
|
206
|
+
errorMessage: formatErrorMessage(error),
|
|
207
|
+
error
|
|
155
208
|
};
|
|
156
209
|
}
|
|
157
210
|
};
|
|
@@ -177,7 +230,8 @@ const updatePageContent = async (pageId, revisionId, content) => {
|
|
|
177
230
|
} catch (error) {
|
|
178
231
|
return {
|
|
179
232
|
success: false,
|
|
180
|
-
errorMessage: formatErrorMessage(error)
|
|
233
|
+
errorMessage: formatErrorMessage(error),
|
|
234
|
+
error
|
|
181
235
|
};
|
|
182
236
|
}
|
|
183
237
|
};
|
|
@@ -207,7 +261,8 @@ const uploadAttachment = async (attachment, pageId, sourceDir) => {
|
|
|
207
261
|
} catch (error) {
|
|
208
262
|
return {
|
|
209
263
|
success: false,
|
|
210
|
-
errorMessage: formatErrorMessage(error)
|
|
264
|
+
errorMessage: formatErrorMessage(error),
|
|
265
|
+
error
|
|
211
266
|
};
|
|
212
267
|
}
|
|
213
268
|
};
|
|
@@ -469,6 +524,7 @@ const uploadFiles = async (files, sourceDir, config) => {
|
|
|
469
524
|
} else if (result.action === "error") {
|
|
470
525
|
stats.pageErrors++;
|
|
471
526
|
console.error(`[ERROR] ${file.localPath} → ${file.growiPath} (${result.errorMessage || "unknown error"})`);
|
|
527
|
+
if (config.verbose && result.error) console.error(formatDetailedError(result.error));
|
|
472
528
|
}
|
|
473
529
|
if (result.pageId && (result.action === "created" || result.action === "updated")) {
|
|
474
530
|
let currentContent = content;
|
|
@@ -489,6 +545,7 @@ const uploadFiles = async (files, sourceDir, config) => {
|
|
|
489
545
|
} else {
|
|
490
546
|
stats.attachmentErrors++;
|
|
491
547
|
console.error(`[ERROR] ${attachment.localPath} → ${file.growiPath} (${attachmentResult.errorMessage || "failed to upload attachment"})`);
|
|
548
|
+
if (config.verbose && attachmentResult.error) console.error(formatDetailedError(attachmentResult.error));
|
|
492
549
|
}
|
|
493
550
|
}
|
|
494
551
|
if (hasAttachments && latestRevisionId) {
|
|
@@ -503,6 +560,7 @@ const uploadFiles = async (files, sourceDir, config) => {
|
|
|
503
560
|
currentRevisionId = updateResult.revisionId;
|
|
504
561
|
} else {
|
|
505
562
|
console.error(`[ERROR] ${file.localPath} → ${file.growiPath} (failed to update attachment links: ${updateResult.errorMessage || "unknown error"})`);
|
|
563
|
+
if (config.verbose && updateResult.error) console.error(formatDetailedError(updateResult.error));
|
|
506
564
|
stats.linkReplacementErrors++;
|
|
507
565
|
}
|
|
508
566
|
}
|
|
@@ -517,6 +575,7 @@ const uploadFiles = async (files, sourceDir, config) => {
|
|
|
517
575
|
currentRevisionId = updateResult.revisionId;
|
|
518
576
|
} else {
|
|
519
577
|
console.error(`[ERROR] ${file.localPath} → ${file.growiPath} (failed to update page links: ${updateResult.errorMessage || "unknown error"})`);
|
|
578
|
+
if (config.verbose && updateResult.error) console.error(formatDetailedError(updateResult.error));
|
|
520
579
|
stats.linkReplacementErrors++;
|
|
521
580
|
}
|
|
522
581
|
}
|
|
@@ -532,8 +591,9 @@ const uploadFiles = async (files, sourceDir, config) => {
|
|
|
532
591
|
//#endregion
|
|
533
592
|
//#region src/index.ts
|
|
534
593
|
const program = new Command();
|
|
535
|
-
const main = async (sourceDir, configPath) => {
|
|
594
|
+
const main = async (sourceDir, configPath, verboseOverride) => {
|
|
536
595
|
const config = loadConfig(configPath);
|
|
596
|
+
if (verboseOverride !== void 0) config.verbose = verboseOverride;
|
|
537
597
|
const sourceDirPath = resolve(sourceDir);
|
|
538
598
|
configureAxios(config.url, config.token);
|
|
539
599
|
const files = await scanMarkdownFiles(sourceDirPath, config.basePath);
|
|
@@ -550,9 +610,9 @@ const main = async (sourceDir, configPath) => {
|
|
|
550
610
|
console.log(`- Attachment errors: ${stats.attachmentErrors}`);
|
|
551
611
|
console.log(`- Link replacement errors: ${stats.linkReplacementErrors}`);
|
|
552
612
|
};
|
|
553
|
-
program.name("growi-uploader").description("A content uploader for GROWI").version(version).argument("<source-dir>", "Source directory containing Markdown files").option("-c, --config <path>", "Path to config file", "growi-uploader.json").action(async (sourceDir, options) => {
|
|
613
|
+
program.name("growi-uploader").description("A content uploader for GROWI").version(version).argument("<source-dir>", "Source directory containing Markdown files").option("-c, --config <path>", "Path to config file", "growi-uploader.json").option("-v, --verbose", "Enable verbose error output with detailed information").action(async (sourceDir, options) => {
|
|
554
614
|
try {
|
|
555
|
-
await main(sourceDir, options.config);
|
|
615
|
+
await main(sourceDir, options.config, options.verbose);
|
|
556
616
|
} catch (error) {
|
|
557
617
|
console.error("Error:", error instanceof Error ? error.message : error);
|
|
558
618
|
process.exit(1);
|