diff --git a/node/test/bundle.test.mjs b/node/test/bundle.test.mjs deleted file mode 100644 index 50d113b5..00000000 --- a/node/test/bundle.test.mjs +++ /dev/null @@ -1,417 +0,0 @@ -import path from 'path'; -import fs from 'fs'; -import { test } from 'uvu'; -import * as assert from 'uvu/assert'; -import {webcrypto as crypto} from 'node:crypto'; - -let bundleAsync; -if (process.env.TEST_WASM === 'node') { - bundleAsync = (await import('../../wasm/wasm-node.mjs')).bundleAsync; -} else if (process.env.TEST_WASM === 'browser') { - // Define crypto globally for old node. - // @ts-ignore - globalThis.crypto ??= crypto; - let wasm = await import('../../wasm/index.mjs'); - await wasm.default(); - bundleAsync = function (options) { - if (!options.resolver?.read) { - options.resolver = { - ...options.resolver, - read: (filePath) => fs.readFileSync(filePath, 'utf8') - }; - } - - return wasm.bundleAsync(options); - } -} else { - bundleAsync = (await import('../index.mjs')).bundleAsync; -} - -test('resolver', async () => { - const inMemoryFs = new Map(Object.entries({ - 'foo.css': ` - @import 'root:bar.css'; - - .foo { color: red; } - `.trim(), - - 'bar.css': ` - @import 'root:hello/world.css'; - - .bar { color: green; } - `.trim(), - - 'hello/world.css': ` - .baz { color: blue; } - `.trim(), - })); - - const { code: buffer } = await bundleAsync({ - filename: 'foo.css', - resolver: { - read(file) { - const result = inMemoryFs.get(path.normalize(file)); - if (!result) throw new Error(`Could not find ${file} in ${Array.from(inMemoryFs.keys()).join(', ')}.`); - return result; - }, - - resolve(specifier) { - return specifier.slice('root:'.length); - }, - }, - }); - const code = buffer.toString('utf-8').trim(); - - const expected = ` -.baz { - color: #00f; -} - -.bar { - color: green; -} - -.foo { - color: red; -} - `.trim(); - if (code !== expected) throw new Error(`\`testResolver()\` failed. Expected:\n${expected}\n\nGot:\n${code}`); -}); - -test('only custom read', async () => { - const inMemoryFs = new Map(Object.entries({ - 'foo.css': ` - @import 'hello/world.css'; - - .foo { color: red; } - `.trim(), - - 'hello/world.css': ` - @import '../bar.css'; - - .bar { color: green; } - `.trim(), - - 'bar.css': ` - .baz { color: blue; } - `.trim(), - })); - - const { code: buffer } = await bundleAsync({ - filename: 'foo.css', - resolver: { - read(file) { - const result = inMemoryFs.get(path.normalize(file)); - if (!result) throw new Error(`Could not find ${file} in ${Array.from(inMemoryFs.keys()).join(', ')}.`); - return result; - }, - }, - }); - const code = buffer.toString('utf-8').trim(); - - const expected = ` -.baz { - color: #00f; -} - -.bar { - color: green; -} - -.foo { - color: red; -} - `.trim(); - if (code !== expected) throw new Error(`\`testOnlyCustomRead()\` failed. Expected:\n${expected}\n\nGot:\n${code}`); -}); - -test('only custom resolve', async () => { - const root = path.join('tests', 'testdata'); - const { code: buffer } = await bundleAsync({ - filename: path.join(root, 'foo.css'), - resolver: { - resolve(specifier) { - // Strip `root:` prefix off specifier and resolve it as an absolute path - // in the test data root. - return path.join(root, specifier.slice('root:'.length)); - }, - }, - }); - const code = buffer.toString('utf-8').trim(); - - const expected = ` -.baz { - color: #00f; -} - -.bar { - color: green; -} - -.foo { - color: red; -} - `.trim(); - if (code !== expected) throw new Error(`\`testOnlyCustomResolve()\` failed. Expected:\n${expected}\n\nGot:\n${code}`); -}); - -test('async read', async () => { - const root = path.join('tests', 'testdata'); - const { code: buffer } = await bundleAsync({ - filename: path.join(root, 'foo.css'), - resolver: { - async read(file) { - return await fs.promises.readFile(file, 'utf8'); - }, - resolve(specifier) { - // Strip `root:` prefix off specifier and resolve it as an absolute path - // in the test data root. - return path.join(root, specifier.slice('root:'.length)); - }, - }, - }); - const code = buffer.toString('utf-8').trim(); - - const expected = ` -.baz { - color: #00f; -} - -.bar { - color: green; -} - -.foo { - color: red; -} - `.trim(); - if (code !== expected) throw new Error(`\`testAsyncRead()\` failed. Expected:\n${expected}\n\nGot:\n${code}`); -}); - -test('read throw', async () => { - let error = undefined; - try { - await bundleAsync({ - filename: 'foo.css', - resolver: { - read(file) { - throw new Error(`Oh noes! Failed to read \`${file}\`.`); - } - }, - }); - } catch (err) { - error = err; - } - - if (!error) throw new Error(`\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); - assert.equal(error.message, `Oh noes! Failed to read \`foo.css\`.`); - assert.equal(error.loc, undefined); // error occurred when reading initial file, no location info available. -}); - -test('async read throw', async () => { - let error = undefined; - try { - await bundleAsync({ - filename: 'foo.css', - resolver: { - async read(file) { - throw new Error(`Oh noes! Failed to read \`${file}\`.`); - } - }, - }); - } catch (err) { - error = err; - } - - if (!error) throw new Error(`\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); - assert.equal(error.message, `Oh noes! Failed to read \`foo.css\`.`); - assert.equal(error.loc, undefined); // error occurred when reading initial file, no location info available. -}); - -test('read throw with location info', async () => { - let error = undefined; - try { - await bundleAsync({ - filename: 'foo.css', - resolver: { - read(file) { - if (file === 'foo.css') { - return '@import "bar.css"'; - } - throw new Error(`Oh noes! Failed to read \`${file}\`.`); - } - }, - }); - } catch (err) { - error = err; - } - - if (!error) throw new Error(`\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); - assert.equal(error.message, `Oh noes! Failed to read \`bar.css\`.`); - assert.equal(error.fileName, 'foo.css'); - assert.equal(error.loc, { - line: 1, - column: 1 - }); -}); - -test('async read throw with location info', async () => { - let error = undefined; - try { - await bundleAsync({ - filename: 'foo.css', - resolver: { - async read(file) { - if (file === 'foo.css') { - return '@import "bar.css"'; - } - throw new Error(`Oh noes! Failed to read \`${file}\`.`); - } - }, - }); - } catch (err) { - error = err; - } - - if (!error) throw new Error(`\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); - assert.equal(error.message, `Oh noes! Failed to read \`bar.css\`.`); - assert.equal(error.fileName, 'foo.css'); - assert.equal(error.loc, { - line: 1, - column: 1 - }); -}); - -test('resolve throw', async () => { - let error = undefined; - try { - await bundleAsync({ - filename: 'tests/testdata/foo.css', - resolver: { - resolve(specifier, originatingFile) { - throw new Error(`Oh noes! Failed to resolve \`${specifier}\` from \`${originatingFile}\`.`); - } - }, - }); - } catch (err) { - error = err; - } - - if (!error) throw new Error(`\`testResolveThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); - assert.equal(error.message, `Oh noes! Failed to resolve \`root:hello/world.css\` from \`tests/testdata/foo.css\`.`); - assert.equal(error.fileName, 'tests/testdata/foo.css'); - assert.equal(error.loc, { - line: 1, - column: 1 - }); -}); - -test('async resolve throw', async () => { - let error = undefined; - try { - await bundleAsync({ - filename: 'tests/testdata/foo.css', - resolver: { - async resolve(specifier, originatingFile) { - throw new Error(`Oh noes! Failed to resolve \`${specifier}\` from \`${originatingFile}\`.`); - } - }, - }); - } catch (err) { - error = err; - } - - if (!error) throw new Error(`\`testResolveThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); - assert.equal(error.message, `Oh noes! Failed to resolve \`root:hello/world.css\` from \`tests/testdata/foo.css\`.`); - assert.equal(error.fileName, 'tests/testdata/foo.css'); - assert.equal(error.loc, { - line: 1, - column: 1 - }); -}); - -test('read return non-string', async () => { - let error = undefined; - try { - await bundleAsync({ - filename: 'foo.css', - resolver: { - read() { - return 1234; // Returns a non-string value. - } - }, - }); - } catch (err) { - error = err; - } - - if (!error) throw new Error(`\`testReadReturnNonString()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); - assert.equal(error.message, 'expect String, got: Number'); -}); - -test('resolve return non-string', async () => { - let error = undefined; - try { - await bundleAsync({ - filename: 'tests/testdata/foo.css', - resolver: { - resolve() { - return 1234; // Returns a non-string value. - } - }, - }); - } catch (err) { - error = err; - } - - if (!error) throw new Error(`\`testResolveReturnNonString()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); - assert.equal(error.message, 'expect String, got: Number'); - assert.equal(error.fileName, 'tests/testdata/foo.css'); - assert.equal(error.loc, { - line: 1, - column: 1 - }); -}); - -test('should throw with location info on syntax errors', async () => { - let error = undefined; - try { - await bundleAsync({ - filename: 'tests/testdata/foo.css', - resolver: { - read() { - return '.foo' - } - }, - }); - } catch (err) { - error = err; - } - - assert.equal(error.message, `Unexpected end of input`); - assert.equal(error.fileName, 'tests/testdata/foo.css'); - assert.equal(error.loc, { - line: 1, - column: 5 - }); -}); - -test('should support throwing in visitors', async () => { - let error = undefined; - try { - await bundleAsync({ - filename: 'tests/testdata/a.css', - visitor: { - Rule() { - throw new Error('Some error') - } - } - }); - } catch (err) { - error = err; - } - - assert.equal(error.message, 'Some error'); -}); - -test.run(); diff --git a/node/test/bundle.test.ts b/node/test/bundle.test.ts new file mode 100644 index 00000000..4f5b5fea --- /dev/null +++ b/node/test/bundle.test.ts @@ -0,0 +1,480 @@ +/// + +import path from 'node:path'; +import fs from 'node:fs'; +import {beforeAll, expect, test} from 'vitest'; +import type {bundleAsync as BundleAsync, Warning} from '../index.js'; + +let bundleAsync: typeof BundleAsync; + +// TODO: export from library? +interface BundleAsyncError extends Warning { + fileName: string; +} + +beforeAll(async () => { + if (import.meta.env.TEST_WASM === 'node') { + bundleAsync = (await import('../../wasm/wasm-node.mjs')).bundleAsync; + } else if (import.meta.env.TEST_WASM === 'browser') { + let wasm = await import('../../wasm/index.mjs'); + await wasm.default(); + bundleAsync = function (options) { + if (!options.resolver?.read) { + options.resolver = { + ...options.resolver, + read: (filePath) => fs.readFileSync(filePath, 'utf8'), + }; + } + + return wasm.bundleAsync(options); + }; + } else { + bundleAsync = (await import('../index.mjs')).bundleAsync; + } +}); + +test('resolver', async () => { + const inMemoryFs = new Map( + Object.entries({ + 'foo.css': ` + @import 'root:bar.css'; + + .foo { color: red; } + `.trim(), + + 'bar.css': ` + @import 'root:hello/world.css'; + + .bar { color: green; } + `.trim(), + + 'hello/world.css': ` + .baz { color: blue; } + `.trim(), + }), + ); + + const {code: buffer} = await bundleAsync({ + filename: 'foo.css', + resolver: { + read(file) { + const result = inMemoryFs.get(path.normalize(file)); + if (!result) + throw new Error( + `Could not find ${file} in ${Array.from(inMemoryFs.keys()).join( + ', ', + )}.`, + ); + return result; + }, + + resolve(specifier) { + return specifier.slice('root:'.length); + }, + }, + }); + const code = buffer.toString().trim(); + + const expected = ` +.baz { + color: #00f; +} + +.bar { + color: green; +} + +.foo { + color: red; +} + `.trim(); + if (code !== expected) + throw new Error( + `\`testResolver()\` failed. Expected:\n${expected}\n\nGot:\n${code}`, + ); +}); + +test('only custom read', async () => { + const inMemoryFs = new Map( + Object.entries({ + 'foo.css': ` + @import 'hello/world.css'; + + .foo { color: red; } + `.trim(), + + 'hello/world.css': ` + @import '../bar.css'; + + .bar { color: green; } + `.trim(), + + 'bar.css': ` + .baz { color: blue; } + `.trim(), + }), + ); + + const {code: buffer} = await bundleAsync({ + filename: 'foo.css', + resolver: { + read(file) { + const result = inMemoryFs.get(path.normalize(file)); + if (!result) + throw new Error( + `Could not find ${file} in ${Array.from(inMemoryFs.keys()).join( + ', ', + )}.`, + ); + return result; + }, + }, + }); + const code = buffer.toString().trim(); + + const expected = ` +.baz { + color: #00f; +} + +.bar { + color: green; +} + +.foo { + color: red; +} + `.trim(); + if (code !== expected) + throw new Error( + `\`testOnlyCustomRead()\` failed. Expected:\n${expected}\n\nGot:\n${code}`, + ); +}); + +test('only custom resolve', async () => { + const root = path.join('tests', 'testdata'); + const {code: buffer} = await bundleAsync({ + filename: path.join(root, 'foo.css'), + resolver: { + resolve(specifier) { + // Strip `root:` prefix off specifier and resolve it as an absolute path + // in the test data root. + return path.join(root, specifier.slice('root:'.length)); + }, + }, + }); + const code = buffer.toString().trim(); + + const expected = ` +.baz { + color: #00f; +} + +.bar { + color: green; +} + +.foo { + color: red; +} + `.trim(); + if (code !== expected) + throw new Error( + `\`testOnlyCustomResolve()\` failed. Expected:\n${expected}\n\nGot:\n${code}`, + ); +}); + +test('async read', async () => { + const root = path.join('tests', 'testdata'); + const {code: buffer} = await bundleAsync({ + filename: path.join(root, 'foo.css'), + resolver: { + async read(file) { + return await fs.promises.readFile(file, 'utf8'); + }, + resolve(specifier) { + // Strip `root:` prefix off specifier and resolve it as an absolute path + // in the test data root. + return path.join(root, specifier.slice('root:'.length)); + }, + }, + }); + const code = buffer.toString().trim(); + + const expected = ` +.baz { + color: #00f; +} + +.bar { + color: green; +} + +.foo { + color: red; +} + `.trim(); + if (code !== expected) + throw new Error( + `\`testAsyncRead()\` failed. Expected:\n${expected}\n\nGot:\n${code}`, + ); +}); + +test('read throw', async () => { + let error: BundleAsyncError | undefined = undefined; + try { + await bundleAsync({ + filename: 'foo.css', + resolver: { + read(file) { + throw new Error(`Oh noes! Failed to read \`${file}\`.`); + }, + }, + }); + } catch (err) { + error = err; + } + + if (!error) + throw new Error( + `\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`, + ); + expect(error.message).toBe(`Oh noes! Failed to read \`foo.css\`.`); + expect(error.loc).toBeUndefined(); // error occurred when reading initial file, no location info available. +}); + +test('async read throw', async () => { + let error: BundleAsyncError | undefined = undefined; + try { + await bundleAsync({ + filename: 'foo.css', + resolver: { + async read(file) { + throw new Error(`Oh noes! Failed to read \`${file}\`.`); + }, + }, + }); + } catch (err) { + error = err; + } + + if (!error) + throw new Error( + `\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`, + ); + expect(error.message).toBe(`Oh noes! Failed to read \`foo.css\`.`); + expect(error.loc).toBeUndefined(); // error occurred when reading initial file, no location info available. +}); + +test('read throw with location info', async () => { + let error: BundleAsyncError | undefined = undefined; + try { + await bundleAsync({ + filename: 'foo.css', + resolver: { + read(file) { + if (file === 'foo.css') { + return '@import "bar.css"'; + } + throw new Error(`Oh noes! Failed to read \`${file}\`.`); + }, + }, + }); + } catch (err) { + error = err; + } + + if (!error) + throw new Error( + `\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`, + ); + expect(error.message).toBe(`Oh noes! Failed to read \`bar.css\`.`); + expect(error.fileName).toBe('foo.css'); + expect(error.loc).toEqual({ + line: 1, + column: 1, + }); +}); + +test('async read throw with location info', async () => { + let error: BundleAsyncError | undefined = undefined; + try { + await bundleAsync({ + filename: 'foo.css', + resolver: { + async read(file) { + if (file === 'foo.css') { + return '@import "bar.css"'; + } + throw new Error(`Oh noes! Failed to read \`${file}\`.`); + }, + }, + }); + } catch (err) { + error = err; + } + + if (!error) + throw new Error( + `\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`, + ); + expect(error.message).toBe(`Oh noes! Failed to read \`bar.css\`.`); + expect(error.fileName).toBe('foo.css'); + expect(error.loc).toEqual({ + line: 1, + column: 1, + }); +}); + +test('resolve throw', async () => { + let error: BundleAsyncError | undefined; + try { + await bundleAsync({ + filename: 'tests/testdata/foo.css', + resolver: { + resolve(specifier, originatingFile) { + throw new Error( + `Oh noes! Failed to resolve \`${specifier}\` from \`${originatingFile}\`.`, + ); + }, + }, + }); + } catch (err) { + error = err; + } + + if (!error) + throw new Error( + `\`testResolveThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`, + ); + expect(error.message).toBe( + `Oh noes! Failed to resolve \`root:hello/world.css\` from \`tests/testdata/foo.css\`.`, + ); + expect(error.fileName).toBe('tests/testdata/foo.css'); + expect(error.loc).toEqual({ + line: 1, + column: 1, + }); +}); + +test('async resolve throw', async () => { + let error: BundleAsyncError | undefined; + try { + await bundleAsync({ + filename: 'tests/testdata/foo.css', + resolver: { + async resolve(specifier, originatingFile) { + throw new Error( + `Oh noes! Failed to resolve \`${specifier}\` from \`${originatingFile}\`.`, + ); + }, + }, + }); + } catch (err) { + error = err; + } + + if (!error) + throw new Error( + `\`testResolveThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`, + ); + expect(error.message).toBe( + `Oh noes! Failed to resolve \`root:hello/world.css\` from \`tests/testdata/foo.css\`.`, + ); + expect(error.fileName).toBe('tests/testdata/foo.css'); + expect(error.loc).toEqual({ + line: 1, + column: 1, + }); +}); + +test('read return non-string', async () => { + let error: BundleAsyncError | undefined; + try { + await bundleAsync({ + filename: 'foo.css', + resolver: { + // @ts-expect-error Test is for unexpected value + read() { + return 1234; // Returns a non-string value. + }, + }, + }); + } catch (err) { + error = err; + } + + if (!error) + throw new Error( + `\`testReadReturnNonString()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`, + ); + expect(error.message).toBe('expect String, got: Number'); +}); + +test('resolve return non-string', async () => { + let error: BundleAsyncError | undefined; + try { + await bundleAsync({ + filename: 'tests/testdata/foo.css', + resolver: { + // @ts-expect-error Test is for unexpected value + resolve() { + return 1234; // Returns a non-string value. + }, + }, + }); + } catch (err) { + error = err; + } + + if (!error) + throw new Error( + `\`testResolveReturnNonString()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`, + ); + expect(error.message).toBe('expect String, got: Number'); + expect(error.fileName).toBe('tests/testdata/foo.css'); + expect(error.loc).toEqual({ + line: 1, + column: 1, + }); +}); + +test('should throw with location info on syntax errors', async () => { + let error: BundleAsyncError | undefined; + try { + await bundleAsync({ + filename: 'tests/testdata/foo.css', + resolver: { + read() { + return '.foo'; + }, + }, + }); + } catch (err) { + error = err; + } + expect(error!.message).toBe(`Unexpected end of input`); + expect(error!.fileName).toBe('tests/testdata/foo.css'); + expect(error!.loc).toEqual({ + line: 1, + column: 5, + }); +}); + +test('should support throwing in visitors', async () => { + let error: BundleAsyncError | undefined; + try { + await bundleAsync({ + filename: 'tests/testdata/a.css', + visitor: { + Rule() { + throw new Error('Some error'); + }, + }, + }); + } catch (err) { + error = err; + } + + expect(error!.message).toBe('Some error'); +}); diff --git a/node/test/composeVisitors.test.mjs b/node/test/composeVisitors.test.mjs deleted file mode 100644 index 7718ec06..00000000 --- a/node/test/composeVisitors.test.mjs +++ /dev/null @@ -1,803 +0,0 @@ -// @ts-check - -import { test } from 'uvu'; -import * as assert from 'uvu/assert'; -import {webcrypto as crypto} from 'node:crypto'; - -let transform, composeVisitors; -if (process.env.TEST_WASM === 'node') { - ({transform, composeVisitors} = await import('../../wasm/wasm-node.mjs')); -} else if (process.env.TEST_WASM === 'browser') { - // Define crypto globally for old node. - // @ts-ignore - globalThis.crypto ??= crypto; - let wasm = await import('../../wasm/index.mjs'); - await wasm.default(); - ({transform, composeVisitors} = wasm); -} else { - ({transform, composeVisitors} = await import('../index.mjs')); -} - -test('different types', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - width: 16px; - color: red; - } - `), - visitor: composeVisitors([ - { - Length(l) { - if (l.unit === 'px') { - return { - unit: 'rem', - value: l.value / 16 - } - } - } - }, - { - Color(c) { - if (c.type === 'rgb') { - return { - type: 'rgb', - r: c.g, - g: c.r, - b: c.b, - alpha: c.alpha - }; - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.foo{color:#0f0;width:1rem}'); -}); - -test('simple matching types', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - width: 16px; - } - `), - visitor: composeVisitors([ - { - Length(l) { - return { - unit: l.unit, - value: l.value * 2 - }; - } - }, - { - Length(l) { - if (l.unit === 'px') { - return { - unit: 'rem', - value: l.value / 16 - } - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.foo{width:2rem}'); -}); - -test('different properties', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - size: 16px; - bg: #ff0; - } - `), - visitor: composeVisitors([ - { - Declaration: { - custom: { - size(v) { - return [ - { property: 'unparsed', value: { propertyId: { property: 'width' }, value: v.value } }, - { property: 'unparsed', value: { propertyId: { property: 'height' }, value: v.value } } - ]; - } - } - } - }, - { - Declaration: { - custom: { - bg(v) { - if (v.value[0].type === 'color') { - return { property: 'background-color', value: v.value[0].value }; - } - } - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.foo{width:16px;height:16px;background-color:#ff0}'); -}); - -test('composed properties', () => { - /** @type {import('../index').Visitor[]} */ - let visitors = [ - { - Declaration: { - custom: { - size(v) { - if (v.value[0].type === 'length') { - return [ - { property: 'width', value: { type: 'length-percentage', value: { type: 'dimension', value: v.value[0].value } } }, - { property: 'height', value: { type: 'length-percentage', value: { type: 'dimension', value: v.value[0].value } } }, - ]; - } - } - } - } - }, - { - Declaration: { - width() { - return []; - } - } - } - ]; - - // Check that it works in any order. - for (let i = 0; i < 2; i++) { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - size: 16px; - } - `), - visitor: composeVisitors(visitors) - }); - - assert.equal(res.code.toString(), '.foo{height:16px}'); - visitors.reverse(); - } -}); - -test('same properties', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - color: red; - } - `), - visitor: composeVisitors([ - { - Declaration: { - color(v) { - if (v.property === 'color' && v.value.type === 'rgb') { - return { - property: 'color', - value: { - type: 'rgb', - r: v.value.g, - g: v.value.r, - b: v.value.b, - alpha: v.value.alpha - } - }; - } - } - } - }, - { - Declaration: { - color(v) { - if (v.property === 'color' && v.value.type === 'rgb' && v.value.g > 0) { - v.value.alpha /= 2; - } - return v; - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.foo{color:#00ff0080}'); -}); - -test('properties plus values', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - size: test; - } - `), - visitor: composeVisitors([ - { - Declaration: { - custom: { - size() { - return [ - { property: 'width', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } }, - { property: 'height', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } }, - ]; - } - } - } - }, - { - Length(l) { - if (l.unit === 'px') { - return { - unit: 'rem', - value: l.value / 16 - } - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.foo{width:2rem;height:2rem}'); -}); - -test('unparsed properties', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - width: test; - } - .bar { - width: 16px; - } - `), - visitor: composeVisitors([ - { - Declaration: { - width(v) { - if (v.property === 'unparsed') { - return [ - { property: 'width', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } }, - { property: 'height', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } }, - ]; - } - } - } - }, - { - Length(l) { - if (l.unit === 'px') { - return { - unit: 'rem', - value: l.value / 16 - } - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.foo{width:2rem;height:2rem}.bar{width:1rem}'); -}); - -test('returning unparsed properties', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - width: test; - } - `), - visitor: composeVisitors([ - { - Declaration: { - width(v) { - if (v.property === 'unparsed' && v.value.value[0].type === 'token' && v.value.value[0].value.type === 'ident') { - return { - property: 'unparsed', - value: { - propertyId: { property: 'width' }, - value: [{ - type: 'var', - value: { - name: { - ident: '--' + v.value.value[0].value.value - } - } - }] - } - } - } - } - } - }, - { - Declaration: { - width(v) { - if (v.property === 'unparsed') { - return { - property: 'unparsed', - value: { - propertyId: { property: 'width' }, - value: [{ - type: 'function', - value: { - name: 'calc', - arguments: v.value.value - } - }] - } - } - } - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.foo{width:calc(var(--test))}'); -}); - -test('all property handlers', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - width: test; - height: test; - } - `), - visitor: composeVisitors([ - { - Declaration(decl) { - if (decl.property === 'unparsed' && decl.value.propertyId.property === 'width') { - return { property: 'width', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } }; - } - } - }, - { - Declaration(decl) { - if (decl.property === 'unparsed' && decl.value.propertyId.property === 'height') { - return { property: 'height', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } }; - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.foo{width:32px;height:32px}'); -}); - -test('all property handlers (exit)', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - width: test; - height: test; - } - `), - visitor: composeVisitors([ - { - DeclarationExit(decl) { - if (decl.property === 'unparsed' && decl.value.propertyId.property === 'width') { - return { property: 'width', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } }; - } - } - }, - { - DeclarationExit(decl) { - if (decl.property === 'unparsed' && decl.value.propertyId.property === 'height') { - return { property: 'height', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } }; - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.foo{width:32px;height:32px}'); -}); - -test('tokens and functions', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - width: f3(f2(f1(test))); - } - `), - visitor: composeVisitors([ - { - FunctionExit: { - f1(f) { - if (f.arguments.length === 1 && f.arguments[0].type === 'token' && f.arguments[0].value.type === 'ident') { - return { - type: 'length', - value: { - unit: 'px', - value: 32 - } - } - } - } - } - }, - { - FunctionExit(f) { - return f.arguments[0]; - } - }, - { - Length(l) { - if (l.unit === 'px') { - return { - unit: 'rem', - value: l.value / 16 - } - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.foo{width:2rem}'); -}); - -test('unknown rules', () => { - let declared = new Map(); - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - @test #056ef0; - - .menu_link { - background: @blue; - } - `), - visitor: composeVisitors([ - { - Rule: { - unknown: { - test(rule) { - rule.name = 'blue'; - return { - type: 'unknown', - value: rule - }; - } - } - } - }, - { - Rule: { - unknown(rule) { - declared.set(rule.name, rule.prelude); - return []; - } - }, - Token: { - 'at-keyword'(token) { - if (declared.has(token.value)) { - return declared.get(token.value); - } - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.menu_link{background:#056ef0}'); -}); - -test('custom at rules', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - @testA; - @testB; - `), - customAtRules: { - testA: {}, - testB: {} - }, - visitor: composeVisitors([ - { - Rule: { - custom: { - testA(rule) { - return { - type: 'style', - value: { - loc: rule.loc, - selectors: [ - [{ type: 'class', name: 'testA' }] - ], - declarations: { - declarations: [ - { - property: 'color', - value: { - type: 'rgb', - r: 0xff, - g: 0x00, - b: 0x00, - alpha: 1, - } - } - ] - } - } - }; - } - } - } - }, - { - Rule: { - custom: { - testB(rule) { - return { - type: 'style', - value: { - loc: rule.loc, - selectors: [ - [{ type: 'class', name: 'testB' }] - ], - declarations: { - declarations: [ - { - property: 'color', - value: { - type: 'rgb', - r: 0x00, - g: 0xff, - b: 0x00, - alpha: 1, - } - } - ] - } - } - }; - } - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.testA{color:red}.testB{color:#0f0}'); -}); - -test('known rules', () => { - let declared = new Map(); - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .test:focus-visible { - margin-left: 20px; - margin-right: @margin-left; - } - `), - targets: { - safari: 14 << 16 - }, - visitor: composeVisitors([ - { - Rule: { - style(rule) { - let valuesByProperty = new Map(); - for (let decl of rule.value.declarations.declarations) { - /** @type string */ - let name = decl.property; - if (decl.property === 'unparsed') { - name = decl.value.propertyId.property; - } - valuesByProperty.set(name, decl); - } - - rule.value.declarations.declarations = rule.value.declarations.declarations.map(decl => { - // Only single value supported. Would need a way to convert parsed values to unparsed tokens otherwise. - if (decl.property === 'unparsed' && decl.value.value.length === 1) { - let token = decl.value.value[0]; - if (token.type === 'token' && token.value.type === 'at-keyword' && valuesByProperty.has(token.value.value)) { - let v = valuesByProperty.get(token.value.value); - return { - /** @type any */ - property: decl.value.propertyId.property, - value: v.value - }; - } - } - return decl; - }); - - return rule; - } - } - }, - { - Rule: { - style(rule) { - let clone = null; - for (let selector of rule.value.selectors) { - for (let [i, component] of selector.entries()) { - if (component.type === 'pseudo-class' && component.kind === 'focus-visible') { - if (clone == null) { - clone = [...rule.value.selectors.map(s => [...s])]; - } - - selector[i] = { type: 'class', name: 'focus-visible' }; - } - } - } - - if (clone) { - return [rule, { type: 'style', value: { ...rule.value, selectors: clone } }]; - } - } - } - } - ]) - }); - - assert.equal(res.code.toString(), '.test.focus-visible{margin-left:20px;margin-right:20px}.test:focus-visible{margin-left:20px;margin-right:20px}'); -}); - -test('environment variables', () => { - /** @type {Record} */ - let tokens = { - '--branding-small': { - type: 'length', - value: { - unit: 'px', - value: 600 - } - }, - '--branding-padding': { - type: 'length', - value: { - unit: 'px', - value: 20 - } - } - }; - - let res = transform({ - filename: 'test.css', - minify: true, - errorRecovery: true, - code: Buffer.from(` - @media (max-width: env(--branding-small)) { - body { - padding: env(--branding-padding); - } - } - `), - visitor: composeVisitors([ - { - EnvironmentVariable: { - '--branding-small': () => tokens['--branding-small'] - } - }, - { - EnvironmentVariable: { - '--branding-padding': () => tokens['--branding-padding'] - } - } - ]) - }); - - assert.equal(res.code.toString(), '@media (width<=600px){body{padding:20px}}'); -}); - -test('variables', () => { - /** @type {Record} */ - let tokens = { - '--branding-small': { - type: 'length', - value: { - unit: 'px', - value: 600 - } - }, - '--branding-padding': { - type: 'length', - value: { - unit: 'px', - value: 20 - } - } - }; - - let res = transform({ - filename: 'test.css', - minify: true, - errorRecovery: true, - code: Buffer.from(` - body { - padding: var(--branding-padding); - width: var(--branding-small); - } - `), - visitor: composeVisitors([ - { - Variable(v) { - if (v.name.ident === '--branding-small') { - return tokens['--branding-small']; - } - } - }, - { - Variable(v) { - if (v.name.ident === '--branding-padding') { - return tokens['--branding-padding']; - } - } - } - ]) - }); - - assert.equal(res.code.toString(), 'body{padding:20px;width:600px}'); -}); - -test('StyleSheet', () => { - let styleSheetCalledCount = 0; - let styleSheetExitCalledCount = 0; - transform({ - filename: 'test.css', - code: Buffer.from(` - body { - color: blue; - } - `), - visitor: composeVisitors([ - { - StyleSheet() { - styleSheetCalledCount++ - }, - StyleSheetExit() { - styleSheetExitCalledCount++ - } - }, - { - StyleSheet() { - styleSheetCalledCount++ - }, - StyleSheetExit() { - styleSheetExitCalledCount++ - } - } - ]) - }); - assert.equal(styleSheetCalledCount, 2); - assert.equal(styleSheetExitCalledCount, 2); -}); - -test.run(); diff --git a/node/test/composeVisitors.test.ts b/node/test/composeVisitors.test.ts new file mode 100644 index 00000000..2e3d9070 --- /dev/null +++ b/node/test/composeVisitors.test.ts @@ -0,0 +1,917 @@ +/// + +import {beforeAll, expect, test} from 'vitest'; +import type { + transform as Transform, + composeVisitors as ComposeVisitors, + TokenOrValue, + Visitor, + SelectorList, +} from '../index.js'; + +let transform: typeof Transform; +let composeVisitors: typeof ComposeVisitors; + +beforeAll(async () => { + if (import.meta.env.TEST_WASM === 'node') { + ({transform, composeVisitors} = await import('../../wasm/wasm-node.mjs')); + } else if (import.meta.env.TEST_WASM === 'browser') { + let wasm = await import('../../wasm/index.mjs'); + await wasm.default(); + ({transform, composeVisitors} = wasm); + } else { + ({transform, composeVisitors} = await import('../index.mjs')); + } +}); + +test('different types', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + width: 16px; + color: red; + } + `), + visitor: composeVisitors([ + { + Length(l) { + if (l.unit === 'px') { + return { + unit: 'rem', + value: l.value / 16, + }; + } + }, + }, + { + Color(c) { + if (typeof c === 'object' && c.type === 'rgb') { + return { + type: 'rgb', + r: c.g, + g: c.r, + b: c.b, + alpha: c.alpha, + }; + } + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('.foo{color:#0f0;width:1rem}'); +}); + +test('simple matching types', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + width: 16px; + } + `), + visitor: composeVisitors([ + { + Length(l) { + return { + unit: l.unit, + value: l.value * 2, + }; + }, + }, + { + Length(l) { + if (l.unit === 'px') { + return { + unit: 'rem', + value: l.value / 16, + }; + } + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('.foo{width:2rem}'); +}); + +test('different properties', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + size: 16px; + bg: #ff0; + } + `), + visitor: composeVisitors([ + { + Declaration: { + custom: { + size(v) { + return [ + { + property: 'unparsed', + value: {propertyId: {property: 'width'}, value: v.value}, + }, + { + property: 'unparsed', + value: {propertyId: {property: 'height'}, value: v.value}, + }, + ]; + }, + }, + }, + }, + { + Declaration: { + custom: { + bg(v) { + if (v.value[0].type === 'color') { + return {property: 'background-color', value: v.value[0].value}; + } + }, + }, + }, + }, + ]), + }); + + expect(res.code.toString()).toBe( + '.foo{width:16px;height:16px;background-color:#ff0}', + ); +}); + +test('composed properties', () => { + let visitors: Visitor[] = [ + { + Declaration: { + custom: { + size(v) { + if (v.value[0].type === 'length') { + return [ + { + property: 'width', + value: { + type: 'length-percentage', + value: {type: 'dimension', value: v.value[0].value}, + }, + }, + { + property: 'height', + value: { + type: 'length-percentage', + value: {type: 'dimension', value: v.value[0].value}, + }, + }, + ]; + } + }, + }, + }, + }, + { + Declaration: { + width() { + return []; + }, + }, + }, + ]; + + // Check that it works in any order. + for (let i = 0; i < 2; i++) { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + size: 16px; + } + `), + visitor: composeVisitors(visitors), + }); + + expect(res.code.toString()).toBe('.foo{height:16px}'); + visitors.reverse(); + } +}); + +test('same properties', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + color: red; + } + `), + visitor: composeVisitors([ + { + Declaration: { + color(v) { + if ( + v.property === 'color' && + typeof v.value === 'object' && + v.value.type === 'rgb' + ) { + return { + property: 'color', + value: { + type: 'rgb', + r: v.value.g, + g: v.value.r, + b: v.value.b, + alpha: v.value.alpha, + }, + }; + } + }, + }, + }, + { + Declaration: { + color(v) { + if ( + v.property === 'color' && + typeof v.value === 'object' && + v.value.type === 'rgb' && + v.value.g > 0 + ) { + v.value.alpha /= 2; + } + return v; + }, + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('.foo{color:#00ff0080}'); +}); + +test('properties plus values', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + size: test; + } + `), + visitor: composeVisitors([ + { + Declaration: { + custom: { + size() { + return [ + { + property: 'width', + value: { + type: 'length-percentage', + value: {type: 'dimension', value: {unit: 'px', value: 32}}, + }, + }, + { + property: 'height', + value: { + type: 'length-percentage', + value: {type: 'dimension', value: {unit: 'px', value: 32}}, + }, + }, + ]; + }, + }, + }, + }, + { + Length(l) { + if (l.unit === 'px') { + return { + unit: 'rem', + value: l.value / 16, + }; + } + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('.foo{width:2rem;height:2rem}'); +}); + +test('unparsed properties', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + width: test; + } + .bar { + width: 16px; + } + `), + visitor: composeVisitors([ + { + Declaration: { + width(v) { + if (v.property === 'unparsed') { + return [ + { + property: 'width', + value: { + type: 'length-percentage', + value: {type: 'dimension', value: {unit: 'px', value: 32}}, + }, + }, + { + property: 'height', + value: { + type: 'length-percentage', + value: {type: 'dimension', value: {unit: 'px', value: 32}}, + }, + }, + ]; + } + }, + }, + }, + { + Length(l) { + if (l.unit === 'px') { + return { + unit: 'rem', + value: l.value / 16, + }; + } + }, + }, + ]), + }); + + expect(res.code.toString()).toBe( + '.foo{width:2rem;height:2rem}.bar{width:1rem}', + ); +}); + +test('returning unparsed properties', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + width: test; + } + `), + visitor: composeVisitors([ + { + Declaration: { + width(v) { + if ( + v.property === 'unparsed' && + v.value.value[0].type === 'token' && + v.value.value[0].value.type === 'ident' + ) { + return { + property: 'unparsed', + value: { + propertyId: {property: 'width'}, + value: [ + { + type: 'var', + value: { + name: { + ident: '--' + v.value.value[0].value.value, + }, + }, + }, + ], + }, + }; + } + }, + }, + }, + { + Declaration: { + width(v) { + if (v.property === 'unparsed') { + return { + property: 'unparsed', + value: { + propertyId: {property: 'width'}, + value: [ + { + type: 'function', + value: { + name: 'calc', + arguments: v.value.value, + }, + }, + ], + }, + }; + } + }, + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('.foo{width:calc(var(--test))}'); +}); + +test('all property handlers', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + width: test; + height: test; + } + `), + visitor: composeVisitors([ + { + Declaration(decl) { + if ( + decl.property === 'unparsed' && + decl.value.propertyId.property === 'width' + ) { + return { + property: 'width', + value: { + type: 'length-percentage', + value: {type: 'dimension', value: {unit: 'px', value: 32}}, + }, + }; + } + }, + }, + { + Declaration(decl) { + if ( + decl.property === 'unparsed' && + decl.value.propertyId.property === 'height' + ) { + return { + property: 'height', + value: { + type: 'length-percentage', + value: {type: 'dimension', value: {unit: 'px', value: 32}}, + }, + }; + } + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('.foo{width:32px;height:32px}'); +}); + +test('all property handlers (exit)', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + width: test; + height: test; + } + `), + visitor: composeVisitors([ + { + DeclarationExit(decl) { + if ( + decl.property === 'unparsed' && + decl.value.propertyId.property === 'width' + ) { + return { + property: 'width', + value: { + type: 'length-percentage', + value: {type: 'dimension', value: {unit: 'px', value: 32}}, + }, + }; + } + }, + }, + { + DeclarationExit(decl) { + if ( + decl.property === 'unparsed' && + decl.value.propertyId.property === 'height' + ) { + return { + property: 'height', + value: { + type: 'length-percentage', + value: {type: 'dimension', value: {unit: 'px', value: 32}}, + }, + }; + } + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('.foo{width:32px;height:32px}'); +}); + +test('tokens and functions', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + width: f3(f2(f1(test))); + } + `), + visitor: composeVisitors([ + { + FunctionExit: { + f1(f) { + if ( + f.arguments.length === 1 && + f.arguments[0].type === 'token' && + f.arguments[0].value.type === 'ident' + ) { + return { + type: 'length', + value: { + unit: 'px', + value: 32, + }, + }; + } + }, + }, + }, + { + FunctionExit(f) { + return f.arguments[0]; + }, + }, + { + Length(l) { + if (l.unit === 'px') { + return { + unit: 'rem', + value: l.value / 16, + }; + } + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('.foo{width:2rem}'); +}); + +test('unknown rules', () => { + let declared = new Map(); + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + @test #056ef0; + + .menu_link { + background: @blue; + } + `), + visitor: composeVisitors([ + { + Rule: { + unknown: { + test(rule) { + rule.name = 'blue'; + return { + type: 'unknown', + value: rule, + }; + }, + }, + }, + }, + { + Rule: { + unknown(rule) { + declared.set(rule.name, rule.prelude); + return []; + }, + }, + Token: { + 'at-keyword'(token) { + if (declared.has(token.value)) { + return declared.get(token.value); + } + }, + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('.menu_link{background:#056ef0}'); +}); + +test('custom at rules', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + @testA; + @testB; + `), + customAtRules: { + testA: {}, + testB: {}, + }, + visitor: composeVisitors([ + { + Rule: { + custom: { + testA(rule) { + return { + type: 'style', + value: { + loc: rule.loc, + selectors: [[{type: 'class', name: 'testA'}]], + declarations: { + declarations: [ + { + property: 'color', + value: { + type: 'rgb', + r: 0xff, + g: 0x00, + b: 0x00, + alpha: 1, + }, + }, + ], + }, + }, + }; + }, + }, + }, + }, + { + Rule: { + custom: { + testB(rule) { + return { + type: 'style', + value: { + loc: rule.loc, + selectors: [[{type: 'class', name: 'testB'}]], + declarations: { + declarations: [ + { + property: 'color', + value: { + type: 'rgb', + r: 0x00, + g: 0xff, + b: 0x00, + alpha: 1, + }, + }, + ], + }, + }, + }; + }, + }, + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('.testA{color:red}.testB{color:#0f0}'); +}); + +test('known rules', () => { + let declared = new Map(); + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .test:focus-visible { + margin-left: 20px; + margin-right: @margin-left; + } + `), + targets: { + safari: 14 << 16, + }, + visitor: composeVisitors([ + { + Rule: { + style(rule) { + let valuesByProperty = new Map(); + for (let decl of rule.value.declarations.declarations) { + let name = decl.property; + if (decl.property === 'unparsed') { + name = decl.value.propertyId.property as typeof name; + } + valuesByProperty.set(name, decl); + } + + rule.value.declarations.declarations = + rule.value.declarations.declarations.map((decl) => { + // Only single value supported. Would need a way to convert parsed values to unparsed tokens otherwise. + if ( + decl.property === 'unparsed' && + decl.value.value.length === 1 + ) { + let token = decl.value.value[0]; + if ( + token.type === 'token' && + token.value.type === 'at-keyword' && + valuesByProperty.has(token.value.value) + ) { + let v = valuesByProperty.get(token.value.value); + return { + property: decl.value.propertyId.property, + value: v.value, + }; + } + } + return decl; + }) as typeof rule.value.declarations.declarations; + + return rule; + }, + }, + }, + { + Rule: { + style(rule) { + let clone: SelectorList | null = null; + for (let selector of rule.value.selectors) { + for (let [i, component] of selector.entries()) { + if ( + component.type === 'pseudo-class' && + component.kind === 'focus-visible' + ) { + if (clone == null) { + clone = [...rule.value.selectors.map((s) => [...s])]; + } + + selector[i] = {type: 'class', name: 'focus-visible'}; + } + } + } + + if (clone) { + return [ + rule, + {type: 'style', value: {...rule.value, selectors: clone}}, + ]; + } + }, + }, + }, + ]), + }); + + expect(res.code.toString()).toBe( + '.test.focus-visible{margin-left:20px;margin-right:20px}.test:focus-visible{margin-left:20px;margin-right:20px}', + ); +}); + +test('environment variables', () => { + let tokens: Record = { + '--branding-small': { + type: 'length', + value: { + unit: 'px', + value: 600, + }, + }, + '--branding-padding': { + type: 'length', + value: { + unit: 'px', + value: 20, + }, + }, + }; + + let res = transform({ + filename: 'test.css', + minify: true, + errorRecovery: true, + code: Buffer.from(` + @media (max-width: env(--branding-small)) { + body { + padding: env(--branding-padding); + } + } + `), + visitor: composeVisitors([ + { + EnvironmentVariable: { + '--branding-small': () => tokens['--branding-small'], + }, + }, + { + EnvironmentVariable: { + '--branding-padding': () => tokens['--branding-padding'], + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('@media (width<=600px){body{padding:20px}}'); +}); + +test('variables', () => { + let tokens: Record = { + '--branding-small': { + type: 'length', + value: { + unit: 'px', + value: 600, + }, + }, + '--branding-padding': { + type: 'length', + value: { + unit: 'px', + value: 20, + }, + }, + }; + + let res = transform({ + filename: 'test.css', + minify: true, + errorRecovery: true, + code: Buffer.from(` + body { + padding: var(--branding-padding); + width: var(--branding-small); + } + `), + visitor: composeVisitors([ + { + Variable(v) { + if (v.name.ident === '--branding-small') { + return tokens['--branding-small']; + } + }, + }, + { + Variable(v) { + if (v.name.ident === '--branding-padding') { + return tokens['--branding-padding']; + } + }, + }, + ]), + }); + + expect(res.code.toString()).toBe('body{padding:20px;width:600px}'); +}); + +test('StyleSheet', () => { + let styleSheetCalledCount = 0; + let styleSheetExitCalledCount = 0; + transform({ + filename: 'test.css', + code: Buffer.from(` + body { + color: blue; + } + `), + visitor: composeVisitors([ + { + StyleSheet() { + styleSheetCalledCount++; + }, + StyleSheetExit() { + styleSheetExitCalledCount++; + }, + }, + { + StyleSheet() { + styleSheetCalledCount++; + }, + StyleSheetExit() { + styleSheetExitCalledCount++; + }, + }, + ]), + }); + expect(styleSheetCalledCount).toBe(2); + expect(styleSheetExitCalledCount).toBe(2); +}); diff --git a/node/test/customAtRules.mjs b/node/test/customAtRules.mjs deleted file mode 100644 index e53f65ad..00000000 --- a/node/test/customAtRules.mjs +++ /dev/null @@ -1,317 +0,0 @@ -// @ts-check - -import { test } from 'uvu'; -import * as assert from 'uvu/assert'; -import fs from 'fs'; -import {webcrypto as crypto} from 'node:crypto'; - -let bundle, transform; -if (process.env.TEST_WASM === 'node') { - ({ bundle, transform } = await import('../../wasm/wasm-node.mjs')); -} else if (process.env.TEST_WASM === 'browser') { - // Define crypto globally for old node. - // @ts-ignore - globalThis.crypto ??= crypto; - let wasm = await import('../../wasm/index.mjs'); - await wasm.default(); - transform = wasm.transform; - bundle = function(options) { - return wasm.bundle({ - ...options, - resolver: { - read: (filePath) => fs.readFileSync(filePath, 'utf8') - } - }); - } -} else { - ({bundle, transform} = await import('../index.mjs')); -} - -test('declaration list', () => { - let definitions = new Map(); - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - @theme spacing { - foo: 16px; - bar: 32px; - } - - .foo { - width: theme('spacing.foo'); - } - `), - customAtRules: { - theme: { - prelude: '', - body: 'declaration-list' - } - }, - visitor: { - Rule: { - custom: { - theme(rule) { - for (let decl of rule.body.value.declarations) { - if (decl.property === 'custom') { - definitions.set(rule.prelude.value + '.' + decl.value.name, decl.value.value); - } - } - return []; - } - } - }, - Function: { - theme(f) { - if (f.arguments[0].type === 'token' && f.arguments[0].value.type === 'string') { - return definitions.get(f.arguments[0].value.value); - } - } - } - } - }); - - assert.equal(res.code.toString(), '.foo{width:16px}'); -}); - -test('mixin', () => { - let mixins = new Map(); - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - @mixin color { - color: red; - - &.bar { - color: yellow; - } - } - - .foo { - @apply color; - } - `), - targets: { chrome: 100 << 16 }, - customAtRules: { - mixin: { - prelude: '', - body: 'style-block' - }, - apply: { - prelude: '' - } - }, - visitor: { - Rule: { - custom: { - mixin(rule) { - mixins.set(rule.prelude.value, rule.body.value); - return []; - }, - apply(rule) { - return mixins.get(rule.prelude.value); - } - } - } - } - }); - - assert.equal(res.code.toString(), '.foo{color:red}.foo.bar{color:#ff0}'); -}); - -test('rule list', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - @breakpoint 1024px { - .foo { color: yellow; } - } - `), - customAtRules: { - breakpoint: { - prelude: '', - body: 'rule-list' - } - }, - visitor: { - Rule: { - custom: { - breakpoint(rule) { - return { - type: 'media', - value: { - query: { - mediaQueries: [{ mediaType: 'all', condition: { type: 'feature', value: { type: 'range', name: 'width', operator: 'less-than-equal', value: rule.prelude } } }] - }, - rules: rule.body.value, - loc: rule.loc - } - } - } - } - } - } - }); - - assert.equal(res.code.toString(), '@media (width<=1024px){.foo{color:#ff0}}'); -}); - - -test('style block', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - .foo { - @breakpoint 1024px { - color: yellow; - - &.bar { - color: red; - } - } - } - `), - targets: { - chrome: 105 << 16 - }, - customAtRules: { - breakpoint: { - prelude: '', - body: 'style-block' - } - }, - visitor: { - Rule: { - custom: { - breakpoint(rule) { - return { - type: 'media', - value: { - query: { - mediaQueries: [{ mediaType: 'all', condition: { type: 'feature', value: { type: 'range', name: 'width', operator: 'less-than-equal', value: rule.prelude } } }] - }, - rules: rule.body.value, - loc: rule.loc - } - } - } - } - } - } - }); - - assert.equal(res.code.toString(), '@media (width<=1024px){.foo{color:#ff0}.foo.bar{color:red}}'); -}); - -test('style block top level', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - @test { - .foo { - background: black; - } - } - `), - customAtRules: { - test: { - body: 'style-block' - } - } - }); - - assert.equal(res.code.toString(), '@test{.foo{background:#000}}'); -}); - -test('multiple', () => { - let res = transform({ - filename: 'test.css', - minify: true, - code: Buffer.from(` - @breakpoint 1024px { - @theme spacing { - foo: 16px; - bar: 32px; - } - } - `), - customAtRules: { - breakpoint: { - prelude: '', - body: 'rule-list' - }, - theme: { - prelude: '', - body: 'declaration-list' - } - }, - visitor: { - Rule: { - custom(rule) { - if (rule.name === 'breakpoint') { - return { - type: 'media', - value: { - query: { - mediaQueries: [{ mediaType: 'all', condition: { type: 'feature', value: { type: 'range', name: 'width', operator: 'less-than-equal', value: rule.prelude } } }] - }, - rules: rule.body.value, - loc: rule.loc - } - } - } else { - return { - type: 'style', - value: { - selectors: [[{ type: 'pseudo-class', kind: 'root' }]], - declarations: rule.body.value, - loc: rule.loc - } - } - } - } - } - } - }); - - assert.equal(res.code.toString(), '@media (width<=1024px){:root{foo:16px;bar:32px}}'); -}); - -test('bundler', () => { - let mixins = new Map(); - let res = bundle({ - filename: 'tests/testdata/apply.css', - minify: true, - targets: { chrome: 100 << 16 }, - customAtRules: { - mixin: { - prelude: '', - body: 'style-block' - }, - apply: { - prelude: '' - } - }, - visitor: { - Rule: { - custom: { - mixin(rule) { - mixins.set(rule.prelude.value, rule.body.value); - return []; - }, - apply(rule) { - return mixins.get(rule.prelude.value); - } - } - } - } - }); - - assert.equal(res.code.toString(), '.foo{color:red}.foo.bar{color:#ff0}'); -}); - -test.run(); diff --git a/node/test/customAtRules.test.ts b/node/test/customAtRules.test.ts new file mode 100644 index 00000000..aa300f5e --- /dev/null +++ b/node/test/customAtRules.test.ts @@ -0,0 +1,363 @@ +/// + +import fs from 'node:fs'; +import {beforeAll, expect, test} from 'vitest'; +import type {bundle as Bundle, transform as Transform} from '../index.js'; + +let bundle: typeof Bundle; +let transform: typeof Transform; + +beforeAll(async () => { + if (import.meta.env.TEST_WASM === 'node') { + ({bundle, transform} = await import('../../wasm/wasm-node.mjs')); + } else if (import.meta.env.TEST_WASM === 'browser') { + let wasm = await import('../../wasm/index.mjs'); + await wasm.default(); + transform = wasm.transform; + bundle = function (options) { + return wasm.bundle({ + ...options, + resolver: { + read: (filePath) => fs.readFileSync(filePath, 'utf8'), + }, + }); + }; + } else { + ({bundle, transform} = await import('../index.mjs')); + } +}); + +test('declaration list', () => { + let definitions = new Map(); + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + @theme spacing { + foo: 16px; + bar: 32px; + } + + .foo { + width: theme('spacing.foo'); + } + `), + customAtRules: { + theme: { + prelude: '', + body: 'declaration-list', + }, + }, + visitor: { + Rule: { + custom: { + theme(rule) { + for (let decl of rule.body.value.declarations) { + if (decl.property === 'custom') { + definitions.set( + rule.prelude.value + '.' + decl.value.name, + decl.value.value, + ); + } + } + return []; + }, + }, + }, + Function: { + theme(f) { + if ( + f.arguments[0].type === 'token' && + f.arguments[0].value.type === 'string' + ) { + return definitions.get(f.arguments[0].value.value); + } + }, + }, + }, + }); + + expect(res.code.toString()).toBe('.foo{width:16px}'); +}); + +test('mixin', () => { + let mixins = new Map(); + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + @mixin color { + color: red; + + &.bar { + color: yellow; + } + } + + .foo { + @apply color; + } + `), + targets: {chrome: 100 << 16}, + customAtRules: { + mixin: { + prelude: '', + body: 'style-block', + }, + apply: { + prelude: '', + }, + }, + visitor: { + Rule: { + custom: { + mixin(rule) { + mixins.set(rule.prelude.value, rule.body.value); + return []; + }, + apply(rule) { + return mixins.get(rule.prelude.value); + }, + }, + }, + }, + }); + + expect(res.code.toString()).toBe('.foo{color:red}.foo.bar{color:#ff0}'); +}); + +test('rule list', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + @breakpoint 1024px { + .foo { color: yellow; } + } + `), + customAtRules: { + breakpoint: { + prelude: '', + body: 'rule-list', + }, + }, + visitor: { + Rule: { + custom: { + breakpoint(rule) { + return { + type: 'media', + value: { + query: { + mediaQueries: [ + { + mediaType: 'all', + condition: { + type: 'feature', + value: { + type: 'range', + name: 'width', + operator: 'less-than-equal', + value: rule.prelude, + }, + }, + }, + ], + }, + rules: rule.body.value, + loc: rule.loc, + }, + }; + }, + }, + }, + }, + }); + + expect(res.code.toString()).toBe('@media (width<=1024px){.foo{color:#ff0}}'); +}); + +test('style block', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + .foo { + @breakpoint 1024px { + color: yellow; + + &.bar { + color: red; + } + } + } + `), + targets: { + chrome: 105 << 16, + }, + customAtRules: { + breakpoint: { + prelude: '', + body: 'style-block', + }, + }, + visitor: { + Rule: { + custom: { + breakpoint(rule) { + return { + type: 'media', + value: { + query: { + mediaQueries: [ + { + mediaType: 'all', + condition: { + type: 'feature', + value: { + type: 'range', + name: 'width', + operator: 'less-than-equal', + value: rule.prelude, + }, + }, + }, + ], + }, + rules: rule.body.value, + loc: rule.loc, + }, + }; + }, + }, + }, + }, + }); + + expect(res.code.toString()).toBe( + '@media (width<=1024px){.foo{color:#ff0}.foo.bar{color:red}}', + ); +}); + +test('style block top level', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + @test { + .foo { + background: black; + } + } + `), + customAtRules: { + test: { + body: 'style-block', + }, + }, + }); + + expect(res.code.toString()).toBe('@test{.foo{background:#000}}'); +}); + +test('multiple', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + @breakpoint 1024px { + @theme spacing { + foo: 16px; + bar: 32px; + } + } + `), + customAtRules: { + breakpoint: { + prelude: '', + body: 'rule-list', + }, + theme: { + prelude: '', + body: 'declaration-list', + }, + }, + visitor: { + Rule: { + custom(rule) { + if (rule.name === 'breakpoint') { + return { + type: 'media', + value: { + query: { + mediaQueries: [ + { + mediaType: 'all', + condition: { + type: 'feature', + value: { + type: 'range', + name: 'width', + operator: 'less-than-equal', + value: rule.prelude, + }, + }, + }, + ], + }, + rules: rule.body.value, + loc: rule.loc, + }, + }; + } else { + return { + type: 'style', + value: { + selectors: [[{type: 'pseudo-class', kind: 'root'}]], + declarations: rule.body.value, + loc: rule.loc, + }, + }; + } + }, + }, + }, + }); + + expect(res.code.toString()).toBe( + '@media (width<=1024px){:root{foo:16px;bar:32px}}', + ); +}); + +test('bundler', () => { + let mixins = new Map(); + let res = bundle({ + filename: 'tests/testdata/apply.css', + minify: true, + targets: {chrome: 100 << 16}, + customAtRules: { + mixin: { + prelude: '', + body: 'style-block', + }, + apply: { + prelude: '', + }, + }, + visitor: { + Rule: { + custom: { + mixin(rule) { + mixins.set(rule.prelude.value, rule.body.value); + return []; + }, + apply(rule) { + return mixins.get(rule.prelude.value); + }, + }, + }, + }, + }); + + expect(res.code.toString()).toBe('.foo{color:red}.foo.bar{color:#ff0}'); +}); diff --git a/node/test/transform.test.mjs b/node/test/transform.test.mjs deleted file mode 100644 index 1b56bbc1..00000000 --- a/node/test/transform.test.mjs +++ /dev/null @@ -1,71 +0,0 @@ -import { test } from 'uvu'; -import * as assert from 'uvu/assert'; -import {webcrypto as crypto} from 'node:crypto'; - -let transform, Features; -if (process.env.TEST_WASM === 'node') { - ({transform, Features} = await import('../../wasm/wasm-node.mjs')); -} else if (process.env.TEST_WASM === 'browser') { - // Define crypto globally for old node. - // @ts-ignore - globalThis.crypto ??= crypto; - let wasm = await import('../../wasm/index.mjs'); - await wasm.default(); - ({transform, Features} = wasm); -} else { - ({transform, Features} = await import('../index.mjs')); -} - -test('can enable non-standard syntax', () => { - let res = transform({ - filename: 'test.css', - code: Buffer.from('.foo >>> .bar { color: red }'), - nonStandard: { - deepSelectorCombinator: true - }, - minify: true - }); - - assert.equal(res.code.toString(), '.foo>>>.bar{color:red}'); -}); - -test('can enable features without targets', () => { - let res = transform({ - filename: 'test.css', - code: Buffer.from('.foo { .bar { color: red }}'), - minify: true, - include: Features.Nesting - }); - - assert.equal(res.code.toString(), '.foo .bar{color:red}'); -}); - -test('can disable features', () => { - let res = transform({ - filename: 'test.css', - code: Buffer.from('.foo { color: lch(50.998% 135.363 338) }'), - minify: true, - targets: { - chrome: 80 << 16 - }, - exclude: Features.Colors - }); - - assert.equal(res.code.toString(), '.foo{color:lch(50.998% 135.363 338)}'); -}); - -test('can disable prefixing', () => { - let res = transform({ - filename: 'test.css', - code: Buffer.from('.foo { user-select: none }'), - minify: true, - targets: { - safari: 15 << 16 - }, - exclude: Features.VendorPrefixes - }); - - assert.equal(res.code.toString(), '.foo{user-select:none}'); -}); - -test.run(); diff --git a/node/test/transform.test.ts b/node/test/transform.test.ts new file mode 100644 index 00000000..5d29dce4 --- /dev/null +++ b/node/test/transform.test.ts @@ -0,0 +1,74 @@ +/// + +import {beforeAll, expect, test} from 'vitest'; +import type { + Features as FeaturesType, + transform as Transform, +} from '../index.js'; + +let transform: typeof Transform; +let Features: typeof FeaturesType; + +beforeAll(async () => { + if (import.meta.env.TEST_WASM === 'node') { + ({transform, Features} = await import('../../wasm/wasm-node.mjs')); + } else if (import.meta.env.TEST_WASM === 'browser') { + let wasm = await import('../../wasm/index.mjs'); + await wasm.default(); + ({transform, Features} = wasm); + } else { + ({transform, Features} = await import('../index.mjs')); + } +}); + +test('can enable non-standard syntax', () => { + let res = transform({ + filename: 'test.css', + code: Buffer.from('.foo >>> .bar { color: red }'), + nonStandard: { + deepSelectorCombinator: true, + }, + minify: true, + }); + + expect(res.code.toString()).toBe('.foo>>>.bar{color:red}'); +}); + +test('can enable features without targets', () => { + let res = transform({ + filename: 'test.css', + code: Buffer.from('.foo { .bar { color: red }}'), + minify: true, + include: Features.Nesting, + }); + + expect(res.code.toString()).toBe('.foo .bar{color:red}'); +}); + +test('can disable features', () => { + let res = transform({ + filename: 'test.css', + code: Buffer.from('.foo { color: lch(50.998% 135.363 338) }'), + minify: true, + targets: { + chrome: 80 << 16, + }, + exclude: Features.Colors, + }); + + expect(res.code.toString()).toBe('.foo{color:lch(50.998% 135.363 338)}'); +}); + +test('can disable prefixing', () => { + let res = transform({ + filename: 'test.css', + code: Buffer.from('.foo { user-select: none }'), + minify: true, + targets: { + safari: 15 << 16, + }, + exclude: Features.VendorPrefixes, + }); + + expect(res.code.toString()).toBe('.foo{user-select:none}'); +}); diff --git a/node/test/visitor.test.mjs b/node/test/visitor.test.ts similarity index 57% rename from node/test/visitor.test.mjs rename to node/test/visitor.test.ts index 3a42a696..8ec42379 100644 --- a/node/test/visitor.test.mjs +++ b/node/test/visitor.test.ts @@ -1,42 +1,59 @@ -// @ts-check - -import { test } from 'uvu'; -import * as assert from 'uvu/assert'; -import fs from 'fs'; -import {webcrypto as crypto} from 'node:crypto'; - -let bundle, bundleAsync, transform, transformStyleAttribute; -if (process.env.TEST_WASM === 'node') { - ({ bundle, bundleAsync, transform, transformStyleAttribute } = await import('../../wasm/wasm-node.mjs')); -} else if (process.env.TEST_WASM === 'browser') { - // Define crypto globally for old node. - // @ts-ignore - globalThis.crypto ??= crypto; - let wasm = await import('../../wasm/index.mjs'); - await wasm.default(); - ({ transform, transformStyleAttribute } = wasm); - bundle = function(options) { - return wasm.bundle({ - ...options, - resolver: { - read: (filePath) => fs.readFileSync(filePath, 'utf8') - } - }); - } - - bundleAsync = function (options) { - if (!options.resolver?.read) { - options.resolver = { - ...options.resolver, - read: (filePath) => fs.readFileSync(filePath, 'utf8') - }; - } - - return wasm.bundleAsync(options); +/// + +import fs from 'node:fs'; +import {beforeAll, expect, test} from 'vitest'; +import type { + bundle as Bundle, + bundleAsync as BundleAsync, + ReturnedRule, + Rule, + Selector, + SelectorList, + Size, + TokenOrValue, + transform as Transform, + transformStyleAttribute as TransformStyleAttribute, +} from '../index.js'; + +let bundle: typeof Bundle; +let bundleAsync: typeof BundleAsync; +let transform: typeof Transform; +let transformStyleAttribute: typeof TransformStyleAttribute; + +beforeAll(async () => { + if (import.meta.env.TEST_WASM === 'node') { + ({bundle, bundleAsync, transform, transformStyleAttribute} = await import( + '../../wasm/wasm-node.mjs' + )); + } else if (import.meta.env.TEST_WASM === 'browser') { + let wasm = await import('../../wasm/index.mjs'); + await wasm.default(); + ({transform, transformStyleAttribute} = wasm); + bundle = function (options) { + return wasm.bundle({ + ...options, + resolver: { + read: (filePath) => fs.readFileSync(filePath, 'utf8'), + }, + }); + }; + + bundleAsync = function (options) { + if (!options.resolver?.read) { + options.resolver = { + ...options.resolver, + read: (filePath) => fs.readFileSync(filePath, 'utf8'), + }; + } + + return wasm.bundleAsync(options); + }; + } else { + ({bundle, bundleAsync, transform, transformStyleAttribute} = await import( + '../index.mjs' + )); } -} else { - ({ bundle, bundleAsync, transform, transformStyleAttribute } = await import('../index.mjs')); -} +}); test('px to rem', () => { // Similar to https://github.com/cuth/postcss-pxtorem @@ -55,14 +72,16 @@ test('px to rem', () => { if (length.unit === 'px') { return { unit: 'rem', - value: length.value / 16 + value: length.value / 16, }; } - } - } + }, + }, }); - assert.equal(res.code.toString(), '.foo{--custom:calc(var(--foo) + 2rem);width:2rem;height:calc(100vh - 4rem)}'); + expect(res.code.toString()).toBe( + '.foo{--custom:calc(var(--foo) + 2rem);width:2rem;height:calc(100vh - 4rem)}', + ); }); test('custom units', () => { @@ -89,34 +108,36 @@ test('custom units', () => { type: 'token', value: { type: 'number', - value: token.value - } + value: token.value, + }, }, { type: 'token', value: { type: 'delim', - value: '*' - } + value: '*', + }, }, { type: 'var', value: { name: { - ident: token.unit - } - } - } - ] - } - } + ident: token.unit, + }, + }, + }, + ], + }, + }; } - } - } - } + }, + }, + }, }); - assert.equal(res.code.toString(), '.foo{--step:.25rem;font-size:calc(3*var(--step))}'); + expect(res.code.toString()).toBe( + '.foo{--step:.25rem;font-size:calc(3*var(--step))}', + ); }); test('design tokens', () => { @@ -129,16 +150,16 @@ test('design tokens', () => { r: 255, g: 0, b: 0, - alpha: 1 - } + alpha: 1, + }, }, 'size.spacing.small': { type: 'length', value: { unit: 'px', - value: 16 - } - } + value: 16, + }, + }, }; let res = transform({ @@ -153,35 +174,38 @@ test('design tokens', () => { visitor: { Function: { 'design-token'(fn) { - if (fn.arguments.length === 1 && fn.arguments[0].type === 'token' && fn.arguments[0].value.type === 'string') { + if ( + fn.arguments.length === 1 && + fn.arguments[0].type === 'token' && + fn.arguments[0].value.type === 'string' + ) { return tokens[fn.arguments[0].value.value]; } - } - } - } + }, + }, + }, }); - assert.equal(res.code.toString(), '.foo{color:red;padding:16px}'); + expect(res.code.toString()).toBe('.foo{color:red;padding:16px}'); }); test('env function', () => { // https://www.npmjs.com/package/postcss-env-function - /** @type {Record} */ - let tokens = { + let tokens: Record = { '--branding-small': { type: 'length', value: { unit: 'px', - value: 600 - } + value: 600, + }, }, '--branding-padding': { type: 'length', value: { unit: 'px', - value: 20 - } - } + value: 20, + }, + }, }; let res = transform({ @@ -200,31 +224,30 @@ test('env function', () => { if (env.name.type === 'custom') { return tokens[env.name.ident]; } - } - } + }, + }, }); - assert.equal(res.code.toString(), '@media (width<=600px){body{padding:20px}}'); + expect(res.code.toString()).toBe('@media (width<=600px){body{padding:20px}}'); }); test('specific environment variables', () => { // https://www.npmjs.com/package/postcss-env-function - /** @type {Record} */ - let tokens = { + let tokens: Record = { '--branding-small': { type: 'length', value: { unit: 'px', - value: 600 - } + value: 600, + }, }, '--branding-padding': { type: 'length', value: { unit: 'px', - value: 20 - } - } + value: 20, + }, + }, }; let res = transform({ @@ -241,12 +264,12 @@ test('specific environment variables', () => { visitor: { EnvironmentVariable: { '--branding-small': () => tokens['--branding-small'], - '--branding-padding': () => tokens['--branding-padding'] - } - } + '--branding-padding': () => tokens['--branding-padding'], + }, + }, }); - assert.equal(res.code.toString(), '@media (width<=600px){body{padding:20px}}'); + expect(res.code.toString()).toBe('@media (width<=600px){body{padding:20px}}'); }); test('url', () => { @@ -263,11 +286,13 @@ test('url', () => { Url(url) { url.url = 'https://mywebsite.com/' + url.url; return url; - } - } + }, + }, }); - assert.equal(res.code.toString(), '.foo{background:url(https://mywebsite.com/foo.png)}'); + expect(res.code.toString()).toBe( + '.foo{background:url(https://mywebsite.com/foo.png)}', + ); }); test('static vars', () => { @@ -288,19 +313,19 @@ test('static vars', () => { unknown(rule) { declared.set(rule.name, rule.prelude); return []; - } + }, }, Token: { 'at-keyword'(token) { if (declared.has(token.value)) { return declared.get(token.value); } - } - } - } + }, + }, + }, }); - assert.equal(res.code.toString(), '.menu_link{background:#056ef0}'); + expect(res.code.toString()).toBe('.menu_link{background:#056ef0}'); }); test('selector prefix', () => { @@ -315,12 +340,16 @@ test('selector prefix', () => { `), visitor: { Selector(selector) { - return [{ type: 'class', name: 'prefix' }, { type: 'combinator', value: 'descendant' }, ...selector]; - } - } + return [ + {type: 'class', name: 'prefix'}, + {type: 'combinator', value: 'descendant'}, + ...selector, + ]; + }, + }, }); - assert.equal(res.code.toString(), '.prefix .a,.prefix .b{color:red}'); + expect(res.code.toString()).toBe('.prefix .a,.prefix .b{color:red}'); }); test('apply', () => { @@ -343,13 +372,17 @@ test('apply', () => { Rule: { style(rule) { for (let selector of rule.value.selectors) { - if (selector.length === 1 && selector[0].type === 'type' && selector[0].name.startsWith('--')) { + if ( + selector.length === 1 && + selector[0].type === 'type' && + selector[0].name.startsWith('--') + ) { defined.set(selector[0].name, rule.value.declarations); - return { type: 'ignored', value: null }; + return {type: 'ignored', value: null}; } } - rule.value.rules = rule.value.rules.filter(child => { + rule.value.rules = rule.value.rules.filter((child) => { if (child.type === 'unknown' && child.value.name === 'apply') { for (let token of child.value.prelude) { if (token.type === 'dashed-ident' && defined.has(token.value)) { @@ -365,12 +398,14 @@ test('apply', () => { }); return rule; - } - } - } + }, + }, + }, }); - assert.equal(res.code.toString(), '.toolbar{color:#fff;border:1px solid green}'); + expect(res.code.toString()).toBe( + '.toolbar{color:#fff;border:1px solid green}', + ); }); test('property lookup', () => { @@ -389,37 +424,43 @@ test('property lookup', () => { style(rule) { let valuesByProperty = new Map(); for (let decl of rule.value.declarations.declarations) { - /** @type string */ let name = decl.property; if (decl.property === 'unparsed') { - name = decl.value.propertyId.property; + name = decl.value.propertyId.property as typeof name; } valuesByProperty.set(name, decl); } - rule.value.declarations.declarations = rule.value.declarations.declarations.map(decl => { - // Only single value supported. Would need a way to convert parsed values to unparsed tokens otherwise. - if (decl.property === 'unparsed' && decl.value.value.length === 1) { - let token = decl.value.value[0]; - if (token.type === 'token' && token.value.type === 'at-keyword' && valuesByProperty.has(token.value.value)) { - let v = valuesByProperty.get(token.value.value); - return { - /** @type any */ - property: decl.value.propertyId.property, - value: v.value - }; + rule.value.declarations.declarations = + rule.value.declarations.declarations.map((decl) => { + // Only single value supported. Would need a way to convert parsed values to unparsed tokens otherwise. + if ( + decl.property === 'unparsed' && + decl.value.value.length === 1 + ) { + let token = decl.value.value[0]; + if ( + token.type === 'token' && + token.value.type === 'at-keyword' && + valuesByProperty.has(token.value.value) + ) { + let v = valuesByProperty.get(token.value.value); + return { + property: decl.value.propertyId.property, + value: v.value, + }; + } } - } - return decl; - }); + return decl; + }) as typeof rule.value.declarations.declarations; return rule; - } - } - } + }, + }, + }, }); - assert.equal(res.code.toString(), '.test{margin-left:20px;margin-right:20px}'); + expect(res.code.toString()).toBe('.test{margin-left:20px;margin-right:20px}'); }); test('focus visible', () => { @@ -433,33 +474,41 @@ test('focus visible', () => { } `), targets: { - safari: 14 << 16 + safari: 14 << 16, }, visitor: { Rule: { style(rule) { - let clone = null; + let clone: SelectorList | null = null; for (let selector of rule.value.selectors) { for (let [i, component] of selector.entries()) { - if (component.type === 'pseudo-class' && component.kind === 'focus-visible') { + if ( + component.type === 'pseudo-class' && + component.kind === 'focus-visible' + ) { if (clone == null) { - clone = [...rule.value.selectors.map(s => [...s])]; + clone = [...rule.value.selectors.map((s) => [...s])]; } - selector[i] = { type: 'class', name: 'focus-visible' }; + selector[i] = {type: 'class', name: 'focus-visible'}; } } } if (clone) { - return [rule, { type: 'style', value: { ...rule.value, selectors: clone } }]; + return [ + rule, + {type: 'style', value: {...rule.value, selectors: clone}}, + ]; } - } - } - } + }, + }, + }, }); - assert.equal(res.code.toString(), '.test.focus-visible{color:red}.test:focus-visible{color:red}'); + expect(res.code.toString()).toBe( + '.test.focus-visible{color:red}.test:focus-visible{color:red}', + ); }); test('dark theme class', () => { @@ -478,45 +527,63 @@ test('dark theme class', () => { Rule: { media(rule) { let q = rule.value.query.mediaQueries[0]; - if (q.condition?.type === 'feature' && q.condition.value.type === 'plain' && q.condition.value.name === 'prefers-color-scheme' && q.condition.value.value.value === 'dark') { - /** @type {import('../ast').Rule[]} */ - let clonedRules = [rule]; + if ( + q.condition?.type === 'feature' && + q.condition.value.type === 'plain' && + q.condition.value.name === 'prefers-color-scheme' && + q.condition.value.value.value === 'dark' + ) { + let clonedRules: Rule[] = [rule]; for (let r of rule.value.rules) { if (r.type === 'style') { - /** @type {import('../ast').Selector[]} */ - let clonedSelectors = []; + let clonedSelectors: Selector[] = []; for (let selector of r.value.selectors) { clonedSelectors.push([ - { type: 'type', name: 'html' }, - { type: 'attribute', name: 'theme', operation: { operator: 'equal', value: 'dark' } }, - { type: 'combinator', value: 'descendant' }, - ...selector + {type: 'type', name: 'html'}, + { + type: 'attribute', + name: 'theme', + operation: {operator: 'equal', value: 'dark'}, + }, + {type: 'combinator', value: 'descendant'}, + ...selector, ]); selector.unshift( - { type: 'type', name: 'html' }, + {type: 'type', name: 'html'}, { type: 'pseudo-class', kind: 'not', selectors: [ - [{ type: 'attribute', name: 'theme', operation: { operator: 'equal', value: 'light' } }] - ] + [ + { + type: 'attribute', + name: 'theme', + operation: {operator: 'equal', value: 'light'}, + }, + ], + ], }, - { type: 'combinator', value: 'descendant' } + {type: 'combinator', value: 'descendant'}, ); } - clonedRules.push({ type: 'style', value: { ...r.value, selectors: clonedSelectors } }); + clonedRules.push({ + type: 'style', + value: {...r.value, selectors: clonedSelectors}, + }); } } return clonedRules; } - } - } - } + }, + }, + }, }); - assert.equal(res.code.toString(), '@media (prefers-color-scheme:dark){html:not([theme=light]) body{background:#000}}html[theme=dark] body{background:#000}'); + expect(res.code.toString()).toBe( + '@media (prefers-color-scheme:dark){html:not([theme=light]) body{background:#000}}html[theme=dark] body{background:#000}', + ); }); test('100vh fix', () => { @@ -535,7 +602,13 @@ test('100vh fix', () => { style(style) { let cloned; for (let property of style.value.declarations.declarations) { - if (property.property === 'height' && property.value.type === 'length-percentage' && property.value.value.type === 'dimension' && property.value.value.value.unit === 'vh' && property.value.value.value.value === 100) { + if ( + property.property === 'height' && + property.value.type === 'length-percentage' && + property.value.value.type === 'dimension' && + property.value.value.value.unit === 'vh' && + property.value.value.value.value === 100 + ) { if (!cloned) { cloned = structuredClone(style); cloned.value.declarations.declarations = []; @@ -544,34 +617,39 @@ test('100vh fix', () => { ...property, value: { type: 'stretch', - vendorPrefix: ['webkit'] - } + vendorPrefix: ['webkit'], + }, }); } } if (cloned) { - return [style, { - type: 'supports', - value: { - condition: { - type: 'declaration', - propertyId: { - property: '-webkit-touch-callout' + return [ + style, + { + type: 'supports', + value: { + condition: { + type: 'declaration', + propertyId: { + property: '-webkit-touch-callout', + }, + value: 'none', }, - value: 'none' + loc: style.value.loc, + rules: [cloned], }, - loc: style.value.loc, - rules: [cloned] - } - }]; + }, + ]; } - } - } - } + }, + }, + }, }); - assert.equal(res.code.toString(), '.foo{color:red;height:100vh}@supports (-webkit-touch-callout:none){.foo{height:-webkit-fill-available}}') + expect(res.code.toString()).toBe( + '.foo{color:red;height:100vh}@supports (-webkit-touch-callout:none){.foo{height:-webkit-fill-available}}', + ); }); test('logical transforms', () => { @@ -595,11 +673,10 @@ test('logical transforms', () => { visitor: { Rule: { style(style) { - /** @type any */ - let cloned; + let cloned: any; for (let property of style.value.declarations.declarations) { if (property.property === 'transform') { - let clonedTransforms = property.value.map(transform => { + let clonedTransforms = property.value.map((transform) => { if (transform.type !== 'translateX') { return transform; } @@ -612,27 +689,41 @@ test('logical transforms', () => { let value; switch (transform.value.type) { case 'dimension': - value = { type: 'dimension', value: { unit: transform.value.value.unit, value: -transform.value.value.value } }; + value = { + type: 'dimension', + value: { + unit: transform.value.value.unit, + value: -transform.value.value.value, + }, + }; break; case 'percentage': - value = { type: 'percentage', value: -transform.value.value }; + value = {type: 'percentage', value: -transform.value.value}; break; case 'calc': - value = { type: 'calc', value: { type: 'product', value: [-1, transform.value.value] } }; + value = { + type: 'calc', + value: { + type: 'product', + value: [-1, transform.value.value], + }, + }; break; } return { type: 'translateX', - value - } + value, + }; }); if (cloned) { - cloned.value.selectors.at(-1).push({ type: 'pseudo-class', kind: 'dir', direction: 'rtl' }); + cloned.value.selectors + .at(-1) + .push({type: 'pseudo-class', kind: 'dir', direction: 'rtl'}); cloned.value.declarations.declarations.push({ ...property, - value: clonedTransforms + value: clonedTransforms, }); } } @@ -641,12 +732,14 @@ test('logical transforms', () => { if (cloned) { return [style, cloned]; } - } - } - } + }, + }, + }, }); - assert.equal(res.code.toString(), '.foo{transform:translate(50px)}.foo:dir(rtl){transform:translate(-50px)}.bar{transform:translate(20%)}.bar:dir(rtl){transform:translate(-20%)}.baz{transform:translate(calc(100vw - 20px))}.baz:dir(rtl){transform:translate(-1*calc(100vw - 20px))}'); + expect(res.code.toString()).toBe( + '.foo{transform:translate(50px)}.foo:dir(rtl){transform:translate(-50px)}.bar{transform:translate(20%)}.bar:dir(rtl){transform:translate(-20%)}.baz{transform:translate(calc(100vw - 20px))}.baz:dir(rtl){transform:translate(-1*calc(100vw - 20px))}', + ); }); test('hover media query', () => { @@ -675,26 +768,32 @@ test('hover media query', () => { for (let rule of media.value.rules) { if (rule.type === 'style') { for (let selector of rule.value.selectors) { - selector.unshift({ type: 'class', name: 'hoverable' }, { type: 'combinator', value: 'descendant' }); + selector.unshift( + {type: 'class', name: 'hoverable'}, + {type: 'combinator', value: 'descendant'}, + ); } } } - return media.value.rules + return media.value.rules; } - } - } - } + }, + }, + }, }); - assert.equal(res.code.toString(), '.hoverable .foo{color:red}'); + expect(res.code.toString()).toBe('.hoverable .foo{color:red}'); }); test('momentum scrolling', () => { // Similar to https://github.com/yunusga/postcss-momentum-scrolling - let visitOverflow = decl => [decl, { - property: '-webkit-overflow-scrolling', - raw: 'touch' - }]; + let visitOverflow = (decl) => [ + decl, + { + property: '-webkit-overflow-scrolling', + raw: 'touch', + }, + ]; let res = transform({ filename: 'test.css', @@ -708,12 +807,14 @@ test('momentum scrolling', () => { Declaration: { overflow: visitOverflow, 'overflow-x': visitOverflow, - 'overflow-y': visitOverflow - } - } + 'overflow-y': visitOverflow, + }, + }, }); - assert.equal(res.code.toString(), '.foo{-webkit-overflow-scrolling:touch;overflow:auto}'); + expect(res.code.toString()).toBe( + '.foo{-webkit-overflow-scrolling:touch;overflow:auto}', + ); }); test('size', () => { @@ -731,20 +832,22 @@ test('size', () => { custom: { size(property) { if (property.value[0].type === 'length') { - /** @type {import('../ast').Size} */ - let value = { type: 'length-percentage', value: { type: 'dimension', value: property.value[0].value } }; + let value: Size = { + type: 'length-percentage', + value: {type: 'dimension', value: property.value[0].value}, + }; return [ - { property: 'width', value }, - { property: 'height', value } + {property: 'width', value}, + {property: 'height', value}, ]; } - } - } - } - } + }, + }, + }, + }, }); - assert.equal(res.code.toString(), '.foo{width:12px;height:12px}'); + expect(res.code.toString()).toBe('.foo{width:12px;height:12px}'); }); test('works with style attributes', () => { @@ -757,14 +860,14 @@ test('works with style attributes', () => { if (length.unit === 'px') { return { unit: 'rem', - value: length.value / 16 + value: length.value / 16, }; } - } - } + }, + }, }); - assert.equal(res.code.toString(), 'height:calc(100vh - 4rem)'); + expect(res.code.toString()).toBe('height:calc(100vh - 4rem)'); }); test('works with bundler', () => { @@ -776,14 +879,16 @@ test('works with bundler', () => { if (length.unit === 'px') { return { unit: 'rem', - value: length.value / 16 + value: length.value / 16, }; } - } - } + }, + }, }); - assert.equal(res.code.toString(), '.b{height:calc(100vh - 4rem)}.a{width:2rem}'); + expect(res.code.toString()).toBe( + '.b{height:calc(100vh - 4rem)}.a{width:2rem}', + ); }); test('works with async bundler', async () => { @@ -795,14 +900,16 @@ test('works with async bundler', async () => { if (length.unit === 'px') { return { unit: 'rem', - value: length.value / 16 + value: length.value / 16, }; } - } - } + }, + }, }); - assert.equal(res.code.toString(), '.b{height:calc(100vh - 4rem)}.a{width:2rem}'); + expect(res.code.toString()).toBe( + '.b{height:calc(100vh - 4rem)}.a{width:2rem}', + ); }); test('dashed idents', () => { @@ -818,11 +925,13 @@ test('dashed idents', () => { visitor: { DashedIdent(ident) { return `--prefix-${ident.slice(2)}`; - } - } + }, + }, }); - assert.equal(res.code.toString(), '.foo{--prefix-foo:#ff0;color:var(--prefix-foo)}'); + expect(res.code.toString()).toBe( + '.foo{--prefix-foo:#ff0;color:var(--prefix-foo)}', + ); }); test('custom idents', () => { @@ -841,11 +950,13 @@ test('custom idents', () => { visitor: { CustomIdent(ident) { return `prefix-${ident}`; - } - } + }, + }, }); - assert.equal(res.code.toString(), '@keyframes prefix-test{0%{color:red}to{color:green}}.foo{animation:prefix-test}'); + expect(res.code.toString()).toBe( + '@keyframes prefix-test{0%{color:red}to{color:green}}.foo{animation:prefix-test}', + ); }); test('returning string values', () => { @@ -862,46 +973,46 @@ test('returning string values', () => { type: 'style', value: { loc: rule.loc, - selectors: [ - [{ type: 'universal' }] - ], + selectors: [[{type: 'universal'}]], declarations: { declarations: [ { property: 'visibility', - raw: 'hi\\64 den' // escapes work for raw but not value + raw: 'hi\\64 den', // escapes work for raw but not value }, { property: 'background', - raw: 'yellow' + raw: 'yellow', }, { property: '--custom', - raw: 'hi' + raw: 'hi', }, { property: 'transition', vendorPrefix: ['moz'], - raw: '200ms test' + raw: '200ms test', }, { property: '-webkit-animation', - raw: '3s cubic-bezier(0.25, 0.1, 0.25, 1) foo' - } - ] - } - } - } - } - } - } + raw: '3s cubic-bezier(0.25, 0.1, 0.25, 1) foo', + }, + ], + }, + }, + }; + }, + }, + }, }); - assert.equal(res.code.toString(), '*{visibility:hidden;--custom:hi;background:#ff0;-moz-transition:test .2s;-webkit-animation:3s foo}'); + expect(res.code.toString()).toBe( + '*{visibility:hidden;--custom:hi;background:#ff0;-moz-transition:test .2s;-webkit-animation:3s foo}', + ); }); test('errors on invalid dashed idents', () => { - assert.throws(() => { + expect(() => transform({ filename: 'test.css', minify: true, @@ -912,25 +1023,28 @@ test('errors on invalid dashed idents', () => { `), visitor: { Function(fn) { - if (fn.arguments[0].type === 'token' && fn.arguments[0].value.type === 'ident') { + if ( + fn.arguments[0].type === 'token' && + fn.arguments[0].value.type === 'ident' + ) { fn.arguments = [ { type: 'var', value: { - name: { ident: fn.arguments[0].value.value } - } - } + name: {ident: fn.arguments[0].value.value}, + }, + }, ]; } return { type: 'function', - value: fn - } - } - } - }) - }, 'Dashed idents must start with --'); + value: fn, + }; + }, + }, + }), + ).toThrowError('Dashed idents must start with --'); }); test('supports returning raw values for tokens', () => { @@ -945,13 +1059,13 @@ test('supports returning raw values for tokens', () => { visitor: { Function: { theme() { - return { raw: 'rgba(255, 0, 0)' }; - } - } - } + return {raw: 'rgba(255, 0, 0)'}; + }, + }, + }, }); - assert.equal(res.code.toString(), '.foo{color:red}'); + expect(res.code.toString()).toBe('.foo{color:red}'); }); test('supports returning raw values as variables', () => { @@ -959,7 +1073,7 @@ test('supports returning raw values as variables', () => { filename: 'test.css', minify: true, cssModules: { - dashedIdents: true + dashedIdents: true, }, code: Buffer.from(` .foo { @@ -969,13 +1083,13 @@ test('supports returning raw values as variables', () => { visitor: { Function: { theme() { - return { raw: 'var(--foo)' }; - } - } - } + return {raw: 'var(--foo)'}; + }, + }, + }, }); - assert.equal(res.code.toString(), '.EgL3uq_foo{color:var(--EgL3uq_foo)}'); + expect(res.code.toString()).toBe('.EgL3uq_foo{color:var(--EgL3uq_foo)}'); }); test('works with currentColor', () => { @@ -990,11 +1104,11 @@ test('works with currentColor', () => { visitor: { Rule(rule) { return rule; - } - } + }, + }, }); - assert.equal(res.code.toString(), '.foo{color:currentColor}'); + expect(res.code.toString()).toBe('.foo{color:currentColor}'); }); test('nth of S to nth-of-type', () => { @@ -1009,17 +1123,22 @@ test('nth of S to nth-of-type', () => { visitor: { Selector(selector) { for (let component of selector) { - if (component.type === 'pseudo-class' && component.kind === 'nth-child' && component.of) { + if ( + component.type === 'pseudo-class' && + component.kind === 'nth-child' && + component.of + ) { delete component.of; + // @ts-ignore component.kind = 'nth-of-type'; } } return selector; - } - } + }, + }, }); - assert.equal(res.code.toString(), 'a:nth-of-type(2n){color:red}'); + expect(res.code.toString()).toBe('a:nth-of-type(2n){color:red}'); }); test('media query raw', () => { @@ -1036,15 +1155,14 @@ test('media query raw', () => { customAtRules: { breakpoints: { prelude: null, - body: "rule-list", + body: 'rule-list', }, }, visitor: { Rule: { custom: { - breakpoints({ body, loc }) { - /** @type {import('lightningcss').ReturnedRule[]} */ - const value = []; + breakpoints({body, loc}) { + const value: ReturnedRule[] = []; for (let rule of body.value) { if (rule.type !== 'style') { @@ -1061,27 +1179,27 @@ test('media query raw', () => { value.push(rule); value.push({ - type: "media", + type: 'media', value: { rules: [clone], loc, query: { - mediaQueries: [ - { raw: '(min-width: 500px)' } - ] - } - } + mediaQueries: [{raw: '(min-width: 500px)'}], + }, + }, }); } return value; - } - } - } - } + }, + }, + }, + }, }); - assert.equal(res.code.toString(), '.m-1{margin:10px}@media (width>=500px){.sm\\:m-1{margin:10px}}'); + expect(res.code.toString()).toBe( + '.m-1{margin:10px}@media (width>=500px){.sm\\:m-1{margin:10px}}', + ); }); test('visit stylesheet', () => { @@ -1099,13 +1217,17 @@ test('visit stylesheet', () => { `), visitor: { StyleSheetExit(stylesheet) { - stylesheet.rules.sort((a, b) => a.value.selectors[0][0].name.localeCompare(b.value.selectors[0][0].name)); + stylesheet.rules.sort((a, b) => + // @ts-expect-error TODO: fix type error + a.value.selectors[0][0].name.localeCompare( + // @ts-expect-error TODO: fix type error + b.value.selectors[0][0].name, + ), + ); return stylesheet; - } - } + }, + }, }); - assert.equal(res.code.toString(), '.bar{width:80px}.foo{width:32px}'); + expect(res.code.toString()).toBe('.bar{width:80px}.foo{width:32px}'); }); - -test.run(); diff --git a/node/tsconfig.json b/node/tsconfig.json index 9b82eaa4..3e2f817c 100644 --- a/node/tsconfig.json +++ b/node/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "node", "isolatedModules": true, "noEmit": true, - "strict": true + "strict": true, + "skipLibCheck": true } } diff --git a/package.json b/package.json index b4c1f4bf..abcdca53 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "sharp": "^0.33.5", "typescript": "^5.7.2", "util": "^0.12.4", - "uvu": "^0.5.6" + "vitest": "^3.1.2" }, "resolutions": { "lightningcss": "link:." @@ -92,6 +92,6 @@ "website:build": "yarn wasm:build-release && parcel build 'website/*.html' website/playground/index.html", "build-ast": "cargo run --example schema --features jsonschema && node scripts/build-ast.js", "tsc": "tsc -p node/tsconfig.json", - "test": "uvu node/test" + "test": "vitest run node/test" } } diff --git a/vitest.workspace.ts b/vitest.workspace.ts new file mode 100644 index 00000000..c6f8e45b --- /dev/null +++ b/vitest.workspace.ts @@ -0,0 +1,27 @@ +import {defineWorkspace} from 'vitest/config'; + +export default defineWorkspace([ + { + test: { + name: 'Node.js', + environment: 'node', + restoreMocks: true, + }, + }, + + // TODO: enable for browser tests + // { + // test: { + // name: 'Browser', + // restoreMocks: true, + // env: { + // TEST_WASM: 'browser', + // }, + // browser: { + // provider: 'playwright', + // enabled: true, + // instances: [{browser: 'chromium'}], + // }, + // }, + // }, +]); diff --git a/yarn.lock b/yarn.lock index 2c0ecdf5..cf96d847 100644 --- a/yarn.lock +++ b/yarn.lock @@ -238,116 +238,241 @@ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f" integrity sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA== +"@esbuild/aix-ppc64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz#b87036f644f572efb2b3c75746c97d1d2d87ace8" + integrity sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag== + "@esbuild/android-arm64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4" integrity sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA== +"@esbuild/android-arm64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz#5ca7dc20a18f18960ad8d5e6ef5cf7b0a256e196" + integrity sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w== + "@esbuild/android-arm@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824" integrity sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w== +"@esbuild/android-arm@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.2.tgz#3c49f607b7082cde70c6ce0c011c362c57a194ee" + integrity sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA== + "@esbuild/android-x64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d" integrity sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew== +"@esbuild/android-x64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.2.tgz#8a00147780016aff59e04f1036e7cb1b683859e2" + integrity sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg== + "@esbuild/darwin-arm64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz#cbee41e988020d4b516e9d9e44dd29200996275e" integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g== +"@esbuild/darwin-arm64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz#486efe7599a8d90a27780f2bb0318d9a85c6c423" + integrity sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA== + "@esbuild/darwin-x64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd" integrity sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A== +"@esbuild/darwin-x64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz#95ee222aacf668c7a4f3d7ee87b3240a51baf374" + integrity sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA== + "@esbuild/freebsd-arm64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487" integrity sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA== +"@esbuild/freebsd-arm64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz#67efceda8554b6fc6a43476feba068fb37fa2ef6" + integrity sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w== + "@esbuild/freebsd-x64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c" integrity sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg== +"@esbuild/freebsd-x64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz#88a9d7ecdd3adadbfe5227c2122d24816959b809" + integrity sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ== + "@esbuild/linux-arm64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b" integrity sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA== +"@esbuild/linux-arm64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz#87be1099b2bbe61282333b084737d46bc8308058" + integrity sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g== + "@esbuild/linux-arm@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef" integrity sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w== +"@esbuild/linux-arm@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz#72a285b0fe64496e191fcad222185d7bf9f816f6" + integrity sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g== + "@esbuild/linux-ia32@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601" integrity sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA== +"@esbuild/linux-ia32@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz#337a87a4c4dd48a832baed5cbb022be20809d737" + integrity sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ== + "@esbuild/linux-loong64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299" integrity sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA== +"@esbuild/linux-loong64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz#1b81aa77103d6b8a8cfa7c094ed3d25c7579ba2a" + integrity sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w== + "@esbuild/linux-mips64el@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec" integrity sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w== +"@esbuild/linux-mips64el@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz#afbe380b6992e7459bf7c2c3b9556633b2e47f30" + integrity sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q== + "@esbuild/linux-ppc64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8" integrity sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg== +"@esbuild/linux-ppc64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz#6bf8695cab8a2b135cca1aa555226dc932d52067" + integrity sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g== + "@esbuild/linux-riscv64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf" integrity sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg== +"@esbuild/linux-riscv64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz#43c2d67a1a39199fb06ba978aebb44992d7becc3" + integrity sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw== + "@esbuild/linux-s390x@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8" integrity sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg== +"@esbuild/linux-s390x@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz#419e25737ec815c6dce2cd20d026e347cbb7a602" + integrity sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q== + "@esbuild/linux-x64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78" integrity sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg== +"@esbuild/linux-x64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz#22451f6edbba84abe754a8cbd8528ff6e28d9bcb" + integrity sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg== + +"@esbuild/netbsd-arm64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz#744affd3b8d8236b08c5210d828b0698a62c58ac" + integrity sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw== + "@esbuild/netbsd-x64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b" integrity sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA== +"@esbuild/netbsd-x64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz#dbbe7521fd6d7352f34328d676af923fc0f8a78f" + integrity sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg== + +"@esbuild/openbsd-arm64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz#f9caf987e3e0570500832b487ce3039ca648ce9f" + integrity sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg== + "@esbuild/openbsd-x64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0" integrity sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw== +"@esbuild/openbsd-x64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz#d2bb6a0f8ffea7b394bb43dfccbb07cabd89f768" + integrity sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw== + "@esbuild/sunos-x64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30" integrity sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA== +"@esbuild/sunos-x64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz#49b437ed63fe333b92137b7a0c65a65852031afb" + integrity sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA== + "@esbuild/win32-arm64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae" integrity sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A== +"@esbuild/win32-arm64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz#081424168463c7d6c7fb78f631aede0c104373cf" + integrity sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q== + "@esbuild/win32-ia32@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67" integrity sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ== +"@esbuild/win32-ia32@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz#3f9e87143ddd003133d21384944a6c6cadf9693f" + integrity sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg== + "@esbuild/win32-x64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae" integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA== +"@esbuild/win32-x64@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz#839f72c2decd378f86b8f525e1979a97b920c67d" + integrity sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA== + "@img/sharp-darwin-arm64@0.33.5": version "0.33.5" resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" @@ -480,7 +605,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== @@ -1301,6 +1426,106 @@ "@parcel/utils" "2.13.3" nullthrows "^1.1.1" +"@rollup/rollup-android-arm-eabi@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz#d964ee8ce4d18acf9358f96adc408689b6e27fe3" + integrity sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg== + +"@rollup/rollup-android-arm64@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz#9b5e130ecc32a5fc1e96c09ff371743ee71a62d3" + integrity sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w== + +"@rollup/rollup-darwin-arm64@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz#ef439182c739b20b3c4398cfc03e3c1249ac8903" + integrity sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ== + +"@rollup/rollup-darwin-x64@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz#d7380c1531ab0420ca3be16f17018ef72dd3d504" + integrity sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA== + +"@rollup/rollup-freebsd-arm64@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz#cbcbd7248823c6b430ce543c59906dd3c6df0936" + integrity sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg== + +"@rollup/rollup-freebsd-x64@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz#96bf6ff875bab5219c3472c95fa6eb992586a93b" + integrity sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw== + +"@rollup/rollup-linux-arm-gnueabihf@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz#d80cd62ce6d40f8e611008d8dbf03b5e6bbf009c" + integrity sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA== + +"@rollup/rollup-linux-arm-musleabihf@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz#75440cfc1e8d0f87a239b4c31dfeaf4719b656b7" + integrity sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg== + +"@rollup/rollup-linux-arm64-gnu@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz#ac527485ecbb619247fb08253ec8c551a0712e7c" + integrity sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg== + +"@rollup/rollup-linux-arm64-musl@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz#74d2b5cb11cf714cd7d1682e7c8b39140e908552" + integrity sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ== + +"@rollup/rollup-linux-loongarch64-gnu@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz#a0a310e51da0b5fea0e944b0abd4be899819aef6" + integrity sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg== + +"@rollup/rollup-linux-powerpc64le-gnu@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz#4077e2862b0ac9f61916d6b474d988171bd43b83" + integrity sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw== + +"@rollup/rollup-linux-riscv64-gnu@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz#5812a1a7a2f9581cbe12597307cc7ba3321cf2f3" + integrity sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA== + +"@rollup/rollup-linux-riscv64-musl@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz#973aaaf4adef4531375c36616de4e01647f90039" + integrity sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ== + +"@rollup/rollup-linux-s390x-gnu@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz#9bad59e907ba5bfcf3e9dbd0247dfe583112f70b" + integrity sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw== + +"@rollup/rollup-linux-x64-gnu@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz#68b045a720bd9b4d905f462b997590c2190a6de0" + integrity sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ== + +"@rollup/rollup-linux-x64-musl@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz#8e703e2c2ad19ba7b2cb3d8c3a4ad11d4ee3a282" + integrity sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw== + +"@rollup/rollup-win32-arm64-msvc@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz#c5bee19fa670ff5da5f066be6a58b4568e9c650b" + integrity sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ== + +"@rollup/rollup-win32-ia32-msvc@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz#846e02c17044bd922f6f483a3b4d36aac6e2b921" + integrity sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA== + +"@rollup/rollup-win32-x64-msvc@4.40.0": + version "4.40.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz#fd92d31a2931483c25677b9c6698106490cbbc76" + integrity sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ== + "@swc/core-darwin-arm64@1.10.1": version "1.10.1" resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.1.tgz#faaaab19b4a039ae67ef661c0144a6f20fe8a78e" @@ -1394,6 +1619,11 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== +"@types/estree@1.0.7", "@types/estree@^1.0.0": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8" + integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== + "@types/glob@^7.1.3": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -1436,6 +1666,65 @@ dependencies: "@types/node" "*" +"@vitest/expect@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.1.2.tgz#b203a7ad2efa6af96c85f6c116216bda259d2bc8" + integrity sha512-O8hJgr+zREopCAqWl3uCVaOdqJwZ9qaDwUP7vy3Xigad0phZe9APxKhPcDNqYYi0rX5oMvwJMSCAXY2afqeTSA== + dependencies: + "@vitest/spy" "3.1.2" + "@vitest/utils" "3.1.2" + chai "^5.2.0" + tinyrainbow "^2.0.0" + +"@vitest/mocker@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-3.1.2.tgz#1ff239036072feb543ab56825ada09b12a075af2" + integrity sha512-kOtd6K2lc7SQ0mBqYv/wdGedlqPdM/B38paPY+OwJ1XiNi44w3Fpog82UfOibmHaV9Wod18A09I9SCKLyDMqgw== + dependencies: + "@vitest/spy" "3.1.2" + estree-walker "^3.0.3" + magic-string "^0.30.17" + +"@vitest/pretty-format@3.1.2", "@vitest/pretty-format@^3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.1.2.tgz#689b0604c0b73fdccb144f11b64d70c9233b23b8" + integrity sha512-R0xAiHuWeDjTSB3kQ3OQpT8Rx3yhdOAIm/JM4axXxnG7Q/fS8XUwggv/A4xzbQA+drYRjzkMnpYnOGAc4oeq8w== + dependencies: + tinyrainbow "^2.0.0" + +"@vitest/runner@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-3.1.2.tgz#ffeba74618046221e944e94f09b565af772170cf" + integrity sha512-bhLib9l4xb4sUMPXnThbnhX2Yi8OutBMA8Yahxa7yavQsFDtwY/jrUZwpKp2XH9DhRFJIeytlyGpXCqZ65nR+g== + dependencies: + "@vitest/utils" "3.1.2" + pathe "^2.0.3" + +"@vitest/snapshot@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-3.1.2.tgz#46c52a417afbf1fe94fba0a5735cbedf9cfc60f6" + integrity sha512-Q1qkpazSF/p4ApZg1vfZSQ5Yw6OCQxVMVrLjslbLFA1hMDrT2uxtqMaw8Tc/jy5DLka1sNs1Y7rBcftMiaSH/Q== + dependencies: + "@vitest/pretty-format" "3.1.2" + magic-string "^0.30.17" + pathe "^2.0.3" + +"@vitest/spy@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-3.1.2.tgz#3a5be04d71c4a458c8d6859503626e2aed61bcbf" + integrity sha512-OEc5fSXMws6sHVe4kOFyDSj/+4MSwst0ib4un0DlcYgQvRuYQ0+M2HyqGaauUMnjq87tmUaMNDxKQx7wNfVqPA== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-3.1.2.tgz#f3ae55b3a205c88c346a2a8dcde7c89210364932" + integrity sha512-5GGd0ytZ7BH3H6JTj9Kw7Prn1Nbg0wZVrIvou+UWxm54d+WoXXgAgjFJ8wn3LdagWLFSEfpPeyYrByZaGEZHLg== + dependencies: + "@vitest/pretty-format" "3.1.2" + loupe "^3.1.3" + tinyrainbow "^2.0.0" + "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" @@ -1493,6 +1782,11 @@ assert@^2.0.0: object.assign "^4.1.4" util "^0.12.5" +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + ast-types@0.15.2: version "0.15.2" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.15.2.tgz#39ae4809393c4b16df751ee563411423e85fb49d" @@ -1603,6 +1897,11 @@ buffer@^5.2.1, buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" @@ -1659,6 +1958,17 @@ caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001704: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001704.tgz#6644fe909d924ac3a7125e8a0ab6af95b1f32990" integrity sha512-+L2IgBbV6gXB4ETf0keSvLr7JUrRVbIaB/lrQ1+z8mRcQiisG5k+lG6O4n6Y5q6f5EuNfaYXKgymucphlEXQew== +chai@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.2.0.tgz#1358ee106763624114addf84ab02697e411c9c05" + integrity sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -1676,6 +1986,11 @@ chalk@^4.0.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -1922,7 +2237,7 @@ data-uri-to-buffer@^4.0.0: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== -debug@4, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -1936,6 +2251,11 @@ debug@4.3.2: dependencies: ms "2.1.2" +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -1954,11 +2274,6 @@ define-properties@^1.1.3, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -dequal@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" - integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== - detect-libc@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" @@ -1979,11 +2294,6 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== -diff@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" - integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== - dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" @@ -2121,6 +2431,11 @@ es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-module-lexer@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21" + integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ== + es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" @@ -2194,6 +2509,37 @@ esbuild@^0.19.8: "@esbuild/win32-ia32" "0.19.12" "@esbuild/win32-x64" "0.19.12" +esbuild@^0.25.0: + version "0.25.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.2.tgz#55a1d9ebcb3aa2f95e8bba9e900c1a5061bc168b" + integrity sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ== + optionalDependencies: + "@esbuild/aix-ppc64" "0.25.2" + "@esbuild/android-arm" "0.25.2" + "@esbuild/android-arm64" "0.25.2" + "@esbuild/android-x64" "0.25.2" + "@esbuild/darwin-arm64" "0.25.2" + "@esbuild/darwin-x64" "0.25.2" + "@esbuild/freebsd-arm64" "0.25.2" + "@esbuild/freebsd-x64" "0.25.2" + "@esbuild/linux-arm" "0.25.2" + "@esbuild/linux-arm64" "0.25.2" + "@esbuild/linux-ia32" "0.25.2" + "@esbuild/linux-loong64" "0.25.2" + "@esbuild/linux-mips64el" "0.25.2" + "@esbuild/linux-ppc64" "0.25.2" + "@esbuild/linux-riscv64" "0.25.2" + "@esbuild/linux-s390x" "0.25.2" + "@esbuild/linux-x64" "0.25.2" + "@esbuild/netbsd-arm64" "0.25.2" + "@esbuild/netbsd-x64" "0.25.2" + "@esbuild/openbsd-arm64" "0.25.2" + "@esbuild/openbsd-x64" "0.25.2" + "@esbuild/sunos-x64" "0.25.2" + "@esbuild/win32-arm64" "0.25.2" + "@esbuild/win32-ia32" "0.25.2" + "@esbuild/win32-x64" "0.25.2" + escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -2219,6 +2565,13 @@ esprima@~4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + event-emitter@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" @@ -2227,6 +2580,11 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" +expect-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.1.tgz#af76d8b357cf5fa76c41c09dafb79c549e75f71f" + integrity sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw== + ext@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" @@ -2257,6 +2615,11 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fdir@^6.4.3, fdir@^6.4.4: + version "6.4.4" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.4.tgz#1cfcf86f875a883e19a8fab53622cfe992e8d2f9" + integrity sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg== + fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -2340,6 +2703,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" @@ -2724,11 +3092,6 @@ klaw-sync@^6.0.0: dependencies: graceful-fs "^4.1.11" -kleur@^4.0.3: - version "4.1.5" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" - integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== - lightningcss@^1.22.1: version "0.0.0" uid "" @@ -2794,6 +3157,11 @@ lodash@^4.17.20, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +loupe@^3.1.0, loupe@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.3.tgz#042a8f7986d77f3d0f98ef7990a2b2fef18b0fd2" + integrity sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug== + lru-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" @@ -2801,6 +3169,13 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" +magic-string@^0.30.17: + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + markdown-it-anchor@^8.6.6: version "8.6.7" resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz#ee6926daf3ad1ed5e4e3968b1740eef1c6399634" @@ -2898,11 +3273,6 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mri@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" - integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -2948,6 +3318,11 @@ nanoid@^3.3.7: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== +nanoid@^3.3.8: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + napi-wasm@^1.0.1: version "1.1.3" resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.3.tgz#7bb95c88e6561f84880bb67195437b1cfbe99224" @@ -3187,6 +3562,16 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -3202,6 +3587,11 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + pkg-dir@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -3435,6 +3825,15 @@ postcss@^8.3.11: picocolors "^1.1.1" source-map-js "^1.2.1" +postcss@^8.5.3: + version "8.5.3" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" + integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== + dependencies: + nanoid "^3.3.8" + picocolors "^1.1.1" + source-map-js "^1.2.1" + posthtml-expressions@^1.7.1: version "1.11.4" resolved "https://registry.yarnpkg.com/posthtml-expressions/-/posthtml-expressions-1.11.4.tgz#eb86666de10940268a74fe0f3fb62d3f7607b5de" @@ -3676,12 +4075,34 @@ rimraf@^2.6.3: dependencies: glob "^7.1.3" -sade@^1.7.3: - version "1.8.1" - resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" - integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== +rollup@^4.34.9: + version "4.40.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.40.0.tgz#13742a615f423ccba457554f006873d5a4de1920" + integrity sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w== dependencies: - mri "^1.1.0" + "@types/estree" "1.0.7" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.40.0" + "@rollup/rollup-android-arm64" "4.40.0" + "@rollup/rollup-darwin-arm64" "4.40.0" + "@rollup/rollup-darwin-x64" "4.40.0" + "@rollup/rollup-freebsd-arm64" "4.40.0" + "@rollup/rollup-freebsd-x64" "4.40.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.40.0" + "@rollup/rollup-linux-arm-musleabihf" "4.40.0" + "@rollup/rollup-linux-arm64-gnu" "4.40.0" + "@rollup/rollup-linux-arm64-musl" "4.40.0" + "@rollup/rollup-linux-loongarch64-gnu" "4.40.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.40.0" + "@rollup/rollup-linux-riscv64-gnu" "4.40.0" + "@rollup/rollup-linux-riscv64-musl" "4.40.0" + "@rollup/rollup-linux-s390x-gnu" "4.40.0" + "@rollup/rollup-linux-x64-gnu" "4.40.0" + "@rollup/rollup-linux-x64-musl" "4.40.0" + "@rollup/rollup-win32-arm64-msvc" "4.40.0" + "@rollup/rollup-win32-ia32-msvc" "4.40.0" + "@rollup/rollup-win32-x64-msvc" "4.40.0" + fsevents "~2.3.2" safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" @@ -3760,6 +4181,11 @@ shelljs@^0.8.4: interpret "^1.0.0" rechoir "^0.6.2" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -3787,6 +4213,16 @@ srcset@4: resolved "https://registry.yarnpkg.com/srcset/-/srcset-4.0.0.tgz#336816b665b14cd013ba545b6fe62357f86e65f4" integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw== +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.9.0.tgz#1a6f7243b339dca4c9fd55e1c7504c77ef23e8f1" + integrity sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw== + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -3897,6 +4333,39 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A== +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinyglobby@^0.2.12, tinyglobby@^0.2.13: + version "0.2.13" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.13.tgz#a0e46515ce6cbcd65331537e57484af5a7b2ff7e" + integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw== + dependencies: + fdir "^6.4.4" + picomatch "^4.0.2" + +tinypool@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2" + integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA== + +tinyrainbow@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz#9509b2162436315e80e3eee0fcce4474d2444294" + integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -3998,15 +4467,57 @@ utility-types@^3.10.0: resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c" integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== -uvu@^0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df" - integrity sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA== - dependencies: - dequal "^2.0.0" - diff "^5.0.0" - kleur "^4.0.3" - sade "^1.7.3" +vite-node@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.1.2.tgz#b17869a12307f5260b20ba4b58cf493afee70aa7" + integrity sha512-/8iMryv46J3aK13iUXsei5G/A3CUlW4665THCPS+K8xAaqrVWiGB4RfXMQXCLjpK9P2eK//BczrVkn5JLAk6DA== + dependencies: + cac "^6.7.14" + debug "^4.4.0" + es-module-lexer "^1.6.0" + pathe "^2.0.3" + vite "^5.0.0 || ^6.0.0" + +"vite@^5.0.0 || ^6.0.0": + version "6.3.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.3.2.tgz#4c1bb01b1cea853686a191657bbc14272a038f0a" + integrity sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg== + dependencies: + esbuild "^0.25.0" + fdir "^6.4.3" + picomatch "^4.0.2" + postcss "^8.5.3" + rollup "^4.34.9" + tinyglobby "^0.2.12" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.1.2.tgz#63afc16b6da3bea6e39f5387d80719e70634ba66" + integrity sha512-WaxpJe092ID1C0mr+LH9MmNrhfzi8I65EX/NRU/Ld016KqQNRgxSOlGNP1hHN+a/F8L15Mh8klwaF77zR3GeDQ== + dependencies: + "@vitest/expect" "3.1.2" + "@vitest/mocker" "3.1.2" + "@vitest/pretty-format" "^3.1.2" + "@vitest/runner" "3.1.2" + "@vitest/snapshot" "3.1.2" + "@vitest/spy" "3.1.2" + "@vitest/utils" "3.1.2" + chai "^5.2.0" + debug "^4.4.0" + expect-type "^1.2.1" + magic-string "^0.30.17" + pathe "^2.0.3" + std-env "^3.9.0" + tinybench "^2.9.0" + tinyexec "^0.3.2" + tinyglobby "^0.2.13" + tinypool "^1.0.2" + tinyrainbow "^2.0.0" + vite "^5.0.0 || ^6.0.0" + vite-node "3.1.2" + why-is-node-running "^2.3.0" w3c-keyname@^2.2.4: version "2.2.8" @@ -4055,6 +4566,14 @@ which@^1.2.9: dependencies: isexe "^2.0.0" +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"