@wirecat/cli-core 0.471.0 → 0.472.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/CHANGELOG.md +12 -0
- package/dist/errors/reasons.js +1 -0
- package/dist/mail/bulk.d.ts +7 -0
- package/dist/mail/bulk.js +3 -0
- package/dist/mail/facts.d.ts +9 -0
- package/dist/mail/facts.js +22 -0
- package/dist/mail/folders.d.ts +10 -0
- package/dist/mail/folders.js +13 -0
- package/dist/mail/gone.d.ts +20 -0
- package/dist/mail/gone.js +53 -0
- package/dist/mail/headers.d.ts +43 -0
- package/dist/mail/headers.js +88 -0
- package/dist/mail/index.d.ts +17 -0
- package/dist/mail/index.js +16 -0
- package/dist/mail/parse.d.ts +24 -0
- package/dist/mail/parse.js +79 -0
- package/dist/mail/presets.d.ts +30 -0
- package/dist/mail/presets.js +95 -0
- package/dist/mail/probe.d.ts +49 -0
- package/dist/mail/probe.js +81 -0
- package/dist/mail/resume.d.ts +61 -0
- package/dist/mail/resume.js +61 -0
- package/dist/mail/rule-input.d.ts +14 -0
- package/dist/mail/rule-input.js +32 -0
- package/dist/mail/rule-tags.d.ts +40 -0
- package/dist/mail/rule-tags.js +87 -0
- package/dist/mail/rules.d.ts +83 -0
- package/dist/mail/rules.js +108 -0
- package/dist/mail/score.d.ts +67 -0
- package/dist/mail/score.js +174 -0
- package/dist/mail/sender-scores.d.ts +30 -0
- package/dist/mail/sender-scores.js +96 -0
- package/dist/mail/source.d.ts +67 -0
- package/dist/mail/source.js +1 -0
- package/dist/mail/stored.d.ts +7 -0
- package/dist/mail/stored.js +36 -0
- package/dist/mail/sync.d.ts +91 -0
- package/dist/mail/sync.js +572 -0
- package/dist/mail/testing/contract.d.ts +20 -0
- package/dist/mail/testing/contract.js +106 -0
- package/dist/mail/testing/fake.d.ts +61 -0
- package/dist/mail/testing/fake.js +136 -0
- package/dist/mail/testing/index.d.ts +2 -0
- package/dist/mail/testing/index.js +2 -0
- package/package.json +12 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { GMAIL_ALL_MAIL } from "../folders.js";
|
|
3
|
+
import { parseMail } from "../parse.js";
|
|
4
|
+
import { syncMail } from "../sync.js";
|
|
5
|
+
const SINCE = new Date("2026-09-01T00:00:00Z");
|
|
6
|
+
/** The fixtures every adapter is seeded with: its harness makes a mailbox that holds exactly these. */
|
|
7
|
+
export const CONTRACT_MAIL = [1, 2, 3].map((n) => ({
|
|
8
|
+
uid: String(100 + n),
|
|
9
|
+
messageId: String(9000 + n),
|
|
10
|
+
threadId: String(n === 3 ? 9001 : 9000 + n),
|
|
11
|
+
receivedAt: `2026-09-${String(10 + n).padStart(2, "0")}T08:00:00Z`,
|
|
12
|
+
subject: `Contract subject ${n}`,
|
|
13
|
+
from: ["Rin Example", "rin@example.test"],
|
|
14
|
+
to: [[null, "owner@example.test"]],
|
|
15
|
+
text: `Contract body ${n}`,
|
|
16
|
+
}));
|
|
17
|
+
/** What every adapter must answer; the optional members are checked only where the source has them. */
|
|
18
|
+
export const mailSourceContract = (make) => {
|
|
19
|
+
const test = (name, check) => ({
|
|
20
|
+
name,
|
|
21
|
+
run: async () => check(await make()),
|
|
22
|
+
});
|
|
23
|
+
const folder = GMAIL_ALL_MAIL;
|
|
24
|
+
return [
|
|
25
|
+
test("lists each message once with a stable id, a thread id and a time inside the window", async (source) => {
|
|
26
|
+
const listed = await source.list(SINCE, folder);
|
|
27
|
+
assert.equal(listed.length, CONTRACT_MAIL.length);
|
|
28
|
+
assert.equal(new Set(listed.map(({ messageId }) => messageId)).size, listed.length);
|
|
29
|
+
for (const item of listed) {
|
|
30
|
+
assert.ok(item.uid && item.messageId && item.threadId, "ids");
|
|
31
|
+
assert.ok(Date.parse(item.receivedAt) >= SINCE.getTime(), "inside the window");
|
|
32
|
+
}
|
|
33
|
+
}),
|
|
34
|
+
test("lists a stable id for the same message on every listing", async (source) => {
|
|
35
|
+
const ids = async () => (await source.list(SINCE, folder)).map(({ messageId }) => messageId).sort();
|
|
36
|
+
assert.deepEqual(await ids(), await ids());
|
|
37
|
+
}),
|
|
38
|
+
test("reads the sender and recipients with `headers`", async (source) => {
|
|
39
|
+
const listed = await source.list(SINCE, folder, { headers: true });
|
|
40
|
+
for (const item of listed) {
|
|
41
|
+
assert.equal(item.headers?.from, "rin@example.test");
|
|
42
|
+
assert.deepEqual(item.headers?.to, ["owner@example.test"]);
|
|
43
|
+
}
|
|
44
|
+
}),
|
|
45
|
+
test("lists only a search's UIDs, and names the UIDVALIDITY", async (source) => {
|
|
46
|
+
if (source.search === undefined)
|
|
47
|
+
return;
|
|
48
|
+
const searched = await source.search(SINCE, folder);
|
|
49
|
+
assert.equal(searched.uids.length, CONTRACT_MAIL.length);
|
|
50
|
+
const [first] = searched.uids;
|
|
51
|
+
const listed = await source.list(SINCE, folder, {
|
|
52
|
+
searched: { ...searched, uids: first === undefined ? [] : [first] },
|
|
53
|
+
});
|
|
54
|
+
assert.deepEqual(listed.map(({ uid }) => uid), first === undefined ? [] : [first]);
|
|
55
|
+
}),
|
|
56
|
+
test("returns each message's bytes in order, and one that cannot be read as an Error, not a failed call", async (source) => {
|
|
57
|
+
const listed = await source.list(SINCE, folder);
|
|
58
|
+
const raws = await source.fetchRaw(listed);
|
|
59
|
+
assert.equal(raws.length, listed.length);
|
|
60
|
+
for (const [index, raw] of raws.entries()) {
|
|
61
|
+
assert.ok(raw instanceof Uint8Array, "bytes");
|
|
62
|
+
const parsed = await parseMail(raw, 10_000);
|
|
63
|
+
assert.match(parsed.subject ?? "", /^Contract subject \d$/);
|
|
64
|
+
assert.equal(parsed.subject, CONTRACT_MAIL.find(({ messageId }) => messageId === listed[index]?.messageId)?.subject);
|
|
65
|
+
}
|
|
66
|
+
const [first] = listed;
|
|
67
|
+
if (first !== undefined) {
|
|
68
|
+
const [unknown] = await source.fetchRaw([{ ...first, uid: "999999" }]);
|
|
69
|
+
assert.ok(unknown instanceof Error);
|
|
70
|
+
}
|
|
71
|
+
}),
|
|
72
|
+
test("finds messages by id, leaving out one that is gone", async (source) => {
|
|
73
|
+
if (source.find === undefined)
|
|
74
|
+
return;
|
|
75
|
+
const found = await source.find(["9001", "no-such-id"], folder);
|
|
76
|
+
assert.deepEqual(found.map(({ messageId }) => messageId), ["9001"]);
|
|
77
|
+
}),
|
|
78
|
+
test("says which UIDs a folder still holds", async (source) => {
|
|
79
|
+
if (source.present === undefined)
|
|
80
|
+
return;
|
|
81
|
+
assert.deepEqual(await source.present(["101", "999999"], folder), ["101"]);
|
|
82
|
+
}),
|
|
83
|
+
];
|
|
84
|
+
};
|
|
85
|
+
/** What `syncMail` must do with any adapter: store each message once under the address's account, and only once. */
|
|
86
|
+
export const mailSyncContract = (make) => {
|
|
87
|
+
const address = "owner@example.test";
|
|
88
|
+
const account = { address };
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
name: "stores each message as an email of its thread under the address's account, and a rerun adds nothing",
|
|
92
|
+
run: async () => {
|
|
93
|
+
const { source, store } = await make();
|
|
94
|
+
const first = await syncMail(source, store, { account, since: SINCE });
|
|
95
|
+
assert.equal(first.saved, CONTRACT_MAIL.length);
|
|
96
|
+
assert.equal(first.complete, true);
|
|
97
|
+
const stored = await store.accounts.storedAccount({ provider: "email", account: address });
|
|
98
|
+
const emails = await store.mail.emails({ accountId: stored.id });
|
|
99
|
+
assert.deepEqual(emails.map(({ externalId }) => externalId).sort(), CONTRACT_MAIL.map(({ messageId }) => messageId));
|
|
100
|
+
const again = await syncMail(source, store, { account, since: SINCE });
|
|
101
|
+
assert.equal(again.saved, 0);
|
|
102
|
+
assert.equal(again.deleted, 0);
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { FileStamp } from "../resume.js";
|
|
2
|
+
import type { MailSource } from "../source.js";
|
|
3
|
+
export type FakePerson = [name: string | null, address: string];
|
|
4
|
+
/** One message of a fake Gmail-like mailbox: its ids, envelope and the parts `rawOf` writes as RFC 5322. */
|
|
5
|
+
export interface FakeMail {
|
|
6
|
+
uid: string;
|
|
7
|
+
messageId: string;
|
|
8
|
+
threadId: string;
|
|
9
|
+
receivedAt: string;
|
|
10
|
+
subject: string;
|
|
11
|
+
from: FakePerson;
|
|
12
|
+
to: FakePerson[];
|
|
13
|
+
cc?: FakePerson[];
|
|
14
|
+
text?: string;
|
|
15
|
+
html?: string;
|
|
16
|
+
listId?: string;
|
|
17
|
+
attachment?: {
|
|
18
|
+
name: string;
|
|
19
|
+
mime: string;
|
|
20
|
+
content: string;
|
|
21
|
+
};
|
|
22
|
+
extraHeaders?: string[];
|
|
23
|
+
/** Plain label names, as a listing gives them; `flagged` adds Starred. */
|
|
24
|
+
labels?: string[];
|
|
25
|
+
seen?: boolean;
|
|
26
|
+
flagged?: boolean;
|
|
27
|
+
/** `trash` puts it in the trash folder; otherwise it is in All Mail, which INBOX also shows. */
|
|
28
|
+
folder?: "trash";
|
|
29
|
+
}
|
|
30
|
+
/** The mailbox a fake source reads live, so a test changes it between syncs; the switches make the source fail. */
|
|
31
|
+
export interface FakeMailbox {
|
|
32
|
+
messages: FakeMail[];
|
|
33
|
+
/** All Mail under the account's language, as Gmail names it. */
|
|
34
|
+
allMail?: string;
|
|
35
|
+
trash?: string;
|
|
36
|
+
validity?: number;
|
|
37
|
+
/** Each answer of `search` and `list` ends in this failure. */
|
|
38
|
+
searchFails?: string;
|
|
39
|
+
/** A refused header fetch fails the whole listing. */
|
|
40
|
+
headersFail?: boolean;
|
|
41
|
+
moveFails?: boolean;
|
|
42
|
+
/** UIDs whose raw bytes cannot be read: `fetchRaw` answers an Error for each of them, and only for them. */
|
|
43
|
+
unreadable?: string[];
|
|
44
|
+
/** What the source was asked: the UIDs of each listing, and `msgid:<id>` for each `find`. */
|
|
45
|
+
asked?: string[];
|
|
46
|
+
}
|
|
47
|
+
/** The RFC 5322 bytes of a fake message, CRLF line ends, the body and attachment in base64. */
|
|
48
|
+
export declare const rawOf: (m: FakeMail) => Uint8Array;
|
|
49
|
+
/**
|
|
50
|
+
* An in-memory mail source with Gmail's shape: ids and threads that outlive a folder, labels and flags, UIDVALIDITY,
|
|
51
|
+
* a trash a move reaches, and All Mail under the account's language. The sync tests and the adapter contract run on it.
|
|
52
|
+
*/
|
|
53
|
+
export declare const fakeMailSource: (mailbox: FakeMailbox) => MailSource;
|
|
54
|
+
/**
|
|
55
|
+
* A snapshot source, as an mbox file is: every message since a date with no UIDs, one stamp that changes when the
|
|
56
|
+
* file does, and no search, find or folders. `archive.mails` and `archive.stamp` are read live.
|
|
57
|
+
*/
|
|
58
|
+
export declare const fakeArchiveSource: (archive: {
|
|
59
|
+
mails: FakeMail[];
|
|
60
|
+
stamp: FileStamp;
|
|
61
|
+
}) => MailSource;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { GMAIL_ALL_MAIL } from "../folders.js";
|
|
3
|
+
import { headersOf } from "../headers.js";
|
|
4
|
+
const TRASH = "[Gmail]/Bin";
|
|
5
|
+
const person = ([name, address]) => (name ? `"${name}" <${address}>` : address);
|
|
6
|
+
/** The RFC 5322 bytes of a fake message, CRLF line ends, the body and attachment in base64. */
|
|
7
|
+
export const rawOf = (m) => {
|
|
8
|
+
const type = m.html ? "text/html" : "text/plain";
|
|
9
|
+
const headers = [
|
|
10
|
+
`From: ${person(m.from)}`,
|
|
11
|
+
`To: ${m.to.map(person).join(", ")}`,
|
|
12
|
+
...(m.cc ? [`Cc: ${m.cc.map(person).join(", ")}`] : []),
|
|
13
|
+
...(m.listId ? [`List-Id: News <${m.listId}>`, `List-Unsubscribe: <mailto:leave@${m.listId}>`] : []),
|
|
14
|
+
`Subject: ${m.subject}`,
|
|
15
|
+
`Message-ID: <${m.messageId}@example.test>`,
|
|
16
|
+
`Date: ${new Date(m.receivedAt).toUTCString()}`,
|
|
17
|
+
"MIME-Version: 1.0",
|
|
18
|
+
...(m.extraHeaders ?? []),
|
|
19
|
+
];
|
|
20
|
+
const body = Buffer.from(m.html ?? m.text ?? "").toString("base64");
|
|
21
|
+
if (!m.attachment)
|
|
22
|
+
return Buffer.from(`${[...headers, `Content-Type: ${type}; charset=utf-8`, "Content-Transfer-Encoding: base64"].join("\r\n")}\r\n\r\n${body}\r\n`);
|
|
23
|
+
const parts = [
|
|
24
|
+
`--b1\r\nContent-Type: ${type}; charset=utf-8\r\nContent-Transfer-Encoding: base64\r\n\r\n${body}`,
|
|
25
|
+
`--b1\r\nContent-Type: ${m.attachment.mime}\r\nContent-Disposition: attachment; filename="${m.attachment.name}"\r\n` +
|
|
26
|
+
`Content-Transfer-Encoding: base64\r\n\r\n${Buffer.from(m.attachment.content).toString("base64")}`,
|
|
27
|
+
];
|
|
28
|
+
return Buffer.from(`${[...headers, 'Content-Type: multipart/mixed; boundary="b1"'].join("\r\n")}\r\n\r\n${parts.join("\r\n")}\r\n--b1--\r\n`);
|
|
29
|
+
};
|
|
30
|
+
const failure = (message) => new CliError("provider_error", message, { reason: "mail.source-failed" });
|
|
31
|
+
const labelsOf = (m) => [...new Set([...(m.labels ?? []), ...(m.flagged ? ["Starred"] : [])])].sort();
|
|
32
|
+
const listingOf = (m, headers, folder) => ({
|
|
33
|
+
uid: m.uid,
|
|
34
|
+
messageId: m.messageId,
|
|
35
|
+
threadId: m.threadId,
|
|
36
|
+
receivedAt: new Date(m.receivedAt).toISOString(),
|
|
37
|
+
size: rawOf(m).length,
|
|
38
|
+
labels: labelsOf(m),
|
|
39
|
+
seen: m.seen ?? false,
|
|
40
|
+
flagged: m.flagged ?? false,
|
|
41
|
+
...(headers ? { headers: headersOf(Buffer.from(rawOf(m)).toString().split("\r\n\r\n")[0] ?? "") } : {}),
|
|
42
|
+
folder,
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* An in-memory mail source with Gmail's shape: ids and threads that outlive a folder, labels and flags, UIDVALIDITY,
|
|
46
|
+
* a trash a move reaches, and All Mail under the account's language. The sync tests and the adapter contract run on it.
|
|
47
|
+
*/
|
|
48
|
+
export const fakeMailSource = (mailbox) => {
|
|
49
|
+
const all = () => mailbox.allMail ?? GMAIL_ALL_MAIL;
|
|
50
|
+
const trash = () => mailbox.trash ?? TRASH;
|
|
51
|
+
const inFolder = (folder) => {
|
|
52
|
+
if (![all(), trash(), "INBOX"].includes(folder))
|
|
53
|
+
throw failure(`Gmail has no such mailbox: ${folder}`);
|
|
54
|
+
return mailbox.messages.filter((m) => (folder === trash() ? m.folder === "trash" : m.folder !== "trash"));
|
|
55
|
+
};
|
|
56
|
+
const search = async (since, folder) => {
|
|
57
|
+
if (mailbox.searchFails)
|
|
58
|
+
throw failure(mailbox.searchFails);
|
|
59
|
+
const from = Date.UTC(since.getUTCFullYear(), since.getUTCMonth(), since.getUTCDate());
|
|
60
|
+
return {
|
|
61
|
+
validity: String(mailbox.validity ?? 12),
|
|
62
|
+
uids: inFolder(folder)
|
|
63
|
+
.filter((m) => Date.parse(m.receivedAt) >= from)
|
|
64
|
+
.map((m) => m.uid),
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
search,
|
|
69
|
+
list: async (since, folder, options) => {
|
|
70
|
+
const { uids } = options?.searched ?? (await search(since, folder));
|
|
71
|
+
if (uids.length > 0)
|
|
72
|
+
mailbox.asked?.push(uids.join(","));
|
|
73
|
+
if (options?.headers && mailbox.headersFail)
|
|
74
|
+
throw failure("the header fetch was refused");
|
|
75
|
+
const wanted = new Set(uids);
|
|
76
|
+
return inFolder(folder)
|
|
77
|
+
.filter((m) => wanted.has(m.uid))
|
|
78
|
+
.map((m) => listingOf(m, options?.headers ?? false, folder));
|
|
79
|
+
},
|
|
80
|
+
find: async (messageIds, folder, options) => {
|
|
81
|
+
for (const id of messageIds)
|
|
82
|
+
mailbox.asked?.push(`msgid:${id}`);
|
|
83
|
+
const found = inFolder(folder).filter((m) => messageIds.includes(m.messageId));
|
|
84
|
+
if (found.length > 0)
|
|
85
|
+
mailbox.asked?.push(found.map((m) => m.uid).join(","));
|
|
86
|
+
return found.map((m) => listingOf(m, options?.headers ?? false, folder));
|
|
87
|
+
},
|
|
88
|
+
move: async (messageIds, from, to) => {
|
|
89
|
+
if (mailbox.moveFails)
|
|
90
|
+
throw failure("UID MOVE did not complete");
|
|
91
|
+
const here = inFolder(from).filter((m) => messageIds.includes(m.messageId));
|
|
92
|
+
if (here.length === 0)
|
|
93
|
+
return { moved: [], missing: [...messageIds] };
|
|
94
|
+
for (const m of here) {
|
|
95
|
+
if (to === trash()) {
|
|
96
|
+
m.folder = "trash";
|
|
97
|
+
m.labels = (m.labels ?? []).filter((label) => label !== "Inbox");
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
delete m.folder;
|
|
101
|
+
if (to === "INBOX" && !(m.labels ?? []).includes("Inbox"))
|
|
102
|
+
m.labels = ["Inbox", ...(m.labels ?? [])];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const moved = here.map((m) => m.messageId);
|
|
106
|
+
return { moved, missing: messageIds.filter((id) => !moved.includes(id)) };
|
|
107
|
+
},
|
|
108
|
+
folders: async () => ({ all: all(), trash: trash() }),
|
|
109
|
+
present: async (uids, folder) => inFolder(folder).flatMap((m) => (uids.includes(m.uid) ? [m.uid] : [])),
|
|
110
|
+
label: async (messageIds, folder, labels) => {
|
|
111
|
+
for (const m of inFolder(folder))
|
|
112
|
+
if (messageIds.includes(m.messageId))
|
|
113
|
+
m.labels = [...new Set([...(m.labels ?? []), ...labels])];
|
|
114
|
+
},
|
|
115
|
+
fetchRaw: async (items) => items.map((item) => {
|
|
116
|
+
const m = mailbox.messages.find((each) => each.uid === item.uid);
|
|
117
|
+
if (m === undefined || mailbox.unreadable?.includes(item.uid))
|
|
118
|
+
return failure(`email ${item.uid} was not read`);
|
|
119
|
+
return rawOf(m);
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* A snapshot source, as an mbox file is: every message since a date with no UIDs, one stamp that changes when the
|
|
125
|
+
* file does, and no search, find or folders. `archive.mails` and `archive.stamp` are read live.
|
|
126
|
+
*/
|
|
127
|
+
export const fakeArchiveSource = (archive) => ({
|
|
128
|
+
list: async (since, folder) => archive.mails
|
|
129
|
+
.filter((m) => Date.parse(m.receivedAt) >= since.getTime())
|
|
130
|
+
.map((m) => ({ ...listingOf(m, true, folder), uid: "" })),
|
|
131
|
+
stamp: async () => ({ ...archive.stamp }),
|
|
132
|
+
fetchRaw: async (items) => items.map((item) => {
|
|
133
|
+
const m = archive.mails.find((each) => each.messageId === item.messageId);
|
|
134
|
+
return m === undefined ? failure(`email ${item.messageId} was not found`) : rawOf(m);
|
|
135
|
+
}),
|
|
136
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.472.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
|
@@ -125,6 +125,14 @@
|
|
|
125
125
|
"types": "./dist/calendar/testing/index.d.ts",
|
|
126
126
|
"default": "./dist/calendar/testing/index.js"
|
|
127
127
|
},
|
|
128
|
+
"./mail": {
|
|
129
|
+
"types": "./dist/mail/index.d.ts",
|
|
130
|
+
"default": "./dist/mail/index.js"
|
|
131
|
+
},
|
|
132
|
+
"./mail/testing": {
|
|
133
|
+
"types": "./dist/mail/testing/index.d.ts",
|
|
134
|
+
"default": "./dist/mail/testing/index.js"
|
|
135
|
+
},
|
|
128
136
|
"./models": {
|
|
129
137
|
"types": "./dist/models/index.d.ts",
|
|
130
138
|
"default": "./dist/models/index.js"
|
|
@@ -200,6 +208,7 @@
|
|
|
200
208
|
"dictionary-en": "4.0.0",
|
|
201
209
|
"dictionary-es": "4.0.0",
|
|
202
210
|
"dictionary-ru": "3.0.0",
|
|
211
|
+
"disposable-email-domains-js": "1.26.0",
|
|
203
212
|
"echarts": "6.1.0",
|
|
204
213
|
"fflate": "0.8.3",
|
|
205
214
|
"iconv-lite": "0.7.3",
|
|
@@ -212,10 +221,12 @@
|
|
|
212
221
|
"mdast-util-gfm": "3.1.0",
|
|
213
222
|
"micromark-extension-gfm": "3.0.0",
|
|
214
223
|
"ogg-opus-decoder": "1.7.5",
|
|
224
|
+
"postal-mime": "4.1.4",
|
|
215
225
|
"psl": "1.15.0",
|
|
216
226
|
"qrcode-generator": "2.0.4",
|
|
217
227
|
"sherpa-onnx": "2.0.0",
|
|
218
228
|
"string-width": "8.3.0",
|
|
229
|
+
"tldts": "7.4.19",
|
|
219
230
|
"unpdf": "1.9.1",
|
|
220
231
|
"valibot": "1.5.0",
|
|
221
232
|
"word-extractor": "1.0.4",
|