Merge branch 'docs-vitepress' of https://github.com/lucide-icons/lucide into docs-vitepress

This commit is contained in:
Eric Fennis
2023-05-09 10:32:15 +02:00
25 changed files with 667 additions and 426 deletions
+1
View File
@@ -28,5 +28,6 @@ docs/.vitepress/dist
docs/.vitepress/.temp
docs/.vitepress/data/iconNodes
docs/.vitepress/data/iconMetaData.ts
docs/.vitepress/data/releaseMetaData.json
docs/.vercel
docs/.nitro
-131
View File
@@ -1,131 +0,0 @@
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import { Contributor } from '../theme/types';
import fetch, { Headers } from 'node-fetch'
const IGNORE_COMMIT_MESSAGES = ['fork', 'optimize'];
function getContentHashOfFile(path) {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256');
const stream = fs.createReadStream(path);
stream.on('error', err => reject(err));
stream.on('data', chunk => hash.update(chunk));
stream.on('end', () => resolve(hash.digest('hex')));
});
}
const fetchCommitsOfIcon = async (name) =>{
try {
const headers = new Headers();
const token = process.env.GITHUB_TOKEN;
const username = process.env.GITHUB_USERNAME;
const password = process.env.GITHUB_API_KEY;
headers.set(
'Authorization',
token ? `Bearer ${token}` : `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`,
);
const res = await fetch(
`https://api.github.com/repos/lucide-icons/lucide/commits?path=icons/${name}.svg`,
{
method: 'GET',
headers,
},
);
const data = await res.json();
return {
name,
commits: data,
};
} catch (error) {
throw new Error(error);
}
};
export const filterCommits = (commits) =>
commits.filter(({ commit }) =>
!IGNORE_COMMIT_MESSAGES.some(ignoreItem =>
commit.message.toLowerCase().includes(ignoreItem),
))
.map(({ author }) => ({
author: author && author.login ? author.login : null,
}))
.filter(({ author }, index, self) => self.findIndex((commit) => commit.author === author) === index);
const getIconHash = async (icon) => await getContentHashOfFile(path.join(process.cwd(), "../icons", `${icon}.svg`))
const iconCacheDir = path.join(process.cwd(),'.vitepress/cache/github-api');
const iconCache = (hash) => path.join(iconCacheDir, `${hash}.json`);
export async function checkIconCache(icon) {
const hash = await getIconHash(icon);
const cachePath = iconCache(hash);
if(fs.existsSync( cachePath )) {
const iconCache = fs.readFileSync(cachePath, "utf8");
return JSON.parse(iconCache)
}
return false
}
async function writeIconCache(icon, content) {
const hash = await getIconHash(icon);
const iconCachePath = iconCache(hash);
if (!fs.existsSync(iconCacheDir)){
fs.mkdirSync(iconCacheDir);
}
fs.writeFileSync(iconCachePath, JSON.stringify(content), 'utf-8');
}
export async function getContributors(icon): Promise<Contributor[]> {
try {
let iconCommits
const iconCache = await checkIconCache(icon);
if (iconCache) {
iconCommits = iconCache
} else {
const { commits } = await fetchCommitsOfIcon(icon);
writeIconCache(icon, commits)
iconCommits = commits
}
if (iconCommits && iconCommits.length) {
return filterCommits(iconCommits);
}
return [];
} catch (error) {
throw new Error(error);
}
}
export async function getAllContributors(icons) {
try {
const AllIconCommits = await Promise.all(icons.map(fetchCommitsOfIcon));
const filteredCommits = AllIconCommits.reduce((acc, { name, commits }) => {
if (commits && commits.length) {
acc[name] = filterCommits(commits)
}
return acc;
}, {});
return filteredCommits
} catch (error) {
console.error(error);
}
}
+5 -5
View File
@@ -3,8 +3,8 @@ import path from "path";
import { parseSync } from "svgson";
import { IconNode } from "../../../packages/lucide-react/src/createLucideIcon";
import { IconEntity } from "../theme/types";
import { getContributors } from "./fetchAllContributors";
import { generateHashedKey } from "./helpers";
import releaseMeta from "../data/releaseMetaData.json";
const directory = path.join(process.cwd(), "../icons");
@@ -25,7 +25,7 @@ export async function getData(name: string, { withChildKeys = false }: GetDataOp
const svgContent = fs.readFileSync(svgPath, "utf8");
const jsonPath = path.join(directory, `${name}.json`);
const jsonContent = fs.readFileSync(jsonPath, "utf8");
const { tags, categories } = JSON.parse(jsonContent);
const { tags, categories, contributors } = JSON.parse(jsonContent);
const iconNode = parseSync(svgContent).children.map(
(child) => {
@@ -39,14 +39,14 @@ export async function getData(name: string, { withChildKeys = false }: GetDataOp
}
) as IconNode
// const contributors = await getContributors(name);
return {
name,
tags,
categories,
// contributors,
iconNode,
//contributors,
contributors: ['ericfennis', 'karsa-mistmere'],
...(releaseMeta[name] ?? {})
};
}
@@ -6,6 +6,6 @@
<style scoped>
.container {
padding: 32px 32px 96px;
padding: 32px;
}
</style>
@@ -41,14 +41,14 @@ const component = computed(() => props.href ? 'a' : 'div')
font-size: 16px;
}
.badge:hover, a.badge:hover {
.badge[href]:hover, a.badge:hover {
border-color: var(--vp-button-alt-hover-border);
color: var(--vp-button-alt-hover-text);
background-color: var(--vp-button-alt-hover-bg);
text-decoration: none;
}
.badge:active {
.badge[href]:active {
border-color: var(--vp-button-alt-active-border);
color: var(--vp-button-alt-active-text);
background-color: var(--vp-button-alt-active-bg);
@@ -85,4 +85,8 @@ const value = computed({
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt);
}
.color-input[value="currentColor"] {
}
</style>
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { rotateCw } from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import IconButton from "./IconButton.vue";
const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
</script>
<template>
<IconButton class="reset-button">
<RotateIcon :size="20"/>
</IconButton>
</template>
<style scoped>
.reset-button {
background: none;
padding: 0;
}
.reset-button .lucide {
transition: ease-in-out 0.1s transform;
}
.reset-button:hover {
background: none;
border-color: transparent;
}
/* a rotate css animation keyframes */
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
.reset-button:active .lucide {
transform: rotate(45deg);
}
</style>
@@ -0,0 +1,32 @@
<script setup lang="ts">
import Badge from '../base/Badge.vue';
import lucidePackage from '../../../../../packages/lucide/package.json';
const currentVersion = lucidePackage.version;
</script>
<template>
<div class="hero-badge">
<Badge>v{{ currentVersion }}</Badge>
</div>
<h1>
<span class="name">Beautiful &</span><br>
<span class="text">consistent icons</span>
</h1>
<p class="tagline">Made by the community.</p>
</template>
<style scoped>
h1 {
line-height: 64px;
font-size: 56px;
max-width: 576px;
font-weight: 700;
white-space: pre-wrap;
}
.name {
color: var(--vp-c-brand);
}
.badge {
display: inline-block;
margin-bottom: 16px;
}
</style>
@@ -1,11 +1,10 @@
<script setup lang="ts">
import { syncRef, useCssVar } from '@vueuse/core'
import HomeContainer from './HomeContainer.vue'
import { rotateCw } from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import RangeSlider from '../base/RangeSlider.vue'
import InputField from '../base/InputField.vue'
import ColorPicker from '../base/ColorPicker.vue'
import ResetButton from '../base/ResetButton.vue'
import HomeIconCustomizerIcons from './HomeIconCustomizerIcons.vue'
import { ref } from 'vue'
@@ -43,14 +42,11 @@ syncRef(color, colorCssVar)
syncRef(strokeWidth, strokeWidthCssVar)
syncRef(size, sizeCssVar)
// TODO Add Reset button
// const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
// function resetStyle () {
// color.value = 'currentColor'
// strokeWidth.value = 2
// size.value = 24
// }
function resetStyle () {
color.value = 'currentColor'
strokeWidth.value = 2
size.value = 24
}
</script>
@@ -60,6 +56,7 @@ syncRef(size, sizeCssVar)
<div class="card-column">
<h2 class="title">
Style as you please
<ResetButton @click="resetStyle"></ResetButton>
</h2>
<p class="copy">
Lucide has a lot of customization options to match the icons with you UI.
@@ -89,7 +86,7 @@ syncRef(size, sizeCssVar)
v-model="strokeWidth"
:min="1"
:max="3"
:steps="0.25"
:step="0.25"
/>
</InputField>
@@ -134,6 +131,11 @@ syncRef(size, sizeCssVar)
line-height: 32px;
font-size: 24px;
font-weight: 600;
display: flex;
flex-direction: row;
gap: 8px;
justify-content: space-between;
align-items: center;
}
.copy {
@@ -170,6 +172,6 @@ syncRef(size, sizeCssVar)
}
.color-picker-field:deep(.display-value) {
width: 130px;
width: 138px;
}
</style>
@@ -33,11 +33,11 @@ const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting
.icon-grid {
display: grid;
gap: 1px;
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
grid-template-rows: repeat(auto-fill, minmax(56px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(68px, 1fr));
grid-template-rows: repeat(auto-fill, minmax(68px, 1fr));
width: 100%;
height:100%;
max-height: 300px;
max-height: 360px;
gap: 1px;
overflow: hidden;
position: relative;
@@ -54,7 +54,28 @@ function copyVue() {
navigator.clipboard.writeText(code)
}
// TODO: This function should be moved to a composable, together with the styling
function copyAngular() {
let attrs = ['']
attrs.push(`name="${props.name}"`)
if (size.value && size.value !== 24) {
attrs.push(`[size]="${size.value}"`)
}
if (color.value && color.value !== 'currentColor') {
attrs.push(`color="${color.value}"`)
}
if (strokeWidth.value && strokeWidth.value !== 2) {
attrs.push(`[strokeWidth]="${strokeWidth.value}"`)
}
const code = `<lucide-icon${attrs.join(' ')}></lucide-icon>`
navigator.clipboard.writeText(code)
}
function confetti(e) {
animate.value = true;
@@ -77,6 +98,7 @@ function confetti(e) {
{ text: 'Copy JSX' , onClick: copyJSX },
{ text: 'Copy Vue' , onClick: copyVue },
{ text: 'Copy Svelte' , onClick: copyJSX },
{ text: 'Copy Angular' , onClick: copyAngular },
]"
/>
</template>
@@ -0,0 +1,81 @@
<script setup lang="ts">
import {IconEntity} from "../../types";
const props = defineProps<{
icon: IconEntity
}>()
</script>
<template>
<div class="contributors" v-if="props.icon.contributors?.length>0">
<h2 class="label">Contributors</h2>
<div class="avatar-group">
<a class="avatar" v-for="contributor in props.icon.contributors" :href="`https://github.com/${contributor}`" target="_blank" :data-name="contributor">
<img class="avatar-image" :alt="contributor" :src="`https://github.com/${contributor}.png?size=128`" />
</a>
</div>
</div>
</template>
<style scoped>
.contributors {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 24px;
}
h2.label {
font-size: 20px;
border: 0;
padding: 0;
margin: 0;
}
.avatar-group {
display: flex;
}
.avatar:not(:first-child) {
margin-left: -24px;
}
.avatar {
position: relative;
}
.avatar:before {
content: attr(data-name);
display: block;
font-size: 12px;
line-height: 20px;
transform: translateX(-50%) scale(0.9);
font-weight: 400;
position: absolute;
top: -28px;
left: 50%;
background: var(--vp-c-brand-dark);
color: white;
z-index: 10;
padding: 2px 8px;
border-radius: 4px;
box-shadow: var(--vp-shadow-1);
opacity: 0;
pointer-events: none;
transition: cubic-bezier(0.19, 1, 0.22, 1) .2s;
transition-property: opacity, transform;
overflow: hidden;
white-space: nowrap;
word-break: break-word;
}
.avatar:hover:before {
opacity: 1;
transform: translateX(-50%) scale(1);
}
.avatar-image {
width: 64px;
height: 64px;
border-radius: 50%;
border: 3px solid var(--vp-c-bg-elv);
}
.avatar:hover .avatar-image {
border: 3px solid var(--vp-c-bg-soft-mute);
}
</style>
@@ -9,7 +9,6 @@
font-size: 24px;
font-weight: 500;
line-height: 32px;
margin-bottom: 12px;
transition: background ease-in .15s;;
padding: 2px 8px;
border-radius: 8px;
@@ -3,10 +3,12 @@
import { computed, ref } from 'vue'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import IconButton from '../base/IconButton.vue';
import IconContributors from './IconContributors.vue';
import IconPreview from './IconPreview.vue';
import { x, expand } from '../../../data/iconNodes'
import { useRouter } from 'vitepress';
import IconInfo from './IconInfo.vue';
import Badge from '../base/Badge.vue';
const props = defineProps<{
icon: IconEntity
@@ -35,9 +37,7 @@
<div class="overlay-container" v-if="props.icon">
<div class="overlay-panel">
<nav class="overlay-menu">
<IconButton @click="go(`/icons/${icon.name}`)">
<component :is="Expand" />
</IconButton>
<Badge>v{{ icon.createdRelease.version }}</Badge>
<IconButton @click="onClose">
<component :is="CloseIcon" />
</IconButton>
@@ -48,7 +48,11 @@
:iconNode="props.icon.iconNode"
customizable
/>
<IconInfo :icon="props.icon" popoverPosition="top" />
<IconInfo :icon="props.icon" popoverPosition="top">
<template v-slot:footer>
<IconContributors :icon="props.icon" />
</template>
</IconInfo>
</div>
</div>
</Transition>
@@ -94,8 +98,8 @@
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
will-change: transform;
pointer-events: all;
height: 240px;
padding: 24px 32px;
height: 288px;
padding: 24px;
display: flex;
box-shadow: var(--vp-shadow-5);
}
@@ -118,6 +122,7 @@
right: 24px;
display: flex;
gap: 8px;
align-items: center;
}
.drawer-enter-active {
@@ -134,6 +139,4 @@
opacity: 0;
}
</style>
@@ -41,7 +41,7 @@ const { page } = useData()
<CopySVGButton :name="icon.name" :popoverPosition="popoverPosition"/>
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
</div>
<slot name="footer" />
</div>
</template>
@@ -52,8 +52,10 @@ const { page } = useData()
gap: 8px;
margin-bottom: 24px;
}
.category {
text-transform: capitalize;
}
.icon-name {
margin-bottom: 12px;
}
</style>
@@ -95,6 +95,6 @@ watch(searchQueryThrottled, (searchString) => {
}
.bottom-page {
height: 200px;
height: 288px;
}
</style>
@@ -3,11 +3,9 @@ import { shallowRef, type Ref } from 'vue'
import { useCssVar, syncRef } from '@vueuse/core'
import { useIconStyleContext } from '../../composables/useIconStyle'
import RangeSlider from '../base/RangeSlider.vue'
import IconButton from '../base/IconButton.vue'
import InputField from '../base/InputField.vue'
import ColorPicker from '../base/ColorPicker.vue'
import { rotateCw } from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import ResetButton from '../base/ResetButton.vue'
const props = defineProps<{
rootEl?: Ref<HTMLElement>
@@ -44,8 +42,6 @@ syncRef(color, colorCssVar)
syncRef(strokeWidth, strokeWidthCssVar)
syncRef(size, sizeCssVar)
const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
function resetStyle () {
color.value = 'currentColor'
strokeWidth.value = 2
@@ -59,9 +55,7 @@ function resetStyle () {
<h2 class="card-title">
Customizer
</h2>
<IconButton class="reset-button" @click="resetStyle">
<RotateIcon :size="20"/>
</IconButton>
<ResetButton @click="resetStyle"></ResetButton>
</div>
<InputField
id="icon-color"
@@ -136,30 +130,4 @@ function resetStyle () {
margin-left: auto;
}
.reset-button {
background: none;
padding: 0;
transition: ease-in-out 0.1s transform;
}
.reset-button:hover {
background: none;
border-color: transparent;
}
/* a rotate css animation keyframes */
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
.reset-button:active {
transform: rotate(45deg);
}
</style>
+2
View File
@@ -4,6 +4,7 @@ import './style.css'
import { Theme } from 'vitepress'
import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue'
import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue'
import HomeHeroInfo from "./components/home/HomeHeroInfo.vue";
import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle'
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView'
@@ -11,6 +12,7 @@ const theme: Partial<Theme> = {
extends: DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
'home-hero-info': () => h(HomeHeroInfo),
'sidebar-nav-after': () => h(IconsSidebarNavAfter),
'home-hero-image': () => h(HomeHeroIconsCard)
})
+9 -5
View File
@@ -4,12 +4,10 @@ export interface IconEntity {
name: string;
tags: string[];
categories: string[];
contributors: Contributor[];
contributors: string[];
iconNode: IconNode;
}
export interface Contributor {
author: string;
createdRelease?: Release;
changedRelease?: Release;
}
export interface Category {
@@ -36,3 +34,9 @@ export interface PackageItem {
private?: boolean
flutter?: object
}
export interface Release {
version: string
date: string
}
+18
View File
@@ -12,7 +12,9 @@ import { computed } from 'vue'
import { useData } from 'vitepress'
import IconPreview from '../.vitepress/theme/components/icons/IconPreview.vue'
import IconInfo from '../.vitepress/theme/components/icons/IconInfo.vue'
import IconContributors from '../.vitepress/theme/components/icons/IconContributors.vue'
import CodeGroup from '../.vitepress/theme/components/base/CodeGroup.vue'
import Badge from '../.vitepress/theme/components/base/Badge.vue'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
import { data } from './codeExamples.data'
import { camelCase, startCase } from 'lodash-es'
@@ -50,6 +52,11 @@ const codeExample = computed(() => data.codeExamples?.map(
/>
</CodeGroup>
</div>
<div class="release-info">
<Badge><strong>Created:</strong> v{{$params.createdRelease.version}}</Badge>
<Badge><strong>Last changed:</strong> v{{$params.changedRelease.version}}</Badge>
</div>
<IconContributors :icon="$params" />
</div>
<style module>
@@ -75,3 +82,14 @@ const codeExample = computed(() => data.codeExamples?.map(
}
}
</style>
<style scoped>
.release-info {
display: flex;
gap: 8px;
flex-direction: column;
align-items: flex-start;
}
.badge {
font-weight: 500;
}
</style>
-1
View File
@@ -21,7 +21,6 @@ hero:
link: https://github.com/lucide-icons/lucide
features:
- title: Lightweight & Scalable
details: Icons are lightweight, highly optimized scalable vector graphics (SVG).
icon: |
+4 -1
View File
@@ -10,10 +10,11 @@
"docs:preview": "vitepress preview",
"build:iconNodes": "node ../scripts/writeIconNodes.mjs",
"build:metaJson": "node ../scripts/writeIconMetaIndex.mjs",
"build:releaseJson": "node ../scripts/writeReleaseMetadata.mjs",
"build:vercelJson": "node ../scripts/writeVercelOutput.mjs",
"dev": "npx nitropack dev",
"build:api": "npx nitropack build",
"build": "pnpm build:iconNodes && pnpm build:metaJson && pnpm build:api && pnpm docs:build && pnpm build:vercelJson",
"build": "pnpm build:iconNodes && pnpm build:metaJson && pnpm build:releaseJson && pnpm build:api && pnpm docs:build && pnpm build:vercelJson",
"preview": "node .output/server/index.mjs"
},
"keywords": [],
@@ -39,8 +40,10 @@
"lucide-vue-next": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"semver": "^7.5.0",
"shiki": "^0.14.2",
"shiki-processor": "^0.1.3",
"simple-git": "^3.18.0",
"svg-pathdata": "^6.0.3",
"vue": "^3.2.47"
}
+2
View File
@@ -41,6 +41,8 @@
"minimist": "^1.2.8",
"node-fetch": "^3.3.1",
"prettier": "2.7.1",
"semver": "^7.5.0",
"simple-git": "^3.18.0",
"svgo": "^3.0.2",
"svgson": "^5.2.1"
},
+311 -218
View File
File diff suppressed because it is too large Load Diff
+94
View File
@@ -0,0 +1,94 @@
import {CleanOptions, simpleGit} from 'simple-git';
import semver from 'semver';
import fs from 'fs';
import path from 'path';
import {readSvgDirectory} from './helpers.mjs';
const currentDir = process.cwd();
const ICONS_DIR = path.resolve(currentDir, '../icons');
const iconJsonFiles = readSvgDirectory(ICONS_DIR, '.json');
const location = path.resolve(currentDir, '.vitepress/data', 'releaseMetaData.json');
if (fs.existsSync(location)) {
fs.unlinkSync(location);
}
export const releaseMin = (a, b) => {
if (!a) return b;
if (!b) return a;
return semver.gt(a.version, b.version) ? b : a;
}
export const releaseMax = (a, b) => {
if (!a) return b;
if (!b) return a;
return semver.gt(a.version, b.version) ? a : b;
}
export const updateReleaseMetadataWithCommit = (metadata, date, release) => {
metadata = metadata || {};
metadata.createdRelease = releaseMin(metadata.createdRelease, release);
metadata.changedRelease = releaseMax(metadata.changedRelease, release);
}
export const fetchAllReleases = async () => {
return (await Promise.all((await simpleGit().raw('show-ref', '--tags', '-d')).trim().split(/\n/)
.map(async (line) => {
const [commit, ref] = line.split(/ /);
const version = semver.coerce(ref.replace('refs/tags/', '')).version;
const date = (await simpleGit().show(['-s', '--format=%cI', commit])).trim();
return {version, date};
}))).filter(({version}) => semver.valid(version))
}
simpleGit().clean(CleanOptions.FORCE);
const findRelease = (date, releases) => {
let closestRelease = null;
for (const release of releases) {
if (release.date >= date && (closestRelease === null || release.date < closestRelease.date)) {
closestRelease = release;
}
}
return closestRelease;
}
const fetchCommits = async (name) => {
const file = `../icons/${name}.svg`;
const {all: commits} = await simpleGit().log(['--reverse', '--follow', '--', file]);
return commits;
}
export const getReleaseMetadata = async (name, aliases, releases) => {
const metadata = {name};
for (let alias of [name, ...(aliases ?? [])]) {
const commits = await fetchCommits(alias);
for (const commit of commits) {
const date = new Date(commit.date).toISOString();
const release = findRelease(date, releases)
updateReleaseMetadataWithCommit(metadata, date, release);
}
}
return metadata;
};
const releases = await fetchAllReleases();
const releaseMetaData = (await Promise.all(iconJsonFiles.map(async (iconJsonFile) => {
const iconName = path.basename(iconJsonFile, '.json');
const aliases = JSON.parse(fs.readFileSync(path.join(ICONS_DIR, iconJsonFile))).aliases;
return await getReleaseMetadata(iconName, aliases, releases);
}))).reduce((acc, {name, ...rest}) => {
acc[name] = rest;
return acc;
}, {})
fs.promises
.writeFile(location, JSON.stringify(releaseMetaData, null, 2), 'utf-8')
.then(() => {
console.log('Successfully written icon release meta cache file');
})
.catch((error) => {
throw new Error(`Something went wrong generating icon release meta cache file,\n ${error}`);
});