simple-icons/scripts/add-icon-data.js

151 lines
4.4 KiB
JavaScript
Raw Normal View History

2024-03-24 17:38:18 +00:00
#!/usr/bin/env node
import process from 'node:process';
2024-03-24 17:38:18 +00:00
import {ExitPromptError, checkbox, confirm, input} from '@inquirer/prompts';
import chalk from 'chalk';
2024-03-24 17:38:18 +00:00
import {search} from 'fast-fuzzy';
import getRelativeLuminance from 'get-relative-luminance';
2024-03-24 17:38:18 +00:00
import autocomplete from 'inquirer-autocomplete-standalone';
import {
URL_REGEX,
collator,
getIconsDataString,
normalizeColor,
2024-03-24 17:38:18 +00:00
titleToSlug,
2023-04-19 13:23:13 +00:00
} from '../sdk.mjs';
2024-03-24 17:38:18 +00:00
import {getJsonSchemaData, writeIconsData} from './utils.js';
const iconsData = JSON.parse(await getIconsDataString());
const jsonSchema = await getJsonSchemaData();
2024-03-24 17:38:18 +00:00
const HEX_REGEX = /^#?[a-f\d]{3,8}$/i;
2024-03-15 04:04:10 +00:00
const aliasTypes = ['aka', 'old'].map((key) => ({
name: `${key} (${jsonSchema.definitions.brand.properties.aliases.properties[key].description})`,
value: key,
}));
const licenseTypes =
jsonSchema.definitions.brand.properties.license.oneOf[0].properties.type.enum.map(
2024-03-24 17:38:18 +00:00
(license) => ({name: license, value: license}),
2024-03-15 04:04:10 +00:00
);
2024-03-15 04:04:10 +00:00
const isValidURL = (input) =>
URL_REGEX.test(input) || 'Must be a valid and secure (https://) URL.';
2024-03-15 04:04:10 +00:00
const isValidHexColor = (input) =>
HEX_REGEX.test(input) || 'Must be a valid hex code.';
const isNewIcon = (input) =>
2024-03-24 17:38:18 +00:00
!iconsData.icons.some(
2024-03-15 04:04:10 +00:00
(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;
2024-03-15 04:04:10 +00:00
if (luminance === -1) return input.toUpperCase();
return chalk.bgHex(`#${color}`).hex(luminance < 0.4 ? '#fff' : '#000')(
2024-03-15 04:04:10 +00:00
input.toUpperCase(),
);
};
2024-03-15 04:04:10 +00:00
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?",
2024-03-24 17:38:18 +00:00
async source(input) {
2024-03-15 04:04:10 +00:00
input = (input || '').trim();
return input
2024-03-24 17:38:18 +00:00
? search(input, licenseTypes, {keySelector: (x) => x.value})
2024-03-15 04:04:10 +00:00
: 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) {
2024-03-24 17:38:18 +00:00
// eslint-disable-next-line no-await-in-loop
2024-03-15 04:04:10 +00:00
result[alias] = await input({
message: `What ${alias} aliases would you like to add? (separate with commas)`,
}).then((aliases) =>
aliases.split(',').map((alias) => alias.trim()),
);
}
2024-03-24 17:38:18 +00:00
2024-03-15 04:04:10 +00:00
return result;
})
: undefined,
};
2024-05-02 20:54:43 +00:00
process.stdout.write(
2024-03-15 04:04:10 +00:00
'About to write the following to simple-icons.json:\n' +
2024-05-02 20:54:43 +00:00
JSON.stringify(answers, null, 4) +
'\n',
2024-03-15 04:04:10 +00:00
);
2024-03-15 04:04:10 +00:00
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);
2024-05-02 20:54:43 +00:00
process.stdout.write(chalk.green('\nData written successfully.\n'));
} else {
2024-05-02 20:54:43 +00:00
process.stdout.write(chalk.red('\nAborted.\n'));
process.exit(1);
}
2024-03-24 17:38:18 +00:00
} catch (error) {
if (error instanceof ExitPromptError) {
2024-05-02 20:54:43 +00:00
process.stdout.write(chalk.red('\nAborted.\n'));
2024-03-15 04:04:10 +00:00
process.exit(1);
}
2024-03-24 17:38:18 +00:00
throw error;
2024-03-15 04:04:10 +00:00
}