From 4a000cd8c73ef5f9af81db3b9f4bf9b9193fc92e Mon Sep 17 00:00:00 2001 From: snickbit Date: Sun, 1 May 2022 20:20:44 -0400 Subject: [PATCH] refactor: typescript rewrite --- indexer.config.json | 2 +- package.json | 9 +- src/actions/add.js | 101 ----------------------- src/actions/add.ts | 96 +++++++++++++++++++++ src/actions/{generate.js => generate.ts} | 10 +-- src/actions/{index.js => index.ts} | 0 src/actions/remove.js | 67 --------------- src/actions/remove.ts | 65 +++++++++++++++ src/{cli.js => cli.ts} | 2 +- src/{index.js => index.ts} | 67 +++++++-------- src/utilities/{common.js => common.ts} | 18 ++-- src/utilities/{data.js => data.ts} | 0 tsconfig.json | 10 +-- 13 files changed, 217 insertions(+), 230 deletions(-) delete mode 100644 src/actions/add.js create mode 100644 src/actions/add.ts rename src/actions/{generate.js => generate.ts} (87%) rename src/actions/{index.js => index.ts} (100%) delete mode 100644 src/actions/remove.js create mode 100644 src/actions/remove.ts rename src/{cli.js => cli.ts} (88%) rename src/{index.js => index.ts} (60%) rename src/utilities/{common.js => common.ts} (93%) rename src/utilities/{data.js => data.ts} (100%) diff --git a/indexer.config.json b/indexer.config.json index ff76fb68..c5cfa019 100644 --- a/indexer.config.json +++ b/indexer.config.json @@ -1,6 +1,6 @@ [ { - "index": "src/actions/index.js", + "index": "src/actions/index.ts", "files": [ { "export": "group", diff --git a/package.json b/package.json index 468cd343..2974aa8a 100644 --- a/package.json +++ b/package.json @@ -30,15 +30,15 @@ }, "scripts": { "start": "node bin/fa.js", - "clean": "shx rm -rf bin dist", + "clean": "shx rm -rf ./dist ./bin", "index": "indexer src/actions", "build": "npm run clean && npm run build:lib && npm run build:cli", "build:lib": "tsup", - "build:cli": "esbuild src/cli.js --bundle --outfile=bin/fa.js --target=node12 --platform=node --external:@fortawesome/*", + "build:cli": "esbuild src/cli.ts --bundle --outfile=bin/fa.js --target=node12 --platform=node --external:@fortawesome/* --sourcemap", "build:meta": "npm run build:cli --minify --metafile=.meta.json", "watch": "concurrently -n lib,cli \"npm run watch:lib\" \"npm run watch:cli\"", "watch:lib": "npm run build:lib --watch", - "watch:cli": "npm run build:cli --watch", + "watch:cli": "npm run build:cli -- --watch", "depcheck": "depcheck", "prod": "npm run clean && concurrently -n lib,cli \"npm run build:lib --minify\" \"npm run build:cli --minify\"" }, @@ -65,11 +65,12 @@ "typescript": "^4.6.3" }, "jest": { + "preset": "ts-jest", "testEnvironment": "node" }, "tsup": { "entry": [ - "src/index.js" + "src/index.ts" ], "clean": true, "dts": true, diff --git a/src/actions/add.js b/src/actions/add.js deleted file mode 100644 index 59c5c452..00000000 --- a/src/actions/add.js +++ /dev/null @@ -1,101 +0,0 @@ -import cli from '@snickbit/node-cli' -import {ask, confirm} from '@snickbit/node-utilities' -import {isEmpty, plural} from '@snickbit/utilities' -import {gql} from '@urql/core' -import {_out, client, normalizeIconName, saveConfig, useConfig} from '../utilities/common' -import generate from './generate' - -export default async function (argv) { - return cli(argv) - .args({ - icons: { - description: 'Icon name', - type: 'array', - required: true - } - }).run().then(async args => { - const config = await useConfig() - let changes = { - icons: 0, - aliases: 0 - } - - const shouldAddAlias = async alias => { - return !config.aliases[alias] || args.force || await confirm(`Alias {magenta}${alias}{/magenta} already exists for icon {cyan}${config.aliases[alias]}{/cyan}, overwrite?`) - } - - for (let item of args.icons) { - let [icon, ...aliases] = item.split(',') - - let iconName = normalizeIconName(icon.replace(/(fa[a-z]?)-/, `$1:`)) - if (!config.icons.includes(iconName)) { - const iconQuery = gql` - query ($query: String) { - search(version: "6.1.1", query: $query, first: 15) { - id - } - } - ` - - let results - try { - results = await client - .query(iconQuery, {query: iconName.replace(/fa:/, '')}) - .toPromise() - } catch (e) { - _out.error(`We couldn't find any icons matching {cyan}${iconName}{/cyan}`) - continue - } - - const icon_results = results?.data?.search?.map(r => r.id) || [] - - if (icon_results.length) { - let icon_selected = icon_results.find(r => r === icon) - - if (!icon_selected) { - if (icon_results.length === 1) { - icon_selected = icon_results[0] - } else { - icon_results.push('None of the above') - icon_selected = await ask(`Found ${icon_results.length} matches for ${icon}`, {type: 'list', choices: icon_results}) - if (icon_selected === 'None of the above') { - continue - } - } - } - - iconName = normalizeIconName(icon_selected.replace(/(fa[a-z]?)-/, `$1:`)) - - if (!config.icons.includes(iconName)) { - config.icons.push(iconName) - _out.v().success(`Added icon {cyan}${iconName}{/cyan}`) - changes.icons++ - } - } - } else { - _out.warn(`Icon {cyan}${iconName}{/cyan} already exists`) - } - - if (!isEmpty(aliases)) { - for (let alias of aliases) { - if (config.aliases[alias] !== iconName && await shouldAddAlias(alias)) { - config.aliases[alias] = iconName - _out.v().success(`Added alias {magenta}${alias}{/magenta}`) - changes.aliases++ - } else { - _out.warn(`Alias {magenta}${alias}{/magenta} skipped`) - } - } - } - } - - if (changes.icons || changes.aliases) { - saveConfig(config) - changes.icons && _out.success(`Added {cyan}${changes.icons} ${plural('icon', changes.icons)}{/cyan}`) - changes.aliases && _out.success(`Added {magenta}${changes.aliases} ${plural('alias', changes.aliases)}{/magenta}`) - return generate() - } else { - _out.done('Nothing to update') - } - }) -} diff --git a/src/actions/add.ts b/src/actions/add.ts new file mode 100644 index 00000000..6b7cb792 --- /dev/null +++ b/src/actions/add.ts @@ -0,0 +1,96 @@ +import cli from '@snickbit/node-cli' +import {ask, confirm} from '@snickbit/node-utilities' +import {isEmpty, plural} from '@snickbit/utilities' +import {gql} from '@urql/core' +import {_out, client, initConfig, normalizeIconName, saveConfig} from '../utilities/common' +import generate from './generate' + +export default async (argv) => cli(argv).args({ + icons: { + description: 'Icon name', + type: 'array', + required: true + } +}).run().then(async args => { + const config = await initConfig() + let changes = { + icons: 0, + aliases: 0 + } + + const shouldAddAlias = async alias => !config.aliases[alias] || args.force || await confirm(`Alias {magenta}${alias}{/magenta} already exists for icon {cyan}${config.aliases[alias]}{/cyan}, overwrite?`) + + for (let item of args.icons) { + let [icon, ...aliases] = item.split(',') + + let iconName = normalizeIconName(icon.replace(/(fa[a-z]?)-/, `$1:`)) + if (!config.icons.includes(iconName)) { + const iconQuery = gql` + query ($query: String) { + search(version: "6.1.1", query: $query, first: 15) { + id + } + } + ` + + let results + try { + results = await client + .query(iconQuery, {query: iconName.replace(/fa:/, '')}) + .toPromise() + } catch (e) { + _out.error(`We couldn't find any icons matching {cyan}${iconName}{/cyan}`) + continue + } + + const icon_results = results?.data?.search?.map(r => r.id) || [] + + if (icon_results.length) { + let icon_selected = icon_results.find(r => r === icon) + + if (!icon_selected) { + if (icon_results.length === 1) { + icon_selected = icon_results[0] + } else { + icon_results.push('None of the above') + icon_selected = await ask(`Found ${icon_results.length} matches for ${icon}`, {type: 'select', choices: icon_results}) + if (icon_selected === 'None of the above') { + continue + } + } + } + + iconName = normalizeIconName(icon_selected.replace(/(fa[a-z]?)-/, `$1:`)) + + if (!config.icons.includes(iconName)) { + config.icons.push(iconName) + _out.v().success(`Added icon {cyan}${iconName}{/cyan}`) + changes.icons++ + } + } + } else { + _out.warn(`Icon {cyan}${iconName}{/cyan} already exists`) + } + + if (!isEmpty(aliases)) { + for (let alias of aliases) { + if (config.aliases[alias] !== iconName && await shouldAddAlias(alias)) { + config.aliases[alias] = iconName + _out.v().success(`Added alias {magenta}${alias}{/magenta}`) + changes.aliases++ + } else { + _out.warn(`Alias {magenta}${alias}{/magenta} skipped`) + } + } + } + } + + if (changes.icons || changes.aliases) { + saveConfig(config) + changes.icons && _out.success(`Added {cyan}${changes.icons} ${plural('icon', changes.icons)}{/cyan}`) + changes.aliases && _out.success(`Added {magenta}${changes.aliases} ${plural('alias', changes.aliases)}{/magenta}`) + return generate() + } else { + _out.done('Nothing to update') + } +}) diff --git a/src/actions/generate.js b/src/actions/generate.ts similarity index 87% rename from src/actions/generate.js rename to src/actions/generate.ts index fe48739c..60260f69 100644 --- a/src/actions/generate.js +++ b/src/actions/generate.ts @@ -1,15 +1,15 @@ import {saveFile} from '@snickbit/node-utilities' import mkdirp from 'mkdirp' import path from 'path' -import {_out, getImportString, getStringContent, parseIcon, saveConfig, useConfig} from '../utilities/common' +import {_out, getImportString, getStringContent, initConfig, parseIcon, saveConfig} from '../utilities/common' export default async function () { - let config = await useConfig() + let config = await initConfig() let content = [] let icons = [] let aliases = {} - function processIcon(raw_icon_name) { + async function processIcon(raw_icon_name) { // get the icon object let icon = parseIcon(raw_icon_name) @@ -32,7 +32,7 @@ export default async function () { } for (let raw_icon_name of config.icons) { - processIcon(raw_icon_name) + await processIcon(raw_icon_name) } for (let [alias, resolved_icon] of Object.entries(config.aliases)) { @@ -43,7 +43,7 @@ export default async function () { if (matching_icons.length === 1) { icon = matching_icons.pop() } else { - icon = processIcon(resolved_icon) + icon = await processIcon(resolved_icon) } if (icon) { aliases[alias] = icon.id diff --git a/src/actions/index.js b/src/actions/index.ts similarity index 100% rename from src/actions/index.js rename to src/actions/index.ts diff --git a/src/actions/remove.js b/src/actions/remove.js deleted file mode 100644 index 4c7d6232..00000000 --- a/src/actions/remove.js +++ /dev/null @@ -1,67 +0,0 @@ -import cli from '@snickbit/node-cli' -import {isEmpty, objectFilter, plural} from '@snickbit/utilities' -import {_out, parseIcon, saveConfig, useConfig} from '../utilities/common' -import generate from './generate' - -export default async function (argv) { - return cli(argv) - .args({ - subjects: { - description: 'Icon or alias to remove (aliases must be combined with the --alias flag)', - type: 'array' - } - }) - .options({ - alias: { - alias: 'a', - description: 'Remove an alias instead of an icon' - } - }) - .run().then(async args => { - const config = await useConfig() - let changes = { - icons: 0, - aliases: 0 - } - - for (let subject of args.subjects) { - const icon = parseIcon(subject) - const iconPredicate = i => i === icon.id || i === icon.name || i === `fa:${icon.name}` - if (!args.alias) { - const icons = config.icons.filter(iconPredicate) - if (!isEmpty(icons)) { - config.icons = config.icons.filter(i => !icons.includes(i)) - _out.v().success(`Removed ${plural('icon', icons.length)} {cyan}${icons.join(', ')}{/cyan}`) - changes.icons += icons.length - } else { - _out.warn(`Icon {cyan}${icon.id}{/cyan} has not been added`) - } - - const aliases = Object.keys(objectFilter(config.aliases, iconPredicate)) - if (!isEmpty(aliases)) { - config.aliases = objectFilter(config.aliases, (i, a) => !aliases.includes(a)) - _out.v().success(`Removed ${plural('alias', icons.length)}{magenta} ${aliases.join(', ')}{/magenta} from icon {cyan}${icon.id}{/cyan}`) - changes.aliases += aliases.length - } else { - _out.warn(`There are no aliases for icon {cyan}${icon.id}{/cyan}`) - } - } else if (config.aliases[subject]) { - const iconName = config.aliases[subject] - delete config.aliases[subject] - _out.v().success(`Removed alias {magenta}${subject}{/magenta} from icon {cyan}${iconName}{/cyan}`) - changes.aliases++ - } else { - _out.warn(`Alias {cyan}${subject}{/cyan} has not been added.`) - } - } - - if (changes.icons || changes.aliases) { - saveConfig(config) - changes.icons && _out.success(`Removed {cyan}${changes.icons} ${plural('icon', changes.icons)}{/cyan}`) - changes.aliases && _out.success(`Removed {magenta}${changes.aliases} ${plural('alias', changes.aliases)}{/magenta}`) - return generate() - } else { - _out.done('Nothing to update') - } - }) -} diff --git a/src/actions/remove.ts b/src/actions/remove.ts new file mode 100644 index 00000000..1bf94a9d --- /dev/null +++ b/src/actions/remove.ts @@ -0,0 +1,65 @@ +import cli from '@snickbit/node-cli' +import {isEmpty, objectFilter, plural} from '@snickbit/utilities' +import {_out, initConfig, parseIcon, saveConfig} from '../utilities/common' +import generate from './generate' + +export default async (argv) => cli(argv) +.args({ + subjects: { + description: 'Icon or alias to remove (aliases must be combined with the --alias flag)', + type: 'array' + } +}) +.options({ + alias: { + alias: 'a', + description: 'Remove an alias instead of an icon' + } +}) +.run().then(async args => { + const config = await initConfig() + let changes = { + icons: 0, + aliases: 0 + } + + for (let subject of args.subjects) { + const icon = parseIcon(subject) + const iconPredicate = i => i === icon.id || i === icon.name || i === `fa:${icon.name}` + if (!args.alias) { + const icons = config.icons.filter(iconPredicate) + if (!isEmpty(icons)) { + config.icons = config.icons.filter(i => !icons.includes(i)) + _out.v().success(`Removed ${plural('icon', icons.length)} {cyan}${icons.join(', ')}{/cyan}`) + changes.icons += icons.length + } else { + _out.warn(`Icon {cyan}${icon.id}{/cyan} has not been added`) + } + + const aliases = Object.keys(objectFilter(config.aliases, iconPredicate)) + if (!isEmpty(aliases)) { + config.aliases = objectFilter(config.aliases, (i, a) => !aliases.includes(a)) + _out.v().success(`Removed ${plural('alias', icons.length)}{magenta} ${aliases.join(', ')}{/magenta} from icon {cyan}${icon.id}{/cyan}`) + changes.aliases += aliases.length + } else { + _out.warn(`There are no aliases for icon {cyan}${icon.id}{/cyan}`) + } + } else if (config.aliases[subject]) { + const iconName = config.aliases[subject] + delete config.aliases[subject] + _out.v().success(`Removed alias {magenta}${subject}{/magenta} from icon {cyan}${iconName}{/cyan}`) + changes.aliases++ + } else { + _out.warn(`Alias {cyan}${subject}{/cyan} has not been added.`) + } + } + + if (changes.icons || changes.aliases) { + saveConfig(config) + changes.icons && _out.success(`Removed {cyan}${changes.icons} ${plural('icon', changes.icons)}{/cyan}`) + changes.aliases && _out.success(`Removed {magenta}${changes.aliases} ${plural('alias', changes.aliases)}{/magenta}`) + return generate() + } else { + _out.done('Nothing to update') + } +}) diff --git a/src/cli.js b/src/cli.ts similarity index 88% rename from src/cli.js rename to src/cli.ts index 2e084d3f..8685653b 100644 --- a/src/cli.js +++ b/src/cli.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -import cli from '@snickbit/node-cli' +import {cli} from '@snickbit/node-cli' import {out} from '@snickbit/out' import packageJson from '../package.json' import * as actions from './actions' diff --git a/src/index.js b/src/index.ts similarity index 60% rename from src/index.js rename to src/index.ts index f707428e..b94fa73d 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,25 +1,24 @@ -import {icon, library} from '@fortawesome/fontawesome-svg-core' +import {icon, IconLookup, library} from '@fortawesome/fontawesome-svg-core' import out from '@snickbit/out' import {default_icon_map} from './utilities/data' +import {IconDefinition, IconName, IconPrefix as IconPrefixBase} from '@fortawesome/fontawesome-common-types' -/** - * @typedef {import('@fortawesome/fontawesome-common-types').IconName} IconName - * @typedef {import('@fortawesome/fontawesome-common-types').IconPrefix} IconPrefix - * @typedef {import('@fortawesome/fontawesome-common-types').IconLookup} IconLookup - * @typedef {import('@fortawesome/fontawesome-common-types').IconDefinition} IconDefinition - * @typedef {import('@fortawesome/fontawesome-common-types').IconPack} IconPack - */ - -/** - * @param app - * @param icon_aliases - * @return {Promise} - */ -export async function useFa(app, icon_aliases) { +export type IconPrefix = IconPrefixBase | 'fa' + +export interface Library { + definitions?: { + [key: string]: IconDefinition + } +} + +export type IconSplit = [IconPrefix, IconName] + +export type IconString = `${IconPrefix}:${IconName}` + +export async function useFa(app, icon_aliases): Promise { out.verbose('Loading Font Awesome icons...') - // noinspection JSUnresolvedVariable - const definitions = library?.definitions || {} + const definitions = (library as unknown as Library)?.definitions || {} let iconDefaultPrefixes = {} for (let [prefixName, prefixGroup] of Object.entries(definitions)) { @@ -29,7 +28,6 @@ export async function useFa(app, icon_aliases) { } function parseIcon(iconData) { - // noinspection JSUnusedLocalSymbols let [width, height, ligatures, , svgPathData] = iconData.icon if (ligatures.length < 2 || iconData.prefix !== 'fad') { ligatures = [0, 0] @@ -49,20 +47,13 @@ export async function useFa(app, icon_aliases) { return svgPathData + '|' + ligatures.join(' ') + ' ' + width + ' ' + height } - /** - * @param {string} icon_name - * @return {[IconPrefix, IconName]} - */ - function splitIconName(icon_name) { - let prefix = 'fa' + function splitIconName(icon_name: string): IconSplit { + let prefix: IconPrefix = 'fa' if (icon_name.includes(':')) { [prefix, icon_name] = icon_name.split(':') } - /** - * @type {`${IconPrefix}:${IconName}`} - */ - let prefixed_icon_name = `${prefix}:${icon_name}` + let prefixed_icon_name: IconString = `${prefix}:${icon_name}` if (icon_name in icon_aliases) { prefixed_icon_name = icon_aliases[icon_name] @@ -70,17 +61,15 @@ export async function useFa(app, icon_aliases) { prefixed_icon_name = icon_aliases[prefixed_icon_name] } - return prefixed_icon_name.includes(':') ? prefixed_icon_name.split(':') : [ - prefix, - prefixed_icon_name - ] + if (prefixed_icon_name.includes(':')) { + [prefix, icon_name] = prefixed_icon_name.split(':') + return [prefix, icon_name] as IconSplit + } else { + return [prefix, prefixed_icon_name] as IconSplit + } } - /** - * @param {string} raw_icon_name - * @return {IconLookup} - */ - function parseIconName(raw_icon_name) { + function parseIconName(raw_icon_name: string): IconLookup { let [prefix, iconName] = splitIconName(raw_icon_name) if (prefix === 'fa' && iconName in iconDefaultPrefixes && iconDefaultPrefixes[iconName].length === 1) { @@ -88,8 +77,8 @@ export async function useFa(app, icon_aliases) { } return { - /** @type {IconName} */ iconName, - /** @type {import('@fortawesome/fontawesome-common-types').IconPrefix} */ prefix + iconName, + prefix } } diff --git a/src/utilities/common.js b/src/utilities/common.ts similarity index 93% rename from src/utilities/common.js rename to src/utilities/common.ts index 6c268988..6ba505b0 100644 --- a/src/utilities/common.js +++ b/src/utilities/common.ts @@ -20,7 +20,7 @@ export function getNodeModulesPath() { export const config_path = path.join(process.cwd(), 'fa.config.json') -export async function useConfig() { +export async function initConfig() { if (!config) { if (!fileExists(config_path)) { // create with inquirer @@ -94,16 +94,22 @@ export async function useConfig() { return config } +export function useConfig() { + if (!config) { + out.throw('No config found!') + } + + icon_prefix_types.fa = icon_prefix_types[config.default] + + return config +} + export function saveConfig(conf) { config = conf return saveFileJson(config_path, config) } -/** - * @param {string} icon_name - * @returns {string} - */ -export function normalizeIconName(icon_name) { +export function normalizeIconName(icon_name: string): string { icon_name = icon_name.replace(/(fa[a-z]?)[-:]/, `$1:`) if (!icon_name.includes(':')) { icon_name = 'fa:' + icon_name diff --git a/src/utilities/data.js b/src/utilities/data.ts similarity index 100% rename from src/utilities/data.js rename to src/utilities/data.ts diff --git a/tsconfig.json b/tsconfig.json index 169c0a6a..31c29743 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,12 @@ { "include": [ - "src/index.js" + "src/**/*.ts" ], "compilerOptions": { - "allowJs": true, - "outFile": "./dist/index.d.ts", - "emitDeclarationOnly": true, "declaration": true, "declarationMap": true, "removeComments": true, - "target": "es2015", + "target": "es2020", "moduleResolution": "node", "newLine": "lf", "module": "commonjs", @@ -17,6 +14,7 @@ "sourceMap": true, "noImplicitAny": false, "noImplicitThis": false, - "esModuleInterop": true + "esModuleInterop": true, + "resolveJsonModule": true } }