@contentrain/wp-import 0.12.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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
- declare function rawToContentrain(raw: RawIR, opts?: {
151
+ interface RawToContentrainOptions {
152
+ /** `updated_by` stamped on every meta entry. Default `@contentrain/wp-import`. */
152
153
  updatedBy?: string;
153
- }): ContentrainResult;
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(raw: 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
- declare function buildCommentsExport(raw: RawIR, entries: EntrySourceMap, opts?: {
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
- }): CommentsExport;
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
@@ -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":"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"}
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":"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"}
package/dist/index.mjs CHANGED
@@ -2200,6 +2200,7 @@ function relationOver(targets) {
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
  function rawToContentrain(raw, opts) {
2202
2202
  const updatedBy = opts?.updatedBy ?? "@contentrain/wp-import";
2203
+ const includeEmails = opts?.includeEmails === true;
2203
2204
  const locale = normLocale(raw.site.language) || "en";
2204
2205
  const models = {};
2205
2206
  const contents = {};
@@ -2502,7 +2503,7 @@ function rawToContentrain(raw, opts) {
2502
2503
  contents.authors[id] = pick({
2503
2504
  name: strip(a.display_name),
2504
2505
  slug: slugify(a.login),
2505
- email: a.email,
2506
+ email: includeEmails ? a.email : null,
2506
2507
  wp_id: a.id
2507
2508
  }, new Set([
2508
2509
  "name",
@@ -3118,7 +3119,7 @@ function rawToContentrain(raw, opts) {
3118
3119
  }
3119
3120
  contents.comments[cRef(c.id)] = pick({
3120
3121
  author: strip(c.author) || "anonymous",
3121
- email: c.email,
3122
+ email: includeEmails ? c.email : null,
3122
3123
  url: c.url,
3123
3124
  body: c.content,
3124
3125
  published_at: c.date,
@@ -3321,9 +3322,42 @@ function selectComments(raw) {
3321
3322
  excluded
3322
3323
  };
3323
3324
  }
3325
+ /**
3326
+ * Comment meta keys that hold a commenter's personal data: IP, browser user
3327
+ * agent, e-mail, avatar URLs (a Gravatar URL carries the e-mail's hash). Same
3328
+ * list as Migrate's intake (`PII_META_KEY`), plus every `akismet_*` key:
3329
+ * `akismet_as_submitted` stores the whole submission (IP, user agent, e-mail,
3330
+ * referrer) and the rest is spam-check bookkeeping. Matched by key, so a
3331
+ * comment with no e-mail still loses its IP and user agent.
3332
+ */
3333
+ 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;
3334
+ /** Anything shaped like an e-mail address, anywhere in a serialized value. */
3335
+ const EMAIL_RE = /[^\s@"'<>;:]+@[^\s@"'<>;:]+\.[a-z]{2,}/i;
3336
+ const serialized = (v) => typeof v === "string" ? v : JSON.stringify(v) ?? "";
3337
+ /**
3338
+ * A comment as it may leave the site. Meta under a personal-data key is always
3339
+ * dropped: IPs and user agents are never exported. Without `includeEmails`,
3340
+ * `email` becomes null and any remaining meta whose value holds an address is
3341
+ * dropped too (an unknown plugin's own key).
3342
+ */
3343
+ function exportableComment(c, includeEmails) {
3344
+ const out = includeEmails ? { ...c } : {
3345
+ ...c,
3346
+ email: null
3347
+ };
3348
+ if (c.meta) {
3349
+ const kept = Object.entries(c.meta).filter(([k, v]) => !COMMENT_PII_META_KEY.test(k) && (includeEmails || !EMAIL_RE.test(serialized(v))));
3350
+ if (kept.length) out.meta = Object.fromEntries(kept);
3351
+ else delete out.meta;
3352
+ }
3353
+ return out;
3354
+ }
3324
3355
  function buildCommentsExport(raw, entries, opts) {
3325
3356
  const threadsClosed = raw.posts.filter((p) => p.comment_status && p.comment_status !== "open").map((p) => p.id);
3326
- const { comments, excluded } = selectComments(raw);
3357
+ const selected = selectComments(raw);
3358
+ const excluded = selected.excluded;
3359
+ const includeEmails = opts?.includeEmails === true;
3360
+ const comments = selected.comments.map((c) => exportableComment(c, includeEmails));
3327
3361
  return {
3328
3362
  version: MIGRATION_CONTRACT_VERSION,
3329
3363
  format: COMMENTS_EXPORT_FORMAT,
@@ -3611,6 +3645,6 @@ function formatSourceDeltaReport(plan) {
3611
3645
  return `${lines.join("\n")}\n`;
3612
3646
  }
3613
3647
  //#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 };
3648
+ 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
3649
 
3616
3650
  //# sourceMappingURL=index.mjs.map