@ai-ecoverse/slicc-kernel 1.0.0 → 1.2.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.
@@ -1,3 +1,152 @@
1
+ // src/fs/meta.ts
2
+ var META_DB = "slicc-kernel";
3
+ var META_VERSION = 1;
4
+ var META_STORE = "entries";
5
+ function parentOf(path) {
6
+ return path.slice(0, path.lastIndexOf("/")) || "/";
7
+ }
8
+ function moved(entry, path) {
9
+ return entry.link === void 0 ? { ...entry, path } : { ...entry, path, dir: parentOf(path) };
10
+ }
11
+ function under(path, root) {
12
+ return path === root || path.startsWith(`${root}/`);
13
+ }
14
+ var MemoryMeta = class {
15
+ entries = /* @__PURE__ */ new Map();
16
+ async get(paths) {
17
+ return paths.map((path) => this.entries.get(path));
18
+ }
19
+ async update(path, change) {
20
+ this.set(path, change(this.entries.get(path)));
21
+ }
22
+ set(path, entry) {
23
+ if (entry) this.entries.set(path, entry);
24
+ else this.entries.delete(path);
25
+ }
26
+ async move(from, to, root) {
27
+ await this.remove([to]);
28
+ const carried = [...this.entries.values()].filter((entry) => under(entry.path, from));
29
+ for (const entry of carried) this.entries.delete(entry.path);
30
+ for (const entry of carried) {
31
+ const path = to + entry.path.slice(from.length);
32
+ if (path !== to) this.entries.set(path, moved(entry, path));
33
+ }
34
+ this.entries.set(to, moved(root(carried.find((entry) => entry.path === from)), to));
35
+ }
36
+ async remove(paths) {
37
+ for (const key of [...this.entries.keys()]) {
38
+ if (paths.some((path) => under(key, path))) this.entries.delete(key);
39
+ }
40
+ }
41
+ async links(dir) {
42
+ return [...this.entries.values()].filter((entry) => entry.dir === dir);
43
+ }
44
+ async under(path) {
45
+ return [...this.entries.values()].filter((entry) => under(entry.path, path));
46
+ }
47
+ async all() {
48
+ return [...this.entries.values()];
49
+ }
50
+ };
51
+ function descendants(path) {
52
+ return IDBKeyRange.bound(`${path}/`, `${path}0`, false, true);
53
+ }
54
+ function settle(transaction, value) {
55
+ return new Promise((resolve, reject) => {
56
+ transaction.oncomplete = () => resolve(value());
57
+ transaction.onabort = () => reject(transaction.error ?? new Error("metadata transaction aborted"));
58
+ });
59
+ }
60
+ var IndexedDbMeta = class {
61
+ db;
62
+ constructor(db) {
63
+ this.db = db;
64
+ db.onversionchange = () => db.close();
65
+ }
66
+ transaction(mode) {
67
+ const transaction = this.db.transaction(META_STORE, mode);
68
+ return [transaction, transaction.objectStore(META_STORE)];
69
+ }
70
+ async get(paths) {
71
+ const [transaction, store] = this.transaction("readonly");
72
+ const found = paths.map(() => void 0);
73
+ paths.forEach((path, i) => {
74
+ const request = store.get(path);
75
+ request.onsuccess = () => {
76
+ found[i] = request.result;
77
+ };
78
+ });
79
+ return settle(transaction, () => found);
80
+ }
81
+ async update(path, change) {
82
+ const [transaction, store] = this.transaction("readwrite");
83
+ const request = store.get(path);
84
+ request.onsuccess = () => {
85
+ const next = change(request.result);
86
+ if (next) store.put(next);
87
+ else store.delete(path);
88
+ };
89
+ return settle(transaction, () => void 0);
90
+ }
91
+ async move(from, to, root) {
92
+ const [transaction, store] = this.transaction("readwrite");
93
+ store.delete(to);
94
+ store.delete(descendants(to));
95
+ const source = store.get(from);
96
+ const below = store.getAll(descendants(from));
97
+ below.onsuccess = () => {
98
+ store.delete(from);
99
+ store.delete(descendants(from));
100
+ for (const entry of below.result) {
101
+ store.put(moved(entry, to + entry.path.slice(from.length)));
102
+ }
103
+ store.put(moved(root(source.result), to));
104
+ };
105
+ return settle(transaction, () => void 0);
106
+ }
107
+ async remove(paths) {
108
+ const [transaction, store] = this.transaction("readwrite");
109
+ for (const path of paths) {
110
+ store.delete(path);
111
+ store.delete(descendants(path));
112
+ }
113
+ return settle(transaction, () => void 0);
114
+ }
115
+ async links(dir) {
116
+ const [transaction, store] = this.transaction("readonly");
117
+ const request = store.index("dir").getAll(dir);
118
+ return settle(transaction, () => request.result);
119
+ }
120
+ async under(path) {
121
+ const [transaction, store] = this.transaction("readonly");
122
+ const self = store.get(path);
123
+ const below = store.getAll(descendants(path));
124
+ return settle(
125
+ transaction,
126
+ () => [self.result, ...below.result].filter(
127
+ (entry) => entry !== void 0
128
+ )
129
+ );
130
+ }
131
+ async all() {
132
+ const [transaction, store] = this.transaction("readonly");
133
+ const request = store.getAll();
134
+ return settle(transaction, () => request.result);
135
+ }
136
+ };
137
+ function openMeta(name, factory) {
138
+ return new Promise((resolve, reject) => {
139
+ const request = factory.open(name, META_VERSION);
140
+ request.onupgradeneeded = (event) => {
141
+ if (event.oldVersion < 1) {
142
+ request.result.createObjectStore(META_STORE, { keyPath: "path" }).createIndex("dir", "dir");
143
+ }
144
+ };
145
+ request.onsuccess = () => resolve(new IndexedDbMeta(request.result));
146
+ request.onerror = () => reject(request.error);
147
+ });
148
+ }
149
+
1
150
  // src/fs/types.ts
2
151
  function fsError(code, path) {
3
152
  return Object.assign(new Error(`${code}: ${path}`), { code });
@@ -14,11 +163,26 @@ function normalizePath(path) {
14
163
  function resolvePath(base, path) {
15
164
  return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
16
165
  }
166
+ function inodeOf(path) {
167
+ let h1 = 3735928559;
168
+ let h2 = 1103547991;
169
+ for (let i = 0; i < path.length; i++) {
170
+ const c = path.charCodeAt(i);
171
+ h1 = Math.imul(h1 ^ c, 2654435761);
172
+ h2 = Math.imul(h2 ^ c, 1597334677);
173
+ }
174
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
175
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
176
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
177
+ }
17
178
 
18
179
  // src/fs/opfs.ts
19
180
  var S_IFDIR = 16384;
20
181
  var S_IFREG = 32768;
182
+ var S_IFLNK = 40960;
21
183
  var PERM_MASK = 4095;
184
+ var READ_ATTEMPTS = 3;
185
+ var MAX_LINKS = 40;
22
186
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
23
187
  var ERRNO_BY_DOM_ERROR = {
24
188
  NotFoundError: "ENOENT",
@@ -32,26 +196,43 @@ function translate(err, path) {
32
196
  const name = err?.name;
33
197
  return fsError(typeof name === "string" && ERRNO_BY_DOM_ERROR[name] || "EIO", path);
34
198
  }
199
+ function missing(err) {
200
+ const code = err?.code;
201
+ return code === "ENOENT" || code === "ENOTDIR";
202
+ }
35
203
  function split(path) {
36
204
  return path.split("/").filter(Boolean);
37
205
  }
206
+ function parentOf2(path) {
207
+ return path.slice(0, path.lastIndexOf("/")) || "/";
208
+ }
209
+ function baseOf(path) {
210
+ return path.slice(path.lastIndexOf("/") + 1);
211
+ }
212
+ function prefixes(path) {
213
+ const parts = split(path);
214
+ return parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`);
215
+ }
38
216
  function within(path, root) {
39
217
  return path === root || path.startsWith(`${root}/`);
40
218
  }
219
+ function isHandle(child) {
220
+ return "kind" in child;
221
+ }
41
222
  var OpfsFs = class {
42
- modes = /* @__PURE__ */ new Map();
43
- times = /* @__PURE__ */ new Map();
44
223
  root;
45
- constructor(root) {
224
+ meta;
225
+ constructor(root, meta = new MemoryMeta()) {
46
226
  this.root = root;
227
+ this.meta = meta;
47
228
  }
48
229
  resolvePath(base, path) {
49
230
  return resolvePath(base, path);
50
231
  }
51
- async dir(path, create = false) {
232
+ async dir(path) {
52
233
  let dir = this.root;
53
234
  try {
54
- for (const part of split(path)) dir = await dir.getDirectoryHandle(part, { create });
235
+ for (const part of split(path)) dir = await dir.getDirectoryHandle(part);
55
236
  } catch (err) {
56
237
  throw translate(err, path);
57
238
  }
@@ -73,42 +254,117 @@ var OpfsFs = class {
73
254
  }
74
255
  return dir.getDirectoryHandle(name);
75
256
  }
76
- mode(path, directory) {
257
+ real(path) {
258
+ return this.handle(path).then(
259
+ () => true,
260
+ () => false
261
+ );
262
+ }
263
+ async linkAlong(path) {
264
+ for (const entry of await this.meta.get(prefixes(path))) {
265
+ if (entry?.link === void 0) continue;
266
+ if (!await this.real(entry.path) && await this.real(parentOf2(entry.path))) return entry;
267
+ }
268
+ return void 0;
269
+ }
270
+ async locate(path, follow) {
271
+ let current = path;
272
+ for (let hops = 0; hops <= MAX_LINKS; hops++) {
273
+ try {
274
+ return { path: current, handle: await this.handle(current) };
275
+ } catch (err) {
276
+ if (!missing(err)) throw err;
277
+ const link = await this.linkAlong(current);
278
+ if (!link) throw err;
279
+ if (link.path === current && !follow) return { path: current, link };
280
+ const rest = current.slice(link.path.length);
281
+ current = resolvePath(parentOf2(link.path), `${link.link}${rest}`);
282
+ }
283
+ }
284
+ throw fsError("ELOOP", path);
285
+ }
286
+ mode(path, directory, meta) {
77
287
  const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
78
- return (directory ? S_IFDIR : S_IFREG) | (this.modes.get(path) ?? fallback);
288
+ return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
79
289
  }
80
- async stat(path) {
81
- const handle = await this.handle(path);
290
+ async statOf(path, handle, meta) {
82
291
  const directory = handle.kind === "directory";
83
292
  const file = directory ? void 0 : await handle.getFile();
293
+ const modified = file?.lastModified ?? 0;
294
+ const mtime = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
84
295
  return {
85
296
  isFile: !directory,
86
297
  isDirectory: directory,
87
298
  isSymbolicLink: false,
88
299
  size: file?.size ?? 0,
89
- mode: this.mode(path, directory),
90
- mtime: new Date(this.times.get(path) ?? file?.lastModified ?? 0)
300
+ mode: this.mode(path, directory, meta),
301
+ mtime: new Date(mtime),
302
+ atime: new Date(meta?.atimeMs ?? mtime),
303
+ ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
304
+ ino: meta?.ino ?? inodeOf(path)
91
305
  };
92
306
  }
93
- lstat(path) {
94
- return this.stat(path);
307
+ linkStat(entry) {
308
+ const time = new Date(entry.mtimeMs ?? 0);
309
+ return {
310
+ isFile: false,
311
+ isDirectory: false,
312
+ isSymbolicLink: true,
313
+ size: new TextEncoder().encode(entry.link).length,
314
+ mode: S_IFLNK | 511,
315
+ mtime: time,
316
+ atime: time,
317
+ ctime: new Date(entry.ctimeMs ?? 0),
318
+ ino: entry.ino ?? inodeOf(entry.path)
319
+ };
320
+ }
321
+ async describe(found) {
322
+ if (found.link) return this.linkStat(found.link);
323
+ const [meta] = await this.meta.get([found.path]);
324
+ return this.statOf(found.path, found.handle, meta);
325
+ }
326
+ async stat(path) {
327
+ return this.describe(await this.locate(path, true));
328
+ }
329
+ async lstat(path) {
330
+ return this.describe(await this.locate(path, false));
95
331
  }
96
332
  async exists(path) {
97
- return this.handle(path).then(
333
+ return this.locate(path, true).then(
98
334
  () => true,
99
335
  () => false
100
336
  );
101
337
  }
102
338
  async readFileBuffer(path) {
103
- const handle = await this.handle(path);
339
+ const handle = (await this.locate(path, true)).handle;
104
340
  if (handle.kind === "directory") throw fsError("EISDIR", path);
105
- return new Uint8Array(await (await handle.getFile()).arrayBuffer());
341
+ for (let attempt = 1; ; attempt++) {
342
+ try {
343
+ return new Uint8Array(await (await handle.getFile()).arrayBuffer());
344
+ } catch (err) {
345
+ if (err?.name !== "NotReadableError" || attempt >= READ_ATTEMPTS) {
346
+ throw translate(err, path);
347
+ }
348
+ await new Promise((resolve) => setTimeout(resolve, attempt * 10));
349
+ }
350
+ }
106
351
  }
107
352
  async readFile(path) {
108
353
  return new TextDecoder().decode(await this.readFileBuffer(path));
109
354
  }
355
+ async writeTarget(path) {
356
+ try {
357
+ return (await this.locate(path, true)).path;
358
+ } catch (err) {
359
+ if (!missing(err)) throw err;
360
+ }
361
+ const [entry] = await this.meta.get([path]);
362
+ const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
363
+ return resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted));
364
+ }
110
365
  async writeFile(path, content) {
111
- const [dir, name] = await this.parent(path);
366
+ const target = await this.writeTarget(path);
367
+ const [dir, name] = await this.parent(target);
112
368
  let file;
113
369
  try {
114
370
  file = await dir.getFileHandle(name, { create: true });
@@ -119,77 +375,146 @@ var OpfsFs = class {
119
375
  const writable = await file.createWritable();
120
376
  await writable.write(content);
121
377
  await writable.close();
122
- this.times.delete(path);
123
378
  }
124
- async readdir(path) {
125
- const handle = await this.handle(path);
379
+ async listing(path) {
380
+ const found = await this.locate(path, true);
381
+ const handle = found.handle;
126
382
  if (handle.kind !== "directory") throw fsError("ENOTDIR", path);
127
- const names = [];
128
- for await (const name of handle.keys()) names.push(name);
129
- return names.sort();
383
+ const children = /* @__PURE__ */ new Map();
384
+ for await (const child of handle.values()) children.set(child.name, child);
385
+ for (const link of await this.meta.links(found.path)) {
386
+ const name = baseOf(link.path);
387
+ if (!children.has(name)) children.set(name, link);
388
+ }
389
+ return [found.path, [...children].sort(([a], [b]) => a < b ? -1 : 1)];
390
+ }
391
+ async readdir(path) {
392
+ return (await this.listing(path))[1].map(([name]) => name);
393
+ }
394
+ async readdirStat(path) {
395
+ const [dir, children] = await this.listing(path);
396
+ const base = dir === "/" ? "" : dir;
397
+ const metas = await this.meta.get(children.map(([name]) => `${base}/${name}`));
398
+ return Promise.all(
399
+ children.map(async ([name, child], i) => {
400
+ if (!isHandle(child)) return [name, this.linkStat(child)];
401
+ const stat = await this.statOf(`${base}/${name}`, child, metas[i]).catch(() => null);
402
+ return [name, stat];
403
+ })
404
+ );
130
405
  }
131
406
  async mkdir(path, options = {}) {
132
- if (options.recursive) {
133
- await this.dir(path, true);
134
- return;
407
+ let found;
408
+ try {
409
+ found = await this.locate(path, true);
410
+ } catch (err) {
411
+ if (!missing(err)) throw err;
135
412
  }
136
- if (await this.exists(path)) throw fsError("EEXIST", path);
137
- const [dir, name] = await this.parent(path);
138
- await dir.getDirectoryHandle(name, { create: true });
413
+ if (found) {
414
+ const directory = found.handle?.kind === "directory";
415
+ if (options.recursive && directory) return;
416
+ throw fsError(options.recursive ? "ENOTDIR" : "EEXIST", path);
417
+ }
418
+ const [entry] = await this.meta.get([path]);
419
+ if (entry?.link !== void 0) throw fsError("EEXIST", path);
420
+ if (options.recursive) await this.mkdir(parentOf2(path), options);
421
+ const parent = await this.locate(parentOf2(path), true);
422
+ if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
423
+ await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
139
424
  }
140
425
  async rm(path, options = {}) {
426
+ let found;
141
427
  try {
142
- const [dir, name] = await this.parent(path);
143
- await dir.removeEntry(name, { recursive: options.recursive === true });
428
+ found = await this.locate(path, false);
144
429
  } catch (err) {
145
430
  const error = translate(err, path);
146
431
  if (options.force && error.code === "ENOENT") return;
147
432
  throw error;
148
433
  }
149
- this.forget(path);
150
- }
151
- forget(path) {
152
- for (const map of [this.modes, this.times]) {
153
- for (const key of [...map.keys()]) if (within(key, path)) map.delete(key);
154
- }
155
- }
156
- carry(from, to) {
157
- for (const map of [this.modes, this.times]) {
158
- for (const [key, value] of [...map]) {
159
- if (!within(key, from)) continue;
160
- map.delete(key);
161
- map.set(to + key.slice(from.length), value);
434
+ if (found.handle) {
435
+ const [dir, name] = await this.parent(found.path);
436
+ try {
437
+ await dir.removeEntry(name, { recursive: options.recursive === true });
438
+ } catch (err) {
439
+ throw translate(err, path);
162
440
  }
163
441
  }
442
+ await this.meta.remove([found.path]);
164
443
  }
165
444
  async rename(from, to) {
166
445
  if (from === to) return;
167
- const source = await this.stat(from);
168
- if (source.isDirectory && within(to, from)) throw fsError("EINVAL", to);
169
- const target = await this.stat(to).catch(() => null);
170
- if (target?.isDirectory && !source.isDirectory) throw fsError("EISDIR", to);
171
- if (target && !target.isDirectory && source.isDirectory) throw fsError("ENOTDIR", to);
172
- if (target?.isDirectory && (await this.readdir(to)).length > 0) {
446
+ const source = await this.locate(from, false);
447
+ const parent = await this.locate(parentOf2(to), true);
448
+ const destination = resolvePath(parent.path, baseOf(to));
449
+ const sourceStat = await this.describe(source);
450
+ if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
451
+ const target = await this.locate(destination, false).catch((err) => {
452
+ if (missing(err)) return null;
453
+ throw err;
454
+ });
455
+ const targetStat = target && await this.describe(target);
456
+ if (targetStat?.isDirectory && !sourceStat.isDirectory) throw fsError("EISDIR", to);
457
+ if (targetStat && !targetStat.isDirectory && sourceStat.isDirectory) {
458
+ throw fsError("ENOTDIR", to);
459
+ }
460
+ if (targetStat?.isDirectory && (await this.readdir(destination)).length > 0) {
173
461
  throw fsError("ENOTEMPTY", to);
174
462
  }
175
- const [dir, name] = await this.parent(to);
176
- const handle = await this.handle(from);
177
- const swap = target?.isDirectory === true;
463
+ const now = Date.now();
464
+ const carry = (entry) => ({
465
+ ino: inodeOf(source.path),
466
+ ...entry,
467
+ path: destination,
468
+ ctimeMs: now
469
+ });
470
+ if (source.link) {
471
+ if (target?.handle) await this.rm(destination);
472
+ await this.meta.move(source.path, destination, carry);
473
+ return;
474
+ }
475
+ const dir = parent.handle;
476
+ const name = baseOf(destination);
477
+ const swap = targetStat?.isDirectory === true;
178
478
  const staging = swap ? `.${name}.${crypto.randomUUID()}` : name;
179
- const moved = await this.place(handle, dir, staging);
180
- if (swap) await this.rm(to, { recursive: true });
181
- this.carry(from, to);
182
- if (!moved) await this.rm(from, { recursive: true });
183
- if (!swap) return;
184
- const staged = await dir.getDirectoryHandle(staging);
185
- if (!await this.place(staged, dir, name)) await dir.removeEntry(staging, { recursive: true });
479
+ const timed = await this.explicitTimes(source.path);
480
+ const moved2 = await this.place(source.handle, dir, staging);
481
+ if (swap) await this.rm(destination, { recursive: true });
482
+ await this.meta.move(source.path, destination, carry);
483
+ if (!moved2) await this.rm(source.path, { recursive: true });
484
+ let copied = !moved2;
485
+ if (swap) {
486
+ const staged = await dir.getDirectoryHandle(staging);
487
+ const placed = await this.place(staged, dir, name);
488
+ if (!placed) await dir.removeEntry(staging, { recursive: true });
489
+ copied ||= !placed;
490
+ }
491
+ if (copied) await this.restamp(destination, timed);
492
+ }
493
+ async modified(path) {
494
+ const handle = await this.handle(path);
495
+ return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
496
+ }
497
+ async explicitTimes(path) {
498
+ const timed = [];
499
+ for (const entry of await this.meta.under(path)) {
500
+ if (entry.mtimeFor === void 0 || entry.link !== void 0) continue;
501
+ const current = await this.modified(entry.path).catch(() => void 0);
502
+ if (current === entry.mtimeFor) timed.push(entry.path.slice(path.length));
503
+ }
504
+ return timed;
505
+ }
506
+ async restamp(path, timed) {
507
+ for (const rest of timed) {
508
+ const mtimeFor = await this.modified(path + rest);
509
+ await this.meta.update(path + rest, (entry) => entry && { ...entry, mtimeFor });
510
+ }
186
511
  }
187
512
  async place(handle, dir, name) {
188
- const moved = await handle.move?.(dir, name).then(
513
+ const moved2 = await handle.move?.(dir, name).then(
189
514
  () => true,
190
515
  () => false
191
516
  );
192
- if (moved) return true;
517
+ if (moved2) return true;
193
518
  try {
194
519
  await this.copy(handle, dir, name);
195
520
  } catch (err) {
@@ -208,23 +533,123 @@ var OpfsFs = class {
208
533
  const target = await dir.getDirectoryHandle(name, { create: true });
209
534
  for await (const child of handle.values()) await this.copy(child, target, child.name);
210
535
  }
211
- async symlink(_target, path) {
212
- throw fsError("ENOSYS", path);
536
+ async symlink(target, path) {
537
+ const parent = await this.locate(parentOf2(path), true);
538
+ if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
539
+ const at = resolvePath(parent.path, baseOf(path));
540
+ if (await this.locate(at, false).then(
541
+ () => true,
542
+ (err) => !missing(err)
543
+ )) {
544
+ throw fsError("EEXIST", path);
545
+ }
546
+ const now = Date.now();
547
+ await this.meta.update(at, () => ({
548
+ path: at,
549
+ link: target,
550
+ dir: parent.path,
551
+ ino: inodeOf(at),
552
+ mtimeMs: now,
553
+ ctimeMs: now
554
+ }));
213
555
  }
214
556
  async readlink(path) {
215
- await this.handle(path);
557
+ const found = await this.locate(path, false);
558
+ if (found.link) return found.link.link;
216
559
  throw fsError("EINVAL", path);
217
560
  }
218
561
  async chmod(path, mode) {
219
- await this.handle(path);
220
- this.modes.set(path, mode & PERM_MASK);
221
- }
222
- async utimes(path, _atime, mtime) {
223
- await this.handle(path);
224
- this.times.set(path, mtime.getTime());
562
+ const found = await this.locate(path, true);
563
+ const ctimeMs = Date.now();
564
+ await this.meta.update(found.path, (entry) => ({
565
+ ...entry,
566
+ path: found.path,
567
+ mode: mode & PERM_MASK,
568
+ ctimeMs
569
+ }));
570
+ }
571
+ async utimes(path, atime, mtime) {
572
+ const found = await this.locate(path, true);
573
+ const handle = found.handle;
574
+ const file = handle.kind === "file" ? await handle.getFile() : void 0;
575
+ const ctimeMs = Date.now();
576
+ await this.meta.update(found.path, (entry) => ({
577
+ ...entry,
578
+ path: found.path,
579
+ atimeMs: atime.getTime(),
580
+ mtimeMs: mtime.getTime(),
581
+ mtimeFor: file?.lastModified ?? 0,
582
+ ctimeMs
583
+ }));
584
+ }
585
+ async reconcile() {
586
+ let removed = 0;
587
+ for (const entry of await this.meta.all()) {
588
+ const real = await this.real(entry.path);
589
+ const stale = entry.link === void 0 ? !real : real || !await this.real(parentOf2(entry.path));
590
+ if (!stale) continue;
591
+ await this.meta.update(entry.path, () => void 0);
592
+ removed++;
593
+ }
594
+ return removed;
225
595
  }
226
596
  };
227
597
 
598
+ // src/kernel/signals.ts
599
+ var SIG = {
600
+ HUP: 1,
601
+ INT: 2,
602
+ QUIT: 3,
603
+ ILL: 4,
604
+ TRAP: 5,
605
+ ABRT: 6,
606
+ BUS: 7,
607
+ FPE: 8,
608
+ KILL: 9,
609
+ USR1: 10,
610
+ SEGV: 11,
611
+ USR2: 12,
612
+ PIPE: 13,
613
+ ALRM: 14,
614
+ TERM: 15,
615
+ STKFLT: 16,
616
+ CHLD: 17,
617
+ CONT: 18,
618
+ STOP: 19,
619
+ TSTP: 20,
620
+ TTIN: 21,
621
+ TTOU: 22,
622
+ URG: 23,
623
+ XCPU: 24,
624
+ XFSZ: 25,
625
+ VTALRM: 26,
626
+ PROF: 27,
627
+ WINCH: 28,
628
+ POLL: 29,
629
+ PWR: 30,
630
+ SYS: 31
631
+ };
632
+ var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
633
+ var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
634
+ function defaultAction(sig) {
635
+ if (sig === SIG.CONT) return "continue";
636
+ if (STOPPING.has(sig)) return "stop";
637
+ return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
638
+ }
639
+ function isSignal(sig) {
640
+ return Number.isInteger(sig) && sig >= 1 && sig <= 31;
641
+ }
642
+ function sigbit(sig) {
643
+ return 1 << sig;
644
+ }
645
+ var SIGNAL_BY_NAME = {
646
+ SIGINT: SIG.INT,
647
+ SIGTERM: SIG.TERM,
648
+ SIGKILL: SIG.KILL,
649
+ SIGSTOP: SIG.STOP,
650
+ SIGCONT: SIG.CONT
651
+ };
652
+
228
653
  // src/commands.ts
229
654
  var NAME = /^[A-Za-z0-9._+-]+$/;
230
655
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -315,19 +740,34 @@ async function scanCommands(fs, modules) {
315
740
  var S_IFDIR2 = 16384;
316
741
  var S_IFREG2 = 32768;
317
742
  var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
318
- function missing(err) {
743
+ var MAX_LINKS2 = 40;
744
+ function missing2(err) {
319
745
  return err?.code === "ENOENT";
320
746
  }
321
- function synthetic(directory) {
747
+ function synthetic(directory, path) {
748
+ const epoch = /* @__PURE__ */ new Date(0);
322
749
  return {
323
750
  isFile: !directory,
324
751
  isDirectory: directory,
325
752
  isSymbolicLink: false,
326
753
  size: 0,
327
754
  mode: (directory ? S_IFDIR2 : S_IFREG2) | 493,
328
- mtime: /* @__PURE__ */ new Date(0)
755
+ mtime: epoch,
756
+ atime: epoch,
757
+ ctime: epoch,
758
+ ino: inodeOf(path)
329
759
  };
330
760
  }
761
+ async function followLinks(fs, path) {
762
+ let current = path;
763
+ for (let hops = 0; hops < MAX_LINKS2; hops++) {
764
+ const st = await fs.lstat(current).catch(() => void 0);
765
+ if (!st?.isSymbolicLink) return current;
766
+ const parent = current.slice(0, current.lastIndexOf("/")) || "/";
767
+ current = fs.resolvePath(parent, await fs.readlink(current));
768
+ }
769
+ return current;
770
+ }
331
771
  function withCommandDirs(fs, names) {
332
772
  async function children(path) {
333
773
  if (path === "/") return ["bin", "usr"];
@@ -336,15 +776,18 @@ function withCommandDirs(fs, names) {
336
776
  return void 0;
337
777
  }
338
778
  async function virtual(path) {
339
- if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true);
779
+ if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
340
780
  const name = COMMAND.exec(path)?.[1];
341
- return name !== void 0 && (await names()).has(name) ? synthetic(false) : void 0;
781
+ return name !== void 0 && (await names()).has(name) ? synthetic(false, path) : void 0;
782
+ }
783
+ async function linked(path) {
784
+ return await virtual(path) ?? virtual(await followLinks(fs, path));
342
785
  }
343
786
  async function stat(path, real) {
344
787
  try {
345
788
  return await real(path);
346
789
  } catch (err) {
347
- const found = missing(err) ? await virtual(path) : void 0;
790
+ const found = missing2(err) ? await linked(path) : void 0;
348
791
  if (found) return found;
349
792
  throw err;
350
793
  }
@@ -356,13 +799,13 @@ function withCommandDirs(fs, names) {
356
799
  try {
357
800
  return await fs.readFileBuffer(path);
358
801
  } catch (err) {
359
- if (missing(err) && (await virtual(path))?.isFile) return new Uint8Array(0);
802
+ if (missing2(err) && (await linked(path))?.isFile) return new Uint8Array(0);
360
803
  throw err;
361
804
  }
362
805
  },
363
806
  writeFile: (path, content) => fs.writeFile(path, content),
364
807
  async exists(path) {
365
- return await fs.exists(path) || await virtual(path) !== void 0;
808
+ return await fs.exists(path) || await linked(path) !== void 0;
366
809
  },
367
810
  stat: (path) => stat(path, (p) => fs.stat(p)),
368
811
  lstat: (path) => stat(path, (p) => fs.lstat(p)),
@@ -370,11 +813,31 @@ function withCommandDirs(fs, names) {
370
813
  const extra = await children(path);
371
814
  if (!extra) return fs.readdir(path);
372
815
  const real = await fs.readdir(path).catch((err) => {
373
- if (missing(err)) return [];
816
+ if (missing2(err)) return [];
374
817
  throw err;
375
818
  });
376
819
  return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
377
820
  },
821
+ async readdirStat(path) {
822
+ const extra = await children(path);
823
+ if (!extra) return fs.readdirStat(path);
824
+ const real = await fs.readdirStat(path).catch((err) => {
825
+ if (missing2(err)) return [];
826
+ throw err;
827
+ });
828
+ const seen = new Set(real.map(([name]) => name));
829
+ const base = path === "/" ? "" : path;
830
+ const added = extra.filter((name) => !seen.has(name));
831
+ const virtuals = await Promise.all(
832
+ added.map(
833
+ async (name) => [
834
+ name,
835
+ await virtual(`${base}/${name}`)
836
+ ]
837
+ )
838
+ );
839
+ return [...real, ...virtuals].sort(([a], [b]) => a < b ? -1 : 1);
840
+ },
378
841
  mkdir: (path, options) => fs.mkdir(path, options),
379
842
  rm: (path, options) => fs.rm(path, options),
380
843
  rename: (from, to) => fs.rename(from, to),
@@ -948,21 +1411,14 @@ function statJson(s) {
948
1411
  isSymbolicLink: s.isSymbolicLink,
949
1412
  size: s.size,
950
1413
  mode: s.mode,
951
- mtimeMs: s.mtime.getTime()
1414
+ mtimeMs: s.mtime.getTime(),
1415
+ atimeMs: s.atime.getTime(),
1416
+ ctimeMs: s.ctime.getTime(),
1417
+ ino: s.ino
952
1418
  };
953
1419
  }
954
1420
  async function readdirStat(fs, dir) {
955
- const names = await fs.readdir(dir);
956
- const base = dir === "/" ? "" : dir;
957
- return Promise.all(
958
- names.map(async (name) => {
959
- try {
960
- return [name, statJson(await fs.lstat(`${base}/${name}`))];
961
- } catch {
962
- return [name, null];
963
- }
964
- })
965
- );
1421
+ return (await fs.readdirStat(dir)).map(([name, stat]) => [name, stat && statJson(stat)]);
966
1422
  }
967
1423
  var done = { ok: true, kind: "void" };
968
1424
  function json(value) {
@@ -1103,7 +1559,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
1103
1559
  Atomics.notify(header, SAB_I_STATE);
1104
1560
  if (offset + chunk >= entry.payload.byteLength) drop(id);
1105
1561
  }
1106
- function settle(id, result) {
1562
+ function settle2(id, result) {
1107
1563
  if (disposed) return;
1108
1564
  const { status, payload } = encodeSabResult(result);
1109
1565
  drop(id);
@@ -1122,7 +1578,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
1122
1578
  const entry = pending.get(data.id);
1123
1579
  const offset = data.offset;
1124
1580
  if (!entry || typeof offset !== "number" || !Number.isInteger(offset) || offset < 0 || offset > entry.payload.byteLength) {
1125
- settle(data.id, { ok: false, errno: "EIO", message: "sync-sab: bad continuation" });
1581
+ settle2(data.id, { ok: false, errno: "EIO", message: "sync-sab: bad continuation" });
1126
1582
  return;
1127
1583
  }
1128
1584
  publish(data.id, entry, offset);
@@ -1136,7 +1592,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
1136
1592
  try {
1137
1593
  dispatched = dispatch(req);
1138
1594
  } catch (err) {
1139
- settle(id, {
1595
+ settle2(id, {
1140
1596
  ok: false,
1141
1597
  errno: "EIO",
1142
1598
  message: err instanceof Error ? err.message : String(err)
@@ -1144,8 +1600,8 @@ function attachSyncSabResponder(port, sab, token, opts) {
1144
1600
  return;
1145
1601
  }
1146
1602
  void dispatched.then(
1147
- (result) => settle(id, result),
1148
- (err) => settle(id, {
1603
+ (result) => settle2(id, result),
1604
+ (err) => settle2(id, {
1149
1605
  ok: false,
1150
1606
  errno: "EIO",
1151
1607
  message: err instanceof Error ? err.message : String(err)
@@ -1163,61 +1619,6 @@ function attachSyncSabResponder(port, sab, token, opts) {
1163
1619
  };
1164
1620
  }
1165
1621
 
1166
- // src/kernel/signals.ts
1167
- var SIG = {
1168
- HUP: 1,
1169
- INT: 2,
1170
- QUIT: 3,
1171
- ILL: 4,
1172
- TRAP: 5,
1173
- ABRT: 6,
1174
- BUS: 7,
1175
- FPE: 8,
1176
- KILL: 9,
1177
- USR1: 10,
1178
- SEGV: 11,
1179
- USR2: 12,
1180
- PIPE: 13,
1181
- ALRM: 14,
1182
- TERM: 15,
1183
- STKFLT: 16,
1184
- CHLD: 17,
1185
- CONT: 18,
1186
- STOP: 19,
1187
- TSTP: 20,
1188
- TTIN: 21,
1189
- TTOU: 22,
1190
- URG: 23,
1191
- XCPU: 24,
1192
- XFSZ: 25,
1193
- VTALRM: 26,
1194
- PROF: 27,
1195
- WINCH: 28,
1196
- POLL: 29,
1197
- PWR: 30,
1198
- SYS: 31
1199
- };
1200
- var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
1201
- var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
1202
- function defaultAction(sig) {
1203
- if (sig === SIG.CONT) return "continue";
1204
- if (STOPPING.has(sig)) return "stop";
1205
- return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
1206
- }
1207
- function isSignal(sig) {
1208
- return Number.isInteger(sig) && sig >= 1 && sig <= 31;
1209
- }
1210
- function sigbit(sig) {
1211
- return 1 << sig;
1212
- }
1213
- var SIGNAL_BY_NAME = {
1214
- SIGINT: SIG.INT,
1215
- SIGTERM: SIG.TERM,
1216
- SIGKILL: SIG.KILL,
1217
- SIGSTOP: SIG.STOP,
1218
- SIGCONT: SIG.CONT
1219
- };
1220
-
1221
1622
  // src/kernel/tty.ts
1222
1623
  var ICRNL = 256;
1223
1624
  var IUTF8 = 16384;
@@ -1878,15 +2279,15 @@ var VfsNodes = class {
1878
2279
  }
1879
2280
  renamed(from, to) {
1880
2281
  if (from === to) return;
1881
- const moved = [];
2282
+ const moved2 = [];
1882
2283
  for (const [p, node] of this.byPath) {
1883
- if (within2(p, from)) moved.push(node);
2284
+ if (within2(p, from)) moved2.push(node);
1884
2285
  else if (within2(p, to)) {
1885
2286
  node.orphaned = true;
1886
2287
  this.byPath.delete(p);
1887
2288
  }
1888
2289
  }
1889
- for (const node of moved) {
2290
+ for (const node of moved2) {
1890
2291
  this.byPath.delete(node.path);
1891
2292
  node.path = to + node.path.slice(from.length);
1892
2293
  this.byPath.set(node.path, node);
@@ -2547,8 +2948,8 @@ function spawnWasmProcess(opts) {
2547
2948
  return dispatchSyncFs(req);
2548
2949
  };
2549
2950
  const responder = attachSyncSabResponder(worker, sab, token, { dispatch });
2550
- let settle;
2551
- const exited = new Promise((resolve) => settle = resolve);
2951
+ let settle2;
2952
+ const exited = new Promise((resolve) => settle2 = resolve);
2552
2953
  let done3 = false;
2553
2954
  let endedBy;
2554
2955
  const finish = (code, sig) => {
@@ -2561,8 +2962,8 @@ function spawnWasmProcess(opts) {
2561
2962
  revokeSyncFsToken(token);
2562
2963
  worker.terminate();
2563
2964
  void process.exit().then(
2564
- () => settle(code),
2565
- () => settle(code)
2965
+ () => settle2(code),
2966
+ () => settle2(code)
2566
2967
  );
2567
2968
  };
2568
2969
  const onMessage = (event) => {
@@ -2719,6 +3120,7 @@ var JobTable = class {
2719
3120
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
2720
3121
  var SHEBANG_MAX = 256;
2721
3122
  var NOT_FOUND = 127;
3123
+ var SHARED_DIRS = ["/tmp", "/home"];
2722
3124
  var encoder3 = new TextEncoder();
2723
3125
  function baseName(path) {
2724
3126
  return path.slice(path.lastIndexOf("/") + 1);
@@ -2766,6 +3168,7 @@ var Launcher = class {
2766
3168
  jobs = new JobTable();
2767
3169
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
2768
3170
  nextPid = 1e3;
3171
+ terminals = 0;
2769
3172
  constructor(options) {
2770
3173
  this.base = options.fs;
2771
3174
  this.createWorker = options.createWorker;
@@ -2783,7 +3186,9 @@ var Launcher = class {
2783
3186
  const command = (await this.commands()).get(name);
2784
3187
  return command && targetOf(command);
2785
3188
  }
2786
- const glue = this.fs.resolvePath(cwd, file);
3189
+ const glue = await followLinks(this.base, this.fs.resolvePath(cwd, file));
3190
+ const linked = COMMAND2.exec(glue)?.[1];
3191
+ if (linked !== void 0) return this.resolve(`/bin/${linked}`, argv0, cwd);
2787
3192
  const wasm = modulePath(glue);
2788
3193
  if (!await this.base.exists(glue) || !await this.base.exists(wasm)) return void 0;
2789
3194
  return { glue, wasm, argv0: baseName(argv0 || file) };
@@ -2815,6 +3220,10 @@ var Launcher = class {
2815
3220
  if (direct) return { target: direct, args: [...direct.prefix ?? [], ...argv.slice(1)] };
2816
3221
  return this.interpreted(file, argv, cwd);
2817
3222
  }
3223
+ async unrunnable(file, cwd) {
3224
+ const head = await this.base.readFileBuffer(this.fs.resolvePath(cwd, file)).catch(() => null);
3225
+ return head && !(head[0] === 35 && head[1] === 33) ? "ENOEXEC" : "ENOENT";
3226
+ }
2818
3227
  module(path) {
2819
3228
  return this.base.stat(path).then((st) => {
2820
3229
  const key = `${path}:${st.size}:${st.mtime.getTime()}`;
@@ -2889,7 +3298,7 @@ var Launcher = class {
2889
3298
  const planned = await this.plan(req.file, req.argv, req.cwd);
2890
3299
  if (!planned) {
2891
3300
  await fds.closeAll();
2892
- throw new SpawnError("ENOENT");
3301
+ throw new SpawnError(await this.unrunnable(req.file, req.cwd));
2893
3302
  }
2894
3303
  const handle = await this.launch(planned, { env: req.env, cwd: req.cwd, fds, report, ppid });
2895
3304
  return childHandle(handle);
@@ -2905,10 +3314,59 @@ var Launcher = class {
2905
3314
  if (sig !== 0) handle.signal(sig);
2906
3315
  return true;
2907
3316
  }
2908
- async run(argv, options = {}) {
3317
+ async prepare() {
3318
+ for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
3319
+ }
3320
+ environment(cwd, extra) {
3321
+ return { PATH: "/usr/bin:/bin", HOME: "/home", ...this.env, PWD: cwd, ...extra };
3322
+ }
3323
+ async starting(argv, dir) {
2909
3324
  this.catalog = void 0;
2910
- const cwd = this.fs.resolvePath("/", options.cwd ?? "/");
2911
- const env = { PATH: "/usr/bin:/bin", HOME: "/", ...this.env, PWD: cwd, ...options.env };
3325
+ const cwd = this.fs.resolvePath("/", dir ?? "/");
3326
+ const [file = ""] = argv;
3327
+ await this.base.mkdir(cwd, { recursive: true });
3328
+ return { cwd, file, planned: await this.plan(file, argv, cwd) };
3329
+ }
3330
+ async openTerminal(argv, options) {
3331
+ const { cwd, file, planned } = await this.starting(argv, options.cwd);
3332
+ if (!planned) throw new Error(`${file}: command not found`);
3333
+ let leader = 0;
3334
+ const tty = new KernelTty(
3335
+ { write: (bytes) => options.onData(bytes.slice()) },
3336
+ (sig) => this.jobs.signalForeground(tty, leader, sig)
3337
+ );
3338
+ tty.name = `/dev/tty${++this.terminals}`;
3339
+ tty.setSize(options.cols ?? 80, options.rows ?? 24);
3340
+ const fds = new FdTable();
3341
+ const stdio = tty.file();
3342
+ fds.installAt(0, stdio);
3343
+ fds.installAt(1, stdio.retain());
3344
+ fds.installAt(2, stdio.retain());
3345
+ const env = this.environment(cwd, {
3346
+ TERM: "xterm-256color",
3347
+ COLORTERM: "truecolor",
3348
+ ...options.env
3349
+ });
3350
+ const report = (message) => options.onData(encoder3.encode(`${message}\r
3351
+ `));
3352
+ const handle = await this.launch(planned, { env, cwd, fds, report });
3353
+ leader = handle.pid;
3354
+ void handle.exited.then(() => tty.hangup());
3355
+ return {
3356
+ pid: handle.pid,
3357
+ exited: handle.exited,
3358
+ write: (bytes) => tty.receive(bytes),
3359
+ resize: (cols, rows) => tty.resize(cols, rows),
3360
+ signal: (sig) => this.jobs.signalForeground(tty, leader, sig),
3361
+ close: () => {
3362
+ tty.signalHangup();
3363
+ tty.hangup();
3364
+ }
3365
+ };
3366
+ }
3367
+ async run(argv, options = {}) {
3368
+ const { cwd, file, planned } = await this.starting(argv, options.cwd);
3369
+ const env = this.environment(cwd, options.env);
2912
3370
  const out = [];
2913
3371
  const err = [];
2914
3372
  const collect = (chunks, tee) => (bytes) => {
@@ -2919,9 +3377,6 @@ var Launcher = class {
2919
3377
  const stderr = collect(err, options.onStderr);
2920
3378
  const report = (message) => stderr(encoder3.encode(`${message}
2921
3379
  `));
2922
- const [file = ""] = argv;
2923
- await this.base.mkdir(cwd, { recursive: true });
2924
- const planned = await this.plan(file, argv, cwd);
2925
3380
  if (!planned) {
2926
3381
  report(`${file}: command not found`);
2927
3382
  return { status: NOT_FOUND, stdout: concat2(out), stderr: concat2(err) };
@@ -2937,24 +3392,64 @@ var Launcher = class {
2937
3392
  };
2938
3393
 
2939
3394
  // src/serve.ts
3395
+ function signalNumber(name) {
3396
+ const key = name.slice(3);
3397
+ if (!name.startsWith("SIG") || !Object.hasOwn(SIG, key))
3398
+ throw new Error(`unknown signal ${name}`);
3399
+ return SIG[key];
3400
+ }
3401
+ function act(session, req) {
3402
+ if (req.action === "write") session.write(req.bytes);
3403
+ else if (req.action === "resize") session.resize(req.cols, req.rows);
3404
+ else if (req.action === "signal") session.signal(signalNumber(req.signal));
3405
+ else session.close();
3406
+ }
2940
3407
  function serveKernel(port, deps) {
2941
3408
  let launcher;
3409
+ const terminals = /* @__PURE__ */ new Map();
2942
3410
  const reply = (id, body) => port.postMessage({ id, ...body });
3411
+ async function ready2() {
3412
+ if (!launcher) throw new Error("the kernel is not initialized");
3413
+ return launcher;
3414
+ }
3415
+ async function terminal(req) {
3416
+ const onData = (bytes) => reply(req.id, { fd: 1, bytes });
3417
+ const session = await (await ready2()).openTerminal(req.argv, { ...req.options, onData });
3418
+ terminals.set(req.id, session);
3419
+ reply(req.id, { started: session.pid });
3420
+ try {
3421
+ return await session.exited;
3422
+ } finally {
3423
+ terminals.delete(req.id);
3424
+ }
3425
+ }
2943
3426
  async function handle(req) {
2944
3427
  if (req.op === "init") {
2945
- launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(
2946
- (root) => new Launcher({
2947
- fs: new OpfsFs(root),
3428
+ launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(async (root) => {
3429
+ const name = req.metadata ?? META_DB;
3430
+ const meta = name === false ? void 0 : await deps.metadata?.(name);
3431
+ const fs = new OpfsFs(root, meta);
3432
+ await fs.reconcile();
3433
+ const started = new Launcher({
3434
+ fs,
2948
3435
  createWorker: deps.createWorker,
2949
3436
  ...req.modules ? { modules: req.modules } : {},
2950
3437
  ...req.env ? { env: req.env } : {}
2951
- })
2952
- );
3438
+ });
3439
+ await started.prepare();
3440
+ return started;
3441
+ });
2953
3442
  await launcher;
2954
3443
  return true;
2955
3444
  }
2956
- if (!launcher) throw new Error("the kernel is not initialized");
2957
- return (await launcher).run(req.argv, {
3445
+ if (req.op === "open-terminal") return terminal(req);
3446
+ if (req.op === "terminal") {
3447
+ const session = terminals.get(req.terminal);
3448
+ if (!session) throw new Error(`no terminal ${req.terminal}`);
3449
+ act(session, req);
3450
+ return true;
3451
+ }
3452
+ return (await ready2()).run(req.argv, {
2958
3453
  ...req.options,
2959
3454
  onStdout: (bytes) => reply(req.id, { fd: 1, bytes }),
2960
3455
  onStderr: (bytes) => reply(req.id, { fd: 2, bytes })
@@ -2973,6 +3468,7 @@ function serveKernel(port, deps) {
2973
3468
  var processWorker = new URL("./process-worker.js", import.meta.url);
2974
3469
  serveKernel(globalThis, {
2975
3470
  storage: () => navigator.storage.getDirectory(),
2976
- createWorker: () => new Worker(processWorker, { type: "module" })
3471
+ createWorker: () => new Worker(processWorker, { type: "module" }),
3472
+ metadata: (name) => openMeta(name, indexedDB)
2977
3473
  });
2978
3474
  //# sourceMappingURL=kernel-worker.js.map