@wasmedia/wexiftool 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/LICENSE-exiftool +19 -0
- package/README.md +105 -2
- package/dist/cli.cjs +19 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.es.js +81 -0
- package/dist/index-B49L_wsJ.cjs +1 -0
- package/dist/index-D8B3sucz.js +681 -0
- package/dist/node-CRF3Yhkk.cjs +1 -0
- package/dist/node-P4tq-yoT.js +102 -0
- package/dist/wexiftool-CL0NBE7L.js +761 -0
- package/dist/wexiftool-DICKi0_a.cjs +4 -0
- package/dist/wexiftool.cjs +1 -0
- package/dist/wexiftool.d.ts +255 -0
- package/dist/wexiftool.es.js +5 -0
- package/dist/worker.cjs +1 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.es.js +64 -0
- package/package.json +69 -4
- package/wasm/package.json +4 -0
- package/wasm/perl.js +2 -0
- package/wasm/perl.wasm +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wmedia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/LICENSE-exiftool
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
ExifTool License Notice
|
|
2
|
+
=======================
|
|
3
|
+
|
|
4
|
+
This package bundles ExifTool by Phil Harvey (https://exiftool.org/).
|
|
5
|
+
|
|
6
|
+
ExifTool is free software; you can redistribute it and/or modify it
|
|
7
|
+
under the same terms as Perl itself, which means either:
|
|
8
|
+
|
|
9
|
+
a) the GNU General Public License as published by the Free Software
|
|
10
|
+
Foundation; either version 1, or (at your option) any later version, or
|
|
11
|
+
|
|
12
|
+
b) the "Artistic License" which comes with Perl.
|
|
13
|
+
|
|
14
|
+
For the full text of these licenses, see:
|
|
15
|
+
- GNU GPL: https://www.gnu.org/licenses/gpl.html
|
|
16
|
+
- Artistic License: https://dev.perl.org/licenses/artistic.html
|
|
17
|
+
|
|
18
|
+
ExifTool homepage: https://exiftool.org/
|
|
19
|
+
ExifTool author: Phil Harvey (philharvey66 at gmail.com)
|
package/README.md
CHANGED
|
@@ -1,3 +1,106 @@
|
|
|
1
|
-
#
|
|
1
|
+
# wexiftool
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://gitlab.com/wasmedia/wexiftool/-/pipelines)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://wasmedia.gitlab.io/wexiftool/)
|
|
6
|
+
|
|
7
|
+
Run the real, full-featured [ExifTool](https://exiftool.org/) in the browser and Node.js — no server required.
|
|
8
|
+
|
|
9
|
+
wexiftool compiles Perl 5.40 to WebAssembly via Emscripten and bundles Phil Harvey's ExifTool into the WASM virtual filesystem. This gives you the complete ExifTool (read **and** write support for 400+ metadata formats) as a JavaScript/TypeScript library.
|
|
10
|
+
|
|
11
|
+
## CLI Usage
|
|
12
|
+
|
|
13
|
+
Use it just like native exiftool — no Perl installation required:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Install globally
|
|
17
|
+
npm install -g @wasmedia/wexiftool
|
|
18
|
+
|
|
19
|
+
# Or use directly with npx
|
|
20
|
+
npx @wasmedia/wexiftool photo.jpg
|
|
21
|
+
npx @wasmedia/wexiftool -Artist="Jane Doe" photo.jpg
|
|
22
|
+
npx @wasmedia/wexiftool -json -G *.jpg
|
|
23
|
+
npx @wasmedia/wexiftool -ver
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
All standard exiftool flags and options work as expected.
|
|
27
|
+
|
|
28
|
+
## API Usage
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { WExifTool } from '@wasmedia/wexiftool';
|
|
32
|
+
|
|
33
|
+
const wexiftool = await WExifTool.create();
|
|
34
|
+
|
|
35
|
+
// Read metadata
|
|
36
|
+
const metadata = await wexiftool.read(file); // File, Blob, ArrayBuffer, or Uint8Array
|
|
37
|
+
|
|
38
|
+
// Write metadata
|
|
39
|
+
const modified = await wexiftool.write(file, {
|
|
40
|
+
Artist: 'Jane Doe',
|
|
41
|
+
Copyright: '2026 Jane Doe',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Pass arbitrary exiftool flags
|
|
45
|
+
const raw = await wexiftool.readRaw(file, ['-G', '-s']);
|
|
46
|
+
|
|
47
|
+
// Run any exiftool command (low-level)
|
|
48
|
+
const result = await wexiftool.exec(['-json', '-G'], files);
|
|
49
|
+
|
|
50
|
+
wexiftool.destroy(); // Free WASM memory
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install @wasmedia/wexiftool
|
|
57
|
+
# or
|
|
58
|
+
pnpm add @wasmedia/wexiftool
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A standalone UMD bundle with inlined WASM is also available for `<script>` tag usage at `https://exiftool.wasmedia.org/wexiftool.standalone.js` (see [Standalone Bundle](docs/standalone-bundle.md)). It's ~70MB and not part of the npm package; the npm package, which loads the WASM separately, is recommended for production.
|
|
62
|
+
|
|
63
|
+
## Demo
|
|
64
|
+
|
|
65
|
+
A playground demo is available where you can drop an image to read and edit its metadata entirely client-side. Deployed via GitLab Pages.
|
|
66
|
+
|
|
67
|
+
## How It Works
|
|
68
|
+
|
|
69
|
+
1. Perl 5.40 is cross-compiled to WASM using Emscripten inside a Docker container
|
|
70
|
+
2. ExifTool's Perl modules are embedded into the Emscripten virtual filesystem (MEMFS)
|
|
71
|
+
3. Image data is written into MEMFS, ExifTool processes it via `callMain()`, results are extracted
|
|
72
|
+
4. A TypeScript API wraps the low-level Emscripten interface with a clean async interface
|
|
73
|
+
|
|
74
|
+
## Building from Source
|
|
75
|
+
|
|
76
|
+
Requires Docker.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pnpm install
|
|
80
|
+
pnpm run build:wasm # Builds Perl + ExifTool WASM (Docker, ~10-15 min first time)
|
|
81
|
+
pnpm run build # Builds the JS/TS library
|
|
82
|
+
pnpm run dev # Launches the demo playground dev server
|
|
83
|
+
pnpm test # Runs tests
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Size
|
|
87
|
+
|
|
88
|
+
The WASM payload is approximately 53MB uncompressed (~13MB gzipped). The library lazy-loads the WASM on first use.
|
|
89
|
+
|
|
90
|
+
## Status
|
|
91
|
+
|
|
92
|
+
End-to-end functional: `exiftool -ver` returns the version, EXIF metadata reads from a real JPEG fixture, and EXIF tag writes round-trip through `WExifTool.write()` → `WExifTool.read()`. The currently statically-linked XS set is `File::Glob` (more in `build/build-xs-extensions.sh`); ExifTool's read and most-of-write paths are pure Perl and work with what `libperl.a` already provides.
|
|
93
|
+
|
|
94
|
+
Build is single-threaded by design (`usethreads=undef`, `usemultiplicity=undef`) and uses no dynamic loading (`usedl=undef`).
|
|
95
|
+
|
|
96
|
+
**Known issue:** writing `EXIF:Copyright` (tag ID `0x8298`) silently corrupts the IFD entry — verified that the resulting file is unreadable by both wexiftool and native exiftool. All other EXIF tags tested round-trip correctly. Suspected signed-vs-unsigned 16-bit arithmetic on the tag ID inside ExifTool's WASM build. See `CHANGELOG.md` Known Issues.
|
|
97
|
+
|
|
98
|
+
## Contributing
|
|
99
|
+
|
|
100
|
+
Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for setup, project layout, and PR guidelines. Notable changes are tracked in [CHANGELOG.md](CHANGELOG.md).
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
|
105
|
+
|
|
106
|
+
This project bundles [ExifTool](https://exiftool.org/) by Phil Harvey, which is licensed under the Perl Artistic License / GPL. See [LICENSE-exiftool](LICENSE-exiftool) for details.
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";const f=require("fs"),u=require("path"),p=require("./wexiftool-DICKi0_a.cjs");async function d(){const t=process.argv.slice(2);t.length===0&&(w(),process.exit(1));const{flags:o,filePaths:i}=x(t);if(o.includes("-ver")||o.includes("-version")){const e=await p.WExifTool.create(),n=await e.exec([...o]);process.stdout.write(n.stdout+`
|
|
3
|
+
`),n.stderr&&process.stderr.write(n.stderr+`
|
|
4
|
+
`),e.destroy(),process.exit(n.exitCode)}i.length===0&&(console.error("Error: No input files specified."),process.exit(1));for(const e of i){const n=u.resolve(e);f.existsSync(n)||(console.error(`Error: File not found: ${e}`),process.exit(1)),f.statSync(n).isFile()||(console.error(`Error: Not a file: ${e}`),process.exit(1))}const c=o.some(e=>e.includes("=")&&e.startsWith("-")&&!e.startsWith("-if")),s=await p.WExifTool.create(),r=new Map(i.map(e=>[e,e])),a=[...o,...i];c&&!o.includes("-overwrite_original")&&!o.includes("-overwrite_original_in_place")&&a.unshift("-overwrite_original");const l=await s.exec(a,r,{writeBack:c});l.stdout&&process.stdout.write(l.stdout+`
|
|
5
|
+
`),l.stderr&&process.stderr.write(l.stderr+`
|
|
6
|
+
`),s.destroy(),process.exit(l.exitCode)}function x(t){const o=[],i=[],c=new Set(["-tagsFromFile","-tagsfromfile","-o","-out","-w","-textOut","-textout","-srcfile","-sep","-d","-dateformat","-dateFormat","-charset","-config"]);let s=0;for(;s<t.length;){const r=t[s];if(r==="--"){i.push(...t.slice(s+1));break}if(r.startsWith("-")){o.push(r);const a=r.toLowerCase().split("=")[0];c.has(a)&&!r.includes("=")&&s+1<t.length&&(s++,o.push(t[s]))}else i.push(r);s++}return{flags:o,filePaths:i}}function w(){console.log(`wexiftool - ExifTool running on WebAssembly
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
wexiftool [OPTIONS] FILE...
|
|
10
|
+
|
|
11
|
+
Examples:
|
|
12
|
+
wexiftool photo.jpg Read all metadata
|
|
13
|
+
wexiftool -json photo.jpg Output as JSON
|
|
14
|
+
wexiftool -Artist="Jane Doe" photo.jpg Write a tag
|
|
15
|
+
wexiftool -G -s photo.jpg Grouped, short output
|
|
16
|
+
wexiftool -ver Show ExifTool version
|
|
17
|
+
|
|
18
|
+
All standard ExifTool options are supported.
|
|
19
|
+
See https://exiftool.org/ for full documentation.`)}d().catch(t=>{console.error(t.message||t),process.exit(1)});
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { }
|
package/dist/cli.es.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync as p, statSync as u } from "fs";
|
|
3
|
+
import { resolve as d } from "path";
|
|
4
|
+
import { W as f } from "./wexiftool-CL0NBE7L.js";
|
|
5
|
+
async function x() {
|
|
6
|
+
const t = process.argv.slice(2);
|
|
7
|
+
t.length === 0 && (h(), process.exit(1));
|
|
8
|
+
const { flags: o, filePaths: i } = w(t);
|
|
9
|
+
if (o.includes("-ver") || o.includes("-version")) {
|
|
10
|
+
const e = await f.create(), n = await e.exec([...o]);
|
|
11
|
+
process.stdout.write(n.stdout + `
|
|
12
|
+
`), n.stderr && process.stderr.write(n.stderr + `
|
|
13
|
+
`), e.destroy(), process.exit(n.exitCode);
|
|
14
|
+
}
|
|
15
|
+
i.length === 0 && (console.error("Error: No input files specified."), process.exit(1));
|
|
16
|
+
for (const e of i) {
|
|
17
|
+
const n = d(e);
|
|
18
|
+
p(n) || (console.error(`Error: File not found: ${e}`), process.exit(1)), u(n).isFile() || (console.error(`Error: Not a file: ${e}`), process.exit(1));
|
|
19
|
+
}
|
|
20
|
+
const a = o.some(
|
|
21
|
+
(e) => e.includes("=") && e.startsWith("-") && !e.startsWith("-if")
|
|
22
|
+
), s = await f.create(), r = new Map(i.map((e) => [e, e])), c = [...o, ...i];
|
|
23
|
+
a && !o.includes("-overwrite_original") && !o.includes("-overwrite_original_in_place") && c.unshift("-overwrite_original");
|
|
24
|
+
const l = await s.exec(c, r, { writeBack: a });
|
|
25
|
+
l.stdout && process.stdout.write(l.stdout + `
|
|
26
|
+
`), l.stderr && process.stderr.write(l.stderr + `
|
|
27
|
+
`), s.destroy(), process.exit(l.exitCode);
|
|
28
|
+
}
|
|
29
|
+
function w(t) {
|
|
30
|
+
const o = [], i = [], a = /* @__PURE__ */ new Set([
|
|
31
|
+
"-tagsFromFile",
|
|
32
|
+
"-tagsfromfile",
|
|
33
|
+
"-o",
|
|
34
|
+
"-out",
|
|
35
|
+
"-w",
|
|
36
|
+
"-textOut",
|
|
37
|
+
"-textout",
|
|
38
|
+
"-srcfile",
|
|
39
|
+
"-sep",
|
|
40
|
+
"-d",
|
|
41
|
+
"-dateformat",
|
|
42
|
+
"-dateFormat",
|
|
43
|
+
"-charset",
|
|
44
|
+
"-config"
|
|
45
|
+
]);
|
|
46
|
+
let s = 0;
|
|
47
|
+
for (; s < t.length; ) {
|
|
48
|
+
const r = t[s];
|
|
49
|
+
if (r === "--") {
|
|
50
|
+
i.push(...t.slice(s + 1));
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
if (r.startsWith("-")) {
|
|
54
|
+
o.push(r);
|
|
55
|
+
const c = r.toLowerCase().split("=")[0];
|
|
56
|
+
a.has(c) && !r.includes("=") && s + 1 < t.length && (s++, o.push(t[s]));
|
|
57
|
+
} else
|
|
58
|
+
i.push(r);
|
|
59
|
+
s++;
|
|
60
|
+
}
|
|
61
|
+
return { flags: o, filePaths: i };
|
|
62
|
+
}
|
|
63
|
+
function h() {
|
|
64
|
+
console.log(`wexiftool - ExifTool running on WebAssembly
|
|
65
|
+
|
|
66
|
+
Usage:
|
|
67
|
+
wexiftool [OPTIONS] FILE...
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
wexiftool photo.jpg Read all metadata
|
|
71
|
+
wexiftool -json photo.jpg Output as JSON
|
|
72
|
+
wexiftool -Artist="Jane Doe" photo.jpg Write a tag
|
|
73
|
+
wexiftool -G -s photo.jpg Grouped, short output
|
|
74
|
+
wexiftool -ver Show ExifTool version
|
|
75
|
+
|
|
76
|
+
All standard ExifTool options are supported.
|
|
77
|
+
See https://exiftool.org/ for full documentation.`);
|
|
78
|
+
}
|
|
79
|
+
x().catch((t) => {
|
|
80
|
+
console.error(t.message || t), process.exit(1);
|
|
81
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var j=Object.defineProperty;var B=(e,t,n)=>t in e?j(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var O=(e,t,n)=>B(e,typeof t!="symbol"?t+"":t,n);const p=require("./wexiftool-DICKi0_a.cjs");function L(e){if(!e.startsWith("/")||e==="/"||e.endsWith("/"))throw new p.WopfsInputError(`mountpoint must be an absolute path other than "/", without a trailing slash: ${e}`)}function q(e){if(e.split("/").some(n=>n===""||n==="."||n===".."))throw new p.WopfsInputError(`input path must be relative and normalized: ${JSON.stringify(e)}`)}function P(e,t){const n=e.analyzePath(t);if(n.exists){if(!n.object||!e.isDir(n.object.mode))throw new p.WopfsInputError(`${t} exists and is not a directory`);return[]}const r=t.split("/").slice(1),a=r.map((f,l)=>"/"+r.slice(0,l+1).join("/")).filter(f=>!e.analyzePath(f).exists);return e.mkdirTree(t),a}function k(e,t){for(const n of[...t].reverse())e.rmdir(n)}function I(e,t){for(const n of e.streams)if(n&&n.node.mount===t)try{e.close(n)}catch{}}function U(e,t){return(t==null?void 0:t.name)==="ErrnoError"?new p.WopfsInputError(`cannot mount at ${e} (errno ${t.errno}); is it already a mount point?`,{cause:t}):t}function v(e,t,n,r){I(e,n);const a=r[0]??t,f=e.cwd();(f===a||f.startsWith(`${a}/`))&&e.chdir(a.slice(0,a.lastIndexOf("/"))||"/");try{e.unmount(t)}catch(l){const h=l==null?void 0:l.errno;throw new p.WopfsStateError(`cannot unmount ${t} (errno ${h}); release() can be retried`,{cause:l})}k(e,r)}function H(){var r,a;const e=globalThis;if((a=(r=e.process)==null?void 0:r.versions)!=null&&a.node&&typeof e.window>"u")return"node";const t=e.DedicatedWorkerGlobalScope,n=e.FileSystemFileHandle;return typeof t=="function"&&globalThis instanceof t&&n&&"createSyncAccessHandle"in n.prototype?"opfs":"memfs"}const J=["ErrnoError","createNode","hashRemoveNode","isDir","isFile","mount","unmount","lookupPath","analyzePath","mkdirTree","rmdir","readdir","writeFile","readFile","chmod","close","cwd","chdir"];function V(e){var r;const t=e==null?void 0:e.FS;if(!t)throw new p.WopfsRuntimeError("module has no FS export; build it with -sEXPORTED_RUNTIME_METHODS=FS");const n=J.filter(a=>typeof t[a]!="function");if(Array.isArray(t.streams)||n.push("streams"),(r=t.filesystems)!=null&&r.MEMFS||n.push("filesystems.MEMFS"),n.length)throw new p.WopfsRuntimeError(`module.FS is missing ${n.join(", ")}: this Emscripten's FS internals differ from the ones wopfs supports (emsdk 5.0.5, 5.0.6)`)}const b={EACCES:2,EINVAL:28,EIO:29,ENODEV:43,ENOENT:44,ENOSPC:51,ENOTEMPTY:55,EPERM:63,EROFS:69},Y=0,K=1,Q=2,G=61440,x=16384,X=32768,N=x|511,Z=X|292,g=e=>(e.mode&G)===x;function tt(e){const t=e._malloc;return typeof t!="function"||!e.HEAPU8?()=>null:n=>{const r=t(n);return r?{ptr:r,heap:e.HEAPU8,free:()=>{var a;return(a=e._free)==null?void 0:a.call(e,r)}}:null}}function et(e,t){const n=o=>new e.ErrnoError(o);function r(o){try{return o()}catch(s){const{name:i,code:c}=s??{};throw i==="ErrnoError"?s:n(i==="QuotaExceededError"||c==="ENOSPC"||c==="EDQUOT"?b.ENOSPC:b.EIO)}}const a=o=>o.wopfs;function f(o,s,i,c){const u=e.createNode(o,s,i,0);return u.node_ops=E,u.stream_ops=d,u.atime=u.mtime=u.ctime=Date.now(),g(u)?u.contents=Object.create(null):u.wopfs=c,o&&(o.contents[s]=u,o.mtime=o.ctime=u.mtime),u}function l(o){const s=o.wopfs;!s||s.input||(s.unlinked=!0,s.openCount===0&&t.discardFile(s.file))}function h(o){const s=a(o);if(s.input)throw n(b.EACCES);return s}const m=o=>{o.mtime=o.ctime=Date.now()},E={getattr(o){const s=g(o)?4096:a(o).size;return{dev:1,ino:o.id,mode:o.mode,nlink:1,uid:0,gid:0,rdev:0,size:s,atime:new Date(o.atime),mtime:new Date(o.mtime),ctime:new Date(o.ctime),blksize:4096,blocks:Math.ceil(s/4096)}},setattr(o,s){for(const i of["mode","atime","mtime","ctime"])s[i]!=null&&(o[i]=s[i]);if(s.size!==void 0){const i=h(o),c=s.size;r(()=>i.file.truncate(c)),i.size=c,m(o)}},lookup(o,s){const i=o.contents[s];if(i)return i;throw n(b.ENOENT)},mknod(o,s,i){if(t.readOnly)throw n(b.EROFS);if(e.isDir(i))return f(o,s,i);if(!e.isFile(i))throw n(b.EPERM);const c=r(()=>t.createFile());if(!c)throw n(b.ENOSPC);return f(o,s,i,{file:c,size:0,input:!1,openCount:0,unlinked:!1})},rename(o,s,i){const c=s.contents[i];if(c){if(g(c)&&Object.keys(c.contents).length)throw n(b.ENOTEMPTY);e.hashRemoveNode(c),l(c)}delete o.parent.contents[o.name],s.contents[i]=o,o.name=i,m(s),m(o.parent)},unlink(o,s){const i=o.contents[s];delete o.contents[s],m(o),l(i)},rmdir(o,s){const i=o.contents[s];if(Object.keys(i.contents).length)throw n(b.ENOTEMPTY);delete o.contents[s],m(o)},readdir(o){return[".","..",...Object.keys(o.contents)]},symlink(){throw n(b.EPERM)}},d={open(o){if(g(o.node))return;const s=(o.flags&3)===Y?a(o.node):h(o.node);s.openCount++},dup(o){g(o.node)||a(o.node).openCount++},close(o){if(g(o.node))return;const s=a(o.node);s.openCount--,s.unlinked?s.openCount===0&&t.discardFile(s.file):s.input||r(()=>s.file.flush())},read(o,s,i,c,u){const w=a(o.node);if(u>=w.size)return 0;const y=Math.min(c,w.size-u);return r(()=>w.file.read(new Uint8Array(s.buffer,s.byteOffset+i,y),u))},write(o,s,i,c,u){if(!c)return 0;const w=h(o.node),y=r(()=>w.file.write(new Uint8Array(s.buffer,s.byteOffset+i,c),u));return w.size=Math.max(w.size,u+y),m(o.node),y},llseek(o,s,i){let c=s;if(i===K?c+=o.position:i===Q&&!g(o.node)&&(c+=a(o.node).size),c<0)throw n(b.EINVAL);return c},mmap(o,s,i){if(g(o.node))throw n(b.ENODEV);const c=t.mmapAlloc(s);if(!c)throw n(b.ENODEV);const u=c.heap.subarray(c.ptr,c.ptr+s),w=a(o.node),y=i<w.size?Math.min(s,w.size-i):0;if(y)try{r(()=>w.file.read(u.subarray(0,y),i))}catch(W){throw c.free(),W}return u.fill(0,y),{ptr:c.ptr,allocated:!0}},msync(o,s,i,c){return d.write(o,s,0,c,i),0},fsync(o){return g(o.node)||r(()=>a(o.node).file.flush()),0}};return{mount(o){const s=f(null,"/",N);s.mount=o;for(const{path:i,file:c}of o.opts.inputs??[]){const u=i.split("/");let w=s;for(const y of u.slice(0,-1))w=w.contents[y]??f(w,y,N);f(w,u[u.length-1],Z,{file:c,size:c.getSize(),input:!0,openCount:0,unlinked:!1})}return s},node_ops:E,stream_ops:d}}function nt(e){const t=[],n=(r,a)=>{for(const[f,l]of Object.entries(r.contents)){const h=a?`${a}/${f}`:f;g(l)?n(l,h):l.wopfs&&!l.wopfs.input&&t.push({path:h,backing:l.wopfs})}};return n(e,""),t.sort((r,a)=>r.path<a.path?-1:r.path>a.path?1:0)}class M{constructor(t){O(this,"data");O(this,"size");this.data=t??new Uint8Array(0),this.size=this.data.length}read(t,n){const r=Math.max(0,Math.min(t.length,this.size-n));return t.set(this.data.subarray(n,n+r)),r}write(t,n){const r=n+t.length;return this.reserve(r),n>this.size&&this.data.fill(0,this.size,n),this.data.set(t,n),this.size=Math.max(this.size,r),t.length}truncate(t){t>this.size&&(this.reserve(t),this.data.fill(0,this.size,t)),this.size=t}getSize(){return this.size}flush(){}close(){}bytes(){return this.data.subarray(0,this.size)}reserve(t){if(t<=this.data.length)return;const n=new Uint8Array(Math.max(t,this.data.length*2,4096));n.set(this.data.subarray(0,this.size)),this.data=n}}function rt(e){let t=!1;return{read:(n,r)=>e.read(n,{at:r}),write:(n,r)=>e.write(n,{at:r}),truncate:n=>e.truncate(n),getSize:()=>e.getSize(),flush:()=>e.flush(),close:()=>{t||(t=!0,e.close())}}}const ot=1<<20;function st(e,t,n){const r=new Uint8Array(Math.min(ot,Math.max(n,1)));t.truncate(0);for(let a=0;a<n;){const f=e.read(r.subarray(0,Math.min(r.length,n-a)),a);if(f<=0)break;t.write(r.subarray(0,f),a),a+=f}}class D{constructor(t){O(this,"options");O(this,"slots");O(this,"free");O(this,"byFile",new Map);O(this,"warned",!1);this.options=t,this.slots=[...t.preopened??[]],this.free=[...this.slots].reverse();for(const n of this.slots)this.byFile.set(n.file,n)}acquire(){let t=this.free.pop();return!t&&this.options.open&&this.slots.length<this.options.capacity&&(t=this.options.open(this.slots.length),this.slots.push(t),this.byFile.set(t.file,t)),t?t.file:this.options.onExhausted==="strict"?null:(this.warned||(this.warned=!0,this.options.warn(`wopfs: scratch pool of ${this.options.capacity} exhausted; holding further new files in memory until commit()`)),new M)}discard(t){const n=this.byFile.get(t);if(n){try{t.truncate(0)}catch{return}this.free.push(n)}}slotOf(t){return this.byFile.get(t)}}function it(e,t,n){const r=e.FS,a=P(r,t),f=et(r,{createFile:()=>n.pool.acquire(),discardFile:m=>n.pool.discard(m),mmapAlloc:tt(e),readOnly:n.readOnly});let l;try{l=r.mount(f,{inputs:n.inputs},t)}catch(m){throw k(r,a),U(t,m)}let h="mounted";return{backend:n.kind,mountpoint:t,async commit(){if(h!=="mounted")throw new p.WopfsStateError(`commit() on a mount that is ${h}`);h="committing",I(r,l.mount);for(const{file:E}of n.inputs)E.close();const m=[];try{for(const{path:E,backing:d}of nt(l))m.push(await n.commitFile(E,d.file,d.size))}catch(E){throw new p.WopfsCommitError(m,E)}return h="committed",m},async release(){if(h!=="released"){h="releasing";try{v(r,t,l.mount,a)}finally{await n.release()}h="released"}}}}function T(e,t,n){return{kind:e,inputs:t,readOnly:!0,pool:new D({capacity:0,onExhausted:"strict",warn:()=>{}}),async commitFile(r){throw new p.WopfsStateError(`an input-only mount (no outputDir) cannot commit ${r}`)},release:n}}function _(e){return typeof e=="object"&&e!==null&&e.kind==="file"&&typeof e.getFile=="function"}function at(e){return typeof e=="object"&&e!==null&&e.kind==="directory"&&typeof e.getFileHandle=="function"}const ct={opfs:"a FileSystemFileHandle, Uint8Array or Blob",node:"a host path string, Uint8Array or Blob",memfs:"a Uint8Array, Blob or FileSystemFileHandle"};async function z(e,t,n){if(e instanceof Uint8Array)return e;if(typeof Blob<"u"&&e instanceof Blob)return new Uint8Array(await e.arrayBuffer());if(n==="memfs"&&_(e))return new Uint8Array(await(await e.getFile()).arrayBuffer());throw new p.WopfsInputError(`input ${t}: the ${n} backend takes ${ct[n]}`)}function R(){return globalThis.crypto.randomUUID().slice(0,8)}async function $(e,t){let n;try{n=await e.createSyncAccessHandle()}catch(r){const a=r==null?void 0:r.name;throw a==="NoModificationAllowedError"||a==="InvalidStateError"?new p.WopfsLockedError(t,r):r}if(typeof n.getSize()!="number")throw await n.close(),new p.WopfsUnsupportedError("this browser's FileSystemSyncAccessHandle methods are asynchronous; wopfs needs the synchronous ones");return rt(n)}async function ft(e,t,n){try{await e.move(t,n)}catch(r){if((r==null?void 0:r.name)!=="InvalidModificationError")throw r;await t.removeEntry(n),await e.move(t,n)}}async function lt(e,t,n,r,a){const f=await $(t,n);try{st(e,f,r),f.flush()}catch(l){throw f.close(),await a().catch(()=>{}),l}f.close()}async function ut(e){const t=e.outputDir;if(t!==void 0&&!at(t))throw new p.WopfsInputError("the opfs backend needs outputDir to be a FileSystemDirectoryHandle");if(t===void 0&&e.scratch>0)throw new p.WopfsInputError(`scratch (${e.scratch}) needs outputDir: without it the mount is input-only`);const n=[],r=`.wopfs-scratch-${R()}`;let a,f=0;const l=async()=>{for(const h of n)try{h.close()}catch{}a&&await t.removeEntry(r,{recursive:!0}).catch(()=>{})};try{const h=[],m=[];for(const[s,i]of e.inputs)if(_(i)){for(const[u,w]of m)if(await w.isSameEntry(i))throw new p.WopfsInputError(`inputs ${JSON.stringify(u)} and ${JSON.stringify(s)} are the same file`);m.push([s,i]);const c=await $(i,s);n.push(c),h.push({path:s,file:c})}else h.push({path:s,file:new M(await z(i,s,"opfs"))});if(t===void 0)return T("opfs",h,l);if(a=await t.getDirectoryHandle(r,{create:!0}),typeof(await a.getFileHandle("probe",{create:!0})).move!="function")throw new p.WopfsUnsupportedError("this browser's FileSystemFileHandle has no move(); wopfs needs it to commit outputs safely");const d=[];for(let s=0;s<e.scratch;s++){const i=`s${s}`,c=await a.getFileHandle(i,{create:!0}),u=await $(c,`${r}/${i}`);n.push(u),d.push({name:i,file:u,handle:c})}const o=new D({capacity:e.scratch,onExhausted:e.onExhausted,preopened:d,warn:e.warn});return{kind:"opfs",inputs:h,pool:o,async commitFile(s,i,c){const u=s.split("/"),w=u.pop();let y=t;for(const F of u)y=await y.getDirectoryHandle(F,{create:!0});const W=o.slotOf(i);let S;if(W)i.flush(),i.close(),S=W.handle;else{const F=`spill${f++}`;S=await a.getFileHandle(F,{create:!0}),await lt(i,S,`${r}/${F}`,c,()=>a.removeEntry(F)),i.close()}return await ft(S,y,w),{path:s,size:c,handle:S}},release:l}}catch(h){throw await l(),h}}async function A(e,t,n){const r=e.FS,a=[];for(const[d,o]of n.inputs)a.push([d,await z(o,d,"memfs")]);const f=P(r,t);let l;try{l=r.mount(r.filesystems.MEMFS,{},t)}catch(d){throw k(r,f),U(t,d)}const h=new Set;try{for(const[d,o]of a){const s=`${t}/${d}`;r.mkdirTree(s.slice(0,s.lastIndexOf("/"))),r.writeFile(s,o),r.chmod(s,292),h.add(r.lookupPath(s).node)}}catch(d){throw r.unmount(t),k(r,f),d}const m=(d,o,s)=>{for(const i of r.readdir(d)){if(i==="."||i==="..")continue;const c=`${d}/${i}`,u=o?`${o}/${i}`:i,w=r.lookupPath(c).node;if(r.isDir(w.mode))m(c,u,s);else if(r.isFile(w.mode)&&!h.has(w)){const y=r.readFile(c);s.push({path:u,size:y.length,bytes:y})}}};let E="mounted";return{backend:"memfs",mountpoint:t,async commit(){if(E!=="mounted")throw new p.WopfsStateError(`commit() on a mount that is ${E}`);I(r,l.mount);const d=[];return m(t,"",d),E="committed",d.sort((o,s)=>o.path<s.path?-1:o.path>s.path?1:0)},async release(){E!=="released"&&(E="releasing",v(r,t,l.mount,f),E="released")}}}function ht(e){const t=Object.entries(e.inputs??{});for(const[f]of t)q(f);const n=new Set(t.map(([f])=>f));for(const[f]of t){const l=f.split("/");for(let h=1;h<l.length;h++){const m=l.slice(0,h).join("/");if(n.has(m))throw new p.WopfsInputError(`input ${JSON.stringify(m)} is a file, but ${JSON.stringify(f)} needs it to be a directory`)}}const r=e.scratch??(e.outputDir===void 0?0:16);if(!Number.isInteger(r)||r<0)throw new p.WopfsInputError(`scratch must be a non-negative integer: ${r}`);const a=e.onExhausted??"spill";if(a!=="spill"&&a!=="strict")throw new p.WopfsInputError(`onExhausted must be "spill" or "strict": ${a}`);return{inputs:t,outputDir:e.outputDir,scratch:r,onExhausted:a,warn:e.onWarning??(f=>console.warn(f))}}async function C(e,t,n){try{return it(e,t,n)}catch(r){throw await n.release(),r}}async function pt(e,t,n={}){V(e),L(t);const r=ht(n),a=n.backend??"auto",f=a==="auto"?H():a;if(f==="memfs")return A(e,t,r);if(f==="node"){const{openNodeBackend:l}=await Promise.resolve().then(()=>require("./node-CRF3Yhkk.cjs"));return C(e,t,await l(r))}if(f!=="opfs")throw new p.WopfsInputError(`unknown backend: ${String(f)}`);try{return await C(e,t,await ut(r))}catch(l){if(a==="auto"&&l instanceof p.WopfsUnsupportedError)return r.warn(`wopfs: ${l.message}; falling back to memfs`),A(e,t,r);throw l}}const dt=Object.freeze(Object.defineProperty({__proto__:null,WopfsCommitError:p.WopfsCommitError,WopfsError:p.WopfsError,WopfsInputError:p.WopfsInputError,WopfsLockedError:p.WopfsLockedError,WopfsRuntimeError:p.WopfsRuntimeError,WopfsStateError:p.WopfsStateError,WopfsUnsupportedError:p.WopfsUnsupportedError,detectBackend:H,mountFiles:pt},Symbol.toStringTag,{value:"Module"}));exports.MemoryFile=M;exports.ScratchPool=D;exports.bytesOf=z;exports.index=dt;exports.inputOnlyBackend=T;exports.randomId=R;
|