1
0
mirror of https://github.com/simple-icons/simple-icons.git synced 2025-01-25 01:32:58 +02:00

Add add-icon-data script (#7849)

Co-authored-by: Álvaro Mondéjar <mondejar1994@gmail.com>
This commit is contained in:
LitoMore 2022-09-25 09:04:58 +08:00 committed by GitHub
parent 62fcc794a2
commit 5958fc117b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 185 additions and 8 deletions

View File

@ -265,6 +265,8 @@ Here is the object of a fictional brand as an example:
} }
``` ```
You can use `npm run add-icon-data` to add metadata via a CLI prompt.
Make sure the icon is added in alphabetical order. If you're in doubt, you can always run `npm run our-lint` - this will tell you if any of the JSON data is in the wrong order. Make sure the icon is added in alphabetical order. If you're in doubt, you can always run `npm run our-lint` - this will tell you if any of the JSON data is in the wrong order.
#### Optional Data #### Optional Data

View File

@ -37,10 +37,13 @@
"url": "https://opencollective.com/simple-icons" "url": "https://opencollective.com/simple-icons"
}, },
"devDependencies": { "devDependencies": {
"chalk": "^5.0.1",
"editorconfig-checker": "4.0.2", "editorconfig-checker": "4.0.2",
"esbuild": "0.15.6", "esbuild": "0.15.6",
"fake-diff": "1.0.0", "fake-diff": "1.0.0",
"get-relative-luminance": "^1.0.0",
"husky": "8.0.1", "husky": "8.0.1",
"inquirer": "^9.1.2",
"is-ci": "3.0.1", "is-ci": "3.0.1",
"jsonschema": "1.4.1", "jsonschema": "1.4.1",
"mocha": "10.0.0", "mocha": "10.0.0",
@ -71,7 +74,8 @@
"pretest": "npm run prepublishOnly", "pretest": "npm run prepublishOnly",
"posttest": "npm run postpublish", "posttest": "npm run postpublish",
"svgo": "svgo --config svgo.config.js", "svgo": "svgo --config svgo.config.js",
"get-filename": "node scripts/get-filename.js" "get-filename": "node scripts/get-filename.js",
"add-icon-data": "node scripts/add-icon-data.js"
}, },
"engines": { "engines": {
"node": ">=0.12.18" "node": ">=0.12.18"

137
scripts/add-icon-data.js Normal file
View File

@ -0,0 +1,137 @@
import fs from 'node:fs/promises';
import inquirer from 'inquirer';
import chalk from 'chalk';
import getRelativeLuminance from 'get-relative-luminance';
import {
URL_REGEX,
collator,
getIconsDataString,
getIconDataPath,
writeIconsData,
titleToSlug,
normalizeColor,
} from './utils.js';
const hexPattern = /^#?[a-f0-9]{3,8}$/i;
const iconsData = JSON.parse(await getIconsDataString());
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 exist';
return true;
};
const hexValidator = (text) =>
hexPattern.test(text) ? true : 'This should be a valid hex code';
const sourceValidator = (text) =>
URL_REGEX.test(text) ? true : 'This should be a secure URL';
const hexTransformer = (text) => {
const color = normalizeColor(text);
const luminance = hexPattern.test(text)
? getRelativeLuminance.default(`#${color}`)
: -1;
if (luminance === -1) return text;
return chalk.bgHex(`#${color}`).hex(luminance < 0.4 ? '#fff' : '#000')(text);
};
const getIconDataFromAnswers = (answers) => ({
title: answers.title,
hex: answers.hex,
source: answers.source,
...(answers.hasGuidelines ? { guidelines: answers.guidelines } : {}),
...(answers.hasLicense
? {
license: {
type: answers.licenseType,
...(answers.licenseUrl ? { url: answers.licenseUrl } : {}),
},
}
: {}),
});
const dataPrompt = [
{
type: 'input',
name: 'title',
message: 'Title',
validate: titleValidator,
},
{
type: 'input',
name: 'hex',
message: 'Hex',
validate: hexValidator,
filter: (text) => normalizeColor(text),
transformer: hexTransformer,
},
{
type: 'input',
name: 'source',
message: 'Source',
validate: sourceValidator,
},
{
type: 'confirm',
name: 'hasGuidelines',
message: 'The icon has brand guidelines?',
},
{
type: 'input',
name: 'guidelines',
message: 'Guidelines',
validate: sourceValidator,
when: ({ hasGuidelines }) => hasGuidelines,
},
{
type: 'confirm',
name: 'hasLicense',
message: 'The icon has brand license?',
},
{
type: 'input',
name: 'licenseType',
message: 'License type',
validate: (text) => Boolean(text),
when: ({ hasLicense }) => hasLicense,
},
{
type: 'input',
name: 'licenseUrl',
message: 'License URL',
suffix: ' (optional)',
validate: (text) => !Boolean(text) || sourceValidator(text),
when: ({ hasLicense }) => hasLicense,
},
{
type: 'confirm',
name: 'confirm',
message: (answers) => {
const icon = getIconDataFromAnswers(answers);
return [
'About to write to simple-icons.json',
chalk.reset(JSON.stringify(icon, null, 4)),
chalk.reset('Is this OK?'),
].join('\n\n');
},
},
];
const answers = await inquirer.prompt(dataPrompt);
const icon = getIconDataFromAnswers(answers);
if (answers.confirm) {
iconsData.icons.push(icon);
iconsData.icons.sort((a, b) => collator.compare(a.title, b.title));
await writeIconsData(iconsData);
} else {
console.log('Aborted.');
process.exit(1);
}

View File

@ -48,7 +48,7 @@ const TESTS = {
/* Check the formatting of the data file */ /* Check the formatting of the data file */
prettified: async (data, dataString) => { prettified: async (data, dataString) => {
const normalizedDataString = normalizeNewlines(dataString); const normalizedDataString = normalizeNewlines(dataString);
const dataPretty = `${JSON.stringify(data, null, ' ')}\n`; const dataPretty = `${JSON.stringify(data, null, 4)}\n`;
if (normalizedDataString !== dataPretty) { if (normalizedDataString !== dataPretty) {
const dataDiff = fakeDiff(normalizedDataString, dataPretty); const dataDiff = fakeDiff(normalizedDataString, dataPretty);

View File

@ -4,7 +4,7 @@
*/ */
import path from 'node:path'; import path from 'node:path';
import { promises as fs } from 'node:fs'; import fs from 'node:fs/promises';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
const TITLE_TO_SLUG_REPLACEMENTS = { const TITLE_TO_SLUG_REPLACEMENTS = {
@ -95,16 +95,23 @@ export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
(_, ref) => ({ quot: '"', amp: '&', lt: '<', gt: '>' }[ref]), (_, 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.
*/
export const getIconDataPath = (rootDir) => {
if (rootDir === undefined) {
rootDir = path.resolve(getDirnameFromImportMeta(import.meta.url), '..');
}
return path.resolve(rootDir, '_data', 'simple-icons.json');
};
/** /**
* Get contents of _data/simple-icons.json. * Get contents of _data/simple-icons.json.
* @param {String|undefined} rootDir Path to the root directory of the project. * @param {String|undefined} rootDir Path to the root directory of the project.
*/ */
export const getIconsDataString = (rootDir) => { export const getIconsDataString = (rootDir) => {
if (rootDir === undefined) { return fs.readFile(getIconDataPath(rootDir), 'utf8');
rootDir = path.resolve(getDirnameFromImportMeta(import.meta.url), '..');
}
const iconDataPath = path.resolve(rootDir, '_data', 'simple-icons.json');
return fs.readFile(iconDataPath, 'utf8');
}; };
/** /**
@ -116,6 +123,19 @@ export const getIconsData = async (rootDir) => {
return JSON.parse(fileContents).icons; 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) => {
return fs.writeFile(
getIconDataPath(rootDir),
`${JSON.stringify(iconsData, null, 4)}\n`,
'utf8',
);
};
/** /**
* Get the directory name where this file is located from `import.meta.url`, * Get the directory name where this file is located from `import.meta.url`,
* equivalent to the `__dirname` global variable in CommonJS. * equivalent to the `__dirname` global variable in CommonJS.
@ -132,6 +152,20 @@ export const normalizeNewlines = (text) => {
return text.replace(/\r\n/g, '\n'); 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. * Get information about third party extensions.
* @param {String} readmePath Path to the README file * @param {String} readmePath Path to the README file