@fullgreengn/converter 1.0.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 ADDED
@@ -0,0 +1,137 @@
1
+ # @fullgreen/converter
2
+
3
+ A CLI to convert:
4
+
5
+ - `.heic` -> `.jpg` or `.png` (via `sharp`)
6
+ - `.docx` -> `.pdf` (via `mammoth` + `puppeteer`)
7
+
8
+ ## Requirements
9
+
10
+ - Node.js 20+
11
+ - `pnpm`
12
+
13
+ ## Install Dependencies
14
+
15
+ ```bash
16
+ pnpm install
17
+ ```
18
+
19
+ If `pnpm` blocks install scripts (common in pnpm v10+), allow Puppeteer and install Chromium:
20
+
21
+ ```bash
22
+ pnpm approve-builds
23
+ pnpm rebuild puppeteer
24
+ pnpm exec puppeteer browsers install chrome
25
+ ```
26
+
27
+ ## Build
28
+
29
+ ```bash
30
+ pnpm build
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### Direct command mode
36
+
37
+ ```bash
38
+ fullgreen-convert <input> [output] [--format jpg|png]
39
+ ```
40
+
41
+ Examples:
42
+
43
+ ```bash
44
+ # Auto output path: ./photo.jpg
45
+ fullgreen-convert ./photo.heic
46
+
47
+ # Explicit output path and format
48
+ fullgreen-convert ./photo.heic ./photo.png --format png
49
+
50
+ # Auto output path: ./report.pdf
51
+ fullgreen-convert ./report.docx
52
+
53
+ # Explicit output path
54
+ fullgreen-convert ./report.docx ./exports/report.pdf
55
+ ```
56
+
57
+ ### Interactive mode
58
+
59
+ If no arguments are provided, the CLI prompts for input and output details:
60
+
61
+ ```bash
62
+ fullgreen-convert
63
+ ```
64
+
65
+ ## Run with pnpm dlx
66
+
67
+ After publishing to npm, run without global install:
68
+
69
+ ```bash
70
+ pnpm dlx @fullgreen/converter ./photo.heic
71
+ ```
72
+
73
+ You can also pass output and format:
74
+
75
+ ```bash
76
+ pnpm dlx @fullgreen/converter ./photo.heic ./photo.png --format png
77
+ ```
78
+
79
+ ## Test Locally with Global Link
80
+
81
+ From project root:
82
+
83
+ ```bash
84
+ pnpm install
85
+ pnpm build
86
+ pnpm link --global
87
+ ```
88
+
89
+ Then use it anywhere:
90
+
91
+ ```bash
92
+ fullgreen-convert /absolute/path/to/input.heic
93
+ ```
94
+
95
+ To remove the global link:
96
+
97
+ ```bash
98
+ pnpm unlink --global @fullgreen/converter
99
+ ```
100
+
101
+ ## Development Scripts
102
+
103
+ ```bash
104
+ pnpm dev
105
+ pnpm typecheck
106
+ pnpm build
107
+ pnpm test
108
+ ```
109
+
110
+ ## Notes
111
+
112
+ - HEIC conversion supports only JPG and PNG outputs.
113
+ - DOCX conversion is Node-only and uses an embedded Chromium runtime from `puppeteer`.
114
+ - The first install can take longer because `puppeteer` downloads a browser binary.
115
+ - Unsupported file extensions are rejected with clear error messages.
116
+
117
+ ## Troubleshooting HEIC Errors
118
+
119
+ If you see errors like:
120
+
121
+ - `heif: Error while loading plugin: Support for this compression format has not been built in`
122
+ - `bad seek` while reading a `.heic` file
123
+
124
+ try:
125
+
126
+ ```bash
127
+ node -v
128
+ pnpm rebuild sharp
129
+ pnpm up sharp
130
+ ```
131
+
132
+ Use Node.js 20 or 22 LTS if possible. Node 25 may have native-module compatibility gaps.
133
+
134
+ On macOS, this CLI automatically falls back to `sips` when `sharp/libheif` cannot decode a HEIC variant.
135
+
136
+ If conversion still fails, the source HEIC may be corrupted or encoded with a codec your runtime cannot decode.
137
+
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import u from"path";import{access as B}from"fs/promises";import p from"chalk";import{Command as N}from"commander";import l from"inquirer";import h from"ora";import{access as x}from"fs/promises";import F from"path";import b from"mammoth";import C from"puppeteer";async function g(e){let{inputPath:t,outputPath:n}=e;await S(t);let r=F.extname(t).toLowerCase();if(r!==".docx")throw new Error(`Unsupported document input format: ${r||"unknown"}. Only .docx is supported.`);let o=await O(t);return await T(o,n),n}async function O(e){try{let t=await b.convertToHtml({path:e});if(!t.value?.trim())throw new Error("DOCX to HTML conversion returned empty content.");return $(t.value)}catch(t){let n=t instanceof Error?t.message:String(t);throw new Error(`DOCX parsing failed: ${n}`)}}async function T(e,t){let n;try{n=await C.launch({headless:!0});let r=await n.newPage();await r.setContent(e,{waitUntil:"networkidle0"}),await r.pdf({path:t,format:"A4",printBackground:!0,margin:{top:"18mm",right:"14mm",bottom:"18mm",left:"14mm"}})}catch(r){let o=r instanceof Error?r.message:String(r),i=o.toLowerCase();throw i.includes("could not find chrome")||i.includes("failed to launch the browser process")?new Error(`PDF rendering failed: Puppeteer browser is not available.
3
+ If you are using pnpm v10+, allow install scripts and install Chromium:
4
+ 1) pnpm approve-builds
5
+ 2) pnpm rebuild puppeteer
6
+ 3) pnpm exec puppeteer browsers install chrome`):new Error(`PDF rendering failed: ${o}`)}finally{n&&await n.close()}}function $(e){return`<!doctype html>
7
+ <html>
8
+ <head>
9
+ <meta charset="utf-8" />
10
+ <style>
11
+ body {
12
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
13
+ line-height: 1.5;
14
+ font-size: 12pt;
15
+ }
16
+ img {
17
+ max-width: 100%;
18
+ height: auto;
19
+ }
20
+ table {
21
+ border-collapse: collapse;
22
+ width: 100%;
23
+ }
24
+ td, th {
25
+ border: 1px solid #ddd;
26
+ padding: 6px;
27
+ }
28
+ </style>
29
+ </head>
30
+ <body>
31
+ ${e}
32
+ </body>
33
+ </html>`}async function S(e){try{await x(e)}catch{throw new Error(`Input file not found: ${e}`)}}import{access as H}from"fs/promises";import j from"path";import{execFile as k}from"child_process";import{promisify as R}from"util";import D from"sharp";var A=R(k);async function d(e){let{inputPath:t,outputPath:n,format:r}=e;await M(t);let o=j.extname(t).toLowerCase();if(o!==".heic")throw new Error(`Unsupported image input format: ${o||"unknown"}. Only .heic is supported.`);if(!["jpg","png"].includes(r))throw new Error(`Unsupported output image format: ${r}. Supported formats are jpg and png.`);try{let i=D(t,{failOn:"error"});r==="jpg"?await i.jpeg({quality:90}).toFile(n):await i.png().toFile(n)}catch(i){if(L(i))try{return await U({inputPath:t,outputPath:n,format:r}),n}catch(s){throw f(i,t,s)}throw f(i,t)}return n}function f(e,t,n){let r=e instanceof Error?e.message:String(e),o=r.toLowerCase(),i=n?`
34
+ Fallback (macOS sips) error: ${n instanceof Error?n.message:String(n)}`:"";return o.includes("support for this compression format has not been built in")||o.includes("heif: error while loading plugin")?new Error(`HEIC decoding is not available in the current sharp/libvips runtime.
35
+ Input: ${t}
36
+ Try one of the following:
37
+ 1) Use Node.js 20 or 22 LTS (Node 25 may not have compatible native binaries yet)
38
+ 2) Reinstall sharp for your platform: pnpm rebuild sharp
39
+ 3) Update sharp to the latest version
40
+ 4) Convert this HEIC using another tool and retry
41
+ Original error: ${r}`+i):o.includes("no decoding plugin installed for this compression format")?new Error(`HEIC decoding plugin is unavailable for this runtime.
42
+ Input: ${t}
43
+ 1) Reinstall sharp for your platform: pnpm rebuild sharp
44
+ Original error: ${r}`+i):o.includes("bad seek")||o.includes("invalid input")?new Error(`The HEIC file appears corrupted or partially unreadable: ${t}
45
+ Original error: ${r}`+i):new Error(`Image conversion failed for ${t}: ${r}${i}`)}function L(e){if(process.platform!=="darwin")return!1;let t=(e instanceof Error?e.message:String(e)).toLowerCase();return t.includes("support for this compression format has not been built in")||t.includes("heif: error while loading plugin")||t.includes("no decoding plugin installed for this compression format")}async function U(e){let{inputPath:t,outputPath:n,format:r}=e;await A("sips",["-s","format",r==="jpg"?"jpeg":"png",t,"--out",n])}async function M(e){try{await H(e)}catch{throw new Error(`Input file not found: ${e}`)}}var w=new N;w.name("fullgreen-convert").description("Convert .heic images to .jpg/.png and .docx documents to .pdf").argument("[input]","input file path").argument("[output]","output file path").option("-f, --format <format>","output format for HEIC conversion (jpg|png)").action(async(e,t,n)=>{try{if(!e){await z();return}await q(e,t,n??{})}catch(r){E(r)}});w.parseAsync(process.argv).catch(e=>E(e));async function q(e,t,n){let r=m(e);await I(r);let o=v(r),i=G(n?.format),s=X({inputPath:r,outputPathRaw:t,inputType:o,imageFormat:i}),c=h(`Converting ${p.cyan(u.basename(r))}...`).start();try{await y({inputPath:r,outputPath:s,inputType:o,imageFormat:i}),c.succeed(p.green(`Conversion completed: ${s}`))}catch(a){throw c.fail(p.red("Conversion failed")),a}}async function z(){let e=await l.prompt([{type:"input",name:"inputPathRaw",message:"Input file path:",validate:a=>a.trim().length>0?!0:"Please provide an input file path."}]),t=m(e.inputPathRaw);await I(t);let n=v(t),r="jpg";n==="heic"&&(r=(await l.prompt([{type:"list",name:"format",message:"Select output image format:",choices:[{name:"JPG",value:"jpg"},{name:"PNG",value:"png"}]}])).format);let o=P({inputPath:t,inputType:n,imageFormat:r}),i=await l.prompt([{type:"input",name:"outputPathRaw",message:"Output file path:",default:o,filter:a=>a.trim(),validate:a=>a.trim().length>0?!0:"Please provide an output file path."}]),s=m(i.outputPathRaw),c=h(`Converting ${p.cyan(u.basename(t))}...`).start();try{await y({inputPath:t,outputPath:s,inputType:n,imageFormat:r}),c.succeed(p.green(`Conversion completed: ${s}`))}catch(a){throw c.fail(p.red("Conversion failed")),a}}async function y(e){let{inputPath:t,outputPath:n,inputType:r,imageFormat:o}=e;if(r==="heic"){await d({inputPath:t,outputPath:n,format:o});return}await g({inputPath:t,outputPath:n})}function v(e){let t=u.extname(e).toLowerCase();if(t===".heic")return"heic";if(t===".docx")return"docx";throw new Error(`Unsupported input format: ${t||"unknown"}. Supported formats are .heic and .docx.`)}function G(e){if(!e)return"jpg";let t=e.toLowerCase();if(t!=="jpg"&&t!=="png")throw new Error(`Invalid format option: ${e}. Allowed values are jpg or png.`);return t}function X(e){let{inputPath:t,outputPathRaw:n,inputType:r,imageFormat:o}=e;return n?m(n):P({inputPath:t,inputType:r,imageFormat:o})}function P(e){let{inputPath:t,inputType:n,imageFormat:r}=e,o=n==="heic"?r:"pdf",i=u.parse(t);return u.join(i.dir,`${i.name}.${o}`)}function m(e){return u.resolve(process.cwd(),e)}async function I(e){try{await B(e)}catch{throw new Error(`Input file not found: ${e}`)}}function E(e){let t=e instanceof Error?e.message:String(e);console.error(p.red(`Error: ${t}`)),process.exit(1)}
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/converters/document.ts","../src/converters/image.ts"],"sourcesContent":["\nimport path from 'node:path';\nimport { access } from 'node:fs/promises';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport inquirer from 'inquirer';\nimport ora from 'ora';\nimport { convertDocxToPdf } from './converters/document.js';\nimport { convertHeicToImage, type ImageOutputFormat } from './converters/image.js';\n\ntype SupportedInputType = 'heic' | 'docx';\n\ninterface CliOptions {\n format?: ImageOutputFormat;\n}\n\nconst program = new Command();\n\nprogram\n .name('fullgreen-convert')\n .description('Convert .heic images to .jpg/.png and .docx documents to .pdf')\n .argument('[input]', 'input file path')\n .argument('[output]', 'output file path')\n .option('-f, --format <format>', 'output format for HEIC conversion (jpg|png)')\n .action(async (input?: string, output?: string, options?: CliOptions) => {\n\ttry {\n\t if (!input) {\n\t\tawait runInteractiveMode();\n\t\treturn;\n\t }\n\n\t await runDirectMode(input, output, options ?? {});\n\t} catch (error) {\n\t handleFatalError(error);\n\t}\n });\n\nprogram.parseAsync(process.argv).catch((error) => handleFatalError(error));\n\nasync function runDirectMode(inputPathRaw: string, outputPathRaw?: string, options?: CliOptions): Promise<void> {\n const inputPath = resolvePath(inputPathRaw);\n await ensureFileExists(inputPath);\n\n const inputType = detectInputType(inputPath);\n const imageFormat = resolveImageFormat(options?.format);\n const outputPath = resolveOutputPath({ inputPath, outputPathRaw, inputType, imageFormat });\n\n const spinner = ora(`Converting ${chalk.cyan(path.basename(inputPath))}...`).start();\n try {\n\tawait convertByType({ inputPath, outputPath, inputType, imageFormat });\n\tspinner.succeed(chalk.green(`Conversion completed: ${outputPath}`));\n } catch (error) {\n\tspinner.fail(chalk.red('Conversion failed'));\n\tthrow error;\n }\n}\n\nasync function runInteractiveMode(): Promise<void> {\n const answers = await inquirer.prompt<{\n\tinputPathRaw: string;\n }>([\n\t{\n\t type: 'input',\n\t name: 'inputPathRaw',\n\t message: 'Input file path:',\n\t validate: (value: string) => (value.trim().length > 0 ? true : 'Please provide an input file path.'),\n\t},\n ]);\n\n const inputPath = resolvePath(answers.inputPathRaw);\n await ensureFileExists(inputPath);\n const inputType = detectInputType(inputPath);\n\n let imageFormat: ImageOutputFormat = 'jpg';\n if (inputType === 'heic') {\n\tconst imageAnswers = await inquirer.prompt<{\n\t format: ImageOutputFormat;\n\t}>([\n\t {\n\t\ttype: 'list',\n\t\tname: 'format',\n\t\tmessage: 'Select output image format:',\n\t\tchoices: [\n\t\t { name: 'JPG', value: 'jpg' },\n\t\t { name: 'PNG', value: 'png' },\n\t\t],\n\t },\n\t]);\n\timageFormat = imageAnswers.format;\n }\n\n const defaultOutputPath = buildDefaultOutputPath({ inputPath, inputType, imageFormat });\n const outputAnswers = await inquirer.prompt<{\n\toutputPathRaw: string;\n }>([\n\t{\n\t type: 'input',\n\t name: 'outputPathRaw',\n\t message: 'Output file path:',\n\t default: defaultOutputPath,\n\t filter: (value: string) => value.trim(),\n\t validate: (value: string) => (value.trim().length > 0 ? true : 'Please provide an output file path.'),\n\t},\n ]);\n\n const outputPath = resolvePath(outputAnswers.outputPathRaw);\n const spinner = ora(`Converting ${chalk.cyan(path.basename(inputPath))}...`).start();\n try {\n\tawait convertByType({ inputPath, outputPath, inputType, imageFormat });\n\tspinner.succeed(chalk.green(`Conversion completed: ${outputPath}`));\n } catch (error) {\n\tspinner.fail(chalk.red('Conversion failed'));\n\tthrow error;\n }\n}\n\nasync function convertByType(params: {\n inputPath: string;\n outputPath: string;\n inputType: SupportedInputType;\n imageFormat: ImageOutputFormat;\n}): Promise<void> {\n const { inputPath, outputPath, inputType, imageFormat } = params;\n\n if (inputType === 'heic') {\n\tawait convertHeicToImage({\n\t inputPath,\n\t outputPath,\n\t format: imageFormat,\n\t});\n\treturn;\n }\n\n await convertDocxToPdf({\n\tinputPath,\n\toutputPath,\n });\n}\n\nfunction detectInputType(inputPath: string): SupportedInputType {\n const extension = path.extname(inputPath).toLowerCase();\n if (extension === '.heic') {\n\treturn 'heic';\n }\n\n if (extension === '.docx') {\n\treturn 'docx';\n }\n\n throw new Error(`Unsupported input format: ${extension || 'unknown'}. Supported formats are .heic and .docx.`);\n}\n\nfunction resolveImageFormat(format?: string): ImageOutputFormat {\n if (!format) {\n\treturn 'jpg';\n }\n\n const normalized = format.toLowerCase();\n if (normalized !== 'jpg' && normalized !== 'png') {\n\tthrow new Error(`Invalid format option: ${format}. Allowed values are jpg or png.`);\n }\n\n return normalized;\n}\n\nfunction resolveOutputPath(params: {\n inputPath: string;\n outputPathRaw?: string;\n inputType: SupportedInputType;\n imageFormat: ImageOutputFormat;\n}): string {\n const { inputPath, outputPathRaw, inputType, imageFormat } = params;\n if (outputPathRaw) {\n\treturn resolvePath(outputPathRaw);\n }\n\n return buildDefaultOutputPath({ inputPath, inputType, imageFormat });\n}\n\nfunction buildDefaultOutputPath(params: {\n inputPath: string;\n inputType: SupportedInputType;\n imageFormat: ImageOutputFormat;\n}): string {\n const { inputPath, inputType, imageFormat } = params;\n const outputExt = inputType === 'heic' ? imageFormat : 'pdf';\n const parsed = path.parse(inputPath);\n return path.join(parsed.dir, `${parsed.name}.${outputExt}`);\n}\n\nfunction resolvePath(filePath: string): string {\n return path.resolve(process.cwd(), filePath);\n}\n\nasync function ensureFileExists(filePath: string): Promise<void> {\n try {\n\tawait access(filePath);\n } catch {\n\tthrow new Error(`Input file not found: ${filePath}`);\n }\n}\n\nfunction handleFatalError(error: unknown): never {\n const message = error instanceof Error ? error.message : String(error);\n console.error(chalk.red(`Error: ${message}`));\n process.exit(1);\n}\n","import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport mammoth from 'mammoth';\nimport puppeteer from 'puppeteer';\n\nexport interface ConvertDocxToPdfOptions {\n inputPath: string;\n outputPath: string;\n}\n\nexport async function convertDocxToPdf(options: ConvertDocxToPdfOptions): Promise<string> {\n const { inputPath, outputPath } = options;\n\n await ensureFileExists(inputPath);\n\n const inputExtension = path.extname(inputPath).toLowerCase();\n if (inputExtension !== '.docx') {\n throw new Error(`Unsupported document input format: ${inputExtension || 'unknown'}. Only .docx is supported.`);\n }\n\n const html = await convertDocxToHtml(inputPath);\n await renderHtmlToPdf(html, outputPath);\n return outputPath;\n}\n\nasync function convertDocxToHtml(inputPath: string): Promise<string> {\n try {\n const result = await mammoth.convertToHtml({ path: inputPath });\n if (!result.value?.trim()) {\n throw new Error('DOCX to HTML conversion returned empty content.');\n }\n\n return wrapHtmlDocument(result.value);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`DOCX parsing failed: ${message}`);\n }\n}\n\nasync function renderHtmlToPdf(html: string, outputPath: string): Promise<void> {\n let browser: Awaited<ReturnType<typeof puppeteer.launch>> | undefined;\n\n try {\n browser = await puppeteer.launch({ headless: true });\n const page = await browser.newPage();\n await page.setContent(html, { waitUntil: 'networkidle0' });\n await page.pdf({\n path: outputPath,\n format: 'A4',\n printBackground: true,\n margin: {\n top: '18mm',\n right: '14mm',\n bottom: '18mm',\n left: '14mm',\n },\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const lowered = message.toLowerCase();\n\n if (lowered.includes('could not find chrome') || lowered.includes('failed to launch the browser process')) {\n throw new Error(\n 'PDF rendering failed: Puppeteer browser is not available.\\n' +\n 'If you are using pnpm v10+, allow install scripts and install Chromium:\\n' +\n '1) pnpm approve-builds\\n' +\n '2) pnpm rebuild puppeteer\\n' +\n '3) pnpm exec puppeteer browsers install chrome',\n );\n }\n\n throw new Error(`PDF rendering failed: ${message}`);\n } finally {\n if (browser) {\n await browser.close();\n }\n }\n}\n\nfunction wrapHtmlDocument(content: string): string {\n return `<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif;\n line-height: 1.5;\n font-size: 12pt;\n }\n img {\n max-width: 100%;\n height: auto;\n }\n table {\n border-collapse: collapse;\n width: 100%;\n }\n td, th {\n border: 1px solid #ddd;\n padding: 6px;\n }\n </style>\n </head>\n <body>\n ${content}\n </body>\n</html>`;\n}\n\nasync function ensureFileExists(filePath: string): Promise<void> {\n try {\n await access(filePath);\n } catch {\n throw new Error(`Input file not found: ${filePath}`);\n }\n}\n\n","import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport sharp from 'sharp';\n\nexport type ImageOutputFormat = 'jpg' | 'png';\n\nexport interface ConvertHeicToImageOptions {\n inputPath: string;\n outputPath: string;\n format: ImageOutputFormat;\n}\n\nconst execFileAsync = promisify(execFile);\n\nexport async function convertHeicToImage(options: ConvertHeicToImageOptions): Promise<string> {\n const { inputPath, outputPath, format } = options;\n\n await ensureFileExists(inputPath);\n\n const inputExtension = path.extname(inputPath).toLowerCase();\n if (inputExtension !== '.heic') {\n throw new Error(`Unsupported image input format: ${inputExtension || 'unknown'}. Only .heic is supported.`);\n }\n\n if (!['jpg', 'png'].includes(format)) {\n throw new Error(`Unsupported output image format: ${format}. Supported formats are jpg and png.`);\n }\n\n try {\n const image = sharp(inputPath, { failOn: 'error' });\n\n if (format === 'jpg') {\n await image.jpeg({ quality: 90 }).toFile(outputPath);\n } else {\n await image.png().toFile(outputPath);\n }\n } catch (error) {\n if (canUseMacOSSipsFallback(error)) {\n try {\n await convertHeicWithSips({ inputPath, outputPath, format });\n return outputPath;\n } catch (fallbackError) {\n throw mapHeicRuntimeError(error, inputPath, fallbackError);\n }\n }\n\n throw mapHeicRuntimeError(error, inputPath);\n }\n\n return outputPath;\n}\n\nfunction mapHeicRuntimeError(error: unknown, inputPath: string, fallbackError?: unknown): Error {\n const rawMessage = error instanceof Error ? error.message : String(error);\n const lowered = rawMessage.toLowerCase();\n const fallbackMessage = fallbackError\n ? `\\nFallback (macOS sips) error: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`\n : '';\n\n if (lowered.includes('support for this compression format has not been built in') || lowered.includes('heif: error while loading plugin')) {\n return new Error(\n `HEIC decoding is not available in the current sharp/libvips runtime.\\n` +\n `Input: ${inputPath}\\n` +\n 'Try one of the following:\\n' +\n '1) Use Node.js 20 or 22 LTS (Node 25 may not have compatible native binaries yet)\\n' +\n '2) Reinstall sharp for your platform: pnpm rebuild sharp\\n' +\n '3) Update sharp to the latest version\\n' +\n '4) Convert this HEIC using another tool and retry\\n' +\n `Original error: ${rawMessage}` +\n fallbackMessage,\n );\n }\n\n if (lowered.includes('no decoding plugin installed for this compression format')) {\n return new Error(\n `HEIC decoding plugin is unavailable for this runtime.\\n` +\n `Input: ${inputPath}\\n` +\n '1) Reinstall sharp for your platform: pnpm rebuild sharp\\n' +\n `Original error: ${rawMessage}` +\n fallbackMessage,\n );\n }\n\n if (lowered.includes('bad seek') || lowered.includes('invalid input')) {\n return new Error(\n `The HEIC file appears corrupted or partially unreadable: ${inputPath}\\n` +\n `Original error: ${rawMessage}` +\n fallbackMessage,\n );\n }\n\n return new Error(`Image conversion failed for ${inputPath}: ${rawMessage}${fallbackMessage}`);\n}\n\nfunction canUseMacOSSipsFallback(error: unknown): boolean {\n if (process.platform !== 'darwin') {\n return false;\n }\n\n const message = (error instanceof Error ? error.message : String(error)).toLowerCase();\n return (\n message.includes('support for this compression format has not been built in') ||\n message.includes('heif: error while loading plugin') ||\n message.includes('no decoding plugin installed for this compression format')\n );\n}\n\nasync function convertHeicWithSips(options: ConvertHeicToImageOptions): Promise<void> {\n const { inputPath, outputPath, format } = options;\n const sipsFormat = format === 'jpg' ? 'jpeg' : 'png';\n\n await execFileAsync('sips', ['-s', 'format', sipsFormat, inputPath, '--out', outputPath]);\n}\n\nasync function ensureFileExists(filePath: string): Promise<void> {\n try {\n await access(filePath);\n } catch {\n throw new Error(`Input file not found: ${filePath}`);\n }\n}\n\n"],"mappings":"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","names":["path","access","chalk","Command","inquirer","ora","access","path","mammoth","puppeteer","convertDocxToPdf","options","inputPath","outputPath","ensureFileExists","inputExtension","html","convertDocxToHtml","renderHtmlToPdf","result","wrapHtmlDocument","error","message","browser","page","lowered","content","filePath","access","path","execFile","promisify","sharp","execFileAsync","convertHeicToImage","options","inputPath","outputPath","format","ensureFileExists","inputExtension","image","error","canUseMacOSSipsFallback","convertHeicWithSips","fallbackError","mapHeicRuntimeError","rawMessage","lowered","fallbackMessage","message","filePath","program","Command","input","output","options","runInteractiveMode","runDirectMode","error","handleFatalError","inputPathRaw","outputPathRaw","inputPath","resolvePath","ensureFileExists","inputType","detectInputType","imageFormat","resolveImageFormat","outputPath","resolveOutputPath","spinner","ora","chalk","path","convertByType","answers","inquirer","value","defaultOutputPath","buildDefaultOutputPath","outputAnswers","params","convertHeicToImage","convertDocxToPdf","extension","format","normalized","outputExt","parsed","filePath","access","message"]}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@fullgreengn/converter",
3
+ "version": "1.0.0",
4
+ "description": "Professional CLI to convert HEIC and DOCX files.",
5
+ "bin": {
6
+ "fullgreen-convert": "dist/index.js"
7
+ },
8
+ "type": "module",
9
+ "main": "dist/index.js",
10
+ "engines": {
11
+ "node": ">=20 <25"
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "dependencies": {
17
+ "chalk": "^5.6.2",
18
+ "commander": "^14.0.3",
19
+ "inquirer": "^13.4.1",
20
+ "mammoth": "^1.9.1",
21
+ "ora": "^9.3.0",
22
+ "puppeteer": "^24.7.2",
23
+ "sharp": "^0.34.5"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25.6.0",
27
+ "tsup": "^8.5.1",
28
+ "typescript": "^5.5.3"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "dev": "tsup --watch",
36
+ "clean": "rm -rf dist",
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "pnpm build && node --test test/smoke.test.mjs"
39
+ }
40
+ }