enex-io 0.1.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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ghazi (mgks.dev)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# enex-io
|
|
2
|
+
|
|
3
|
+
> High-performance parser and generator for Evernote & Apple Notes (.enex) files.
|
|
4
|
+
|
|
5
|
+
<a href="https://www.npmjs.com/package/enex-io"><img src="https://img.shields.io/npm/v/enex-io.svg?style=flat-square&color=007acc" alt="npm version"></a>
|
|
6
|
+
<a href="https://bundlephobia.com/package/enex-io"><img src="https://img.shields.io/bundlephobia/minzip/enex-io?style=flat-square" alt="size"></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/enex-io"><img src="https://img.shields.io/npm/dt/enex-io.svg?style=flat-square&color=success" alt="npm downloads"></a>
|
|
8
|
+
<a href="https://github.com/mgks/enex-io/blob/main/LICENSE"><img src="https://img.shields.io/github/license/mgks/enex-io.svg?style=flat-square&color=blue" alt="license"></a>
|
|
9
|
+
<a href="https://github.com/mgks/enex-io/stargazers"><img src="https://img.shields.io/github/stars/mgks/enex-io?style=flat-square&logo=github" alt="stars"></a>
|
|
10
|
+
|
|
11
|
+
A lightweight, zero-dependency Node.js library and CLI tool to convert **.enex** files to JSON and back. Perfect for migrations, backups, and data processing.
|
|
12
|
+
|
|
13
|
+
## 📦 Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Install globally for CLI usage
|
|
17
|
+
npm install -g enex-io
|
|
18
|
+
|
|
19
|
+
# Install as a dependency in your project
|
|
20
|
+
npm install enex-io
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 💻 CLI Usage
|
|
24
|
+
|
|
25
|
+
**Convert ENEX to JSON**
|
|
26
|
+
```bash
|
|
27
|
+
enex-io to-json my-notes.enex
|
|
28
|
+
# Output: my-notes.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Convert JSON to ENEX**
|
|
32
|
+
```bash
|
|
33
|
+
enex-io to-enex backup.json
|
|
34
|
+
# Output: backup.enex
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Options**
|
|
38
|
+
```bash
|
|
39
|
+
-o, --output <path> Specify output file path
|
|
40
|
+
-p, --pretty Pretty print JSON output (default: true)
|
|
41
|
+
--version Show version number
|
|
42
|
+
--help Show help
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 🔧 API Usage
|
|
46
|
+
|
|
47
|
+
Built for modern Node.js environments (ESM).
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
import { parseEnex, generateEnex } from 'enex-io';
|
|
51
|
+
import fs from 'fs';
|
|
52
|
+
|
|
53
|
+
// 1. Parse ENEX to Object
|
|
54
|
+
const xml = fs.readFileSync('notes.enex', 'utf-8');
|
|
55
|
+
const notes = parseEnex(xml);
|
|
56
|
+
|
|
57
|
+
console.log(notes[0].title); // "My Note"
|
|
58
|
+
|
|
59
|
+
// 2. Generate ENEX from Object
|
|
60
|
+
const myNotes = [
|
|
61
|
+
{
|
|
62
|
+
title: "Hello World",
|
|
63
|
+
content: "<div>This is a <b>test</b>.</div>",
|
|
64
|
+
tags: ["personal", "test"],
|
|
65
|
+
created: "2023-10-27T10:00:00.000Z",
|
|
66
|
+
updated: "2023-10-27T12:00:00.000Z"
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const enexOutput = generateEnex(myNotes);
|
|
71
|
+
fs.writeFileSync('export.enex', enexOutput);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 🧩 Type Definition
|
|
75
|
+
|
|
76
|
+
The standard Note object used by the parser and generator:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
interface Note {
|
|
80
|
+
title: string;
|
|
81
|
+
content: string; // HTML content string
|
|
82
|
+
tags: string[];
|
|
83
|
+
created: string; // ISO 8601 Date String
|
|
84
|
+
updated: string; // ISO 8601 Date String
|
|
85
|
+
author?: string;
|
|
86
|
+
sourceUrl?: string;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
93
|
+
|
|
94
|
+
> **{ github.com/mgks }**
|
|
95
|
+
>
|
|
96
|
+
>  
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{Command as O}from"commander";import s from"fs";import{createRequire as j}from"module";import*as f from"cheerio";import a from"dayjs";function x(t,e={}){let i=`<?xml version="1.0" encoding="UTF-8"?>
|
|
3
|
+
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export3.dtd">
|
|
4
|
+
<en-export export-date="${a().format("YYYYMMDDTHHmmssZ")}" application="${e.application||"enex-io"}" version="1.0">`;for(let n of t)i+=`
|
|
5
|
+
<note>
|
|
6
|
+
<title>${u(n.title)}</title>
|
|
7
|
+
<content><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
8
|
+
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
|
|
9
|
+
<en-note>${w(n.content)}</en-note>]]></content>
|
|
10
|
+
<created>${m(n.created)}</created>
|
|
11
|
+
<updated>${m(n.updated)}</updated>
|
|
12
|
+
${n.tags.map(o=>`<tag>${u(o)}</tag>`).join("")}
|
|
13
|
+
</note>`;return i+=`
|
|
14
|
+
</en-export>`,i}function g(t){let e=f.load(t,{xmlMode:!0}),r=[];return e("note").each((i,n)=>{let o=e(n),$=o.find("title").text()||"Untitled",S=o.find("content").text(),p=/<en-note[^>]*>([\s\S]*)<\/en-note>/.exec(S),y=p&&p[1]?p[1]:"",E=d(o.find("created").text()),Y=d(o.find("updated").text()),l=[];o.find("tag").each((N,v)=>{l.push(e(v).text())}),r.push({title:$,content:y,created:E,updated:Y,tags:l})}),r}function u(t){return t?t.replace(/[<>&'"]/g,e=>{switch(e){case"<":return"<";case">":return">";case"&":return"&";case"'":return"'";case'"':return""";default:return e}}):""}function w(t){return t?t.replace(/<br>/g,"<br/>"):""}function m(t){return a(t).isValid()?a(t).format("YYYYMMDDTHHmmssZ"):a().format("YYYYMMDDTHHmmssZ")}function d(t){if(!t)return new Date().toISOString();let e=t.replace(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/,"$1-$2-$3T$4:$5:$6Z");return a(e).isValid()?e:new Date().toISOString()}var D=j(import.meta.url),h=D("../package.json"),c=new O;c.name("enex-io").description(h.description).version(h.version,"-v, --version").helpOption("-h, --help","Display help for command").showHelpAfterError();c.command("to-json").description("Convert an .enex file into a structured .json file").argument("<input>","Input .enex file path").option("-o, --output <path>","Output .json file path").option("-p, --pretty","Pretty print JSON output",!0).action((t,e)=>{try{if(!s.existsSync(t))throw new Error(`Input file not found: ${t}`);console.log(`\u23F3 Parsing ${t}...`);let r=s.readFileSync(t,"utf-8"),i=g(r),n=e.output||t.replace(/\.enex$/i,".json"),o=e.pretty?JSON.stringify(i,null,2):JSON.stringify(i);s.writeFileSync(n,o),console.log(`\u2705 Success! Converted ${i.length} notes to ${n}`)}catch(r){console.error(`\u274C Error: ${r.message}`),process.exit(1)}});c.command("to-enex").description("Convert a .json file into an importable .enex file").argument("<input>","Input .json file path").option("-o, --output <path>","Output .enex file path").option("-t, --title <name>","Application name","enex-io").action((t,e)=>{try{if(!s.existsSync(t))throw new Error(`Input file not found: ${t}`);console.log(`\u23F3 Generating ENEX from ${t}...`);let r=s.readFileSync(t,"utf-8"),i=JSON.parse(r);if(!Array.isArray(i))throw new Error("JSON must be an array of note objects");let n=x(i,{application:e.title}),o=e.output||t.replace(/\.json$/i,".enex");s.writeFileSync(o,n),console.log(`\u2705 Success! Generated .enex file at ${o}`)}catch(r){console.error(`\u274C Error: ${r.message}`),process.exit(1)}});c.parse();
|
|
15
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport fs from 'fs';\nimport { createRequire } from 'node:module';\nimport { parseEnex, generateEnex } from './index.js';\nimport { Note } from './types.js';\n\n// 1. Clean Metadata Loading\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json');\n\nconst program = new Command();\n\nprogram\n .name('enex-io')\n .description(pkg.description)\n .version(pkg.version, '-v, --version')\n .helpOption('-h, --help', 'Display help for command')\n .showHelpAfterError();\n\n// --- Command: ENEX -> JSON ---\nprogram\n .command('to-json')\n .description('Convert an .enex file into a structured .json file')\n .argument('<input>', 'Input .enex file path')\n .option('-o, --output <path>', 'Output .json file path')\n .option('-p, --pretty', 'Pretty print JSON output', true)\n .action((input, options) => {\n try {\n if (!fs.existsSync(input)) throw new Error(`Input file not found: ${input}`);\n \n console.log(`⏳ Parsing ${input}...`);\n const xml = fs.readFileSync(input, 'utf-8');\n const notes = parseEnex(xml);\n \n const outPath = options.output || input.replace(/\\.enex$/i, '.json');\n const jsonContent = options.pretty ? JSON.stringify(notes, null, 2) : JSON.stringify(notes);\n\n fs.writeFileSync(outPath, jsonContent);\n console.log(`✅ Success! Converted ${notes.length} notes to ${outPath}`);\n } catch (e: any) {\n console.error(`❌ Error: ${e.message}`);\n process.exit(1);\n }\n });\n\n// --- Command: JSON -> ENEX ---\nprogram\n .command('to-enex')\n .description('Convert a .json file into an importable .enex file')\n .argument('<input>', 'Input .json file path')\n .option('-o, --output <path>', 'Output .enex file path')\n .option('-t, --title <name>', 'Application name', 'enex-io')\n .action((input, options) => {\n try {\n if (!fs.existsSync(input)) throw new Error(`Input file not found: ${input}`);\n\n console.log(`⏳ Generating ENEX from ${input}...`);\n const json = fs.readFileSync(input, 'utf-8');\n const notes: Note[] = JSON.parse(json);\n \n if (!Array.isArray(notes)) throw new Error(\"JSON must be an array of note objects\");\n \n const xml = generateEnex(notes, { application: options.title });\n const outPath = options.output || input.replace(/\\.json$/i, '.enex');\n \n fs.writeFileSync(outPath, xml);\n console.log(`✅ Success! Generated .enex file at ${outPath}`);\n } catch (e: any) {\n console.error(`❌ Error: ${e.message}`);\n process.exit(1);\n }\n });\n\nprogram.parse();","import * as cheerio from 'cheerio';\nimport dayjs from 'dayjs';\nimport { Note, EnexOptions } from './types.js'; // Fix: Added .js\n\nexport function generateEnex(notes: Note[], options: EnexOptions = {}): string {\n const dateStr = dayjs().format('YYYYMMDDTHHmmssZ');\n \n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE en-export SYSTEM \"http://xml.evernote.com/pub/evernote-export3.dtd\">\n<en-export export-date=\"${dateStr}\" application=\"${options.application || 'enex-io'}\" version=\"1.0\">`;\n\n for (const note of notes) {\n xml += `\n <note>\n <title>${escapeXml(note.title)}</title>\n <content><![CDATA[<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\">\n<en-note>${formatContent(note.content)}</en-note>]]></content>\n <created>${formatDate(note.created)}</created>\n <updated>${formatDate(note.updated)}</updated>\n ${note.tags.map(tag => `<tag>${escapeXml(tag)}</tag>`).join('')}\n </note>`;\n }\n\n xml += `\\n</en-export>`;\n return xml;\n}\n\nexport function parseEnex(xmlContent: string): Note[] {\n const $ = cheerio.load(xmlContent, { xmlMode: true });\n const notes: Note[] = [];\n\n $('note').each((_, el) => {\n const node = $(el);\n const title = node.find('title').text() || 'Untitled';\n const contentRaw = node.find('content').text();\n \n // Extract inner HTML from the CDATA/en-note wrapper\n const contentMatch = /<en-note[^>]*>([\\s\\S]*)<\\/en-note>/.exec(contentRaw);\n \n // Fix: Strict Check. If match exists, use index 1, else empty string.\n // This solves \"Type undefined is not assignable to string\"\n const content = (contentMatch && contentMatch[1]) ? contentMatch[1] : '';\n\n const created = parseEnexDate(node.find('created').text());\n const updated = parseEnexDate(node.find('updated').text());\n \n const tags: string[] = [];\n \n // Fix: Wrapped in block {} to avoid returning 'number' (array length)\n // This solves \"Type number is not assignable to void\"\n node.find('tag').each((_, tag) => {\n tags.push($(tag).text());\n });\n\n notes.push({ title, content, created, updated, tags });\n });\n\n return notes;\n}\n\n// --- Helpers ---\n\nfunction escapeXml(unsafe: string): string {\n if (!unsafe) return '';\n return unsafe.replace(/[<>&'\"]/g, (c) => {\n switch (c) {\n case '<': return '<';\n case '>': return '>';\n case '&': return '&';\n case '\\'': return ''';\n case '\"': return '"';\n default: return c;\n }\n });\n}\n\nfunction formatContent(html: string): string {\n if (!html) return '';\n return html.replace(/<br>/g, '<br/>');\n}\n\nfunction formatDate(isoString: string): string {\n return dayjs(isoString).isValid() \n ? dayjs(isoString).format('YYYYMMDDTHHmmssZ') \n : dayjs().format('YYYYMMDDTHHmmssZ');\n}\n\nfunction parseEnexDate(enexDate: string): string {\n if (!enexDate) return new Date().toISOString();\n const iso = enexDate.replace(/^(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})Z$/, '$1-$2-$3T$4:$5:$6Z');\n return dayjs(iso).isValid() ? iso : new Date().toISOString();\n}"],"mappings":"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","names":["Command","fs","createRequire","cheerio","dayjs","generateEnex","notes","options","xml","note","escapeXml","formatContent","formatDate","tag","parseEnex","xmlContent","$","_","el","node","title","contentRaw","contentMatch","content","created","parseEnexDate","updated","tags","unsafe","c","html","isoString","enexDate","iso","require","createRequire","pkg","program","Command","input","options","fs","xml","notes","parseEnex","outPath","jsonContent","e","json","generateEnex"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface Note {
|
|
2
|
+
title: string;
|
|
3
|
+
content: string;
|
|
4
|
+
tags: string[];
|
|
5
|
+
created: string;
|
|
6
|
+
updated: string;
|
|
7
|
+
author?: string;
|
|
8
|
+
sourceUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
interface EnexOptions {
|
|
11
|
+
version?: string;
|
|
12
|
+
application?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare function generateEnex(notes: Note[], options?: EnexOptions): string;
|
|
16
|
+
declare function parseEnex(xmlContent: string): Note[];
|
|
17
|
+
|
|
18
|
+
export { generateEnex, parseEnex };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import*as m from"cheerio";import r from"dayjs";function S(t,e={}){let a=`<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export3.dtd">
|
|
3
|
+
<en-export export-date="${r().format("YYYYMMDDTHHmmssZ")}" application="${e.application||"enex-io"}" version="1.0">`;for(let n of t)a+=`
|
|
4
|
+
<note>
|
|
5
|
+
<title>${d(n.title)}</title>
|
|
6
|
+
<content><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
7
|
+
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
|
|
8
|
+
<en-note>${D(n.content)}</en-note>]]></content>
|
|
9
|
+
<created>${p(n.created)}</created>
|
|
10
|
+
<updated>${p(n.updated)}</updated>
|
|
11
|
+
${n.tags.map(o=>`<tag>${d(o)}</tag>`).join("")}
|
|
12
|
+
</note>`;return a+=`
|
|
13
|
+
</en-export>`,a}function h(t){let e=m.load(t,{xmlMode:!0}),i=[];return e("note").each((a,n)=>{let o=e(n),l=o.find("title").text()||"Untitled",f=o.find("content").text(),s=/<en-note[^>]*>([\s\S]*)<\/en-note>/.exec(f),x=s&&s[1]?s[1]:"",g=u(o.find("created").text()),Y=u(o.find("updated").text()),c=[];o.find("tag").each((T,$)=>{c.push(e($).text())}),i.push({title:l,content:x,created:g,updated:Y,tags:c})}),i}function d(t){return t?t.replace(/[<>&'"]/g,e=>{switch(e){case"<":return"<";case">":return">";case"&":return"&";case"'":return"'";case'"':return""";default:return e}}):""}function D(t){return t?t.replace(/<br>/g,"<br/>"):""}function p(t){return r(t).isValid()?r(t).format("YYYYMMDDTHHmmssZ"):r().format("YYYYMMDDTHHmmssZ")}function u(t){if(!t)return new Date().toISOString();let e=t.replace(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/,"$1-$2-$3T$4:$5:$6Z");return r(e).isValid()?e:new Date().toISOString()}export{S as generateEnex,h as parseEnex};
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import * as cheerio from 'cheerio';\nimport dayjs from 'dayjs';\nimport { Note, EnexOptions } from './types.js'; // Fix: Added .js\n\nexport function generateEnex(notes: Note[], options: EnexOptions = {}): string {\n const dateStr = dayjs().format('YYYYMMDDTHHmmssZ');\n \n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE en-export SYSTEM \"http://xml.evernote.com/pub/evernote-export3.dtd\">\n<en-export export-date=\"${dateStr}\" application=\"${options.application || 'enex-io'}\" version=\"1.0\">`;\n\n for (const note of notes) {\n xml += `\n <note>\n <title>${escapeXml(note.title)}</title>\n <content><![CDATA[<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\">\n<en-note>${formatContent(note.content)}</en-note>]]></content>\n <created>${formatDate(note.created)}</created>\n <updated>${formatDate(note.updated)}</updated>\n ${note.tags.map(tag => `<tag>${escapeXml(tag)}</tag>`).join('')}\n </note>`;\n }\n\n xml += `\\n</en-export>`;\n return xml;\n}\n\nexport function parseEnex(xmlContent: string): Note[] {\n const $ = cheerio.load(xmlContent, { xmlMode: true });\n const notes: Note[] = [];\n\n $('note').each((_, el) => {\n const node = $(el);\n const title = node.find('title').text() || 'Untitled';\n const contentRaw = node.find('content').text();\n \n // Extract inner HTML from the CDATA/en-note wrapper\n const contentMatch = /<en-note[^>]*>([\\s\\S]*)<\\/en-note>/.exec(contentRaw);\n \n // Fix: Strict Check. If match exists, use index 1, else empty string.\n // This solves \"Type undefined is not assignable to string\"\n const content = (contentMatch && contentMatch[1]) ? contentMatch[1] : '';\n\n const created = parseEnexDate(node.find('created').text());\n const updated = parseEnexDate(node.find('updated').text());\n \n const tags: string[] = [];\n \n // Fix: Wrapped in block {} to avoid returning 'number' (array length)\n // This solves \"Type number is not assignable to void\"\n node.find('tag').each((_, tag) => {\n tags.push($(tag).text());\n });\n\n notes.push({ title, content, created, updated, tags });\n });\n\n return notes;\n}\n\n// --- Helpers ---\n\nfunction escapeXml(unsafe: string): string {\n if (!unsafe) return '';\n return unsafe.replace(/[<>&'\"]/g, (c) => {\n switch (c) {\n case '<': return '<';\n case '>': return '>';\n case '&': return '&';\n case '\\'': return ''';\n case '\"': return '"';\n default: return c;\n }\n });\n}\n\nfunction formatContent(html: string): string {\n if (!html) return '';\n return html.replace(/<br>/g, '<br/>');\n}\n\nfunction formatDate(isoString: string): string {\n return dayjs(isoString).isValid() \n ? dayjs(isoString).format('YYYYMMDDTHHmmssZ') \n : dayjs().format('YYYYMMDDTHHmmssZ');\n}\n\nfunction parseEnexDate(enexDate: string): string {\n if (!enexDate) return new Date().toISOString();\n const iso = enexDate.replace(/^(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})Z$/, '$1-$2-$3T$4:$5:$6Z');\n return dayjs(iso).isValid() ? iso : new Date().toISOString();\n}"],"mappings":"AAAA,UAAYA,MAAa,UACzB,OAAOC,MAAW,QAGX,SAASC,EAAaC,EAAeC,EAAuB,CAAC,EAAW,CAG7E,IAAIC,EAAM;AAAA;AAAA,0BAFMJ,EAAM,EAAE,OAAO,kBAAkB,CAIlB,kBAAkBG,EAAQ,aAAe,SAAS,mBAEjF,QAAWE,KAAQH,EACjBE,GAAO;AAAA;AAAA,aAEEE,EAAUD,EAAK,KAAK,CAAC;AAAA;AAAA;AAAA,WAGvBE,EAAcF,EAAK,OAAO,CAAC;AAAA,eACvBG,EAAWH,EAAK,OAAO,CAAC;AAAA,eACxBG,EAAWH,EAAK,OAAO,CAAC;AAAA,MACjCA,EAAK,KAAK,IAAII,GAAO,QAAQH,EAAUG,CAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC;AAAA,WAIjE,OAAAL,GAAO;AAAA,cACAA,CACT,CAEO,SAASM,EAAUC,EAA4B,CACpD,IAAMC,EAAY,OAAKD,EAAY,CAAE,QAAS,EAAK,CAAC,EAC9CT,EAAgB,CAAC,EAEvB,OAAAU,EAAE,MAAM,EAAE,KAAK,CAACC,EAAGC,IAAO,CACxB,IAAMC,EAAOH,EAAEE,CAAE,EACXE,EAAQD,EAAK,KAAK,OAAO,EAAE,KAAK,GAAK,WACrCE,EAAaF,EAAK,KAAK,SAAS,EAAE,KAAK,EAGvCG,EAAe,qCAAqC,KAAKD,CAAU,EAInEE,EAAWD,GAAgBA,EAAa,CAAC,EAAKA,EAAa,CAAC,EAAI,GAEhEE,EAAUC,EAAcN,EAAK,KAAK,SAAS,EAAE,KAAK,CAAC,EACnDO,EAAUD,EAAcN,EAAK,KAAK,SAAS,EAAE,KAAK,CAAC,EAEnDQ,EAAiB,CAAC,EAIxBR,EAAK,KAAK,KAAK,EAAE,KAAK,CAACF,EAAGJ,IAAQ,CAChCc,EAAK,KAAKX,EAAEH,CAAG,EAAE,KAAK,CAAC,CACzB,CAAC,EAEDP,EAAM,KAAK,CAAE,MAAAc,EAAO,QAAAG,EAAS,QAAAC,EAAS,QAAAE,EAAS,KAAAC,CAAK,CAAC,CACvD,CAAC,EAEMrB,CACT,CAIA,SAASI,EAAUkB,EAAwB,CACzC,OAAKA,EACEA,EAAO,QAAQ,WAAaC,GAAM,CACvC,OAAQA,EAAG,CACT,IAAK,IAAK,MAAO,OACjB,IAAK,IAAK,MAAO,OACjB,IAAK,IAAK,MAAO,QACjB,IAAK,IAAM,MAAO,SAClB,IAAK,IAAK,MAAO,SACjB,QAAS,OAAOA,CAClB,CACF,CAAC,EAVmB,EAWtB,CAEA,SAASlB,EAAcmB,EAAsB,CAC3C,OAAKA,EACEA,EAAK,QAAQ,QAAS,OAAO,EADlB,EAEpB,CAEA,SAASlB,EAAWmB,EAA2B,CAC7C,OAAO3B,EAAM2B,CAAS,EAAE,QAAQ,EAC5B3B,EAAM2B,CAAS,EAAE,OAAO,kBAAkB,EAC1C3B,EAAM,EAAE,OAAO,kBAAkB,CACvC,CAEA,SAASqB,EAAcO,EAA0B,CAC/C,GAAI,CAACA,EAAU,OAAO,IAAI,KAAK,EAAE,YAAY,EAC7C,IAAMC,EAAMD,EAAS,QAAQ,iDAAkD,oBAAoB,EACnG,OAAO5B,EAAM6B,CAAG,EAAE,QAAQ,EAAIA,EAAM,IAAI,KAAK,EAAE,YAAY,CAC7D","names":["cheerio","dayjs","generateEnex","notes","options","xml","note","escapeXml","formatContent","formatDate","tag","parseEnex","xmlContent","$","_","el","node","title","contentRaw","contentMatch","content","created","parseEnexDate","updated","tags","unsafe","c","html","isoString","enexDate","iso"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "enex-io",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "High-performance parser and generator for Evernote/Apple Notes (.enex) files.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"enex-io": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "node --import=tsx --test test/*.ts",
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"prepublishOnly": "npm run build && npm test",
|
|
25
|
+
"lint": "xo"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"fs",
|
|
29
|
+
"converter",
|
|
30
|
+
"enex",
|
|
31
|
+
"evernote",
|
|
32
|
+
"apple-notes",
|
|
33
|
+
"parser",
|
|
34
|
+
"xml"
|
|
35
|
+
],
|
|
36
|
+
"author": {
|
|
37
|
+
"name": "mgks",
|
|
38
|
+
"url": "https://mgks.dev"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/mgks/enex-io.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/mgks/enex-io/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/mgks/enex-io#readme",
|
|
49
|
+
"funding": "https://github.com/sponsors/mgks",
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"cheerio": "^1.1.2",
|
|
52
|
+
"commander": "^11.1.0",
|
|
53
|
+
"dayjs": "^1.11.10"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.0.0",
|
|
57
|
+
"tsup": "^8.0.0",
|
|
58
|
+
"tsx": "^4.0.0",
|
|
59
|
+
"typescript": "^5.0.0",
|
|
60
|
+
"xo": "^0.56.0"
|
|
61
|
+
},
|
|
62
|
+
"xo": {
|
|
63
|
+
"prettier": true,
|
|
64
|
+
"rules": {
|
|
65
|
+
"unicorn/filename-case": "off"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|