Repository: denoland/deno
Pull request: fix(node/cluster): improve stubs to make log4js work

- Add missing exports to `node:cluster`
- Fix default export not being an instance of `EventEmitter`
- Fix aliasing of properties
- Fix `disconnected` -> `disconnect` export naming

This makes `log4js` work in Deno. `karma` starts too, but somehow the server isn't responding. That looks like a different issue.

Fixes https://github.com/denoland/deno/issues/24858

Changed files:
filename: ext/node/polyfills/cluster.ts
patch: @@ -2,6 +2,7 @@
 // Copyright Joyent and Node contributors. All rights reserved. MIT license.
 
 import { notImplemented } from "ext:deno_node/_utils.ts";
+import { EventEmitter } from "node:events";
 
 /** A Worker object contains all public information and method about a worker.
  * In the primary it can be obtained using cluster.workers. In a worker it can
@@ -13,20 +14,23 @@ export class Worker {
   }
 }
 /** Calls .disconnect() on each worker in cluster.workers. */
-export function disconnected() {
-  notImplemented("cluster.disconnected");
+export function disconnect() {
+  notImplemented("cluster.disconnect");
 }
 /** Spawn a new worker process. */
-export function fork() {
+// deno-lint-ignore no-explicit-any
+export function fork(_env?: any): Worker {
   notImplemented("cluster.fork");
 }
 /** True if the process is a primary. This is determined by
  * the process.env.NODE_UNIQUE_ID. If process.env.NODE_UNIQUE_ID is undefined,
  * then isPrimary is true. */
-export const isPrimary = undefined;
+// TODO(@marvinhagemeister): Replace this with an env check once
+// we properly set NODE_UNIQUE_ID
+export const isPrimary = true;
 /** True if the process is not a primary (it is the negation of
  * cluster.isPrimary). */
-export const isWorker = undefined;
+export const isWorker = false;
 /** Deprecated alias for cluster.isPrimary. details. */
 export const isMaster = isPrimary;
 /** The scheduling policy, either cluster.SCHED_RR for round-robin or
@@ -35,35 +39,54 @@ export const isMaster = isPrimary;
  * .setupPrimary() is called, whichever comes first. */
 export const schedulingPolicy = undefined;
 /** The settings object */
-export const settings = undefined;
-/** Deprecated alias for .setupPrimary(). */
-export function setupMaster() {
-  notImplemented("cluster.setupMaster");
-}
+export const settings = {};
 /** setupPrimary is used to change the default 'fork' behavior. Once called,
  * the settings will be present in cluster.settings. */
 export function setupPrimary() {
   notImplemented("cluster.setupPrimary");
 }
+/** Deprecated alias for .setupPrimary(). */
+export const setupMaster = setupPrimary;
 /** A reference to the current worker object. Not available in the primary
  * process. */
 export const worker = undefined;
 /** A hash that stores the active worker objects, keyed by id field. Makes it
  * easy to loop through all the workers. It is only available in the primary
  * process. */
-export const workers = undefined;
+export const workers = {};
 
-export default {
-  Worker,
-  disconnected,
-  fork,
-  isPrimary,
-  isWorker,
-  isMaster,
-  schedulingPolicy,
-  settings,
-  setupMaster,
-  setupPrimary,
-  worker,
-  workers,
+export const SCHED_NONE = 1;
+export const SCHED_RR = 2;
+
+const cluster = new EventEmitter() as EventEmitter & {
+  isWorker: boolean;
+  isMaster: boolean;
+  isPrimary: boolean;
+  Worker: Worker;
+  workers: Record<string, Worker>;
+  settings: Record<string, unknown>;
+  // deno-lint-ignore no-explicit-any
+  setupPrimary(options?: any): void;
+  // deno-lint-ignore no-explicit-any
+  setupMaster(options?: any): void;
+  // deno-lint-ignore no-explicit-any
+  fork(env: any): Worker;
+  // deno-lint-ignore no-explicit-any
+  disconnect(cb: any): void;
+  SCHED_NONE: 1;
+  SCHED_RR: 2;
 };
+cluster.isWorker = isWorker;
+cluster.isMaster = isMaster;
+cluster.isPrimary = isPrimary;
+cluster.Worker = Worker;
+cluster.workers = workers;
+cluster.settings = {};
+cluster.setupPrimary = setupPrimary;
+cluster.setupMaster = setupMaster;
+cluster.fork = fork;
+cluster.disconnect = disconnect;
+cluster.SCHED_NONE = SCHED_NONE;
+cluster.SCHED_RR = SCHED_RR;
+
+export default cluster;

filename: tests/integration/node_unit_tests.rs
patch: @@ -57,6 +57,7 @@ util::unit_test_factory!(
     assertion_error_test,
     buffer_test,
     child_process_test,
+    cluster_test,
     console_test,
     crypto_cipher_gcm_test = crypto / crypto_cipher_gcm_test,
     crypto_cipher_test = crypto / crypto_cipher_test,

filename: tests/unit_node/cluster_test.ts
patch: @@ -0,0 +1,43 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals } from "@std/assert";
+import cluster from "node:cluster";
+import * as clusterNamed from "node:cluster";
+
+Deno.test("[node/cluster] has all node exports", () => {
+  assertEquals(cluster.isPrimary, true);
+  assertEquals(cluster.isMaster, true);
+  assertEquals(cluster.isWorker, false);
+  assertEquals(typeof cluster.disconnect, "function");
+  assertEquals(typeof cluster.on, "function");
+  assertEquals(cluster.workers, {});
+  assertEquals(cluster.settings, {});
+  assertEquals(cluster.SCHED_NONE, 1);
+  assertEquals(cluster.SCHED_RR, 2);
+  assertEquals(typeof cluster.fork, "function");
+  assertEquals(typeof cluster.disconnect, "function");
+  assertEquals(typeof cluster.setupPrimary, "function");
+  assertEquals(cluster.setupPrimary, cluster.setupMaster);
+
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.setupPrimary, clusterNamed.setupPrimary);
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.setupMaster, clusterNamed.setupMaster);
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.workers, clusterNamed.workers);
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.settings, clusterNamed.settings);
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.fork, clusterNamed.fork);
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.disconnect, clusterNamed.disconnect);
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.SCHED_NONE, clusterNamed.SCHED_NONE);
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.SCHED_RR, clusterNamed.SCHED_RR);
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.isWorker, clusterNamed.isWorker);
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.isPrimary, clusterNamed.isPrimary);
+  // @ts-ignore Our @types/node version is too old
+  assertEquals(cluster.isMaster, clusterNamed.isMaster);
+});