diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 37c5d12..dfc99ab 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -25,4 +25,6 @@ jobs: node-version: 22.x cache: 'npm' - run: npm install + - run: npm run lint + - run: npm run type-check - run: npm run test:ci diff --git a/src/cli/parseCli.ts b/src/cli/parseCli.ts index e932667..128eeb9 100644 --- a/src/cli/parseCli.ts +++ b/src/cli/parseCli.ts @@ -102,10 +102,16 @@ export function parseCliArgs( } // Collect the headers passed via cli - const headers = {}; - for (const header of args.headers) { - const [key, value] = header.split(":") as Record; - headers[key.trim()] = value.trim(); + const headers: Record = {}; + if (args.headers && Array.isArray(args.headers)) { + for (const header of args.headers) { + if (typeof header === "string") { + const [key, value] = header.split(":") as [PotHeaders, string]; + if (key && value) { + headers[key.trim()] = value.trim(); + } + } + } } const parsedArgs: Args = { @@ -169,11 +175,11 @@ export function parseJsonArgs( const currentWorkingDirectory = process.cwd(); const slug = path.basename(path.resolve(currentWorkingDirectory)); - let scriptName: string; + let scriptName: string | undefined; if (args.scriptName) { - scriptName = args.scriptName.split(",").map((s) => s.trim()); - if (scriptName.length === 1) { - scriptName = scriptName[0]; + const scripts = args.scriptName.toString().split(",").map((s) => s.trim()); + if (scripts.length === 1) { + scriptName = scripts[0]; } } diff --git a/src/extractors/auditStrings.ts b/src/extractors/auditStrings.ts index a1badb9..77d119d 100644 --- a/src/extractors/auditStrings.ts +++ b/src/extractors/auditStrings.ts @@ -15,7 +15,7 @@ export function audit(args: Args, translationsUnion: SetOfBlocks) { //if there are no errors, we can remove the audit.log file try { unlinkSync(path.join(args.paths.cwd, "audit.log")); - } catch (error) { + } catch (_error) { //ignore } } else { diff --git a/src/extractors/headers.ts b/src/extractors/headers.ts index a5ddd77..292be8a 100644 --- a/src/extractors/headers.ts +++ b/src/extractors/headers.ts @@ -1,14 +1,12 @@ -import path from "node:path"; -import { SetOfBlocks } from "gettext-merger"; -import { boolean } from "yargs"; -import type PackageI18n from "../assets/package-i18n.js"; -import { modulePath } from "../const.js"; -import { getEncodingCharset } from "../fs/fs.js"; -import type { Args, I18nHeaders, PotHeaders } from "../types.js"; -import { getPkgJsonData } from "../utils/common.js"; -import { buildBlock } from "../utils/extractors.js"; -import { extractCssThemeData } from "./css.js"; -import { extractPhpPluginData } from "./php.js"; +import path from 'node:path' +import { SetOfBlocks } from 'gettext-merger' +import { modulePath } from '../const.js' +import { getEncodingCharset } from '../fs/fs.js' +import type { Args, AuthorData, I18nHeaders, PotHeaders } from '../types.js' +import { getPkgJsonData } from '../utils/common.js' +import { buildBlock } from '../utils/extractors.js' +import { extractCssThemeData } from './css.js' +import { extractPhpPluginData } from './php.js' /** * Checks if required fields are missing and logs a clear error message @@ -20,14 +18,20 @@ function validateRequiredFields( headerData: I18nHeaders, debug: boolean, ): boolean { - const requiredFields = [ - { key: "slug", name: "Plugin/Theme slug", placeholder: "PLUGIN NAME" }, - { key: "author", name: "Author name", placeholder: "AUTHOR" }, - { key: "version", name: "Version", placeholder: "" }, - { key: "email", name: "Author email", placeholder: "AUTHOR EMAIL" }, - { key: "xDomain", name: "Text domain", placeholder: "PLUGIN TEXTDOMAIN" }, - ]; - + // Define the required fields with strict key types + const requiredFields: { + key: keyof I18nHeaders; + name: string; + placeholder: string; + }[] = [ + { key: "slug", name: "Plugin/Theme slug", placeholder: "PLUGIN NAME" }, + { key: "author", name: "Author name", placeholder: "AUTHOR" }, + { key: "version", name: "Version", placeholder: "" }, + { key: "email", name: "Author email", placeholder: "AUTHOR EMAIL" }, + { key: "xDomain", name: "Text domain", placeholder: "PLUGIN TEXTDOMAIN" }, + ]; + + // Filter out the missing or default fields const missingFields = requiredFields.filter( (field) => !headerData[field.key] || @@ -83,11 +87,9 @@ function validateRequiredFields( * @returns an object with name, email, and website * @param authorData */ -function extractAuthorData(authorData: string | object): { - name: string; - email?: string; - website?: string; -} { +function extractAuthorData( + authorData: string | AuthorData, +): AuthorData | undefined { // Default result with placeholder values const defaultResult = { name: "AUTHOR", email: "AUTHOR EMAIL" }; @@ -129,41 +131,46 @@ function extractAuthorData(authorData: string | object): { * @param pkgJsonData The package.json data object * @returns Author data with name, email and website */ -export function getAuthorFromPackage(pkgJsonData: PackageI18n): { - name: string; - email?: string; - website?: string; -} { +export function getAuthorFromPackage( + pkgJsonData: Record, +): AuthorData { // Check multiple possible locations for author information - const locations = [ + const fields = [ "author", // Standard author field "authors", // Some packages use authors (plural) "contributors", // Try contributors if no author - "maintainers", // Try maintainers as last resort - ]; + "maintainers", // Try maintainers as a last resort + ] as string[]; // Try each location in order - for (const location of locations) { - if (pkgJsonData[location]) { - let authorData: { - name: string; - email?: string; - website?: string; - }; - if (typeof pkgJsonData[location] === "string") { - authorData = extractAuthorData(pkgJsonData[location]); - } else if (typeof pkgJsonData[location] === "object") { - for (const author of pkgJsonData[location]) { + for (const field of fields) { + const value = pkgJsonData[field]; + if (value) { + let authorData: AuthorData | undefined; + + if (typeof value === "string") { + authorData = extractAuthorData(value); + } else if (Array.isArray(value)) { + for (const author of value) { if (!author) continue; - authorData = extractAuthorData(author); - if (authorData) break; + if ( + typeof author === "string" || + (typeof author === "object") + ) { + authorData = extractAuthorData(author as string | AuthorData); + if (authorData) break; + } } + } else if (typeof value === "object") { + // Handle single object author field + authorData = extractAuthorData(value as AuthorData); } + if ( authorData?.name !== "AUTHOR" || authorData?.email !== "AUTHOR EMAIL" ) { - return authorData; + return authorData as AuthorData; // Returns the valid author data found } } } @@ -187,9 +194,9 @@ function consolidateUserHeaderData(args: Args): I18nHeaders { "authors", "contributors", "maintainers", - ) as Record<[keyof PackageI18n], string>; + ); // get author data from package.json - const pkgAuthor = getAuthorFromPackage(pkgJsonData); + const pkgAuthor = getAuthorFromPackage(pkgJsonData as unknown as Record); // get the current directory name as slug const currentDir = path @@ -212,16 +219,16 @@ function consolidateUserHeaderData(args: Args): I18nHeaders { return { ...args.headers, - name: args.headers?.name || slug, + name: args.headers?.name?.toString() || slug, author: authorName, - authorString: authorString, // this is the author with email address in this format: author + authorString: authorString, // this is the author + email address in this format: author slug, email, bugs, license: args.headers?.license || "gpl-2.0 or later", - version: args.headers?.version || pkgJsonData.version || "0.0.1", + version: args.headers?.version || (pkgJsonData.version as string) || "0.0.1", language: "en", - xDomain: args.headers?.textDomain || slug, + xDomain: args.headers?.textDomain?.toString() || slug, }; } @@ -252,7 +259,6 @@ export async function generateHeader( // Validate required fields - exit early if validation fails if (!validateRequiredFields(headerData, args.debug)) { process.exit(1); // Exit with error code - return null; // This is never reached but helps with TypeScript } return { diff --git a/src/extractors/json.ts b/src/extractors/json.ts index fcec59a..a7879b9 100644 --- a/src/extractors/json.ts +++ b/src/extractors/json.ts @@ -16,7 +16,7 @@ import { JsonSchemaExtractor } from "./schema.js"; export async function parseJsonFile(opts: { fileContent: string; filename: "block.json" | "theme.json"; -}): Promise { +}): Promise { const isTheme = opts.filename === "theme.json"; const schema: { url: string; fallback: I18nSchema } = { url: isTheme diff --git a/src/extractors/schema.ts b/src/extractors/schema.ts index c94de57..10e4dae 100644 --- a/src/extractors/schema.ts +++ b/src/extractors/schema.ts @@ -1,24 +1,23 @@ import type { Block } from "gettext-merger"; -import type BlockI18n from "../assets/block-i18n.js"; import * as blocki18n from "../assets/block-i18n.js"; -import type ThemeI18n from "../assets/theme-i18n.js"; import * as themei18n from "../assets/theme-i18n.js"; import type { I18nSchema } from "../types.js"; /** * Extracts strings from JSON files using the I18n schema. */ +// biome-ignore lint/complexity/noStaticOnlyClass: export class JsonSchemaExtractor { private static schemaCache: { [url: string]: I18nSchema } = {}; /** Theme */ static themeJsonSource = "http://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json"; - static themeJsonFallback = themei18n as ThemeI18n; + static themeJsonFallback = themei18n; /** Block */ static blockJsonSource = "http://develop.svn.wordpress.org/trunk/src/wp-includes/block-i18n.json"; - static blockJsonFallback = blocki18n as BlockI18n; + static blockJsonFallback = blocki18n; /** * Load the schema from the specified URL, with a fallback URL if needed. @@ -36,10 +35,7 @@ export class JsonSchemaExtractor { } try { - console.log(`\n[i] Loading schema from ${url}`); const response = await fetch(url, { - responseType: "json", - accept: "application/json", headers: { "Access-Control-Allow-Origin": "*", }, @@ -56,12 +52,11 @@ export class JsonSchemaExtractor { return fallback; } - console.log("Schema loaded successfully"); JsonSchemaExtractor.schemaCache[url] = response; return response; } catch (error) { console.error( - `\nFailed to load schema from ${url}. Using fallback. Error: ${error.message}`, + `\nFailed to load schema from ${url}. Using fallback. Error: ${(error as Error).message}`, ); JsonSchemaExtractor.schemaCache[url] = fallback; return fallback; @@ -110,7 +105,7 @@ export class JsonSchemaExtractor { options, ); } catch (error) { - console.error(`Error parsing JSON: ${error.message}`); + console.error(`Error parsing JSON: ${(error as Error).message}`); return; } } @@ -134,7 +129,7 @@ export class JsonSchemaExtractor { }, ): Block[] | undefined { const { filename = "block.json", addReferences = false } = options; - const translations = []; + const translations: Block[] = []; /** * Recursive function to extract translatable strings @@ -142,7 +137,11 @@ export class JsonSchemaExtractor { * @param {*} currentSchema - The current node in the schema * @param {Array} path - The current path in the JSON */ - function extract(currentJson, currentSchema, path = []) { + function extract( + currentJson: Record, + currentSchema: I18nSchema, + path: string[] = [], + ) { // If either is null or undefined, there's nothing to do if (!currentJson || !currentSchema) return; @@ -161,7 +160,7 @@ export class JsonSchemaExtractor { // It's a string - add it to translations addTranslation( currentJson[key], - currentSchema[key], + currentSchema[key] as string, filename, addReferences, ); @@ -179,10 +178,15 @@ export class JsonSchemaExtractor { ); } else if ( typeof currentJson[key] === "object" && + currentJson[key] !== null && typeof currentSchema[key] === "object" ) { // It's an object - recurse - extract(currentJson[key], currentSchema[key], [...path, key]); + extract( + currentJson[key] as Record, + currentSchema[key] as I18nSchema, + [...path, key], + ); } } } @@ -191,18 +195,18 @@ export class JsonSchemaExtractor { /** * Handles arrays in JSON and schema - * @param {Array} jsonArray - The JSON array - * @param {Array} schemaArray - The schema array - * @param {Array} path - The current path + * @param {unknown[]} jsonArray - The JSON array + * @param {string[] | I18nSchema[]} schemaArray - The schema array + * @param {string[]} path - The current path * @param {string} filename - The name of the file * @param {boolean} addReferences - whenever to add references */ function handleArrays( - jsonArray, - schemaArray, - path, - filename, - addReferences, + jsonArray: unknown[], + schemaArray: string[] | I18nSchema[], + path: string[], + filename: string, + addReferences: boolean, ) { // If the schema has at least one element, use it as a template if (schemaArray.length > 0) { @@ -212,19 +216,30 @@ export class JsonSchemaExtractor { for (const jsonItem of jsonArray) { if (typeof jsonItem === "string") { // If the JSON element is a string, add it directly - addTranslation(jsonItem, schemaTemplate, filename, addReferences); - } else if (typeof jsonItem === "object") { + addTranslation( + jsonItem, + schemaTemplate as string, + filename, + addReferences, + ); + } else if (typeof jsonItem === "object" && jsonItem !== null) { // If it's an object, recurse if (typeof schemaTemplate === "object") { - extract(jsonItem, schemaTemplate, path); + extract( + jsonItem as Record, + schemaTemplate as I18nSchema, + path, + ); } else { // Edge case: handles cases like keywords: ["string1", "string2"] // when the schema has keywords: ["keyword context"] for (const key of Object.keys(jsonItem)) { - if (typeof jsonItem[key] === "string") { + // Type guard to ensure we are accessing a string + const value = (jsonItem as Record)[key]; + if (typeof value === "string") { addTranslation( - jsonItem[key], - schemaTemplate, + value, + schemaTemplate as string, filename, addReferences, ); @@ -243,7 +258,7 @@ export class JsonSchemaExtractor { * @param {string} filename - The name of the file for references * @param {boolean} addReferences - Whether to add references */ - function addTranslation(msgctxt, msgid, filename, addReferences) { + function addTranslation(msgctxt: string, msgid: string, filename: string, addReferences: boolean) { if (!msgctxt) return; // Do not add empty strings const translation = { diff --git a/src/fs/fs.ts b/src/fs/fs.ts index 876c291..f5623fb 100644 --- a/src/fs/fs.ts +++ b/src/fs/fs.ts @@ -69,7 +69,8 @@ export function getEncodingCharset(charset: string | undefined): string { * @param args - the command line arguments */ function getOutputFilePath(args: Args): string { - const { out, headers, options } = args.paths; + const { paths, headers, options } = args; + const out = paths.out let i18nFolder = out ?? headers?.domainPath ?? "languages"; // Remove leading and trailing slashes diff --git a/src/fs/glob.ts b/src/fs/glob.ts index 3d256e4..682c836 100644 --- a/src/fs/glob.ts +++ b/src/fs/glob.ts @@ -1,14 +1,13 @@ -import path from "node:path"; -import { Glob, type Path } from "glob"; -import { minimatch } from "minimatch"; +import path from 'node:path' +import { Glob, type Path } from 'glob' +import { minimatch } from 'minimatch' +import * as javascript from 'tree-sitter-javascript' // @ts-expect-error -import * as javascript from "tree-sitter-javascript"; +import * as php from 'tree-sitter-php' // @ts-expect-error -import * as php from "tree-sitter-php"; -// @ts-expect-error -import * as ts from "tree-sitter-typescript"; -import type { Args, Patterns } from "../types.js"; -import { detectPatternType, getFileExtension } from "../utils/common.js"; +import * as ts from 'tree-sitter-typescript' +import type { Args, Patterns } from '../types.js' +import { detectPatternType, getFileExtension } from '../utils/common.js' /** * Return the parser based on the file extension diff --git a/src/parser/exec.ts b/src/parser/exec.ts index 9454bbf..c56bd88 100644 --- a/src/parser/exec.ts +++ b/src/parser/exec.ts @@ -67,12 +67,12 @@ export async function exec(args: Args): Promise { audit(args, translationsUnion); } - /** generate the json file based on the --json flag passed */ + /** generate the JSON file based on the --json flag passed */ if (args.options?.json) { return outputJson(args, potHeader, translationsUnion); } - /** Generate the pot file json */ + /** Generate the pot file JSON */ const getTextTranslations: GetTextTranslations = { charset: getEncodingCharset(args.options?.charset), headers: potHeader as { [headerName: string]: string }, diff --git a/src/parser/makeJson.ts b/src/parser/makeJson.ts index ec60416..36a7108 100644 --- a/src/parser/makeJson.ts +++ b/src/parser/makeJson.ts @@ -1,7 +1,7 @@ import crypto from "node:crypto"; import * as fs from "node:fs"; import path from "node:path"; -import { transformSync } from "@babel/core"; +import { type NodePath, transformSync, type types as BabelTypes } from "@babel/core"; import type { SetOfBlocks } from "gettext-merger"; import { type GetTextTranslation, @@ -432,29 +432,34 @@ export class MakeJsonCommand { plugins: [ ({ types: t }) => ({ visitor: { - CallExpression(path) { + CallExpression(path: NodePath) { const callee = path.node.callee; // Check for pattern like: (fn)("...") - if ( - t.isSequenceExpression(callee) && - t.isMemberExpression(callee.expressions[1]) - ) { - const property = callee.expressions[1].property; - - if ( - t.isIdentifier(property) && - allowedFunctions.has(property.name) - ) { - // Replace with direct function call: __("..."), _n(...), etc. - path.node.callee = t.identifier(property.name); + if (t.isSequenceExpression(callee)) { + const seqExpr = callee as BabelTypes.SequenceExpression; + const secondExpression = seqExpr.expressions[1]; + + if (t.isMemberExpression(secondExpression)) { + const memberExpr = secondExpression as BabelTypes.MemberExpression; + const property = memberExpr.property; + + if (t.isIdentifier(property)) { + // Cast to Identifier + const identifier = property as BabelTypes.Identifier; + + if (allowedFunctions.has(identifier.name)) { + // Replace with direct function call: __("..."), _n(...), etc. + path.node.callee = t.identifier(identifier.name); + } + } } } }, }, }), ], - }).code as string; + })?.code ?? ''; return doTree(transformedScript, script, this.debug); } diff --git a/src/parser/makePot.ts b/src/parser/makePot.ts index 371273d..8da1ff2 100644 --- a/src/parser/makePot.ts +++ b/src/parser/makePot.ts @@ -11,7 +11,7 @@ import { exec } from "./exec.js"; * @return {string} - a promise that resolves when the pot file is generated */ export async function makePot(args: Args): Promise { - /** Collect metadata from the get package json */ + /** Collect metadata from the get-go package JSON */ const pkgData = extractPackageJson(args); /** Get metadata from the main file (theme and plugin) */ diff --git a/src/parser/progress.ts b/src/parser/progress.ts index 7f79819..9b995f7 100644 --- a/src/parser/progress.ts +++ b/src/parser/progress.ts @@ -4,11 +4,11 @@ import type { Args } from "../types.js"; /** * Initializes a progress bar and returns the progress bar element. * - * @param {Args} args - The argument object containing the source directory and other options. - * @param {number} filesCount - An array of file names. + * @param {Args} _args - The argument object containing the source directory and other options. + * @param {number} _filesCount - An array of file names. * @return {cliProgress.SingleBar} The progress bar element. */ -export function initProgress(args: Args, filesCount: number): SingleBar { +export function initProgress(_args: Args, _filesCount: number): SingleBar { // Set up the progress bar return new cliProgress.SingleBar( { diff --git a/src/parser/taskRunner.ts b/src/parser/taskRunner.ts index 2a5f03f..545816c 100644 --- a/src/parser/taskRunner.ts +++ b/src/parser/taskRunner.ts @@ -18,7 +18,7 @@ export async function taskRunner( args: Args, progressBar: SingleBar, ) { - const messages = []; + const messages: string[] = []; await Promise.allSettled(tasks) .then((strings) => { /** @@ -37,11 +37,12 @@ export async function taskRunner( * Add the strings to the destination set */ destination.addArray(result.blocks); + const strings = result.blocks.map((b) => b.msgid); /* Log the results */ messages.push( - `āœ… ${result.path} [${result.blocks.map((b) => b.msgid).join(", ")}]`, + `āœ… ${result.path} - ${strings.length} strings found [${strings.join(", ")}]`, ); - } else messages.push(`āŒ ${result.path} has no strings`); + } else messages.push(`āŒ ${result.path} - has no strings`); } } }) diff --git a/src/types.ts b/src/types.ts index 7796601..e3fc3a1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -103,6 +103,7 @@ export interface Args { packageName?: string; headers: { [key in PotHeaders]: string }; output?: boolean; + theme?: boolean; fileComment?: string; charset?: string; skip: { @@ -206,3 +207,9 @@ export interface I18nHeaders { slug: string; email: string | undefined; } + +export interface AuthorData { + name: string; + email?: string; + website?: string; +} diff --git a/src/utils/common.ts b/src/utils/common.ts index e6469d2..6d6b303 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -163,8 +163,7 @@ export function printTimeElapsed( timeEnd: Date = new Date(), ) { console.log( - `\nšŸš€ ${scriptName}: Task completed! ${scriptName.split("-")[1]} file created in ${ - timeEnd.getTime() - timeStart.getTime() + `\nšŸš€ ${scriptName}: Task completed! ${scriptName.split("-")[1]} file created in ${timeEnd.getTime() - timeStart.getTime() }ms`, ); } diff --git a/src/utils/extractors.ts b/src/utils/extractors.ts index b16d73d..4904c15 100644 --- a/src/utils/extractors.ts +++ b/src/utils/extractors.ts @@ -45,18 +45,18 @@ export const buildBlock = ( * Extracts strings from parsed JSON data. * * @param {Record | Parser.SyntaxNode} parsed - The parsed JSON data or syntax node. - * @param {string | Parser} filename - The filename or parser. + * @param {string | Parser} _filename - The filename or parser. * @param filepath - the path to the file being parsed * @return {SetOfBlocks} An array of translation strings. */ export function yieldParsedData( - parsed: Block[], - filename: "block.json" | "theme.json", + parsed: Block[] | undefined, + _filename: "block.json" | "theme.json", filepath: string, ): SetOfBlocks { const gettextTranslations: SetOfBlocks = new SetOfBlocks([], filepath); - if (parsed.length === 0) { + if (!parsed) { return gettextTranslations; } diff --git a/src/utils/output.ts b/src/utils/output.ts index 31f0195..826bacc 100644 --- a/src/utils/output.ts +++ b/src/utils/output.ts @@ -1,6 +1,7 @@ import type { SetOfBlocks } from "gettext-merger"; import Tannin from "tannin"; import type { Args } from "../types.js"; +import type { GetTextTranslation } from 'gettext-parser' /** * Outputs the pot file in json format based on the command line arguments --json option @@ -14,13 +15,15 @@ export function outputJson( args: Args, potHeader: Record | null, translationsUnion: SetOfBlocks, -) { - const jedData: { - [p: string]: { [p: string]: [string, string] }; - } = { +): string { + const jedData = { [args.slug]: { "": potHeader ?? {}, - ...(translationsUnion.toJson() as { [p: string]: [string, string] }), + ...(translationsUnion.toJson() as{ + [key: string]: { + [key: string]: GetTextTranslation; + }; + }), }, }; const i18n = new Tannin(jedData); diff --git a/tests/extract-headers.test.js b/tests/extract-headers.test.js index f00ca98..d1a240d 100644 --- a/tests/extract-headers.test.js +++ b/tests/extract-headers.test.js @@ -1,7 +1,7 @@ const { describe, it } = require("node:test"); const { join } = require("node:path"); const assert = require("node:assert"); -const { extractMainFileData } = require("../lib"); +const { extractMainFileData, getAuthorFromPackage } = require("../lib"); describe("should parse plugin main file", () => { describe("should parse plugin.php", () => { @@ -52,3 +52,71 @@ describe("should parse theme main file", () => { }); }); }); + +describe("getAuthorFromPackage", () => { + it("extracts author from string with name and email", () => { + const pkgJson = { + author: "My Name ", + }; + const author = getAuthorFromPackage(pkgJson); + assert.deepStrictEqual(author, { + name: "My Name", + email: "myname@example.com", + website: undefined, + }); + }); + + it("extracts author from string with specific user format", () => { + const pkgJson = { + author: "my name ", + }; + const author = getAuthorFromPackage(pkgJson); + assert.deepStrictEqual(author, { + name: "my name", + email: "myname@asdasdasdasd.it", + website: undefined, + }); + }); + + it("extracts author from string with name, email and url", () => { + const pkgJson = { + author: "My Name (https://example.com)", + }; + const author = getAuthorFromPackage(pkgJson); + assert.deepStrictEqual(author, { + name: "My Name", + email: "myname@example.com", + website: "https://example.com", + }); + }); + + it("extracts author from object", () => { + const pkgJson = { + author: { + name: "Object Author", + email: "obj@example.com", + website: "https://obj.example.com" + } + }; + const author = getAuthorFromPackage(pkgJson); + assert.deepStrictEqual(author, { + name: "Object Author", + email: "obj@example.com", + website: "https://obj.example.com", + }); + }); + + it("extracts author from array of strings", () => { + const pkgJson = { + authors: [ + "Array Author " + ] + }; + const author = getAuthorFromPackage(pkgJson); + assert.deepStrictEqual(author, { + name: "Array Author", + email: "array@example.com", + website: undefined + }); + }); +});