Compare commits

...

11 Commits

Author SHA1 Message Date
Eric Fennis
3c3b25202a Make code point consistent 2025-12-18 12:48:57 +01:00
Eric Fennis
4efc8e07a0 adds addLigatures to config 2025-12-18 12:16:00 +01:00
Eric Fennis
5be50c241c Merge branch 'main' of https://github.com/lucide-icons/lucide into fix-stable-code-points 2025-12-18 12:15:22 +01:00
Eric Fennis
506f3652c3 Adds next solution after build 2025-12-18 12:14:28 +01:00
Eric Fennis
23b85f7834 Fixing code points build 2025-12-18 10:58:14 +01:00
Eric Fennis
9392a8f84f update lockfile 2025-12-18 10:17:14 +01:00
Eric Fennis
115fb243af Merge branch 'main' of https://github.com/lucide-icons/lucide into fix-stable-code-points 2025-12-18 10:16:46 +01:00
Eric Fennis
5408bc1d69 Adjust workflow 2025-12-10 14:13:56 +01:00
Eric Fennis
efa795aa4c Merge branch 'main' of https://github.com/lucide-icons/lucide into fix-stable-code-points 2025-12-10 13:27:12 +01:00
Eric Fennis
ba46fcf4fc Add lucide-font to gitignore 2025-12-10 13:27:03 +01:00
Eric Fennis
484984ad68 Refactor font building 2025-12-10 10:45:52 +01:00
23 changed files with 2297 additions and 282 deletions

View File

@@ -11,6 +11,9 @@ permissions:
id-token: write # Required for OIDC
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
create-release:
if: github.repository == 'lucide-icons/lucide' && startsWith(github.event.head_commit.message, 'feat(icons)')

View File

@@ -22,6 +22,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)
@@ -135,11 +138,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

1
.gitignore vendored
View File

@@ -14,6 +14,7 @@ coverage
stats
*.log
outlined
lucide-font
packages/**/src/icons/*.js
packages/**/src/icons/*.ts
packages/**/src/icons/*.tsx

View File

View File

@@ -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",

143
pnpm-lock.yaml generated
View File

@@ -199,7 +199,7 @@ importers:
version: 7.7.1
nitropack:
specifier: 2.8.1
version: 2.8.1(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.2)(xml2js@0.6.2)
version: 2.8.1(@vercel/blob@2.0.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.2)(xml2js@0.6.2)
rollup-plugin-copy:
specifier: ^3.5.0
version: 3.5.0
@@ -226,7 +226,7 @@ importers:
version: 6.9.1
astro:
specifier: ^5.16.0
version: 5.16.0(@types/node@24.10.1)(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(less@4.2.0)(rollup@4.53.3)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(typescript@5.9.3)(yaml@2.8.0)
version: 5.16.0(@types/node@24.10.1)(@vercel/blob@2.0.0)(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(less@4.2.0)(rollup@4.53.3)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(typescript@5.9.3)(yaml@2.8.0)
jest-serializer-html:
specifier: ^7.1.0
version: 7.1.0
@@ -813,10 +813,16 @@ importers:
minimist:
specifier: ^1.2.8
version: 1.2.8
oslllo-svg-fixer:
specifier: ^5.0.0
version: 5.0.0(encoding@0.1.13)
svgtofont:
specifier: ^6.5.0
version: 6.5.0(@types/svg2ttf@5.0.1)(chokidar@3.6.0)
devDependencies:
'@lucide/helpers':
specifier: workspace:*
version: link:../build-helpers
'@types/fs-extra':
specifier: ^11.0.4
version: 11.0.4
@@ -858,15 +864,6 @@ importers:
specifier: ^22
version: 22.19.1
tools/outline-svg:
dependencies:
minimist:
specifier: ^1.2.8
version: 1.2.8
oslllo-svg-fixer:
specifier: ^5.0.0
version: 5.0.0(encoding@0.1.13)
tools/rollup-plugins:
dependencies:
'@rollup/plugin-node-resolve':
@@ -5612,6 +5609,10 @@ packages:
cpu: [x64]
os: [win32]
'@vercel/blob@2.0.0':
resolution: {integrity: sha512-oAj7Pdy83YKSwIaMFoM7zFeLYWRc+qUpW3PiDSblxQMnGFb43qs4bmfq7dr/+JIfwhs6PTwe1o2YBwKhyjWxXw==}
engines: {node: '>=20.0.0'}
'@vercel/nft@0.24.4':
resolution: {integrity: sha512-KjYAZty7boH5fi5udp6p+lNu6nawgs++pHW+3koErMgbRkkHuToGX/FwjN5clV1FcaM3udfd4zW/sUapkMgpZw==}
engines: {node: '>=16'}
@@ -6167,6 +6168,9 @@ packages:
async-limiter@1.0.1:
resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==}
async-retry@1.3.3:
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
async-sema@3.1.1:
resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==}
@@ -8112,10 +8116,6 @@ packages:
resolution: {integrity: sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==}
engines: {node: '>=8'}
fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
fast-glob@3.3.3:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
@@ -8880,6 +8880,10 @@ packages:
is-buffer@1.1.6:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
is-buffer@2.0.5:
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
engines: {node: '>=4'}
is-builtin-module@3.2.1:
resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
engines: {node: '>=6'}
@@ -8978,6 +8982,9 @@ packages:
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
engines: {node: '>= 0.4'}
is-node-process@1.2.0:
resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==}
is-number-object@1.0.7:
resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
engines: {node: '>= 0.4'}
@@ -12382,6 +12389,10 @@ packages:
throat@5.0.0:
resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==}
throttleit@2.1.0:
resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
engines: {node: '>=18'}
through2@2.0.5:
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
@@ -16704,14 +16715,14 @@ snapshots:
dependencies:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 24.10.1
'@types/node': 22.19.1
jest-mock: 29.7.0
'@jest/fake-timers@29.7.0':
dependencies:
'@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0
'@types/node': 24.10.1
'@types/node': 22.19.1
jest-message-util: 29.7.0
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -16745,7 +16756,7 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.4
'@types/istanbul-reports': 3.0.1
'@types/node': 24.10.1
'@types/node': 22.19.1
'@types/yargs': 17.0.35
chalk: 4.1.2
@@ -18452,11 +18463,11 @@ snapshots:
'@types/body-parser@1.19.5':
dependencies:
'@types/connect': 3.4.38
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/bonjour@3.5.13':
dependencies:
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/chai@5.2.2':
dependencies:
@@ -18465,17 +18476,17 @@ snapshots:
'@types/connect-history-api-fallback@1.5.4':
dependencies:
'@types/express-serve-static-core': 4.19.5
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/connect@3.4.38':
dependencies:
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/cookie@0.4.1': {}
'@types/cors@2.8.17':
dependencies:
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/debug@4.1.12':
dependencies:
@@ -18503,7 +18514,7 @@ snapshots:
'@types/express-serve-static-core@4.19.5':
dependencies:
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/qs': 6.9.15
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
@@ -18517,7 +18528,7 @@ snapshots:
'@types/fontkit@2.0.8':
dependencies:
'@types/node': 24.10.1
'@types/node': 22.19.1
'@types/fs-extra@11.0.4':
dependencies:
@@ -18531,11 +18542,11 @@ snapshots:
'@types/glob@7.2.0':
dependencies:
'@types/minimatch': 6.0.0
'@types/node': 24.10.1
'@types/node': 22.19.1
'@types/graceful-fs@4.1.9':
dependencies:
'@types/node': 24.10.1
'@types/node': 22.19.1
'@types/hast@3.0.4':
dependencies:
@@ -18596,7 +18607,7 @@ snapshots:
'@types/node-forge@1.3.11':
dependencies:
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/node@12.20.55': {}
@@ -18635,7 +18646,7 @@ snapshots:
'@types/resolve@1.17.1':
dependencies:
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/resolve@1.20.2': {}
@@ -18650,7 +18661,7 @@ snapshots:
'@types/send@0.17.4':
dependencies:
'@types/mime': 1.3.5
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/serve-index@1.9.4':
dependencies:
@@ -18659,12 +18670,12 @@ snapshots:
'@types/serve-static@1.15.7':
dependencies:
'@types/http-errors': 2.0.4
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/send': 0.17.4
'@types/sockjs@0.3.36':
dependencies:
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/stack-utils@2.0.1': {}
@@ -18677,7 +18688,7 @@ snapshots:
'@types/ws@8.5.12':
dependencies:
'@types/node': 12.20.55
'@types/node': 22.19.1
'@types/yargs-parser@21.0.3': {}
@@ -18947,6 +18958,15 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.6.2':
optional: true
'@vercel/blob@2.0.0':
dependencies:
async-retry: 1.3.3
is-buffer: 2.0.5
is-node-process: 1.2.0
throttleit: 2.1.0
undici: 5.28.5
optional: true
'@vercel/nft@0.24.4(encoding@0.1.13)':
dependencies:
'@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13)
@@ -19591,7 +19611,7 @@ snapshots:
dependencies:
tslib: 2.8.1
astro@5.16.0(@types/node@24.10.1)(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(less@4.2.0)(rollup@4.53.3)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(typescript@5.9.3)(yaml@2.8.0):
astro@5.16.0(@types/node@24.10.1)(@vercel/blob@2.0.0)(db0@0.3.4)(ioredis@5.8.2)(jiti@2.6.1)(less@4.2.0)(rollup@4.53.3)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(typescript@5.9.3)(yaml@2.8.0):
dependencies:
'@astrojs/compiler': 2.13.0
'@astrojs/internal-helpers': 0.7.5
@@ -19646,7 +19666,7 @@ snapshots:
ultrahtml: 1.6.0
unifont: 0.6.0
unist-util-visit: 5.0.0
unstorage: 1.17.3(db0@0.3.4)(ioredis@5.8.2)
unstorage: 1.17.3(@vercel/blob@2.0.0)(db0@0.3.4)(ioredis@5.8.2)
vfile: 6.0.3
vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
vitefu: 1.1.1(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0))
@@ -19697,6 +19717,11 @@ snapshots:
async-limiter@1.0.1: {}
async-retry@1.3.3:
dependencies:
retry: 0.13.1
optional: true
async-sema@3.1.1: {}
async@2.6.4:
@@ -20260,7 +20285,7 @@ snapshots:
chrome-launcher@0.15.2:
dependencies:
'@types/node': 24.10.1
'@types/node': 22.19.1
escape-string-regexp: 4.0.0
is-wsl: 2.2.0
lighthouse-logger: 1.4.2
@@ -20271,7 +20296,7 @@ snapshots:
chromium-edge-launcher@0.2.0:
dependencies:
'@types/node': 24.10.1
'@types/node': 22.19.1
escape-string-regexp: 4.0.0
is-wsl: 2.2.0
lighthouse-logger: 1.4.2
@@ -20503,7 +20528,7 @@ snapshots:
copy-webpack-plugin@10.2.1(webpack@5.76.1(@swc/core@1.7.23(@swc/helpers@0.5.17))(esbuild@0.14.22)):
dependencies:
fast-glob: 3.3.2
fast-glob: 3.3.3
glob-parent: 6.0.2
globby: 12.2.0
normalize-path: 3.0.0
@@ -21072,7 +21097,7 @@ snapshots:
dependencies:
'@types/cookie': 0.4.1
'@types/cors': 2.8.17
'@types/node': 12.20.55
'@types/node': 22.19.1
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.4.2
@@ -21937,14 +21962,6 @@ snapshots:
merge2: 1.4.1
micromatch: 4.0.8
fast-glob@3.3.2:
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.8
fast-glob@3.3.3:
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -22380,7 +22397,7 @@ snapshots:
dependencies:
array-union: 3.0.1
dir-glob: 3.0.1
fast-glob: 3.3.2
fast-glob: 3.3.3
ignore: 5.3.2
merge2: 1.4.1
slash: 4.0.0
@@ -22884,6 +22901,9 @@ snapshots:
is-buffer@1.1.6: {}
is-buffer@2.0.5:
optional: true
is-builtin-module@3.2.1:
dependencies:
builtin-modules: 3.3.0
@@ -22960,6 +22980,9 @@ snapshots:
is-negative-zero@2.0.3: {}
is-node-process@1.2.0:
optional: true
is-number-object@1.0.7:
dependencies:
has-tostringtag: 1.0.2
@@ -23170,7 +23193,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9
'@types/node': 24.10.1
'@types/node': 22.19.1
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@@ -23197,7 +23220,7 @@ snapshots:
jest-mock@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 24.10.1
'@types/node': 22.19.1
jest-util: 29.7.0
jest-regex-util@29.6.3: {}
@@ -23209,7 +23232,7 @@ snapshots:
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 24.10.1
'@types/node': 22.19.1
chalk: 4.1.2
ci-info: 3.8.0
graceful-fs: 4.2.11
@@ -23226,13 +23249,13 @@ snapshots:
jest-worker@27.5.1:
dependencies:
'@types/node': 12.20.55
'@types/node': 22.19.1
merge-stream: 2.0.0
supports-color: 8.1.1
jest-worker@29.7.0:
dependencies:
'@types/node': 24.10.1
'@types/node': 22.19.1
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
@@ -24637,7 +24660,7 @@ snapshots:
node-gyp-build: 4.8.4
optional: true
nitropack@2.8.1(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.2)(xml2js@0.6.2):
nitropack@2.8.1(@vercel/blob@2.0.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.2)(xml2js@0.6.2):
dependencies:
'@cloudflare/kv-asset-handler': 0.3.4
'@netlify/functions': 2.8.2
@@ -24702,7 +24725,7 @@ snapshots:
unctx: 2.4.1
unenv: 1.10.0
unimport: 3.14.6(rollup@4.53.3)
unstorage: 1.17.3(db0@0.3.4)(ioredis@5.8.2)
unstorage: 1.17.3(@vercel/blob@2.0.0)(db0@0.3.4)(ioredis@5.8.2)
optionalDependencies:
xml2js: 0.6.2
transitivePeerDependencies:
@@ -25147,7 +25170,7 @@ snapshots:
dependencies:
ansi-colors: 4.1.3
cli-progress: 3.12.0
fast-glob: 3.3.2
fast-glob: 3.3.3
oslllo-potrace: 3.0.0(encoding@0.1.13)
oslllo-svg2: 3.0.0(encoding@0.1.13)
oslllo-validator: 3.1.0
@@ -27289,7 +27312,7 @@ snapshots:
stylus-loader@6.2.0(stylus@0.56.0)(webpack@5.76.1(@swc/core@1.7.23(@swc/helpers@0.5.17))(esbuild@0.14.22)):
dependencies:
fast-glob: 3.3.2
fast-glob: 3.3.3
klona: 2.0.6
normalize-path: 3.0.0
stylus: 0.56.0
@@ -27621,6 +27644,9 @@ snapshots:
throat@5.0.0: {}
throttleit@2.1.0:
optional: true
through2@2.0.5:
dependencies:
readable-stream: 2.3.8
@@ -28082,7 +28108,7 @@ snapshots:
'@unrs/resolver-binding-win32-ia32-msvc': 1.6.2
'@unrs/resolver-binding-win32-x64-msvc': 1.6.2
unstorage@1.17.3(db0@0.3.4)(ioredis@5.8.2):
unstorage@1.17.3(@vercel/blob@2.0.0)(db0@0.3.4)(ioredis@5.8.2):
dependencies:
anymatch: 3.1.3
chokidar: 4.0.3
@@ -28093,6 +28119,7 @@ snapshots:
ofetch: 1.5.1
ufo: 1.6.1
optionalDependencies:
'@vercel/blob': 2.0.0
db0: 0.3.4
ioredis: 5.8.2

File diff suppressed because it is too large Load Diff

View File

@@ -1,149 +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,
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();

View File

@@ -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"

View File

@@ -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
// 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');
baseCodePoints[alias] = endCodePoint + index + 1;
});
})
)
if (saveCodePoints) {
await fs.writeFile(
path.join(cwd(), 'codepoints.json'),
JSON.stringify(baseCodePoints, null, 2),
'utf-8'
);
}
return baseCodePoints;
}

View File

@@ -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');
}

View File

@@ -0,0 +1,15 @@
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;
});
}

View File

@@ -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'));

View File

@@ -0,0 +1,49 @@
import { promises as fs } from 'fs';
import SVGFixer from 'oslllo-svg-fixer';
import { getAllIconAliases, 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);
}
}

View File

@@ -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';

View File

@@ -0,0 +1,20 @@
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]
})
}

View File

@@ -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}`);
}

View File

@@ -1,3 +0,0 @@
# @lucide/outline-svg
A internal used package to outline SVGs.

View File

@@ -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();

View File

@@ -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"
}
}

View File

@@ -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",
},
}