diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 7492a7c..33d8ed3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,124 +1,84 @@ # Copilot Instructions for Bowser -## Project Overview - -Bowser is a small, fast, and rich-API browser/platform/engine detector for both browser and Node.js environments. It's designed to parse User-Agent strings and provide detailed information about browsers, operating systems, platforms, and rendering engines. - ## Architecture -### Core Components +Bowser parses User-Agent strings through a pipeline of four independent parser modules, each exporting an ordered array of descriptors: -- **`src/bowser.js`**: Main entry point and public API. Provides static methods `getParser()` and `parse()`. -- **`src/parser.js`**: Core parsing engine that orchestrates all parsers and returns structured results. -- **`src/parser-browsers.js`**: Browser detection logic using regex patterns. -- **`src/parser-os.js`**: Operating system detection logic. -- **`src/parser-platforms.js`**: Platform type detection (desktop, tablet, mobile). -- **`src/parser-engines.js`**: Rendering engine detection (WebKit, Blink, Gecko, etc.). -- **`src/constants.js`**: Centralized constants including browser aliases and mappings. -- **`src/utils.js`**: Utility functions for string matching and manipulation. +- `src/parser-browsers.js` → `src/parser-os.js` → `src/parser-platforms.js` → `src/parser-engines.js` -### Build Output +`src/parser.js` (the `Parser` class) orchestrates these by iterating each descriptor list using `Utils.find()`, testing via regex arrays or custom `test(parser)` functions, then calling `describe(ua)` on the first match. **Descriptor order matters** — more specific browsers (e.g., Opera, Edge) must come before generic ones (e.g., Chrome, Safari) to avoid false matches. -- **`es5.js`**: ES5 transpiled version (default export). -- **`bundled.js`**: ES5 version with babel-polyfill included. +`src/bowser.js` is a stateless facade exposing `Bowser.getParser(UA)` and `Bowser.parse(UA)`. `src/constants.js` holds bidirectional maps (`BROWSER_ALIASES_MAP` ↔ `BROWSER_MAP`) used for `satisfies()` alias lookups. `src/utils.js` provides regex helpers (`getFirstMatch`, `getSecondMatch`) and version comparison logic. -## Development Workflow +Also supports User-Agent Client Hints via an optional `clientHints` parameter on the constructor. -### Setup +## Commands -```bash -npm install +- **Build**: `npm run build` (Webpack + Babel → `es5.js`, `bundled.js`) +- **Test**: `npm test` (AVA — runs both `test/unit/` and `test/acceptance/`) +- **Lint**: `npm run lint:check` / `npm run lint:fix` + +## Adding Browser/Bot Support (5 files to touch) + +1. **`src/parser-browsers.js`**: Add a descriptor with `test` (regex array) and `describe(ua)` returning `{ name, version }`. Place it above any generic browser it might conflict with. +2. **`src/parser-platforms.js`**: Add a platform descriptor if the new entry is a bot (return `{ type: PLATFORMS_MAP.bot, vendor }`) or needs specific device detection. +3. **`src/constants.js`**: Add entries to both `BROWSER_ALIASES_MAP` (display name → alias) and `BROWSER_MAP` (alias → display name). Alias convention: lowercase, underscores for spaces/dashes, drop "browser" suffix (e.g., `'UC Browser'` → `'uc'`, `'Opera Coast'` → `'opera_coast'`). +4. **`test/acceptance/useragentstrings.yml`**: Add real UA strings under the browser's display name with full `spec` (browser, os, platform, engine). The acceptance test iterates this YAML and asserts `Bowser.parse(ua)` deep-equals the spec. The top-level key **must match** the browser's `name` field exactly. +5. **`index.d.ts`**: Update TypeScript definitions if adding new API surface. + +Run `npm test` to validate — acceptance tests auto-generate from the YAML file. + +## Descriptor Pattern + +Every parser file exports an array of descriptors following this pattern: + +```js +{ + test: [/someregex/i], // array of RegExp, OR: + // test(parser) { return parser.test(/regex/); }, // function form for complex logic + describe(ua) { + return { + name: 'BrowserName', + version: Utils.getFirstMatch(/someregex\/(\d+(\.\d+)+)/i, ua), + }; + }, +} ``` -### Key Commands +Engine descriptors in `src/parser-engines.js` may use `test(parser)` to access `parser.getBrowserName()` for browser-dependent engine detection (e.g., Edge → Blink vs EdgeHTML). -- **Build**: `npm run build` - Compiles source files using Webpack and Babel. -- **Test**: `npm test` - Runs unit and acceptance tests using AVA. -- **Lint**: `npm run lint:check` - Checks code style using ESLint. -- **Lint Fix**: `npm run lint:fix` - Auto-fixes linting issues. -- **Watch Mode**: `npm run watch` - Builds on file changes. -- **Test Watch**: `npm run test:watch` - Runs tests on file changes. +## `satisfies()` Version DSL -### Testing +`Parser.satisfies(checkTree)` accepts a nested object for conditional browser/OS/platform checks. Keys in the check tree are resolved via `BROWSER_ALIASES_MAP` (so both `'chrome'` and `'Chrome'` work). Version operators: `>`, `>=`, `<`, `<=`, `=`, `~` (loose/prefix match). Values can be a single string or an array of version strings (OR logic). -- Tests are located in `test/acceptance/` and `test/unit/`. -- Acceptance tests use real User-Agent strings from `test/acceptance/useragentstrings.yml`. -- Always update `useragentstrings.yml` when adding browser support. -- Test framework: AVA with Babel integration. +```js +// Simple browser check +parser.satisfies({ chrome: '>118', firefox: '~100' }) -## Coding Standards +// Nested by OS or platform +parser.satisfies({ windows: { chrome: '>118' }, macos: { safari: '>=15' } }) +parser.satisfies({ desktop: { chrome: '>118' }, mobile: { safari: '>=15' } }) +``` -### Style Guide +Returns `true`/`false` if the browser matches, or `undefined` if the browser isn't mentioned in the tree. The `~` operator does a loose prefix comparison (e.g., `~100` matches `100.x.x`). -- **ESLint Config**: Based on Airbnb Base style guide. -- **Parser**: Uses `babel-eslint`. -- **Exceptions**: - - Underscore-dangle allowed for private properties. - - `no-void` disabled. - - ES6 imports must include `.js` extension. +## Code Conventions -### Naming Conventions +- ES6 modules with **explicit `.js` extensions** in imports: `import Utils from './utils.js'` +- Airbnb ESLint base; underscore-prefixed private properties (`_ua`, `_hints`) +- Use `void (0)` instead of `undefined` for comparisons (legacy convention) +- JSDoc on all public methods with `@param`, `@return`, `@example` +- Browser versions are always **strings**, never numbers -- **Browser Aliases**: Use lowercase letters, replace spaces/dashes with underscores, drop "browser" suffix. - - Examples: `Opera Coast` → `opera_coast`, `UC Browser` → `uc`, `SeaMonkey` → `seamonkey`. -- **Private Properties**: Prefix with underscore (e.g., `_ua`). -- **Constants**: Use `UPPER_SNAKE_CASE` for constant maps and aliases. +## Testing Details -### Code Patterns +- **Acceptance tests** (`test/acceptance/test-list-of-ua.js`): Loads `useragentstrings.yml`, tests every UA against both `src/bowser.js` (ES6) and `es5.js` (built) — so **build before running tests** if you changed source. +- **Unit tests** (`test/unit/`): Test individual Parser/Bowser/Utils methods. Use `new Parser(UA, true)` (skipParsing=true) for isolated method testing. +- Test framework: AVA with Babel transpilation via `@babel/register`. -- Use ES6 modules with explicit `.js` extensions. -- Prefer static methods in Bowser class. -- Use class-based structure for Parser. -- Regex patterns should be well-documented and tested. -- Keep parsers modular and focused on single responsibility. +## Branching -## Adding Browser Support - -When adding support for a new browser: - -1. Add regex pattern to `src/parser-browsers.js`. -2. Add browser name to `BROWSER_ALIASES_MAP` in `src/constants.js`. -3. Add corresponding entry to `BROWSER_MAP`. -4. Add test cases to `test/acceptance/useragentstrings.yml`. -5. Run tests to verify: `npm test`. -6. Check for duplicates before adding aliases. - -## Branching Strategy - -- **`master`**: Development branch. -- **`production`**: Production branch. -- **New Features**: Branch from `master`, PR back to `master`. -- **Hot-fixes/Browser Support**: Branch from `production`, PR back to `production`. - -## Important Files - -- **`index.d.ts`**: TypeScript definitions. -- **`.babelrc`**: Babel configuration for ES5 transpilation. -- **`webpack.config.js`**: Build configuration. -- **`.eslintrc.yml`**: Linting rules. -- **`package.json`**: Dependencies and scripts. - -## API Design Principles - -- Keep the API simple and intuitive. -- Bowser class should be stateless and provide factory methods. -- Parser class handles instance-specific logic. -- Results should be structured and predictable. -- Support both immediate parsing and lazy parsing. - -## Performance Considerations - -- Parsers use lazy evaluation where possible. -- Regex patterns are optimized for common browsers first. -- Optional `skipParsing` parameter for delayed parsing. -- Minimal bundle size is a priority (~4.8kB gzipped). - -## Documentation - -- Use JSDoc comments for all public APIs. -- Document parameters, return types, and provide examples. -- Update README.md for API changes. -- Generate docs with: `npm run generate-docs`. +- **`master`** is the production branch. ## Common Pitfalls diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..caad724 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,89 @@ +# Agents Instructions for Bowser + +## Architecture + +Bowser parses User-Agent strings through a pipeline of four independent parser modules, each exporting an ordered array of descriptors: + +- `src/parser-browsers.js` → `src/parser-os.js` → `src/parser-platforms.js` → `src/parser-engines.js` + +`src/parser.js` (the `Parser` class) orchestrates these by iterating each descriptor list using `Utils.find()`, testing via regex arrays or custom `test(parser)` functions, then calling `describe(ua)` on the first match. **Descriptor order matters** — more specific browsers (e.g., Opera, Edge) must come before generic ones (e.g., Chrome, Safari) to avoid false matches. + +`src/bowser.js` is a stateless facade exposing `Bowser.getParser(UA)` and `Bowser.parse(UA)`. `src/constants.js` holds bidirectional maps (`BROWSER_ALIASES_MAP` ↔ `BROWSER_MAP`) used for `satisfies()` alias lookups. `src/utils.js` provides regex helpers (`getFirstMatch`, `getSecondMatch`) and version comparison logic. + +Also supports User-Agent Client Hints via an optional `clientHints` parameter on the constructor. + +## Commands + +- **Build**: `npm run build` (Webpack + Babel → `es5.js`, `bundled.js`) +- **Test**: `npm test` (AVA — runs both `test/unit/` and `test/acceptance/`) +- **Lint**: `npm run lint:check` / `npm run lint:fix` + +## Adding Browser/Bot Support (5 files to touch) + +1. **`src/parser-browsers.js`**: Add a descriptor with `test` (regex array) and `describe(ua)` returning `{ name, version }`. Place it above any generic browser it might conflict with. +2. **`src/parser-platforms.js`**: Add a platform descriptor if the new entry is a bot (return `{ type: PLATFORMS_MAP.bot, vendor }`) or needs specific device detection. +3. **`src/constants.js`**: Add entries to both `BROWSER_ALIASES_MAP` (display name → alias) and `BROWSER_MAP` (alias → display name). Alias convention: lowercase, underscores for spaces/dashes, drop "browser" suffix (e.g., `'UC Browser'` → `'uc'`, `'Opera Coast'` → `'opera_coast'`). +4. **`test/acceptance/useragentstrings.yml`**: Add real UA strings under the browser's display name with full `spec` (browser, os, platform, engine). The acceptance test iterates this YAML and asserts `Bowser.parse(ua)` deep-equals the spec. The top-level key **must match** the browser's `name` field exactly. +5. **`index.d.ts`**: Update TypeScript definitions if adding new API surface. + +Run `npm test` to validate — acceptance tests auto-generate from the YAML file. + +## Descriptor Pattern + +Every parser file exports an array of descriptors following this pattern: + +```js +{ + test: [/someregex/i], // array of RegExp, OR: + // test(parser) { return parser.test(/regex/); }, // function form for complex logic + describe(ua) { + return { + name: 'BrowserName', + version: Utils.getFirstMatch(/someregex\/(\d+(\.\d+)+)/i, ua), + }; + }, +} +``` + +Engine descriptors in `src/parser-engines.js` may use `test(parser)` to access `parser.getBrowserName()` for browser-dependent engine detection (e.g., Edge → Blink vs EdgeHTML). + +## `satisfies()` Version DSL + +`Parser.satisfies(checkTree)` accepts a nested object for conditional browser/OS/platform checks. Keys in the check tree are resolved via `BROWSER_ALIASES_MAP` (so both `'chrome'` and `'Chrome'` work). Version operators: `>`, `>=`, `<`, `<=`, `=`, `~` (loose/prefix match). Values can be a single string or an array of version strings (OR logic). + +```js +// Simple browser check +parser.satisfies({ chrome: '>118', firefox: '~100' }) + +// Nested by OS or platform +parser.satisfies({ windows: { chrome: '>118' }, macos: { safari: '>=15' } }) +parser.satisfies({ desktop: { chrome: '>118' }, mobile: { safari: '>=15' } }) +``` + +Returns `true`/`false` if the browser matches, or `undefined` if the browser isn't mentioned in the tree. The `~` operator does a loose prefix comparison (e.g., `~100` matches `100.x.x`). + +## Code Conventions + +- ES6 modules with **explicit `.js` extensions** in imports: `import Utils from './utils.js'` +- Airbnb ESLint base; underscore-prefixed private properties (`_ua`, `_hints`) +- Use `void (0)` instead of `undefined` for comparisons (legacy convention) +- JSDoc on all public methods with `@param`, `@return`, `@example` +- Browser versions are always **strings**, never numbers + +## Testing Details + +- **Acceptance tests** (`test/acceptance/test-list-of-ua.js`): Loads `useragentstrings.yml`, tests every UA against both `src/bowser.js` (ES6) and `es5.js` (built) — so **build before running tests** if you changed source. +- **Unit tests** (`test/unit/`): Test individual Parser/Bowser/Utils methods. Use `new Parser(UA, true)` (skipParsing=true) for isolated method testing. +- Test framework: AVA with Babel transpilation via `@babel/register`. + +## Branching + +- **`master`** is the production branch. + +## Common Pitfalls + +- Always check `BROWSER_ALIASES_MAP` for existing aliases before adding new ones. +- User-Agent strings can be complex; test edge cases thoroughly. +- Remember to update both the alias map and the reverse map in constants. +- Browser versions should be treated as strings, not numbers. +- Keep regex patterns readable with comments explaining their purpose.