files-from-path
Advanced tools
Comparing version
@@ -5,10 +5,6 @@ 'use strict'; | ||
async function getFilesFromPath() { | ||
async function filesFromPaths() { | ||
throw new Error('Unsupported in this environment'); | ||
} | ||
async function* filesFromPath() { | ||
throw new Error('Unsupported in this environment'); | ||
} | ||
exports.filesFromPath = filesFromPath; | ||
exports.getFilesFromPath = getFilesFromPath; | ||
exports.filesFromPaths = filesFromPaths; |
@@ -5,107 +5,82 @@ 'use strict'; | ||
var Path = require('path'); | ||
var fs = require('graceful-fs'); | ||
var util = require('util'); | ||
var glob = require('it-glob'); | ||
var errCode = require('err-code'); | ||
var path = require('path'); | ||
var stream = require('stream'); | ||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var Path__default = /*#__PURE__*/_interopDefaultLegacy(Path); | ||
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); | ||
var glob__default = /*#__PURE__*/_interopDefaultLegacy(glob); | ||
var errCode__default = /*#__PURE__*/_interopDefaultLegacy(errCode); | ||
var path__default = /*#__PURE__*/_interopDefaultLegacy(path); | ||
const fsStat = util.promisify(fs__default["default"].stat); | ||
async function getFilesFromPath(paths, options) { | ||
const fsReaddir = util.promisify(fs__default["default"].readdir); | ||
async function filesFromPaths(paths, options) { | ||
let commonParts; | ||
const files = []; | ||
for await (const file of filesFromPath(paths, options)) { | ||
files.push(file); | ||
for (const p of paths) { | ||
for await (const file of filesFromPath(p, options)) { | ||
files.push(file); | ||
const nameParts = file.name.split(path__default["default"].sep); | ||
if (commonParts == null) { | ||
commonParts = nameParts.slice(0, -1); | ||
continue; | ||
} | ||
for (let i = 0; i < commonParts.length; i++) { | ||
if (commonParts[i] !== nameParts[i]) { | ||
commonParts = commonParts.slice(0, i); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
return files; | ||
const commonPath = `${ commonParts ?? [].join('/') }/`; | ||
return files.map(f => ({ | ||
...f, | ||
name: f.name.slice(commonPath.length) | ||
})); | ||
} | ||
async function* filesFromPath(paths, options) { | ||
options = options || {}; | ||
if (typeof paths === 'string') { | ||
paths = [paths]; | ||
} | ||
const globSourceOptions = { | ||
recursive: true, | ||
glob: { | ||
dot: Boolean(options.hidden), | ||
ignore: Array.isArray(options.ignore) ? options.ignore : [], | ||
follow: options.followSymlinks != null ? options.followSymlinks : true | ||
} | ||
async function* filesFromPath(filepath, options = {}) { | ||
filepath = path__default["default"].resolve(filepath); | ||
const hidden = options.hidden ?? false; | ||
const filter = filepath => { | ||
if (!hidden && path__default["default"].basename(filepath).startsWith('.')) | ||
return false; | ||
return true; | ||
}; | ||
for await (const path of paths) { | ||
if (typeof path !== 'string') { | ||
throw errCode__default["default"](new Error('Path must be a string'), 'ERR_INVALID_PATH', { path }); | ||
} | ||
const absolutePath = Path__default["default"].resolve(process.cwd(), path); | ||
const stat = await fsStat(absolutePath); | ||
const prefix = options.pathPrefix || Path__default["default"].dirname(absolutePath); | ||
let mode = options.mode; | ||
if (options.preserveMode) { | ||
mode = stat.mode; | ||
} | ||
let mtime = options.mtime; | ||
if (options.preserveMtime) { | ||
mtime = stat.mtime; | ||
} | ||
yield* toGlobSource({ | ||
path, | ||
type: stat.isDirectory() ? 'dir' : 'file', | ||
prefix, | ||
mode, | ||
mtime, | ||
size: stat.size, | ||
preserveMode: options.preserveMode, | ||
preserveMtime: options.preserveMtime | ||
}, globSourceOptions); | ||
const name = filepath; | ||
const stat = await fsStat(name); | ||
if (!filter(name)) { | ||
return; | ||
} | ||
} | ||
async function* toGlobSource({path, type, prefix, mode, mtime, size, preserveMode, preserveMtime}, options) { | ||
options = options || {}; | ||
const baseName = Path__default["default"].basename(path); | ||
if (type === 'file') { | ||
if (stat.isFile()) { | ||
yield { | ||
name: `/${ baseName.replace(prefix, '') }`, | ||
stream: () => fs__default["default"].createReadStream(Path__default["default"].isAbsolute(path) ? path : Path__default["default"].join(process.cwd(), path)), | ||
mode, | ||
mtime, | ||
size | ||
name, | ||
stream: () => stream.Readable.toWeb(fs__default["default"].createReadStream(name)), | ||
size: stat.size | ||
}; | ||
return; | ||
} else if (stat.isDirectory()) { | ||
yield* filesFromDir(name, filter); | ||
} | ||
const globOptions = Object.assign({}, options.glob, { | ||
cwd: path, | ||
nodir: false, | ||
realpath: false, | ||
absolute: true | ||
}); | ||
for await (const p of glob__default["default"](path, '**/*', globOptions)) { | ||
const stat = await fsStat(p); | ||
if (!stat.isFile()) { | ||
} | ||
async function* filesFromDir(dir, filter) { | ||
const entries = await fsReaddir(path__default["default"].join(dir), { withFileTypes: true }); | ||
for (const entry of entries) { | ||
if (!filter(entry.name)) { | ||
continue; | ||
} | ||
if (preserveMode || preserveMtime) { | ||
if (preserveMode) { | ||
mode = stat.mode; | ||
} | ||
if (preserveMtime) { | ||
mtime = stat.mtime; | ||
} | ||
if (entry.isFile()) { | ||
const name = path__default["default"].join(dir, entry.name); | ||
const {size} = await fsStat(name); | ||
yield { | ||
name, | ||
stream: () => stream.Readable.toWeb(fs__default["default"].createReadStream(name)), | ||
size | ||
}; | ||
} else if (entry.isDirectory()) { | ||
yield* filesFromDir(path__default["default"].join(dir, entry.name), filter); | ||
} | ||
yield { | ||
name: toPosix(p.replace(prefix, '')), | ||
stream: () => fs__default["default"].createReadStream(p), | ||
mode, | ||
mtime, | ||
size: stat.size | ||
}; | ||
} | ||
} | ||
const toPosix = path => path.replace(/\\/g, '/'); | ||
exports.filesFromPath = filesFromPath; | ||
exports.getFilesFromPath = getFilesFromPath; | ||
exports.filesFromPaths = filesFromPaths; |
@@ -1,6 +0,3 @@ | ||
export async function getFilesFromPath() { | ||
export async function filesFromPaths() { | ||
throw new Error('Unsupported in this environment'); | ||
} | ||
export async function* filesFromPath() { | ||
throw new Error('Unsupported in this environment'); | ||
} |
@@ -1,95 +0,73 @@ | ||
import Path from 'path'; | ||
import fs from 'graceful-fs'; | ||
import { promisify } from 'util'; | ||
import glob from 'it-glob'; | ||
import errCode from 'err-code'; | ||
import path from 'path'; | ||
import { Readable } from 'stream'; | ||
const fsStat = promisify(fs.stat); | ||
export async function getFilesFromPath(paths, options) { | ||
const fsReaddir = promisify(fs.readdir); | ||
export async function filesFromPaths(paths, options) { | ||
let commonParts; | ||
const files = []; | ||
for await (const file of filesFromPath(paths, options)) { | ||
files.push(file); | ||
for (const p of paths) { | ||
for await (const file of filesFromPath(p, options)) { | ||
files.push(file); | ||
const nameParts = file.name.split(path.sep); | ||
if (commonParts == null) { | ||
commonParts = nameParts.slice(0, -1); | ||
continue; | ||
} | ||
for (let i = 0; i < commonParts.length; i++) { | ||
if (commonParts[i] !== nameParts[i]) { | ||
commonParts = commonParts.slice(0, i); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
return files; | ||
const commonPath = `${ commonParts ?? [].join('/') }/`; | ||
return files.map(f => ({ | ||
...f, | ||
name: f.name.slice(commonPath.length) | ||
})); | ||
} | ||
export async function* filesFromPath(paths, options) { | ||
options = options || {}; | ||
if (typeof paths === 'string') { | ||
paths = [paths]; | ||
} | ||
const globSourceOptions = { | ||
recursive: true, | ||
glob: { | ||
dot: Boolean(options.hidden), | ||
ignore: Array.isArray(options.ignore) ? options.ignore : [], | ||
follow: options.followSymlinks != null ? options.followSymlinks : true | ||
} | ||
async function* filesFromPath(filepath, options = {}) { | ||
filepath = path.resolve(filepath); | ||
const hidden = options.hidden ?? false; | ||
const filter = filepath => { | ||
if (!hidden && path.basename(filepath).startsWith('.')) | ||
return false; | ||
return true; | ||
}; | ||
for await (const path of paths) { | ||
if (typeof path !== 'string') { | ||
throw errCode(new Error('Path must be a string'), 'ERR_INVALID_PATH', { path }); | ||
} | ||
const absolutePath = Path.resolve(process.cwd(), path); | ||
const stat = await fsStat(absolutePath); | ||
const prefix = options.pathPrefix || Path.dirname(absolutePath); | ||
let mode = options.mode; | ||
if (options.preserveMode) { | ||
mode = stat.mode; | ||
} | ||
let mtime = options.mtime; | ||
if (options.preserveMtime) { | ||
mtime = stat.mtime; | ||
} | ||
yield* toGlobSource({ | ||
path, | ||
type: stat.isDirectory() ? 'dir' : 'file', | ||
prefix, | ||
mode, | ||
mtime, | ||
size: stat.size, | ||
preserveMode: options.preserveMode, | ||
preserveMtime: options.preserveMtime | ||
}, globSourceOptions); | ||
const name = filepath; | ||
const stat = await fsStat(name); | ||
if (!filter(name)) { | ||
return; | ||
} | ||
} | ||
async function* toGlobSource({path, type, prefix, mode, mtime, size, preserveMode, preserveMtime}, options) { | ||
options = options || {}; | ||
const baseName = Path.basename(path); | ||
if (type === 'file') { | ||
if (stat.isFile()) { | ||
yield { | ||
name: `/${ baseName.replace(prefix, '') }`, | ||
stream: () => fs.createReadStream(Path.isAbsolute(path) ? path : Path.join(process.cwd(), path)), | ||
mode, | ||
mtime, | ||
size | ||
name, | ||
stream: () => Readable.toWeb(fs.createReadStream(name)), | ||
size: stat.size | ||
}; | ||
return; | ||
} else if (stat.isDirectory()) { | ||
yield* filesFromDir(name, filter); | ||
} | ||
const globOptions = Object.assign({}, options.glob, { | ||
cwd: path, | ||
nodir: false, | ||
realpath: false, | ||
absolute: true | ||
}); | ||
for await (const p of glob(path, '**/*', globOptions)) { | ||
const stat = await fsStat(p); | ||
if (!stat.isFile()) { | ||
} | ||
async function* filesFromDir(dir, filter) { | ||
const entries = await fsReaddir(path.join(dir), { withFileTypes: true }); | ||
for (const entry of entries) { | ||
if (!filter(entry.name)) { | ||
continue; | ||
} | ||
if (preserveMode || preserveMtime) { | ||
if (preserveMode) { | ||
mode = stat.mode; | ||
} | ||
if (preserveMtime) { | ||
mtime = stat.mtime; | ||
} | ||
if (entry.isFile()) { | ||
const name = path.join(dir, entry.name); | ||
const {size} = await fsStat(name); | ||
yield { | ||
name, | ||
stream: () => Readable.toWeb(fs.createReadStream(name)), | ||
size | ||
}; | ||
} else if (entry.isDirectory()) { | ||
yield* filesFromDir(path.join(dir, entry.name), filter); | ||
} | ||
yield { | ||
name: toPosix(p.replace(prefix, '')), | ||
stream: () => fs.createReadStream(p), | ||
mode, | ||
mtime, | ||
size: stat.size | ||
}; | ||
} | ||
} | ||
const toPosix = path => path.replace(/\\/g, '/'); | ||
} |
{ | ||
"name": "files-from-path", | ||
"version": "0.2.6", | ||
"description": "Match provided glob paths to file objects with readable stream", | ||
"version": "1.0.0", | ||
"description": "Expand paths to file-like objects with name, readable stream and size.", | ||
"main": "./cjs/src/index.js", | ||
@@ -34,16 +34,16 @@ "types": "./types/index.d.ts", | ||
"dependencies": { | ||
"err-code": "^3.0.1", | ||
"graceful-fs": "^4.2.9", | ||
"ipfs-unixfs": "^6.0.5", | ||
"it-glob": "^0.0.13" | ||
"graceful-fs": "^4.2.10" | ||
}, | ||
"devDependencies": { | ||
"@types/graceful-fs": "^4.1.5", | ||
"@types/node": "^16.3.2", | ||
"ava": "^4.1.0", | ||
"@types/graceful-fs": "^4.1.6", | ||
"@types/node": "^18.15.1", | ||
"ava": "^5.2.0", | ||
"ipjs": "^5.0.3", | ||
"standard": "^16.0.3", | ||
"typescript": "^4.3.5", | ||
"standard": "^17.0.0", | ||
"typescript": "^4.9.5", | ||
"unlimited": "^1.2.2" | ||
}, | ||
"engines": { | ||
"node": ">=18" | ||
}, | ||
"browser": { | ||
@@ -50,0 +50,0 @@ ".": "./cjs/src/index.browser.js", |
# files-from-path | ||
> Match provided glob paths to file objects with readable stream | ||
> Expand paths to file-like objects with name, readable stream and size. | ||
[](https://github.com/web3-storage/files-from-path/actions/workflows/main.yml) | ||
[](https://david-dm.org/web3-storage/files-from-path) | ||
[](https://standardjs.com) | ||
[](https://www.npmjs.com/package/files-from-path) | ||
[](https://bundlephobia.com/result?p=files-from-path) | ||
@@ -14,4 +12,3 @@ ## Install | ||
```sh | ||
# install it as a dependency | ||
$ npm i files-from-path | ||
npm install files-from-path | ||
``` | ||
@@ -22,8 +19,19 @@ | ||
```js | ||
import { filesFromPath } from 'files-from-path' | ||
import { filesFromPaths } from 'files-from-path' | ||
for await (const f of filesFromPath(`path/to/somewhere`)) { | ||
console.log(f) | ||
// { name: '/path/to/me', stream: [Function: stream] } | ||
} | ||
// Given a file system like: | ||
// path/to/file.txt | ||
// path/to/dir/a.pdf | ||
// path/to/dir/images/cat.gif | ||
const files = await filesFromPaths(['path/to/file.txt', 'path/to/dir']) | ||
console.log(files) | ||
// Output: | ||
// [ | ||
// { name: 'file.txt', stream: [Function: stream] }, | ||
// { name: 'dir/b.pdf', stream: [Function: stream] }, | ||
// { name: 'dir/images/cat.gif', stream: [Function: stream] }, | ||
// ] | ||
// Note: common sub-path ("path/to/") is removed. | ||
``` | ||
@@ -33,26 +41,24 @@ | ||
### filesFromPath | ||
### filesFromPaths | ||
The following parameters can be provided to `filesFromPath`. | ||
The following parameters can be provided to `filesFromPaths`: | ||
| Name | Type | Description | | ||
|------|------|-------------| | ||
| paths | `Iterable<string> | AsyncIterable<string> | string` | File system path(s) to glob from | | ||
| paths | `Iterable<string>` | File system path(s) to read from | | ||
| [options] | `object` | options | | ||
| [options.hidden] | `boolean` | Include .dot files in matched paths | | ||
| [options.ignore] | `string[]` | Glob paths to ignore | | ||
| [options.followSymlinks] | `boolean` | follow symlinks | | ||
| [options.preserveMode] | `boolean` | preserve mode | | ||
| [options.mode] | `number` | mode to use - if preserveMode is true this will be ignored | | ||
| [options.preserveMtime] | `boolean` | preserve mtime | | ||
| [options.pathPrefix] | `string` | base path prefix that will get stripped out of the filenames yielded | | ||
| [options.hidden] | `boolean` | Include .dot files in matched paths (default: `false`) | | ||
It `yields` file like objects in the form of `{ name: String, stream: AsyncIterator<Buffer> }` | ||
It returns an _array_ of file-like objects in the form: | ||
### getFilesFromPath | ||
```ts | ||
{ | ||
name: String | ||
stream (): ReadableStream<Uint8Array> | ||
size: number | ||
} | ||
``` | ||
This takes the same parameters as `filesFromPath`, but returns a `Promise<{ name: String, stream: AsyncIterator<Buffer> }[]>` by creating an array with all the yield file like objects from the path. | ||
## Releasing | ||
You can publish by either running npm publish in the dist directory or using npx ipjs publish. |
@@ -1,7 +0,3 @@ | ||
export async function getFilesFromPath () { | ||
export async function filesFromPaths () { | ||
throw new Error('Unsupported in this environment') | ||
} | ||
export async function * filesFromPath () { | ||
throw new Error('Unsupported in this environment') | ||
} |
204
src/index.js
@@ -1,166 +0,94 @@ | ||
import Path from 'path' | ||
import fs from 'graceful-fs' | ||
import { promisify } from 'util' | ||
import glob from 'it-glob' | ||
import errCode from 'err-code' | ||
import path from 'path' | ||
import { Readable } from 'stream' | ||
/** @typedef {Pick<File, 'stream'|'name'|'size'>} FileLike */ | ||
// https://github.com/isaacs/node-graceful-fs/issues/160 | ||
const fsStat = promisify(fs.stat) | ||
const fsReaddir = promisify(fs.readdir) | ||
// ported to esm from https://github.com/ipfs/js-ipfs-utils/blob/d7e7bde061ef928d72f34c17d4d6310a7215bd73/src/files/glob-source.js | ||
// converts path -> name and content -> stream to fit the web3.storage expectation | ||
/** | ||
* @typedef FromPathOptions | ||
* @property {boolean} [hidden] - Include .dot files in matched paths | ||
* @property {Array<string>} [ignore] - Glob paths to ignore | ||
* @property {boolean} [followSymlinks] - follow symlinks | ||
* @property {boolean} [preserveMode] - preserve mode | ||
* @property {boolean} [preserveMtime] - preserve mtime | ||
* @property {number} [mode] - mode to use - if preserveMode is true this will be ignored | ||
* @property {import('ipfs-unixfs').MtimeLike} [mtime] - mtime to use - if preserveMtime is true this will be ignored | ||
* @property {string} [pathPrefix] - base path prefix that will get stripped out of the filenames yielded | ||
* | ||
* @typedef FileObject | ||
* @property {string} name | ||
* @property {() => fs.ReadStream} stream | ||
* @param {Iterable<string>} paths | ||
* @param {object} [options] | ||
* @param {boolean} [options.hidden] | ||
* @returns {Promise<FileLike[]>} | ||
*/ | ||
/** | ||
* Gets all the FileObjects that match requested file paths. | ||
* | ||
* @param {Iterable<string> | AsyncIterable<string> | string} paths - File system path(s) to glob from | ||
* @param {FromPathOptions} [options] - options | ||
* @returns {Promise<FileObject[]>} | ||
*/ | ||
export async function getFilesFromPath (paths, options) { | ||
export async function filesFromPaths (paths, options) { | ||
/** @type {string[]|undefined} */ | ||
let commonParts | ||
const files = [] | ||
for await (const file of filesFromPath(paths, options)) { | ||
files.push(file) | ||
for (const p of paths) { | ||
for await (const file of filesFromPath(p, options)) { | ||
files.push(file) | ||
const nameParts = file.name.split(path.sep) | ||
if (commonParts == null) { | ||
commonParts = nameParts.slice(0, -1) | ||
continue | ||
} | ||
for (let i = 0; i < commonParts.length; i++) { | ||
if (commonParts[i] !== nameParts[i]) { | ||
commonParts = commonParts.slice(0, i) | ||
break | ||
} | ||
} | ||
} | ||
} | ||
return files | ||
const commonPath = `${(commonParts ?? []).join('/')}/` | ||
return files.map(f => ({ ...f, name: f.name.slice(commonPath.length) })) | ||
} | ||
/** | ||
* Create an async iterator that yields paths that match requested file paths. | ||
* | ||
* @param {Iterable<string> | AsyncIterable<string> | string} paths - File system path(s) to glob from | ||
* @param {FromPathOptions} [options] - options | ||
* @yields {FileObject} | ||
* @param {string} filepath | ||
* @param {object} [options] | ||
* @param {boolean} [options.hidden] | ||
* @returns {AsyncIterableIterator<FileLike>} | ||
*/ | ||
export async function * filesFromPath (paths, options) { | ||
options = options || {} | ||
async function * filesFromPath (filepath, options = {}) { | ||
filepath = path.resolve(filepath) | ||
const hidden = options.hidden ?? false | ||
if (typeof paths === 'string') { | ||
paths = [paths] | ||
/** @param {string} filepath */ | ||
const filter = filepath => { | ||
if (!hidden && path.basename(filepath).startsWith('.')) return false | ||
return true | ||
} | ||
const globSourceOptions = { | ||
recursive: true, | ||
glob: { | ||
dot: Boolean(options.hidden), | ||
ignore: Array.isArray(options.ignore) ? options.ignore : [], | ||
follow: options.followSymlinks != null ? options.followSymlinks : true | ||
} | ||
const name = filepath | ||
const stat = await fsStat(name) | ||
if (!filter(name)) { | ||
return | ||
} | ||
// Check the input paths comply with options.recursive and convert to glob sources | ||
for await (const path of paths) { | ||
if (typeof path !== 'string') { | ||
throw errCode( | ||
new Error('Path must be a string'), | ||
'ERR_INVALID_PATH', | ||
{ path } | ||
) | ||
} | ||
const absolutePath = Path.resolve(process.cwd(), path) | ||
const stat = await fsStat(absolutePath) | ||
const prefix = options.pathPrefix || Path.dirname(absolutePath) | ||
let mode = options.mode | ||
if (options.preserveMode) { | ||
// @ts-ignore | ||
mode = stat.mode | ||
} | ||
let mtime = options.mtime | ||
if (options.preserveMtime) { | ||
// @ts-ignore | ||
mtime = stat.mtime | ||
} | ||
yield * toGlobSource({ | ||
path, | ||
type: stat.isDirectory() ? 'dir' : 'file', | ||
prefix, | ||
mode, | ||
mtime, | ||
size: stat.size, | ||
preserveMode: options.preserveMode, | ||
preserveMtime: options.preserveMtime | ||
}, globSourceOptions) | ||
if (stat.isFile()) { | ||
// @ts-expect-error node web stream not type compatible with web stream | ||
yield { name, stream: () => Readable.toWeb(fs.createReadStream(name)), size: stat.size } | ||
} else if (stat.isDirectory()) { | ||
yield * filesFromDir(name, filter) | ||
} | ||
} | ||
// @ts-ignore | ||
async function * toGlobSource ({ path, type, prefix, mode, mtime, size, preserveMode, preserveMtime }, options) { | ||
options = options || {} | ||
const baseName = Path.basename(path) | ||
if (type === 'file') { | ||
yield { | ||
name: `/${baseName.replace(prefix, '')}`, | ||
stream: () => fs.createReadStream(Path.isAbsolute(path) ? path : Path.join(process.cwd(), path)), | ||
mode, | ||
mtime, | ||
size | ||
} | ||
return | ||
} | ||
const globOptions = Object.assign({}, options.glob, { | ||
cwd: path, | ||
nodir: false, | ||
realpath: false, | ||
absolute: true | ||
}) | ||
for await (const p of glob(path, '**/*', globOptions)) { | ||
const stat = await fsStat(p) | ||
if (!stat.isFile()) { | ||
// skip dirs. | ||
/** | ||
* @param {string} dir | ||
* @param {(name: string) => boolean} filter | ||
* @returns {AsyncIterableIterator<FileLike>} | ||
*/ | ||
async function * filesFromDir (dir, filter) { | ||
const entries = await fsReaddir(path.join(dir), { withFileTypes: true }) | ||
for (const entry of entries) { | ||
if (!filter(entry.name)) { | ||
continue | ||
} | ||
if (preserveMode || preserveMtime) { | ||
if (preserveMode) { | ||
mode = stat.mode | ||
} | ||
if (preserveMtime) { | ||
mtime = stat.mtime | ||
} | ||
if (entry.isFile()) { | ||
const name = path.join(dir, entry.name) | ||
const { size } = await fsStat(name) | ||
// @ts-expect-error node web stream not type compatible with web stream | ||
yield { name, stream: () => Readable.toWeb(fs.createReadStream(name)), size } | ||
} else if (entry.isDirectory()) { | ||
yield * filesFromDir(path.join(dir, entry.name), filter) | ||
} | ||
yield { | ||
name: toPosix(p.replace(prefix, '')), | ||
stream: () => fs.createReadStream(p), | ||
mode, | ||
mtime, | ||
size: stat.size | ||
} | ||
} | ||
} | ||
/** | ||
* @param {string} path | ||
*/ | ||
const toPosix = path => path.replace(/\\/g, '/') |
@@ -0,1 +1,2 @@ | ||
/* global WritableStream */ | ||
import test from 'ava' | ||
@@ -5,42 +6,37 @@ import Path from 'path' | ||
import os from 'os' | ||
import fs from 'graceful-fs' | ||
import fs from 'fs' | ||
import unlimited from 'unlimited' | ||
import { promisify } from 'util' | ||
import { filesFromPath, getFilesFromPath } from '../src/index.js' | ||
import { filesFromPaths } from '../src/index.js' | ||
// https://github.com/isaacs/node-graceful-fs/issues/160 | ||
const fsMkdir = promisify(fs.mkdir) | ||
const fsWriteFile = promisify(fs.writeFile) | ||
const fsRm = promisify(fs.rm) | ||
test('gets files from node_modules', async (t) => { | ||
const files = await filesFromPaths(['node_modules']) | ||
t.log(`${files.length} files in node_modules`) | ||
t.true(files.length > 1) | ||
}) | ||
test('yields files from fixtures folder', async t => { | ||
const files = [] | ||
for await (const f of filesFromPath(`${process.cwd()}/test/fixtures`)) { | ||
files.push(f) | ||
test('includes file size', async (t) => { | ||
const files = await filesFromPaths(['test/fixtures/empty.car']) | ||
t.is(files.length, 1) | ||
t.is(files[0].size, 18) | ||
}) | ||
test('removes common path prefix', async (t) => { | ||
const files = await filesFromPaths(['test/fixtures/dir/file2.txt', './test/fixtures/empty.car']) | ||
t.true(files.length > 1) | ||
for (const file of files) { | ||
t.false(file.name.startsWith('test/fixtures/')) | ||
} | ||
}) | ||
t.true(files.length === 2) | ||
test('single file has name', async (t) => { | ||
const files = await filesFromPaths(['test/fixtures/empty.car']) | ||
t.is(files.length, 1) | ||
t.is(files[0].name, 'empty.car') | ||
}) | ||
test('gets files from fixtures folder', async t => { | ||
const files = await getFilesFromPath(`${process.cwd()}/test/fixtures`) | ||
t.true(files.length === 2) | ||
const files = await filesFromPaths([`${process.cwd()}/test/fixtures`]) | ||
t.true(files.length === 3) | ||
}) | ||
test('removes custom prefix', async t => { | ||
const files = await getFilesFromPath(`${process.cwd()}/test/fixtures`) | ||
const pathPrefix = Path.join(process.cwd(), 'test', 'fixtures') | ||
const filesWithoutPrefix = await getFilesFromPath(`${process.cwd()}/test/fixtures`, { pathPrefix }) | ||
files.forEach(f => { | ||
t.true(f.name.includes('fixtures')) | ||
}) | ||
filesWithoutPrefix.forEach(f => { | ||
t.false(f.name.includes('fixtures')) | ||
}) | ||
}) | ||
test('allows read of more files than ulimit maxfiles', async t => { | ||
@@ -51,3 +47,3 @@ const totalFiles = 256 | ||
try { | ||
const files = await getFilesFromPath(dir) | ||
const files = await filesFromPaths([dir]) | ||
t.is(files.length, totalFiles) | ||
@@ -60,19 +56,24 @@ | ||
await t.notThrowsAsync(() => Promise.all(files.map(async f => { | ||
for await (const _ of f.stream()) { // eslint-disable-line no-unused-vars | ||
// make slow so we open all the files | ||
await new Promise(resolve => setTimeout(resolve, 5000)) | ||
} | ||
await f.stream().pipeTo(new WritableStream({ | ||
async write () { | ||
// make slow so we open all the files | ||
await new Promise(resolve => setTimeout(resolve, 5000)) | ||
} | ||
})) | ||
}))) | ||
} finally { | ||
await fsRm(dir, { recursive: true, force: true }) | ||
await fs.promises.rm(dir, { recursive: true, force: true }) | ||
} | ||
}) | ||
/** | ||
* @param {number} n | ||
*/ | ||
async function generateTestData (n) { | ||
const dirName = Path.join(os.tmpdir(), `files-from-path-test-${Date.now()}`) | ||
await fsMkdir(dirName) | ||
await fs.promises.mkdir(dirName) | ||
for (let i = 0; i < n; i++) { | ||
await fsWriteFile(Path.join(dirName, `${i}.json`), '{}') | ||
await fs.promises.writeFile(Path.join(dirName, `${i}.json`), '{}') | ||
} | ||
return dirName | ||
} |
@@ -19,3 +19,3 @@ { | ||
"esModuleInterop": true, | ||
"target": "ES2018", | ||
"target": "ESNext", | ||
"module": "ESNext", | ||
@@ -22,0 +22,0 @@ "moduleResolution": "node", |
@@ -1,3 +0,2 @@ | ||
export function getFilesFromPath(): Promise<void>; | ||
export function filesFromPath(): AsyncGenerator<never, void, unknown>; | ||
export function filesFromPaths(): Promise<void>; | ||
//# sourceMappingURL=index.browser.d.ts.map |
/** | ||
* @typedef FromPathOptions | ||
* @property {boolean} [hidden] - Include .dot files in matched paths | ||
* @property {Array<string>} [ignore] - Glob paths to ignore | ||
* @property {boolean} [followSymlinks] - follow symlinks | ||
* @property {boolean} [preserveMode] - preserve mode | ||
* @property {boolean} [preserveMtime] - preserve mtime | ||
* @property {number} [mode] - mode to use - if preserveMode is true this will be ignored | ||
* @property {import('ipfs-unixfs').MtimeLike} [mtime] - mtime to use - if preserveMtime is true this will be ignored | ||
* @property {string} [pathPrefix] - base path prefix that will get stripped out of the filenames yielded | ||
* | ||
* @typedef FileObject | ||
* @property {string} name | ||
* @property {() => fs.ReadStream} stream | ||
* @param {Iterable<string>} paths | ||
* @param {object} [options] | ||
* @param {boolean} [options.hidden] | ||
* @returns {Promise<FileLike[]>} | ||
*/ | ||
/** | ||
* Gets all the FileObjects that match requested file paths. | ||
* | ||
* @param {Iterable<string> | AsyncIterable<string> | string} paths - File system path(s) to glob from | ||
* @param {FromPathOptions} [options] - options | ||
* @returns {Promise<FileObject[]>} | ||
*/ | ||
export function getFilesFromPath(paths: Iterable<string> | AsyncIterable<string> | string, options?: FromPathOptions | undefined): Promise<FileObject[]>; | ||
/** | ||
* Create an async iterator that yields paths that match requested file paths. | ||
* | ||
* @param {Iterable<string> | AsyncIterable<string> | string} paths - File system path(s) to glob from | ||
* @param {FromPathOptions} [options] - options | ||
* @yields {FileObject} | ||
*/ | ||
export function filesFromPath(paths: Iterable<string> | AsyncIterable<string> | string, options?: FromPathOptions | undefined): AsyncGenerator<{ | ||
name: string; | ||
stream: () => fs.ReadStream; | ||
mode: any; | ||
mtime: any; | ||
size: any; | ||
}, void, unknown>; | ||
export type FromPathOptions = { | ||
/** | ||
* - Include .dot files in matched paths | ||
*/ | ||
export function filesFromPaths(paths: Iterable<string>, options?: { | ||
hidden?: boolean | undefined; | ||
/** | ||
* - Glob paths to ignore | ||
*/ | ||
ignore?: string[] | undefined; | ||
/** | ||
* - follow symlinks | ||
*/ | ||
followSymlinks?: boolean | undefined; | ||
/** | ||
* - preserve mode | ||
*/ | ||
preserveMode?: boolean | undefined; | ||
/** | ||
* - preserve mtime | ||
*/ | ||
preserveMtime?: boolean | undefined; | ||
/** | ||
* - mode to use - if preserveMode is true this will be ignored | ||
*/ | ||
mode?: number | undefined; | ||
/** | ||
* - mtime to use - if preserveMtime is true this will be ignored | ||
*/ | ||
mtime?: import("ipfs-unixfs/types/src/types").MtimeLike | undefined; | ||
/** | ||
* - base path prefix that will get stripped out of the filenames yielded | ||
*/ | ||
pathPrefix?: string | undefined; | ||
}; | ||
export type FileObject = { | ||
name: string; | ||
stream: () => fs.ReadStream; | ||
}; | ||
import fs from "graceful-fs"; | ||
} | undefined): Promise<FileLike[]>; | ||
export type FileLike = Pick<File, 'stream' | 'name' | 'size'>; | ||
//# sourceMappingURL=index.d.ts.map |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1
-75%19
5.56%1
-50%62
10.71%15045
-30.26%364
-32.34%2
Infinity%1
Infinity%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated