Simplify build scripts

This commit is contained in:
Andras Schmelczer 2026-10-29 08:18:21 +00:00
commit 03856fe868
19 changed files with 300 additions and 634 deletions

View file

@ -1,14 +1,20 @@
name: Check
name: Check and publish
on:
push:
branches: ['main']
branches: ["main"]
tags: ["*"]
workflow_dispatch:
pull_request:
branches: ['main']
branches: ["main"]
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: '-Dwarnings'
RUSTFLAGS: "-Dwarnings"
concurrency:
group: reconcile-${{ github.ref }}
cancel-in-progress: false
jobs:
build:
@ -20,8 +26,9 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22.x'
node-version: "22.x"
check-latest: true
registry-url: https://registry.npmjs.org
- name: Cache Rust dependencies
uses: actions/cache@v4
@ -40,8 +47,6 @@ jobs:
uses: actions/cache@v4
with:
path: |
reconcile-js/node_modules
examples/website/node_modules
~/.npm
key: >-
${{ runner.os }}-npm-${{
@ -64,6 +69,12 @@ jobs:
curl --proto '=https' --tlsv1.2 -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Install dependencies
run: scripts/setup.sh
- name: Build bindings
run: scripts/build.sh
- name: Lint
run: scripts/lint.sh
@ -77,3 +88,183 @@ jobs:
- name: Build website
run: scripts/build-website.sh
- name: Pack verified npm package
working-directory: reconcile-js
run: |
mkdir -p ../dist/npm
npm pack --pack-destination ../dist/npm
- name: Publish to npm
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
run: npm publish dist/npm/*.tgz
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Deploy to pages mount
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: http://forgejo:3000/andras/ci-actions/deploy-pages@main
with:
source: examples/website/dist
target: reconcile
publish-crate:
needs: build
runs-on: docker
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install Rust toolchain
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain none --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }}
publish-pypi:
needs: build
runs-on: docker
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-pypi-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-pypi-
${{ runner.os }}-cargo-
# clang/lld/llvm provide clang-cl, lld-link and llvm-lib, which cargo-xwin
# uses to cross-compile the Windows wheel from this Linux runner.
- name: Install cross-compilation system dependencies
run: |
apt-get update
apt-get install -y clang lld llvm
- name: Install Rust toolchain
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain none --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# The x86_64 Linux targets ship in rust-toolchain.toml; add the cross targets.
- name: Add cross-compilation Rust targets
run: |
rustup target add aarch64-unknown-linux-gnu aarch64-unknown-linux-musl \
aarch64-apple-darwin x86_64-apple-darwin x86_64-pc-windows-msvc
# zig is the C toolchain maturin's `--zig` uses to produce manylinux2014
# wheels with a pinned (old) glibc, independent of the runner's glibc.
# It also links the musllinux and macOS wheels, without a macOS SDK.
- name: Install zig
run: |
ZIG_VERSION=0.13.0
ZIG_INSTALL_DIR=$(mktemp -d "${RUNNER_TEMP:-/tmp}/reconcile-zig.XXXXXX")
curl --proto '=https' --tlsv1.2 -fLsS \
"https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" \
| tar -xJ -C "$ZIG_INSTALL_DIR"
echo "$ZIG_INSTALL_DIR/zig-linux-x86_64-${ZIG_VERSION}" >> "$GITHUB_PATH"
- name: Install cargo-xwin
run: command -v cargo-xwin || cargo install --locked cargo-xwin
- name: Install uv
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Copy README and license
run: cp README.md LICENSE reconcile-python/
- name: Install Python build dependencies
working-directory: reconcile-python
run: uv sync --frozen --no-install-project
- name: Build sdist
working-directory: reconcile-python
run: uv run --no-sync maturin sdist --out dist
- name: Check sdist size
working-directory: reconcile-python
run: |
size=$(stat -c %s dist/*.tar.gz)
echo "sdist is $size bytes"
test "$size" -lt 5000000
- name: Build Linux x86_64 wheel
working-directory: reconcile-python
run: >-
uv run --no-sync maturin build --release --out dist
--compatibility manylinux2014
--target x86_64-unknown-linux-gnu --zig
- name: Build Linux aarch64 wheel
working-directory: reconcile-python
run: >-
uv run --no-sync maturin build --release --out dist
--compatibility manylinux2014
--target aarch64-unknown-linux-gnu --zig
- name: Build musllinux x86_64 wheel
working-directory: reconcile-python
run: >-
uv run --no-sync maturin build --release --out dist
--compatibility musllinux_1_2
--target x86_64-unknown-linux-musl --zig
- name: Build musllinux aarch64 wheel
working-directory: reconcile-python
run: >-
uv run --no-sync maturin build --release --out dist
--compatibility musllinux_1_2
--target aarch64-unknown-linux-musl --zig
- name: Build macOS x86_64 wheel
working-directory: reconcile-python
run: >-
uv run --no-sync maturin build --release --out dist
--target x86_64-apple-darwin --zig
- name: Build macOS arm64 wheel
working-directory: reconcile-python
run: >-
uv run --no-sync maturin build --release --out dist
--target aarch64-apple-darwin --zig
- name: Build Windows x86_64 wheel
working-directory: reconcile-python
run: >-
uv run --no-sync maturin build --release --out dist
--target x86_64-pc-windows-msvc
- name: Publish to PyPI
working-directory: reconcile-python
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: uv run --no-sync maturin upload --skip-existing dist/*

View file

@ -1,307 +0,0 @@
name: Publish
on:
push:
branches: ["main"]
tags: ["*"]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-Dwarnings"
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22.x"
check-latest: true
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Cache npm dependencies
uses: actions/cache@v4
with:
path: |
reconcile-js/node_modules
examples/website/node_modules
~/.npm
key: >-
${{ runner.os }}-npm-${{
hashFiles(
'reconcile-js/package-lock.json',
'examples/website/package-lock.json'
)
}}
restore-keys: |
${{ runner.os }}-npm-
- name: Install Rust toolchain
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain none --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Install uv
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Lint
run: scripts/lint.sh
- name: Verify checkout is clean
run: |
git status --short
test -z "$(git status --porcelain)"
- name: Test
run: scripts/test.sh
- name: Build website
run: scripts/build-website.sh
- name: Deploy to pages mount
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: http://forgejo:3000/andras/ci-actions/deploy-pages@main
with:
source: examples/website/dist
target: reconcile
publish-crate:
needs: build
runs-on: docker
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install Rust toolchain
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain none --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }}
publish-npm:
needs: build
runs-on: docker
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22.x"
check-latest: true
registry-url: "https://registry.npmjs.org"
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Cache npm dependencies
uses: actions/cache@v4
with:
path: |
reconcile-js/node_modules
~/.npm
key: >-
${{ runner.os }}-npm-${{
hashFiles('reconcile-js/package-lock.json')
}}
restore-keys: |
${{ runner.os }}-npm-
- name: Install Rust toolchain
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain none --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Build website
run: scripts/build-website.sh
- name: Publish reconcile-js to NPM
run: |
cd reconcile-js
cp ../README.md .
npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish-pypi:
needs: build
runs-on: docker
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-pypi-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-pypi-
${{ runner.os }}-cargo-
# clang/lld/llvm provide clang-cl, lld-link and llvm-lib, which cargo-xwin
# uses to cross-compile the Windows wheel from this Linux runner.
- name: Install cross-compilation system dependencies
run: |
apt-get update
apt-get install -y clang lld llvm
- name: Install Rust toolchain
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain none --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# The x86_64 Linux targets ship in rust-toolchain.toml; add the cross targets.
- name: Add cross-compilation Rust targets
run: |
rustup target add aarch64-unknown-linux-gnu aarch64-unknown-linux-musl \
aarch64-apple-darwin x86_64-apple-darwin x86_64-pc-windows-msvc
# zig is the C toolchain maturin's `--zig` uses to produce manylinux2014
# wheels with a pinned (old) glibc, independent of the runner's glibc.
# It also links the musllinux and macOS wheels, without a macOS SDK.
- name: Install zig
run: |
ZIG_VERSION=0.13.0
ZIG_INSTALL_DIR=$(mktemp -d "${RUNNER_TEMP:-/tmp}/reconcile-zig.XXXXXX")
curl --proto '=https' --tlsv1.2 -fLsS \
"https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" \
| tar -xJ -C "$ZIG_INSTALL_DIR"
echo "$ZIG_INSTALL_DIR/zig-linux-x86_64-${ZIG_VERSION}" >> "$GITHUB_PATH"
- name: Install cargo-xwin
run: command -v cargo-xwin || cargo install --locked cargo-xwin
- name: Install uv
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Copy README and license
run: cp README.md LICENSE reconcile-python/
- name: Build sdist
working-directory: reconcile-python
run: uv run maturin sdist --out dist
- name: Check sdist size
working-directory: reconcile-python
run: |
size=$(stat -c %s dist/*.tar.gz)
echo "sdist is $size bytes"
test "$size" -lt 5000000
- name: Build Linux x86_64 wheel
working-directory: reconcile-python
run: >-
uv run maturin build --release --out dist
--compatibility manylinux2014
--target x86_64-unknown-linux-gnu --zig
- name: Build Linux aarch64 wheel
working-directory: reconcile-python
run: >-
uv run maturin build --release --out dist
--compatibility manylinux2014
--target aarch64-unknown-linux-gnu --zig
- name: Build musllinux x86_64 wheel
working-directory: reconcile-python
run: >-
uv run maturin build --release --out dist
--compatibility musllinux_1_2
--target x86_64-unknown-linux-musl --zig
- name: Build musllinux aarch64 wheel
working-directory: reconcile-python
run: >-
uv run maturin build --release --out dist
--compatibility musllinux_1_2
--target aarch64-unknown-linux-musl --zig
- name: Build macOS x86_64 wheel
working-directory: reconcile-python
run: >-
uv run maturin build --release --out dist
--target x86_64-apple-darwin --zig
- name: Build macOS arm64 wheel
working-directory: reconcile-python
run: >-
uv run maturin build --release --out dist
--target aarch64-apple-darwin --zig
- name: Build Windows x86_64 wheel
working-directory: reconcile-python
run: >-
uv run maturin build --release --out dist
--target x86_64-pc-windows-msvc
# Forgejo cannot use PyPI trusted publishing (OIDC), so authenticate with
# an API token. --skip-existing makes re-runs of a tag idempotent.
- name: Publish to PyPI
working-directory: reconcile-python
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: uv run maturin upload --skip-existing dist/*

View file

@ -134,9 +134,10 @@ Contributions are welcome!
### Python setup
Install [uv][uv] and build the extension for development:
Install [uv][uv], then build the extension for Python-only development:
```sh
cp README.md LICENSE reconcile-python/
cd reconcile-python
uv run maturin develop
```
@ -166,6 +167,16 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
### Scripts
After installing Node 22, rustup, and uv, run `scripts/check.sh` for the full
install, build, lint, test, and demo build sequence.
For individual steps, run `scripts/setup.sh` once to install dependencies and
`scripts/build.sh` to build the JS and Python bindings. Run the build again after
changing Rust or binding code. The lint, test, and website scripts use those
existing dependencies and build outputs.
- **Install dependencies**: `scripts/setup.sh`
- **Build bindings**: `scripts/build.sh` (or `scripts/build-js.sh` for JS only)
- **Run tests**: `scripts/test.sh`
- **Check lint and formatting**: `scripts/lint.sh`
- **Run the demo locally**: `scripts/dev-website.sh`

View file

@ -14,10 +14,8 @@
"mini-css-extract-plugin": "^2.10.1",
"prettier": "^3.8.1",
"reconcile-text": "file:../../reconcile-js",
"resolve-url-loader": "^5.0.0",
"sass": "^1.98.0",
"sass-loader": "^16.0.7",
"svg-inline-loader": "^0.8.2",
"terser-webpack-plugin": "^5.4.0",
"ts-loader": "^9.5.4",
"typescript": "^5.9.3",
@ -48,8 +46,7 @@
"tslib": "2.8.1",
"typescript": "5.9.3",
"webpack": "^5.105.4",
"webpack-cli": "^6.0.1",
"webpack-merge": "^6.0.1"
"webpack-cli": "^6.0.1"
}
},
"../../reconcile-js/node_modules/@babel/code-frame": {
@ -6041,18 +6038,6 @@
"acorn": "^8.14.0"
}
},
"node_modules/adjust-sourcemap-loader": {
"version": "4.0.0",
"dev": true,
"license": "MIT",
"dependencies": {
"loader-utils": "^2.0.0",
"regex-parser": "^2.2.11"
},
"engines": {
"node": ">=8.9"
}
},
"node_modules/ajv": {
"version": "8.18.0",
"dev": true,
@ -6151,14 +6136,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/big.js": {
"version": "5.2.2",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/body-parser": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz",
@ -6539,11 +6516,6 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/convert-source-map": {
"version": "1.9.0",
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
@ -6952,14 +6924,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/emojis-list": {
"version": "3.0.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@ -7957,17 +7921,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/json5": {
"version": "2.2.3",
"dev": true,
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"dev": true,
@ -7999,19 +7952,6 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/loader-utils": {
"version": "2.0.4",
"dev": true,
"license": "MIT",
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/locate-path": {
"version": "5.0.0",
"dev": true,
@ -8162,14 +8102,6 @@
"webpack": "^5.0.0"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ms": {
"version": "2.0.0",
"dev": true,
@ -8298,14 +8230,6 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@ -8792,11 +8716,6 @@
"dev": true,
"license": "Apache-2.0"
},
"node_modules/regex-parser": {
"version": "2.3.1",
"dev": true,
"license": "MIT"
},
"node_modules/relateurl": {
"version": "0.2.7",
"dev": true,
@ -8929,21 +8848,6 @@
"node": ">=8"
}
},
"node_modules/resolve-url-loader": {
"version": "5.0.0",
"dev": true,
"license": "MIT",
"dependencies": {
"adjust-sourcemap-loader": "^4.0.0",
"convert-source-map": "^1.7.0",
"loader-utils": "^2.0.0",
"postcss": "^8.2.14",
"source-map": "0.6.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@ -9452,11 +9356,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/simple-html-tokenizer": {
"version": "0.1.1",
"dev": true,
"license": "MIT"
},
"node_modules/source-map": {
"version": "0.6.1",
"dev": true,
@ -9530,40 +9429,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/svg-inline-loader": {
"version": "0.8.2",
"dev": true,
"license": "MIT",
"dependencies": {
"loader-utils": "^1.1.0",
"object-assign": "^4.0.1",
"simple-html-tokenizer": "^0.1.1"
}
},
"node_modules/svg-inline-loader/node_modules/json5": {
"version": "1.0.2",
"dev": true,
"license": "MIT",
"dependencies": {
"minimist": "^1.2.0"
},
"bin": {
"json5": "lib/cli.js"
}
},
"node_modules/svg-inline-loader/node_modules/loader-utils": {
"version": "1.4.2",
"dev": true,
"license": "MIT",
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/svgo": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.5.tgz",

View file

@ -30,10 +30,8 @@
"mini-css-extract-plugin": "^2.10.1",
"prettier": "^3.8.1",
"reconcile-text": "file:../../reconcile-js",
"resolve-url-loader": "^5.0.0",
"sass": "^1.98.0",
"sass-loader": "^16.0.7",
"svg-inline-loader": "^0.8.2",
"terser-webpack-plugin": "^5.4.0",
"ts-loader": "^9.5.4",
"typescript": "^5.9.3",

View file

@ -55,21 +55,13 @@ module.exports = (_env, argv) => ({
].filter(Boolean),
module: {
rules: [
{
test: /\.svg$/i,
use: 'svg-inline-loader',
},
{
test: /\.scss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'resolve-url-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true, // required by resolve-url-loader
},
},
],
},

View file

@ -19,8 +19,7 @@
"tslib": "2.8.1",
"typescript": "5.9.3",
"webpack": "^5.105.4",
"webpack-cli": "^6.0.1",
"webpack-merge": "^6.0.1"
"webpack-cli": "^6.0.1"
}
},
"../pkg": {

View file

@ -34,7 +34,6 @@
],
"scripts": {
"build": "node scripts/build-rn.mjs && webpack --mode production",
"prepack": "node -e \"require('node:fs').copyFileSync('../LICENSE', 'LICENSE')\"",
"format": "prettier --write \"./**/*.(ts|mjs|scss|json|html)\"",
"format:check": "prettier --check --ignore-path ../.gitignore \"./**/*.(ts|mjs|scss|json|html)\"",
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
@ -50,7 +49,6 @@
"tslib": "2.8.1",
"typescript": "5.9.3",
"webpack": "^5.105.4",
"webpack-cli": "^6.0.1",
"webpack-merge": "^6.0.1"
"webpack-cli": "^6.0.1"
}
}

View file

@ -3,19 +3,14 @@
// wasm2js (pure-JS) translation.
import { execFileSync } from 'node:child_process';
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { readFileSync, rmSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { homedir } from 'node:os';
const here = dirname(fileURLToPath(import.meta.url));
const reconcileJsDir = resolve(here, '..');
const repoRoot = resolve(reconcileJsDir, '..');
const releaseWasm = resolve(
repoRoot,
'target/wasm32-unknown-unknown/release/reconcile_text.wasm'
);
const outDir = resolve(reconcileJsDir, 'pkg-rn');
const bgWasm = resolve(outDir, 'reconcile_text_bg.wasm');
const bgWasmJs = resolve(outDir, 'reconcile_text_bg.wasm.js');
@ -29,87 +24,19 @@ function run(cmd, args) {
execFileSync(cmd, args, { stdio: 'inherit' });
}
// Locate the wasm-bindgen CLI. It MUST match the `wasm-bindgen` crate version pinned
// in Cargo.toml: a mismatched CLI emits bindings the runtime can't use. So we resolve
// the required version first and verify every candidate against it, failing loudly
// rather than silently falling back to whatever other version happens to be around.
function findWasmBindgen() {
const cargoToml = readFileSync(resolve(repoRoot, 'Cargo.toml'), 'utf8');
const wanted = cargoToml.match(
/wasm-bindgen\s*=\s*\{[^}]*version\s*=\s*"([^"]+)"/
)?.[1];
if (!wanted) {
throw new Error(
'[build-rn] Could not parse the pinned wasm-bindgen version from Cargo.toml, so ' +
'the required CLI version is unknown. Has the dependency declaration changed?'
);
}
// 1. On PATH: accept it only if its version matches the pin.
let onPath = null;
try {
onPath = execFileSync('which', ['wasm-bindgen'], { encoding: 'utf8' }).trim();
} catch {
/* not on PATH; try the wasm-pack cache next */
}
if (onPath) {
const version = execFileSync(onPath, ['--version'], { encoding: 'utf8' }).match(
/\d+\.\d+\.\d+/
)?.[0];
if (version !== wanted) {
throw new Error(
`[build-rn] wasm-bindgen on PATH (${onPath}) is ${version ?? 'an unknown version'}, ` +
`but Cargo.toml pins ${wanted}. Install the matching CLI ` +
`(\`cargo install wasm-bindgen-cli --version ${wanted}\`) or remove the mismatched one.`
);
}
return onPath;
}
const cacheRoots = [
resolve(homedir(), 'Library/Caches/.wasm-pack'),
resolve(homedir(), '.cache/.wasm-pack'),
];
for (const root of cacheRoots) {
if (!existsSync(root)) {
continue;
}
for (const entry of readdirSync(root)) {
const candidate = resolve(root, entry, 'wasm-bindgen');
if (!existsSync(candidate)) {
continue;
}
let version;
try {
version = execFileSync(candidate, ['--version'], { encoding: 'utf8' }).match(
/\d+\.\d+\.\d+/
)?.[0];
} catch {
continue; // not an invokable wasm-bindgen; ignore
}
if (version === wanted) {
return candidate;
}
}
}
throw new Error(
`[build-rn] No wasm-bindgen ${wanted} found on PATH or in the wasm-pack cache. ` +
'Run `wasm-pack build --target web --features wasm` first (it caches the matching ' +
`wasm-bindgen), or \`cargo install wasm-bindgen-cli --version ${wanted}\`.`
);
}
if (!existsSync(releaseWasm)) {
throw new Error(
`Missing ${releaseWasm}.\nRun \`wasm-pack build --target web --features wasm\` from the repo root first.`
);
}
console.log('[build-rn] generating bundler-target bindings with wasm-bindgen');
console.log('[build-rn] generating bundler-target bindings with wasm-pack');
rmSync(outDir, { recursive: true, force: true });
const wasmBindgen = findWasmBindgen();
run(wasmBindgen, ['--target', 'bundler', '--out-dir', outDir, releaseWasm]);
run('wasm-pack', [
'build',
repoRoot,
'--target',
'bundler',
'--out-dir',
outDir,
'--no-opt',
'--features',
'wasm',
]);
// --- Patch wasm-bindgen's cached-memory getters for wasm2js -----------------
//

View file

@ -1,5 +1,4 @@
const path = require('path');
const { merge } = require('webpack-merge');
const common = {
optimization: {
@ -16,9 +15,6 @@ const common = {
performance: {
hints: false,
},
experiments: {
asyncWebAssembly: true,
},
module: {
rules: [
{
@ -38,7 +34,8 @@ const common = {
module.exports = [
// Web build: real WebAssembly, instantiated synchronously from inlined base64.
merge(common, {
{
...common,
target: 'web',
entry: './src/index.ts',
output: {
@ -50,10 +47,11 @@ module.exports = [
},
globalObject: 'this',
},
}),
},
// Node build: real WebAssembly.
merge(common, {
{
...common,
target: 'node',
entry: './src/index.ts',
output: {
@ -61,11 +59,12 @@ module.exports = [
filename: 'reconcile.node.js',
libraryTarget: 'commonjs2',
},
}),
},
// React Native build: wasm2js (pure JS), for Hermes which has no
// `WebAssembly` global. Sources come from `pkg-rn/`
merge(common, {
{
...common,
target: 'web',
entry: './src/index.rn.ts',
output: {
@ -77,5 +76,5 @@ module.exports = [
},
globalObject: 'this',
},
}),
},
];

7
scripts/build-js.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
wasm-pack build --target web --features wasm,console_error_panic_hook
npm run build --prefix reconcile-js

View file

@ -1,14 +1,6 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
which wasm-pack || cargo install wasm-pack
wasm-pack build --target web --features wasm
cd reconcile-js
npm ci
npm run build
cd ../examples/website
npm ci
npm run build
cd -
npm run build --prefix examples/website

7
scripts/build.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
scripts/build-js.sh
uv run --directory reconcile-python --no-sync maturin develop -q

View file

@ -36,7 +36,7 @@ sed_inplace() {
echo "Bumping versions"
which cargo-set-version || cargo install cargo-edit
cargo set-version --bump $1
cargo set-version --bump "$1"
NEWVER=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
@ -45,7 +45,7 @@ which wasm-pack || cargo install wasm-pack
wasm-pack build --target web --features wasm
cd reconcile-js
npm version $1
npm version "$NEWVER" --no-git-tag-version
npm install
cd ../reconcile-python
@ -60,12 +60,11 @@ npm install
cd ../..
git add .
TAG=$(node -p "require('./reconcile-js/package.json').version")
git commit -m "Bump versions to $TAG"
git commit -m "Bump versions to $NEWVER"
git push
echo "Tagging $TAG"
git tag -a $TAG -m "Release $TAG"
git push origin $TAG
echo "Tagging $NEWVER"
git tag -a $NEWVER -m "Release $NEWVER"
git push origin $NEWVER
echo "Done"

10
scripts/check.sh Executable file
View file

@ -0,0 +1,10 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
scripts/setup.sh
scripts/build.sh
scripts/lint.sh
scripts/test.sh
scripts/build-website.sh

View file

@ -1,13 +1,7 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
wasm-pack build --target web --features wasm
cd reconcile-js
npm install
npm run build
cd ../examples/website
npm install
npm run start
scripts/build-js.sh
npm start --prefix examples/website

View file

@ -1,27 +1,15 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
which cargo-machete || cargo install cargo-machete
cargo machete
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
cd reconcile-js
npm ci
npm run format:check
npm run format:check --prefix reconcile-js
npm run format:check --prefix examples/website
cd ../examples/website
npm ci
npm run format:check
cd ../../reconcile-python
cp ../README.md ../LICENSE .
uv run maturin develop -q
uv run ruff check python/ tests/
uv run ruff format --check python/ tests/
uv run pyright python/ tests/
cd -
echo "Success!"
uv run --directory reconcile-python --no-sync ruff check python/ tests/
uv run --directory reconcile-python --no-sync ruff format --check python/ tests/
uv run --directory reconcile-python --no-sync pyright python/ tests/

19
scripts/setup.sh Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
node_version=$(node --version | cut -d'.' -f1 | tr -d 'v')
if [ "$node_version" != "22" ]; then
echo "Error: Node.js version 22 is required, but found version $node_version"
exit 1
fi
command -v wasm-pack || cargo install --locked wasm-pack
command -v cargo-machete || cargo install --locked cargo-machete
npm ci --prefix reconcile-js
npm ci --prefix examples/website
cp README.md LICENSE reconcile-js/
cp README.md LICENSE reconcile-python/
uv sync --directory reconcile-python --frozen --no-install-project

View file

@ -1,36 +1,13 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
which cargo-insta || cargo install cargo-insta
which wasm-pack || cargo install wasm-pack
node_version=$(node --version | cut -d'.' -f1 | tr -d 'v')
if [ "$node_version" != "22" ]; then
echo "Error: Node.js version 22 is required, but found version $node_version"
exit 1
fi
wasm-pack build --target web --features wasm,console_error_panic_hook
cargo test --verbose --features serde -- --include-ignored
cargo test
cargo test --features serde
cargo test --features serde -- --include-ignored
cargo test
cargo test --features wasm
cargo test --features all
wasm-pack test --node --features wasm,console_error_panic_hook
cd reconcile-js
npm ci
npm run build
npm run test
cd -
cd reconcile-python
cp ../README.md ../LICENSE .
uv run maturin develop
uv run pytest -v
cd -
echo "Success!"
npm test --prefix reconcile-js
uv run --directory reconcile-python --no-sync pytest -v