@zenfs/core 1.8.4 → 1.8.6

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.
@@ -103,7 +103,9 @@ export class OverlayFS extends FileSystem {
103
103
  this.writable.syncSync(path, data, stats);
104
104
  }
105
105
  async read(path, buffer, offset, end) {
106
- return (await this.writable.exists(path)) ? await this.writable.read(path, buffer, offset, end) : await this.readable.read(path, buffer, offset, end);
106
+ return (await this.writable.exists(path))
107
+ ? await this.writable.read(path, buffer, offset, end)
108
+ : await this.readable.read(path, buffer, offset, end);
107
109
  }
108
110
  readSync(path, buffer, offset, end) {
109
111
  return this.writable.existsSync(path) ? this.writable.readSync(path, buffer, offset, end) : this.readable.readSync(path, buffer, offset, end);
@@ -403,7 +405,7 @@ export class OverlayFS extends FileSystem {
403
405
  }
404
406
  checkInitialized() {
405
407
  if (!this._isInitialized) {
406
- throw new ErrnoError(Errno.EPERM, 'OverlayFS is not initialized. Please initialize OverlayFS using its initialize() method before using it.');
408
+ throw new ErrnoError(Errno.EPERM, 'Overlay is not initialized');
407
409
  }
408
410
  if (!this._deleteLogError) {
409
411
  return;
@@ -4,6 +4,7 @@ import { Errno, ErrnoError } from '../../error.js';
4
4
  import { FileSystem } from '../../filesystem.js';
5
5
  import { Async } from '../../mixins/async.js';
6
6
  import { Stats } from '../../stats.js';
7
+ import { decodeUTF8 } from '../../utils.js';
7
8
  import { InMemory } from '../memory.js';
8
9
  import * as RPC from './rpc.js';
9
10
  /**
@@ -33,7 +34,10 @@ export class PortFS extends Async(FileSystem) {
33
34
  };
34
35
  }
35
36
  rpc(method, ...args) {
36
- return RPC.request({ method, args }, { ...this.options, fs: this });
37
+ return RPC.request({ method, args }, {
38
+ ...this.options,
39
+ fs: this,
40
+ });
37
41
  }
38
42
  async ready() {
39
43
  await this.rpc('ready');
@@ -56,7 +56,7 @@ export interface FileData {
56
56
  flag: string;
57
57
  stats: StatsLike<number>;
58
58
  }
59
- export { FileData as File };
59
+ export type { FileData as File };
60
60
  export declare function isMessage(arg: unknown): arg is Message;
61
61
  type _Executor = Parameters<ConstructorParameters<typeof Promise<any>>[0]>;
62
62
  export interface Executor {
@@ -23,8 +23,10 @@ export declare class Index extends Map<string, Readonly<Inode>> {
23
23
  * Converts the index to a string
24
24
  */
25
25
  toString(): string;
26
+ directoryEntries(path: string): Record<string, number>;
26
27
  /**
27
- * Gets a list of entries for each directory in the index. Memoized.
28
+ * Gets a list of entries for each directory in the index.
29
+ * Use
28
30
  */
29
31
  directories(): Map<string, Record<string, number>>;
30
32
  /**
@@ -25,12 +25,25 @@ export class Index extends Map {
25
25
  toString() {
26
26
  return JSON.stringify(this.toJSON());
27
27
  }
28
+ directoryEntries(path) {
29
+ const node = this.get(path);
30
+ if (!node)
31
+ throw ErrnoError.With('ENOENT', path);
32
+ if ((node.mode & S_IFMT) != S_IFDIR)
33
+ throw ErrnoError.With('ENOTDIR', path);
34
+ const entries = {};
35
+ for (const entry of this.keys()) {
36
+ if (dirname(entry) == path && entry != path) {
37
+ entries[basename(entry)] = this.get(entry).ino;
38
+ }
39
+ }
40
+ return entries;
41
+ }
28
42
  /**
29
- * Gets a list of entries for each directory in the index. Memoized.
43
+ * Gets a list of entries for each directory in the index.
44
+ * Use
30
45
  */
31
46
  directories() {
32
- if (this._directories)
33
- return this._directories;
34
47
  const dirs = new Map();
35
48
  for (const [path, node] of this) {
36
49
  if ((node.mode & S_IFMT) != S_IFDIR)
@@ -42,7 +55,6 @@ export class Index extends Map {
42
55
  }
43
56
  dirs.set(path, entries);
44
57
  }
45
- this._directories = dirs;
46
58
  return dirs;
47
59
  }
48
60
  /**
@@ -247,7 +247,9 @@ export class StoreFS extends FileSystem {
247
247
  const sameParent = _new.dir == _old.dir;
248
248
  // Prevent us from re-grabbing the same directory listing, which still contains `old_path.base.`
249
249
  const newDirNode = sameParent ? oldDirNode : await this.findInode(tx, _new.dir, 'rename');
250
- const newDirList = sameParent ? oldDirList : decodeDirListing((_b = (await tx.get(newDirNode.data))) !== null && _b !== void 0 ? _b : _throw(ErrnoError.With('ENOENT', _new.dir, 'rename')));
250
+ const newDirList = sameParent
251
+ ? oldDirList
252
+ : decodeDirListing((_b = (await tx.get(newDirNode.data))) !== null && _b !== void 0 ? _b : _throw(ErrnoError.With('ENOENT', _new.dir, 'rename')));
251
253
  if (newDirList[_new.base]) {
252
254
  // If it's a file, delete it, if it's a directory, throw a permissions error.
253
255
  const existing = new Inode((_c = (await tx.get(newDirList[_new.base]))) !== null && _c !== void 0 ? _c : _throw(ErrnoError.With('ENOENT', newPath, 'rename')));
@@ -298,7 +300,9 @@ export class StoreFS extends FileSystem {
298
300
  const sameParent = _new.dir === _old.dir;
299
301
  // Prevent us from re-grabbing the same directory listing, which still contains `old_path.base.`
300
302
  const newDirNode = sameParent ? oldDirNode : this.findInodeSync(tx, _new.dir, 'rename');
301
- const newDirList = sameParent ? oldDirList : decodeDirListing((_b = tx.getSync(newDirNode.data)) !== null && _b !== void 0 ? _b : _throw(ErrnoError.With('ENOENT', _new.dir, 'rename')));
303
+ const newDirList = sameParent
304
+ ? oldDirList
305
+ : decodeDirListing((_b = tx.getSync(newDirNode.data)) !== null && _b !== void 0 ? _b : _throw(ErrnoError.With('ENOENT', _new.dir, 'rename')));
302
306
  if (newDirList[_new.base]) {
303
307
  // If it's a file, delete it, if it's a directory, throw a permissions error.
304
308
  const existing = new Inode((_c = tx.getSync(newDirList[_new.base])) !== null && _c !== void 0 ? _c : _throw(ErrnoError.With('ENOENT', newPath, 'rename')));
@@ -692,7 +696,9 @@ export class StoreFS extends FileSystem {
692
696
  return rootIno;
693
697
  }
694
698
  const { dir: parent, base: filename } = parse(path);
695
- const inode = parent == '/' ? new Inode((_a = (await tx.get(rootIno))) !== null && _a !== void 0 ? _a : _throw(ErrnoError.With('ENOENT', parent, syscall))) : await this.findInode(tx, parent, syscall, visited);
699
+ const inode = parent == '/'
700
+ ? new Inode((_a = (await tx.get(rootIno))) !== null && _a !== void 0 ? _a : _throw(ErrnoError.With('ENOENT', parent, syscall)))
701
+ : await this.findInode(tx, parent, syscall, visited);
696
702
  const dirList = decodeDirListing((_b = (await tx.get(inode.data))) !== null && _b !== void 0 ? _b : _throw(ErrnoError.With('ENODATA', parent, syscall)));
697
703
  if (!(filename in dirList)) {
698
704
  throw ErrnoError.With('ENOENT', resolve(parent, filename), syscall);
@@ -716,7 +722,9 @@ export class StoreFS extends FileSystem {
716
722
  return rootIno;
717
723
  }
718
724
  const { dir: parent, base: filename } = parse(path);
719
- const inode = parent == '/' ? new Inode((_a = tx.getSync(rootIno)) !== null && _a !== void 0 ? _a : _throw(ErrnoError.With('ENOENT', parent, syscall))) : this.findInodeSync(tx, parent, syscall, visited);
725
+ const inode = parent == '/'
726
+ ? new Inode((_a = tx.getSync(rootIno)) !== null && _a !== void 0 ? _a : _throw(ErrnoError.With('ENOENT', parent, syscall)))
727
+ : this.findInodeSync(tx, parent, syscall, visited);
720
728
  const dir = decodeDirListing((_b = tx.getSync(inode.data)) !== null && _b !== void 0 ? _b : _throw(ErrnoError.With('ENODATA', parent, syscall)));
721
729
  if (!(filename in dir)) {
722
730
  throw ErrnoError.With('ENOENT', resolve(parent, filename), syscall);
@@ -109,7 +109,7 @@ let Inode = (() => {
109
109
  this.data = __runInitializers(this, _data_initializers, randomInt(0, size_max));
110
110
  /** For future use */
111
111
  this.__data_old = (__runInitializers(this, _data_extraInitializers), __runInitializers(this, ___data_old_initializers, 0));
112
- this.size = (__runInitializers(this, ___data_old_extraInitializers), __runInitializers(this, _size_initializers, 4096));
112
+ this.size = (__runInitializers(this, ___data_old_extraInitializers), __runInitializers(this, _size_initializers, 0));
113
113
  this.mode = (__runInitializers(this, _size_extraInitializers), __runInitializers(this, _mode_initializers, 0));
114
114
  this.nlink = (__runInitializers(this, _mode_extraInitializers), __runInitializers(this, _nlink_initializers, 1));
115
115
  this.uid = (__runInitializers(this, _nlink_extraInitializers), __runInitializers(this, _uid_initializers, 0));
@@ -243,7 +243,19 @@ export function Async(FS) {
243
243
  * Patch all async methods to also call their synchronous counterparts unless called from the queue
244
244
  */
245
245
  _patchAsync() {
246
- const asyncFSMethodKeys = ['rename', 'stat', 'createFile', 'openFile', 'unlink', 'rmdir', 'mkdir', 'readdir', 'link', 'sync', 'exists'];
246
+ const asyncFSMethodKeys = [
247
+ 'rename',
248
+ 'stat',
249
+ 'createFile',
250
+ 'openFile',
251
+ 'unlink',
252
+ 'rmdir',
253
+ 'mkdir',
254
+ 'readdir',
255
+ 'link',
256
+ 'sync',
257
+ 'exists',
258
+ ];
247
259
  for (const key of asyncFSMethodKeys) {
248
260
  if (typeof this[key] !== 'function')
249
261
  continue;
package/dist/stats.js CHANGED
@@ -231,7 +231,11 @@ export class BigIntStats extends StatsCommon {
231
231
  * @internal
232
232
  */
233
233
  export function isStatsEqual(left, right) {
234
- return left.size == right.size && +left.atime == +right.atime && +left.mtime == +right.mtime && +left.ctime == +right.ctime && left.mode == right.mode;
234
+ return (left.size == right.size &&
235
+ +left.atime == +right.atime &&
236
+ +left.mtime == +right.mtime &&
237
+ +left.ctime == +right.ctime &&
238
+ left.mode == right.mode);
235
239
  }
236
240
  /** @internal */
237
241
  export const ZenFsType = 0x7a656e6673; // 'z' 'e' 'n' 'f' 's'
package/dist/utils.js CHANGED
@@ -56,7 +56,7 @@ export { /** @deprecated @hidden */ decodeUTF8 as decode };
56
56
  * @hidden
57
57
  */
58
58
  export function decodeDirListing(data) {
59
- return JSON.parse(decodeUTF8(data), (k, v) => (k == '' ? v : typeof v == 'string' ? BigInt(v).toString(16).slice(0, Math.min(v.length, 8)) : v));
59
+ return JSON.parse(decodeUTF8(data), (k, v) => k == '' ? v : typeof v == 'string' ? BigInt(v).toString(16).slice(0, Math.min(v.length, 8)) : v);
60
60
  }
61
61
  /**
62
62
  * Encodes a directory listing
@@ -564,7 +564,11 @@ export async function writeFile(path, data, _options) {
564
564
  try {
565
565
  const options = normalizeOptions(_options, 'utf8', 'w+', 0o644);
566
566
  const handle = __addDisposableResource(env_3, path instanceof FileHandle ? path : await open.call(this, path.toString(), options.flag, options.mode), true);
567
- const _data = typeof data == 'string' ? data : data instanceof DataView ? new Uint8Array(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)) : data;
567
+ const _data = typeof data == 'string'
568
+ ? data
569
+ : data instanceof DataView
570
+ ? new Uint8Array(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength))
571
+ : data;
568
572
  if (typeof _data != 'string' && !(_data instanceof Uint8Array)) {
569
573
  throw new ErrnoError(Errno.EINVAL, 'The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received ' + typeof data, handle.file.path, 'writeFile');
570
574
  }
@@ -1110,7 +1114,10 @@ export async function cp(source, destination, opts) {
1110
1114
  if (!(opts === null || opts === void 0 ? void 0 : opts.recursive)) {
1111
1115
  throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp');
1112
1116
  }
1113
- const [entries] = await Promise.all([readdir.call(this, source, { withFileTypes: true }), mkdir.call(this, destination, { recursive: true })] // Ensure the destination directory exists
1117
+ const [entries] = await Promise.all([
1118
+ readdir.call(this, source, { withFileTypes: true }),
1119
+ mkdir.call(this, destination, { recursive: true }),
1120
+ ] // Ensure the destination directory exists
1114
1121
  );
1115
1122
  const _cp = async (dirent) => {
1116
1123
  if (opts.filter && !opts.filter(join(source, dirent.name), join(destination, dirent.name))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -15,7 +15,8 @@
15
15
  ],
16
16
  "bin": {
17
17
  "make-index": "scripts/make-index.js",
18
- "zenfs-test": "scripts/test.js"
18
+ "zenfs-test": "scripts/test.js",
19
+ "zci": "scripts/ci-cli.js"
19
20
  },
20
21
  "files": [
21
22
  "dist",
@@ -73,14 +74,15 @@
73
74
  },
74
75
  "devDependencies": {
75
76
  "@eslint/js": "^9.8.0",
77
+ "@octokit/action": "^7.0.0",
76
78
  "@types/eslint__js": "^8.42.3",
79
+ "c8": "^10.1.2",
77
80
  "eslint": "^9.15.0",
78
81
  "globals": "^15.9.0",
79
82
  "prettier": "^3.2.5",
80
83
  "tsx": "^4.19.1",
81
84
  "typedoc": "^0.27.1",
82
85
  "typescript": "^5.7.2",
83
- "typescript-eslint": "^8.16.0",
84
- "c8": "^10.1.2"
86
+ "typescript-eslint": "^8.16.0"
85
87
  }
86
88
  }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import { parseArgs } from 'node:util';
4
+ import * as ci from './ci.js';
5
+
6
+ const {
7
+ values: options,
8
+ positionals: [subcommand, ...args],
9
+ } = parseArgs({
10
+ options: {
11
+ help: { short: 'h', type: 'boolean', default: false },
12
+ },
13
+ allowPositionals: true,
14
+ });
15
+
16
+ if (options.help || !subcommand) {
17
+ console.log(`Usage:
18
+ zci [-h | --help] Show this help message
19
+ zci init Create checks in a queued state
20
+ zci cleanup Mark all remaining checks are completed with a neutral status
21
+ zci run <name> <cmd> Run a command and use its exit code as the completion status
22
+ zci list List checks`);
23
+ process.exit();
24
+ }
25
+
26
+ switch (subcommand) {
27
+ case 'init':
28
+ for (const [id, name] of Object.entries(ci.checkNames)) {
29
+ await ci.createCheck(id, name);
30
+ }
31
+ break;
32
+ case 'list': {
33
+ const max = Math.max(...Object.keys(ci.checkNames).map(id => id.length)) + 1;
34
+ for (const [id, name] of Object.entries(ci.checkNames)) {
35
+ console.log(id.padEnd(max), name);
36
+ }
37
+ break;
38
+ }
39
+ case 'run': {
40
+ const [name, ...command] = args;
41
+
42
+ await ci.startCheck(name);
43
+
44
+ const { status } = spawnSync(command.join(' '), { shell: true, stdio: 'inherit' });
45
+
46
+ await ci.completeCheck(name, status ? 'failure' : 'success');
47
+
48
+ process.exit(status);
49
+ }
50
+ case 'cleanup': {
51
+ for (const id of Object.keys(ci.checkNames)) {
52
+ await ci.completeCheck(id, 'neutral');
53
+ }
54
+ break;
55
+ }
56
+ default:
57
+ console.error('Unknown subcommand:', subcommand);
58
+ process.exit(1);
59
+ }
package/scripts/test.js CHANGED
@@ -1,25 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { execSync } from 'node:child_process';
4
- import { existsSync, mkdirSync, rmSync, globSync } from 'node:fs';
5
- import { join } from 'node:path';
4
+ import { existsSync, globSync, mkdirSync, rmSync } from 'node:fs';
5
+ import { join, parse } from 'node:path';
6
6
  import { parseArgs } from 'node:util';
7
7
 
8
8
  const { values: options, positionals } = parseArgs({
9
9
  options: {
10
+ // Output
10
11
  help: { short: 'h', type: 'boolean', default: false },
11
12
  verbose: { short: 'w', type: 'boolean', default: false },
12
13
  quiet: { short: 'q', type: 'boolean', default: false },
14
+ 'file-names': { short: 'N', type: 'boolean', default: false },
15
+ ci: { short: 'C', type: 'boolean', default: false },
16
+
17
+ // Test behavior
13
18
  test: { short: 't', type: 'string' },
14
19
  force: { short: 'f', type: 'boolean', default: false },
15
20
  auto: { short: 'a', type: 'boolean', default: false },
16
21
  build: { short: 'b', type: 'boolean', default: false },
17
22
  common: { short: 'c', type: 'boolean', default: false },
23
+ inspect: { short: 'I', type: 'boolean', default: false },
24
+ 'exit-on-fail': { short: 'e', type: 'boolean' },
25
+
26
+ // Coverage
18
27
  coverage: { type: 'string', default: 'tests/.coverage' },
19
28
  preserve: { short: 'p', type: 'boolean' },
20
- 'exit-on-fail': { short: 'e', type: 'boolean' },
21
- report: { type: 'boolean' },
22
- clean: { type: 'boolean' },
29
+ report: { type: 'boolean', default: false },
30
+ clean: { type: 'boolean', default: false },
23
31
  },
24
32
  allowPositionals: true,
25
33
  });
@@ -29,21 +37,27 @@ if (options.help) {
29
37
 
30
38
  Paths: The setup files to run tests on
31
39
 
32
- Options:
40
+ Behavior:
33
41
  -a, --auto Automatically detect setup files
34
42
  -b, --build Run the npm build script prior to running tests
35
43
  -c, --common Also run tests not specific to any backend
36
44
  -e, --exit-on-fail If any tests suites fail, exit immediately
45
+ -t, --test <glob> Which FS test suite(s) to run
46
+ -f, --force Whether to use --test-force-exit
47
+ -I, --inspect Use the inspector for debugging
48
+
49
+ Output:
37
50
  -h, --help Outputs this help message
38
51
  -w, --verbose Output verbose messages
39
52
  -q, --quiet Don't output normal messages
40
- -t, --test <glob> Which FS test suite(s) to run
41
- -f, --force Whether to use --test-force-exit
53
+ -N, --file-names Use full file paths for tests from setup files instead of the base name
54
+ -C, --ci Continuous integration (CI) mode. This interacts with the Github
55
+ Checks API for better test status. Requires @octokit/action
42
56
 
43
57
  Coverage:
44
58
  --coverage <dir> Override the default coverage data directory
45
- -p,--preserve Do not delete or report coverage data
46
- --report ONLY report coverage
59
+ -p, --preserve Do not delete or report coverage data
60
+ --report ONLY report coverage
47
61
  --clean ONLY clean up coverage directory`);
48
62
  process.exit();
49
63
  }
@@ -66,6 +80,9 @@ if (options.report) {
66
80
  process.exit();
67
81
  }
68
82
 
83
+ let ci;
84
+ if (options.ci) ci = await import('./ci.js');
85
+
69
86
  options.verbose && options.force && console.debug('Forcing tests to exit (--test-force-exit)');
70
87
 
71
88
  if (options.build) {
@@ -104,9 +121,11 @@ function color(text, code) {
104
121
  return `\x1b[${code}m${text}\x1b[0m`;
105
122
  }
106
123
 
107
- function status(name) {
124
+ async function status(name) {
108
125
  const start = performance.now();
109
126
 
127
+ if (options.ci) await ci.startCheck(name);
128
+
110
129
  const time = () => {
111
130
  let delta = Math.round(performance.now() - start),
112
131
  unit = 'ms';
@@ -120,11 +139,13 @@ function status(name) {
120
139
  };
121
140
 
122
141
  return {
123
- pass() {
142
+ async pass() {
124
143
  if (!options.quiet) console.log(`${color('passed', 32)}: ${name} ${time()}`);
144
+ if (options.ci) await ci.completeCheck(name, 'success');
125
145
  },
126
- fail() {
146
+ async fail() {
127
147
  console.error(`${color('failed', '1;31')}: ${name} ${time()}`);
148
+ if (options.ci) await ci.completeCheck(name, 'failure');
128
149
  process.exitCode = 1;
129
150
  if (options['exit-on-fail']) process.exit();
130
151
  },
@@ -136,14 +157,14 @@ mkdirSync(options.coverage, { recursive: true });
136
157
 
137
158
  if (options.common) {
138
159
  !options.quiet && console.log('Running common tests...');
139
- const { pass, fail } = status('Common tests');
160
+ const { pass, fail } = await status('Common tests');
140
161
  try {
141
- execSync("tsx --test --experimental-test-coverage 'tests/*.test.ts' 'tests/**/!(fs)/*.test.ts'", {
162
+ execSync(`tsx ${options.inspect ? 'inspect' : ''} --test --experimental-test-coverage 'tests/*.test.ts' 'tests/**/!(fs)/*.test.ts'`, {
142
163
  stdio: ['ignore', options.verbose ? 'inherit' : 'ignore', 'inherit'],
143
164
  });
144
- pass();
165
+ await pass();
145
166
  } catch {
146
- fail();
167
+ await fail();
147
168
  }
148
169
  }
149
170
 
@@ -155,18 +176,31 @@ for (const setupFile of positionals) {
155
176
  continue;
156
177
  }
157
178
 
158
- !options.quiet && console.log('Running tests:', setupFile);
159
179
  process.env.SETUP = setupFile;
160
180
 
161
- const { pass, fail } = status(setupFile);
181
+ const name = options['file-names'] && !options.ci ? setupFile : parse(setupFile).name;
182
+
183
+ !options.quiet && console.log('Running tests:', name);
184
+
185
+ const { pass, fail } = await status(name);
162
186
 
163
187
  try {
164
- execSync(['tsx --test --experimental-test-coverage', options.force ? '--test-force-exit' : '', testsGlob, process.env.CMD].join(' '), {
165
- stdio: ['ignore', options.verbose ? 'inherit' : 'ignore', 'inherit'],
166
- });
167
- pass();
188
+ execSync(
189
+ [
190
+ 'tsx',
191
+ options.inspect ? 'inspect' : '',
192
+ '--test --experimental-test-coverage',
193
+ options.force ? '--test-force-exit' : '',
194
+ testsGlob,
195
+ process.env.CMD,
196
+ ].join(' '),
197
+ {
198
+ stdio: ['ignore', options.verbose ? 'inherit' : 'ignore', 'inherit'],
199
+ }
200
+ );
201
+ await pass();
168
202
  } catch {
169
- fail();
203
+ await fail();
170
204
  }
171
205
  }
172
206
 
@@ -11,6 +11,6 @@ until nc -z localhost 26514; do
11
11
  sleep 0.5
12
12
  done
13
13
 
14
- npx zenfs-test $SCRIPT_DIR/setup.ts --preserve --force $@
14
+ npx zenfs-test $SCRIPT_DIR/cow+fetch.ts --preserve --force "$@"
15
15
 
16
16
  kill $PID
File without changes