chore(scripts): Refactor scripts to typescript (#3316)

* Adjust typescript types

* adjust types

* fix types in all helper files

* Fix types

* Migrate js files to ts files

* Refactor to TS files

* Rename extentions

* Adjust imports

* Fix builds

* Update lockfile

* Fix last typescript migration

* Fix entry path @lucide/outline-svg

* Fix types

* add checkout step

* format files

* Format files
This commit is contained in:
Eric Fennis
2025-06-18 15:47:24 +02:00
committed by GitHub
parent 7517894f2d
commit 3e644fda2d
111 changed files with 833 additions and 527 deletions

View File

@@ -26,38 +26,63 @@ async function getReleaseMetaData() {
return releaseMetaData;
}
function convertReleaseMetaData(releaseMetaData) {
return Object.entries(releaseMetaData)
.map(([key, value]) => [key, addAttribute(value, 'name', key)])
.map(([, value]) => value)
type Releases = Record<string, ReleaseMetaData>;
type ReleaseMetaData = {
createdRelease: {
version: string;
date: string;
};
changedRelease: {
version: string;
date: string;
};
};
type ReleaseMetaDataWithName = ReleaseMetaData & {
name: string;
};
function convertReleaseMetaData(releases: Releases) {
return Object.entries(releases)
.map(([key, data]) => ({
...data,
name: key,
}))
.sort((a, b) => sortMultiple(a, b, [sortByCreatedReleaseDate, sortByName]))
.map((value, index) => addAttribute(value, 'index', index))
.map((value, index) => addAttribute(value, 'unicode', index + startUnicode));
.map((value, index) => ({ ...value, index }))
.map((value, index) => ({
...value,
unicode: index + startUnicode,
}));
}
function addAttribute(obj, attribute, value) {
obj[attribute] = value;
return obj;
}
type CollatorFunction = (a: ReleaseMetaDataWithName, b: ReleaseMetaDataWithName) => number;
function sortMultiple(a, b, collators = []) {
const comparison = collators.shift()(a, b);
function sortMultiple(
a: ReleaseMetaDataWithName,
b: ReleaseMetaDataWithName,
collators: CollatorFunction[] = [],
) {
const comparison = collators?.shift?.()?.(a, b) ?? 0;
if (comparison === 0 && collators.length > 0) return sortMultiple(a, b, collators);
return comparison;
}
function sortByCreatedReleaseDate(a, b) {
const dates = [a, b].map((value) => new Date(value.createdRelease.date).valueOf());
return (dates[0] > dates[1]) - (dates[0] < dates[1]);
function sortByCreatedReleaseDate(a: ReleaseMetaDataWithName, b: ReleaseMetaDataWithName) {
const [dateA, dateB] = [a, b].map((value) => new Date(value.createdRelease.date).valueOf());
return Number(dateA > dateB) - Number(dateA < dateB);
}
function sortByName(a, b) {
function sortByName(a: ReleaseMetaDataWithName, b: ReleaseMetaDataWithName) {
return new Intl.Collator('en-US').compare(a.name, b.name);
}
function getIconUnicode(name) {
const { unicode } = releaseMetaData.find(({ name: iconname }) => iconname === name);
return String.fromCharCode(unicode);
function getIconUnicode(name: string): [string, number] {
const { unicode } = releaseMetaData.find(({ name: iconName }) => iconName === name) ?? {
unicode: startUnicode,
};
return [String.fromCharCode(unicode), startUnicode];
}
async function init() {
@@ -83,7 +108,7 @@ async function init() {
generateInfoData: true,
website: {
title: 'Lucide',
logo: null,
logo: undefined,
meta: {
description: 'Lucide icons as TTF/EOT/WOFF/WOFF2/SVG.',
keywords: 'Lucide,TTF,EOT,WOFF,WOFF2,SVG',

View File

@@ -3,9 +3,10 @@
"description": "A internal used package to build the font.",
"private": true,
"version": "1.0.0",
"main": "index.js",
"main": "main.ts",
"type": "module",
"scripts": {
"start": "node ./main.mjs"
"start": "node ./main.ts"
},
"keywords": [],
"author": "",
@@ -14,5 +15,10 @@
"fs-extra": "^11.2.0",
"minimist": "^1.2.8",
"svgtofont": "^6.0.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/minimist": "^1.2.5",
"@types/node": "^22"
}
}

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"declaration": true,
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "nodenext",
"module": "nodenext",
"target": "ESNext",
"esModuleInterop": true,
"lib": ["esnext"],
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"noEmit": true,
"sourceMap": true,
"outDir": "./dist",
},
}

View File

@@ -1,20 +0,0 @@
export * from './src/toCamelCase.mjs';
export * from './src/toPascalCase.mjs';
export * from './src/toKebabCase.mjs';
export * from './src/resetFile.mjs';
export * from './src/readFile.mjs';
export * from './src/appendFile.mjs';
export * from './src/writeFile.mjs';
export * from './src/writeFileIfNotExists.mjs';
export * from './src/readAllMetadata.mjs';
export * from './src/readMetadata.mjs';
export * from './src/readSvgDirectory.mjs';
export * from './src/readSvg.mjs';
export * from './src/writeSvgFile.mjs';
export * from './src/hash.mjs';
export * from './src/generateHashedKey.mjs';
export * from './src/hasDuplicatedChildren.mjs';
export * from './src/mergeArrays.mjs';
export * from './src/getCurrentDirPath.mjs';
export * from './src/minifySvg.mjs';
export * from './src/shuffleArray.mjs';

View File

@@ -0,0 +1,20 @@
export * from './src/toCamelCase.ts';
export * from './src/toPascalCase.ts';
export * from './src/toKebabCase.ts';
export * from './src/resetFile.ts';
export * from './src/readFile.ts';
export * from './src/appendFile.ts';
export * from './src/writeFile.ts';
export * from './src/writeFileIfNotExists.ts';
export * from './src/readAllMetadata.ts';
export * from './src/readMetadata.ts';
export * from './src/readSvgDirectory.ts';
export * from './src/readSvg.ts';
export * from './src/writeSvgFile.ts';
export * from './src/hash.ts';
export * from './src/generateHashedKey.ts';
export * from './src/hasDuplicatedChildren.ts';
export * from './src/mergeArrays.ts';
export * from './src/getCurrentDirPath.ts';
export * from './src/minifySvg.ts';
export * from './src/shuffleArray.ts';

View File

@@ -3,9 +3,12 @@
"private": true,
"version": "1.0.0",
"description": "A internal used package with helpers.",
"main": "helpers.mjs",
"types": "module",
"main": "helpers.ts",
"types": "helpers.ts",
"author": "",
"license": "ISC",
"type": "module"
"type": "module",
"devDependencies": {
"@types/node": "^22"
}
}

View File

@@ -9,5 +9,8 @@ import path from 'path';
* @param {string} fileName
* @param {string} outputDirectory
*/
export const appendFile = (content, fileName, outputDirectory) =>
fs.appendFile(path.join(outputDirectory, fileName), content, 'utf-8');
export const appendFile = (
content: string,
fileName: string,
outputDirectory: string,
): Promise<void> => fs.appendFile(path.join(outputDirectory, fileName), content, 'utf-8');

View File

@@ -1,5 +1,5 @@
/* eslint-disable import/prefer-default-export */
import { hash } from './hash.mjs';
import { hash } from './hash.ts';
/**
* Generate Hashed string based on name and attributes
@@ -9,4 +9,10 @@ import { hash } from './hash.mjs';
* @param {object} seed.attributes An object of SVGElement Attrbutes
* @returns {string} A hashed string of 6 characters
*/
export const generateHashedKey = ({ name, attributes }) => hash(JSON.stringify([name, attributes]));
export const generateHashedKey = ({
name,
attributes,
}: {
name: string;
attributes: Record<string, unknown>;
}): string => hash(JSON.stringify([name, attributes]));

View File

@@ -8,4 +8,5 @@ import { fileURLToPath } from 'url';
* @param {string} currentPath
* @returns {string}
*/
export const getCurrentDirPath = (currentPath) => path.dirname(fileURLToPath(currentPath));
export const getCurrentDirPath = (currentPath: string): string =>
path.dirname(fileURLToPath(currentPath));

View File

@@ -1,5 +1,5 @@
/* eslint-disable import/prefer-default-export */
import { generateHashedKey } from './generateHashedKey.mjs';
import { generateHashedKey } from './generateHashedKey.ts';
/**
* Checks if array of items contains duplicated items
@@ -7,7 +7,9 @@ import { generateHashedKey } from './generateHashedKey.mjs';
* @param {array} children an array of items
* @returns {Boolean} if items contains duplicated items.
*/
export const hasDuplicatedChildren = (children) => {
export const hasDuplicatedChildren = (
children: Array<{ name: string; attributes: Record<string, unknown> }>,
): boolean => {
const hashedKeys = children.map(generateHashedKey);
return !hashedKeys.every(

View File

@@ -6,7 +6,7 @@
* @param {number} seed
* @returns {string} A hashed string of 6 characters
*/
export const hash = (string, seed = 5381) => {
export const hash = (string: string, seed: number = 5381): string => {
let i = string.length;
while (i) {

View File

@@ -6,7 +6,7 @@
* @param {array} b
* @returns {array}
*/
export const mergeArrays = (a, b) => {
export const mergeArrays = <T>(a: T[], b: T[]): T[] => {
a = a.concat(b);
a = a.filter((i, p) => a.indexOf(i) === p);
return a;

View File

@@ -5,7 +5,7 @@
* @param {string} string
* @returns string
*/
export function minifySvg(string) {
export function minifySvg(string: string): string {
return string
? string
.replace(/>[\r\n ]+</g, '><')

View File

@@ -1,7 +1,6 @@
/* eslint-disable import/prefer-default-export */
import fs from 'fs/promises';
import path from 'path';
import { readMetadata } from './readMetadata.mjs';
import { readMetadata } from './readMetadata.ts';
/**
* Reads metadata from the icons/categories directories
@@ -9,7 +8,7 @@ import { readMetadata } from './readMetadata.mjs';
* @param {string} directory
* @returns {object} A map of icon or category metadata
*/
export const readAllMetadata = async (directory) => {
export const readAllMetadata = async (directory: string): Promise<Record<string, unknown>> => {
const directoryContent = await fs.readdir(directory);
const metaDataPromises = directoryContent

View File

@@ -8,4 +8,5 @@ import path from 'path';
* @param {string} path
* @returns {string} The contents of a file
*/
export const readFile = (path) => fs.readFile(path.resolve(__dirname, '../', path), 'utf-8');
export const readFile = (filePath: string): Promise<string | Buffer> =>
fs.readFile(path.resolve(__dirname, '../', filePath), 'utf-8');

View File

@@ -1,16 +0,0 @@
/* eslint-disable import/prefer-default-export */
import fs from 'fs/promises';
import path from 'path';
/**
* Reads metadata for an icon or category
*
* @param {string} fileName
* @param {string} directory
* @returns {object} The metadata for the icon or category
*/
export const readMetadata = async (fileName, directory) => {
const metadataFileContent = await fs.readFile(path.join(directory, fileName), 'utf-8');
return JSON.parse(metadataFileContent);
};

View File

@@ -0,0 +1,23 @@
/* eslint-disable import/prefer-default-export */
import fs from 'fs/promises';
import path from 'path';
/**
* Reads metadata for an icon or category
*
* @param {string} fileName
* @param {string} directory
* @returns {object} The metadata for the icon or category
*/
export const readMetadata = async (fileName: string, directory: string): Promise<unknown> => {
let metadataFileContent: string | Buffer = await fs.readFile(
path.join(directory, fileName),
'utf-8',
);
if (Buffer.isBuffer(metadataFileContent)) {
metadataFileContent = metadataFileContent.toString('utf-8');
}
return JSON.parse(metadataFileContent);
};

View File

@@ -8,5 +8,5 @@ import path from 'path';
* @param {string} fileName
* @param {string} directory
*/
export const readSvg = (fileName, directory) =>
export const readSvg = (fileName: string, directory: string): Promise<string> =>
fs.readFile(path.join(directory, fileName), 'utf-8');

View File

@@ -1,4 +1,5 @@
/* eslint-disable import/prefer-default-export */
import { type PathLike } from 'fs';
import fs from 'fs/promises';
import path from 'path';
@@ -9,7 +10,10 @@ import path from 'path';
* @param {string} fileExtension
* @returns {array} An array of file paths containing svgs
*/
export const readSvgDirectory = async (directory, fileExtension = '.svg') => {
export const readSvgDirectory = async (
directory: PathLike,
fileExtension: string = '.svg',
): Promise<string[]> => {
const directoryContents = await fs.readdir(directory);
return directoryContents.filter((file) => path.extname(file) === fileExtension);

View File

@@ -8,5 +8,5 @@ import path from 'path';
* @param {string} fileName
* @param {string} outputDirectory
*/
export const resetFile = (fileName, outputDirectory) =>
export const resetFile = (fileName: string, outputDirectory: string): Promise<void> =>
fs.writeFile(path.join(outputDirectory, fileName), '', 'utf-8');

View File

@@ -3,7 +3,7 @@
* @param {array} array
* @returns {array}
*/
export const shuffleArray = (array) => {
export const shuffleArray = <T>(array: T[]): T[] => {
// eslint-disable-next-line no-plusplus
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));

View File

@@ -5,7 +5,7 @@
* @param {string} string
* @returns {string} A camelized string
*/
export const toCamelCase = (string) =>
export const toCamelCase = (string: string): string =>
string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) =>
p2 ? p2.toUpperCase() : p1.toLowerCase(),
);

View File

@@ -5,4 +5,5 @@
* @param {string} string
* @returns {string} A kebabized string
*/
export const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
export const toKebabCase = (string: string): string =>
string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();

View File

@@ -1,5 +1,5 @@
/* eslint-disable import/prefer-default-export */
import { toCamelCase } from './toCamelCase.mjs';
import { toCamelCase } from './toCamelCase.ts';
/**
* Converts string to PascalCase
@@ -7,7 +7,7 @@ import { toCamelCase } from './toCamelCase.mjs';
* @param {string} string
* @returns {string} A pascalized string
*/
export const toPascalCase = (string) => {
export const toPascalCase = (string: string): string => {
const camelCase = toCamelCase(string);
return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);

View File

@@ -5,9 +5,12 @@ import path from 'path';
/**
* writes content to a file
*
* @param {string} content
* @param {string} fileName
* @param {string} outputDirectory
* @param {string} content
*/
export const writeSvgFile = (fileName, outputDirectory, content) =>
fs.writeFile(path.join(outputDirectory, fileName), content, 'utf-8');
export const writeFile = (
content: string,
fileName: string,
outputDirectory: string,
): Promise<void> => fs.writeFile(path.join(outputDirectory, fileName), content, 'utf-8');

View File

@@ -1,7 +1,7 @@
/* eslint-disable import/prefer-default-export */
import fs from 'fs/promises';
import fs from 'fs';
import path from 'path';
import { writeFile } from './writeFile.mjs';
import { writeFile } from './writeFile.ts';
/**
* writes content to a file if it does not exist
@@ -10,7 +10,11 @@ import { writeFile } from './writeFile.mjs';
* @param {string} fileName
* @param {string} outputDirectory
*/
export const writeFileIfNotExists = (content, fileName, outputDirectory) => {
export const writeFileIfNotExists = (
content: string,
fileName: string,
outputDirectory: string,
): void => {
if (!fs.existsSync(path.join(outputDirectory, fileName))) {
writeFile(content, fileName, outputDirectory);
}

View File

@@ -5,9 +5,12 @@ import path from 'path';
/**
* writes content to a file
*
* @param {string} content
* @param {string} fileName
* @param {string} outputDirectory
* @param {string} content
*/
export const writeFile = (content, fileName, outputDirectory) =>
fs.writeFile(path.join(outputDirectory, fileName), content, 'utf-8');
export const writeSvgFile = (
fileName: string,
outputDirectory: string,
content: string,
): Promise<void> => fs.writeFile(path.join(outputDirectory, fileName), content, 'utf-8');

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"declaration": true,
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"module": "ESNext",
"target": "ESNext",
"esModuleInterop": true,
"lib": ["esnext"],
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"noEmit": true,
"sourceMap": true,
"outDir": "./dist",
},
}

View File

@@ -2,8 +2,23 @@ import path from 'path';
import fs from 'fs';
// eslint-disable-next-line import/no-extraneous-dependencies
import { toPascalCase, resetFile, appendFile } from '@lucide/helpers';
import deprecationReasonTemplate from '../../utils/deprecationReasonTemplate.mjs';
import getExportString from './getExportString.mjs';
import deprecationReasonTemplate from '../../utils/deprecationReasonTemplate.ts';
import getExportString from './getExportString.ts';
import type { IconMetadata, IconNode } from '../../types.ts';
import { type INode } from 'svgson';
interface GenerateAliasesFilesOptions {
iconNodes: Record<string, INode>;
outputDirectory: string;
fileExtension: string;
iconFileExtension?: string;
iconMetaData: Record<string, IconMetadata>;
aliasImportFileExtension: string;
aliasNamesOnly?: boolean;
separateAliasesFile?: boolean;
separateAliasesFileExtension?: string;
showLog?: boolean;
}
export default async function generateAliasesFiles({
iconNodes,
@@ -16,7 +31,7 @@ export default async function generateAliasesFiles({
separateAliasesFile = false,
separateAliasesFileExtension,
showLog = true,
}) {
}: GenerateAliasesFilesOptions) {
const iconsDistDirectory = path.join(outputDirectory, `icons`);
const icons = Object.keys(iconNodes);
@@ -47,7 +62,7 @@ export default async function generateAliasesFiles({
return {
name: alias,
deprecated: false,
};
} as const;
}
return alias;
});

View File

@@ -1,8 +1,8 @@
const getExportString = (
componentName,
iconName,
aliasImportFileExtension,
deprecated,
componentName: string,
iconName: string,
aliasImportFileExtension: string,
deprecated?: boolean,
deprecationReason = '',
) =>
deprecated

View File

@@ -1,5 +1,14 @@
import path from 'path';
import { resetFile, appendFile } from '@lucide/helpers';
import type { IconMetadata, IconNode } from '../types.ts';
interface GenerateDynamicImports {
iconNodes: Record<string, IconNode>;
outputDirectory: string;
fileExtension: string;
iconMetaData: Record<string, IconMetadata>;
showLog?: boolean;
}
export default async function generateDynamicImports({
iconNodes,
@@ -7,7 +16,7 @@ export default async function generateDynamicImports({
fileExtension,
iconMetaData,
showLog = true,
}) {
}: GenerateDynamicImports) {
const fileName = path.basename(`dynamicIconImports${fileExtension}`);
const icons = Object.keys(iconNodes);

View File

@@ -2,12 +2,13 @@ import path from 'path';
// eslint-disable-next-line import/no-extraneous-dependencies
import { toPascalCase, toCamelCase, resetFile, appendFile } from '@lucide/helpers';
import type { IconNode } from '../types.ts';
export default async function generateExportFile(
inputEntry,
outputDirectory,
iconNodes,
exportModuleNameCasing,
inputEntry: string,
outputDirectory: string,
iconNodes: IconNode,
exportModuleNameCasing: 'camel' | 'pascal',
iconFileExtension = '',
) {
const fileName = path.basename(inputEntry);

View File

@@ -2,7 +2,22 @@ import fs from 'fs';
import path from 'path';
import prettier from 'prettier';
import { readSvg, toPascalCase } from '@lucide/helpers';
import deprecationReasonTemplate from '../utils/deprecationReasonTemplate.mjs';
import deprecationReasonTemplate from '../utils/deprecationReasonTemplate.ts';
import type { IconMetadata, IconNode, Path, TemplateFunction } from '../types.ts';
import { type INode } from 'svgson';
interface GenerateIconFiles {
iconNodes: Record<string, INode>;
outputDirectory: Path;
template: TemplateFunction;
showLog?: boolean;
iconFileExtension?: string;
separateIconFileExport?: boolean;
separateIconFileExportExtension?: string;
pretty?: boolean;
iconsDir: string;
iconMetaData: Record<string, IconMetadata>;
}
function generateIconFiles({
iconNodes,
@@ -15,7 +30,7 @@ function generateIconFiles({
pretty = true,
iconsDir,
iconMetaData,
}) {
}: GenerateIconFiles) {
const icons = Object.keys(iconNodes);
const iconsDistDirectory = path.join(outputDirectory, `icons`);
@@ -27,13 +42,15 @@ function generateIconFiles({
const location = path.join(iconsDistDirectory, `${iconName}${iconFileExtension}`);
const componentName = toPascalCase(iconName);
let { children } = iconNodes[iconName];
children = children.map(({ name, attributes }) => [name, attributes]);
const children: IconNode = iconNodes[iconName].children.map(({ name, attributes }) => [
name,
attributes,
]);
const getSvg = () => readSvg(`${iconName}.svg`, iconsDir);
const { deprecated = false, toBeRemovedInVersion = null } = iconMetaData[iconName];
const { deprecated = false, toBeRemovedInVersion = undefined } = iconMetaData[iconName];
const deprecationReason = deprecated
? deprecationReasonTemplate(iconMetaData[iconName].deprecationReason, {
? deprecationReasonTemplate(iconMetaData[iconName]?.deprecationReason ?? '', {
componentName,
iconName,
toBeRemovedInVersion,
@@ -50,7 +67,7 @@ function generateIconFiles({
});
const output = pretty
? prettier.format(elementTemplate, {
? await prettier.format(elementTemplate, {
singleQuote: true,
trailingComma: 'all',
printWidth: 100,

View File

@@ -4,16 +4,37 @@ import path from 'path';
import getArgumentOptions from 'minimist';
import { readSvgDirectory } from '@lucide/helpers';
import renderIconsObject from './render/renderIconsObject.mjs';
import generateIconFiles from './building/generateIconFiles.mjs';
import generateExportsFile from './building/generateExportsFile.mjs';
import renderIconsObject from './render/renderIconsObject.ts';
import generateIconFiles from './building/generateIconFiles.ts';
import generateExportsFile from './building/generateExportsFile.ts';
import generateAliasesFiles from './building/aliases/generateAliasesFiles.mjs';
import generateAliasesFiles from './building/aliases/generateAliasesFiles.ts';
// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member
import getIconMetaData from './utils/getIconMetaData.mjs';
import generateDynamicImports from './building/generateDynamicImports.mjs';
import getIconMetaData from './utils/getIconMetaData.ts';
import generateDynamicImports from './building/generateDynamicImports.ts';
const cliArguments = getArgumentOptions(process.argv.slice(2));
interface CliArguments {
renderUniqueKey?: boolean;
templateSrc?: string;
silent?: boolean;
iconFileExtension?: string;
importImportFileExtension?: string;
exportFileName?: string;
exportModuleNameCasing?: 'camel' | 'pascal';
withAliases?: boolean;
aliasNamesOnly?: boolean;
withDynamicImports?: boolean;
separateAliasesFile?: boolean;
separateAliasesFileExtension?: string;
separateIconFileExport?: boolean;
separateIconFileExportExtension?: string;
aliasesFileExtension?: string;
aliasImportFileExtension?: string;
pretty?: boolean;
output: string | undefined;
}
const cliArguments = getArgumentOptions(process.argv.slice(2)) as unknown as CliArguments;
const ICONS_DIR = path.resolve(process.cwd(), '../../icons');
const OUTPUT_DIR = path.resolve(process.cwd(), cliArguments.output || '../build');
@@ -64,7 +85,7 @@ async function buildIcons() {
iconFileExtension,
separateIconFileExport,
separateIconFileExportExtension,
pretty: JSON.parse(pretty),
pretty: JSON.parse(String(pretty)),
iconsDir: ICONS_DIR,
iconMetaData,
});

View File

@@ -1,3 +1,3 @@
export { default as getAliases } from './utils/getAliases.mjs';
export { default as getIconMetaData } from './utils/getIconMetaData.mjs';
export { default as renderIconsObject } from './render/renderIconsObject.mjs';
export { default as getAliases } from './utils/getAliases';
export { default as getIconMetaData } from './utils/getIconMetaData';
export { default as renderIconsObject } from './render/renderIconsObject';

View File

@@ -2,22 +2,38 @@
"name": "@lucide/build-icons",
"description": "A internal used package to build icon code files for the lucide icon library packages.",
"version": "1.1.0",
"main": "index.mjs",
"main": "index.ts",
"type": "module",
"scripts": {
"start": "node ./cli.mjs"
"start": "node ./cli.ts"
},
"bin": {
"build-icons": "./cli.mjs"
"build-icons": "./cli.ts"
},
"engines": {
"node": ">=23.0.0"
"node": ">= 23"
},
"exports": {
".": {
"import": "./index.ts",
"require": "./index.ts"
},
"./cli": {
"import": "./cli.ts",
"require": "./cli.ts"
},
"./utils/*": {
"import": "./utils/*.ts",
"require": "./utils/*.ts"
}
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@lucide/helpers": "workspace:*"
"@lucide/helpers": "workspace:*",
"@types/minimist": "^1.2.5",
"@types/node": "^22"
},
"dependencies": {
"minimist": "^1.2.7",

View File

@@ -1,16 +1,16 @@
import { basename } from 'path';
import { parseSync } from 'svgson';
import { type INode, parseSync } from 'svgson';
import { generateHashedKey, readSvg, hasDuplicatedChildren } from '@lucide/helpers';
/**
* Build an object in the format: `{ <name>: <contents> }`.
* @param {string[]} svgFiles - A list of filenames.
* @param {Function} getSvg - A function that returns the contents of an SVG file given a filename.
* @param {string} iconsDirectory - The directory where the icons are stored.
* @returns {Object}
*/
export default async function generateIconObject(
svgFiles,
iconsDirectory,
svgFiles: string[],
iconsDirectory: string,
renderUniqueKey = false,
) {
const svgsContentPromises = svgFiles.map(async (svgFile) => {
@@ -39,7 +39,7 @@ export default async function generateIconObject(
const svgsContents = await Promise.all(svgsContentPromises);
return svgsContents.reduce((icons, icon) => {
return svgsContents.reduce<Record<string, INode>>((icons, icon) => {
icons[icon.name] = icon.contents;
return icons;
}, {});

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"strict": false,
"strict": true,
"declaration": true,
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
@@ -8,10 +8,10 @@
"module": "ESNext",
"target": "ESNext",
"esModuleInterop": true,
"allowJs": true,
"lib": ["esnext"],
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"noEmit": true,
"sourceMap": true,
"outDir": "./dist",
},
}

View File

@@ -0,0 +1,45 @@
import { type INode } from 'svgson';
export type SVGProps = Record<string, string | number>;
export type IconNode = [tag: string, attrs: SVGProps][];
export type IconNodeWithChildren = [tag: string, attrs: SVGProps, children: IconNode];
export type TemplateFunction = (params: {
componentName: string;
iconName: string;
children: IconNode;
getSvg: () => Promise<string>;
deprecated?: boolean;
deprecationReason?: string;
}) => Promise<string>;
export type Path = string;
export type AliasDeprecationReason = 'alias.typo' | 'alias.name' | 'alias.duplicate';
export type AliasDeprecation = {
name: string;
deprecated: true;
deprecationReason: AliasDeprecationReason;
toBeRemovedInVersion: string;
};
export type IconDeprecationReason = 'icon.brand' | '';
export type IconMetadataBase = {
toBeRemovedInVersion?: string;
categories?: string[];
aliases?: (string | AliasDeprecation)[];
tags?: string[];
deprecationReason?: IconDeprecationReason;
deprecated?: boolean;
};
export type IconMetadataWithDeprecation = IconMetadataBase & {
deprecated: true;
deprecationReason?: IconDeprecationReason;
};
export type IconMetadata = IconMetadataBase | IconMetadataWithDeprecation;

View File

@@ -1,4 +1,4 @@
const base64SVG = (svgContents) =>
const base64SVG = (svgContents: string) =>
Buffer.from(
svgContents
.replace('\n', '')

View File

@@ -0,0 +1,16 @@
import { type IconNode } from '../types.ts';
export interface ExportTemplate {
componentName: string;
iconName: string;
children: IconNode;
getSvg: () => Promise<string>;
deprecated: boolean;
deprecationReason: string;
}
export type TemplateFunction = (params: ExportTemplate) => Promise<string>;
const defineExportTemplate = (exportFunction: TemplateFunction) => exportFunction;
export default defineExportTemplate;

View File

@@ -1,6 +1,16 @@
import type { AliasDeprecationReason, IconDeprecationReason } from '../types.ts';
export default function deprecationReasonTemplate(
deprecationReason,
{ componentName, iconName, toBeRemovedInVersion },
deprecationReason: AliasDeprecationReason | IconDeprecationReason,
{
componentName,
iconName,
toBeRemovedInVersion,
}: {
componentName: string;
iconName: string;
toBeRemovedInVersion?: string;
},
) {
const removalNotice = toBeRemovedInVersion
? ` This ${
@@ -13,7 +23,7 @@ export default function deprecationReasonTemplate(
return `Renamed because of typo, use {@link ${componentName}} instead.${removalNotice}`;
case 'alias.duplicate':
return `The icon was combined with another icon that shares the same use case, use {@link ${componentName}} instead.${removalNotice}`;
case 'alias.naming':
case 'alias.name':
return `The name of this icon was changed because it didn't meet our guidelines anymore, use {@link ${componentName}} instead.${removalNotice}`;
case 'icon.brand':
return `Brand icons have been deprecated and are due to be removed, please refer to https://github.com/lucide-icons/lucide/issues/670. We recommend using https://simpleicons.org/?q=${iconName} instead.${removalNotice}`;

View File

@@ -1,7 +1,8 @@
import path from 'path';
import { readSvgDirectory } from '@lucide/helpers';
import { Path } from '../types';
async function getAliases(iconDirectory) {
async function getAliases(iconDirectory: Path) {
const iconJsons = await readSvgDirectory(iconDirectory, '.json');
const aliasesEntries = await Promise.all(
iconJsons.map(async (jsonFile) => {

View File

@@ -1,10 +1,11 @@
import path from 'path';
import { readSvgDirectory } from '@lucide/helpers';
import { type IconMetadata } from '../types.ts';
async function getIconMetaData(iconDirectory) {
async function getIconMetaData(iconDirectory: string): Promise<Record<string, IconMetadata>> {
const iconJsons = await readSvgDirectory(iconDirectory, '.json');
const aliasesEntries = await Promise.all(
iconJsons.map(async (jsonFile) => {
iconJsons.map(async (jsonFile: string) => {
/** eslint-disable */
const file = await import(path.join(iconDirectory, jsonFile), { with: { type: 'json' } });
return [path.basename(jsonFile, '.json'), file.default];

14
tools/outline-svg/oslllo-svg-fixer.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
declare module 'oslllo-svg-fixer' {
export interface SVGFixerOptions {
showProgressBar?: boolean;
traceResolution?: number;
}
export default function SVGFixer(
inputDir: string,
outputDir: string,
options?: SVGFixerOptions,
): {
fix: () => Promise<void>;
};
}

View File

@@ -3,9 +3,10 @@
"description": "A internal used package to outline SVGs.",
"private": true,
"version": "2.0.0",
"main": "index.js",
"main": "main.ts",
"type": "module",
"scripts": {
"start": "node ./main.mjs"
"start": "node ./main.ts"
},
"keywords": [],
"author": "",

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"declaration": true,
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"module": "ESNext",
"target": "ESNext",
"esModuleInterop": true,
"lib": ["esnext"],
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"noEmit": true,
"sourceMap": true,
"outDir": "./dist",
},
}