1
0
mirror of https://github.com/svg/svgo.git synced 2025-08-01 18:46:52 +03:00

fix: improve jsdoc types and remove excludes (#2107)

This commit is contained in:
Seth Falco
2025-04-28 22:24:16 +01:00
committed by GitHub
parent 10c4287051
commit 71a1254895
17 changed files with 252 additions and 172 deletions

View File

@ -14,15 +14,15 @@
import SAX from 'sax';
import { textElems } from '../plugins/_collections.js';
class SvgoParserError extends Error {
export class SvgoParserError extends Error {
/**
* @param message {string}
* @param line {number}
* @param column {number}
* @param source {string}
* @param file {void | string}
* @param {string} message
* @param {number} line
* @param {number} column
* @param {string} source
* @param {string|undefined} file
*/
constructor(message, line, column, source, file) {
constructor(message, line, column, source, file = undefined) {
super(message);
this.name = 'SvgoParserError';
this.message = `${file || '<input>'}:${line}:${column}: ${message}`;
@ -34,6 +34,7 @@ class SvgoParserError extends Error {
Error.captureStackTrace(this, SvgoParserError);
}
}
toString() {
const lines = this.source.split(/\r?\n/);
const startLine = Math.max(this.line - 3, 0);

View File

@ -67,12 +67,10 @@ const entities = {
/**
* convert XAST to SVG string
*
* @type {(data: XastRoot, config: StringifyOptions) => string}
* @type {(data: XastRoot, userOptions?: StringifyOptions) => string}
*/
export const stringifySvg = (data, userOptions = {}) => {
/**
* @type {Options}
*/
/** @type {Options} */
const config = { ...defaults, ...userOptions };
const indent = config.indent;
let newIndent = ' ';
@ -81,9 +79,7 @@ export const stringifySvg = (data, userOptions = {}) => {
} else if (typeof indent === 'string') {
newIndent = indent;
}
/**
* @type {State}
*/
/** @type {State} */
const state = {
indent: newIndent,
textContext: null,

4
lib/svgo-node.d.ts vendored
View File

@ -1,6 +1,6 @@
import { Config } from './svgo';
import type { Config } from './svgo.js';
export * from './svgo';
export * from './svgo.js';
/**
* If you write a tool on top of svgo you might need a way to load svgo config.

View File

@ -1,6 +1,5 @@
import os from 'os';
import fs from 'fs';
import { pathToFileURL } from 'url';
import path from 'path';
import {
VERSION,
@ -11,10 +10,17 @@ import {
_collections,
} from './svgo.js';
/**
* @typedef {import('./svgo.js').Config} Config
* @typedef {import('./svgo.js').Output} Output
*/
/**
* @param {string} configFile
* @returns {Promise<Config>}
*/
const importConfig = async (configFile) => {
// dynamic import expects file url instead of path and may fail
// when windows path is provided
const imported = await import(pathToFileURL(configFile));
const imported = await import(path.resolve(configFile));
const config = imported.default;
if (config == null || typeof config !== 'object' || Array.isArray(config)) {
@ -23,6 +29,10 @@ const importConfig = async (configFile) => {
return config;
};
/**
* @param {string} file
* @returns {Promise<boolean>}
*/
const isFile = async (file) => {
try {
const stats = await fs.promises.stat(file);
@ -40,6 +50,11 @@ export {
_collections,
};
/**
* @param {string} configFile
* @param {string} cwd
* @returns {Promise<?Config>}
*/
export const loadConfig = async (configFile, cwd = process.cwd()) => {
if (configFile != null) {
if (path.isAbsolute(configFile)) {
@ -71,6 +86,11 @@ export const loadConfig = async (configFile, cwd = process.cwd()) => {
}
};
/**
* @param {string} input
* @param {Config} config
* @returns {Output}
*/
export const optimize = (input, config) => {
if (config == null) {
config = {};

View File

@ -3,7 +3,7 @@ import path from 'path';
import { optimize, loadConfig } from './svgo-node.js';
/**
* @typedef {import('../lib/types.js').Plugin} Plugin
* @typedef {import('../lib/types.js').Plugin<?>} Plugin
*/
const describeLF = os.EOL === '\r\n' ? describe.skip : describe;

2
lib/svgo.d.ts vendored
View File

@ -33,7 +33,7 @@ export type PluginConfig =
}[keyof BuiltinsWithRequiredParams]
| CustomPlugin;
type BuiltinPlugin<Name, Params> = {
export type BuiltinPlugin<Name, Params> = {
/** Name of the plugin, also known as the plugin ID. */
name: Name;
description?: string;

View File

@ -9,6 +9,9 @@ import _collections from '../plugins/_collections.js';
/**
* @typedef {import('./svgo.js').BuiltinPluginOrPreset<?, ?>} BuiltinPluginOrPreset
* @typedef {import('./svgo.js').Config} Config
* @typedef {import('./svgo.js').Output} Output
* @typedef {import('./svgo.js').PluginConfig} PluginConfig
*/
const pluginsMap = new Map();
@ -31,6 +34,10 @@ function getPlugin(name) {
return pluginsMap.get(name);
}
/**
* @param {string|PluginConfig} plugin
* @returns {?PluginConfig}
*/
const resolvePluginConfig = (plugin) => {
if (typeof plugin === 'string') {
// resolve builtin plugin specified as string
@ -49,6 +56,7 @@ const resolvePluginConfig = (plugin) => {
throw Error(`Plugin name must be specified`);
}
// use custom plugin implementation
// @ts-expect-error Checking for CustomPlugin with the presence of fn
let fn = plugin.fn;
if (fn == null) {
// resolve builtin plugin implementation
@ -75,6 +83,11 @@ export {
_collections,
};
/**
* @param {string} input
* @param {Config} config
* @returns {Output}
*/
export const optimize = (input, config) => {
if (config == null) {
config = {};
@ -107,6 +120,8 @@ export const optimize = (input, config) => {
'Warning: plugins list includes null or undefined elements, these will be ignored.',
);
}
/** @type {Config} */
const globalOverrides = {};
if (config.floatPrecision != null) {
globalOverrides.floatPrecision = config.floatPrecision;

View File

@ -1,5 +1,10 @@
import { jest } from '@jest/globals';
import { optimize } from './svgo.js';
import { SvgoParserError } from './parser.js';
/**
* @typedef {import('./svgo.js').CustomPlugin} CustomPlugin
*/
test('allow to setup default preset', () => {
const svg = `
@ -65,6 +70,7 @@ test('warn when user tries enable plugins in preset', () => {
const warn = jest.spyOn(console, 'warn');
optimize(svg, {
plugins: [
// @ts-expect-error Testing if we receive config that diverges from type definitions.
{
name: 'preset-default',
params: {
@ -138,6 +144,7 @@ describe('allow to configure EOL', () => {
</svg>
`;
const { data } = optimize(svg, {
// @ts-expect-error Testing if we receive config that diverges from type definitions.
js2svg: { eol: 'invalid', pretty: true, indent: 2 },
});
expect(data).toBe(
@ -256,32 +263,35 @@ test('plugin precision should override preset precision', () => {
});
test('provides informative error in result', () => {
expect.assertions(6);
const svg = `<svg viewBox="0 0 120 120">
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
</svg>
`;
try {
optimize(svg, { path: 'test.svg' });
} catch (error) {
const error = new SvgoParserError(
'Unquoted attribute value',
2,
33,
svg,
'test.svg',
);
expect(() => optimize(svg, { path: 'test.svg' })).toThrow(error);
expect(error.name).toBe('SvgoParserError');
expect(error.message).toBe('test.svg:2:33: Unquoted attribute value');
expect(error.reason).toBe('Unquoted attribute value');
expect(error.line).toBe(2);
expect(error.column).toBe(33);
expect(error.source).toBe(svg);
}
});
test('provides code snippet in rendered error', () => {
expect.assertions(1);
const svg = `<svg viewBox="0 0 120 120">
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
</svg>
`;
try {
optimize(svg, { path: 'test.svg' });
} catch (error) {
const error = new SvgoParserError(
'Unquoted attribute value',
2,
29,
svg,
'test.svg',
);
expect(() => optimize(svg, { path: 'test.svg' })).toThrow(error);
expect(error.toString())
.toBe(`SvgoParserError: test.svg:2:29: Unquoted attribute value
@ -291,11 +301,9 @@ test('provides code snippet in rendered error', () => {
3 | </svg>
4 |
`);
}
});
test('supports errors without path', () => {
expect.assertions(1);
const svg = `<svg viewBox="0 0 120 120">
<circle/>
<circle/>
@ -309,9 +317,8 @@ test('supports errors without path', () => {
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
</svg>
`;
try {
optimize(svg);
} catch (error) {
const error = new SvgoParserError('Unquoted attribute value', 11, 29, svg);
expect(() => optimize(svg)).toThrow(error);
expect(error.toString())
.toBe(`SvgoParserError: <input>:11:29: Unquoted attribute value
@ -322,18 +329,16 @@ test('supports errors without path', () => {
12 | </svg>
13 |
`);
}
});
test('slices long line in error code snippet', () => {
expect.assertions(1);
const svg = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" viewBox="0 0 230 120">
<path d="M318.198 551.135 530.33 918.56l-289.778-77.646 38.823-144.889c77.646-289.778 294.98-231.543 256.156-86.655s178.51 203.124 217.334 58.235q58.234-217.334 250.955 222.534t579.555 155.292z stroke-width="1.5" fill="red" stroke="red" />
</svg>
`;
try {
optimize(svg);
} catch (error) {
const error = new SvgoParserError('Invalid attribute name', 2, 211, svg);
expect(() => optimize(svg)).toThrow(error);
expect(error.toString())
.toBe(`SvgoParserError: <input>:2:211: Invalid attribute name
@ -343,12 +348,13 @@ test('slices long line in error code snippet', () => {
3 |
4 |
`);
}
});
test('multipass option should trigger plugins multiple times', () => {
const svg = `<svg id="abcdefghijklmnopqrstuvwxyz"></svg>`;
/** @type {number[]} */
const list = [];
/** @type {CustomPlugin} */
const testPlugin = {
name: 'testPlugin',
fn: (_root, _params, info) => {

View File

@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
import { encodeSVGDatauri, decodeSVGDatauri } from './tools.js';
import { loadConfig, optimize } from '../svgo-node.js';
import { builtin } from '../builtin.js';
import { SvgoParserError } from '../parser.js';
/**
* @typedef {import('commander').Command} Command
@ -14,8 +15,6 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
const pkgPath = path.join(__dirname, '../../package.json');
const PKG = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
const regSVGFile = /\.svg$/i;
/**
* Synchronously check if path is a directory. Tolerant to errors like ENOENT.
*
@ -89,22 +88,17 @@ export default function makeProgram(program) {
.action(action);
}
/**
* @param {string[]} args
* @param {any} opts
* @param {Command} command
* @returns
*/
async function action(args, opts, command) {
var input = opts.input || args;
var output = opts.output;
var config = {};
if (opts.precision != null) {
const number = Number.parseInt(opts.precision, 10);
if (Number.isNaN(number)) {
console.error(
"error: option '-p, --precision' argument must be an integer number",
);
process.exit(1);
} else {
opts.precision = number;
}
}
const input = opts.input || args;
let output = opts.output;
/** @type {any} */
let config = {};
if (opts.datauri != null) {
if (
@ -155,13 +149,8 @@ async function action(args, opts, command) {
return command.help();
}
if (
typeof process == 'object' &&
process.versions &&
process.versions.node &&
PKG &&
PKG.engines.node
) {
if (process?.versions?.node && PKG.engines.node) {
// @ts-expect-error We control this and ensure it is never null.
var nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0];
if (parseFloat(process.versions.node) < parseFloat(nodeVersion)) {
throw Error(
@ -188,13 +177,20 @@ async function action(args, opts, command) {
// --exclude
config.exclude = opts.exclude
? opts.exclude.map((pattern) => RegExp(pattern))
? opts.exclude.map((/** @type {string} */ pattern) => RegExp(pattern))
: [];
// --precision
if (opts.precision != null) {
var precision = Math.min(Math.max(0, opts.precision), 20);
config.floatPrecision = precision;
const number = Number.parseInt(opts.precision, 10);
if (Number.isNaN(number)) {
console.error(
"error: option '-p, --precision' argument must be an integer number",
);
process.exit(1);
} else {
config.floatPrecision = Math.min(Math.max(0, number), 20);
}
}
// --multipass
@ -227,7 +223,7 @@ async function action(args, opts, command) {
if (output) {
if (input.length && input[0] != '-') {
if (output.length == 1 && checkIsDir(output[0])) {
var dir = output[0];
const dir = output[0];
for (var i = 0; i < input.length; i++) {
output[i] = checkIsDir(input[i])
? input[i]
@ -270,7 +266,9 @@ async function action(args, opts, command) {
// file
} else {
await Promise.all(
input.map((file, n) => optimizeFile(config, file, output[n])),
input.map((/** @type {string} */ file, /** @type {number} */ n) =>
optimizeFile(config, file, output[n]),
),
);
}
@ -285,10 +283,10 @@ async function action(args, opts, command) {
/**
* Optimize SVG files in a directory.
*
* @param {Object} config options
* @param {any} config options
* @param {string} dir input directory
* @param {string} output output directory
* @return {Promise}
* @return {Promise<any>}
*/
function optimizeFolder(config, dir, output) {
if (!config.quiet) {
@ -302,11 +300,11 @@ function optimizeFolder(config, dir, output) {
/**
* Process given files, take only SVG.
*
* @param {Object} config options
* @param {any} config options
* @param {string} dir input directory
* @param {Array} files list of file names in the directory
* @param {string[]} files list of file names in the directory
* @param {string} output output directory
* @return {Promise}
* @return {Promise<any>}
*/
function processDirectory(config, dir, files, output) {
// take only *.svg files, recursively if necessary
@ -330,27 +328,31 @@ function processDirectory(config, dir, files, output) {
/**
* Get SVG files descriptions.
*
* @param {Object} config options
* @param {any} config options
* @param {string} dir input directory
* @param {Array} files list of file names in the directory
* @param {string[]} files list of file names in the directory
* @param {string} output output directory
* @return {Array}
* @return {any[]}
*/
function getFilesDescriptions(config, dir, files, output) {
const filesInThisFolder = files
.filter(
(name) =>
regSVGFile.test(name) &&
!config.exclude.some((regExclude) => regExclude.test(name)),
name.slice(-4).toLowerCase() === '.svg' &&
!config.exclude.some((/** @type {RegExp} */ regExclude) =>
regExclude.test(name),
),
)
.map((name) => ({
inputPath: path.resolve(dir, name),
outputPath: path.resolve(output, name),
}));
return config.recursive
? [].concat(
filesInThisFolder,
if (!config.recursive) {
return filesInThisFolder;
}
return filesInThisFolder.concat(
files
.filter((name) => checkIsDir(path.resolve(dir, name)))
.map((subFolderName) => {
@ -364,9 +366,8 @@ function getFilesDescriptions(config, dir, files, output) {
subFolderOutput,
);
})
.reduce((a, b) => [].concat(a, b), []),
)
: filesInThisFolder;
.reduce((a, b) => a.concat(b), []),
);
}
/**
@ -375,7 +376,7 @@ function getFilesDescriptions(config, dir, files, output) {
* @param {Object} config options
* @param {string} file
* @param {string} output
* @return {Promise}
* @return {Promise<any>}
*/
function optimizeFile(config, file, output) {
return fs.promises.readFile(file, 'utf8').then(
@ -387,13 +388,14 @@ function optimizeFile(config, file, output) {
/**
* Optimize SVG data.
*
* @param {Object} config options
* @param {any} config options
* @param {?{ path: string }} info
* @param {string} data SVG content to optimize
* @param {string} output where to write optimized file
* @param {string} [input] input file name (being used if output is a directory)
* @return {Promise}
* @param {any} input input file name (being used if output is a directory)
* @return {Promise<any>}
*/
function processSVGData(config, info, data, output, input) {
function processSVGData(config, info, data, output, input = undefined) {
var startTime = Date.now(),
prevFileSize = Buffer.byteLength(data, 'utf8');
@ -401,7 +403,7 @@ function processSVGData(config, info, data, output, input) {
try {
result = optimize(data, { ...config, ...info });
} catch (error) {
if (error.name === 'SvgoParserError') {
if (error instanceof SvgoParserError) {
console.error(colors.red(error.toString()));
process.exit(1);
} else {
@ -441,7 +443,7 @@ function processSVGData(config, info, data, output, input) {
* @param {string} input
* @param {string} output output file name. '-' for stdout
* @param {string} data data to write
* @return {Promise}
* @return {Promise<void>}
*/
function writeOutput(input, output, data) {
if (output == '-') {
@ -488,11 +490,11 @@ function printProfitInfo(inBytes, outBytes) {
/**
* Check for errors, if it's a dir optimize the dir.
*
* @param {Object} config
* @param {any} config
* @param {string} input
* @param {string} output
* @param {Error} error
* @return {Promise}
* @param {Error & { code: string, path: string }} error
* @return {Promise<void>}
*/
function checkOptimizeFileError(config, input, output, error) {
if (error.code == 'EISDIR') {
@ -511,8 +513,8 @@ function checkOptimizeFileError(config, input, output, error) {
* @param {string} input
* @param {string} output
* @param {string} data
* @param {Error} error
* @return {Promise}
* @param {Error & { code: string }} error
* @return {Promise<void>}
*/
function checkWriteFileError(input, output, data, error) {
if (error.code == 'EISDIR' && input) {

View File

@ -1,49 +1,95 @@
/**
* @typedef {import('../types.js').XastChild} XastChild
* @typedef {import('../types.js').XastElement} XastElement
* @typedef {import('../types.js').XastNode} XastNode
* @typedef {import('../types.js').XastParent} XastParent
*/
/**
* @param {XastNode} node
* @returns {boolean}
*/
const isTag = (node) => {
return node.type === 'element';
};
/**
* @param {<T>(v: T) => boolean} test
* @param {XastNode[]} elems
* @returns {boolean}
*/
const existsOne = (test, elems) => {
return elems.some((elem) => {
if (isTag(elem)) {
return test(elem) || existsOne(test, getChildren(elem));
} else {
return false;
}
return isTag(elem) && (test(elem) || existsOne(test, getChildren(elem)));
});
};
/**
* @param {XastElement} elem
* @param {string} name
* @returns {?string}
*/
const getAttributeValue = (elem, name) => {
return elem.attributes[name];
};
/**
* @param {XastNode & { children?: XastChild[] }} node
* @returns {XastChild[]}
*/
const getChildren = (node) => {
return node.children || [];
};
/**
* @param {XastElement} elemAst
* @returns {string}
*/
const getName = (elemAst) => {
return elemAst.name;
};
/**
* @param {XastNode & { parentNode?: XastParent }} node
* @returns {?XastParent}
*/
const getParent = (node) => {
return node.parentNode || null;
};
/**
* @param {XastElement} elem
* @returns {XastChild[]}
*/
const getSiblings = (elem) => {
var parent = getParent(elem);
const parent = getParent(elem);
return parent ? getChildren(parent) : [];
};
/**
* @param {XastParent} node
* @returns {string}
*/
const getText = (node) => {
if (node.children[0].type === 'text' && node.children[0].type === 'cdata') {
if (node.children[0].type === 'text' || node.children[0].type === 'cdata') {
return node.children[0].value;
}
return '';
};
/**
* @param {XastElement} elem
* @param {string} name
* @returns {boolean}
*/
const hasAttrib = (elem, name) => {
return elem.attributes[name] !== undefined;
};
/**
* @param {Array<?XastNode>} nodes
* @returns {Array<?XastNode>}
*/
const removeSubsets = (nodes) => {
let idx = nodes.length;
let node;
@ -72,6 +118,11 @@ const removeSubsets = (nodes) => {
return nodes;
};
/**
* @param {<T>(v: T) => boolean} test
* @param {XastNode[]} elems
* @returns {XastNode[]}
*/
const findAll = (test, elems) => {
const result = [];
for (const elem of elems) {
@ -85,6 +136,11 @@ const findAll = (test, elems) => {
return result;
};
/**
* @param {<T>(v: T) => boolean} test
* @param {XastNode[]} elems
* @returns {?XastNode}
*/
const findOne = (test, elems) => {
for (const elem of elems) {
if (isTag(elem)) {
@ -100,7 +156,7 @@ const findOne = (test, elems) => {
return null;
};
const svgoCssSelectAdapter = {
export default {
isTag,
existsOne,
getAttributeValue,
@ -114,5 +170,3 @@ const svgoCssSelectAdapter = {
findAll,
findOne,
};
export default svgoCssSelectAdapter;

View File

@ -1,8 +1,9 @@
import { visit } from '../xast.js';
/**
* @typedef {import('../svgo').BuiltinPlugin<string, Object>} BuiltinPlugin
* @typedef {import('../svgo').BuiltinPluginOrPreset<?, ?>} BuiltinPreset
* @typedef {import('../svgo.js').BuiltinPlugin<string, ?>} BuiltinPlugin
* @typedef {import('../svgo.js').BuiltinPluginOrPreset<?, ?>} BuiltinPreset
* @typedef {import('../types.js').XastNode} XastNode
*/
/**
@ -10,10 +11,11 @@ import { visit } from '../xast.js';
*
* @module plugins
*
* @param {Object} ast input ast
* @param {Object} info extra information
* @param {Array} plugins plugins object from config
* @return {Object} output ast
* @param {XastNode} ast Input AST.
* @param {Object} info Extra information.
* @param {Array<any>} plugins Plugins property from config.
* @param {any} overrides
* @param {any} globalOverrides
*/
export const invokePlugins = (
ast,

View File

@ -116,6 +116,7 @@
},
"devDependencies": {
"@eslint/js": "^9.3.0",
"@jest/globals": "^29.7.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
@ -135,7 +136,7 @@
"rimraf": "^5.0.7",
"rollup": "^4.17.2",
"tar-stream": "^3.1.7",
"typescript": "^5.4.5"
"typescript": "^5.8.3"
},
"resolutions": {
"sax@^1.4.1": "patch:sax@npm%3A1.4.1#./.yarn/patches/sax-npm-1.4.1-503b1923cb.patch"

View File

@ -54,9 +54,7 @@ export const fn = (_root, params) => {
if (match) {
// round it to the fixed precision
let num = Number(Number(match[1]).toFixed(floatPrecision));
/**
* @type {any}
*/
/** @type {any} */
let matchedUnit = match[3] || '';
/**
* @type{'' | keyof typeof absoluteLengths}

View File

@ -60,13 +60,9 @@ export const fn = (_root, params) => {
if (match) {
// round it to the fixed precision
let num = Number(Number(match[1]).toFixed(floatPrecision));
/**
* @type {any}
*/
/** @type {any} */
let matchedUnit = match[3] || '';
/**
* @type{'' | keyof typeof absoluteLengths}
*/
/** @type {'' | keyof typeof absoluteLengths} */
let units = matchedUnit;
// convert absolute values to pixels

View File

@ -78,7 +78,6 @@ export const fn = (root, params) => {
const cssText = node.children
.filter((child) => child.type === 'text' || child.type === 'cdata')
// @ts-expect-error
.map((child) => child.value)
.join('');
@ -368,7 +367,6 @@ export const fn = (root, params) => {
},
});
// csstree v2 changed this type
if (style.cssAst.children.isEmpty) {
// remove empty style element
detachNodeFromParent(style.node, style.parentNode);

View File

@ -9,15 +9,5 @@
"strict": true,
"resolveJsonModule": true
},
"include": ["lib/**/*.js", "plugins/**/*", "test/cli/**/*"],
"exclude": [
"lib/svgo-node.js",
"lib/svgo-node.test.js",
"lib/svgo.js",
"lib/builtin.js",
"lib/svgo.test.js",
"lib/svgo/**/*.js",
"plugins/plugins.js",
"plugins/preset-default.js"
]
"include": ["lib/**/*.js", "plugins/**/*", "test/cli/**/*"]
}

View File

@ -4491,6 +4491,7 @@ __metadata:
resolution: "svgo@workspace:."
dependencies:
"@eslint/js": ^9.3.0
"@jest/globals": ^29.7.0
"@rollup/plugin-commonjs": ^25.0.7
"@rollup/plugin-node-resolve": ^15.2.3
"@rollup/plugin-terser": ^0.4.4
@ -4517,7 +4518,7 @@ __metadata:
rollup: ^4.17.2
sax: ^1.4.1
tar-stream: ^3.1.7
typescript: ^5.4.5
typescript: ^5.8.3
bin:
svgo: ./bin/svgo.js
languageName: unknown
@ -4626,23 +4627,23 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:^5.4.5":
version: 5.4.5
resolution: "typescript@npm:5.4.5"
"typescript@npm:^5.8.3":
version: 5.8.3
resolution: "typescript@npm:5.8.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 53c879c6fa1e3bcb194b274d4501ba1985894b2c2692fa079db03c5a5a7140587a1e04e1ba03184605d35f439b40192d9e138eb3279ca8eee313c081c8bcd9b0
checksum: cb1d081c889a288b962d3c8ae18d337ad6ee88a8e81ae0103fa1fecbe923737f3ba1dbdb3e6d8b776c72bc73bfa6d8d850c0306eed1a51377d2fccdfd75d92c4
languageName: node
linkType: hard
"typescript@patch:typescript@^5.4.5#~builtin<compat/typescript>":
version: 5.4.5
resolution: "typescript@patch:typescript@npm%3A5.4.5#~builtin<compat/typescript>::version=5.4.5&hash=5adc0c"
"typescript@patch:typescript@^5.8.3#~builtin<compat/typescript>":
version: 5.8.3
resolution: "typescript@patch:typescript@npm%3A5.8.3#~builtin<compat/typescript>::version=5.8.3&hash=5786d5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: d59e26e74f6b444517d0ba16e0ee16e75c652c2b49a59f2ebdbeb16647a855fd50c7fc786a58987e45f03bce0677092e2dd3333649fd53b11d0b0d271455837c
checksum: f1743b6850976b3debf7cbd53d2bc0b67e75d47eb6410db564c8bb475e92a8a48f8a3fcd14d89cf93426835281c31a8f8a94dad90be4dc899279a898532ba97f
languageName: node
linkType: hard