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:
parent
62fcc794a2
commit
5958fc117b
@ -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
|
||||||
|
@ -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
137
scripts/add-icon-data.js
Normal 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);
|
||||||
|
}
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user