@tybys/wasm-util 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.
@@ -0,0 +1,25 @@
1
+ /** @public */
2
+ export class Memory extends WebAssembly.Memory {
3
+ // eslint-disable-next-line @typescript-eslint/no-useless-constructor
4
+ constructor(descriptor) {
5
+ super(descriptor);
6
+ }
7
+ get HEAP8() { return new Int8Array(super.buffer); }
8
+ get HEAPU8() { return new Uint8Array(super.buffer); }
9
+ get HEAP16() { return new Int16Array(super.buffer); }
10
+ get HEAPU16() { return new Uint16Array(super.buffer); }
11
+ get HEAP32() { return new Int32Array(super.buffer); }
12
+ get HEAPU32() { return new Uint32Array(super.buffer); }
13
+ get HEAP64() { return new BigInt64Array(super.buffer); }
14
+ get HEAPU64() { return new BigUint64Array(super.buffer); }
15
+ get HEAPF32() { return new Float32Array(super.buffer); }
16
+ get HEAPF64() { return new Float64Array(super.buffer); }
17
+ get view() { return new DataView(super.buffer); }
18
+ }
19
+ /** @public */
20
+ export function extendMemory(memory) {
21
+ if (Object.getPrototypeOf(memory) === WebAssembly.Memory.prototype) {
22
+ Object.setPrototypeOf(memory, Memory.prototype);
23
+ }
24
+ return memory;
25
+ }
@@ -0,0 +1,97 @@
1
+ import { WasiErrno } from "./types.mjs";
2
+ export function strerror(errno) {
3
+ switch (errno) {
4
+ case WasiErrno.ESUCCESS: return 'Success';
5
+ case WasiErrno.E2BIG: return 'Argument list too long';
6
+ case WasiErrno.EACCES: return 'Permission denied';
7
+ case WasiErrno.EADDRINUSE: return 'Address in use';
8
+ case WasiErrno.EADDRNOTAVAIL: return 'Address not available';
9
+ case WasiErrno.EAFNOSUPPORT: return 'Address family not supported by protocol';
10
+ case WasiErrno.EAGAIN: return 'Resource temporarily unavailable';
11
+ case WasiErrno.EALREADY: return 'Operation already in progress';
12
+ case WasiErrno.EBADF: return 'Bad file descriptor';
13
+ case WasiErrno.EBADMSG: return 'Bad message';
14
+ case WasiErrno.EBUSY: return 'Resource busy';
15
+ case WasiErrno.ECANCELED: return 'Operation canceled';
16
+ case WasiErrno.ECHILD: return 'No child process';
17
+ case WasiErrno.ECONNABORTED: return 'Connection aborted';
18
+ case WasiErrno.ECONNREFUSED: return 'Connection refused';
19
+ case WasiErrno.ECONNRESET: return 'Connection reset by peer';
20
+ case WasiErrno.EDEADLK: return 'Resource deadlock would occur';
21
+ case WasiErrno.EDESTADDRREQ: return 'Destination address required';
22
+ case WasiErrno.EDOM: return 'Domain error';
23
+ case WasiErrno.EDQUOT: return 'Quota exceeded';
24
+ case WasiErrno.EEXIST: return 'File exists';
25
+ case WasiErrno.EFAULT: return 'Bad address';
26
+ case WasiErrno.EFBIG: return 'File too large';
27
+ case WasiErrno.EHOSTUNREACH: return 'Host is unreachable';
28
+ case WasiErrno.EIDRM: return 'Identifier removed';
29
+ case WasiErrno.EILSEQ: return 'Illegal byte sequence';
30
+ case WasiErrno.EINPROGRESS: return 'Operation in progress';
31
+ case WasiErrno.EINTR: return 'Interrupted system call';
32
+ case WasiErrno.EINVAL: return 'Invalid argument';
33
+ case WasiErrno.EIO: return 'I/O error';
34
+ case WasiErrno.EISCONN: return 'Socket is connected';
35
+ case WasiErrno.EISDIR: return 'Is a directory';
36
+ case WasiErrno.ELOOP: return 'Symbolic link loop';
37
+ case WasiErrno.EMFILE: return 'No file descriptors available';
38
+ case WasiErrno.EMLINK: return 'Too many links';
39
+ case WasiErrno.EMSGSIZE: return 'Message too large';
40
+ case WasiErrno.EMULTIHOP: return 'Multihop attempted';
41
+ case WasiErrno.ENAMETOOLONG: return 'Filename too long';
42
+ case WasiErrno.ENETDOWN: return 'Network is down';
43
+ case WasiErrno.ENETRESET: return 'Connection reset by network';
44
+ case WasiErrno.ENETUNREACH: return 'Network unreachable';
45
+ case WasiErrno.ENFILE: return 'Too many files open in system';
46
+ case WasiErrno.ENOBUFS: return 'No buffer space available';
47
+ case WasiErrno.ENODEV: return 'No such device';
48
+ case WasiErrno.ENOENT: return 'No such file or directory';
49
+ case WasiErrno.ENOEXEC: return 'Exec format error';
50
+ case WasiErrno.ENOLCK: return 'No locks available';
51
+ case WasiErrno.ENOLINK: return 'Link has been severed';
52
+ case WasiErrno.ENOMEM: return 'Out of memory';
53
+ case WasiErrno.ENOMSG: return 'No message of the desired type';
54
+ case WasiErrno.ENOPROTOOPT: return 'Protocol not available';
55
+ case WasiErrno.ENOSPC: return 'No space left on device';
56
+ case WasiErrno.ENOSYS: return 'Function not implemented';
57
+ case WasiErrno.ENOTCONN: return 'Socket not connected';
58
+ case WasiErrno.ENOTDIR: return 'Not a directory';
59
+ case WasiErrno.ENOTEMPTY: return 'Directory not empty';
60
+ case WasiErrno.ENOTRECOVERABLE: return 'State not recoverable';
61
+ case WasiErrno.ENOTSOCK: return 'Not a socket';
62
+ case WasiErrno.ENOTSUP: return 'Not supported';
63
+ case WasiErrno.ENOTTY: return 'Not a tty';
64
+ case WasiErrno.ENXIO: return 'No such device or address';
65
+ case WasiErrno.EOVERFLOW: return 'Value too large for data type';
66
+ case WasiErrno.EOWNERDEAD: return 'Previous owner died';
67
+ case WasiErrno.EPERM: return 'Operation not permitted';
68
+ case WasiErrno.EPIPE: return 'Broken pipe';
69
+ case WasiErrno.EPROTO: return 'Protocol error';
70
+ case WasiErrno.EPROTONOSUPPORT: return 'Protocol not supported';
71
+ case WasiErrno.EPROTOTYPE: return 'Protocol wrong type for socket';
72
+ case WasiErrno.ERANGE: return 'Result not representable';
73
+ case WasiErrno.EROFS: return 'Read-only file system';
74
+ case WasiErrno.ESPIPE: return 'Invalid seek';
75
+ case WasiErrno.ESRCH: return 'No such process';
76
+ case WasiErrno.ESTALE: return 'Stale file handle';
77
+ case WasiErrno.ETIMEDOUT: return 'Operation timed out';
78
+ case WasiErrno.ETXTBSY: return 'Text file busy';
79
+ case WasiErrno.EXDEV: return 'Cross-device link';
80
+ case WasiErrno.ENOTCAPABLE: return 'Capabilities insufficient';
81
+ default: return 'Unknown error';
82
+ }
83
+ }
84
+ export class WasiError extends Error {
85
+ constructor(message, errno) {
86
+ super(message);
87
+ this.errno = errno;
88
+ }
89
+ getErrorMessage() {
90
+ return strerror(this.errno);
91
+ }
92
+ }
93
+ Object.defineProperty(WasiError.prototype, 'name', {
94
+ configurable: true,
95
+ writable: true,
96
+ value: 'WasiError'
97
+ });
@@ -0,0 +1,216 @@
1
+ import { WasiErrno, FileControlFlag, WasiFileType, WasiWhence } from "./types.mjs";
2
+ import { getRights } from "./rights.mjs";
3
+ import { WasiError } from "./error.mjs";
4
+ export function concatBuffer(buffers, size) {
5
+ let total = 0;
6
+ if (typeof size === 'number' && size >= 0) {
7
+ total = size;
8
+ }
9
+ else {
10
+ for (let i = 0; i < buffers.length; i++) {
11
+ const buffer = buffers[i];
12
+ total += buffer.length;
13
+ }
14
+ }
15
+ let pos = 0;
16
+ const ret = new Uint8Array(total);
17
+ for (let i = 0; i < buffers.length; i++) {
18
+ const buffer = buffers[i];
19
+ ret.set(buffer, pos);
20
+ pos += buffer.length;
21
+ }
22
+ return ret;
23
+ }
24
+ export class FileDescriptor {
25
+ constructor(id, fd, path, realPath, type, rightsBase, rightsInheriting, preopen) {
26
+ this.id = id;
27
+ this.fd = fd;
28
+ this.path = path;
29
+ this.realPath = realPath;
30
+ this.type = type;
31
+ this.rightsBase = rightsBase;
32
+ this.rightsInheriting = rightsInheriting;
33
+ this.preopen = preopen;
34
+ this.pos = BigInt(0);
35
+ this.size = BigInt(0);
36
+ }
37
+ seek(offset, whence) {
38
+ if (whence === WasiWhence.SET) {
39
+ this.pos = BigInt(offset);
40
+ }
41
+ else if (whence === WasiWhence.CUR) {
42
+ this.pos += BigInt(offset);
43
+ }
44
+ else if (whence === WasiWhence.END) {
45
+ this.pos = BigInt(this.size) - BigInt(offset);
46
+ }
47
+ else {
48
+ throw new WasiError('Unknown whence', WasiErrno.EIO);
49
+ }
50
+ return this.pos;
51
+ }
52
+ }
53
+ export class StandardOutput extends FileDescriptor {
54
+ constructor(log, id, fd, path, realPath, type, rightsBase, rightsInheriting, preopen) {
55
+ super(id, fd, path, realPath, type, rightsBase, rightsInheriting, preopen);
56
+ this._log = log;
57
+ this._buf = null;
58
+ }
59
+ write(buffer) {
60
+ const originalBuffer = buffer;
61
+ if (this._buf) {
62
+ buffer = concatBuffer([this._buf, buffer]);
63
+ this._buf = null;
64
+ }
65
+ if (buffer.indexOf(10) === -1) {
66
+ this._buf = buffer;
67
+ return originalBuffer.byteLength;
68
+ }
69
+ let written = 0;
70
+ let lastBegin = 0;
71
+ let index;
72
+ while ((index = buffer.indexOf(10, written)) !== -1) {
73
+ const str = new TextDecoder().decode(buffer.subarray(lastBegin, index));
74
+ this._log(str);
75
+ written += index - lastBegin + 1;
76
+ lastBegin = index + 1;
77
+ }
78
+ if (written < buffer.length) {
79
+ this._buf = buffer.slice(written);
80
+ }
81
+ return originalBuffer.byteLength;
82
+ }
83
+ }
84
+ export function toFileType(stat) {
85
+ if (stat.isBlockDevice())
86
+ return WasiFileType.BLOCK_DEVICE;
87
+ if (stat.isCharacterDevice())
88
+ return WasiFileType.CHARACTER_DEVICE;
89
+ if (stat.isDirectory())
90
+ return WasiFileType.DIRECTORY;
91
+ if (stat.isSocket())
92
+ return WasiFileType.SOCKET_STREAM;
93
+ if (stat.isFile())
94
+ return WasiFileType.REGULAR_FILE;
95
+ if (stat.isSymbolicLink())
96
+ return WasiFileType.SYMBOLIC_LINK;
97
+ return WasiFileType.UNKNOWN;
98
+ }
99
+ export function toFileStat(view, buf, stat) {
100
+ view.setBigUint64(buf, stat.dev, true);
101
+ view.setBigUint64(buf + 8, stat.ino, true);
102
+ view.setBigUint64(buf + 16, BigInt(toFileType(stat)), true);
103
+ view.setBigUint64(buf + 24, stat.nlink, true);
104
+ view.setBigUint64(buf + 32, stat.size, true);
105
+ view.setBigUint64(buf + 40, stat.atimeMs * BigInt(1000000), true);
106
+ view.setBigUint64(buf + 48, stat.mtimeMs * BigInt(1000000), true);
107
+ view.setBigUint64(buf + 56, stat.ctimeMs * BigInt(1000000), true);
108
+ }
109
+ export class FileDescriptorTable {
110
+ constructor(options) {
111
+ this.used = 0;
112
+ this.size = options.size;
113
+ this.fds = Array(options.size);
114
+ this.stdio = [options.in, options.out, options.err];
115
+ this.fs = options.fs;
116
+ this.print = options.print;
117
+ this.printErr = options.printErr;
118
+ this.insertStdio(options.in, 0, '<stdin>');
119
+ this.insertStdio(options.out, 1, '<stdout>');
120
+ this.insertStdio(options.err, 2, '<stderr>');
121
+ }
122
+ insertStdio(fd, expected, name) {
123
+ const type = WasiFileType.CHARACTER_DEVICE;
124
+ const { base, inheriting } = getRights(this.stdio, fd, FileControlFlag.O_RDWR, type);
125
+ const wrap = this.insert(fd, name, name, type, base, inheriting, 0);
126
+ if (wrap.id !== expected) {
127
+ throw new WasiError(`id: ${wrap.id} !== expected: ${expected}`, WasiErrno.EBADF);
128
+ }
129
+ return wrap;
130
+ }
131
+ insert(fd, mappedPath, realPath, type, rightsBase, rightsInheriting, preopen) {
132
+ var _a, _b;
133
+ let index = -1;
134
+ if (this.used >= this.size) {
135
+ const newSize = this.size * 2;
136
+ this.fds.length = newSize;
137
+ index = this.size;
138
+ this.size = newSize;
139
+ }
140
+ else {
141
+ for (let i = 0; i < this.size; ++i) {
142
+ if (this.fds[i] == null) {
143
+ index = i;
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ let entry;
149
+ if (mappedPath === '<stdout>') {
150
+ entry = new StandardOutput((_a = this.print) !== null && _a !== void 0 ? _a : console.log, index, fd, mappedPath, realPath, type, rightsBase, rightsInheriting, preopen);
151
+ }
152
+ else if (mappedPath === '<stderr>') {
153
+ entry = new StandardOutput((_b = this.printErr) !== null && _b !== void 0 ? _b : console.error, index, fd, mappedPath, realPath, type, rightsBase, rightsInheriting, preopen);
154
+ }
155
+ else {
156
+ entry = new FileDescriptor(index, fd, mappedPath, realPath, type, rightsBase, rightsInheriting, preopen);
157
+ }
158
+ this.fds[index] = entry;
159
+ this.used++;
160
+ return entry;
161
+ }
162
+ getFileTypeByFd(fd) {
163
+ const stat = this.fs.fstatSync(fd);
164
+ return toFileType(stat);
165
+ }
166
+ insertPreopen(fd, mappedPath, realPath) {
167
+ const type = this.getFileTypeByFd(fd);
168
+ if (type !== WasiFileType.DIRECTORY) {
169
+ throw new WasiError(`Preopen not dir: ["${mappedPath}", "${realPath}"]`, WasiErrno.ENOTDIR);
170
+ }
171
+ const result = getRights(this.stdio, fd, 0, type);
172
+ return this.insert(fd, mappedPath, realPath, type, result.base, result.inheriting, 1);
173
+ }
174
+ get(id, base, inheriting) {
175
+ if (id >= this.size) {
176
+ throw new WasiError('Invalid fd', WasiErrno.EBADF);
177
+ }
178
+ const entry = this.fds[id];
179
+ if (!entry || entry.id !== id) {
180
+ throw new WasiError('Bad file descriptor', WasiErrno.EBADF);
181
+ }
182
+ /* Validate that the fd has the necessary rights. */
183
+ if ((~entry.rightsBase & base) !== BigInt(0) || (~entry.rightsInheriting & inheriting) !== BigInt(0)) {
184
+ throw new WasiError('Capabilities insufficient', WasiErrno.ENOTCAPABLE);
185
+ }
186
+ return entry;
187
+ }
188
+ remove(id) {
189
+ if (id >= this.size) {
190
+ throw new WasiError('Invalid fd', WasiErrno.EBADF);
191
+ }
192
+ const entry = this.fds[id];
193
+ if (!entry || entry.id !== id) {
194
+ throw new WasiError('Bad file descriptor', WasiErrno.EBADF);
195
+ }
196
+ this.fds[id] = undefined;
197
+ this.used--;
198
+ }
199
+ renumber(dst, src) {
200
+ if (dst === src)
201
+ return;
202
+ if (dst >= this.size || src >= this.size) {
203
+ throw new WasiError('Invalid fd', WasiErrno.EBADF);
204
+ }
205
+ const dstEntry = this.fds[dst];
206
+ const srcEntry = this.fds[src];
207
+ if (!dstEntry || !srcEntry || dstEntry.id !== dst || srcEntry.id !== src) {
208
+ throw new WasiError('Invalid fd', WasiErrno.EBADF);
209
+ }
210
+ this.fs.closeSync(dstEntry.fd);
211
+ this.fds[dst] = this.fds[src];
212
+ this.fds[dst].id = dst;
213
+ this.fds[src] = undefined;
214
+ this.used--;
215
+ }
216
+ }
@@ -0,0 +1,132 @@
1
+ import { WASI as _WASI } from "./preview1.mjs";
2
+ import { validateObject, validateArray, validateBoolean, validateFunction, validateUndefined, validateString } from "./util.mjs";
3
+ const kEmptyObject = Object.freeze(Object.create(null));
4
+ const kExitCode = Symbol('kExitCode');
5
+ const kSetMemory = Symbol('kSetMemory');
6
+ const kStarted = Symbol('kStarted');
7
+ const kInstance = Symbol('kInstance');
8
+ function setupInstance(self, instance) {
9
+ validateObject(instance, 'instance');
10
+ validateObject(instance.exports, 'instance.exports');
11
+ self[kInstance] = instance;
12
+ self[kSetMemory](instance.exports.memory);
13
+ }
14
+ // /** @public */
15
+ // export type WasiSnapshotPreview1 = Omit<_WASI, '_setMemory'>
16
+ /** @public */
17
+ export class WASI {
18
+ constructor(options = kEmptyObject) {
19
+ var _a;
20
+ validateObject(options, 'options');
21
+ if (options.args !== undefined) {
22
+ validateArray(options.args, 'options.args');
23
+ }
24
+ const args = ((_a = options.args) !== null && _a !== void 0 ? _a : []).map(String);
25
+ const env = [];
26
+ if (options.env !== undefined) {
27
+ validateObject(options.env, 'options.env');
28
+ Object.entries(options.env).forEach(({ 0: key, 1: value }) => {
29
+ if (value !== undefined) {
30
+ env.push(`${key}=${value}`);
31
+ }
32
+ });
33
+ }
34
+ const preopens = [];
35
+ if (options.preopens !== undefined) {
36
+ validateObject(options.preopens, 'options.preopens');
37
+ Object.entries(options.preopens).forEach(({ 0: key, 1: value }) => preopens.push({ mappedPath: String(key), realPath: String(value) }));
38
+ }
39
+ if (preopens.length > 0) {
40
+ if (options.filesystem === undefined) {
41
+ throw new Error('filesystem is disabled, can not preopen directory');
42
+ }
43
+ }
44
+ if (options.filesystem !== undefined) {
45
+ validateObject(options.filesystem, 'options.filesystem');
46
+ validateString(options.filesystem.type, 'options.filesystem.type');
47
+ if (options.filesystem.type !== 'memfs') {
48
+ throw new Error(`Filesystem type ${options.filesystem.type} is not supported, only "memfs" is supported currently`);
49
+ }
50
+ try {
51
+ validateObject(options.filesystem.fs, 'options.filesystem.fs');
52
+ }
53
+ catch (_) {
54
+ throw new Error('Node.js fs like implementation is not provided');
55
+ }
56
+ }
57
+ if (options.print !== undefined)
58
+ validateFunction(options.print, 'options.print');
59
+ if (options.printErr !== undefined)
60
+ validateFunction(options.printErr, 'options.printErr');
61
+ // const { stdin = 0, stdout = 1, stderr = 2 } = options
62
+ // validateInt32(stdin, 'options.stdin', 0)
63
+ // validateInt32(stdout, 'options.stdout', 0)
64
+ // validateInt32(stderr, 'options.stderr', 0)
65
+ // const stdio = [stdin, stdout, stderr] as const
66
+ const stdio = [0, 1, 2];
67
+ const wrap = new _WASI(args, env, preopens, stdio, options.filesystem, options.print, options.printErr);
68
+ for (const prop in wrap) {
69
+ wrap[prop] = wrap[prop].bind(wrap);
70
+ }
71
+ if (options.returnOnExit !== undefined) {
72
+ validateBoolean(options.returnOnExit, 'options.returnOnExit');
73
+ if (options.returnOnExit) {
74
+ wrap.proc_exit = wasiReturnOnProcExit.bind(this);
75
+ }
76
+ }
77
+ this[kSetMemory] = wrap._setMemory;
78
+ delete wrap._setMemory;
79
+ this.wasiImport = wrap;
80
+ this[kStarted] = false;
81
+ this[kExitCode] = 0;
82
+ this[kInstance] = undefined;
83
+ }
84
+ // Must not export _initialize, must export _start
85
+ start(instance) {
86
+ if (this[kStarted]) {
87
+ throw new Error('WASI instance has already started');
88
+ }
89
+ this[kStarted] = true;
90
+ setupInstance(this, instance);
91
+ const { _start, _initialize } = this[kInstance].exports;
92
+ validateFunction(_start, 'instance.exports._start');
93
+ validateUndefined(_initialize, 'instance.exports._initialize');
94
+ let ret;
95
+ try {
96
+ ret = _start();
97
+ }
98
+ catch (err) {
99
+ if (err !== kExitCode) {
100
+ throw err;
101
+ }
102
+ }
103
+ if (ret instanceof Promise) {
104
+ return ret.then(() => this[kExitCode], (err) => {
105
+ if (err !== kExitCode) {
106
+ throw err;
107
+ }
108
+ return this[kExitCode];
109
+ });
110
+ }
111
+ return this[kExitCode];
112
+ }
113
+ // Must not export _start, may optionally export _initialize
114
+ initialize(instance) {
115
+ if (this[kStarted]) {
116
+ throw new Error('WASI instance has already started');
117
+ }
118
+ this[kStarted] = true;
119
+ setupInstance(this, instance);
120
+ const { _start, _initialize } = this[kInstance].exports;
121
+ validateUndefined(_start, 'instance.exports._start');
122
+ if (_initialize !== undefined) {
123
+ validateFunction(_initialize, 'instance.exports._initialize');
124
+ return _initialize();
125
+ }
126
+ }
127
+ }
128
+ function wasiReturnOnProcExit(rval) {
129
+ this[kExitCode] = rval;
130
+ // eslint-disable-next-line @typescript-eslint/no-throw-literal
131
+ throw kExitCode;
132
+ }
@@ -0,0 +1,168 @@
1
+ import { validateString } from "./util.mjs";
2
+ const CHAR_DOT = 46; /* . */
3
+ const CHAR_FORWARD_SLASH = 47; /* / */
4
+ function isPosixPathSeparator(code) {
5
+ return code === CHAR_FORWARD_SLASH;
6
+ }
7
+ function normalizeString(path, allowAboveRoot, separator, isPathSeparator) {
8
+ let res = '';
9
+ let lastSegmentLength = 0;
10
+ let lastSlash = -1;
11
+ let dots = 0;
12
+ let code = 0;
13
+ for (let i = 0; i <= path.length; ++i) {
14
+ if (i < path.length) {
15
+ code = path.charCodeAt(i);
16
+ }
17
+ else if (isPathSeparator(code)) {
18
+ break;
19
+ }
20
+ else {
21
+ code = CHAR_FORWARD_SLASH;
22
+ }
23
+ if (isPathSeparator(code)) {
24
+ if (lastSlash === i - 1 || dots === 1) {
25
+ // NOOP
26
+ }
27
+ else if (dots === 2) {
28
+ if (res.length < 2 || lastSegmentLength !== 2 ||
29
+ res.charCodeAt(res.length - 1) !== CHAR_DOT ||
30
+ res.charCodeAt(res.length - 2) !== CHAR_DOT) {
31
+ if (res.length > 2) {
32
+ const lastSlashIndex = res.indexOf(separator);
33
+ if (lastSlashIndex === -1) {
34
+ res = '';
35
+ lastSegmentLength = 0;
36
+ }
37
+ else {
38
+ res = res.slice(0, lastSlashIndex);
39
+ lastSegmentLength =
40
+ res.length - 1 - res.indexOf(separator);
41
+ }
42
+ lastSlash = i;
43
+ dots = 0;
44
+ continue;
45
+ }
46
+ else if (res.length !== 0) {
47
+ res = '';
48
+ lastSegmentLength = 0;
49
+ lastSlash = i;
50
+ dots = 0;
51
+ continue;
52
+ }
53
+ }
54
+ if (allowAboveRoot) {
55
+ res += res.length > 0 ? `${separator}..` : '..';
56
+ lastSegmentLength = 2;
57
+ }
58
+ }
59
+ else {
60
+ if (res.length > 0) {
61
+ res += `${separator}${path.slice(lastSlash + 1, i)}`;
62
+ }
63
+ else {
64
+ res = path.slice(lastSlash + 1, i);
65
+ }
66
+ lastSegmentLength = i - lastSlash - 1;
67
+ }
68
+ lastSlash = i;
69
+ dots = 0;
70
+ }
71
+ else if (code === CHAR_DOT && dots !== -1) {
72
+ ++dots;
73
+ }
74
+ else {
75
+ dots = -1;
76
+ }
77
+ }
78
+ return res;
79
+ }
80
+ export function resolve(...args) {
81
+ let resolvedPath = '';
82
+ let resolvedAbsolute = false;
83
+ for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
84
+ const path = i >= 0 ? args[i] : '/';
85
+ validateString(path, 'path');
86
+ // Skip empty entries
87
+ if (path.length === 0) {
88
+ continue;
89
+ }
90
+ resolvedPath = `${path}/${resolvedPath}`;
91
+ resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH;
92
+ }
93
+ // At this point the path should be resolved to a full absolute path, but
94
+ // handle relative paths to be safe (might happen when process.cwd() fails)
95
+ // Normalize the path
96
+ resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator);
97
+ if (resolvedAbsolute) {
98
+ return `/${resolvedPath}`;
99
+ }
100
+ return resolvedPath.length > 0 ? resolvedPath : '.';
101
+ }
102
+ export function relative(from, to) {
103
+ validateString(from, 'from');
104
+ validateString(to, 'to');
105
+ if (from === to)
106
+ return '';
107
+ // Trim leading forward slashes.
108
+ from = resolve(from);
109
+ to = resolve(to);
110
+ if (from === to)
111
+ return '';
112
+ const fromStart = 1;
113
+ const fromEnd = from.length;
114
+ const fromLen = fromEnd - fromStart;
115
+ const toStart = 1;
116
+ const toLen = to.length - toStart;
117
+ // Compare paths to find the longest common path from root
118
+ const length = (fromLen < toLen ? fromLen : toLen);
119
+ let lastCommonSep = -1;
120
+ let i = 0;
121
+ for (; i < length; i++) {
122
+ const fromCode = from.charCodeAt(fromStart + i);
123
+ if (fromCode !== to.charCodeAt(toStart + i)) {
124
+ break;
125
+ }
126
+ else if (fromCode === CHAR_FORWARD_SLASH) {
127
+ lastCommonSep = i;
128
+ }
129
+ }
130
+ if (i === length) {
131
+ if (toLen > length) {
132
+ if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
133
+ // We get here if `from` is the exact base path for `to`.
134
+ // For example: from='/foo/bar'; to='/foo/bar/baz'
135
+ return to.slice(toStart + i + 1);
136
+ }
137
+ if (i === 0) {
138
+ // We get here if `from` is the root
139
+ // For example: from='/'; to='/foo'
140
+ return to.slice(toStart + i);
141
+ }
142
+ }
143
+ else if (fromLen > length) {
144
+ if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
145
+ // We get here if `to` is the exact base path for `from`.
146
+ // For example: from='/foo/bar/baz'; to='/foo/bar'
147
+ lastCommonSep = i;
148
+ }
149
+ else if (i === 0) {
150
+ // We get here if `to` is the root.
151
+ // For example: from='/foo/bar'; to='/'
152
+ lastCommonSep = 0;
153
+ }
154
+ }
155
+ }
156
+ let out = '';
157
+ // Generate the relative path based on the path difference between `to`
158
+ // and `from`.
159
+ for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
160
+ if (i === fromEnd ||
161
+ from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
162
+ out += out.length === 0 ? '..' : '/..';
163
+ }
164
+ }
165
+ // Lastly, append the rest of the destination (`to`) path that comes after
166
+ // the common path parts.
167
+ return `${out}${to.slice(toStart + lastCommonSep)}`;
168
+ }