kordoc 2.1.0 → 2.2.1

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.
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/detect.ts
4
+ import JSZip from "jszip";
5
+ function magicBytes(buffer) {
6
+ return new Uint8Array(buffer, 0, Math.min(4, buffer.byteLength));
7
+ }
8
+ function isZipFile(buffer) {
9
+ const b = magicBytes(buffer);
10
+ return b[0] === 80 && b[1] === 75 && b[2] === 3 && b[3] === 4;
11
+ }
12
+ function isHwpxFile(buffer) {
13
+ return isZipFile(buffer);
14
+ }
15
+ function isOldHwpFile(buffer) {
16
+ const b = magicBytes(buffer);
17
+ return b[0] === 208 && b[1] === 207 && b[2] === 17 && b[3] === 224;
18
+ }
19
+ function isPdfFile(buffer) {
20
+ const b = magicBytes(buffer);
21
+ return b[0] === 37 && b[1] === 80 && b[2] === 68 && b[3] === 70;
22
+ }
23
+ function detectFormat(buffer) {
24
+ if (buffer.byteLength < 4) return "unknown";
25
+ if (isZipFile(buffer)) return "hwpx";
26
+ if (isOldHwpFile(buffer)) return "hwp";
27
+ if (isPdfFile(buffer)) return "pdf";
28
+ return "unknown";
29
+ }
30
+ async function detectZipFormat(buffer) {
31
+ try {
32
+ const zip = await JSZip.loadAsync(buffer);
33
+ if (zip.file("xl/workbook.xml")) return "xlsx";
34
+ if (zip.file("word/document.xml")) return "docx";
35
+ if (zip.file("Contents/content.hpf") || zip.file("mimetype")) return "hwpx";
36
+ const hasSection = Object.keys(zip.files).some((f) => f.startsWith("Contents/"));
37
+ if (hasSection) return "hwpx";
38
+ return "unknown";
39
+ } catch {
40
+ return "unknown";
41
+ }
42
+ }
43
+
44
+ export {
45
+ isZipFile,
46
+ isHwpxFile,
47
+ isOldHwpFile,
48
+ isPdfFile,
49
+ detectFormat,
50
+ detectZipFormat
51
+ };
52
+ //# sourceMappingURL=chunk-MUAWCQDY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/detect.ts"],"sourcesContent":["/** 매직 바이트 기반 파일 포맷 감지 */\r\n\r\nimport JSZip from \"jszip\"\r\nimport type { FileType } from \"./types.js\"\r\n\r\n/** 매직 바이트 뷰 생성 (복사 없이 view) */\r\nfunction magicBytes(buffer: ArrayBuffer): Uint8Array {\r\n return new Uint8Array(buffer, 0, Math.min(4, buffer.byteLength))\r\n}\r\n\r\n/** ZIP 파일 여부: PK\\x03\\x04 */\r\nexport function isZipFile(buffer: ArrayBuffer): boolean {\r\n const b = magicBytes(buffer)\r\n return b[0] === 0x50 && b[1] === 0x4b && b[2] === 0x03 && b[3] === 0x04\r\n}\r\n\r\n/** HWPX (ZIP 기반 한컴 문서): PK\\x03\\x04 — 하위 호환용 */\r\nexport function isHwpxFile(buffer: ArrayBuffer): boolean {\r\n return isZipFile(buffer)\r\n}\r\n\r\n/** HWP 5.x (OLE2 바이너리 한컴 문서): \\xD0\\xCF\\x11\\xE0 */\r\nexport function isOldHwpFile(buffer: ArrayBuffer): boolean {\r\n const b = magicBytes(buffer)\r\n return b[0] === 0xd0 && b[1] === 0xcf && b[2] === 0x11 && b[3] === 0xe0\r\n}\r\n\r\n/** PDF 문서: %PDF */\r\nexport function isPdfFile(buffer: ArrayBuffer): boolean {\r\n const b = magicBytes(buffer)\r\n return b[0] === 0x25 && b[1] === 0x50 && b[2] === 0x44 && b[3] === 0x46\r\n}\r\n\r\n/** 동기 포맷 감지 — ZIP은 모두 \"hwpx\"로 반환 (하위 호환) */\r\nexport function detectFormat(buffer: ArrayBuffer): FileType {\r\n if (buffer.byteLength < 4) return \"unknown\"\r\n if (isZipFile(buffer)) return \"hwpx\"\r\n if (isOldHwpFile(buffer)) return \"hwp\"\r\n if (isPdfFile(buffer)) return \"pdf\"\r\n return \"unknown\"\r\n}\r\n\r\n/**\r\n * ZIP 내부 구조 기반 포맷 세분화.\r\n * HWPX, XLSX, DOCX 모두 ZIP이므로 내부 파일로 구분.\r\n */\r\nexport async function detectZipFormat(buffer: ArrayBuffer): Promise<\"hwpx\" | \"xlsx\" | \"docx\" | \"unknown\"> {\r\n try {\r\n const zip = await JSZip.loadAsync(buffer)\r\n // XLSX: xl/workbook.xml\r\n if (zip.file(\"xl/workbook.xml\")) return \"xlsx\"\r\n // DOCX: word/document.xml\r\n if (zip.file(\"word/document.xml\")) return \"docx\"\r\n // HWPX: Contents/ 또는 content.hpf 또는 mimetype\r\n if (zip.file(\"Contents/content.hpf\") || zip.file(\"mimetype\")) return \"hwpx\"\r\n // 기타 ZIP 내에 section 파일이 있으면 HWPX로 추정\r\n const hasSection = Object.keys(zip.files).some(f => f.startsWith(\"Contents/\"))\r\n if (hasSection) return \"hwpx\"\r\n return \"unknown\"\r\n } catch {\r\n return \"unknown\"\r\n }\r\n}\r\n"],"mappings":";;;AAEA,OAAO,WAAW;AAIlB,SAAS,WAAW,QAAiC;AACnD,SAAO,IAAI,WAAW,QAAQ,GAAG,KAAK,IAAI,GAAG,OAAO,UAAU,CAAC;AACjE;AAGO,SAAS,UAAU,QAA8B;AACtD,QAAM,IAAI,WAAW,MAAM;AAC3B,SAAO,EAAE,CAAC,MAAM,MAAQ,EAAE,CAAC,MAAM,MAAQ,EAAE,CAAC,MAAM,KAAQ,EAAE,CAAC,MAAM;AACrE;AAGO,SAAS,WAAW,QAA8B;AACvD,SAAO,UAAU,MAAM;AACzB;AAGO,SAAS,aAAa,QAA8B;AACzD,QAAM,IAAI,WAAW,MAAM;AAC3B,SAAO,EAAE,CAAC,MAAM,OAAQ,EAAE,CAAC,MAAM,OAAQ,EAAE,CAAC,MAAM,MAAQ,EAAE,CAAC,MAAM;AACrE;AAGO,SAAS,UAAU,QAA8B;AACtD,QAAM,IAAI,WAAW,MAAM;AAC3B,SAAO,EAAE,CAAC,MAAM,MAAQ,EAAE,CAAC,MAAM,MAAQ,EAAE,CAAC,MAAM,MAAQ,EAAE,CAAC,MAAM;AACrE;AAGO,SAAS,aAAa,QAA+B;AAC1D,MAAI,OAAO,aAAa,EAAG,QAAO;AAClC,MAAI,UAAU,MAAM,EAAG,QAAO;AAC9B,MAAI,aAAa,MAAM,EAAG,QAAO;AACjC,MAAI,UAAU,MAAM,EAAG,QAAO;AAC9B,SAAO;AACT;AAMA,eAAsB,gBAAgB,QAAoE;AACxG,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,UAAU,MAAM;AAExC,QAAI,IAAI,KAAK,iBAAiB,EAAG,QAAO;AAExC,QAAI,IAAI,KAAK,mBAAmB,EAAG,QAAO;AAE1C,QAAI,IAAI,KAAK,sBAAsB,KAAK,IAAI,KAAK,UAAU,EAAG,QAAO;AAErE,UAAM,aAAa,OAAO,KAAK,IAAI,KAAK,EAAE,KAAK,OAAK,EAAE,WAAW,WAAW,CAAC;AAC7E,QAAI,WAAY,QAAO;AACvB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
package/dist/cli.js CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- detectFormat,
4
- parse
5
- } from "./chunk-GJ2S6IMC.js";
6
2
  import {
7
3
  VERSION,
4
+ parse,
5
+ sanitizeError,
8
6
  toArrayBuffer
9
- } from "./chunk-PKIJLEV6.js";
10
- import "./chunk-MOL7MDBG.js";
7
+ } from "./chunk-FINXMRCH.js";
8
+ import {
9
+ detectFormat
10
+ } from "./chunk-MUAWCQDY.js";
11
+ import "./chunk-3TBUDJDE.js";
11
12
 
12
13
  // src/cli.ts
13
14
  import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
@@ -64,7 +65,11 @@ program.name("kordoc").description("\uBAA8\uB450 \uD30C\uC2F1\uD574\uBC84\uB9AC\
64
65
  if (opts.outDir && result.images?.length) {
65
66
  markdown = markdown.replace(/!\[image\]\(image_/g, "![image](images/image_");
66
67
  }
67
- const output = opts.format === "json" ? JSON.stringify(result, null, 2) : markdown;
68
+ const output = opts.format === "json" ? JSON.stringify(
69
+ result,
70
+ (_key, value) => value instanceof Uint8Array ? Buffer.from(value).toString("base64") : value,
71
+ 2
72
+ ) : markdown;
68
73
  const saveImages = (dir) => {
69
74
  if (!result.images?.length) return;
70
75
  const imgDir = resolve(dir, "images");
@@ -92,7 +97,6 @@ program.name("kordoc").description("\uBAA8\uB450 \uD30C\uC2F1\uD574\uBC84\uB9AC\
92
97
  process.stdout.write(output + "\n");
93
98
  }
94
99
  } catch (err) {
95
- const { sanitizeError } = await import("./utils-BWQ2RGUD.js");
96
100
  process.stderr.write(`
97
101
  [kordoc] ERROR: ${fileName} \u2014 ${sanitizeError(err)}
98
102
  `);
@@ -101,7 +105,7 @@ program.name("kordoc").description("\uBAA8\uB450 \uD30C\uC2F1\uD574\uBC84\uB9AC\
101
105
  }
102
106
  });
103
107
  program.command("watch <dir>").description("\uB514\uB809\uD1A0\uB9AC \uAC10\uC2DC \u2014 \uC0C8 \uBB38\uC11C \uC790\uB3D9 \uBCC0\uD658").option("--webhook <url>", "\uACB0\uACFC \uC804\uC1A1 \uC6F9\uD6C5 URL").option("-d, --out-dir <dir>", "\uBCC0\uD658 \uACB0\uACFC \uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC").option("-p, --pages <range>", "\uD398\uC774\uC9C0/\uC139\uC158 \uBC94\uC704").option("--format <type>", "\uCD9C\uB825 \uD615\uC2DD: markdown \uB610\uB294 json", "markdown").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (dir, opts) => {
104
- const { watchDirectory } = await import("./watch-X7IC7MLF.js");
108
+ const { watchDirectory } = await import("./watch-Q6L4UBTC.js");
105
109
  await watchDirectory({
106
110
  dir,
107
111
  outDir: opts.outDir,
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/** kordoc CLI — 모두 파싱해버리겠다 */\n\nimport { readFileSync, writeFileSync, mkdirSync, statSync } from \"fs\"\nimport { basename, resolve } from \"path\"\nimport { Command } from \"commander\"\nimport { parse, detectFormat } from \"./index.js\"\nimport type { ParseOptions } from \"./types.js\"\nimport { VERSION, toArrayBuffer } from \"./utils.js\"\n\nconst program = new Command()\n\nprogram\n .name(\"kordoc\")\n .description(\"모두 파싱해버리겠다 — HWP, HWPX, PDF, XLSX, DOCX → Markdown\")\n .version(VERSION)\n .argument(\"<files...>\", \"변환할 파일 경로 (HWP, HWPX, PDF, XLSX, DOCX)\")\n .option(\"-o, --output <path>\", \"출력 파일 경로 (단일 파일 시)\")\n .option(\"-d, --out-dir <dir>\", \"출력 디렉토리 (다중 파일 시)\")\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위 (예: 1-3, 1,3,5)\")\n .option(\"--format <type>\", \"출력 형식: markdown (기본) 또는 json\", \"markdown\")\n .option(\"--no-header-footer\", \"PDF 머리글/바닥글 자동 제거\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (files: string[], opts) => {\n const validFormats = [\"markdown\", \"json\"]\n if (!validFormats.includes(opts.format)) {\n process.stderr.write(`[kordoc] 지원하지 않는 형식: ${opts.format} (markdown 또는 json)\\n`)\n process.exit(1)\n }\n for (let fi = 0; fi < files.length; fi++) {\n const filePath = files[fi]\n const absPath = resolve(filePath)\n const fileName = basename(absPath)\n const filePrefix = files.length > 1 ? `[${fi + 1}/${files.length}] ` : \"\"\n\n try {\n const fileSize = statSync(absPath).size\n if (fileSize > 500 * 1024 * 1024) {\n process.stderr.write(`\\n[kordoc] SKIP: ${fileName} — 파일이 너무 큽니다 (${(fileSize / 1024 / 1024).toFixed(1)}MB)\\n`)\n process.exitCode = 1\n continue\n }\n const buffer = readFileSync(absPath)\n const arrayBuffer = toArrayBuffer(buffer)\n const format = detectFormat(arrayBuffer)\n\n if (!opts.silent) {\n process.stderr.write(`[kordoc] ${filePrefix}${fileName} (${format}) ...`)\n }\n\n const parseOptions: ParseOptions = {}\n if (opts.pages) parseOptions.pages = opts.pages as string\n if (opts.headerFooter === false) parseOptions.removeHeaderFooter = false\n if (!opts.silent) {\n parseOptions.onProgress = (current: number, total: number) => {\n process.stderr.write(`\\r[kordoc] ${filePrefix}${fileName} (${format}) [${current}/${total}]`)\n }\n }\n const result = await parse(arrayBuffer, parseOptions)\n\n if (!result.success) {\n process.stderr.write(` FAIL\\n`)\n process.stderr.write(` → ${result.error}\\n`)\n process.exitCode = 1\n continue\n }\n\n if (!opts.silent) process.stderr.write(` OK\\n`)\n\n let markdown = result.markdown\n // --out-dir 시 이미지 참조 경로에 images/ 접두사 추가\n if (opts.outDir && result.images?.length) {\n markdown = markdown.replace(/!\\[image\\]\\(image_/g, \"![image](images/image_\")\n }\n const output = opts.format === \"json\"\n ? JSON.stringify(result, null, 2)\n : markdown\n\n // 이미지 저장 (--out-dir 또는 --output 시)\n const saveImages = (dir: string) => {\n if (!result.images?.length) return\n const imgDir = resolve(dir, \"images\")\n mkdirSync(imgDir, { recursive: true })\n for (const img of result.images) {\n writeFileSync(resolve(imgDir, img.filename), img.data)\n }\n if (!opts.silent) process.stderr.write(` → ${result.images.length}개 이미지 → ${imgDir}\\n`)\n }\n\n if (opts.output && files.length === 1) {\n writeFileSync(opts.output, output, \"utf-8\")\n if (!opts.silent) process.stderr.write(` → ${opts.output}\\n`)\n saveImages(resolve(opts.output, \"..\"))\n } else if (opts.outDir) {\n mkdirSync(opts.outDir, { recursive: true })\n const outExt = opts.format === \"json\" ? \".json\" : \".md\"\n const outPath = resolve(opts.outDir, fileName.replace(/\\.[^.]+$/, outExt))\n writeFileSync(outPath, output, \"utf-8\")\n if (!opts.silent) process.stderr.write(` → ${outPath}\\n`)\n saveImages(opts.outDir)\n } else {\n process.stdout.write(output + \"\\n\")\n }\n } catch (err) {\n const { sanitizeError } = await import(\"./utils.js\")\n process.stderr.write(`\\n[kordoc] ERROR: ${fileName} — ${sanitizeError(err)}\\n`)\n process.exitCode = 1\n }\n }\n })\n\nprogram\n .command(\"watch <dir>\")\n .description(\"디렉토리 감시 — 새 문서 자동 변환\")\n .option(\"--webhook <url>\", \"결과 전송 웹훅 URL\")\n .option(\"-d, --out-dir <dir>\", \"변환 결과 출력 디렉토리\")\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위\")\n .option(\"--format <type>\", \"출력 형식: markdown 또는 json\", \"markdown\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (dir: string, opts) => {\n const { watchDirectory } = await import(\"./watch.js\")\n await watchDirectory({\n dir,\n outDir: opts.outDir,\n webhook: opts.webhook,\n format: opts.format,\n pages: opts.pages,\n silent: opts.silent,\n })\n })\n\nprogram.parse()\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/** kordoc CLI — 모두 파싱해버리겠다 */\r\n\r\nimport { readFileSync, writeFileSync, mkdirSync, statSync } from \"fs\"\r\nimport { basename, resolve } from \"path\"\r\nimport { Command } from \"commander\"\r\nimport { parse, detectFormat } from \"./index.js\"\r\nimport type { ParseOptions } from \"./types.js\"\r\nimport { VERSION, toArrayBuffer, sanitizeError } from \"./utils.js\"\r\n\r\nconst program = new Command()\r\n\r\nprogram\r\n .name(\"kordoc\")\r\n .description(\"모두 파싱해버리겠다 — HWP, HWPX, PDF, XLSX, DOCX → Markdown\")\r\n .version(VERSION)\r\n .argument(\"<files...>\", \"변환할 파일 경로 (HWP, HWPX, PDF, XLSX, DOCX)\")\r\n .option(\"-o, --output <path>\", \"출력 파일 경로 (단일 파일 시)\")\r\n .option(\"-d, --out-dir <dir>\", \"출력 디렉토리 (다중 파일 시)\")\r\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위 (예: 1-3, 1,3,5)\")\r\n .option(\"--format <type>\", \"출력 형식: markdown (기본) 또는 json\", \"markdown\")\r\n .option(\"--no-header-footer\", \"PDF 머리글/바닥글 자동 제거\")\r\n .option(\"--silent\", \"진행 메시지 숨기기\")\r\n .action(async (files: string[], opts) => {\r\n const validFormats = [\"markdown\", \"json\"]\r\n if (!validFormats.includes(opts.format)) {\r\n process.stderr.write(`[kordoc] 지원하지 않는 형식: ${opts.format} (markdown 또는 json)\\n`)\r\n process.exit(1)\r\n }\r\n for (let fi = 0; fi < files.length; fi++) {\r\n const filePath = files[fi]\r\n const absPath = resolve(filePath)\r\n const fileName = basename(absPath)\r\n const filePrefix = files.length > 1 ? `[${fi + 1}/${files.length}] ` : \"\"\r\n\r\n try {\r\n const fileSize = statSync(absPath).size\r\n if (fileSize > 500 * 1024 * 1024) {\r\n process.stderr.write(`\\n[kordoc] SKIP: ${fileName} — 파일이 너무 큽니다 (${(fileSize / 1024 / 1024).toFixed(1)}MB)\\n`)\r\n process.exitCode = 1\r\n continue\r\n }\r\n const buffer = readFileSync(absPath)\r\n const arrayBuffer = toArrayBuffer(buffer)\r\n const format = detectFormat(arrayBuffer)\r\n\r\n if (!opts.silent) {\r\n process.stderr.write(`[kordoc] ${filePrefix}${fileName} (${format}) ...`)\r\n }\r\n\r\n const parseOptions: ParseOptions = {}\r\n if (opts.pages) parseOptions.pages = opts.pages as string\r\n if (opts.headerFooter === false) parseOptions.removeHeaderFooter = false\r\n if (!opts.silent) {\r\n parseOptions.onProgress = (current: number, total: number) => {\r\n process.stderr.write(`\\r[kordoc] ${filePrefix}${fileName} (${format}) [${current}/${total}]`)\r\n }\r\n }\r\n const result = await parse(arrayBuffer, parseOptions)\r\n\r\n if (!result.success) {\r\n process.stderr.write(` FAIL\\n`)\r\n process.stderr.write(` → ${result.error}\\n`)\r\n process.exitCode = 1\r\n continue\r\n }\r\n\r\n if (!opts.silent) process.stderr.write(` OK\\n`)\r\n\r\n let markdown = result.markdown\r\n // --out-dir 시 이미지 참조 경로에 images/ 접두사 추가\r\n if (opts.outDir && result.images?.length) {\r\n markdown = markdown.replace(/!\\[image\\]\\(image_/g, \"![image](images/image_\")\r\n }\r\n const output = opts.format === \"json\"\r\n ? JSON.stringify(result, (_key, value) =>\r\n value instanceof Uint8Array ? Buffer.from(value).toString(\"base64\") : value\r\n , 2)\r\n : markdown\r\n\r\n // 이미지 저장 (--out-dir 또는 --output 시)\r\n const saveImages = (dir: string) => {\r\n if (!result.images?.length) return\r\n const imgDir = resolve(dir, \"images\")\r\n mkdirSync(imgDir, { recursive: true })\r\n for (const img of result.images) {\r\n writeFileSync(resolve(imgDir, img.filename), img.data)\r\n }\r\n if (!opts.silent) process.stderr.write(` → ${result.images.length}개 이미지 → ${imgDir}\\n`)\r\n }\r\n\r\n if (opts.output && files.length === 1) {\r\n writeFileSync(opts.output, output, \"utf-8\")\r\n if (!opts.silent) process.stderr.write(` → ${opts.output}\\n`)\r\n saveImages(resolve(opts.output, \"..\"))\r\n } else if (opts.outDir) {\r\n mkdirSync(opts.outDir, { recursive: true })\r\n const outExt = opts.format === \"json\" ? \".json\" : \".md\"\r\n const outPath = resolve(opts.outDir, fileName.replace(/\\.[^.]+$/, outExt))\r\n writeFileSync(outPath, output, \"utf-8\")\r\n if (!opts.silent) process.stderr.write(` → ${outPath}\\n`)\r\n saveImages(opts.outDir)\r\n } else {\r\n process.stdout.write(output + \"\\n\")\r\n }\r\n } catch (err) {\r\n process.stderr.write(`\\n[kordoc] ERROR: ${fileName} — ${sanitizeError(err)}\\n`)\r\n process.exitCode = 1\r\n }\r\n }\r\n })\r\n\r\nprogram\r\n .command(\"watch <dir>\")\r\n .description(\"디렉토리 감시 — 새 문서 자동 변환\")\r\n .option(\"--webhook <url>\", \"결과 전송 웹훅 URL\")\r\n .option(\"-d, --out-dir <dir>\", \"변환 결과 출력 디렉토리\")\r\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위\")\r\n .option(\"--format <type>\", \"출력 형식: markdown 또는 json\", \"markdown\")\r\n .option(\"--silent\", \"진행 메시지 숨기기\")\r\n .action(async (dir: string, opts) => {\r\n const { watchDirectory } = await import(\"./watch.js\")\r\n await watchDirectory({\r\n dir,\r\n outDir: opts.outDir,\r\n webhook: opts.webhook,\r\n format: opts.format,\r\n pages: opts.pages,\r\n silent: opts.silent,\r\n })\r\n })\r\n\r\nprogram.parse()\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ detectFormat,
4
+ detectZipFormat,
5
+ isHwpxFile,
6
+ isOldHwpFile,
7
+ isPdfFile,
8
+ isZipFile
9
+ } from "./chunk-MUAWCQDY.js";
10
+ export {
11
+ detectFormat,
12
+ detectZipFormat,
13
+ isHwpxFile,
14
+ isOldHwpFile,
15
+ isPdfFile,
16
+ isZipFile
17
+ };
18
+ //# sourceMappingURL=detect-63IGCXTH.js.map