file-fetch
Advanced tools
Comparing version 1.7.0 to 2.0.0
121
index.js
@@ -1,117 +0,10 @@ | ||
/* global URL */ | ||
import factory from './factory.js' | ||
import fetch from './fetch.js' | ||
const fs = require('fs') | ||
const path = require('path') | ||
const { promisify } = require('util') | ||
const getStream = require('get-stream') | ||
const { contentType } = require('mime-types') | ||
const { Headers } = require('node-fetch') | ||
const ReadableError = require('readable-error') | ||
const { Readable } = require('readable-stream') | ||
const Headers = globalThis.Headers | ||
const { R_OK } = fs.constants | ||
const access = promisify(fs.access) | ||
const stat = promisify(fs.stat) | ||
function decodeIRI (iri, baseDir, baseURL) { | ||
// IRIs without file scheme are used directly | ||
if (!iri.startsWith('file:') && !baseURL) { | ||
return path.join(baseDir, iri) | ||
} | ||
const pathname = decodeURIComponent(new URL(iri, baseURL).pathname) | ||
// remove the leading slash for IRIs with file scheme and relative path | ||
if (!iri.startsWith('file:/') && | ||
(!baseURL || !pathname.startsWith('/'))) { | ||
return './' + (path.join(baseDir, '.' + pathname)) | ||
} | ||
return pathname | ||
export { | ||
fetch as default, | ||
factory, | ||
Headers | ||
} | ||
async function silentFileSize (pathname) { | ||
try { | ||
return (await stat(pathname)).size | ||
} catch (err) { | ||
return null | ||
} | ||
} | ||
function response (status, body, headers) { | ||
return { | ||
status: status, | ||
ok: status >= 200 && status <= 299, | ||
headers: new Headers(headers), | ||
body: body, | ||
text: async () => getStream(body), | ||
json: async () => JSON.parse(await getStream(body)) | ||
} | ||
} | ||
function create ({ baseDir = '', baseURL } = {}) { | ||
return async function fetch (iri, { body, contentTypeLookup = contentType, method = 'GET' } = {}) { | ||
method = method.toUpperCase() | ||
const pathname = decodeIRI(iri, baseDir, baseURL) | ||
const extension = path.extname(pathname) | ||
if (method === 'GET') { | ||
const size = await silentFileSize(pathname) | ||
return new Promise((resolve) => { | ||
const stream = fs.createReadStream(pathname) | ||
stream.on('error', () => { | ||
resolve(response(404, new ReadableError(new Error('File not found')))) | ||
}) | ||
stream.on('open', () => { | ||
resolve(response(200, stream, { | ||
'content-length': size.toString(), | ||
'content-type': contentTypeLookup(extension) || contentType(extension) | ||
})) | ||
}) | ||
}) | ||
} | ||
if (method === 'HEAD') { | ||
try { | ||
await access(pathname, R_OK) | ||
} catch (error) { | ||
return response(404, new ReadableError(new Error('File not found'))) | ||
} | ||
const stream = new Readable() | ||
stream.push(null) | ||
return response(200, stream, { | ||
'content-type': contentTypeLookup(extension) || contentType(extension) | ||
}) | ||
} | ||
if (method === 'PUT') { | ||
if (!body) { | ||
return response(406, new ReadableError(new Error('body required'))) | ||
} | ||
return new Promise((resolve) => { | ||
body.pipe(fs.createWriteStream(pathname)).on('finish', () => { | ||
resolve(response(201)) | ||
}).on('error', (err) => { | ||
resolve(response(500, new ReadableError(err))) | ||
}) | ||
}) | ||
} | ||
return response(405, new ReadableError(new Error('method not allowed'))) | ||
} | ||
} | ||
const fetch = create() | ||
fetch.Headers = Headers | ||
fetch.create = create | ||
module.exports = fetch |
{ | ||
"name": "file-fetch", | ||
"version": "1.7.0", | ||
"version": "2.0.0", | ||
"description": "fetch for read and write access to the local file system", | ||
"type": "module", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "standard && nyc --reporter=lcov --reporter=text mocha" | ||
"test": "stricter-standard && c8 --reporter=lcov --reporter=text-summary mocha" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/bergos/file-fetch.git" | ||
"url": "https://github.com/bergos/file-fetch.git" | ||
}, | ||
@@ -20,9 +21,2 @@ "keywords": [ | ||
], | ||
"nyc": { | ||
"statements": 100, | ||
"branches": 100, | ||
"lines": 100, | ||
"functions": 100, | ||
"check-coverage": true | ||
}, | ||
"author": "Thomas Bergwinkl <bergi@axolotlfarm.org> (https://www.bergnet.org/people/bergi/card#me)", | ||
@@ -38,13 +32,13 @@ "contributors": [ | ||
"dependencies": { | ||
"get-stream": "^6.0.1", | ||
"mime-types": "^2.1.30", | ||
"node-fetch": "^2.6.1", | ||
"readable-error": "^1.0.0", | ||
"readable-stream": "^3.6.0" | ||
"mime-types": "^2.1.35", | ||
"readable-stream": "^4.4.2", | ||
"stream-chunks": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"mocha": "^8.4.0", | ||
"nyc": "^15.1.0", | ||
"standard": "^16.0.3" | ||
"c8": "^8.0.1", | ||
"is-stream": "^3.0.0", | ||
"mocha": "^10.2.0", | ||
"stricter-standard": "^0.3.0", | ||
"temp": "^0.9.4" | ||
} | ||
} |
123
README.md
# file-fetch | ||
`file-fetch` is a [nodeify-fetch](https://www.npmjs.com/package/nodeify-fetch) compatible fetch for read and write access to the local file system using `file://` URLs (including | ||
[![build status](https://img.shields.io/github/actions/workflow/status/bergos/file-fetch/test.yaml?branch=master)](https://github.com/bergos/file-fetch/actions/workflows/test.yaml) | ||
[![npm version](https://img.shields.io/npm/v/file-fetch.svg)](https://www.npmjs.com/package/file-fetch) | ||
`file-fetch` is a [nodeify-fetch](https://www.npmjs.com/package/nodeify-fetch) compatible fetch for read and write access to the local file system using `file:` URLs and URIs (including | ||
implicit ones using relative paths). | ||
@@ -8,20 +11,122 @@ | ||
Only the URL is required to read a file: | ||
### Read | ||
Reading a file from the file system is as easy as fetching it on the Web. | ||
Call `fetch` with the URL, and the content is provided as `Readable` stream in `res.body`. | ||
The example below uses an absolute URL, but relative paths are also supported. | ||
See the [Supported URLs and URIs](#supported-urls-and-uris) section for more details. | ||
```js | ||
const fileFetch = require('file-fetch') | ||
import fetch from 'file-fetch' | ||
fileFetch('file://etc/hosts').then((res) => { | ||
res.body.pipe(stdout) | ||
}) | ||
const res = await fetch(new URL('example.js', import.meta.url)) | ||
res.body.pipe(process.stdout) | ||
``` | ||
To write files the method `PUT` must be used and readable stream must be given as body: | ||
It's also possible to handle the content without streams. | ||
The async `res.text()` method returns the whole content as a string. | ||
```js | ||
fileFetch('file://tmp/example.log', { | ||
import fetch from 'file-fetch' | ||
const res = await fetch(new URL('example.js', import.meta.url)) | ||
console.log(await res.text()) | ||
``` | ||
A similar method `res.json()` is available to parse JSON content and return the parsed result. | ||
```js | ||
import fetch from 'file-fetch' | ||
const res = await fetch(new URL('example.js', import.meta.url)) | ||
console.log(await res.json()) | ||
``` | ||
### Write | ||
Writing content to a file is done with the same function but with the `PUT` method. | ||
The content must be provided as a `string` or a `Readable` stream object. | ||
```js | ||
import fetch from 'file-fetch' | ||
await fetch('file:///tmp/example.log', { | ||
method: 'PUT', | ||
body: stream | ||
body: 'test' | ||
}) | ||
``` | ||
```js | ||
import fetch from 'file-fetch' | ||
import { Readable } from 'readable-stream' | ||
await fetch('file:///tmp/example.log', { | ||
method: 'PUT', | ||
body: Readable.from(['test']) | ||
}) | ||
``` | ||
## Options | ||
`file-fetch` supports the following non-standard options: | ||
- `baseURL`: A `string` or `URL` used to resolve relative paths and URIs. | ||
- `contentType`: A `string` or `function` to determine the media type based on the file extension or a fixed value. | ||
It can be useful if file extensions or media types not covered by [mime-db](https://www.npmjs.com/package/mime-db) are required. | ||
## Custom fetch with fixed baseURL or contentType lookup | ||
Custom fetch instances can be useful if requests should be processed with relative paths to a directory that is not the current working directory. | ||
The `contentType` argument can also be predefined for the instance. | ||
The example below shows how to set the `baseURL` to a relative path of the current script and how to use a custom `contentType` function: | ||
```js | ||
import { factory as fetchFactory } from 'file-fetch' | ||
const baseURL = new URL('examples', import.meta.url) | ||
const contentType = ext => ext === 'json' ? 'application/ld+json' : 'application/octet-stream' | ||
const fetch = fetchFactory({ baseURL, contentType }) | ||
const res = await fetch('example.js') | ||
const text = await res.text() | ||
``` | ||
## Supported URLs and URIs | ||
Different styles of URLs and URIs are supported. | ||
### Absolute URLs | ||
An absolute URL for a `file` schema must start with `file:///`. | ||
No further resolve logic is used. | ||
Example: | ||
``` | ||
file:///home/user/tmp/content.txt | ||
``` | ||
### URIs | ||
URIs are supported for use cases where a `file` scheme is required to distinguish identifiers by scheme and if relative paths are required. | ||
The [relative paths](#relative-paths) logic is used to resolve the full URL. | ||
Example: | ||
``` | ||
file:tmp/content.txt | ||
``` | ||
### Relative paths | ||
Relative paths are resolved with the given `baseURL` or, if not given, with the working directory. | ||
Example: | ||
``` | ||
tmp/content.txt | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
24448
3
22
518
132
0
Yes
5
19
+ Addedstream-chunks@^1.0.0
+ Addedabort-controller@3.0.0(transitive)
+ Addedbase64-js@1.5.1(transitive)
+ Addedbuffer@6.0.3(transitive)
+ Addedevent-target-shim@5.0.1(transitive)
+ Addedevents@3.3.0(transitive)
+ Addedieee754@1.2.1(transitive)
+ Addedprocess@0.11.10(transitive)
+ Addedreadable-stream@4.5.2(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedstream-chunks@1.0.0(transitive)
+ Addedstring_decoder@1.3.0(transitive)
- Removedget-stream@^6.0.1
- Removednode-fetch@^2.6.1
- Removedreadable-error@^1.0.0
- Removedcore-util-is@1.0.3(transitive)
- Removedget-stream@6.0.1(transitive)
- Removedinherits@2.0.4(transitive)
- Removedisarray@1.0.0(transitive)
- Removednode-fetch@2.7.0(transitive)
- Removedprocess-nextick-args@2.0.1(transitive)
- Removedreadable-error@1.0.0(transitive)
- Removedreadable-stream@2.3.83.6.2(transitive)
- Removedsafe-buffer@5.1.2(transitive)
- Removedstring_decoder@1.1.1(transitive)
- Removedtr46@0.0.3(transitive)
- Removedutil-deprecate@1.0.2(transitive)
- Removedwebidl-conversions@3.0.1(transitive)
- Removedwhatwg-url@5.0.0(transitive)
Updatedmime-types@^2.1.35
Updatedreadable-stream@^4.4.2