From dece597a0ce25d787152f2d5cf8c1f12b831a7bf Mon Sep 17 00:00:00 2001 From: KCM Date: Sat, 7 Mar 2026 14:25:21 -0600 Subject: [PATCH 1/2] refactor: replace npm glob with node fs glob. --- package-lock.json | 12 +++++-- package.json | 3 +- src/cli.ts | 25 ++++++++------ test/cli.ts | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 454b9d7..483f37d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,14 @@ { "name": "@knighted/module", - "version": "1.5.1", + "version": "1.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@knighted/module", - "version": "1.5.1", + "version": "1.5.2", "license": "MIT", "dependencies": { - "glob": "^13.0.6", "magic-string": "^0.30.21", "oxc-parser": "^0.116.0", "periscopic": "^4.0.2" @@ -3649,6 +3648,7 @@ "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "minimatch": "^10.2.2", @@ -3666,6 +3666,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, "license": "MIT", "engines": { "node": "18 || 20 || >=22" @@ -3675,6 +3676,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -3687,6 +3689,7 @@ "version": "11.2.6", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -3696,6 +3699,7 @@ "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" @@ -3711,6 +3715,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", @@ -4250,6 +4255,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" diff --git a/package.json b/package.json index b4d5a5c..87d2c50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@knighted/module", - "version": "1.5.1", + "version": "1.5.2", "description": "Bidirectional transform for ES modules and CommonJS.", "type": "module", "main": "dist/module.js", @@ -75,7 +75,6 @@ "typescript": "^5.9.3" }, "dependencies": { - "glob": "^13.0.6", "magic-string": "^0.30.21", "oxc-parser": "^0.116.0", "periscopic": "^4.0.2" diff --git a/src/cli.ts b/src/cli.ts index be9aae0..012ba8b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -5,9 +5,8 @@ import { stderr as defaultStderr, } from 'node:process' import { parseArgs } from 'node:util' -import { readFile, mkdir, writeFile } from 'node:fs/promises' -import { dirname, resolve, relative, join, basename } from 'node:path' -import { glob } from 'glob' +import { readFile, mkdir, writeFile, glob, stat } from 'node:fs/promises' +import { dirname, resolve, relative, join, basename, isAbsolute } from 'node:path' import type { TemplateLiteral } from '@oxc-project/types' @@ -539,14 +538,20 @@ const normalizeSourceMapArgv = (argv: string[]) => { const expandFiles = async (patterns: string[], cwd: string, ignore?: string[]) => { const files = new Set() for (const pattern of patterns) { - const matches = await glob(pattern, { + for await (const match of glob(pattern, { cwd, - absolute: true, - nodir: true, - windowsPathsNoEscape: true, - ignore, - }) - for (const m of matches) files.add(resolve(m)) + exclude: ignore, + })) { + const candidate = isAbsolute(match) ? resolve(match) : resolve(cwd, match) + try { + const stats = await stat(candidate) + if (!stats.isDirectory()) { + files.add(candidate) + } + } catch { + continue + } + } } return [...files] } diff --git a/test/cli.ts b/test/cli.ts index f3e6b94..87a559e 100644 --- a/test/cli.ts +++ b/test/cli.ts @@ -123,6 +123,24 @@ test('--ignore excludes glob matches', async () => { } }) +test('glob expansion excludes directories', async () => { + const temp = await mkdtemp(join(tmpdir(), 'module-cli-glob-nodir-')) + const nested = join(temp, 'nested') + const input = join(temp, 'input.cjs') + + await mkdir(nested, { recursive: true }) + await copyFile(fixture, input) + + try { + const result = runCli(['--list', '--target', 'module', '--cwd', temp, '*']) + + assert.equal(result.status, 0) + assert.ok(result.stdout.includes('input.cjs')) + } finally { + await rm(temp, { recursive: true, force: true }) + } +}) + test('-H error exits on dual package hazard', async () => { const temp = await mkdtemp(join(tmpdir(), 'module-cli-dual-hazard-')) const file = join(temp, 'entry.mjs') @@ -670,6 +688,74 @@ test('globals-only pre-tsc flow matches README example', async () => { } }) +test('globals-only pre-tsc flow with README glob pattern', async () => { + const temp = await mkdtemp(join(tmpdir(), 'module-cli-pre-tsc-glob-')) + const srcDir = join(temp, 'src') + const nestedDir = join(srcDir, 'nested') + const file = join(nestedDir, 'index.ts') + + await mkdir(nestedDir, { recursive: true }) + await writeFile( + join(temp, 'tsconfig.json'), + JSON.stringify( + { + compilerOptions: { + target: 'ES2020', + module: 'commonjs', + outDir: 'dist', + strict: false, + esModuleInterop: true, + types: ['node'], + typeRoots: [join(projectRoot, 'node_modules', '@types')], + }, + include: ['src'], + }, + null, + 2, + ), + 'utf8', + ) + await writeFile( + file, + [ + "import fs from 'node:fs'", + 'export const meta = import.meta.url', + 'export const size = fs.statSync(__filename).size', + '', + ].join('\n'), + 'utf8', + ) + + try { + const before = spawnSync(process.execPath, [tscBin, '-p', temp], { encoding: 'utf8' }) + assert.notEqual(before.status, 0) + + const result = runCli( + [ + '--target', + 'commonjs', + '--transform-syntax', + 'globals-only', + '--ignore', + 'node_modules/**', + '--in-place', + 'src/**/*.{ts,js,mts,cts}', + ], + undefined, + { cwd: temp }, + ) + + assert.equal(result.status, 0) + const transformed = await readFile(file, 'utf8') + assert.ok(!transformed.includes('import.meta')) + + const after = spawnSync(process.execPath, [tscBin, '-p', temp], { encoding: 'utf8' }) + assert.equal(after.status, 0, after.stderr || after.stdout) + } finally { + await rm(temp, { recursive: true, force: true }) + } +}) + test('--dry-run does not create outputs when out-dir provided', async () => { const temp = await mkdtemp(join(tmpdir(), 'module-cli-')) const outDir = join(temp, 'out') From 7dd51c66490cea1a959039eea3b4adf135cec0ff Mon Sep 17 00:00:00 2001 From: KCM Date: Sat, 7 Mar 2026 14:50:21 -0600 Subject: [PATCH 2/2] refactor: better loop performance, error handling, and test validation. --- src/cli.ts | 16 +++++----------- test/cli.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 012ba8b..2db53c8 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -5,8 +5,8 @@ import { stderr as defaultStderr, } from 'node:process' import { parseArgs } from 'node:util' -import { readFile, mkdir, writeFile, glob, stat } from 'node:fs/promises' -import { dirname, resolve, relative, join, basename, isAbsolute } from 'node:path' +import { readFile, mkdir, writeFile, glob } from 'node:fs/promises' +import { dirname, resolve, relative, join, basename } from 'node:path' import type { TemplateLiteral } from '@oxc-project/types' @@ -541,16 +541,10 @@ const expandFiles = async (patterns: string[], cwd: string, ignore?: string[]) = for await (const match of glob(pattern, { cwd, exclude: ignore, + withFileTypes: true, })) { - const candidate = isAbsolute(match) ? resolve(match) : resolve(cwd, match) - try { - const stats = await stat(candidate) - if (!stats.isDirectory()) { - files.add(candidate) - } - } catch { - continue - } + if (match.isDirectory()) continue + files.add(resolve(match.parentPath, match.name)) } } return [...files] diff --git a/test/cli.ts b/test/cli.ts index 87a559e..14051ba 100644 --- a/test/cli.ts +++ b/test/cli.ts @@ -136,6 +136,41 @@ test('glob expansion excludes directories', async () => { assert.equal(result.status, 0) assert.ok(result.stdout.includes('input.cjs')) + assert.ok(!result.stdout.includes('nested')) + } finally { + await rm(temp, { recursive: true, force: true }) + } +}) + +test('windows: backslash glob and ignore patterns are honored', async () => { + if (process.platform !== 'win32') return + + const temp = await mkdtemp(join(tmpdir(), 'module-cli-win-glob-')) + const srcDir = join(temp, 'src') + const keep = join(srcDir, 'keep.cjs') + const ignoredDir = join(temp, 'node_modules', 'pkg') + const ignored = join(ignoredDir, 'index.cjs') + + await mkdir(srcDir, { recursive: true }) + await mkdir(ignoredDir, { recursive: true }) + await copyFile(fixture, keep) + await copyFile(fixture, ignored) + + try { + const result = runCli([ + '--list', + '--target', + 'module', + '--cwd', + temp, + 'src\\**\\*.cjs', + '--ignore', + 'node_modules\\**', + ]) + + assert.equal(result.status, 0) + assert.ok(result.stdout.includes('keep.cjs')) + assert.ok(!result.stdout.includes('node_modules')) } finally { await rm(temp, { recursive: true, force: true }) }