Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 151 additions & 31 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ jobs:
echo 'name_suffix=' >> "$GITHUB_OUTPUT"
fi

build-and-test:
name: Test and Build (${{ matrix.os }}, Node ${{ matrix.node }}) ${{ needs.compute-matrix.outputs.name_suffix }}
unit:
name: Units (${{ matrix.os }}, Node ${{ matrix.node }})
needs: compute-matrix
runs-on: ${{ matrix.os }}
permissions:
Expand All @@ -48,55 +48,175 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Typecheck
run: npm run typecheck
- name: Verify test groups
run: npm run test:inventory
- name: Units with coverage (no package build)
if: matrix.os == 'ubuntu-latest' && matrix.node == 22
run: npm run test:run -- --selectProjects unit-node unit-dom --ci --coverage --coverageDirectory=coverage/unit
- name: Units (no package build)
if: matrix.os != 'ubuntu-latest' || matrix.node != 22
run: npm run test:run -- --selectProjects unit-node unit-dom --ci
- name: Upload unit coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == 22
uses: actions/upload-artifact@v4
with:
name: coverage-unit-${{ matrix.os }}-node${{ matrix.node }}
path: coverage/unit/coverage-final.json

build:
name: Build integrations (${{ matrix.os }}, Node ${{ matrix.node }})
needs: compute-matrix
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.compute-matrix.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Build package
run: npm run build
- name: Compiler, CLI and declaration integrations with coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == 22
run: npm run test:run -- --selectProjects build types --ci --maxWorkers=2 --coverage --coverageDirectory=coverage/build
- name: Compiler, CLI and declaration integrations
if: matrix.os != 'ubuntu-latest' || matrix.node != 22
run: npm run test:run -- --selectProjects build types --ci --maxWorkers=2
- name: Upload build coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == 22
uses: actions/upload-artifact@v4
with:
name: coverage-build-${{ matrix.os }}-node${{ matrix.node }}
path: coverage/build/coverage-final.json

fixtures:
name: Fixture types (${{ matrix.os }}, Node ${{ matrix.node }})
needs: compute-matrix
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.compute-matrix.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Build package, prepare and typecheck every application
run: npm run typecheck:integration

browser:
name: Browser (${{ matrix.browser }}, Node ${{ matrix.node }})
needs: compute-matrix
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
node: ${{ fromJSON(needs.compute-matrix.outputs.matrix).node }}
browser: [chrome, firefox]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Set up Chrome
if: matrix.os == 'ubuntu-latest'
if: matrix.browser == 'chrome'
id: chrome
uses: browser-actions/setup-chrome@v2
with:
chrome-version: stable

- name: Install dependencies
run: npm ci

- name: Set up Firefox
if: matrix.os == 'ubuntu-latest'
if: matrix.browser == 'firefox'
id: firefox
uses: browser-actions/setup-firefox@v1
with:
firefox-version: latest

- name: Typecheck
run: npm run typecheck

- name: Typecheck integration fixtures
run: npm run typecheck:integration

- name: Test
if: matrix.os == 'ubuntu-latest'
run: npm run test:ci
- name: Build package
run: npm run build
- name: Browser integrations
run: npm run test:run -- --selectProjects ${{ matrix.browser }} --ci --maxWorkers=2
env:
ADNBN_CHROME_BIN: ${{ steps.chrome.outputs.chrome-path }}
ADNBN_FIREFOX_BIN: ${{ steps.firefox.outputs.firefox-path }}

# Chrome CDP and Firefox BiDi extension scenarios run on the Linux runner.
# The rest of the test suite remains part of the full OS x Node matrix.
- name: Test (non-browser)
if: matrix.os != 'ubuntu-latest'
run: npm run test:ci:non-browser

- name: Build
run: npm run build

- name: Upload coverage artifact
if: always()
coverage:
name: Coverage (ubuntu-latest, Node 22)
needs: [compute-matrix, unit, build]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Download unit coverage
uses: actions/download-artifact@v4
with:
name: coverage-unit-ubuntu-latest-node22
path: coverage/unit
- name: Download build coverage
uses: actions/download-artifact@v4
with:
name: coverage-build-ubuntu-latest-node22
path: coverage/build
- name: Merge source coverage from both groups
run: node scripts/merge-coverage.mjs coverage/unit/coverage-final.json coverage/build/coverage-final.json
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.os }}-node${{ matrix.node }}
path: coverage
name: coverage-ubuntu-latest-node22
path: coverage/merged

# Keep the existing required check names while the work runs in independent jobs.
build-and-test:
name: Test and Build (${{ matrix.os }}, Node ${{ matrix.node }}) ${{ needs.compute-matrix.outputs.name_suffix }}
if: ${{ always() && needs.compute-matrix.result == 'success' }}
needs: [compute-matrix, unit, build, fixtures, browser, coverage]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.compute-matrix.outputs.matrix) }}
steps:
- name: Require all test groups
env:
RESULTS: ${{ join(needs.*.result, ',') }}
run: |
if [[ "$RESULTS" == *failure* || "$RESULTS" == *cancelled* || "$RESULTS" == *skipped* ]]; then
exit 1
fi
9 changes: 2 additions & 7 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
#!/usr/bin/env sh

# Husky pre-commit hook: format and run related tests on staged files

npm run format || exit 1
npm run typecheck || exit 1
npm run test || exit 1


# Check staged formatting; tests and typechecks run at pre-push and in CI.
node scripts/pre-commit.mjs
7 changes: 2 additions & 5 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
#!/usr/bin/env sh

# Husky pre-push hook: typecheck, run full tests, and build

npm run typecheck || exit 1
npm run test:ci || exit 1
npm run build || exit 1
# Browser scenarios and coverage run in CI; build the package only once here.
npm run test:pre-push
25 changes: 25 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,31 @@
- Imports of actual JavaScript files, package subpaths, and loader-specific resources such as `.ts?raw` keep the syntax required by their owner. This rule does not rewrite raw template contents or generated JavaScript imports.
- Apply this convention to new and changed imports; do not mass-rewrite unrelated files.

## Code layout

- Always write `if` and `else` bodies as multiline blocks with braces, even for a single `return`, `throw`, or expression. Never use inline guards such as `if (!current()) return;`.
- Put a blank line before `return` when another statement precedes it in the same block. A `return` that is the first or only statement in a block follows the opening brace directly, as in the example below.
- Separate a multiline statement or code block from adjacent code with a blank line above and below. This includes multiline declarations, calls, callbacks, and control-flow blocks. Do not add empty padding immediately inside opening or closing braces, and keep `} else {`, `} catch (...) {`, and `} finally {` together.
- Apply these rules to implementation code, tests, fixtures, and examples. Keep formatting changes within the files being worked on unless a broader cleanup is explicitly requested.

```ts
if (!current()) {
return;
}

const node = createNode(anchor);

if (!node.target) {
node.unmount();

return;
}

register(node);

return node;
```

## Symbol naming

- Apply these conventions throughout the project: shared types, runtime APIs, entrypoints, CLI, bundler plugins, and tests.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ the browser-specific build and manifest.
npm i -D adnbn
```

TypeScript projects require TypeScript **5.6.3 or newer** to check the published declarations and
their dependencies. Addon Bone's own TypeScript dependency does not replace your project's compiler.

## Get started

Create a project, then build it for production:
Expand Down
78 changes: 74 additions & 4 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type {Config} from "jest";
import {availableParallelism} from "node:os";
import {fileURLToPath} from "node:url";

const config: Config = {
verbose: true,
testEnvironment: "jsdom",
const shared: Config = {
rootDir: import.meta.dirname,
testEnvironment: "node",
globals: {ADNBN_TEST_ROOT: import.meta.dirname},
setupFiles: ["<rootDir>/tests/jest.setup.ts"],
modulePathIgnorePatterns: ["<rootDir>/.cache/"],
Expand Down Expand Up @@ -40,7 +42,75 @@ const config: Config = {
},
],
},
testMatch: ["**/*.test.ts"],
};

const buildTests = [
"<rootDir>/src/cli/bundler/plugins/**/*.test.ts",
"<rootDir>/src/cli/index.test.ts",
"<rootDir>/src/cli/virtual/virtual.test.ts",
"<rootDir>/src/cli/plugins/content/RelayDeclaration.test.ts",
"<rootDir>/src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts",
"<rootDir>/tests/build-output.test.ts",
"<rootDir>/tests/integration/build/**/*.test.ts",
];
const domTests = [
"<rootDir>/src/entry/**/*.test.ts",
"<rootDir>/src/frame/**/*.test.ts",
"<rootDir>/src/sandbox/providers/**/*.test.ts",
"<rootDir>/src/offscreen/OffscreenBridge.test.ts",
"<rootDir>/src/locale/adapters/**/*.test.ts",
"<rootDir>/src/message/adapters/**/*.test.ts",
];
const exclude = (patterns: string[]) =>
patterns.map(pattern => `!${pattern.replace("<rootDir>", import.meta.dirname.replaceAll("\\", "/"))}`);

const config: Config = {
verbose: false,
maxWorkers: Math.max(1, Math.min(8, availableParallelism() - 1)),
coverageProvider: "babel",
projects: [
{
...shared,
displayName: "unit-node",
testMatch: [
"<rootDir>/src/**/*.test.ts",
"<rootDir>/tests/*.test.ts",
"<rootDir>/tests/integration/utils/**/*.test.ts",
"<rootDir>/tests/integration/browser/utils/**/*.test.ts",
...exclude(buildTests),
...exclude(domTests),
],
},
{
...shared,
displayName: "unit-dom",
testEnvironment: "jsdom",
// Content builders use the import-only await-lock package; exercise its real implementation.
moduleNameMapper: {
...shared.moduleNameMapper,
"^await-lock$": fileURLToPath(import.meta.resolve("await-lock")),
},
transformIgnorePatterns: ["/node_modules/(?!await-lock/)"],
testMatch: domTests,
},
{...shared, displayName: "build", testMatch: buildTests},
{...shared, displayName: "types", testMatch: ["<rootDir>/tests/integration/types/**/*.test.ts"]},
{
...shared,
displayName: "chrome",
setupFiles: [],
testMatch: [
"<rootDir>/tests/integration/browser/**/*.integration.test.ts",
...exclude(["<rootDir>/tests/integration/browser/**/*.firefox.integration.test.ts"]),
],
},
{
...shared,
displayName: "firefox",
setupFiles: [],
testMatch: ["<rootDir>/tests/integration/browser/**/*.firefox.integration.test.ts"],
},
],
};

export default config;
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading