@symbiote-native/file-system 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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +262 -0
  3. package/build/angular/index.d.ts +1 -0
  4. package/build/angular/index.js +1 -0
  5. package/build/core/file-system.d.ts +169 -0
  6. package/build/core/file-system.js +404 -0
  7. package/build/core/index.d.ts +2 -0
  8. package/build/core/index.js +2 -0
  9. package/build/core/native-module.d.ts +32 -0
  10. package/build/core/native-module.js +2 -0
  11. package/build/core/types.d.ts +104 -0
  12. package/build/core/types.js +15 -0
  13. package/build/next/directory.d.ts +36 -0
  14. package/build/next/directory.js +59 -0
  15. package/build/next/file.d.ts +71 -0
  16. package/build/next/file.js +207 -0
  17. package/build/next/index.d.ts +5 -0
  18. package/build/next/index.js +5 -0
  19. package/build/next/native-module.d.ts +109 -0
  20. package/build/next/native-module.js +2 -0
  21. package/build/next/network-tasks.d.ts +118 -0
  22. package/build/next/network-tasks.js +333 -0
  23. package/build/next/node-path.d.ts +16 -0
  24. package/build/next/node-path.js +353 -0
  25. package/build/next/path-utilities.d.ts +42 -0
  26. package/build/next/path-utilities.js +110 -0
  27. package/build/next/paths.d.ts +22 -0
  28. package/build/next/paths.js +43 -0
  29. package/build/next/streams.d.ts +25 -0
  30. package/build/next/streams.js +55 -0
  31. package/build/next/types.d.ts +152 -0
  32. package/build/next/types.js +19 -0
  33. package/build/next/url-utilities.d.ts +3 -0
  34. package/build/next/url-utilities.js +62 -0
  35. package/build/next/watcher.d.ts +19 -0
  36. package/build/next/watcher.js +64 -0
  37. package/build-ngc/angular/index.d.ts +1 -0
  38. package/build-ngc/angular/index.js +2 -0
  39. package/build-ngc/angular/index.js.map +1 -0
  40. package/build-ngc/next/directory.d.ts +36 -0
  41. package/build-ngc/next/directory.js +60 -0
  42. package/build-ngc/next/directory.js.map +1 -0
  43. package/build-ngc/next/file.d.ts +71 -0
  44. package/build-ngc/next/file.js +208 -0
  45. package/build-ngc/next/file.js.map +1 -0
  46. package/build-ngc/next/index.d.ts +5 -0
  47. package/build-ngc/next/index.js +6 -0
  48. package/build-ngc/next/index.js.map +1 -0
  49. package/build-ngc/next/native-module.d.ts +109 -0
  50. package/build-ngc/next/native-module.js +3 -0
  51. package/build-ngc/next/native-module.js.map +1 -0
  52. package/build-ngc/next/network-tasks.d.ts +118 -0
  53. package/build-ngc/next/network-tasks.js +334 -0
  54. package/build-ngc/next/network-tasks.js.map +1 -0
  55. package/build-ngc/next/node-path.d.ts +16 -0
  56. package/build-ngc/next/node-path.js +354 -0
  57. package/build-ngc/next/node-path.js.map +1 -0
  58. package/build-ngc/next/path-utilities.d.ts +42 -0
  59. package/build-ngc/next/path-utilities.js +111 -0
  60. package/build-ngc/next/path-utilities.js.map +1 -0
  61. package/build-ngc/next/paths.d.ts +22 -0
  62. package/build-ngc/next/paths.js +44 -0
  63. package/build-ngc/next/paths.js.map +1 -0
  64. package/build-ngc/next/streams.d.ts +25 -0
  65. package/build-ngc/next/streams.js +56 -0
  66. package/build-ngc/next/streams.js.map +1 -0
  67. package/build-ngc/next/types.d.ts +152 -0
  68. package/build-ngc/next/types.js +20 -0
  69. package/build-ngc/next/types.js.map +1 -0
  70. package/build-ngc/next/url-utilities.d.ts +3 -0
  71. package/build-ngc/next/url-utilities.js +63 -0
  72. package/build-ngc/next/url-utilities.js.map +1 -0
  73. package/build-ngc/next/watcher.d.ts +19 -0
  74. package/build-ngc/next/watcher.js +65 -0
  75. package/build-ngc/next/watcher.js.map +1 -0
  76. package/native-link.json +17 -0
  77. package/package.json +154 -0
  78. package/src/angular/index.ts +1 -0
  79. package/src/core/file-system.ts +634 -0
  80. package/src/core/index.ts +2 -0
  81. package/src/core/native-module.ts +99 -0
  82. package/src/core/types.ts +135 -0
  83. package/src/next/directory.ts +83 -0
  84. package/src/next/file.ts +318 -0
  85. package/src/next/index.ts +5 -0
  86. package/src/next/native-module.ts +188 -0
  87. package/src/next/network-tasks.ts +456 -0
  88. package/src/next/node-path.ts +362 -0
  89. package/src/next/path-utilities.ts +144 -0
  90. package/src/next/paths.ts +52 -0
  91. package/src/next/streams-ambient.d.ts +34 -0
  92. package/src/next/streams.ts +81 -0
  93. package/src/next/types.ts +194 -0
  94. package/src/next/url-utilities.ts +70 -0
  95. package/src/next/watcher.ts +106 -0
@@ -0,0 +1,333 @@
1
+ import { Directory } from './directory.js';
2
+ import { File } from './file.js';
3
+ import { expoFileSystemNext } from './native-module.js';
4
+ import { FileSystemUploadType, } from './types.js';
5
+ function createAbortError(reason) {
6
+ const message = typeof reason === 'string' ? reason : 'The operation was aborted.';
7
+ const error = new Error(message);
8
+ error.name = 'AbortError';
9
+ return error;
10
+ }
11
+ function assertNetworkTaskState(state, allowedStates, methodName) {
12
+ if (!allowedStates.includes(state)) {
13
+ throw new Error(`Cannot call ${methodName}() in state "${state}"`);
14
+ }
15
+ }
16
+ function wireNetworkTaskAbortSignal(signal, cancel) {
17
+ if (signal?.aborted) {
18
+ throw createAbortError();
19
+ }
20
+ if (signal) {
21
+ const abortHandler = () => cancel();
22
+ signal.addEventListener('abort', abortHandler, { once: true });
23
+ return abortHandler;
24
+ }
25
+ return undefined;
26
+ }
27
+ function wireNetworkTaskProgress(onProgress, addListener) {
28
+ if (onProgress) {
29
+ return addListener(onProgress);
30
+ }
31
+ return undefined;
32
+ }
33
+ function cleanupNetworkTask(signal, subscription, abortHandler) {
34
+ subscription?.remove();
35
+ if (abortHandler && signal) {
36
+ signal.removeEventListener('abort', abortHandler);
37
+ }
38
+ }
39
+ /**
40
+ * Represents an upload task with progress tracking and cancellation support.
41
+ *
42
+ * Upload tasks start in the `idle` state. Calling `uploadAsync()` moves the task to `active`,
43
+ * then to `completed`, `cancelled`, or `error`.
44
+ */
45
+ export class UploadTask {
46
+ _state = 'idle';
47
+ _file;
48
+ _url;
49
+ _options;
50
+ _subscription;
51
+ _abortHandler;
52
+ _nativeTask;
53
+ /**
54
+ * Creates an upload task. The task does not start automatically — call `uploadAsync()` to
55
+ * begin uploading.
56
+ */
57
+ constructor(file, url, options) {
58
+ this._nativeTask = new expoFileSystemNext.FileSystemUploadTask();
59
+ this._file = file;
60
+ this._url = url;
61
+ this._options = options;
62
+ }
63
+ /** The current state of the upload task. */
64
+ get state() {
65
+ return this._state;
66
+ }
67
+ /**
68
+ * Starts the upload operation. Can only be called once, while the task is `idle`. The promise
69
+ * resolves with response metadata and body for completed HTTP responses, including non-2xx
70
+ * status codes. It is rejected when the file cannot be read, the request fails, or the task is
71
+ * cancelled. If `options.signal` is aborted, the promise is rejected with an `AbortError`.
72
+ */
73
+ async uploadAsync() {
74
+ assertNetworkTaskState(this._state, ['idle'], 'uploadAsync');
75
+ this._state = 'active';
76
+ try {
77
+ this._abortHandler = wireNetworkTaskAbortSignal(this._options?.signal, () => this.cancel());
78
+ this._subscription = wireNetworkTaskProgress(this._options?.onProgress, listener => this.addListener('progress', listener));
79
+ const nativeOpts = {
80
+ httpMethod: this._options?.httpMethod || 'POST',
81
+ uploadType: this._options?.uploadType ?? FileSystemUploadType.BINARY_CONTENT,
82
+ headers: this._options?.headers,
83
+ fieldName: this._options?.fieldName,
84
+ mimeType: this._options?.mimeType,
85
+ parameters: this._options?.parameters,
86
+ sessionType: this._options?.sessionType,
87
+ };
88
+ const result = await this._nativeTask.start(this._url, this._file, nativeOpts);
89
+ this._state = 'completed';
90
+ // Emit a synthetic final progress to guarantee 100% is reported. Native progress events
91
+ // may not fire for small files, and even when they do, the event can race with promise
92
+ // resolution (listener removed before delivery).
93
+ if (this._options?.onProgress && this._file.exists) {
94
+ const size = this._file.size ?? 0;
95
+ if (size > 0) {
96
+ this._options.onProgress({ bytesSent: size, totalBytes: size });
97
+ }
98
+ }
99
+ return result;
100
+ }
101
+ catch (error) {
102
+ if (this._options?.signal?.aborted) {
103
+ this._state = 'cancelled';
104
+ throw createAbortError();
105
+ }
106
+ if (this.state === 'cancelled') {
107
+ throw error;
108
+ }
109
+ this._state = 'error';
110
+ throw error;
111
+ }
112
+ finally {
113
+ cleanupNetworkTask(this._options?.signal, this._subscription, this._abortHandler);
114
+ this._subscription = undefined;
115
+ this._abortHandler = undefined;
116
+ }
117
+ }
118
+ /**
119
+ * Adds a listener for upload progress events. Prefer the `onProgress` option unless manual
120
+ * subscription control is needed.
121
+ */
122
+ addListener(eventName, listener) {
123
+ return this._nativeTask.addListener(eventName, listener);
124
+ }
125
+ /** Releases the native task handle manually. */
126
+ release() {
127
+ this._nativeTask.release();
128
+ }
129
+ /**
130
+ * Cancels the upload operation. If `uploadAsync()` is pending, its promise is rejected after
131
+ * the native request is cancelled. Has no effect once the task reaches a terminal state.
132
+ */
133
+ cancel() {
134
+ if (['completed', 'cancelled', 'error'].includes(this._state))
135
+ return;
136
+ this._state = 'cancelled';
137
+ this._nativeTask.cancel();
138
+ cleanupNetworkTask(this._options?.signal, this._subscription, this._abortHandler);
139
+ this._subscription = undefined;
140
+ this._abortHandler = undefined;
141
+ }
142
+ }
143
+ /**
144
+ * Represents a download task with pause/resume support and progress tracking.
145
+ *
146
+ * Download tasks start in the `idle` state. Calling `downloadAsync()` moves the task to
147
+ * `active`; pausing moves it to `paused`, and a completed, cancelled, or failed transfer moves
148
+ * it to the corresponding terminal state.
149
+ */
150
+ export class DownloadTask {
151
+ _state = 'idle';
152
+ _url;
153
+ _destination;
154
+ _options;
155
+ _resumeData;
156
+ _subscription;
157
+ _abortHandler;
158
+ _inFlightOperation;
159
+ _pauseRequest;
160
+ _nativeTask;
161
+ /**
162
+ * Creates a download task. The task does not start automatically — call `downloadAsync()` to
163
+ * begin downloading.
164
+ */
165
+ constructor(url, destination, options) {
166
+ this._nativeTask = new expoFileSystemNext.FileSystemDownloadTask();
167
+ this._url = url;
168
+ this._destination = destination;
169
+ this._options = options;
170
+ }
171
+ /** The current state of the download task. */
172
+ get state() {
173
+ return this._state;
174
+ }
175
+ /**
176
+ * Starts the download operation. Can only be called once, while the task is `idle`. Resolves
177
+ * with the downloaded file when the transfer completes, or with `null` if the task is paused
178
+ * before completion.
179
+ */
180
+ async downloadAsync() {
181
+ assertNetworkTaskState(this._state, ['idle'], 'downloadAsync');
182
+ this._state = 'active';
183
+ this._pauseRequest = undefined;
184
+ const operation = this._runDownloadOperation(() => this._nativeTask.start(this._url, this._destination, {
185
+ headers: this._options?.headers,
186
+ sessionType: this._options?.sessionType,
187
+ }));
188
+ this._inFlightOperation = operation;
189
+ return operation;
190
+ }
191
+ /**
192
+ * Requests pausing the active download operation. The pending `downloadAsync()`/`resumeAsync()`
193
+ * promise resolves with `null` once native code produces resume data. Use `pauseAsync()` to
194
+ * wait for that to happen.
195
+ */
196
+ pause() {
197
+ assertNetworkTaskState(this._state, ['active'], 'pause');
198
+ this._pauseRequest = Promise.resolve(this._nativeTask.pause()).then(result => {
199
+ this._resumeData = result?.resumeData ?? undefined;
200
+ });
201
+ }
202
+ /**
203
+ * Requests pausing the active download operation and waits until the task reaches the
204
+ * `paused` state.
205
+ */
206
+ async pauseAsync() {
207
+ this.pause();
208
+ await this._pauseRequest;
209
+ await this._inFlightOperation;
210
+ }
211
+ /**
212
+ * Resumes a paused download operation. Resolves with the downloaded file when the transfer
213
+ * completes, or with `null` if paused again before completion.
214
+ */
215
+ async resumeAsync() {
216
+ assertNetworkTaskState(this._state, ['paused'], 'resumeAsync');
217
+ if (!this._resumeData) {
218
+ throw new Error('No resume data available. Was the download paused before any data was received?');
219
+ }
220
+ this._state = 'active';
221
+ this._pauseRequest = undefined;
222
+ const operation = this._runDownloadOperation(() => this._nativeTask.resume(this._url, this._destination, this._resumeData, {
223
+ headers: this._options?.headers,
224
+ sessionType: this._options?.sessionType,
225
+ }));
226
+ this._inFlightOperation = operation;
227
+ return operation;
228
+ }
229
+ /**
230
+ * Adds a listener for download progress events. Prefer the `onProgress` option unless manual
231
+ * subscription control is needed.
232
+ */
233
+ addListener(eventName, listener) {
234
+ return this._nativeTask.addListener(eventName, listener);
235
+ }
236
+ /** Releases the native task handle manually. */
237
+ release() {
238
+ this._nativeTask.release();
239
+ }
240
+ /**
241
+ * Cancels the download operation. If `downloadAsync()`/`resumeAsync()` is pending, its promise
242
+ * is rejected after the native request is cancelled. Has no effect once the task reaches a
243
+ * terminal state.
244
+ */
245
+ cancel() {
246
+ if (['completed', 'cancelled', 'error'].includes(this._state))
247
+ return;
248
+ this._state = 'cancelled';
249
+ this._pauseRequest = undefined;
250
+ this._nativeTask.cancel();
251
+ cleanupNetworkTask(this._options?.signal, this._subscription, this._abortHandler);
252
+ this._subscription = undefined;
253
+ this._abortHandler = undefined;
254
+ }
255
+ /**
256
+ * Returns the paused task state that can be persisted and restored later. Can only be called
257
+ * while the task is `paused`.
258
+ */
259
+ savable() {
260
+ assertNetworkTaskState(this._state, ['paused'], 'savable');
261
+ return {
262
+ url: this._url,
263
+ fileUri: this._destination.uri,
264
+ isDirectory: this._destination instanceof Directory,
265
+ headers: this._options?.headers,
266
+ resumeData: this._resumeData,
267
+ };
268
+ }
269
+ /**
270
+ * Creates a paused download task from saved state, to continue a download after persisting the
271
+ * value returned by `savable()`.
272
+ */
273
+ static fromSavable(state, options) {
274
+ if (!state.resumeData) {
275
+ throw new Error('Cannot restore task: DownloadPauseState has no resumeData');
276
+ }
277
+ const dest = state.isDirectory
278
+ ? new Directory(state.fileUri)
279
+ : new File(state.fileUri);
280
+ const mergedOptions = options || state.headers
281
+ ? { ...options, headers: { ...state.headers, ...options?.headers } }
282
+ : undefined;
283
+ const task = new DownloadTask(state.url, dest, mergedOptions);
284
+ task._resumeData = state.resumeData;
285
+ task._state = 'paused';
286
+ return task;
287
+ }
288
+ async _runDownloadOperation(operation) {
289
+ try {
290
+ this._abortHandler = wireNetworkTaskAbortSignal(this._options?.signal, () => this.cancel());
291
+ this._subscription = wireNetworkTaskProgress(this._options?.onProgress, listener => this.addListener('progress', listener));
292
+ const result = await operation();
293
+ if (result) {
294
+ this._state = 'completed';
295
+ this._resumeData = undefined;
296
+ const file = new File(result);
297
+ this._emitFinalProgressEvent(file.size);
298
+ return file;
299
+ }
300
+ await this._pauseRequest;
301
+ this._state = 'paused';
302
+ return null;
303
+ }
304
+ catch (error) {
305
+ if (this._options?.signal?.aborted) {
306
+ this._state = 'cancelled';
307
+ throw createAbortError();
308
+ }
309
+ if (this.state === 'cancelled') {
310
+ throw error;
311
+ }
312
+ this._state = 'error';
313
+ throw error;
314
+ }
315
+ finally {
316
+ cleanupNetworkTask(this._options?.signal, this._subscription, this._abortHandler);
317
+ this._subscription = undefined;
318
+ this._abortHandler = undefined;
319
+ this._inFlightOperation = undefined;
320
+ }
321
+ }
322
+ _emitFinalProgressEvent(fileSize) {
323
+ // Emit a synthetic final progress to guarantee 100% is reported. Native progress events may
324
+ // not fire for small files, and even when they do, the event can race with promise
325
+ // resolution (listener removed before delivery).
326
+ if (this._options?.onProgress && fileSize > 0) {
327
+ this._options.onProgress({
328
+ bytesWritten: fileSize,
329
+ totalBytes: fileSize,
330
+ });
331
+ }
332
+ }
333
+ }
@@ -0,0 +1,16 @@
1
+ export declare function resolve(...args: string[]): string;
2
+ export declare function normalize(path: string): string;
3
+ export declare function isAbsolute(path: string): boolean;
4
+ export declare function join(...args: string[]): string;
5
+ export declare function relative(from: string, to: string): string;
6
+ export declare function dirname(path: string): string;
7
+ export declare function basename(path: string, suffix?: string): string;
8
+ export declare function extname(path: string): string;
9
+ export declare function parse(path: string): {
10
+ root: string;
11
+ dir: string;
12
+ base: string;
13
+ ext: string;
14
+ name: string;
15
+ };
16
+ export declare const sep = "/";
@@ -0,0 +1,353 @@
1
+ // Ported verbatim from expo-file-system's own vendored copy of Node's posix `path` module
2
+ // (Copyright Joyent, Inc. and other Node contributors, MIT licensed) — a self-contained,
3
+ // mechanical algorithm with zero native dependency, so a straight port carries low risk.
4
+ function isPathSeparator(code) {
5
+ return code === '/';
6
+ }
7
+ // Resolves . and .. elements in a path with directory names
8
+ function normalizeString(path, allowAboveRoot, separator) {
9
+ let res = '';
10
+ let lastSegmentLength = 0;
11
+ let lastSlash = -1;
12
+ let dots = 0;
13
+ let code = '';
14
+ for (let i = 0; i <= path.length; ++i) {
15
+ if (i < path.length)
16
+ code = path.charAt(i);
17
+ else if (isPathSeparator(code))
18
+ break;
19
+ else
20
+ code = '/';
21
+ if (isPathSeparator(code)) {
22
+ if (lastSlash === i - 1 || dots === 1) {
23
+ // NOOP
24
+ }
25
+ else if (dots === 2) {
26
+ if (res.length < 2 ||
27
+ lastSegmentLength !== 2 ||
28
+ res.charAt(res.length - 1) !== '.' ||
29
+ res.charAt(res.length - 2) !== '.') {
30
+ if (res.length > 2) {
31
+ const lastSlashIndex = res.lastIndexOf(separator);
32
+ if (lastSlashIndex === -1) {
33
+ res = '';
34
+ lastSegmentLength = 0;
35
+ }
36
+ else {
37
+ res = res.slice(0, lastSlashIndex);
38
+ lastSegmentLength = res.length - 1 - res.lastIndexOf(separator);
39
+ }
40
+ lastSlash = i;
41
+ dots = 0;
42
+ continue;
43
+ }
44
+ else if (res.length !== 0) {
45
+ res = '';
46
+ lastSegmentLength = 0;
47
+ lastSlash = i;
48
+ dots = 0;
49
+ continue;
50
+ }
51
+ }
52
+ if (allowAboveRoot) {
53
+ res += res.length > 0 ? `${separator}..` : '..';
54
+ lastSegmentLength = 2;
55
+ }
56
+ }
57
+ else {
58
+ if (res.length > 0)
59
+ res += `${separator}${path.slice(lastSlash + 1, i)}`;
60
+ else
61
+ res = path.slice(lastSlash + 1, i);
62
+ lastSegmentLength = i - lastSlash - 1;
63
+ }
64
+ lastSlash = i;
65
+ dots = 0;
66
+ }
67
+ else if (code === '.' && dots !== -1) {
68
+ ++dots;
69
+ }
70
+ else {
71
+ dots = -1;
72
+ }
73
+ }
74
+ return res;
75
+ }
76
+ export function resolve(...args) {
77
+ let resolvedPath = '';
78
+ let resolvedAbsolute = false;
79
+ for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
80
+ const path = i >= 0 ? args[i] : '';
81
+ if (!path || path.length === 0) {
82
+ continue;
83
+ }
84
+ resolvedPath = `${path}/${resolvedPath}`;
85
+ resolvedAbsolute = !!(path && path.charAt(0) === '/');
86
+ }
87
+ resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/');
88
+ if (resolvedAbsolute) {
89
+ return `/${resolvedPath}`;
90
+ }
91
+ return resolvedPath.length > 0 ? resolvedPath : '.';
92
+ }
93
+ export function normalize(path) {
94
+ if (path.length === 0)
95
+ return '.';
96
+ const isAbsolute = path.charAt(0) === '/';
97
+ const trailingSeparator = path.charAt(path.length - 1) === '/';
98
+ path = normalizeString(path, !isAbsolute, '/');
99
+ if (path.length === 0) {
100
+ if (isAbsolute)
101
+ return '/';
102
+ return trailingSeparator ? './' : '.';
103
+ }
104
+ if (trailingSeparator)
105
+ path += '/';
106
+ return isAbsolute ? `/${path}` : path;
107
+ }
108
+ export function isAbsolute(path) {
109
+ return path.length > 0 && path.charAt(0) === '/';
110
+ }
111
+ export function join(...args) {
112
+ if (args.length === 0)
113
+ return '.';
114
+ const path = [];
115
+ for (let i = 0; i < args.length; ++i) {
116
+ const arg = args[i];
117
+ if (arg && arg.length > 0) {
118
+ path.push(arg);
119
+ }
120
+ }
121
+ if (path.length === 0)
122
+ return '.';
123
+ return normalize(path.join('/'));
124
+ }
125
+ export function relative(from, to) {
126
+ if (from === to)
127
+ return '';
128
+ from = resolve(from);
129
+ to = resolve(to);
130
+ if (from === to)
131
+ return '';
132
+ const fromStart = 1;
133
+ const fromEnd = from.length;
134
+ const fromLen = fromEnd - fromStart;
135
+ const toStart = 1;
136
+ const toLen = to.length - toStart;
137
+ const length = fromLen < toLen ? fromLen : toLen;
138
+ let lastCommonSep = -1;
139
+ let i = 0;
140
+ for (; i < length; i++) {
141
+ const fromChar = from.charAt(fromStart + i);
142
+ if (fromChar !== to.charAt(toStart + i))
143
+ break;
144
+ else if (fromChar === '/')
145
+ lastCommonSep = i;
146
+ }
147
+ if (i === length) {
148
+ if (toLen > length) {
149
+ if (to.charAt(toStart + i) === '/') {
150
+ return to.slice(toStart + i + 1);
151
+ }
152
+ if (i === 0) {
153
+ return to.slice(toStart + i);
154
+ }
155
+ }
156
+ else if (fromLen > length) {
157
+ if (from.charAt(fromStart + i) === '/') {
158
+ lastCommonSep = i;
159
+ }
160
+ else if (i === 0) {
161
+ lastCommonSep = 0;
162
+ }
163
+ }
164
+ }
165
+ let out = '';
166
+ for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
167
+ if (i === fromEnd || from.charAt(i) === '/') {
168
+ out += out.length === 0 ? '..' : '/..';
169
+ }
170
+ }
171
+ return `${out}${to.slice(toStart + lastCommonSep)}`;
172
+ }
173
+ export function dirname(path) {
174
+ if (path.length === 0)
175
+ return '.';
176
+ const hasRoot = path.charAt(0) === '/';
177
+ let end = -1;
178
+ let matchedSlash = true;
179
+ for (let i = path.length - 1; i >= 1; --i) {
180
+ if (path.charAt(i) === '/') {
181
+ if (!matchedSlash) {
182
+ end = i;
183
+ break;
184
+ }
185
+ }
186
+ else {
187
+ matchedSlash = false;
188
+ }
189
+ }
190
+ if (end === -1)
191
+ return hasRoot ? '/' : '.';
192
+ if (hasRoot && end === 1)
193
+ return '//';
194
+ return path.slice(0, end);
195
+ }
196
+ export function basename(path, suffix) {
197
+ let start = 0;
198
+ let end = -1;
199
+ let matchedSlash = true;
200
+ if (suffix !== undefined &&
201
+ suffix.length > 0 &&
202
+ suffix.length <= path.length) {
203
+ if (suffix === path)
204
+ return '';
205
+ let extIdx = suffix.length - 1;
206
+ let firstNonSlashEnd = -1;
207
+ for (let i = path.length - 1; i >= 0; --i) {
208
+ const code = path.charAt(i);
209
+ if (code === '/') {
210
+ if (!matchedSlash) {
211
+ start = i + 1;
212
+ break;
213
+ }
214
+ }
215
+ else {
216
+ if (firstNonSlashEnd === -1) {
217
+ matchedSlash = false;
218
+ firstNonSlashEnd = i + 1;
219
+ }
220
+ if (extIdx >= 0) {
221
+ if (code === suffix.charAt(extIdx)) {
222
+ if (--extIdx === -1) {
223
+ end = i;
224
+ }
225
+ }
226
+ else {
227
+ extIdx = -1;
228
+ end = firstNonSlashEnd;
229
+ }
230
+ }
231
+ }
232
+ }
233
+ if (start === end)
234
+ end = firstNonSlashEnd;
235
+ else if (end === -1)
236
+ end = path.length;
237
+ return path.slice(start, end);
238
+ }
239
+ for (let i = path.length - 1; i >= 0; --i) {
240
+ if (path.charAt(i) === '/') {
241
+ if (!matchedSlash) {
242
+ start = i + 1;
243
+ break;
244
+ }
245
+ }
246
+ else if (end === -1) {
247
+ matchedSlash = false;
248
+ end = i + 1;
249
+ }
250
+ }
251
+ if (end === -1)
252
+ return '';
253
+ return path.slice(start, end);
254
+ }
255
+ export function extname(path) {
256
+ let startDot = -1;
257
+ let startPart = 0;
258
+ let end = -1;
259
+ let matchedSlash = true;
260
+ let preDotState = 0;
261
+ for (let i = path.length - 1; i >= 0; --i) {
262
+ const code = path.charAt(i);
263
+ if (code === '/') {
264
+ if (!matchedSlash) {
265
+ startPart = i + 1;
266
+ break;
267
+ }
268
+ continue;
269
+ }
270
+ if (end === -1) {
271
+ matchedSlash = false;
272
+ end = i + 1;
273
+ }
274
+ if (code === '.') {
275
+ if (startDot === -1)
276
+ startDot = i;
277
+ else if (preDotState !== 1)
278
+ preDotState = 1;
279
+ }
280
+ else if (startDot !== -1) {
281
+ preDotState = -1;
282
+ }
283
+ }
284
+ if (startDot === -1 ||
285
+ end === -1 ||
286
+ preDotState === 0 ||
287
+ (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)) {
288
+ return '';
289
+ }
290
+ return path.slice(startDot, end);
291
+ }
292
+ export function parse(path) {
293
+ const ret = { root: '', dir: '', base: '', ext: '', name: '' };
294
+ if (path.length === 0)
295
+ return ret;
296
+ const isAbsolutePath = path.charAt(0) === '/';
297
+ let start;
298
+ if (isAbsolutePath) {
299
+ ret.root = '/';
300
+ start = 1;
301
+ }
302
+ else {
303
+ start = 0;
304
+ }
305
+ let startDot = -1;
306
+ let startPart = 0;
307
+ let end = -1;
308
+ let matchedSlash = true;
309
+ let i = path.length - 1;
310
+ let preDotState = 0;
311
+ for (; i >= start; --i) {
312
+ const code = path.charAt(i);
313
+ if (code === '/') {
314
+ if (!matchedSlash) {
315
+ startPart = i + 1;
316
+ break;
317
+ }
318
+ continue;
319
+ }
320
+ if (end === -1) {
321
+ matchedSlash = false;
322
+ end = i + 1;
323
+ }
324
+ if (code === '.') {
325
+ if (startDot === -1)
326
+ startDot = i;
327
+ else if (preDotState !== 1)
328
+ preDotState = 1;
329
+ }
330
+ else if (startDot !== -1) {
331
+ preDotState = -1;
332
+ }
333
+ }
334
+ if (end !== -1) {
335
+ const nameStart = startPart === 0 && isAbsolutePath ? 1 : startPart;
336
+ if (startDot === -1 ||
337
+ preDotState === 0 ||
338
+ (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)) {
339
+ ret.base = ret.name = path.slice(nameStart, end);
340
+ }
341
+ else {
342
+ ret.name = path.slice(nameStart, startDot);
343
+ ret.base = path.slice(nameStart, end);
344
+ ret.ext = path.slice(startDot, end);
345
+ }
346
+ }
347
+ if (startPart > 0)
348
+ ret.dir = path.slice(0, startPart - 1);
349
+ else if (isAbsolutePath)
350
+ ret.dir = '/';
351
+ return ret;
352
+ }
353
+ export const sep = '/';