@zenera/sandbox 1.1.57 → 1.1.58

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/dist/fs.d.ts CHANGED
@@ -13,12 +13,12 @@ export interface FsDriver {
13
13
  /** how this driver's paths are spelled: the host's for `localFs`, posix otherwise */
14
14
  readonly path: PlatformPath;
15
15
  /**
16
- * The path with every symlink resolved; throws `ENOENT` when it does not
17
- * exist. Synchronous because containment is decided synchronously. A
18
- * driver whose files live elsewhere may answer lexically here, provided
19
- * the far side refuses an escape on its own.
16
+ * Where a path really is: absolute, with every symlink in the part that
17
+ * exists resolved, and the part that does not exist yet appended as
18
+ * written — what a create needs, and what containment is decided on. A
19
+ * path through a file is `ENOTDIR`, as `realpath` says.
20
20
  */
21
- realpathSync(path: string): string;
21
+ resolve(path: string): Promise<string>;
22
22
  stat(path: string): Promise<FsStat>;
23
23
  readdir(path: string): Promise<FsEntry[]>;
24
24
  readFile(path: string): Promise<Buffer>;
package/dist/fs.js CHANGED
@@ -1,10 +1,25 @@
1
- import { realpathSync } from 'node:fs';
2
- import { cp, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
1
+ import { cp, mkdir, open, readFile, readdir, realpath, rename, rm, stat, writeFile, } from 'node:fs/promises';
3
2
  import path, {} from 'node:path';
4
3
  /** This machine's disk, through `node:fs`. */
5
4
  export const localFs = {
6
5
  path,
7
- realpathSync: (p) => realpathSync(p),
6
+ async resolve(p) {
7
+ const missing = [];
8
+ let at = path.resolve(p);
9
+ for (;;) {
10
+ try {
11
+ return path.join(await realpath(at), ...missing.reverse());
12
+ }
13
+ catch (err) {
14
+ const up = path.dirname(at);
15
+ if (err.code !== 'ENOENT' || up === at) {
16
+ throw err;
17
+ }
18
+ missing.push(path.basename(at));
19
+ at = up;
20
+ }
21
+ }
22
+ },
8
23
  stat: (p) => stat(p),
9
24
  readdir: (p) => readdir(p, { withFileTypes: true }),
10
25
  readFile: (p) => readFile(p),
@@ -6,7 +6,8 @@ export declare class InMemoryFs implements FsDriver {
6
6
  constructor(files?: Record<string, string | Uint8Array>);
7
7
  /** An empty directory, with its parents. */
8
8
  dir(at: string): this;
9
- realpathSync(at: string): string;
9
+ /** No symlinks here, so only a path through a file needs saying anything about. */
10
+ resolve(at: string): Promise<string>;
10
11
  stat(at: string): Promise<FsStat>;
11
12
  readdir(at: string): Promise<FsEntry[]>;
12
13
  readFile(at: string): Promise<Buffer>;
@@ -15,10 +15,15 @@ export class InMemoryFs {
15
15
  this.#mkdirs(this.#abs(at));
16
16
  return this;
17
17
  }
18
- realpathSync(at) {
18
+ /** No symlinks here, so only a path through a file needs saying anything about. */
19
+ async resolve(at) {
19
20
  const abs = this.#abs(at);
20
- if (!this.#nodes.has(abs)) {
21
- throw fsError('ENOENT', 'realpath', at);
21
+ let probe = abs;
22
+ while (!this.#nodes.has(probe)) {
23
+ probe = posix.dirname(probe);
24
+ }
25
+ if (probe !== abs && this.#nodes.get(probe)?.kind === 'file') {
26
+ throw fsError('ENOTDIR', 'realpath', at);
22
27
  }
23
28
  return abs;
24
29
  }
package/dist/testing.js CHANGED
@@ -52,8 +52,18 @@ export function describeFsDriver(name, make) {
52
52
  code: 'ENOENT',
53
53
  path: expect.stringContaining('ghost.txt'),
54
54
  });
55
- expect(() => fs.realpathSync(at('ghost', 'deeper'))).toThrow(expect.objectContaining({ code: 'ENOENT' }));
56
- expect(fs.realpathSync(at())).toBe(fs.realpathSync(at('.')));
55
+ expect(await fs.resolve(at('.'))).toBe(await fs.resolve(at()));
56
+ }));
57
+ it('resolves where a path really is, the part that does not exist yet included', withFs(async ({ fs }, at) => {
58
+ const root = await fs.resolve(at());
59
+ await fs.mkdirp(at('real', 'dir'));
60
+ expect(await fs.resolve(at('real', 'dir'))).toBe(fs.path.join(root, 'real', 'dir'));
61
+ expect(await fs.resolve(at('real', 'ghost', 'deeper.txt'))).toBe(fs.path.join(root, 'real', 'ghost', 'deeper.txt'));
62
+ expect(await fs.resolve(at('real', 'dir', '..', 'x'))).toBe(fs.path.join(root, 'real', 'x'));
63
+ await fs.writeFile(at('file.txt'), 'x');
64
+ await expect(fs.resolve(at('file.txt', 'under'))).rejects.toMatchObject({
65
+ code: 'ENOTDIR',
66
+ });
57
67
  }));
58
68
  });
59
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/sandbox",
3
- "version": "1.1.57",
3
+ "version": "1.1.58",
4
4
  "description": "Where an agent's commands run and the files they see: the Sandbox contract, a podman implementation, filesystem drivers and a contract test suite for other implementations.",
5
5
  "keywords": [
6
6
  "sandbox",