pax_global_header00006660000000000000000000000064143131420030014501gustar00rootroot0000000000000052 comment=13e59eeea3c163ed5edf0b8069387c7ee8cf82a6 fs-mkdirp-stream-2.0.1/000077500000000000000000000000001431314200300146665ustar00rootroot00000000000000fs-mkdirp-stream-2.0.1/.editorconfig000066400000000000000000000003261431314200300173440ustar00rootroot00000000000000# http://editorconfig.org root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf [*.md] trim_trailing_whitespace = false fs-mkdirp-stream-2.0.1/.eslintignore000066400000000000000000000000121431314200300173620ustar00rootroot00000000000000coverage/ fs-mkdirp-stream-2.0.1/.eslintrc000066400000000000000000000000301431314200300165030ustar00rootroot00000000000000{ "extends": "gulp" } fs-mkdirp-stream-2.0.1/.gitattributes000066400000000000000000000000161431314200300175560ustar00rootroot00000000000000* text eol=lf fs-mkdirp-stream-2.0.1/.github/000077500000000000000000000000001431314200300162265ustar00rootroot00000000000000fs-mkdirp-stream-2.0.1/.github/workflows/000077500000000000000000000000001431314200300202635ustar00rootroot00000000000000fs-mkdirp-stream-2.0.1/.github/workflows/dev.yml000066400000000000000000000031051431314200300215630ustar00rootroot00000000000000name: dev on: pull_request: push: branches: - master - main env: CI: true jobs: prettier: name: Format code runs-on: ubuntu-latest if: ${{ github.event_name == 'push' }} steps: - name: Checkout uses: actions/checkout@v2 - name: Prettier uses: gulpjs/prettier_action@v3.0 with: commit_message: 'chore: Run prettier' prettier_options: '--write .' test: name: Tests for Node ${{ matrix.node }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: node: [10, 12, 14, 16] os: [ubuntu-latest, windows-latest, macos-latest] steps: - name: Clone repository uses: actions/checkout@v2 - name: Set Node.js version uses: actions/setup-node@v2 with: node-version: ${{ matrix.node }} - run: node --version - run: npm --version - name: Install npm dependencies run: npm install - name: Run lint run: npm run lint - name: Run tests run: npm test - name: Coveralls uses: coverallsapp/github-action@v1.1.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} flag-name: ${{matrix.os}}-node-${{ matrix.node }} parallel: true coveralls: needs: test name: Finish up runs-on: ubuntu-latest steps: - name: Coveralls Finished uses: coverallsapp/github-action@v1.1.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true fs-mkdirp-stream-2.0.1/.github/workflows/release.yml000066400000000000000000000005511431314200300224270ustar00rootroot00000000000000name: release on: push: branches: - master - main jobs: release-please: runs-on: ubuntu-latest steps: - uses: GoogleCloudPlatform/release-please-action@v2 with: token: ${{ secrets.GITHUB_TOKEN }} release-type: node package-name: release-please-action bump-minor-pre-major: true fs-mkdirp-stream-2.0.1/.gitignore000066400000000000000000000017111431314200300166560ustar00rootroot00000000000000# Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # next.js build output .next # Garbage files .DS_Store # Test results test.xunit fs-mkdirp-stream-2.0.1/.npmrc000066400000000000000000000000231431314200300160010ustar00rootroot00000000000000package-lock=false fs-mkdirp-stream-2.0.1/.prettierignore000066400000000000000000000000441431314200300177270ustar00rootroot00000000000000coverage/ .nyc_output/ CHANGELOG.md fs-mkdirp-stream-2.0.1/CHANGELOG.md000066400000000000000000000037331431314200300165050ustar00rootroot00000000000000# Changelog ### [2.0.1](https://www.github.com/gulpjs/fs-mkdirp-stream/compare/v2.0.0...v2.0.1) (2022-09-17) ### Bug Fixes * Continue upon ENOSUP chmod failures ([#19](https://www.github.com/gulpjs/fs-mkdirp-stream/issues/19)) ([b63196c](https://www.github.com/gulpjs/fs-mkdirp-stream/commit/b63196cea9a2c201f61ce6c449aac5199ab52676)) ## [2.0.0](https://www.github.com/gulpjs/fs-mkdirp-stream/compare/v1.0.0...v2.0.0) (2022-08-30) ### ⚠ BREAKING CHANGES * Rework errors surfaced when encountering files or symlinks (#4) * Ensure correct node version >=10.13.0 (fixes #10) (#12) * Switch to streamx & remove `obj` API (closes #7) (#11) * Stop using `process.umask()` & fallback to node default mode (#6) * Upgrade scaffold, dropping node <10 support ### Features * Ensure correct node version >=10.13.0 (fixes [#10](https://www.github.com/gulpjs/fs-mkdirp-stream/issues/10)) ([#12](https://www.github.com/gulpjs/fs-mkdirp-stream/issues/12)) ([e5690b4](https://www.github.com/gulpjs/fs-mkdirp-stream/commit/e5690b488bfd093f09a59889dbced36ff85c8878)) * Stop using `process.umask()` & fallback to node default mode ([#6](https://www.github.com/gulpjs/fs-mkdirp-stream/issues/6)) ([f78d60b](https://www.github.com/gulpjs/fs-mkdirp-stream/commit/f78d60b12da14db2639d0964f81f254f16b20ba5)) * Switch to streamx & remove `obj` API (closes [#7](https://www.github.com/gulpjs/fs-mkdirp-stream/issues/7)) ([#11](https://www.github.com/gulpjs/fs-mkdirp-stream/issues/11)) ([072d026](https://www.github.com/gulpjs/fs-mkdirp-stream/commit/072d0262d167bd7bbacd875b032835c60661f6f8)) ### Bug Fixes * Rework errors surfaced when encountering files or symlinks ([#4](https://www.github.com/gulpjs/fs-mkdirp-stream/issues/4)) ([3fc3dee](https://www.github.com/gulpjs/fs-mkdirp-stream/commit/3fc3dee4ef6108271f8837e9616652e9e8c6274c)) ### Miscellaneous Chores * Upgrade scaffold, dropping node <10 support ([bda1dee](https://www.github.com/gulpjs/fs-mkdirp-stream/commit/bda1dee735c61617a5f51ac4e3871969a675d1f5)) fs-mkdirp-stream-2.0.1/LICENSE000066400000000000000000000022031431314200300156700ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017, 2020-2021 Blaine Bublitz and Eric Schoffstall Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fs-mkdirp-stream-2.0.1/README.md000066400000000000000000000046521431314200300161540ustar00rootroot00000000000000

# fs-mkdirp-stream [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][ci-image]][ci-url] [![Coveralls Status][coveralls-image]][coveralls-url] Ensure directories exist before writing to them. ## Usage ```js var { Readable, Writable } = require('streamx'); var mkdirpStream = require('fs-mkdirp-stream'); Readable.from([{ dirname: './path/to/my/', path: './path/to/my/file.js' }]) .pipe( mkdirpStream(function (obj, callback) { // callback can take 3 arguments (err, dirname, mode) callback(null, obj.dirname); }) ) .pipe( new Writable({ write: function (obj, cb) { // This will be called once the directory exists // obj === { dirname: '/path/to/my/', path: '/path/to/my/file.js' } cb(); }, }) ); ``` ## API ### `mkdirpStream(resolver)` Takes a `resolver` function or string and returns a `streamx.Transform` stream. If the `resolver` is a function, it will be called once per chunk with the signature `(chunk, callback)`. The `callback(error, dirpath, mode)` must be called with the `dirpath` to be created as the 2nd parameter or an `error` as the 1st parameter; optionally with a `mode` as the 3rd parameter. If the `resolver` is a string, it will be created/ensured for each chunk (e.g. if it were deleted between chunks, it would be recreated). When using a string, a custom `mode` can't be used. ## License MIT Contains a custom implementation of `mkdirp` originally based on https://github.com/substack/node-mkdirp (Licensed MIT/X11 - Copyright 2010 James Halliday) with heavy modification to better support custom modes. [downloads-image]: https://img.shields.io/npm/dm/fs-mkdirp-stream.svg?style=flat-square [npm-url]: https://www.npmjs.com/package/fs-mkdirp-stream [npm-image]: https://img.shields.io/npm/v/fs-mkdirp-stream.svg?style=flat-square [ci-url]: https://github.com/gulpjs/fs-mkdirp-stream/actions?query=workflow:dev [ci-image]: https://img.shields.io/github/workflow/status/gulpjs/fs-mkdirp-stream/dev?style=flat-square [coveralls-url]: https://coveralls.io/r/gulpjs/fs-mkdirp-stream [coveralls-image]: https://img.shields.io/coveralls/gulpjs/fs-mkdirp-stream/master.svg?style=flat-square fs-mkdirp-stream-2.0.1/index.js000066400000000000000000000015511431314200300163350ustar00rootroot00000000000000'use strict'; var Transform = require('streamx').Transform; var mkdirp = require('./mkdirp'); function toFunction(dirpath) { function stringResolver(chunk, callback) { callback(null, dirpath); } return stringResolver; } function mkdirpStream(resolver) { // Handle resolver that's just a dirpath if (typeof resolver === 'string') { resolver = toFunction(resolver); } return new Transform({ transform: function (chunk, callback) { resolver(chunk, onDirpath); function onDirpath(dirpathErr, dirpath, mode) { if (dirpathErr) { return callback(dirpathErr); } mkdirp(dirpath, mode, onMkdirp); } function onMkdirp(mkdirpErr) { if (mkdirpErr) { return callback(mkdirpErr); } callback(null, chunk); } }, }); } module.exports = mkdirpStream; fs-mkdirp-stream-2.0.1/mkdirp.js000066400000000000000000000060641431314200300165200ustar00rootroot00000000000000'use strict'; var path = require('path'); var fs = require('graceful-fs'); var MASK_MODE = parseInt('7777', 8); // Utility for passing dirpath that was used with `fs.stat` function stat(dirpath, cb) { fs.stat(dirpath, onStat); function onStat(err, stats) { cb(err, dirpath, stats); } } // Utility for passing dirpath that was used with `fs.lstat` function lstat(dirpath, cb) { fs.lstat(dirpath, onStat); function onStat(err, stats) { cb(err, dirpath, stats); } } function mkdirp(dirpath, mode, callback) { if (typeof mode === 'function') { callback = mode; mode = undefined; } if (typeof mode === 'string') { mode = parseInt(mode, 8); } dirpath = path.resolve(dirpath); fs.mkdir(dirpath, mode, onMkdir); function onMkdir(mkdirErr) { if (!mkdirErr) { return stat(dirpath, onStat); } switch (mkdirErr.code) { case 'ENOENT': { return mkdirp(path.dirname(dirpath), onRecurse); } case 'EEXIST': { return stat(dirpath, onStat); } case 'ENOTDIR': { // On ENOTDIR, this will traverse up the tree until it finds something it can stat return stat(dirpath, onErrorRecurse); } default: { return callback(mkdirErr); } } function onErrorRecurse(err, dirpath, stats) { if (err) { return stat(path.dirname(dirpath), onErrorRecurse); } onStat(err, dirpath, stats); } function onStat(statErr, dirpath, stats) { if (statErr) { // If we have ENOENT here it might be a symlink, // so we need to recurse to error with the target file name if (statErr.code === 'ENOENT') { return lstat(dirpath, onStat); } return callback(statErr); } if (!stats.isDirectory()) { return lstat(dirpath, onNonDirectory); } if (!mode) { return callback(); } if ((stats.mode & MASK_MODE) === mode) { return callback(); } fs.chmod(dirpath, mode, onChmod); } function onChmod(chmodErr) { if (chmodErr && chmodErr.code !== 'ENOSUP') { return callback(chmodErr); } callback(); } function onNonDirectory(err, dirpath, stats) { if (err) { // Just being cautious by bubbling the mkdir error return callback(mkdirErr); } if (stats.isSymbolicLink()) { return fs.readlink(dirpath, onReadlink); } // Trying to readdir will surface the ENOTDIR we want // TODO: Use `opendir` when we support node >12 fs.readdir(dirpath, callback); } function onReadlink(err, link) { if (err) { // Just being cautious by bubbling the mkdir error return callback(mkdirErr); } // Trying to readdir will surface the ENOTDIR we want // TODO: Use `opendir` when we support node >12 fs.readdir(link, callback); } } function onRecurse(recurseErr) { if (recurseErr) { return callback(recurseErr); } mkdirp(dirpath, mode, callback); } } module.exports = mkdirp; fs-mkdirp-stream-2.0.1/package.json000066400000000000000000000022141431314200300171530ustar00rootroot00000000000000{ "name": "fs-mkdirp-stream", "version": "2.0.1", "description": "Ensure directories exist before writing to them.", "author": "Gulp Team (https://gulpjs.com/)", "contributors": [ "Blaine Bublitz " ], "repository": "gulpjs/fs-mkdirp-stream", "license": "MIT", "engines": { "node": ">=10.13.0" }, "main": "index.js", "files": [ "LICENSE", "index.js", "mkdirp.js" ], "scripts": { "lint": "eslint .", "pretest": "npm run lint", "test": "nyc mocha --async-only" }, "dependencies": { "graceful-fs": "^4.2.8", "streamx": "^2.12.0" }, "devDependencies": { "eslint": "^7.32.0", "eslint-config-gulp": "^5.0.1", "eslint-plugin-node": "^11.1.0", "expect": "^27.4.2", "mocha": "^8.4.0", "nyc": "^15.1.0", "readable-stream": "^3.6.0", "rimraf": "^3.0.2", "sinon": "^12.0.1" }, "nyc": { "reporter": [ "lcov", "text-summary" ] }, "prettier": { "singleQuote": true }, "keywords": [ "fs", "mkdirp", "stream", "mkdir", "directory", "directories", "ensure" ] } fs-mkdirp-stream-2.0.1/test/000077500000000000000000000000001431314200300156455ustar00rootroot00000000000000fs-mkdirp-stream-2.0.1/test/index.js000066400000000000000000000115111431314200300173110ustar00rootroot00000000000000'use strict'; var os = require('os'); var path = require('path'); var fs = require('graceful-fs'); var sinon = require('sinon'); var expect = require('expect'); var rimraf = require('rimraf'); var mkdirpStream = require('../'); function suite(moduleName) { var stream = require(moduleName); function sink() { return new stream.Writable({ objectMode: true, write: function (chunk, enc, cb) { if (typeof enc === 'function') { cb = enc; } cb(); }, }); } describe('mkdirpStream (' + moduleName + ')', function () { var MASK_MODE = parseInt('7777', 8); var isWindows = os.platform() === 'win32'; var outputBase = path.join(__dirname, './out-fixtures'); var outputDirpath = path.join(outputBase, './foo'); function cleanup(done) { this.timeout(20000); // Async del to get sort-of-fix for https://github.com/isaacs/rimraf/issues/72 rimraf(outputBase, done); } function masked(mode) { return mode & MASK_MODE; } function statMode(outputPath) { return masked(fs.lstatSync(outputPath).mode); } function applyUmask(mode) { if (typeof mode !== 'number') { mode = parseInt(mode, 8); } // Set to use to "get" it var current = process.umask(0); // Then set it back for the next test process.umask(current); return mode & ~current; } beforeEach(cleanup); afterEach(cleanup); beforeEach(function (done) { fs.mkdir(outputBase, function (err) { if (err) { return done(err); } // Linux inherits the setgid of the directory and it messes up our assertions // So we explixitly set the mode to 777 before each test fs.chmod(outputBase, '777', done); }); }); it('exports a main function', function (done) { expect(typeof mkdirpStream).toEqual('function'); done(); }); it('takes a string to create', function (done) { function assert(err) { expect(statMode(outputDirpath)).toBeDefined(); done(err); } stream.pipeline( [stream.Readable.from(['test']), mkdirpStream(outputDirpath), sink()], assert ); }); it('takes a resolver function that receives chunk', function (done) { var expected = 'test'; function resolver(chunk, cb) { expect(chunk).toEqual(expected); cb(null, outputDirpath); } function assert(err) { expect(statMode(outputDirpath)).toBeDefined(); done(err); } stream.pipeline( [stream.Readable.from(['test']), mkdirpStream(resolver), sink()], assert ); }); it('can pass a mode as the 3rd argument to the resolver callback', function (done) { if (isWindows) { this.skip(); return; } var mode = applyUmask('700'); var expected = 'test'; function resolver(chunk, cb) { expect(chunk).toEqual(expected); cb(null, outputDirpath, mode); } function assert(err) { expect(statMode(outputDirpath)).toEqual(mode); done(err); } stream.pipeline( [stream.Readable.from(['test']), mkdirpStream(resolver), sink()], assert ); }); it('can pass an error as the 1st argument to the resolver callback to error', function (done) { function resolver(chunk, cb) { cb(new Error('boom')); } function notExists() { statMode(outputDirpath); } function assert(err) { expect(err).toBeDefined(); expect(notExists).toThrow(); done(); } stream.pipeline( [stream.Readable.from(['test']), mkdirpStream(resolver), sink()], assert ); }); it('works with objectMode', function (done) { function resolver(chunk, cb) { expect(typeof chunk).toEqual('object'); expect(chunk.dirname).toBeDefined(); cb(null, chunk.dirname); } function assert(err) { expect(statMode(outputDirpath)).toBeDefined(); done(err); } stream.pipeline( [ stream.Readable.from([{ dirname: outputDirpath }]), mkdirpStream(resolver), sink(), ], assert ); }); it('bubbles mkdir errors', function (done) { sinon.stub(fs, 'mkdir').callsFake(function (dirpath, mode, cb) { cb(new Error('boom')); }); function notExists() { statMode(outputDirpath); } function assert(err) { expect(err).toBeDefined(); expect(notExists).toThrow(); fs.mkdir.restore(); done(); } stream.pipeline( [stream.Readable.from(['test']), mkdirpStream(outputDirpath), sink()], assert ); }); }); } suite('stream'); suite('streamx'); suite('readable-stream'); fs-mkdirp-stream-2.0.1/test/mkdirp.js000066400000000000000000000360701431314200300174770ustar00rootroot00000000000000'use strict'; var os = require('os'); var path = require('path'); var fs = require('graceful-fs'); var sinon = require('sinon'); var expect = require('expect'); var rimraf = require('rimraf'); var mkdirp = require('../mkdirp'); var log = { expected: function (expected) { if (process.env.VERBOSE) { console.log('Expected mode:', expected.toString(8)); } }, found: function (found) { if (process.env.VERBOSE) { console.log('Found mode', found.toString(8)); } }, }; function suite() { var MASK_MODE = parseInt('7777', 8); var DEFAULT_DIR_MODE = parseInt('0777', 8); var isWindows = os.platform() === 'win32'; var outputBase = path.join(__dirname, './out-fixtures'); var outputDirpath = path.join(outputBase, './foo'); var outputNestedPath = path.join(outputDirpath, './test.txt'); var outputNestedDirpath = path.join(outputDirpath, './bar/baz/'); var contents = 'Hello World!\n'; function cleanup(done) { this.timeout(20000); // Async del to get sort-of-fix for https://github.com/isaacs/rimraf/issues/72 rimraf(outputBase, done); } function masked(mode) { return mode & MASK_MODE; } function createdMode(outputPath) { var mode = masked(fs.lstatSync(outputPath).mode); log.found(mode); return mode; } function expectedMode(mode) { if (typeof mode !== 'number') { mode = parseInt(mode, 8); } log.expected(mode); return mode; } function expectedDefaultMode() { // Set to use to "get" it var current = process.umask(0); // Then set it back for the next test process.umask(current); var mode = DEFAULT_DIR_MODE & ~current; log.expected(mode); return mode; } beforeEach(cleanup); afterEach(cleanup); beforeEach(function (done) { fs.mkdir(outputBase, function (err) { if (err) { return done(err); } // Linux inherits the setgid of the directory and it messes up our assertions // So we explixitly set the mode to 777 before each test fs.chmod(outputBase, '777', done); }); }); it('makes a single directory', function (done) { mkdirp(outputDirpath, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputDirpath)).toBeDefined(); done(); }); }); it('makes single directory w/ default mode', function (done) { if (isWindows) { this.skip(); return; } mkdirp(outputDirpath, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputDirpath)).toEqual(expectedDefaultMode()); done(); }); }); it('makes multiple directories', function (done) { mkdirp(outputNestedDirpath, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputNestedDirpath)).toBeDefined(); done(); }); }); it('makes multiple directories w/ default mode', function (done) { if (isWindows) { this.skip(); return; } mkdirp(outputNestedDirpath, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputNestedDirpath)).toEqual(expectedDefaultMode()); done(); }); }); it('makes directory with custom mode as string', function (done) { if (isWindows) { this.skip(); return; } var mode = '777'; mkdirp(outputDirpath, mode, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputDirpath)).toEqual(expectedMode(mode)); done(); }); }); it('makes directory with custom mode as octal', function (done) { if (isWindows) { this.skip(); return; } var mode = parseInt('777', 8); mkdirp(outputDirpath, mode, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputDirpath)).toEqual(expectedMode(mode)); done(); }); }); it('does not mask a custom mode', function (done) { if (isWindows) { this.skip(); return; } var mode = parseInt('777', 8); mkdirp(outputDirpath, mode, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputDirpath)).toEqual(mode); done(); }); }); it('can create a directory with setgid permission', function (done) { if (isWindows) { this.skip(); return; } var mode = '2700'; mkdirp(outputDirpath, mode, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputDirpath)).toEqual(expectedMode(mode)); done(); }); }); it('does not change directory mode if exists and no mode given', function (done) { if (isWindows) { this.skip(); return; } var mode = '777'; mkdirp(outputDirpath, mode, function (err) { expect(err).toBeFalsy(); mkdirp(outputDirpath, function (err2) { expect(err2).toBeFalsy(); expect(createdMode(outputDirpath)).toEqual(expectedMode(mode)); done(); }); }); }); it('makes multiple directories with custom mode', function (done) { if (isWindows) { this.skip(); return; } var mode = '777'; mkdirp(outputNestedDirpath, mode, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputNestedDirpath)).toEqual(expectedMode(mode)); done(); }); }); it('uses default mode on intermediate directories', function (done) { if (isWindows) { this.skip(); return; } var intermediateDirpath = path.dirname(outputNestedDirpath); var mode = '777'; mkdirp(outputNestedDirpath, mode, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputDirpath)).toEqual(expectedDefaultMode()); expect(createdMode(intermediateDirpath)).toEqual(expectedDefaultMode()); expect(createdMode(outputNestedDirpath)).toEqual(expectedMode(mode)); done(); }); }); it('changes mode of existing directory', function (done) { if (isWindows) { this.skip(); return; } var mode = '777'; fs.mkdir(outputDirpath, function (err) { expect(err).toBeFalsy(); expect(createdMode(outputDirpath)).toEqual(expectedDefaultMode()); mkdirp(outputDirpath, mode, function (err2) { expect(err2).toBeFalsy(); expect(createdMode(outputDirpath)).toEqual(expectedMode(mode)); done(); }); }); }); it('surfaces chmod errors', function (done) { sinon.stub(fs, 'chmod').callsFake(function (p, mode, cb) { cb(new Error('boom')); }); var mode = '777'; mkdirp(outputDirpath, mode, function (err) { fs.chmod.restore(); expect(err).toBeDefined(); done(); }); }); it('does not surface error ENOSUP if chmod is unsupported on the path', function (done) { sinon.stub(fs, 'chmod').callsFake(function (p, mode, cb) { var err = new Error('boom'); err.code = 'ENOSUP'; cb(err); }); var mode = '777'; mkdirp(outputDirpath, mode, function (err) { fs.chmod.restore(); expect(err).not.toBeDefined(); done(); }); }); it('errors with ENOTDIR if file in path', function (done) { fs.mkdir(outputDirpath, function (err) { expect(err).toBeFalsy(); fs.writeFile(outputNestedPath, contents, function (err2) { expect(err2).toBeFalsy(); mkdirp(outputNestedPath, function (err3) { expect(err3).toBeDefined(); expect(err3.code).toEqual('ENOTDIR'); expect(err3.path).toEqual(outputNestedPath); done(); }); }); }); }); it('errors with ENOTDIR if file in path of nested mkdirp', function (done) { var nestedPastFile = path.join(outputNestedPath, './bar/baz/'); fs.mkdir(outputDirpath, function (err) { expect(err).toBeFalsy(); fs.writeFile(outputNestedPath, contents, function (err2) { expect(err2).toBeFalsy(); mkdirp(nestedPastFile, function (err3) { expect(err3).toBeDefined(); expect(err3.code).toEqual('ENOTDIR'); expect(err3.path).toEqual(outputNestedPath); done(); }); }); }); }); it('does not change mode of existing file', function (done) { if (isWindows) { this.skip(); return; } var mode = '777'; fs.mkdir(outputDirpath, function (err) { expect(err).toBeFalsy(); fs.writeFile(outputNestedPath, contents, function (err2) { expect(err2).toBeFalsy(); var existingMode = createdMode(outputNestedPath); expect(existingMode).not.toEqual(mode); mkdirp(outputNestedPath, mode, function (err3) { expect(err3).toBeDefined(); expect(createdMode(outputNestedPath)).toEqual(existingMode); done(); }); }); }); }); it('surfaces mkdir errors that happening during recursion', function (done) { var ogMkdir = fs.mkdir; var stub = sinon.stub(fs, 'mkdir').callsFake(function (dirpath, mode, cb) { if (stub.callCount === 1) { return ogMkdir(dirpath, mode, cb); } cb(new Error('boom')); }); mkdirp(outputNestedDirpath, function (err) { fs.mkdir.restore(); expect(err).toBeDefined(); done(); }); }); it('surfaces fs.stat errors', function (done) { sinon.stub(fs, 'stat').callsFake(function (dirpath, cb) { cb(new Error('boom')); }); mkdirp(outputDirpath, function (err) { fs.stat.restore(); expect(err).toBeDefined(); done(); }); }); it('does not attempt fs.chmod if custom mode matches mode on disk', function (done) { if (isWindows) { this.skip(); return; } var mode = '777'; mkdirp(outputDirpath, mode, function (err) { expect(err).toBeFalsy(); var spy = sinon.spy(fs, 'chmod'); mkdirp(outputDirpath, mode, function (err) { fs.chmod.restore(); expect(err).toBeFalsy(); expect(spy.callCount).toEqual(0); done(); }); }); }); describe('symlinks', function () { before(function () { if (isWindows) { this.skip(); return; } }); it('succeeds with a directory at the target of a symlink', function (done) { var target = path.join(outputBase, 'target'); fs.mkdir(target, function (err) { expect(err).toBeFalsy(); fs.symlink(target, outputDirpath, function (err) { expect(err).toBeFalsy(); mkdirp(outputDirpath, function (err) { expect(err).toBeFalsy(); expect(createdMode(target)).toBeDefined(); done(); }); }); }); }); it('changes mode of existing directory at the target of a symlink', function (done) { var target = path.join(outputBase, 'target'); var mode = '777'; fs.mkdir(target, function (err) { expect(err).toBeFalsy(); fs.symlink(target, outputDirpath, function (err2) { expect(err2).toBeFalsy(); expect(createdMode(target)).toEqual(expectedDefaultMode()); mkdirp(outputDirpath, mode, function (err3) { expect(err3).toBeFalsy(); expect(createdMode(target)).toEqual(expectedMode(mode)); done(); }); }); }); }); it('creates nested directories at the target of a symlink', function (done) { var target = path.join(outputBase, 'target'); var expected = path.join(target, './bar/baz/'); fs.mkdir(target, function (err) { expect(err).toBeFalsy(); fs.symlink(target, outputDirpath, function (err2) { expect(err2).toBeFalsy(); mkdirp(outputNestedDirpath, function (err3) { expect(err3).toBeFalsy(); expect(createdMode(expected)).toBeDefined(); done(); }); }); }); }); it('errors with ENOTDIR if the target of a symlink is a file', function (done) { var target = path.join(outputBase, 'test.txt'); fs.mkdir(outputDirpath, function (err) { expect(err).toBeFalsy(); fs.writeFile(target, contents, function (err2) { expect(err2).toBeFalsy(); fs.symlink(target, outputNestedPath, function (err3) { expect(err3).toBeFalsy(); mkdirp(outputNestedPath, function (err4) { expect(err4).toBeDefined(); expect(err4.code).toEqual('ENOTDIR'); expect(err4.path).toEqual(target); done(); }); }); }); }); }); it('errors with ENOTDIR if the target of a symlink is a file in a nested mkdirp', function (done) { var target = path.join(outputBase, 'test.txt'); fs.writeFile(target, contents, function (err) { expect(err).toBeFalsy(); fs.symlink(target, outputDirpath, function (err2) { expect(err2).toBeFalsy(); mkdirp(outputNestedDirpath, function (err3) { expect(err3).toBeDefined(); expect(err3.code).toEqual('ENOTDIR'); expect(err3.path).toEqual(target); done(); }); }); }); }); it('errors with ENOENT if the target of a symlink is missing (a.k.a. dangling symlink)', function (done) { var target = path.join(outputBase, 'dangling-link'); fs.symlink(target, outputDirpath, function (err) { expect(err).toBeFalsy(); mkdirp(outputDirpath, function (err2) { expect(err2).toBeDefined(); expect(err2.code).toEqual('ENOENT'); expect(err2.path).toEqual(target); done(); }); }); }); it('properly surfaces top-level error if lstat fails', function (done) { var target = path.join(outputBase, 'test.txt'); sinon.stub(fs, 'lstat').callsFake(function (dirpath, cb) { cb(new Error('boom')); }); fs.mkdir(outputDirpath, function (err) { expect(err).toBeFalsy(); fs.writeFile(target, contents, function (err2) { expect(err2).toBeFalsy(); fs.symlink(target, outputNestedPath, function (err3) { expect(err3).toBeFalsy(); mkdirp(outputNestedPath, function (err4) { fs.lstat.restore(); expect(err4).toBeDefined(); expect(err4.code).toEqual('EEXIST'); expect(err4.path).toEqual(outputNestedPath); done(); }); }); }); }); }); it('properly surfaces top-level error if readlink fails', function (done) { var target = path.join(outputBase, 'target'); sinon.stub(fs, 'readlink').callsFake(function (dirpath, cb) { cb(new Error('boom')); }); fs.symlink(target, outputDirpath, function (err) { expect(err).toBeFalsy(); mkdirp(outputDirpath, function (err2) { fs.readlink.restore(); expect(err2).toBeDefined(); expect(err2.code).toEqual('EEXIST'); expect(err2.path).toEqual(outputDirpath); done(); }); }); }); }); } describe('mkdirp', suite); describe('mkdirp with umask', function () { var startingUmask; before(function (done) { startingUmask = process.umask(parseInt('066', 8)); done(); }); after(function (done) { process.umask(startingUmask); done(); }); // Initialize the normal suite suite(); });