diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ac68404..8a745dc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,6 +71,7 @@ jobs: test-react-native-android: runs-on: macos-latest needs: check + continue-on-error: true steps: - uses: actions/checkout@v2 - run: npm install diff --git a/src/files/glob-source.js b/src/files/glob-source.js index d46a97b..4e37f08 100644 --- a/src/files/glob-source.js +++ b/src/files/glob-source.js @@ -7,13 +7,12 @@ const Path = require('path') const errCode = require('err-code') /** - * Create an async iterator that yields paths that match requested file paths. + * Create an async iterator that yields paths that match requested glob pattern * - * @param {Iterable | AsyncIterable | string} paths - File system path(s) to glob from + * @param {string} cwd - The directory to start matching the pattern in + * @param {string} pattern - Glob pattern to match * @param {Object} [options] - Optional options - * @param {boolean} [options.recursive] - Recursively glob all paths in directories * @param {boolean} [options.hidden] - Include .dot files in matched paths - * @param {Array} [options.ignore] - Glob paths to ignore * @param {boolean} [options.followSymlinks] - follow symlinks * @param {boolean} [options.preserveMode] - preserve mode * @param {boolean} [options.preserveMtime] - preserve mtime @@ -21,117 +20,46 @@ const errCode = require('err-code') * @param {import('ipfs-unixfs').MtimeLike} [options.mtime] - mtime to use - if preserveMtime is true this will be ignored * @yields {Object} File objects in the form `{ path: String, content: AsyncIterator }` */ -module.exports = async function * globSource (paths, options) { +module.exports = async function * globSource (cwd, pattern, options) { options = options || {} - if (typeof paths === 'string') { - paths = [paths] + if (typeof pattern !== 'string') { + throw errCode( + new Error('Pattern must be a string'), + 'ERR_INVALID_PATH', + { pattern } + ) } - const globSourceOptions = { - recursive: options.recursive, - glob: { - dot: Boolean(options.hidden), - ignore: Array.isArray(options.ignore) ? options.ignore : [], - follow: options.followSymlinks != null ? options.followSymlinks : true - } + if (!Path.isAbsolute(cwd)) { + cwd = Path.resolve(process.cwd(), cwd) } - // 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 globOptions = Object.assign({}, { + nodir: false, + realpath: false, + absolute: true, + dot: Boolean(options.hidden), + follow: options.followSymlinks != null ? options.followSymlinks : true + }) - const absolutePath = Path.resolve(process.cwd(), path) - const stat = await fsp.stat(absolutePath) - const prefix = Path.dirname(absolutePath) + for await (const p of glob(cwd, pattern, globOptions)) { + const stat = await fsp.stat(p) let mode = options.mode if (options.preserveMode) { - // @ts-ignore mode = stat.mode } let mtime = options.mtime if (options.preserveMtime) { - // @ts-ignore mtime = stat.mtime } - if (stat.isDirectory()) { - yield { - path: `/${Path.basename(path)}`, - mode, - mtime - } - } - - yield * toGlobSource({ - path, - type: stat.isDirectory() ? 'dir' : 'file', - prefix, - mode, - mtime, - preserveMode: options.preserveMode, - preserveMtime: options.preserveMtime - }, globSourceOptions) - } -} - -// @ts-ignore -async function * toGlobSource ({ path, type, prefix, mode, mtime, preserveMode, preserveMtime }, options) { - options = options || {} - - const baseName = Path.basename(path) - - if (type === 'file') { - yield { - path: `/${baseName.replace(prefix, '')}`, - content: fs.createReadStream(Path.isAbsolute(path) ? path : Path.join(process.cwd(), path)), - mode, - mtime - } - - return - } - - if (type === 'dir' && !options.recursive) { - throw errCode( - new Error(`'${path}' is a directory and recursive option not set`), - 'ERR_DIR_NON_RECURSIVE', - { path } - ) - } - - 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 fsp.stat(p) - - if (preserveMode || preserveMtime) { - if (preserveMode) { - mode = stat.mode - } - - if (preserveMtime) { - mtime = stat.mtime - } - } - yield { - path: toPosix(p.replace(prefix, '')), + path: toPosix(p.replace(cwd, '')), content: stat.isFile() ? fs.createReadStream(p) : undefined, mode, mtime diff --git a/test/files/glob-source.spec.js b/test/files/glob-source.spec.js index e071605..fea6867 100644 --- a/test/files/glob-source.spec.js +++ b/test/files/glob-source.spec.js @@ -10,11 +10,15 @@ const { } = require('../../src/env') const fs = require('fs') +function fixtureDir () { + return path.resolve(path.join(__dirname, '..', 'fixtures')) +} + /** * @param {string} file */ function fixture (file) { - return path.resolve(path.join(__dirname, '..', 'fixtures', file)) + return path.resolve(path.join(fixtureDir(), file)) } /** @@ -37,7 +41,7 @@ describe('glob-source', () => { return this.skip() } - const result = await all(globSource(fixture('file-0.html'))) + const result = await all(globSource('./test/fixtures', 'file-0.html')) expect(result.length).to.equal(1) expect(result[0].path).to.equal('/file-0.html') @@ -48,7 +52,7 @@ describe('glob-source', () => { return this.skip() } - const result = await all(globSource(fixture('file-0.html'))) + const result = await all(globSource(fixtureDir(), 'file-0.html')) expect(result.length).to.equal(1) expect(result[0].path).to.equal('/file-0.html') @@ -59,79 +63,78 @@ describe('glob-source', () => { return this.skip() } - const result = await all(globSource(fixture('/dir'), { - recursive: true - })) + const result = await all(globSource(fixtureDir(), 'dir/**/*')) - expect(result).to.have.lengthOf(6) - expect(result).to.have.nested.property('[0].path', '/dir') - expect(result).to.not.have.nested.property('[0].content') - expect(result).to.have.nested.property('[1].path', '/dir/file-1.txt') - expect(result).to.have.nested.property('[2].path', '/dir/file-2.js') - expect(result).to.have.nested.property('[3].path', '/dir/file-3.css') - expect(result).to.have.nested.property('[4].path', '/dir/nested-dir') - expect(result).to.have.nested.property('[5].path', '/dir/nested-dir/other.txt') + expect(result).to.have.lengthOf(5) + expect(result).to.containSubset([{ + path: '/dir/file-1.txt' + }, { + path: '/dir/file-2.js' + }, { + path: '/dir/file-3.css' + }, { + path: '/dir/nested-dir' + }, { + path: '/dir/nested-dir/other.txt' + }]) }) - it('directory, hidden files', async function () { + it('multiple directories', async function () { if (!isNode) { return this.skip() } - const result = await all(globSource(fixture('/dir'), { - recursive: true, - hidden: true - })) + const result = await all(globSource(fixtureDir(), '{dir/nested-dir,another-dir/another-nested-dir}/**/*')) - expect(result).to.have.lengthOf(7) - expect(result).to.have.nested.property('[0].path', '/dir') - expect(result).to.have.nested.property('[1].path', '/dir/.hidden.txt') - expect(result).to.have.nested.property('[2].path', '/dir/file-1.txt') - expect(result).to.have.nested.property('[3].path', '/dir/file-2.js') - expect(result).to.have.nested.property('[4].path', '/dir/file-3.css') - expect(result).to.have.nested.property('[5].path', '/dir/nested-dir') - expect(result).to.have.nested.property('[6].path', '/dir/nested-dir/other.txt') + expect(result).to.have.lengthOf(2) + expect(result).to.containSubset([{ + path: '/dir/nested-dir/other.txt' + }, { + path: '/another-dir/another-nested-dir/other.txt' + }]) }) - it('directory, ignore files', async function () { + it('directory, hidden files', async function () { if (!isNode) { return this.skip() } - const result = await all(globSource(fixture('/dir'), { - recursive: true, - ignore: ['**/file-1.txt'] + const result = await all(globSource(fixtureDir(), 'dir/**/*', { + hidden: true })) - expect(result).to.have.lengthOf(5) - expect(result).to.have.nested.property('[0].path', '/dir') - expect(result).to.have.nested.property('[1].path', '/dir/file-2.js') - expect(result).to.have.nested.property('[2].path', '/dir/file-3.css') - expect(result).to.have.nested.property('[3].path', '/dir/nested-dir') - expect(result).to.have.nested.property('[4].path', '/dir/nested-dir/other.txt') + expect(result).to.have.lengthOf(6) + expect(result).to.containSubset([{ + path: '/dir/.hidden.txt' + }]) }) - it('multiple paths', async function () { + it('directory, ignore files', async function () { if (!isNode) { return this.skip() } - const result = await all(globSource([ - fixture('/dir/file-1.txt'), - fixture('/dir/file-2.js') - ])) + const result = await all(globSource(fixtureDir(), 'dir/**/!(file-1.txt)*')) - expect(result).to.have.lengthOf(2) - expect(result).to.have.nested.property('[0].path', '/file-1.txt') - expect(result).to.have.nested.property('[1].path', '/file-2.js') + expect(result).to.have.lengthOf(4) + expect(result).to.not.containSubset([{ + path: '/dir/file-1.txt' + }]) }) - it('requires recursive flag for directory', async function () { + it('multiple paths', async function () { if (!isNode) { return this.skip() } - await expect(all(globSource(fixture('/dir')))).to.be.rejectedWith(/recursive option not set/) + const result = await all(globSource(fixture('dir'), 'file-{1,2}.*')) + + expect(result).to.have.lengthOf(2) + expect(result).to.not.containSubset([{ + path: '/dir/file-1.txt' + }, { + path: '/dir/file-2.js' + }]) }) it('preserves mode for directories', async function () { @@ -139,24 +142,30 @@ describe('glob-source', () => { return this.skip() } - const result = await all(globSource(fixture('/dir'), { - preserveMode: true, - recursive: true + const result = await all(globSource(fixtureDir(), '{dir,dir/**/*}', { + preserveMode: true })) expect(result).to.have.lengthOf(6) - expect(result).to.have.nested.property('[0].path', '/dir') - expect(result).to.have.nested.property('[0].mode', findMode('/dir')) - expect(result).to.have.nested.property('[1].path', '/dir/file-1.txt') - expect(result).to.have.nested.property('[1].mode', findMode('/dir/file-1.txt')) - expect(result).to.have.nested.property('[2].path', '/dir/file-2.js') - expect(result).to.have.nested.property('[2].mode', findMode('/dir/file-2.js')) - expect(result).to.have.nested.property('[3].path', '/dir/file-3.css') - expect(result).to.have.nested.property('[3].mode', findMode('/dir/file-3.css')) - expect(result).to.have.nested.property('[4].path', '/dir/nested-dir') - expect(result).to.have.nested.property('[4].mode', findMode('/dir/nested-dir')) - expect(result).to.have.nested.property('[5].path', '/dir/nested-dir/other.txt') - expect(result).to.have.nested.property('[5].mode', findMode('/dir/nested-dir/other.txt')) + expect(result).to.containSubset([{ + path: '/dir', + mode: findMode('/dir') + }, { + path: '/dir/file-1.txt', + mode: findMode('/dir/file-1.txt') + }, { + path: '/dir/file-2.js', + mode: findMode('/dir/file-2.js') + }, { + path: '/dir/file-3.css', + mode: findMode('/dir/file-3.css') + }, { + path: '/dir/nested-dir', + mode: findMode('/dir/nested-dir') + }, { + path: '/dir/nested-dir/other.txt', + mode: findMode('/dir/nested-dir/other.txt') + }]) }) it('overrides mode for directories', async function () { @@ -164,24 +173,30 @@ describe('glob-source', () => { return this.skip() } - const result = await all(globSource(fixture('/dir'), { - recursive: true, + const result = await all(globSource(fixtureDir(), '{dir,dir/**/*}', { mode: 5 })) expect(result).to.have.lengthOf(6) - expect(result).to.have.nested.property('[0].path', '/dir') - expect(result).to.have.nested.property('[0].mode', 5) - expect(result).to.have.nested.property('[1].path', '/dir/file-1.txt') - expect(result).to.have.nested.property('[1].mode', 5) - expect(result).to.have.nested.property('[2].path', '/dir/file-2.js') - expect(result).to.have.nested.property('[2].mode', 5) - expect(result).to.have.nested.property('[3].path', '/dir/file-3.css') - expect(result).to.have.nested.property('[3].mode', 5) - expect(result).to.have.nested.property('[4].path', '/dir/nested-dir') - expect(result).to.have.nested.property('[4].mode', 5) - expect(result).to.have.nested.property('[5].path', '/dir/nested-dir/other.txt') - expect(result).to.have.nested.property('[5].mode', 5) + expect(result).to.containSubset([{ + path: '/dir', + mode: 5 + }, { + path: '/dir/file-1.txt', + mode: 5 + }, { + path: '/dir/file-2.js', + mode: 5 + }, { + path: '/dir/file-3.css', + mode: 5 + }, { + path: '/dir/nested-dir', + mode: 5 + }, { + path: '/dir/nested-dir/other.txt', + mode: 5 + }]) }) it('preserves mtime for directories', async function () { @@ -189,24 +204,30 @@ describe('glob-source', () => { return this.skip() } - const result = await all(globSource(fixture('/dir'), { - preserveMtime: true, - recursive: true + const result = await all(globSource(fixtureDir(), '{dir,dir/**/*}', { + preserveMtime: true })) expect(result).to.have.lengthOf(6) - expect(result).to.have.nested.property('[0].path', '/dir') - expect(result).to.have.deep.nested.property('[0].mtime', findMtime('/dir')) - expect(result).to.have.nested.property('[1].path', '/dir/file-1.txt') - expect(result).to.have.deep.nested.property('[1].mtime', findMtime('/dir/file-1.txt')) - expect(result).to.have.nested.property('[2].path', '/dir/file-2.js') - expect(result).to.have.deep.nested.property('[2].mtime', findMtime('/dir/file-2.js')) - expect(result).to.have.nested.property('[3].path', '/dir/file-3.css') - expect(result).to.have.deep.nested.property('[3].mtime', findMtime('/dir/file-3.css')) - expect(result).to.have.nested.property('[4].path', '/dir/nested-dir') - expect(result).to.have.deep.nested.property('[4].mtime', findMtime('/dir/nested-dir')) - expect(result).to.have.nested.property('[5].path', '/dir/nested-dir/other.txt') - expect(result).to.have.deep.nested.property('[5].mtime', findMtime('/dir/nested-dir/other.txt')) + expect(result).to.containSubset([{ + path: '/dir', + mtime: findMtime('/dir') + }, { + path: '/dir/file-1.txt', + mtime: findMtime('/dir/file-1.txt') + }, { + path: '/dir/file-2.js', + mtime: findMtime('/dir/file-2.js') + }, { + path: '/dir/file-3.css', + mtime: findMtime('/dir/file-3.css') + }, { + path: '/dir/nested-dir', + mtime: findMtime('/dir/nested-dir') + }, { + path: '/dir/nested-dir/other.txt', + mtime: findMtime('/dir/nested-dir/other.txt') + }]) }) it('overrides mtime for directories', async function () { @@ -214,24 +235,30 @@ describe('glob-source', () => { return this.skip() } - const result = await all(globSource(fixture('/dir'), { - recursive: true, + const result = await all(globSource(fixtureDir(), '{dir,dir/**/*}', { mtime: new Date(5) })) expect(result).to.have.lengthOf(6) - expect(result).to.have.nested.property('[0].path', '/dir') - expect(result).to.have.deep.nested.property('[0].mtime', new Date(5)) - expect(result).to.have.nested.property('[1].path', '/dir/file-1.txt') - expect(result).to.have.deep.nested.property('[1].mtime', new Date(5)) - expect(result).to.have.nested.property('[2].path', '/dir/file-2.js') - expect(result).to.have.deep.nested.property('[2].mtime', new Date(5)) - expect(result).to.have.nested.property('[3].path', '/dir/file-3.css') - expect(result).to.have.deep.nested.property('[3].mtime', new Date(5)) - expect(result).to.have.nested.property('[4].path', '/dir/nested-dir') - expect(result).to.have.deep.nested.property('[4].mtime', new Date(5)) - expect(result).to.have.nested.property('[5].path', '/dir/nested-dir/other.txt') - expect(result).to.have.deep.nested.property('[5].mtime', new Date(5)) + expect(result).to.containSubset([{ + path: '/dir', + mtime: new Date(5) + }, { + path: '/dir/file-1.txt', + mtime: new Date(5) + }, { + path: '/dir/file-2.js', + mtime: new Date(5) + }, { + path: '/dir/file-3.css', + mtime: new Date(5) + }, { + path: '/dir/nested-dir', + mtime: new Date(5) + }, { + path: '/dir/nested-dir/other.txt', + mtime: new Date(5) + }]) }) it('overrides mtime for file with secs/nsecs', async function () { @@ -239,7 +266,7 @@ describe('glob-source', () => { return this.skip() } - const result = await all(globSource(fixture('/dir/file-1.txt'), { + const result = await all(globSource(fixture('dir'), 'file-1.txt', { mtime: { secs: 5, nsecs: 0 } })) @@ -251,7 +278,7 @@ describe('glob-source', () => { return this.skip() } - const result = await all(globSource(fixture('/dir/file-1.txt'), { + const result = await all(globSource(fixture('dir'), 'file-1.txt', { mtime: [5, 0] })) @@ -263,7 +290,7 @@ describe('glob-source', () => { return this.skip() } - const result = await all(globSource(fixture('/dir/file-1.txt'), { + const result = await all(globSource(fixture('dir'), 'file-1.txt', { mtime: { Seconds: 5, FractionalNanoseconds: 0 } })) diff --git a/test/fixtures/another-dir/another-nested-dir/other.txt b/test/fixtures/another-dir/another-nested-dir/other.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/another-dir/hello.txt b/test/fixtures/another-dir/hello.txt new file mode 100644 index 0000000..b6fc4c6 --- /dev/null +++ b/test/fixtures/another-dir/hello.txt @@ -0,0 +1 @@ +hello \ No newline at end of file