@getguru/import-engine 0.1.1 → 0.1.3

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.
Files changed (2) hide show
  1. package/docx-import-cli.js +243 -11
  2. package/package.json +1 -1
@@ -98755,6 +98755,34 @@ var importType = typeArgIndex !== -1 ? process.argv[typeArgIndex + 1] : "";
98755
98755
  var dom = new import_jsdom.JSDOM("", {
98756
98756
  url: `http://localhost:8000/docx-import/index.html?type=${importType}&embed=`
98757
98757
  });
98758
+ var UNUSED_INPUT_EVENTS = /* @__PURE__ */ new Set([
98759
+ "focus",
98760
+ "focusin",
98761
+ "keydown",
98762
+ "keyup",
98763
+ "mouseover",
98764
+ "mousedown",
98765
+ "mouseup",
98766
+ "click",
98767
+ "mouseout"
98768
+ ]);
98769
+ var addWindowListener = dom.window.addEventListener;
98770
+ dom.window.addEventListener = function(type2, ...rest2) {
98771
+ if (UNUSED_INPUT_EVENTS.has(type2)) {
98772
+ return void 0;
98773
+ }
98774
+ return addWindowListener.call(this, type2, ...rest2);
98775
+ };
98776
+ var windowProperties = require("jsdom/lib/jsdom/living/window-properties.js");
98777
+ for (const hook of ["elementAttached", "elementDetached", "elementAttributeModified"]) {
98778
+ const track = windowProperties[hook];
98779
+ windowProperties[hook] = function(element, ...rest2) {
98780
+ if (!element._ownerDocument._defaultView) {
98781
+ return void 0;
98782
+ }
98783
+ return track.call(this, element, ...rest2);
98784
+ };
98785
+ }
98758
98786
  var ALLOWED_GLOBALS = [
98759
98787
  "window",
98760
98788
  "document",
@@ -102636,6 +102664,52 @@ function markLinkRecorded(doc, link2) {
102636
102664
  function isLinkRecorded(doc, link2) {
102637
102665
  return !!doc.recordedLinks && doc.recordedLinks.has(link2);
102638
102666
  }
102667
+ var RECORDED_LINK_ATTRIBUTE = "data-guru-import-recorded-link";
102668
+ function serializeDoctype(doctype) {
102669
+ if (!doctype) {
102670
+ return "";
102671
+ }
102672
+ const quote = (id) => id.includes('"') ? `'${id}'` : `"${id}"`;
102673
+ if (doctype.publicId) {
102674
+ return `<!DOCTYPE ${doctype.name} PUBLIC ${quote(doctype.publicId)}${doctype.systemId ? " " + quote(doctype.systemId) : ""}>`;
102675
+ }
102676
+ if (doctype.systemId) {
102677
+ return `<!DOCTYPE ${doctype.name} SYSTEM ${quote(doctype.systemId)}>`;
102678
+ }
102679
+ return `<!DOCTYPE ${doctype.name}>`;
102680
+ }
102681
+ function serializeDocTree(div, doc) {
102682
+ for (const element of div.querySelectorAll("pre, textarea, listing")) {
102683
+ const first2 = element.firstChild;
102684
+ if (first2 && first2.nodeType === 3 && first2.data.startsWith("\n")) {
102685
+ first2.data = "\n" + first2.data;
102686
+ }
102687
+ }
102688
+ if (doc.recordedLinks) {
102689
+ for (const element of div.querySelectorAll("*")) {
102690
+ if (doc.recordedLinks.has(element)) {
102691
+ element.setAttribute(RECORDED_LINK_ATTRIBUTE, "");
102692
+ }
102693
+ }
102694
+ }
102695
+ return flattenString(serializeDoctype(div.ownerDocument.doctype) + div.outerHTML);
102696
+ }
102697
+ function flattenString(string) {
102698
+ string.charCodeAt(0);
102699
+ return string;
102700
+ }
102701
+ function parseDocTree(source, doc) {
102702
+ const div = new DOMParser().parseFromString(source, "text/html").documentElement;
102703
+ const hadRecordedLinks = !!doc.recordedLinks;
102704
+ delete doc.recordedLinks;
102705
+ if (hadRecordedLinks) {
102706
+ for (const element of div.querySelectorAll(`[${RECORDED_LINK_ATTRIBUTE}]`)) {
102707
+ element.removeAttribute(RECORDED_LINK_ATTRIBUTE);
102708
+ markLinkRecorded(doc, element);
102709
+ }
102710
+ }
102711
+ return div;
102712
+ }
102639
102713
  function safeDecode(string) {
102640
102714
  try {
102641
102715
  return decodeURIComponent(string);
@@ -111340,11 +111414,17 @@ function buildSlabPostSlugIndex(allDocs, zip) {
111340
111414
  }
111341
111415
  return index;
111342
111416
  }
111417
+ function getSlabPostSlugIndex(zip, allDocs) {
111418
+ if (!zip.slabPostSlugIndex) {
111419
+ zip.slabPostSlugIndex = buildSlabPostSlugIndex(allDocs, zip);
111420
+ }
111421
+ return zip.slabPostSlugIndex;
111422
+ }
111343
111423
  function resolveSlabPostLinks(div, doc, allDocs) {
111344
111424
  if (!doc.zip?.isSlabExport) {
111345
111425
  return;
111346
111426
  }
111347
- const index = buildSlabPostSlugIndex(allDocs, doc.zip);
111427
+ const index = getSlabPostSlugIndex(doc.zip, allDocs);
111348
111428
  Array.from(div.querySelectorAll("a[href]")).forEach((link2) => {
111349
111429
  const href = link2.getAttribute("href");
111350
111430
  if (!href || SLAB_TOPIC_URL_PATTERN.test(href)) {
@@ -114320,7 +114400,7 @@ var import_jszip5 = __toESM(require_lib3());
114320
114400
  var import_url_join6 = __toESM(require_url_join());
114321
114401
 
114322
114402
  // packages/import-engine/package.json
114323
- var version = "0.1.1";
114403
+ var version = "0.1.3";
114324
114404
 
114325
114405
  // src/docx-import/detectors.js
114326
114406
  function isGoogleSitesDoc(div) {
@@ -114344,7 +114424,7 @@ var showError;
114344
114424
  var updateLoader;
114345
114425
  var showReport;
114346
114426
  var docLinkIndex = /* @__PURE__ */ new Map();
114347
- var toolVersion = true ? `${version}+${"e19020c"}` : version;
114427
+ var toolVersion = true ? `${version}+${"8671a17"}` : version;
114348
114428
  var pendingUpload = null;
114349
114429
  function confirmPendingUpload() {
114350
114430
  const upload = pendingUpload;
@@ -114508,6 +114588,21 @@ async function processImage(doc, image2, imageIndex, newZip, resourceLookup = {}
114508
114588
  }
114509
114589
  });
114510
114590
  }
114591
+ var resourceLookupSizes = /* @__PURE__ */ new WeakMap();
114592
+ function getResourceLookupSize(resourceLookup) {
114593
+ if (!resourceLookupSizes.has(resourceLookup)) {
114594
+ resourceLookupSizes.set(resourceLookup, Object.keys(resourceLookup).length);
114595
+ }
114596
+ return resourceLookupSizes.get(resourceLookup);
114597
+ }
114598
+ function addToResourceLookup(resourceLookup, unsafeResourceName, safeResourceName) {
114599
+ const size2 = getResourceLookupSize(resourceLookup);
114600
+ const isNewKey = !Object.hasOwn(resourceLookup, unsafeResourceName);
114601
+ resourceLookup[unsafeResourceName] = safeResourceName;
114602
+ if (isNewKey && Object.hasOwn(resourceLookup, unsafeResourceName)) {
114603
+ resourceLookupSizes.set(resourceLookup, size2 + 1);
114604
+ }
114605
+ }
114511
114606
  async function resolveResource(element, doc, newZip, resourceLookup = {}) {
114512
114607
  return new Promise(async function(resolve, reject2) {
114513
114608
  try {
@@ -114536,10 +114631,10 @@ async function resolveResource(element, doc, newZip, resourceLookup = {}) {
114536
114631
  } else {
114537
114632
  safeResourceName = sanitizeFilename(unsafeResourceName);
114538
114633
  if (safeResourceName !== unsafeResourceName) {
114539
- const nextIndex = Object.keys(resourceLookup).length;
114634
+ const nextIndex = getResourceLookupSize(resourceLookup);
114540
114635
  safeResourceName = `file${nextIndex}_${safeResourceName}`;
114541
114636
  }
114542
- resourceLookup[unsafeResourceName] = safeResourceName;
114637
+ addToResourceLookup(resourceLookup, unsafeResourceName, safeResourceName);
114543
114638
  }
114544
114639
  const resourcePath = "resources/" + safeResourceName;
114545
114640
  const fileData = await (import_jszip5.default.support.uint8array ? file.async("uint8array") : file.async("base64"));
@@ -114774,7 +114869,7 @@ async function preProcessDoc(doc, docTitleLookup = {}) {
114774
114869
  doc.zip.source = source;
114775
114870
  }
114776
114871
  }
114777
- doc.htmlContent = div;
114872
+ doc.htmlSource = serializeDocTree(div, doc);
114778
114873
  updateLoader({
114779
114874
  step: 1,
114780
114875
  docIndex: doc.index,
@@ -114805,7 +114900,8 @@ async function postProcessDoc(newZip, doc, resourceLookup = {}, externalLookup =
114805
114900
  writeCardToZip(newZip, doc, "");
114806
114901
  return;
114807
114902
  }
114808
- const div = doc.htmlContent;
114903
+ const div = parseDocTree(doc.htmlSource, doc);
114904
+ delete doc.htmlSource;
114809
114905
  updateLoader({
114810
114906
  step: 1,
114811
114907
  docIndex: doc.index,
@@ -114874,6 +114970,14 @@ async function postProcessDoc(newZip, doc, resourceLookup = {}, externalLookup =
114874
114970
  }
114875
114971
  writeCardToZip(newZip, doc, cleanUpHtml(div, doc));
114876
114972
  }
114973
+ function releaseDocContent(doc, buffersReadFromZip) {
114974
+ delete doc.htmlSource;
114975
+ delete doc.originalContent;
114976
+ delete doc.recordedLinks;
114977
+ if (buffersReadFromZip.has(doc)) {
114978
+ delete doc.buffer;
114979
+ }
114980
+ }
114877
114981
  function makeErrorHandler(progress) {
114878
114982
  return function handleError(event) {
114879
114983
  const message = event.reason?.message || event.message || event.error?.message || "Unknown error";
@@ -114957,6 +115061,7 @@ async function runProcessDocs(params, progress) {
114957
115061
  zipToCheck.docxAnchorIndex = buildDocxAnchorIndex(docs, zipToCheck);
114958
115062
  }
114959
115063
  }
115064
+ const buffersReadFromZip = /* @__PURE__ */ new WeakSet();
114960
115065
  await doAll(docs, async function(doc, docIndex, nextDoc) {
114961
115066
  doc.index = docIndex;
114962
115067
  try {
@@ -114965,6 +115070,7 @@ async function runProcessDocs(params, progress) {
114965
115070
  } else if (doc.zip) {
114966
115071
  const buffer = await doc.zip.file(doc.filename).async("arraybuffer");
114967
115072
  doc.buffer = buffer;
115073
+ buffersReadFromZip.add(doc);
114968
115074
  await preProcessDoc(doc, docTitleLookup);
114969
115075
  } else {
114970
115076
  await preProcessDoc(doc);
@@ -114972,6 +115078,7 @@ async function runProcessDocs(params, progress) {
114972
115078
  } catch (err) {
114973
115079
  recordFailure(doc, err);
114974
115080
  } finally {
115081
+ delete doc.div;
114975
115082
  nextDoc();
114976
115083
  }
114977
115084
  });
@@ -114981,6 +115088,9 @@ async function runProcessDocs(params, progress) {
114981
115088
  if (zipToCheck.source === "confluence") {
114982
115089
  zipToCheck.confluencePageIndex = buildConfluencePageIndex(docs, zipToCheck);
114983
115090
  }
115091
+ if (zipToCheck.isSlabExport) {
115092
+ zipToCheck.slabPostSlugIndex = buildSlabPostSlugIndex(docs, zipToCheck);
115093
+ }
114984
115094
  }
114985
115095
  await doAll(docs, async function(doc, docIndex, nextDoc) {
114986
115096
  try {
@@ -114988,6 +115098,7 @@ async function runProcessDocs(params, progress) {
114988
115098
  } catch (err) {
114989
115099
  recordFailure(doc, err);
114990
115100
  } finally {
115101
+ releaseDocContent(doc, buffersReadFromZip);
114991
115102
  nextDoc();
114992
115103
  }
114993
115104
  });
@@ -115155,6 +115266,91 @@ function setSubtract(a, b) {
115155
115266
  return !b.includes(value);
115156
115267
  });
115157
115268
  }
115269
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
115270
+ var EXTERNAL_MANIFEST_MAX_BYTES = 1048576;
115271
+ var EXTERNAL_MAX_ENTRIES = 500;
115272
+ var EXTERNAL_NAME_RE = /^[\p{L}\p{N}._-]{1,128}$/u;
115273
+ var EXTERNAL_REFUSED = "the server won't fetch this image; the card will link to the original URL";
115274
+ function checkExternalImages(zip, task, report) {
115275
+ var state = { enabled: false, names: /* @__PURE__ */ new Set(), referenced: [] };
115276
+ report.externalImages = state;
115277
+ var collectionFile = zip.file("collection.yaml");
115278
+ var manifestFile = zip.file("resources/external.json");
115279
+ function readCollection() {
115280
+ if (!collectionFile) {
115281
+ return Promise.resolve();
115282
+ }
115283
+ return collectionFile.async("string").then(function(yamlString) {
115284
+ try {
115285
+ var collection = yaml.load(yamlString);
115286
+ state.enabled = !!collection && typeof collection == "object" && collection.FetchExternalImages === true;
115287
+ } catch (e) {
115288
+ }
115289
+ }, function() {
115290
+ });
115291
+ }
115292
+ function readManifest() {
115293
+ if (!manifestFile) {
115294
+ return Promise.resolve();
115295
+ }
115296
+ return manifestFile.async("string").then(function(text2) {
115297
+ var size2 = manifestFile._data && manifestFile._data.uncompressedSize || text2.length;
115298
+ var dropped = "The server will ignore the whole manifest, so no external images will be fetched";
115299
+ if (size2 > EXTERNAL_MANIFEST_MAX_BYTES) {
115300
+ report.error(`resources/external.json is larger than ${EXTERNAL_MANIFEST_MAX_BYTES} bytes. ${dropped}.`);
115301
+ return;
115302
+ }
115303
+ var manifest;
115304
+ try {
115305
+ manifest = JSON.parse(text2);
115306
+ } catch (e) {
115307
+ report.error(`resources/external.json is not valid JSON (${e.message}). ${dropped}.`);
115308
+ return;
115309
+ }
115310
+ if (!manifest || typeof manifest != "object" || Array.isArray(manifest)) {
115311
+ report.error(`resources/external.json must be a JSON object mapping file names to URLs. ${dropped}.`);
115312
+ return;
115313
+ }
115314
+ var keys2 = Object.keys(manifest);
115315
+ if (keys2.some(function(key) {
115316
+ return typeof manifest[key] != "string";
115317
+ })) {
115318
+ report.error(`resources/external.json must map every file name to a URL string. ${dropped}.`);
115319
+ return;
115320
+ }
115321
+ keys2.forEach(function(name, index) {
115322
+ state.names.add(name);
115323
+ var url = manifest[name];
115324
+ var label = `resources/external.json entry "${escapeQuotes(name)}"`;
115325
+ if (index >= EXTERNAL_MAX_ENTRIES) {
115326
+ report.warn(`${label} is past the first ${EXTERNAL_MAX_ENTRIES} entries: ${EXTERNAL_REFUSED}.`);
115327
+ } else if (!EXTERNAL_NAME_RE.test(name) || name.includes("..")) {
115328
+ report.warn(`${label} has an invalid file name: ${EXTERNAL_REFUSED}.`);
115329
+ } else if (url.trim() === "") {
115330
+ report.warn(`${label} has a blank URL: ${EXTERNAL_REFUSED}.`);
115331
+ } else if (!/^https:\/\//i.test(url.trim())) {
115332
+ report.warn(`${label} is not an https URL: ${EXTERNAL_REFUSED}.`);
115333
+ }
115334
+ if (zip.file("resources/" + name)) {
115335
+ report.info(`${label} also exists as resources/${escapeQuotes(name)} in the zip, so the server skips the fetch.`);
115336
+ }
115337
+ });
115338
+ }, function() {
115339
+ });
115340
+ }
115341
+ return readCollection().then(readManifest).then(function() {
115342
+ if (manifestFile && !state.enabled) {
115343
+ report.warn(`resources/external.json is included but collection.yaml doesn't set FetchExternalImages: true, so the server ignores it.`);
115344
+ }
115345
+ if (state.enabled && !manifestFile) {
115346
+ report.warn(`collection.yaml sets FetchExternalImages: true but the zip has no resources/external.json.`);
115347
+ }
115348
+ if (state.enabled) {
115349
+ report.syncError(`If this is a sync, FetchExternalImages is ignored (external images are only fetched on imports).`);
115350
+ }
115351
+ report.next();
115352
+ });
115353
+ }
115158
115354
  function escapeQuotes(value) {
115159
115355
  value = value || "";
115160
115356
  return value.replace(/"/g, `\\"`);
@@ -115189,11 +115385,18 @@ function checkCardYaml(zip, task, report) {
115189
115385
  return zip.file(task.filename).async("string").then(function(yamlString) {
115190
115386
  try {
115191
115387
  var card = yaml.load(yamlString);
115192
- var possibleKeys = ["ExternalId", "ExternalUrl", "Title", "Tags", "externalLastUpdated"];
115388
+ var possibleKeys = ["ExternalId", "ExternalUrl", "Title", "Tags", "externalLastUpdated", "ID"];
115193
115389
  var otherKeys = setSubtract(Object.keys(card), possibleKeys);
115194
115390
  otherKeys.forEach(function(key) {
115195
115391
  report.warn(`Unexpected property "${key}" in the card's yaml.`);
115196
115392
  });
115393
+ if (card.ID !== void 0 && card.ID !== null) {
115394
+ if (typeof card.ID != "string" || !UUID_RE2.test(card.ID)) {
115395
+ report.error(`The card's ID property must be a UUID string (e.g. ID: "123e4567-e89b-12d3-a456-426614174000"), but it was: ${escapeHtml3(String(card.ID))}`);
115396
+ } else {
115397
+ report.info(`The card has an ID, so it updates the existing card with that ID in the same collection if one exists, otherwise a new card is created.`);
115398
+ }
115399
+ }
115197
115400
  if (!card.Title) {
115198
115401
  report.error(`A "Title" property is required in the card's yaml.`);
115199
115402
  } else {
@@ -115258,7 +115461,17 @@ function checkCardHtml(zip, task, report) {
115258
115461
  report.resourceReferences.push(reference2);
115259
115462
  }
115260
115463
  } else {
115261
- report.error(`The referenced resource "${reference2}" does not exist in the zip.`);
115464
+ var ext = report.externalImages || { enabled: false, names: /* @__PURE__ */ new Set(), referenced: [] };
115465
+ var extName = reference2.slice("resources/".length);
115466
+ if (ext.names.has(extName) && ext.enabled) {
115467
+ if (!ext.referenced.includes(extName)) {
115468
+ ext.referenced.push(extName);
115469
+ }
115470
+ } else if (ext.names.has(extName)) {
115471
+ report.error(`The referenced resource "${reference2}" is listed in resources/external.json but collection.yaml doesn't set FetchExternalImages: true, so it won't be fetched.`);
115472
+ } else {
115473
+ report.error(`The referenced resource "${reference2}" does not exist in the zip.`);
115474
+ }
115262
115475
  }
115263
115476
  } else if (reference2.startsWith("cards/")) {
115264
115477
  if (zip.file(reference2 + ".yaml")) {
@@ -115515,7 +115728,7 @@ function checkCollectionYaml(zip, task, report) {
115515
115728
  }
115516
115729
  try {
115517
115730
  var collection = yaml.load(yamlString);
115518
- var possibleKeys = ["Items", "Tags", "Title", "Version"];
115731
+ var possibleKeys = ["Items", "Tags", "Title", "Version", "FetchExternalImages", "ReuseFolders"];
115519
115732
  var otherKeys = setSubtract(Object.keys(collection), possibleKeys);
115520
115733
  otherKeys.forEach(function(key) {
115521
115734
  report.warn(`Unexpected property "${key}" in collection.yaml.`);
@@ -115532,6 +115745,14 @@ function checkCollectionYaml(zip, task, report) {
115532
115745
  }
115533
115746
  }
115534
115747
  var isV2 = collection.Version == 2;
115748
+ ["FetchExternalImages", "ReuseFolders"].forEach(function(key) {
115749
+ if (collection[key] !== void 0 && collection[key] !== null && typeof collection[key] != "boolean") {
115750
+ report.warn(`The collection's ${key} property must be true or false (${key} = ${escapeHtml3(String(collection[key]))}).`);
115751
+ }
115752
+ });
115753
+ if (collection.ReuseFolders === true && !isV2) {
115754
+ report.warn(`ReuseFolders is ignored unless the collection has "Version: 2" (it applies only to Version 2 non-sync imports).`);
115755
+ }
115535
115756
  if (collection.Items) {
115536
115757
  if (!Array.isArray(collection.Items)) {
115537
115758
  report.error(`The collection's Items list is optional, but if provided it must be a list.`);
@@ -115635,7 +115856,16 @@ function checkResource(zip, task, report) {
115635
115856
  } else {
115636
115857
  report.resources += 1;
115637
115858
  }
115638
- if (!report.resourceReferences.includes(task.filename)) {
115859
+ if (task.filename == "resources/external.json") {
115860
+ var ext = report.externalImages;
115861
+ if (ext) {
115862
+ ext.names.forEach(function(name) {
115863
+ if (!ext.referenced.includes(name) && !report.resourceReferences.includes("resources/" + name)) {
115864
+ report.warn(`resources/external.json entry "${escapeQuotes(name)}" isn't referenced by any cards.`);
115865
+ }
115866
+ });
115867
+ }
115868
+ } else if (!report.resourceReferences.includes(task.filename)) {
115639
115869
  report.warn(`Resource ${task.filename} is included in the zip but isn't referenced by any cards.`);
115640
115870
  }
115641
115871
  if (zip.file(task.filename)._data.uncompressedSize > 2e9) {
@@ -115732,6 +115962,7 @@ function checkZip(zip) {
115732
115962
  collectionRoots: [],
115733
115963
  tags: [],
115734
115964
  resourceReferences: [],
115965
+ externalImages: { enabled: false, names: /* @__PURE__ */ new Set(), referenced: [] },
115735
115966
  cardLinks: 0,
115736
115967
  boardLinks: 0,
115737
115968
  checkCount: 0,
@@ -115805,6 +116036,7 @@ function checkZip(zip) {
115805
116036
  info(`Unexpected file ${fullPath}`);
115806
116037
  }
115807
116038
  }
116039
+ tasks.push({ func: checkExternalImages, context: "resources/external.json", checks: 3, priority: 0 });
115808
116040
  tasks.push({ func: checkFolderTree, checks: 5, priority: 6 });
115809
116041
  tasks.push({ func: checkStats, checks: 3, priority: 10 });
115810
116042
  tasks.sort(function(a, b) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getguru/import-engine",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Command-line engine that turns Notion, Confluence, Google, Word, PDF, Markdown and spreadsheet exports into Guru import zips.",
5
5
  "bin": {
6
6
  "guru-import-engine": "docx-import-cli.js"