Compare commits

...

55 Commits
3.0.2 ... main

Author SHA1 Message Date
Vjacheslav Trushkin a3d158f99c chore: make dockerfile work 2025-11-27 20:31:47 +02:00
Vjacheslav Trushkin 407792e2bc chore: publish new minor version 2025-11-27 19:14:13 +02:00
Vjacheslav Trushkin 8e35844326 chore: update all dependencies, update routes for latest fastify 2025-11-27 17:31:14 +02:00
Vjacheslav Trushkin f79638ef5f chore: update dependencies, clean up renovate config 2025-11-18 09:07:18 +02:00
Vjacheslav Trushkin 23b5dc510e
Merge pull request #61 from iconify/revert-51-renovate/fastify-formbody-8.x
Revert "fix(deps): update dependency @fastify/formbody to v8"
2025-11-18 08:30:40 +02:00
Vjacheslav Trushkin 238e12a016
Revert "fix(deps): update dependency @fastify/formbody to v8" 2025-11-18 08:30:29 +02:00
Vjacheslav Trushkin 26828c1152
Merge pull request #55 from iconify/renovate/node-22.x
chore(deps): update node.js to v22
2025-10-16 09:11:08 +03:00
renovate[bot] d24e229645
chore(deps): update node.js to v22 2025-10-14 05:30:30 +00:00
Vjacheslav Trushkin 62fa26b541
Merge pull request #51 from iconify/renovate/fastify-formbody-8.x
fix(deps): update dependency @fastify/formbody to v8
2025-10-14 08:28:09 +03:00
renovate[bot] 65af386d3c
fix(deps): update dependency @fastify/formbody to v8 2025-10-14 05:26:58 +00:00
Vjacheslav Trushkin ae0e4c929d
Merge pull request #42 from iconify/renovate/typescript-5.x
chore(deps): update dependency typescript to ^5.9.3
2025-10-14 08:25:25 +03:00
renovate[bot] 116d8e20df
chore(deps): update dependency typescript to ^5.9.3 2025-10-14 05:25:18 +00:00
Vjacheslav Trushkin a7e3d99d9e
Merge pull request #41 from iconify/renovate/node-20.x
chore(deps): update dependency @types/node to ^20.19.21
2025-10-14 08:24:36 +03:00
Vjacheslav Trushkin 2521142eca
Merge pull request #40 from iconify/renovate/fastify-4.x
fix(deps): update dependency fastify to ^4.29.1
2025-10-14 08:24:24 +03:00
Vjacheslav Trushkin 84590145c9
Merge pull request #39 from iconify/renovate/iconify-tools-4.x
fix(deps): update dependency @iconify/tools to ^4.1.4
2025-10-14 08:24:04 +03:00
renovate[bot] 88b88251aa
chore(deps): update dependency @types/node to ^20.19.21 2025-10-14 05:23:20 +00:00
Vjacheslav Trushkin 262783f704
Merge pull request #45 from iconify/renovate/npm-11.x
chore(deps): update npm to v11.6.2
2025-10-14 08:23:03 +03:00
Vjacheslav Trushkin 48b0da6d55
Merge pull request #50 from iconify/renovate/major-jest-monorepo
chore(deps): update dependency @types/jest to v30
2025-10-14 08:22:52 +03:00
Vjacheslav Trushkin 119538fbff
Merge pull request #47 from iconify/renovate/actions-checkout-5.x
chore(deps): update actions/checkout action to v5
2025-10-14 08:22:18 +03:00
renovate[bot] 3141131ab7
chore(deps): update dependency @types/jest to v30 2025-10-14 05:22:12 +00:00
renovate[bot] ba72402310
chore(deps): update actions/checkout action to v5 2025-10-14 05:21:57 +00:00
Vjacheslav Trushkin 6f33b7c4c5
Merge pull request #49 from iconify/renovate/actions-setup-node-6.x
chore(deps): update actions/setup-node action to v6
2025-10-14 08:20:38 +03:00
renovate[bot] 44884118f3
chore(deps): update actions/setup-node action to v6 2025-10-14 03:49:55 +00:00
renovate[bot] 9ec6b365a0
chore(deps): update npm to v11.6.2 2025-10-12 08:03:54 +00:00
renovate[bot] 5e1dbe8d4f
fix(deps): update dependency fastify to ^4.29.1 2025-10-08 19:57:57 +00:00
renovate[bot] 9b7b1a4279
fix(deps): update dependency @iconify/tools to ^4.1.4 2025-10-08 19:57:49 +00:00
Vjacheslav Trushkin 871028175b
chore: update renovate.json 2025-10-06 12:45:59 +03:00
Vjacheslav Trushkin a752e1ee7f
Merge pull request #35 from iconify/renovate/configure
chore: Configure Renovate
2025-10-06 12:45:06 +03:00
renovate[bot] 39e5bb4817
Add renovate.json 2025-10-06 08:02:52 +00:00
Vjacheslav Trushkin 2afff788df chore: update version number 2025-02-12 00:24:55 +02:00
Vjacheslav Trushkin 8300891cba chore: update dependencies 2025-02-12 00:20:29 +02:00
Vjacheslav Trushkin f62b8ba8e6 chore: clean up params in svg query 2025-02-12 00:16:48 +02:00
Vjacheslav Trushkin 65b0eca32e chore: update dependencies 2024-10-16 20:23:53 +03:00
Vjacheslav Trushkin a02dd19d51 chore: fix error codes 2024-10-16 20:20:08 +03:00
Vjacheslav Trushkin e44822a40d chore: allow updating specific importer 2024-05-14 16:33:31 +03:00
Vjacheslav Trushkin c5260541b8 chore: move github data to .github repo 2024-05-14 15:55:46 +03:00
Vjacheslav Trushkin 1d55e2dafd chore: publish as latest tag 2024-04-26 16:16:10 +03:00
Vjacheslav Trushkin 5d296cd782 chore: publish 3.1.0 2024-04-26 16:14:16 +03:00
Vjacheslav Trushkin 9c6a063676 chore: use null constructor when cloning icon set 2024-04-25 23:37:49 +03:00
Vjacheslav Trushkin c153237943 chore: revert mlly change 2024-02-14 11:05:50 +02:00
Vjacheslav Trushkin 283142a8d7 chore: update dependencies, log icon sets package version 2024-02-13 21:16:48 +02:00
Vjacheslav Trushkin c1ac0a5609 chore: update ci 2024-02-07 10:37:42 +02:00
Vjacheslav Trushkin 49a077bc7b chore: update ci 2024-02-07 10:36:29 +02:00
Vjacheslav Trushkin cb8baf9acf chore: update dependencies, use mlly to resolve packages 2024-02-07 10:33:40 +02:00
Vjacheslav Trushkin 0b9427baa4 chore: allow npm fallback if cannot resolve local package 2024-01-23 23:46:40 +02:00
Vjacheslav Trushkin 95423f118b chore: fix full local package importer 2024-01-18 21:50:58 +02:00
Vjacheslav Trushkin a391ce96cf chore: premade importer for npm package 2024-01-17 18:06:14 +02:00
Vjacheslav Trushkin 2963c7a666 chore: add options for init function, expose loading state 2024-01-17 14:29:31 +02:00
Vjacheslav Trushkin ceb3fc4394 chore: split api responses from server, allowing reuse, prepare npm package 2024-01-17 09:45:43 +02:00
Vjacheslav Trushkin 2181b8022c chore: change default redirect 2024-01-16 22:22:28 +02:00
Vjacheslav Trushkin 6471e051e9 chore: switch to es modules 2024-01-16 21:02:18 +02:00
Vjacheslav Trushkin 32582d8dae chore: update dependencies 2024-01-16 18:18:22 +02:00
Vjacheslav Trushkin ceaea7ceca chore: update dependencies 2023-05-31 23:16:00 +03:00
Vjacheslav Trushkin 5994184446 chore: update license years and doc links 2023-05-31 23:14:06 +03:00
Vjacheslav Trushkin c53a1e7a82 feat: add min parameter to search query 2023-05-22 08:57:19 +03:00
89 changed files with 3579 additions and 4091 deletions

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
github: cyberalien

View File

@ -15,10 +15,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: 18
node-version: 'latest'
- name: 📦 Install dependencies
run: npm ci

18
.npmignore Normal file
View File

@ -0,0 +1,18 @@
/.idea
/.vscode
.DS_Store
/.env
/.editorconfig
/.prettierrc
*.map
/docker.sh
/Dockerfile
/tsconfig*.*
/vitest.config.*
/.github
/src
/node_modules
/cache
/tmp
/icons
/tests

View File

@ -1,11 +1,11 @@
ARG ARCH=amd64
ARG NODE_VERSION=18
ARG NODE_VERSION=22
ARG OS=bullseye-slim
ARG ICONIFY_API_VERSION=3.0.0
ARG ICONIFY_API_VERSION=3.2.0
ARG SRC_PATH=./
#### Stage BASE ########################################################################################################
FROM ${ARCH}/node:${NODE_VERSION}-${OS} AS base
FROM --platform=${ARCH} node:${NODE_VERSION}-${OS} AS base
# This gives node.js apps access to the OS CAs
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
@ -53,8 +53,8 @@ COPY ${SRC_PATH}icons/ /data/iconify-api/icons/
# Build API
RUN npm run build
#### Stage RELEASE #####################################################################################################
FROM iconify-api-install AS RELEASE
#### Stage release #####################################################################################################
FROM iconify-api-install AS release
ARG BUILD_DATE
ARG BUILD_VERSION
ARG BUILD_REF

View File

@ -6,6 +6,12 @@ This repository contains Iconify API script. It is a HTTP server, written in Nod
- Generates SVG, which you can link to in HTML or stylesheet.
- Provides search engine for hosted icons, which can be used by icon pickers.
## NPM Package
This package is also available at NPM, allowing using API code in custom wrappers.
NPM package contains only compiled files, to build custom Docker image you need to use source files from Git repository, not NPM package.
## Docker
To build a Docker image, run `./docker.sh`.
@ -85,7 +91,7 @@ By default, API will use memory management functions. It stores only recently us
000
If your API gets a lot of traffic (above 1k requests per minute), it is better to not use memory management. With such high number of queries, disc read/write operations might cause degraded performance. API can easily handle 10 times more traffic on a basic VPS if everything is in memory and can be accessed instantly.
See [memory management in full API docs](https://docs.iconify.design/api/hosting-js/config.html).
See [memory management in full API docs](https://iconify.design/docs/api/hosting-js/config.html).
### Updating icons
@ -125,7 +131,7 @@ Previous version of API was also available as PHP script. This has been disconti
This file is basic.
Full documentation is available on [Iconify documentation website](https://docs.iconify.design/api/).
Full documentation is available on [Iconify documentation website](https://iconify.design/docs/api/).
## Sponsors
@ -143,4 +149,4 @@ Iconify API is licensed under MIT license.
This licence does not apply to icons hosted on API and files generated by API. You can host icons with any license, without any restrictions. Common decency applies, such as not hosting pirated versions of commercial icon sets (not sure why anyone would use commercial icon sets when so many excellent open source icon sets are available, but anyway...).
© 2022 Vjacheslav Trushkin / Iconify OÜ
© 2022-PRESENT Vjacheslav Trushkin

2
license.txt Executable file → Normal file
View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Vjacheslav Trushkin
Copyright (c) 2022-PRESENT Vjacheslav Trushkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

6539
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,17 +3,17 @@
"description": "Iconify API",
"author": "Vjacheslav Trushkin",
"license": "MIT",
"private": true,
"version": "3.0.2",
"version": "3.2.0",
"type": "module",
"bugs": "https://github.com/iconify/api/issues",
"homepage": "https://github.com/iconify/api",
"repository": {
"type": "git",
"url": "https://github.com/iconify/api.git"
},
"packageManager": "npm@9.6.6",
"packageManager": "npm@11.6.4",
"engines": {
"node": ">=16.15.0"
"node": ">=22.20.0"
},
"scripts": {
"build": "tsc -b",
@ -26,17 +26,17 @@
"docker:publish": "docker push iconify/api"
},
"dependencies": {
"@fastify/formbody": "^7.4.0",
"@iconify/tools": "^2.2.6",
"@fastify/formbody": "^8.0.2",
"@iconify/tools": "^5.0.0",
"@iconify/types": "^2.0.0",
"@iconify/utils": "^2.1.5",
"dotenv": "^16.0.3",
"fastify": "^4.17.0"
"@iconify/utils": "^3.1.0",
"dotenv": "^17.2.3",
"fastify": "^5.6.2"
},
"devDependencies": {
"@types/jest": "^29.5.1",
"@types/node": "^18.16.12",
"typescript": "^5.0.4",
"vitest": "^0.31.1"
"@types/jest": "^30.0.0",
"@types/node": "^24.10.1",
"typescript": "^5.9.3",
"vitest": "^4.0.14"
}
}

27
renovate.json Normal file
View File

@ -0,0 +1,27 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"rangeStrategy": "bump",
"packageRules": [
{
"matchDepTypes": ["peerDependencies"],
"enabled": false
},
{
"matchPackageNames": ["fastify"],
"allowedVersions": "<4.0.0"
},
{
"matchPackageNames": ["@fastify/formbody"],
"allowedVersions": "<8.0.0"
},
{
"matchPackageNames": ["@iconify/utils"],
"allowedVersions": "<3.0.0"
},
{
"matchPackageNames": ["@iconify/tools"],
"allowedVersions": "<5.0.0"
}
]
}

View File

@ -1,13 +1,13 @@
import type { AppConfig } from '../types/config/app';
import type { SplitIconSetConfig } from '../types/config/split';
import type { MemoryStorageConfig } from '../types/storage';
import type { AppConfig } from '../types/config/app.js';
import type { SplitIconSetConfig } from '../types/config/split.js';
import type { MemoryStorageConfig } from '../types/storage.js';
/**
* Main configuration
*/
export const appConfig: AppConfig = {
// Index page
redirectIndex: 'https://docs.iconify.design/api/',
redirectIndex: 'https://iconify.design/docs/api/',
// Region to add to `/version` response
// Used to tell which server is responding when running multiple servers

View File

@ -1,10 +1,10 @@
import { DirectoryDownloader } from '../downloaders/directory';
import { createJSONDirectoryImporter } from '../importers/full/directory-json';
import { directoryExists } from '../misc/files';
import type { Importer } from '../types/importers';
import type { ImportedData } from '../types/importers/common';
import { fullPackageImporter } from './importers/full-package';
import { splitPackagesImporter } from './importers/split-packages';
import { DirectoryDownloader } from '../downloaders/directory.js';
import { createJSONDirectoryImporter } from '../importers/full/directory-json.js';
import { directoryExists } from '../misc/files.js';
import type { Importer } from '../types/importers.js';
import type { ImportedData } from '../types/importers/common.js';
import { fullPackageImporter } from './importers/full-package.js';
import { splitPackagesImporter } from './importers/split-packages.js';
/**
* Sources

View File

@ -0,0 +1,40 @@
import { createRequire } from 'node:module';
import { dirname } from 'node:path';
import { Importer } from '../../types/importers.js';
import { createIconSetsPackageImporter } from '../../importers/full/json.js';
import { ImportedData } from '../../types/importers/common.js';
import { DirectoryDownloader } from '../../downloaders/directory.js';
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote.js';
import { RemoteDownloader } from '../../downloaders/remote.js';
/**
* Create importer for package
*/
export function createPackageIconSetImporter(
packageName = '@iconify/json',
useRemoteFallback = false,
autoUpdateRemotePackage = false
): Importer {
// Try to locate package
let dir: string | undefined;
try {
const req = createRequire(import.meta.url);
const filename = req.resolve(`${packageName}/package.json`);
dir = filename ? dirname(filename) : undefined;
} catch (err) {
//
}
if (dir) {
return createIconSetsPackageImporter(new DirectoryDownloader<ImportedData>(dir), {});
}
if (!useRemoteFallback) {
throw new Error(`Cannot find package "${packageName}"`);
}
// Try to download it, update if
const npm: RemoteDownloaderOptions = {
downloadType: 'npm',
package: packageName,
};
return createIconSetsPackageImporter(new RemoteDownloader<ImportedData>(npm, autoUpdateRemotePackage));
}

View File

@ -1,7 +1,7 @@
import { RemoteDownloader } from '../../downloaders/remote';
import { createIconSetsPackageImporter } from '../../importers/full/json';
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote';
import type { ImportedData } from '../../types/importers/common';
import { RemoteDownloader } from '../../downloaders/remote.js';
import { createIconSetsPackageImporter } from '../../importers/full/json.js';
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote.js';
import type { ImportedData } from '../../types/importers/common.js';
/**
* Importer for all icon sets from `@iconify/json` package

View File

@ -1,7 +1,7 @@
import { RemoteDownloader } from '../../downloaders/remote';
import { createJSONCollectionsListImporter } from '../../importers/collections/collections';
import { createJSONPackageIconSetImporter } from '../../importers/icon-set/json-package';
import type { IconSetImportedData, ImportedData } from '../../types/importers/common';
import { RemoteDownloader } from '../../downloaders/remote.js';
import { createJSONCollectionsListImporter } from '../../importers/collections/collections.js';
import { createJSONPackageIconSetImporter } from '../../importers/icon-set/json-package.js';
import type { IconSetImportedData, ImportedData } from '../../types/importers/common.js';
// Automatically update on startup: boolean
const autoUpdate = true;

View File

@ -1,5 +1,5 @@
import type { IconifyJSON } from '@iconify/types';
import type { IconSetIconsListIcons, IconSetAPIv2IconsList } from '../../../types/icon-set/extra';
import type { IconSetIconsListIcons, IconSetAPIv2IconsList } from '../../../types/icon-set/extra.js';
/**
* Prepare data for icons list API v2 response

View File

@ -1,13 +1,13 @@
import type { IconifyAliases, IconifyJSON, IconifyOptional } from '@iconify/types';
import { defaultIconProps } from '@iconify/utils/lib/icon/defaults';
import { appConfig } from '../../../config/app';
import { appConfig } from '../../../config/app.js';
import type {
IconSetIconNames,
IconSetIconsListIcons,
IconSetIconsListTag,
IconStyle,
} from '../../../types/icon-set/extra';
import { getIconStyle } from './style';
} from '../../../types/icon-set/extra.js';
import { getIconStyle } from './style.js';
const customisableProps = Object.keys(defaultIconProps) as (keyof IconifyOptional)[];

View File

@ -1,4 +1,4 @@
import type { IconStyle } from '../../../types/icon-set/extra';
import type { IconStyle } from '../../../types/icon-set/extra.js';
function getValues(body: string, prop: string): string[] {
const chunks = body.split(prop + '="');

View File

@ -1,5 +1,5 @@
import type { IconifyJSON } from '@iconify/types';
import type { IconSetIconsListIcons } from '../../../types/icon-set/extra';
import type { IconSetIconsListIcons } from '../../../types/icon-set/extra.js';
/**
* Removes bad items

View File

@ -1,7 +1,7 @@
import type { IconifyIcons, IconifyJSON } from '@iconify/types';
import { defaultIconDimensions } from '@iconify/utils/lib/icon/defaults';
import type { SplitIconSetConfig } from '../../../types/config/split';
import type { SplitIconifyJSONMainData } from '../../../types/icon-set/split';
import type { SplitIconSetConfig } from '../../../types/config/split.js';
import type { SplitIconifyJSONMainData } from '../../../types/icon-set/split.js';
const iconDimensionProps = Object.keys(defaultIconDimensions) as (keyof typeof defaultIconDimensions)[];
@ -21,7 +21,14 @@ export function splitIconSetMainData(iconSet: IconifyJSON): SplitIconifyJSONMain
for (let i = 0; i < iconSetMainDataProps.length; i++) {
const prop = iconSetMainDataProps[i];
if (iconSet[prop]) {
result[prop as 'prefix'] = iconSet[prop as 'prefix'];
const value = iconSet[prop as 'prefix'];
if (typeof value === 'object') {
// Make sure object has null as constructor
result[prop as 'prefix'] = Object.create(null);
Object.assign(result[prop as 'prefix'], iconSet[prop as 'prefix']);
} else {
result[prop as 'prefix'] = iconSet[prop as 'prefix'];
}
} else if (prop === 'aliases') {
result[prop] = Object.create(null);
}

View File

@ -1,17 +1,16 @@
import type { IconifyIcons, IconifyJSON } from '@iconify/types';
import { appConfig, splitIconSetConfig, storageConfig } from '../../../config/app';
import type { SplitIconSetConfig } from '../../../types/config/split';
import type { StorageIconSetThemes, StoredIconSet, StoredIconSetDone } from '../../../types/icon-set/storage';
import type { SplitRecord } from '../../../types/split';
import type { MemoryStorage, MemoryStorageItem } from '../../../types/storage';
import { createSplitRecordsTree, splitRecords } from '../../storage/split';
import { createStorage, createStoredItem } from '../../storage/create';
import { getIconSetSplitChunksCount, splitIconSetMainData } from './split';
import { removeBadIconSetItems } from '../lists/validate';
import { prepareAPIv2IconsList } from '../lists/icons-v2';
import { generateIconSetIconsTree } from '../lists/icons';
import { themeKeys } from './themes';
import { findIconSetThemes } from './themes';
import { appConfig, splitIconSetConfig, storageConfig } from '../../../config/app.js';
import type { SplitIconSetConfig } from '../../../types/config/split.js';
import type { StorageIconSetThemes, StoredIconSet, StoredIconSetDone } from '../../../types/icon-set/storage.js';
import type { SplitRecord } from '../../../types/split.js';
import type { MemoryStorage, MemoryStorageItem } from '../../../types/storage.js';
import { createSplitRecordsTree, splitRecords } from '../../storage/split.js';
import { createStorage, createStoredItem } from '../../storage/create.js';
import { getIconSetSplitChunksCount, splitIconSetMainData } from './split.js';
import { removeBadIconSetItems } from '../lists/validate.js';
import { prepareAPIv2IconsList } from '../lists/icons-v2.js';
import { generateIconSetIconsTree } from '../lists/icons.js';
import { themeKeys, findIconSetThemes } from './themes.js';
/**
* Storage

View File

@ -1,5 +1,5 @@
import { IconifyJSON } from '@iconify/types';
import { StorageIconSetThemes } from '../../../types/icon-set/storage';
import { StorageIconSetThemes } from '../../../types/icon-set/storage.js';
/**
* Themes to copy

View File

@ -1,9 +1,9 @@
import type { ExtendedIconifyAlias, ExtendedIconifyIcon, IconifyIcons } from '@iconify/types';
import { mergeIconData } from '@iconify/utils/lib/icon/merge';
import type { SplitIconifyJSONMainData } from '../../../types/icon-set/split';
import type { StoredIconSet } from '../../../types/icon-set/storage';
import { searchSplitRecordsTree } from '../../storage/split';
import { getStoredItem } from '../../storage/get';
import type { SplitIconifyJSONMainData } from '../../../types/icon-set/split.js';
import type { StoredIconSet } from '../../../types/icon-set/storage.js';
import { searchSplitRecordsTree } from '../../storage/split.js';
import { getStoredItem } from '../../storage/get.js';
interface PrepareResult {
// Merged properties

View File

@ -1,7 +1,7 @@
import type { IconifyJSON, IconifyAliases, IconifyIcons } from '@iconify/types';
import type { StoredIconSet } from '../../../types/icon-set/storage';
import { searchSplitRecordsTreeForSet } from '../../storage/split';
import { getStoredItem } from '../../storage/get';
import type { StoredIconSet } from '../../../types/icon-set/storage.js';
import { searchSplitRecordsTreeForSet } from '../../storage/split.js';
import { getStoredItem } from '../../storage/get.js';
/**
* Get list of icons that must be retrieved

View File

@ -1,6 +1,6 @@
import type { StoredIconSet } from '../types/icon-set/storage';
import type { IconSetEntry, Importer } from '../types/importers';
import { updateSearchIndex } from './search';
import type { StoredIconSet } from '../types/icon-set/storage.js';
import type { IconSetEntry, Importer } from '../types/importers.js';
import { updateSearchIndex } from './search.js';
/**
* All importers
@ -125,8 +125,9 @@ export function updateIconSets(): number {
/**
* Trigger update
*/
export function triggerIconSetsUpdate() {
export function triggerIconSetsUpdate(index?: number | null, done?: (success?: boolean) => void) {
if (!importers) {
done?.();
return;
}
console.log('Checking for updates...');
@ -140,6 +141,9 @@ export function triggerIconSetsUpdate() {
// Check for updates
let updated = false;
for (let i = 0; i < importers?.length; i++) {
if (typeof index === 'number' && i !== index) {
continue;
}
updated = (await importers[i].checkForUpdate()) || updated;
}
return updated;
@ -147,6 +151,10 @@ export function triggerIconSetsUpdate() {
.then((updated) => {
console.log(updated ? 'Update complete' : 'Nothing to update');
updateIconSets();
done?.(true);
})
.catch(console.error);
.catch((err) => {
console.error(err);
done?.(false);
});
}

View File

@ -22,6 +22,13 @@ export function loaded() {
}
}
/**
* Get state
*/
export function isLoading() {
return loading;
}
/**
* Run when app is ready
*/

View File

@ -1,6 +1,6 @@
import { appConfig } from '../config/app';
import type { IconSetEntry } from '../types/importers';
import type { SearchIndexData } from '../types/search';
import { appConfig } from '../config/app.js';
import type { IconSetEntry } from '../types/importers.js';
import type { SearchIndexData } from '../types/search.js';
interface SearchIndex {
data?: SearchIndexData;

View File

@ -1,10 +1,10 @@
import { appConfig } from '../../config/app';
import type { IconSetIconNames } from '../../types/icon-set/extra';
import type { IconSetEntry } from '../../types/importers';
import type { SearchIndexData, SearchKeywordsEntry, SearchParams, SearchResultsData } from '../../types/search';
import { getPartialKeywords } from './partial';
import { filterSearchPrefixes, filterSearchPrefixesList } from './prefixes';
import { splitKeyword } from './split';
import { appConfig } from '../../config/app.js';
import type { IconSetIconNames } from '../../types/icon-set/extra.js';
import type { IconSetEntry } from '../../types/importers.js';
import type { SearchIndexData, SearchKeywordsEntry, SearchParams, SearchResultsData } from '../../types/search.js';
import { getPartialKeywords } from './partial.js';
import { filterSearchPrefixes, filterSearchPrefixesList } from './prefixes.js';
import { splitKeyword } from './split.js';
/**
* Run search
@ -69,6 +69,7 @@ export function search(
return newItem;
};
const limit = params.limit;
const softLimit = params.softLimit;
interface ExtendedSearchKeywordsEntry extends SearchKeywordsEntry {
// Add prefixes cache to avoid re-calculating it for every partial keyword
@ -256,9 +257,9 @@ export function search(
// Extract results
const results: string[] = [];
const prefixes: Set<string> = new Set();
for (let i = 0; i < allMatches.length && results.length < limit; i++) {
for (let i = 0; i < allMatches.length && (softLimit || results.length < limit); i++) {
const { names } = allMatches[i];
for (let j = 0; j < names.length && results.length < limit; j++) {
for (let j = 0; j < names.length && (softLimit || results.length < limit); j++) {
const name = names[j];
results.push(name);
prefixes.add(name.split(':').shift() as string);

View File

@ -1,5 +1,5 @@
import type { PartialKeywords, SearchIndexData } from '../../types/search';
import { searchIndex } from '../search';
import type { PartialKeywords, SearchIndexData } from '../../types/search.js';
import { searchIndex } from '../search.js';
export const minPartialKeywordLength = 2;

View File

@ -1,6 +1,6 @@
import { appConfig } from '../../config/app';
import type { IconSetEntry } from '../../types/importers';
import type { SearchIndexData, SearchParams } from '../../types/search';
import { appConfig } from '../../config/app.js';
import type { IconSetEntry } from '../../types/importers.js';
import type { SearchIndexData, SearchParams } from '../../types/search.js';
/**
* Filter prefixes by keyword

View File

@ -1,8 +1,8 @@
import { matchIconName } from '@iconify/utils/lib/icon/name';
import { paramToBoolean } from '../../misc/bool';
import type { IconStyle } from '../../types/icon-set/extra';
import type { SearchKeywords, SearchKeywordsEntry } from '../../types/search';
import { minPartialKeywordLength } from './partial';
import { paramToBoolean } from '../../misc/bool.js';
import type { IconStyle } from '../../types/icon-set/extra.js';
import type { SearchKeywords, SearchKeywordsEntry } from '../../types/search.js';
import { minPartialKeywordLength } from './partial.js';
interface SplitOptions {
// Can include prefix

View File

@ -1,4 +1,4 @@
import type { MemoryStorageItem, MemoryStorageCallback } from '../../types/storage';
import type { MemoryStorageItem, MemoryStorageCallback } from '../../types/storage.js';
/**
* Run all callbacks from storage

View File

@ -1,5 +1,5 @@
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage';
import { runStorageCallbacks } from './callbacks';
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage.js';
import { runStorageCallbacks } from './callbacks.js';
/**
* Stop timer

View File

@ -1,7 +1,7 @@
import { appConfig } from '../../config/app';
import type { MemoryStorage, MemoryStorageConfig, MemoryStorageItem } from '../../types/storage';
import { cleanupStoredItem } from './cleanup';
import { writeStoredItem } from './write';
import { appConfig } from '../../config/app.js';
import type { MemoryStorage, MemoryStorageConfig, MemoryStorageItem } from '../../types/storage.js';
import { cleanupStoredItem } from './cleanup.js';
import { writeStoredItem } from './write.js';
/**
* Create storage

View File

@ -1,5 +1,5 @@
import type { MemoryStorageItem, MemoryStorageCallback, MemoryStorage } from '../../types/storage';
import { loadStoredItem } from './load';
import type { MemoryStorageItem, MemoryStorageCallback, MemoryStorage } from '../../types/storage.js';
import { loadStoredItem } from './load.js';
/**
* Get storage data when ready

View File

@ -1,7 +1,7 @@
import { readFile, readFileSync } from 'node:fs';
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage';
import { runStorageCallbacks } from './callbacks';
import { addStorageToCleanup } from './cleanup';
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage.js';
import { runStorageCallbacks } from './callbacks.js';
import { addStorageToCleanup } from './cleanup.js';
/**
* Load data

View File

@ -1,4 +1,4 @@
import type { SplitDataTree, SplitRecord, SplitRecordCallback } from '../../types/split';
import type { SplitDataTree, SplitRecord, SplitRecordCallback } from '../../types/split.js';
/**
* Split records into `count` chunks

View File

@ -1,6 +1,6 @@
import { rm } from 'node:fs/promises';
import { appConfig } from '../../config/app';
import type { MemoryStorage } from '../../types/storage';
import { appConfig } from '../../config/app.js';
import type { MemoryStorage } from '../../types/storage.js';
/**
* Remove old cache

View File

@ -1,7 +1,7 @@
import { writeFile, mkdir } from 'node:fs';
import { dirname } from 'node:path';
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage';
import { addStorageToCleanup } from './cleanup';
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage.js';
import { addStorageToCleanup } from './cleanup.js';
const createdDirs: Set<string> = new Set();

View File

@ -1,4 +1,4 @@
import type { DownloaderStatus, DownloaderType } from '../types/downloaders/base';
import type { DownloaderStatus, DownloaderType } from '../types/downloaders/base.js';
/**
* loadDataFromDirectory()

View File

@ -1,4 +1,4 @@
import { BaseDownloader } from './base';
import { BaseDownloader } from './base.js';
/**
* Custom downloader

View File

@ -1,5 +1,5 @@
import { directoryExists, hashFiles, listFilesInDirectory } from '../misc/files';
import { BaseDownloader } from './base';
import { directoryExists, hashFiles, listFilesInDirectory } from '../misc/files.js';
import { BaseDownloader } from './base.js';
/**
* Directory downloader

View File

@ -1,9 +1,9 @@
import { directoryExists } from '../misc/files';
import type { RemoteDownloaderOptions, RemoteDownloaderVersion } from '../types/downloaders/remote';
import { BaseDownloader } from './base';
import { downloadRemoteArchive } from './remote/download';
import { getRemoteDownloaderCacheKey } from './remote/key';
import { getDownloaderVersion, saveDownloaderVersion } from './remote/versions';
import { directoryExists } from '../misc/files.js';
import type { RemoteDownloaderOptions, RemoteDownloaderVersion } from '../types/downloaders/remote.js';
import { BaseDownloader } from './base.js';
import { downloadRemoteArchive } from './remote/download.js';
import { getRemoteDownloaderCacheKey } from './remote/key.js';
import { getDownloaderVersion, saveDownloaderVersion } from './remote/versions.js';
/**
* Remote downloader

View File

@ -2,7 +2,7 @@ import { execAsync } from '@iconify/tools/lib/misc/exec';
import { getGitHubRepoHash } from '@iconify/tools/lib/download/github/hash';
import { getGitLabRepoHash } from '@iconify/tools/lib/download/gitlab/hash';
import { getNPMVersion, getPackageVersion } from '@iconify/tools/lib/download/npm/version';
import { directoryExists } from '../../misc/files';
import { directoryExists } from '../../misc/files.js';
import type {
GitDownloaderOptions,
GitDownloaderVersion,
@ -12,7 +12,7 @@ import type {
GitLabDownloaderVersion,
NPMDownloaderOptions,
NPMDownloaderVersion,
} from '../../types/downloaders/remote';
} from '../../types/downloaders/remote.js';
/**
* Check git repo for update

View File

@ -2,15 +2,15 @@ import { downloadGitRepo } from '@iconify/tools/lib/download/git';
import { downloadGitHubRepo } from '@iconify/tools/lib/download/github';
import { downloadGitLabRepo } from '@iconify/tools/lib/download/gitlab';
import { downloadNPMPackage } from '@iconify/tools/lib/download/npm';
import { appConfig } from '../../config/app';
import type { RemoteDownloaderOptions, RemoteDownloaderVersion } from '../../types/downloaders/remote';
import { appConfig } from '../../config/app.js';
import type { RemoteDownloaderOptions, RemoteDownloaderVersion } from '../../types/downloaders/remote.js';
import {
isGitHubUpdateAvailable,
isGitLabUpdateAvailable,
isGitUpdateAvailable,
isNPMUpdateAvailable,
} from './check-update';
import { getDownloadDirectory } from './target';
} from './check-update.js';
import { getDownloadDirectory } from './target.js';
/**
* Download files from remote archive

View File

@ -1,5 +1,5 @@
import { hashString } from '../../misc/hash';
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote';
import { hashString } from '../../misc/hash.js';
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote.js';
/**
* Get cache key

View File

@ -1,6 +1,6 @@
import { appConfig } from '../../config/app';
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote';
import { getRemoteDownloaderCacheKey } from './key';
import { appConfig } from '../../config/app.js';
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote.js';
import { getRemoteDownloaderCacheKey } from './key.js';
/**
* Get directory

View File

@ -1,11 +1,11 @@
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { dirname } from 'node:path';
import { appConfig } from '../../config/app';
import { appConfig } from '../../config/app.js';
import type {
RemoteDownloaderType,
RemoteDownloaderVersion,
RemoteDownloaderVersionMixin,
} from '../../types/downloaders/remote';
} from '../../types/downloaders/remote.js';
// Storage
type StoredVersions = Record<string, RemoteDownloaderVersion>;

View File

@ -0,0 +1,10 @@
export function errorText(code: number) {
switch (code) {
case 404:
return 'Not found';
case 400:
return 'Bad request';
}
return 'Internal server error';
}

View File

@ -0,0 +1,6 @@
/**
* Basic cleanup for parameters
*/
export function cleanupQueryValue(value: string | undefined) {
return value ? value.replace(/['"<>&]/g, '') : undefined;
}

View File

@ -0,0 +1,45 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { checkJSONPQuery, sendJSONResponse } from './json.js';
import { createIconsDataResponse } from '../responses/icons.js';
type CallbackResult = object | number;
/**
* Handle icons data API response
*/
export function handleIconsDataResponse(
prefix: string,
wrapJS: boolean,
query: FastifyRequest['query'],
res: FastifyReply
) {
const q = (query || {}) as Record<string, string>;
// Check for JSONP
const wrap = checkJSONPQuery(q, wrapJS, 'SimpleSVG._loaderCallback');
if (!wrap) {
// Invalid JSONP callback
res.send(400);
return;
}
// Function to send response
const respond = (result: CallbackResult) => {
if (typeof result === 'number') {
res.send(result);
} else {
sendJSONResponse(result, q, wrap, res);
}
};
// Get result
const result = createIconsDataResponse(prefix, q);
if (result instanceof Promise) {
result.then(respond).catch((err) => {
console.error(err);
respond(500);
});
} else {
respond(result);
}
}

44
src/http/helpers/send.ts Normal file
View File

@ -0,0 +1,44 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { checkJSONPQuery, sendJSONResponse } from './json.js';
import { errorText } from './errors.js';
type CallbackResult = object | number;
/**
* Handle JSON API response generated by a callback
*/
export function handleJSONResponse(
req: FastifyRequest,
res: FastifyReply,
callback: (query: Record<string, string>) => CallbackResult | Promise<CallbackResult>
) {
const q = (req.query || {}) as Record<string, string>;
// Check for JSONP
const wrap = checkJSONPQuery(q);
if (!wrap) {
// Invalid JSONP callback
res.code(400).send(errorText(400));
return;
}
// Function to send response
const respond = (result: CallbackResult) => {
if (typeof result === 'number') {
res.code(result).send(errorText(result));
} else {
sendJSONResponse(result, q, wrap, res);
}
};
// Get result
const result = callback(q);
if (result instanceof Promise) {
result.then(respond).catch((err) => {
console.error(err);
respond(500);
});
} else {
respond(result);
}
}

View File

@ -1,19 +1,21 @@
import fastify from 'fastify';
import fastifyFormBody from '@fastify/formbody';
import { appConfig, httpHeaders } from '../config/app';
import { runWhenLoaded } from '../data/loading';
import { iconNameRoutePartialRegEx, iconNameRouteRegEx, splitIconName } from '../misc/name';
import { generateAPIv1IconsListResponse } from './responses/collection-v1';
import { generateAPIv2CollectionResponse } from './responses/collection-v2';
import { generateCollectionsListResponse } from './responses/collections';
import { generateIconsDataResponse } from './responses/icons';
import { generateKeywordsResponse } from './responses/keywords';
import { generateLastModifiedResponse } from './responses/modified';
import { generateAPIv2SearchResponse } from './responses/search';
import { generateSVGResponse } from './responses/svg';
import { generateUpdateResponse } from './responses/update';
import { initVersionResponse, versionResponse } from './responses/version';
import { generateIconsStyleResponse } from './responses/css';
import { appConfig, httpHeaders } from '../config/app.js';
import { runWhenLoaded } from '../data/loading.js';
import { iconNameRoutePartialRegEx, iconNameRouteRegEx, splitIconName } from '../misc/name.js';
import { createAPIv1IconsListResponse } from './responses/collection-v1.js';
import { createAPIv2CollectionResponse } from './responses/collection-v2.js';
import { createCollectionsListResponse } from './responses/collections.js';
import { handleIconsDataResponse } from './helpers/send-icons.js';
import { createKeywordsResponse } from './responses/keywords.js';
import { createLastModifiedResponse } from './responses/modified.js';
import { createAPIv2SearchResponse } from './responses/search.js';
import { generateSVGResponse } from './responses/svg.js';
import { generateUpdateResponse } from './responses/update.js';
import { initVersionResponse, versionResponse } from './responses/version.js';
import { generateIconsStyleResponse } from './responses/css.js';
import { handleJSONResponse } from './helpers/send.js';
import { errorText } from './helpers/errors.js';
/**
* Start HTTP server
@ -21,7 +23,9 @@ import { generateIconsStyleResponse } from './responses/css';
export async function startHTTPServer() {
// Create HTP server
const server = fastify({
caseSensitive: true,
routerOptions: {
caseSensitive: true,
},
});
// Support `application/x-www-form-urlencoded`
@ -81,19 +85,19 @@ export async function startHTTPServer() {
generateSVGResponse(name.prefix, name.name, req.query, res);
});
} else {
res.send(404);
res.code(404).send(errorText(404));
}
});
// Icons data: /prefix/icons.json, /prefix.json
server.get('/:prefix(' + iconNameRoutePartialRegEx + ')/icons.json', (req, res) => {
runWhenLoaded(() => {
generateIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
handleIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
});
});
server.get('/:prefix(' + iconNameRoutePartialRegEx + ').json', (req, res) => {
runWhenLoaded(() => {
generateIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
handleIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
});
});
@ -107,19 +111,19 @@ export async function startHTTPServer() {
// Icons data: /prefix/icons.js, /prefix.js
server.get('/:prefix(' + iconNameRoutePartialRegEx + ')/icons.js', (req, res) => {
runWhenLoaded(() => {
generateIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
handleIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
});
});
server.get('/:prefix(' + iconNameRoutePartialRegEx + ').js', (req, res) => {
runWhenLoaded(() => {
generateIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
handleIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
});
});
// Last modification time
server.get('/last-modified', (req, res) => {
runWhenLoaded(() => {
generateLastModifiedResponse(req.query, res);
handleJSONResponse(req, res, createLastModifiedResponse);
});
});
@ -127,26 +131,26 @@ export async function startHTTPServer() {
// Icon sets list
server.get('/collections', (req, res) => {
runWhenLoaded(() => {
generateCollectionsListResponse(req.query, res);
handleJSONResponse(req, res, createCollectionsListResponse);
});
});
// Icons list, API v2
server.get('/collection', (req, res) => {
runWhenLoaded(() => {
generateAPIv2CollectionResponse(req.query, res);
handleJSONResponse(req, res, createAPIv2CollectionResponse);
});
});
// Icons list, API v1
server.get('/list-icons', (req, res) => {
runWhenLoaded(() => {
generateAPIv1IconsListResponse(req.query, res, false);
handleJSONResponse(req, res, (q) => createAPIv1IconsListResponse(q, false));
});
});
server.get('/list-icons-categorized', (req, res) => {
runWhenLoaded(() => {
generateAPIv1IconsListResponse(req.query, res, true);
handleJSONResponse(req, res, (q) => createAPIv1IconsListResponse(q, true));
});
});
@ -154,14 +158,14 @@ export async function startHTTPServer() {
// Search, currently version 2
server.get('/search', (req, res) => {
runWhenLoaded(() => {
generateAPIv2SearchResponse(req.query, res);
handleJSONResponse(req, res, createAPIv2SearchResponse);
});
});
// Keywords
server.get('/keywords', (req, res) => {
runWhenLoaded(() => {
generateKeywordsResponse(req.query, res);
handleJSONResponse(req, res, createKeywordsResponse);
});
});
}
@ -177,7 +181,7 @@ export async function startHTTPServer() {
// Options
server.options('/*', (req, res) => {
res.send(200);
res.code(204).header('Content-Length', '0').send();
});
// Robots
@ -197,21 +201,21 @@ export async function startHTTPServer() {
// Redirect
server.get('/', (req, res) => {
res.redirect(301, appConfig.redirectIndex);
res.redirect(appConfig.redirectIndex, 301);
});
// Error handling
server.setDefaultRoute((req, res) => {
server.setNotFoundHandler((req, res) => {
res.statusCode = 404;
console.log('404:', req.url);
// Need to set custom headers because hooks don't work here
for (let i = 0; i < headers.length; i++) {
const header = headers[i];
res.setHeader(header.key, header.value);
res.header(header.key, header.value);
}
res.end();
res.send();
});
// Start it

View File

@ -1,17 +1,18 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { getPrefixes, iconSets } from '../../data/icon-sets';
import type { IconSetAPIv2IconsList } from '../../types/icon-set/extra';
import type { StoredIconSet } from '../../types/icon-set/storage';
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
import type { IconSetAPIv2IconsList } from '../../types/icon-set/extra.js';
import type { StoredIconSet } from '../../types/icon-set/storage.js';
import type {
APIv1ListIconsBaseResponse,
APIv1ListIconsCategorisedResponse,
APIv1ListIconsResponse,
} from '../../types/server/v1';
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
import { filterPrefixesByPrefix } from '../helpers/prefixes';
} from '../../types/server/v1.js';
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
// Response results, depends on `categorised` option
type PossibleResults = APIv1ListIconsResponse | APIv1ListIconsCategorisedResponse;
/**
* Send API v2 response
* Create API v1 response
*
* This response ignores the following parameters:
* - `aliases` -> always enabled
@ -19,27 +20,15 @@ import { filterPrefixesByPrefix } from '../helpers/prefixes';
*
* Those parameters are always requested anyway, so does not make sense to re-create data in case they are disabled
*/
export function generateAPIv1IconsListResponse(
query: FastifyRequest['query'],
res: FastifyReply,
export function createAPIv1IconsListResponse(
query: Record<string, string>,
categorised: boolean
) {
const q = (query || {}) as Record<string, string>;
const wrap = checkJSONPQuery(q);
if (!wrap) {
// Invalid JSONP callback
res.send(400);
return;
}
): PossibleResults | Record<string, PossibleResults> | number {
function parse(
prefix: string,
iconSet: StoredIconSet,
v2Cache: IconSetAPIv2IconsList
): APIv1ListIconsResponse | APIv1ListIconsCategorisedResponse {
const icons = iconSet.icons;
// Generate common data
const base: APIv1ListIconsBaseResponse = {
prefix,
@ -48,13 +37,13 @@ export function generateAPIv1IconsListResponse(
if (v2Cache.title) {
base.title = v2Cache.title;
}
if (q.info && v2Cache.info) {
if (query.info && v2Cache.info) {
base.info = v2Cache.info;
}
if (q.aliases && v2Cache.aliases) {
if (query.aliases && v2Cache.aliases) {
base.aliases = v2Cache.aliases;
}
if (q.chars && v2Cache.chars) {
if (query.chars && v2Cache.chars) {
base.chars = v2Cache.chars;
}
@ -81,22 +70,20 @@ export function generateAPIv1IconsListResponse(
return result;
}
if (q.prefix) {
const prefix = q.prefix;
if (query.prefix) {
const prefix = query.prefix;
const iconSet = iconSets[prefix]?.item;
if (!iconSet || !iconSet.apiV2IconsCache) {
res.send(404);
return;
return 404;
}
sendJSONResponse(parse(prefix, iconSet, iconSet.apiV2IconsCache), q, wrap, res);
return;
return parse(prefix, iconSet, iconSet.apiV2IconsCache);
}
if (q.prefixes) {
if (query.prefixes) {
const prefixes = filterPrefixesByPrefix(
getPrefixes(),
{
prefixes: q.prefixes,
prefixes: query.prefixes,
},
false
);
@ -125,20 +112,18 @@ export function generateAPIv1IconsListResponse(
if (!items.length) {
// Empty list
res.send(404);
return;
return 404;
}
// Get all items
const result = Object.create(null) as Record<string, ReturnType<typeof parse>>;
const result = Object.create(null) as Record<string, PossibleResults>;
for (let i = 0; i < items.length; i++) {
const item = items[i];
result[item.prefix] = parse(item.prefix, item.iconSet, item.v2Cache);
}
sendJSONResponse(result, q, wrap, res);
return;
return result;
}
// Invalid
res.send(400);
return 400;
}

View File

@ -1,7 +1,5 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { iconSets } from '../../data/icon-sets';
import type { APIv2CollectionResponse } from '../../types/server/v2';
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
import { iconSets } from '../../data/icon-sets.js';
import type { APIv2CollectionResponse } from '../../types/server/v2.js';
/**
* Send API v2 response
@ -12,29 +10,18 @@ import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
*
* Those parameters are always requested anyway, so does not make sense to re-create data in case they are disabled
*/
export function generateAPIv2CollectionResponse(query: FastifyRequest['query'], res: FastifyReply) {
const q = (query || {}) as Record<string, string>;
const wrap = checkJSONPQuery(q);
if (!wrap) {
// Invalid JSONP callback
res.send(400);
return;
}
export function createAPIv2CollectionResponse(q: Record<string, string>): APIv2CollectionResponse | number {
// Get icon set
const prefix = q.prefix;
if (!prefix || !iconSets[prefix]) {
res.send(404);
return;
return 404;
}
const iconSet = iconSets[prefix].item;
const apiV2IconsCache = iconSet.apiV2IconsCache;
if (!apiV2IconsCache) {
// Disabled
res.send(404);
return;
return 404;
}
// Generate response
@ -52,5 +39,5 @@ export function generateAPIv2CollectionResponse(query: FastifyRequest['query'],
delete response.chars;
}
sendJSONResponse(response, q, wrap, res);
return response;
}

View File

@ -1,8 +1,6 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { getPrefixes, iconSets } from '../../data/icon-sets';
import type { APIv2CollectionsResponse } from '../../types/server/v2';
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
import { filterPrefixesByPrefix } from '../helpers/prefixes';
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
import type { APIv2CollectionsResponse } from '../../types/server/v2.js';
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
/**
* Send response
@ -12,15 +10,7 @@ import { filterPrefixesByPrefix } from '../helpers/prefixes';
* Ignored parameters:
* - hidden (always enabled)
*/
export function generateCollectionsListResponse(query: FastifyRequest['query'], res: FastifyReply) {
const q = (query || {}) as Record<string, string>;
const wrap = checkJSONPQuery(q);
if (!wrap) {
// Invalid JSONP callback
res.send(400);
return;
}
export function createCollectionsListResponse(q: Record<string, string>): APIv2CollectionsResponse {
// Filter prefixes
const prefixes = filterPrefixesByPrefix(getPrefixes('info'), q, false);
const response = Object.create(null) as APIv2CollectionsResponse;
@ -33,5 +23,5 @@ export function generateCollectionsListResponse(query: FastifyRequest['query'],
}
}
sendJSONResponse(response, q, wrap, res);
return response;
}

View File

@ -1,10 +1,12 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { stringToColor } from '@iconify/utils/lib/colors';
import { getIconsCSS } from '@iconify/utils/lib/css/icons';
import { getStoredIconsData } from '../../data/icon-set/utils/get-icons';
import { iconSets } from '../../data/icon-sets';
import type { IconCSSIconSetOptions } from '@iconify/utils/lib/css/types';
import { paramToBoolean } from '../../misc/bool';
import { getStoredIconsData } from '../../data/icon-set/utils/get-icons.js';
import { iconSets } from '../../data/icon-sets.js';
import { paramToBoolean } from '../../misc/bool.js';
import { errorText } from '../helpers/errors.js';
import { cleanupQueryValue } from '../helpers/query.js';
/**
* Check selector for weird stuff
@ -26,7 +28,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
if (!names || !names.length) {
// Missing or invalid icons parameter
res.send(404);
res.code(404).send(errorText(404));
return;
}
@ -34,7 +36,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
const iconSet = iconSets[prefix];
if (!iconSet) {
// No such icon set
res.send(404);
res.code(404).send(errorText(404));
return;
}
@ -56,7 +58,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
// 'color': string
// Sets color for monotone images
const color = qOptions.color;
const color = cleanupQueryValue(qOptions.color);
if (typeof color === 'string' && stringToColor(color)) {
options.color = color;
}
@ -97,7 +99,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
// 'commonSelector': string
// Common selector for all requested icons
// Alias: 'common'
const commonSelector = qOptions.commonSelector || q.common;
const commonSelector = cleanupQueryValue(qOptions.commonSelector || q.common);
if (checkSelector(commonSelector)) {
options.commonSelector = commonSelector;
}
@ -105,7 +107,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
// 'iconSelector': string
// Icon selector
// Alias: 'selector'
const iconSelector = qOptions.iconSelector || q.selector;
const iconSelector = cleanupQueryValue(qOptions.iconSelector || q.selector);
if (checkSelector(iconSelector)) {
options.iconSelector = iconSelector;
}
@ -113,7 +115,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
// 'overrideSelector': string
// Selector for rules in icon that override common rules
// Alias: 'override'
const overrideSelector = qOptions.overrideSelector || q.override;
const overrideSelector = cleanupQueryValue(qOptions.overrideSelector || q.override);
if (checkSelector(overrideSelector)) {
options.overrideSelector = overrideSelector;
}

View File

@ -1,45 +1,63 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { getStoredIconsData } from '../../data/icon-set/utils/get-icons';
import { iconSets } from '../../data/icon-sets';
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
import { IconifyJSON } from '@iconify/types';
import { getStoredIconsData } from '../../data/icon-set/utils/get-icons.js';
import { iconSets } from '../../data/icon-sets.js';
/**
* Generate icons data
*/
export function generateIconsDataResponse(
export function createIconsDataResponse(
prefix: string,
wrapJS: boolean,
query: FastifyRequest['query'],
res: FastifyReply
) {
const q = (query || {}) as Record<string, string>;
const names = q.icons?.split(',');
q: Record<string, string | string[]>
): number | IconifyJSON | Promise<IconifyJSON | number> {
const iconNames = q.icons;
const names = typeof iconNames === 'string' ? iconNames.split(',') : iconNames;
if (!names || !names.length) {
// Missing or invalid icons parameter
res.send(404);
return;
}
// Check for JSONP
const wrap = checkJSONPQuery(q, wrapJS, 'SimpleSVG._loaderCallback');
if (!wrap) {
// Invalid JSONP callback
res.send(400);
return;
return 404;
}
// Get icon set
const iconSet = iconSets[prefix];
if (!iconSet) {
// No such icon set
res.send(404);
return;
return 404;
}
// Get icons
// Get icons, possibly sync
let syncData: IconifyJSON | undefined;
let resolveData: undefined | ((data: IconifyJSON) => void);
getStoredIconsData(iconSet.item, names, (data) => {
// Send data
sendJSONResponse(data, q, wrap, res);
if (resolveData) {
resolveData(data);
} else {
syncData = data;
}
});
if (syncData) {
return syncData;
}
return new Promise((resolve) => {
resolveData = resolve;
});
}
/**
* Awaitable version of createIconsDataResponse()
*/
export function createIconsDataResponseAsync(
prefix: string,
q: Record<string, string | string[]>
): Promise<IconifyJSON | number> {
return new Promise((resolve, reject) => {
const result = createIconsDataResponse(prefix, q);
if (result instanceof Promise) {
result.then(resolve).catch(reject);
} else {
resolve(result);
}
});
}

View File

@ -1,27 +1,16 @@
import { matchIconName } from '@iconify/utils';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { searchIndex } from '../../data/search';
import { getPartialKeywords } from '../../data/search/partial';
import type { APIv3KeywordsQuery, APIv3KeywordsResponse } from '../../types/server/keywords';
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
import { matchIconName } from '@iconify/utils/lib/icon/name';
import { searchIndex } from '../../data/search.js';
import { getPartialKeywords } from '../../data/search/partial.js';
import type { APIv3KeywordsQuery, APIv3KeywordsResponse } from '../../types/server/keywords.js';
/**
* Generate icons data
* Find full keywords for partial keyword
*/
export function generateKeywordsResponse(query: FastifyRequest['query'], res: FastifyReply) {
const q = (query || {}) as Record<string, string>;
const wrap = checkJSONPQuery(q);
if (!wrap) {
// Invalid JSONP callback
res.send(400);
return;
}
export function createKeywordsResponse(q: Record<string, string>): number | APIv3KeywordsResponse {
// Check if search data is available
const searchIndexData = searchIndex.data;
if (!searchIndexData) {
res.send(404);
return;
return 404;
}
const keywords = searchIndexData.keywords;
@ -32,15 +21,16 @@ export function generateKeywordsResponse(query: FastifyRequest['query'], res: Fa
let failed = false;
if (typeof q.prefix === 'string') {
// Keywords should start with prefix
test = q.prefix;
suffixes = false;
} else if (typeof q.keyword === 'string') {
// All keywords that contain keyword
test = q.keyword;
suffixes = true;
} else {
// Invalid query
res.send(400);
return;
return 400;
}
test = test.toLowerCase().trim();
@ -71,5 +61,5 @@ export function generateKeywordsResponse(query: FastifyRequest['query'], res: Fa
matches: failed || invalid ? [] : getPartialKeywords(test, suffixes, searchIndexData)?.slice(0) || [],
};
sendJSONResponse(response, q, wrap, res);
return response;
}

View File

@ -1,21 +1,11 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { getPrefixes, iconSets } from '../../data/icon-sets';
import type { APIv3LastModifiedResponse } from '../../types/server/modified';
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
import { filterPrefixesByPrefix } from '../helpers/prefixes';
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
import type { APIv3LastModifiedResponse } from '../../types/server/modified.js';
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
/**
* Generate icons data
* Get last modified time for all icon sets
*/
export function generateLastModifiedResponse(query: FastifyRequest['query'], res: FastifyReply) {
const q = (query || {}) as Record<string, string>;
const wrap = checkJSONPQuery(q);
if (!wrap) {
// Invalid JSONP callback
res.send(400);
return;
}
export function createLastModifiedResponse(q: Record<string, string>): number | APIv3LastModifiedResponse {
// Filter prefixes
const prefixes = filterPrefixesByPrefix(getPrefixes(), q, false);
@ -36,5 +26,5 @@ export function generateLastModifiedResponse(query: FastifyRequest['query'], res
}
}
sendJSONResponse(response, q, wrap, res);
return response;
}

View File

@ -1,11 +1,9 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { iconSets } from '../../data/icon-sets';
import { searchIndex } from '../../data/search';
import { search } from '../../data/search/index';
import { paramToBoolean } from '../../misc/bool';
import type { SearchParams } from '../../types/search';
import type { APIv2SearchParams, APIv2SearchResponse } from '../../types/server/v2';
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
import { iconSets } from '../../data/icon-sets.js';
import { searchIndex } from '../../data/search.js';
import { search } from '../../data/search/index.js';
import { paramToBoolean } from '../../misc/bool.js';
import type { SearchParams } from '../../types/search.js';
import type { APIv2SearchParams, APIv2SearchResponse } from '../../types/server/v2.js';
const minSearchLimit = 32;
const maxSearchLimit = 999;
@ -14,28 +12,17 @@ const defaultSearchLimit = minSearchLimit * 2;
/**
* Send API v2 response
*/
export function generateAPIv2SearchResponse(query: FastifyRequest['query'], res: FastifyReply) {
const q = (query || {}) as Record<string, string>;
const wrap = checkJSONPQuery(q);
if (!wrap) {
// Invalid JSONP callback
res.send(400);
return;
}
export function createAPIv2SearchResponse(q: Record<string, string>): number | APIv2SearchResponse {
// Check if search data is available
const searchIndexData = searchIndex.data;
if (!searchIndexData) {
res.send(404);
return;
return 404;
}
// Get query
const keyword = q.query;
if (!keyword) {
res.send(400);
return;
return 400;
}
// Convert to params
@ -49,18 +36,24 @@ export function generateAPIv2SearchResponse(query: FastifyRequest['query'], res:
if (v2Query.limit) {
const limit = parseInt(v2Query.limit);
if (!limit) {
res.send(400);
return;
return 400;
}
params.limit = Math.max(minSearchLimit, Math.min(limit, maxSearchLimit));
}
if (v2Query.min) {
const limit = parseInt(v2Query.min);
if (!limit) {
return 400;
}
params.limit = Math.max(minSearchLimit, Math.min(limit, maxSearchLimit));
params.softLimit = true;
}
let start = 0;
if (v2Query.start) {
start = parseInt(v2Query.start);
if (isNaN(start) || start < 0 || start >= params.limit) {
res.send(400);
return;
return 400;
}
}
@ -121,5 +114,5 @@ export function generateAPIv2SearchResponse(query: FastifyRequest['query'], res:
};
}
sendJSONResponse(response, q, wrap, res);
return response;
}

View File

@ -1,8 +1,14 @@
import { defaultIconDimensions, flipFromString, iconToHTML, iconToSVG, rotateFromString } from '@iconify/utils';
import { iconToHTML } from '@iconify/utils/lib/svg/html';
import { iconToSVG } from '@iconify/utils/lib/svg/build';
import { flipFromString } from '@iconify/utils/lib/customisations/flip';
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
import { defaultIconDimensions } from '@iconify/utils/lib/icon/defaults';
import { defaultIconCustomisations, IconifyIconCustomisations } from '@iconify/utils/lib/customisations/defaults';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { getStoredIconData } from '../../data/icon-set/utils/get-icon';
import { iconSets } from '../../data/icon-sets';
import { getStoredIconData } from '../../data/icon-set/utils/get-icon.js';
import { iconSets } from '../../data/icon-sets.js';
import { errorText } from '../helpers/errors.js';
import { cleanupQueryValue } from '../helpers/query.js';
/**
* Generate SVG
@ -12,7 +18,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
const iconSetItem = iconSets[prefix]?.item;
if (!iconSetItem) {
// No such icon set
res.send(404);
res.code(404).send(errorText(404));
return;
}
@ -20,7 +26,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
const icons = iconSetItem.icons;
if (!(icons.visible[name] || icons.hidden[name]) && !iconSetItem.icons.chars?.[name]) {
// No such icon
res.send(404);
res.code(404).send(errorText(404));
return;
}
@ -28,7 +34,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
getStoredIconData(iconSetItem, name, (data) => {
if (!data) {
// Invalid icon
res.send(404);
res.code(404).send(errorText(404));
return;
}
@ -38,8 +44,8 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
const customisations: IconifyIconCustomisations = {};
// Dimensions
customisations.width = q.width || defaultIconCustomisations.width;
customisations.height = q.height || defaultIconCustomisations.height;
customisations.width = cleanupQueryValue(q.width) || defaultIconCustomisations.width;
customisations.height = cleanupQueryValue(q.height) || defaultIconCustomisations.height;
// Rotation
customisations.rotate = q.rotate ? rotateFromString(q.rotate, 0) : 0;
@ -70,7 +76,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
let html = iconToHTML(body, svg.attributes);
// Change color
const color = q.color;
const color = cleanupQueryValue(q.color);
if (color && html.indexOf('currentColor') !== -1 && color.indexOf('"') === -1) {
html = html.split('currentColor').join(color);
}

View File

@ -1,7 +1,7 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { appConfig } from '../../config/app';
import { triggerIconSetsUpdate } from '../../data/icon-sets';
import { runWhenLoaded } from '../../data/loading';
import { appConfig } from '../../config/app.js';
import { triggerIconSetsUpdate } from '../../data/icon-sets.js';
import { runWhenLoaded } from '../../data/loading.js';
let pendingUpdate = false;
let lastError = 0;

View File

@ -1,6 +1,6 @@
import { readFile } from 'node:fs/promises';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { appConfig } from '../../config/app';
import { appConfig } from '../../config/app.js';
let version: string | undefined;

View File

@ -1,11 +1,11 @@
import type { BaseDownloader } from '../../downloaders/base';
import { maybeAwait } from '../../misc/async';
import type { BaseDownloader } from '../../downloaders/base.js';
import { maybeAwait } from '../../misc/async.js';
import type {
BaseCollectionsImporter,
CreateIconSetImporter,
CreateIconSetImporterResult,
} from '../../types/importers/collections';
import type { ImportedData } from '../../types/importers/common';
} from '../../types/importers/collections.js';
import type { ImportedData } from '../../types/importers/common.js';
/**
* Base collections list importer

View File

@ -1,10 +1,10 @@
import { readFile } from 'node:fs/promises';
import { matchIconName } from '@iconify/utils/lib/icon/name';
import type { BaseDownloader } from '../../downloaders/base';
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections';
import type { ImportedData } from '../../types/importers/common';
import { createBaseCollectionsListImporter } from './base';
import type { IconifyInfo } from '@iconify/types';
import { matchIconName } from '@iconify/utils/lib/icon/name';
import type { BaseDownloader } from '../../downloaders/base.js';
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections.js';
import type { ImportedData } from '../../types/importers/common.js';
import { createBaseCollectionsListImporter } from './base.js';
interface JSONCollectionsListImporterOptions {
// File to load

View File

@ -1,7 +1,7 @@
import { CustomDownloader } from '../../downloaders/custom';
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections';
import type { ImportedData } from '../../types/importers/common';
import { createBaseCollectionsListImporter } from './base';
import { CustomDownloader } from '../../downloaders/custom.js';
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections.js';
import type { ImportedData } from '../../types/importers/common.js';
import { createBaseCollectionsListImporter } from './base.js';
/**
* Create importer for hardcoded list of icon sets

View File

@ -1,8 +1,8 @@
import { readFile } from 'node:fs/promises';
import { quicklyValidateIconSet } from '@iconify/utils/lib/icon-set/validate-basic';
import { asyncStoreLoadedIconSet } from '../../data/icon-set/store/storage';
import type { StoredIconSet } from '../../types/icon-set/storage';
import { prependSlash } from '../../misc/files';
import { asyncStoreLoadedIconSet } from '../../data/icon-set/store/storage.js';
import type { StoredIconSet } from '../../types/icon-set/storage.js';
import { prependSlash } from '../../misc/files.js';
export interface IconSetJSONOptions {
// Ignore bad prefix?

View File

@ -1,8 +1,8 @@
import { readFile } from 'node:fs/promises';
import { quicklyValidateIconSet } from '@iconify/utils/lib/icon-set/validate-basic';
import { asyncStoreLoadedIconSet } from '../../data/icon-set/store/storage';
import type { StoredIconSet } from '../../types/icon-set/storage';
import { appConfig } from '../../config/app';
import { asyncStoreLoadedIconSet } from '../../data/icon-set/store/storage.js';
import type { StoredIconSet } from '../../types/icon-set/storage.js';
import { appConfig } from '../../config/app.js';
export interface IconSetJSONPackageOptions {
// Ignore bad prefix?

View File

@ -1,12 +1,12 @@
import { readdir, stat } from 'node:fs/promises';
import { matchIconName } from '@iconify/utils/lib/icon/name';
import type { BaseDownloader } from '../../downloaders/base';
import { DirectoryDownloader } from '../../downloaders/directory';
import type { StoredIconSet } from '../../types/icon-set/storage';
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections';
import type { ImportedData } from '../../types/importers/common';
import { createJSONIconSetImporter } from '../icon-set/json';
import { createBaseCollectionsListImporter } from '../collections/base';
import type { BaseDownloader } from '../../downloaders/base.js';
import { DirectoryDownloader } from '../../downloaders/directory.js';
import type { StoredIconSet } from '../../types/icon-set/storage.js';
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections.js';
import type { ImportedData } from '../../types/importers/common.js';
import { createJSONIconSetImporter } from '../icon-set/json.js';
import { createBaseCollectionsListImporter } from '../collections/base.js';
interface JSONDirectoryImporterOptions {
// Icon set filter

View File

@ -1,12 +1,12 @@
import { readFile } from 'node:fs/promises';
import { matchIconName } from '@iconify/utils/lib/icon/name';
import type { BaseDownloader } from '../../downloaders/base';
import { DirectoryDownloader } from '../../downloaders/directory';
import type { StoredIconSet } from '../../types/icon-set/storage';
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections';
import type { ImportedData } from '../../types/importers/common';
import { createJSONIconSetImporter } from '../icon-set/json';
import { createBaseCollectionsListImporter } from '../collections/base';
import type { BaseDownloader } from '../../downloaders/base.js';
import { DirectoryDownloader } from '../../downloaders/directory.js';
import type { StoredIconSet } from '../../types/icon-set/storage.js';
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections.js';
import type { ImportedData } from '../../types/importers/common.js';
import { createJSONIconSetImporter } from '../icon-set/json.js';
import { createBaseCollectionsListImporter } from '../collections/base.js';
interface IconSetsPackageImporterOptions {
// Icon set filter

View File

@ -1,7 +1,7 @@
import type { BaseDownloader } from '../../downloaders/base';
import type { StoredIconSet } from '../../types/icon-set/storage';
import type { ImportedData } from '../../types/importers/common';
import type { BaseFullImporter } from '../../types/importers/full';
import type { BaseDownloader } from '../../downloaders/base.js';
import type { StoredIconSet } from '../../types/icon-set/storage.js';
import type { ImportedData } from '../../types/importers/common.js';
import type { BaseFullImporter } from '../../types/importers/full.js';
/**
* Base full importer

View File

@ -1,10 +1,10 @@
import { readdir, stat } from 'node:fs/promises';
import { matchIconName } from '@iconify/utils/lib/icon/name';
import type { BaseDownloader } from '../../downloaders/base';
import type { ImportedData } from '../../types/importers/common';
import type { BaseFullImporter } from '../../types/importers/full';
import { createBaseImporter } from './base';
import { IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json';
import type { BaseDownloader } from '../../downloaders/base.js';
import type { ImportedData } from '../../types/importers/common.js';
import type { BaseFullImporter } from '../../types/importers/full.js';
import { createBaseImporter } from './base.js';
import { type IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json.js';
interface JSONDirectoryImporterOptions extends IconSetJSONOptions {
// Icon set filter

View File

@ -1,11 +1,11 @@
import { readFile } from 'node:fs/promises';
import { matchIconName } from '@iconify/utils/lib/icon/name';
import type { BaseDownloader } from '../../downloaders/base';
import type { ImportedData } from '../../types/importers/common';
import type { BaseFullImporter } from '../../types/importers/full';
import { createBaseImporter } from './base';
import { IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json';
import type { IconifyInfo } from '@iconify/types';
import { matchIconName } from '@iconify/utils/lib/icon/name';
import type { BaseDownloader } from '../../downloaders/base.js';
import type { ImportedData } from '../../types/importers/common.js';
import type { BaseFullImporter } from '../../types/importers/full.js';
import { createBaseImporter } from './base.js';
import { IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json.js';
interface IconSetsPackageImporterOptions extends IconSetJSONOptions {
// Icon set filter
@ -23,6 +23,17 @@ export function createIconSetsPackageImporter<Downloader extends BaseDownloader<
// Load collections list
obj._loadCollectionsListFromDirectory = async (path: string) => {
// Log version
try {
const packageJSON = JSON.parse(await readFile(path + '/package.json', 'utf8'));
if (packageJSON.name && packageJSON.version) {
console.log(`Loading ${packageJSON.name} ${packageJSON.version}`);
}
} catch {
//
}
// Get prefixes
let prefixes: string[];
let data: Record<string, IconifyInfo>;
try {

View File

@ -1,7 +1,7 @@
import type { BaseDownloader } from '../../downloaders/base';
import type { BaseIconSetImporter } from '../../types/importers/icon-set';
import type { IconSetImportedData } from '../../types/importers/common';
import { IconSetJSONPackageOptions, importIconSetFromJSONPackage } from '../common/json-package';
import type { BaseDownloader } from '../../downloaders/base.js';
import type { BaseIconSetImporter } from '../../types/importers/icon-set.js';
import type { IconSetImportedData } from '../../types/importers/common.js';
import { IconSetJSONPackageOptions, importIconSetFromJSONPackage } from '../common/json-package.js';
interface JSONPackageIconSetImporterOptions extends IconSetJSONPackageOptions {
// Icon set prefix

View File

@ -1,7 +1,7 @@
import type { BaseDownloader } from '../../downloaders/base';
import type { BaseIconSetImporter } from '../../types/importers/icon-set';
import type { IconSetImportedData } from '../../types/importers/common';
import { IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json';
import type { BaseDownloader } from '../../downloaders/base.js';
import type { BaseIconSetImporter } from '../../types/importers/icon-set.js';
import type { IconSetImportedData } from '../../types/importers/common.js';
import { IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json.js';
interface JSONIconSetImporterOptions extends IconSetJSONOptions {
// Icon set prefix

View File

@ -1,30 +1,19 @@
import { config } from 'dotenv';
import { getImporters } from './config/icon-sets';
import { iconSetsStorage } from './data/icon-set/store/storage';
import { setImporters, updateIconSets } from './data/icon-sets';
import { loaded } from './data/loading';
import { cleanupStorageCache } from './data/storage/startup';
import { startHTTPServer } from './http';
import { loadEnvConfig } from './misc/load-config';
import { loaded } from './data/loading.js';
import { startHTTPServer } from './http/index.js';
import { loadEnvConfig } from './misc/load-config.js';
import { initAPI } from './init.js';
(async () => {
// Configure environment
config();
loadEnvConfig();
// Reset old cache
await cleanupStorageCache(iconSetsStorage);
// Start HTTP server
startHTTPServer();
// Get all importers and load data
const importers = await getImporters();
for (let i = 0; i < importers.length; i++) {
await importers[i].init();
}
setImporters(importers);
updateIconSets();
// Init API
await initAPI();
// Loaded
loaded();

36
src/init.ts Normal file
View File

@ -0,0 +1,36 @@
import { getImporters } from './config/icon-sets.js';
import { iconSetsStorage } from './data/icon-set/store/storage.js';
import { setImporters, updateIconSets } from './data/icon-sets.js';
import { cleanupStorageCache } from './data/storage/startup.js';
import { Importer } from './types/importers.js';
interface InitOptions {
// Cleanup storage cache
cleanup?: boolean;
// Importers
importers?: Importer[];
}
/**
* Init API
*/
export async function initAPI(options: InitOptions = {}) {
// Reset old cache
if (options.cleanup !== false) {
await cleanupStorageCache(iconSetsStorage);
}
// Get all importers and load data
let importers = options.importers;
if (!importers) {
importers = await getImporters();
}
for (let i = 0; i < importers.length; i++) {
await importers[i].init();
}
// Update
setImporters(importers);
updateIconSets();
}

View File

@ -1,7 +1,7 @@
import { stat } from 'node:fs/promises';
import { scanDirectory } from '@iconify/tools/lib/misc/scan';
import type { FileEntry } from '../types/files';
import { hashString } from './hash';
import type { FileEntry } from '../types/files.js';
import { hashString } from './hash.js';
/**
* List all files in directory

View File

@ -1,5 +1,5 @@
import { appConfig, splitIconSetConfig, storageConfig } from '../config/app';
import { paramToBoolean } from './bool';
import { appConfig, splitIconSetConfig, storageConfig } from '../config/app.js';
import { paramToBoolean } from './bool.js';
interface ConfigurableItem {
config: unknown;

View File

@ -1,4 +1,4 @@
import type { StoredIconSet } from '../icon-set/storage';
import type { StoredIconSet } from '../icon-set/storage.js';
/**
* Generated data

View File

@ -1,8 +1,8 @@
import type { IconifyIcons, IconifyInfo, IconifyJSON, IconifyMetaData } from '@iconify/types';
import type { SplitDataTree } from '../split';
import type { MemoryStorage, MemoryStorageItem } from '../storage';
import type { IconSetIconsListIcons, IconSetAPIv2IconsList } from './extra';
import type { SplitIconifyJSONMainData } from './split';
import type { IconifyIcons, IconifyInfo, IconifyMetaData } from '@iconify/types';
import type { SplitDataTree } from '../split.js';
import type { MemoryStorage, MemoryStorageItem } from '../storage.js';
import type { IconSetIconsListIcons, IconSetAPIv2IconsList } from './extra.js';
import type { SplitIconifyJSONMainData } from './split.js';
/**
* Themes

View File

@ -1,6 +1,6 @@
import type { BaseDownloader } from '../downloaders/base';
import type { StoredIconSet } from './icon-set/storage';
import type { ImportedData } from './importers/common';
import type { BaseDownloader } from '../downloaders/base.js';
import type { StoredIconSet } from './icon-set/storage.js';
import type { ImportedData } from './importers/common.js';
/**
* Importer

View File

@ -1,7 +1,7 @@
import type { BaseDownloader } from '../../downloaders/base';
import type { MaybeAsync } from '../async';
import type { BaseMainImporter, IconSetImportedData } from './common';
import type { BaseIconSetImporter } from './icon-set';
import type { BaseDownloader } from '../../downloaders/base.js';
import type { MaybeAsync } from '../async.js';
import type { BaseMainImporter, IconSetImportedData } from './common.js';
import type { BaseIconSetImporter } from './icon-set.js';
/**
* Loader for child element

View File

@ -1,5 +1,5 @@
import type { DownloaderType } from '../downloaders/base';
import type { StoredIconSet } from '../icon-set/storage';
import type { DownloaderType } from '../downloaders/base.js';
import type { StoredIconSet } from '../icon-set/storage.js';
/**
* Base icon set importer interface

View File

@ -1,4 +1,4 @@
import type { BaseMainImporter, IconSetImportedData } from './common';
import type { BaseMainImporter, IconSetImportedData } from './common.js';
/**
* Base full importer

View File

@ -1,4 +1,4 @@
import type { BaseImporter, IconSetImportedData } from './common';
import type { BaseImporter, IconSetImportedData } from './common.js';
/**
* Base icon set importer interface

View File

@ -1,4 +1,4 @@
import type { IconStyle } from './icon-set/extra';
import type { IconStyle } from './icon-set/extra.js';
/**
* List of keywords that can be used to autocomplete keyword
@ -50,6 +50,7 @@ export interface SearchParams {
// Search results limit
limit: number;
softLimit?: boolean; // True if limit can be exceeded
// Toggle partial matches
partial?: boolean;

View File

@ -103,7 +103,9 @@ export interface APIv2SearchParams extends APIv2CommonParams {
query: SearchQuery;
// Maximum number of items in response
limit?: number;
// If `min` is set, `limit` is ignored
limit?: number; // Hard limit. Number of results will not exceed `limit`.
min?: number; // Soft limit. Number of results can exceed `limit` if function already retrieved more icons.
// Start index for results
start?: number;

View File

@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "CommonJS",
"module": "ESNext",
"strict": true,
"skipLibCheck": true,
"moduleResolution": "node",