files-from-path
Advanced tools
Comparing version 0.2.6 to 1.0.0
@@ -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. | ||
[![Build](https://github.com/web3-storage/files-from-path/actions/workflows/main.yml/badge.svg)](https://github.com/web3-storage/files-from-path/actions/workflows/main.yml) | ||
[![dependencies Status](https://status.david-dm.org/gh/web3-storage/files-from-path.svg)](https://david-dm.org/web3-storage/files-from-path) | ||
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) | ||
[![Downloads](https://img.shields.io/npm/dm/files-from-path.svg)](https://www.npmjs.com/package/files-from-path) | ||
[![Minzipped size](https://badgen.net/bundlephobia/minzip/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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
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
19
1
62
15045
364
1
1
- Removederr-code@^3.0.1
- Removedipfs-unixfs@^6.0.5
- Removedit-glob@^0.0.13
- Removed@protobufjs/aspromise@1.1.2(transitive)
- Removed@protobufjs/base64@1.1.2(transitive)
- Removed@protobufjs/codegen@2.0.4(transitive)
- Removed@protobufjs/eventemitter@1.1.0(transitive)
- Removed@protobufjs/fetch@1.1.0(transitive)
- Removed@protobufjs/float@1.0.2(transitive)
- Removed@protobufjs/inquire@1.1.0(transitive)
- Removed@protobufjs/path@1.1.2(transitive)
- Removed@protobufjs/pool@1.1.0(transitive)
- Removed@protobufjs/utf8@1.1.0(transitive)
- Removed@types/long@4.0.2(transitive)
- Removed@types/minimatch@3.0.5(transitive)
- Removed@types/node@22.9.0(transitive)
- Removedbalanced-match@1.0.2(transitive)
- Removedbrace-expansion@1.1.11(transitive)
- Removedconcat-map@0.0.1(transitive)
- Removederr-code@3.0.1(transitive)
- Removedipfs-unixfs@6.0.9(transitive)
- Removedit-glob@0.0.13(transitive)
- Removedlong@4.0.0(transitive)
- Removedminimatch@3.1.2(transitive)
- Removedprotobufjs@6.11.4(transitive)
- Removedundici-types@6.19.8(transitive)
Updatedgraceful-fs@^4.2.10