From a27e16da4299070fd15b2dc200f296e3340f2b79 Mon Sep 17 00:00:00 2001 From: Martyn Janes Date: Mon, 26 Oct 2026 07:48:10 +0800 Subject: [PATCH 1/2] feat: add runtime detection --- cspell.json | 3 +- packages/core/docs/examples.md | 14 +++ packages/core/src/index.ts | 3 + packages/core/src/models/runtimeEngine.ts | 48 ++++++++ packages/core/src/models/runtimeOs.ts | 33 +++++ packages/core/src/utils/runtime.ts | 113 ++++++++++++++++++ packages/core/tests/utils/runtime.spec.ts | 103 ++++++++++++++++ .../tests/ciphers/chacha20poly1305.spec.ts | 7 +- .../tests/helpers/nativeModulesCrypto.spec.ts | 29 +++-- packages/crypto/tests/nativeCryptoVariants.ts | 7 +- .../tests/tsTransform.spec.ts | 5 +- 11 files changed, 342 insertions(+), 23 deletions(-) create mode 100644 packages/core/src/models/runtimeEngine.ts create mode 100644 packages/core/src/models/runtimeOs.ts create mode 100644 packages/core/src/utils/runtime.ts create mode 100644 packages/core/tests/utils/runtime.spec.ts diff --git a/cspell.json b/cspell.json index b434610ca..34ac8cb26 100644 --- a/cspell.json +++ b/cspell.json @@ -72,7 +72,8 @@ "wordprocessingml", "spreadsheetml", "presentationml", - "unuse" + "unuse", + "workerd" ], "ignoreRegExpList": [ "/from\\s+(['\"]).*\\1/", diff --git a/packages/core/docs/examples.md b/packages/core/docs/examples.md index e304d55c1..b6f747d25 100644 --- a/packages/core/docs/examples.md +++ b/packages/core/docs/examples.md @@ -256,6 +256,20 @@ NativeModules.getModule('node:crypto'); // the module, or undefined if init() wa NativeModules.names(); // ['node:crypto'] ``` +## Runtime + +```typescript +import { Runtime, RuntimeEngine, RuntimeOs } from '@3sixty/core'; + +Runtime.engine(); // 'node', 'bun', 'deno', 'workerd', 'browser', 'webWorker' or 'unknown' +Runtime.version(); // e.g. '24.18.0' on Node, the user agent in a browser +Runtime.os(); // 'windows', 'mac', 'linux' or 'unknown' + +if (Runtime.engine() === RuntimeEngine.Bun && Runtime.os() === RuntimeOs.Linux) { + // Bun specific handling +} +``` + ## RandomHelper and NumberHelper ```typescript diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e20ad8666..444acdda1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -50,6 +50,8 @@ export * from "./models/ISharedObjectBufferWorkerMessage.js"; export * from "./models/IUrlParts.js"; export * from "./models/IValidationFailure.js"; export * from "./models/mutexMessageTypes.js"; +export * from "./models/runtimeEngine.js"; +export * from "./models/runtimeOs.js"; export * from "./models/sharedObjectBufferMessageTypes.js"; export * from "./types/bitString.js"; export * from "./types/duration.js"; @@ -70,6 +72,7 @@ export * from "./utils/i18n.js"; export * from "./utils/is.js"; export * from "./utils/mutex.js"; export * from "./utils/nativeModules.js"; +export * from "./utils/runtime.js"; export * from "./utils/sharedObjectBuffer.js"; export * from "./utils/sharedStore.js"; export * from "./utils/validation.js"; diff --git a/packages/core/src/models/runtimeEngine.ts b/packages/core/src/models/runtimeEngine.ts new file mode 100644 index 000000000..5722f72bd --- /dev/null +++ b/packages/core/src/models/runtimeEngine.ts @@ -0,0 +1,48 @@ +// Copyright 2026 3Sixty Trade Ltd. +// SPDX-License-Identifier: Apache-2.0. + +/** + * The JavaScript runtime engines that can be detected. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export const RuntimeEngine = { + /** + * Node.js. + */ + Node: "node", + + /** + * Bun. + */ + Bun: "bun", + + /** + * Deno. + */ + Deno: "deno", + + /** + * Cloudflare Workers. + */ + Workerd: "workerd", + + /** + * A browser window. + */ + Browser: "browser", + + /** + * A web worker. + */ + WebWorker: "webWorker", + + /** + * A runtime that could not be identified. + */ + Unknown: "unknown" +} as const; + +/** + * The JavaScript runtime engines that can be detected. + */ +export type RuntimeEngine = (typeof RuntimeEngine)[keyof typeof RuntimeEngine]; diff --git a/packages/core/src/models/runtimeOs.ts b/packages/core/src/models/runtimeOs.ts new file mode 100644 index 000000000..402acd967 --- /dev/null +++ b/packages/core/src/models/runtimeOs.ts @@ -0,0 +1,33 @@ +// Copyright 2026 3Sixty Trade Ltd. +// SPDX-License-Identifier: Apache-2.0. + +/** + * The operating systems that can be detected. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export const RuntimeOs = { + /** + * Windows. + */ + Windows: "windows", + + /** + * macOS. + */ + Mac: "mac", + + /** + * Linux. + */ + Linux: "linux", + + /** + * An operating system that could not be identified. + */ + Unknown: "unknown" +} as const; + +/** + * The operating systems that can be detected. + */ +export type RuntimeOs = (typeof RuntimeOs)[keyof typeof RuntimeOs]; diff --git a/packages/core/src/utils/runtime.ts b/packages/core/src/utils/runtime.ts new file mode 100644 index 000000000..8c12bf46f --- /dev/null +++ b/packages/core/src/utils/runtime.ts @@ -0,0 +1,113 @@ +// Copyright 2026 3Sixty Trade Ltd. +// SPDX-License-Identifier: Apache-2.0. +import { Is } from "./is.js"; +import { NativeModules } from "./nativeModules.js"; +import { RuntimeEngine } from "../models/runtimeEngine.js"; +import { RuntimeOs } from "../models/runtimeOs.js"; + +/** + * Detect the JavaScript runtime the code is executing in. + */ +export class Runtime { + /** + * Runtime name for the class. + */ + public static readonly CLASS_NAME: string = nameof(); + + /** + * Get the runtime engine. Bun, Deno and Cloudflare Workers are checked before Node + * as they all provide a Node compatible process global. + * @returns The runtime engine. + */ + public static engine(): RuntimeEngine { + if (Is.stringValue(Runtime.processVersions()?.bun)) { + return RuntimeEngine.Bun; + } + if (Is.object(NativeModules.getType("Deno"))) { + return RuntimeEngine.Deno; + } + if (Runtime.userAgent() === "Cloudflare-Workers") { + return RuntimeEngine.Workerd; + } + if (Is.stringValue(Runtime.processVersions()?.node)) { + return RuntimeEngine.Node; + } + if ( + Is.object(NativeModules.getType("window")) && + Is.object(NativeModules.getType("document")) + ) { + return RuntimeEngine.Browser; + } + if (Is.function(NativeModules.getType("importScripts"))) { + return RuntimeEngine.WebWorker; + } + return RuntimeEngine.Unknown; + } + + /** + * Get the version of the runtime engine. + * @returns The version, the user agent for browsers and workers, or an empty string if unknown. + */ + public static version(): string { + switch (Runtime.engine()) { + case RuntimeEngine.Bun: + return Runtime.processVersions()?.bun ?? ""; + case RuntimeEngine.Deno: + return NativeModules.getType<{ version?: { deno?: string } }>("Deno")?.version?.deno ?? ""; + case RuntimeEngine.Node: + return Runtime.processVersions()?.node ?? ""; + case RuntimeEngine.Workerd: + case RuntimeEngine.Browser: + case RuntimeEngine.WebWorker: + return Runtime.userAgent() ?? ""; + default: + return ""; + } + } + + /** + * Get the operating system. + * @returns The operating system. + */ + public static os(): RuntimeOs { + const name = + NativeModules.getType<{ build?: { os?: string } }>("Deno")?.build?.os ?? + NativeModules.getType<{ platform?: string }>("process")?.platform ?? + NativeModules.getType<{ userAgentData?: { platform?: string }; platform?: string }>( + "navigator" + )?.userAgentData?.platform ?? + NativeModules.getType<{ platform?: string }>("navigator")?.platform ?? + Runtime.userAgent() ?? + ""; + + if (/^win|windows/i.test(name)) { + return RuntimeOs.Windows; + } + if (/^darwin|mac/i.test(name)) { + return RuntimeOs.Mac; + } + if (/linux/i.test(name)) { + return RuntimeOs.Linux; + } + return RuntimeOs.Unknown; + } + + /** + * Get the process versions if there is a process global. + * @returns The versions, or undefined if there is no process global. + * @internal + */ + private static processVersions(): { [name: string]: string | undefined } | undefined { + return NativeModules.getType<{ versions?: { [name: string]: string | undefined } }>("process") + ?.versions; + } + + /** + * Get the navigator user agent if there is a navigator global. + * @returns The user agent, or undefined if there is no navigator global. + * @internal + */ + private static userAgent(): string | undefined { + return NativeModules.getType<{ userAgent?: string }>("navigator")?.userAgent; + } +} diff --git a/packages/core/tests/utils/runtime.spec.ts b/packages/core/tests/utils/runtime.spec.ts new file mode 100644 index 000000000..00a372c37 --- /dev/null +++ b/packages/core/tests/utils/runtime.spec.ts @@ -0,0 +1,103 @@ +// Copyright 2026 3Sixty Trade Ltd. +// SPDX-License-Identifier: Apache-2.0. +import { RuntimeEngine } from "../../src/models/runtimeEngine.js"; +import { RuntimeOs } from "../../src/models/runtimeOs.js"; +import { Runtime } from "../../src/utils/runtime.js"; + +/** + * Remove the server runtime globals so the browser and worker checks are reached. + */ +function stubNoServerRuntime(): void { + vi.stubGlobal("Deno", undefined); + vi.stubGlobal("process", undefined); +} + +describe("Runtime", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + test("can detect the current engine", () => { + expect(Runtime.engine()).toEqual( + process.versions.bun === undefined ? RuntimeEngine.Node : RuntimeEngine.Bun + ); + }); + + test("can get the current version", () => { + expect(Runtime.version()).toEqual(process.versions.bun ?? process.versions.node); + }); + + test("can detect the current os", () => { + const platforms: { [platform: string]: RuntimeOs } = { + win32: RuntimeOs.Windows, + darwin: RuntimeOs.Mac, + linux: RuntimeOs.Linux + }; + expect(Runtime.os()).toEqual(platforms[process.platform] ?? RuntimeOs.Unknown); + }); + + test("can detect Bun", () => { + vi.stubGlobal("process", { versions: { node: "24.0.0", bun: "1.3.0" }, platform: "linux" }); + expect(Runtime.engine()).toEqual(RuntimeEngine.Bun); + expect(Runtime.version()).toEqual("1.3.0"); + expect(Runtime.os()).toEqual(RuntimeOs.Linux); + }); + + test("can detect Deno before the Node compatible process global", () => { + vi.stubGlobal("Deno", { version: { deno: "2.1.0" }, build: { os: "darwin" } }); + vi.stubGlobal("process", { versions: { node: "22.0.0" }, platform: "linux" }); + expect(Runtime.engine()).toEqual(RuntimeEngine.Deno); + expect(Runtime.version()).toEqual("2.1.0"); + expect(Runtime.os()).toEqual(RuntimeOs.Mac); + }); + + test("can detect Cloudflare Workers", () => { + stubNoServerRuntime(); + vi.stubGlobal("navigator", { userAgent: "Cloudflare-Workers" }); + expect(Runtime.engine()).toEqual(RuntimeEngine.Workerd); + expect(Runtime.version()).toEqual("Cloudflare-Workers"); + }); + + test("can detect Node", () => { + vi.stubGlobal("Deno", undefined); + vi.stubGlobal("process", { versions: { node: "24.1.0" }, platform: "win32" }); + expect(Runtime.engine()).toEqual(RuntimeEngine.Node); + expect(Runtime.version()).toEqual("24.1.0"); + expect(Runtime.os()).toEqual(RuntimeOs.Windows); + }); + + test("can detect a browser", () => { + stubNoServerRuntime(); + vi.stubGlobal("window", {}); + vi.stubGlobal("document", {}); + vi.stubGlobal("navigator", { + userAgent: "Mozilla/5.0 (X11; Linux x86_64)", + userAgentData: { platform: "Windows" } + }); + expect(Runtime.engine()).toEqual(RuntimeEngine.Browser); + expect(Runtime.version()).toEqual("Mozilla/5.0 (X11; Linux x86_64)"); + expect(Runtime.os()).toEqual(RuntimeOs.Windows); + }); + + test("can detect a web worker", () => { + stubNoServerRuntime(); + vi.stubGlobal("importScripts", () => {}); + vi.stubGlobal("navigator", { userAgent: "Mozilla/5.0", platform: "MacIntel" }); + expect(Runtime.engine()).toEqual(RuntimeEngine.WebWorker); + expect(Runtime.os()).toEqual(RuntimeOs.Mac); + }); + + test("can detect the os from the user agent", () => { + stubNoServerRuntime(); + vi.stubGlobal("navigator", { userAgent: "Mozilla/5.0 (X11; Linux x86_64)" }); + expect(Runtime.os()).toEqual(RuntimeOs.Linux); + }); + + test("returns unknown when nothing can be detected", () => { + stubNoServerRuntime(); + vi.stubGlobal("navigator", undefined); + expect(Runtime.engine()).toEqual(RuntimeEngine.Unknown); + expect(Runtime.version()).toEqual(""); + expect(Runtime.os()).toEqual(RuntimeOs.Unknown); + }); +}); diff --git a/packages/crypto/tests/ciphers/chacha20poly1305.spec.ts b/packages/crypto/tests/ciphers/chacha20poly1305.spec.ts index cc607192f..95310f9ab 100644 --- a/packages/crypto/tests/ciphers/chacha20poly1305.spec.ts +++ b/packages/crypto/tests/ciphers/chacha20poly1305.spec.ts @@ -1,18 +1,19 @@ // Copyright 2024 IOTA Stiftung. // Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. -import { Converter, NativeModules } from "@3sixty/core"; +import { Converter, NativeModules, Runtime, RuntimeEngine } from "@3sixty/core"; import testVectors from "./chacha20poly1305.json" with { type: "json" }; import { ChaCha20Poly1305 } from "../../src/ciphers/chaCha20Poly1305.js"; import { - IS_BUN, NATIVE_CRYPTO_VARIANTS, unregisterNodeCrypto, useNativeCryptoVariant } from "../nativeCryptoVariants.js"; // Bun does not provide chacha20-poly1305 natively, so only the pure JavaScript variant runs there -const variants = NATIVE_CRYPTO_VARIANTS.filter(variant => !IS_BUN || !variant.useNodeCrypto); +const variants = NATIVE_CRYPTO_VARIANTS.filter( + variant => Runtime.engine() !== RuntimeEngine.Bun || !variant.useNodeCrypto +); describe.each(variants)("ChaCha20Poly1305 ($implementation)", ({ useNodeCrypto }) => { beforeAll(async () => { diff --git a/packages/crypto/tests/helpers/nativeModulesCrypto.spec.ts b/packages/crypto/tests/helpers/nativeModulesCrypto.spec.ts index 995834a27..b1bc90274 100644 --- a/packages/crypto/tests/helpers/nativeModulesCrypto.spec.ts +++ b/packages/crypto/tests/helpers/nativeModulesCrypto.spec.ts @@ -1,9 +1,8 @@ // Copyright 2026 IOTA Stiftung. // Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. -import { NativeModules } from "@3sixty/core"; +import { NativeModules, Runtime, RuntimeEngine } from "@3sixty/core"; import { NativeModulesCrypto } from "../../src/helpers/nativeModulesCrypto.js"; -import { IS_BUN } from "../nativeCryptoVariants.js"; /** * Drop node:crypto from the NativeModules registry. @@ -28,13 +27,16 @@ describe("NativeModulesCrypto", () => { expect(NativeModulesCrypto.getNodeCryptoCipher("chacha20-poly1305")).toBeUndefined(); }); - test.skipIf(IS_BUN)("returns the module when the cipher is supported", async () => { - expect(await NativeModules.init(["node:crypto"])).toEqual({}); + test.skipIf(Runtime.engine() === RuntimeEngine.Bun)( + "returns the module when the cipher is supported", + async () => { + expect(await NativeModules.init(["node:crypto"])).toEqual({}); - const nodeCrypto = NativeModulesCrypto.getNodeCryptoCipher("chacha20-poly1305"); - expect(nodeCrypto).toBeDefined(); - expect(typeof nodeCrypto?.createCipheriv).toEqual("function"); - }); + const nodeCrypto = NativeModulesCrypto.getNodeCryptoCipher("chacha20-poly1305"); + expect(nodeCrypto).toBeDefined(); + expect(typeof nodeCrypto?.createCipheriv).toEqual("function"); + } + ); test("returns undefined when the cipher is not one this build supports", async () => { expect(await NativeModules.init(["node:crypto"])).toEqual({}); @@ -138,11 +140,14 @@ describe("NativeModulesCrypto", () => { expect(NativeModulesCrypto.getNodeCryptoCurve("secp256k1")).toBeUndefined(); }); - test.skipIf(IS_BUN)("returns the module when the curve is supported", async () => { - expect(await NativeModules.init(["node:crypto"])).toEqual({}); + test.skipIf(Runtime.engine() === RuntimeEngine.Bun)( + "returns the module when the curve is supported", + async () => { + expect(await NativeModules.init(["node:crypto"])).toEqual({}); - expect(NativeModulesCrypto.getNodeCryptoCurve("secp256k1")).toBeDefined(); - }); + expect(NativeModulesCrypto.getNodeCryptoCurve("secp256k1")).toBeDefined(); + } + ); test("returns undefined when the curve is not one this build supports", async () => { expect(await NativeModules.init(["node:crypto"])).toEqual({}); diff --git a/packages/crypto/tests/nativeCryptoVariants.ts b/packages/crypto/tests/nativeCryptoVariants.ts index 89dd805eb..e1883fa0e 100644 --- a/packages/crypto/tests/nativeCryptoVariants.ts +++ b/packages/crypto/tests/nativeCryptoVariants.ts @@ -1,14 +1,9 @@ // Copyright 2026 IOTA Stiftung. // Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. -import { Is, NativeModules } from "@3sixty/core"; +import { NativeModules } from "@3sixty/core"; import { NativeModulesCrypto } from "../src/helpers/nativeModulesCrypto.js"; -/** - * True on Bun, whose BoringSSL based node:crypto lacks some algorithms OpenSSL provides. - */ -export const IS_BUN: boolean = Is.stringValue(process.versions.bun); - /** * The implementations every class with a node:crypto backed path must behave identically under. */ diff --git a/packages/nameof-transformer/tests/tsTransform.spec.ts b/packages/nameof-transformer/tests/tsTransform.spec.ts index 1139731d5..4d8166b55 100644 --- a/packages/nameof-transformer/tests/tsTransform.spec.ts +++ b/packages/nameof-transformer/tests/tsTransform.spec.ts @@ -7,6 +7,9 @@ import { tsTransform } from "../src/tsTransform.js"; let projectDir: string; +// The native compiler's sync API reads Node internal pipe handles on POSIX, which Bun does not provide +const UNSUPPORTED_RUNTIME = process.versions.bun !== undefined && process.platform !== "win32"; + /** * Write a project with the given source file. * @param source The content of the source file. @@ -30,7 +33,7 @@ function writeProject(source: string): void { writeFileSync(path.join(projectDir, "src", "index.ts"), source); } -describe("tsTransform", () => { +describe.skipIf(UNSUPPORTED_RUNTIME)("tsTransform", () => { beforeEach(() => { projectDir = mkdtempSync(path.join(tmpdir(), "tstransform-")); }); From 3dc6a5a3208e316ceded0bb4f260c13cc05bfc3c Mon Sep 17 00:00:00 2001 From: Martyn Janes Date: Mon, 26 Oct 2026 07:53:40 +0800 Subject: [PATCH 2/2] fix: run tstransform on every runtime --- .../nameof-transformer/bin/tstransform.js | 2 +- .../src/models/IBuildOrchestratorApi.ts | 4 +-- .../nameof-transformer/src/tsTransform.ts | 27 ++++++++++++------- .../tests/tsTransform.spec.ts | 25 ++++++++--------- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/packages/nameof-transformer/bin/tstransform.js b/packages/nameof-transformer/bin/tstransform.js index 225d239d5..eeace8e6b 100644 --- a/packages/nameof-transformer/bin/tstransform.js +++ b/packages/nameof-transformer/bin/tstransform.js @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0. import { tsTransform } from '../dist/es/index.js'; -const output = tsTransform(process.argv.slice(2), process.cwd()); +const output = await tsTransform(process.argv.slice(2), process.cwd()); if (output.length > 0) { process.stderr.write(output); process.exit(1); diff --git a/packages/nameof-transformer/src/models/IBuildOrchestratorApi.ts b/packages/nameof-transformer/src/models/IBuildOrchestratorApi.ts index 635c1cab4..3e73d51c0 100644 --- a/packages/nameof-transformer/src/models/IBuildOrchestratorApi.ts +++ b/packages/nameof-transformer/src/models/IBuildOrchestratorApi.ts @@ -1,6 +1,6 @@ // Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. -import type { BuildOrchestrator, CompilerOptions } from "typescript-native/sync"; +import type { BuildOrchestrator, CompilerOptions } from "typescript-native/async"; /** * The internal API method for creating a build orchestrator. @@ -22,5 +22,5 @@ export interface IBuildOrchestratorApi { compilerOptions: CompilerOptions; overrideCompilerOptions: CompilerOptions; } - ): BuildOrchestrator; + ): Promise; } diff --git a/packages/nameof-transformer/src/tsTransform.ts b/packages/nameof-transformer/src/tsTransform.ts index 08ff717d5..ebb6e4863 100644 --- a/packages/nameof-transformer/src/tsTransform.ts +++ b/packages/nameof-transformer/src/tsTransform.ts @@ -2,8 +2,12 @@ // SPDX-License-Identifier: Apache-2.0. import { readFileSync } from "node:fs"; import path from "node:path"; +import { + API, + type Diagnostic, + formatDiagnosticsWithColorAndContext +} from "typescript-native/async"; import { type FileSystemCallbacks, serverFS } from "typescript-native/fs"; -import { API, type Diagnostic, formatDiagnosticsWithColorAndContext } from "typescript-native/sync"; import { manual } from "./manual.js"; import type { IBuildOrchestratorApi } from "./models/IBuildOrchestratorApi.js"; @@ -42,12 +46,13 @@ function readFile(fileName: string): string | undefined | typeof serverFS.useOS /** * Compile a project with the native TypeScript compiler, replacing the nameof calls - * in the source files as they are read through a virtual file system. + * in the source files as they are read through a virtual file system. Uses the async + * API as the sync API relies on Node internal pipe handles that other runtimes lack. * @param args The tsc command line arguments. * @param cwd The working directory containing the project. * @returns The diagnostics output, empty if the compilation succeeded. */ -export function tsTransform(args: string[], cwd: string): string { +export async function tsTransform(args: string[], cwd: string): Promise { const fs: FileSystemCallbacks = { directoryExists: serverFS.useOS, fileExists: serverFS.useOS, @@ -61,7 +66,7 @@ export function tsTransform(args: string[], cwd: string): string { const api = new API({ cwd, fs }); try { - const commandLine = api.parseCommandLine(args); + const commandLine = await api.parseCommandLine(args); const diagnostics: Diagnostic[] = [...commandLine.errors]; if (diagnostics.length === 0) { const { project, ...compilerOptions } = commandLine.options; @@ -70,19 +75,23 @@ export function tsTransform(args: string[], cwd: string): string { // Current clients spread the options so only the nested compilerOptions key // reaches the server, clients with https://github.com/microsoft/TypeScript/pull/64759 // only send overrideCompilerOptions. Remove compilerOptions once that is released. - const orchestrator = (api as unknown as IBuildOrchestratorApi).createBuildOrchestrator( + const orchestrator = await (api as unknown as IBuildOrchestratorApi).createBuildOrchestrator( [project ?? path.join(cwd, "tsconfig.json")], - { cwd, compilerOptions, overrideCompilerOptions: compilerOptions } + { + cwd, + compilerOptions, + overrideCompilerOptions: compilerOptions + } ); try { - diagnostics.push(...(orchestrator.build().diagnostics ?? [])); + diagnostics.push(...((await orchestrator.build()).diagnostics ?? [])); } finally { - orchestrator.dispose(); + await orchestrator.dispose(); } } return diagnostics.length > 0 ? formatDiagnosticsWithColorAndContext(diagnostics, api) : ""; } finally { - api.close(); + await api.close(); } } diff --git a/packages/nameof-transformer/tests/tsTransform.spec.ts b/packages/nameof-transformer/tests/tsTransform.spec.ts index 4d8166b55..9cdd734c9 100644 --- a/packages/nameof-transformer/tests/tsTransform.spec.ts +++ b/packages/nameof-transformer/tests/tsTransform.spec.ts @@ -7,9 +7,6 @@ import { tsTransform } from "../src/tsTransform.js"; let projectDir: string; -// The native compiler's sync API reads Node internal pipe handles on POSIX, which Bun does not provide -const UNSUPPORTED_RUNTIME = process.versions.bun !== undefined && process.platform !== "win32"; - /** * Write a project with the given source file. * @param source The content of the source file. @@ -33,7 +30,7 @@ function writeProject(source: string): void { writeFileSync(path.join(projectDir, "src", "index.ts"), source); } -describe.skipIf(UNSUPPORTED_RUNTIME)("tsTransform", () => { +describe("tsTransform", () => { beforeEach(() => { projectDir = mkdtempSync(path.join(tmpdir(), "tstransform-")); }); @@ -42,14 +39,14 @@ describe.skipIf(UNSUPPORTED_RUNTIME)("tsTransform", () => { rmSync(projectDir, { recursive: true, force: true }); }); - test("can compile a project replacing the nameof calls", () => { + test("can compile a project replacing the nameof calls", async () => { writeProject( "interface IMyObject { prop: number }\n" + "export const typeName: string = nameof();\n" + "export const camelName: string = nameofCamelCase();\n" ); - const output = tsTransform([], projectDir); + const output = await tsTransform([], projectDir); expect(output).toEqual(""); const js = readFileSync(path.join(projectDir, "dist", "index.js"), "utf8"); @@ -57,21 +54,21 @@ describe.skipIf(UNSUPPORTED_RUNTIME)("tsTransform", () => { expect(js).toContain('camelName = "myObject";'); }); - test("can type check a project without emitting", () => { + test("can type check a project without emitting", async () => { writeProject( "export const typeName: string = nameof();\ninterface IMyObject { prop: number }\n" ); - const output = tsTransform(["--noEmit"], projectDir); + const output = await tsTransform(["--noEmit"], projectDir); expect(output).toEqual(""); expect(() => readFileSync(path.join(projectDir, "dist", "index.js"))).toThrow(); }); - test("can compile a project incrementally", () => { + test("can compile a project incrementally", async () => { writeProject("export const value = 1;\n"); - const output = tsTransform( + const output = await tsTransform( ["--incremental", "--tsBuildInfoFile", ".cache/tsconfig.tsbuildinfo"], projectDir ); @@ -80,18 +77,18 @@ describe.skipIf(UNSUPPORTED_RUNTIME)("tsTransform", () => { expect(existsSync(path.join(projectDir, ".cache", "tsconfig.tsbuildinfo"))).toEqual(true); }); - test("can return the diagnostics for a project with errors", () => { + test("can return the diagnostics for a project with errors", async () => { writeProject('export const value: number = "text";\n'); - const output = tsTransform([], projectDir); + const output = await tsTransform([], projectDir); expect(output).toContain("TS2322"); }); - test("can return the diagnostics for invalid command line arguments", () => { + test("can return the diagnostics for invalid command line arguments", async () => { writeProject("export const value = 1;\n"); - const output = tsTransform(["--notAnOption"], projectDir); + const output = await tsTransform(["--notAnOption"], projectDir); expect(output).toContain("TS5023"); });