├── .prettierignore ├── .npmrc ├── .gitignore ├── .editorconfig ├── .github └── workflows │ ├── bb.yml │ └── main.yml ├── tsconfig.json ├── index.js ├── license ├── package.json ├── lib └── index.js ├── readme.md └── test.js /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.md 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | ignore-scripts=true 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.d.ts 3 | *.log 4 | coverage/ 5 | node_modules/ 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/workflows/bb.yml: -------------------------------------------------------------------------------- 1 | name: bb 2 | on: 3 | issues: 4 | types: [opened, reopened, edited, closed, labeled, unlabeled] 5 | pull_request_target: 6 | types: [opened, reopened, edited, closed, labeled, unlabeled] 7 | jobs: 8 | main: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: unifiedjs/beep-boop-beta@main 12 | with: 13 | repo-token: ${{secrets.GITHUB_TOKEN}} 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "customConditions": ["development"], 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "exactOptionalPropertyTypes": true, 8 | "lib": ["es2022"], 9 | "module": "node16", 10 | "strict": true, 11 | "target": "es2022" 12 | }, 13 | "exclude": ["coverage/", "node_modules/"], 14 | "include": ["**/*.js"] 15 | } 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('./lib/index.js').BufferEncoding} BufferEncoding 3 | * @typedef {import('./lib/index.js').Callback} Callback 4 | * @typedef {import('./lib/index.js').Compatible} Compatible 5 | * @typedef {import('./lib/index.js').ReadOptions} ReadOptions 6 | * @typedef {import('./lib/index.js').WriteOptions} WriteOptions 7 | */ 8 | 9 | export {toVFile, read, readSync, write, writeSync} from './lib/index.js' 10 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: 3 | - pull_request 4 | - push 5 | jobs: 6 | main: 7 | name: '${{matrix.node}} on ${{matrix.os}}' 8 | runs-on: ${{matrix.os}} 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: ${{matrix.node}} 14 | - run: npm install 15 | - run: npm test 16 | - uses: codecov/codecov-action@v3 17 | strategy: 18 | matrix: 19 | os: 20 | - ubuntu-latest 21 | - windows-latest 22 | node: 23 | - lts/gallium 24 | - node 25 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2015 Titus Wormer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "to-vfile", 3 | "version": "8.0.0", 4 | "description": "vfile utility to read and write to the file system", 5 | "license": "MIT", 6 | "keywords": [ 7 | "vfile", 8 | "vfile-util", 9 | "util", 10 | "utility", 11 | "virtual", 12 | "file", 13 | "text", 14 | "processing", 15 | "file-path", 16 | "path" 17 | ], 18 | "repository": "vfile/to-vfile", 19 | "bugs": "https://github.com/vfile/to-vfile/issues", 20 | "funding": { 21 | "type": "opencollective", 22 | "url": "https://opencollective.com/unified" 23 | }, 24 | "author": "Titus Wormer (https://wooorm.com)", 25 | "contributors": [ 26 | "Titus Wormer (https://wooorm.com)" 27 | ], 28 | "sideEffects": false, 29 | "type": "module", 30 | "exports": "./index.js", 31 | "files": [ 32 | "lib/", 33 | "index.d.ts", 34 | "index.js" 35 | ], 36 | "dependencies": { 37 | "vfile": "^6.0.0" 38 | }, 39 | "devDependencies": { 40 | "@types/node": "^20.0.0", 41 | "c8": "^8.0.0", 42 | "is-buffer": "^2.0.0", 43 | "prettier": "^2.0.0", 44 | "remark-cli": "^11.0.0", 45 | "remark-preset-wooorm": "^9.0.0", 46 | "type-coverage": "^2.0.0", 47 | "typescript": "^5.0.0", 48 | "xo": "^0.54.0" 49 | }, 50 | "scripts": { 51 | "prepack": "npm run build && npm run format", 52 | "build": "tsc --build --clean && tsc --build && type-coverage", 53 | "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", 54 | "test-api": "node --conditions development test.js", 55 | "test-coverage": "c8 --100 --reporter lcov npm run test-api", 56 | "test": "npm run build && npm run format && npm run test-coverage" 57 | }, 58 | "prettier": { 59 | "bracketSpacing": false, 60 | "semi": false, 61 | "singleQuote": true, 62 | "tabWidth": 2, 63 | "trailingComma": "none", 64 | "useTabs": false 65 | }, 66 | "remarkConfig": { 67 | "plugins": [ 68 | "remark-preset-wooorm" 69 | ] 70 | }, 71 | "typeCoverage": { 72 | "atLeast": 100, 73 | "detail": true, 74 | "ignoreCatch": true, 75 | "strict": true 76 | }, 77 | "xo": { 78 | "prettier": true 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('vfile').VFileOptions} Options 3 | * @typedef {import('vfile').VFileValue} Value 4 | */ 5 | 6 | /** 7 | * @typedef {'ascii' | 'base64' | 'base64url' | 'binary' | 'hex' | 'latin1' | 'ucs-2' | 'ucs2' | 'utf-8' | 'utf16le' | 'utf8'} BufferEncoding 8 | * Encodings supported by the buffer class. 9 | * 10 | * This is a copy of the types from Node, copied to prevent Node globals from 11 | * being needed. 12 | * Copied from: . 13 | * 14 | * @typedef ReadOptions 15 | * Configuration for `fs.readFile`. 16 | * @property {BufferEncoding | null | undefined} [encoding] 17 | * Encoding to read file as, will turn `file.value` into a string if passed. 18 | * @property {string | undefined} [flag] 19 | * File system flags to use. 20 | * 21 | * @typedef WriteOptions 22 | * Configuration for `fs.writeFile`. 23 | * @property {BufferEncoding | null | undefined} [encoding] 24 | * Encoding to write file as. 25 | * @property {string | undefined} [flag] 26 | * File system flags to use. 27 | * @property {number | string | undefined} [mode] 28 | * File mode (permission and sticky bits) if the file was newly created. 29 | * 30 | * @typedef {URL | Value} Path 31 | * URL to file or path to file. 32 | * 33 | * > 👉 **Note**: `Value` is used here because it’s a smarter `Buffer` 34 | * @typedef {Options | Path | VFile} Compatible 35 | * URL to file, path to file, options for file, or actual file. 36 | */ 37 | 38 | /** 39 | * @callback Callback 40 | * Callback called after reading or writing a file. 41 | * @param {NodeJS.ErrnoException | undefined} error 42 | * Error when reading or writing was not successful. 43 | * @param {VFile | null | undefined} file 44 | * File when reading or writing was successful. 45 | * @returns {undefined} 46 | * Nothing. 47 | * 48 | * @callback Resolve 49 | * @param {VFile} result 50 | * File. 51 | * @returns {void} 52 | * Nothing (note: has to be `void` for TSs `Promise` interface). 53 | * 54 | * @callback Reject 55 | * @param {NodeJS.ErrnoException} error 56 | * Error. 57 | * @param {VFile | undefined} [result] 58 | * File. 59 | * @returns {void} 60 | * Nothing (note: has to be `void` for TSs `Promise` interface). 61 | */ 62 | 63 | import fs from 'node:fs' 64 | import path from 'node:path' 65 | import {VFile} from 'vfile' 66 | 67 | // To do: next major: remove `toVFile`, only accept `VFile`s, 68 | // do not return anything. 69 | 70 | /** 71 | * Create a virtual file and read it in, async. 72 | * 73 | * @overload 74 | * @param {Compatible} description 75 | * @param {BufferEncoding | ReadOptions | null | undefined} options 76 | * @param {Callback} callback 77 | * @returns {undefined} 78 | * 79 | * @overload 80 | * @param {Compatible} description 81 | * @param {Callback} callback 82 | * @returns {undefined} 83 | * 84 | * @overload 85 | * @param {Compatible} description 86 | * @param {BufferEncoding | ReadOptions | null | undefined} [options] 87 | * @returns {Promise} 88 | * 89 | * @param {Compatible} description 90 | * Path to file, file options, or file itself. 91 | * @param {BufferEncoding | Callback | ReadOptions | null | undefined} [options] 92 | * Encoding to use or Node.JS read options. 93 | * @param {Callback | null | undefined} [callback] 94 | * Callback called when done. 95 | * @returns {Promise | undefined} 96 | * Nothing when a callback is given, otherwise promise that resolves to given 97 | * file or new file. 98 | */ 99 | export function read(description, options, callback) { 100 | const file = toVFile(description) 101 | 102 | if (!callback && typeof options === 'function') { 103 | callback = options 104 | options = undefined 105 | } 106 | 107 | if (!callback) { 108 | return new Promise(executor) 109 | } 110 | 111 | executor(resolve, callback) 112 | 113 | /** 114 | * @param {VFile} result 115 | */ 116 | function resolve(result) { 117 | // @ts-expect-error: `callback` always defined. 118 | callback(undefined, result) 119 | } 120 | 121 | /** 122 | * @param {Resolve} resolve 123 | * @param {Reject} reject 124 | * @returns {void} 125 | * Nothing (note: has to be `void` for TSs `Promise` interface). 126 | */ 127 | function executor(resolve, reject) { 128 | /** @type {string} */ 129 | let fp 130 | 131 | try { 132 | fp = path.resolve(file.cwd, file.path) 133 | } catch (error) { 134 | const exception = /** @type {NodeJS.ErrnoException} */ (error) 135 | return reject(exception) 136 | } 137 | 138 | // @ts-expect-error: `options` is not a callback. 139 | fs.readFile(fp, options, done) 140 | 141 | /** 142 | * @param {NodeJS.ErrnoException | undefined} error 143 | * @param {Value} result 144 | */ 145 | function done(error, result) { 146 | if (error) { 147 | reject(error) 148 | } else { 149 | file.value = result 150 | resolve(file) 151 | } 152 | } 153 | } 154 | } 155 | 156 | /** 157 | * Create a virtual file and read it in, synchronously. 158 | * 159 | * @param {Compatible} description 160 | * Path to file, file options, or file itself. 161 | * @param {BufferEncoding | ReadOptions | null | undefined} [options] 162 | * Encoding to use or Node.JS read options. 163 | * @returns {VFile} 164 | * Given file or new file. 165 | */ 166 | export function readSync(description, options) { 167 | const file = toVFile(description) 168 | file.value = fs.readFileSync(path.resolve(file.cwd, file.path), options) 169 | return file 170 | } 171 | 172 | /** 173 | * Create a virtual file from a description. 174 | * 175 | * This is like `VFile`, but it accepts a file path instead of file contents. 176 | * 177 | * If `options` is a string, URL, or buffer, it’s used as the path. 178 | * Otherwise, if it’s a file, that’s returned instead. 179 | * Otherwise, the options are passed through to `new VFile()`. 180 | * 181 | * @param {Compatible | null | undefined} [description] 182 | * Path to file, file options, or file itself. 183 | * @returns {VFile} 184 | * Given file or new file. 185 | */ 186 | export function toVFile(description) { 187 | if (typeof description === 'string' || description instanceof URL) { 188 | description = {path: description} 189 | } else if (isUint8Array(description)) { 190 | description = {path: new TextDecoder().decode(description)} 191 | } 192 | 193 | return looksLikeAVFile(description) ? description : new VFile(description) 194 | } 195 | 196 | /** 197 | * Create a virtual file and write it, async. 198 | * 199 | * @overload 200 | * @param {Compatible} description 201 | * @param {BufferEncoding | WriteOptions | null | undefined} options 202 | * @param {Callback} callback 203 | * @returns {undefined} 204 | * 205 | * @overload 206 | * @param {Compatible} description 207 | * @param {Callback} callback 208 | * @returns {undefined} 209 | * 210 | * @overload 211 | * @param {Compatible} description 212 | * @param {BufferEncoding | WriteOptions | null | undefined} [options] 213 | * @returns {Promise} 214 | * 215 | * @param {Compatible} description 216 | * Path to file, file options, or file itself. 217 | * @param {BufferEncoding | Callback | WriteOptions | null | undefined} [options] 218 | * Encoding to use or Node.JS write options. 219 | * @param {Callback | null | undefined} [callback] 220 | * Callback called when done. 221 | * @returns 222 | * Nothing when a callback is given, otherwise promise that resolves to given 223 | * file or new file. 224 | */ 225 | export function write(description, options, callback) { 226 | const file = toVFile(description) 227 | 228 | // Weird, right? Otherwise `fs` doesn’t accept it. 229 | if (!callback && typeof options === 'function') { 230 | callback = options 231 | options = undefined 232 | } 233 | 234 | if (!callback) { 235 | return new Promise(executor) 236 | } 237 | 238 | executor(resolve, callback) 239 | 240 | /** 241 | * @param {VFile} result 242 | */ 243 | function resolve(result) { 244 | // @ts-expect-error: `callback` always defined. 245 | callback(undefined, result) 246 | } 247 | 248 | /** 249 | * @param {Resolve} resolve 250 | * @param {Reject} reject 251 | */ 252 | function executor(resolve, reject) { 253 | /** @type {string} */ 254 | let fp 255 | 256 | try { 257 | fp = path.resolve(file.cwd, file.path) 258 | } catch (error) { 259 | const exception = /** @type {NodeJS.ErrnoException} */ (error) 260 | return reject(exception) 261 | } 262 | 263 | // @ts-expect-error: `options` is not a callback. 264 | fs.writeFile(fp, file.value || '', options || undefined, done) 265 | 266 | /** 267 | * @param {NodeJS.ErrnoException | undefined} error 268 | */ 269 | function done(error) { 270 | if (error) { 271 | reject(error) 272 | } else { 273 | resolve(file) 274 | } 275 | } 276 | } 277 | } 278 | 279 | /** 280 | * Create a virtual file and write it, synchronously. 281 | * 282 | * @param {Compatible} description 283 | * Path to file, file options, or file itself. 284 | * @param {BufferEncoding | WriteOptions | null | undefined} [options] 285 | * Encoding to use or Node.JS write options. 286 | * @returns {VFile} 287 | * Given file or new file. 288 | */ 289 | export function writeSync(description, options) { 290 | const file = toVFile(description) 291 | fs.writeFileSync(path.resolve(file.cwd, file.path), file.value || '', options) 292 | return file 293 | } 294 | 295 | /** 296 | * Check if something looks like a vfile. 297 | * 298 | * @param {Compatible | null | undefined} value 299 | * Value. 300 | * @returns {value is VFile} 301 | * Whether `value` looks like a `VFile`. 302 | */ 303 | function looksLikeAVFile(value) { 304 | return Boolean( 305 | value && 306 | typeof value === 'object' && 307 | 'message' in value && 308 | 'messages' in value 309 | ) 310 | } 311 | 312 | /** 313 | * Check whether `value` is an `Uint8Array`. 314 | * 315 | * @param {unknown} value 316 | * thing. 317 | * @returns {value is Uint8Array} 318 | * Whether `value` is an `Uint8Array`. 319 | */ 320 | function isUint8Array(value) { 321 | return Boolean( 322 | value && 323 | typeof value === 'object' && 324 | 'byteLength' in value && 325 | 'byteOffset' in value 326 | ) 327 | } 328 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # to-vfile 2 | 3 | [![Build][build-badge]][build] 4 | [![Coverage][coverage-badge]][coverage] 5 | [![Downloads][downloads-badge]][downloads] 6 | [![Sponsors][sponsors-badge]][collective] 7 | [![Backers][backers-badge]][collective] 8 | [![Chat][chat-badge]][chat] 9 | 10 | [vfile][] utility to read and write to the file system. 11 | 12 | ## Contents 13 | 14 | * [What is this?](#what-is-this) 15 | * [When should I use this?](#when-should-i-use-this) 16 | * [Install](#install) 17 | * [Use](#use) 18 | * [API](#api) 19 | * [`toVFile(description)`](#tovfiledescription) 20 | * [`read(description[, options][, callback])`](#readdescription-options-callback) 21 | * [`readSync(description[, options])`](#readsyncdescription-options) 22 | * [`write(description[, options][, callback])`](#writedescription-options-callback) 23 | * [`writeSync(description[, options])`](#writesyncdescription-options) 24 | * [`BufferEncoding`](#bufferencoding) 25 | * [`Callback`](#callback) 26 | * [`Compatible`](#compatible) 27 | * [`ReadOptions`](#readoptions) 28 | * [`WriteOptions`](#writeoptions) 29 | * [Types](#types) 30 | * [Compatibility](#compatibility) 31 | * [Contribute](#contribute) 32 | * [License](#license) 33 | 34 | ## What is this? 35 | 36 | This utility puts the file system first. 37 | Where `vfile` itself focusses on file values (the file contents), this instead 38 | focuses on the file system, which is a common case when working with *actual* 39 | files from Node.js. 40 | 41 | ## When should I use this? 42 | 43 | Use this if you know there’s a file system and want to use it. 44 | Use `vfile` if there might not be a file system. 45 | 46 | ## Install 47 | 48 | This package is [ESM only][esm]. 49 | In Node.js (version 16+), install with [npm][]: 50 | 51 | ```sh 52 | npm install to-vfile 53 | ``` 54 | 55 | In Deno with [`esm.sh`][esmsh]: 56 | 57 | ```js 58 | import {toVFile, read, readSync, write, writeSync} from 'https://esm.sh/to-vfile@8' 59 | ``` 60 | 61 | ## Use 62 | 63 | ```js 64 | import {toVFile, read} from 'to-vfile' 65 | 66 | console.log(toVFile('readme.md')) 67 | console.log(toVFile(new URL('readme.md', import.meta.url))) 68 | console.log(await read('.git/HEAD')) 69 | console.log(await read('.git/HEAD', 'utf8')) 70 | ``` 71 | 72 | Yields: 73 | 74 | ```js 75 | VFile { 76 | cwd: '/Users/tilde/Projects/oss/to-vfile', 77 | data: {}, 78 | history: [ 'readme.md' ], 79 | messages: [] 80 | } 81 | VFile { 82 | cwd: '/Users/tilde/Projects/oss/to-vfile', 83 | data: {}, 84 | history: [ '/Users/tilde/Projects/oss/to-vfile/readme.md' ], 85 | messages: [] 86 | } 87 | VFile { 88 | cwd: '/Users/tilde/Projects/oss/to-vfile', 89 | data: {}, 90 | history: [ '.git/HEAD' ], 91 | messages: [], 92 | value: 93 | } 94 | VFile { 95 | cwd: '/Users/tilde/Projects/oss/to-vfile', 96 | data: {}, 97 | history: [ '.git/HEAD' ], 98 | messages: [], 99 | value: 'ref: refs/heads/main\n' 100 | } 101 | ``` 102 | 103 | ## API 104 | 105 | This package exports the identifiers [`read`][api-read], 106 | [`readSync`][api-read-sync], [`toVFile`][api-to-vfile], 107 | [`write`][api-write], and [`writeSync`][api-write-sync]. 108 | There is no default export. 109 | 110 | ### `toVFile(description)` 111 | 112 | Create a virtual file from a description. 113 | 114 | This is like `VFile`, but it accepts a file path instead of file cotnents. 115 | 116 | If `options` is a string, URL, or buffer, it’s used as the path. 117 | Otherwise, if it’s a file, that’s returned instead. 118 | Otherwise, the options are passed through to `new VFile()`. 119 | 120 | ###### Parameters 121 | 122 | * `description` ([`Compatible`][api-compatible], optional) 123 | — path to file, file options, or file itself 124 | 125 | ###### Returns 126 | 127 | Given file or new file ([`VFile`][vfile]). 128 | 129 | ### `read(description[, options][, callback])` 130 | 131 | Create a virtual file and read it in, async. 132 | 133 | ###### Signatures 134 | 135 | * `(description[, options], Callback): undefined` 136 | * `(description[, options]): Promise` 137 | 138 | ###### Parameters 139 | 140 | * `description` ([`Compatible`][api-compatible]) 141 | — path to file, file options, or file itself 142 | * `options` ([`BufferEncoding`][api-buffer-encoding], 143 | [`ReadOptions`][api-read-options], optional) 144 | * `callback` ([`Callback`][api-callback], optional) 145 | — callback called when done 146 | 147 | ###### Returns 148 | 149 | Nothing when a callback is given, otherwise [promise][] that resolves to given 150 | file or new file ([`VFile`][vfile]). 151 | 152 | ### `readSync(description[, options])` 153 | 154 | Create a virtual file and read it in, synchronously. 155 | 156 | ###### Parameters 157 | 158 | * `description` ([`Compatible`][api-compatible]) 159 | — path to file, file options, or file itself 160 | * `options` ([`BufferEncoding`][api-buffer-encoding], 161 | [`ReadOptions`][api-read-options], optional) 162 | 163 | ###### Returns 164 | 165 | Given file or new file ([`VFile`][vfile]). 166 | 167 | ### `write(description[, options][, callback])` 168 | 169 | Create a virtual file and write it, async. 170 | 171 | ###### Signatures 172 | 173 | * `(description[, options], Callback): undefined` 174 | * `(description[, options]): Promise` 175 | 176 | ###### Parameters 177 | 178 | * `description` ([`Compatible`][api-compatible]) 179 | — path to file, file options, or file itself 180 | * `options` ([`BufferEncoding`][api-buffer-encoding], 181 | [`WriteOptions`][api-write-options], optional) 182 | * `callback` ([`Callback`][api-callback], optional) 183 | — callback called when done 184 | 185 | ###### Returns 186 | 187 | Nothing when a callback is given, otherwise [promise][] that resolves to given 188 | file or new file ([`VFile`][vfile]). 189 | 190 | ### `writeSync(description[, options])` 191 | 192 | Create a virtual file and write it, synchronously. 193 | 194 | ###### Parameters 195 | 196 | * `description` ([`Compatible`][api-compatible]) 197 | — path to file, file options, or file itself 198 | * `options` ([`BufferEncoding`][api-buffer-encoding], 199 | [`WriteOptions`][api-write-options], optional) 200 | 201 | ###### Returns 202 | 203 | Given file or new file ([`VFile`][vfile]). 204 | 205 | ### `BufferEncoding` 206 | 207 | Encodings supported by the buffer class (TypeScript type). 208 | 209 | This is a copy of the types from Node. 210 | 211 | ###### Type 212 | 213 | ```ts 214 | type BufferEncoding = 215 | | 'ascii' 216 | | 'base64' 217 | | 'base64url' 218 | | 'binary' 219 | | 'hex' 220 | | 'latin1' 221 | | 'ucs-2' 222 | | 'ucs2' 223 | | 'utf-8' 224 | | 'utf16le' 225 | | 'utf8' 226 | ``` 227 | 228 | ### `Callback` 229 | 230 | Callback called after reading or writing a file (TypeScript type). 231 | 232 | ###### Parameters 233 | 234 | * `error` (`Error`, optional) 235 | — error when reading or writing was not successful 236 | * `file` ([`VFile`][vfile], optional) 237 | — file when reading or writing was successful 238 | 239 | ###### Returns 240 | 241 | Nothing (`undefined`). 242 | 243 | ### `Compatible` 244 | 245 | URL to file, path to file, options for file, or actual file (TypeScript type). 246 | 247 | ###### Type 248 | 249 | ```ts 250 | type Compatible = Uint8Array | URL | VFile | VFileOptions | string 251 | ``` 252 | 253 | See [`VFileOptions`][vfile-options] and [`VFile`][vfile]. 254 | 255 | ### `ReadOptions` 256 | 257 | Configuration for `fs.readFile` (TypeScript type). 258 | 259 | ###### Fields 260 | 261 | * `encoding` ([`BufferEncoding`][api-buffer-encoding], optional) 262 | — encoding to read file as, will turn `file.value` into a `string` if passed 263 | * `flag` (`string`, optional) 264 | — file system flags to use 265 | 266 | ### `WriteOptions` 267 | 268 | Configuration for `fs.writeFile` (TypeScript type). 269 | 270 | ###### Fields 271 | 272 | * `encoding` ([`BufferEncoding`][api-buffer-encoding], optional) 273 | — encoding to write file as when `file.value` is a `string` 274 | * `mode` (`number | string`, optional) 275 | — file mode (permission and sticky bits) if the file was newly created 276 | * `flag` (`string`, optional) 277 | — file system flags to use 278 | 279 | ## Types 280 | 281 | This package is fully typed with [TypeScript][]. 282 | It exports the additional types 283 | [`BufferEncoding`][api-buffer-encoding], 284 | [`Callback`][api-callback], 285 | [`Compatible`][api-compatible], 286 | [`ReadOptions`][api-read-options], and 287 | [`WriteOptions`][api-write-options]. 288 | 289 | ## Compatibility 290 | 291 | Projects maintained by the unified collective are compatible with maintained 292 | versions of Node.js. 293 | 294 | When we cut a new major release, we drop support for unmaintained versions of 295 | Node. 296 | This means we try to keep the current release line, `vfile@^8`, 297 | compatible with Node.js 16. 298 | 299 | ## Contribute 300 | 301 | See [`contributing.md`][contributing] in [`vfile/.github`][health] for ways to 302 | get started. 303 | See [`support.md`][support] for ways to get help. 304 | 305 | This project has a [code of conduct][coc]. 306 | By interacting with this repository, organization, or community you agree to 307 | abide by its terms. 308 | 309 | ## License 310 | 311 | [MIT][license] © [Titus Wormer][author] 312 | 313 | 314 | 315 | [build-badge]: https://github.com/vfile/to-vfile/workflows/main/badge.svg 316 | 317 | [build]: https://github.com/vfile/to-vfile/actions 318 | 319 | [coverage-badge]: https://img.shields.io/codecov/c/github/vfile/to-vfile.svg 320 | 321 | [coverage]: https://codecov.io/github/vfile/to-vfile 322 | 323 | [downloads-badge]: https://img.shields.io/npm/dm/to-vfile.svg 324 | 325 | [downloads]: https://www.npmjs.com/package/to-vfile 326 | 327 | [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg 328 | 329 | [backers-badge]: https://opencollective.com/unified/backers/badge.svg 330 | 331 | [collective]: https://opencollective.com/unified 332 | 333 | [chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg 334 | 335 | [chat]: https://github.com/vfile/vfile/discussions 336 | 337 | [npm]: https://docs.npmjs.com/cli/install 338 | 339 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c 340 | 341 | [esmsh]: https://esm.sh 342 | 343 | [typescript]: https://www.typescriptlang.org 344 | 345 | [contributing]: https://github.com/vfile/.github/blob/main/contributing.md 346 | 347 | [support]: https://github.com/vfile/.github/blob/main/support.md 348 | 349 | [health]: https://github.com/vfile/.github 350 | 351 | [coc]: https://github.com/vfile/.github/blob/main/code-of-conduct.md 352 | 353 | [license]: license 354 | 355 | [author]: https://wooorm.com 356 | 357 | [vfile]: https://github.com/vfile/vfile 358 | 359 | [vfile-options]: https://github.com/vfile/vfile#options 360 | 361 | [promise]: https://developer.mozilla.org/Web/JavaScript/Reference/Global_Objects/Promise 362 | 363 | [api-read]: #readdescription-options-callback 364 | 365 | [api-read-sync]: #readsyncdescription-options 366 | 367 | [api-to-vfile]: #tovfiledescription 368 | 369 | [api-write]: #writedescription-options-callback 370 | 371 | [api-write-sync]: #writesyncdescription-options 372 | 373 | [api-buffer-encoding]: #bufferencoding 374 | 375 | [api-callback]: #callback 376 | 377 | [api-compatible]: #compatible 378 | 379 | [api-read-options]: #readoptions 380 | 381 | [api-write-options]: #writeoptions 382 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import {Buffer} from 'node:buffer' 3 | import fs from 'node:fs' 4 | import path from 'node:path' 5 | import process from 'node:process' 6 | import {fileURLToPath} from 'node:url' 7 | import test from 'node:test' 8 | import buffer from 'is-buffer' 9 | import {toVFile, read, readSync, write, writeSync} from 'to-vfile' 10 | 11 | const join = path.join 12 | 13 | const fixture = fs.readFileSync('readme.md', 'utf8') 14 | 15 | test('toVFile', async function (t) { 16 | assert.deepEqual( 17 | Object.keys(await import('to-vfile')).sort(), 18 | ['read', 'readSync', 'toVFile', 'write', 'writeSync'], 19 | 'should expose the public api' 20 | ) 21 | 22 | await t.test('should accept a string as `.path`', function () { 23 | const file = toVFile(join('foo', 'bar', 'baz.qux')) 24 | 25 | assert.equal(file.path, join('foo', 'bar', 'baz.qux')) 26 | assert.equal(file.basename, 'baz.qux') 27 | assert.equal(file.stem, 'baz') 28 | assert.equal(file.extname, '.qux') 29 | assert.equal(file.dirname, join('foo', 'bar')) 30 | assert.equal(file.value, undefined) 31 | }) 32 | 33 | await t.test('should accept a buffer as `.path`', function () { 34 | const file = toVFile(Buffer.from('readme.md')) 35 | 36 | assert.equal(file.path, 'readme.md') 37 | assert.equal(file.value, undefined) 38 | }) 39 | 40 | await t.test('should accept a uint array as `.path`', function () { 41 | const file = toVFile(new Uint8Array([0x61, 0x62, 0x63, 0x2e, 0x6d, 0x64])) 42 | 43 | assert.equal(file.path, 'abc.md') 44 | }) 45 | 46 | await t.test('should accept an object', function () { 47 | const file = toVFile({ 48 | dirname: join('foo', 'bar'), 49 | stem: 'baz', 50 | extname: '.qux' 51 | }) 52 | 53 | assert.equal(file.path, join('foo', 'bar', 'baz.qux')) 54 | assert.equal(file.basename, 'baz.qux') 55 | assert.equal(file.stem, 'baz') 56 | assert.equal(file.extname, '.qux') 57 | assert.equal(file.dirname, join('foo', 'bar')) 58 | assert.equal(file.value, undefined) 59 | }) 60 | 61 | await t.test('should accept a vfile', function () { 62 | const first = toVFile() 63 | const second = toVFile(first) 64 | 65 | assert.equal(first, second) 66 | }) 67 | 68 | await t.test('should accept a WHATWG URL object', function () { 69 | const dir = fileURLToPath(new URL('./', import.meta.url)) 70 | const file = toVFile(new URL('baz.qux', import.meta.url)) 71 | 72 | assert.equal(file.path, join(dir, 'baz.qux')) 73 | assert.equal(file.basename, 'baz.qux') 74 | assert.equal(file.stem, 'baz') 75 | assert.equal(file.extname, '.qux') 76 | assert.equal(file.dirname, dir.replace(/[/\\]$/, '')) 77 | assert.equal(file.value, undefined) 78 | }) 79 | }) 80 | 81 | test('readSync', async function (t) { 82 | await t.test('should fail without path', function () { 83 | assert.throws(function () { 84 | // @ts-expect-error check that a runtime error is thrown. 85 | readSync() 86 | }, /path/i) 87 | }) 88 | 89 | await t.test('should work (buffer without encoding)', function () { 90 | const file = readSync('readme.md') 91 | 92 | assert.equal(file.path, 'readme.md') 93 | assert.ok(buffer(file.value)) 94 | assert.equal(file.toString(), fixture) 95 | }) 96 | 97 | await t.test('should work (string with encoding)', function () { 98 | const file = readSync('readme.md', 'utf8') 99 | 100 | assert.equal(file.path, 'readme.md') 101 | assert.equal(typeof file.value, 'string') 102 | assert.equal(file.toString(), fixture) 103 | }) 104 | 105 | assert.throws( 106 | function () { 107 | readSync('missing.md') 108 | }, 109 | /ENOENT/, 110 | 'should throw on non-existing files' 111 | ) 112 | 113 | await t.test('should honor file.cwd when file.path is relative', function () { 114 | const cwd = path.join(process.cwd(), 'lib') 115 | const file = readSync({path: 'index.js', cwd}, 'utf8') 116 | 117 | assert.equal(typeof file.value, 'string') 118 | }) 119 | 120 | await t.test( 121 | 'should honor file.cwd when file.path is relative, even with relative cwd', 122 | function () { 123 | const file = readSync({path: 'index.js', cwd: 'lib'}, 'utf8') 124 | 125 | assert.equal(typeof file.value, 'string') 126 | } 127 | ) 128 | 129 | assert.throws( 130 | function () { 131 | readSync({ 132 | path: path.join(process.cwd(), 'core.js'), 133 | cwd: path.join(process.cwd(), 'lib') 134 | }) 135 | }, 136 | /ENOENT/, 137 | 'should ignore file.cwd when file.path is absolute' 138 | ) 139 | }) 140 | 141 | test('read', async function (t) { 142 | await t.test('should pass an error without path', async function () { 143 | await new Promise(function (ok) { 144 | // @ts-expect-error: check that a runtime error is thrown. 145 | read(undefined, function (error) { 146 | assert.ok(/path/i.test(String(error))) 147 | ok(undefined) 148 | }) 149 | }) 150 | }) 151 | 152 | await t.test('should work (buffer without encoding)', async function () { 153 | await new Promise(function (ok) { 154 | read('readme.md', function (error, file) { 155 | assert.ifError(error) 156 | assert(file, 'expected file') 157 | assert.equal(file.path, 'readme.md') 158 | assert.ok(buffer(file.value)) 159 | assert.equal(file.toString(), fixture) 160 | ok(undefined) 161 | }) 162 | }) 163 | }) 164 | 165 | await t.test( 166 | 'should work in promise mode (buffer without encoding)', 167 | async function () { 168 | const result = await read('readme.md') 169 | assert.equal(result.path, 'readme.md') 170 | assert.ok(buffer(result.value)) 171 | assert.equal(result.toString(), fixture) 172 | } 173 | ) 174 | 175 | await t.test('should work (string with encoding)', async function () { 176 | await new Promise(function (ok) { 177 | read('readme.md', 'utf8', function (error, file) { 178 | assert.ifError(error) 179 | assert(file, 'expected file') 180 | assert.equal(file.path, 'readme.md') 181 | assert.equal(typeof file.value, 'string') 182 | assert.equal(file.toString(), fixture) 183 | ok(undefined) 184 | }) 185 | }) 186 | }) 187 | 188 | await t.test( 189 | 'should work in promise mode (string with encoding)', 190 | async function () { 191 | const result = await read('readme.md', 'utf8') 192 | 193 | assert.equal(result.path, 'readme.md') 194 | assert.equal(typeof result.value, 'string') 195 | assert.equal(result.toString(), fixture) 196 | } 197 | ) 198 | 199 | await t.test( 200 | 'should return an error on non-existing files', 201 | async function () { 202 | await new Promise(function (ok) { 203 | read('missing.md', 'utf8', function (error, file) { 204 | assert(error, 'expected error') 205 | assert.equal(file, undefined) 206 | assert.ok(error instanceof Error) 207 | assert.ok(/ENOENT/.test(error.message)) 208 | ok(undefined) 209 | }) 210 | }) 211 | } 212 | ) 213 | 214 | await t.test( 215 | 'should reject on non-existing files in promise mode', 216 | async function () { 217 | try { 218 | await read('missing.md') 219 | assert.fail('should reject, not resolve') 220 | } catch (error) { 221 | assert.ok(error instanceof Error) 222 | assert.ok(/ENOENT/.test(error.message)) 223 | } 224 | } 225 | ) 226 | }) 227 | 228 | test('writeSync', async function (t) { 229 | const filePath = 'fixture.txt' 230 | const invalidFilePath = join('invalid', 'path', 'to', 'fixture.txt') 231 | 232 | await t.test('should fail without path', function () { 233 | assert.throws(function () { 234 | // @ts-expect-error check that a runtime error is thrown. 235 | writeSync() 236 | }, /path/i) 237 | }) 238 | 239 | await t.test('should work (buffer without encoding)', function () { 240 | const result = writeSync({ 241 | path: filePath, 242 | value: Buffer.from('föo') 243 | }) 244 | 245 | assert.equal(result.path, filePath) 246 | assert.equal(String(result), 'föo') 247 | assert.equal(fs.readFileSync(filePath, 'utf8'), 'föo') 248 | }) 249 | 250 | await t.test('should work (string)', function () { 251 | const result = writeSync({path: filePath, value: 'bär'}) 252 | 253 | assert.equal(result.path, filePath) 254 | assert.equal(String(result), 'bär') 255 | assert.equal(fs.readFileSync(filePath, 'utf8'), 'bär') 256 | }) 257 | 258 | await t.test('should work (undefined)', function () { 259 | const result = writeSync(filePath) 260 | 261 | assert.equal(result.path, filePath) 262 | assert.equal(String(result), '') 263 | assert.equal(fs.readFileSync(filePath, 'utf8'), '') 264 | 265 | fs.unlinkSync(filePath) 266 | }) 267 | 268 | assert.throws( 269 | function () { 270 | writeSync(invalidFilePath) 271 | }, 272 | /ENOENT/, 273 | 'should throw on files that cannot be written' 274 | ) 275 | }) 276 | 277 | test('write', async function (t) { 278 | const filePath = 'fixture.txt' 279 | const invalidFilePath = join('invalid', 'path', 'to', 'fixture.txt') 280 | 281 | await t.test('should pass an error without path', async function () { 282 | await new Promise(function (ok) { 283 | // @ts-expect-error: check that a runtime error is thrown. 284 | write(undefined, function (error) { 285 | assert.ok(/path/i.test(String(error))) 286 | ok(undefined) 287 | }) 288 | }) 289 | }) 290 | 291 | await t.test('should work (buffer without encoding)', async function () { 292 | const file = {path: filePath, value: Buffer.from('bäz')} 293 | 294 | await new Promise(function (ok) { 295 | write(file, function (error, result) { 296 | assert.ifError(error) 297 | assert(result, 'expected result') 298 | assert.equal(result.path, filePath) 299 | assert.equal(fs.readFileSync(filePath, 'utf8'), 'bäz') 300 | ok(undefined) 301 | }) 302 | }) 303 | }) 304 | 305 | await t.test('should work (string)', async function () { 306 | const file = {path: filePath, value: 'qüx'} 307 | 308 | await new Promise(function (ok) { 309 | write(file, function (error, result) { 310 | assert.ifError(error) 311 | assert(result, 'expected result') 312 | assert.equal(result.path, filePath) 313 | assert.equal(fs.readFileSync(filePath, 'utf8'), 'qüx') 314 | ok(undefined) 315 | }) 316 | }) 317 | }) 318 | 319 | await t.test('should work in promise mode (string)', async function () { 320 | const result = await write({path: filePath, value: 'qüx-promise'}) 321 | assert.equal(result.path, filePath) 322 | assert.equal(fs.readFileSync(filePath, 'utf8'), 'qüx-promise') 323 | }) 324 | 325 | await t.test('should work (string with encoding)', async function () { 326 | const file = {path: filePath, value: '62c3a472'} 327 | 328 | await new Promise(function (ok) { 329 | write(file, 'hex', function (error, result) { 330 | assert.ifError(error) 331 | assert(result, 'expected result') 332 | assert.equal(result.path, filePath) 333 | assert.equal(fs.readFileSync(filePath, 'utf8'), 'bär') 334 | ok(undefined) 335 | }) 336 | }) 337 | }) 338 | 339 | await t.test( 340 | 'should work in promise mode (string with encoding)', 341 | async function () { 342 | const result = await write( 343 | {path: filePath, value: '62c3a4722d70726f6d697365'}, 344 | 'hex' 345 | ) 346 | assert.equal(result.path, filePath) 347 | assert.equal(fs.readFileSync(filePath, 'utf8'), 'bär-promise') 348 | } 349 | ) 350 | 351 | await t.test('should work (undefined)', async function () { 352 | await new Promise(function (ok) { 353 | write(filePath, function (error, result) { 354 | const doc = fs.readFileSync(filePath, 'utf8') 355 | 356 | fs.unlinkSync(filePath) 357 | 358 | assert(result, 'expected result') 359 | assert.ifError(error) 360 | assert.equal(result.path, filePath) 361 | assert.equal(doc, '') 362 | ok(undefined) 363 | }) 364 | }) 365 | }) 366 | 367 | await t.test('should work in promise mode (undefined)', async function () { 368 | const result = await write(filePath) 369 | 370 | const doc = fs.readFileSync(filePath, 'utf8') 371 | 372 | fs.unlinkSync(filePath) 373 | 374 | assert.equal(result.path, filePath) 375 | assert.equal(doc, '') 376 | }) 377 | 378 | await t.test( 379 | 'should pass an error for files that cannot be written', 380 | async function () { 381 | await new Promise(function (ok) { 382 | write(invalidFilePath, function (error) { 383 | assert(error, 'expected error') 384 | assert.ok(/ENOENT/.test(error.message)) 385 | ok(undefined) 386 | }) 387 | }) 388 | } 389 | ) 390 | 391 | await t.test( 392 | 'should reject for files that cannot be written in promise mode', 393 | async function () { 394 | try { 395 | await write(invalidFilePath) 396 | assert.fail('should reject, not resolve') 397 | } catch (error) { 398 | assert(error instanceof Error) 399 | assert.ok(/ENOENT/.test(error.message)) 400 | } 401 | } 402 | ) 403 | }) 404 | --------------------------------------------------------------------------------