readdir-glob reaches minimatch twice: the directory walk, and the pattern/ignore matching behind archiver's glob(). The second is where a semantic change shows up as silently skipped files rather than an error, so the check archives a tree by pattern with an ignore and asserts what it kept: a.txt in, sub/** out.
75 lines
2.6 KiB
JavaScript
75 lines
2.6 KiB
JavaScript
/* Guards the readdir-glob -> minimatch override in client/package.json.
|
|
*
|
|
* The override forces minimatch 11 where readdir-glob asks for ^10, and
|
|
* readdir-glob reaches minimatch on two paths: the directory walk, and the
|
|
* pattern/ignore matching behind archiver's glob(). Both are exercised here,
|
|
* because a matcher that breaks shows up as files silently skipped rather than
|
|
* as an error.
|
|
*
|
|
* Entries are read from archiver's own 'entry' events, so nothing has to be
|
|
* unzipped and the check needs no reader dependency.
|
|
*/
|
|
import fs from 'node:fs'
|
|
import os from 'node:os'
|
|
import path from 'node:path'
|
|
import { ZipArchive } from 'archiver'
|
|
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'dc-arch-'))
|
|
const source = path.join(root, 'source')
|
|
|
|
const cleanup = () => fs.rmSync(root, { recursive: true, force: true })
|
|
const fail = (message) => {
|
|
console.error(message)
|
|
cleanup()
|
|
process.exit(1)
|
|
}
|
|
|
|
fs.mkdirSync(source)
|
|
fs.writeFileSync(path.join(source, 'a.txt'), 'hello')
|
|
fs.mkdirSync(path.join(source, 'sub'))
|
|
fs.writeFileSync(path.join(source, 'sub', 'b.txt'), 'world')
|
|
fs.writeFileSync(path.join(source, 'sub', 'c.md'), 'ignored by pattern')
|
|
|
|
let runs = 0
|
|
|
|
const archiveWith = (configure) =>
|
|
new Promise((resolve, reject) => {
|
|
const out = path.join(root, `test-${++runs}.zip`)
|
|
const sink = fs.createWriteStream(out)
|
|
const archive = new ZipArchive({ zlib: { level: 9 } })
|
|
const names = []
|
|
archive.on('entry', (entry) => names.push(entry.name))
|
|
archive.on('error', reject)
|
|
sink.on('error', reject)
|
|
sink.on('close', () => resolve(names))
|
|
archive.pipe(sink)
|
|
configure(archive)
|
|
archive.finalize()
|
|
})
|
|
|
|
try {
|
|
/* the directory walk: every file, nested directories included */
|
|
const walked = await archiveWith((archive) => archive.directory(source, false))
|
|
const missing = ['a.txt', 'sub/b.txt'].filter((n) => !walked.includes(n))
|
|
if (missing.length) {
|
|
fail('directory walk missed: ' + missing.join(', ') + ' (got: ' + walked.join(', ') + ')')
|
|
}
|
|
|
|
/* pattern and ignore matching, which is minimatch used directly */
|
|
const globbed = await archiveWith((archive) =>
|
|
archive.glob('**/*.txt', { cwd: source, ignore: ['sub/**'] })
|
|
)
|
|
if (!globbed.includes('a.txt')) {
|
|
fail('glob did not match a.txt (got: ' + globbed.join(', ') + ')')
|
|
}
|
|
if (globbed.some((n) => n.startsWith('sub/'))) {
|
|
fail('glob ignore did not exclude sub/ (got: ' + globbed.join(', ') + ')')
|
|
}
|
|
|
|
console.log('archiver walk ok: ' + walked.join(', '))
|
|
console.log('archiver glob ok: ' + globbed.join(', '))
|
|
cleanup()
|
|
} catch (err) {
|
|
fail('archiver error: ' + err.message)
|
|
}
|