@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 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.
@@ -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
- # Temporary Holding Version
1
+ # wexiftool
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![pipeline status](https://gitlab.com/wasmedia/wexiftool/badges/main/pipeline.svg)](https://gitlab.com/wasmedia/wexiftool/-/pipelines)
4
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ [![demo](https://img.shields.io/badge/demo-GitLab%20Pages-orange)](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;