diff --git a/.github/workflows/memory-test.yml b/.github/workflows/memory-test.yml index c6758939..908bed1e 100644 --- a/.github/workflows/memory-test.yml +++ b/.github/workflows/memory-test.yml @@ -17,6 +17,7 @@ jobs: build_and_test: name: Memory leak detect job runs-on: ubuntu-latest + timeout-minutes: 40 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml index 0277db79..ee03cf7a 100644 --- a/.github/workflows/test-release.yaml +++ b/.github/workflows/test-release.yaml @@ -400,7 +400,7 @@ jobs: - name: Install ziglang uses: goto-bus-stop/setup-zig@v2 with: - version: 0.10.1 + version: 0.11.0 - name: Cache cargo uses: actions/cache@v3 @@ -486,6 +486,43 @@ jobs: - name: Check build run: cargo check -p ${{ matrix.settings.package }} -F ${{ matrix.settings.features }} + test-latest-bun: + runs-on: ubuntu-latest + name: Test latest bun + timeout-minutes: 10 + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'yarn' + - name: Install + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: x86_64-unknown-linux-gnu + - name: Cache cargo + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: stable-x86_64-unknown-linux-gnu-node@18-cargo-cache + - name: Install dependencies + run: yarn install --immutable --mode=skip-build + - name: Build + run: | + bun run build + bun run build:test + - name: Test + run: bun run test:bun + release-npm: runs-on: ubuntu-latest needs: diff --git a/cli/ava.config.js b/cli/ava.config.js deleted file mode 100644 index 3c7783e6..00000000 --- a/cli/ava.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - extensions: { - ts: 'module', - }, - files: ['**/__tests__/**/*.spec.ts'], -} diff --git a/cli/package.json b/cli/package.json index 9e34bab4..3e41309c 100644 --- a/cli/package.json +++ b/cli/package.json @@ -95,5 +95,13 @@ "build": "tsc && yarn build:cjs", "build:cjs": "node ./esbuild.mjs", "test": "node --loader ts-node/esm/transpile-only ../node_modules/ava/entrypoints/cli.mjs" + }, + "ava": { + "extensions": { + "ts": "module" + }, + "files": [ + "**/__tests__/**/*.spec.ts" + ] } } diff --git a/cli/tsconfig.json b/cli/tsconfig.json index 757bcfeb..92d0e13d 100644 --- a/cli/tsconfig.json +++ b/cli/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "strict": true, "target": "ES2022", - "module": "ESNext", + "module": "NodeNext", "moduleResolution": "nodenext", "resolveJsonModule": true, "allowSyntheticDefaultImports": true, diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.md b/examples/napi/__tests__/__snapshots__/values.spec.ts.md index b45716d5..00e2d657 100644 --- a/examples/napi/__tests__/__snapshots__/values.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/values.spec.ts.md @@ -12,6 +12,8 @@ Generated by [AVA](https://avajs.dev). '@napi-rs/cli', '@types/lodash', 'ava', + 'cross-env', + 'electron', 'lodash', 'sinon', ] diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap index 8f5900f6..d16555c1 100644 Binary files a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap differ diff --git a/examples/napi/__tests__/bun-test.js b/examples/napi/__tests__/bun-test.js new file mode 100644 index 00000000..402d264c --- /dev/null +++ b/examples/napi/__tests__/bun-test.js @@ -0,0 +1,3 @@ +import { test, expect } from 'bun:test' + +export { test, expect } diff --git a/examples/napi/__tests__/error-msg.spec.ts b/examples/napi/__tests__/error-msg.spec.ts index 38a3d253..2a0600a2 100644 --- a/examples/napi/__tests__/error-msg.spec.ts +++ b/examples/napi/__tests__/error-msg.spec.ts @@ -1,6 +1,10 @@ +import { createRequire } from 'node:module' + import test from 'ava' -import { receiveString } from '..' +const require = createRequire(import.meta.url) + +const { receiveString }: typeof import('../index.js') = require('../index.node') test('Function message', (t) => { // @ts-expect-error diff --git a/examples/napi/__tests__/generator.spec.ts b/examples/napi/__tests__/generator.spec.ts index 4f2cfea8..7fb2eff7 100644 --- a/examples/napi/__tests__/generator.spec.ts +++ b/examples/napi/__tests__/generator.spec.ts @@ -1,6 +1,14 @@ +import { createRequire } from 'node:module' + import test from 'ava' -import { Fib, Fib2, Fib3 } from '..' +const require = createRequire(import.meta.url) + +const { + Fib, + Fib2, + Fib3, +}: typeof import('../index.js') = require('../index.node') for (const [index, factory] of [ () => new Fib(), diff --git a/examples/napi/__tests__/object-attr.spec.ts b/examples/napi/__tests__/object-attr.spec.ts index e06dc214..4e401063 100644 --- a/examples/napi/__tests__/object-attr.spec.ts +++ b/examples/napi/__tests__/object-attr.spec.ts @@ -1,6 +1,12 @@ +import { createRequire } from 'node:module' + import test from 'ava' -import { NotWritableClass } from '..' +const require = createRequire(import.meta.url) + +const { + NotWritableClass, +}: typeof import('../index.js') = require('../index.node') test('Not Writable Class', (t) => { const obj = new NotWritableClass('1') diff --git a/examples/napi/__tests__/strict.spec.ts b/examples/napi/__tests__/strict.spec.ts index 43e47e57..5bef827a 100644 --- a/examples/napi/__tests__/strict.spec.ts +++ b/examples/napi/__tests__/strict.spec.ts @@ -1,6 +1,10 @@ +import { createRequire } from 'node:module' + import test from 'ava' -import { +const require = createRequire(import.meta.url) + +const { validateArray, validateTypedArray, validateBigint, @@ -20,7 +24,7 @@ import { returnUndefinedIfInvalid, returnUndefinedIfInvalidPromise, validateOptional, -} from '..' +}: typeof import('../index.d.ts') = require('../index.node') test('should validate array', (t) => { t.is(validateArray([1, 2, 3]), 3) diff --git a/examples/napi/__tests__/test.framework.js b/examples/napi/__tests__/test.framework.js new file mode 100644 index 00000000..63c720b0 --- /dev/null +++ b/examples/napi/__tests__/test.framework.js @@ -0,0 +1,67 @@ +const { bun } = process.versions + +/**@type {import('ava').TestFn} */ +let testRunner + +if (bun) { + const { test, expect } = await import('./bun-test.js') + const testContext = { + is: (actual, expected) => { + expect(actual).toEqual(expected) + }, + deepEqual: (actual, expected) => { + expect(actual).toEqual(expected) + }, + throws: (fn, expected) => { + if (expected) { + expect(fn).toThrow(expected) + } else { + expect(fn).toThrow() + } + }, + notThrows: (fn, expected) => { + if (expected) { + expect(fn).not.toThrow(expected) + } else { + expect(fn).not.toThrow() + } + }, + throwsAsync: async (fn, expected) => { + if (expected) { + expect(fn instanceof Promise ? fn : await fn()).rejects.toEqual( + expected, + ) + } else { + expect(fn instanceof Promise ? fn : await fn()).rejects.toBeTruthy() + } + }, + notThrowsAsync: async (fn, expected) => { + if (expected) { + expect(fn instanceof Promise ? fn : await fn()).resolves.toBe(expected) + } else { + expect(fn instanceof Promise ? fn : await fn()).resolves.toBeTruthy() + } + }, + true: (actual, message) => { + expect(actual).toBe(true, message) + }, + false: (actual, message) => { + expect(actual).toBe(false, message) + }, + } + testRunner = (title, spec) => { + test(title, async () => { + await Promise.resolve(spec(testContext)) + }) + } + testRunner.skip = (label, fn) => { + test.skip(label, () => { + fn(testContext) + }) + } +} else { + const test = (await import('ava')).default + testRunner = test +} + +export { testRunner as test } diff --git a/examples/napi/__tests__/tsfn-error.js b/examples/napi/__tests__/tsfn-error.cjs similarity index 100% rename from examples/napi/__tests__/tsfn-error.js rename to examples/napi/__tests__/tsfn-error.cjs diff --git a/examples/napi/__tests__/typegen.spec.ts b/examples/napi/__tests__/typegen.spec.ts index 68d624d0..dd440622 100644 --- a/examples/napi/__tests__/typegen.spec.ts +++ b/examples/napi/__tests__/typegen.spec.ts @@ -1,8 +1,11 @@ -import { readFileSync } from 'fs' -import { join } from 'path' +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' import test from 'ava' +const __dirname = join(fileURLToPath(import.meta.url), '..') + test('should generate correct type def file', (t) => { t.snapshot(readFileSync(join(__dirname, '..', 'index.d.ts'), 'utf8')) }) diff --git a/examples/napi/__tests__/unload.spec.js b/examples/napi/__tests__/unload.spec.js index 2b9266e6..6b995e93 100644 --- a/examples/napi/__tests__/unload.spec.js +++ b/examples/napi/__tests__/unload.spec.js @@ -1,8 +1,12 @@ // use the commonjs syntax to prevent compiler from transpiling the module syntax -const path = require('path') +import { createRequire } from 'node:module' +import * as path from 'node:path' -const test = require('ava').default +import test from 'ava' + +const require = createRequire(import.meta.url) +const __dirname = path.dirname(new URL(import.meta.url).pathname) test('unload module', (t) => { const { add } = require('../index.node') diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index 6a644e6b..cce98b95 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -1,10 +1,18 @@ -import { exec } from 'child_process' -import { join } from 'path' +import { exec } from 'node:child_process' +import { createRequire } from 'node:module' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' -import test from 'ava' import { spy } from 'sinon' -import { +import type { AliasedStruct } from '../index.js' + +import { test } from './test.framework.js' + +const require = createRequire(import.meta.url) +const __dirname = join(fileURLToPath(import.meta.url), '..') + +const { DEFAULT_COST, add, fibonacci, @@ -78,7 +86,6 @@ import { receiveAllOptionalObject, fnReceivedAliased, ALIAS, - AliasedStruct, appendBuffer, returnNull, returnUndefined, @@ -134,7 +141,7 @@ import { chronoNativeDateTime, chronoNativeDateTimeReturn, throwAsyncError, -} from '..' +}: typeof import('../index.d.ts') = require('../index.node') test('export const', (t) => { t.is(DEFAULT_COST, 12) @@ -251,7 +258,7 @@ test('class factory', (t) => { const error = t.throws(() => new ClassWithFactory()) t.true( - error!.message.startsWith( + error?.message.startsWith( 'Class contains no `constructor`, can not new it!', ), ) @@ -463,7 +470,7 @@ test('should throw if object type is not matched', (t) => { // @ts-expect-error const err1 = t.throws(() => receiveStrictObject({ name: 1 })) t.is( - err1!.message, + err1?.message, 'Failed to convert JavaScript value `Number 1 ` into rust type `String`', ) // @ts-expect-error @@ -472,7 +479,7 @@ test('should throw if object type is not matched', (t) => { }) test('aliased rust struct and enum', (t) => { - const a: ALIAS = ALIAS.A + const a = ALIAS.A const b: AliasedStruct = { a, b: 1, @@ -506,7 +513,7 @@ test('serde-roundtrip', (t) => { t.is(testSerdeRoundtrip(null), null) let err = t.throws(() => testSerdeRoundtrip(undefined)) - t.is(err!.message, 'undefined cannot be represented as a serde_json::Value') + t.is(err?.message, 'undefined cannot be represented as a serde_json::Value') err = t.throws(() => testSerdeRoundtrip(() => {})) t.is( @@ -681,7 +688,7 @@ test('external', (t) => { // @ts-expect-error const e = t.throws(() => getExternal(ext2)) t.is( - e!.message, + e?.message, 'T on `get_value_external` is not the type of wrapped object', ) }) @@ -783,7 +790,7 @@ Napi4Test('throw error from thread safe function', async (t) => { threadsafeFunctionThrowError(reject) }) const err = await t.throwsAsync(throwPromise) - t.is(err!.message, 'ThrowFromNative') + t.is(err?.message, 'ThrowFromNative') }) Napi4Test('thread safe function closure capture data', (t) => { @@ -803,7 +810,7 @@ Napi4Test('resolve value from thread safe function fatal mode', async (t) => { }) Napi4Test('throw error from thread safe function fatal mode', (t) => { - const p = exec('node ./tsfn-error.js', { + const p = exec('node ./tsfn-error.cjs', { cwd: __dirname, }) let stderr = Buffer.from([]) diff --git a/examples/napi/__tests__/worker-thread.spec.ts b/examples/napi/__tests__/worker-thread.spec.cjs similarity index 71% rename from examples/napi/__tests__/worker-thread.spec.ts rename to examples/napi/__tests__/worker-thread.spec.cjs index b812cd0c..e1429460 100644 --- a/examples/napi/__tests__/worker-thread.spec.ts +++ b/examples/napi/__tests__/worker-thread.spec.cjs @@ -1,9 +1,9 @@ -import { join } from 'path' -import { Worker } from 'worker_threads' +const { join } = require('node:path') +const { Worker } = require('node:worker_threads') -import test from 'ava' +const test = require('ava').default -import { Animal, Kind, DEFAULT_COST } from '..' +const { Animal, Kind, DEFAULT_COST } = require('../index.node') // aarch64-unknown-linux-gnu is extremely slow in CI, skip it or it will timeout const t = @@ -12,8 +12,10 @@ const t = t('should be able to require in worker thread', async (t) => { await Promise.all( Array.from({ length: 100 }).map(() => { - const w = new Worker(join(__dirname, 'worker.js')) - return new Promise((resolve, reject) => { + const w = new Worker(join(__dirname, 'worker.cjs'), { + execArgv: [] + }) + return new Promise((resolve, reject) => { w.postMessage({ type: 'require' }) w.on('message', (msg) => { t.is(msg, Animal.withKind(Kind.Cat).whoami() + DEFAULT_COST) @@ -35,8 +37,10 @@ t('custom GC works on worker_threads', async (t) => { await Promise.all( Array.from({ length: 50 }).map(() => Promise.all([ - new Promise((resolve, reject) => { - const w = new Worker(join(__dirname, 'worker.js')) + new Promise((resolve, reject) => { + const w = new Worker(join(__dirname, 'worker.cjs'), { + execArgv: [] + }) w.postMessage({ type: 'async:buffer', }) @@ -50,8 +54,10 @@ t('custom GC works on worker_threads', async (t) => { }).then((w) => { return w.terminate() }), - new Promise((resolve, reject) => { - const w = new Worker(join(__dirname, 'worker.js')) + new Promise((resolve, reject) => { + const w = new Worker(join(__dirname, 'worker.cjs'), { + execArgv: [] + }) w.postMessage({ type: 'async:arraybuffer', }) @@ -73,8 +79,10 @@ t('custom GC works on worker_threads', async (t) => { t('should be able to new Class in worker thread concurrently', async (t) => { await Promise.all( Array.from({ length: 100 }).map(() => { - const w = new Worker(join(__dirname, 'worker.js')) - return new Promise((resolve, reject) => { + const w = new Worker(join(__dirname, 'worker.cjs'), { + execArgv: [] + }) + return new Promise((resolve, reject) => { w.postMessage({ type: 'constructor' }) w.on('message', (msg) => { t.is(msg, 'Ellie') diff --git a/examples/napi/__tests__/worker.js b/examples/napi/__tests__/worker.cjs similarity index 100% rename from examples/napi/__tests__/worker.js rename to examples/napi/__tests__/worker.cjs diff --git a/examples/napi/electron.js b/examples/napi/electron.cjs similarity index 100% rename from examples/napi/electron.js rename to examples/napi/electron.cjs diff --git a/examples/napi/package.json b/examples/napi/package.json index 980eb609..3fe0bbd2 100644 --- a/examples/napi/package.json +++ b/examples/napi/package.json @@ -2,35 +2,34 @@ "name": "@examples/napi", "private": true, "version": "0.0.0", + "type": "module", "main": "./index.node", "types": "./index.d.ts", "scripts": { "build": "napi-raw build --no-js", - "test": "ava" + "test": "cross-env TS_NODE_PROJECT=./tsconfig.json node --es-module-specifier-resolution=node --loader ts-node/esm/transpile-only ../../node_modules/ava/entrypoints/cli.mjs" }, "devDependencies": { "@napi-rs/cli": "workspace:*", "@types/lodash": "^4.14.195", "ava": "^5.3.1", + "cross-env": "7.0.3", + "electron": "^26.2.1", "lodash": "^4.17.21", "sinon": "^15.2.0" }, "ava": { - "extensions": [ - "ts", - "tsx", - "js" - ], - "require": [ - "ts-node/register/transpile-only" - ], + "extensions": { + "ts": "module", + "cts": "commonjs", + "cjs": true + }, "files": [ "__tests__/**/*.spec.ts", - "__tests__/**/*.spec.js" + "__tests__/**/*.spec.cts", + "__tests__/**/*.spec.js", + "__tests__/**/*.spec.cjs" ], - "environmentVariables": { - "TS_NODE_PROJECT": "../tsconfig.json" - }, "timeout": "10m" } } diff --git a/examples/napi/tsconfig.json b/examples/napi/tsconfig.json index 225193d4..dd161b20 100644 --- a/examples/napi/tsconfig.json +++ b/examples/napi/tsconfig.json @@ -3,10 +3,13 @@ "include": ["."], "compilerOptions": { "outDir": "./dist", - "rootDir": "__tests__", - "target": "ES2018", + "rootDir": ".", + "target": "ESNext", + "module": "ESNext", "skipLibCheck": false, - "noEmit": true + "noEmit": true, + "types": ["bun-types"], + "importHelpers": false }, - "exclude": ["dist", "electron.js", "electron-renderer"] + "exclude": ["dist", "electron.cjs", "electron-renderer", "node_modules"] } diff --git a/package.json b/package.json index 4ec6ecff..52d35cde 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,9 @@ "format:toml": "taplo format", "lint": "eslint -c .eslintrc.yml .", "test": "lerna run test --concurrency=1 --ignore @napi-rs/cli", + "test:bun": "bun test examples/napi/__tests__/values.spec.ts", "test:cli": "yarn workspace @napi-rs/cli test", - "test:electron": "electron examples/napi/electron.js", + "test:electron": "electron examples/napi/electron.cjs", "test:macro": "cargo test -p napi-examples", "test:memory": "node memory-testing/index.mjs", "postinstall": "husky install", @@ -75,9 +76,10 @@ "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "ava": "^5.3.1", + "bun-types": "^1.0.2", "c8": "^8.0.0", "cross-env": "^7.0.3", - "electron": "26.2.1", + "electron": "^26.2.1", "esbuild": "^0.19.0", "eslint": "^8.45.0", "eslint-config-prettier": "^9.0.0", diff --git a/yarn.lock b/yarn.lock index 524e5059..0805d5d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -338,6 +338,8 @@ __metadata: "@napi-rs/cli": "workspace:*" "@types/lodash": ^4.14.195 ava: ^5.3.1 + cross-env: 7.0.3 + electron: ^26.2.1 lodash: ^4.17.21 sinon: ^15.2.0 languageName: unknown @@ -2309,6 +2311,13 @@ __metadata: languageName: node linkType: hard +"bun-types@npm:^1.0.2": + version: 1.0.2 + resolution: "bun-types@npm:1.0.2" + checksum: 02f9b6b37a7c3d6199e9cb990a635f374967a4a9708e525ca5f895268ced52e3bdb90d3252f3fd90d85287fd75a07b0ecbc5bcb9e003cba63c10aaa99ac9e070 + languageName: node + linkType: hard + "byte-size@npm:8.1.1": version: 8.1.1 resolution: "byte-size@npm:8.1.1" @@ -2983,7 +2992,7 @@ __metadata: languageName: node linkType: hard -"cross-env@npm:^7.0.3": +"cross-env@npm:7.0.3, cross-env@npm:^7.0.3": version: 7.0.3 resolution: "cross-env@npm:7.0.3" dependencies: @@ -3306,7 +3315,7 @@ __metadata: languageName: node linkType: hard -"electron@npm:26.2.1": +"electron@npm:^26.2.1": version: 26.2.1 resolution: "electron@npm:26.2.1" dependencies: @@ -6493,9 +6502,10 @@ __metadata: "@typescript-eslint/eslint-plugin": ^6.0.0 "@typescript-eslint/parser": ^6.0.0 ava: ^5.3.1 + bun-types: ^1.0.2 c8: ^8.0.0 cross-env: ^7.0.3 - electron: 26.2.1 + electron: ^26.2.1 esbuild: ^0.19.0 eslint: ^8.45.0 eslint-config-prettier: ^9.0.0