@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 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.4.0";
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
- axios.defaults.baseURL = `${growiUrl}/_api/v3`;
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)) return `${error.response?.status} ${error.response?.data?.message || error.message}`;
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onozaty/growi-uploader",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "A content uploader for GROWI",
5
5
  "type": "module",
6
6
  "bin": {