From 91235e026a58dab123494d424aca27cc493143b3 Mon Sep 17 00:00:00 2001 From: lojhan Date: Thu, 2 Apr 2026 08:04:36 -0300 Subject: [PATCH 1/2] fix: avoid IPC mode on isolation none --- package-lock.json | 14 +----- src/index.ts | 9 ++++ src/shared-resources.ts | 58 ++++++++++++++++++++++- src/types.ts | 1 + test/integration/shared-resources.test.ts | 13 +++++ test/unit/execution-mode.test.ts | 56 ++++++++++++++++++++++ 6 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 test/unit/execution-mode.test.ts diff --git a/package-lock.json b/package-lock.json index 032238a..56971e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "url": "https://github.com/pokujs/shared-resources?sponsor=1" }, "peerDependencies": { - "poku": "canary" + "poku": "^4.1.0" } }, "node_modules/@babel/code-frame": { @@ -227,9 +227,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -247,9 +244,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -267,9 +261,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -287,9 +278,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ diff --git a/src/index.ts b/src/index.ts index 2e4c61d..4494afc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,8 @@ import type { SharedResourcesConfig } from './types.js'; import { configureCodecs, globalRegistry, + resetSharedResourcesRuntime, + setExecutionMode, setupSharedResourceIPC, } from './shared-resources.js'; @@ -15,11 +17,18 @@ export const sharedResources = (config?: SharedResourcesConfig): PokuPlugin => { onTestProcess(child) { setupSharedResourceIPC(child); }, + setup(context) { + setExecutionMode( + context.configs.isolation === 'none' ? 'in-process' : 'process' + ); + }, async teardown() { const entries = Object.values(globalRegistry); for (const entry of entries) if (entry.onDestroy) await entry.onDestroy(entry.state); + + resetSharedResourcesRuntime(); }, }; }; diff --git a/src/shared-resources.ts b/src/shared-resources.ts index b05e57e..2ab2352 100644 --- a/src/shared-resources.ts +++ b/src/shared-resources.ts @@ -12,6 +12,7 @@ import type { ResourceContext, SendIPCMessageOptions, SharedResourceEntry, + SharedResourceExecutionMode, } from './types.js'; import process from 'node:process'; import { pathToFileURL } from 'node:url'; @@ -29,6 +30,7 @@ const isWindows = process.platform === 'win32'; const resourceRegistry = new ResourceRegistry(); const moduleCounters = new Map(); +let executionMode: SharedResourceExecutionMode = 'process'; export const SHARED_RESOURCE_MESSAGE_TYPES = { REQUEST_RESOURCE: 'shared_resources_requestResource', @@ -39,6 +41,19 @@ export const SHARED_RESOURCE_MESSAGE_TYPES = { export const globalRegistry = resourceRegistry.getRegistry(); +export const setExecutionMode = (mode: SharedResourceExecutionMode) => { + executionMode = mode; +}; + +export const getExecutionMode = () => executionMode; + +export const resetSharedResourcesRuntime = () => { + resourceRegistry.clear(); + resourceRegistry.setIsRegistering(false); + moduleCounters.clear(); + executionMode = 'process'; +}; + const create = ( factory: () => T, options?: { @@ -72,10 +87,19 @@ const use = async ( ): Promise> => { const { name } = context; - // Parent Process (Host) - if (!process.send || resourceRegistry.getIsRegistering()) { + // In-process execution always resolves resources directly from the local registry. + if ( + executionMode === 'in-process' || + !process.send || + resourceRegistry.getIsRegistering() + ) { const existing = resourceRegistry.get(name); if (existing) { + if (executionMode === 'in-process') + return constructInProcessResource( + existing.state as Record + ) as MethodsToRPC; + return existing.state as MethodsToRPC; } @@ -87,6 +111,11 @@ const use = async ( | undefined, }); + if (executionMode === 'in-process') + return constructInProcessResource( + state as Record + ) as MethodsToRPC; + return state as MethodsToRPC; } @@ -148,6 +177,12 @@ export const sendIPCMessage = ( }; const requestResource = async (name: string, module: string) => { + if (executionMode === 'in-process') { + throw new Error( + 'Cannot request shared resources through IPC while running in in-process mode.' + ); + } + const requestId = `${name}-${Date.now()}-${Math.random()}`; const response = await sendIPCMessage({ @@ -183,6 +218,12 @@ const remoteProcedureCall = async ( method: string, args: unknown[] ) => { + if (executionMode === 'in-process') { + throw new Error( + 'Cannot run shared resource RPCs through IPC while running in in-process mode.' + ); + } + const requestId = `${name}-${method}-${Date.now()}-${Math.random()}`; const response = await sendIPCMessage({ @@ -332,6 +373,8 @@ export const setupSharedResourceIPC = ( child: IPCEventEmitter | ChildProcess, registry: Record = globalRegistry ): void => { + if (executionMode === 'in-process') return; + child.on('message', async (message: IPCMessage) => { if (message.type === SHARED_RESOURCE_MESSAGE_TYPES.REQUEST_RESOURCE) await handleRequestResource(message, registry, child); @@ -497,6 +540,17 @@ const constructSharedResourceWithRPCs = ( }); }; +const constructInProcessResource = (target: Record) => + new Proxy(target, { + get(target, prop, receiver) { + const value = Reflect.get(target, prop, receiver); + + if (typeof value !== 'function') return value; + + return async (...args: unknown[]) => value.apply(target, args); + }, + }); + export const resource = { create, use, diff --git a/src/types.ts b/src/types.ts index fa4dd63..d606373 100644 --- a/src/types.ts +++ b/src/types.ts @@ -142,3 +142,4 @@ export type SharedResourcesConfig = { // biome-ignore lint/suspicious/noExplicitAny: see ArgCodec codecs?: ArgCodec[]; }; +export type SharedResourceExecutionMode = 'process' | 'in-process'; diff --git a/test/integration/shared-resources.test.ts b/test/integration/shared-resources.test.ts index a1c350d..b8370f3 100644 --- a/test/integration/shared-resources.test.ts +++ b/test/integration/shared-resources.test.ts @@ -8,6 +8,19 @@ describe('Shared Resources', async () => { noExit: true, plugins: [sharedResources()], concurrency: 0, + timeout: 10000, + }); + + assert.strictEqual(code, 0, 'Exit Code needs to be 0'); + }); + + await it('Runs on isolation: none', async () => { + const code = await poku('test/__fixtures__/parallel', { + noExit: true, + plugins: [sharedResources()], + isolation: 'none', + concurrency: 0, + timeout: 10000, }); assert.strictEqual(code, 0, 'Exit Code needs to be 0'); diff --git a/test/unit/execution-mode.test.ts b/test/unit/execution-mode.test.ts new file mode 100644 index 0000000..c7a0fb7 --- /dev/null +++ b/test/unit/execution-mode.test.ts @@ -0,0 +1,56 @@ +import { assert, test } from 'poku'; +import { + getExecutionMode, + resetSharedResourcesRuntime, + setExecutionMode, + setupSharedResourceIPC, +} from '../../src/shared-resources.js'; + +test('Execution mode', async () => { + await test('should switch between process and in-process mode', () => { + setExecutionMode('in-process'); + assert.strictEqual( + getExecutionMode(), + 'in-process', + 'Mode should be in-process' + ); + + setExecutionMode('process'); + assert.strictEqual(getExecutionMode(), 'process', 'Mode should be process'); + }); + + await test('should skip IPC setup when running in-process', () => { + setExecutionMode('in-process'); + + let listenerCalls = 0; + const child = { + on() { + listenerCalls++; + }, + send() { + return true; + }, + }; + + setupSharedResourceIPC(child as never); + + assert.strictEqual( + listenerCalls, + 0, + 'No message listener should be attached in in-process mode' + ); + + setExecutionMode('process'); + }); + + await test('should reset execution mode to process', () => { + setExecutionMode('in-process'); + resetSharedResourcesRuntime(); + + assert.strictEqual( + getExecutionMode(), + 'process', + 'Reset should restore process mode' + ); + }); +}); From 941a28cf8f28c7e4a1fcb878ae153be90c8a40ee Mon Sep 17 00:00:00 2001 From: lojhan Date: Thu, 2 Apr 2026 08:10:42 -0300 Subject: [PATCH 2/2] fix: poku package version --- package-lock.json | 8 ++++---- package.json | 2 +- src/index.ts | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 56971e0..5449b17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@biomejs/biome": "^1.9.4", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", "@types/node": "^25.3.3", - "poku": "^4.1.0", + "poku": "4.2.0", "prettier": "^3.8.1", "tsx": "^4.21.0", "typescript": "^5.9.3" @@ -970,9 +970,9 @@ "license": "ISC" }, "node_modules/poku": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/poku/-/poku-4.1.0.tgz", - "integrity": "sha512-gHyR0sE1zZ7qDowChiToZjQ75Dwqf0JDA3cHh5hVD8K00HOnVW4nr9XlximThE/AyenlxVatSEuiLfwYFcJS7w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/poku/-/poku-4.2.0.tgz", + "integrity": "sha512-GygMGFGgEJ9kfs6Z+QPg/ODs9OF3oGHN8+hYIxtBox3pwYISO+Vu660vH1e+YzjpGoaoy2o5y6YwE1tX5yZx3Q==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index f8a12d0..7608738 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@biomejs/biome": "^1.9.4", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", "@types/node": "^25.3.3", - "poku": "^4.1.0", + "poku": "4.2.0", "prettier": "^3.8.1", "tsx": "^4.21.0", "typescript": "^5.9.3" diff --git a/src/index.ts b/src/index.ts index 4494afc..552294b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,9 +18,9 @@ export const sharedResources = (config?: SharedResourcesConfig): PokuPlugin => { setupSharedResourceIPC(child); }, setup(context) { - setExecutionMode( - context.configs.isolation === 'none' ? 'in-process' : 'process' - ); + const { isolation } = context.configs; + + setExecutionMode(isolation === 'none' ? 'in-process' : 'process'); }, async teardown() { const entries = Object.values(globalRegistry);