diff --git a/package.json b/package.json index 49c1b9a0..8ef6d298 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "galactus": "^2.0.2", "graceful-fs": "^4.2.11", "junk": "^4.0.1", - "parse-author": "^2.0.0", "plist": "^3.1.0", "resedit": "^2.0.3", "semver": "^7.7.2", @@ -60,7 +59,6 @@ "@types/debug": "^4.1.12", "@types/graceful-fs": "^4.1.9", "@types/node": "~22.10.7", - "@types/parse-author": "^2.0.2", "@types/plist": "^3.0.5", "@types/semver": "^7.7.0", "@types/yargs-parser": "^21.0.3", diff --git a/src/infer.ts b/src/infer.ts index 4331553e..101de7f4 100644 --- a/src/infer.ts +++ b/src/infer.ts @@ -1,4 +1,3 @@ -import parseAuthor from 'parse-author'; import path from 'node:path'; import { createRequire } from 'node:module'; import { promisifiedGracefulFs } from './util.js'; @@ -16,6 +15,43 @@ type PackageJSON = { const ELECTRON_PACKAGES = ['electron', 'electron-nightly'] as const; +// Inlined from parse-author / author-regex (MIT, Jon Schlinkert) +function authorRegex() { + return /^\s*([^<(]*?)\s*([<(]([^>)]*?)[>)])?\s*([<(]([^>)]*?)[>)])*\s*$/; +} + +export function parseAuthor(str: string) { + if (typeof str !== 'string') { + throw new TypeError('expected author to be a string'); + } + + if (!str || !/\w/.test(str)) { + return {}; + } + + const match = ([] as (string | undefined)[]).concat.apply([], authorRegex().exec(str) || []); + const author: { name?: string; email?: string; url?: string } = {}; + + if (match[1]) { + author.name = match[1]; + } + + for (let i = 2; i < match.length; i++) { + const val = match[i]; + + if (i % 2 === 0 && val && match[i + 1]) { + if (val.charAt(0) === '<') { + author.email = match[i + 1]; + i++; + } else if (val.charAt(0) === '(') { + author.url = match[i + 1]; + i++; + } + } + } + return author; +} + async function* walkPackageJSONs(dir: string): AsyncGenerator<{ src: string; pkg: PackageJSON }> { let prev: string | undefined; let cur = path.resolve(dir); diff --git a/test/infer.spec.ts b/test/infer.spec.ts index 6714e695..04d7aa8c 100644 --- a/test/infer.spec.ts +++ b/test/infer.spec.ts @@ -2,12 +2,52 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import url from 'node:url'; -import { getMetadataFromPackageJSON } from '../src/infer.js'; +import { getMetadataFromPackageJSON, parseAuthor } from '../src/infer.js'; import { Options } from '../src/types.js'; import semver from 'semver'; import config from './config.json' with { type: 'json' }; import { beforeEach, describe, it, expect } from 'vitest'; +describe('parseAuthor', () => { + it('parses name only', () => { + expect(parseAuthor('Foo Bar')).toEqual({ name: 'Foo Bar' }); + }); + + it('parses name and email', () => { + expect(parseAuthor('Foo Bar ')).toEqual({ + name: 'Foo Bar', + email: 'foo.bar@example.com', + }); + }); + + it('parses name, email, and url', () => { + expect(parseAuthor('Foo Bar (https://example.com)')).toEqual({ + name: 'Foo Bar', + email: 'foo.bar@example.com', + url: 'https://example.com', + }); + }); + + it('parses email and url without a name', () => { + expect(parseAuthor(' (https://example.com)')).toEqual({ + email: 'foo.bar@example.com', + url: 'https://example.com', + }); + }); + + it('returns an empty object for an empty string', () => { + expect(parseAuthor('')).toEqual({}); + }); + + it('returns an empty object for whitespace-only input', () => { + expect(parseAuthor(' ')).toEqual({}); + }); + + it('throws if input is not a string', () => { + expect(() => parseAuthor(123 as unknown as string)).toThrow(TypeError); + }); +}); + describe('getMetadataFromPackageJSON', () => { it.each([ ['electron-nightly', 'infer-electron-nightly'], diff --git a/yarn.lock b/yarn.lock index e89d6be2..8e429837 100644 --- a/yarn.lock +++ b/yarn.lock @@ -76,7 +76,6 @@ __metadata: "@types/debug": "npm:^4.1.12" "@types/graceful-fs": "npm:^4.1.9" "@types/node": "npm:~22.10.7" - "@types/parse-author": "npm:^2.0.2" "@types/plist": "npm:^3.0.5" "@types/semver": "npm:^7.7.0" "@types/yargs-parser": "npm:^21.0.3" @@ -91,7 +90,6 @@ __metadata: oxfmt: "npm:^0.44.0" oxlint: "npm:^1.59.0" oxlint-tsgolint: "npm:^0.20.0" - parse-author: "npm:^2.0.0" plist: "npm:^3.1.0" resedit: "npm:^2.0.3" semver: "npm:^7.7.2" @@ -1185,13 +1183,6 @@ __metadata: languageName: node linkType: hard -"@types/parse-author@npm:^2.0.2": - version: 2.0.3 - resolution: "@types/parse-author@npm:2.0.3" - checksum: 10c0/a572efe529d410c0550bbf62c3060c06d441edcebf80a50f20d3cd7107bd6ae71bcd7bebb43993e9b1394afab253eedc2f2ae70fbfd8c96df444235513b7ba42 - languageName: node - linkType: hard - "@types/plist@npm:^3.0.5": version: 3.0.5 resolution: "@types/plist@npm:3.0.5" @@ -1403,13 +1394,6 @@ __metadata: languageName: node linkType: hard -"author-regex@npm:^1.0.0": - version: 1.0.0 - resolution: "author-regex@npm:1.0.0" - checksum: 10c0/3f3a5ad6660be010bd5b979fac180f435bd9615e81db2b1cdac081eb3f639461f6c3927ced956e377a5c91cc789e3de3a3e900e296c1423971043c8fd8be0b73 - languageName: node - linkType: hard - "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -2836,15 +2820,6 @@ __metadata: languageName: node linkType: hard -"parse-author@npm:^2.0.0": - version: 2.0.0 - resolution: "parse-author@npm:2.0.0" - dependencies: - author-regex: "npm:^1.0.0" - checksum: 10c0/8b4c19523588a4271c89f64e8167be7c80b4765059c865d38a996c37d080c7e5123e3e2d07d47290891628ad27f4dfb692e3b61156c52fcc0af6cdce3ed57afd - languageName: node - linkType: hard - "path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1"