From c1bc0b8955779c14cada92329ae07dd9559b7911 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Thu, 21 May 2026 21:15:22 +0100 Subject: [PATCH 1/4] Migrate to forgejo --- .forgejo/workflows/check.yml | 69 ++++++++++++ .forgejo/workflows/publish.yml | 167 ++++++++++++++++++++++++++++ .github/dependabot.yml | 26 ----- .github/workflows/check.yml | 197 --------------------------------- .github/workflows/gh-pages.yml | 72 ------------ 5 files changed, 236 insertions(+), 295 deletions(-) create mode 100644 .forgejo/workflows/check.yml create mode 100644 .forgejo/workflows/publish.yml delete mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/check.yml delete mode 100644 .github/workflows/gh-pages.yml diff --git a/.forgejo/workflows/check.yml b/.forgejo/workflows/check.yml new file mode 100644 index 0000000..ae76d62 --- /dev/null +++ b/.forgejo/workflows/check.yml @@ -0,0 +1,69 @@ +name: Check + +on: + push: + branches: ['main'] + pull_request: + branches: ['main'] + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: '-Dwarnings' + +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: Lint + run: scripts/lint.sh + + - name: Test + run: scripts/test.sh + + - name: Build website + run: scripts/build-website.sh diff --git a/.forgejo/workflows/publish.yml b/.forgejo/workflows/publish.yml new file mode 100644 index 0000000..47f7c32 --- /dev/null +++ b/.forgejo/workflows/publish.yml @@ -0,0 +1,167 @@ +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: Lint + run: scripts/lint.sh + + - 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' + run: | + apt-get update && apt-get install -y rsync + rsync -a --delete examples/website/dist/ /pages/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 }} diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index f5af792..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,26 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - - package-ecosystem: 'cargo' - directories: ['**'] - schedule: - interval: 'daily' - - - package-ecosystem: 'github-actions' - directories: ['**'] - schedule: - interval: 'daily' - - - package-ecosystem: 'npm' - directories: ['/reconcile-js', '/examples/website'] - schedule: - interval: 'daily' - - - package-ecosystem: 'pip' - directories: ['/reconcile-python'] - schedule: - interval: 'daily' diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml deleted file mode 100644 index 9e0b2bb..0000000 --- a/.github/workflows/check.yml +++ /dev/null @@ -1,197 +0,0 @@ -name: Check & publish - -on: - push: - branches: ['main'] - tags: ['*'] - pull_request: - branches: ['main'] - -env: - CARGO_TERM_COLOR: always - RUSTFLAGS: '-Dwarnings' - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - name: Setup Node.js environment - uses: actions/setup-node@v6.3.0 - with: - node-version: '22.x' - check-latest: true - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Cache Rust dependencies - uses: actions/cache@v5 - 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: Lint - run: scripts/lint.sh - - - name: Test - run: scripts/test.sh - - publish-crate: - needs: build - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') - - steps: - - uses: actions/checkout@v6 - - - name: Cache Rust dependencies - uses: actions/cache@v5 - 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: Publish to crates.io - run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }} - - publish-npm: - needs: build - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') - permissions: - contents: read - id-token: write - - steps: - - uses: actions/checkout@v6 - - - name: Setup Node.js environment - uses: actions/setup-node@v6.3.0 - with: - node-version: '24.x' - check-latest: true - - - name: Cache Rust dependencies - uses: actions/cache@v5 - 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@v5 - with: - path: | - reconcile-js/node_modules - ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('reconcile-js/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- - - - name: Build website - run: scripts/build-website.sh - - - name: Publish reconcile-js to NPM - run: | - cd reconcile-js - cp ../README.md . - npm publish --access public - - build-python-wheels: - needs: build - if: startsWith(github.ref, 'refs/tags/') - strategy: - matrix: - include: - - os: ubuntu-latest - target: x86_64 - - os: ubuntu-latest - target: aarch64 - - os: macos-latest - target: x86_64 - - os: macos-latest - target: aarch64 - - os: windows-latest - target: x86_64 - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-python@v6 - with: - python-version: '3.x' - - - name: Copy README - run: cp README.md reconcile-python/ - - - uses: PyO3/maturin-action@v1 - with: - target: ${{ matrix.target }} - args: --release --out dist --find-interpreter - manylinux: auto - working-directory: reconcile-python - - - uses: actions/upload-artifact@v7 - with: - name: wheels-${{ matrix.os }}-${{ matrix.target }} - path: reconcile-python/dist/*.whl - - build-python-sdist: - needs: build - if: startsWith(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - name: Copy README - run: cp README.md reconcile-python/ - - - uses: PyO3/maturin-action@v1 - with: - command: sdist - args: --out dist - working-directory: reconcile-python - - - uses: actions/upload-artifact@v7 - with: - name: sdist - path: reconcile-python/dist/*.tar.gz - - publish-pypi: - needs: [build-python-wheels, build-python-sdist] - runs-on: ubuntu-latest - permissions: - id-token: write - - steps: - - uses: actions/download-artifact@v8 - with: - pattern: '{wheels-*,sdist}' - merge-multiple: true - path: dist - - - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml deleted file mode 100644 index 7ac04ea..0000000 --- a/.github/workflows/gh-pages.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Deploy Website to GitHub Pages - -on: - push: - branches: - - main - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: 'pages' - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Cache Rust dependencies - uses: actions/cache@v5 - 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@v5 - with: - path: | - reconcile-js/node_modules - ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('reconcile-js/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- - - - name: Build wasm - run: | - which wasm-pack || cargo install wasm-pack - scripts/build-website.sh - - - name: Upload artifact - uses: actions/upload-pages-artifact@v4 - with: - path: examples/website/dist - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 From 8e237bc232e4242eb26770d55c0dde267d576a29 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Fri, 22 May 2026 08:05:55 +0100 Subject: [PATCH 2/4] Improve TS docs --- docs/advanced-ts.md | 251 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 206 insertions(+), 45 deletions(-) diff --git a/docs/advanced-ts.md b/docs/advanced-ts.md index 7e53bf5..dd1633d 100644 --- a/docs/advanced-ts.md +++ b/docs/advanced-ts.md @@ -2,40 +2,65 @@ ## Edit Provenance -Track which changes came from where using `reconcileWithHistory`: +Track which changes came from where using `reconcileWithHistory`. The result's +`history` field is typed as `SpanWithHistory[]`, and each span's `history` is a +`History` string-literal union. -```javascript -const result = reconcileWithHistory( - 'Hello world', - 'Hello beautiful world', - 'Hi world' -); +```typescript +import { reconcileWithHistory, type History, type SpanWithHistory } from 'reconcile-text'; -console.log(result.text); // "Hi beautiful world" -console.log(result.history); /* -[ - { - "text": "Hello", - "history": "RemovedFromRight" - }, - { - "text": "Hi", - "history": "AddedFromRight" - }, - { - "text": " beautiful", - "history": "AddedFromLeft" - }, - { - "text": " ", - "history": "Unchanged" - }, - { - "text": "world", - "history": "Unchanged" +const result = reconcileWithHistory('Hello world', 'Hello beautiful world', 'Hi world'); + +console.log(result.text); // "Hi beautiful world" + +const history: SpanWithHistory[] = result.history; +console.log(history); +// [ +// { text: "Hello", history: "RemovedFromRight" }, +// { text: "Hi", history: "AddedFromRight" }, +// { text: " beautiful", history: "AddedFromLeft" }, +// { text: " ", history: "Unchanged" }, +// { text: "world", history: "Unchanged" }, +// ] + +const classByHistory = { + Unchanged: 'merge-unchanged', + AddedFromLeft: 'merge-added-left', + AddedFromRight: 'merge-added-right', + RemovedFromLeft: 'merge-removed-left', + RemovedFromRight: 'merge-removed-right', +} satisfies Record; +``` + +Using `satisfies Record` keeps the object literal's values +narrow while forcing every history case to be handled. If a future version adds +another `History` value, TypeScript will point at this mapping. + +For control flow, use the same union as an exhaustiveness check: + +```typescript +import type { History } from 'reconcile-text'; + +function historyLabel(history: History): string { + switch (history) { + case 'Unchanged': + return 'unchanged'; + case 'AddedFromLeft': + return 'added by left'; + case 'AddedFromRight': + return 'added by right'; + case 'RemovedFromLeft': + return 'removed from left'; + case 'RemovedFromRight': + return 'removed from right'; + default: + return assertNever(history); } -] -*/ +} + +function assertNever(value: never): never { + throw new Error(`Unhandled history value: ${value}`); +} ``` ## Tokenisation Strategies @@ -45,26 +70,162 @@ console.log(result.history); /* - **Word tokeniser** (`"Word"`) - Splits on word boundaries (recommended for prose) - **Character tokeniser** (`"Character"`) - Individual characters (fine-grained control) - **Line tokeniser** (`"Line"`) - Line-by-line (similar to `git merge` or more precisely [`git merge-file`](https://git-scm.com/docs/git-merge-file)) +- **Markdown tokeniser** (`"Markdown"`) - Splits on Markdown structural boundaries (headings, list items, paragraphs) + +```typescript +import { reconcile, type BuiltinTokenizer } from 'reconcile-text'; + +const tokenizers = [ + 'Word', + 'Character', + 'Line', + 'Markdown', +] as const satisfies readonly BuiltinTokenizer[]; + +const result = reconcile('abc', 'axc', 'abyc', 'Character'); +console.log(result.text); // "axyc" + +for (const tokenizer of tokenizers) { + const merged = reconcile( + '# Title\n\n- old item\n', + '# Title\n\n- old item\n- left item\n', + '# New title\n\n- old item\n', + tokenizer + ); + + console.log(tokenizer, merged.text); +} +``` ## Cursor Tracking -`reconcile-text` automatically tracks cursor positions through merges, which is useful for collaborative editors. Selections can be tracked by providing them as a pair of cursors. +`reconcile-text` automatically tracks cursor positions through merges, which is +useful for collaborative editors. Selections can be tracked by providing them as +a pair of cursors. -```javascript -const result = reconcile( - 'Hello world', - { - text: 'Hello beautiful world', - cursors: [{ id: 1, position: 6 }], // After "Hello " - }, - { - text: 'Hi world', - cursors: [{ id: 2, position: 0 }], // At the beginning - } -); +```typescript +import { reconcile, type TextWithOptionalCursors } from 'reconcile-text'; + +const left = { + text: 'Hello beautiful world', + cursors: [{ id: 1, position: 6 }], // After "Hello " +} satisfies TextWithOptionalCursors; + +const right = { + text: 'Hi world', + cursors: [{ id: 2, position: 0 }], // At the beginning +} satisfies TextWithOptionalCursors; + +const result = reconcile('Hello world', left, right); // Result: "Hi beautiful world" with repositioned cursors -console.log(result.text); // "Hi beautiful world" +console.log(result.text); // "Hi beautiful world" console.log(result.cursors); // [{ id: 2, position: 0 }, { id: 1, position: 3 }] ``` + > The `cursors` list is sorted by character position (not IDs). + +## Generic Helpers and Inference + +The exported merge functions are intentionally small: they merge strings, or +strings plus cursor metadata. In TypeScript applications, keep domain-specific +metadata in your own typed wrappers and let inference preserve the surrounding +shape. + +```typescript +import { reconcile, type BuiltinTokenizer } from 'reconcile-text'; + +type ReconciledText = Omit & { + text: string; +}; + +function reconcileDraft( + parent: TDraft, + left: TDraft, + right: TDraft, + tokenizer?: BuiltinTokenizer +): ReconciledText { + return { + ...right, + text: reconcile(parent.text, left.text, right.text, tokenizer).text, + }; +} + +interface MarkdownDraft { + id: string; + text: string; + updatedAt: Date; +} + +const parent: MarkdownDraft = { + id: 'intro', + text: '# Title\n\nOld text\n', + updatedAt: new Date('2026-01-01T00:00:00Z'), +}; + +const left: MarkdownDraft = { + ...parent, + text: '# Title\n\nOld text\n\n- left note\n', +}; + +const right: MarkdownDraft = { + ...parent, + text: '# New title\n\nOld text\n', +}; + +const merged = reconcileDraft(parent, left, right, 'Markdown'); +// merged is inferred as { id: string; updatedAt: Date; text: string } +``` + +Use `satisfies` for configuration objects and cursor payloads when you want +compile-time checking without widening everything to the library interface. + +```typescript +import type { BuiltinTokenizer, TextWithOptionalCursors } from 'reconcile-text'; + +const mergeOptions = { + tokenizer: 'Markdown', + renderDeletedSpans: true, +} satisfies { + tokenizer: BuiltinTokenizer; + renderDeletedSpans: boolean; +}; + +const documentWithSelection = { + text: 'Hello beautiful world', + cursors: [ + { id: 1, position: 6 }, + { id: 2, position: 15 }, + ], +} satisfies TextWithOptionalCursors; +``` + +## Compact Diffs + +Generate and apply compact diff representations. The TypeScript type is +`Array` for `diff()` and `Array` for +`undiff()`, because the underlying WebAssembly layer may represent integer +entries as `bigint`. + +```typescript +import { diff, undiff } from 'reconcile-text'; + +const original = 'Hello world'; +const changed = 'Hello beautiful world'; + +// Generate a compact diff +const changes = diff(original, changed); +console.log(changes); // [5, " beautiful world"] + +// Reconstruct the changed text from the diff +const reconstructed = undiff(original, changes); +console.assert(reconstructed === changed); +``` + +Diff entries are positive integers (retain N characters), negative integers +(delete N characters), and strings (insert text). + +## Complete Example + +For a complete browser example that renders `SpanWithHistory` values and cursor +selections, see the [example website source](../examples/website/src/index.ts). From 22723cbcae5af3b578ed95c4e952d3a78312c53d Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Fri, 22 May 2026 08:07:36 +0100 Subject: [PATCH 3/4] Remove --- scripts/lint.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/lint.sh b/scripts/lint.sh index c46991d..6ae4f66 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -5,9 +5,6 @@ set -e which cargo-machete || cargo install cargo-machete cargo machete -which lychee || cargo install lychee -lychee --verbose --exclude npmjs.com README.md - cargo clippy --all-targets --all-features --fix --allow-dirty --allow-staged cargo fmt --all From 17a96be0fc499540761d782e036679bafe829407 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Fri, 22 May 2026 08:17:19 +0100 Subject: [PATCH 4/4] Install UV --- .forgejo/workflows/check.yml | 5 +++++ .forgejo/workflows/publish.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/.forgejo/workflows/check.yml b/.forgejo/workflows/check.yml index ae76d62..6e13d91 100644 --- a/.forgejo/workflows/check.yml +++ b/.forgejo/workflows/check.yml @@ -59,6 +59,11 @@ jobs: | 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 diff --git a/.forgejo/workflows/publish.yml b/.forgejo/workflows/publish.yml index 47f7c32..d546361 100644 --- a/.forgejo/workflows/publish.yml +++ b/.forgejo/workflows/publish.yml @@ -63,6 +63,11 @@ jobs: | 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