@contentrain/wp-import 0.12.2 → 0.13.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/README.md +1 -0
- package/dist/index.d.mts +32 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +55 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ const commentsExport = buildCommentsExport(raw, entry_source_map)
|
|
|
37
37
|
- **UTC dates** — GMT columns preferred, stamped ISO 8601 `Z`.
|
|
38
38
|
- **Pure conversion** — `rawToContentrain` returns a canonical-serialized file map (sorted keys, 2-space indent, trailing newline); writing to disk is the caller's one line.
|
|
39
39
|
- **EntrySourceMap produced at the only place that can know it** — the WP-id → entry-address mapping the comments intake requires.
|
|
40
|
+
- **No e-mail addresses by default** — a WXR export carries author and commenter e-mails; `rawToContentrain` leaves them out of the store unless called with `{ includeEmails: true }` (the store usually ends up in git), and `buildCommentsExport` sets `email: null` unless given the same option. Comment meta under a personal-data key (IP, user agent, e-mail, avatar URLs, every `akismet_*`; `COMMENT_PII_META_KEY`) never travels, with or without it. The REST path never has them. Names, logins, commenter website URLs and comment text are written.
|
|
40
41
|
- **Only public discussion leaves the site** — `buildCommentsExport` carries comments on published, unprotected entries that are approved or pending (pending lands in the receiver's moderation queue). Comments on drafts, private, scheduled or password-protected entries, any other status (spam, trash, `post-trashed`, a plugin's) and entries the import does not hold, stay behind; `excluded` counts them.
|
|
41
42
|
|
|
42
43
|
Streaming WXR parse (sax): a 100 MB export holds only its records in memory.
|
package/dist/index.d.mts
CHANGED
|
@@ -148,9 +148,19 @@ interface ContentrainResult {
|
|
|
148
148
|
entry_source_map: EntrySourceMap;
|
|
149
149
|
report: ImportReport;
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
interface RawToContentrainOptions {
|
|
152
|
+
/** `updated_by` stamped on every meta entry. Default `@contentrain/wp-import`. */
|
|
152
153
|
updatedBy?: string;
|
|
153
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Write author and commenter e-mail addresses into the `authors` and
|
|
156
|
+
* `comments` entries. Default `false`: a WXR export carries them, but they
|
|
157
|
+
* are third parties' personal data and a store usually ends up in git
|
|
158
|
+
* (sometimes a public repo), so they stay out unless asked for. The REST
|
|
159
|
+
* path never has them.
|
|
160
|
+
*/
|
|
161
|
+
includeEmails?: boolean;
|
|
162
|
+
}
|
|
163
|
+
declare function rawToContentrain(input: RawIR, opts?: RawToContentrainOptions): ContentrainResult;
|
|
154
164
|
//#endregion
|
|
155
165
|
//#region src/comments.d.ts
|
|
156
166
|
/**
|
|
@@ -163,9 +173,26 @@ declare function selectComments(raw: RawIR): {
|
|
|
163
173
|
comments: RawComment[];
|
|
164
174
|
excluded: NonNullable<CommentsExport['excluded']>;
|
|
165
175
|
};
|
|
166
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Comment meta keys that hold a commenter's personal data: IP, browser user
|
|
178
|
+
* agent, e-mail, avatar URLs (a Gravatar URL carries the e-mail's hash). Same
|
|
179
|
+
* list as Migrate's intake (`PII_META_KEY`), plus every `akismet_*` key:
|
|
180
|
+
* `akismet_as_submitted` stores the whole submission (IP, user agent, e-mail,
|
|
181
|
+
* referrer) and the rest is spam-check bookkeeping. Matched by key, so a
|
|
182
|
+
* comment with no e-mail still loses its IP and user agent.
|
|
183
|
+
*/
|
|
184
|
+
declare const COMMENT_PII_META_KEY: RegExp;
|
|
185
|
+
interface CommentsExportOptions {
|
|
167
186
|
generated_at?: string;
|
|
168
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Carry commenter e-mail addresses. Default `false`: the export is written
|
|
189
|
+
* next to the store and is personal data of third parties; a receiving
|
|
190
|
+
* service that needs addresses asks for them. IPs, user agents and avatar
|
|
191
|
+
* URLs are never carried, with or without this option.
|
|
192
|
+
*/
|
|
193
|
+
includeEmails?: boolean;
|
|
194
|
+
}
|
|
195
|
+
declare function buildCommentsExport(raw: RawIR, entries: EntrySourceMap, opts?: CommentsExportOptions): CommentsExport;
|
|
169
196
|
/** Handoff summary for the export: counts + closed threads + what could not be addressed. */
|
|
170
197
|
declare function summarizeComments(exp: CommentsExport): HandoffComments;
|
|
171
198
|
//#endregion
|
|
@@ -266,5 +293,5 @@ declare function planSourceDelta(input: PlanSourceDeltaInput): SourceDeltaPlan;
|
|
|
266
293
|
/** The plan as text, for a dry run: one line per record, then conflicts, unmapped records, redirects and warnings. */
|
|
267
294
|
declare function formatSourceDeltaReport(plan: SourceDeltaPlan): string;
|
|
268
295
|
//#endregion
|
|
269
|
-
export { type ContentrainResult, DEFAULT_IMPORTERS, DEFAULT_TAXONOMIES, type DeltaStore, type ImportReport, LANDING_PAGE_TYPE, PLUGIN_META, type PlanSourceDeltaInput, type RestImportOptions, type RestImportResult, SKIP_TYPES, type UnserializeResult, type WxrParseResult, type WxrStats, addressSlug, buildCommentsExport, canon, fetchRestRawIR, formatSourceDeltaReport, hexId, looksSerialized, pageTypeOf, parseWxr, phpUnserialize, planSourceDelta, rawToContentrain, selectComments, slugify, strip, summarizeComments, taxModelId, tryUnserialize };
|
|
296
|
+
export { COMMENT_PII_META_KEY, type CommentsExportOptions, type ContentrainResult, DEFAULT_IMPORTERS, DEFAULT_TAXONOMIES, type DeltaStore, type ImportReport, LANDING_PAGE_TYPE, PLUGIN_META, type PlanSourceDeltaInput, type RawToContentrainOptions, type RestImportOptions, type RestImportResult, SKIP_TYPES, type UnserializeResult, type WxrParseResult, type WxrStats, addressSlug, buildCommentsExport, canon, fetchRestRawIR, formatSourceDeltaReport, hexId, looksSerialized, pageTypeOf, parseWxr, phpUnserialize, planSourceDelta, rawToContentrain, selectComments, slugify, strip, summarizeComments, taxModelId, tryUnserialize };
|
|
270
297
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/wxr.ts","../src/rest.ts","../src/contentrain.ts","../src/comments.ts","../src/php-unserialize.ts","../src/core.ts","../src/delta.ts"],"mappings":";;;;UA2BiB,QAAA;EACf,UAAA;IAAc,QAAA;IAAkB,OAAA;IAAiB,MAAA;IAAgB,IAAA;EAAA;EACjE,UAAA;EACA,gBAAA;EACA,MAAA,EAAQ,MAAA;AAAA;AAAA,UAGO,cAAA;EACf,GAAA,EAAK,KAAA;EACL,KAAA,EAAO,QAAA;AAAA;AAAA,iBAiBa,QAAA,CAAS,KAAA,WAAgB,QAAA,EAAU,IAAA;EAAS,IAAA;AAAA,IAAkB,OAAA,CAAQ,cAAA;;;UCnC3E,iBAAA;EACf,MAAA;;EAEA,SAAA,UAAmB,KAAA;EDMI;;;;;;;;ECGvB,IAAA;IAAS,IAAA;IAAc,WAAA;EAAA;EACvB,OAAA;EDAc;AAGhB;;;;ECGE,WAAA;EDFK;;;;ECOL,QAAA;EACA,IAAA;AAAA;AAAA,UAGe,gBAAA;EACf,GAAA,EAAK,KAAA;EACL,QAAA;EDKkF;;;;;;;;ECIlF,UAAA;IAAc,MAAA;IAA0C,SAAA;EAAA;;;AAvC1D;;;;;;;;;;EAoDE,IAAA;AAAA;AAAA,iBA4EoB,cAAA,CAAe,OAAA,EAAS,iBAAA,GAAoB,OAAA,CAAQ,gBAAA;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/wxr.ts","../src/rest.ts","../src/contentrain.ts","../src/comments.ts","../src/php-unserialize.ts","../src/core.ts","../src/delta.ts"],"mappings":";;;;UA2BiB,QAAA;EACf,UAAA;IAAc,QAAA;IAAkB,OAAA;IAAiB,MAAA;IAAgB,IAAA;EAAA;EACjE,UAAA;EACA,gBAAA;EACA,MAAA,EAAQ,MAAA;AAAA;AAAA,UAGO,cAAA;EACf,GAAA,EAAK,KAAA;EACL,KAAA,EAAO,QAAA;AAAA;AAAA,iBAiBa,QAAA,CAAS,KAAA,WAAgB,QAAA,EAAU,IAAA;EAAS,IAAA;AAAA,IAAkB,OAAA,CAAQ,cAAA;;;UCnC3E,iBAAA;EACf,MAAA;;EAEA,SAAA,UAAmB,KAAA;EDMI;;;;;;;;ECGvB,IAAA;IAAS,IAAA;IAAc,WAAA;EAAA;EACvB,OAAA;EDAc;AAGhB;;;;ECGE,WAAA;EDFK;;;;ECOL,QAAA;EACA,IAAA;AAAA;AAAA,UAGe,gBAAA;EACf,GAAA,EAAK,KAAA;EACL,QAAA;EDKkF;;;;;;;;ECIlF,UAAA;IAAc,MAAA;IAA0C,SAAA;EAAA;;;AAvC1D;;;;;;;;;;EAoDE,IAAA;AAAA;AAAA,iBA4EoB,cAAA,CAAe,OAAA,EAAS,iBAAA,GAAoB,OAAA,CAAQ,gBAAA;;;;UClHzD,QAAA;EACf,KAAA;EACA,IAAA;EFPuB;EESvB,IAAA;EFLc;EEOd,IAAA;EFVc;;;;EEed,MAAA;AAAA;AAAA,UAkBe,YAAA;EACf,cAAA;EACA,aAAA;EFhCc;EEkCd,UAAA,EAAY,QAAA;EACZ,cAAA;EACA,WAAA,EAAa,MAAA;EACb,UAAA,EAAY,MAAA;EFjCZ;EEmCA,WAAA,EAAa,MAAA;EFlCb;EEoCA,mBAAA,EAAqB,MAAA;EFpCN;EEsCf,mBAAA,EAAqB,MAAA;EFrBD;EEuBpB,oBAAA;;EAEA,iBAAA,EAAmB,MAAA;EFzBuE;EE2B1F,sBAAA,EAAwB,MAAA;EACxB,aAAA;EACA,iBAAA;EACA,MAAA,EAAQ,MAAA;IAAiB,IAAA;IAAc,MAAA;IAAgB,MAAA;IAAgB,OAAA;EAAA;EF9BiC;EEgCxG,OAAA;;EAEA,kBAAA;;ADrEF;;;;;EC4EE,yBAAA;EDzEmB;;;;EC8EnB,kBAAA;AAAA;AAAA,UAGe,iBAAA;EACf,KAAA,EAAO,MAAA;EACP,gBAAA,EAAkB,cAAA;EAClB,MAAA,EAAQ,YAAA;AAAA;AAAA,UA0CO,uBAAA;;EAEf,SAAA;EDtGA;;;;;;;EC8GA,aAAA;AAAA;AAAA,iBAqBc,gBAAA,CAAiB,KAAA,EAAO,KAAA,EAAO,IAAA,GAAO,uBAAA,GAA0B,iBAAA;;;;;;AFvJhF;;;iBGbgB,cAAA,CAAe,GAAA,EAAK,KAAA;EAAU,QAAA,EAAU,UAAA;EAAc,QAAA,EAAU,WAAA,CAAY,cAAA;AAAA;;;;;;;;;cAwB/E,oBAAA,EAAoB,MAAA;AAAA,UAuBhB,qBAAA;EACf,YAAA;EH1Be;;;;;;EGiCf,aAAA;AAAA;AAAA,iBAGc,mBAAA,CAAoB,GAAA,EAAK,KAAA,EAAO,OAAA,EAAS,cAAA,EAAgB,IAAA,GAAO,qBAAA,GAAwB,cAAA;;iBAoBxF,iBAAA,CAAkB,GAAA,EAAK,cAAA,GAAiB,eAAA;;;UCxFvC,iBAAA;EACf,EAAA;EACA,UAAA;EACA,KAAA;EACA,KAAA;AAAA;AAAA,iBAGc,eAAA,CAAgB,CAAA,YAAa,CAAA;AAAA,iBAI7B,cAAA,CAAe,GAAA;;iBAyFf,cAAA,CAAe,CAAA,WAAY,iBAAA;;;;;;;;cCrE9B,KAAA,GAAS,CAAA;;cAGT,KAAA,GAAS,CAAA;;cAcT,KAAA,GAAS,CAAA;;;;;;;;ALCtB;;cK8Ba,UAAA,EAAU,MAAA;;;;;;;cASV,iBAAA;AAAA,cACA,UAAA,GAAc,IAAA;;cAGd,WAAA,EAAW,MAAA;;cAOX,UAAA,GAAc,CAAA;AAAA,cAGd,OAAA,GAAW,CAAA;;AJxFxB;;;;;cIsGa,WAAA,GAAe,GAAA;;;;UCrGX,UAAA;EACf,KAAA,EAAO,MAAA;EACP,gBAAA,EAAkB,cAAA;AAAA;AAAA,UAGH,oBAAA;ENOD;EMLd,KAAA,EAAO,eAAA;ENEO;EMAd,KAAA,EAAO,UAAA;ENA0C;EMEjD,QAAA,GAAW,UAAA;ENDX;;;;;EMOA,UAAA;ENFe;;;;EMOf,SAAA;AAAA;AAAA,cAGW,kBAAA;AAAA,cACA,iBAAA;;;ANQb;;;;;;;;;;iBM8NgB,eAAA,CAAgB,KAAA,EAAO,oBAAA,GAAuB,eAAA;;iBAkC9C,uBAAA,CAAwB,IAAA,EAAM,eAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -2198,8 +2198,25 @@ function relationOver(targets) {
|
|
|
2198
2198
|
}
|
|
2199
2199
|
/** Post, term, user or attachment ids of an ACF reference value: ids, numeric strings or `{ ID }` objects. */
|
|
2200
2200
|
const idsOf = (v) => (Array.isArray(v) ? v : [v]).map((x) => typeof x === "number" ? x : typeof x === "string" && /^\d+$/.test(x) ? Number(x) : x && typeof x === "object" ? Number(x.ID ?? x.id ?? x.term_id) : NaN).filter((n) => Number.isSafeInteger(n) && n > 0);
|
|
2201
|
-
|
|
2201
|
+
/**
|
|
2202
|
+
* The page-type mapping the WordPress readers apply (`pageTypeOf`: Elementor's `e-landing-page` → `page`), applied again
|
|
2203
|
+
* here so a RawIR from any producer — a Bridge export, a hand-built fixture — gets it too, not only one read by
|
|
2204
|
+
* `parseWxr` / `fetchRestRawIR`. Idempotent: an already-mapped RawIR comes back as the same object.
|
|
2205
|
+
*/
|
|
2206
|
+
function withPageTypes(raw) {
|
|
2207
|
+
const postNeeds = raw.posts.some((p) => pageTypeOf(p.type) !== p.type);
|
|
2208
|
+
const commentNeeds = (raw.comments ?? []).some((c) => c.post_type !== void 0 && pageTypeOf(c.post_type) !== c.post_type);
|
|
2209
|
+
if (!postNeeds && !commentNeeds) return raw;
|
|
2210
|
+
return {
|
|
2211
|
+
...raw,
|
|
2212
|
+
posts: postNeeds ? raw.posts.map((p) => pageTypeOf(p.type) === p.type ? p : Object.assign({}, p, { type: pageTypeOf(p.type) })) : raw.posts,
|
|
2213
|
+
...raw.comments && commentNeeds ? { comments: raw.comments.map((c) => c.post_type === void 0 || pageTypeOf(c.post_type) === c.post_type ? c : Object.assign({}, c, { post_type: pageTypeOf(c.post_type) })) } : {}
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
function rawToContentrain(input, opts) {
|
|
2217
|
+
const raw = withPageTypes(input);
|
|
2202
2218
|
const updatedBy = opts?.updatedBy ?? "@contentrain/wp-import";
|
|
2219
|
+
const includeEmails = opts?.includeEmails === true;
|
|
2203
2220
|
const locale = normLocale(raw.site.language) || "en";
|
|
2204
2221
|
const models = {};
|
|
2205
2222
|
const contents = {};
|
|
@@ -2502,7 +2519,7 @@ function rawToContentrain(raw, opts) {
|
|
|
2502
2519
|
contents.authors[id] = pick({
|
|
2503
2520
|
name: strip(a.display_name),
|
|
2504
2521
|
slug: slugify(a.login),
|
|
2505
|
-
email: a.email,
|
|
2522
|
+
email: includeEmails ? a.email : null,
|
|
2506
2523
|
wp_id: a.id
|
|
2507
2524
|
}, new Set([
|
|
2508
2525
|
"name",
|
|
@@ -3118,7 +3135,7 @@ function rawToContentrain(raw, opts) {
|
|
|
3118
3135
|
}
|
|
3119
3136
|
contents.comments[cRef(c.id)] = pick({
|
|
3120
3137
|
author: strip(c.author) || "anonymous",
|
|
3121
|
-
email: c.email,
|
|
3138
|
+
email: includeEmails ? c.email : null,
|
|
3122
3139
|
url: c.url,
|
|
3123
3140
|
body: c.content,
|
|
3124
3141
|
published_at: c.date,
|
|
@@ -3321,9 +3338,42 @@ function selectComments(raw) {
|
|
|
3321
3338
|
excluded
|
|
3322
3339
|
};
|
|
3323
3340
|
}
|
|
3341
|
+
/**
|
|
3342
|
+
* Comment meta keys that hold a commenter's personal data: IP, browser user
|
|
3343
|
+
* agent, e-mail, avatar URLs (a Gravatar URL carries the e-mail's hash). Same
|
|
3344
|
+
* list as Migrate's intake (`PII_META_KEY`), plus every `akismet_*` key:
|
|
3345
|
+
* `akismet_as_submitted` stores the whole submission (IP, user agent, e-mail,
|
|
3346
|
+
* referrer) and the rest is spam-check bookkeeping. Matched by key, so a
|
|
3347
|
+
* comment with no e-mail still loses its IP and user agent.
|
|
3348
|
+
*/
|
|
3349
|
+
const COMMENT_PII_META_KEY = /(?:^|[_-])(?:ip|ip_address|author_ip|user_ip|agent|user_agent|author_agent|email|author_email|mail|avatar|avatar_url|avatar_urls|author_avatar_urls)$|^_?akismet_/i;
|
|
3350
|
+
/** Anything shaped like an e-mail address, anywhere in a serialized value. */
|
|
3351
|
+
const EMAIL_RE = /[^\s@"'<>;:]+@[^\s@"'<>;:]+\.[a-z]{2,}/i;
|
|
3352
|
+
const serialized = (v) => typeof v === "string" ? v : JSON.stringify(v) ?? "";
|
|
3353
|
+
/**
|
|
3354
|
+
* A comment as it may leave the site. Meta under a personal-data key is always
|
|
3355
|
+
* dropped: IPs and user agents are never exported. Without `includeEmails`,
|
|
3356
|
+
* `email` becomes null and any remaining meta whose value holds an address is
|
|
3357
|
+
* dropped too (an unknown plugin's own key).
|
|
3358
|
+
*/
|
|
3359
|
+
function exportableComment(c, includeEmails) {
|
|
3360
|
+
const out = includeEmails ? { ...c } : {
|
|
3361
|
+
...c,
|
|
3362
|
+
email: null
|
|
3363
|
+
};
|
|
3364
|
+
if (c.meta) {
|
|
3365
|
+
const kept = Object.entries(c.meta).filter(([k, v]) => !COMMENT_PII_META_KEY.test(k) && (includeEmails || !EMAIL_RE.test(serialized(v))));
|
|
3366
|
+
if (kept.length) out.meta = Object.fromEntries(kept);
|
|
3367
|
+
else delete out.meta;
|
|
3368
|
+
}
|
|
3369
|
+
return out;
|
|
3370
|
+
}
|
|
3324
3371
|
function buildCommentsExport(raw, entries, opts) {
|
|
3325
3372
|
const threadsClosed = raw.posts.filter((p) => p.comment_status && p.comment_status !== "open").map((p) => p.id);
|
|
3326
|
-
const
|
|
3373
|
+
const selected = selectComments(raw);
|
|
3374
|
+
const excluded = selected.excluded;
|
|
3375
|
+
const includeEmails = opts?.includeEmails === true;
|
|
3376
|
+
const comments = selected.comments.map((c) => exportableComment(c, includeEmails));
|
|
3327
3377
|
return {
|
|
3328
3378
|
version: MIGRATION_CONTRACT_VERSION,
|
|
3329
3379
|
format: COMMENTS_EXPORT_FORMAT,
|
|
@@ -3611,6 +3661,6 @@ function formatSourceDeltaReport(plan) {
|
|
|
3611
3661
|
return `${lines.join("\n")}\n`;
|
|
3612
3662
|
}
|
|
3613
3663
|
//#endregion
|
|
3614
|
-
export { DEFAULT_IMPORTERS, DEFAULT_TAXONOMIES, LANDING_PAGE_TYPE, PLUGIN_META, SKIP_TYPES, addressSlug, buildCommentsExport, canon, fetchRestRawIR, formatSourceDeltaReport, hexId, looksSerialized, pageTypeOf, parseWxr, phpUnserialize, planSourceDelta, rawToContentrain, selectComments, slugify, strip, summarizeComments, taxModelId, tryUnserialize };
|
|
3664
|
+
export { COMMENT_PII_META_KEY, DEFAULT_IMPORTERS, DEFAULT_TAXONOMIES, LANDING_PAGE_TYPE, PLUGIN_META, SKIP_TYPES, addressSlug, buildCommentsExport, canon, fetchRestRawIR, formatSourceDeltaReport, hexId, looksSerialized, pageTypeOf, parseWxr, phpUnserialize, planSourceDelta, rawToContentrain, selectComments, slugify, strip, summarizeComments, taxModelId, tryUnserialize };
|
|
3615
3665
|
|
|
3616
3666
|
//# sourceMappingURL=index.mjs.map
|