Skip to content

Commit be505e9

Browse files
authored
feat: internal Rust napi parser + merge eslint-plugin-runner into @rslint/core (#1035)
1 parent 80a041b commit be505e9

128 files changed

Lines changed: 5368 additions & 6140 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/move-artifacts/move-artifacts.js

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,42 @@ async function moveArtifacts() {
3333
console.log('Starting artifact move process...');
3434

3535
try {
36-
// Find and move rslint binaries
36+
// Move Go CLI binaries into the @rslint/native-{tuple} subpackages (flat,
37+
// alongside the .node). Artifacts are named `{platform}-rslint`. Go
38+
// binaries are statically linked, so one linux build serves both glibc and
39+
// musl — it's copied into both the -gnu and -musl tuple dirs.
40+
const platformToTuples = {
41+
'darwin-arm64': ['darwin-arm64'],
42+
'darwin-x64': ['darwin-x64'],
43+
'linux-arm64': ['linux-arm64-gnu', 'linux-arm64-musl'],
44+
'linux-x64': ['linux-x64-gnu', 'linux-x64-musl'],
45+
'win32-arm64': ['win32-arm64-msvc'],
46+
'win32-x64': ['win32-x64-msvc'],
47+
};
48+
3749
const rslintFiles = findBinaries('binaries', '-rslint');
3850
console.log(`Found ${rslintFiles.length} rslint binary files`);
3951

4052
for (const file of rslintFiles) {
4153
console.log(`Processing ${file}`);
4254
const isWindows = file.includes('win32');
4355
const filename = path.basename(file);
44-
const dirname = filename.replace(/-rslint$/, '');
45-
const targetDir = path.join('npm', 'rslint', dirname);
46-
47-
const targetFile = path.join(
48-
targetDir,
49-
isWindows ? 'rslint.exe' : 'rslint',
50-
);
51-
52-
// Create target directory and copy file
53-
fs.mkdirSync(targetDir, { recursive: true });
54-
fs.copyFileSync(file, targetFile);
55-
fs.chmodSync(targetFile, 0o755); // Make executable
56-
57-
console.log(`Copied ${file} to ${targetFile}`);
56+
const platform = filename.replace(/-rslint$/, ''); // e.g. linux-x64
57+
const tuples = platformToTuples[platform];
58+
if (!tuples) {
59+
console.log(`Warning: no tuple mapping for ${platform}, skipping`);
60+
continue;
61+
}
62+
const binName = isWindows ? 'rslint.exe' : 'rslint';
63+
64+
for (const tuple of tuples) {
65+
const targetDir = path.join('npm', 'rslint', tuple);
66+
const targetFile = path.join(targetDir, binName);
67+
fs.mkdirSync(targetDir, { recursive: true });
68+
fs.copyFileSync(file, targetFile);
69+
fs.chmodSync(targetFile, 0o755); // Make executable
70+
console.log(`Copied ${file} to ${targetFile}`);
71+
}
5872
}
5973

6074
// Find and move tsgo binaries to lib directory
@@ -129,6 +143,26 @@ async function moveArtifacts() {
129143
console.log(`Copied built files to ${targetLibDir}`);
130144
}
131145

146+
// Move napi `.node` parser binaries into the @rslint/native-{tuple}
147+
// subpackages (flat, alongside the Go binary). Artifacts are named
148+
// `rslint.{tuple}.node`; target is `npm/rslint/{tuple}/` (libc suffix kept
149+
// so gnu/musl stay separate). Not chmod'd — a `.node` is dlopen'd.
150+
const nodeFiles = findBinaries('binaries', 'rslint.');
151+
console.log(`Found ${nodeFiles.length} napi .node files`);
152+
153+
for (const file of nodeFiles) {
154+
console.log(`Processing ${file}`);
155+
const filename = path.basename(file); // rslint.linux-x64-gnu.node
156+
const tuple = filename.replace(/^rslint\./, '').replace(/\.node$/, ''); // linux-x64-gnu
157+
const targetDir = path.join('npm', 'rslint', tuple);
158+
const targetFile = path.join(targetDir, filename);
159+
160+
fs.mkdirSync(targetDir, { recursive: true });
161+
fs.copyFileSync(file, targetFile);
162+
163+
console.log(`Copied ${file} to ${targetFile}`);
164+
}
165+
132166
console.log('Artifact move process completed successfully!');
133167
} catch (error) {
134168
console.error('Error:', error.message);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Setup Rust
2+
description: Install the pinned Rust toolchain and optionally enable build caching.
3+
4+
inputs:
5+
targets:
6+
description: Comma-separated target triples to install (e.g. aarch64-unknown-linux-gnu).
7+
required: false
8+
default: ''
9+
components:
10+
description: Comma-separated rustup components to install (e.g. rustfmt, clippy).
11+
required: false
12+
default: ''
13+
cache:
14+
description: Whether to enable Swatinem/rust-cache.
15+
required: false
16+
default: 'true'
17+
cache-key:
18+
description: Extra key segment for the cache (e.g. the target triple on the napi matrix).
19+
required: false
20+
default: ''
21+
cache-workspaces:
22+
description: Workspaces passed to Swatinem/rust-cache (defaults to the repo root).
23+
required: false
24+
default: ''
25+
cache-bin:
26+
description: >
27+
Whether rust-cache caches ~/.cargo/bin. Set 'false' on runner images where
28+
caching the cargo shim breaks `cargo fmt`/`clippy` (Swatinem/rust-cache#341).
29+
required: false
30+
default: 'true'
31+
32+
runs:
33+
using: composite
34+
steps:
35+
- name: Install Rust
36+
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
37+
with:
38+
targets: ${{ inputs.targets }}
39+
components: ${{ inputs.components }}
40+
41+
- name: Rust cache
42+
if: ${{ inputs.cache == 'true' }}
43+
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
44+
with:
45+
key: ${{ inputs.cache-key }}
46+
workspaces: ${{ inputs.cache-workspaces }}
47+
cache-bin: ${{ inputs.cache-bin }}
48+
cache-on-failure: true

.github/workflows/benchmark-cli.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,19 @@ jobs:
2828
- name: Setup Node.js
2929
uses: ./.github/actions/setup-node
3030

31+
- name: Setup Rust
32+
uses: ./.github/actions/setup-rust
33+
with:
34+
cache: 'false'
35+
36+
# build:js is now one rslib build covering the library surface AND the
37+
# eslint-plugin worker; the worker imports @rslint/native, whose
38+
# index.d.ts comes from `napi build`. So build napi first even though the
39+
# CLI benchmarks only exercise the Go binary.
40+
- name: Build napi parser
41+
run: pnpm --filter @rslint/native run build
3142
- name: Build JS package
32-
run: pnpm --filter @rslint/core run build
43+
run: pnpm --filter @rslint/core run build:bin && pnpm --filter @rslint/core run build:js
3344

3445
- name: Run CLI benchmarks
3546
uses: CodSpeedHQ/action@d872884a306dd4853acf0f584f4b706cf0cc72a2

.github/workflows/ci.yml

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ jobs:
142142
- name: Setup Node.js
143143
uses: ./.github/actions/setup-node
144144

145+
# napi build produces @rslint/native's index.d.ts (needed by core's
146+
# build:js dts) + the win32 .node (needed at test runtime).
147+
- name: Setup Rust
148+
uses: ./.github/actions/setup-rust
149+
- name: Build napi parser (native)
150+
run: pnpm --filter @rslint/native run build
151+
145152
- name: Download rslint.exe
146153
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
147154
with:
@@ -155,7 +162,6 @@ jobs:
155162
pnpm --filter @rslint/core run build:js
156163
pnpm --filter @typescript-eslint/rule-tester build
157164
pnpm --filter rslint build
158-
pnpm --filter @rslint/eslint-plugin-runner build
159165
160166
- name: VSCode Test Cache
161167
uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732
@@ -224,6 +230,13 @@ jobs:
224230
- name: Setup Node.js
225231
uses: ./.github/actions/setup-node
226232

233+
# napi build produces @rslint/native's index.d.ts (needed by core's
234+
# build:js dts) + the host .node (needed at test runtime).
235+
- name: Setup Rust
236+
uses: ./.github/actions/setup-rust
237+
- name: Build napi parser (native)
238+
run: pnpm --filter @rslint/native run build
239+
227240
- name: Format
228241
if: runner.os == 'Linux'
229242
run: pnpm format:check
@@ -279,8 +292,16 @@ jobs:
279292
- name: Setup Node.js
280293
uses: ./.github/actions/setup-node
281294

295+
- name: Setup Rust
296+
uses: ./.github/actions/setup-rust
297+
with:
298+
cache: 'false'
299+
300+
# build:js is one rslib build (library surface + eslint-plugin worker);
301+
# the worker needs @rslint/native's index.d.ts from `napi build`.
282302
- name: Build
283303
run: |
304+
pnpm --filter @rslint/native run build
284305
pnpm --filter '@rslint/core' build:js
285306
pnpm --filter '@rslint/wasm' build
286307
test-rust:
@@ -297,15 +318,10 @@ jobs:
297318
submodules: true
298319

299320
- name: Setup Rust
300-
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
321+
uses: ./.github/actions/setup-rust
301322
with:
302323
components: rustfmt, clippy
303-
304-
- name: Rust Cache
305-
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
306-
with:
307-
workspaces: '.'
308-
cache-on-failure: true
324+
cache-workspaces: '.'
309325
# Workaround for Swatinem/rust-cache#341: on the new macos runner image
310326
# caching ~/.cargo/bin restores a broken `cargo` shim that resolves to
311327
# rustup-init, causing `cargo fmt`/`clippy` to fail with

.github/workflows/release.yml

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
publish-npm:
4848
if: ${{ inputs.to_release == 'all' || inputs.to_release == 'npm' }}
4949
name: ${{ inputs.dry_run == true && 'Dry Run - NPM Packages' || 'Publish NPM Packages' }}
50-
needs: [build]
50+
needs: [build, napi-build]
5151
runs-on: ubuntu-22.04
5252
environment: release
5353
permissions:
@@ -96,6 +96,17 @@ jobs:
9696

9797
- name: Move binaries
9898
uses: ./.github/actions/move-artifacts
99+
# @rslint/native ships index.js (the napi loader) + index.d.ts, both
100+
# generated by `napi build` (gitignored). core's build:js dts bundle
101+
# also needs index.d.ts. The per-platform .node binaries ship in the
102+
# @rslint/native-{tuple} subpackages, populated from the napi-build
103+
# artifacts by move-artifacts above.
104+
- name: Setup Rust
105+
uses: ./.github/actions/setup-rust
106+
with:
107+
cache: 'false'
108+
- name: Build napi parser (index.js + index.d.ts)
109+
run: pnpm --filter @rslint/native build
99110
- name: Build @rslint/core dist
100111
run: pnpm --filter @rslint/core build:js
101112
- name: Publish npm packages
@@ -244,6 +255,15 @@ jobs:
244255
- name: Format code
245256
run: pnpm format:check
246257

258+
# napi build produces index.d.ts (core build:js dts) + host .node
259+
# (runner tests at the Test step run the worker, which loads it).
260+
- name: Setup Rust
261+
uses: ./.github/actions/setup-rust
262+
with:
263+
cache: 'false'
264+
- name: Build napi parser (native)
265+
run: pnpm --filter @rslint/native build
266+
247267
- name: Build
248268
run: pnpm run build
249269

@@ -315,12 +335,7 @@ jobs:
315335
cache-name: ${{ matrix.node_os }}-${{ matrix.node_arch }}
316336

317337
- name: Setup Rust
318-
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
319-
320-
- name: Cache Rust
321-
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
322-
with:
323-
cache-on-failure: true
338+
uses: ./.github/actions/setup-rust
324339

325340
- name: Install pnpm
326341
run: corepack enable
@@ -361,3 +376,87 @@ jobs:
361376
with:
362377
name: ${{ matrix.node_os }}-${{ matrix.node_arch }}-tsgo-built
363378
path: typescript-go/built
379+
380+
# napi `.node` parser binaries, one per target → the @rslint/native-{tuple}
381+
# subpackages. Separate matrix from the Go build job (which is ubuntu-only):
382+
# napi win32-msvc/darwin need native runners since zig and
383+
# @napi-rs/cross-toolchain can't target windows-msvc. Each leg uploads a
384+
# uniquely-named artifact (`native-{tuple}` — upload-artifact@v4 rejects
385+
# dupes); move-artifacts matches the `rslint.` filename prefix. gnu cross
386+
# legs use --use-napi-cross (@napi-rs/cross-toolchain, gnu-only); musl uses
387+
# -x (cargo-zigbuild) since cross-toolchain has no musl sysroot.
388+
napi-build:
389+
name: Build napi (${{ matrix.settings.target }})
390+
strategy:
391+
fail-fast: false
392+
matrix:
393+
settings:
394+
- host: macos-latest
395+
target: aarch64-apple-darwin
396+
artifact: native-darwin-arm64
397+
- host: macos-latest
398+
target: x86_64-apple-darwin
399+
artifact: native-darwin-x64
400+
- host: windows-latest
401+
target: x86_64-pc-windows-msvc
402+
artifact: native-win32-x64-msvc
403+
- host: windows-latest
404+
target: aarch64-pc-windows-msvc
405+
artifact: native-win32-arm64-msvc
406+
- host: rspack-ubuntu-22.04-large
407+
target: x86_64-unknown-linux-gnu
408+
artifact: native-linux-x64-gnu
409+
- host: rspack-ubuntu-22.04-large
410+
target: aarch64-unknown-linux-gnu
411+
artifact: native-linux-arm64-gnu
412+
cross: '--use-napi-cross'
413+
- host: rspack-ubuntu-22.04-large
414+
target: x86_64-unknown-linux-musl
415+
artifact: native-linux-x64-musl
416+
cross: '-x'
417+
- host: rspack-ubuntu-22.04-large
418+
target: aarch64-unknown-linux-musl
419+
artifact: native-linux-arm64-musl
420+
cross: '-x'
421+
runs-on: ${{ matrix.settings.host }}
422+
steps:
423+
- name: Checkout
424+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
425+
with:
426+
fetch-depth: 1
427+
ref: ${{ github.event.inputs.branch }}
428+
submodules: true
429+
430+
- name: Setup Node.js
431+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
432+
with:
433+
node-version: '24'
434+
435+
- name: Install pnpm
436+
run: corepack enable
437+
438+
- name: Setup Rust
439+
uses: ./.github/actions/setup-rust
440+
with:
441+
targets: ${{ matrix.settings.target }}
442+
cache-key: ${{ matrix.settings.target }}
443+
444+
- name: Install dependencies
445+
run: pnpm install --frozen-lockfile
446+
447+
# musl legs cross-compile via cargo-zigbuild (napi -x); zig must be on
448+
# PATH (@napi-rs/cli auto-installs cargo-zigbuild when missing, but not
449+
# zig itself).
450+
- name: Setup zig (musl cross-compile)
451+
if: ${{ contains(matrix.settings.target, 'musl') }}
452+
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
453+
454+
- name: Build napi
455+
run: pnpm --filter @rslint/native exec napi build --platform --release --target ${{ matrix.settings.target }} ${{ matrix.settings.cross || '' }}
456+
457+
- name: Upload napi artifact
458+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
459+
with:
460+
name: ${{ matrix.settings.artifact }}
461+
path: crates/rslint-native/rslint.*.node
462+
if-no-files-found: error

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,16 @@ npm/*/rslint
151151
npm/*/rslint.exe
152152
npm/tsgo/*/local
153153

154+
# napi build artifacts (generated by @napi-rs/cli; never committed)
155+
crates/rslint-native/index.js
156+
crates/rslint-native/index.d.ts
157+
crates/rslint-native/*.node
158+
# per-platform binaries (napi .node + Go CLI) land in the subpackages at
159+
# publish time via move-artifacts; never committed
160+
npm/rslint/*/*.node
161+
npm/rslint/*/rslint
162+
npm/rslint/*/rslint.exe
163+
154164
.idea
155165

156166
## vscode settings

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,5 @@ internal/rules/**/*.md
2727
website/docs/en/rules/_meta.json
2828
website/docs/en/rules/*/
2929
packages/vscode-extension/__tests__/fixtures-monorepo/packages/broken/
30+
crates/rslint-native/index.js
31+
crates/rslint-native/index.d.ts

0 commit comments

Comments
 (0)