Add XO linter (#10643)
This commit is contained in:
parent
d66bdb1380
commit
bf69b6dee0
30 changed files with 633 additions and 493 deletions
2
.github/renovate.json5
vendored
2
.github/renovate.json5
vendored
|
@ -16,5 +16,5 @@
|
|||
separateMajorMinor: false,
|
||||
|
||||
// We manually update digest dependencies (eg. hashes in Github actions)
|
||||
digest: { enabled: false },
|
||||
digest: {enabled: false},
|
||||
}
|
||||
|
|
8
.github/workflows/create-release.yml
vendored
8
.github/workflows/create-release.yml
vendored
|
@ -54,13 +54,13 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: npm i --ignore-scripts --no-audit --no-fund
|
||||
- name: Update major version in CDN URLs
|
||||
run: node ./scripts/release/update-cdn-urls.js
|
||||
run: ./scripts/release/update-cdn-urls.js
|
||||
- name: Update SVGs count milestone
|
||||
run: node ./scripts/release/update-svgs-count.js
|
||||
run: ./scripts/release/update-svgs-count.js
|
||||
- name: Update slugs table
|
||||
run: node ./scripts/release/update-slugs-table.js
|
||||
run: ./scripts/release/update-slugs-table.js
|
||||
- name: Update SDK Typescript definitions
|
||||
run: node ./scripts/release/update-sdk-ts-defs.js
|
||||
run: ./scripts/release/update-sdk-ts-defs.js
|
||||
- name: Commit version bump
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
|
|
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
|
@ -43,9 +43,9 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: npm i --ignore-scripts --no-audit --no-fund
|
||||
- name: Reformat to regular markdown
|
||||
run: node ./scripts/release/reformat-markdown.js "${{ steps.get-version.outputs.version }}"
|
||||
run: ./scripts/release/reformat-markdown.js "${{ steps.get-version.outputs.version }}"
|
||||
- name: Update SDK Typescript definitions
|
||||
run: node ./scripts/release/update-sdk-ts-defs.js
|
||||
run: ./scripts/release/update-sdk-ts-defs.js
|
||||
- name: Build NodeJS package
|
||||
run: npm run build
|
||||
- name: Deploy to NPM
|
||||
|
@ -65,7 +65,7 @@ jobs:
|
|||
- id: get-version
|
||||
uses: ./.github/actions/get-version
|
||||
- name: Reformat to regular markdown
|
||||
run: node ./scripts/release/reformat-markdown.js "${{ steps.get-version.outputs.version }}"
|
||||
run: ./scripts/release/reformat-markdown.js "${{ steps.get-version.outputs.version }}"
|
||||
- name: Configure GIT credentials
|
||||
run: |
|
||||
git config user.name "${GITHUB_ACTOR}"
|
||||
|
|
|
@ -32,12 +32,12 @@
|
|||
"aka": {
|
||||
"description": "The brand is also known as (e.g. full length name or abbreviation)",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
"items": {"type": "string"}
|
||||
},
|
||||
"dup": {
|
||||
"description": "Different brands that use the exact same icon",
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/duplicate" }
|
||||
"items": {"$ref": "#/definitions/duplicate"}
|
||||
},
|
||||
"loc": {
|
||||
"description": "Localized names of the brand",
|
||||
|
@ -46,7 +46,7 @@
|
|||
"old": {
|
||||
"description": "Old names, for backwards compatibility",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
"items": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"minProperties": 1,
|
||||
|
@ -702,7 +702,7 @@
|
|||
"icons": {
|
||||
"description": "A list of brands",
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/brand" }
|
||||
"items": {"$ref": "#/definitions/brand"}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
|
|
@ -6,12 +6,3 @@
|
|||
|
||||
# We use our own formatting for the data files.
|
||||
_data/simple-icons.json
|
||||
|
||||
# JavaScript templates are invalid JavaScript so cannot be formatted.
|
||||
scripts/build/templates/*.js
|
||||
|
||||
# Generated JavaScript files don't need to be formatted
|
||||
index.js
|
||||
index.mjs
|
||||
index.d.ts
|
||||
sdk.js
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"singleQuote": true
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": false
|
||||
}
|
||||
|
|
49
.xo-config.json
Normal file
49
.xo-config.json
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"prettier": true,
|
||||
"space": 2,
|
||||
"plugins": ["import"],
|
||||
"rules": {
|
||||
"n/no-unsupported-features": "off",
|
||||
"n/no-unsupported-features/node-builtins": "off",
|
||||
"n/file-extension-in-import": "off",
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{
|
||||
"ignoreCase": false,
|
||||
"ignoreDeclarationSort": true,
|
||||
"ignoreMemberSort": false,
|
||||
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
|
||||
"allowSeparatedGroups": false
|
||||
}
|
||||
],
|
||||
"import/no-named-as-default": "off",
|
||||
"import/extensions": "off",
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"groups": ["builtin", "external", "parent", "sibling", "index"],
|
||||
"alphabetize": {
|
||||
"order": "asc",
|
||||
"caseInsensitive": true
|
||||
},
|
||||
"warnOnUnassignedImports": true,
|
||||
"newlines-between": "never"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["sdk.mjs", "sdk.d.ts"],
|
||||
"nodeVersion": ">=14"
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"scripts/**/*",
|
||||
"tests/**/*",
|
||||
"svglint.config.mjs",
|
||||
"svgo.config.mjs"
|
||||
],
|
||||
"nodeVersion": ">=18"
|
||||
}
|
||||
]
|
||||
}
|
26
package.json
26
package.json
|
@ -88,6 +88,7 @@
|
|||
"chalk": "5.3.0",
|
||||
"editorconfig-checker": "5.1.5",
|
||||
"esbuild": "0.19.4",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"fake-diff": "1.0.0",
|
||||
"fast-fuzzy": "1.12.0",
|
||||
"get-relative-luminance": "1.0.0",
|
||||
|
@ -97,23 +98,24 @@
|
|||
"markdown-link-check": "3.11.2",
|
||||
"mocha": "10.2.0",
|
||||
"named-html-entities-json": "1.0.0",
|
||||
"prettier": "3.0.3",
|
||||
"svg-path-bbox": "1.2.5",
|
||||
"svg-path-segments": "1.0.0",
|
||||
"svglint": "2.4.0",
|
||||
"svgo": "3.0.2",
|
||||
"svgpath": "2.6.0",
|
||||
"typescript": "5.2.2"
|
||||
"typescript": "5.2.2",
|
||||
"xo": "0.58.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node scripts/build/package.js",
|
||||
"clean": "node scripts/build/clean.js",
|
||||
"format": "prettier --cache --write .",
|
||||
"lint": "npm run ourlint && npm run jslint && npm run jsonlint && npm run svglint && npm run wslint",
|
||||
"ourlint": "node scripts/lint/ourlint.js",
|
||||
"jslint": "prettier --cache --check .",
|
||||
"jsonlint": "node scripts/lint/jsonlint.js",
|
||||
"svglint": "svglint --ci $npm_config_icons",
|
||||
"build": "./scripts/build/package.js",
|
||||
"clean": "./scripts/build/clean.js",
|
||||
"format": "prettier --cache --write --ignore-unknown '**/*.!(js|jsx|mjs|cjs|ts|tsx|mts|cts|svg)' && xo --fix",
|
||||
"lint": "npm run ourlint && npm run prettierlint && npm run jslint && npm run jsonlint && npm run svglint && npm run wslint",
|
||||
"ourlint": "./scripts/lint/ourlint.js",
|
||||
"prettierlint": "prettier --cache --check --ignore-unknown '**/*.!(js|jsx|mjs|cjs|ts|tsx|mts|cts|svg)'",
|
||||
"jslint": "xo",
|
||||
"jsonlint": "./scripts/lint/jsonlint.js",
|
||||
"svglint": "svglint --ci $npm_config_icons --config svglint.config.mjs",
|
||||
"wslint": "editorconfig-checker",
|
||||
"prepare": "husky",
|
||||
"prepublishOnly": "npm run build",
|
||||
|
@ -121,8 +123,8 @@
|
|||
"test": "mocha tests --reporter tests/min-reporter.cjs --inline-diffs",
|
||||
"pretest": "npm run prepublishOnly",
|
||||
"posttest": "npm run postpublish",
|
||||
"get-filename": "node scripts/get-filename.js",
|
||||
"add-icon-data": "node scripts/add-icon-data.js"
|
||||
"get-filename": "./scripts/get-filename.js",
|
||||
"add-icon-data": "./scripts/add-icon-data.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12.18"
|
||||
|
|
29
scripts/add-icon-data.js
Normal file → Executable file
29
scripts/add-icon-data.js
Normal file → Executable file
|
@ -1,22 +1,23 @@
|
|||
#!/usr/bin/env node
|
||||
import process from 'node:process';
|
||||
import {ExitPromptError, checkbox, confirm, input} from '@inquirer/prompts';
|
||||
import chalk from 'chalk';
|
||||
import { input, confirm, checkbox, ExitPromptError } from '@inquirer/prompts';
|
||||
import autocomplete from 'inquirer-autocomplete-standalone';
|
||||
import {search} from 'fast-fuzzy';
|
||||
import getRelativeLuminance from 'get-relative-luminance';
|
||||
import { search } from 'fast-fuzzy';
|
||||
import autocomplete from 'inquirer-autocomplete-standalone';
|
||||
import {
|
||||
URL_REGEX,
|
||||
collator,
|
||||
getIconsDataString,
|
||||
titleToSlug,
|
||||
normalizeColor,
|
||||
titleToSlug,
|
||||
} from '../sdk.mjs';
|
||||
import { getJsonSchemaData, writeIconsData } from './utils.js';
|
||||
import {getJsonSchemaData, writeIconsData} from './utils.js';
|
||||
|
||||
const iconsData = JSON.parse(await getIconsDataString());
|
||||
const jsonSchema = await getJsonSchemaData();
|
||||
|
||||
const HEX_REGEX = /^#?[a-f0-9]{3,8}$/i;
|
||||
const HEX_REGEX = /^#?[a-f\d]{3,8}$/i;
|
||||
|
||||
const aliasTypes = ['aka', 'old'].map((key) => ({
|
||||
name: `${key} (${jsonSchema.definitions.brand.properties.aliases.properties[key].description})`,
|
||||
|
@ -25,7 +26,7 @@ const aliasTypes = ['aka', 'old'].map((key) => ({
|
|||
|
||||
const licenseTypes =
|
||||
jsonSchema.definitions.brand.properties.license.oneOf[0].properties.type.enum.map(
|
||||
(license) => ({ name: license, value: license }),
|
||||
(license) => ({name: license, value: license}),
|
||||
);
|
||||
|
||||
const isValidURL = (input) =>
|
||||
|
@ -35,7 +36,7 @@ const isValidHexColor = (input) =>
|
|||
HEX_REGEX.test(input) || 'Must be a valid hex code.';
|
||||
|
||||
const isNewIcon = (input) =>
|
||||
!iconsData.icons.find(
|
||||
!iconsData.icons.some(
|
||||
(icon) =>
|
||||
icon.title === input || titleToSlug(icon.title) === titleToSlug(input),
|
||||
) || 'This icon title or slug already exists.';
|
||||
|
@ -83,10 +84,10 @@ try {
|
|||
? {
|
||||
type: await autocomplete({
|
||||
message: "What is the icon's license?",
|
||||
source: async (input) => {
|
||||
async source(input) {
|
||||
input = (input || '').trim();
|
||||
return input
|
||||
? search(input, licenseTypes, { keySelector: (x) => x.value })
|
||||
? search(input, licenseTypes, {keySelector: (x) => x.value})
|
||||
: licenseTypes;
|
||||
},
|
||||
}),
|
||||
|
@ -107,12 +108,14 @@ try {
|
|||
}).then(async (aliases) => {
|
||||
const result = {};
|
||||
for (const alias of aliases) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
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,
|
||||
|
@ -136,11 +139,11 @@ try {
|
|||
console.log(chalk.red('\nAborted.'));
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof ExitPromptError) {
|
||||
} catch (error) {
|
||||
if (error instanceof ExitPromptError) {
|
||||
console.log(chalk.red('\nAborted.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
throw err;
|
||||
throw error;
|
||||
}
|
||||
|
|
38
scripts/build/clean.js
Normal file → Executable file
38
scripts/build/clean.js
Normal file → Executable file
|
@ -1,29 +1,37 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview
|
||||
* Clean files built by the build process.
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { getDirnameFromImportMeta } from '../../sdk.mjs';
|
||||
import process from 'node:process';
|
||||
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
||||
|
||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||
const files = ['index.js', 'index.mjs', 'index.d.ts', 'sdk.js'];
|
||||
|
||||
const fileExists = (fpath) =>
|
||||
new Promise((r) => fs.access(fpath, fs.constants.F_OK, (e) => r(!e)));
|
||||
fs
|
||||
.access(fpath, fs.constants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
Promise.all(
|
||||
files.map(async (file) => {
|
||||
const filepath = path.join(rootDirectory, file);
|
||||
if (!(await fileExists(filepath))) {
|
||||
console.error(`File ${file} does not exist, skipping...`);
|
||||
return;
|
||||
}
|
||||
return fs.promises.unlink(filepath);
|
||||
}),
|
||||
).catch((error) => {
|
||||
console.error(`Error cleaning files: ${error.message}`);
|
||||
try {
|
||||
Promise.all(
|
||||
files.map(async (file) => {
|
||||
const filepath = path.join(rootDirectory, file);
|
||||
if (!(await fileExists(filepath))) {
|
||||
console.error(`File ${file} does not exist, skipping...`);
|
||||
return;
|
||||
}
|
||||
|
||||
return fs.unlink(filepath);
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error cleaning files:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
65
scripts/build/package.js
Normal file → Executable file
65
scripts/build/package.js
Normal file → Executable file
|
@ -1,36 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview
|
||||
* Simple Icons package build script.
|
||||
*/
|
||||
|
||||
import { promises as fs } from 'node:fs';
|
||||
import {promises as fs} from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import util from 'node:util';
|
||||
import { transform as esbuildTransform } from 'esbuild';
|
||||
import {transform as esbuildTransform} from 'esbuild';
|
||||
import {
|
||||
collator,
|
||||
getDirnameFromImportMeta,
|
||||
getIconSlug,
|
||||
getIconsData,
|
||||
slugToVariableName,
|
||||
svgToPath,
|
||||
titleToHtmlFriendly,
|
||||
slugToVariableName,
|
||||
getIconsData,
|
||||
getDirnameFromImportMeta,
|
||||
collator,
|
||||
} from '../../sdk.mjs';
|
||||
|
||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||
|
||||
const UTF8 = 'utf8';
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const iconsDir = path.resolve(rootDir, 'icons');
|
||||
const indexJsFile = path.resolve(rootDir, 'index.js');
|
||||
const indexMjsFile = path.resolve(rootDir, 'index.mjs');
|
||||
const sdkJsFile = path.resolve(rootDir, 'sdk.js');
|
||||
const sdkMjsFile = path.resolve(rootDir, 'sdk.mjs');
|
||||
const indexDtsFile = path.resolve(rootDir, 'index.d.ts');
|
||||
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||
const iconsDirectory = path.resolve(rootDirectory, 'icons');
|
||||
const indexJsFile = path.resolve(rootDirectory, 'index.js');
|
||||
const indexMjsFile = path.resolve(rootDirectory, 'index.mjs');
|
||||
const sdkJsFile = path.resolve(rootDirectory, 'sdk.js');
|
||||
const sdkMjsFile = path.resolve(rootDirectory, 'sdk.mjs');
|
||||
const indexDtsFile = path.resolve(rootDirectory, 'index.d.ts');
|
||||
|
||||
const templatesDir = path.resolve(__dirname, 'templates');
|
||||
const iconObjectTemplateFile = path.resolve(templatesDir, 'icon-object.js');
|
||||
const templatesDirectory = path.resolve(__dirname, 'templates');
|
||||
const iconObjectTemplateFile = path.resolve(
|
||||
templatesDirectory,
|
||||
'icon-object.js.template',
|
||||
);
|
||||
|
||||
const build = async () => {
|
||||
const icons = await getIconsData();
|
||||
|
@ -38,8 +42,9 @@ const build = async () => {
|
|||
|
||||
// Local helper functions
|
||||
const escape = (value) => {
|
||||
return value.replace(/(?<!\\)'/g, "\\'");
|
||||
return value.replaceAll(/(?<!\\)'/g, "\\'");
|
||||
};
|
||||
|
||||
const licenseToObject = (license) => {
|
||||
if (license === undefined) {
|
||||
return;
|
||||
|
@ -48,8 +53,10 @@ const build = async () => {
|
|||
if (license.url === undefined) {
|
||||
license.url = `https://spdx.org/licenses/${license.type}`;
|
||||
}
|
||||
|
||||
return license;
|
||||
};
|
||||
|
||||
const iconToObject = (icon) => {
|
||||
return util.format(
|
||||
iconObjectTemplate,
|
||||
|
@ -65,11 +72,13 @@ const build = async () => {
|
|||
: '',
|
||||
);
|
||||
};
|
||||
const writeJs = async (filepath, rawJavaScript, opts = null) => {
|
||||
opts = opts === null ? { minify: true } : opts;
|
||||
const { code } = await esbuildTransform(rawJavaScript, opts);
|
||||
|
||||
const writeJs = async (filepath, rawJavaScript, options = null) => {
|
||||
options = options === null ? {minify: true} : options;
|
||||
const {code} = await esbuildTransform(rawJavaScript, options);
|
||||
await fs.writeFile(filepath, code);
|
||||
};
|
||||
|
||||
const writeTs = async (filepath, rawTypeScript) => {
|
||||
await fs.writeFile(filepath, rawTypeScript);
|
||||
};
|
||||
|
@ -78,13 +87,13 @@ const build = async () => {
|
|||
const buildIcons = await Promise.all(
|
||||
icons.map(async (icon) => {
|
||||
const filename = getIconSlug(icon);
|
||||
const svgFilepath = path.resolve(iconsDir, `${filename}.svg`);
|
||||
const svgFilepath = path.resolve(iconsDirectory, `${filename}.svg`);
|
||||
icon.svg = await fs.readFile(svgFilepath, UTF8);
|
||||
icon.path = svgToPath(icon.svg);
|
||||
icon.slug = filename;
|
||||
const iconObject = iconToObject(icon);
|
||||
const iconExportName = slugToVariableName(icon.slug);
|
||||
return { icon, iconObject, iconExportName };
|
||||
return {icon, iconObject, iconExportName};
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -93,33 +102,33 @@ const build = async () => {
|
|||
const iconsBarrelMjs = [];
|
||||
|
||||
buildIcons.sort((a, b) => collator.compare(a.icon.title, b.icon.title));
|
||||
for (const { iconObject, iconExportName } of buildIcons) {
|
||||
for (const {iconObject, iconExportName} of buildIcons) {
|
||||
iconsBarrelDts.push(`export const ${iconExportName}:I;`);
|
||||
iconsBarrelJs.push(`${iconExportName}:${iconObject},`);
|
||||
iconsBarrelMjs.push(`export const ${iconExportName}=${iconObject}`);
|
||||
}
|
||||
|
||||
// constants used in templates to reduce package size
|
||||
// Constants used in templates to reduce package size
|
||||
const constantsString = `const a='<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>',b='</title><path d="',c='"/></svg>';`;
|
||||
|
||||
// write our file containing the exports of all icons in CommonJS ...
|
||||
// Write our file containing the exports of all icons in CommonJS ...
|
||||
const rawIndexJs = `${constantsString}module.exports={${iconsBarrelJs.join(
|
||||
'',
|
||||
)}};`;
|
||||
await writeJs(indexJsFile, rawIndexJs);
|
||||
// and ESM
|
||||
// ... and ESM
|
||||
const rawIndexMjs = constantsString + iconsBarrelMjs.join('');
|
||||
await writeJs(indexMjsFile, rawIndexMjs);
|
||||
// and create a type declaration file
|
||||
// ... and create a type declaration file
|
||||
const rawIndexDts = `import {SimpleIcon} from "./types";export {SimpleIcon};type I=SimpleIcon;${iconsBarrelDts.join(
|
||||
'',
|
||||
)}`;
|
||||
await writeTs(indexDtsFile, rawIndexDts);
|
||||
|
||||
// create a CommonJS SDK file
|
||||
// Create a CommonJS SDK file
|
||||
await writeJs(sdkJsFile, await fs.readFile(sdkMjsFile, UTF8), {
|
||||
format: 'cjs',
|
||||
});
|
||||
};
|
||||
|
||||
build();
|
||||
await build();
|
||||
|
|
8
scripts/get-filename.js
Normal file → Executable file
8
scripts/get-filename.js
Normal file → Executable file
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview
|
||||
* Script that takes a brand name as argument and outputs the corresponding
|
||||
|
@ -5,16 +6,13 @@
|
|||
*/
|
||||
|
||||
import process from 'node:process';
|
||||
import { titleToSlug } from '../sdk.mjs';
|
||||
import {titleToSlug} from '../sdk.mjs';
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
console.error('Provide a brand name as argument');
|
||||
process.exit(1);
|
||||
} else {
|
||||
const brandName = process.argv
|
||||
.slice(3)
|
||||
.reduce((acc, arg) => `${acc} ${arg}`, process.argv[2]);
|
||||
|
||||
const brandName = process.argv[2];
|
||||
const filename = titleToSlug(brandName);
|
||||
console.log(`For '${brandName}' use the file 'icons/${filename}.svg'`);
|
||||
}
|
||||
|
|
11
scripts/lint/jsonlint.js
Normal file → Executable file
11
scripts/lint/jsonlint.js
Normal file → Executable file
|
@ -1,20 +1,21 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview
|
||||
* CLI tool to run jsonschema on the simple-icons.json data file.
|
||||
*/
|
||||
|
||||
import process from 'node:process';
|
||||
import { Validator } from 'jsonschema';
|
||||
import { getIconsData } from '../../sdk.mjs';
|
||||
import { getJsonSchemaData } from '../utils.js';
|
||||
import {Validator} from 'jsonschema';
|
||||
import {getIconsData} from '../../sdk.mjs';
|
||||
import {getJsonSchemaData} from '../utils.js';
|
||||
|
||||
const icons = await getIconsData();
|
||||
const schema = await getJsonSchemaData();
|
||||
|
||||
const validator = new Validator();
|
||||
const result = validator.validate({ icons }, schema);
|
||||
const result = validator.validate({icons}, schema);
|
||||
if (result.errors.length > 0) {
|
||||
result.errors.forEach((error) => console.error(error));
|
||||
for (const error of result.errors) console.error(error);
|
||||
console.error(`Found ${result.errors.length} error(s) in simple-icons.json`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
40
scripts/lint/ourlint.js
Normal file → Executable file
40
scripts/lint/ourlint.js
Normal file → Executable file
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview
|
||||
* Linters for the package that can't easily be implemented in the existing
|
||||
|
@ -5,9 +6,8 @@
|
|||
*/
|
||||
|
||||
import process from 'node:process';
|
||||
import { URL } from 'node:url';
|
||||
import fakeDiff from 'fake-diff';
|
||||
import { getIconsDataString, normalizeNewlines, collator } from '../../sdk.mjs';
|
||||
import {collator, getIconsDataString, normalizeNewlines} from '../../sdk.mjs';
|
||||
|
||||
/**
|
||||
* Contains our tests so they can be isolated from each other.
|
||||
|
@ -15,39 +15,43 @@ import { getIconsDataString, normalizeNewlines, collator } from '../../sdk.mjs';
|
|||
*/
|
||||
const TESTS = {
|
||||
/* Tests whether our icons are in alphabetical order */
|
||||
alphabetical: (data) => {
|
||||
alphabetical(data) {
|
||||
const collector = (invalidEntries, icon, index, array) => {
|
||||
if (index > 0) {
|
||||
const prev = array[index - 1];
|
||||
const comparison = collator.compare(icon.title, prev.title);
|
||||
const previous = array[index - 1];
|
||||
const comparison = collator.compare(icon.title, previous.title);
|
||||
if (comparison < 0) {
|
||||
invalidEntries.push(icon);
|
||||
} else if (comparison === 0) {
|
||||
if (prev.slug) {
|
||||
if (!icon.slug || collator.compare(icon.slug, prev.slug) < 0) {
|
||||
invalidEntries.push(icon);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
comparison === 0 &&
|
||||
previous.slug &&
|
||||
(!icon.slug || collator.compare(icon.slug, previous.slug) < 0)
|
||||
) {
|
||||
invalidEntries.push(icon);
|
||||
}
|
||||
}
|
||||
|
||||
return invalidEntries;
|
||||
};
|
||||
|
||||
const format = (icon) => {
|
||||
if (icon.slug) {
|
||||
return `${icon.title} (${icon.slug})`;
|
||||
}
|
||||
|
||||
return icon.title;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unicorn/no-array-reduce, unicorn/no-array-callback-reference
|
||||
const invalids = data.icons.reduce(collector, []);
|
||||
if (invalids.length) {
|
||||
if (invalids.length > 0) {
|
||||
return `Some icons aren't in alphabetical order:
|
||||
${invalids.map((icon) => format(icon)).join(', ')}`;
|
||||
}
|
||||
},
|
||||
|
||||
/* Check the formatting of the data file */
|
||||
prettified: (data, dataString) => {
|
||||
prettified(data, dataString) {
|
||||
const normalizedDataString = normalizeNewlines(dataString);
|
||||
const dataPretty = `${JSON.stringify(data, null, 4)}\n`;
|
||||
|
||||
|
@ -58,9 +62,9 @@ const TESTS = {
|
|||
},
|
||||
|
||||
/* Check redundant trailing slash in URL */
|
||||
checkUrl: (data) => {
|
||||
checkUrl(data) {
|
||||
const hasRedundantTrailingSlash = (url) => {
|
||||
const origin = new URL(url).origin;
|
||||
const {origin} = new global.URL(url);
|
||||
return /^\/+$/.test(url.replace(origin, ''));
|
||||
};
|
||||
|
||||
|
@ -89,9 +93,11 @@ const data = JSON.parse(dataString);
|
|||
|
||||
const errors = (
|
||||
await Promise.all(Object.values(TESTS).map((test) => test(data, dataString)))
|
||||
).filter(Boolean);
|
||||
)
|
||||
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||
.filter(Boolean);
|
||||
|
||||
if (errors.length > 0) {
|
||||
errors.forEach((error) => console.error(`\u001b[31m${error}\u001b[0m`));
|
||||
for (const error of errors) console.error(`\u001B[31m${error}\u001B[0m`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
24
scripts/release/reformat-markdown.js
Normal file → Executable file
24
scripts/release/reformat-markdown.js
Normal file → Executable file
|
@ -1,19 +1,21 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview
|
||||
* Rewrite some Markdown files.
|
||||
*/
|
||||
|
||||
import {readFile, writeFile} from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { writeFile, readFile } from 'node:fs/promises';
|
||||
import { getDirnameFromImportMeta } from '../../sdk.mjs';
|
||||
import process from 'node:process';
|
||||
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
||||
|
||||
const LINKS_BRANCH = process.argv[2] || 'develop';
|
||||
|
||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const readmeFile = path.resolve(rootDir, 'README.md');
|
||||
const disclaimerFile = path.resolve(rootDir, 'DISCLAIMER.md');
|
||||
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||
const readmeFile = path.resolve(rootDirectory, 'README.md');
|
||||
const disclaimerFile = path.resolve(rootDirectory, 'DISCLAIMER.md');
|
||||
|
||||
const reformat = async (filePath) => {
|
||||
const fileContent = await readFile(filePath, 'utf8');
|
||||
|
@ -21,17 +23,17 @@ const reformat = async (filePath) => {
|
|||
filePath,
|
||||
fileContent
|
||||
// Replace all CDN links with raw links
|
||||
.replace(
|
||||
.replaceAll(
|
||||
/https:\/\/cdn.simpleicons.org\/(.+)\/000\/fff/g,
|
||||
`https://raw.githubusercontent.com/simple-icons/simple-icons/${LINKS_BRANCH}/icons/$1.svg`,
|
||||
)
|
||||
// Replace all GitHub blockquotes with regular markdown
|
||||
// Reference: https://github.com/orgs/community/discussions/16925
|
||||
.replace(
|
||||
/\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\](?!\()/g,
|
||||
function (str, $0) {
|
||||
const capital = $0.substr(0, 1);
|
||||
const body = $0.substr(1).toLowerCase();
|
||||
.replaceAll(
|
||||
/\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)](?!\()/g,
|
||||
function (string_, $0) {
|
||||
const capital = $0.slice(0, 1);
|
||||
const body = $0.slice(1).toLowerCase();
|
||||
return `**${capital + body}**`;
|
||||
},
|
||||
),
|
||||
|
|
23
scripts/release/update-cdn-urls.js
Normal file → Executable file
23
scripts/release/update-cdn-urls.js
Normal file → Executable file
|
@ -1,35 +1,36 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview
|
||||
* Updates the CDN URLs in the README.md to match the major version in the
|
||||
* NPM package manifest. Does nothing if the README.md is already up-to-date.
|
||||
*/
|
||||
|
||||
import process from 'node:process';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { getDirnameFromImportMeta } from '../../sdk.mjs';
|
||||
import process from 'node:process';
|
||||
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
||||
|
||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const packageJsonFile = path.resolve(rootDir, 'package.json');
|
||||
const readmeFile = path.resolve(rootDir, 'README.md');
|
||||
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||
const packageJsonFile = path.resolve(rootDirectory, 'package.json');
|
||||
const readmeFile = path.resolve(rootDirectory, 'README.md');
|
||||
|
||||
const getMajorVersion = (semVerVersion) => {
|
||||
const majorVersionAsString = semVerVersion.split('.')[0];
|
||||
return parseInt(majorVersionAsString);
|
||||
const getMajorVersion = (semVersion) => {
|
||||
const majorVersionAsString = semVersion.split('.')[0];
|
||||
return Number.parseInt(majorVersionAsString, 10);
|
||||
};
|
||||
|
||||
const getManifest = async () => {
|
||||
const manifestRaw = await fs.readFile(packageJsonFile, 'utf-8');
|
||||
const manifestRaw = await fs.readFile(packageJsonFile, 'utf8');
|
||||
return JSON.parse(manifestRaw);
|
||||
};
|
||||
|
||||
const updateVersionInReadmeIfNecessary = async (majorVersion) => {
|
||||
let content = await fs.readFile(readmeFile, 'utf8');
|
||||
|
||||
content = content.replace(
|
||||
/simple-icons@v[0-9]+/g,
|
||||
content = content.replaceAll(
|
||||
/simple-icons@v\d+/g,
|
||||
`simple-icons@v${majorVersion}`,
|
||||
);
|
||||
|
||||
|
|
34
scripts/release/update-sdk-ts-defs.js
Normal file → Executable file
34
scripts/release/update-sdk-ts-defs.js
Normal file → Executable file
|
@ -1,33 +1,34 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview
|
||||
* Updates the SDK Typescript definitions located in the file sdk.d.ts
|
||||
* to match the current definitions of functions of sdk.mjs.
|
||||
*/
|
||||
|
||||
import process from 'node:process';
|
||||
import {execSync} from 'node:child_process';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { getDirnameFromImportMeta } from '../../sdk.mjs';
|
||||
import process from 'node:process';
|
||||
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
||||
|
||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||
|
||||
const sdkTs = path.resolve(rootDir, 'sdk.d.ts');
|
||||
const sdkMts = path.resolve(rootDir, 'sdk.d.mts');
|
||||
const sdkMjs = path.resolve(rootDir, 'sdk.mjs');
|
||||
const sdkTs = path.resolve(rootDirectory, 'sdk.d.ts');
|
||||
const sdkMts = path.resolve(rootDirectory, 'sdk.d.mts');
|
||||
const sdkMjs = path.resolve(rootDirectory, 'sdk.mjs');
|
||||
|
||||
const generateSdkMts = async () => {
|
||||
// remove temporally type definitions imported with comments
|
||||
// Remove temporally type definitions imported with comments
|
||||
// in sdk.mjs to avoid circular imports
|
||||
const originalSdkMjsContent = await fs.readFile(sdkMjs, 'utf-8');
|
||||
const tempSdkMjsContent = originalSdkMjsContent
|
||||
const originalSdkMjsContent = await fs.readFile(sdkMjs, 'utf8');
|
||||
const temporarySdkMjsContent = originalSdkMjsContent
|
||||
.split('\n')
|
||||
.filter((line) => {
|
||||
return !line.startsWith(' * @typedef {import("./sdk")');
|
||||
})
|
||||
.join('\n');
|
||||
await fs.writeFile(sdkMjs, tempSdkMjsContent);
|
||||
await fs.writeFile(sdkMjs, temporarySdkMjsContent);
|
||||
try {
|
||||
execSync(
|
||||
'npx tsc sdk.mjs' +
|
||||
|
@ -41,6 +42,7 @@ const generateSdkMts = async () => {
|
|||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await fs.writeFile(sdkMjs, originalSdkMjsContent);
|
||||
};
|
||||
|
||||
|
@ -49,13 +51,15 @@ const generateSdkTs = async () => {
|
|||
.access(sdkMts)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
fileExists && (await fs.unlink(sdkMts));
|
||||
if (fileExists) await fs.unlink(sdkMts);
|
||||
await generateSdkMts();
|
||||
|
||||
const autogeneratedMsg = '/* The next code is autogenerated from sdk.mjs */';
|
||||
const autogeneratedMessage =
|
||||
'/* The next code is autogenerated from sdk.mjs */';
|
||||
const newSdkTsContent =
|
||||
(await fs.readFile(sdkTs, 'utf-8')).split(autogeneratedMsg)[0] +
|
||||
`${autogeneratedMsg}\n\n${await fs.readFile(sdkMts, 'utf-8')}`;
|
||||
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||
(await fs.readFile(sdkTs, 'utf8')).split(autogeneratedMessage)[0] +
|
||||
`${autogeneratedMessage}\n\n${await fs.readFile(sdkMts, 'utf8')}`;
|
||||
|
||||
await fs.writeFile(sdkTs, newSdkTsContent);
|
||||
await fs.unlink(sdkMts);
|
||||
|
|
14
scripts/release/update-slugs-table.js
Normal file → Executable file
14
scripts/release/update-slugs-table.js
Normal file → Executable file
|
@ -1,22 +1,23 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview
|
||||
* Generates a MarkDown file that lists every brand name and their slug.
|
||||
*/
|
||||
|
||||
import { promises as fs } from 'node:fs';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { getIconsData, getIconSlug } from '../../sdk.mjs';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {getIconSlug, getIconsData} from '../../sdk.mjs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const slugsFile = path.resolve(rootDir, 'slugs.md');
|
||||
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||
const slugsFile = path.resolve(rootDirectory, 'slugs.md');
|
||||
|
||||
let content = `<!--
|
||||
This file is automatically generated. If you want to change something, please
|
||||
update the script at '${path.relative(rootDir, __filename)}'.
|
||||
update the script at '${path.relative(rootDirectory, __filename)}'.
|
||||
-->
|
||||
|
||||
# Simple Icons slugs
|
||||
|
@ -31,4 +32,5 @@ for (const icon of icons) {
|
|||
const brandSlug = getIconSlug(icon);
|
||||
content += `| \`${brandName}\` | \`${brandSlug}\` |\n`;
|
||||
}
|
||||
|
||||
await fs.writeFile(slugsFile, content);
|
||||
|
|
20
scripts/release/update-svgs-count.js
Normal file → Executable file
20
scripts/release/update-svgs-count.js
Normal file → Executable file
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview
|
||||
* Replaces the SVG count milestone "Over <NUMBER> Free SVG icons..." located
|
||||
|
@ -5,32 +6,33 @@
|
|||
* more than the previous milestone.
|
||||
*/
|
||||
|
||||
import process from 'node:process';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { getDirnameFromImportMeta, getIconsData } from '../../sdk.mjs';
|
||||
import process from 'node:process';
|
||||
import {getDirnameFromImportMeta, getIconsData} from '../../sdk.mjs';
|
||||
|
||||
const regexMatcher = /Over\s(\d+)\s/;
|
||||
const updateRange = 100;
|
||||
|
||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const readmeFile = path.resolve(rootDir, 'README.md');
|
||||
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||
const readmeFile = path.resolve(rootDirectory, 'README.md');
|
||||
|
||||
const readmeContent = await fs.readFile(readmeFile, 'utf-8');
|
||||
const readmeContent = await fs.readFile(readmeFile, 'utf8');
|
||||
|
||||
let overNIconsInReadme;
|
||||
try {
|
||||
overNIconsInReadme = parseInt(regexMatcher.exec(readmeContent)[1]);
|
||||
} catch (err) {
|
||||
overNIconsInReadme = Number.parseInt(regexMatcher.exec(readmeContent)[1], 10);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Failed to obtain number of SVG icons of current milestone in README:',
|
||||
err,
|
||||
error,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const nIcons = (await getIconsData()).length;
|
||||
const iconsData = await getIconsData();
|
||||
const nIcons = iconsData.length;
|
||||
const newNIcons = overNIconsInReadme + updateRange;
|
||||
|
||||
if (nIcons > newNIcons) {
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
import { getDirnameFromImportMeta, getIconDataPath } from '../sdk.mjs';
|
||||
import path from 'node:path';
|
||||
import {getDirnameFromImportMeta, getIconDataPath} from '../sdk.mjs';
|
||||
|
||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||
|
||||
/**
|
||||
* Get JSON schema data.
|
||||
* @param {String} rootDir Path to the root directory of the project.
|
||||
* @param {String} rootDirectory Path to the root directory of the project.
|
||||
*/
|
||||
export const getJsonSchemaData = async (
|
||||
rootDir = path.resolve(__dirname, '..'),
|
||||
rootDirectory = path.resolve(__dirname, '..'),
|
||||
) => {
|
||||
const jsonSchemaPath = path.resolve(rootDir, '.jsonschema.json');
|
||||
const jsonSchemaPath = path.resolve(rootDirectory, '.jsonschema.json');
|
||||
const jsonSchemaString = await fs.readFile(jsonSchemaPath, 'utf8');
|
||||
return JSON.parse(jsonSchemaString);
|
||||
};
|
||||
|
@ -19,14 +19,14 @@ export const getJsonSchemaData = async (
|
|||
/**
|
||||
* Write icons data to _data/simple-icons.json.
|
||||
* @param {Object} iconsData Icons data object.
|
||||
* @param {String} rootDir Path to the root directory of the project.
|
||||
* @param {String} rootDirectory Path to the root directory of the project.
|
||||
*/
|
||||
export const writeIconsData = async (
|
||||
iconsData,
|
||||
rootDir = path.resolve(__dirname, '..'),
|
||||
rootDirectory = path.resolve(__dirname, '..'),
|
||||
) => {
|
||||
await fs.writeFile(
|
||||
getIconDataPath(rootDir),
|
||||
getIconDataPath(rootDirectory),
|
||||
`${JSON.stringify(iconsData, null, 4)}\n`,
|
||||
'utf8',
|
||||
);
|
||||
|
|
16
sdk.d.ts
vendored
16
sdk.d.ts
vendored
|
@ -3,7 +3,7 @@
|
|||
* Types for Simple Icons SDK.
|
||||
*/
|
||||
|
||||
import type { CustomLicense, SPDXLicense } from './types';
|
||||
import type {CustomLicense, SPDXLicense} from './types';
|
||||
|
||||
/**
|
||||
* The data for a third-party extension.
|
||||
|
@ -33,14 +33,14 @@ type ThirdPartyExtensionSubject = {
|
|||
export type Aliases = {
|
||||
aka?: string[];
|
||||
dup?: DuplicateAlias[];
|
||||
loc?: { [key: string]: string };
|
||||
loc?: Record<string, string>;
|
||||
};
|
||||
|
||||
type DuplicateAlias = {
|
||||
title: string;
|
||||
hex?: string;
|
||||
guidelines?: string;
|
||||
loc?: { [key: string]: string };
|
||||
loc?: Record<string, string>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -62,8 +62,8 @@ export type IconData = {
|
|||
|
||||
/* The next code is autogenerated from sdk.mjs */
|
||||
|
||||
export const URL_REGEX: RegExp;
|
||||
export const SVG_PATH_REGEX: RegExp;
|
||||
export const URL_REGEX: RegExp; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
export const SVG_PATH_REGEX: RegExp; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
export function getDirnameFromImportMeta(importMetaUrl: string): string;
|
||||
export function getIconSlug(icon: IconData): string;
|
||||
export function svgToPath(svg: string): string;
|
||||
|
@ -71,9 +71,9 @@ export function titleToSlug(title: string): string;
|
|||
export function slugToVariableName(slug: string): string;
|
||||
export function titleToHtmlFriendly(brandTitle: string): string;
|
||||
export function htmlFriendlyToTitle(htmlFriendlyTitle: string): string;
|
||||
export function getIconDataPath(rootDir?: string): string;
|
||||
export function getIconsDataString(rootDir?: string): string;
|
||||
export function getIconsData(rootDir?: string): IconData[];
|
||||
export function getIconDataPath(rootDirectory?: string): string;
|
||||
export function getIconsDataString(rootDirectory?: string): string;
|
||||
export function getIconsData(rootDirectory?: string): IconData[];
|
||||
export function normalizeNewlines(text: string): string;
|
||||
export function normalizeColor(text: string): string;
|
||||
export function getThirdPartyExtensions(
|
||||
|
|
64
sdk.mjs
64
sdk.mjs
|
@ -3,9 +3,9 @@
|
|||
* Simple Icons SDK.
|
||||
*/
|
||||
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import path from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
/**
|
||||
* @typedef {import("./sdk").ThirdPartyExtension} ThirdPartyExtension
|
||||
|
@ -26,12 +26,12 @@ const TITLE_TO_SLUG_REPLACEMENTS = {
|
|||
ŧ: 't',
|
||||
};
|
||||
|
||||
const TITLE_TO_SLUG_CHARS_REGEX = RegExp(
|
||||
const TITLE_TO_SLUG_CHARS_REGEX = new RegExp(
|
||||
`[${Object.keys(TITLE_TO_SLUG_REPLACEMENTS).join('')}]`,
|
||||
'g',
|
||||
);
|
||||
|
||||
const TITLE_TO_SLUG_RANGE_REGEX = /[^a-z0-9]/g;
|
||||
const TITLE_TO_SLUG_RANGE_REGEX = /[^a-z\d]/g;
|
||||
|
||||
/**
|
||||
* Regex to validate HTTPs URLs.
|
||||
|
@ -41,7 +41,7 @@ export const URL_REGEX = /^https:\/\/[^\s"']+$/;
|
|||
/**
|
||||
* Regex to validate SVG paths.
|
||||
*/
|
||||
export const SVG_PATH_REGEX = /^m[-mzlhvcsqtae0-9,. ]+$/i;
|
||||
export const SVG_PATH_REGEX = /^m[-mzlhvcsqtae\d,. ]+$/i;
|
||||
|
||||
/**
|
||||
* Get the directory name where this file is located from `import.meta.url`,
|
||||
|
@ -74,12 +74,12 @@ export const svgToPath = (svg) => svg.split('"', 8)[7];
|
|||
export const titleToSlug = (title) =>
|
||||
title
|
||||
.toLowerCase()
|
||||
.replace(
|
||||
.replaceAll(
|
||||
TITLE_TO_SLUG_CHARS_REGEX,
|
||||
(char) => TITLE_TO_SLUG_REPLACEMENTS[char],
|
||||
)
|
||||
.normalize('NFD')
|
||||
.replace(TITLE_TO_SLUG_RANGE_REGEX, '');
|
||||
.replaceAll(TITLE_TO_SLUG_RANGE_REGEX, '');
|
||||
|
||||
/**
|
||||
* Converts a slug into a variable name that can be exported.
|
||||
|
@ -99,12 +99,12 @@ export const slugToVariableName = (slug) => {
|
|||
*/
|
||||
export const titleToHtmlFriendly = (brandTitle) =>
|
||||
brandTitle
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/./g, (char) => {
|
||||
const charCode = char.charCodeAt(0);
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll(/./g, (char) => {
|
||||
const charCode = char.codePointAt(0);
|
||||
return charCode > 127 ? `&#${charCode};` : char;
|
||||
});
|
||||
|
||||
|
@ -116,43 +116,45 @@ export const titleToHtmlFriendly = (brandTitle) =>
|
|||
*/
|
||||
export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
|
||||
htmlFriendlyTitle
|
||||
.replace(/&#([0-9]+);/g, (_, num) => String.fromCodePoint(parseInt(num)))
|
||||
.replace(
|
||||
.replaceAll(/&#(\d+);/g, (_, number_) =>
|
||||
String.fromCodePoint(Number.parseInt(number_, 10)),
|
||||
)
|
||||
.replaceAll(
|
||||
/&(quot|amp|lt|gt);/g,
|
||||
(_, ref) => ({ quot: '"', amp: '&', lt: '<', gt: '>' })[ref],
|
||||
(_, reference) => ({quot: '"', amp: '&', lt: '<', gt: '>'})[reference],
|
||||
);
|
||||
|
||||
/**
|
||||
* Get path of *_data/simpe-icons.json*.
|
||||
* @param {String} rootDir Path to the root directory of the project
|
||||
* @param {String} rootDirectory Path to the root directory of the project
|
||||
* @returns {String} Path of *_data/simple-icons.json*
|
||||
*/
|
||||
export const getIconDataPath = (
|
||||
rootDir = getDirnameFromImportMeta(import.meta.url),
|
||||
rootDirectory = getDirnameFromImportMeta(import.meta.url),
|
||||
) => {
|
||||
return path.resolve(rootDir, '_data', 'simple-icons.json');
|
||||
return path.resolve(rootDirectory, '_data', 'simple-icons.json');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get contents of *_data/simple-icons.json*.
|
||||
* @param {String} rootDir Path to the root directory of the project
|
||||
* @param {String} rootDirectory Path to the root directory of the project
|
||||
* @returns {String} Content of *_data/simple-icons.json*
|
||||
*/
|
||||
export const getIconsDataString = (
|
||||
rootDir = getDirnameFromImportMeta(import.meta.url),
|
||||
rootDirectory = getDirnameFromImportMeta(import.meta.url),
|
||||
) => {
|
||||
return fs.readFile(getIconDataPath(rootDir), 'utf8');
|
||||
return fs.readFile(getIconDataPath(rootDirectory), 'utf8');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get icons data as object from *_data/simple-icons.json*.
|
||||
* @param {String} rootDir Path to the root directory of the project
|
||||
* @param {String} rootDirectory Path to the root directory of the project
|
||||
* @returns {IconData[]} Icons data as array from *_data/simple-icons.json*
|
||||
*/
|
||||
export const getIconsData = async (
|
||||
rootDir = getDirnameFromImportMeta(import.meta.url),
|
||||
rootDirectory = getDirnameFromImportMeta(import.meta.url),
|
||||
) => {
|
||||
const fileContents = await getIconsDataString(rootDir);
|
||||
const fileContents = await getIconsDataString(rootDirectory);
|
||||
return JSON.parse(fileContents).icons;
|
||||
};
|
||||
|
||||
|
@ -162,7 +164,7 @@ export const getIconsData = async (
|
|||
* @returns {String} The text with Windows newline characters replaced by Unix ones
|
||||
*/
|
||||
export const normalizeNewlines = (text) => {
|
||||
return text.replace(/\r\n/g, '\n');
|
||||
return text.replaceAll('\r\n', '\n');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -173,10 +175,14 @@ export const normalizeNewlines = (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('');
|
||||
color = color
|
||||
.slice(0, 3)
|
||||
.map((x) => x.repeat(2))
|
||||
.join('');
|
||||
} else if (color.length > 6) {
|
||||
color = color.slice(0, 6);
|
||||
}
|
||||
|
||||
return color;
|
||||
};
|
||||
|
||||
|
@ -201,11 +207,11 @@ export const getThirdPartyExtensions = async (
|
|||
module = module.split('<img src="')[0];
|
||||
return {
|
||||
module: {
|
||||
name: /\[(.+)\]/.exec(module)[1],
|
||||
name: /\[(.+)]/.exec(module)[1],
|
||||
url: /\((.+)\)/.exec(module)[1],
|
||||
},
|
||||
author: {
|
||||
name: /\[(.+)\]/.exec(author)[1],
|
||||
name: /\[(.+)]/.exec(author)[1],
|
||||
url: /\((.+)\)/.exec(author)[1],
|
||||
},
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
const config = {
|
||||
multipass: true,
|
||||
eol: 'lf',
|
||||
plugins: [
|
||||
|
@ -62,7 +62,7 @@ export default {
|
|||
// Convert basic shapes (such as <circle>) to <path>
|
||||
name: 'convertShapeToPath',
|
||||
params: {
|
||||
// including <arc>
|
||||
// Including <arc>
|
||||
convertArcs: true,
|
||||
},
|
||||
},
|
||||
|
@ -93,7 +93,7 @@ export default {
|
|||
// to the <svg> tag if it's not there already
|
||||
name: 'addAttributesToSVGElement',
|
||||
params: {
|
||||
attributes: [{ role: 'img', xmlns: 'http://www.w3.org/2000/svg' }],
|
||||
attributes: [{role: 'img', xmlns: 'http://www.w3.org/2000/svg'}],
|
||||
},
|
||||
},
|
||||
'removeOffCanvasPaths',
|
||||
|
@ -102,3 +102,5 @@ export default {
|
|||
'reusePaths',
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { test } from 'mocha';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { getThirdPartyExtensions } from '../sdk.mjs';
|
||||
import {strict as assert} from 'node:assert';
|
||||
import {test} from 'mocha';
|
||||
import {getThirdPartyExtensions} from '../sdk.mjs';
|
||||
|
||||
test('README third party extensions must be alphabetically sorted', async () => {
|
||||
const thirdPartyExtensions = await getThirdPartyExtensions();
|
||||
assert.ok(thirdPartyExtensions.length > 0);
|
||||
|
||||
const thirdPartyExtensionsNames = thirdPartyExtensions.map(
|
||||
(ext) => ext.module.name,
|
||||
(extension) => extension.module.name,
|
||||
);
|
||||
|
||||
const expectedOrder = thirdPartyExtensionsNames.slice().sort();
|
||||
const expectedOrder = [...thirdPartyExtensionsNames].sort();
|
||||
assert.deepEqual(
|
||||
thirdPartyExtensionsNames,
|
||||
expectedOrder,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getIconsData, getIconSlug, slugToVariableName } from '../sdk.mjs';
|
||||
import * as simpleIcons from '../index.mjs';
|
||||
import { testIcon } from './test-icon.js';
|
||||
import {getIconSlug, getIconsData, slugToVariableName} from '../sdk.mjs';
|
||||
import {testIcon} from './test-icon.js';
|
||||
|
||||
for (const icon of await getIconsData()) {
|
||||
const slug = getIconSlug(icon);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { reporters, Runner } = require('mocha');
|
||||
const {reporters, Runner} = require('mocha');
|
||||
|
||||
const { EVENT_RUN_END } = Runner.constants;
|
||||
const {EVENT_RUN_END} = Runner.constants;
|
||||
|
||||
class EvenMoreMin extends reporters.Base {
|
||||
constructor(runner) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {strict as assert} from 'node:assert';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'mocha';
|
||||
import {describe, it} from 'mocha';
|
||||
import {
|
||||
SVG_PATH_REGEX,
|
||||
URL_REGEX,
|
||||
|
@ -9,7 +9,7 @@ import {
|
|||
titleToSlug,
|
||||
} from '../sdk.mjs';
|
||||
|
||||
const iconsDir = path.resolve(
|
||||
const iconsDirectory = path.resolve(
|
||||
getDirnameFromImportMeta(import.meta.url),
|
||||
'..',
|
||||
'icons',
|
||||
|
@ -26,7 +26,7 @@ const iconsDir = path.resolve(
|
|||
* @param {String} slug Icon data slug
|
||||
*/
|
||||
export const testIcon = (icon, subject, slug) => {
|
||||
const svgPath = path.resolve(iconsDir, `${slug}.svg`);
|
||||
const svgPath = path.resolve(iconsDirectory, `${slug}.svg`);
|
||||
|
||||
describe(icon.title, () => {
|
||||
it('has the correct "title"', () => {
|
||||
|
@ -81,7 +81,7 @@ export const testIcon = (icon, subject, slug) => {
|
|||
});
|
||||
|
||||
if (icon.slug) {
|
||||
// if an icon data has a slug, it must be different to the
|
||||
// If an icon data has a slug, it must be different to the
|
||||
// slug inferred from the title, which prevents adding
|
||||
// unnecessary slugs to icons data
|
||||
it(`'${icon.title}' slug must be necessary`, () => {
|
||||
|
|
5
types.d.ts
vendored
5
types.d.ts
vendored
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
export type License = SPDXLicense | CustomLicense;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export type SPDXLicense = {
|
||||
type: string;
|
||||
url: string;
|
||||
|
@ -18,7 +19,7 @@ export type CustomLicense = {
|
|||
/**
|
||||
* The data for a Simple Icon as is exported by the npm package.
|
||||
*/
|
||||
export interface SimpleIcon {
|
||||
export type SimpleIcon = {
|
||||
title: string;
|
||||
slug: string;
|
||||
svg: string;
|
||||
|
@ -27,4 +28,4 @@ export interface SimpleIcon {
|
|||
hex: string;
|
||||
guidelines?: string;
|
||||
license?: License;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue