From 454f3cb9ae3ec3eff3d4bffbb0f8ac5fbb9ccab5 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 12 Oct 2026 14:17:21 +0900 Subject: [PATCH 1/3] feat(modules)!: share ESM and CommonJS exports The implementation is cached after the first load, so removing its global export leaves the other wrapper without an export. Retain a version-specific symbol so ESM imports, CommonJS requires, and independent copies of one version receive the same function. BREAKING CHANGE: Require Node.js 18 or later. --- .npmignore | 1 + CHANGELOG.md | 3 + esm/package.json | 2 +- esm/wrapper.d.ts | 4 - esm/wrapper.mjs | 11 ++- index.d.ts | 42 +++++----- index.js | 33 ++++---- package.json | 19 +++-- readme.md | 5 ++ test-fixtures/consumer.cjs | 6 ++ test-fixtures/consumer.mjs | 5 ++ test-fixtures/modules.cjs | 54 +++++++++++++ test-fixtures/types.cts | 28 +++++++ test-fixtures/types.mts | 22 ++++++ test-modules.js | 157 +++++++++++++++++++++++++++++++++++++ test.js | 4 +- tsconfig.json | 2 +- wrapper.cjs | 9 +++ 18 files changed, 351 insertions(+), 56 deletions(-) delete mode 100644 esm/wrapper.d.ts create mode 100644 test-fixtures/consumer.cjs create mode 100644 test-fixtures/consumer.mjs create mode 100644 test-fixtures/modules.cjs create mode 100644 test-fixtures/types.cts create mode 100644 test-fixtures/types.mts create mode 100644 test-modules.js create mode 100644 wrapper.cjs diff --git a/.npmignore b/.npmignore index 99f4787..810e9ee 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,7 @@ **/* !index.d.ts !index.js +!wrapper.cjs !LICENSE !package.json !esm/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 83290b7..1853b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Next +- Require Node.js 18 or later. +- Use ESM and CommonJS wrappers that share an export through a version-specific global symbol. + - Changed the default circular marker to `'[Circular: N]'`, where `N` is the referenced ancestor's depth, starting at 0 for the root. Set `circularValue: '[Circular]'` to retain the previous format. This changes the serialized output for circular references. diff --git a/esm/package.json b/esm/package.json index f0eba66..c0e20c6 100644 --- a/esm/package.json +++ b/esm/package.json @@ -1,6 +1,6 @@ { "name": "safe-stable-stringify", - "version": "2.5.0", + "version": "3.0.0", "type": "module", "main": "./wrapper.mjs", "exports": "./wrapper.mjs" diff --git a/esm/wrapper.d.ts b/esm/wrapper.d.ts deleted file mode 100644 index 2351eb1..0000000 --- a/esm/wrapper.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { stringify } from '../index.js' - -export * from '../index.js' -export default stringify diff --git a/esm/wrapper.mjs b/esm/wrapper.mjs index 0cf01f0..40fb691 100644 --- a/esm/wrapper.mjs +++ b/esm/wrapper.mjs @@ -1,6 +1,9 @@ -import cjsModule from '../index.js' +import '../index.js' -export const configure = cjsModule.configure +const sharedGlobal = /** @type {Record} */ ( + globalThis +) -export const stringify = cjsModule -export default cjsModule +export const stringify = sharedGlobal[Symbol.for('safe-stable-stringify@3.0.0')] +export const configure = stringify.configure +export default stringify diff --git a/index.d.ts b/index.d.ts index c6f078f..955403b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,24 +1,28 @@ -export type Replacer = (number | string)[] | null | undefined | ((key: string, value: unknown) => string | number | boolean | null | object) - -export function stringify(value: undefined | symbol | ((...args: unknown[]) => unknown), replacer?: Replacer, space?: string | number): undefined -export function stringify(value: string | number | unknown[] | null | boolean | object, replacer?: Replacer, space?: string | number): string -export function stringify(value: unknown, replacer?: ((key: string, value: unknown) => unknown) | (number | string)[] | null | undefined, space?: string | number): string | undefined - -export interface StringifyOptions { - bigint?: boolean | 'string', - /** Defaults to '[Circular: N]', where N is the ancestor depth, starting at 0 for the root. */ - circularValue?: string | null | TypeErrorConstructor | ErrorConstructor, - deterministic?: boolean | ((a: string, b: string) => number), - maximumBreadth?: number, - maximumDepth?: number, - strict?: boolean, - safe?: boolean, +interface StringifyFunction { + (value: undefined | symbol | ((...args: unknown[]) => unknown), replacer?: stringify.Replacer, space?: string | number): undefined + (value: string | number | unknown[] | null | boolean | object, replacer?: stringify.Replacer, space?: string | number): string + (value: unknown, replacer?: ((key: string, value: unknown) => unknown) | (number | string)[] | null | undefined, space?: string | number): string | undefined } -export namespace stringify { - export function configure(options: StringifyOptions): typeof stringify +declare const stringify: StringifyFunction & { + configure(options: stringify.StringifyOptions): StringifyFunction + stringify: typeof stringify + default: typeof stringify } -export function configure(options: StringifyOptions): typeof stringify +declare namespace stringify { + export type Replacer = (number | string)[] | null | undefined | ((key: string, value: unknown) => string | number | boolean | null | object) + + export interface StringifyOptions { + bigint?: boolean | 'string', + /** Defaults to '[Circular: N]', where N is the ancestor depth, starting at 0 for the root. */ + circularValue?: string | null | TypeErrorConstructor | ErrorConstructor, + deterministic?: boolean | ((a: string, b: string) => number), + maximumBreadth?: number, + maximumDepth?: number, + strict?: boolean, + safe?: boolean, + } +} -export default stringify +export = stringify diff --git a/index.js b/index.js index 9a00308..e46515c 100644 --- a/index.js +++ b/index.js @@ -4,23 +4,6 @@ const { hasOwnProperty } = Object.prototype /** @typedef {{toJSON: (key: string) => unknown}} ToJSON */ -const stringify = configure() - -// @ts-expect-error -stringify.configure = configure -// @ts-expect-error -stringify.stringify = stringify - -// @ts-expect-error -stringify.default = stringify - -// @ts-expect-error used for named export -exports.stringify = stringify -// @ts-expect-error used for named export -exports.configure = configure - -module.exports = stringify - // eslint-disable-next-line no-control-regex const strEscapeSequencesRegExp = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]/ @@ -779,3 +762,19 @@ function configure (options) { return stringify } + +const sharedGlobal = /** @type {Record} */ ( + globalThis +) +const exportSymbol = Symbol.for('safe-stable-stringify@3.0.0') + +if (sharedGlobal[exportSymbol] === undefined) { + const stringify = configure() + // @ts-expect-error + stringify.configure = configure + // @ts-expect-error + stringify.stringify = stringify + // @ts-expect-error + stringify.default = stringify + sharedGlobal[exportSymbol] = /** @type {typeof import('./index.js').default} */ (stringify) +} diff --git a/package.json b/package.json index 4739c69..d38c6f2 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "safe-stable-stringify", - "version": "2.5.0", + "version": "3.0.0", "description": "Deterministic and safely JSON.stringify to quickly serialize JavaScript objects", "exports": { - "require": "./index.js", - "import": "./esm/wrapper.js" + "types": "./index.d.ts", + "require": "./wrapper.cjs", + "import": "./esm/wrapper.mjs" }, "keywords": [ "stable", @@ -21,18 +22,18 @@ "fast", "bigint" ], - "main": "index.js", + "main": "wrapper.cjs", "scripts": { - "test": "standard && tap test.js", - "tap": "tap test.js", - "tap:only": "tap test.js --watch --only", + "test": "standard && tap test.js test-modules.js", + "tap": "tap test.js test-modules.js", + "tap:only": "tap test.js test-modules.js --watch --only", "benchmark": "node --allow-natives-syntax benchmark.js", "compare": "node --allow-natives-syntax compare.js", "lint": "standard --fix", "tsc": "tsc --project tsconfig.json" }, "engines": { - "node": ">=10" + "node": ">=18" }, "author": "Ruben Bridgewater", "license": "MIT", @@ -41,6 +42,7 @@ "@types/json-stable-stringify": "^1.2.0", "@types/node": "^18.19.130", "clone": "^2.1.2", + "esbuild": "^0.21.5", "fast-json-stable-stringify": "^2.1.0", "fast-safe-stringify": "^2.1.1", "fast-stable-stringify": "^1.0.0", @@ -49,6 +51,7 @@ "json-stable-stringify": "^1.3.0", "json-stringify-deterministic": "^1.0.12", "json-stringify-safe": "^5.0.1", + "rollup": "^3.30.0", "bench-node": "^0.14.0", "standard": "^17.1.2", "tap": "^16.3.10", diff --git a/readme.md b/readme.md index 0a1119e..a474406 100644 --- a/readme.md +++ b/readme.md @@ -8,6 +8,11 @@ Gracefully handles circular structures and bigint instead of throwing. Optional custom circular values, deterministic behavior or strict JSON compatibility check. +The ESM default export and the CommonJS export are the same function. +Imports and `require()` calls can be used together in either order. +Copies of the same package version share their export through a retained +global symbol. Different versions use separate symbols. + ## stringify(value[, replacer[, space]]) The same as [JSON.stringify][]. diff --git a/test-fixtures/consumer.cjs b/test-fixtures/consumer.cjs new file mode 100644 index 0000000..921d698 --- /dev/null +++ b/test-fixtures/consumer.cjs @@ -0,0 +1,6 @@ +'use strict' + +const stringify = require('safe-stable-stringify') + +if (stringify !== stringify.default || stringify !== stringify.stringify) throw new Error('Export mismatch') +if (stringify({ b: 2, a: 1 }) !== '{"a":1,"b":2}') throw new Error('Incorrect output') diff --git a/test-fixtures/consumer.mjs b/test-fixtures/consumer.mjs new file mode 100644 index 0000000..4ccf0ce --- /dev/null +++ b/test-fixtures/consumer.mjs @@ -0,0 +1,5 @@ +import stringify, { configure, stringify as named } from 'safe-stable-stringify' + +if (stringify !== named || configure !== stringify.configure) throw new Error('Export mismatch') +if (stringify({ b: 2, a: 1 }) !== '{"a":1,"b":2}') throw new Error('Incorrect output') +if (configure({ deterministic: false })({ b: 2, a: 1 }) !== '{"b":2,"a":1}') throw new Error('Incorrect configuration') diff --git a/test-fixtures/modules.cjs b/test-fixtures/modules.cjs new file mode 100644 index 0000000..825573b --- /dev/null +++ b/test-fixtures/modules.cjs @@ -0,0 +1,54 @@ +'use strict' + +const assert = require('node:assert/strict') + +const { version } = require('./node_modules/safe-stable-stringify/package.json') +const root = globalThis + +async function check () { + let cjs + let esm + let otherVersion + if (process.argv[2] !== 'import-first') { + cjs = require('./first.cjs') + otherVersion = require('./other-version') + esm = await import('./first.mjs') + } else { + esm = await import('./first.mjs') + otherVersion = require('./other-version') + cjs = require('./first.cjs') + } + assert.notEqual(otherVersion, cjs) + assert.equal(otherVersion({ b: 2, a: 1 }), '{"a":1,"b":2}') + assert.equal(root[Symbol.for(`safe-stable-stringify@${version}`)], cjs) + assert.equal(cjs, esm.default) + assert.equal(cjs, esm.stringify) + assert.equal(cjs.configure, esm.configure) + assert.equal(cjs, cjs.default) + assert.equal(cjs, cjs.stringify) + assert.equal(cjs({ b: 2, a: 1 }), '{"a":1,"b":2}') + const configured = esm.configure({ deterministic: false }) + assert.equal(configured.stringify, undefined) + assert.equal(configured.default, undefined) + assert.equal(configured.configure, undefined) + assert.equal(configured({ b: 2, a: 1 }), '{"b":2,"a":1}') + assert.equal(require('./second.cjs'), cjs) + assert.equal(require('./same-version'), cjs) + const modules = await Promise.all([ + import('./second.mjs'), + import('./first.mjs'), + import('./same-version/esm/wrapper.mjs') + ]) + for (const module of modules) { + assert.equal(module.default, cjs) + assert.equal(module.configure, cjs.configure) + } +} + +/** @param {Error} error */ +function onError (error) { + console.error(error) + process.exitCode = 1 +} + +check().catch(onError) diff --git a/test-fixtures/types.cts b/test-fixtures/types.cts new file mode 100644 index 0000000..75cb77e --- /dev/null +++ b/test-fixtures/types.cts @@ -0,0 +1,28 @@ +import stringify = require('safe-stable-stringify') +import type { Replacer, StringifyOptions } from 'safe-stable-stringify' + +const options: StringifyOptions = { deterministic: false } +const replacer: Replacer = ['value'] +const result: string | undefined = stringify({ value: 1 }) +const alias: string | undefined = stringify.stringify({ result }) +stringify.default({ alias }) +stringify.configure(options)({ value: 1 }, replacer) + +const namespacedOptions: stringify.StringifyOptions = options +const namespacedReplacer: stringify.Replacer = replacer +stringify.configure(namespacedOptions)({ value: 1 }, namespacedReplacer) + +const configured = stringify.configure(options) +const configuredResult: string = configured('value') +const omitted: undefined = configured(undefined) +configured({ configuredResult, omitted }) + +// @ts-expect-error +configured.stringify({ value: 1 }) +// @ts-expect-error +configured.default({ value: 1 }) +// @ts-expect-error +configured.configure(options) + +// @ts-expect-error +stringify.configure({ bigint: 'invalid' }) diff --git a/test-fixtures/types.mts b/test-fixtures/types.mts new file mode 100644 index 0000000..2cf2d82 --- /dev/null +++ b/test-fixtures/types.mts @@ -0,0 +1,22 @@ +import stringify, { configure, stringify as named, type Replacer, type StringifyOptions } from 'safe-stable-stringify' + +const options: StringifyOptions = { deterministic: false } +const replacer: Replacer = ['value'] +const result: string | undefined = stringify({ value: 1 }) +const alias: string | undefined = named({ result }) +configure(options)({ alias }, replacer) + +const configured = configure(options) +const configuredResult: string = configured('value') +const omitted: undefined = configured(undefined) +configured({ configuredResult, omitted }) + +// @ts-expect-error +configured.stringify({ value: 1 }) +// @ts-expect-error +configured.default({ value: 1 }) +// @ts-expect-error +configured.configure(options) + +// @ts-expect-error +configure({ bigint: 'invalid' }) diff --git a/test-modules.js b/test-modules.js new file mode 100644 index 0000000..0956a69 --- /dev/null +++ b/test-modules.js @@ -0,0 +1,157 @@ +'use strict' + +const { execFile } = require('node:child_process') +const { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } = require('node:fs') +const { tmpdir } = require('node:os') +const { join } = require('node:path') +const { promisify } = require('node:util') + +const { build } = require('esbuild') +const { rollup } = require('rollup') +const { test } = require('tap') + +const execFileAsync = promisify(execFile) +const { version } = require('./package.json') + +/** + * @param {string} directory + * @param {string} packageVersion + */ +function writePackage (directory, packageVersion) { + const files = { + '': ['package.json', 'index.js', 'index.d.ts', 'wrapper.cjs'], + esm: readdirSync(join(__dirname, 'esm')) + } + for (const [subdirectory, names] of Object.entries(files)) { + mkdirSync(join(directory, subdirectory), { recursive: true }) + for (const name of names) { + let source = readFileSync(join(__dirname, subdirectory, name), 'utf8') + if (name === 'package.json') { + const metadata = JSON.parse(source) + metadata.version = packageVersion + source = JSON.stringify(metadata) + } else { + source = source.replaceAll(`safe-stable-stringify@${version}`, `safe-stable-stringify@${packageVersion}`) + } + writeFileSync(join(directory, subdirectory, name), source) + } + } +} + +/** @param {import('tap').Test} assert */ +function createPackageFixture (assert) { + const directory = mkdtempSync(join(tmpdir(), 'safe-stable-stringify-modules-')) + const packageDirectory = join(directory, 'node_modules', 'safe-stable-stringify') + assert.teardown(() => rmSync(directory, { recursive: true, force: true })) + writePackage(packageDirectory, version) + for (const name of readdirSync(join(__dirname, 'test-fixtures'))) { + writeFileSync(join(directory, name), readFileSync(join(__dirname, 'test-fixtures', name))) + } + return directory +} + +/** @param {import('tap').Test} assert */ +async function testModuleEntryPoints (assert) { + const directory = createPackageFixture(assert) + writePackage(join(directory, 'same-version'), version) + writePackage(join(directory, 'other-version'), '0.0.0-test') + for (const name of ['first', 'second']) { + writeFileSync(join(directory, `${name}.cjs`), "module.exports = require('safe-stable-stringify')\n") + writeFileSync(join(directory, `${name}.mjs`), "export { default, configure, stringify } from 'safe-stable-stringify'\n") + } + + const results = await Promise.all([ + execFileAsync(process.execPath, ['modules.cjs', 'require-first'], { cwd: directory }), + execFileAsync(process.execPath, ['modules.cjs', 'import-first'], { cwd: directory }) + ]) + for (const { stdout, stderr } of results) { + assert.equal(stdout, '', 'no output') + assert.equal(stderr, '', 'no errors') + } +} + +test('module entry points share exports across consumers and load orders', testModuleEntryPoints) + +/** @param {import('tap').Test} assert */ +function testReload (assert) { + const stringify = require('./wrapper.cjs') + delete require.cache[require.resolve('./wrapper.cjs')] + delete require.cache[require.resolve('./index.js')] + const reloaded = require('./wrapper.cjs') + assert.equal(reloaded, stringify, 're-evaluation preserves the export') + assert.equal(reloaded({ b: 2, a: 1 }), '{"a":1,"b":2}') + assert.end() +} + +test('module re-evaluation reuses the retained export', testReload) + +/** @param {import('tap').Test} assert */ +async function testBundlers (assert) { + const directory = createPackageFixture(assert) + await Promise.all([ + build({ + entryPoints: [join(directory, 'consumer.mjs')], + bundle: true, + platform: 'browser', + format: 'esm', + minify: true, + outfile: join(directory, 'esbuild.mjs') + }), + build({ + entryPoints: [join(directory, 'consumer.cjs')], + bundle: true, + platform: 'browser', + format: 'cjs', + minify: true, + outfile: join(directory, 'esbuild.cjs') + }) + ]) + const rollupEntry = join(directory, 'node_modules', 'safe-stable-stringify', 'esm', 'wrapper.mjs') + const bundle = await rollup({ input: rollupEntry }) + try { + await bundle.write({ file: join(directory, 'rollup.mjs'), format: 'esm' }) + } finally { + await bundle.close() + } + const esmSource = readFileSync(join(directory, 'consumer.mjs'), 'utf8') + writeFileSync(join(directory, 'rollup-consumer.mjs'), esmSource.replace('safe-stable-stringify', './rollup.mjs')) + const results = await Promise.all([ + execFileAsync(process.execPath, [join(directory, 'esbuild.mjs')]), + execFileAsync(process.execPath, [join(directory, 'esbuild.cjs')]), + execFileAsync(process.execPath, [join(directory, 'rollup-consumer.mjs')]) + ]) + for (const { stdout, stderr } of results) { + assert.equal(stdout, '', 'bundle produces no output') + assert.equal(stderr, '', 'bundle produces no errors') + } +} + +test('bundlers preserve the side effect and exports', testBundlers) + +/** @param {import('tap').Test} assert */ +async function testTypes (assert) { + const directory = createPackageFixture(assert) + const tasks = [] + for (const [module, resolution, ...files] of [ + ['NodeNext', 'NodeNext', 'types.cts', 'types.mts'], + ['CommonJS', 'Node', 'types.cts', 'types.mts'], + ['ESNext', 'Bundler', 'types.mts'] + ]) { + tasks.push(execFileAsync(process.execPath, [ + require.resolve('typescript/bin/tsc'), + '--noEmit', + '--strict', + '--module', module, + '--moduleResolution', resolution, + '--target', 'ES2020', + ...files + ], { cwd: directory })) + } + const results = await Promise.all(tasks) + for (const { stdout, stderr } of results) { + assert.equal(stdout, '', 'both module formats compile without errors') + assert.equal(stderr, '', 'compiler produces no errors') + } +} + +test('TypeScript resolves both module formats', testTypes) diff --git a/test.js b/test.js index 86b2011..b06a36d 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,5 @@ const { test } = require('tap') -const { stringify } = require('./index.js') +const { stringify } = require('./wrapper.cjs') const clone = require('clone') const { constants: { MAX_STRING_LENGTH } } = require('buffer') const { runInNewContext } = require('vm') @@ -84,7 +84,7 @@ function identityReplacer (key, value) { /** * @param {import('tap').Test} assert - * @param {typeof stringify} serializer + * @param {ReturnType} serializer * @param {object} value * @param {object} expected * @param {string[]} keys diff --git a/tsconfig.json b/tsconfig.json index 9162395..be233c6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,6 @@ "dom" ] }, - "include": ["**/*.js", "**/*.ts", "esm/wrapper.mjs"], + "include": ["**/*.js", "**/*.ts", "**/*.cts", "**/*.mts", "wrapper.cjs", "esm/wrapper.mjs"], "exclude": ["compare.js", "benchmark.js", "./coverage"] } diff --git a/wrapper.cjs b/wrapper.cjs new file mode 100644 index 0000000..4145fa5 --- /dev/null +++ b/wrapper.cjs @@ -0,0 +1,9 @@ +'use strict' + +require('./index.js') + +const sharedGlobal = /** @type {Record} */ ( + globalThis +) + +module.exports = sharedGlobal[Symbol.for('safe-stable-stringify@3.0.0')] From 8ebf2a1820a917f163b87e7dd2fe2dcd934d4d47 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Tue, 13 Oct 2026 13:12:37 +0900 Subject: [PATCH 2/3] fix(modules): keep the shared export non-enumerable The retained export is enumerable, so object spreads of globalThis copy the function along with other global properties. --- CHANGELOG.md | 4 ++-- index.js | 7 ++++++- readme.md | 5 ----- test-fixtures/modules.cjs | 1 + test.js | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1853b04..ef491cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ ## Next -- Require Node.js 18 or later. -- Use ESM and CommonJS wrappers that share an export through a version-specific global symbol. +- **Breaking change:** Require Node.js 18 or later. +- Fix ESM imports in Node.js and bundlers, including applications that also use CommonJS. - Changed the default circular marker to `'[Circular: N]'`, where `N` is the referenced ancestor's depth, starting at 0 for the root. Set `circularValue: '[Circular]'` to retain the previous format. This changes the serialized output for circular references. diff --git a/index.js b/index.js index e46515c..e0aa013 100644 --- a/index.js +++ b/index.js @@ -776,5 +776,10 @@ if (sharedGlobal[exportSymbol] === undefined) { stringify.stringify = stringify // @ts-expect-error stringify.default = stringify - sharedGlobal[exportSymbol] = /** @type {typeof import('./index.js').default} */ (stringify) + Object.defineProperty(sharedGlobal, exportSymbol, { + configurable: true, + enumerable: false, + value: /** @type {typeof import('./index.js').default} */ (stringify), + writable: true + }) } diff --git a/readme.md b/readme.md index a474406..0a1119e 100644 --- a/readme.md +++ b/readme.md @@ -8,11 +8,6 @@ Gracefully handles circular structures and bigint instead of throwing. Optional custom circular values, deterministic behavior or strict JSON compatibility check. -The ESM default export and the CommonJS export are the same function. -Imports and `require()` calls can be used together in either order. -Copies of the same package version share their export through a retained -global symbol. Different versions use separate symbols. - ## stringify(value[, replacer[, space]]) The same as [JSON.stringify][]. diff --git a/test-fixtures/modules.cjs b/test-fixtures/modules.cjs index 825573b..9b5f0d5 100644 --- a/test-fixtures/modules.cjs +++ b/test-fixtures/modules.cjs @@ -21,6 +21,7 @@ async function check () { assert.notEqual(otherVersion, cjs) assert.equal(otherVersion({ b: 2, a: 1 }), '{"a":1,"b":2}') assert.equal(root[Symbol.for(`safe-stable-stringify@${version}`)], cjs) + assert.equal(Object.getOwnPropertyDescriptor(root, Symbol.for(`safe-stable-stringify@${version}`)).enumerable, false) assert.equal(cjs, esm.default) assert.equal(cjs, esm.stringify) assert.equal(cjs.configure, esm.configure) diff --git a/test.js b/test.js index b06a36d..9cc06bc 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,5 @@ const { test } = require('tap') -const { stringify } = require('./wrapper.cjs') +const { stringify } = require('.') const clone = require('clone') const { constants: { MAX_STRING_LENGTH } } = require('buffer') const { runInNewContext } = require('vm') From 42eb8f14e5dcaca919a04334daac535314aac558 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Tue, 13 Oct 2026 07:55:01 +0200 Subject: [PATCH 3/3] Apply suggestion from @BridgeAR --- esm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esm/package.json b/esm/package.json index c0e20c6..f0eba66 100644 --- a/esm/package.json +++ b/esm/package.json @@ -1,6 +1,6 @@ { "name": "safe-stable-stringify", - "version": "3.0.0", + "version": "2.5.0", "type": "module", "main": "./wrapper.mjs", "exports": "./wrapper.mjs"