diff --git a/.dockerignore b/.dockerignore index 3f4f042f..018644c5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,4 @@ npm-debug.log /index.js /index.mjs /index.d.ts +/sdk.js diff --git a/.gitignore b/.gitignore index f434dc32..f794d3ca 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /index.js /index.mjs /index.d.ts +/sdk.js # Ignore all files in the icons folder icons/* @@ -21,6 +22,8 @@ yarn.lock # Dependency directories node_modules/ +# Generated files +*.tgz ### macOS ### # General diff --git a/.npmignore b/.npmignore index 3689bed7..c97285f7 100644 --- a/.npmignore +++ b/.npmignore @@ -12,3 +12,6 @@ !index.mjs !index.d.ts !types.d.ts +!sdk.mjs +!sdk.js +!sdk.d.ts diff --git a/.prettierignore b/.prettierignore index 05fc64a7..1609b2c5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -13,3 +13,4 @@ scripts/build/templates/*.js index.js index.mjs index.d.ts +sdk.js diff --git a/.svglintrc.mjs b/.svglintrc.mjs index 9954a148..5b210675 100644 --- a/.svglintrc.mjs +++ b/.svglintrc.mjs @@ -4,7 +4,7 @@ import { getDirnameFromImportMeta, htmlFriendlyToTitle, collator, -} from './scripts/utils.js'; +} from './sdk.mjs'; import svgpath from 'svgpath'; import svgPathBbox from 'svg-path-bbox'; import parsePath from 'svg-path-segments'; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a3f7c9f..fe744d23 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -396,3 +396,22 @@ Then, start a Docker container for simple-icons and attach to it: ```shell docker run -it --rm --entrypoint "/bin/ash" simple-icons ``` + +## Developing Third-Party Extensions + +A SDK is included in the `simple-icons/sdk` entrypoint of the npm package to make it easier the development of third party extensions with Javascript and Typescript. + +```typescript +import { getIconsData, type IconData } from 'simple-icons/sdk'; + +const iconsData: IconData[] = getIconsData(); +``` + +```javascript +import { getIconsData } from 'simple-icons/sdk'; + +/* @typedef {import("./simple-icons/sdk").IconData} IconData */ + +/* @type {IconData[]} */ +const iconsData = getIconsData(); +``` diff --git a/package.json b/package.json index eabb05a3..563cfe92 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,25 @@ }, "./icons/*": [ "./icons/*" - ] + ], + "./sdk": { + "import": { + "types": "./sdk.d.ts", + "default": "./sdk.mjs" + }, + "module": { + "types": "./sdk.d.ts", + "default": "./sdk.mjs" + }, + "require": { + "types": "./sdk.d.ts", + "default": "./sdk.js" + }, + "default": { + "types": "./sdk.d.ts", + "default": "./sdk.js" + } + } }, "sideEffects": false, "repository": { diff --git a/scripts/add-icon-data.js b/scripts/add-icon-data.js index 8093bc34..eb1b8d18 100644 --- a/scripts/add-icon-data.js +++ b/scripts/add-icon-data.js @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import inquirer from 'inquirer'; import chalk from 'chalk'; import getRelativeLuminance from 'get-relative-luminance'; @@ -7,11 +6,10 @@ import { collator, getJsonSchemaData, getIconsDataString, - getIconDataPath, - writeIconsData, titleToSlug, normalizeColor, -} from './utils.js'; +} from '../sdk.mjs'; +import { writeIconsData } from './utils.js'; const hexPattern = /^#?[a-f0-9]{3,8}$/i; diff --git a/scripts/build/package.js b/scripts/build/package.js index 62e9e1b7..d312b6d0 100644 --- a/scripts/build/package.js +++ b/scripts/build/package.js @@ -1,10 +1,7 @@ #!/usr/bin/env node /** * @fileoverview - * Compiles our icons into static .js files that can be imported in the browser - * and are tree-shakeable. The static .js files go in icons/{filename}.js. Also - * generates an index.js that exports all icons by title, but is not - * tree-shakeable + * Simple Icons package build script. */ import { promises as fs } from 'node:fs'; @@ -19,7 +16,7 @@ import { getIconsData, getDirnameFromImportMeta, collator, -} from '../utils.js'; +} from '../../sdk.mjs'; const __dirname = getDirnameFromImportMeta(import.meta.url); @@ -29,6 +26,8 @@ const rootDir = path.resolve(__dirname, '..', '..'); const iconsDir = path.resolve(rootDir, 'icons'); const indexJsFile = path.resolve(rootDir, 'index.js'); const indexMjsFile = path.resolve(rootDir, 'index.mjs'); +const sdkJsFile = path.resolve(rootDir, 'sdk.js'); +const sdkMjsFile = path.resolve(rootDir, 'sdk.mjs'); const indexDtsFile = path.resolve(rootDir, 'index.d.ts'); const templatesDir = path.resolve(__dirname, 'templates'); @@ -68,10 +67,9 @@ const build = async () => { licenseToObject(icon.license), ); }; - const writeJs = async (filepath, rawJavaScript) => { - const { code } = await esbuildTransform(rawJavaScript, { - minify: true, - }); + const writeJs = async (filepath, rawJavaScript, opts = null) => { + opts = opts === null ? { minify: true } : opts; + const { code } = await esbuildTransform(rawJavaScript, opts); await fs.writeFile(filepath, code); }; const writeTs = async (filepath, rawTypeScript) => { @@ -119,6 +117,11 @@ const build = async () => { '', )}`; await writeTs(indexDtsFile, rawIndexDts); + + // create a CommonJS SDK file + await writeJs(sdkJsFile, await fs.readFile(sdkMjsFile, UTF8), { + format: 'cjs', + }); }; build(); diff --git a/scripts/get-filename.js b/scripts/get-filename.js index 6649c8a2..1b34d91d 100644 --- a/scripts/get-filename.js +++ b/scripts/get-filename.js @@ -5,7 +5,7 @@ * icon SVG filename to standard output. */ -import { titleToSlug } from './utils.js'; +import { titleToSlug } from '../sdk.mjs'; if (process.argv.length < 3) { console.error('Provide a brand name as argument'); diff --git a/scripts/lint/jsonlint.js b/scripts/lint/jsonlint.js index 920d0bd4..dd223c45 100644 --- a/scripts/lint/jsonlint.js +++ b/scripts/lint/jsonlint.js @@ -4,14 +4,10 @@ * CLI tool to run jsonschema on the simple-icons.json data file. */ -import { promises as fs } from 'node:fs'; import path from 'node:path'; import { Validator } from 'jsonschema'; -import { - getDirnameFromImportMeta, - getIconsData, - getJsonSchemaData, -} from '../utils.js'; +import { getDirnameFromImportMeta, getIconsData } from '../../sdk.mjs'; +import { getJsonSchemaData } from '../utils.js'; const icons = await getIconsData(); const __dirname = getDirnameFromImportMeta(import.meta.url); diff --git a/scripts/lint/ourlint.js b/scripts/lint/ourlint.js index 3512a55b..dfb346fb 100644 --- a/scripts/lint/ourlint.js +++ b/scripts/lint/ourlint.js @@ -6,7 +6,7 @@ */ import fakeDiff from 'fake-diff'; -import { getIconsDataString, normalizeNewlines, collator } from '../utils.js'; +import { getIconsDataString, normalizeNewlines, collator } from '../../sdk.mjs'; /** * Contains our tests so they can be isolated from each other. diff --git a/scripts/release/bump-version.js b/scripts/release/bump-version.js index d19b7bb0..fd1acbf0 100644 --- a/scripts/release/bump-version.js +++ b/scripts/release/bump-version.js @@ -6,7 +6,7 @@ import fs from 'node:fs'; import path from 'node:path'; -import { getDirnameFromImportMeta } from '../utils.js'; +import { getDirnameFromImportMeta } from '../../sdk.mjs'; const __dirname = getDirnameFromImportMeta(import.meta.url); diff --git a/scripts/release/update-cdn-urls.js b/scripts/release/update-cdn-urls.js index ed311a18..38eaaf4b 100644 --- a/scripts/release/update-cdn-urls.js +++ b/scripts/release/update-cdn-urls.js @@ -7,7 +7,7 @@ import fs from 'node:fs'; import path from 'node:path'; -import { getDirnameFromImportMeta } from '../utils.js'; +import { getDirnameFromImportMeta } from '../../sdk.mjs'; const __dirname = getDirnameFromImportMeta(import.meta.url); diff --git a/scripts/release/update-slugs-table.js b/scripts/release/update-slugs-table.js index 250c96d6..e8ddd952 100644 --- a/scripts/release/update-slugs-table.js +++ b/scripts/release/update-slugs-table.js @@ -7,7 +7,7 @@ import { promises as fs } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { getIconsData, getIconSlug } from '../utils.js'; +import { getIconsData, getIconSlug } from '../../sdk.mjs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/scripts/release/update-svgs-count.js b/scripts/release/update-svgs-count.js index 4c426254..484c891e 100644 --- a/scripts/release/update-svgs-count.js +++ b/scripts/release/update-svgs-count.js @@ -7,7 +7,7 @@ */ import { promises as fs } from 'node:fs'; import path from 'node:path'; -import { getDirnameFromImportMeta, getIconsData } from '../utils.js'; +import { getDirnameFromImportMeta, getIconsData } from '../../sdk.mjs'; const regexMatcher = /Over\s(\d+)\s/; const updateRange = 100; diff --git a/scripts/utils.js b/scripts/utils.js index 50af0bf6..c0b2c913 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -1,110 +1,9 @@ -/** - * @fileoverview - * Some common utilities for scripts. - */ - import path from 'node:path'; import fs from 'node:fs/promises'; -import { fileURLToPath } from 'node:url'; - -const TITLE_TO_SLUG_REPLACEMENTS = { - '+': 'plus', - '.': 'dot', - '&': 'and', - đ: 'd', - ħ: 'h', - ı: 'i', - ĸ: 'k', - ŀ: 'l', - ł: 'l', - ß: 'ss', - ŧ: 't', -}; - -const TITLE_TO_SLUG_CHARS_REGEX = RegExp( - `[${Object.keys(TITLE_TO_SLUG_REPLACEMENTS).join('')}]`, - 'g', -); - -const TITLE_TO_SLUG_RANGE_REGEX = /[^a-z0-9]/g; - -export const URL_REGEX = /^https:\/\/[^\s]+$/; - -/** - * Get the directory name where this file is located from `import.meta.url`, - * equivalent to the `__dirname` global variable in CommonJS. - * @param {String} importMetaUrl import.meta.url - */ -export const getDirnameFromImportMeta = (importMetaUrl) => - path.dirname(fileURLToPath(importMetaUrl)); +import { getDirnameFromImportMeta, getIconDataPath } from '../sdk.mjs'; const __dirname = getDirnameFromImportMeta(import.meta.url); -/** - * Get the slug/filename for an icon. - * @param {Object} icon The icon data as it appears in _data/simple-icons.json - */ -export const getIconSlug = (icon) => icon.slug || titleToSlug(icon.title); - -/** - * Extract the path from an icon SVG content. - * @param {Object} svg The icon SVG content - **/ -export const svgToPath = (svg) => svg.match(/ - title - .toLowerCase() - .replace( - TITLE_TO_SLUG_CHARS_REGEX, - (char) => TITLE_TO_SLUG_REPLACEMENTS[char], - ) - .normalize('NFD') - .replace(TITLE_TO_SLUG_RANGE_REGEX, ''); - -/** - * Converts a slug into a variable name that can be exported. - * @param {String} slug The slug to convert - */ -export const slugToVariableName = (slug) => { - const slugFirstLetter = slug[0].toUpperCase(); - const slugRest = slug.slice(1); - return `si${slugFirstLetter}${slugRest}`; -}; - -/** - * Converts a brand title (as it is seen in simple-icons.json) into a brand - * title in HTML/SVG friendly format. - * @param {String} brandTitle The title to convert - */ -export const titleToHtmlFriendly = (brandTitle) => - brandTitle - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(//g, '>') - .replace(/./g, (char) => { - const charCode = char.charCodeAt(0); - return charCode > 127 ? `&#${charCode};` : char; - }); - -/** - * Converts a brand title in HTML/SVG friendly format into a brand title (as - * it is seen in simple-icons.json) - * @param {String} htmlFriendlyTitle The title to convert - */ -export const htmlFriendlyToTitle = (htmlFriendlyTitle) => - htmlFriendlyTitle - .replace(/&#([0-9]+);/g, (_, num) => String.fromCharCode(parseInt(num))) - .replace( - /&(quot|amp|lt|gt);/g, - (_, ref) => ({ quot: '"', amp: '&', lt: '<', gt: '>' }[ref]), - ); - /** * Get JSON schema data. * @param {String|undefined} rootDir Path to the root directory of the project. @@ -112,104 +11,23 @@ export const htmlFriendlyToTitle = (htmlFriendlyTitle) => export const getJsonSchemaData = async ( rootDir = path.resolve(__dirname, '..'), ) => { - const __dirname = getDirnameFromImportMeta(import.meta.url); const jsonSchemaPath = path.resolve(rootDir, '.jsonschema.json'); const jsonSchemaString = await fs.readFile(jsonSchemaPath, 'utf8'); return JSON.parse(jsonSchemaString); }; -/** - * Get path of _data/simpe-icons.json. - * @param {String|undefined} rootDir Path to the root directory of the project. - */ -export const getIconDataPath = ( - rootDir = path.resolve(getDirnameFromImportMeta(import.meta.url), '..'), -) => { - return path.resolve(rootDir, '_data', 'simple-icons.json'); -}; - -/** - * Get contents of _data/simple-icons.json. - * @param {String|undefined} rootDir Path to the root directory of the project. - */ -export const getIconsDataString = (rootDir) => { - return fs.readFile(getIconDataPath(rootDir), 'utf8'); -}; - -/** - * Get icons data as object from _data/simple-icons.json. - * @param {String|undefined} rootDir Path to the root directory of the project. - */ -export const getIconsData = async (rootDir) => { - const fileContents = await getIconsDataString(rootDir); - return JSON.parse(fileContents).icons; -}; - /** * Write icons data to _data/simple-icons.json. * @param {Object} iconsData Icons data object. * @param {String|undefined} rootDir Path to the root directory of the project. */ -export const writeIconsData = async (iconsData, rootDir) => { +export const writeIconsData = async ( + iconsData, + rootDir = path.resolve(__dirname, '..'), +) => { return fs.writeFile( getIconDataPath(rootDir), `${JSON.stringify(iconsData, null, 4)}\n`, 'utf8', ); }; - -/** - * Replace Windows newline characters by Unix ones. - * @param {String} text The text to replace - */ -export const normalizeNewlines = (text) => { - return text.replace(/\r\n/g, '\n'); -}; - -/** - * Convert non-6-digit hex color to 6-digit. - * @param {String} text The color text - */ -export const normalizeColor = (text) => { - let color = text.replace('#', '').toUpperCase(); - if (color.length < 6) { - color = [...color.slice(0, 3)].map((x) => x.repeat(2)).join(''); - } else if (color.length > 6) { - color = color.slice(0, 6); - } - return color; -}; - -/** - * Get information about third party extensions. - * @param {String} readmePath Path to the README file - */ -export const getThirdPartyExtensions = async (readmePath) => - normalizeNewlines(await fs.readFile(readmePath, 'utf8')) - .split('## Third-Party Extensions\n\n')[1] - .split('\n\n')[0] - .split('\n') - .slice(2) - .map((line) => { - const [module, author] = line.split(' | '); - return { - module: { - name: /\[(.+)\]/.exec(module)[1], - url: /\((.+)\)/.exec(module.split('')[0])[1], - }, - author: { - name: /\[(.+)\]/.exec(author)[1], - url: /\((.+)\)/.exec(author)[1], - }, - }; - }); - -/** - * `Intl.Collator` object ready to be used for icon titles sorting. - * @type {Intl.Collator} - * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator - **/ -export const collator = new Intl.Collator('en', { - usage: 'search', - caseFirst: 'upper', -}); diff --git a/sdk.d.ts b/sdk.d.ts new file mode 100644 index 00000000..2362a47c --- /dev/null +++ b/sdk.d.ts @@ -0,0 +1,96 @@ +/** + * @fileoverview + * Types for Simple Icons SDK. + */ + +/** + * The data for a third-party extension. + * + * Includes the module and author of the extension, + * both including a name and URL. + * + * @see {@link https://github.com/simple-icons/simple-icons#third-party-extensions Third-Party Extensions} + */ +export type ThirdPartyExtension = { + module: ThirdPartyExtensionSubject; + author: ThirdPartyExtensionSubject; +}; + +type ThirdPartyExtensionSubject = { + name: string; + url: string; +}; + +/** + * The license for a Simple Icon. + * + * Corresponds to the `license` property in the *_data/simple-icons.json* file. + * + * @see {@link https://github.com/simple-icons/simple-icons/blob/develop/CONTRIBUTING.md#optional-data Optional Data} + */ +export type License = SPDXLicense | CustomLicense; + +type SPDXLicense = { + type: string; + url?: string; +}; + +type CustomLicense = { + type: 'custom'; + url: string; +}; + +/** + * The aliases for a Simple Icon. + * + * Corresponds to the `aliases` property in the *_data/simple-icons.json* file. + * + * @see {@link https://github.com/simple-icons/simple-icons/blob/develop/CONTRIBUTING.md#aliases Aliases} + */ +export type Aliases = { + aka?: string[]; + dup?: DuplicateAlias[]; + loc?: { [key: string]: string }; +}; + +type DuplicateAlias = { + title: string; + hex?: string; + guidelines?: string; +}; + +/** + * The data for a Simple Icon. + * + * Corresponds to the data stored for each icon in the *_data/simple-icons.json* file. + * + * @see {@link https://github.com/mondeja/simple-icons/blob/utils-entrypoint/CONTRIBUTING.md#7-update-the-json-data-for-simpleiconsorg Update the JSON Data for SimpleIcons.org} + */ +export type IconData = { + title: string; + hex: string; + source: string; + slug?: string; + guidelines?: string; + license?: License; + aliases?: Aliases; +}; + +export const URL_REGEX: RegExp; + +export function getDirnameFromImportMeta(importMetaUrl: string): string; +export function getIconSlug(icon: IconData): string; +export function svgToPath(svg: string): string; +export function titleToSlug(title: string): string; +export function slugToVariableName(slug: string): string; +export function titleToHtmlFriendly(brandTitle: string): string; +export function htmlFriendlyToTitle(htmlFriendlyTitle: string): string; +export function getIconDataPath(rootDir?: string): string; +export function getIconsDataString(rootDir?: string): string; +export function getIconsData(rootDir?: string): IconData[]; +export function normalizeNewlines(text: string): string; +export function normalizeColor(text: string): string; +export function getThirdPartyExtensions( + readmePath?: string, +): Promise; +export const collator: Intl.Collator; diff --git a/sdk.mjs b/sdk.mjs new file mode 100644 index 00000000..7f3d6721 --- /dev/null +++ b/sdk.mjs @@ -0,0 +1,222 @@ +/** + * @fileoverview + * Simple Icons SDK. + */ + +import path from 'node:path'; +import fs from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; + +/** + * @typedef {import("./sdk").ThirdPartyExtension} ThirdPartyExtension + * @typedef {import("./sdk").IconData} IconData + */ + +const TITLE_TO_SLUG_REPLACEMENTS = { + '+': 'plus', + '.': 'dot', + '&': 'and', + đ: 'd', + ħ: 'h', + ı: 'i', + ĸ: 'k', + ŀ: 'l', + ł: 'l', + ß: 'ss', + ŧ: 't', +}; + +const TITLE_TO_SLUG_CHARS_REGEX = RegExp( + `[${Object.keys(TITLE_TO_SLUG_REPLACEMENTS).join('')}]`, + 'g', +); + +const TITLE_TO_SLUG_RANGE_REGEX = /[^a-z0-9]/g; + +/** + * Regex to validate HTTPs URLs. + */ +export const URL_REGEX = /^https:\/\/[^\s]+$/; + +/** + * Get the directory name where this file is located from `import.meta.url`, + * equivalent to the `__dirname` global variable in CommonJS. + * @param {String} importMetaUrl import.meta.url + * @returns {String} Directory name in which this file is located + */ +export const getDirnameFromImportMeta = (importMetaUrl) => + path.dirname(fileURLToPath(importMetaUrl)); + +/** + * Get the slug/filename for an icon. + * @param {IconData} icon The icon data as it appears in *_data/simple-icons.json* + * @returns {String} The slug/filename for the icon + */ +export const getIconSlug = (icon) => icon.slug || titleToSlug(icon.title); + +/** + * Extract the path from an icon SVG content. + * @param {String} svg The icon SVG content + * @returns {String} The path from the icon SVG content + **/ +export const svgToPath = (svg) => svg.match(/ + title + .toLowerCase() + .replace( + TITLE_TO_SLUG_CHARS_REGEX, + (char) => TITLE_TO_SLUG_REPLACEMENTS[char], + ) + .normalize('NFD') + .replace(TITLE_TO_SLUG_RANGE_REGEX, ''); + +/** + * Converts a slug into a variable name that can be exported. + * @param {String} slug The slug to convert + * @returns {String} The variable name for the slug + */ +export const slugToVariableName = (slug) => { + const slugFirstLetter = slug[0].toUpperCase(); + const slugRest = slug.slice(1); + return `si${slugFirstLetter}${slugRest}`; +}; + +/** + * Converts a brand title as defined in *_data/simple-icons.json* into a brand + * title in HTML/SVG friendly format. + * @param {String} brandTitle The title to convert + * @returns {String} The brand title in HTML/SVG friendly format + */ +export const titleToHtmlFriendly = (brandTitle) => + brandTitle + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>') + .replace(/./g, (char) => { + const charCode = char.charCodeAt(0); + return charCode > 127 ? `&#${charCode};` : char; + }); + +/** + * Converts a brand title in HTML/SVG friendly format into a brand title (as + * it is seen in *_data/simple-icons.json*) + * @param {String} htmlFriendlyTitle The title to convert + * @returns {String} The brand title in HTML/SVG friendly format + */ +export const htmlFriendlyToTitle = (htmlFriendlyTitle) => + htmlFriendlyTitle + .replace(/&#([0-9]+);/g, (_, num) => String.fromCharCode(parseInt(num))) + .replace( + /&(quot|amp|lt|gt);/g, + (_, ref) => ({ quot: '"', amp: '&', lt: '<', gt: '>' }[ref]), + ); + +/** + * Get path of *_data/simpe-icons.json*. + * @param {String|undefined} rootDir Path to the root directory of the project + * @returns {String} Path of *_data/simple-icons.json* + */ +export const getIconDataPath = ( + rootDir = getDirnameFromImportMeta(import.meta.url), +) => { + return path.resolve(rootDir, '_data', 'simple-icons.json'); +}; + +/** + * Get contents of *_data/simple-icons.json*. + * @param {String|undefined} rootDir Path to the root directory of the project + * @returns {String} Content of *_data/simple-icons.json* + */ +export const getIconsDataString = ( + rootDir = getDirnameFromImportMeta(import.meta.url), +) => { + return fs.readFile(getIconDataPath(rootDir), 'utf8'); +}; + +/** + * Get icons data as object from *_data/simple-icons.json*. + * @param {String|undefined} rootDir Path to the root directory of the project + * @returns {IconData[]} Icons data as array from *_data/simple-icons.json* + */ +export const getIconsData = async ( + rootDir = getDirnameFromImportMeta(import.meta.url), +) => { + const fileContents = await getIconsDataString(rootDir); + return JSON.parse(fileContents).icons; +}; + +/** + * Replace Windows newline characters by Unix ones. + * @param {String} text The text to replace + * @returns {String} The text with Windows newline characters replaced by Unix ones + */ +export const normalizeNewlines = (text) => { + return text.replace(/\r\n/g, '\n'); +}; + +/** + * Convert non-6-digit hex color to 6-digit with the character `#` stripped. + * @param {String} text The color text + * @returns {String} The color text in 6-digit hex format + */ +export const normalizeColor = (text) => { + let color = text.replace('#', '').toUpperCase(); + if (color.length < 6) { + color = [...color.slice(0, 3)].map((x) => x.repeat(2)).join(''); + } else if (color.length > 6) { + color = color.slice(0, 6); + } + return color; +}; + +/** + * Get information about third party extensions from the README table. + * @param {String|undefined} readmePath Path to the README file + * @returns {Promise} Information about third party extensions + */ +export const getThirdPartyExtensions = async ( + readmePath = path.join( + getDirnameFromImportMeta(import.meta.url), + 'README.md', + ), +) => + normalizeNewlines(await fs.readFile(readmePath, 'utf8')) + .split('## Third-Party Extensions\n\n')[1] + .split('\n\n')[0] + .split('\n') + .slice(2) + .map((line) => { + let [module, author] = line.split(' | '); + + // README shipped with package has not Github theme image links + module = module.includes('') + ? module.split('')[0] + : module.split('