Deprecate tags.json (#896)

* Deprecates tags.json and icons.json

* Removed tags.json dependency from Figma plugin

* Add JSON descriptor information to readme

* Restore packages/index.tsx

* Update packages/lucide-figma/src/api/fetchIcons.ts

Add multiple fetches to `Promise.all`

* Added caching to API endpoints

* Updates pnpm-lock.yaml

* Add tags to static

* Trigger site build

* Added prebuild script to generate api caches at build time

* Migrated NextCache function from arrow to regular to simplify markup

* test if contents are read from cache

* Add cache clear to prebuild

* removes debug object

---------

Co-authored-by: Karsa <karsa@karsa.org>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
This commit is contained in:
Karsa
2023-02-16 08:26:29 +01:00
committed by GitHub
parent 4a55ae4b91
commit 8c4a41035b
33 changed files with 338 additions and 16189 deletions

View File

@@ -9,7 +9,7 @@ at https://github.com/lucide-icons/lucide/blob/main/docs/ICON_DESIGN_GUIDE.md be
and please fill everything below. --> and please fill everything below. -->
- **Name of the icon** : <!-- `icon` --> - **Name of the icon** : <!-- `icon` -->
- **Tags (alternative names for this icon)** (add them in tags.json) : - **Tags (alternative names for this icon)** (add them in as a separate json file using the same icon name) :
- **What is the purpose of this icon?** : <!-- Shows that one can click it to... / Is used to denote or label... --> - **What is the purpose of this icon?** : <!-- Shows that one can click it to... / Is used to denote or label... -->
- **100% scale preview** : <!-- upload an image --> - **100% scale preview** : <!-- upload an image -->
- **Have you considered alternative possibilities** for its naming or design? : - **Have you considered alternative possibilities** for its naming or design? :

2
.gitignore vendored
View File

@@ -18,3 +18,5 @@ packages/**/src/icons/*.ts
packages/**/src/icons/*.tsx packages/**/src/icons/*.tsx
packages/**/src/aliases.ts packages/**/src/aliases.ts
packages/**/LICENSE packages/**/LICENSE
categories.json
tags.json

File diff suppressed because it is too large Load Diff

View File

@@ -80,3 +80,21 @@ For each icon these attributes are applied, corresponding to the above rules.
Code of paths can get really big. Code of paths can get really big.
To reduce file size we like to minify the code. To reduce file size we like to minify the code.
We recommend to use the [SVGOMG](https://jakearchibald.github.io/svgomg/) to minify paths. We recommend to use the [SVGOMG](https://jakearchibald.github.io/svgomg/) to minify paths.
### JSON metadata descriptor
Each icon added must also come with a matching JSON file listing tags and categories for the icon.
Please use the following template:
```json
{
"$schema": "../icon.schema.json",
"tags": [
"foo",
"bar"
],
"categories": [
"devices"
]
}
```

9682
icons.json

File diff suppressed because it is too large Load Diff

View File

@@ -1,84 +0,0 @@
{
"$id": "https://lucide.dev/icons.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://json-schema.org/draft/2020-12/vocab/applicator": true,
"https://json-schema.org/draft/2020-12/vocab/unevaluated": true,
"https://json-schema.org/draft/2020-12/vocab/validation": true,
"https://json-schema.org/draft/2020-12/vocab/meta-data": true,
"https://json-schema.org/draft/2020-12/vocab/format-annotation": true,
"https://json-schema.org/draft/2020-12/vocab/content": true
},
"title": "Lucide Icons schema",
"type": "object",
"properties": {
"icons": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/types/icon"
}
},
"categories": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/types/category"
}
}
},
"$defs": {
"types": {
"icon": {
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
},
"categories": {
"type": "array",
"items": {
"$ref": "#/$defs/types/category-reference"
},
"uniqueItems": true
}
}
},
"category": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"icon": {
"$ref": "#/$defs/types/icon-reference"
},
"weight": {
"type": "integer"
}
},
"required": [
"title",
"icon"
]
},
"icon-reference": {
"type": "string",
"format": "uri-reference"
},
"category-reference": {
"type": "string",
"format": "uri-reference"
}
}
},
"description": "A JSON Schema for icons, tags & categories defined by Lucide Icons."
}

View File

@@ -4,8 +4,7 @@
"arrow", "arrow",
"expand", "expand",
"horizontal", "horizontal",
"unfold", "unfold"
"horizonal"
], ],
"categories": [ "categories": [
"arrows" "arrows"

View File

@@ -4,8 +4,7 @@
"arrow", "arrow",
"collapse", "collapse",
"fold", "fold",
"horizontal", "horizontal"
"horizonal"
], ],
"categories": [ "categories": [
"arrows" "arrows"

9
icons/subtitles.json Normal file
View File

@@ -0,0 +1,9 @@
{
"$schema": "../icon.schema.json",
"tags": [
"captions",
"closed captions",
"accessibility"
],
"categories": []
}

View File

@@ -16,13 +16,12 @@
"build:outline-icons": "pnpm --filter outline-svg start", "build:outline-icons": "pnpm --filter outline-svg start",
"generate:supersprite": "node ./scripts/generateSuperSVG.mjs", "generate:supersprite": "node ./scripts/generateSuperSVG.mjs",
"optimize": "node ./scripts/optimizeSvgs.mjs", "optimize": "node ./scripts/optimizeSvgs.mjs",
"addtags": "node ./scripts/addMissingKeysToTags.mjs", "addjsons": "node scripts/addMissingIconJsonFiles.mjs",
"checkIcons": "node scripts/checkIconsAndCategories.mjs --presets @babel/env", "checkIcons": "node scripts/checkIconsAndCategories.mjs --presets @babel/env",
"tags2icons": "node scripts/migrateTagsToIcons.mjs --presets @babel/env", "tags2icons": "node scripts/migrateTagsToIcons.mjs --presets @babel/env",
"icons2tags": "node scripts/migrateIconsToTags.mjs --presets @babel/env", "icons2tags": "node scripts/migrateIconsToTags.mjs --presets @babel/env",
"icons2categories": "node scripts/migrateIconsToCategories.mjs --presets @babel/env", "icons2categories": "node scripts/migrateIconsToCategories.mjs --presets @babel/env",
"categories2icons": "node scripts/migrateCategoriesToIcons.mjs --presets @babel/env", "categories2icons": "node scripts/migrateCategoriesToIcons.mjs --presets @babel/env",
"icons2files": "node scripts/migrateIconsToJsonFiles.mjs --presets @babel/env",
"generate:changelog": "node ./scripts/generateChangelog.mjs", "generate:changelog": "node ./scripts/generateChangelog.mjs",
"postinstall": "husky install", "postinstall": "husky install",
"lint": "eslint --ext .ts,.js,.mjs ./{packages/lucide,scripts}" "lint": "eslint --ext .ts,.js,.mjs ./{packages/lucide,scripts}"

View File

@@ -23,8 +23,10 @@ export const fetchIcons = async (cachedIcons? : LucideIcons): Promise<LucideIcon
return cachedIcons return cachedIcons
} }
const iconNodesResponse = await fetch(`https://unpkg.com/lucide-static@${packageJson.version}/icon-nodes.json`) const [iconNodesResponse, tagsResponse] = await Promise.all([
const tagsResponse = await fetch('https://unpkg.com/lucide-static@latest/tags.json') fetch('https://lucide.dev/api/icon-nodes'),
fetch('https://lucide.dev/api/tags')
])
const iconNodes = await iconNodesResponse.json(); const iconNodes = await iconNodesResponse.json();
const tags = await tagsResponse.json(); const tags = await tagsResponse.json();

View File

@@ -12,11 +12,11 @@
"main": "lib/index.js", "main": "lib/index.js",
"scripts": { "scripts": {
"copy:icons": "cp -r ../../icons icons", "copy:icons": "cp -r ../../icons icons",
"copy:tags": "cp ../../tags.json tags.json",
"copy:license": "cp ../../LICENSE ./LICENSE", "copy:license": "cp ../../LICENSE ./LICENSE",
"build": "pnpm clean && pnpm copy:license && pnpm copy:icons && pnpm copy:tags && pnpm build:lib", "build:tags": "node ../../scripts/migrateIconsToTags.mjs",
"build": "pnpm clean && pnpm copy:license && pnpm copy:icons && pnpm build:lib && pnpm build:tags",
"build:lib": "node ./scripts/buildLib.mjs", "build:lib": "node ./scripts/buildLib.mjs",
"clean": "rm -rf lib && rm -rf build && rm -rf icons && rm -f sprite.svg && rm -f tags.json", "clean": "rm -rf lib && rm -rf build && rm -rf icons && rm -f sprite.svg",
"version": "pnpm version --git-tag-version=false" "version": "pnpm version --git-tag-version=false"
}, },
"devDependencies": { "devDependencies": {

100
pnpm-lock.yaml generated
View File

@@ -407,6 +407,9 @@ importers:
react-dom: 17.0.2 react-dom: 17.0.2
react-svg-loader: ^3.0.3 react-svg-loader: ^3.0.3
react-test-renderer: 17.0.2 react-test-renderer: 17.0.2
svgson: ^5.2.1
ts-node: ~10.9.1
tslib: ^2.4.0
typescript: ^4.3.5 typescript: ^4.3.5
dependencies: dependencies:
'@chakra-ui/react': 1.8.8_vt5jn3lkv6hocyebmjm7gusm6e '@chakra-ui/react': 1.8.8_vt5jn3lkv6hocyebmjm7gusm6e
@@ -431,6 +434,7 @@ importers:
react-color: 2.19.3_react@17.0.2 react-color: 2.19.3_react@17.0.2
react-dom: 17.0.2_react@17.0.2 react-dom: 17.0.2_react@17.0.2
react-svg-loader: 3.0.3 react-svg-loader: 3.0.3
svgson: 5.2.1
devDependencies: devDependencies:
'@next/eslint-plugin-next': 12.2.5 '@next/eslint-plugin-next': 12.2.5
'@testing-library/dom': 7.31.2 '@testing-library/dom': 7.31.2
@@ -447,9 +451,11 @@ importers:
babel-loader: 8.2.5_webpack@5.74.0 babel-loader: 8.2.5_webpack@5.74.0
eslint: 8.22.0 eslint: 8.22.0
eslint-config-prettier: 8.5.0_eslint@8.22.0 eslint-config-prettier: 8.5.0_eslint@8.22.0
jest: 26.6.3 jest: 26.6.3_ts-node@10.9.1
prettier: 2.7.1 prettier: 2.7.1
react-test-renderer: 17.0.2_react@17.0.2 react-test-renderer: 17.0.2_react@17.0.2
ts-node: 10.9.1_gqqbkana4qauswgj6o2wzoub7a
tslib: 2.4.0
typescript: 4.8.4 typescript: 4.8.4
tools/build-icons: tools/build-icons:
@@ -7487,7 +7493,7 @@ packages:
slash: 3.0.0 slash: 3.0.0
dev: true dev: true
/@jest/core/26.6.3: /@jest/core/26.6.3_ts-node@10.9.1:
resolution: {integrity: sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==} resolution: {integrity: sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==}
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
dependencies: dependencies:
@@ -7502,14 +7508,14 @@ packages:
exit: 0.1.2 exit: 0.1.2
graceful-fs: 4.2.10 graceful-fs: 4.2.10
jest-changed-files: 26.6.2 jest-changed-files: 26.6.2
jest-config: 26.6.3 jest-config: 26.6.3_ts-node@10.9.1
jest-haste-map: 26.6.2 jest-haste-map: 26.6.2
jest-message-util: 26.6.2 jest-message-util: 26.6.2
jest-regex-util: 26.0.0 jest-regex-util: 26.0.0
jest-resolve: 26.6.2 jest-resolve: 26.6.2
jest-resolve-dependencies: 26.6.3 jest-resolve-dependencies: 26.6.3
jest-runner: 26.6.3 jest-runner: 26.6.3_ts-node@10.9.1
jest-runtime: 26.6.3 jest-runtime: 26.6.3_ts-node@10.9.1
jest-snapshot: 26.6.2 jest-snapshot: 26.6.2
jest-util: 26.6.2 jest-util: 26.6.2
jest-validate: 26.6.2 jest-validate: 26.6.2
@@ -7632,15 +7638,15 @@ packages:
collect-v8-coverage: 1.0.1 collect-v8-coverage: 1.0.1
dev: true dev: true
/@jest/test-sequencer/26.6.3: /@jest/test-sequencer/26.6.3_ts-node@10.9.1:
resolution: {integrity: sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==} resolution: {integrity: sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==}
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
dependencies: dependencies:
'@jest/test-result': 26.6.2 '@jest/test-result': 26.6.2
graceful-fs: 4.2.10 graceful-fs: 4.2.10
jest-haste-map: 26.6.2 jest-haste-map: 26.6.2
jest-runner: 26.6.3 jest-runner: 26.6.3_ts-node@10.9.1
jest-runtime: 26.6.3 jest-runtime: 26.6.3_ts-node@10.9.1
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- canvas - canvas
@@ -10147,14 +10153,6 @@ packages:
acorn: 8.8.1 acorn: 8.8.1
dev: true dev: true
/acorn-jsx/5.3.2_acorn@8.8.0:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
acorn: 8.8.0
dev: true
/acorn-jsx/5.3.2_acorn@8.8.1: /acorn-jsx/5.3.2_acorn@8.8.1:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies: peerDependencies:
@@ -14113,8 +14111,8 @@ packages:
resolution: {integrity: sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==} resolution: {integrity: sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
acorn: 8.8.0 acorn: 8.8.1
acorn-jsx: 5.3.2_acorn@8.8.0 acorn-jsx: 5.3.2_acorn@8.8.1
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
dev: true dev: true
@@ -16097,12 +16095,12 @@ packages:
throat: 5.0.0 throat: 5.0.0
dev: true dev: true
/jest-cli/26.6.3: /jest-cli/26.6.3_ts-node@10.9.1:
resolution: {integrity: sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==} resolution: {integrity: sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==}
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
hasBin: true hasBin: true
dependencies: dependencies:
'@jest/core': 26.6.3 '@jest/core': 26.6.3_ts-node@10.9.1
'@jest/test-result': 26.6.2 '@jest/test-result': 26.6.2
'@jest/types': 26.6.2 '@jest/types': 26.6.2
chalk: 4.1.2 chalk: 4.1.2
@@ -16110,7 +16108,7 @@ packages:
graceful-fs: 4.2.10 graceful-fs: 4.2.10
import-local: 3.1.0 import-local: 3.1.0
is-ci: 2.0.0 is-ci: 2.0.0
jest-config: 26.6.3 jest-config: 26.6.3_ts-node@10.9.1
jest-util: 26.6.2 jest-util: 26.6.2
jest-validate: 26.6.2 jest-validate: 26.6.2
prompts: 2.4.2 prompts: 2.4.2
@@ -16123,7 +16121,7 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/jest-config/26.6.3: /jest-config/26.6.3_ts-node@10.9.1:
resolution: {integrity: sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==} resolution: {integrity: sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==}
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
peerDependencies: peerDependencies:
@@ -16133,7 +16131,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@babel/core': 7.20.2 '@babel/core': 7.20.2
'@jest/test-sequencer': 26.6.3 '@jest/test-sequencer': 26.6.3_ts-node@10.9.1
'@jest/types': 26.6.2 '@jest/types': 26.6.2
babel-jest: 26.6.3_@babel+core@7.20.2 babel-jest: 26.6.3_@babel+core@7.20.2
chalk: 4.1.2 chalk: 4.1.2
@@ -16143,13 +16141,14 @@ packages:
jest-environment-jsdom: 26.6.2 jest-environment-jsdom: 26.6.2
jest-environment-node: 26.6.2 jest-environment-node: 26.6.2
jest-get-type: 26.3.0 jest-get-type: 26.3.0
jest-jasmine2: 26.6.3 jest-jasmine2: 26.6.3_ts-node@10.9.1
jest-regex-util: 26.0.0 jest-regex-util: 26.0.0
jest-resolve: 26.6.2 jest-resolve: 26.6.2
jest-util: 26.6.2 jest-util: 26.6.2
jest-validate: 26.6.2 jest-validate: 26.6.2
micromatch: 4.0.5 micromatch: 4.0.5
pretty-format: 26.6.2 pretty-format: 26.6.2
ts-node: 10.9.1_gqqbkana4qauswgj6o2wzoub7a
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- canvas - canvas
@@ -16278,7 +16277,7 @@ packages:
fsevents: 2.3.2 fsevents: 2.3.2
dev: true dev: true
/jest-jasmine2/26.6.3: /jest-jasmine2/26.6.3_ts-node@10.9.1:
resolution: {integrity: sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==} resolution: {integrity: sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==}
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
dependencies: dependencies:
@@ -16295,7 +16294,7 @@ packages:
jest-each: 26.6.2 jest-each: 26.6.2
jest-matcher-utils: 26.6.2 jest-matcher-utils: 26.6.2
jest-message-util: 26.6.2 jest-message-util: 26.6.2
jest-runtime: 26.6.3 jest-runtime: 26.6.3_ts-node@10.9.1
jest-snapshot: 26.6.2 jest-snapshot: 26.6.2
jest-util: 26.6.2 jest-util: 26.6.2
pretty-format: 26.6.2 pretty-format: 26.6.2
@@ -16421,7 +16420,7 @@ packages:
slash: 3.0.0 slash: 3.0.0
dev: true dev: true
/jest-runner/26.6.3: /jest-runner/26.6.3_ts-node@10.9.1:
resolution: {integrity: sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==} resolution: {integrity: sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==}
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
dependencies: dependencies:
@@ -16434,13 +16433,13 @@ packages:
emittery: 0.7.2 emittery: 0.7.2
exit: 0.1.2 exit: 0.1.2
graceful-fs: 4.2.10 graceful-fs: 4.2.10
jest-config: 26.6.3 jest-config: 26.6.3_ts-node@10.9.1
jest-docblock: 26.0.0 jest-docblock: 26.0.0
jest-haste-map: 26.6.2 jest-haste-map: 26.6.2
jest-leak-detector: 26.6.2 jest-leak-detector: 26.6.2
jest-message-util: 26.6.2 jest-message-util: 26.6.2
jest-resolve: 26.6.2 jest-resolve: 26.6.2
jest-runtime: 26.6.3 jest-runtime: 26.6.3_ts-node@10.9.1
jest-util: 26.6.2 jest-util: 26.6.2
jest-worker: 26.6.2 jest-worker: 26.6.2
source-map-support: 0.5.21 source-map-support: 0.5.21
@@ -16453,7 +16452,7 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/jest-runtime/26.6.3: /jest-runtime/26.6.3_ts-node@10.9.1:
resolution: {integrity: sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==} resolution: {integrity: sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==}
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
hasBin: true hasBin: true
@@ -16473,7 +16472,7 @@ packages:
exit: 0.1.2 exit: 0.1.2
glob: 7.2.3 glob: 7.2.3
graceful-fs: 4.2.10 graceful-fs: 4.2.10
jest-config: 26.6.3 jest-config: 26.6.3_ts-node@10.9.1
jest-haste-map: 26.6.2 jest-haste-map: 26.6.2
jest-message-util: 26.6.2 jest-message-util: 26.6.2
jest-mock: 26.6.2 jest-mock: 26.6.2
@@ -16618,14 +16617,14 @@ packages:
supports-color: 8.1.1 supports-color: 8.1.1
dev: true dev: true
/jest/26.6.3: /jest/26.6.3_ts-node@10.9.1:
resolution: {integrity: sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==} resolution: {integrity: sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==}
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
hasBin: true hasBin: true
dependencies: dependencies:
'@jest/core': 26.6.3 '@jest/core': 26.6.3_ts-node@10.9.1
import-local: 3.1.0 import-local: 3.1.0
jest-cli: 26.6.3 jest-cli: 26.6.3_ts-node@10.9.1
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- canvas - canvas
@@ -22243,7 +22242,7 @@ packages:
jest-worker: 27.5.1 jest-worker: 27.5.1
schema-utils: 3.1.1 schema-utils: 3.1.1
serialize-javascript: 6.0.0 serialize-javascript: 6.0.0
terser: 5.14.2 terser: 5.16.1
webpack: 5.74.0 webpack: 5.74.0
dev: true dev: true
@@ -22495,6 +22494,37 @@ packages:
yn: 3.1.1 yn: 3.1.1
dev: true dev: true
/ts-node/10.9.1_gqqbkana4qauswgj6o2wzoub7a:
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
'@types/node': '*'
typescript: '>=2.7'
peerDependenciesMeta:
'@swc/core':
optional: true
'@swc/wasm':
optional: true
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.9
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.3
'@types/node': 14.18.25
acorn: 8.8.1
acorn-walk: 8.2.0
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 4.8.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/ts-toolbelt/9.6.0: /ts-toolbelt/9.6.0:
resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==}
dev: true dev: true

View File

@@ -0,0 +1,21 @@
import path from 'path';
import {getCurrentDirPath, readAllMetadata, readSvgDirectory, writeFile} from './helpers.mjs';
const currentDir = getCurrentDirPath(import.meta.url);
const ICONS_DIR = path.resolve(currentDir, '../icons');
const icons = readAllMetadata(ICONS_DIR);
const svgFiles = readSvgDirectory(ICONS_DIR);
const iconNames = svgFiles.map((icon) => icon.split('.')[0]);
iconNames.forEach(iconName => {
if (typeof icons[iconName] === 'undefined') {
const iconContent = JSON.stringify({
"$schema": "../icon.schema.json",
"tags": [],
"categories": []
}, null, 2);
writeFile(iconContent, `${iconName}.json`, path.resolve(currentDir, '..'));
}
});

View File

@@ -1,42 +0,0 @@
import path from 'path';
import tags from '../tags.json' assert { type: 'json' };
import { readSvgDirectory, writeFile, getCurrentDirPath } from './helpers.mjs';
const currentDir = getCurrentDirPath(import.meta.url);
const ICONS_DIR = path.resolve(currentDir, '../icons');
console.log(`Read all tags`);
const svgFiles = readSvgDirectory(ICONS_DIR);
const iconNames = svgFiles.map((icon) => icon.split('.')[0]);
const iconTags = iconNames
.map((iconName) => ({
name: iconName,
tags: tags[iconName] || [],
}))
.sort((a, b) => {
const nameA = a.name;
const nameB = b.name;
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
});
const newTags = iconTags.reduce((acc, { name, tags }) => {
acc[name] = tags;
return acc;
}, {});
const tagsContent = JSON.stringify(newTags, null, 2);
writeFile(tagsContent, 'tags.json', path.resolve(currentDir, '..'));

View File

@@ -1,50 +1,51 @@
import path from 'path'; import path from 'path';
import icons from '../icons.json' assert { type: 'json' }; import { readSvgDirectory, getCurrentDirPath, readAllMetadata } from './helpers.mjs';
import { readSvgDirectory, getCurrentDirPath } from './helpers.mjs';
const currentDir = getCurrentDirPath(import.meta.url) const currentDir = getCurrentDirPath(import.meta.url)
const ICONS_DIR = path.resolve(currentDir, '../icons'); const ICONS_DIR = path.resolve(currentDir, '../icons');
const icons = readAllMetadata(ICONS_DIR);
const CATEGORIES_DIR = path.resolve(currentDir, '../categories');
const categories = readAllMetadata(CATEGORIES_DIR);
console.log(`Read all icons`); console.log(`Read all icons`);
const svgFiles = readSvgDirectory(ICONS_DIR); const svgFiles = readSvgDirectory(ICONS_DIR);
const iconNames = svgFiles.map(icon => icon.split('.')[0]); const iconNames = svgFiles.map(icon => icon.split('.')[0]);
let error = false; let error = false;
iconNames.forEach(iconName => { iconNames.forEach(iconName => {
if (typeof icons.icons[iconName] === 'undefined') { if (typeof icons[iconName] === 'undefined') {
console.error(`'${iconName}.svg' is not present in 'icons.json'.`); console.error(`'${iconName}.svg' does not have a matching JSON file.`);
error = true; error = true;
} }
}); });
Object.keys(icons.icons).forEach(iconName => { Object.keys(icons).forEach(iconName => {
const icon = icons.icons[iconName]; const icon = icons[iconName];
if (iconNames.indexOf(iconName) === -1) { if (iconNames.indexOf(iconName) === -1) {
console.error(`'${iconName}.svg' does not exist.`); console.error(`'${iconName}.svg' does not exist.`);
error = true; error = true;
} }
icon.categories.forEach(categoryName => { icon.categories.forEach(categoryName => {
if (typeof icons.categories[categoryName] === 'undefined') { if (typeof categories[categoryName] === 'undefined') {
console.error(`Icon '${iconName}' refers to the non-existing category '${categoryName}'.`); console.error(`Icon '${iconName}' refers to the non-existing category '${categoryName}'.`);
error = true; error = true;
} }
}); });
}); });
Object.keys(icons.categories).forEach(categoryName => { Object.keys(categories).forEach(categoryName => {
const category = icons.categories[categoryName]; const category = categories[categoryName];
if (!category.icon) { if (!category.icon) {
console.error(`Category '${categoryName}' does not use an icon '${category.icon}'.`); console.error(`Category '${categoryName}' does not use an icon '${category.icon}'.`);
error = true; error = true;
} else if (typeof icons.icons[category.icon] === 'undefined') { } else if (typeof icons[category.icon] === 'undefined') {
console.error(`Category '${categoryName}' uses the non-existing icon '${category.icon}'.`); console.error(`Category '${categoryName}' uses the non-existing icon '${category.icon}'.`);
error = true; error = true;
} }
}); });
if (error) { if (error) {
throw new Error('At least one error in icons.json prevents from committing changes.'); throw new Error('At least one error in icon JSONs prevents from committing changes.');
} }

View File

@@ -70,6 +70,30 @@ export const appendFile = (content, fileName, outputDirectory) =>
export const writeFile = (content, fileName, outputDirectory) => export const writeFile = (content, fileName, outputDirectory) =>
fs.writeFileSync(path.join(outputDirectory, fileName), content, 'utf-8'); fs.writeFileSync(path.join(outputDirectory, fileName), content, 'utf-8');
/**
* Reads metadata from the icons/categories directories
*
* @param {string} directory
* @returns {object} A map of icon or category metadata
*/
export const readAllMetadata = (directory) =>
fs.readdirSync(directory).filter((file) => path.extname(file) === '.json').reduce(
(acc, fileName, i) => {
acc[path.basename(fileName, '.json')] = readMetadata(fileName, directory);
return acc;
}, {}
);
/**
* 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 = (fileName, directory) =>
JSON.parse(fs.readFileSync(path.join(directory, fileName), 'utf-8'));
/** /**
* reads the icon directory * reads the icon directory
* *

View File

@@ -1,16 +1,19 @@
import path from 'path'; import path from 'path';
import icons from '../icons.json' assert { type: 'json' };
import categories from '../categories.json' assert { type: 'json' }; import categories from '../categories.json' assert { type: 'json' };
import { mergeArrays, writeFile, getCurrentDirPath } from './helpers.mjs'; import { mergeArrays, writeFile, readAllMetadata, getCurrentDirPath } from './helpers.mjs';
const currentDir = getCurrentDirPath(import.meta.url) const currentDir = getCurrentDirPath(import.meta.url)
const ICONS_DIR = path.resolve(currentDir, '../icons');
const icons = readAllMetadata(ICONS_DIR);
Object.keys(categories).forEach(categoryName => { Object.keys(categories).forEach(categoryName => {
categories[categoryName].forEach(iconName => { categories[categoryName].forEach(iconName => {
mergeArrays(icons.icons[iconName].categories, [categoryName]); icons[iconName].categories = mergeArrays(icons[iconName].categories, [categoryName]);
}); });
}); });
const iconsContent = JSON.stringify(icons, null, 2); Object.keys(icons).forEach(iconName => {
const iconContent = JSON.stringify(icons[iconName], null, 2);
writeFile(iconContent, `${iconName}.json`, path.resolve(currentDir, '../icons'));
})
writeFile(iconsContent, 'icons.json', path.resolve(currentDir, '..'));

View File

@@ -1,12 +1,13 @@
import path from 'path'; import path from 'path';
import icons from '../icons.json' assert { type: 'json' }; import { writeFile, getCurrentDirPath, readAllMetadata } from './helpers.mjs';
import { writeFile, getCurrentDirPath } from './helpers.mjs';
const currentDir = getCurrentDirPath(import.meta.url) const currentDir = getCurrentDirPath(import.meta.url)
const ICONS_DIR = path.resolve(currentDir, '../icons');
const icons = readAllMetadata(ICONS_DIR);
const newCategories = {}; const newCategories = {};
Object.keys(icons.icons).forEach(iconName => { Object.keys(icons).forEach(iconName => {
icons.icons[iconName].categories.forEach(categoryName => { icons[iconName].categories.forEach(categoryName => {
newCategories[categoryName] = newCategories[categoryName] || []; newCategories[categoryName] = newCategories[categoryName] || [];
newCategories[categoryName].push(iconName); newCategories[categoryName].push(iconName);
}); });

View File

@@ -1,23 +0,0 @@
import path from 'path';
import icons from '../icons.json' assert { type: 'json' };
import { writeFile, getCurrentDirPath } from './helpers.mjs';
const currentDir = getCurrentDirPath(import.meta.url)
Object.keys(icons.icons).forEach(iconName => {
const icon = {
"$schema": "../icon.schema.json",
...icons.icons[iconName]
};
const iconContent = JSON.stringify(icon, null, 2);
writeFile(iconContent, 'icons/'+iconName+'.json', path.resolve(currentDir, '..'));
});
Object.keys(icons.categories).forEach(categoryName => {
const category = {
"$schema": "../category.schema.json",
...icons.categories[categoryName]
};
const categoryContent = JSON.stringify(category, null, 2);
writeFile(categoryContent, 'categories/'+categoryName+'.json', path.resolve(currentDir, '..'));
});

View File

@@ -1,44 +1,17 @@
import path from 'path'; import path from 'path';
import tags from '../tags.json' assert { type: 'json' }; import { writeFile, getCurrentDirPath, readAllMetadata } from './helpers.mjs';
import icons from '../icons.json' assert { type: 'json' };
import { readSvgDirectory, writeFile, mergeArrays, getCurrentDirPath } from './helpers.mjs';
const currentDir = getCurrentDirPath(import.meta.url) const currentDir = getCurrentDirPath(import.meta.url);
const ICONS_DIR = path.resolve(currentDir, '../icons'); const ICONS_DIR = path.resolve(currentDir, '../icons');
const icons = readAllMetadata(ICONS_DIR);
console.log(`Read all icons`); const tags = Object.keys(icons)
.sort()
.reduce((acc, iconName) => {
acc[iconName] = icons[iconName].tags;
return acc;
}, {});
const svgFiles = readSvgDirectory(ICONS_DIR); const tagsContent = JSON.stringify(tags, null, 2);
const iconNames = svgFiles.map(icon => icon.split('.')[0]); writeFile(tagsContent, 'tags.json', path.resolve(process.cwd()));
const iconList = iconNames
.map(iconName => ({
name: iconName,
icon: icons.icons[iconName] || { tags: [] },
tags: tags[iconName] || [],
}))
.sort((a, b) => {
const nameA = a.name;
const nameB = b.name;
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
});
const newTags = iconList.reduce((acc, { name, icon, tags }) => {
acc[name] = mergeArrays(icon.tags, tags);
return acc;
}, {});
const tagsContent = JSON.stringify(newTags, null, 2);
writeFile(tagsContent, 'tags.json', path.resolve(currentDir, '..'));

View File

@@ -1,47 +1,21 @@
import path from 'path'; import path from 'path';
import tags from '../tags.json' assert { type: 'json' }; import tags from '../tags.json' assert { type: 'json' };
import icons from '../icons.json' assert { type: 'json' }; import { readSvgDirectory, readAllMetadata, writeFile, mergeArrays, getCurrentDirPath } from './helpers.mjs';
import { readSvgDirectory, writeFile, mergeArrays, getCurrentDirPath } from './helpers.mjs';
const currentDir = getCurrentDirPath(import.meta.url) const currentDir = getCurrentDirPath(import.meta.url)
const ICONS_DIR = path.resolve(currentDir, '../icons'); const ICONS_DIR = path.resolve(currentDir, '../icons');
const icons = readAllMetadata(ICONS_DIR);
console.log(`Read all icons`);
const svgFiles = readSvgDirectory(ICONS_DIR); const svgFiles = readSvgDirectory(ICONS_DIR);
const iconNames = svgFiles.map(icon => icon.split('.')[0]); const iconNames = svgFiles.map((icon) => icon.split('.')[0]);
const iconList = iconNames iconNames.forEach(iconName => {
.map(iconName => ({ icons[iconName] = icons[iconName] || {
name: iconName, "$schema": "../icon.schema.json",
icon: icons.icons[iconName] || { tags: [], categories: [] }, "tags": [],
tags: tags[iconName] || [], "categories": []
})) };
.sort((a, b) => { icons[iconName].tags = mergeArrays(icons[iconName].tags, tags[iconName]);
const nameA = a.name; const iconContent = JSON.stringify(icons[iconName], null, 2);
const nameB = b.name; writeFile(iconContent, `${iconName}.json`, path.resolve(currentDir, '../icons'));
})
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
});
const newIcons = icons;
newIcons.icons = iconList.reduce((acc, { name, icon, tags }) => {
acc[name] = icon;
acc[name].tags = mergeArrays(icon.tags, tags);
return acc;
}, {});
const iconsContent = JSON.stringify(newIcons, null, 2);
writeFile(iconsContent, 'icons.json', path.resolve(currentDir, '..'));

View File

@@ -1,12 +1,13 @@
{ {
"name": "@lucide/site",
"private": true, "private": true,
"name": "site",
"version": "1.0.0", "version": "1.0.0",
"author": "John Letey", "author": "John Letey",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"copy-assets": "mkdir -p ./public/docs/images && cp -rf ../docs/images ./public/docs", "copy-assets": "mkdir -p ./public/docs/images && cp -rf ../docs/images ./public/docs",
"build": "pnpm copy-assets && next build", "prebuild": "ts-node scripts/preBuild.tsx",
"build": "pnpm copy-assets && pnpm prebuild && next build",
"export": "next export -o build", "export": "next export -o build",
"deploy": "pnpm build && pnpm export", "deploy": "pnpm build && pnpm export",
"lint": "eslint .", "lint": "eslint .",
@@ -36,7 +37,8 @@
"react": "17.0.2", "react": "17.0.2",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-svg-loader": "^3.0.3" "react-svg-loader": "^3.0.3",
"svgson": "^5.2.1"
}, },
"devDependencies": { "devDependencies": {
"@next/eslint-plugin-next": "^12.2.5", "@next/eslint-plugin-next": "^12.2.5",
@@ -57,6 +59,8 @@
"jest": "^26.5.2", "jest": "^26.5.2",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"react-test-renderer": "17.0.2", "react-test-renderer": "17.0.2",
"ts-node": "~10.9.1",
"tslib": "^2.4.0",
"typescript": "^4.3.5" "typescript": "^4.3.5"
} }
} }

17
site/scripts/preBuild.tsx Normal file
View File

@@ -0,0 +1,17 @@
import {fetchTags} from "../src/lib/fetchTags";
import {fetchIconNodes} from "../src/lib/fetchIconNodes";
import NextCache from "../src/lib/nextCache";
const clearCache = async () => {
await NextCache.clear('api-tags', 'api-icon-nodes')
}
const buildCache = async () => {
await Promise.all([fetchTags(), fetchIconNodes()])
}
const rebuildCache = async () => {
await Promise.all([clearCache(), buildCache()])
}
rebuildCache().then(() => null)

View File

@@ -40,7 +40,6 @@ const Header = ({ data }: HeaderProps) => {
const { iconsRef } = useCustomizeIconContext(); const { iconsRef } = useCustomizeIconContext();
const downloadAllIcons = async () => { const downloadAllIcons = async () => {
console.log(iconsRef);
setZippingIcons(true); setZippingIcons(true);
let iconEntries: IconContent[] = Object.entries(iconsRef.current).map(([name, svgEl]) => [ let iconEntries: IconContent[] = Object.entries(iconsRef.current).map(([name, svgEl]) => [

View File

@@ -0,0 +1,15 @@
import NextCache from './nextCache';
import {parseSync} from 'svgson';
import {getAllData} from './icons';
export type IconNode = [string, object, IconNode[]];
export type IconNodes = {[iconName: string]: IconNode};
export function fetchIconNodes(writeCache = true): Promise<IconNodes> {
return NextCache.resolve('api-icon-nodes', async () => {
return (await getAllData()).reduce((acc, icon) => {
acc[icon.name] = parseSync(icon.src).children.map(({name, attributes}) => [name, attributes]);
return acc;
}, {});
}, writeCache);
}

View File

@@ -0,0 +1,13 @@
import NextCache from './nextCache';
import {getAllData} from './icons';
export type Tags = {[iconName: string]: string[]};
export function fetchTags(writeCache = true): Promise<Tags> {
return NextCache.resolve('api-tags', async () => {
return (await getAllData()).reduce((acc, icon) => {
acc[icon.name] = icon.tags;
return acc;
}, {});
}, writeCache);
}

View File

@@ -1,31 +1,32 @@
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import { parseSync } from 'svgson';
import tags from '../../../tags.json';
import { IconEntity } from "../types"; import { IconEntity } from "../types";
import { getContributors } from "./fetchAllContributors"; import { getContributors } from "./fetchAllContributors";
const directory = path.join(process.cwd(), "../icons"); const directory = path.join(process.cwd(), "../icons");
export function getAllNames() { export function getAllNames() {
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.svg'); const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
return fileNames.map((fileName) => { return fileNames
return fileName.replace(/\.svg$/, ""); .filter((fileName) => fs.existsSync(directory + '/' + path.basename(fileName, '.json') + '.svg'))
}); .map((fileName) => path.basename(fileName, '.json'));
} }
export async function getData(name: string) { export async function getData(name: string) {
const fullPath = path.join(directory, `${name}.svg`); const svgPath = path.join(directory, `${name}.svg`);
const fileContent = fs.readFileSync(fullPath, "utf8"); const svgContent = fs.readFileSync(svgPath, "utf8");
const jsonPath = path.join(directory, `${name}.json`);
const jsonContent = fs.readFileSync(jsonPath, "utf8");
const iconJson = JSON.parse(jsonContent);
const contributors = await getContributors(name); const contributors = await getContributors(name);
return { return {
...iconJson,
name, name,
tags: tags[name] || [],
contributors, contributors,
src: fileContent src: svgContent
}; };
} }

View File

@@ -0,0 +1,53 @@
import path from 'path';
import fs from 'fs';
const cacheDir = path.join(process.cwd(), '.next/cache');
const cachePath = (cacheKey: string) => path.join(cacheDir, `${cacheKey}.json`);
type AtomicCacheable = object|string|number|boolean|null;
type Cacheable = AtomicCacheable|AtomicCacheable[];
function read<T extends Cacheable>(cacheKey: string): T {
if (fs.existsSync(cachePath(cacheKey))) {
const iconCache = fs.readFileSync(cachePath(cacheKey), "utf8")
return JSON.parse(iconCache)
}
return null
}
function write<T extends Cacheable>(cacheKey: string, content: T): void {
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir)
}
fs.writeFileSync(cachePath(cacheKey), JSON.stringify(content), 'utf-8')
}
function clear(...cacheKeys: string[]) {
for (const cacheKey of cacheKeys) {
const itemCachePath = cachePath(cacheKey)
if (fs.existsSync(itemCachePath)) {
fs.unlinkSync(itemCachePath)
}
}
}
async function resolve<T extends Cacheable>(cacheKey: string, contentResolver: () => Promise<T>|T, writeCache = true): Promise<T> {
try {
let cacheItem = await read<T>(cacheKey)
if (cacheItem === null) {
cacheItem = await contentResolver()
if (writeCache) {
write(cacheKey, cacheItem)
}
}
return cacheItem;
} catch (error) {
throw new Error(error)
}
}
const NextCache = {read, write, resolve, clear}
export default NextCache

View File

@@ -0,0 +1,8 @@
import {fetchIconNodes} from '../../../lib/fetchIconNodes';
export default async function handler(req, res) {
res.setHeader(
'Cache-Control',
'public, max-age=86400'
).status(200).json(await fetchIconNodes(false));
}

View File

@@ -0,0 +1,8 @@
import {fetchTags} from '../../../lib/fetchTags';
export default async function handler(req, res) {
res.setHeader(
'Cache-Control',
'public, max-age=86400'
).status(200).json(await fetchTags(false));
}

View File

@@ -1,4 +1,9 @@
{ {
"ts-node": {
"compilerOptions": {
"module": "commonjs"
}
},
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"lib": [ "lib": [

4640
tags.json

File diff suppressed because it is too large Load Diff