diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a86bac4..a5488b3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,3 +21,21 @@ jobs: test_target: "*.test.ts" jsr_dependencies: "@std/assert @std/async @cross/runtime" npm_dependencies: "sinon" + npm_build: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - name: Setup Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v2.x + - name: Build npm package + run: deno task build:dist + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + - name: Smoke test Node artifact + run: node -e "import('./dist/npm.js')" diff --git a/build/build.ts b/build/build.ts index 1b8ce59..75f5c87 100644 --- a/build/build.ts +++ b/build/build.ts @@ -1,6 +1,5 @@ import esbuild from "esbuild"; -import { dtsPlugin } from "esbuild-plugin-d.ts"; -import { cp, readFile, writeFile } from "@cross/fs"; +import { readFile, writeFile } from "@cross/fs"; import { dirname, fromFileUrl, resolve } from "@std/path"; /** @@ -68,50 +67,51 @@ if (command === "clean") { /* Handle argument `build`: Transpile and generate typings */ } else if (command === "build") { + // Plugin to bundle JSR packages like @cross/runtime by resolving them via Deno's cache + const jsrResolverPlugin: esbuild.Plugin = { + name: "jsr-resolver", + setup(pluginBuild) { + pluginBuild.onResolve({ filter: /^@cross\/runtime$/ }, async (_args) => { + const cmd = new Deno.Command("deno", { + args: ["info", "--json", "jsr:@cross/runtime"], + cwd: relativeProjectRoot, + stdout: "piped", + stderr: "piped", + }); + const { stdout, success } = await cmd.output(); + if (!success) return null; + const info = JSON.parse(new TextDecoder().decode(stdout)); + const mod = info.modules?.find((m: { specifier: string; local?: string }) => m.local && m.specifier?.endsWith("mod.ts")); + if (mod?.local) return { path: mod.local, namespace: "jsr-ts" }; + return null; + }); + pluginBuild.onLoad({ filter: /.*/, namespace: "jsr-ts" }, async (args) => { + const contents = await Deno.readTextFile(args.path); + return { contents, loader: "ts" }; + }); + }, + }; + + // Build the ESM JavaScript bundle await build({ - entryPoints: [resolve(relativeProjectRoot, "mod.ts")], + entryPoints: [resolve(relativeProjectRoot, "npm.ts")], bundle: true, minify: true, sourcemap: false, + outdir: resolvedDistPath, + platform: "node", + format: "esm", // Mark runtime-specific modules as external - they won't be bundled // node:test is a Node.js built-in, bun:test is a Bun built-in external: ["bun:test", "node:test"], + plugins: [jsrResolverPlugin], // Use banner to add a comment banner: { js: `// @cross/test - Cross-runtime testing for Deno, Bun, and Node.js // This build is for Node.js. For Deno, use JSR: jsr:@cross/test `, }, - }, [ - { - outdir: resolvedDistPath, - platform: "node", - format: "cjs", - outExtension: { ".js": ".cjs" }, - }, - { - outdir: resolvedDistPath, - platform: "node", - format: "esm", - plugins: [dtsPlugin({ - experimentalBundling: true, - tsconfig: { - compilerOptions: { - declaration: true, - emitDeclarationOnly: true, - allowImportingTsExtensions: true, - lib: ["es6", "dom"], - }, - }, - })], - }, - ]); - - // Just re-use the .d.ts for commonjs, as .d.cts - await cp( - resolve(resolvedDistPath, "mod.d.ts"), - resolve(resolvedDistPath, "mod.d.cts"), - ); + }); /* Handle argument `package`: Generate package.json based on a base config and values from deno.json */ } else if (command === "package") { diff --git a/build/package.template.json b/build/package.template.json index 6f1086f..8a5b4e7 100644 --- a/build/package.template.json +++ b/build/package.template.json @@ -26,21 +26,10 @@ "framework" ], "type": "module", - "main": "./dist/mod.cjs", - "module": "./dist/mod.js", - "types": "./dist/mod.d.ts", + "main": "./dist/npm.js", "exports": { "./package.json": "./package.json", - ".": { - "import": { - "types": "./dist/mod.d.ts", - "default": "./dist/mod.js" - }, - "require": { - "types": "./dist/mod.d.cts", - "default": "./dist/mod.cjs" - } - } + ".": "./dist/npm.js" }, "license": "MIT" } diff --git a/deno.json b/deno.json index 2a197b6..c0304ff 100644 --- a/deno.json +++ b/deno.json @@ -24,6 +24,6 @@ "sinon": "npm:sinon@~19.0.2" }, "publish": { - "exclude": [".github", "*.test.ts", "build", "dist"] + "exclude": [".github", "*.test.ts", "build", "dist", "npm.ts"] } } diff --git a/npm.ts b/npm.ts new file mode 100644 index 0000000..c96ef61 --- /dev/null +++ b/npm.ts @@ -0,0 +1,30 @@ +// Entry point for the npm (Node.js) build - uses the Node.js shim directly. +// For cross-runtime usage via JSR, use jsr:@cross/test instead. +import type { BrowserTestResult, TestSubject, WrappedTestOptions } from "./mod.ts"; +import { wrappedTest } from "./shims/node.ts"; + +export type { BrowserTestResult, ContextStepFunction, SimpleStepFunction, StepFunction, StepOptions, StepSubject, TestContext, TestSubject, WrappedTest, WrappedTestOptions } from "./mod.ts"; + +/** + * Defines and executes a single test (Node.js). + * @param name - The name of the test. + * @param testFn - The function containing the test logic. + * @param options? - Options for the test. + */ +export async function test(name: string, testFn: TestSubject, options: WrappedTestOptions = {}) { + await wrappedTest(name, testFn, options); +} + +/** + * Not applicable in Node.js - always returns undefined. + */ +export function getTestResults(): BrowserTestResult[] | undefined { + return undefined; +} + +/** + * Not applicable in Node.js - no-op. + */ +export function printTestSummary(): void { + // no-op +}