Set up comprehensive frontend testing infrastructure

- Install Jest for unit testing with React Testing Library
- Install Playwright for end-to-end testing
- Configure Jest with proper TypeScript support and module mapping
- Create test setup files and utilities for both unit and e2e tests

Components:
* Jest configuration with coverage thresholds
* Playwright configuration with browser automation
* Unit tests for LoginForm, AuthContext, and useSocketIO hook
* E2E tests for authentication, dashboard, and agents workflows
* GitHub Actions workflow for automated testing
* Mock data and API utilities for consistent testing
* Test documentation with best practices

Testing features:
- Unit tests with 70% coverage threshold
- E2E tests with API mocking and user journey testing
- CI/CD integration for automated test runs
- Cross-browser testing support with Playwright
- Authentication system testing end-to-end

🚀 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-11 14:06:34 +10:00
parent c6d69695a8
commit aacb45156b
6109 changed files with 777927 additions and 1 deletions

View File

@@ -0,0 +1,9 @@
# Changes to CSS Parser Algorithms
### 3.0.5
_May 27, 2025_
- Updated [`@csstools/css-tokenizer`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer) to [`3.0.4`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer/CHANGELOG.md#304) (patch)
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms/CHANGELOG.md)

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright 2022 Romain Menke, Antonio Laguna <antonio@laguna.es>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,119 @@
# CSS Parser Algorithms <img src="https://cssdb.org/images/css.svg" alt="for CSS" width="90" height="90" align="right">
[<img alt="npm version" src="https://img.shields.io/npm/v/@csstools/css-parser-algorithms.svg" height="20">][npm-url]
[<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/actions/workflows/test.yml/badge.svg?branch=main" height="20">][cli-url]
[<img alt="Discord" src="https://shields.io/badge/Discord-5865F2?logo=discord&logoColor=white">][discord]
Implemented from : https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/
## API
[Read the API docs](./docs/css-parser-algorithms.md)
## Usage
Add [CSS Parser Algorithms] to your project:
```bash
npm install @csstools/css-parser-algorithms @csstools/css-tokenizer --save-dev
```
[CSS Parser Algorithms] only accepts tokenized CSS.
It must be used together with `@csstools/css-tokenizer`.
```js
import { tokenizer, TokenType } from '@csstools/css-tokenizer';
import { parseComponentValue } from '@csstools/css-parser-algorithms';
const myCSS = `@media only screen and (min-width: 768rem) {
.foo {
content: 'Some content!' !important;
}
}
`;
const t = tokenizer({
css: myCSS,
});
const tokens = [];
{
while (!t.endOfFile()) {
tokens.push(t.nextToken());
}
tokens.push(t.nextToken()); // EOF-token
}
const options = {
onParseError: ((err) => {
throw err;
}),
};
const result = parseComponentValue(tokens, options);
console.log(result);
```
### Available functions
- [`parseComponentValue`](https://www.w3.org/TR/css-syntax-3/#parse-component-value)
- [`parseListOfComponentValues`](https://www.w3.org/TR/css-syntax-3/#parse-list-of-component-values)
- [`parseCommaSeparatedListOfComponentValues`](https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values)
### Utilities
#### `gatherNodeAncestry`
The AST does not expose the entire ancestry of each node.
The walker methods do provide access to the current parent, but also not the entire ancestry.
To gather the entire ancestry for a a given sub tree of the AST you can use `gatherNodeAncestry`.
The result is a `Map` with the child nodes as keys and the parents as values.
This allows you to lookup any ancestor of any node.
```js
import { parseComponentValue } from '@csstools/css-parser-algorithms';
const result = parseComponentValue(tokens, options);
const ancestry = gatherNodeAncestry(result);
```
### Options
```ts
{
onParseError?: (error: ParseError) => void
}
```
#### `onParseError`
The parser algorithms are forgiving and won't stop when a parse error is encountered.
Parse errors also aren't tokens.
To receive parsing error information you can set a callback.
Parser errors will try to inform you about the point in the parsing logic the error happened.
This tells you the kind of error.
## Goals and non-goals
Things this package aims to be:
- specification compliant CSS parser
- a reliable low level package to be used in CSS sub-grammars
What it is not:
- opinionated
- fast
- small
- a replacement for PostCSS (PostCSS is fast and also an ecosystem)
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
[discord]: https://discord.gg/bUadyRwkJS
[npm-url]: https://www.npmjs.com/package/@csstools/css-parser-algorithms
[CSS Parser Algorithms]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,604 @@
/**
* Parse CSS following the {@link https://drafts.csswg.org/css-syntax/#parsing | CSS Syntax Level 3 specification}.
*
* @remarks
* The tokenizing and parsing tools provided by CSS Tools are designed to be low level and generic with strong ties to their respective specifications.
*
* Any analysis or mutation of CSS source code should be done with the least powerful tool that can accomplish the task.
* For many applications it is sufficient to work with tokens.
* For others you might need to use {@link https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms | @csstools/css-parser-algorithms} or a more specific parser.
*
* The implementation of the AST nodes is kept lightweight and simple.
* Do not expect magic methods, instead assume that arrays and class instances behave like any other JavaScript.
*
* @example
* Parse a string of CSS into a component value:
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseComponentValue } from '@csstools/css-parser-algorithms';
*
* const myCSS = `calc(1px * 2)`;
*
* const componentValue = parseComponentValue(tokenize({
* css: myCSS,
* }));
*
* console.log(componentValue);
* ```
*
* @example
* Use the right algorithm for the job.
*
* Algorithms that can parse larger structures (comma-separated lists, ...) can also parse smaller structures.
* However, the opposite is not true.
*
* If your context allows a list of component values, use {@link parseListOfComponentValues}:
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseListOfComponentValues } from '@csstools/css-parser-algorithms';
*
* parseListOfComponentValues(tokenize({ css: `10x 20px` }));
* ```
*
* If your context allows a comma-separated list of component values, use {@link parseCommaSeparatedListOfComponentValues}:
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
*
* parseCommaSeparatedListOfComponentValues(tokenize({ css: `20deg, 50%, 30%` }));
* ```
*
* @example
* Use the stateful walkers to keep track of the context of a given component value.
*
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseComponentValue, isSimpleBlockNode } from '@csstools/css-parser-algorithms';
*
* const myCSS = `calc(1px * (5 / 2))`;
*
* const componentValue = parseComponentValue(tokenize({ css: myCSS }));
*
* let state = { inSimpleBlock: false };
* componentValue.walk((entry) => {
* if (isSimpleBlockNode(entry)) {
* entry.state.inSimpleBlock = true;
* return;
* }
*
* if (entry.state.inSimpleBlock) {
* console.log(entry.node.toString()); // `5`, ...
* }
* }, state);
* ```
*
* @packageDocumentation
*/
import type { CSSToken } from '@csstools/css-tokenizer';
import { ParseError } from '@csstools/css-tokenizer';
import type { TokenFunction } from '@csstools/css-tokenizer';
export declare class CommentNode {
/**
* The node type, always `ComponentValueType.Comment`
*/
type: ComponentValueType;
/**
* The comment token.
*/
value: CSSToken;
constructor(value: CSSToken);
/**
* Retrieve the tokens for the current comment.
* This is the inverse of parsing from a list of tokens.
*/
tokens(): Array<CSSToken>;
/**
* Convert the current comment to a string.
* This is not a true serialization.
* It is purely a concatenation of the string representation of the tokens.
*/
toString(): string;
/**
* @internal
*
* A debug helper to convert the current object to a JSON representation.
* This is useful in asserts and to store large ASTs in files.
*/
toJSON(): Record<string, unknown>;
/**
* @internal
*/
isCommentNode(): this is CommentNode;
/**
* @internal
*/
static isCommentNode(x: unknown): x is CommentNode;
}
export declare type ComponentValue = FunctionNode | SimpleBlockNode | WhitespaceNode | CommentNode | TokenNode;
export declare enum ComponentValueType {
Function = "function",
SimpleBlock = "simple-block",
Whitespace = "whitespace",
Comment = "comment",
Token = "token"
}
export declare type ContainerNode = FunctionNode | SimpleBlockNode;
export declare abstract class ContainerNodeBaseClass {
/**
* The contents of the `Function` or `Simple Block`.
* This is a list of component values.
*/
value: Array<ComponentValue>;
/**
* Retrieve the index of the given item in the current node.
* For most node types this will be trivially implemented as `this.value.indexOf(item)`.
*/
indexOf(item: ComponentValue): number | string;
/**
* Retrieve the item at the given index in the current node.
* For most node types this will be trivially implemented as `this.value[index]`.
*/
at(index: number | string): ComponentValue | undefined;
/**
* Iterates over each item in the `value` array of the current node.
*
* @param cb - The callback function to execute for each item.
* The function receives an object containing the current node (`node`), its parent (`parent`),
* and an optional `state` object.
* A second parameter is the index of the current node.
* The function can return `false` to stop the iteration.
*
* @param state - An optional state object that can be used to pass additional information to the callback function.
* The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
*
* @returns `false` if the iteration was halted, `undefined` otherwise.
*/
forEach<T extends Record<string, unknown>, U extends ContainerNode>(this: U, cb: (entry: {
node: ComponentValue;
parent: ContainerNode;
state?: T;
}, index: number | string) => boolean | void, state?: T): false | undefined;
/**
* Walks the current node and all its children.
*
* @param cb - The callback function to execute for each item.
* The function receives an object containing the current node (`node`), its parent (`parent`),
* and an optional `state` object.
* A second parameter is the index of the current node.
* The function can return `false` to stop the iteration.
*
* @param state - An optional state object that can be used to pass additional information to the callback function.
* The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
* However changes are passed down to child node iterations.
*
* @returns `false` if the iteration was halted, `undefined` otherwise.
*/
walk<T extends Record<string, unknown>, U extends ContainerNode>(this: U, cb: (entry: {
node: ComponentValue;
parent: ContainerNode;
state?: T;
}, index: number | string) => boolean | void, state?: T): false | undefined;
}
/**
* Iterates over each item in a list of component values.
*
* @param cb - The callback function to execute for each item.
* The function receives an object containing the current node (`node`), its parent (`parent`),
* and an optional `state` object.
* A second parameter is the index of the current node.
* The function can return `false` to stop the iteration.
*
* @param state - An optional state object that can be used to pass additional information to the callback function.
* The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
*
* @returns `false` if the iteration was halted, `undefined` otherwise.
*/
export declare function forEach<T extends Record<string, unknown>>(componentValues: Array<ComponentValue>, cb: (entry: {
node: ComponentValue;
parent: ContainerNode | {
value: Array<ComponentValue>;
};
state?: T;
}, index: number | string) => boolean | void, state?: T): false | undefined;
/**
* A function node.
*
* @example
* ```js
* const node = parseComponentValue(tokenize('calc(1 + 1)'));
*
* isFunctionNode(node); // true
* node.getName(); // 'calc'
* ```
*/
export declare class FunctionNode extends ContainerNodeBaseClass {
/**
* The node type, always `ComponentValueType.Function`
*/
type: ComponentValueType;
/**
* The token for the name of the function.
*/
name: TokenFunction;
/**
* The token for the closing parenthesis of the function.
* If the function is unclosed, this will be an EOF token.
*/
endToken: CSSToken;
constructor(name: TokenFunction, endToken: CSSToken, value: Array<ComponentValue>);
/**
* Retrieve the name of the current function.
* This is the parsed and unescaped name of the function.
*/
getName(): string;
/**
* Normalize the current function:
* 1. if the "endToken" is EOF, replace with a ")-token"
*/
normalize(): void;
/**
* Retrieve the tokens for the current function.
* This is the inverse of parsing from a list of tokens.
*/
tokens(): Array<CSSToken>;
/**
* Convert the current function to a string.
* This is not a true serialization.
* It is purely a concatenation of the string representation of the tokens.
*/
toString(): string;
/**
* @internal
*
* A debug helper to convert the current object to a JSON representation.
* This is useful in asserts and to store large ASTs in files.
*/
toJSON(): unknown;
/**
* @internal
*/
isFunctionNode(): this is FunctionNode;
/**
* @internal
*/
static isFunctionNode(x: unknown): x is FunctionNode;
}
/**
* AST nodes do not have a `parent` property or method.
* This makes it harder to traverse the AST upwards.
* This function builds a `Map<Child, Parent>` that can be used to lookup ancestors of a node.
*
* @remarks
* There is no magic behind this or the map it returns.
* Mutating the AST will not update the map.
*
* Types are erased and any content of the map has type `unknown`.
* If someone knows a clever way to type this, please let us know.
*
* @example
* ```js
* const ancestry = gatherNodeAncestry(mediaQuery);
* mediaQuery.walk((entry) => {
* const node = entry.node; // directly exposed
* const parent = entry.parent; // directly exposed
* const grandParent: unknown = ancestry.get(parent); // lookup
*
* console.log('node', node);
* console.log('parent', parent);
* console.log('grandParent', grandParent);
* });
* ```
*/
export declare function gatherNodeAncestry(node: {
walk(cb: (entry: {
node: unknown;
parent: unknown;
}, index: number | string) => boolean | void): false | undefined;
}): Map<unknown, unknown>;
/**
* Check if the current object is a `CommentNode`.
* This is a type guard.
*/
export declare function isCommentNode(x: unknown): x is CommentNode;
/**
* Check if the current object is a `FunctionNode`.
* This is a type guard.
*/
export declare function isFunctionNode(x: unknown): x is FunctionNode;
/**
* Check if the current object is a `SimpleBlockNode`.
* This is a type guard.
*/
export declare function isSimpleBlockNode(x: unknown): x is SimpleBlockNode;
/**
* Check if the current object is a `TokenNode`.
* This is a type guard.
*/
export declare function isTokenNode(x: unknown): x is TokenNode;
/**
* Check if the current object is a `WhitespaceNode`.
* This is a type guard.
*/
export declare function isWhitespaceNode(x: unknown): x is WhitespaceNode;
/**
* Check if the current object is a `WhiteSpaceNode` or a `CommentNode`.
* This is a type guard.
*/
export declare function isWhiteSpaceOrCommentNode(x: unknown): x is WhitespaceNode | CommentNode;
/**
* Parse a comma-separated list of component values.
*
* @example
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
*
* parseCommaSeparatedListOfComponentValues(tokenize({ css: `20deg, 50%, 30%` }));
* ```
*/
export declare function parseCommaSeparatedListOfComponentValues(tokens: Array<CSSToken>, options?: {
onParseError?: (error: ParseError) => void;
}): Array<Array<ComponentValue>>;
/**
* Parse a single component value.
*
* @example
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
*
* parseCommaSeparatedListOfComponentValues(tokenize({ css: `10px` }));
* parseCommaSeparatedListOfComponentValues(tokenize({ css: `calc((10px + 1x) * 4)` }));
* ```
*/
export declare function parseComponentValue(tokens: Array<CSSToken>, options?: {
onParseError?: (error: ParseError) => void;
}): ComponentValue | undefined;
/**
* Parse a list of component values.
*
* @example
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseListOfComponentValues } from '@csstools/css-parser-algorithms';
*
* parseListOfComponentValues(tokenize({ css: `20deg 30%` }));
* ```
*/
export declare function parseListOfComponentValues(tokens: Array<CSSToken>, options?: {
onParseError?: (error: ParseError) => void;
}): Array<ComponentValue>;
/**
* Replace specific component values in a list of component values.
* A helper for the most common and simplistic cases when mutating an AST.
*/
export declare function replaceComponentValues(componentValuesList: Array<Array<ComponentValue>>, replaceWith: (componentValue: ComponentValue) => Array<ComponentValue> | ComponentValue | void): Array<Array<ComponentValue>>;
/**
* A simple block node.
*
* @example
* ```js
* const node = parseComponentValue(tokenize('[foo=bar]'));
*
* isSimpleBlockNode(node); // true
* node.startToken; // [TokenType.OpenSquare, '[', 0, 0, undefined]
* ```
*/
export declare class SimpleBlockNode extends ContainerNodeBaseClass {
/**
* The node type, always `ComponentValueType.SimpleBlock`
*/
type: ComponentValueType;
/**
* The token for the opening token of the block.
*/
startToken: CSSToken;
/**
* The token for the closing token of the block.
* If the block is closed it will be the mirror variant of the `startToken`.
* If the block is unclosed, this will be an EOF token.
*/
endToken: CSSToken;
constructor(startToken: CSSToken, endToken: CSSToken, value: Array<ComponentValue>);
/**
* Normalize the current simple block
* 1. if the "endToken" is EOF, replace with the mirror token of the "startToken"
*/
normalize(): void;
/**
* Retrieve the tokens for the current simple block.
* This is the inverse of parsing from a list of tokens.
*/
tokens(): Array<CSSToken>;
/**
* Convert the current simple block to a string.
* This is not a true serialization.
* It is purely a concatenation of the string representation of the tokens.
*/
toString(): string;
/**
* @internal
*
* A debug helper to convert the current object to a JSON representation.
* This is useful in asserts and to store large ASTs in files.
*/
toJSON(): unknown;
/**
* @internal
*/
isSimpleBlockNode(): this is SimpleBlockNode;
/**
* @internal
*/
static isSimpleBlockNode(x: unknown): x is SimpleBlockNode;
}
/**
* Returns the start and end index of a node in the CSS source string.
*/
export declare function sourceIndices(x: {
tokens(): Array<CSSToken>;
} | Array<{
tokens(): Array<CSSToken>;
}>): [number, number];
/**
* Concatenate the string representation of a collection of component values.
* This is not a proper serializer that will handle escaping and whitespace.
* It only produces valid CSS for token lists that are also valid.
*/
export declare function stringify(componentValueLists: Array<Array<ComponentValue>>): string;
export declare class TokenNode {
/**
* The node type, always `ComponentValueType.Token`
*/
type: ComponentValueType;
/**
* The token.
*/
value: CSSToken;
constructor(value: CSSToken);
/**
* This is the inverse of parsing from a list of tokens.
*/
tokens(): [CSSToken];
/**
* Convert the current token to a string.
* This is not a true serialization.
* It is purely the string representation of token.
*/
toString(): string;
/**
* @internal
*
* A debug helper to convert the current object to a JSON representation.
* This is useful in asserts and to store large ASTs in files.
*/
toJSON(): Record<string, unknown>;
/**
* @internal
*/
isTokenNode(): this is TokenNode;
/**
* @internal
*/
static isTokenNode(x: unknown): x is TokenNode;
}
/**
* Walks each item in a list of component values all of their children.
*
* @param cb - The callback function to execute for each item.
* The function receives an object containing the current node (`node`), its parent (`parent`),
* and an optional `state` object.
* A second parameter is the index of the current node.
* The function can return `false` to stop the iteration.
*
* @param state - An optional state object that can be used to pass additional information to the callback function.
* The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
* However changes are passed down to child node iterations.
*
* @returns `false` if the iteration was halted, `undefined` otherwise.
*
* @example
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseListOfComponentValues, isSimpleBlockNode } from '@csstools/css-parser-algorithms';
*
* const myCSS = `calc(1px * (5 / 2)) 10px`;
*
* const componentValues = parseListOfComponentValues(tokenize({ css: myCSS }));
*
* let state = { inSimpleBlock: false };
* walk(componentValues, (entry) => {
* if (isSimpleBlockNode(entry)) {
* entry.state.inSimpleBlock = true;
* return;
* }
*
* if (entry.state.inSimpleBlock) {
* console.log(entry.node.toString()); // `5`, ...
* }
* }, state);
* ```
*/
export declare function walk<T extends Record<string, unknown>>(componentValues: Array<ComponentValue>, cb: (entry: {
node: ComponentValue;
parent: ContainerNode | {
value: Array<ComponentValue>;
};
state?: T;
}, index: number | string) => boolean | void, state?: T): false | undefined;
/**
* Generate a function that finds the next element that should be visited when walking an AST.
* Rules :
* 1. the previous iteration is used as a reference, so any checks are relative to the start of the current iteration.
* 2. the next element always appears after the current index.
* 3. the next element always exists in the list.
* 4. replacing an element does not cause the replaced element to be visited.
* 5. removing an element does not cause elements to be skipped.
* 6. an element added later in the list will be visited.
*/
export declare function walkerIndexGenerator<T>(initialList: Array<T>): (list: Array<T>, child: T, index: number) => number;
export declare class WhitespaceNode {
/**
* The node type, always `ComponentValueType.WhiteSpace`
*/
type: ComponentValueType;
/**
* The list of consecutive whitespace tokens.
*/
value: Array<CSSToken>;
constructor(value: Array<CSSToken>);
/**
* Retrieve the tokens for the current whitespace.
* This is the inverse of parsing from a list of tokens.
*/
tokens(): Array<CSSToken>;
/**
* Convert the current whitespace to a string.
* This is not a true serialization.
* It is purely a concatenation of the string representation of the tokens.
*/
toString(): string;
/**
* @internal
*
* A debug helper to convert the current object to a JSON representation.
* This is useful in asserts and to store large ASTs in files.
*/
toJSON(): Record<string, unknown>;
/**
* @internal
*/
isWhitespaceNode(): this is WhitespaceNode;
/**
* @internal
*/
static isWhitespaceNode(x: unknown): x is WhitespaceNode;
}
export { }

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,65 @@
{
"name": "@csstools/css-parser-algorithms",
"description": "Algorithms to help you parse CSS from an array of tokens.",
"version": "3.0.5",
"contributors": [
{
"name": "Antonio Laguna",
"email": "antonio@laguna.es",
"url": "https://antonio.laguna.es"
},
{
"name": "Romain Menke",
"email": "romainmenke@gmail.com"
}
],
"license": "MIT",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"engines": {
"node": ">=18"
},
"type": "module",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
},
"require": {
"default": "./dist/index.cjs"
}
}
},
"files": [
"CHANGELOG.md",
"LICENSE.md",
"README.md",
"dist"
],
"peerDependencies": {
"@csstools/css-tokenizer": "^3.0.4"
},
"scripts": {},
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/csstools/postcss-plugins.git",
"directory": "packages/css-parser-algorithms"
},
"bugs": "https://github.com/csstools/postcss-plugins/issues",
"keywords": [
"css",
"parser"
]
}