Skip to content

Instantly share code, notes, and snippets.

@boneskull
Created March 1, 2024 20:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save boneskull/dd11a4d73024909299e26c95c85edc70 to your computer and use it in GitHub Desktop.
Save boneskull/dd11a4d73024909299e26c95c85edc70 to your computer and use it in GitHub Desktop.
create memfs volume from filesystem
#!/usr/bin/env node
/* eslint-disable n/shebang */
// @ts-check
/**
* Prints a `memfs` volume JSON from the filesystem given glob patterns and/or
* directories
*
* Directories are handled recursively
*
* Note: The memfs volume will be "squashed" to `cwd`, even if a glob pattern or
* directory is not an ancestor of `cwd`.
*
* @packageDocumentation
* @see {@link https://npm.im/memfs}
*/
import { glob } from 'glob'
import { memfs } from 'memfs'
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { parseArgs } from 'node:util'
const MEMFS_ROOT = '/'
function showHelp() {
console.log(`
fs-to-volume [options..] [directory..]
Creates memfs volume JSON from the filesystem
Options:
--glob, -g - Glob patterns (can be used multiple times)
--help - Show this help message
--root - memfs root directory (default: '/')
`)
}
/**
* Creates a `memfs` `Volume` from the filesystem
*
* All paths will be computed relative to the current working directory.
*
* @param {string[]} dirs - Directories to include (recursively). If unspecified
* or empty, `cwd` is used
* @param {string[]} globPatterns - Glob patterns to include
* @param {string} root - Memfs root
* @returns {Promise<ReturnType<typeof memfs>['vol']>}
*/
async function createVolumeFromFs(
dirs = [],
globPatterns = [],
root = MEMFS_ROOT
) {
if (!dirs.length) {
dirs.push('.')
}
// recursify dirs
const dirPatterns = dirs.map((pattern) => path.join(pattern, '**'))
const patterns = [...new Set([...dirPatterns, ...globPatterns])]
const files = await glob(patterns, {
// we don't actually need filetypes, but we want the PathScurry objects
withFileTypes: true,
// don't need dirs because memfs creates dir structure from relative
// filepaths
nodir: true,
root,
})
/** @type {import('memfs').DirectoryJSON} */
const json = {}
// instead of building a JSON object, we could actually use mkdir/writeFile,
// but that's slow
await Promise.all(
files.map(async (file) => {
// yes, this should work with binaries
const content = await readFile(file.fullpath())
json[file.relativePosix()] = content
})
)
const { vol } = memfs(json, root)
return vol
}
async function main() {
const {
positionals,
values: { help, glob: globPatterns = [], root = MEMFS_ROOT },
} = parseArgs({
allowPositionals: true,
options: {
glob: {
short: 'g',
type: 'string',
multiple: true,
default: [],
},
root: { type: 'string', default: MEMFS_ROOT },
help: { type: 'boolean' },
},
})
if (help) {
showHelp()
return
}
const vol = await createVolumeFromFs(positionals, globPatterns, root)
console.log(JSON.stringify(vol.toJSON(), null, 2))
}
// equiv of `require.main === module`
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main().catch((err) => {
console.error(err)
process.exitCode = 1
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment