mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-12 13:38:32 +00:00
Merge branch 'update-nitro' of https://github.com/lucide-icons/lucide into vitepress-nitro-plugin
This commit is contained in:
@@ -17,6 +17,10 @@ permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
|
||||
# This is added to make sure we wait until last release is done, this is needed for the font-build process
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
if: github.repository == 'lucide-icons/lucide' && startsWith(github.event.head_commit.message, 'feat(icons)') && github.event_name != 'workflow_dispatch'
|
||||
|
||||
@@ -16,6 +16,9 @@ permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
pre-release:
|
||||
if: github.repository == 'lucide-icons/lucide' && contains('["ericfennis", "karsa-mistmere", "jguddas"]', github.actor)
|
||||
@@ -129,11 +132,8 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Outline svg Icons
|
||||
run: pnpm build:outline-icons
|
||||
|
||||
- name: Create font in ./lucide-font
|
||||
run: pnpm build:font
|
||||
run: pnpm build:font --saveCodePoints
|
||||
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -14,6 +14,7 @@ coverage
|
||||
stats
|
||||
*.log
|
||||
outlined
|
||||
lucide-font
|
||||
packages/**/src/icons/*.js
|
||||
packages/**/src/icons/*.ts
|
||||
packages/**/src/icons/*.tsx
|
||||
|
||||
@@ -34,7 +34,7 @@ export default eventHandler((event) => {
|
||||
const iconNode = iconNodes[backdropName];
|
||||
|
||||
const LucideIcon = createLucideIcon(backdropName, iconNode);
|
||||
const svg = renderToStaticMarkup(<LucideIcon/>);
|
||||
const svg = renderToStaticMarkup(<LucideIcon />);
|
||||
const backdropString = svg.replaceAll('\n', '').replace(/<svg[^>]*>|<\/svg>/g, '');
|
||||
|
||||
children.push(
|
||||
@@ -42,7 +42,7 @@ export default eventHandler((event) => {
|
||||
backdropString={backdropString}
|
||||
src={src.replace(/<svg[^>]*>|<\/svg>/g, '')}
|
||||
color="#777"
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,12 +52,11 @@ export default eventHandler((event) => {
|
||||
return renderToString(
|
||||
<SvgPreview
|
||||
src={src.replace(/<svg[^>]*>|<\/svg>/g, '')}
|
||||
|
||||
showGrid
|
||||
height={height}
|
||||
width={width}
|
||||
>
|
||||
{children}
|
||||
</SvgPreview>
|
||||
)
|
||||
</SvgPreview>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -26,10 +26,10 @@ export default eventHandler((event) => {
|
||||
|
||||
const children = [];
|
||||
|
||||
const Icon = createLucideIcon(name, iconNodes[name])
|
||||
const Icon = createLucideIcon(name, iconNodes[name]);
|
||||
|
||||
const oldSrc = iconNodes[name]
|
||||
? renderToStaticMarkup(<Icon/>)
|
||||
? renderToStaticMarkup(<Icon />)
|
||||
.replaceAll('\n', '')
|
||||
.replace(/<svg[^>]*>|<\/svg>/g, '')
|
||||
: '';
|
||||
@@ -46,6 +46,6 @@ export default eventHandler((event) => {
|
||||
width={width}
|
||||
>
|
||||
{children}
|
||||
</Diff>
|
||||
)
|
||||
</Diff>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ export default eventHandler((event) => {
|
||||
const src = Buffer.from(data, 'base64').toString('utf8');
|
||||
const iconNode = parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`).children.map(
|
||||
({ name, attributes }) => [name, attributes],
|
||||
) as IconNode
|
||||
) as IconNode;
|
||||
|
||||
defaultContentType(event, 'image/svg+xml');
|
||||
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000');
|
||||
@@ -25,13 +25,13 @@ export default eventHandler((event) => {
|
||||
strokeWidth={strokeWidth}
|
||||
>
|
||||
<style>
|
||||
{ `@media screen and (prefers-color-scheme: light) {
|
||||
{`@media screen and (prefers-color-scheme: light) {
|
||||
svg { fill: transparent !important; }
|
||||
}
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
svg { stroke: #fff; fill: transparent !important; }
|
||||
}`}
|
||||
</style>
|
||||
</Icon>
|
||||
)
|
||||
</Icon>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -53,6 +53,6 @@ export default eventHandler((event) => {
|
||||
width={width}
|
||||
>
|
||||
{children}
|
||||
</Diff>
|
||||
)
|
||||
</Diff>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -397,7 +397,10 @@ interface SvgPreviewProps extends React.SVGProps<SVGSVGElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const SvgPreview = ({ src, children, height = 24, width = 24, showGrid = false, ...props }: SvgPreviewProps, ref) => {
|
||||
const SvgPreview = (
|
||||
{ src, children, height = 24, width = 24, showGrid = false, ...props }: SvgPreviewProps,
|
||||
ref,
|
||||
) => {
|
||||
const subGridSize =
|
||||
Math.max(height, width) % 3 === 0
|
||||
? Math.max(height, width) > 24
|
||||
|
||||
@@ -22,27 +22,23 @@ const iconAliasesRedirectRoutes = Object.entries(iconMetaData)
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
const vercelOutputJSON = path.resolve(currentDir, '.vercel/output/config.json');
|
||||
|
||||
const vercelConfig = await fs.promises.readFile(vercelOutputJSON, 'utf-8');
|
||||
|
||||
const vercelRouteConfig = JSON.parse(vercelConfig);
|
||||
|
||||
vercelRouteConfig.routes = [
|
||||
...iconAliasesRedirectRoutes,
|
||||
...vercelRouteConfig.routes,
|
||||
]
|
||||
vercelRouteConfig.routes = [...iconAliasesRedirectRoutes, ...vercelRouteConfig.routes];
|
||||
|
||||
// Adjust the existing catch-all route to only catch API routes, so that we can add a new catch-all route for 404s
|
||||
const allCatchRoute = '/(.*)';
|
||||
const fallBackIndex = vercelRouteConfig.routes.findIndex(route => route.src === allCatchRoute);
|
||||
const fallBackIndex = vercelRouteConfig.routes.findIndex((route) => route.src === allCatchRoute);
|
||||
|
||||
vercelRouteConfig.routes[fallBackIndex].src = '/api/(.*)'
|
||||
vercelRouteConfig.routes[fallBackIndex].src = '/api/(.*)';
|
||||
vercelRouteConfig.routes.push({
|
||||
src: allCatchRoute,
|
||||
dest: '/404.html',
|
||||
})
|
||||
});
|
||||
|
||||
const output = JSON.stringify(vercelRouteConfig, null, 2);
|
||||
|
||||
|
||||
+1
-3
@@ -8,9 +8,7 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"~/.vitepress/*": [
|
||||
"./.vitepress/*"
|
||||
],
|
||||
"~/.vitepress/*": ["./.vitepress/*"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"7ender",
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"tags": [
|
||||
"fishing",
|
||||
"rod",
|
||||
"hobby",
|
||||
"equipment",
|
||||
"reel"
|
||||
],
|
||||
"categories": [
|
||||
"sports",
|
||||
"travel"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M4 11h1" />
|
||||
<path d="M8 15a2 2 0 0 1-4 0V3a1 1 0 0 1 1-1h.5C14 2 20 9 20 18v4" />
|
||||
<circle cx="18" cy="18" r="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 338 B |
+1
-1
@@ -16,7 +16,7 @@
|
||||
"lucide-svelte": "pnpm --filter lucide-svelte",
|
||||
"lucide-static": "pnpm --filter lucide-static",
|
||||
"build:outline-icons": "pnpm --filter outline-svg start",
|
||||
"build:font": "pnpm --filter docs prebuild:releaseJson && pnpm --filter build-font start",
|
||||
"build:font": "pnpm --filter build-font start",
|
||||
"optimize": "node ./scripts/optimizeSvgs.mts",
|
||||
"addjsons": "node ./scripts/addMissingIconJsonFiles.mts",
|
||||
"checkIcons": "node ./scripts/checkIconsAndCategories.mts",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"@testing-library/preact": "^3.2.3",
|
||||
"jest-serializer-html": "^7.1.0",
|
||||
"preact": "^10.27.3",
|
||||
"rollup": "^4.53.3",
|
||||
"rollup": "^4.59.0",
|
||||
"rollup-plugin-dts": "^6.2.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.2.4",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"react-dom": "^18.0.0",
|
||||
"react-native": "^0.76.0",
|
||||
"react-native-svg": "^15.8.0",
|
||||
"rollup": "^4.53.3",
|
||||
"rollup": "^4.59.0",
|
||||
"rollup-plugin-dts": "^6.2.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.2.4",
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
"jest-serializer-html": "^7.1.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"rollup": "^4.53.3",
|
||||
"rollup": "^4.59.0",
|
||||
"rollup-plugin-dts": "^6.2.3",
|
||||
"rollup-plugin-preserve-directives": "^0.4.0",
|
||||
"typescript": "^5.8.3",
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"babel-preset-solid": "^1.8.12",
|
||||
"jest-serializer-html": "^7.1.0",
|
||||
"rollup": "^4.53.3",
|
||||
"rollup": "^4.59.0",
|
||||
"solid-js": "^1.9.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.2.4",
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"@types/node": "^22.15.30",
|
||||
"prettier": "^2.3.2",
|
||||
"svgson": "^5.2.1",
|
||||
"rollup": "^4.53.3",
|
||||
"rollup": "^4.59.0",
|
||||
"rollup-plugin-dts": "^6.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"@testing-library/vue": "^8.1.0",
|
||||
"@vitejs/plugin-vue": "^6.0.2",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"rollup": "^4.53.3",
|
||||
"rollup": "^4.59.0",
|
||||
"rollup-plugin-dts": "^6.2.3",
|
||||
"vite": "^7.2.4",
|
||||
"vitest": "^4.0.12",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"@testing-library/vue": "^5.9.0",
|
||||
"@vitejs/plugin-vue2": "2.2.0",
|
||||
"@vue/test-utils": "1.3.0",
|
||||
"rollup": "^4.53.3",
|
||||
"rollup": "^4.59.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.2.4",
|
||||
"vitest": "^4.0.12",
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"@rollup/plugin-replace": "^6.0.2",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"jest-serializer-html": "^7.1.0",
|
||||
"rollup": "^4.53.3",
|
||||
"rollup": "^4.59.0",
|
||||
"rollup-plugin-dts": "^6.2.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.2.4",
|
||||
|
||||
Generated
+183
-380
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,150 +0,0 @@
|
||||
import { readJson } from 'fs-extra/esm';
|
||||
import svgtofont from 'svgtofont';
|
||||
import getArgumentOptions from 'minimist';
|
||||
import path from 'path';
|
||||
|
||||
const fontName = 'lucide';
|
||||
const classNamePrefix = 'icon';
|
||||
const startUnicode = 57400;
|
||||
|
||||
const inputDir = path.join(process.cwd(), '../../', 'outlined');
|
||||
const cliArguments = getArgumentOptions(process.argv.slice(2));
|
||||
const { outputDir = 'lucide-font' } = cliArguments;
|
||||
const targetDir = path.join(process.cwd(), '../../', outputDir);
|
||||
const releaseMetaDataDir = path.join(process.cwd(), '../../', 'docs/.vitepress/data');
|
||||
const releaseMetaDataPath = path.resolve(releaseMetaDataDir, 'releaseMetaData.json');
|
||||
|
||||
const releaseMetaData = convertReleaseMetaData(await getReleaseMetaData());
|
||||
|
||||
async function getReleaseMetaData() {
|
||||
let releaseMetaData = {};
|
||||
try {
|
||||
releaseMetaData = await readJson(releaseMetaDataPath);
|
||||
} catch (err) {
|
||||
throw new Error('Execution stopped because no release information was found.');
|
||||
}
|
||||
return releaseMetaData;
|
||||
}
|
||||
|
||||
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) => ({ ...value, index }))
|
||||
.map((value, index) => ({
|
||||
...value,
|
||||
unicode: index + startUnicode,
|
||||
}));
|
||||
}
|
||||
|
||||
type CollatorFunction = (a: ReleaseMetaDataWithName, b: ReleaseMetaDataWithName) => number;
|
||||
|
||||
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: 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: ReleaseMetaDataWithName, b: ReleaseMetaDataWithName) {
|
||||
return new Intl.Collator('en-US').compare(a.name, b.name);
|
||||
}
|
||||
|
||||
function getIconUnicode(name: string): [string, number] {
|
||||
const { unicode } = releaseMetaData.find(({ name: iconName }) => iconName === name) ?? {
|
||||
unicode: startUnicode,
|
||||
};
|
||||
return [String.fromCharCode(unicode), startUnicode];
|
||||
}
|
||||
|
||||
async function init() {
|
||||
console.time('Font generation');
|
||||
try {
|
||||
await svgtofont({
|
||||
src: path.resolve(process.cwd(), inputDir),
|
||||
dist: path.resolve(process.cwd(), targetDir),
|
||||
// styleTemplates: path.resolve(process.cwd(), 'styles'), // Add different templates if needed
|
||||
fontName,
|
||||
classNamePrefix,
|
||||
css: {
|
||||
fontSize: 'inherit',
|
||||
},
|
||||
emptyDist: true,
|
||||
useCSSVars: false,
|
||||
outSVGReact: false,
|
||||
outSVGPath: false,
|
||||
addLigatures: true,
|
||||
svgicons2svgfont: {
|
||||
fontHeight: 1000, // At least 1000 is recommended
|
||||
normalize: false,
|
||||
},
|
||||
generateInfoData: true,
|
||||
website: {
|
||||
title: 'Lucide',
|
||||
logo: undefined,
|
||||
meta: {
|
||||
description: 'Lucide icons as TTF/EOT/WOFF/WOFF2/SVG.',
|
||||
keywords: 'Lucide,TTF,EOT,WOFF,WOFF2,SVG',
|
||||
},
|
||||
corners: {
|
||||
url: 'https://github.com/lucide-icons/lucide',
|
||||
width: 62, // default: 60
|
||||
height: 62, // default: 60
|
||||
bgColor: '#dc3545', // default: '#151513'
|
||||
},
|
||||
links: [
|
||||
{
|
||||
title: 'GitHub',
|
||||
url: 'https://github.com/lucide-icons/lucide',
|
||||
},
|
||||
{
|
||||
title: 'Feedback',
|
||||
url: 'https://github.com/lucide-icons/lucide/issues',
|
||||
},
|
||||
{
|
||||
title: 'Font Class',
|
||||
url: 'index.html',
|
||||
},
|
||||
{
|
||||
title: 'Unicode',
|
||||
url: 'unicode.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
getIconUnicode,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
console.timeEnd('Font generation');
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -6,7 +6,7 @@
|
||||
"main": "main.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node ./main.ts"
|
||||
"start": "node ./src/main.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -14,9 +14,11 @@
|
||||
"dependencies": {
|
||||
"fs-extra": "^11.2.0",
|
||||
"minimist": "^1.2.8",
|
||||
"oslllo-svg-fixer": "^5.0.0",
|
||||
"svgtofont": "^6.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lucide/helpers": "workspace:*",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^22"
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { type IconAliases } from '@lucide/helpers';
|
||||
import path from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
import { cwd } from 'process';
|
||||
|
||||
export type CodePoints = Record<string, number>;
|
||||
|
||||
async function getLatestCodePoints(): Promise<CodePoints> {
|
||||
// This is for the first release where no codepoints.json exists yet
|
||||
const codepointsContents = await fs.readFile(path.join(cwd(), 'codepoints.json'), 'utf-8');
|
||||
|
||||
return JSON.parse(codepointsContents) as CodePoints;
|
||||
|
||||
// TODO: Next releases will use the codepoints.json from latest release in lucide-static.
|
||||
// const codepointsContents = await fetch('https://unpkg.com/lucide-static@latest/font/codepoints.json')
|
||||
// return codepointsContents.json() as Promise<CodePoints>
|
||||
}
|
||||
|
||||
interface AllocateCodePointsOptions {
|
||||
saveCodePoints?: boolean;
|
||||
iconsWithAliases: IconAliases;
|
||||
}
|
||||
|
||||
export async function allocateCodePoints({
|
||||
saveCodePoints = false,
|
||||
iconsWithAliases,
|
||||
}: AllocateCodePointsOptions): Promise<CodePoints> {
|
||||
const baseCodePoints = await getLatestCodePoints();
|
||||
|
||||
const endCodePoint = Math.max(...Object.values(baseCodePoints));
|
||||
|
||||
await Promise.all(
|
||||
iconsWithAliases.map(async ([iconName, aliases]) => {
|
||||
if (!baseCodePoints[iconName]) {
|
||||
console.log('Code point not found creating new one for', iconName);
|
||||
baseCodePoints[iconName] = endCodePoint + 1;
|
||||
}
|
||||
|
||||
aliases.forEach((alias, index) => {
|
||||
if (baseCodePoints[alias]) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Code point not found creating new one for', alias);
|
||||
|
||||
baseCodePoints[alias] = endCodePoint + index + 1;
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
if (saveCodePoints) {
|
||||
await fs.writeFile(
|
||||
path.join(cwd(), 'codepoints.json'),
|
||||
JSON.stringify(baseCodePoints, null, 2),
|
||||
'utf-8',
|
||||
);
|
||||
}
|
||||
|
||||
return baseCodePoints;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import svgtofont from 'svgtofont';
|
||||
import { type CodePoints } from './allocateCodepoints.ts';
|
||||
|
||||
interface BuildFontOptions {
|
||||
inputDir: string;
|
||||
targetDir: string;
|
||||
fontName: string;
|
||||
classNamePrefix: string;
|
||||
codePoints: CodePoints;
|
||||
startUnicode: number;
|
||||
}
|
||||
|
||||
export async function buildFont({
|
||||
inputDir,
|
||||
targetDir,
|
||||
fontName,
|
||||
classNamePrefix,
|
||||
codePoints,
|
||||
startUnicode,
|
||||
}: BuildFontOptions) {
|
||||
console.time('Font generation');
|
||||
try {
|
||||
await svgtofont({
|
||||
src: inputDir,
|
||||
dist: targetDir,
|
||||
fontName,
|
||||
classNamePrefix,
|
||||
css: {
|
||||
fontSize: 'inherit',
|
||||
},
|
||||
emptyDist: true,
|
||||
useCSSVars: false,
|
||||
outSVGReact: false,
|
||||
outSVGPath: false,
|
||||
addLigatures: true,
|
||||
svgicons2svgfont: {
|
||||
fontHeight: 1000, // At least 1000 is recommended
|
||||
normalize: false,
|
||||
},
|
||||
generateInfoData: true,
|
||||
website: {
|
||||
title: 'Lucide',
|
||||
logo: undefined,
|
||||
meta: {
|
||||
description: 'Lucide icons as TTF/EOT/WOFF/WOFF2/SVG.',
|
||||
keywords: 'Lucide,TTF,EOT,WOFF,WOFF2,SVG',
|
||||
},
|
||||
corners: {
|
||||
url: 'https://github.com/lucide-icons/lucide',
|
||||
width: 62, // default: 60
|
||||
height: 62, // default: 60
|
||||
bgColor: '#dc3545', // default: '#151513'
|
||||
},
|
||||
links: [
|
||||
{
|
||||
title: 'GitHub',
|
||||
url: 'https://github.com/lucide-icons/lucide',
|
||||
},
|
||||
{
|
||||
title: 'Feedback',
|
||||
url: 'https://github.com/lucide-icons/lucide/issues',
|
||||
},
|
||||
{
|
||||
title: 'Font Class',
|
||||
url: 'index.html',
|
||||
},
|
||||
{
|
||||
title: 'Unicode',
|
||||
url: 'unicode.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
getIconUnicode: (name: string) => {
|
||||
if (!codePoints[name]) {
|
||||
throw new Error(`No codepoint found for icon: ${name}`);
|
||||
}
|
||||
|
||||
const unicode = codePoints[name];
|
||||
return [String.fromCharCode(unicode), startUnicode];
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
console.timeEnd('Font generation');
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { type IconAliases } from '@lucide/helpers';
|
||||
import { type CodePoints } from './allocateCodepoints.ts';
|
||||
|
||||
export function hasMissingCodePoints(
|
||||
iconsWithAliases: IconAliases,
|
||||
codePoints: CodePoints,
|
||||
): boolean {
|
||||
return iconsWithAliases
|
||||
.map(([iconName, aliases]) => [iconName, ...aliases])
|
||||
.flat()
|
||||
.some((name) => {
|
||||
if (!codePoints?.[name]) {
|
||||
console.log(`Missing code point for icon/alias: ${name}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import getArgumentOptions from 'minimist';
|
||||
import path from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
import { getAllIconAliases } from '@lucide/helpers';
|
||||
import { outlineSVG } from './outlineSVGs.ts';
|
||||
import { allocateCodePoints } from './allocateCodepoints.ts';
|
||||
import { buildFont } from './buildFont.ts';
|
||||
import { hasMissingCodePoints } from './helpers.ts';
|
||||
|
||||
const fontName = 'lucide';
|
||||
const classNamePrefix = 'icon';
|
||||
const startUnicode = 57400;
|
||||
const outputDir = 'lucide-font';
|
||||
|
||||
const { saveCodePoints = false } = getArgumentOptions(process.argv.slice(2)) ?? {};
|
||||
|
||||
const repoRoot = path.join(process.cwd(), '../../');
|
||||
const iconsDir = path.join(repoRoot, 'icons');
|
||||
const outlinedDir = path.join(repoRoot, 'outlined');
|
||||
const targetDir = path.join(repoRoot, outputDir);
|
||||
|
||||
const iconsWithAliases = await getAllIconAliases(iconsDir);
|
||||
|
||||
await outlineSVG({
|
||||
iconsDir,
|
||||
outlinedDir,
|
||||
iconsWithAliases,
|
||||
});
|
||||
|
||||
const codePoints = await allocateCodePoints({
|
||||
saveCodePoints,
|
||||
iconsWithAliases,
|
||||
});
|
||||
|
||||
if (hasMissingCodePoints(iconsWithAliases, codePoints)) {
|
||||
throw new Error('Some icons or aliases are missing code points. See log for details.');
|
||||
}
|
||||
|
||||
await buildFont({
|
||||
inputDir: outlinedDir,
|
||||
targetDir,
|
||||
fontName,
|
||||
classNamePrefix,
|
||||
codePoints,
|
||||
startUnicode,
|
||||
});
|
||||
|
||||
await fs.copyFile(
|
||||
path.join(process.cwd(), 'codepoints.json'),
|
||||
path.join(targetDir, 'codepoints.json'),
|
||||
);
|
||||
@@ -0,0 +1,49 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import SVGFixer from 'oslllo-svg-fixer';
|
||||
import { type IconAliases } from '@lucide/helpers';
|
||||
import path from 'path';
|
||||
|
||||
interface OutlineSVGOptions {
|
||||
iconsDir: string;
|
||||
outlinedDir: string;
|
||||
iconsWithAliases: IconAliases;
|
||||
}
|
||||
|
||||
export async function outlineSVG({ iconsDir, outlinedDir, iconsWithAliases }: OutlineSVGOptions) {
|
||||
console.time('icon outliner');
|
||||
try {
|
||||
try {
|
||||
await fs.mkdir(outlinedDir);
|
||||
} catch (error) {} // eslint-disable-line no-empty
|
||||
|
||||
await SVGFixer(iconsDir, outlinedDir, {
|
||||
showProgressBar: true,
|
||||
traceResolution: 800,
|
||||
}).fix();
|
||||
|
||||
console.log('Duplicate icons with aliases..');
|
||||
|
||||
await Promise.all(
|
||||
iconsWithAliases.map(async ([iconName, aliases]) => {
|
||||
const sourcePath = path.join(outlinedDir, `${iconName}.svg`);
|
||||
|
||||
await Promise.all(
|
||||
aliases.map(async (aliasName) => {
|
||||
const destinationPath = path.join(outlinedDir, `${aliasName}.svg`);
|
||||
|
||||
try {
|
||||
await fs.copyFile(sourcePath, destinationPath);
|
||||
console.log(`Copied ${iconName}.svg to ${aliasName}.svg`);
|
||||
} catch (err) {
|
||||
console.log(`Failed to copy ${sourcePath} to ${destinationPath}:`, err);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
console.timeEnd('icon outliner');
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export * from './src/appendFile.ts';
|
||||
export * from './src/writeFile.ts';
|
||||
export * from './src/writeFileIfNotExists.ts';
|
||||
export * from './src/readAllMetadata.ts';
|
||||
export * from './src/getAllIconAliases.ts';
|
||||
export * from './src/readMetadata.ts';
|
||||
export * from './src/readSvgDirectory.ts';
|
||||
export * from './src/readSvg.ts';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { readAllMetadata } from './readAllMetadata.ts';
|
||||
|
||||
export type IconAliases = [iconName: string, aliases: string[]][];
|
||||
|
||||
export const getAllIconAliases = async (iconsDir: string): Promise<IconAliases> => {
|
||||
const metaDataFiles = await readAllMetadata(iconsDir);
|
||||
|
||||
return Object.entries(metaDataFiles).map(([iconName, metadata]) => {
|
||||
const { aliases } = metadata;
|
||||
|
||||
if (!aliases?.length) return [iconName, []];
|
||||
|
||||
const aliasesNames = aliases.map((alias) => (typeof alias === 'string' ? alias : alias.name));
|
||||
|
||||
return [iconName, aliasesNames];
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { readMetadata } from './readMetadata.ts';
|
||||
import { type IconMetadata } from '../../build-icons/types.ts';
|
||||
|
||||
/**
|
||||
* Reads metadata from the icons/categories directories
|
||||
@@ -8,7 +9,7 @@ import { readMetadata } from './readMetadata.ts';
|
||||
* @param {string} directory
|
||||
* @returns {object} A map of icon or category metadata
|
||||
*/
|
||||
export const readAllMetadata = async (directory: string): Promise<Record<string, unknown>> => {
|
||||
export const readAllMetadata = async (directory: string): Promise<Record<string, IconMetadata>> => {
|
||||
const directoryContent = await fs.readdir(directory);
|
||||
|
||||
const metaDataPromises = directoryContent
|
||||
@@ -16,6 +17,7 @@ export const readAllMetadata = async (directory: string): Promise<Record<string,
|
||||
.map(async (file) => [path.basename(file, '.json'), await readMetadata(file, directory)]);
|
||||
|
||||
const metadata = await Promise.all(metaDataPromises);
|
||||
|
||||
if (metadata.length === 0) {
|
||||
throw new Error(`No metadata files found in directory: ${directory}`);
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# @lucide/outline-svg
|
||||
|
||||
A internal used package to outline SVGs.
|
||||
@@ -1,29 +0,0 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import SVGFixer from 'oslllo-svg-fixer';
|
||||
import getArgumentOptions from 'minimist';
|
||||
import path from 'path';
|
||||
|
||||
const inputDir = path.join(process.cwd(), '../../icons');
|
||||
const cliArguments = getArgumentOptions(process.argv.slice(2));
|
||||
const { outputDir = 'outlined' } = cliArguments;
|
||||
const targetDir = path.join(process.cwd(), '../../', outputDir);
|
||||
|
||||
async function init() {
|
||||
console.time('icon outliner');
|
||||
try {
|
||||
try {
|
||||
await fs.mkdir(targetDir);
|
||||
} catch (error) {} // eslint-disable-line no-empty
|
||||
|
||||
await SVGFixer(inputDir, targetDir, {
|
||||
showProgressBar: true,
|
||||
traceResolution: 800,
|
||||
}).fix();
|
||||
|
||||
console.timeEnd('icon outliner');
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "@lucide/outline-svg",
|
||||
"description": "A internal used package to outline SVGs.",
|
||||
"private": true,
|
||||
"version": "2.0.0",
|
||||
"main": "main.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node ./main.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.8",
|
||||
"oslllo-svg-fixer": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"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",
|
||||
},
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@rollup/plugin-replace": "^6.0.3",
|
||||
"esbuild": "^0.27.0",
|
||||
"rollup": "^4.53.3",
|
||||
"rollup": "^4.59.0",
|
||||
"rollup-plugin-esbuild": "^6.2.1",
|
||||
"rollup-plugin-license": "^3.6.0",
|
||||
"rollup-plugin-summary": "^3.0.1",
|
||||
|
||||
Reference in New Issue
Block a user