From 868eef73b0d5815911440f1e4218594ecb2e4a07 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 5 Aug 2026 13:17:07 +0200 Subject: [PATCH] Fix CommonJS runtime compatibility --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 5 + package-lock.json | 4 +- package.json | 2 +- src/res/DataLoader.js | 20 ++- src/res/DataLoader.mjs | 20 ++- src/res/DataLoader.res | 6 +- src/res/DataLoader.resi | 4 +- src/res/ResGraph__Connections.js | 6 +- src/res/ResGraph__Connections.mjs | 6 +- src/res/ResGraph__Connections.res | 2 +- src/res/ResGraph__Utils.js | 8 +- src/res/ResGraph__Utils.mjs | 8 +- src/res/ResGraph__Utils.res | 4 +- src/res/dataLoaderCompat.cjs | 5 + src/res/graphqlRelayConnections.cjs | 201 ++++++++++++++++++++++++++++ src/res/stableStringify.cjs | 50 +++++++ tests/package-runtime-outputs.mjs | 60 ++++++++- 18 files changed, 372 insertions(+), 41 deletions(-) create mode 100644 src/res/dataLoaderCompat.cjs create mode 100644 src/res/graphqlRelayConnections.cjs create mode 100644 src/res/stableStringify.cjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a53ea50..eab280ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,7 +117,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: 24 + node-version: 20.11.0 registry-url: 'https://registry.npmjs.org' - run: npm ci diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d011afd..643a375a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +## 1.3.4 + +- Fix CommonJS DataLoader construction and provide CommonJS-safe connection, + base64, and stable-stringify helpers across the supported Node.js range. + ## 1.3.3 - Publish both ESM (`.mjs`) and CommonJS (`.js`) runtime artifacts so generated diff --git a/package-lock.json b/package-lock.json index 95c499b4..da2f5b4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "resgraph", - "version": "1.3.3", + "version": "1.3.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "resgraph", - "version": "1.3.3", + "version": "1.3.4", "license": "ISC", "dependencies": { "@glennsl/rescript-fetch": "^0.2.2", diff --git a/package.json b/package.json index 66b9ff35..f206cc28 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "resgraph", - "version": "1.3.3", + "version": "1.3.4", "description": "Build GraphQL servers in ReScript.", "main": "index.js", "scripts": { diff --git a/src/res/DataLoader.js b/src/res/DataLoader.js index e0d7c2d8..b06919c8 100644 --- a/src/res/DataLoader.js +++ b/src/res/DataLoader.js @@ -1,15 +1,21 @@ // Generated by ReScript, PLEASE EDIT WITH CARE 'use strict'; -let Dataloader = require("dataloader").default; let Stdlib_Lazy = require("@rescript/runtime/lib/js/Stdlib_Lazy.js"); let Primitive_option = require("@rescript/runtime/lib/js/Primitive_option.js"); -let StableStringifyMjs = require("./stableStringify.mjs"); +let StableStringifyCjs = require("./stableStringify.cjs"); +let DataLoaderCompatCjs = require("./dataLoaderCompat.cjs"); -let Plain = {}; +function make(prim0, prim1) { + return new DataLoaderCompatCjs.DataLoader(prim0, prim1 !== undefined ? Primitive_option.valFromOption(prim1) : undefined); +} + +let Plain = { + make: make +}; function stableStringifyValue(prim) { - return StableStringifyMjs.stableStringify(prim); + return StableStringifyCjs.stableStringify(prim); } function mapOptions(options) { @@ -25,11 +31,11 @@ function mapOptions(options) { } function makeSingle(loadFn, options) { - return Stdlib_Lazy.make(() => new Dataloader(keys => Promise.all(keys.map(loadFn)), Primitive_option.toUndefined(mapOptions(options)))); + return Stdlib_Lazy.make(() => new DataLoaderCompatCjs.DataLoader(keys => Promise.all(keys.map(loadFn)), Primitive_option.toUndefined(mapOptions(options)))); } function makeBatched(loadFn, options) { - return Stdlib_Lazy.make(() => new Dataloader(loadFn, Primitive_option.toUndefined(mapOptions(options)))); + return Stdlib_Lazy.make(() => new DataLoaderCompatCjs.DataLoader(loadFn, Primitive_option.toUndefined(mapOptions(options)))); } function load(lazyLoader, key) { @@ -77,4 +83,4 @@ exports.clearAll = clearAll; exports.prime = prime; exports.primeWithPromise = primeWithPromise; exports.name = name; -/* dataloader Not a pure module */ +/* ./stableStringify.cjs Not a pure module */ diff --git a/src/res/DataLoader.mjs b/src/res/DataLoader.mjs index d79a4421..660626de 100644 --- a/src/res/DataLoader.mjs +++ b/src/res/DataLoader.mjs @@ -1,14 +1,20 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -import Dataloader from "dataloader"; import * as Stdlib_Lazy from "@rescript/runtime/lib/es6/Stdlib_Lazy.js"; import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js"; -import * as StableStringifyMjs from "./stableStringify.mjs"; +import * as StableStringifyCjs from "./stableStringify.cjs"; +import * as DataLoaderCompatCjs from "./dataLoaderCompat.cjs"; -let Plain = {}; +function make(prim0, prim1) { + return new DataLoaderCompatCjs.DataLoader(prim0, prim1 !== undefined ? Primitive_option.valFromOption(prim1) : undefined); +} + +let Plain = { + make: make +}; function stableStringifyValue(prim) { - return StableStringifyMjs.stableStringify(prim); + return StableStringifyCjs.stableStringify(prim); } function mapOptions(options) { @@ -24,11 +30,11 @@ function mapOptions(options) { } function makeSingle(loadFn, options) { - return Stdlib_Lazy.make(() => new Dataloader(keys => Promise.all(keys.map(loadFn)), Primitive_option.toUndefined(mapOptions(options)))); + return Stdlib_Lazy.make(() => new DataLoaderCompatCjs.DataLoader(keys => Promise.all(keys.map(loadFn)), Primitive_option.toUndefined(mapOptions(options)))); } function makeBatched(loadFn, options) { - return Stdlib_Lazy.make(() => new Dataloader(loadFn, Primitive_option.toUndefined(mapOptions(options)))); + return Stdlib_Lazy.make(() => new DataLoaderCompatCjs.DataLoader(loadFn, Primitive_option.toUndefined(mapOptions(options)))); } function load(lazyLoader, key) { @@ -78,4 +84,4 @@ export { primeWithPromise, name, } -/* dataloader Not a pure module */ +/* ./stableStringify.cjs Not a pure module */ diff --git a/src/res/DataLoader.res b/src/res/DataLoader.res index 97eb40b2..1c7989bd 100644 --- a/src/res/DataLoader.res +++ b/src/res/DataLoader.res @@ -40,8 +40,8 @@ module Plain = { type batchFn<'key, 'value> = array<'key> => promise> - @new @module("dataloader") - external make: (batchFn<'key, 'value>, ~options: options=?) => t<'key, 'value> = "default" + @new @module("./dataLoaderCompat.cjs") + external make: (batchFn<'key, 'value>, ~options: options=?) => t<'key, 'value> = "DataLoader" /** * Loads a key, returning a `promise` for the value represented by that key. @@ -108,7 +108,7 @@ type options = { name?: string, } -@module("./stableStringify.mjs") +@module("./stableStringify.cjs") external stableStringifyValue: 'any => 'any = "stableStringify" let mapOptions = options => { diff --git a/src/res/DataLoader.resi b/src/res/DataLoader.resi index 2f7192d4..78d5c7b6 100644 --- a/src/res/DataLoader.resi +++ b/src/res/DataLoader.resi @@ -42,8 +42,8 @@ module Plain: { type batchFn<'key, 'value> = array<'key> => promise> /** Create a new data loader, from a batch load function. */ - @new @module("dataloader") - external make: (batchFn<'key, 'value>, ~options: options=?) => t<'key, 'value> = "default" + @new @module("./dataLoaderCompat.cjs") + external make: (batchFn<'key, 'value>, ~options: options=?) => t<'key, 'value> = "DataLoader" /** * Loads a key, returning a `promise` for the value represented by that key. */ diff --git a/src/res/ResGraph__Connections.js b/src/res/ResGraph__Connections.js index 7a444cea..b23ad734 100644 --- a/src/res/ResGraph__Connections.js +++ b/src/res/ResGraph__Connections.js @@ -1,11 +1,11 @@ // Generated by ReScript, PLEASE EDIT WITH CARE 'use strict'; -let GraphqlRelayConnectionsMjs = require("./graphqlRelayConnections.mjs"); +let GraphqlRelayConnectionsCjs = require("./graphqlRelayConnections.cjs"); function connectionFromArray(prim0, prim1) { - return GraphqlRelayConnectionsMjs.connectionFromArray(prim0, prim1); + return GraphqlRelayConnectionsCjs.connectionFromArray(prim0, prim1); } exports.connectionFromArray = connectionFromArray; -/* ./graphqlRelayConnections.mjs Not a pure module */ +/* ./graphqlRelayConnections.cjs Not a pure module */ diff --git a/src/res/ResGraph__Connections.mjs b/src/res/ResGraph__Connections.mjs index 9b1e25ef..987093b2 100644 --- a/src/res/ResGraph__Connections.mjs +++ b/src/res/ResGraph__Connections.mjs @@ -1,12 +1,12 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -import * as GraphqlRelayConnectionsMjs from "./graphqlRelayConnections.mjs"; +import * as GraphqlRelayConnectionsCjs from "./graphqlRelayConnections.cjs"; function connectionFromArray(prim0, prim1) { - return GraphqlRelayConnectionsMjs.connectionFromArray(prim0, prim1); + return GraphqlRelayConnectionsCjs.connectionFromArray(prim0, prim1); } export { connectionFromArray, } -/* ./graphqlRelayConnections.mjs Not a pure module */ +/* ./graphqlRelayConnections.cjs Not a pure module */ diff --git a/src/res/ResGraph__Connections.res b/src/res/ResGraph__Connections.res index e24542c2..d9df820d 100644 --- a/src/res/ResGraph__Connections.res +++ b/src/res/ResGraph__Connections.res @@ -74,6 +74,6 @@ type connectionArgs = { ...backwardConnectionArgs, } -@module("./graphqlRelayConnections.mjs") +@module("./graphqlRelayConnections.cjs") external connectionFromArray: (array<'node>, ~args: connectionArgs) => connection> = "connectionFromArray" diff --git a/src/res/ResGraph__Utils.js b/src/res/ResGraph__Utils.js index 07a97f8a..801cf5a1 100644 --- a/src/res/ResGraph__Utils.js +++ b/src/res/ResGraph__Utils.js @@ -1,14 +1,14 @@ // Generated by ReScript, PLEASE EDIT WITH CARE 'use strict'; -let GraphqlRelayConnectionsMjs = require("./graphqlRelayConnections.mjs"); +let GraphqlRelayConnectionsCjs = require("./graphqlRelayConnections.cjs"); function encode(prim) { - return GraphqlRelayConnectionsMjs.encodeBase64(prim); + return GraphqlRelayConnectionsCjs.encodeBase64(prim); } function decode(prim) { - return GraphqlRelayConnectionsMjs.decodeBase64(prim); + return GraphqlRelayConnectionsCjs.decodeBase64(prim); } let Base64 = { @@ -17,4 +17,4 @@ let Base64 = { }; exports.Base64 = Base64; -/* ./graphqlRelayConnections.mjs Not a pure module */ +/* ./graphqlRelayConnections.cjs Not a pure module */ diff --git a/src/res/ResGraph__Utils.mjs b/src/res/ResGraph__Utils.mjs index 06b6c1f2..0caf807a 100644 --- a/src/res/ResGraph__Utils.mjs +++ b/src/res/ResGraph__Utils.mjs @@ -1,13 +1,13 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -import * as GraphqlRelayConnectionsMjs from "./graphqlRelayConnections.mjs"; +import * as GraphqlRelayConnectionsCjs from "./graphqlRelayConnections.cjs"; function encode(prim) { - return GraphqlRelayConnectionsMjs.encodeBase64(prim); + return GraphqlRelayConnectionsCjs.encodeBase64(prim); } function decode(prim) { - return GraphqlRelayConnectionsMjs.decodeBase64(prim); + return GraphqlRelayConnectionsCjs.decodeBase64(prim); } let Base64 = { @@ -18,4 +18,4 @@ let Base64 = { export { Base64, } -/* ./graphqlRelayConnections.mjs Not a pure module */ +/* ./graphqlRelayConnections.cjs Not a pure module */ diff --git a/src/res/ResGraph__Utils.res b/src/res/ResGraph__Utils.res index 4ddd14db..12fd7d83 100644 --- a/src/res/ResGraph__Utils.res +++ b/src/res/ResGraph__Utils.res @@ -1,7 +1,7 @@ module Base64 = { - @module("./graphqlRelayConnections.mjs") + @module("./graphqlRelayConnections.cjs") external encode: string => string = "encodeBase64" - @module("./graphqlRelayConnections.mjs") + @module("./graphqlRelayConnections.cjs") external decode: string => string = "decodeBase64" } diff --git a/src/res/dataLoaderCompat.cjs b/src/res/dataLoaderCompat.cjs new file mode 100644 index 00000000..9a28aa2e --- /dev/null +++ b/src/res/dataLoaderCompat.cjs @@ -0,0 +1,5 @@ +"use strict"; + +const importedDataLoader = require("dataloader"); + +exports.DataLoader = importedDataLoader.default ?? importedDataLoader; diff --git a/src/res/graphqlRelayConnections.cjs b/src/res/graphqlRelayConnections.cjs new file mode 100644 index 00000000..98438b14 --- /dev/null +++ b/src/res/graphqlRelayConnections.cjs @@ -0,0 +1,201 @@ +"use strict"; + +/** +Copyright (c) GraphQL Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ + +// Most of this file is inlined from https://github.com/graphql/graphql-relay-js + +/* Mostly taken from `url-safe-base64` */ + +const ENC = { + "+": "-", + "/": "_", + "=": ",", +}; +const DEC = { + "-": "+", + _: "/", + ",": "=", +}; + +const encode = (base64) => { + return base64.replace(/[+/=]/g, (m) => ENC[m]); +}; + +const decode = (safe) => { + return safe.replace(/[-_.,]/g, (m) => DEC[m]); +}; + +const encodeBase64 = (content) => + encode(Buffer.from(content).toString("base64")); + +const decodeBase64 = (content) => + Buffer.from(decode(content), "base64").toString("utf-8"); + +/** + * A simple function that accepts an array and connection arguments, and returns + * a connection object for use in GraphQL. It uses array offsets as pagination, + * so pagination will only work if the array is static. + */ +function connectionFromArray(data, args) { + return connectionFromArraySlice(data, args, { + sliceStart: 0, + arrayLength: data.length, + }); +} + +/** + * A version of `connectionFromArray` that takes a promised array, and returns a + * promised connection. + */ +function connectionFromPromisedArray(dataPromise, args) { + return dataPromise.then((data) => connectionFromArray(data, args)); +} + +/** + * Given a slice (subset) of an array, returns a connection object for use in + * GraphQL. + * + * This function is similar to `connectionFromArray`, but is intended for use + * cases where you know the cardinality of the connection, consider it too large + * to materialize the entire array, and instead wish pass in a slice of the + * total result large enough to cover the range specified in `args`. + */ +function connectionFromArraySlice(arraySlice, args, meta) { + const { after, before, first, last } = args; + const { sliceStart, arrayLength } = meta; + const sliceEnd = sliceStart + arraySlice.length; + + let startOffset = Math.max(sliceStart, 0); + let endOffset = Math.min(sliceEnd, arrayLength); + + const afterOffset = getOffsetWithDefault(after, -1); + if (0 <= afterOffset && afterOffset < arrayLength) { + startOffset = Math.max(startOffset, afterOffset + 1); + } + + const beforeOffset = getOffsetWithDefault(before, endOffset); + if (0 <= beforeOffset && beforeOffset < arrayLength) { + endOffset = Math.min(endOffset, beforeOffset); + } + + if (typeof first === "number") { + if (first < 0) { + throw new Error('Argument "first" must be a non-negative integer'); + } + + endOffset = Math.min(endOffset, startOffset + first); + } + if (typeof last === "number") { + if (last < 0) { + throw new Error('Argument "last" must be a non-negative integer'); + } + + startOffset = Math.max(startOffset, endOffset - last); + } + + // If supplied slice is too large, trim it down before mapping over it. + const slice = arraySlice.slice( + startOffset - sliceStart, + endOffset - sliceStart + ); + + const edges = slice.map((value, index) => ({ + cursor: offsetToCursor(startOffset + index), + node: value, + })); + + const firstEdge = edges[0]; + const lastEdge = edges[edges.length - 1]; + const lowerBound = after != null ? afterOffset + 1 : 0; + const upperBound = before != null ? beforeOffset : arrayLength; + return { + edges, + pageInfo: { + startCursor: firstEdge ? firstEdge.cursor : null, + endCursor: lastEdge ? lastEdge.cursor : null, + hasPreviousPage: + typeof last === "number" ? startOffset > lowerBound : false, + hasNextPage: typeof first === "number" ? endOffset < upperBound : false, + }, + }; +} + +/** + * A version of `connectionFromArraySlice` that takes a promised array slice, + * and returns a promised connection. + */ +function connectionFromPromisedArraySlice(dataPromise, args, arrayInfo) { + return dataPromise.then((data) => + connectionFromArraySlice(data, args, arrayInfo) + ); +} + +const PREFIX = "arrayconnection:"; + +/** + * Creates the cursor string from an offset. + */ +function offsetToCursor(offset) { + return encodeBase64(PREFIX + offset.toString()); +} + +/** + * Extracts the offset from the cursor string. + */ +function cursorToOffset(cursor) { + return parseInt(decodeBase64(cursor).substring(PREFIX.length), 10); +} + +/** + * Return the cursor associated with an object in an array. + */ +function cursorForObjectInConnection(data, object) { + const offset = data.indexOf(object); + if (offset === -1) { + return null; + } + return offsetToCursor(offset); +} + +/** + * Given an optional cursor and a default offset, returns the offset + * to use; if the cursor contains a valid offset, that will be used, + * otherwise it will be the default. + */ +function getOffsetWithDefault(cursor, defaultOffset) { + if (typeof cursor !== "string") { + return defaultOffset; + } + const offset = cursorToOffset(cursor); + return isNaN(offset) ? defaultOffset : offset; +} + +exports.encodeBase64 = encodeBase64; +exports.decodeBase64 = decodeBase64; +exports.connectionFromArray = connectionFromArray; +exports.connectionFromPromisedArray = connectionFromPromisedArray; +exports.connectionFromArraySlice = connectionFromArraySlice; +exports.connectionFromPromisedArraySlice = connectionFromPromisedArraySlice; +exports.offsetToCursor = offsetToCursor; +exports.cursorToOffset = cursorToOffset; +exports.cursorForObjectInConnection = cursorForObjectInConnection; +exports.getOffsetWithDefault = getOffsetWithDefault; diff --git a/src/res/stableStringify.cjs b/src/res/stableStringify.cjs new file mode 100644 index 00000000..3af43787 --- /dev/null +++ b/src/res/stableStringify.cjs @@ -0,0 +1,50 @@ +"use strict"; + +// Modified and taken from https://github.com/epoberezkin/fast-json-stable-stringify/blob/master/index.js +function stableStringify(data) { + let seen = []; + return (function stringify(node) { + let colonSeparator = ":"; + if (node && typeof node.toJSON === "function") { + node = node.toJSON(); + } + + if (node === undefined) return; + if (typeof node === "number") return isFinite(node) ? "" + node : "null"; + if (typeof node !== "object") return JSON.stringify(node, undefined, ""); + + let i; + let out; + if (Array.isArray(node)) { + let sortedNode = node.slice().sort(); + out = "["; + for (i = 0; i < sortedNode.length; i++) { + if (i) out += ","; + out += stringify(sortedNode[i]) || "null"; + } + return out + "]"; + } + + if (node === null) return "null"; + + if (seen.indexOf(node) !== -1) { + return JSON.stringify("__cycle__"); + } + + let seenIndex = seen.push(node) - 1; + let keys = Object.keys(node).sort(); + out = ""; + for (i = 0; i < keys.length; i++) { + let key = keys[i]; + let value = stringify(node[key]); + + if (!value) continue; + if (out) out += ","; + out += JSON.stringify(key) + colonSeparator + value; + } + seen.splice(seenIndex, 1); + return "{" + out + "}"; + })(data); +} + +exports.stableStringify = stableStringify; diff --git a/tests/package-runtime-outputs.mjs b/tests/package-runtime-outputs.mjs index a8a0a572..5dff002e 100644 --- a/tests/package-runtime-outputs.mjs +++ b/tests/package-runtime-outputs.mjs @@ -1,5 +1,6 @@ import assert from "node:assert/strict"; import {execFileSync} from "node:child_process"; +import {readFile, readdir} from "node:fs/promises"; import {createRequire} from "node:module"; import path from "node:path"; import {fileURLToPath, pathToFileURL} from "node:url"; @@ -16,6 +17,56 @@ const esModuleRuntime = await import(pathToFileURL(esModulePath)); assert.ok(Object.keys(commonJsRuntime).length > 0, "CommonJS runtime is empty"); assert.ok(Object.keys(esModuleRuntime).length > 0, "ESM runtime is empty"); +const commonJsDataLoader = require(path.join(packageRoot, "src/res/DataLoader.js")); +const esModuleDataLoader = await import( + pathToFileURL(path.join(packageRoot, "src/res/DataLoader.mjs")), +); + +assert.equal( + await commonJsDataLoader.load( + commonJsDataLoader.makeSingle(async (key) => `commonjs:${key}`), + "loaded", + ), + "commonjs:loaded", +); +assert.equal( + await esModuleDataLoader.load( + esModuleDataLoader.makeSingle(async (key) => `esm:${key}`), + "loaded", + ), + "esm:loaded", +); + +const commonJsConnections = require( + path.join(packageRoot, "src/res/ResGraph__Connections.js"), +); +const commonJsUtils = require( + path.join(packageRoot, "src/res/ResGraph__Utils.js"), +); +const connection = commonJsConnections.connectionFromArray( + ["first", "second"], + {after: undefined, before: undefined, first: 1, last: undefined}, +); +assert.deepEqual(connection.edges.map(({node}) => node), ["first"]); +assert.equal(connection.pageInfo.hasNextPage, true); +assert.equal( + commonJsUtils.Base64.decode(commonJsUtils.Base64.encode("ResGraph ✓")), + "ResGraph ✓", +); + +const runtimeDirectory = path.join(packageRoot, "src/res"); +const commonJsFiles = (await readdir(runtimeDirectory)) + .filter((fileName) => fileName.endsWith(".js")) + .map((fileName) => path.join(runtimeDirectory, fileName)); +for (const filePath of commonJsFiles) { + const source = await readFile(filePath, "utf8"); + assert.doesNotMatch( + source, + /require\s*\([^)]*\.mjs["']/, + `${path.relative(packageRoot, filePath)} requires an ESM module`, + ); +} + const packOutput = execFileSync( "npm", ["pack", "--dry-run", "--json", "--ignore-scripts"], @@ -32,5 +83,12 @@ assert.ok( packedFiles.has("src/res/ResGraph.mjs"), "npm package is missing ESM runtime", ); +for (const helperPath of [ + "src/res/dataLoaderCompat.cjs", + "src/res/graphqlRelayConnections.cjs", + "src/res/stableStringify.cjs", +]) { + assert.ok(packedFiles.has(helperPath), `npm package is missing ${helperPath}`); +} -console.log("Verified ESM and CommonJS runtime package outputs."); +console.log("Verified ESM and CommonJS runtime behavior and package outputs.");