@bobfrankston/gcards 0.1.31 → 0.1.32

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 (3) hide show
  1. package/gcards.ts +2 -2
  2. package/gfix.ts +49 -28
  3. package/package.json +2 -2
package/gcards.ts CHANGED
@@ -129,7 +129,7 @@ async function refreshAccessToken(): Promise<string> {
129
129
  return currentAccessToken;
130
130
  }
131
131
 
132
- /** Get sort key from contact: fileAs (First Last) > displayNameLastFirst (Last, First) > displayName */
132
+ /** Get sort key from contact: fileAs (Last, First) > displayNameLastFirst (Last, First) > displayName */
133
133
  function getSortKey(person: GooglePerson): string {
134
134
  const fileAs = person.fileAses?.[0]?.value;
135
135
  if (fileAs) return fileAs;
@@ -139,7 +139,7 @@ function getSortKey(person: GooglePerson): string {
139
139
  }
140
140
 
141
141
  function saveIndex(paths: UserPaths, index: ContactIndex): void {
142
- // Sort contacts by sortKey (fileAs='First Last' > displayNameLastFirst='Last, First' > displayName)
142
+ // Sort contacts by sortKey (fileAs='Last, First' > displayNameLastFirst='Last, First' > displayName)
143
143
  const sortedContacts: Record<string, IndexEntry> = {};
144
144
  const entries = Object.entries(index.contacts);
145
145
  entries.sort((a, b) => {
package/gfix.ts CHANGED
@@ -395,7 +395,7 @@ async function runBirthdayExtract(user: string, mode: 'inspect' | 'apply'): Prom
395
395
  }
396
396
  }
397
397
 
398
- /** Fix fileAs fields: normalize "Last, First" to "First Last"
398
+ /** Fix fileAs fields: normalize to "Last, First" format and clean spurious characters
399
399
  * Also fix displayNameLastFirst to proper "Last, First" format */
400
400
  async function runFileAsFix(user: string, mode: 'inspect' | 'apply'): Promise<void> {
401
401
  const paths = getUserPaths(user);
@@ -438,42 +438,63 @@ async function runFileAsFix(user: string, mode: 'inspect' | 'apply'): Promise<vo
438
438
  }
439
439
  }
440
440
 
441
- // Fix fileAs: "Last, First" -> "First Last"
441
+ // Fix fileAs: should be "Last, First" format
442
442
  if (contact.fileAses?.length) {
443
443
  const fileAs = contact.fileAses[0].value || '';
444
+ const expectedFileAs = givenName && familyName ? `${familyName}, ${givenName}` : '';
444
445
 
445
- // Check if fileAs is "Last, First" format
446
- const commaMatch = fileAs.match(/^(.+),\s*(.+)$/);
447
- if (commaMatch) {
448
- const [, lastPart, firstPart] = commaMatch;
449
-
450
- // If we have givenName and familyName, we can auto-fix
451
- if (givenName && familyName) {
452
- // Verify the parts match (case-insensitive)
453
- const firstMatches = firstPart.toLowerCase().startsWith(givenName.toLowerCase()) ||
454
- givenName.toLowerCase().startsWith(firstPart.toLowerCase());
455
- const lastMatches = lastPart.toLowerCase() === familyName.toLowerCase();
456
-
457
- if (firstMatches && lastMatches) {
458
- const newFileAs = `${givenName} ${familyName}`;
459
- console.log(` ${displayName}: fileAs "${fileAs}" -> "${newFileAs}"`);
460
-
461
- if (mode === 'apply') {
462
- contact.fileAses[0].value = newFileAs;
463
- modified = true;
464
- }
465
- fileAsFixedCount++;
466
- } else {
467
- // Mismatch - add to review
446
+ // Check for malformed fileAs (e.g., "-, Eric Giler", "- Eric", etc.)
447
+ const malformedMatch = fileAs.match(/^[-,\s]+(.+)$/);
448
+ if (malformedMatch && givenName && familyName) {
449
+ // Spurious dash/comma prefix
450
+ console.log(` ${displayName}: fileAs "${fileAs}" -> "${expectedFileAs}" (cleaned malformed)`);
451
+ if (mode === 'apply') {
452
+ contact.fileAses[0].value = expectedFileAs;
453
+ modified = true;
454
+ }
455
+ fileAsFixedCount++;
456
+ } else if (expectedFileAs && fileAs !== expectedFileAs) {
457
+ // Check if fileAs is "First Last" format (wrong) or just doesn't match
458
+ const isFirstLast = fileAs === `${givenName} ${familyName}`;
459
+ const commaMatch = fileAs.match(/^(.+),\s*(.+)$/);
460
+
461
+ if (isFirstLast) {
462
+ // Wrong format: "First Last" instead of "Last, First"
463
+ console.log(` ${displayName}: fileAs "${fileAs}" -> "${expectedFileAs}" (wrong format)`);
464
+ if (mode === 'apply') {
465
+ contact.fileAses[0].value = expectedFileAs;
466
+ modified = true;
467
+ }
468
+ fileAsFixedCount++;
469
+ } else if (commaMatch) {
470
+ const [, lastPart, firstPart] = commaMatch;
471
+ // Verify the parts match
472
+ const firstMatches = firstPart.trim().toLowerCase() === givenName.toLowerCase();
473
+ const lastMatches = lastPart.trim().toLowerCase() === familyName.toLowerCase();
474
+
475
+ if (!firstMatches || !lastMatches) {
476
+ // Parts don't match - needs review
468
477
  needsReview.push({ givenName, familyName, fileAs, jsonFile: file });
469
478
  skippedCount++;
470
479
  }
471
- } else {
472
- // No familyName - need manual review
473
- needsReview.push({ givenName: givenName || displayName, familyName, fileAs, jsonFile: file });
480
+ } else if (fileAs && !commaMatch) {
481
+ // Not in "Last, First" format at all
482
+ needsReview.push({ givenName, familyName, fileAs, jsonFile: file });
474
483
  skippedCount++;
475
484
  }
476
485
  }
486
+ } else if (givenName && familyName) {
487
+ // Missing fileAs - add it
488
+ const expectedFileAs = `${familyName}, ${givenName}`;
489
+ console.log(` ${displayName}: Adding fileAs "${expectedFileAs}"`);
490
+ if (mode === 'apply') {
491
+ contact.fileAses = [{
492
+ metadata: { primary: true },
493
+ value: expectedFileAs
494
+ }];
495
+ modified = true;
496
+ }
497
+ fileAsFixedCount++;
477
498
  }
478
499
 
479
500
  if (modified) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/gcards",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "Google Contacts cleanup and management tool",
5
5
  "type": "module",
6
6
  "main": "gcards.ts",
@@ -29,7 +29,7 @@
29
29
  "author": "Bob Frankston",
30
30
  "license": "MIT",
31
31
  "devDependencies": {
32
- "@types/node": "^22.10.1",
32
+ "@types/node": "^25.0.3",
33
33
  "tsx": "^4.21.0"
34
34
  },
35
35
  "dependencies": {