{"id":13388520,"url":"https://github.com/rich-harris/sander","last_synced_at":"2025-06-18T14:07:55.988Z","repository":{"id":20722136,"uuid":"24006231","full_name":"Rich-Harris/sander","owner":"Rich-Harris","description":"Promise-based power tool for common filesystem tasks","archived":false,"fork":false,"pushed_at":"2024-08-16T01:29:49.000Z","size":53,"stargazers_count":119,"open_issues_count":10,"forks_count":12,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-16T09:49:38.023Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Rich-Harris.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-09-13T21:23:19.000Z","updated_at":"2024-11-22T10:35:31.000Z","dependencies_parsed_at":"2024-10-25T04:15:44.592Z","dependency_job_id":"df24557f-96d1-4d3a-b89b-42ba531af8d8","html_url":"https://github.com/Rich-Harris/sander","commit_stats":{"total_commits":69,"total_committers":3,"mean_commits":23.0,"dds":0.08695652173913049,"last_synced_commit":"000590a0a4a954e0488c028fad4b7ca801023aee"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/Rich-Harris/sander","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rich-Harris%2Fsander","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rich-Harris%2Fsander/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rich-Harris%2Fsander/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rich-Harris%2Fsander/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Rich-Harris","download_url":"https://codeload.github.com/Rich-Harris/sander/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rich-Harris%2Fsander/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260567252,"owners_count":23029071,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T13:00:42.974Z","updated_at":"2025-06-18T14:07:50.974Z","avatar_url":"https://github.com/Rich-Harris.png","language":"JavaScript","readme":"# sander\n\nA Promise-based power tool for common filesystem tasks in node.js.\n\n## Installation\n\n```bash\nnpm install sander\n```\n\n## Another wrapper around `fs`? Really?\n\nYup. Working with the low-level `fs` API is the fastest road to callback hell, and while a lot of the existing `fs` wrappers add a whole load of missing features, they don't really mitigate the fundamental suckiness of working with the filesystem in a painful, imperative way, which forces you to handle errors at every step of the journey towards the centre of the node.js [pyramid of doom](http://stackoverflow.com/search?q=pyramid+of+doom).\n\n**Enough! Manual filing is tedious - you need a power tool.** Instead of writing this...\n\n```js\nvar path = require( 'path' ),\n    fs = require( 'fs' ),\n    mkdirp = require( 'mkdirp' );\n\nvar dest = path.resolve( basedir, filename );\n\nmkdirp( path.dirname( dest ), function ( err ) {\n  if ( err ) throw err;\n\n  fs.writeFile( dest, data, function ( err ) {\n    if ( err ) throw err;\n    doTheNextThing();\n  });\n});\n```\n\n...write this:\n\n```js\nvar sander = require( 'sander' );\nsander.writeFile( basedir, filename, data ).then( doTheNextThing );\n```\n\nIt uses [graceful-fs](https://github.com/isaacs/node-graceful-fs) rather than the built-in `fs` module, to eliminate [EMFILE](http://blog.izs.me/post/56827866110/wtf-is-emfile-and-why-does-it-happen-to-me) from the list of things you have to worry about.\n\n\n## Conventions\n\n### Promises\n\nAll async methods (those whose `fs` equivalents would take a callback, e.g. `sander.readFile`) return a Promise. If you're not familiar with Promises, [read up on them on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) - they're coming in ES6 and are already supported in many browsers, and I guarantee they'll make your life easier.\n\n(Node doesn't natively support promises yet - we're using [es6-promise](https://github.com/jakearchibald/es6-promise) for maximum compatibility. For convenience, the `Promise` constructor is exposed as `sander.Promise`.)\n\n### Intermediate folder creation\n\nWhen writing files and folders, intermediate folders are automatically created as necessary. (I've never encountered a situation where I wanted an `ENOENT` error instead of having this be done for me.)\n\n### Automatic path resolution\n\nWherever appropriate, method arguments are joined together with `path.resolve()` - so the following are equivalent:\n\n```js\nsander.readFile( 'foo', 'bar', 'baz' );\nsander.readFile( path.resolve( 'foo', 'bar', 'baz' ) );\nsander.readFile( 'foo/bar/baz' ); // or 'foo\\bar\\baz' on Windows\n```\n\n### Methods that involve two paths\n\nSome operations, such as renaming files, require two paths to be specified. The convention for handling this in sander is as follows:\n\n```js\nsander.rename( basedir, oldname ).to( basedir, newname );\n```\n\n\n\n## Methods\n\n### `fs` methods\n\nIn addition to the extra methods (listed below), all `fs` methods have `sander` equivalents. The synchronous methods (those ending `Sync`) are the same as the `fs` originals except that path resolution and intermediate folder creation are automatically handled (see [conventions](#conventions), above). All async methods return a promise.\n\nFor more information about what these methods do, consult the [node documentation](http://nodejs.org/api/fs.html).\n\nIn the list below, `...paths` indicates you can use one or more strings in sequence, as per the [automatic path resolution](#automatic-path-resolution) convention. An `fd` argument refers to a file descriptor, which you'd generate with `sander.open()` or `sander.openSync()`. Arguments wrapped in `[]` characters are optional.\n\n```js\nsander.appendFile(...paths, data, [options])\nsander.appendFileSync(...paths, data, [options])\nsander.chmod(...paths, {mode: mode})\nsander.chmodSync(...paths, {mode: mode})\nsander.chown(...paths, uid, gid)\nsander.chownSync(...paths, uid, gid)\nsander.close(fd)\nsander.closeSync(fd)\nsander.createReadStream(...paths, [options])\nsander.createWriteStream(...paths, [options])\nsander.exists(...paths)\nsander.existsSync(...paths)\nsander.fchmod(fd, {mode: mode})\nsander.fchmodSync(fd, {mode: mode})\nsander.fchown(fd, uid, gid)\nsander.fchownSync(fd, uid, gid)\nsander.fstat(fd)\nsander.fstatSync(fd)\nsander.fsync(fd)\nsander.fsyncSync(fd)\nsander.ftruncate(fd, len)\nsander.ftruncateSync(fd, len)\nsander.futimes(fd, atime, mtime)\nsander.futimesSync(fd, atime, mtime)\nsander.lchmod(...paths, {mode: mode})\nsander.lchmodSync(...paths, {mode: mode})\nsander.lchown(...paths, uid, gid)\nsander.lchownSync(...paths, uid, gid)\nsander.link(...paths).to(...paths)\nsander.linkSync(...paths).to(...paths)\nsander.lstat(...paths)\nsander.lstatSync(...paths)\nsander.mkdir(...paths, [{mode: mode}])\nsander.mkdirSync(...paths, [{mode: mode}])\nsander.open(...paths, flags, [{mode: mode}])\nsander.openSync(...paths, flags, [{mode: mode}])\nsander.read(fd, buffer, offset, length, position)\nsander.readSync(fd, buffer, offset, length, position)\nsander.readdir(...paths)\nsander.readdirSync(...paths)\nsander.readFile(...paths, [options])\nsander.readFileSync(...paths, [options])\nsander.readlink(...paths)\nsander.readlinkSync(...paths)\nsander.realpath(...paths, [cache])\nsander.realpathSync(...paths, [cache])\nsander.rename(...paths).to(...paths)\nsander.renameSync(...paths).to(...paths)\nsander.rmdir(...paths)\nsander.rmdirSync(...paths)\nsander.stat(...paths)\nsander.statSync(...paths)\nsander.symlink(...paths).to(...paths, [{type: type}])\nsander.symlinkSync(...paths).to(...paths, [{type: type}])\nsander.truncate(...paths, len)\nsander.truncateSync(...paths, len)\nsander.unlink(...paths)\nsander.unlinkSync(...paths)\nsander.utimes(...paths, atime, mtime)\nsander.utimesSync(...paths, atime, mtime)\nsander.unwatchFile(...paths, [listener])\nsander.watch(...paths, [options], [listener])\nsander.watchFile(...paths, [options], listener)\nsander.write(fd, buffer, offset, length, position)\nsander.writeSync(fd, buffer, offset, length, position)\nsander.writeFile(...paths, data, [options])\nsander.writeFileSync(...paths, data, [options])\n```\n\nNote that with the `chmod`/`fchmod`/`lchmod`/`symlink`/`mkdir`/`open` methods (and their synchronous equivalents), the `mode` and `type` arguments must be passed as objects with a `mode` or `type` property. This is so that sander knows which arguments should be treated as parts of a path (because they're strings) and which shouldn't.\n\nThe same is true for methods like `readFile` - whereas in node you can do `fs.readFile('path/to/file.txt', 'utf-8')` if you want to specify utf-8 encoding, with sander the final argument should be a `{encoding: 'utf-8'}` object.\n\n\n### Extra methods\n\n```js\n// Copy a file using streams. `readOptions` is passed to `fs.createReadStream`,\n// while `writeOptions` is passed to `fs.createWriteStream`\nsander.copyFile(...paths, [readOptions]).to(...paths, [writeOptions])\n\n// Copy a file synchronously. `readOptions`, is passed to `fs.readFileSync`,\n// while `writeOptions` is passed to `fs.writeFileSync`\nsander.copyFileSync(...paths, [readOptions]).to(...paths, [writeOptions])\n\n// Copy a directory, recursively. `readOptions` and `writeOptions` are\n// treated as per `sander.copyFile[Sync]`\nsander.copydir(...paths, [readOptions]).to(...paths, [writeOptions])\nsander.copydirSync(...paths, [readOptions]).to(...paths, [writeOptions])\n\n// List contents of a directory, recursively\nsander.lsr(...paths)\nsander.lsrSync(...paths)\n\n// Remove a directory and its contents\nsander.rimraf(...paths)\nsander.rimrafSync(...paths)\n\n// Symlink a file or directory, unless we're on Windows in which\n// case fall back to copying to avoid permissions issues\nsander.symlinkOrCopy(...paths).to(...paths);\nsander.symlinkOrCopySync(...paths).to(...paths);\n```\n\n\n### License\n\nMIT\n\n","funding_links":[],"categories":["Packages"],"sub_categories":["Filesystem"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frich-harris%2Fsander","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frich-harris%2Fsander","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frich-harris%2Fsander/lists"}