diff --git a/scripts/add-icon-data.js b/scripts/add-icon-data.js index 861f4d23e..785130d5c 100644 --- a/scripts/add-icon-data.js +++ b/scripts/add-icon-data.js @@ -13,171 +13,122 @@ import { } from '../sdk.mjs'; import { getJsonSchemaData, writeIconsData } from './utils.js'; -const hexPattern = /^#?[a-f0-9]{3,8}$/i; - const iconsData = JSON.parse(await getIconsDataString()); const jsonSchema = await getJsonSchemaData(); -const titleValidator = (text) => { - if (!text) return 'This field is required'; - if ( - iconsData.icons.find( - (x) => x.title === text || titleToSlug(x.title) === titleToSlug(text), - ) - ) - return 'This icon title or slug already exists'; - return true; -}; +const HEX_REGEX = /^#?[a-f0-9]{3,8}$/i; -const hexValidator = (text) => - hexPattern.test(text) || 'This should be a valid hex code'; +const aliasTypes = ['aka', 'old'].map((key) => ({ + name: `${key} (${jsonSchema.definitions.brand.properties.aliases.properties[key].description})`, + value: key, +})); -const sourceValidator = (text) => - URL_REGEX.test(text) || 'This should be a secure URL'; +const licenseTypes = + jsonSchema.definitions.brand.properties.license.oneOf[0].properties.type.enum.map( + (license) => ({ name: license, value: license }), + ); -const hexTransformer = (text) => { - const color = normalizeColor(text); - const luminance = hexPattern.test(text) +const isValidURL = (input) => + URL_REGEX.test(input) || 'Must be a valid and secure (https://) URL.'; + +const isValidHexColor = (input) => + HEX_REGEX.test(input) || 'Must be a valid hex code.'; + +const isNewIcon = (input) => + !iconsData.icons.find( + (icon) => + icon.title === input || titleToSlug(icon.title) === titleToSlug(input), + ) || 'This icon title or slug already exists.'; + +const previewHexColor = (input) => { + const color = normalizeColor(input); + const luminance = HEX_REGEX.test(input) ? getRelativeLuminance.default(`#${color}`) : -1; - if (luminance === -1) return text.toUpperCase(); + if (luminance === -1) return input.toUpperCase(); return chalk.bgHex(`#${color}`).hex(luminance < 0.4 ? '#fff' : '#000')( - text.toUpperCase(), + input.toUpperCase(), ); }; -const aliasesTransformer = (text) => - text - .split(',') - .map((x) => chalk.cyan(x)) - .join(','); +try { + const answers = { + title: await input({ + message: 'What is the title of this icon?', + validate: (input) => + input.trim().length > 0 ? isNewIcon(input) : 'This field is required.', + }), + hex: normalizeColor( + await input({ + message: 'What is the brand color of this icon?', + validate: isValidHexColor, + transformer: previewHexColor, + }), + ), + source: await input({ + message: 'What is the source URL of the icon?', + validate: isValidURL, + }), + guidelines: (await confirm({ + message: 'Does this icon have brand guidelines?', + })) + ? await input({ + message: 'What is the URL for the brand guidelines?', + validate: isValidURL, + }) + : undefined, + license: (await confirm({ + message: 'Does this icon have a license?', + })) + ? { + type: await autocomplete({ + message: "What is the icon's license?", + source: async (input) => { + input = (input || '').trim(); + return input + ? search(input, licenseTypes, { keySelector: (x) => x.value }) + : licenseTypes; + }, + }), + url: + (await input({ + message: `What is the URL for the license? (optional)`, + validate: (input) => input.length === 0 || isValidURL(input), + })) || undefined, + } + : undefined, + aliases: (await confirm({ + message: 'Does this icon have brand aliases?', + default: false, + })) + ? await checkbox({ + message: 'What types of aliases do you want to add?', + choices: aliasTypes, + }).then(async (aliases) => { + const result = {}; + for (const alias of aliases) { + result[alias] = await input({ + message: `What ${alias} aliases would you like to add? (separate with commas)`, + }).then((aliases) => + aliases.split(',').map((alias) => alias.trim()), + ); + } + return result; + }) + : undefined, + }; -const aliasesChoices = Object.entries( - jsonSchema.definitions.brand.properties.aliases.properties, -) - .filter(([k]) => ['aka', 'old'].includes(k)) - .map(([k, v]) => ({ name: `${k}: ${v.description}`, value: k })); + console.log( + 'About to write the following to simple-icons.json:\n' + + JSON.stringify(answers, null, 4), + ); -const getIconDataFromAnswers = (answers) => ({ - title: answers.title, - hex: normalizeColor(answers.hex), - source: answers.source, - ...(answers.hasGuidelines ? { guidelines: answers.guidelines } : {}), - ...(answers.hasLicense - ? { - license: { - type: answers.licenseType, - ...(answers.licenseUrl ? { url: answers.licenseUrl } : {}), - }, - } - : {}), - ...(answers.hasAliases - ? { - aliases: aliasesChoices.reduce((previous, current) => { - const promptKey = `${current.value}AliasesList`; - if (answers[promptKey]) - return { - ...previous, - [current.value]: answers[promptKey] - .split(',') - .map((x) => x.trim()), - }; - return previous; - }, {}), - } - : {}), -}); - -const run = async () => { - const answers = {}; - - answers.title = await input({ - message: 'Title:', - validate: titleValidator, - }); - - answers.hex = await input({ - message: 'Hex:', - validate: hexValidator, - transformer: hexTransformer, - }); - - answers.source = await input({ - message: 'Source URL:', - validate: sourceValidator, - }); - - answers.hasGuidelines = await confirm({ - message: 'The icon has brand guidelines?', - }); - - if (answers.hasGuidelines) { - answers.guidelines = await input({ - message: 'Guidelines URL:', - validate: sourceValidator, - }); - } - - answers.hasLicense = await confirm({ - message: 'The icon has brand license?', - }); - - if (answers.hasLicense) { - const licenseTypes = - jsonSchema.definitions.brand.properties.license.oneOf[0].properties.type.enum.map( - (license) => { - return { value: license }; - }, - ); - answers.licenseType = await autocomplete({ - message: 'License type:', - source: async (input) => { - input = (input || '').trim(); - return input - ? search(input, licenseTypes, { keySelector: (x) => x.value }) - : licenseTypes; - }, - }); - - answers.licenseUrl = await input({ - message: `License URL ${chalk.reset('(optional)')}:`, - validate: (text) => text.length === 0 || sourceValidator(text), - }); - } - - answers.hasAliases = await confirm({ - message: 'This icon has brand aliases?', - default: false, - }); - - if (answers.hasAliases) { - answers.aliasesTypes = await checkbox({ - message: 'What types of aliases do you want to add?', - choices: aliasesChoices, - }); - - for (const x of aliasesChoices) { - if (!answers?.aliasesTypes?.includes(x.value)) continue; - answers[`${x.value}AliasesList`] = await input({ - message: x.value + chalk.reset(' (separate with commas)'), - validate: (text) => text.trim().length > 0, - transformer: aliasesTransformer, - }); - } - } - - answers.confirmToAdd = await confirm({ - message: [ - 'About to write the following to simple-icons.json:', - chalk.reset(JSON.stringify(getIconDataFromAnswers(answers), null, 4)), - chalk.reset('Is this OK?'), - ].join('\n\n'), - }); - - const icon = getIconDataFromAnswers(answers); - - if (answers.confirmToAdd) { - iconsData.icons.push(icon); + if ( + await confirm({ + message: 'Is this OK?', + }) + ) { + iconsData.icons.push(answers); iconsData.icons.sort((a, b) => collator.compare(a.title, b.title)); await writeIconsData(iconsData); console.log(chalk.green('\nData written successfully.')); @@ -185,19 +136,11 @@ const run = async () => { console.log(chalk.red('\nAborted.')); process.exit(1); } -}; - -const main = async () => { - try { - await run(); - } catch (err) { - if (err instanceof ExitPromptError) { - console.log(chalk.red('\nAborted.')); - process.exit(1); - } - - throw err; +} catch (err) { + if (err instanceof ExitPromptError) { + console.log(chalk.red('\nAborted.')); + process.exit(1); } -}; -await main(); + throw err; +}