diff --git a/.forgejo/workflows/check.yml b/.forgejo/workflows/check.yml index 19d18d9..68766bf 100644 --- a/.forgejo/workflows/check.yml +++ b/.forgejo/workflows/check.yml @@ -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/* diff --git a/.forgejo/workflows/publish.yml b/.forgejo/workflows/publish.yml deleted file mode 100644 index c961b87..0000000 --- a/.forgejo/workflows/publish.yml +++ /dev/null @@ -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/* diff --git a/README.md b/README.md index 4958a37..c74a0dd 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/examples/website/package-lock.json b/examples/website/package-lock.json index 13abfe3..35fd949 100644 --- a/examples/website/package-lock.json +++ b/examples/website/package-lock.json @@ -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", diff --git a/examples/website/package.json b/examples/website/package.json index d751474..007031a 100644 --- a/examples/website/package.json +++ b/examples/website/package.json @@ -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", diff --git a/examples/website/webpack.config.js b/examples/website/webpack.config.js index 8a21293..fbb2078 100644 --- a/examples/website/webpack.config.js +++ b/examples/website/webpack.config.js @@ -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 - }, }, ], }, diff --git a/reconcile-js/package-lock.json b/reconcile-js/package-lock.json index 7b1ce42..a84e7f1 100644 --- a/reconcile-js/package-lock.json +++ b/reconcile-js/package-lock.json @@ -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": { diff --git a/reconcile-js/package.json b/reconcile-js/package.json index 8c79bfe..c2527bf 100644 --- a/reconcile-js/package.json +++ b/reconcile-js/package.json @@ -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" } } diff --git a/reconcile-js/scripts/build-rn.mjs b/reconcile-js/scripts/build-rn.mjs index dadbc82..b07636a 100644 --- a/reconcile-js/scripts/build-rn.mjs +++ b/reconcile-js/scripts/build-rn.mjs @@ -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 ----------------- // diff --git a/reconcile-js/webpack.config.js b/reconcile-js/webpack.config.js index 280bc52..2243b5d 100644 --- a/reconcile-js/webpack.config.js +++ b/reconcile-js/webpack.config.js @@ -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', }, - }), + }, ]; diff --git a/scripts/build-js.sh b/scripts/build-js.sh new file mode 100755 index 0000000..0c7d0fb --- /dev/null +++ b/scripts/build-js.sh @@ -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 diff --git a/scripts/build-website.sh b/scripts/build-website.sh index 734d0d4..9b72bd5 100755 --- a/scripts/build-website.sh +++ b/scripts/build-website.sh @@ -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 diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..1776e2a --- /dev/null +++ b/scripts/build.sh @@ -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 diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 58a487d..bd2bcfd 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -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" diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..e1fd95b --- /dev/null +++ b/scripts/check.sh @@ -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 diff --git a/scripts/dev-website.sh b/scripts/dev-website.sh index bff09f6..7fb45dc 100755 --- a/scripts/dev-website.sh +++ b/scripts/dev-website.sh @@ -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 diff --git a/scripts/lint.sh b/scripts/lint.sh index e5f50bc..2bba61b 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -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/ diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100755 index 0000000..07a8ea0 --- /dev/null +++ b/scripts/setup.sh @@ -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 diff --git a/scripts/test.sh b/scripts/test.sh index 6638643..e6ac208 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -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