@hyzhak/apple-notes-export 0.1.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.
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/cli.js +511 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +392 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ievgenii Krevenets
|
|
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,72 @@
|
|
|
1
|
+
# apple-notes-extractor
|
|
2
|
+
|
|
3
|
+
Apple Notes Extractor CLI (macOS + Apple Notes via JXA)
|
|
4
|
+
|
|
5
|
+
## Quick use
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Export all notes to an absolute target directory
|
|
9
|
+
node dist/cli.js --target /tmp/notes-export --force
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Folder filters
|
|
13
|
+
|
|
14
|
+
Keep only specific folders (prefix match, case-insensitive):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
node dist/cli.js \
|
|
18
|
+
--target /tmp/notes-export \
|
|
19
|
+
--force \
|
|
20
|
+
--include-folder "Work/Projects" "Personal"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Exclude folders:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node dist/cli.js \
|
|
27
|
+
--target /tmp/notes-export \
|
|
28
|
+
--force \
|
|
29
|
+
--exclude-folder "Archive" "Trash"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Include and exclude together (include evaluated first, then exclude):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
node dist/cli.js \
|
|
36
|
+
--target /tmp/notes-export \
|
|
37
|
+
--force \
|
|
38
|
+
--include-folder "Work" \
|
|
39
|
+
--exclude-folder "Work/Secret"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Notes:
|
|
43
|
+
|
|
44
|
+
- Paths are matched by prefix; `Work` matches `Work/Sub/Note`.
|
|
45
|
+
- Attachments flag exists but export is not implemented yet; CLI will warn and proceed without attachments.
|
|
46
|
+
|
|
47
|
+
## Verbosity
|
|
48
|
+
|
|
49
|
+
- Default: info (progress + per-note/skip lines).
|
|
50
|
+
- Quiet: `--quiet` or `-q` (progress only).
|
|
51
|
+
- Verbose: `-v` adds extra detail; `-vv` enables debug (also set by `NOTES_DEBUG=1`).
|
|
52
|
+
|
|
53
|
+
## Date filters
|
|
54
|
+
|
|
55
|
+
Filter by created/modified timestamps (UTC, inclusive bounds):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
node dist/cli.js \
|
|
59
|
+
--target /tmp/notes-export \
|
|
60
|
+
--force \
|
|
61
|
+
--created-after 2024-01-01T00:00:00Z \
|
|
62
|
+
--modified-before 2024-12-31T23:59:59Z
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install
|
|
69
|
+
npm run lint
|
|
70
|
+
npm test
|
|
71
|
+
npm run build
|
|
72
|
+
```
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
|
|
8
|
+
// src/lib/export-context.ts
|
|
9
|
+
import fs from "fs/promises";
|
|
10
|
+
import path from "path";
|
|
11
|
+
var INDEX_FILENAME = "index.json";
|
|
12
|
+
async function ensureDirectoryExists(targetDir) {
|
|
13
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
async function isDirectoryEmpty(dir, allowedNames = []) {
|
|
16
|
+
const entries = await fs.readdir(dir);
|
|
17
|
+
const meaningful = entries.filter((name) => !allowedNames.includes(name));
|
|
18
|
+
return meaningful.length === 0;
|
|
19
|
+
}
|
|
20
|
+
async function validateTargetDirectory(targetDir, force) {
|
|
21
|
+
const stats = await fs.stat(targetDir).catch(() => void 0);
|
|
22
|
+
if (!stats) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!stats.isDirectory()) {
|
|
26
|
+
throw new Error(`Target path must be a directory: ${targetDir}`);
|
|
27
|
+
}
|
|
28
|
+
const allowed = ["node-compile-cache"];
|
|
29
|
+
if (!await isDirectoryEmpty(targetDir, allowed) && !force) {
|
|
30
|
+
throw new Error("Target directory must be empty unless --force is provided.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function createExportContext(config) {
|
|
34
|
+
if (!path.isAbsolute(config.targetDir)) {
|
|
35
|
+
throw new Error("Target directory must be an absolute path.");
|
|
36
|
+
}
|
|
37
|
+
const force = Boolean(config.force);
|
|
38
|
+
const includeAttachments = Boolean(config.includeAttachments);
|
|
39
|
+
await ensureDirectoryExists(config.targetDir);
|
|
40
|
+
await validateTargetDirectory(config.targetDir, force);
|
|
41
|
+
const notesPath = path.join(config.targetDir, "notes");
|
|
42
|
+
const artifactsPath = path.join(config.targetDir, "artifacts");
|
|
43
|
+
const indexPath = path.join(config.targetDir, INDEX_FILENAME);
|
|
44
|
+
await fs.mkdir(notesPath, { recursive: true });
|
|
45
|
+
if (includeAttachments) {
|
|
46
|
+
await fs.mkdir(artifactsPath, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
targetDir: config.targetDir,
|
|
50
|
+
notesPath,
|
|
51
|
+
artifactsPath,
|
|
52
|
+
indexPath,
|
|
53
|
+
includeAttachments
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/lib/notes-bridge.ts
|
|
58
|
+
var MacOSUnsupportedError = class extends Error {
|
|
59
|
+
constructor(message = "Apple Notes export requires macOS.") {
|
|
60
|
+
super(message);
|
|
61
|
+
this.name = "MacOSUnsupportedError";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var NotesBridgeError = class extends Error {
|
|
65
|
+
constructor(message) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.name = "NotesBridgeError";
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function ensureMacOS() {
|
|
71
|
+
if (process.platform !== "darwin") {
|
|
72
|
+
throw new MacOSUnsupportedError();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/lib/export-runner.ts
|
|
77
|
+
import fs3 from "fs/promises";
|
|
78
|
+
|
|
79
|
+
// src/lib/notes/reader.ts
|
|
80
|
+
import { run } from "@jxa/run";
|
|
81
|
+
|
|
82
|
+
// src/lib/notes/normalize.ts
|
|
83
|
+
function coerceString(value) {
|
|
84
|
+
if (value == null) return null;
|
|
85
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
86
|
+
return String(value);
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function toIsoString(value) {
|
|
91
|
+
if (!value) return null;
|
|
92
|
+
if (typeof value === "string" || value instanceof Date) {
|
|
93
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
94
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function normalizeNote(raw, fallback) {
|
|
99
|
+
const merged = {
|
|
100
|
+
id: raw?.id ?? fallback?.id ?? null,
|
|
101
|
+
name: raw?.name ?? fallback?.name ?? null,
|
|
102
|
+
bodyHtml: raw?.bodyHtml ?? fallback?.bodyHtml ?? "",
|
|
103
|
+
folderPath: raw?.folderPath ?? fallback?.folderPath ?? "Notes",
|
|
104
|
+
createdAtUtc: raw?.createdAtUtc ?? fallback?.createdAtUtc ?? null,
|
|
105
|
+
modifiedAtUtc: raw?.modifiedAtUtc ?? fallback?.modifiedAtUtc ?? null
|
|
106
|
+
};
|
|
107
|
+
const id = coerceString(merged.id);
|
|
108
|
+
if (!id) {
|
|
109
|
+
throw new NotesBridgeError("Apple Notes returned a note without an id.");
|
|
110
|
+
}
|
|
111
|
+
const name = coerceString(merged.name) ?? id;
|
|
112
|
+
const folderPath = coerceString(merged.folderPath) ?? "Notes";
|
|
113
|
+
const createdAtUtc = toIsoString(merged.createdAtUtc) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
114
|
+
const modifiedAtUtc = toIsoString(merged.modifiedAtUtc) ?? createdAtUtc;
|
|
115
|
+
const bodyHtml = coerceString(merged.bodyHtml) ?? "";
|
|
116
|
+
return {
|
|
117
|
+
id,
|
|
118
|
+
name,
|
|
119
|
+
bodyHtml,
|
|
120
|
+
folderPath,
|
|
121
|
+
createdAtUtc,
|
|
122
|
+
modifiedAtUtc,
|
|
123
|
+
attachments: []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/lib/notes/jxa-helpers.ts
|
|
128
|
+
var currentLevel = process.env.NOTES_DEBUG ? 2 /* Debug */ : 1 /* Info */;
|
|
129
|
+
var setLogLevel = (level) => {
|
|
130
|
+
currentLevel = level;
|
|
131
|
+
};
|
|
132
|
+
var debugLog = (message, data) => {
|
|
133
|
+
if (currentLevel < 2 /* Debug */) return;
|
|
134
|
+
const payload = data === void 0 ? "" : ` ${JSON.stringify(data)}`;
|
|
135
|
+
console.error(`[notes-debug] ${message}${payload}`);
|
|
136
|
+
};
|
|
137
|
+
var logProgress = (message, data) => {
|
|
138
|
+
if (currentLevel < 1 /* Info */ && message !== "export.progress" && message !== "export.count" && message !== "export.start" && message !== "export.index.write" && message !== "export.written") {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const payload = data === void 0 ? "" : ` ${JSON.stringify(data)}`;
|
|
142
|
+
console.error(`[notes] ${message}${payload}`);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/lib/notes/reader.ts
|
|
146
|
+
async function getNoteCount(options = {}) {
|
|
147
|
+
ensureMacOS();
|
|
148
|
+
const runner = options.runJxa ?? run;
|
|
149
|
+
try {
|
|
150
|
+
const count = await runner(() => {
|
|
151
|
+
const Notes = Application("Notes");
|
|
152
|
+
const raw = typeof Notes.notes === "function" ? Notes.notes() : [];
|
|
153
|
+
const length = raw.length;
|
|
154
|
+
return typeof length === "number" && length >= 0 ? length : 0;
|
|
155
|
+
});
|
|
156
|
+
const safeCount = typeof count === "number" && count >= 0 ? count : 0;
|
|
157
|
+
debugLog("notes.count", { count: safeCount });
|
|
158
|
+
return safeCount;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
161
|
+
throw new NotesBridgeError(`Failed to count notes: ${message}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function readNoteByIndex(index, options = {}) {
|
|
165
|
+
ensureMacOS();
|
|
166
|
+
const runner = options.runJxa ?? run;
|
|
167
|
+
try {
|
|
168
|
+
const raw = await runner((idx) => {
|
|
169
|
+
const Notes = Application("Notes");
|
|
170
|
+
const notes = typeof Notes.notes === "function" ? Notes.notes() : [];
|
|
171
|
+
const inRange = idx >= 0 && idx < notes.length;
|
|
172
|
+
if (!inRange) return null;
|
|
173
|
+
const note = notes[idx];
|
|
174
|
+
if (!note) return null;
|
|
175
|
+
const callMethod = (target, method) => {
|
|
176
|
+
if (!target || typeof target[method] !== "function") {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
return target[method]();
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const containerPath = () => {
|
|
186
|
+
const names = [];
|
|
187
|
+
let current = callMethod(note, "container");
|
|
188
|
+
while (current) {
|
|
189
|
+
const containerName = callMethod(current, "name");
|
|
190
|
+
if (typeof containerName === "string" && containerName.trim().length > 0) {
|
|
191
|
+
names.push(containerName);
|
|
192
|
+
}
|
|
193
|
+
current = callMethod(current, "container");
|
|
194
|
+
}
|
|
195
|
+
return names.length ? names.reverse().join("/") : "Notes";
|
|
196
|
+
};
|
|
197
|
+
return {
|
|
198
|
+
id: callMethod(note, "id") ?? callMethod(note, "uuid"),
|
|
199
|
+
name: callMethod(note, "name"),
|
|
200
|
+
bodyHtml: callMethod(note, "body"),
|
|
201
|
+
folderPath: containerPath(),
|
|
202
|
+
createdAtUtc: callMethod(note, "creationDate"),
|
|
203
|
+
modifiedAtUtc: callMethod(note, "modificationDate")
|
|
204
|
+
};
|
|
205
|
+
}, index);
|
|
206
|
+
if (!raw) {
|
|
207
|
+
throw new NotesBridgeError(`Note at index ${index} not found.`);
|
|
208
|
+
}
|
|
209
|
+
const normalized = normalizeNote(raw, { folderPath: "Notes" });
|
|
210
|
+
debugLog("notes.read", { index, id: normalized.id, name: normalized.name });
|
|
211
|
+
return normalized;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
214
|
+
throw new NotesBridgeError(`Failed to read note at index ${index}: ${message}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/services/file-writer.ts
|
|
219
|
+
import fs2 from "fs/promises";
|
|
220
|
+
import path3 from "path";
|
|
221
|
+
|
|
222
|
+
// src/services/pathing.ts
|
|
223
|
+
import path2 from "path";
|
|
224
|
+
function sanitizeSegment(input) {
|
|
225
|
+
const trimmed = input.trim();
|
|
226
|
+
const safe = trimmed.replace(/[\s]+/g, "-").replace(/[^a-zA-Z0-9-_]/g, "").replace(/-+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
227
|
+
return safe || "note";
|
|
228
|
+
}
|
|
229
|
+
function slugifyNote(note) {
|
|
230
|
+
const namePart = sanitizeSegment(note.name);
|
|
231
|
+
const idPart = sanitizeSegment(note.id);
|
|
232
|
+
return `${namePart}_${idPart}`;
|
|
233
|
+
}
|
|
234
|
+
function normalizeFolderPath(folderPath) {
|
|
235
|
+
const parts = folderPath.split("/").filter(Boolean).map(sanitizeSegment);
|
|
236
|
+
return parts.join("/");
|
|
237
|
+
}
|
|
238
|
+
function buildNotePath(note, notesRoot) {
|
|
239
|
+
const folderRel = normalizeFolderPath(note.folderPath || "Notes");
|
|
240
|
+
const filename = `${slugifyNote(note)}.html`;
|
|
241
|
+
const relativeHtmlPath = path2.posix.join(folderRel, filename);
|
|
242
|
+
const absoluteHtmlPath = path2.join(notesRoot, relativeHtmlPath);
|
|
243
|
+
return { relativeHtmlPath, absoluteHtmlPath };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/services/file-writer.ts
|
|
247
|
+
async function writeNoteHtml(note, notesRoot) {
|
|
248
|
+
const { absoluteHtmlPath, relativeHtmlPath } = buildNotePath(note, notesRoot);
|
|
249
|
+
await fs2.mkdir(path3.dirname(absoluteHtmlPath), { recursive: true });
|
|
250
|
+
await fs2.writeFile(absoluteHtmlPath, note.bodyHtml, "utf8");
|
|
251
|
+
const created = new Date(note.createdAtUtc);
|
|
252
|
+
const modified = new Date(note.modifiedAtUtc);
|
|
253
|
+
const atime = Number.isNaN(created.getTime()) ? /* @__PURE__ */ new Date() : created;
|
|
254
|
+
const mtime = Number.isNaN(modified.getTime()) ? atime : modified;
|
|
255
|
+
await fs2.utimes(absoluteHtmlPath, atime, mtime);
|
|
256
|
+
return { absoluteHtmlPath, relativeHtmlPath };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/lib/filter-schema.ts
|
|
260
|
+
var normalize = (value) => value.trim().replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
261
|
+
function cleanList(values) {
|
|
262
|
+
if (!values) return void 0;
|
|
263
|
+
const cleaned = values.flatMap((v) => v.split(",")).map(normalize).filter((v) => v.length > 0);
|
|
264
|
+
return cleaned.length ? cleaned : void 0;
|
|
265
|
+
}
|
|
266
|
+
function parseFolderFilters(input) {
|
|
267
|
+
return {
|
|
268
|
+
include: cleanList(input.includeFolders),
|
|
269
|
+
exclude: cleanList(input.excludeFolders)
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function matchesFolder(folderPath, filters) {
|
|
273
|
+
const normalized = normalize(folderPath);
|
|
274
|
+
const matchesAny = (needles) => needles?.some((needle) => normalized === needle || normalized.startsWith(`${needle}/`)) ?? false;
|
|
275
|
+
if (filters.include && !matchesAny(filters.include)) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
if (filters.exclude && matchesAny(filters.exclude)) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
function parseDate(value) {
|
|
284
|
+
if (!value) return void 0;
|
|
285
|
+
const parsed = new Date(value);
|
|
286
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
287
|
+
throw new Error(`Invalid date: ${value}`);
|
|
288
|
+
}
|
|
289
|
+
return parsed;
|
|
290
|
+
}
|
|
291
|
+
function parseDateFilters(input) {
|
|
292
|
+
return {
|
|
293
|
+
createdAfter: parseDate(input.createdAfter),
|
|
294
|
+
createdBefore: parseDate(input.createdBefore),
|
|
295
|
+
modifiedAfter: parseDate(input.modifiedAfter),
|
|
296
|
+
modifiedBefore: parseDate(input.modifiedBefore)
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/lib/filtering.ts
|
|
301
|
+
function shouldExportFolder(folderPath, filters) {
|
|
302
|
+
return matchesFolder(folderPath, filters);
|
|
303
|
+
}
|
|
304
|
+
function matchesDate(value, after, before) {
|
|
305
|
+
const parsed = new Date(value);
|
|
306
|
+
if (Number.isNaN(parsed.getTime())) return false;
|
|
307
|
+
if (after && parsed.getTime() < after.getTime()) return false;
|
|
308
|
+
if (before && parsed.getTime() > before.getTime()) return false;
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
function filterNote(note, filters) {
|
|
312
|
+
const folderFilters = filters.folders ?? {};
|
|
313
|
+
if (!shouldExportFolder(note.folderPath, folderFilters)) {
|
|
314
|
+
return { allowed: false, reason: `Filtered by folder: ${note.folderPath}` };
|
|
315
|
+
}
|
|
316
|
+
const dateFilters = filters.dates ?? {};
|
|
317
|
+
const createdOk = matchesDate(note.createdAtUtc, dateFilters.createdAfter, dateFilters.createdBefore);
|
|
318
|
+
const modifiedOk = matchesDate(note.modifiedAtUtc, dateFilters.modifiedAfter, dateFilters.modifiedBefore);
|
|
319
|
+
if (!createdOk || !modifiedOk) {
|
|
320
|
+
return { allowed: false, reason: "Filtered by date" };
|
|
321
|
+
}
|
|
322
|
+
return { allowed: true };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/lib/progress-reporter.ts
|
|
326
|
+
var ProgressReporter = class {
|
|
327
|
+
lastAt = 0;
|
|
328
|
+
rateMs;
|
|
329
|
+
constructor(options = {}) {
|
|
330
|
+
this.rateMs = options.rateMs ?? 2e3;
|
|
331
|
+
}
|
|
332
|
+
emit(event, data, force = false) {
|
|
333
|
+
const now = Date.now();
|
|
334
|
+
if (!force && now - this.lastAt < this.rateMs && event === "export.progress") {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
this.lastAt = now;
|
|
338
|
+
logProgress(event, data);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
var createProgressReporter = (options) => new ProgressReporter(options);
|
|
342
|
+
|
|
343
|
+
// src/lib/export-runner.ts
|
|
344
|
+
async function exportNotes(context, readerOptions = {}, filters = {}, options = {}) {
|
|
345
|
+
if (options.logLevel !== void 0) {
|
|
346
|
+
setLogLevel(options.logLevel);
|
|
347
|
+
}
|
|
348
|
+
logProgress("export.start", { target: context.targetDir });
|
|
349
|
+
const reporter = createProgressReporter();
|
|
350
|
+
const total = await getNoteCount(readerOptions);
|
|
351
|
+
logProgress("export.count", { total });
|
|
352
|
+
const entries = [];
|
|
353
|
+
let first = null;
|
|
354
|
+
const skipped = [];
|
|
355
|
+
for (let i = 0; i < total; i += 1) {
|
|
356
|
+
let note;
|
|
357
|
+
try {
|
|
358
|
+
note = await readNoteByIndex(i, readerOptions);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
361
|
+
if (error instanceof NotesBridgeError) {
|
|
362
|
+
logProgress("export.skip", { idx: i + 1, reason: message });
|
|
363
|
+
skipped.push({ index: i, reason: message });
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
const gate = filterNote(note, filters);
|
|
369
|
+
if (!gate.allowed) {
|
|
370
|
+
logProgress("export.skip", { idx: i + 1, reason: "filtered", folder: note.folderPath });
|
|
371
|
+
skipped.push({ index: i, reason: gate.reason ?? "Filtered" });
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
logProgress("export.note", { idx: i + 1, id: note.id });
|
|
375
|
+
const result = await writeNoteHtml(note, context.notesPath);
|
|
376
|
+
const entry = {
|
|
377
|
+
noteId: note.id,
|
|
378
|
+
noteName: note.name,
|
|
379
|
+
artifacts: [],
|
|
380
|
+
folderPath: note.folderPath,
|
|
381
|
+
createdAtUtc: note.createdAtUtc,
|
|
382
|
+
modifiedAtUtc: note.modifiedAtUtc,
|
|
383
|
+
htmlPath: result.relativeHtmlPath
|
|
384
|
+
};
|
|
385
|
+
entries.push(entry);
|
|
386
|
+
if (!first) {
|
|
387
|
+
first = {
|
|
388
|
+
id: entry.noteId,
|
|
389
|
+
name: entry.noteName,
|
|
390
|
+
folderPath: entry.folderPath,
|
|
391
|
+
htmlPath: entry.htmlPath
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
if (typeof readerOptions.limit === "number" && entries.length >= readerOptions.limit) {
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
reporter.emit("export.progress", {
|
|
398
|
+
processed: entries.length + skipped.length,
|
|
399
|
+
exported: entries.length,
|
|
400
|
+
skipped: skipped.length,
|
|
401
|
+
total
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
logProgress("export.written", { count: entries.length });
|
|
405
|
+
await fs3.writeFile(context.indexPath, JSON.stringify(entries, null, 2), "utf8");
|
|
406
|
+
logProgress("export.index.write", { count: entries.length, path: context.indexPath });
|
|
407
|
+
return {
|
|
408
|
+
exported: entries.length,
|
|
409
|
+
skipped,
|
|
410
|
+
indexPath: context.indexPath,
|
|
411
|
+
notesPath: context.notesPath,
|
|
412
|
+
firstNote: first
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/cli/index.ts
|
|
417
|
+
var require2 = createRequire(import.meta.url);
|
|
418
|
+
var cliPath = new URL(".", import.meta.url).pathname;
|
|
419
|
+
var pkgUrl = cliPath.includes("/dist/") ? new URL("../package.json", import.meta.url) : new URL("../../package.json", import.meta.url);
|
|
420
|
+
var version = require2(pkgUrl.pathname).version ?? "unknown";
|
|
421
|
+
var cliSchema = z.object({
|
|
422
|
+
target: z.string().min(1, "Target directory is required"),
|
|
423
|
+
force: z.boolean().optional(),
|
|
424
|
+
includeAttachments: z.boolean().optional(),
|
|
425
|
+
includeFolder: z.array(z.string()).optional(),
|
|
426
|
+
excludeFolder: z.array(z.string()).optional(),
|
|
427
|
+
createdAfter: z.string().optional(),
|
|
428
|
+
createdBefore: z.string().optional(),
|
|
429
|
+
modifiedAfter: z.string().optional(),
|
|
430
|
+
modifiedBefore: z.string().optional(),
|
|
431
|
+
quiet: z.boolean().optional(),
|
|
432
|
+
verbose: z.number().int().nonnegative().optional()
|
|
433
|
+
});
|
|
434
|
+
async function main(argv) {
|
|
435
|
+
const program = new Command();
|
|
436
|
+
program.name("apple-notes-export").description("Apple Notes export CLI (MVP smoke run)").version(version).requiredOption("-t, --target <dir>", "Absolute path to target directory").option("-f, --force", "Allow non-empty target directory", false).option("--include-attachments", "Attachments export is not yet implemented", false).option("--include-folder <paths...>", "Include only folders (prefix match)", []).option("--exclude-folder <paths...>", "Exclude folders (prefix match)", []).option("--created-after <iso>", "Only export notes created on/after this UTC date").option("--created-before <iso>", "Only export notes created on/before this UTC date").option("--modified-after <iso>", "Only export notes modified on/after this UTC date").option("--modified-before <iso>", "Only export notes modified on/before this UTC date").option("-q, --quiet", "Reduce log noise (progress only)", false).option(
|
|
437
|
+
"-v, --verbose",
|
|
438
|
+
"Increase verbosity (repeat for more detail)",
|
|
439
|
+
(_value, previous) => previous + 1,
|
|
440
|
+
0
|
|
441
|
+
);
|
|
442
|
+
program.exitOverride();
|
|
443
|
+
try {
|
|
444
|
+
program.parse(argv);
|
|
445
|
+
const options = program.opts();
|
|
446
|
+
const parsed = cliSchema.parse({
|
|
447
|
+
target: options.target,
|
|
448
|
+
force: Boolean(options.force),
|
|
449
|
+
includeAttachments: Boolean(options.includeAttachments),
|
|
450
|
+
includeFolder: options.includeFolder,
|
|
451
|
+
excludeFolder: options.excludeFolder,
|
|
452
|
+
createdAfter: options.createdAfter,
|
|
453
|
+
createdBefore: options.createdBefore,
|
|
454
|
+
modifiedAfter: options.modifiedAfter,
|
|
455
|
+
modifiedBefore: options.modifiedBefore,
|
|
456
|
+
quiet: Boolean(options.quiet),
|
|
457
|
+
verbose: options.verbose
|
|
458
|
+
});
|
|
459
|
+
ensureMacOS();
|
|
460
|
+
if (parsed.includeAttachments) {
|
|
461
|
+
process.stderr.write("Warning: --include-attachments is not implemented yet; continuing without attachments.\n");
|
|
462
|
+
}
|
|
463
|
+
const context = await createExportContext({
|
|
464
|
+
targetDir: parsed.target,
|
|
465
|
+
force: parsed.force,
|
|
466
|
+
includeAttachments: false
|
|
467
|
+
});
|
|
468
|
+
const filters = parseFolderFilters({
|
|
469
|
+
includeFolders: parsed.includeFolder,
|
|
470
|
+
excludeFolders: parsed.excludeFolder
|
|
471
|
+
});
|
|
472
|
+
const dateFilters = parseDateFilters({
|
|
473
|
+
createdAfter: parsed.createdAfter,
|
|
474
|
+
createdBefore: parsed.createdBefore,
|
|
475
|
+
modifiedAfter: parsed.modifiedAfter,
|
|
476
|
+
modifiedBefore: parsed.modifiedBefore
|
|
477
|
+
});
|
|
478
|
+
const logLevel = parsed.quiet ? 0 : Math.min(2, (parsed.verbose ?? 0) + 1);
|
|
479
|
+
const result = await exportNotes(
|
|
480
|
+
context,
|
|
481
|
+
{},
|
|
482
|
+
{ folders: filters, dates: dateFilters },
|
|
483
|
+
{ logLevel }
|
|
484
|
+
);
|
|
485
|
+
const summary = {
|
|
486
|
+
status: "ok",
|
|
487
|
+
indexPath: result.indexPath,
|
|
488
|
+
notesPath: result.notesPath,
|
|
489
|
+
exported: result.exported,
|
|
490
|
+
skipped: result.skipped,
|
|
491
|
+
firstNote: result.firstNote
|
|
492
|
+
};
|
|
493
|
+
process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
|
|
494
|
+
process.exitCode = 0;
|
|
495
|
+
} catch (error) {
|
|
496
|
+
const maybeCommanderError = error;
|
|
497
|
+
if (maybeCommanderError?.code === "commander.executeSubCommandAsync") {
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
process.stderr.write(`Error: ${maybeCommanderError.message}
|
|
501
|
+
`);
|
|
502
|
+
process.exitCode = 1;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
506
|
+
void main(process.argv);
|
|
507
|
+
}
|
|
508
|
+
export {
|
|
509
|
+
main
|
|
510
|
+
};
|
|
511
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/index.ts","../src/lib/export-context.ts","../src/lib/notes-bridge.ts","../src/lib/export-runner.ts","../src/lib/notes/reader.ts","../src/lib/notes/normalize.ts","../src/lib/notes/jxa-helpers.ts","../src/services/file-writer.ts","../src/services/pathing.ts","../src/lib/filter-schema.ts","../src/lib/filtering.ts","../src/lib/progress-reporter.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { z } from 'zod';\nimport { createRequire } from 'node:module';\nimport { createExportContext } from '../lib/export-context';\nimport { ensureMacOS } from '../lib/notes-bridge';\nimport { exportNotes } from '../lib/export-runner';\nimport { parseFolderFilters, parseDateFilters } from '../lib/filter-schema';\n\nconst require = createRequire(import.meta.url);\n// Keep CLI usable from both compiled dist/ and ts-node/src:\n// - dist/cli.js sits beside dist/package.json -> ../package.json\n// - src/cli/index.ts sits under src/ -> ../../package.json\nconst cliPath = new URL('.', import.meta.url).pathname;\nconst pkgUrl = cliPath.includes('/dist/')\n ? new URL('../package.json', import.meta.url)\n : new URL('../../package.json', import.meta.url);\nconst version =\n (require(pkgUrl.pathname) as { version?: string }).version ??\n 'unknown';\n\nconst cliSchema = z.object({\n target: z.string().min(1, 'Target directory is required'),\n force: z.boolean().optional(),\n includeAttachments: z.boolean().optional(),\n includeFolder: z.array(z.string()).optional(),\n excludeFolder: z.array(z.string()).optional(),\n createdAfter: z.string().optional(),\n createdBefore: z.string().optional(),\n modifiedAfter: z.string().optional(),\n modifiedBefore: z.string().optional(),\n quiet: z.boolean().optional(),\n verbose: z.number().int().nonnegative().optional()\n});\n\nexport async function main(argv: string[]): Promise<void> {\n const program = new Command();\n program\n .name('apple-notes-export')\n .description('Apple Notes export CLI (MVP smoke run)')\n .version(version)\n .requiredOption('-t, --target <dir>', 'Absolute path to target directory')\n .option('-f, --force', 'Allow non-empty target directory', false)\n .option('--include-attachments', 'Attachments export is not yet implemented', false)\n .option('--include-folder <paths...>', 'Include only folders (prefix match)', [])\n .option('--exclude-folder <paths...>', 'Exclude folders (prefix match)', [])\n .option('--created-after <iso>', 'Only export notes created on/after this UTC date')\n .option('--created-before <iso>', 'Only export notes created on/before this UTC date')\n .option('--modified-after <iso>', 'Only export notes modified on/after this UTC date')\n .option('--modified-before <iso>', 'Only export notes modified on/before this UTC date')\n .option('-q, --quiet', 'Reduce log noise (progress only)', false)\n .option(\n '-v, --verbose',\n 'Increase verbosity (repeat for more detail)',\n (_value, previous: number) => previous + 1,\n 0\n );\n\n program.exitOverride();\n\n try {\n program.parse(argv);\n const options = program.opts<{\n target: string;\n force?: boolean;\n includeAttachments?: boolean;\n includeFolder?: string[];\n excludeFolder?: string[];\n createdAfter?: string;\n createdBefore?: string;\n modifiedAfter?: string;\n modifiedBefore?: string;\n quiet?: boolean;\n verbose?: number;\n }>();\n const parsed = cliSchema.parse({\n target: options.target,\n force: Boolean(options.force),\n includeAttachments: Boolean(options.includeAttachments),\n includeFolder: options.includeFolder,\n excludeFolder: options.excludeFolder,\n createdAfter: options.createdAfter,\n createdBefore: options.createdBefore,\n modifiedAfter: options.modifiedAfter,\n modifiedBefore: options.modifiedBefore,\n quiet: Boolean(options.quiet),\n verbose: options.verbose\n });\n\n ensureMacOS();\n\n if (parsed.includeAttachments) {\n // Attachments are deferred; keep UX explicit.\n process.stderr.write('Warning: --include-attachments is not implemented yet; continuing without attachments.\\n');\n }\n\n const context = await createExportContext({\n targetDir: parsed.target,\n force: parsed.force,\n includeAttachments: false\n });\n\n const filters = parseFolderFilters({\n includeFolders: parsed.includeFolder,\n excludeFolders: parsed.excludeFolder\n });\n\n const dateFilters = parseDateFilters({\n createdAfter: parsed.createdAfter,\n createdBefore: parsed.createdBefore,\n modifiedAfter: parsed.modifiedAfter,\n modifiedBefore: parsed.modifiedBefore\n });\n\n const logLevel = parsed.quiet ? 0 : Math.min(2, (parsed.verbose ?? 0) + 1);\n\n const result = await exportNotes(\n context,\n {},\n { folders: filters, dates: dateFilters },\n { logLevel }\n );\n\n const summary = {\n status: 'ok',\n indexPath: result.indexPath,\n notesPath: result.notesPath,\n exported: result.exported,\n skipped: result.skipped,\n firstNote: result.firstNote\n };\n\n process.stdout.write(JSON.stringify(summary, null, 2) + '\\n');\n process.exitCode = 0;\n } catch (error) {\n const maybeCommanderError = error as Error & { code?: string };\n if (maybeCommanderError?.code === 'commander.executeSubCommandAsync') {\n throw error;\n }\n process.stderr.write(`Error: ${(maybeCommanderError as Error).message}\\n`);\n process.exitCode = 1;\n }\n}\n\nif (import.meta.url === `file://${process.argv[1]}`) {\n void main(process.argv);\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nconst INDEX_FILENAME = 'index.json';\n\nexport interface ExportConfig {\n targetDir: string;\n force?: boolean;\n includeAttachments?: boolean;\n}\n\nexport interface ExportContext {\n targetDir: string;\n notesPath: string;\n artifactsPath: string;\n indexPath: string;\n includeAttachments: boolean;\n}\n\nasync function ensureDirectoryExists(targetDir: string): Promise<void> {\n await fs.mkdir(targetDir, { recursive: true });\n}\n\nasync function isDirectoryEmpty(dir: string, allowedNames: string[] = []): Promise<boolean> {\n const entries = await fs.readdir(dir);\n const meaningful = entries.filter((name) => !allowedNames.includes(name));\n return meaningful.length === 0;\n}\n\nexport async function validateTargetDirectory(targetDir: string, force: boolean): Promise<void> {\n const stats = await fs.stat(targetDir).catch(() => undefined);\n if (!stats) {\n return;\n }\n if (!stats.isDirectory()) {\n throw new Error(`Target path must be a directory: ${targetDir}`);\n }\n // Some environments drop a node-compile-cache folder beside the app; allow it when empty otherwise.\n const allowed = ['node-compile-cache'];\n if (!(await isDirectoryEmpty(targetDir, allowed)) && !force) {\n throw new Error('Target directory must be empty unless --force is provided.');\n }\n}\n\nexport async function createExportContext(config: ExportConfig): Promise<ExportContext> {\n if (!path.isAbsolute(config.targetDir)) {\n throw new Error('Target directory must be an absolute path.');\n }\n\n const force = Boolean(config.force);\n const includeAttachments = Boolean(config.includeAttachments);\n\n await ensureDirectoryExists(config.targetDir);\n await validateTargetDirectory(config.targetDir, force);\n\n const notesPath = path.join(config.targetDir, 'notes');\n const artifactsPath = path.join(config.targetDir, 'artifacts');\n const indexPath = path.join(config.targetDir, INDEX_FILENAME);\n\n await fs.mkdir(notesPath, { recursive: true });\n if (includeAttachments) {\n await fs.mkdir(artifactsPath, { recursive: true });\n }\n\n return {\n targetDir: config.targetDir,\n notesPath,\n artifactsPath,\n indexPath,\n includeAttachments\n };\n}\n","export class MacOSUnsupportedError extends Error {\n constructor(message = 'Apple Notes export requires macOS.') {\n super(message);\n this.name = 'MacOSUnsupportedError';\n }\n}\n\nexport class NotesBridgeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'NotesBridgeError';\n }\n}\n\nexport function ensureMacOS(): void {\n if (process.platform !== 'darwin') {\n throw new MacOSUnsupportedError();\n }\n}\n","import fs from 'node:fs/promises';\nimport type { ExportContext } from './export-context';\nimport { getNoteCount, readNoteByIndex } from './notes/reader';\nimport type { NotesReaderOptions } from './notes/types';\nimport { LogLevel, logProgress, setLogLevel } from './notes/jxa-helpers';\nimport { NotesBridgeError } from './notes-bridge';\nimport { writeNoteHtml } from '../services/file-writer';\nimport type { IndexEntry, Note } from '../models/note';\nimport type { DateFilters, FolderFilters } from './filter-schema';\nimport { filterNote } from './filtering';\nimport { createProgressReporter } from './progress-reporter';\n\nexport interface ExportSummary {\n exported: number;\n skipped: Array<{ index: number; reason: string }>;\n indexPath: string;\n notesPath: string;\n firstNote:\n | {\n id: string;\n name: string;\n folderPath: string;\n htmlPath: string;\n }\n | null;\n}\n\nexport async function exportNotes(\n context: ExportContext,\n readerOptions: NotesReaderOptions = {},\n filters: { folders?: FolderFilters; dates?: DateFilters } = {},\n options: { logLevel?: LogLevel } = {}\n): Promise<ExportSummary> {\n if (options.logLevel !== undefined) {\n setLogLevel(options.logLevel);\n }\n logProgress('export.start', { target: context.targetDir });\n const reporter = createProgressReporter();\n const total = await getNoteCount(readerOptions);\n logProgress('export.count', { total });\n const entries: IndexEntry[] = [];\n let first: ExportSummary['firstNote'] = null;\n const skipped: ExportSummary['skipped'] = [];\n for (let i = 0; i < total; i += 1) {\n let note: Note;\n try {\n note = await readNoteByIndex(i, readerOptions);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (error instanceof NotesBridgeError) {\n logProgress('export.skip', { idx: i + 1, reason: message });\n skipped.push({ index: i, reason: message });\n continue;\n }\n throw error;\n }\n const gate = filterNote(note, filters);\n if (!gate.allowed) {\n logProgress('export.skip', { idx: i + 1, reason: 'filtered', folder: note.folderPath });\n skipped.push({ index: i, reason: gate.reason ?? 'Filtered' });\n continue;\n }\n\n logProgress('export.note', { idx: i + 1, id: note.id });\n const result = await writeNoteHtml(note, context.notesPath);\n const entry = {\n noteId: note.id,\n noteName: note.name,\n artifacts: [],\n folderPath: note.folderPath,\n createdAtUtc: note.createdAtUtc,\n modifiedAtUtc: note.modifiedAtUtc,\n htmlPath: result.relativeHtmlPath\n };\n entries.push(entry);\n if (!first) {\n first = {\n id: entry.noteId,\n name: entry.noteName,\n folderPath: entry.folderPath,\n htmlPath: entry.htmlPath\n };\n }\n if (typeof readerOptions.limit === 'number' && entries.length >= readerOptions.limit) {\n break;\n }\n reporter.emit('export.progress', {\n processed: entries.length + skipped.length,\n exported: entries.length,\n skipped: skipped.length,\n total\n });\n }\n\n logProgress('export.written', { count: entries.length });\n await fs.writeFile(context.indexPath, JSON.stringify(entries, null, 2), 'utf8');\n logProgress('export.index.write', { count: entries.length, path: context.indexPath });\n\n return {\n exported: entries.length,\n skipped,\n indexPath: context.indexPath,\n notesPath: context.notesPath,\n firstNote: first\n };\n}\n","import { run } from '@jxa/run';\nimport { ensureMacOS, NotesBridgeError } from '../notes-bridge';\nimport type { Note } from '../../models/note';\nimport type { NotesReaderOptions, RawNoteResult, NotesApp } from './types';\nimport { normalizeNote } from './normalize';\nimport type { RunJxa } from './jxa-helpers';\nimport { debugLog } from './jxa-helpers';\n\ndeclare function Application(name: string): unknown;\n\nexport async function getNoteCount(options: NotesReaderOptions = {}): Promise<number> {\n ensureMacOS();\n const runner: RunJxa = options.runJxa ?? run;\n try {\n const count = (await runner(() => {\n const Notes = Application('Notes') as NotesApp;\n const raw = typeof Notes.notes === 'function' ? Notes.notes() : [];\n const length = (raw as { length?: number }).length;\n return typeof length === 'number' && length >= 0 ? length : 0;\n })) as number;\n const safeCount = typeof count === 'number' && count >= 0 ? count : 0;\n debugLog('notes.count', { count: safeCount });\n return safeCount;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new NotesBridgeError(`Failed to count notes: ${message}`);\n }\n}\n\nexport async function readNoteByIndex(\n index: number,\n options: NotesReaderOptions = {}\n): Promise<Note> {\n ensureMacOS();\n const runner: RunJxa = options.runJxa ?? run;\n try {\n const raw = (await runner((idx: number) => {\n const Notes = Application('Notes') as NotesApp;\n type JxaNote = {\n id?: () => unknown;\n uuid?: () => unknown;\n name?: () => unknown;\n body?: () => unknown;\n container?: () => unknown;\n creationDate?: () => unknown;\n modificationDate?: () => unknown;\n };\n const notes = (typeof Notes.notes === 'function' ? Notes.notes() : []) as JxaNote[];\n\n const inRange = idx >= 0 && idx < (notes as { length: number }).length;\n if (!inRange) return null;\n\n const note: JxaNote | undefined = notes[idx];\n if (!note) return null;\n\n const callMethod = <T>(target: unknown, method: string): T | null => {\n if (!target || typeof (target as Record<string, unknown>)[method] !== 'function') {\n return null;\n }\n try {\n return (target as { [key: string]: () => T })[method]();\n } catch {\n return null;\n }\n };\n\n const containerPath = (): string => {\n const names: string[] = [];\n let current = callMethod<unknown>(note, 'container');\n while (current) {\n const containerName = callMethod<string>(current, 'name');\n if (typeof containerName === 'string' && containerName.trim().length > 0) {\n names.push(containerName);\n }\n current = callMethod<unknown>(current, 'container');\n }\n return names.length ? names.reverse().join('/') : 'Notes';\n };\n\n return {\n id: callMethod(note, 'id') ?? callMethod(note, 'uuid'),\n name: callMethod(note, 'name'),\n bodyHtml: callMethod(note, 'body'),\n folderPath: containerPath(),\n createdAtUtc: callMethod(note, 'creationDate'),\n modifiedAtUtc: callMethod(note, 'modificationDate')\n } satisfies RawNoteResult;\n }, index)) as RawNoteResult | null;\n\n if (!raw) {\n throw new NotesBridgeError(`Note at index ${index} not found.`);\n }\n\n const normalized = normalizeNote(raw, { folderPath: 'Notes' });\n debugLog('notes.read', { index, id: normalized.id, name: normalized.name });\n return normalized;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new NotesBridgeError(`Failed to read note at index ${index}: ${message}`);\n }\n}\n","import type { Note } from '../../models/note';\nimport { NotesBridgeError } from '../notes-bridge';\nimport type { RawNoteResult } from './types';\n\nexport function coerceString(value: unknown): string | null {\n if (value == null) return null;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n return null;\n}\n\nfunction toIsoString(value: unknown): string | null {\n if (!value) return null;\n if (typeof value === 'string' || value instanceof Date) {\n const date = value instanceof Date ? value : new Date(value);\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n }\n return null;\n}\n\nexport function normalizeNote(raw: RawNoteResult | null, fallback?: Partial<RawNoteResult>): Note {\n const merged: RawNoteResult = {\n id: raw?.id ?? fallback?.id ?? null,\n name: raw?.name ?? fallback?.name ?? null,\n bodyHtml: raw?.bodyHtml ?? fallback?.bodyHtml ?? '',\n folderPath: raw?.folderPath ?? fallback?.folderPath ?? 'Notes',\n createdAtUtc: raw?.createdAtUtc ?? fallback?.createdAtUtc ?? null,\n modifiedAtUtc: raw?.modifiedAtUtc ?? fallback?.modifiedAtUtc ?? null\n };\n\n const id = coerceString(merged.id);\n if (!id) {\n throw new NotesBridgeError('Apple Notes returned a note without an id.');\n }\n\n const name = coerceString(merged.name) ?? id;\n const folderPath = coerceString(merged.folderPath) ?? 'Notes';\n const createdAtUtc = toIsoString(merged.createdAtUtc) ?? new Date().toISOString();\n const modifiedAtUtc = toIsoString(merged.modifiedAtUtc) ?? createdAtUtc;\n const bodyHtml = coerceString(merged.bodyHtml) ?? '';\n\n return {\n id,\n name,\n bodyHtml,\n folderPath,\n createdAtUtc,\n modifiedAtUtc,\n attachments: []\n };\n}\n","export type RunJxa = (fn: (...args: any[]) => any, ...args: any[]) => Promise<any>;\n\nexport enum LogLevel {\n Quiet = 0,\n Info = 1,\n Debug = 2\n}\n\nlet currentLevel: LogLevel = process.env.NOTES_DEBUG ? LogLevel.Debug : LogLevel.Info;\n\nexport const setLogLevel = (level: LogLevel): void => {\n currentLevel = level;\n};\n\nexport const debugLog = (message: string, data?: unknown): void => {\n if (currentLevel < LogLevel.Debug) return;\n const payload = data === undefined ? '' : ` ${JSON.stringify(data)}`;\n console.error(`[notes-debug] ${message}${payload}`);\n};\n\nexport const logProgress = (message: string, data?: unknown): void => {\n if (currentLevel < LogLevel.Info && message !== 'export.progress' && message !== 'export.count' && message !== 'export.start' && message !== 'export.index.write' && message !== 'export.written') {\n return;\n }\n const payload = data === undefined ? '' : ` ${JSON.stringify(data)}`;\n console.error(`[notes] ${message}${payload}`);\n};\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Note } from '../models/note';\nimport { buildNotePath, type NotePathInfo } from './pathing';\n\nexport type WriteNoteResult = NotePathInfo;\n\nexport async function writeNoteHtml(note: Note, notesRoot: string): Promise<WriteNoteResult> {\n const { absoluteHtmlPath, relativeHtmlPath } = buildNotePath(note, notesRoot);\n await fs.mkdir(path.dirname(absoluteHtmlPath), { recursive: true });\n await fs.writeFile(absoluteHtmlPath, note.bodyHtml, 'utf8');\n\n const created = new Date(note.createdAtUtc);\n const modified = new Date(note.modifiedAtUtc);\n const atime = Number.isNaN(created.getTime()) ? new Date() : created;\n const mtime = Number.isNaN(modified.getTime()) ? atime : modified;\n\n await fs.utimes(absoluteHtmlPath, atime, mtime);\n\n return { absoluteHtmlPath, relativeHtmlPath };\n}\n","import path from 'node:path';\nimport type { Note } from '../models/note';\n\nfunction sanitizeSegment(input: string): string {\n const trimmed = input.trim();\n const safe = trimmed\n .replace(/[\\s]+/g, '-')\n .replace(/[^a-zA-Z0-9-_]/g, '')\n .replace(/-+/g, '-')\n .replace(/^[-_]+|[-_]+$/g, '');\n return safe || 'note';\n}\n\nfunction slugifyNote(note: Pick<Note, 'name' | 'id'>): string {\n const namePart = sanitizeSegment(note.name);\n const idPart = sanitizeSegment(note.id);\n return `${namePart}_${idPart}`;\n}\n\nfunction normalizeFolderPath(folderPath: string): string {\n const parts = folderPath.split('/').filter(Boolean).map(sanitizeSegment);\n return parts.join('/');\n}\n\nexport interface NotePathInfo {\n relativeHtmlPath: string;\n absoluteHtmlPath: string;\n}\n\nexport function buildNotePath(note: Note, notesRoot: string): NotePathInfo {\n const folderRel = normalizeFolderPath(note.folderPath || 'Notes');\n const filename = `${slugifyNote(note)}.html`;\n const relativeHtmlPath = path.posix.join(folderRel, filename);\n const absoluteHtmlPath = path.join(notesRoot, relativeHtmlPath);\n return { relativeHtmlPath, absoluteHtmlPath };\n}\n\nexport function defaultSlug(note: Pick<Note, 'name' | 'id'>): string {\n return slugifyNote(note);\n}\n","export interface FolderFilters {\n include?: string[];\n exclude?: string[];\n}\n\nexport interface DateFilters {\n createdAfter?: Date;\n createdBefore?: Date;\n modifiedAfter?: Date;\n modifiedBefore?: Date;\n}\n\nconst normalize = (value: string): string => value.trim().replace(/^\\/+|\\/+$/g, '').toLowerCase();\n\nfunction cleanList(values?: string[]): string[] | undefined {\n if (!values) return undefined;\n const cleaned = values\n .flatMap((v) => v.split(','))\n .map(normalize)\n .filter((v) => v.length > 0);\n return cleaned.length ? cleaned : undefined;\n}\n\nexport function parseFolderFilters(input: {\n includeFolders?: string[];\n excludeFolders?: string[];\n}): FolderFilters {\n return {\n include: cleanList(input.includeFolders),\n exclude: cleanList(input.excludeFolders)\n };\n}\n\nexport function matchesFolder(folderPath: string, filters: FolderFilters): boolean {\n const normalized = normalize(folderPath);\n\n const matchesAny = (needles?: string[]) =>\n needles?.some((needle) => normalized === needle || normalized.startsWith(`${needle}/`)) ?? false;\n\n if (filters.include && !matchesAny(filters.include)) {\n return false;\n }\n if (filters.exclude && matchesAny(filters.exclude)) {\n return false;\n }\n return true;\n}\n\nfunction parseDate(value?: string): Date | undefined {\n if (!value) return undefined;\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n throw new Error(`Invalid date: ${value}`);\n }\n return parsed;\n}\n\nexport function parseDateFilters(input: {\n createdAfter?: string;\n createdBefore?: string;\n modifiedAfter?: string;\n modifiedBefore?: string;\n}): DateFilters {\n return {\n createdAfter: parseDate(input.createdAfter),\n createdBefore: parseDate(input.createdBefore),\n modifiedAfter: parseDate(input.modifiedAfter),\n modifiedBefore: parseDate(input.modifiedBefore)\n };\n}\n","import type { DateFilters, FolderFilters } from './filter-schema';\nimport { matchesFolder } from './filter-schema';\nimport type { Note } from '../models/note';\n\nexport function shouldExportFolder(folderPath: string, filters: FolderFilters): boolean {\n return matchesFolder(folderPath, filters);\n}\n\nfunction matchesDate(value: string, after?: Date, before?: Date): boolean {\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) return false;\n if (after && parsed.getTime() < after.getTime()) return false;\n if (before && parsed.getTime() > before.getTime()) return false;\n return true;\n}\n\nexport function filterNote(\n note: Note,\n filters: { folders?: FolderFilters; dates?: DateFilters }\n): { allowed: boolean; reason?: string } {\n const folderFilters = filters.folders ?? {};\n if (!shouldExportFolder(note.folderPath, folderFilters)) {\n return { allowed: false, reason: `Filtered by folder: ${note.folderPath}` };\n }\n\n const dateFilters = filters.dates ?? {};\n const createdOk = matchesDate(note.createdAtUtc, dateFilters.createdAfter, dateFilters.createdBefore);\n const modifiedOk = matchesDate(note.modifiedAtUtc, dateFilters.modifiedAfter, dateFilters.modifiedBefore);\n\n if (!createdOk || !modifiedOk) {\n return { allowed: false, reason: 'Filtered by date' };\n }\n\n return { allowed: true };\n}\n","import { logProgress } from './notes/jxa-helpers';\n\ntype EventName =\n | 'export.progress'\n | 'export.start'\n | 'export.count'\n | 'export.note'\n | 'export.skip'\n | 'export.written'\n | 'export.index.write';\n\ninterface ProgressReporterOptions {\n /**\n * Minimum milliseconds between emissions of repeated progress events.\n */\n rateMs?: number;\n}\n\nexport class ProgressReporter {\n private lastAt = 0;\n private readonly rateMs: number;\n\n constructor(options: ProgressReporterOptions = {}) {\n this.rateMs = options.rateMs ?? 2000;\n }\n\n emit(event: EventName, data?: unknown, force = false): void {\n const now = Date.now();\n if (!force && now - this.lastAt < this.rateMs && event === 'export.progress') {\n return;\n }\n this.lastAt = now;\n logProgress(event, data);\n }\n}\n\nexport const createProgressReporter = (options?: ProgressReporterOptions): ProgressReporter =>\n new ProgressReporter(options);\n"],"mappings":"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","names":["fs","fs","path","path","fs","path","fs","require"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
interface ExportConfig {
|
|
2
|
+
targetDir: string;
|
|
3
|
+
force?: boolean;
|
|
4
|
+
includeAttachments?: boolean;
|
|
5
|
+
}
|
|
6
|
+
interface ExportContext {
|
|
7
|
+
targetDir: string;
|
|
8
|
+
notesPath: string;
|
|
9
|
+
artifactsPath: string;
|
|
10
|
+
indexPath: string;
|
|
11
|
+
includeAttachments: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare function createExportContext(config: ExportConfig): Promise<ExportContext>;
|
|
14
|
+
|
|
15
|
+
interface NotesReaderOptions {
|
|
16
|
+
runJxa?: (fn: (...args: any[]) => any, ...args: any[]) => Promise<any>;
|
|
17
|
+
limit?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare enum LogLevel {
|
|
21
|
+
Quiet = 0,
|
|
22
|
+
Info = 1,
|
|
23
|
+
Debug = 2
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface FolderFilters {
|
|
27
|
+
include?: string[];
|
|
28
|
+
exclude?: string[];
|
|
29
|
+
}
|
|
30
|
+
interface DateFilters {
|
|
31
|
+
createdAfter?: Date;
|
|
32
|
+
createdBefore?: Date;
|
|
33
|
+
modifiedAfter?: Date;
|
|
34
|
+
modifiedBefore?: Date;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ExportSummary {
|
|
38
|
+
exported: number;
|
|
39
|
+
skipped: Array<{
|
|
40
|
+
index: number;
|
|
41
|
+
reason: string;
|
|
42
|
+
}>;
|
|
43
|
+
indexPath: string;
|
|
44
|
+
notesPath: string;
|
|
45
|
+
firstNote: {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
folderPath: string;
|
|
49
|
+
htmlPath: string;
|
|
50
|
+
} | null;
|
|
51
|
+
}
|
|
52
|
+
declare function exportNotes(context: ExportContext, readerOptions?: NotesReaderOptions, filters?: {
|
|
53
|
+
folders?: FolderFilters;
|
|
54
|
+
dates?: DateFilters;
|
|
55
|
+
}, options?: {
|
|
56
|
+
logLevel?: LogLevel;
|
|
57
|
+
}): Promise<ExportSummary>;
|
|
58
|
+
|
|
59
|
+
interface Note {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
bodyHtml: string;
|
|
63
|
+
folderPath: string;
|
|
64
|
+
createdAtUtc: string;
|
|
65
|
+
modifiedAtUtc: string;
|
|
66
|
+
attachments: string[];
|
|
67
|
+
}
|
|
68
|
+
interface IndexEntry {
|
|
69
|
+
noteId: string;
|
|
70
|
+
noteName: string;
|
|
71
|
+
artifacts: string[];
|
|
72
|
+
folderPath: string;
|
|
73
|
+
createdAtUtc: string;
|
|
74
|
+
modifiedAtUtc: string;
|
|
75
|
+
htmlPath: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
declare function getNoteCount(options?: NotesReaderOptions): Promise<number>;
|
|
79
|
+
declare function readNoteByIndex(index: number, options?: NotesReaderOptions): Promise<Note>;
|
|
80
|
+
|
|
81
|
+
interface NotePathInfo {
|
|
82
|
+
relativeHtmlPath: string;
|
|
83
|
+
absoluteHtmlPath: string;
|
|
84
|
+
}
|
|
85
|
+
declare function buildNotePath(note: Note, notesRoot: string): NotePathInfo;
|
|
86
|
+
declare function defaultSlug(note: Pick<Note, 'name' | 'id'>): string;
|
|
87
|
+
|
|
88
|
+
export { type IndexEntry, type Note, buildNotePath, createExportContext, defaultSlug, exportNotes, getNoteCount, readNoteByIndex };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// src/lib/export-context.ts
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
var INDEX_FILENAME = "index.json";
|
|
5
|
+
async function ensureDirectoryExists(targetDir) {
|
|
6
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
7
|
+
}
|
|
8
|
+
async function isDirectoryEmpty(dir, allowedNames = []) {
|
|
9
|
+
const entries = await fs.readdir(dir);
|
|
10
|
+
const meaningful = entries.filter((name) => !allowedNames.includes(name));
|
|
11
|
+
return meaningful.length === 0;
|
|
12
|
+
}
|
|
13
|
+
async function validateTargetDirectory(targetDir, force) {
|
|
14
|
+
const stats = await fs.stat(targetDir).catch(() => void 0);
|
|
15
|
+
if (!stats) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!stats.isDirectory()) {
|
|
19
|
+
throw new Error(`Target path must be a directory: ${targetDir}`);
|
|
20
|
+
}
|
|
21
|
+
const allowed = ["node-compile-cache"];
|
|
22
|
+
if (!await isDirectoryEmpty(targetDir, allowed) && !force) {
|
|
23
|
+
throw new Error("Target directory must be empty unless --force is provided.");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function createExportContext(config) {
|
|
27
|
+
if (!path.isAbsolute(config.targetDir)) {
|
|
28
|
+
throw new Error("Target directory must be an absolute path.");
|
|
29
|
+
}
|
|
30
|
+
const force = Boolean(config.force);
|
|
31
|
+
const includeAttachments = Boolean(config.includeAttachments);
|
|
32
|
+
await ensureDirectoryExists(config.targetDir);
|
|
33
|
+
await validateTargetDirectory(config.targetDir, force);
|
|
34
|
+
const notesPath = path.join(config.targetDir, "notes");
|
|
35
|
+
const artifactsPath = path.join(config.targetDir, "artifacts");
|
|
36
|
+
const indexPath = path.join(config.targetDir, INDEX_FILENAME);
|
|
37
|
+
await fs.mkdir(notesPath, { recursive: true });
|
|
38
|
+
if (includeAttachments) {
|
|
39
|
+
await fs.mkdir(artifactsPath, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
targetDir: config.targetDir,
|
|
43
|
+
notesPath,
|
|
44
|
+
artifactsPath,
|
|
45
|
+
indexPath,
|
|
46
|
+
includeAttachments
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/lib/export-runner.ts
|
|
51
|
+
import fs3 from "fs/promises";
|
|
52
|
+
|
|
53
|
+
// src/lib/notes/reader.ts
|
|
54
|
+
import { run } from "@jxa/run";
|
|
55
|
+
|
|
56
|
+
// src/lib/notes-bridge.ts
|
|
57
|
+
var MacOSUnsupportedError = class extends Error {
|
|
58
|
+
constructor(message = "Apple Notes export requires macOS.") {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = "MacOSUnsupportedError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var NotesBridgeError = class extends Error {
|
|
64
|
+
constructor(message) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.name = "NotesBridgeError";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
function ensureMacOS() {
|
|
70
|
+
if (process.platform !== "darwin") {
|
|
71
|
+
throw new MacOSUnsupportedError();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/lib/notes/normalize.ts
|
|
76
|
+
function coerceString(value) {
|
|
77
|
+
if (value == null) return null;
|
|
78
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
79
|
+
return String(value);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function toIsoString(value) {
|
|
84
|
+
if (!value) return null;
|
|
85
|
+
if (typeof value === "string" || value instanceof Date) {
|
|
86
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
87
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function normalizeNote(raw, fallback) {
|
|
92
|
+
const merged = {
|
|
93
|
+
id: raw?.id ?? fallback?.id ?? null,
|
|
94
|
+
name: raw?.name ?? fallback?.name ?? null,
|
|
95
|
+
bodyHtml: raw?.bodyHtml ?? fallback?.bodyHtml ?? "",
|
|
96
|
+
folderPath: raw?.folderPath ?? fallback?.folderPath ?? "Notes",
|
|
97
|
+
createdAtUtc: raw?.createdAtUtc ?? fallback?.createdAtUtc ?? null,
|
|
98
|
+
modifiedAtUtc: raw?.modifiedAtUtc ?? fallback?.modifiedAtUtc ?? null
|
|
99
|
+
};
|
|
100
|
+
const id = coerceString(merged.id);
|
|
101
|
+
if (!id) {
|
|
102
|
+
throw new NotesBridgeError("Apple Notes returned a note without an id.");
|
|
103
|
+
}
|
|
104
|
+
const name = coerceString(merged.name) ?? id;
|
|
105
|
+
const folderPath = coerceString(merged.folderPath) ?? "Notes";
|
|
106
|
+
const createdAtUtc = toIsoString(merged.createdAtUtc) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
107
|
+
const modifiedAtUtc = toIsoString(merged.modifiedAtUtc) ?? createdAtUtc;
|
|
108
|
+
const bodyHtml = coerceString(merged.bodyHtml) ?? "";
|
|
109
|
+
return {
|
|
110
|
+
id,
|
|
111
|
+
name,
|
|
112
|
+
bodyHtml,
|
|
113
|
+
folderPath,
|
|
114
|
+
createdAtUtc,
|
|
115
|
+
modifiedAtUtc,
|
|
116
|
+
attachments: []
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/lib/notes/jxa-helpers.ts
|
|
121
|
+
var currentLevel = process.env.NOTES_DEBUG ? 2 /* Debug */ : 1 /* Info */;
|
|
122
|
+
var setLogLevel = (level) => {
|
|
123
|
+
currentLevel = level;
|
|
124
|
+
};
|
|
125
|
+
var debugLog = (message, data) => {
|
|
126
|
+
if (currentLevel < 2 /* Debug */) return;
|
|
127
|
+
const payload = data === void 0 ? "" : ` ${JSON.stringify(data)}`;
|
|
128
|
+
console.error(`[notes-debug] ${message}${payload}`);
|
|
129
|
+
};
|
|
130
|
+
var logProgress = (message, data) => {
|
|
131
|
+
if (currentLevel < 1 /* Info */ && message !== "export.progress" && message !== "export.count" && message !== "export.start" && message !== "export.index.write" && message !== "export.written") {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const payload = data === void 0 ? "" : ` ${JSON.stringify(data)}`;
|
|
135
|
+
console.error(`[notes] ${message}${payload}`);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/lib/notes/reader.ts
|
|
139
|
+
async function getNoteCount(options = {}) {
|
|
140
|
+
ensureMacOS();
|
|
141
|
+
const runner = options.runJxa ?? run;
|
|
142
|
+
try {
|
|
143
|
+
const count = await runner(() => {
|
|
144
|
+
const Notes = Application("Notes");
|
|
145
|
+
const raw = typeof Notes.notes === "function" ? Notes.notes() : [];
|
|
146
|
+
const length = raw.length;
|
|
147
|
+
return typeof length === "number" && length >= 0 ? length : 0;
|
|
148
|
+
});
|
|
149
|
+
const safeCount = typeof count === "number" && count >= 0 ? count : 0;
|
|
150
|
+
debugLog("notes.count", { count: safeCount });
|
|
151
|
+
return safeCount;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
154
|
+
throw new NotesBridgeError(`Failed to count notes: ${message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function readNoteByIndex(index, options = {}) {
|
|
158
|
+
ensureMacOS();
|
|
159
|
+
const runner = options.runJxa ?? run;
|
|
160
|
+
try {
|
|
161
|
+
const raw = await runner((idx) => {
|
|
162
|
+
const Notes = Application("Notes");
|
|
163
|
+
const notes = typeof Notes.notes === "function" ? Notes.notes() : [];
|
|
164
|
+
const inRange = idx >= 0 && idx < notes.length;
|
|
165
|
+
if (!inRange) return null;
|
|
166
|
+
const note = notes[idx];
|
|
167
|
+
if (!note) return null;
|
|
168
|
+
const callMethod = (target, method) => {
|
|
169
|
+
if (!target || typeof target[method] !== "function") {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
return target[method]();
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const containerPath = () => {
|
|
179
|
+
const names = [];
|
|
180
|
+
let current = callMethod(note, "container");
|
|
181
|
+
while (current) {
|
|
182
|
+
const containerName = callMethod(current, "name");
|
|
183
|
+
if (typeof containerName === "string" && containerName.trim().length > 0) {
|
|
184
|
+
names.push(containerName);
|
|
185
|
+
}
|
|
186
|
+
current = callMethod(current, "container");
|
|
187
|
+
}
|
|
188
|
+
return names.length ? names.reverse().join("/") : "Notes";
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
id: callMethod(note, "id") ?? callMethod(note, "uuid"),
|
|
192
|
+
name: callMethod(note, "name"),
|
|
193
|
+
bodyHtml: callMethod(note, "body"),
|
|
194
|
+
folderPath: containerPath(),
|
|
195
|
+
createdAtUtc: callMethod(note, "creationDate"),
|
|
196
|
+
modifiedAtUtc: callMethod(note, "modificationDate")
|
|
197
|
+
};
|
|
198
|
+
}, index);
|
|
199
|
+
if (!raw) {
|
|
200
|
+
throw new NotesBridgeError(`Note at index ${index} not found.`);
|
|
201
|
+
}
|
|
202
|
+
const normalized = normalizeNote(raw, { folderPath: "Notes" });
|
|
203
|
+
debugLog("notes.read", { index, id: normalized.id, name: normalized.name });
|
|
204
|
+
return normalized;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
207
|
+
throw new NotesBridgeError(`Failed to read note at index ${index}: ${message}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/services/file-writer.ts
|
|
212
|
+
import fs2 from "fs/promises";
|
|
213
|
+
import path3 from "path";
|
|
214
|
+
|
|
215
|
+
// src/services/pathing.ts
|
|
216
|
+
import path2 from "path";
|
|
217
|
+
function sanitizeSegment(input) {
|
|
218
|
+
const trimmed = input.trim();
|
|
219
|
+
const safe = trimmed.replace(/[\s]+/g, "-").replace(/[^a-zA-Z0-9-_]/g, "").replace(/-+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
220
|
+
return safe || "note";
|
|
221
|
+
}
|
|
222
|
+
function slugifyNote(note) {
|
|
223
|
+
const namePart = sanitizeSegment(note.name);
|
|
224
|
+
const idPart = sanitizeSegment(note.id);
|
|
225
|
+
return `${namePart}_${idPart}`;
|
|
226
|
+
}
|
|
227
|
+
function normalizeFolderPath(folderPath) {
|
|
228
|
+
const parts = folderPath.split("/").filter(Boolean).map(sanitizeSegment);
|
|
229
|
+
return parts.join("/");
|
|
230
|
+
}
|
|
231
|
+
function buildNotePath(note, notesRoot) {
|
|
232
|
+
const folderRel = normalizeFolderPath(note.folderPath || "Notes");
|
|
233
|
+
const filename = `${slugifyNote(note)}.html`;
|
|
234
|
+
const relativeHtmlPath = path2.posix.join(folderRel, filename);
|
|
235
|
+
const absoluteHtmlPath = path2.join(notesRoot, relativeHtmlPath);
|
|
236
|
+
return { relativeHtmlPath, absoluteHtmlPath };
|
|
237
|
+
}
|
|
238
|
+
function defaultSlug(note) {
|
|
239
|
+
return slugifyNote(note);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/services/file-writer.ts
|
|
243
|
+
async function writeNoteHtml(note, notesRoot) {
|
|
244
|
+
const { absoluteHtmlPath, relativeHtmlPath } = buildNotePath(note, notesRoot);
|
|
245
|
+
await fs2.mkdir(path3.dirname(absoluteHtmlPath), { recursive: true });
|
|
246
|
+
await fs2.writeFile(absoluteHtmlPath, note.bodyHtml, "utf8");
|
|
247
|
+
const created = new Date(note.createdAtUtc);
|
|
248
|
+
const modified = new Date(note.modifiedAtUtc);
|
|
249
|
+
const atime = Number.isNaN(created.getTime()) ? /* @__PURE__ */ new Date() : created;
|
|
250
|
+
const mtime = Number.isNaN(modified.getTime()) ? atime : modified;
|
|
251
|
+
await fs2.utimes(absoluteHtmlPath, atime, mtime);
|
|
252
|
+
return { absoluteHtmlPath, relativeHtmlPath };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/lib/filter-schema.ts
|
|
256
|
+
var normalize = (value) => value.trim().replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
257
|
+
function matchesFolder(folderPath, filters) {
|
|
258
|
+
const normalized = normalize(folderPath);
|
|
259
|
+
const matchesAny = (needles) => needles?.some((needle) => normalized === needle || normalized.startsWith(`${needle}/`)) ?? false;
|
|
260
|
+
if (filters.include && !matchesAny(filters.include)) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (filters.exclude && matchesAny(filters.exclude)) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/lib/filtering.ts
|
|
270
|
+
function shouldExportFolder(folderPath, filters) {
|
|
271
|
+
return matchesFolder(folderPath, filters);
|
|
272
|
+
}
|
|
273
|
+
function matchesDate(value, after, before) {
|
|
274
|
+
const parsed = new Date(value);
|
|
275
|
+
if (Number.isNaN(parsed.getTime())) return false;
|
|
276
|
+
if (after && parsed.getTime() < after.getTime()) return false;
|
|
277
|
+
if (before && parsed.getTime() > before.getTime()) return false;
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
function filterNote(note, filters) {
|
|
281
|
+
const folderFilters = filters.folders ?? {};
|
|
282
|
+
if (!shouldExportFolder(note.folderPath, folderFilters)) {
|
|
283
|
+
return { allowed: false, reason: `Filtered by folder: ${note.folderPath}` };
|
|
284
|
+
}
|
|
285
|
+
const dateFilters = filters.dates ?? {};
|
|
286
|
+
const createdOk = matchesDate(note.createdAtUtc, dateFilters.createdAfter, dateFilters.createdBefore);
|
|
287
|
+
const modifiedOk = matchesDate(note.modifiedAtUtc, dateFilters.modifiedAfter, dateFilters.modifiedBefore);
|
|
288
|
+
if (!createdOk || !modifiedOk) {
|
|
289
|
+
return { allowed: false, reason: "Filtered by date" };
|
|
290
|
+
}
|
|
291
|
+
return { allowed: true };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/lib/progress-reporter.ts
|
|
295
|
+
var ProgressReporter = class {
|
|
296
|
+
lastAt = 0;
|
|
297
|
+
rateMs;
|
|
298
|
+
constructor(options = {}) {
|
|
299
|
+
this.rateMs = options.rateMs ?? 2e3;
|
|
300
|
+
}
|
|
301
|
+
emit(event, data, force = false) {
|
|
302
|
+
const now = Date.now();
|
|
303
|
+
if (!force && now - this.lastAt < this.rateMs && event === "export.progress") {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
this.lastAt = now;
|
|
307
|
+
logProgress(event, data);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
var createProgressReporter = (options) => new ProgressReporter(options);
|
|
311
|
+
|
|
312
|
+
// src/lib/export-runner.ts
|
|
313
|
+
async function exportNotes(context, readerOptions = {}, filters = {}, options = {}) {
|
|
314
|
+
if (options.logLevel !== void 0) {
|
|
315
|
+
setLogLevel(options.logLevel);
|
|
316
|
+
}
|
|
317
|
+
logProgress("export.start", { target: context.targetDir });
|
|
318
|
+
const reporter = createProgressReporter();
|
|
319
|
+
const total = await getNoteCount(readerOptions);
|
|
320
|
+
logProgress("export.count", { total });
|
|
321
|
+
const entries = [];
|
|
322
|
+
let first = null;
|
|
323
|
+
const skipped = [];
|
|
324
|
+
for (let i = 0; i < total; i += 1) {
|
|
325
|
+
let note;
|
|
326
|
+
try {
|
|
327
|
+
note = await readNoteByIndex(i, readerOptions);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
330
|
+
if (error instanceof NotesBridgeError) {
|
|
331
|
+
logProgress("export.skip", { idx: i + 1, reason: message });
|
|
332
|
+
skipped.push({ index: i, reason: message });
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
const gate = filterNote(note, filters);
|
|
338
|
+
if (!gate.allowed) {
|
|
339
|
+
logProgress("export.skip", { idx: i + 1, reason: "filtered", folder: note.folderPath });
|
|
340
|
+
skipped.push({ index: i, reason: gate.reason ?? "Filtered" });
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
logProgress("export.note", { idx: i + 1, id: note.id });
|
|
344
|
+
const result = await writeNoteHtml(note, context.notesPath);
|
|
345
|
+
const entry = {
|
|
346
|
+
noteId: note.id,
|
|
347
|
+
noteName: note.name,
|
|
348
|
+
artifacts: [],
|
|
349
|
+
folderPath: note.folderPath,
|
|
350
|
+
createdAtUtc: note.createdAtUtc,
|
|
351
|
+
modifiedAtUtc: note.modifiedAtUtc,
|
|
352
|
+
htmlPath: result.relativeHtmlPath
|
|
353
|
+
};
|
|
354
|
+
entries.push(entry);
|
|
355
|
+
if (!first) {
|
|
356
|
+
first = {
|
|
357
|
+
id: entry.noteId,
|
|
358
|
+
name: entry.noteName,
|
|
359
|
+
folderPath: entry.folderPath,
|
|
360
|
+
htmlPath: entry.htmlPath
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
if (typeof readerOptions.limit === "number" && entries.length >= readerOptions.limit) {
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
reporter.emit("export.progress", {
|
|
367
|
+
processed: entries.length + skipped.length,
|
|
368
|
+
exported: entries.length,
|
|
369
|
+
skipped: skipped.length,
|
|
370
|
+
total
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
logProgress("export.written", { count: entries.length });
|
|
374
|
+
await fs3.writeFile(context.indexPath, JSON.stringify(entries, null, 2), "utf8");
|
|
375
|
+
logProgress("export.index.write", { count: entries.length, path: context.indexPath });
|
|
376
|
+
return {
|
|
377
|
+
exported: entries.length,
|
|
378
|
+
skipped,
|
|
379
|
+
indexPath: context.indexPath,
|
|
380
|
+
notesPath: context.notesPath,
|
|
381
|
+
firstNote: first
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
export {
|
|
385
|
+
buildNotePath,
|
|
386
|
+
createExportContext,
|
|
387
|
+
defaultSlug,
|
|
388
|
+
exportNotes,
|
|
389
|
+
getNoteCount,
|
|
390
|
+
readNoteByIndex
|
|
391
|
+
};
|
|
392
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/export-context.ts","../src/lib/export-runner.ts","../src/lib/notes/reader.ts","../src/lib/notes-bridge.ts","../src/lib/notes/normalize.ts","../src/lib/notes/jxa-helpers.ts","../src/services/file-writer.ts","../src/services/pathing.ts","../src/lib/filter-schema.ts","../src/lib/filtering.ts","../src/lib/progress-reporter.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nconst INDEX_FILENAME = 'index.json';\n\nexport interface ExportConfig {\n targetDir: string;\n force?: boolean;\n includeAttachments?: boolean;\n}\n\nexport interface ExportContext {\n targetDir: string;\n notesPath: string;\n artifactsPath: string;\n indexPath: string;\n includeAttachments: boolean;\n}\n\nasync function ensureDirectoryExists(targetDir: string): Promise<void> {\n await fs.mkdir(targetDir, { recursive: true });\n}\n\nasync function isDirectoryEmpty(dir: string, allowedNames: string[] = []): Promise<boolean> {\n const entries = await fs.readdir(dir);\n const meaningful = entries.filter((name) => !allowedNames.includes(name));\n return meaningful.length === 0;\n}\n\nexport async function validateTargetDirectory(targetDir: string, force: boolean): Promise<void> {\n const stats = await fs.stat(targetDir).catch(() => undefined);\n if (!stats) {\n return;\n }\n if (!stats.isDirectory()) {\n throw new Error(`Target path must be a directory: ${targetDir}`);\n }\n // Some environments drop a node-compile-cache folder beside the app; allow it when empty otherwise.\n const allowed = ['node-compile-cache'];\n if (!(await isDirectoryEmpty(targetDir, allowed)) && !force) {\n throw new Error('Target directory must be empty unless --force is provided.');\n }\n}\n\nexport async function createExportContext(config: ExportConfig): Promise<ExportContext> {\n if (!path.isAbsolute(config.targetDir)) {\n throw new Error('Target directory must be an absolute path.');\n }\n\n const force = Boolean(config.force);\n const includeAttachments = Boolean(config.includeAttachments);\n\n await ensureDirectoryExists(config.targetDir);\n await validateTargetDirectory(config.targetDir, force);\n\n const notesPath = path.join(config.targetDir, 'notes');\n const artifactsPath = path.join(config.targetDir, 'artifacts');\n const indexPath = path.join(config.targetDir, INDEX_FILENAME);\n\n await fs.mkdir(notesPath, { recursive: true });\n if (includeAttachments) {\n await fs.mkdir(artifactsPath, { recursive: true });\n }\n\n return {\n targetDir: config.targetDir,\n notesPath,\n artifactsPath,\n indexPath,\n includeAttachments\n };\n}\n","import fs from 'node:fs/promises';\nimport type { ExportContext } from './export-context';\nimport { getNoteCount, readNoteByIndex } from './notes/reader';\nimport type { NotesReaderOptions } from './notes/types';\nimport { LogLevel, logProgress, setLogLevel } from './notes/jxa-helpers';\nimport { NotesBridgeError } from './notes-bridge';\nimport { writeNoteHtml } from '../services/file-writer';\nimport type { IndexEntry, Note } from '../models/note';\nimport type { DateFilters, FolderFilters } from './filter-schema';\nimport { filterNote } from './filtering';\nimport { createProgressReporter } from './progress-reporter';\n\nexport interface ExportSummary {\n exported: number;\n skipped: Array<{ index: number; reason: string }>;\n indexPath: string;\n notesPath: string;\n firstNote:\n | {\n id: string;\n name: string;\n folderPath: string;\n htmlPath: string;\n }\n | null;\n}\n\nexport async function exportNotes(\n context: ExportContext,\n readerOptions: NotesReaderOptions = {},\n filters: { folders?: FolderFilters; dates?: DateFilters } = {},\n options: { logLevel?: LogLevel } = {}\n): Promise<ExportSummary> {\n if (options.logLevel !== undefined) {\n setLogLevel(options.logLevel);\n }\n logProgress('export.start', { target: context.targetDir });\n const reporter = createProgressReporter();\n const total = await getNoteCount(readerOptions);\n logProgress('export.count', { total });\n const entries: IndexEntry[] = [];\n let first: ExportSummary['firstNote'] = null;\n const skipped: ExportSummary['skipped'] = [];\n for (let i = 0; i < total; i += 1) {\n let note: Note;\n try {\n note = await readNoteByIndex(i, readerOptions);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (error instanceof NotesBridgeError) {\n logProgress('export.skip', { idx: i + 1, reason: message });\n skipped.push({ index: i, reason: message });\n continue;\n }\n throw error;\n }\n const gate = filterNote(note, filters);\n if (!gate.allowed) {\n logProgress('export.skip', { idx: i + 1, reason: 'filtered', folder: note.folderPath });\n skipped.push({ index: i, reason: gate.reason ?? 'Filtered' });\n continue;\n }\n\n logProgress('export.note', { idx: i + 1, id: note.id });\n const result = await writeNoteHtml(note, context.notesPath);\n const entry = {\n noteId: note.id,\n noteName: note.name,\n artifacts: [],\n folderPath: note.folderPath,\n createdAtUtc: note.createdAtUtc,\n modifiedAtUtc: note.modifiedAtUtc,\n htmlPath: result.relativeHtmlPath\n };\n entries.push(entry);\n if (!first) {\n first = {\n id: entry.noteId,\n name: entry.noteName,\n folderPath: entry.folderPath,\n htmlPath: entry.htmlPath\n };\n }\n if (typeof readerOptions.limit === 'number' && entries.length >= readerOptions.limit) {\n break;\n }\n reporter.emit('export.progress', {\n processed: entries.length + skipped.length,\n exported: entries.length,\n skipped: skipped.length,\n total\n });\n }\n\n logProgress('export.written', { count: entries.length });\n await fs.writeFile(context.indexPath, JSON.stringify(entries, null, 2), 'utf8');\n logProgress('export.index.write', { count: entries.length, path: context.indexPath });\n\n return {\n exported: entries.length,\n skipped,\n indexPath: context.indexPath,\n notesPath: context.notesPath,\n firstNote: first\n };\n}\n","import { run } from '@jxa/run';\nimport { ensureMacOS, NotesBridgeError } from '../notes-bridge';\nimport type { Note } from '../../models/note';\nimport type { NotesReaderOptions, RawNoteResult, NotesApp } from './types';\nimport { normalizeNote } from './normalize';\nimport type { RunJxa } from './jxa-helpers';\nimport { debugLog } from './jxa-helpers';\n\ndeclare function Application(name: string): unknown;\n\nexport async function getNoteCount(options: NotesReaderOptions = {}): Promise<number> {\n ensureMacOS();\n const runner: RunJxa = options.runJxa ?? run;\n try {\n const count = (await runner(() => {\n const Notes = Application('Notes') as NotesApp;\n const raw = typeof Notes.notes === 'function' ? Notes.notes() : [];\n const length = (raw as { length?: number }).length;\n return typeof length === 'number' && length >= 0 ? length : 0;\n })) as number;\n const safeCount = typeof count === 'number' && count >= 0 ? count : 0;\n debugLog('notes.count', { count: safeCount });\n return safeCount;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new NotesBridgeError(`Failed to count notes: ${message}`);\n }\n}\n\nexport async function readNoteByIndex(\n index: number,\n options: NotesReaderOptions = {}\n): Promise<Note> {\n ensureMacOS();\n const runner: RunJxa = options.runJxa ?? run;\n try {\n const raw = (await runner((idx: number) => {\n const Notes = Application('Notes') as NotesApp;\n type JxaNote = {\n id?: () => unknown;\n uuid?: () => unknown;\n name?: () => unknown;\n body?: () => unknown;\n container?: () => unknown;\n creationDate?: () => unknown;\n modificationDate?: () => unknown;\n };\n const notes = (typeof Notes.notes === 'function' ? Notes.notes() : []) as JxaNote[];\n\n const inRange = idx >= 0 && idx < (notes as { length: number }).length;\n if (!inRange) return null;\n\n const note: JxaNote | undefined = notes[idx];\n if (!note) return null;\n\n const callMethod = <T>(target: unknown, method: string): T | null => {\n if (!target || typeof (target as Record<string, unknown>)[method] !== 'function') {\n return null;\n }\n try {\n return (target as { [key: string]: () => T })[method]();\n } catch {\n return null;\n }\n };\n\n const containerPath = (): string => {\n const names: string[] = [];\n let current = callMethod<unknown>(note, 'container');\n while (current) {\n const containerName = callMethod<string>(current, 'name');\n if (typeof containerName === 'string' && containerName.trim().length > 0) {\n names.push(containerName);\n }\n current = callMethod<unknown>(current, 'container');\n }\n return names.length ? names.reverse().join('/') : 'Notes';\n };\n\n return {\n id: callMethod(note, 'id') ?? callMethod(note, 'uuid'),\n name: callMethod(note, 'name'),\n bodyHtml: callMethod(note, 'body'),\n folderPath: containerPath(),\n createdAtUtc: callMethod(note, 'creationDate'),\n modifiedAtUtc: callMethod(note, 'modificationDate')\n } satisfies RawNoteResult;\n }, index)) as RawNoteResult | null;\n\n if (!raw) {\n throw new NotesBridgeError(`Note at index ${index} not found.`);\n }\n\n const normalized = normalizeNote(raw, { folderPath: 'Notes' });\n debugLog('notes.read', { index, id: normalized.id, name: normalized.name });\n return normalized;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new NotesBridgeError(`Failed to read note at index ${index}: ${message}`);\n }\n}\n","export class MacOSUnsupportedError extends Error {\n constructor(message = 'Apple Notes export requires macOS.') {\n super(message);\n this.name = 'MacOSUnsupportedError';\n }\n}\n\nexport class NotesBridgeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'NotesBridgeError';\n }\n}\n\nexport function ensureMacOS(): void {\n if (process.platform !== 'darwin') {\n throw new MacOSUnsupportedError();\n }\n}\n","import type { Note } from '../../models/note';\nimport { NotesBridgeError } from '../notes-bridge';\nimport type { RawNoteResult } from './types';\n\nexport function coerceString(value: unknown): string | null {\n if (value == null) return null;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n return null;\n}\n\nfunction toIsoString(value: unknown): string | null {\n if (!value) return null;\n if (typeof value === 'string' || value instanceof Date) {\n const date = value instanceof Date ? value : new Date(value);\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n }\n return null;\n}\n\nexport function normalizeNote(raw: RawNoteResult | null, fallback?: Partial<RawNoteResult>): Note {\n const merged: RawNoteResult = {\n id: raw?.id ?? fallback?.id ?? null,\n name: raw?.name ?? fallback?.name ?? null,\n bodyHtml: raw?.bodyHtml ?? fallback?.bodyHtml ?? '',\n folderPath: raw?.folderPath ?? fallback?.folderPath ?? 'Notes',\n createdAtUtc: raw?.createdAtUtc ?? fallback?.createdAtUtc ?? null,\n modifiedAtUtc: raw?.modifiedAtUtc ?? fallback?.modifiedAtUtc ?? null\n };\n\n const id = coerceString(merged.id);\n if (!id) {\n throw new NotesBridgeError('Apple Notes returned a note without an id.');\n }\n\n const name = coerceString(merged.name) ?? id;\n const folderPath = coerceString(merged.folderPath) ?? 'Notes';\n const createdAtUtc = toIsoString(merged.createdAtUtc) ?? new Date().toISOString();\n const modifiedAtUtc = toIsoString(merged.modifiedAtUtc) ?? createdAtUtc;\n const bodyHtml = coerceString(merged.bodyHtml) ?? '';\n\n return {\n id,\n name,\n bodyHtml,\n folderPath,\n createdAtUtc,\n modifiedAtUtc,\n attachments: []\n };\n}\n","export type RunJxa = (fn: (...args: any[]) => any, ...args: any[]) => Promise<any>;\n\nexport enum LogLevel {\n Quiet = 0,\n Info = 1,\n Debug = 2\n}\n\nlet currentLevel: LogLevel = process.env.NOTES_DEBUG ? LogLevel.Debug : LogLevel.Info;\n\nexport const setLogLevel = (level: LogLevel): void => {\n currentLevel = level;\n};\n\nexport const debugLog = (message: string, data?: unknown): void => {\n if (currentLevel < LogLevel.Debug) return;\n const payload = data === undefined ? '' : ` ${JSON.stringify(data)}`;\n console.error(`[notes-debug] ${message}${payload}`);\n};\n\nexport const logProgress = (message: string, data?: unknown): void => {\n if (currentLevel < LogLevel.Info && message !== 'export.progress' && message !== 'export.count' && message !== 'export.start' && message !== 'export.index.write' && message !== 'export.written') {\n return;\n }\n const payload = data === undefined ? '' : ` ${JSON.stringify(data)}`;\n console.error(`[notes] ${message}${payload}`);\n};\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Note } from '../models/note';\nimport { buildNotePath, type NotePathInfo } from './pathing';\n\nexport type WriteNoteResult = NotePathInfo;\n\nexport async function writeNoteHtml(note: Note, notesRoot: string): Promise<WriteNoteResult> {\n const { absoluteHtmlPath, relativeHtmlPath } = buildNotePath(note, notesRoot);\n await fs.mkdir(path.dirname(absoluteHtmlPath), { recursive: true });\n await fs.writeFile(absoluteHtmlPath, note.bodyHtml, 'utf8');\n\n const created = new Date(note.createdAtUtc);\n const modified = new Date(note.modifiedAtUtc);\n const atime = Number.isNaN(created.getTime()) ? new Date() : created;\n const mtime = Number.isNaN(modified.getTime()) ? atime : modified;\n\n await fs.utimes(absoluteHtmlPath, atime, mtime);\n\n return { absoluteHtmlPath, relativeHtmlPath };\n}\n","import path from 'node:path';\nimport type { Note } from '../models/note';\n\nfunction sanitizeSegment(input: string): string {\n const trimmed = input.trim();\n const safe = trimmed\n .replace(/[\\s]+/g, '-')\n .replace(/[^a-zA-Z0-9-_]/g, '')\n .replace(/-+/g, '-')\n .replace(/^[-_]+|[-_]+$/g, '');\n return safe || 'note';\n}\n\nfunction slugifyNote(note: Pick<Note, 'name' | 'id'>): string {\n const namePart = sanitizeSegment(note.name);\n const idPart = sanitizeSegment(note.id);\n return `${namePart}_${idPart}`;\n}\n\nfunction normalizeFolderPath(folderPath: string): string {\n const parts = folderPath.split('/').filter(Boolean).map(sanitizeSegment);\n return parts.join('/');\n}\n\nexport interface NotePathInfo {\n relativeHtmlPath: string;\n absoluteHtmlPath: string;\n}\n\nexport function buildNotePath(note: Note, notesRoot: string): NotePathInfo {\n const folderRel = normalizeFolderPath(note.folderPath || 'Notes');\n const filename = `${slugifyNote(note)}.html`;\n const relativeHtmlPath = path.posix.join(folderRel, filename);\n const absoluteHtmlPath = path.join(notesRoot, relativeHtmlPath);\n return { relativeHtmlPath, absoluteHtmlPath };\n}\n\nexport function defaultSlug(note: Pick<Note, 'name' | 'id'>): string {\n return slugifyNote(note);\n}\n","export interface FolderFilters {\n include?: string[];\n exclude?: string[];\n}\n\nexport interface DateFilters {\n createdAfter?: Date;\n createdBefore?: Date;\n modifiedAfter?: Date;\n modifiedBefore?: Date;\n}\n\nconst normalize = (value: string): string => value.trim().replace(/^\\/+|\\/+$/g, '').toLowerCase();\n\nfunction cleanList(values?: string[]): string[] | undefined {\n if (!values) return undefined;\n const cleaned = values\n .flatMap((v) => v.split(','))\n .map(normalize)\n .filter((v) => v.length > 0);\n return cleaned.length ? cleaned : undefined;\n}\n\nexport function parseFolderFilters(input: {\n includeFolders?: string[];\n excludeFolders?: string[];\n}): FolderFilters {\n return {\n include: cleanList(input.includeFolders),\n exclude: cleanList(input.excludeFolders)\n };\n}\n\nexport function matchesFolder(folderPath: string, filters: FolderFilters): boolean {\n const normalized = normalize(folderPath);\n\n const matchesAny = (needles?: string[]) =>\n needles?.some((needle) => normalized === needle || normalized.startsWith(`${needle}/`)) ?? false;\n\n if (filters.include && !matchesAny(filters.include)) {\n return false;\n }\n if (filters.exclude && matchesAny(filters.exclude)) {\n return false;\n }\n return true;\n}\n\nfunction parseDate(value?: string): Date | undefined {\n if (!value) return undefined;\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n throw new Error(`Invalid date: ${value}`);\n }\n return parsed;\n}\n\nexport function parseDateFilters(input: {\n createdAfter?: string;\n createdBefore?: string;\n modifiedAfter?: string;\n modifiedBefore?: string;\n}): DateFilters {\n return {\n createdAfter: parseDate(input.createdAfter),\n createdBefore: parseDate(input.createdBefore),\n modifiedAfter: parseDate(input.modifiedAfter),\n modifiedBefore: parseDate(input.modifiedBefore)\n };\n}\n","import type { DateFilters, FolderFilters } from './filter-schema';\nimport { matchesFolder } from './filter-schema';\nimport type { Note } from '../models/note';\n\nexport function shouldExportFolder(folderPath: string, filters: FolderFilters): boolean {\n return matchesFolder(folderPath, filters);\n}\n\nfunction matchesDate(value: string, after?: Date, before?: Date): boolean {\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) return false;\n if (after && parsed.getTime() < after.getTime()) return false;\n if (before && parsed.getTime() > before.getTime()) return false;\n return true;\n}\n\nexport function filterNote(\n note: Note,\n filters: { folders?: FolderFilters; dates?: DateFilters }\n): { allowed: boolean; reason?: string } {\n const folderFilters = filters.folders ?? {};\n if (!shouldExportFolder(note.folderPath, folderFilters)) {\n return { allowed: false, reason: `Filtered by folder: ${note.folderPath}` };\n }\n\n const dateFilters = filters.dates ?? {};\n const createdOk = matchesDate(note.createdAtUtc, dateFilters.createdAfter, dateFilters.createdBefore);\n const modifiedOk = matchesDate(note.modifiedAtUtc, dateFilters.modifiedAfter, dateFilters.modifiedBefore);\n\n if (!createdOk || !modifiedOk) {\n return { allowed: false, reason: 'Filtered by date' };\n }\n\n return { allowed: true };\n}\n","import { logProgress } from './notes/jxa-helpers';\n\ntype EventName =\n | 'export.progress'\n | 'export.start'\n | 'export.count'\n | 'export.note'\n | 'export.skip'\n | 'export.written'\n | 'export.index.write';\n\ninterface ProgressReporterOptions {\n /**\n * Minimum milliseconds between emissions of repeated progress events.\n */\n rateMs?: number;\n}\n\nexport class ProgressReporter {\n private lastAt = 0;\n private readonly rateMs: number;\n\n constructor(options: ProgressReporterOptions = {}) {\n this.rateMs = options.rateMs ?? 2000;\n }\n\n emit(event: EventName, data?: unknown, force = false): void {\n const now = Date.now();\n if (!force && now - this.lastAt < this.rateMs && event === 'export.progress') {\n return;\n }\n this.lastAt = now;\n logProgress(event, data);\n }\n}\n\nexport const createProgressReporter = (options?: ProgressReporterOptions): ProgressReporter =>\n new ProgressReporter(options);\n"],"mappings":"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","names":["fs","fs","path","path","fs","path","fs"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hyzhak/apple-notes-export",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CLI to export Apple Notes to deterministic local artifacts",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Ievgenii Krevenets",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/hyzhak/apple-notes-extractor.git"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"private": false,
|
|
13
|
+
"bin": {
|
|
14
|
+
"apple-notes-export": "dist/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"module": "dist/index.js",
|
|
18
|
+
"types": "dist/index.d.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"build:watch": "tsup --watch",
|
|
28
|
+
"lint": "eslint .",
|
|
29
|
+
"lint:fix": "eslint . --fix",
|
|
30
|
+
"format": "prettier --check .",
|
|
31
|
+
"format:fix": "prettier --write .",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest watch",
|
|
34
|
+
"typecheck": "tsc --build"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": "^24.0.0 || >=24"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@jxa/run": "^1.4.0",
|
|
41
|
+
"commander": "^12.1.0",
|
|
42
|
+
"zod": "^3.23.8"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^22.9.3",
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^8.16.0",
|
|
47
|
+
"@typescript-eslint/parser": "^8.16.0",
|
|
48
|
+
"@vitest/coverage-v8": "^2.1.6",
|
|
49
|
+
"eslint": "^9.17.0",
|
|
50
|
+
"eslint-config-prettier": "^9.1.0",
|
|
51
|
+
"prettier": "^3.4.2",
|
|
52
|
+
"tsup": "^8.3.5",
|
|
53
|
+
"typescript-eslint": "^8.16.0",
|
|
54
|
+
"typescript": "^5.6.3",
|
|
55
|
+
"vitest": "^2.1.6"
|
|
56
|
+
}
|
|
57
|
+
}
|