Skip to content

Commit 09a4d4e

Browse files
test: further.
1 parent e0b2b7c commit 09a4d4e

3 files changed

Lines changed: 189 additions & 2 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ type ModuleOptions = {
134134
detectCircularRequires?: 'off' | 'warn' | 'error'
135135
detectDualPackageHazard?: 'off' | 'warn' | 'error'
136136
dualPackageHazardScope?: 'file' | 'project'
137-
dualPackageHazardAllowlist?: string | string[]
137+
dualPackageHazardAllowlist?: string[]
138138
requireSource?: 'builtin' | 'create-require'
139139
importMetaPrelude?: 'off' | 'auto' | 'on'
140140
cjsDefault?: 'module-exports' | 'auto' | 'none'
@@ -163,7 +163,7 @@ type ModuleOptions = {
163163
- `detectCircularRequires` (`off`): optionally detect relative static require cycles across `.js`/`.mjs`/`.cjs`/`.ts`/`.mts`/`.cts` (realpath-normalized) and warn/throw.
164164
- `detectDualPackageHazard` (`warn`): flag when `import` and `require` mix for the same package or root/subpath are combined in ways that can resolve to separate module instances (dual packages). Set to `error` to fail the transform.
165165
- `dualPackageHazardScope` (`file`): `file` preserves the legacy per-file detector; `project` aggregates package usage across all CLI inputs (useful in monorepos/hoisted installs) and emits one diagnostic per package.
166-
- `dualPackageHazardAllowlist` (`[]`): suppress dual-package hazard diagnostics for the listed packages. Accepts a string or array; entries are trimmed and empty values dropped. Applies to both `file` and `project` scopes and is also available via the CLI flag `--dual-package-hazard-allowlist pkg1,pkg2`.
166+
- `dualPackageHazardAllowlist` (`[]`): suppress dual-package hazard diagnostics for the listed packages. Accepts an array in the API; entries are trimmed and empty values dropped. The CLI flag `--dual-package-hazard-allowlist pkg1,pkg2` parses a comma- or space-separated string into this array. Applies to both `file` and `project` scopes.
167167
- `topLevelAwait` (`error`): throw, wrap, or preserve when TLA appears in CommonJS output. `wrap` runs the file body inside an async IIFE (exports may resolve after the initial tick); `preserve` leaves `await` at top level, which Node will reject for CJS.
168168
- `rewriteSpecifier` (off): rewrite relative specifiers to a chosen extension or via a callback. Precedence: the callback (if provided) runs first; if it returns a string, that wins. If it returns `undefined` or `null`, the appenders still apply.
169169
- `requireSource` (`builtin`): whether `require` comes from Node or `createRequire`.

test/cli.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,130 @@ test('--dual-package-hazard-allowlist suppresses hazards', async () => {
227227
}
228228
})
229229

230+
test('--dual-package-hazard-allowlist parses multiple comma-separated packages', async () => {
231+
const temp = await mkdtemp(join(tmpdir(), 'module-cli-dual-hazard-allow-multi-'))
232+
const file = join(temp, 'entry.mjs')
233+
const packages = ['x-core', 'y-core', 'z-core']
234+
235+
for (const pkg of packages) {
236+
const pkgDir = join(temp, 'node_modules', pkg)
237+
await mkdir(pkgDir, { recursive: true })
238+
await writeFile(
239+
join(pkgDir, 'package.json'),
240+
JSON.stringify(
241+
{
242+
name: pkg,
243+
version: '1.0.0',
244+
exports: {
245+
'.': { import: './index.mjs', require: './index.cjs' },
246+
'./module': './index.mjs',
247+
},
248+
main: './index.cjs',
249+
},
250+
null,
251+
2,
252+
),
253+
'utf8',
254+
)
255+
}
256+
257+
await writeFile(
258+
file,
259+
[
260+
"import { X } from 'x-core/module'",
261+
"const core = require('x-core')",
262+
"import { Y } from 'y-core/module'",
263+
"const y = require('y-core')",
264+
"import { Z } from 'z-core/module'",
265+
"const z = require('z-core')",
266+
'console.log(core, X, y, Y, z, Z)',
267+
'',
268+
].join('\n'),
269+
'utf8',
270+
)
271+
272+
try {
273+
const result = runCli([
274+
'--target',
275+
'commonjs',
276+
'--cwd',
277+
temp,
278+
'--dual-package-hazard-allowlist',
279+
' x-core , , y-core ',
280+
'entry.mjs',
281+
])
282+
283+
assert.equal(result.status, 0)
284+
assert.match(result.stderr, /z-core/)
285+
assert.ok(!/x-core/.test(result.stderr))
286+
assert.ok(!/y-core/.test(result.stderr))
287+
} finally {
288+
await rm(temp, { recursive: true, force: true })
289+
}
290+
})
291+
292+
test('--dual-package-hazard-allowlist parses comma-delimited list without spaces', async () => {
293+
const temp = await mkdtemp(join(tmpdir(), 'module-cli-dual-hazard-allow-csv-'))
294+
const file = join(temp, 'entry.mjs')
295+
const packages = ['x-core', 'y-core', 'z-core']
296+
297+
for (const pkg of packages) {
298+
const pkgDir = join(temp, 'node_modules', pkg)
299+
await mkdir(pkgDir, { recursive: true })
300+
await writeFile(
301+
join(pkgDir, 'package.json'),
302+
JSON.stringify(
303+
{
304+
name: pkg,
305+
version: '1.0.0',
306+
exports: {
307+
'.': { import: './index.mjs', require: './index.cjs' },
308+
'./module': './index.mjs',
309+
},
310+
main: './index.cjs',
311+
},
312+
null,
313+
2,
314+
),
315+
'utf8',
316+
)
317+
}
318+
319+
await writeFile(
320+
file,
321+
[
322+
"import { X } from 'x-core/module'",
323+
"const core = require('x-core')",
324+
"import { Y } from 'y-core/module'",
325+
"const y = require('y-core')",
326+
"import { Z } from 'z-core/module'",
327+
"const z = require('z-core')",
328+
'console.log(core, X, y, Y, z, Z)',
329+
'',
330+
].join('\n'),
331+
'utf8',
332+
)
333+
334+
try {
335+
const result = runCli([
336+
'--target',
337+
'commonjs',
338+
'--cwd',
339+
temp,
340+
'--dual-package-hazard-allowlist',
341+
'x-core,y-core',
342+
'entry.mjs',
343+
])
344+
345+
assert.equal(result.status, 0)
346+
assert.match(result.stderr, /z-core/)
347+
assert.ok(!/x-core/.test(result.stderr))
348+
assert.ok(!/y-core/.test(result.stderr))
349+
} finally {
350+
await rm(temp, { recursive: true, force: true })
351+
}
352+
})
353+
230354
test('--dual-package-hazard-scope project aggregates across files', async () => {
231355
const temp = await mkdtemp(join(tmpdir(), 'module-cli-dual-hazard-project-'))
232356
const fileImport = join(temp, 'entry.mjs')

test/module.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,69 @@ describe('@knighted/module', () => {
165165
assert.equal(diagnostics.length, 0)
166166
})
167167

168+
it('supports multi-package allowlists and ignores empty entries', async t => {
169+
const temp = await mkdtemp(join(tmpdir(), 'module-dual-hazard-allow-multi-'))
170+
const file = join(temp, 'entry.mjs')
171+
const packages = ['x-core', 'y-core', 'z-core']
172+
173+
for (const pkg of packages) {
174+
const pkgDir = join(temp, 'node_modules', pkg)
175+
await mkdir(pkgDir, { recursive: true })
176+
await writeFile(
177+
join(pkgDir, 'package.json'),
178+
JSON.stringify(
179+
{
180+
name: pkg,
181+
version: '1.0.0',
182+
exports: {
183+
'.': { import: './index.mjs', require: './index.cjs' },
184+
'./module': './index.mjs',
185+
},
186+
main: './index.cjs',
187+
},
188+
null,
189+
2,
190+
),
191+
'utf8',
192+
)
193+
}
194+
195+
await writeFile(
196+
file,
197+
[
198+
"import { X } from 'x-core/module'",
199+
"const core = require('x-core')",
200+
"import { Y } from 'y-core/module'",
201+
"const y = require('y-core')",
202+
"import { Z } from 'z-core/module'",
203+
"const z = require('z-core')",
204+
'console.log(core, X, y, Y, z, Z)',
205+
'',
206+
].join('\n'),
207+
'utf8',
208+
)
209+
210+
t.after(() => rm(temp, { recursive: true, force: true }))
211+
212+
const diagnostics: any[] = []
213+
await transform(file, {
214+
target: 'commonjs',
215+
detectDualPackageHazard: 'warn',
216+
dualPackageHazardAllowlist: [' x-core ', '', 'y-core', ' '],
217+
diagnostics: diag => diagnostics.push(diag),
218+
cwd: temp,
219+
})
220+
221+
assert.ok(
222+
diagnostics.length > 0,
223+
'expected remaining hazards for non-allowlisted pkg',
224+
)
225+
assert.ok(diagnostics.every(d => /z-core/.test(d.message)))
226+
const codes = new Set(diagnostics.map(d => d.code))
227+
assert.ok(codes.has('dual-package-mixed-specifiers'))
228+
assert.ok(codes.has('dual-package-conditional-exports'))
229+
})
230+
168231
it('warns on hazard across export forms and dynamic import', async t => {
169232
const temp = await mkdtemp(join(tmpdir(), 'module-dual-hazard-exports-'))
170233
const file = join(temp, 'entry.mjs')

0 commit comments

Comments
 (0)