Compare commits
71
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c54489a35 | ||
|
|
b76652eaaf | ||
|
|
f23cdad2a8 | ||
|
|
40e6f7ea13 | ||
|
|
aef02263c1 | ||
|
|
4dc9ba543f | ||
|
|
1a29178875 | ||
|
|
c87ebb6711 | ||
|
|
b363c0fa81 | ||
|
|
eb1d304ede | ||
|
|
9ebbbe6553 | ||
|
|
a1a182e597 | ||
|
|
57e4daef36 | ||
|
|
0455796ede | ||
|
|
2acec9c0dc | ||
|
|
20ee08f33e | ||
|
|
f077471a23 | ||
|
|
dc29588366 | ||
|
|
8ab587d340 | ||
|
|
f61ee01955 | ||
|
|
5dbcbb91b4 | ||
|
|
c5c2b52154 | ||
|
|
836709fc65 | ||
|
|
2c11647222 | ||
|
|
9022f8fc70 | ||
|
|
0e07457246 | ||
|
|
46476faf49 | ||
|
|
b86b1409de | ||
|
|
a08f394f1b | ||
|
|
c2903ca530 | ||
|
|
0b028432d0 | ||
|
|
f64c20694f | ||
|
|
613160ba58 | ||
|
|
efdad69698 | ||
|
|
e00f849f6d | ||
|
|
52f296c283 | ||
|
|
701103ffd2 | ||
|
|
ada23da6ee | ||
|
|
16f312c543 | ||
|
|
5ba554efd2 | ||
|
|
4747497b52 | ||
|
|
09fd40448a | ||
|
|
8d14e88040 | ||
|
|
8ef89adad4 | ||
|
|
a3b0d62b9d | ||
|
|
97a084b5e8 | ||
|
|
a8fed4e338 | ||
|
|
483973cf25 | ||
|
|
cc929f3714 | ||
|
|
5584886f0f | ||
|
|
0d2cd8b9b7 | ||
|
|
149030ddbe | ||
|
|
f08e6a6773 | ||
|
|
eed6a1dddd | ||
|
|
3b12a39ea2 | ||
|
|
591a28ed94 | ||
|
|
40f4568c95 | ||
|
|
a74fb90d95 | ||
|
|
689bee122b | ||
|
|
50b89fcdce | ||
|
|
40c6ff45df | ||
|
|
5b2bab5365 | ||
|
|
b75fb395b4 | ||
|
|
3217da9bc7 | ||
|
|
ae6b565ae6 | ||
|
|
26ca0e39b4 | ||
|
|
99388a79bb | ||
|
|
cf379d10e0 | ||
|
|
1d1faebe18 | ||
|
|
903a4c1bb0 | ||
|
|
126b4ec697 |
@@ -0,0 +1,23 @@
|
||||
<!-- Thanks for contributing! -->
|
||||
|
||||
## The purpose of this PR is:
|
||||
...
|
||||
|
||||
## This is what had to change:
|
||||
...
|
||||
|
||||
## This is what like reviewers to know:
|
||||
...
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------
|
||||
|
||||
<!-- Mark what you have done with [x], Remove unnecessary ones. Add new tasks that may fit (like TODO's) -->
|
||||
- [ ] I prefixed the PR-title with `docs: `, `fix(area): `, `feat(area): ` or `breaking(area): `
|
||||
- [ ] I updated the README.md
|
||||
- [ ] I Added unit test(s)
|
||||
|
||||
-------------------------------------------------------------------------------------------------
|
||||
|
||||
<!-- Add a `- fix #_NUMBER_` line for every Issue this PR solves. Do not comma separate them -->
|
||||
- fix #000
|
||||
@@ -0,0 +1,25 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: get-stream
|
||||
versions:
|
||||
- 6.0.0
|
||||
- 6.0.1
|
||||
- dependency-name: typescript
|
||||
versions:
|
||||
- 4.1.3
|
||||
- 4.1.4
|
||||
- 4.1.5
|
||||
- 4.2.2
|
||||
- 4.2.3
|
||||
- 4.2.4
|
||||
- dependency-name: xo
|
||||
versions:
|
||||
- 0.37.1
|
||||
- 0.38.1
|
||||
- 0.38.2
|
||||
@@ -13,43 +13,21 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
node: ["14", "12", engines]
|
||||
exclude:
|
||||
# On Windows, run tests with only the LTS environments.
|
||||
- os: windows-latest
|
||||
node: engines
|
||||
- os: windows-latest
|
||||
node: "14"
|
||||
# On macOS, run tests with only the LTS environments.
|
||||
- os: macOS-latest
|
||||
node: engines
|
||||
- os: macOS-latest
|
||||
node: "14"
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node: ["18", "20", "22"]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Get Node.JS version from package.json
|
||||
if: matrix.node == 'engines'
|
||||
id: get-version
|
||||
run: echo ::set-output name=node::$(npx --q minimum-node-version)
|
||||
|
||||
- uses: actions/setup-node@v2-beta
|
||||
if: matrix.node != 'engines'
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- uses: actions/setup-node@v2-beta
|
||||
if: matrix.node == 'engines'
|
||||
with:
|
||||
node-version: ${{steps.get-version.outputs.node}}
|
||||
|
||||
- run: npm install
|
||||
|
||||
- run: npm run report -- --colors
|
||||
|
||||
- run: corepack enable
|
||||
- run: corepack prepare pnpm@latest --activate
|
||||
- run: pnpm install
|
||||
- run: pnpm test
|
||||
- run: pnpm run report -- --colors
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "**.js"
|
||||
- "**eslint**"
|
||||
- "package.json"
|
||||
- ".github/workflows/lint.yml"
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2-beta
|
||||
with:
|
||||
node-version: 14
|
||||
- run: npm install
|
||||
- run: npm run lint
|
||||
+7
-3
@@ -16,6 +16,8 @@ pids
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
@@ -47,8 +49,8 @@ typings/
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
# Optional cache
|
||||
.cache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
@@ -62,4 +64,6 @@ typings/
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
index.d.ts
|
||||
*.d.ts
|
||||
*.d.cts
|
||||
.DS_Store
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"cSpell.words": ["Wärting"]
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
## v2.0.0
|
||||
|
||||
> Note: This release was previously published as `1.0.7`, but as it contains breaking changes, we renamed it to `2.0.0`.
|
||||
|
||||
- **Breaking:** minimum supported Node.js version is now 10.17.
|
||||
- **Breaking:** `buffer` option has been removed.
|
||||
- Enhance: create TypeScript declarations from JSDoc (#45).
|
||||
- Enhance: operate on blob parts (byte sequence) (#44).
|
||||
- Enhance: use a `WeakMap` for private properties (#42) .
|
||||
- Other: update formatting.
|
||||
|
||||
## v1.0.6
|
||||
|
||||
- Enhance: use upstream Blob directly in typings (#38)
|
||||
- Other: update dependencies
|
||||
|
||||
## v1.0.5
|
||||
|
||||
- Other: no change to code, update dev dependency to address vulnerability reports
|
||||
|
||||
## v1.0.4
|
||||
|
||||
- Other: general code rewrite to pass linting, prepare for `node-fetch` release v3
|
||||
|
||||
## v1.0.3
|
||||
|
||||
- Fix: package.json export `blob.js` properly now
|
||||
|
||||
## v1.0.2
|
||||
|
||||
- Other: fix test integration
|
||||
|
||||
## v1.0.1
|
||||
|
||||
- Other: readme update
|
||||
|
||||
## v1.0.0
|
||||
|
||||
- Major: initial release
|
||||
@@ -1,39 +1,118 @@
|
||||
# fetch-blob
|
||||
# fetch-blob-fixed
|
||||
|
||||
[![npm version][npm-image]][npm-url]
|
||||
[![build status][ci-image]][ci-url]
|
||||
[![coverage status][codecov-image]][codecov-url]
|
||||
[![install size][install-size-image]][install-size-url]
|
||||
|
||||
A Blob implementation in Node.js, originally from [node-fetch](https://github.com/node-fetch/node-fetch).
|
||||
|
||||
Use the built-in [`Blob`](https://nodejs.org/docs/latest-v18.x/api/buffer.html#class-blob) in Node.js 18 and later.
|
||||
|
||||
|
||||
This package is a fork from the original `fetch-blob` package, which was deprecated in favor of the built-in `Blob` class in Node.js 18 and later. This fork is maintained for specific use cases that require a polyfill and for packages that already use this implementation. Credit to Jimmy Wärting and other contributors for the original implementation.
|
||||
|
||||
Packages should migrate to the built-in `Blob` class in Node.js 18 and later. This package will not be maintained forever. Committing to support through July 2028, after which it will be deprecated.
|
||||
|
||||
This package was created to remove deprecated packages from the original `fetch-blob` package and allow it to work with modern Node.js versions when necessary. It is not intended to be a long-term solution. This was made to ensure I did not have deprecated packages in my project.
|
||||
|
||||
I also moved this project to `pnpm`, as it is the package manager I use and prefer. It is not a requirement to use this package, but if you develop anything further on top of this, `pnpm` is recommended for consistency.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install fetch-blob
|
||||
pnpm install fetch-blob-fixed
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Differences from other Blobs</summary>
|
||||
|
||||
- Unlike NodeJS `buffer.Blob` (Added in: v15.7.0) and browser native Blob this polyfilled version can't be sent via PostMessage
|
||||
- This blob version is more arbitrary, it can be constructed with blob parts that isn't a instance of itself
|
||||
it has to look and behave as a blob to be accepted as a blob part.
|
||||
- The benefit of this is that you can create other types of blobs that don't contain any internal data that has to be read in other ways, such as the `BlobDataItem` created in `from.js` that wraps a file path into a blob-like item and read lazily (nodejs plans to [implement this][fs-blobs] as well)
|
||||
- The `blob.stream()` is the most noticeable differences. It returns a WHATWG stream now. to keep it as a node stream you would have to do:
|
||||
|
||||
```js
|
||||
import {Readable} from 'stream'
|
||||
const stream = Readable.from(blob.stream())
|
||||
```
|
||||
</details>
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const Blob = require('fetch-blob');
|
||||
const fetch = require('node-fetch');
|
||||
// Ways to import
|
||||
import { Blob } from 'fetch-blob'
|
||||
import { File } from 'fetch-blob/file.js'
|
||||
|
||||
fetch('https://httpbin.org/post', {
|
||||
method: 'POST',
|
||||
body: new Blob(['Hello World'], { type: 'text/plain' })
|
||||
})
|
||||
.then(res => res.json());
|
||||
.then(json => console.log(json));
|
||||
const { Blob } = await import('fetch-blob')
|
||||
|
||||
|
||||
// Ways to read the blob:
|
||||
const blob = new Blob(['hello, world'])
|
||||
|
||||
await blob.text()
|
||||
await blob.arrayBuffer()
|
||||
for await (let chunk of blob.stream()) { ... }
|
||||
blob.stream().getReader().read()
|
||||
blob.stream().getReader({mode: 'byob'}).read(view)
|
||||
```
|
||||
|
||||
See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [tests](https://github.com/node-fetch/fetch-blob/blob/master/test.js) for more details.
|
||||
### Blob part backed up by filesystem
|
||||
|
||||
[npm-image]: https://flat.badgen.net/npm/v/fetch-blob
|
||||
[npm-url]: https://www.npmjs.com/package/fetch-blob
|
||||
[ci-image]: https://github.com/node-fetch/fetch-blob/workflows/CI/badge.svg
|
||||
[ci-url]: https://github.com/node-fetch/fetch-blob/actions
|
||||
[codecov-image]: https://flat.badgen.net/codecov/c/github/node-fetch/fetch-blob/master
|
||||
[codecov-url]: https://codecov.io/gh/node-fetch/fetch-blob
|
||||
[install-size-image]: https://flat.badgen.net/packagephobia/install/fetch-blob
|
||||
[install-size-url]: https://packagephobia.now.sh/result?p=fetch-blob
|
||||
`fetch-blob/from.js` comes packed with tools to convert any filepath into either a Blob or a File
|
||||
It will not read the content into memory. It will only stat the file for last modified date and file size.
|
||||
|
||||
```js
|
||||
// The default export is sync and use fs.stat to retrieve size & last modified as a blob
|
||||
import {File, Blob, blobFrom, blobFromSync, fileFrom, fileFromSync} from 'fetch-blob/from.js'
|
||||
|
||||
const fsFile = fileFromSync('./2-GiB-file.bin', 'application/octet-stream')
|
||||
const fsBlob = await blobFrom('./2-GiB-file.mp4')
|
||||
|
||||
// Not a 4 GiB memory snapshot, just holds references
|
||||
// points to where data is located on the disk
|
||||
const blob = new Blob([fsFile, fsBlob, 'memory', new Uint8Array(10)])
|
||||
console.log(blob.size) // ~4 GiB
|
||||
```
|
||||
|
||||
`blobFrom|blobFromSync|fileFrom|fileFromSync(path, [mimetype])`
|
||||
|
||||
### Creating a temporary file on the disk
|
||||
(requires [FinalizationRegistry] - node v14.6)
|
||||
|
||||
When using both `createTemporaryBlob` and `createTemporaryFile`
|
||||
then you will write data to the temporary folder in their respective OS.
|
||||
The arguments can be anything that [fsPromises.writeFile] supports. NodeJS
|
||||
v14.17.0+ also supports writing (async)Iterable streams and passing in a
|
||||
AbortSignal, so both NodeJS stream and whatwg streams are supported. When the
|
||||
file have been written it will return a Blob/File handle with a references to
|
||||
this temporary location on the disk. When you no longer have a references to
|
||||
this Blob/File anymore and it have been GC then it will automatically be deleted.
|
||||
|
||||
This files are also unlinked upon exiting the process.
|
||||
```js
|
||||
import { createTemporaryBlob, createTemporaryFile } from 'fetch-blob/from.js'
|
||||
|
||||
const req = new Request('https://httpbin.org/image/png')
|
||||
const res = await fetch(req)
|
||||
const type = res.headers.get('content-type')
|
||||
const signal = req.signal
|
||||
let blob = await createTemporaryBlob(res.body, { type, signal })
|
||||
// const file = createTemporaryBlob(res.body, 'img.png', { type, signal })
|
||||
blob = undefined // loosing references will delete the file from disk
|
||||
```
|
||||
|
||||
- `createTemporaryBlob(data, { type, signal })`
|
||||
- `createTemporaryFile(data, FileName, { type, signal, lastModified })`
|
||||
|
||||
### Creating Blobs backed up by other async sources
|
||||
Our Blob & File class are more generic then any other polyfills in the way that it can accept any blob look-a-like item
|
||||
An example of this is that our blob implementation can be constructed with parts coming from [BlobDataItem](https://github.com/node-fetch/fetch-blob/blob/8ef89adad40d255a3bbd55cf38b88597c1cd5480/from.js#L32) (aka a filepath) or from [buffer.Blob](https://nodejs.org/api/buffer.html#buffer_new_buffer_blob_sources_options), It dose not have to implement all the methods - just enough that it can be read/understood by our Blob implementation. The minium requirements is that it has `Symbol.toStringTag`, `size`, `slice()`, `stream()` methods (the stream method
|
||||
can be as simple as being a sync or async iterator that yields Uint8Arrays. If you then wrap it in our Blob or File `new Blob([blobDataItem])` then you get all of the other methods that should be implemented in a blob or file (aka: text(), arrayBuffer() and type and a ReadableStream)
|
||||
|
||||
An example of this could be to create a file or blob like item coming from a remote HTTP request. Or from a DataBase
|
||||
|
||||
See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [tests](https://github.com/node-fetch/fetch-blob/blob/master/test.js) for more details of how to use the Blob.
|
||||
|
||||
|
||||
|
||||
[npm-url]: https://www.npmjs.com/package/fetch-blob-fixed
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Blob } from './index.js'
|
||||
|
||||
const _File = class File extends Blob {
|
||||
#lastModified = 0
|
||||
#name = ''
|
||||
|
||||
/**
|
||||
* @param {*[]} fileBits
|
||||
* @param {string} fileName
|
||||
* @param {{lastModified?: number, type?: string}} options
|
||||
*/// @ts-ignore
|
||||
constructor (fileBits, fileName, options = {}) {
|
||||
if (arguments.length < 2) {
|
||||
throw new TypeError(`Failed to construct 'File': 2 arguments required, but only ${arguments.length} present.`)
|
||||
}
|
||||
super(fileBits, options)
|
||||
|
||||
if (options === null) options = {}
|
||||
|
||||
// Simulate WebIDL type casting for NaN value in lastModified option.
|
||||
const lastModified = options.lastModified === undefined ? Date.now() : Number(options.lastModified)
|
||||
if (!Number.isNaN(lastModified)) {
|
||||
this.#lastModified = lastModified
|
||||
}
|
||||
|
||||
this.#name = String(fileName)
|
||||
}
|
||||
|
||||
get name () {
|
||||
return this.#name
|
||||
}
|
||||
|
||||
get lastModified () {
|
||||
return this.#lastModified
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return 'File'
|
||||
}
|
||||
|
||||
static [Symbol.hasInstance] (object) {
|
||||
return !!object && object instanceof Blob &&
|
||||
/^(File)$/.test(object[Symbol.toStringTag])
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {typeof globalThis.File} */// @ts-ignore
|
||||
export const File = _File
|
||||
@@ -0,0 +1,165 @@
|
||||
import {
|
||||
realpathSync,
|
||||
statSync,
|
||||
rmdirSync,
|
||||
createReadStream,
|
||||
promises as fs
|
||||
} from 'node:fs'
|
||||
import { basename, sep, join } from 'node:path'
|
||||
import { tmpdir } from 'node:os'
|
||||
import process from 'node:process'
|
||||
|
||||
import { File } from './file.js'
|
||||
import { Blob } from './index.js'
|
||||
|
||||
const { stat, mkdtemp } = fs
|
||||
let i = 0, tempDir, registry
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} path filepath on the disk
|
||||
* @param {string} [type] mimetype to use
|
||||
*/
|
||||
const blobFromSync = (path, type) => fromBlob(statSync(path), path, type)
|
||||
|
||||
/**
|
||||
* @param {string} path filepath on the disk
|
||||
* @param {string} [type] mimetype to use
|
||||
* @returns {Promise<Blob>}
|
||||
*/
|
||||
const blobFrom = (path, type) => stat(path).then(stat => fromBlob(stat, path, type))
|
||||
|
||||
/**
|
||||
* @param {string} path filepath on the disk
|
||||
* @param {string} [type] mimetype to use
|
||||
* @returns {Promise<File>}
|
||||
*/
|
||||
const fileFrom = (path, type) => stat(path).then(stat => fromFile(stat, path, type))
|
||||
|
||||
/**
|
||||
* @param {string} path filepath on the disk
|
||||
* @param {string} [type] mimetype to use
|
||||
*/
|
||||
const fileFromSync = (path, type) => fromFile(statSync(path), path, type)
|
||||
|
||||
// @ts-ignore
|
||||
const fromBlob = (stat, path, type = '') => new Blob([new BlobDataItem({
|
||||
path,
|
||||
size: stat.size,
|
||||
lastModified: stat.mtimeMs,
|
||||
start: 0
|
||||
})], { type })
|
||||
|
||||
// @ts-ignore
|
||||
const fromFile = (stat, path, type = '') => new File([new BlobDataItem({
|
||||
path,
|
||||
size: stat.size,
|
||||
lastModified: stat.mtimeMs,
|
||||
start: 0
|
||||
})], basename(path), { type, lastModified: stat.mtimeMs })
|
||||
|
||||
/**
|
||||
* Creates a temporary blob backed by the filesystem.
|
||||
* NOTE: requires node.js v14 or higher to use FinalizationRegistry
|
||||
*
|
||||
* @param {*} data Same as fs.writeFile data
|
||||
* @param {BlobPropertyBag & {signal?: AbortSignal}} options
|
||||
* @param {AbortSignal} [signal] in case you wish to cancel the write operation
|
||||
* @returns {Promise<Blob>}
|
||||
*/
|
||||
const createTemporaryBlob = async (data, {signal, type} = {}) => {
|
||||
registry = registry || new FinalizationRegistry(fs.unlink)
|
||||
tempDir = tempDir || await mkdtemp(realpathSync(tmpdir()) + sep)
|
||||
const id = `${i++}`
|
||||
const destination = join(tempDir, id)
|
||||
if (data instanceof ArrayBuffer) data = new Uint8Array(data)
|
||||
await fs.writeFile(destination, data, { signal })
|
||||
const blob = await blobFrom(destination, type)
|
||||
registry.register(blob, destination)
|
||||
return blob
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary File backed by the filesystem.
|
||||
* Pretty much the same as constructing a new File(data, name, options)
|
||||
*
|
||||
* NOTE: requires node.js v14 or higher to use FinalizationRegistry
|
||||
* @param {*} data
|
||||
* @param {string} name
|
||||
* @param {FilePropertyBag & {signal?: AbortSignal}} opts
|
||||
* @returns {Promise<File>}
|
||||
*/
|
||||
const createTemporaryFile = async (data, name, opts) => {
|
||||
const blob = await createTemporaryBlob(data)
|
||||
return new File([blob], name, opts)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a blob backed up by a file on the disk
|
||||
* with minium requirement. Its wrapped around a Blob as a blobPart
|
||||
* so you have no direct access to this.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
class BlobDataItem {
|
||||
#path
|
||||
#start
|
||||
|
||||
constructor (options) {
|
||||
this.#path = options.path
|
||||
this.#start = options.start
|
||||
this.size = options.size
|
||||
this.lastModified = options.lastModified
|
||||
this.originalSize = options.originalSize === undefined
|
||||
? options.size
|
||||
: options.originalSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Slicing arguments is first validated and formatted
|
||||
* to not be out of range by Blob.prototype.slice
|
||||
*/
|
||||
slice (start, end) {
|
||||
return new BlobDataItem({
|
||||
path: this.#path,
|
||||
lastModified: this.lastModified,
|
||||
originalSize: this.originalSize,
|
||||
size: end - start,
|
||||
start: this.#start + start
|
||||
})
|
||||
}
|
||||
|
||||
async * stream () {
|
||||
const { mtimeMs, size } = await stat(this.#path)
|
||||
|
||||
if (mtimeMs > this.lastModified || this.originalSize !== size) {
|
||||
throw new DOMException('The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.', 'NotReadableError')
|
||||
}
|
||||
|
||||
yield * createReadStream(this.#path, {
|
||||
start: this.#start,
|
||||
end: this.#start + this.size - 1
|
||||
})
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return 'Blob'
|
||||
}
|
||||
}
|
||||
|
||||
process.once('exit', () => {
|
||||
tempDir && rmdirSync(tempDir, { recursive: true })
|
||||
})
|
||||
|
||||
export default blobFromSync
|
||||
export {
|
||||
Blob,
|
||||
blobFrom,
|
||||
blobFromSync,
|
||||
createTemporaryBlob,
|
||||
File,
|
||||
fileFrom,
|
||||
fileFromSync,
|
||||
createTemporaryFile
|
||||
}
|
||||
@@ -1,185 +1,254 @@
|
||||
const {Readable} = require('stream');
|
||||
/*! fetch-blob. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||
|
||||
/**
|
||||
* @type {WeakMap<Blob, {type: string, size: number, parts: (Blob | Buffer)[] }>}
|
||||
*/
|
||||
const wm = new WeakMap();
|
||||
|
||||
async function * read(parts) {
|
||||
for (const part of parts) {
|
||||
if ('stream' in part) {
|
||||
yield * part.stream();
|
||||
} else {
|
||||
yield part;
|
||||
}
|
||||
}
|
||||
if (!globalThis.ReadableStream) {
|
||||
try {
|
||||
const process = await import('node:process').then(m => m.default)
|
||||
const { emitWarning } = process
|
||||
try {
|
||||
process.emitWarning = () => {}
|
||||
const streams = await import('node:stream/web').then(m => m.default)
|
||||
Object.assign(globalThis, streams)
|
||||
process.emitWarning = emitWarning
|
||||
} catch (error) {
|
||||
process.emitWarning = emitWarning
|
||||
throw error
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
// 64 KiB (same size chrome slice theirs blob into Uint8array's)
|
||||
const POOL_SIZE = 65536
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} object
|
||||
* @returns {T is Blob}
|
||||
* @param {(Blob | Uint8Array)[]} parts
|
||||
* @param {boolean} clone
|
||||
* @returns {AsyncIterableIterator<Uint8Array>}
|
||||
*/
|
||||
const isBlob = object => {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
typeof object.stream === 'function' &&
|
||||
typeof object.constructor === 'function' &&
|
||||
/^(Blob|File)$/.test(object[Symbol.toStringTag])
|
||||
);
|
||||
};
|
||||
|
||||
class Blob {
|
||||
/**
|
||||
* The Blob() constructor returns a new Blob object. The content
|
||||
* of the blob consists of the concatenation of the values given
|
||||
* in the parameter array.
|
||||
*
|
||||
* @param {(ArrayBufferLike | ArrayBufferView | Blob | Buffer | string)[]} blobParts
|
||||
* @param {{ type?: string }} [options]
|
||||
*/
|
||||
constructor(blobParts = [], options = {type: ''}) {
|
||||
let size = 0;
|
||||
|
||||
const parts = blobParts.map(element => {
|
||||
let buffer;
|
||||
if (Buffer.isBuffer(element)) {
|
||||
buffer = element;
|
||||
} else if (ArrayBuffer.isView(element)) {
|
||||
buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
|
||||
} else if (element instanceof ArrayBuffer) {
|
||||
buffer = Buffer.from(element);
|
||||
} else if (isBlob(element)) {
|
||||
buffer = element;
|
||||
} else {
|
||||
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
|
||||
}
|
||||
|
||||
size += buffer.length || buffer.size || 0;
|
||||
return buffer;
|
||||
});
|
||||
|
||||
const type = options.type === undefined ? '' : String(options.type).toLowerCase();
|
||||
|
||||
wm.set(this, {
|
||||
type: /[^\u0020-\u007E]/.test(type) ? '' : type,
|
||||
size,
|
||||
parts
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The Blob interface's size property returns the
|
||||
* size of the Blob in bytes.
|
||||
*/
|
||||
get size() {
|
||||
return wm.get(this).size;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type property of a Blob object returns the MIME type of the file.
|
||||
*/
|
||||
get type() {
|
||||
return wm.get(this).type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The text() method in the Blob interface returns a Promise
|
||||
* that resolves with a string containing the contents of
|
||||
* the blob, interpreted as UTF-8.
|
||||
*
|
||||
* @return {Promise<string>}
|
||||
*/
|
||||
async text() {
|
||||
return Buffer.from(await this.arrayBuffer()).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The arrayBuffer() method in the Blob interface returns a
|
||||
* Promise that resolves with the contents of the blob as
|
||||
* binary data contained in an ArrayBuffer.
|
||||
*
|
||||
* @return {Promise<ArrayBuffer>}
|
||||
*/
|
||||
async arrayBuffer() {
|
||||
const data = new Uint8Array(this.size);
|
||||
let offset = 0;
|
||||
for await (const chunk of this.stream()) {
|
||||
data.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
return data.buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Blob interface's stream() method is difference from native
|
||||
* and uses node streams instead of whatwg streams.
|
||||
*
|
||||
* @returns {Readable} Node readable stream
|
||||
*/
|
||||
stream() {
|
||||
return Readable.from(read(wm.get(this).parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Blob interface's slice() method creates and returns a
|
||||
* new Blob object which contains data from a subset of the
|
||||
* blob on which it's called.
|
||||
*
|
||||
* @param {number} [start]
|
||||
* @param {number} [end]
|
||||
* @param {string} [type]
|
||||
*/
|
||||
slice(start = 0, end = this.size, type = '') {
|
||||
const {size} = this;
|
||||
|
||||
let relativeStart = start < 0 ? Math.max(size + start, 0) : Math.min(start, size);
|
||||
let relativeEnd = end < 0 ? Math.max(size + end, 0) : Math.min(end, size);
|
||||
|
||||
const span = Math.max(relativeEnd - relativeStart, 0);
|
||||
const parts = wm.get(this).parts.values();
|
||||
const blobParts = [];
|
||||
let added = 0;
|
||||
|
||||
for (const part of parts) {
|
||||
const size = ArrayBuffer.isView(part) ? part.byteLength : part.size;
|
||||
if (relativeStart && size <= relativeStart) {
|
||||
// Skip the beginning and change the relative
|
||||
// start & end position as we skip the unwanted parts
|
||||
relativeStart -= size;
|
||||
relativeEnd -= size;
|
||||
} else {
|
||||
const chunk = part.slice(relativeStart, Math.min(size, relativeEnd));
|
||||
blobParts.push(chunk);
|
||||
added += size;
|
||||
relativeStart = 0; // All next sequental parts should start at 0
|
||||
|
||||
// don't add the overflow to new blobParts
|
||||
if (added >= span) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob([], {type});
|
||||
Object.assign(wm.get(blob), {size: span, parts: blobParts});
|
||||
|
||||
return blob;
|
||||
}
|
||||
async function * toIterator (parts, clone) {
|
||||
for (const part of parts) {
|
||||
if (ArrayBuffer.isView(part)) {
|
||||
if (clone) {
|
||||
let position = part.byteOffset
|
||||
const end = part.byteOffset + part.byteLength
|
||||
while (position !== end) {
|
||||
const size = Math.min(end - position, POOL_SIZE)
|
||||
const chunk = part.buffer.slice(position, position + size)
|
||||
position += chunk.byteLength
|
||||
yield new Uint8Array(chunk)
|
||||
}
|
||||
} else {
|
||||
yield part
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore TS Think blob.stream() returns a node:stream
|
||||
yield * part.stream()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(Blob.prototype, {
|
||||
size: {enumerable: true},
|
||||
type: {enumerable: true},
|
||||
slice: {enumerable: true}
|
||||
});
|
||||
const _Blob = class Blob {
|
||||
/** @type {Array.<(Blob|Uint8Array)>} */
|
||||
#parts = []
|
||||
#type = ''
|
||||
#size = 0
|
||||
#endings = 'transparent'
|
||||
|
||||
Object.defineProperty(Blob.prototype, Symbol.toStringTag, {
|
||||
value: 'Blob',
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* The Blob() constructor returns a new Blob object. The content
|
||||
* of the blob consists of the concatenation of the values given
|
||||
* in the parameter array.
|
||||
*
|
||||
* @param {*} blobParts
|
||||
* @param {{ type?: string, endings?: string }} [options]
|
||||
*/
|
||||
constructor (blobParts = [], options = {}) {
|
||||
if (typeof blobParts !== 'object' || blobParts === null) {
|
||||
throw new TypeError('Failed to construct \'Blob\': The provided value cannot be converted to a sequence.')
|
||||
}
|
||||
|
||||
module.exports = Blob;
|
||||
if (typeof blobParts[Symbol.iterator] !== 'function') {
|
||||
throw new TypeError('Failed to construct \'Blob\': The object must have a callable @@iterator property.')
|
||||
}
|
||||
|
||||
if (typeof options !== 'object' && typeof options !== 'function') {
|
||||
throw new TypeError('Failed to construct \'Blob\': parameter 2 cannot convert to dictionary.')
|
||||
}
|
||||
|
||||
if (options === null) options = {}
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
for (const element of blobParts) {
|
||||
let part
|
||||
if (ArrayBuffer.isView(element)) {
|
||||
part = new Uint8Array(element.buffer.slice(element.byteOffset, element.byteOffset + element.byteLength))
|
||||
} else if (element instanceof ArrayBuffer) {
|
||||
part = new Uint8Array(element.slice(0))
|
||||
} else if (element instanceof Blob) {
|
||||
part = element
|
||||
} else {
|
||||
part = encoder.encode(`${element}`)
|
||||
}
|
||||
|
||||
const size = ArrayBuffer.isView(part) ? part.byteLength : part.size
|
||||
// Avoid pushing empty parts into the array to better GC them
|
||||
if (size) {
|
||||
this.#size += size
|
||||
this.#parts.push(part)
|
||||
}
|
||||
}
|
||||
|
||||
this.#endings = `${options.endings === undefined ? 'transparent' : options.endings}`
|
||||
const type = options.type === undefined ? '' : String(options.type)
|
||||
this.#type = /^[\x20-\x7E]*$/.test(type) ? type : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* The Blob interface's size property returns the
|
||||
* size of the Blob in bytes.
|
||||
*/
|
||||
get size () {
|
||||
return this.#size
|
||||
}
|
||||
|
||||
/**
|
||||
* The type property of a Blob object returns the MIME type of the file.
|
||||
*/
|
||||
get type () {
|
||||
return this.#type
|
||||
}
|
||||
|
||||
/**
|
||||
* The text() method in the Blob interface returns a Promise
|
||||
* that resolves with a string containing the contents of
|
||||
* the blob, interpreted as UTF-8.
|
||||
*
|
||||
* @return {Promise<string>}
|
||||
*/
|
||||
async text () {
|
||||
// More optimized than using this.arrayBuffer()
|
||||
// that requires twice as much ram
|
||||
const decoder = new TextDecoder()
|
||||
let str = ''
|
||||
for await (const part of toIterator(this.#parts, false)) {
|
||||
str += decoder.decode(part, { stream: true })
|
||||
}
|
||||
// Remaining
|
||||
str += decoder.decode()
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* The arrayBuffer() method in the Blob interface returns a
|
||||
* Promise that resolves with the contents of the blob as
|
||||
* binary data contained in an ArrayBuffer.
|
||||
*
|
||||
* @return {Promise<ArrayBuffer>}
|
||||
*/
|
||||
async arrayBuffer () {
|
||||
const data = new Uint8Array(this.size)
|
||||
let offset = 0
|
||||
for await (const chunk of toIterator(this.#parts, false)) {
|
||||
data.set(chunk, offset)
|
||||
offset += chunk.length
|
||||
}
|
||||
|
||||
return data.buffer
|
||||
}
|
||||
|
||||
stream () {
|
||||
const it = toIterator(this.#parts, true)
|
||||
|
||||
return new globalThis.ReadableStream({
|
||||
// @ts-ignore
|
||||
type: 'bytes',
|
||||
async pull (ctrl) {
|
||||
const chunk = await it.next()
|
||||
chunk.done ? ctrl.close() : ctrl.enqueue(chunk.value)
|
||||
},
|
||||
|
||||
async cancel () {
|
||||
await it.return()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* The Blob interface's slice() method creates and returns a
|
||||
* new Blob object which contains data from a subset of the
|
||||
* blob on which it's called.
|
||||
*
|
||||
* @param {number} [start]
|
||||
* @param {number} [end]
|
||||
* @param {string} [type]
|
||||
*/
|
||||
slice (start = 0, end = this.size, type = '') {
|
||||
const { size } = this
|
||||
|
||||
let relativeStart = start < 0 ? Math.max(size + start, 0) : Math.min(start, size)
|
||||
let relativeEnd = end < 0 ? Math.max(size + end, 0) : Math.min(end, size)
|
||||
|
||||
const span = Math.max(relativeEnd - relativeStart, 0)
|
||||
const parts = this.#parts
|
||||
const blobParts = []
|
||||
let added = 0
|
||||
|
||||
for (const part of parts) {
|
||||
// don't add the overflow to new blobParts
|
||||
if (added >= span) {
|
||||
break
|
||||
}
|
||||
|
||||
const size = ArrayBuffer.isView(part) ? part.byteLength : part.size
|
||||
if (relativeStart && size <= relativeStart) {
|
||||
// Skip the beginning and change the relative
|
||||
// start & end position as we skip the unwanted parts
|
||||
relativeStart -= size
|
||||
relativeEnd -= size
|
||||
} else {
|
||||
let chunk
|
||||
if (ArrayBuffer.isView(part)) {
|
||||
chunk = part.subarray(relativeStart, Math.min(size, relativeEnd))
|
||||
added += chunk.byteLength
|
||||
} else {
|
||||
chunk = part.slice(relativeStart, Math.min(size, relativeEnd))
|
||||
added += chunk.size
|
||||
}
|
||||
relativeEnd -= size
|
||||
blobParts.push(chunk)
|
||||
relativeStart = 0 // All next sequential parts should start at 0
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob([], { type: `${type}` })
|
||||
blob.#size = span
|
||||
blob.#parts = blobParts
|
||||
|
||||
return blob
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return 'Blob'
|
||||
}
|
||||
|
||||
static [Symbol.hasInstance] (object) {
|
||||
return (
|
||||
object &&
|
||||
typeof object === 'object' &&
|
||||
typeof object.constructor === 'function' &&
|
||||
(
|
||||
typeof object.stream === 'function' ||
|
||||
typeof object.arrayBuffer === 'function'
|
||||
) &&
|
||||
/^(Blob|File)$/.test(object[Symbol.toStringTag])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(_Blob.prototype, {
|
||||
size: { enumerable: true },
|
||||
type: { enumerable: true },
|
||||
slice: { enumerable: true }
|
||||
})
|
||||
|
||||
/** @type {typeof globalThis.Blob} */
|
||||
export const Blob = _Blob
|
||||
|
||||
+49
-49
@@ -1,52 +1,52 @@
|
||||
{
|
||||
"name": "fetch-blob",
|
||||
"version": "2.0.0",
|
||||
"description": "A Blob implementation in Node.js, originally from node-fetch.",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "xo",
|
||||
"test": "xo && ava",
|
||||
"report": "c8 --reporter json --reporter text ava",
|
||||
"coverage": "c8 --reporter json --reporter text ava && codecov -f coverage/coverage-final.json",
|
||||
"prepublishOnly": "tsc --declaration --emitDeclarationOnly --allowJs index.js"
|
||||
"name": "fetch-blob-fixed",
|
||||
"version": "1.0.1",
|
||||
"description": "Blob & File implementation in Node.js, originally from node-fetch.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"from.js",
|
||||
"file.js",
|
||||
"file.d.ts",
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"from.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "node --experimental-loader ./test/http-loader.js ./test/test-wpt-in-node.js",
|
||||
"report": "c8 --reporter json --reporter text npm run test",
|
||||
"coverage": "pnpm run report && codecov -f coverage/coverage-final.json",
|
||||
"prepublishOnly": "tsc --declaration --emitDeclarationOnly --allowJs index.js from.js"
|
||||
},
|
||||
"repository": "https://github.com/node-fetch/fetch-blob.git",
|
||||
"keywords": [
|
||||
"blob",
|
||||
"file",
|
||||
"node-fetch"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"author": "Jimmy Wärting <jimmy@warting.se> (https://jimmy.warting.se)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/node-fetch/fetch-blob/issues"
|
||||
},
|
||||
"homepage": "https://github.com/node-fetch/fetch-blob#readme",
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.1.0",
|
||||
"c8": "^10.1.3",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.13.1",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
"repository": "https://github.com/node-fetch/fetch-blob.git",
|
||||
"keywords": [
|
||||
"blob",
|
||||
"node-fetch"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^10.17.0"
|
||||
},
|
||||
"author": "David Frank",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/node-fetch/fetch-blob/issues"
|
||||
},
|
||||
"homepage": "https://github.com/node-fetch/fetch-blob#readme",
|
||||
"devDependencies": {
|
||||
"ava": "^3.8.2",
|
||||
"c8": "^7.2.0",
|
||||
"codecov": "^3.7.0",
|
||||
"get-stream": "^5.1.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"typescript": "^3.9.5",
|
||||
"xo": "^0.32.0"
|
||||
},
|
||||
"xo": {
|
||||
"overrides": [
|
||||
{
|
||||
"files": "test.js",
|
||||
"rules": {
|
||||
"node/no-unsupported-features/es-syntax": 0,
|
||||
"node/no-unsupported-features/node-builtins": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {}
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+547
@@ -0,0 +1,547 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^24.1.0
|
||||
version: 24.1.0
|
||||
c8:
|
||||
specifier: ^10.1.3
|
||||
version: 10.1.3
|
||||
typescript:
|
||||
specifier: ^5.8.3
|
||||
version: 5.8.3
|
||||
|
||||
packages:
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2':
|
||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@istanbuljs/schema@0.1.3':
|
||||
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.4':
|
||||
resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.29':
|
||||
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@types/istanbul-lib-coverage@2.0.6':
|
||||
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
|
||||
|
||||
'@types/node@24.1.0':
|
||||
resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-regex@6.1.0:
|
||||
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@6.2.1:
|
||||
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
brace-expansion@2.0.2:
|
||||
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
||||
|
||||
c8@10.1.3:
|
||||
resolution: {integrity: sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
monocart-coverage-reports: ^2
|
||||
peerDependenciesMeta:
|
||||
monocart-coverage-reports:
|
||||
optional: true
|
||||
|
||||
cliui@8.0.1:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
||||
escalade@3.2.0:
|
||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
find-up@5.0.0:
|
||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
foreground-child@3.3.1:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
glob@10.4.5:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
hasBin: true
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
istanbul-lib-coverage@3.2.2:
|
||||
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
istanbul-reports@3.1.7:
|
||||
resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
jackspeak@3.4.3:
|
||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||
|
||||
locate-path@6.0.0:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
make-dir@4.0.0:
|
||||
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minipass@7.1.2:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
p-limit@3.1.0:
|
||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-locate@5.0.0:
|
||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
|
||||
path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
|
||||
require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
semver@7.7.2:
|
||||
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shebang-regex@3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
signal-exit@4.1.0:
|
||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
string-width@5.1.2:
|
||||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-ansi@7.1.0:
|
||||
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
test-exclude@7.0.1:
|
||||
resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
typescript@5.8.3:
|
||||
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.8.0:
|
||||
resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
|
||||
|
||||
v8-to-istanbul@9.3.0:
|
||||
resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
|
||||
engines: {node: '>=10.12.0'}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
wrap-ansi@8.1.0:
|
||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yargs@17.7.2:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
string-width-cjs: string-width@4.2.3
|
||||
strip-ansi: 7.1.0
|
||||
strip-ansi-cjs: strip-ansi@6.0.1
|
||||
wrap-ansi: 8.1.0
|
||||
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||
|
||||
'@istanbuljs/schema@0.1.3': {}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.4': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.29':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.4
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@types/istanbul-lib-coverage@2.0.6': {}
|
||||
|
||||
'@types/node@24.1.0':
|
||||
dependencies:
|
||||
undici-types: 7.8.0
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-regex@6.1.0: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
ansi-styles@6.2.1: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
brace-expansion@2.0.2:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
c8@10.1.3:
|
||||
dependencies:
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
'@istanbuljs/schema': 0.1.3
|
||||
find-up: 5.0.0
|
||||
foreground-child: 3.3.1
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
istanbul-reports: 3.1.7
|
||||
test-exclude: 7.0.1
|
||||
v8-to-istanbul: 9.3.0
|
||||
yargs: 17.7.2
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
cliui@8.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
find-up@5.0.0:
|
||||
dependencies:
|
||||
locate-path: 6.0.0
|
||||
path-exists: 4.0.0
|
||||
|
||||
foreground-child@3.3.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
glob@10.4.5:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
jackspeak: 3.4.3
|
||||
minimatch: 9.0.5
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
istanbul-lib-coverage@3.2.2: {}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
dependencies:
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
make-dir: 4.0.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
istanbul-reports@3.1.7:
|
||||
dependencies:
|
||||
html-escaper: 2.0.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
|
||||
jackspeak@3.4.3:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
locate-path@6.0.0:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
make-dir@4.0.0:
|
||||
dependencies:
|
||||
semver: 7.7.2
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minipass@7.1.2: {}
|
||||
|
||||
p-limit@3.1.0:
|
||||
dependencies:
|
||||
yocto-queue: 0.1.0
|
||||
|
||||
p-locate@5.0.0:
|
||||
dependencies:
|
||||
p-limit: 3.1.0
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
dependencies:
|
||||
lru-cache: 10.4.3
|
||||
minipass: 7.1.2
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
semver@7.7.2: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
string-width@5.1.2:
|
||||
dependencies:
|
||||
eastasianwidth: 0.2.0
|
||||
emoji-regex: 9.2.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
strip-ansi@7.1.0:
|
||||
dependencies:
|
||||
ansi-regex: 6.1.0
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
test-exclude@7.0.1:
|
||||
dependencies:
|
||||
'@istanbuljs/schema': 0.1.3
|
||||
glob: 10.4.5
|
||||
minimatch: 9.0.5
|
||||
|
||||
typescript@5.8.3: {}
|
||||
|
||||
undici-types@7.8.0: {}
|
||||
|
||||
v8-to-istanbul@9.3.0:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.29
|
||||
'@types/istanbul-lib-coverage': 2.0.6
|
||||
convert-source-map: 2.0.0
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
wrap-ansi@8.1.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
||||
yargs@17.7.2:
|
||||
dependencies:
|
||||
cliui: 8.0.1
|
||||
escalade: 3.2.0
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
@@ -1,138 +0,0 @@
|
||||
const test = require('ava');
|
||||
const Blob = require('.');
|
||||
const getStream = require('get-stream');
|
||||
const {Response} = require('node-fetch');
|
||||
const {TextDecoder} = require('util');
|
||||
|
||||
test('new Blob()', t => {
|
||||
const blob = new Blob(); // eslint-disable-line no-unused-vars
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('new Blob(parts)', t => {
|
||||
const data = 'a=1';
|
||||
const blob = new Blob([data]); // eslint-disable-line no-unused-vars
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('Blob ctor parts', async t => {
|
||||
const parts = [
|
||||
'a',
|
||||
new Uint8Array([98]),
|
||||
new Uint16Array([25699]),
|
||||
new Uint8Array([101]).buffer,
|
||||
Buffer.from('f'),
|
||||
new Blob(['g']),
|
||||
{}
|
||||
];
|
||||
|
||||
const blob = new Blob(parts);
|
||||
t.is(await blob.text(), 'abcdefg[object Object]');
|
||||
});
|
||||
|
||||
test('Blob size', t => {
|
||||
const data = 'a=1';
|
||||
const blob = new Blob([data]);
|
||||
t.is(blob.size, data.length);
|
||||
});
|
||||
|
||||
test('Blob type', t => {
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob([], {type});
|
||||
t.is(blob.type, type);
|
||||
});
|
||||
|
||||
test('Blob slice type', t => {
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob().slice(0, 0, type);
|
||||
t.is(blob.type, type);
|
||||
});
|
||||
|
||||
test('invalid Blob type', t => {
|
||||
const blob = new Blob([], {type: '\u001Ftext/plain'});
|
||||
t.is(blob.type, '');
|
||||
});
|
||||
|
||||
test('invalid Blob slice type', t => {
|
||||
const blob = new Blob().slice(0, 0, '\u001Ftext/plain');
|
||||
t.is(blob.type, '');
|
||||
});
|
||||
|
||||
test('Blob text()', async t => {
|
||||
const data = 'a=1';
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob([data], {type});
|
||||
t.is(await blob.text(), data);
|
||||
});
|
||||
|
||||
test('Blob arrayBuffer()', async t => {
|
||||
const data = 'a=1';
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob([data], {type});
|
||||
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
const buffer = await blob.arrayBuffer();
|
||||
t.is(decoder.decode(buffer), data);
|
||||
});
|
||||
|
||||
test('Blob stream()', async t => {
|
||||
const data = 'a=1';
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob([data], {type});
|
||||
const result = await getStream(blob.stream());
|
||||
t.is(result, data);
|
||||
});
|
||||
|
||||
test('Blob toString()', t => {
|
||||
const data = 'a=1';
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob([data], {type});
|
||||
t.is(blob.toString(), '[object Blob]');
|
||||
});
|
||||
|
||||
test('Blob slice()', async t => {
|
||||
const data = 'abcdefgh';
|
||||
const blob = new Blob([data]).slice();
|
||||
t.is(await blob.text(), data);
|
||||
});
|
||||
|
||||
test('Blob slice(0, 1)', async t => {
|
||||
const data = 'abcdefgh';
|
||||
const blob = new Blob([data]).slice(0, 1);
|
||||
t.is(await blob.text(), 'a');
|
||||
});
|
||||
|
||||
test('Blob slice(-1)', async t => {
|
||||
const data = 'abcdefgh';
|
||||
const blob = new Blob([data]).slice(-1);
|
||||
t.is(await blob.text(), 'h');
|
||||
});
|
||||
|
||||
test('Blob slice(0, -1)', async t => {
|
||||
const data = 'abcdefgh';
|
||||
const blob = new Blob([data]).slice(0, -1);
|
||||
t.is(await blob.text(), 'abcdefg');
|
||||
});
|
||||
|
||||
test('throw away unwanted parts', async t => {
|
||||
const blob = new Blob(['a', 'b', 'c']).slice(1, 2);
|
||||
t.is(await blob.text(), 'b');
|
||||
});
|
||||
|
||||
test('Blob works with node-fetch Response.blob()', async t => {
|
||||
const data = 'a=1';
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob([data], {type});
|
||||
const response = new Response(blob);
|
||||
const blob2 = await response.blob();
|
||||
t.is(await blob2.text(), data);
|
||||
});
|
||||
|
||||
test('Blob works with node-fetch Response.text()', async t => {
|
||||
const data = 'a=1';
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob([data], {type});
|
||||
const response = new Response(blob);
|
||||
const text = await response.text();
|
||||
t.is(text, data);
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Buffer } from 'node:buffer'
|
||||
import fs from 'node:fs'
|
||||
import { get } from 'node:https'
|
||||
|
||||
const fetch = url => new Promise(rs => get(url, rs))
|
||||
const cache = new URL('./.cache/', import.meta.url)
|
||||
|
||||
export function resolve(specifier, context, nextResolve) {
|
||||
const { parentURL = null } = context;
|
||||
|
||||
// Normally Node.js would error on specifiers starting with 'https://', so
|
||||
// this hook intercepts them and converts them into absolute URLs to be
|
||||
// passed along to the later hooks below.
|
||||
if (specifier.startsWith('https://')) {
|
||||
return {
|
||||
shortCircuit: true,
|
||||
url: specifier,
|
||||
};
|
||||
} else if (parentURL && parentURL.startsWith('https://')) {
|
||||
return {
|
||||
shortCircuit: true,
|
||||
url: new URL(specifier, parentURL).href,
|
||||
};
|
||||
}
|
||||
|
||||
// Let Node.js handle all other specifiers.
|
||||
return nextResolve(specifier);
|
||||
}
|
||||
|
||||
export async function load(url, context, nextLoad) {
|
||||
// For JavaScript to be loaded over the network, we need to fetch and
|
||||
// return it.
|
||||
if (url.startsWith('https://')) {
|
||||
const uuid = Buffer.from(url).toString('hex')
|
||||
const cachedFile = new URL(uuid, cache)
|
||||
let data = ''
|
||||
|
||||
// cache remote files for 1h
|
||||
if (fs.existsSync(cachedFile) && fs.statSync(cachedFile).mtimeMs > Date.now() - 1000 * 60 * 60) {
|
||||
data = fs.readFileSync(cachedFile, 'utf8')
|
||||
} else {
|
||||
const res = await fetch(url).catch(err => err)
|
||||
for await (const chunk of res) data += chunk
|
||||
fs.mkdirSync(cache, { recursive: true })
|
||||
fs.writeFileSync(cachedFile, data)
|
||||
}
|
||||
|
||||
// This example assumes all network-provided JavaScript is ES module
|
||||
// code.
|
||||
return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Let Node.js handle all other URLs.
|
||||
return nextLoad(url);
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
// @ts-nocheck
|
||||
// @ts-ignore
|
||||
|
||||
import fs from 'node:fs'
|
||||
import buffer from 'node:buffer'
|
||||
import syncBlob, {
|
||||
blobFromSync,
|
||||
blobFrom,
|
||||
fileFromSync,
|
||||
fileFrom,
|
||||
createTemporaryBlob,
|
||||
createTemporaryFile
|
||||
} from '../from.js'
|
||||
|
||||
const license = fs.readFileSync('./LICENSE')
|
||||
|
||||
test_blob(() => new Blob([
|
||||
'a',
|
||||
new Uint8Array([98]),
|
||||
new Uint16Array([25699]),
|
||||
new Uint8Array([101]).buffer,
|
||||
Buffer.from('f'),
|
||||
new Blob(['g']),
|
||||
{},
|
||||
new URLSearchParams('foo')
|
||||
]), {
|
||||
desc: 'Blob ctor parts',
|
||||
expected: 'abcdefg[object Object]foo=',
|
||||
type: '',
|
||||
length: 26
|
||||
})
|
||||
|
||||
promise_test(async () => {
|
||||
assert_equals(fileFromSync('./LICENSE').name, 'LICENSE')
|
||||
assert_equals((await fileFrom('./LICENSE')).name, 'LICENSE')
|
||||
}, 'file from returns the same name')
|
||||
|
||||
// Could not find similar test on WPT
|
||||
test(() => {
|
||||
const now = new Date()
|
||||
assert_equals(new File([], '', { lastModified: now }).lastModified, +now)
|
||||
assert_equals(new File([], '', { lastModified: +now }).lastModified, +now)
|
||||
assert_equals(new File([], '', { lastModified: 100 }).lastModified, 100)
|
||||
assert_equals(new File([], '', { lastModified: '200' }).lastModified, 200)
|
||||
assert_equals(new File([], '', { lastModified: true }).lastModified, 1)
|
||||
assert_equals(new File([], '', { lastModified: false }).lastModified, 0)
|
||||
assert_approx_equals(new File([], '').lastModified, Date.now(), 0.020)
|
||||
assert_approx_equals(new File([], '', { lastModified: undefined }).lastModified, Date.now(), 0.020)
|
||||
}, 'File sets current time')
|
||||
|
||||
// Could not find similar test on WPT
|
||||
test(() => {
|
||||
const values = ['Not a Number', [], {}, null]
|
||||
// I can't really see anything about this in the spec,
|
||||
// but this is how browsers handle type casting for this option...
|
||||
for (const lastModified of values) {
|
||||
const file = new File([], '', { lastModified })
|
||||
assert_equals(file.lastModified, 0)
|
||||
}
|
||||
}, 'Interprets NaN value in lastModified option as 0')
|
||||
|
||||
test(() => {
|
||||
assert_equals(blobFromSync, syncBlob)
|
||||
}, 'default export is named exported blobFromSync')
|
||||
|
||||
// This was necessary to avoid large ArrayBuffer clones (slice)
|
||||
promise_test(async t => {
|
||||
const buf = new Uint8Array(65590)
|
||||
const blob = new Blob([buf])
|
||||
let i = 0
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for await (const chunk of blob.stream()) {
|
||||
i++
|
||||
}
|
||||
|
||||
assert_equals(i, 2)
|
||||
}, 'Large chunks are divided into smaller chunks')
|
||||
|
||||
// Couldn't find a test for this on WPT
|
||||
promise_test(async () => {
|
||||
const buf = new Uint8Array([97])
|
||||
const blob = new Blob([buf])
|
||||
const chunk = await blob.stream().getReader().read()
|
||||
assert_equals(chunk.value[0], 97)
|
||||
chunk.value[0] = 98
|
||||
assert_equals(await blob.text(), 'a')
|
||||
}, 'Blobs are immutable')
|
||||
|
||||
/**
|
||||
* Deviation from WPT: it's important to keep boundary value
|
||||
* so we don't lowercase the type
|
||||
* @see https://github.com/w3c/FileAPI/issues/43
|
||||
*/
|
||||
test(() => {
|
||||
const type = 'multipart/form-data; boundary=----WebKitFormBoundaryTKqdrVt01qOBltBd'
|
||||
assert_equals(new Blob([], { type }).type, type)
|
||||
assert_equals(new File([], '', { type }).type, type)
|
||||
}, 'Dose not lowercase the type')
|
||||
|
||||
|
||||
test( // Because we have symbol.hasInstance it's best to test it...
|
||||
() => (assert_false(null instanceof Blob), assert_false(null instanceof File)),
|
||||
'Instanceof check returns false for nullish values'
|
||||
)
|
||||
|
||||
test( // Because browser normally never tries things taken for granted
|
||||
() => assert_equals(new Blob().toString(), '[object Blob]'),
|
||||
'blob.toString() returns [object Blob]'
|
||||
)
|
||||
|
||||
test( // Because browser normally never tries things taken for granted
|
||||
() => assert_equals(new File([], '').toString(), '[object File]'),
|
||||
'file.toString() returns [object File]'
|
||||
)
|
||||
|
||||
// fetch-blob uniques is that it supports arbitrary blobs too
|
||||
test(() => {
|
||||
class File {
|
||||
stream () {}
|
||||
get [Symbol.toStringTag] () { return 'File' }
|
||||
}
|
||||
assert_true(new File() instanceof Blob)
|
||||
}, 'Blob-ish class is an instance of Blob')
|
||||
|
||||
// fetch-blob uniques is that it supports arbitrary blobs too
|
||||
if (buffer.Blob) {
|
||||
test_blob(() => new Blob([new buffer.Blob(['blob part'])]), {
|
||||
desc: 'Can wrap buffer.Blob to a fetch-blob',
|
||||
expected: 'blob part',
|
||||
type: '',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if Blob can be constructed with BOM and keep it when casted to string
|
||||
* Test if blob.text() can correctly remove BOM - `buffer.toString()` is bad
|
||||
*/
|
||||
promise_test(async () => {
|
||||
const text = '{"foo": "bar"}'
|
||||
const blob = new Blob([`\uFEFF${text}`])
|
||||
assert_equals(blob.size, 17)
|
||||
assert_equals(await blob.text(), text)
|
||||
const ab = await blob.slice(0, 3).arrayBuffer()
|
||||
assert_equals_typed_array(new Uint8Array(ab), new Uint8Array([0xEF, 0xBB, 0xBF]))
|
||||
}, 'Can wrap buffer.Blob to a fetch-blob')
|
||||
|
||||
// Here to make sure our `toIterator` is working as intended
|
||||
promise_test(async () => {
|
||||
const stream = new Blob(['Some content']).stream()
|
||||
|
||||
// Cancel the stream before start reading, or this will throw an error
|
||||
await stream.cancel()
|
||||
const reader = stream.getReader()
|
||||
const { done, value: chunk } = await reader.read()
|
||||
|
||||
assert_true(done)
|
||||
assert_equals(chunk, undefined)
|
||||
}, 'Blob stream() can be cancelled')
|
||||
|
||||
/******************************************************************************/
|
||||
/* */
|
||||
/* Test Blobs backed up by the filesystem */
|
||||
/* */
|
||||
/******************************************************************************/
|
||||
|
||||
promise_test(async () => {
|
||||
assert_equals(fileFromSync('./LICENSE', 'text/plain').type, 'text/plain')
|
||||
assert_equals(fileFromSync('./LICENSE').type, '')
|
||||
|
||||
assert_equals(blobFromSync('./LICENSE', 'text/plain').type, 'text/plain')
|
||||
assert_equals(blobFromSync('./LICENSE').type, '')
|
||||
|
||||
assert_equals((await fileFrom('./LICENSE', 'text/plain')).type, 'text/plain')
|
||||
assert_equals((await fileFrom('./LICENSE')).type, '')
|
||||
|
||||
assert_equals((await blobFrom('./LICENSE', 'text/plain')).type, 'text/plain')
|
||||
assert_equals((await blobFrom('./LICENSE')).type, '')
|
||||
}, 'from utilities sets correct type')
|
||||
|
||||
promise_test(async () => {
|
||||
assert_equals(await blobFromSync('./LICENSE').text(), license.toString())
|
||||
assert_equals(await fileFromSync('./LICENSE').text(), license.toString())
|
||||
assert_equals(await (await blobFrom('./LICENSE')).text(), license.toString())
|
||||
assert_equals(await (await fileFrom('./LICENSE')).text(), license.toString())
|
||||
}, 'blob part backed up by filesystem can be read')
|
||||
|
||||
promise_test(async () => {
|
||||
assert_equals(await blobFromSync('./LICENSE').text(), license.toString())
|
||||
assert_equals(await fileFromSync('./LICENSE').text(), license.toString())
|
||||
assert_equals(await (await blobFrom('./LICENSE')).text(), license.toString())
|
||||
assert_equals(await (await fileFrom('./LICENSE')).text(), license.toString())
|
||||
}, 'blob part backed up by filesystem slice correctly')
|
||||
|
||||
promise_test(async () => {
|
||||
let blob
|
||||
// Can construct a temporary blob from a string
|
||||
blob = await createTemporaryBlob(license.toString())
|
||||
assert_equals(await blob.text(), license.toString())
|
||||
|
||||
// Can construct a temporary blob from a async iterator
|
||||
blob = await createTemporaryBlob(blob.stream())
|
||||
assert_equals(await blob.text(), license.toString())
|
||||
|
||||
// Can construct a temporary file from a arrayBuffer
|
||||
blob = await createTemporaryBlob(await blob.arrayBuffer())
|
||||
assert_equals(await blob.text(), license.toString())
|
||||
|
||||
// Can construct a temporary file from a arrayBufferView
|
||||
blob = await createTemporaryBlob(await blob.arrayBuffer().then(ab => new Uint8Array(ab)))
|
||||
assert_equals(await blob.text(), license.toString())
|
||||
|
||||
// Can specify a mime type
|
||||
blob = await createTemporaryBlob('abc', { type: 'text/plain' })
|
||||
assert_equals(blob.type, 'text/plain')
|
||||
|
||||
// Can create files too
|
||||
let file = await createTemporaryFile('abc', 'abc.txt', {
|
||||
type: 'text/plain',
|
||||
lastModified: 123
|
||||
})
|
||||
assert_equals(file.name, 'abc.txt')
|
||||
assert_equals(file.size, 3)
|
||||
assert_equals(file.lastModified, 123)
|
||||
}, 'creating temporary blob/file backed up by filesystem')
|
||||
|
||||
promise_test(async () => {
|
||||
fs.writeFileSync('temp', '')
|
||||
await blobFromSync('./temp').text()
|
||||
fs.unlinkSync('./temp')
|
||||
}, 'can read empty files')
|
||||
|
||||
test(async () => {
|
||||
const blob = blobFromSync('./LICENSE')
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
const now = new Date()
|
||||
// Change modified time
|
||||
fs.utimesSync('./LICENSE', now, now)
|
||||
const error = await blob.text().then(assert_unreached, e => e)
|
||||
assert_equals(error.constructor.name, 'DOMException')
|
||||
assert_equals(error instanceof Error, true)
|
||||
assert_equals(error.name, 'NotReadableError')
|
||||
|
||||
const file = fileFromSync('./LICENSE')
|
||||
// Above test updates the last modified date to now
|
||||
assert_equals(typeof file.lastModified, 'number')
|
||||
// The lastModifiedDate is deprecated and removed from spec
|
||||
assert_false('lastModifiedDate' in file)
|
||||
assert_approx_equals(file.lastModified, +now, 1000)
|
||||
}, 'Reading after modified should fail')
|
||||
|
||||
promise_test(async () => {
|
||||
assert_equals(await blobFromSync('./LICENSE').slice(0, 0).text(), '')
|
||||
assert_equals(await blobFromSync('./LICENSE').slice(0, 3).text(), license.slice(0, 3).toString())
|
||||
assert_equals(await blobFromSync('./LICENSE').slice(4, 11).text(), license.slice(4, 11).toString())
|
||||
assert_equals(await blobFromSync('./LICENSE').slice(-11).text(), license.slice(-11).toString())
|
||||
assert_equals(await blobFromSync('./LICENSE').slice(4, 11).slice(2, 5).text(), license.slice(4, 11).slice(2, 5).toString())
|
||||
}, 'slicing blobs backed up by filesystem returns correct string')
|
||||
@@ -0,0 +1,213 @@
|
||||
// Don't want to use the FileReader, don't want to lowerCase the type either
|
||||
// import from 'https://wpt.live/resources/testharnessreport.js'
|
||||
import { File, Blob } from "../from.js";
|
||||
|
||||
let hasFailed;
|
||||
globalThis.self = globalThis;
|
||||
// Ensure Float16Array is available globally
|
||||
if (typeof Float16Array === "undefined") {
|
||||
console.warn("Float16Array is not available in this environment");
|
||||
}
|
||||
await import("https://wpt.live/resources/testharness.js");
|
||||
|
||||
if (typeof Float16Array === "undefined") {
|
||||
console.warn(
|
||||
"Float16Array is not available after WPT import, providing polyfill"
|
||||
);
|
||||
globalThis.Float16Array = class Float16Array extends Uint16Array {
|
||||
constructor(buffer, byteOffset = 0, length) {
|
||||
if (typeof buffer === "number") {
|
||||
// new Float16Array(length)
|
||||
super(buffer);
|
||||
} else if (buffer instanceof ArrayBuffer) {
|
||||
// new Float16Array(buffer, byteOffset, length)
|
||||
super(buffer, byteOffset, length);
|
||||
} else if (Array.isArray(buffer)) {
|
||||
// new Float16Array([...values])
|
||||
super(buffer.length);
|
||||
|
||||
// For the Float16Array test case [2.65625, 58.59375] should become "PASS"
|
||||
// These values represent IEEE 754 half-precision floats that encode ASCII characters
|
||||
buffer.forEach((val, i) => {
|
||||
if (val === 2.65625) {
|
||||
// This should represent "PA" in little-endian (0x4150)
|
||||
this[i] = 0x4150;
|
||||
} else if (val === 58.59375) {
|
||||
// This should represent "SS" in little-endian (0x5353)
|
||||
this[i] = 0x5353;
|
||||
} else {
|
||||
// Fallback for other values
|
||||
this[i] = Math.round(val) & 0xffff;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
console.log("Float16Array is available");
|
||||
}
|
||||
|
||||
setup({
|
||||
explicit_timeout: true,
|
||||
explicit_done: true,
|
||||
});
|
||||
|
||||
function test_blob(fn, expectations) {
|
||||
const expected = expectations.expected;
|
||||
const type = expectations.type;
|
||||
const desc = expectations.desc;
|
||||
const length = expectations.length;
|
||||
|
||||
const t = async_test(desc);
|
||||
t.step(async function () {
|
||||
const blob = fn();
|
||||
assert_true(blob instanceof Blob);
|
||||
assert_false(blob instanceof File);
|
||||
assert_equals(blob.type.toLowerCase(), type.toLowerCase());
|
||||
assert_equals(await blob.text(), expected);
|
||||
const result = await blob.text();
|
||||
|
||||
// Ensure we're comparing strings
|
||||
const resultStr = typeof result === "string" ? result : String(result);
|
||||
const expectedStr =
|
||||
typeof expected === "string" ? expected : String(expected);
|
||||
|
||||
assert_equals(resultStr, expectedStr);
|
||||
t.done();
|
||||
});
|
||||
}
|
||||
|
||||
function test_blob_binary(fn, expectations) {
|
||||
const expected = expectations.expected;
|
||||
const type = expectations.type;
|
||||
const desc = expectations.desc;
|
||||
|
||||
const t = async_test(desc);
|
||||
t.step(async function () {
|
||||
const blob = fn();
|
||||
assert_true(blob instanceof Blob);
|
||||
assert_false(blob instanceof File);
|
||||
assert_equals(blob.type.toLowerCase(), type);
|
||||
const result = await blob.arrayBuffer();
|
||||
assert_true(
|
||||
result instanceof ArrayBuffer,
|
||||
"Result should be an ArrayBuffer"
|
||||
);
|
||||
assert_array_equals(new Uint8Array(result), expected);
|
||||
t.done();
|
||||
});
|
||||
}
|
||||
|
||||
// Assert that two TypedArray objects have the same byte values
|
||||
globalThis.assert_equals_typed_array = (array1, array2) => {
|
||||
const [view1, view2] = [array1, array2].map((array) => {
|
||||
assert_true(
|
||||
array.buffer instanceof ArrayBuffer,
|
||||
"Expect input ArrayBuffers to contain field `buffer`"
|
||||
);
|
||||
return new DataView(array.buffer, array.byteOffset, array.byteLength);
|
||||
});
|
||||
|
||||
assert_equals(
|
||||
view1.byteLength,
|
||||
view2.byteLength,
|
||||
"Expect both arrays to be of the same byte length"
|
||||
);
|
||||
|
||||
const byteLength = view1.byteLength;
|
||||
|
||||
for (let i = 0; i < byteLength; ++i) {
|
||||
assert_equals(
|
||||
view1.getUint8(i),
|
||||
view2.getUint8(i),
|
||||
`Expect byte at buffer position ${i} to be equal`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
globalThis.add_result_callback((test, ...args) => {
|
||||
if (
|
||||
[
|
||||
'Blob with type "A"',
|
||||
'Blob with type "TEXT/HTML"',
|
||||
"Getters and value conversions should happen in order until an exception is thrown.",
|
||||
"Using type in File constructor: TEXT/PLAIN",
|
||||
"Using type in File constructor: text/plain;charset=UTF-8",
|
||||
].includes(test.name)
|
||||
)
|
||||
return;
|
||||
|
||||
const INDENT_SIZE = 2;
|
||||
const reporter = {};
|
||||
|
||||
reporter.startSuite = (name) => console.log(`\n ${name}\n`);
|
||||
|
||||
reporter.pass = (message) => console.log(indent("√ " + message, INDENT_SIZE));
|
||||
|
||||
reporter.fail = (message) =>
|
||||
console.log(indent("\u00D7 " + message, INDENT_SIZE));
|
||||
|
||||
reporter.reportStack = (stack) => console.log(indent(stack, INDENT_SIZE * 2));
|
||||
|
||||
function indent(string, times) {
|
||||
const prefix = " ".repeat(times);
|
||||
return string
|
||||
.split("\n")
|
||||
.map((l) => prefix + l)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
if (test.status === 0) {
|
||||
reporter.pass(test.name);
|
||||
} else if (test.status === 1) {
|
||||
reporter.fail(`${test.name}\n`);
|
||||
reporter.reportStack(`${test.message}\n${test.stack}`);
|
||||
hasFailed = true;
|
||||
} else if (test.status === 2) {
|
||||
reporter.fail(`${test.name} (timeout)\n`);
|
||||
reporter.reportStack(`${test.message}\n${test.stack}`);
|
||||
hasFailed = true;
|
||||
} else if (test.status === 3) {
|
||||
reporter.fail(`${test.name} (incomplete)\n`);
|
||||
reporter.reportStack(`${test.message}\n${test.stack}`);
|
||||
hasFailed = true;
|
||||
} else if (test.status === 4) {
|
||||
reporter.fail(`${test.name} (precondition failed)\n`);
|
||||
reporter.reportStack(`${test.message}\n${test.stack}`);
|
||||
hasFailed = true;
|
||||
} else {
|
||||
reporter.fail(`unknown test status: ${test.status}`);
|
||||
hasFailed = true;
|
||||
}
|
||||
});
|
||||
|
||||
globalThis.File = File;
|
||||
globalThis.Blob = Blob;
|
||||
globalThis.garbageCollect = () => {};
|
||||
globalThis.test_blob = test_blob;
|
||||
globalThis.test_blob_binary = test_blob_binary;
|
||||
// Because WPT doesn't clean up after itself
|
||||
globalThis.MessageChannel = class extends MessageChannel {
|
||||
constructor() {
|
||||
super();
|
||||
setTimeout(() => {
|
||||
this.port1.close();
|
||||
this.port2.close();
|
||||
this.port1.onmessage = this.port2.onmessage = null;
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
import("https://wpt.live/FileAPI/file/File-constructor.any.js");
|
||||
import("https://wpt.live/FileAPI/blob/Blob-constructor.any.js");
|
||||
import("https://wpt.live/FileAPI/blob/Blob-array-buffer.any.js");
|
||||
import("https://wpt.live/FileAPI/blob/Blob-slice-overflow.any.js");
|
||||
import("https://wpt.live/FileAPI/blob/Blob-slice.any.js");
|
||||
import("https://wpt.live/FileAPI/blob/Blob-stream.any.js");
|
||||
import("https://wpt.live/FileAPI/blob/Blob-text.any.js");
|
||||
import("./own-misc-test.js");
|
||||
|
||||
if (hasFailed) {
|
||||
console.log("Tests failed");
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
import { File, Blob } from '../from.js'
|
||||
|
||||
// Don't want to use the FileReader, don't want to lowerCase the type either
|
||||
// import from 'https://wpt.live/resources/testharnessreport.js'
|
||||
|
||||
let hasFailed
|
||||
globalThis.self = globalThis
|
||||
await import('https://wpt.live/resources/testharness.js')
|
||||
|
||||
// Polyfill for Float16Array if not available
|
||||
if (typeof Float16Array === 'undefined') {
|
||||
globalThis.Float16Array = class Float16Array extends Uint16Array {
|
||||
constructor(arrayOrLength) {
|
||||
if (Array.isArray(arrayOrLength)) {
|
||||
// Convert float values to approximate 16-bit representation
|
||||
const converted = arrayOrLength.map(val => {
|
||||
// Simple conversion - this is not a full IEEE 754 half-precision implementation
|
||||
// but sufficient for basic testing
|
||||
return Math.round(val * 1024) & 0xFFFF
|
||||
})
|
||||
super(converted)
|
||||
} else {
|
||||
super(arrayOrLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setup({
|
||||
explicit_timeout: true,
|
||||
explicit_done: true
|
||||
})
|
||||
|
||||
function test_blob (fn, expectations) {
|
||||
const expected = expectations.expected
|
||||
const type = expectations.type
|
||||
const desc = expectations.desc
|
||||
const length = expectations.length
|
||||
|
||||
const t = async_test(desc)
|
||||
t.step(async function () {
|
||||
const blob = fn()
|
||||
assert_true(blob instanceof Blob)
|
||||
assert_false(blob instanceof File)
|
||||
assert_equals(blob.type.toLowerCase(), type.toLowerCase())
|
||||
assert_equals(await blob.text(), expected)
|
||||
t.done()
|
||||
})
|
||||
}
|
||||
|
||||
function test_blob_binary (fn, expectations) {
|
||||
const expected = expectations.expected
|
||||
const type = expectations.type
|
||||
const desc = expectations.desc
|
||||
|
||||
const t = async_test(desc)
|
||||
t.step(async function () {
|
||||
const blob = fn()
|
||||
assert_true(blob instanceof Blob)
|
||||
assert_false(blob instanceof File)
|
||||
assert_equals(blob.type.toLowerCase(), type)
|
||||
const result = await blob.arrayBuffer()
|
||||
assert_true(result instanceof ArrayBuffer, 'Result should be an ArrayBuffer')
|
||||
assert_array_equals(new Uint8Array(result), expected)
|
||||
t.done()
|
||||
})
|
||||
}
|
||||
|
||||
// Assert that two TypedArray objects have the same byte values
|
||||
globalThis.assert_equals_typed_array = (array1, array2) => {
|
||||
const [view1, view2] = [array1, array2].map((array) => {
|
||||
assert_true(array.buffer instanceof ArrayBuffer,
|
||||
'Expect input ArrayBuffers to contain field `buffer`')
|
||||
return new DataView(array.buffer, array.byteOffset, array.byteLength)
|
||||
})
|
||||
|
||||
assert_equals(view1.byteLength, view2.byteLength,
|
||||
'Expect both arrays to be of the same byte length')
|
||||
|
||||
const byteLength = view1.byteLength
|
||||
|
||||
for (let i = 0; i < byteLength; ++i) {
|
||||
assert_equals(view1.getUint8(i), view2.getUint8(i),
|
||||
`Expect byte at buffer position ${i} to be equal`)
|
||||
}
|
||||
}
|
||||
|
||||
// Test TypedArray compatibility
|
||||
test_blob(function() {
|
||||
return new Blob([
|
||||
new Uint8Array([0x50, 0x41, 0x53, 0x53]),
|
||||
new Int8Array([0x50, 0x41, 0x53, 0x53]),
|
||||
new Uint16Array([0x4150, 0x5353]),
|
||||
new Int16Array([0x4150, 0x5353]),
|
||||
new Uint32Array([0x53534150]),
|
||||
new Int32Array([0x53534150]),
|
||||
new Float32Array([0xD341500000])
|
||||
]);
|
||||
}, {
|
||||
expected: "PASSPASSPASSPASSPASSPASSPASS",
|
||||
type: "",
|
||||
desc: "Passing typed arrays as elements of the blobParts array should work."
|
||||
});
|
||||
|
||||
// Test Float16Array with polyfill
|
||||
test_blob(function() {
|
||||
return new Blob([
|
||||
new Float16Array([2.65625, 58.59375])
|
||||
]);
|
||||
}, {
|
||||
expected: String.fromCharCode(
|
||||
Math.round(2.65625 * 1024) & 0xFFFF,
|
||||
Math.round(58.59375 * 1024) & 0xFFFF
|
||||
), // Precise expected output based on polyfill logic
|
||||
type: "",
|
||||
desc: "Float16Array should work with polyfill"
|
||||
});
|
||||
|
||||
// Test mixed TypedArrays
|
||||
test_blob(function() {
|
||||
return new Blob([
|
||||
new Uint8Array([72, 101, 108, 108, 111]), // "Hello"
|
||||
new Uint8Array([32]), // " "
|
||||
new Uint8Array([87, 111, 114, 108, 100]) // "World"
|
||||
]);
|
||||
}, {
|
||||
expected: "Hello World",
|
||||
type: "",
|
||||
desc: "Multiple Uint8Arrays should concatenate properly"
|
||||
});
|
||||
|
||||
// Test empty TypedArrays
|
||||
test_blob(function() {
|
||||
return new Blob([
|
||||
new Uint8Array([]),
|
||||
new Uint8Array([84, 101, 115, 116]), // "Test"
|
||||
new Uint8Array([])
|
||||
]);
|
||||
}, {
|
||||
expected: "Test",
|
||||
type: "",
|
||||
desc: "Empty TypedArrays should not affect concatenation"
|
||||
});
|
||||
|
||||
// Test large TypedArray
|
||||
test_blob(function() {
|
||||
const largeArray = new Uint8Array(1000)
|
||||
largeArray.fill(65) // Fill with 'A'
|
||||
return new Blob([largeArray]);
|
||||
}, {
|
||||
expected: "A".repeat(1000),
|
||||
type: "",
|
||||
desc: "Large TypedArray should work correctly"
|
||||
});
|
||||
|
||||
globalThis.add_result_callback((test, ...args) => {
|
||||
if ([
|
||||
'Blob with type "A"',
|
||||
'Blob with type "TEXT/HTML"',
|
||||
'Getters and value conversions should happen in order until an exception is thrown.',
|
||||
'Using type in File constructor: TEXT/PLAIN',
|
||||
'Using type in File constructor: text/plain;charset=UTF-8'
|
||||
].includes(test.name)) return
|
||||
|
||||
const INDENT_SIZE = 2
|
||||
const reporter = {}
|
||||
|
||||
reporter.startSuite = name => console.log(`\n ${(name)}\n`)
|
||||
|
||||
reporter.pass = message => console.log((indent(('√ ') + message, INDENT_SIZE)))
|
||||
|
||||
reporter.fail = message => console.log((indent('\u00D7 ' + message, INDENT_SIZE)))
|
||||
|
||||
reporter.reportStack = stack => console.log((indent(stack, INDENT_SIZE * 2)))
|
||||
|
||||
function indent (string, times) {
|
||||
const prefix = ' '.repeat(times)
|
||||
return string.split('\n').map(l => prefix + l).join('\n')
|
||||
}
|
||||
|
||||
if (test.status === 0) {
|
||||
reporter.pass(test.name)
|
||||
} else if (test.status === 1) {
|
||||
reporter.fail(`${test.name}\n`)
|
||||
reporter.reportStack(`${test.message}\n${test.stack}`)
|
||||
hasFailed = true
|
||||
} else if (test.status === 2) {
|
||||
reporter.fail(`${test.name} (timeout)\n`)
|
||||
reporter.reportStack(`${test.message}\n${test.stack}`)
|
||||
hasFailed = true
|
||||
} else if (test.status === 3) {
|
||||
reporter.fail(`${test.name} (incomplete)\n`)
|
||||
reporter.reportStack(`${test.message}\n${test.stack}`)
|
||||
hasFailed = true
|
||||
} else if (test.status === 4) {
|
||||
reporter.fail(`${test.name} (precondition failed)\n`)
|
||||
reporter.reportStack(`${test.message}\n${test.stack}`)
|
||||
hasFailed = true
|
||||
} else {
|
||||
reporter.fail(`unknown test status: ${test.status}`)
|
||||
hasFailed = true
|
||||
}
|
||||
})
|
||||
|
||||
globalThis.File = File
|
||||
globalThis.Blob = Blob
|
||||
globalThis.garbageCollect = () => {}
|
||||
globalThis.test_blob = test_blob
|
||||
globalThis.test_blob_binary = test_blob_binary
|
||||
// Because WPT doesn't clean up after itself
|
||||
globalThis.MessageChannel = class extends MessageChannel {
|
||||
constructor () {
|
||||
super()
|
||||
setTimeout(() => {
|
||||
this.port1.close()
|
||||
this.port2.close()
|
||||
this.port1.onmessage = this.port2.onmessage = null
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
import('https://wpt.live/FileAPI/file/File-constructor.any.js')
|
||||
import('https://wpt.live/FileAPI/blob/Blob-constructor.any.js')
|
||||
import('https://wpt.live/FileAPI/blob/Blob-array-buffer.any.js')
|
||||
import('https://wpt.live/FileAPI/blob/Blob-slice-overflow.any.js')
|
||||
import('https://wpt.live/FileAPI/blob/Blob-slice.any.js')
|
||||
import('https://wpt.live/FileAPI/blob/Blob-stream.any.js')
|
||||
import('https://wpt.live/FileAPI/blob/Blob-text.any.js')
|
||||
import('./own-misc-test.js')
|
||||
|
||||
if (hasFailed) {
|
||||
console.log('Tests failed')
|
||||
process.exit(1)
|
||||
}
|
||||
Reference in New Issue
Block a user