@treeviz/gedcom-parser 2.0.0 → 2.0.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/dist/cli/index.js CHANGED
@@ -2212,7 +2212,7 @@ var Indi = class extends Common {
2212
2212
  title,
2213
2213
  url,
2214
2214
  contentType: type,
2215
- downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName().replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
2215
+ downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
2216
2216
  };
2217
2217
  }
2218
2218
  })
@@ -2241,7 +2241,7 @@ var Indi = class extends Common {
2241
2241
  const deathObj = this.get("DEAT.OBJE")?.toList().copy();
2242
2242
  objeList?.merge(birthObj).merge(deathObj);
2243
2243
  (this.get("FAMS")?.toValueList().values() ?? []).concat(this.get("FAMC")?.toValueList().values() ?? []).forEach((fam) => {
2244
- objeList.merge(fam?.get("MARR.OBJE"));
2244
+ objeList?.merge(fam?.get("MARR.OBJE"));
2245
2245
  });
2246
2246
  objeList?.forEach((o, index) => {
2247
2247
  if (!o) {
@@ -2267,7 +2267,7 @@ var Indi = class extends Common {
2267
2267
  title,
2268
2268
  url,
2269
2269
  contentType: type,
2270
- downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName().replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
2270
+ downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
2271
2271
  };
2272
2272
  }
2273
2273
  });
@@ -2365,14 +2365,11 @@ var Indi = class extends Common {
2365
2365
  const sourList = this.get("SOUR")?.toList().copy();
2366
2366
  sourList?.forEach((sour) => {
2367
2367
  const sourObje = sour?.get("OBJE")?.toList();
2368
- objeList.merge(sourObje);
2368
+ objeList?.merge(sourObje);
2369
2369
  });
2370
- if (!objeList || objeList.length === 0) {
2371
- return void 0;
2372
- }
2373
2370
  const rfn = this.get("RFN")?.toValue();
2374
2371
  const geniId = rfn?.replace(/^geni:/, "") || "unknown";
2375
- objeList.forEach((obje, index) => {
2372
+ objeList?.forEach((obje, index) => {
2376
2373
  if (!obje) {
2377
2374
  return;
2378
2375
  }
@@ -2411,7 +2408,7 @@ var Indi = class extends Common {
2411
2408
  return list;
2412
2409
  }
2413
2410
  const rfn = this.get("RFN")?.toValue();
2414
- const treeId = rfn || "universal";
2411
+ const treeId = this.getUniversalTreeId() || rfn || "universal";
2415
2412
  objeList.forEach((obje, index) => {
2416
2413
  if (!obje) {
2417
2414
  return;
@@ -6248,7 +6245,7 @@ var Families = class _Families extends List {
6248
6245
 
6249
6246
  // package.json
6250
6247
  var package_default = {
6251
- version: "2.0.0"};
6248
+ version: "2.0.1"};
6252
6249
 
6253
6250
  // src/utils/get-product-details.ts
6254
6251
  var isDevelopment = () => {
@@ -7554,14 +7551,17 @@ function formatLifespan(birthDate, deathDate) {
7554
7551
 
7555
7552
  // src/cli/commands/convert.ts
7556
7553
  function registerConvertCommand(program2) {
7557
- program2.command("convert <file>").description("Convert GEDCOM to another format").requiredOption("-f, --format <format>", "Output format: json, csv, markdown").option("-o, --output <file>", "Output file path").action((file, options) => {
7554
+ program2.command("convert <file>").description("Convert GEDCOM to another format").requiredOption(
7555
+ "-f, --format <format>",
7556
+ "Output format: json, csv, markdown"
7557
+ ).option("-o, --output <file>", "Output file path").action((file, options) => {
7558
7558
  try {
7559
7559
  const content = readGedcomFile(file);
7560
7560
  const { gedcom: tree } = parser_default.parse(content);
7561
7561
  const individuals = tree.indis();
7562
7562
  let outputContent = "";
7563
7563
  if (options.format === "json") {
7564
- const jsonData = individuals.map((indi) => ({
7564
+ const jsonData = individuals?.map((indi) => ({
7565
7565
  id: indi.id,
7566
7566
  name: cleanGedcomName(indi.NAME?.toValue()),
7567
7567
  sex: indi.SEX?.value || null,
@@ -7573,8 +7573,10 @@ function registerConvertCommand(program2) {
7573
7573
  outputContent = formatJson(jsonData);
7574
7574
  } else if (options.format === "csv") {
7575
7575
  const lines = [];
7576
- lines.push("ID,Name,Sex,Birth Date,Birth Place,Death Date,Death Place");
7577
- individuals.forEach((indi) => {
7576
+ lines.push(
7577
+ "ID,Name,Sex,Birth Date,Birth Place,Death Date,Death Place"
7578
+ );
7579
+ individuals?.forEach((indi) => {
7578
7580
  const csvEscape = (str) => {
7579
7581
  if (!str) return "";
7580
7582
  if (str.includes(",") || str.includes('"')) {
@@ -7582,15 +7584,19 @@ function registerConvertCommand(program2) {
7582
7584
  }
7583
7585
  return str;
7584
7586
  };
7585
- lines.push([
7586
- csvEscape(indi.id),
7587
- csvEscape(cleanGedcomName(indi.NAME?.toValue())),
7588
- csvEscape(indi.SEX?.value),
7589
- csvEscape(indi.BIRT?.DATE?.toValue()),
7590
- csvEscape(indi.BIRT?.PLAC?.value),
7591
- csvEscape(indi.DEAT?.DATE?.toValue()),
7592
- csvEscape(indi.DEAT?.PLAC?.value)
7593
- ].join(","));
7587
+ lines.push(
7588
+ [
7589
+ csvEscape(indi.id),
7590
+ csvEscape(
7591
+ cleanGedcomName(indi.NAME?.toValue())
7592
+ ),
7593
+ csvEscape(indi.SEX?.value),
7594
+ csvEscape(indi.BIRT?.DATE?.toValue()),
7595
+ csvEscape(indi.BIRT?.PLAC?.value),
7596
+ csvEscape(indi.DEAT?.DATE?.toValue()),
7597
+ csvEscape(indi.DEAT?.PLAC?.value)
7598
+ ].join(",")
7599
+ );
7594
7600
  });
7595
7601
  outputContent = lines.join("\n");
7596
7602
  } else if (options.format === "markdown") {
@@ -7598,12 +7604,14 @@ function registerConvertCommand(program2) {
7598
7604
  lines.push("# GEDCOM Individuals\n");
7599
7605
  lines.push("| ID | Name | Sex | Birth | Death |");
7600
7606
  lines.push("|----|------|-----|-------|-------|");
7601
- individuals.forEach((indi) => {
7607
+ individuals?.forEach((indi) => {
7602
7608
  const name = cleanGedcomName(indi.NAME?.toValue()) || "?";
7603
7609
  const sex = indi.SEX?.value || "?";
7604
7610
  const birth = indi.BIRT?.DATE?.toValue() || "?";
7605
7611
  const death = indi.DEAT?.DATE?.toValue() || "?";
7606
- lines.push(`| ${indi.id} | ${name} | ${sex} | ${birth} | ${death} |`);
7612
+ lines.push(
7613
+ `| ${indi.id} | ${name} | ${sex} | ${birth} | ${death} |`
7614
+ );
7607
7615
  });
7608
7616
  outputContent = lines.join("\n");
7609
7617
  } else {
@@ -7612,7 +7620,11 @@ function registerConvertCommand(program2) {
7612
7620
  }
7613
7621
  if (options.output) {
7614
7622
  writeFileSync(options.output, outputContent, "utf-8");
7615
- console.log(formatSuccess(`Converted to ${options.format} and saved to ${options.output}`));
7623
+ console.log(
7624
+ formatSuccess(
7625
+ `Converted to ${options.format} and saved to ${options.output}`
7626
+ )
7627
+ );
7616
7628
  } else {
7617
7629
  console.log(outputContent);
7618
7630
  }
@@ -7622,49 +7634,73 @@ function registerConvertCommand(program2) {
7622
7634
  });
7623
7635
  }
7624
7636
  function registerExtractCommand(program2) {
7625
- program2.command("extract <file>").description("Extract a subset of individuals to a new GEDCOM file").requiredOption("-o, --output <file>", "Output file path (required)").option("--surname <name>", "Filter by surname").option("--birth-after <year>", "Include individuals born after this year").option("--birth-before <year>", "Include individuals born before this year").option("--death-after <year>", "Include individuals who died after this year").option("--death-before <year>", "Include individuals who died before this year").action((file, options) => {
7637
+ program2.command("extract <file>").description("Extract a subset of individuals to a new GEDCOM file").requiredOption("-o, --output <file>", "Output file path (required)").option("--surname <name>", "Filter by surname").option(
7638
+ "--birth-after <year>",
7639
+ "Include individuals born after this year"
7640
+ ).option(
7641
+ "--birth-before <year>",
7642
+ "Include individuals born before this year"
7643
+ ).option(
7644
+ "--death-after <year>",
7645
+ "Include individuals who died after this year"
7646
+ ).option(
7647
+ "--death-before <year>",
7648
+ "Include individuals who died before this year"
7649
+ ).action((file, options) => {
7626
7650
  try {
7627
7651
  const content = readGedcomFile(file);
7628
7652
  const { gedcom: tree } = parser_default.parse(content);
7629
7653
  const individuals = tree.indis();
7630
7654
  const results = [];
7631
- individuals.forEach((indi) => {
7655
+ individuals?.forEach((indi) => {
7632
7656
  let matches = true;
7633
7657
  if (options.surname && matches) {
7634
7658
  const searchSurname = options.surname.toLowerCase();
7635
- const name = cleanGedcomName(indi.NAME?.toValue()).toLowerCase();
7659
+ const name = cleanGedcomName(
7660
+ indi.NAME?.toValue()
7661
+ ).toLowerCase();
7636
7662
  matches = name.includes(searchSurname);
7637
7663
  }
7638
7664
  if (options.birthAfter && matches) {
7639
7665
  const year = parseInt(options.birthAfter, 10);
7640
7666
  const birthDate = indi.BIRT?.DATE?.toValue();
7641
7667
  const match = birthDate?.match(/\d{4}/);
7642
- matches = match && parseInt(match[0], 10) > year;
7668
+ matches = Boolean(
7669
+ match && parseInt(match[0], 10) > year
7670
+ );
7643
7671
  }
7644
7672
  if (options.birthBefore && matches) {
7645
7673
  const year = parseInt(options.birthBefore, 10);
7646
7674
  const birthDate = indi.BIRT?.DATE?.toValue();
7647
7675
  const match = birthDate?.match(/\d{4}/);
7648
- matches = match && parseInt(match[0], 10) < year;
7676
+ matches = Boolean(
7677
+ match && parseInt(match[0], 10) < year
7678
+ );
7649
7679
  }
7650
7680
  if (options.deathAfter && matches) {
7651
7681
  const year = parseInt(options.deathAfter, 10);
7652
7682
  const deathDate = indi.DEAT?.DATE?.toValue();
7653
7683
  const match = deathDate?.match(/\d{4}/);
7654
- matches = match && parseInt(match[0], 10) > year;
7684
+ matches = Boolean(
7685
+ match && parseInt(match[0], 10) > year
7686
+ );
7655
7687
  }
7656
7688
  if (options.deathBefore && matches) {
7657
7689
  const year = parseInt(options.deathBefore, 10);
7658
7690
  const deathDate = indi.DEAT?.DATE?.toValue();
7659
7691
  const match = deathDate?.match(/\d{4}/);
7660
- matches = match && parseInt(match[0], 10) < year;
7692
+ matches = Boolean(
7693
+ match && parseInt(match[0], 10) < year
7694
+ );
7661
7695
  }
7662
7696
  if (matches) {
7663
7697
  results.push(indi);
7664
7698
  }
7665
7699
  });
7666
7700
  if (results.length === 0) {
7667
- console.log(formatError("No individuals match the criteria"));
7701
+ console.log(
7702
+ formatError("No individuals match the criteria")
7703
+ );
7668
7704
  process.exit(1);
7669
7705
  }
7670
7706
  const lines = [];
@@ -7676,12 +7712,18 @@ function registerExtractCommand(program2) {
7676
7712
  results.forEach((indi) => {
7677
7713
  const raw = indi.raw();
7678
7714
  if (raw) {
7679
- lines.push(...raw.split("\n").filter((line) => line.trim()));
7715
+ lines.push(
7716
+ ...raw.split("\n").filter((line) => line.trim())
7717
+ );
7680
7718
  }
7681
7719
  });
7682
7720
  lines.push("0 TRLR");
7683
7721
  writeFileSync(options.output, lines.join("\n"), "utf-8");
7684
- console.log(formatSuccess(`Extracted ${results.length} individuals to ${options.output}`));
7722
+ console.log(
7723
+ formatSuccess(
7724
+ `Extracted ${results.length} individuals to ${options.output}`
7725
+ )
7726
+ );
7685
7727
  } catch (error) {
7686
7728
  handleError(error, "Failed to extract individuals");
7687
7729
  }
@@ -7692,7 +7734,7 @@ function registerExtractCommand(program2) {
7692
7734
  function findIndividuals(tree, options) {
7693
7735
  const individuals = tree.indis();
7694
7736
  const results = [];
7695
- individuals.forEach((indi) => {
7737
+ individuals?.forEach((indi) => {
7696
7738
  let matches = true;
7697
7739
  if (options.id && indi.id !== options.id) {
7698
7740
  matches = false;
@@ -7755,7 +7797,7 @@ function formatFindResults(results, json = false) {
7755
7797
  const lifespan = formatLifespan(birthDate, deathDate);
7756
7798
  console.log(
7757
7799
  formatListItem(
7758
- `${formatId(indi.id)} ${formatName(name)} ${lifespan}`
7800
+ `${formatId(indi.id ?? "")} ${formatName(name)} ${lifespan}`
7759
7801
  )
7760
7802
  );
7761
7803
  const birthPlace = indi.BIRT?.PLAC?.value;
@@ -7934,9 +7976,6 @@ function registerGetCommand(program2) {
7934
7976
  } else if (id.startsWith("@S")) {
7935
7977
  record = tree.sour(id);
7936
7978
  recordType = "Source";
7937
- } else if (id.startsWith("@N")) {
7938
- record = tree.note(id);
7939
- recordType = "Note";
7940
7979
  } else {
7941
7980
  record = tree.indi(id);
7942
7981
  recordType = "Individual";
@@ -8023,37 +8062,60 @@ function registerInfoCommand(program2) {
8023
8062
  if (options.json) {
8024
8063
  console.log(formatJson(info));
8025
8064
  } else {
8026
- console.log(formatSuccess("GEDCOM file parsed successfully\n"));
8065
+ console.log(
8066
+ formatSuccess("GEDCOM file parsed successfully\n")
8067
+ );
8027
8068
  console.log(formatHeader("File Information"));
8028
8069
  console.log(`${formatLabel("File")} ${formatValue(file)}`);
8029
- console.log(`${formatLabel("GEDCOM Version")} ${formatValue(version)}`);
8070
+ console.log(
8071
+ `${formatLabel("GEDCOM Version")} ${formatValue(version)}`
8072
+ );
8030
8073
  console.log();
8031
8074
  console.log(formatHeader("Statistics"));
8032
- console.log(`${formatLabel("Individuals")} ${formatCount(individuals?.length || 0)}`);
8033
- console.log(`${formatLabel("Families")} ${formatCount(families?.length || 0)}`);
8034
- console.log(`${formatLabel("Sources")} ${formatCount(sources?.length || 0)}`);
8035
- console.log(`${formatLabel("Repositories")} ${formatCount(repos?.length || 0)}`);
8036
- console.log(`${formatLabel("Media Objects")} ${formatCount(objes?.length || 0)}`);
8037
- console.log(`${formatLabel("Submitters")} ${formatCount(submitters?.length || 0)}`);
8075
+ console.log(
8076
+ `${formatLabel("Individuals")} ${formatCount(individuals?.length || 0)}`
8077
+ );
8078
+ console.log(
8079
+ `${formatLabel("Families")} ${formatCount(families?.length || 0)}`
8080
+ );
8081
+ console.log(
8082
+ `${formatLabel("Sources")} ${formatCount(sources?.length || 0)}`
8083
+ );
8084
+ console.log(
8085
+ `${formatLabel("Repositories")} ${formatCount(repos?.length || 0)}`
8086
+ );
8087
+ console.log(
8088
+ `${formatLabel("Media Objects")} ${formatCount(objes?.length || 0)}`
8089
+ );
8090
+ console.log(
8091
+ `${formatLabel("Submitters")} ${formatCount(submitters?.length || 0)}`
8092
+ );
8038
8093
  if (options.verbose) {
8039
8094
  console.log();
8040
8095
  console.log(formatHeader("Additional Details"));
8041
8096
  const surnames = /* @__PURE__ */ new Map();
8042
- individuals.forEach((indi) => {
8097
+ individuals?.forEach((indi) => {
8043
8098
  const name = indi.NAME?.toValue();
8044
8099
  if (name) {
8045
8100
  const match = name.match(/\/(.+?)\//);
8046
8101
  if (match) {
8047
8102
  const surname = match[1];
8048
- surnames.set(surname, (surnames.get(surname) || 0) + 1);
8103
+ surnames.set(
8104
+ surname,
8105
+ (surnames.get(surname) || 0) + 1
8106
+ );
8049
8107
  }
8050
8108
  }
8051
8109
  });
8052
8110
  const topSurnames = Array.from(surnames.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5);
8053
8111
  if (topSurnames.length > 0) {
8054
- console.log(`${formatLabel("Most Common Surnames")}`);
8112
+ console.log(
8113
+ `${formatLabel("Most Common Surnames")}`
8114
+ );
8055
8115
  topSurnames.forEach(([surname, count]) => {
8056
- console.log(` - ${surname}: ${formatCount(count)}`);
8116
+ console.log(
8117
+ ` - ${surname}: ${formatCount(count)}`
8118
+ );
8057
8119
  });
8058
8120
  }
8059
8121
  }
@@ -8164,7 +8226,7 @@ function showIndividual(tree, individual) {
8164
8226
  const name = cleanGedcomName(individual.NAME?.toValue());
8165
8227
  console.log(
8166
8228
  formatHeader(`
8167
- ${formatId(individual.id)} ${formatName(name)}`)
8229
+ ${formatId(individual.id ?? "")} ${formatName(name)}`)
8168
8230
  );
8169
8231
  console.log("");
8170
8232
  if (individual.SEX?.value) {
@@ -8269,7 +8331,7 @@ ${formatId(individual.id)} ${formatName(name)}`)
8269
8331
  );
8270
8332
  console.log(
8271
8333
  formatListItem(
8272
- ` ${formatId(child.id)} ${formatName(childName)}`
8334
+ ` ${formatId(child.id ?? "")} ${formatName(childName)}`
8273
8335
  )
8274
8336
  );
8275
8337
  }
@@ -8712,7 +8774,13 @@ var GedcomRepl = class {
8712
8774
  }
8713
8775
  }
8714
8776
  try {
8715
- const result = getValue(this.context.tree, id, path, json, raw);
8777
+ const result = getValue(
8778
+ this.context.tree,
8779
+ id || "",
8780
+ path,
8781
+ json,
8782
+ raw
8783
+ );
8716
8784
  console.log(result);
8717
8785
  } catch (error) {
8718
8786
  console.log(formatError(error.message));
@@ -8748,12 +8816,13 @@ var GedcomRepl = class {
8748
8816
  }
8749
8817
  const individual = this.context.selectedPerson;
8750
8818
  const relatives = /* @__PURE__ */ new Set();
8751
- relatives.add(individual.id);
8819
+ individual?.id && relatives.add(individual.id);
8752
8820
  if (includeAncestors) {
8753
8821
  const getAncestors = (indi, depth) => {
8754
8822
  if (depth > maxDepth) return;
8755
8823
  const parents = indi.getParents();
8756
8824
  parents?.forEach((parent) => {
8825
+ if (!parent?.id) return;
8757
8826
  relatives.add(parent.id);
8758
8827
  getAncestors(parent, depth + 1);
8759
8828
  });
@@ -8765,6 +8834,7 @@ var GedcomRepl = class {
8765
8834
  if (depth > maxDepth) return;
8766
8835
  const children = indi.getChildren();
8767
8836
  children?.forEach((child) => {
8837
+ if (!child?.id) return;
8768
8838
  relatives.add(child.id);
8769
8839
  getDescendants(child, depth + 1);
8770
8840
  });
@@ -8773,12 +8843,14 @@ var GedcomRepl = class {
8773
8843
  }
8774
8844
  const allRelatives = Array.from(relatives).map((relId) => this.context.tree.indi(relId)).filter((indi) => indi !== null);
8775
8845
  if (isJson) {
8776
- const jsonData = allRelatives.map((indi) => ({
8777
- id: indi.id,
8778
- name: cleanGedcomName(indi.NAME?.toValue()),
8779
- birthDate: indi.BIRT?.DATE?.toValue() || null,
8780
- deathDate: indi.DEAT?.DATE?.toValue() || null
8781
- }));
8846
+ const jsonData = allRelatives.map(
8847
+ (indi) => indi && {
8848
+ id: indi.id,
8849
+ name: cleanGedcomName(indi.NAME?.toValue()),
8850
+ birthDate: indi.BIRT?.DATE?.toValue() || null,
8851
+ deathDate: indi.DEAT?.DATE?.toValue() || null
8852
+ }
8853
+ ).filter(Boolean);
8782
8854
  console.log(
8783
8855
  formatJson({ count: jsonData.length, individuals: jsonData })
8784
8856
  );
@@ -8788,6 +8860,7 @@ var GedcomRepl = class {
8788
8860
  `)
8789
8861
  );
8790
8862
  allRelatives.forEach((indi) => {
8863
+ if (!indi) return;
8791
8864
  const name = cleanGedcomName(indi.NAME?.toValue());
8792
8865
  const lifespan = formatLifespan(
8793
8866
  indi.BIRT?.DATE?.toValue(),
@@ -8795,7 +8868,7 @@ var GedcomRepl = class {
8795
8868
  );
8796
8869
  console.log(
8797
8870
  formatListItem(
8798
- `${formatId(indi.id)} ${formatName(name)} ${lifespan}`
8871
+ `${formatId(indi.id ?? "")} ${formatName(name)} ${lifespan}`
8799
8872
  )
8800
8873
  );
8801
8874
  });
@@ -8810,19 +8883,19 @@ var GedcomRepl = class {
8810
8883
  const version = header?.GEDC?.VERS?.value || "Unknown";
8811
8884
  const individuals = this.context.tree.indis();
8812
8885
  const families = this.context.tree.fams();
8813
- individuals.forEach((indi) => {
8886
+ individuals?.forEach((indi) => {
8814
8887
  if (!indi.NAME?.toValue()) {
8815
8888
  warnings.push(`Individual ${indi.id} is missing a name`);
8816
8889
  }
8817
8890
  });
8818
8891
  let missingBirthDates = 0;
8819
- individuals.forEach((indi) => {
8892
+ individuals?.forEach((indi) => {
8820
8893
  if (!indi.BIRT?.DATE?.toValue()) {
8821
8894
  missingBirthDates++;
8822
8895
  }
8823
8896
  });
8824
8897
  let missingDeathDates = 0;
8825
- individuals.forEach((indi) => {
8898
+ individuals?.forEach((indi) => {
8826
8899
  if (indi.DEAT && !indi.DEAT.DATE?.toValue()) {
8827
8900
  missingDeathDates++;
8828
8901
  }
@@ -8830,15 +8903,18 @@ var GedcomRepl = class {
8830
8903
  const seenIds = /* @__PURE__ */ new Set();
8831
8904
  const duplicateIds = [];
8832
8905
  const checkDuplicates = (item) => {
8906
+ if (!item.id) {
8907
+ return;
8908
+ }
8833
8909
  if (seenIds.has(item.id)) {
8834
8910
  duplicateIds.push(item.id);
8835
8911
  errors.push(`Duplicate ID found: ${item.id}`);
8836
8912
  }
8837
8913
  seenIds.add(item.id);
8838
8914
  };
8839
- individuals.forEach(checkDuplicates);
8840
- families.forEach(checkDuplicates);
8841
- families.forEach((fam) => {
8915
+ individuals?.forEach(checkDuplicates);
8916
+ families?.forEach(checkDuplicates);
8917
+ families?.forEach((fam) => {
8842
8918
  const husb = fam.HUSB?.value;
8843
8919
  const wife = fam.WIFE?.value;
8844
8920
  if (!husb && !wife) {
@@ -8856,7 +8932,7 @@ var GedcomRepl = class {
8856
8932
  }
8857
8933
  });
8858
8934
  if (isStrict) {
8859
- individuals.forEach((indi) => {
8935
+ individuals?.forEach((indi) => {
8860
8936
  const birthDate = indi.BIRT?.DATE?.toValue();
8861
8937
  const deathDate = indi.DEAT?.DATE?.toValue();
8862
8938
  if (birthDate && birthDate.includes("INVALID")) {
@@ -8980,7 +9056,7 @@ function registerRelativesCommand(program2) {
8980
9056
  try {
8981
9057
  const content = readGedcomFile(file);
8982
9058
  const { gedcom: tree } = parser_default.parse(content);
8983
- const individual = tree.indi(id);
9059
+ const individual = id && tree.indi(id);
8984
9060
  if (!individual) {
8985
9061
  console.error(formatError(`Individual ${id} not found`));
8986
9062
  process.exit(1);
@@ -8995,6 +9071,7 @@ function registerRelativesCommand(program2) {
8995
9071
  if (depth > maxDepth) return;
8996
9072
  const parents = indi.getParents();
8997
9073
  parents?.forEach((parent) => {
9074
+ if (!parent?.id) return;
8998
9075
  relatives.add(parent.id);
8999
9076
  getAncestors(parent, depth + 1);
9000
9077
  });
@@ -9006,6 +9083,7 @@ function registerRelativesCommand(program2) {
9006
9083
  if (depth > maxDepth) return;
9007
9084
  const children = indi.getChildren();
9008
9085
  children?.forEach((child) => {
9086
+ if (!child?.id) return;
9009
9087
  relatives.add(child.id);
9010
9088
  getDescendants(child, depth + 1);
9011
9089
  });
@@ -9016,25 +9094,45 @@ function registerRelativesCommand(program2) {
9016
9094
  if (options.output) {
9017
9095
  const newContent = createSubsetGedcom(tree, allRelatives);
9018
9096
  writeFileSync(options.output, newContent, "utf-8");
9019
- console.log(formatSuccess(`Saved ${allRelatives.length} individuals to ${options.output}`));
9097
+ console.log(
9098
+ formatSuccess(
9099
+ `Saved ${allRelatives.length} individuals to ${options.output}`
9100
+ )
9101
+ );
9020
9102
  } else if (options.json) {
9021
- const jsonData = allRelatives.map((indi) => ({
9022
- id: indi.id,
9023
- name: cleanGedcomName(indi.NAME?.toValue()),
9024
- birthDate: indi.BIRT?.DATE?.toValue() || null,
9025
- deathDate: indi.DEAT?.DATE?.toValue() || null
9026
- }));
9027
- console.log(formatJson({ count: jsonData.length, individuals: jsonData }));
9103
+ const jsonData = allRelatives.map(
9104
+ (indi) => indi && {
9105
+ id: indi.id,
9106
+ name: cleanGedcomName(indi.NAME?.toValue()),
9107
+ birthDate: indi.BIRT?.DATE?.toValue() || null,
9108
+ deathDate: indi.DEAT?.DATE?.toValue() || null
9109
+ }
9110
+ ).filter(Boolean);
9111
+ console.log(
9112
+ formatJson({
9113
+ count: jsonData.length,
9114
+ individuals: jsonData
9115
+ })
9116
+ );
9028
9117
  } else {
9029
- console.log(formatHeader(`Found ${allRelatives.length} relative(s)
9030
- `));
9118
+ console.log(
9119
+ formatHeader(
9120
+ `Found ${allRelatives.length} relative(s)
9121
+ `
9122
+ )
9123
+ );
9031
9124
  allRelatives.forEach((indi) => {
9125
+ if (!indi) return;
9032
9126
  const name = cleanGedcomName(indi.NAME?.toValue());
9033
9127
  const lifespan = formatLifespan(
9034
9128
  indi.BIRT?.DATE?.toValue(),
9035
9129
  indi.DEAT?.DATE?.toValue()
9036
9130
  );
9037
- console.log(formatListItem(`${formatId(indi.id)} ${formatName(name)} ${lifespan}`));
9131
+ console.log(
9132
+ formatListItem(
9133
+ `${formatId(indi.id ?? "")} ${formatName(name)} ${lifespan}`
9134
+ )
9135
+ );
9038
9136
  });
9039
9137
  }
9040
9138
  } catch (error) {
@@ -9052,7 +9150,9 @@ function createSubsetGedcom(tree, individuals) {
9052
9150
  individuals.forEach((indi) => {
9053
9151
  const raw = indi.raw();
9054
9152
  if (raw) {
9055
- lines.push(...raw.split("\n").filter((line) => line.trim()));
9153
+ lines.push(
9154
+ ...raw.split("\n").filter((line) => line.trim())
9155
+ );
9056
9156
  }
9057
9157
  });
9058
9158
  lines.push("0 TRLR");
@@ -9072,20 +9172,22 @@ function registerValidateCommand(program2) {
9072
9172
  const individuals = tree.indis();
9073
9173
  const families = tree.fams();
9074
9174
  let missingNames = 0;
9075
- individuals.forEach((indi) => {
9175
+ individuals?.forEach((indi) => {
9076
9176
  if (!indi.NAME?.toValue()) {
9077
9177
  missingNames++;
9078
- warnings.push(`Individual ${indi.id} is missing a name`);
9178
+ warnings.push(
9179
+ `Individual ${indi.id} is missing a name`
9180
+ );
9079
9181
  }
9080
9182
  });
9081
9183
  let missingBirthDates = 0;
9082
- individuals.forEach((indi) => {
9184
+ individuals?.forEach((indi) => {
9083
9185
  if (!indi.BIRT?.DATE?.toValue()) {
9084
9186
  missingBirthDates++;
9085
9187
  }
9086
9188
  });
9087
9189
  let missingDeathDates = 0;
9088
- individuals.forEach((indi) => {
9190
+ individuals?.forEach((indi) => {
9089
9191
  if (indi.DEAT && !indi.DEAT.DATE?.toValue()) {
9090
9192
  missingDeathDates++;
9091
9193
  }
@@ -9099,22 +9201,28 @@ function registerValidateCommand(program2) {
9099
9201
  }
9100
9202
  seenIds.add(item.id);
9101
9203
  };
9102
- individuals.forEach(checkDuplicates);
9103
- families.forEach(checkDuplicates);
9104
- families.forEach((fam) => {
9204
+ individuals?.forEach(checkDuplicates);
9205
+ families?.forEach(checkDuplicates);
9206
+ families?.forEach((fam) => {
9105
9207
  const husb = fam.HUSB?.value;
9106
9208
  const wife = fam.WIFE?.value;
9107
9209
  if (!husb && !wife) {
9108
- warnings.push(`Family ${fam.id} has no husband or wife`);
9210
+ warnings.push(
9211
+ `Family ${fam.id} has no husband or wife`
9212
+ );
9109
9213
  }
9110
9214
  if (husb && !tree.indi(husb)) {
9111
- errors.push(`Family ${fam.id} references non-existent husband ${husb}`);
9215
+ errors.push(
9216
+ `Family ${fam.id} references non-existent husband ${husb}`
9217
+ );
9112
9218
  }
9113
9219
  if (wife && !tree.indi(wife)) {
9114
- errors.push(`Family ${fam.id} references non-existent wife ${wife}`);
9220
+ errors.push(
9221
+ `Family ${fam.id} references non-existent wife ${wife}`
9222
+ );
9115
9223
  }
9116
9224
  });
9117
- individuals.forEach((indi) => {
9225
+ individuals?.forEach((indi) => {
9118
9226
  const birthDate = indi.BIRT?.DATE?.toValue();
9119
9227
  const deathDate = indi.DEAT?.DATE?.toValue();
9120
9228
  if (birthDate && birthDate.includes("INVALID")) {
@@ -9134,14 +9242,24 @@ function registerValidateCommand(program2) {
9134
9242
  console.log(formatJson(result));
9135
9243
  } else {
9136
9244
  if (result.valid) {
9137
- console.log(formatSuccess(`Valid GEDCOM ${version} file`));
9245
+ console.log(
9246
+ formatSuccess(`Valid GEDCOM ${version} file`)
9247
+ );
9138
9248
  } else {
9139
- console.log(formatError(`Invalid GEDCOM file - ${errors.length} error(s) found`));
9249
+ console.log(
9250
+ formatError(
9251
+ `Invalid GEDCOM file - ${errors.length} error(s) found`
9252
+ )
9253
+ );
9140
9254
  }
9141
9255
  console.log();
9142
9256
  console.log(formatHeader("Validation Summary"));
9143
- console.log(`${formatError("Errors:")} ${formatCount(errors.length)}`);
9144
- console.log(`${formatWarning("Warnings:")} ${formatCount(warnings.length)}`);
9257
+ console.log(
9258
+ `${formatError("Errors:")} ${formatCount(errors.length)}`
9259
+ );
9260
+ console.log(
9261
+ `${formatWarning("Warnings:")} ${formatCount(warnings.length)}`
9262
+ );
9145
9263
  if (errors.length > 0) {
9146
9264
  console.log();
9147
9265
  console.log(formatHeader("Errors"));
@@ -9149,32 +9267,62 @@ function registerValidateCommand(program2) {
9149
9267
  console.log(formatListItem(formatError(error)));
9150
9268
  });
9151
9269
  if (errors.length > 10) {
9152
- console.log(formatListItem(`... and ${errors.length - 10} more errors`));
9270
+ console.log(
9271
+ formatListItem(
9272
+ `... and ${errors.length - 10} more errors`
9273
+ )
9274
+ );
9153
9275
  }
9154
9276
  }
9155
9277
  if (warnings.length > 0) {
9156
9278
  console.log();
9157
9279
  console.log(formatHeader("Warnings"));
9158
9280
  if (missingBirthDates > 0) {
9159
- console.log(formatListItem(formatWarning(`Missing birth dates: ${missingBirthDates} individuals`)));
9281
+ console.log(
9282
+ formatListItem(
9283
+ formatWarning(
9284
+ `Missing birth dates: ${missingBirthDates} individuals`
9285
+ )
9286
+ )
9287
+ );
9160
9288
  }
9161
9289
  if (missingDeathDates > 0) {
9162
- console.log(formatListItem(formatWarning(`Missing death dates: ${missingDeathDates} individuals`)));
9290
+ console.log(
9291
+ formatListItem(
9292
+ formatWarning(
9293
+ `Missing death dates: ${missingDeathDates} individuals`
9294
+ )
9295
+ )
9296
+ );
9163
9297
  }
9164
9298
  if (duplicateIds.length > 0) {
9165
- console.log(formatListItem(formatWarning(`Duplicate IDs: ${duplicateIds.length}`)));
9299
+ console.log(
9300
+ formatListItem(
9301
+ formatWarning(
9302
+ `Duplicate IDs: ${duplicateIds.length}`
9303
+ )
9304
+ )
9305
+ );
9166
9306
  }
9167
- const otherWarnings = warnings.filter((w) => !w.includes("birth") && !w.includes("death"));
9307
+ const otherWarnings = warnings.filter(
9308
+ (w) => !w.includes("birth") && !w.includes("death")
9309
+ );
9168
9310
  otherWarnings.slice(0, 5).forEach((warning) => {
9169
9311
  console.log(formatListItem(formatWarning(warning)));
9170
9312
  });
9171
9313
  if (otherWarnings.length > 5) {
9172
- console.log(formatListItem(`... and ${otherWarnings.length - 5} more warnings`));
9314
+ console.log(
9315
+ formatListItem(
9316
+ `... and ${otherWarnings.length - 5} more warnings`
9317
+ )
9318
+ );
9173
9319
  }
9174
9320
  }
9175
9321
  if (options.fix) {
9176
9322
  console.log();
9177
- console.log(formatWarning("Fix option is not yet implemented"));
9323
+ console.log(
9324
+ formatWarning("Fix option is not yet implemented")
9325
+ );
9178
9326
  }
9179
9327
  }
9180
9328
  if (!result.valid) {