Compare commits

..

No commits in common. "main" and "3.1.0-beta.8" have entirely different histories.

15 changed files with 3500 additions and 2528 deletions

1
.github/FUNDING.yml vendored Normal file
View File

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

View File

@ -15,8 +15,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'latest'

View File

@ -1,11 +1,11 @@
ARG ARCH=amd64
ARG NODE_VERSION=22
ARG NODE_VERSION=18
ARG OS=bullseye-slim
ARG ICONIFY_API_VERSION=3.2.0
ARG ICONIFY_API_VERSION=3.0.0
ARG SRC_PATH=./
#### Stage BASE ########################################################################################################
FROM --platform=${ARCH} node:${NODE_VERSION}-${OS} AS base
FROM ${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

0
license.txt Normal file → Executable file
View File

5878
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,11 @@
"description": "Iconify API",
"author": "Vjacheslav Trushkin",
"license": "MIT",
"version": "3.2.0",
"version": "3.1.0-beta.8",
"publishConfig": {
"access": "public",
"tag": "next"
},
"type": "module",
"bugs": "https://github.com/iconify/api/issues",
"homepage": "https://github.com/iconify/api",
@ -11,9 +15,9 @@
"type": "git",
"url": "https://github.com/iconify/api.git"
},
"packageManager": "npm@11.6.4",
"packageManager": "npm@10.4.0",
"engines": {
"node": ">=22.20.0"
"node": ">=16.15.0"
},
"scripts": {
"build": "tsc -b",
@ -26,17 +30,17 @@
"docker:publish": "docker push iconify/api"
},
"dependencies": {
"@fastify/formbody": "^8.0.2",
"@iconify/tools": "^5.0.0",
"@fastify/formbody": "^7.4.0",
"@iconify/tools": "^4.0.2",
"@iconify/types": "^2.0.0",
"@iconify/utils": "^3.1.0",
"dotenv": "^17.2.3",
"fastify": "^5.6.2"
"@iconify/utils": "^2.1.22",
"dotenv": "^16.4.4",
"fastify": "^4.26.1"
},
"devDependencies": {
"@types/jest": "^30.0.0",
"@types/node": "^24.10.1",
"typescript": "^5.9.3",
"vitest": "^4.0.14"
"@types/jest": "^29.5.12",
"@types/node": "^20.11.17",
"typescript": "^5.3.3",
"vitest": "^1.2.2"
}
}

View File

@ -1,27 +0,0 @@
{
"$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

@ -21,14 +21,7 @@ export function splitIconSetMainData(iconSet: IconifyJSON): SplitIconifyJSONMain
for (let i = 0; i < iconSetMainDataProps.length; i++) {
const prop = iconSetMainDataProps[i];
if (iconSet[prop]) {
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'];
}
result[prop as 'prefix'] = iconSet[prop as 'prefix'];
} else if (prop === 'aliases') {
result[prop] = Object.create(null);
}

View File

@ -125,7 +125,7 @@ export function updateIconSets(): number {
/**
* Trigger update
*/
export function triggerIconSetsUpdate(index?: number | null, done?: (success?: boolean) => void) {
export function triggerIconSetsUpdate(done?: (success?: boolean) => void) {
if (!importers) {
done?.();
return;
@ -141,9 +141,6 @@ export function triggerIconSetsUpdate(index?: number | null, done?: (success?: b
// 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;

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { checkJSONPQuery, sendJSONResponse } from './json.js';
import { errorText } from './errors.js';
type CallbackResult = object | number;
@ -18,14 +17,14 @@ export function handleJSONResponse(
const wrap = checkJSONPQuery(q);
if (!wrap) {
// Invalid JSONP callback
res.code(400).send(errorText(400));
res.send(400);
return;
}
// Function to send response
const respond = (result: CallbackResult) => {
if (typeof result === 'number') {
res.code(result).send(errorText(result));
res.send(result);
} else {
sendJSONResponse(result, q, wrap, res);
}

View File

@ -15,7 +15,6 @@ 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
@ -23,9 +22,7 @@ import { errorText } from './helpers/errors.js';
export async function startHTTPServer() {
// Create HTP server
const server = fastify({
routerOptions: {
caseSensitive: true,
},
caseSensitive: true,
});
// Support `application/x-www-form-urlencoded`
@ -85,7 +82,7 @@ export async function startHTTPServer() {
generateSVGResponse(name.prefix, name.name, req.query, res);
});
} else {
res.code(404).send(errorText(404));
res.send(404);
}
});
@ -181,7 +178,7 @@ export async function startHTTPServer() {
// Options
server.options('/*', (req, res) => {
res.code(204).header('Content-Length', '0').send();
res.send(200);
});
// Robots
@ -201,21 +198,21 @@ export async function startHTTPServer() {
// Redirect
server.get('/', (req, res) => {
res.redirect(appConfig.redirectIndex, 301);
res.redirect(301, appConfig.redirectIndex);
});
// Error handling
server.setNotFoundHandler((req, res) => {
server.setDefaultRoute((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.header(header.key, header.value);
res.setHeader(header.key, header.value);
}
res.send();
res.end();
});
// Start it

View File

@ -5,8 +5,6 @@ import type { IconCSSIconSetOptions } from '@iconify/utils/lib/css/types';
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
@ -28,7 +26,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
if (!names || !names.length) {
// Missing or invalid icons parameter
res.code(404).send(errorText(404));
res.send(404);
return;
}
@ -36,7 +34,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
const iconSet = iconSets[prefix];
if (!iconSet) {
// No such icon set
res.code(404).send(errorText(404));
res.send(404);
return;
}
@ -58,7 +56,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
// 'color': string
// Sets color for monotone images
const color = cleanupQueryValue(qOptions.color);
const color = qOptions.color;
if (typeof color === 'string' && stringToColor(color)) {
options.color = color;
}
@ -99,7 +97,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
// 'commonSelector': string
// Common selector for all requested icons
// Alias: 'common'
const commonSelector = cleanupQueryValue(qOptions.commonSelector || q.common);
const commonSelector = qOptions.commonSelector || q.common;
if (checkSelector(commonSelector)) {
options.commonSelector = commonSelector;
}
@ -107,7 +105,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
// 'iconSelector': string
// Icon selector
// Alias: 'selector'
const iconSelector = cleanupQueryValue(qOptions.iconSelector || q.selector);
const iconSelector = qOptions.iconSelector || q.selector;
if (checkSelector(iconSelector)) {
options.iconSelector = iconSelector;
}
@ -115,7 +113,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
// 'overrideSelector': string
// Selector for rules in icon that override common rules
// Alias: 'override'
const overrideSelector = cleanupQueryValue(qOptions.overrideSelector || q.override);
const overrideSelector = qOptions.overrideSelector || q.override;
if (checkSelector(overrideSelector)) {
options.overrideSelector = overrideSelector;
}

View File

@ -7,8 +7,6 @@ import { defaultIconCustomisations, IconifyIconCustomisations } from '@iconify/u
import type { FastifyReply, FastifyRequest } from 'fastify';
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
@ -18,7 +16,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
const iconSetItem = iconSets[prefix]?.item;
if (!iconSetItem) {
// No such icon set
res.code(404).send(errorText(404));
res.send(404);
return;
}
@ -26,7 +24,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.code(404).send(errorText(404));
res.send(404);
return;
}
@ -34,7 +32,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
getStoredIconData(iconSetItem, name, (data) => {
if (!data) {
// Invalid icon
res.code(404).send(errorText(404));
res.send(404);
return;
}
@ -44,8 +42,8 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
const customisations: IconifyIconCustomisations = {};
// Dimensions
customisations.width = cleanupQueryValue(q.width) || defaultIconCustomisations.width;
customisations.height = cleanupQueryValue(q.height) || defaultIconCustomisations.height;
customisations.width = q.width || defaultIconCustomisations.width;
customisations.height = q.height || defaultIconCustomisations.height;
// Rotation
customisations.rotate = q.rotate ? rotateFromString(q.rotate, 0) : 0;
@ -76,7 +74,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
let html = iconToHTML(body, svg.attributes);
// Change color
const color = cleanupQueryValue(q.color);
const color = q.color;
if (color && html.indexOf('currentColor') !== -1 && color.indexOf('"') === -1) {
html = html.split('currentColor').join(color);
}