@jsenv/core 41.4.1 → 41.4.2
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/build/build.js +173 -4
- package/dist/build/jsenv_core_packages.js +91 -1
- package/dist/html/client_monitor_page.html +4 -1
- package/dist/html/clients_page.html +13 -5
- package/dist/js/client_reporter.js +185 -109
- package/dist/js/page_switcher.js +731 -0
- package/dist/start_dev_server/jsenv_core_packages.js +695 -4
- package/dist/start_dev_server/start_dev_server.js +357 -78
- package/package.json +2 -2
- package/src/dev/dev_server_plugins/dev_server_plugin_serve_source_files.js +55 -5
- package/src/dev/start_dev_server.js +13 -3
- package/src/kitchen/kitchen.js +12 -3
- package/src/kitchen/url_graph/url_graph.js +11 -0
- package/src/plugins/client_monitoring/client/client_monitor_page.html +4 -1
- package/src/plugins/client_monitoring/client/client_reporter.js +185 -109
- package/src/plugins/client_monitoring/client/clients_page.html +13 -5
- package/src/plugins/client_monitoring/jsenv_plugin_client_monitoring.js +76 -68
- package/src/plugins/page_switcher/client/page_switcher.js +731 -0
- package/src/plugins/page_switcher/jsenv_plugin_page_switcher.js +42 -0
- package/src/plugins/protocol_file/html_pages.js +122 -0
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +24 -0
- package/src/plugins/resolution_node_esm/node_esm_resolver.js +5 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createSupportsColor, isUnicodeSupported, eastAsianWidth, clearTerminal, eraseLines } from "./jsenv_core_node_modules.js";
|
|
2
2
|
import { stripVTControlCharacters } from "node:util";
|
|
3
|
-
import { readFileSync, existsSync, chmodSync, statSync, lstatSync, readdirSync, openSync, closeSync, unlinkSync, rmdirSync, mkdirSync, writeFileSync as writeFileSync$1, watch, realpathSync } from "node:fs";
|
|
3
|
+
import { readFileSync, existsSync, readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, readdirSync, openSync, closeSync, unlinkSync, rmdirSync, mkdirSync, writeFileSync as writeFileSync$1, watch, realpathSync } from "node:fs";
|
|
4
4
|
import { extname, basename, dirname } from "node:path";
|
|
5
5
|
import crypto, { createHash } from "node:crypto";
|
|
6
6
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
@@ -1978,7 +1978,7 @@ const compareFileUrls = (a, b) => {
|
|
|
1978
1978
|
return comparePathnames(new URL(a).pathname, new URL(b).pathname);
|
|
1979
1979
|
};
|
|
1980
1980
|
|
|
1981
|
-
const isWindows$
|
|
1981
|
+
const isWindows$3 = process.platform === "win32";
|
|
1982
1982
|
const baseUrlFallback = fileSystemPathToUrl(process.cwd());
|
|
1983
1983
|
|
|
1984
1984
|
/**
|
|
@@ -2001,7 +2001,7 @@ const ensureWindowsDriveLetter = (url, baseUrl) => {
|
|
|
2001
2001
|
throw new Error(`absolute url expect but got ${url}`);
|
|
2002
2002
|
}
|
|
2003
2003
|
|
|
2004
|
-
if (!isWindows$
|
|
2004
|
+
if (!isWindows$3) {
|
|
2005
2005
|
return url;
|
|
2006
2006
|
}
|
|
2007
2007
|
|
|
@@ -2113,6 +2113,457 @@ const readPackageAtOrNull = (packageDirectoryUrl) => {
|
|
|
2113
2113
|
}
|
|
2114
2114
|
};
|
|
2115
2115
|
|
|
2116
|
+
const createCallbackListNotifiedOnce = () => {
|
|
2117
|
+
let callbacks = [];
|
|
2118
|
+
let status = "waiting";
|
|
2119
|
+
let currentCallbackIndex = -1;
|
|
2120
|
+
|
|
2121
|
+
const callbackListOnce = {};
|
|
2122
|
+
|
|
2123
|
+
const add = (callback) => {
|
|
2124
|
+
if (status !== "waiting") {
|
|
2125
|
+
emitUnexpectedActionWarning({ action: "add", status });
|
|
2126
|
+
return removeNoop;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
if (typeof callback !== "function") {
|
|
2130
|
+
throw new Error(`callback must be a function, got ${callback}`);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
// don't register twice
|
|
2134
|
+
const existingCallback = callbacks.find((callbackCandidate) => {
|
|
2135
|
+
return callbackCandidate === callback;
|
|
2136
|
+
});
|
|
2137
|
+
if (existingCallback) {
|
|
2138
|
+
emitCallbackDuplicationWarning();
|
|
2139
|
+
return removeNoop;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
callbacks.push(callback);
|
|
2143
|
+
return () => {
|
|
2144
|
+
if (status === "notified") {
|
|
2145
|
+
// once called removing does nothing
|
|
2146
|
+
// as the callbacks array is frozen to null
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
const index = callbacks.indexOf(callback);
|
|
2151
|
+
if (index === -1) {
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
if (status === "looping") {
|
|
2156
|
+
if (index <= currentCallbackIndex) {
|
|
2157
|
+
// The callback was already called (or is the current callback)
|
|
2158
|
+
// We don't want to mutate the callbacks array
|
|
2159
|
+
// or it would alter the looping done in "call" and the next callback
|
|
2160
|
+
// would be skipped
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
// Callback is part of the next callback to call,
|
|
2165
|
+
// we mutate the callbacks array to prevent this callback to be called
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
callbacks.splice(index, 1);
|
|
2169
|
+
};
|
|
2170
|
+
};
|
|
2171
|
+
|
|
2172
|
+
const notify = (param) => {
|
|
2173
|
+
if (status !== "waiting") {
|
|
2174
|
+
emitUnexpectedActionWarning({ action: "call", status });
|
|
2175
|
+
return [];
|
|
2176
|
+
}
|
|
2177
|
+
status = "looping";
|
|
2178
|
+
const values = callbacks.map((callback, index) => {
|
|
2179
|
+
currentCallbackIndex = index;
|
|
2180
|
+
return callback(param);
|
|
2181
|
+
});
|
|
2182
|
+
callbackListOnce.notified = true;
|
|
2183
|
+
status = "notified";
|
|
2184
|
+
// we reset callbacks to null after looping
|
|
2185
|
+
// so that it's possible to remove during the loop
|
|
2186
|
+
callbacks = null;
|
|
2187
|
+
currentCallbackIndex = -1;
|
|
2188
|
+
|
|
2189
|
+
return values;
|
|
2190
|
+
};
|
|
2191
|
+
|
|
2192
|
+
callbackListOnce.notified = false;
|
|
2193
|
+
callbackListOnce.add = add;
|
|
2194
|
+
callbackListOnce.notify = notify;
|
|
2195
|
+
|
|
2196
|
+
return callbackListOnce;
|
|
2197
|
+
};
|
|
2198
|
+
|
|
2199
|
+
const emitUnexpectedActionWarning = ({ action, status }) => {
|
|
2200
|
+
if (typeof process.emitWarning === "function") {
|
|
2201
|
+
process.emitWarning(
|
|
2202
|
+
`"${action}" should not happen when callback list is ${status}`,
|
|
2203
|
+
{
|
|
2204
|
+
CODE: "UNEXPECTED_ACTION_ON_CALLBACK_LIST",
|
|
2205
|
+
detail: `Code is potentially executed when it should not`,
|
|
2206
|
+
},
|
|
2207
|
+
);
|
|
2208
|
+
} else {
|
|
2209
|
+
console.warn(
|
|
2210
|
+
`"${action}" should not happen when callback list is ${status}`,
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
};
|
|
2214
|
+
|
|
2215
|
+
const emitCallbackDuplicationWarning = () => {
|
|
2216
|
+
if (typeof process.emitWarning === "function") {
|
|
2217
|
+
process.emitWarning(`Trying to add a callback already in the list`, {
|
|
2218
|
+
CODE: "CALLBACK_DUPLICATION",
|
|
2219
|
+
detail: `Code is potentially executed more than it should`,
|
|
2220
|
+
});
|
|
2221
|
+
} else {
|
|
2222
|
+
console.warn(`Trying to add same callback twice`);
|
|
2223
|
+
}
|
|
2224
|
+
};
|
|
2225
|
+
|
|
2226
|
+
const removeNoop = () => {};
|
|
2227
|
+
|
|
2228
|
+
/*
|
|
2229
|
+
* See callback_race.md
|
|
2230
|
+
*/
|
|
2231
|
+
|
|
2232
|
+
const raceCallbacks = (raceDescription, winnerCallback) => {
|
|
2233
|
+
let cleanCallbacks = [];
|
|
2234
|
+
let status = "racing";
|
|
2235
|
+
|
|
2236
|
+
const clean = () => {
|
|
2237
|
+
cleanCallbacks.forEach((clean) => {
|
|
2238
|
+
clean();
|
|
2239
|
+
});
|
|
2240
|
+
cleanCallbacks = null;
|
|
2241
|
+
};
|
|
2242
|
+
|
|
2243
|
+
const cancel = () => {
|
|
2244
|
+
if (status !== "racing") {
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
status = "cancelled";
|
|
2248
|
+
clean();
|
|
2249
|
+
};
|
|
2250
|
+
|
|
2251
|
+
Object.keys(raceDescription).forEach((candidateName) => {
|
|
2252
|
+
const register = raceDescription[candidateName];
|
|
2253
|
+
const returnValue = register((data) => {
|
|
2254
|
+
if (status !== "racing") {
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
status = "done";
|
|
2258
|
+
clean();
|
|
2259
|
+
winnerCallback({
|
|
2260
|
+
name: candidateName,
|
|
2261
|
+
data,
|
|
2262
|
+
});
|
|
2263
|
+
});
|
|
2264
|
+
if (typeof returnValue === "function") {
|
|
2265
|
+
cleanCallbacks.push(returnValue);
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
2268
|
+
|
|
2269
|
+
return cancel;
|
|
2270
|
+
};
|
|
2271
|
+
|
|
2272
|
+
/*
|
|
2273
|
+
* https://github.com/whatwg/dom/issues/920
|
|
2274
|
+
*/
|
|
2275
|
+
|
|
2276
|
+
|
|
2277
|
+
const Abort = {
|
|
2278
|
+
isAbortError: (error) => {
|
|
2279
|
+
return error && error.name === "AbortError";
|
|
2280
|
+
},
|
|
2281
|
+
|
|
2282
|
+
startOperation: () => {
|
|
2283
|
+
return createOperation();
|
|
2284
|
+
},
|
|
2285
|
+
|
|
2286
|
+
throwIfAborted: (signal) => {
|
|
2287
|
+
if (signal.aborted) {
|
|
2288
|
+
const error = new Error(`The operation was aborted`);
|
|
2289
|
+
error.name = "AbortError";
|
|
2290
|
+
error.type = "aborted";
|
|
2291
|
+
throw error;
|
|
2292
|
+
}
|
|
2293
|
+
},
|
|
2294
|
+
};
|
|
2295
|
+
|
|
2296
|
+
const createOperation = () => {
|
|
2297
|
+
const operationAbortController = new AbortController();
|
|
2298
|
+
// const abortOperation = (value) => abortController.abort(value)
|
|
2299
|
+
const operationSignal = operationAbortController.signal;
|
|
2300
|
+
|
|
2301
|
+
// abortCallbackList is used to ignore the max listeners warning from Node.js
|
|
2302
|
+
// this warning is useful but becomes problematic when it's expect
|
|
2303
|
+
// (a function doing 20 http call in parallel)
|
|
2304
|
+
// To be 100% sure we don't have memory leak, only Abortable.asyncCallback
|
|
2305
|
+
// uses abortCallbackList to know when something is aborted
|
|
2306
|
+
const abortCallbackList = createCallbackListNotifiedOnce();
|
|
2307
|
+
const endCallbackList = createCallbackListNotifiedOnce();
|
|
2308
|
+
|
|
2309
|
+
let isAbortAfterEnd = false;
|
|
2310
|
+
|
|
2311
|
+
operationSignal.onabort = () => {
|
|
2312
|
+
operationSignal.onabort = null;
|
|
2313
|
+
|
|
2314
|
+
const allAbortCallbacksPromise = Promise.all(abortCallbackList.notify());
|
|
2315
|
+
if (!isAbortAfterEnd) {
|
|
2316
|
+
addEndCallback(async () => {
|
|
2317
|
+
await allAbortCallbacksPromise;
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
};
|
|
2321
|
+
|
|
2322
|
+
const throwIfAborted = () => {
|
|
2323
|
+
Abort.throwIfAborted(operationSignal);
|
|
2324
|
+
};
|
|
2325
|
+
|
|
2326
|
+
// add a callback called on abort
|
|
2327
|
+
// differences with signal.addEventListener('abort')
|
|
2328
|
+
// - operation.end awaits the return value of this callback
|
|
2329
|
+
// - It won't increase the count of listeners for "abort" that would
|
|
2330
|
+
// trigger max listeners warning when count > 10
|
|
2331
|
+
const addAbortCallback = (callback) => {
|
|
2332
|
+
// It would be painful and not super redable to check if signal is aborted
|
|
2333
|
+
// before deciding if it's an abort or end callback
|
|
2334
|
+
// with pseudo-code below where we want to stop server either
|
|
2335
|
+
// on abort or when ended because signal is aborted
|
|
2336
|
+
// operation[operation.signal.aborted ? 'addAbortCallback': 'addEndCallback'](async () => {
|
|
2337
|
+
// await server.stop()
|
|
2338
|
+
// })
|
|
2339
|
+
if (operationSignal.aborted) {
|
|
2340
|
+
return addEndCallback(callback);
|
|
2341
|
+
}
|
|
2342
|
+
return abortCallbackList.add(callback);
|
|
2343
|
+
};
|
|
2344
|
+
|
|
2345
|
+
const addEndCallback = (callback) => {
|
|
2346
|
+
return endCallbackList.add(callback);
|
|
2347
|
+
};
|
|
2348
|
+
|
|
2349
|
+
const end = async ({ abortAfterEnd = false } = {}) => {
|
|
2350
|
+
await Promise.all(endCallbackList.notify());
|
|
2351
|
+
|
|
2352
|
+
// "abortAfterEnd" can be handy to ensure "abort" callbacks
|
|
2353
|
+
// added with { once: true } are removed
|
|
2354
|
+
// It might also help garbage collection because
|
|
2355
|
+
// runtime implementing AbortSignal (Node.js, browsers) can consider abortSignal
|
|
2356
|
+
// as settled and clean up things
|
|
2357
|
+
if (abortAfterEnd) {
|
|
2358
|
+
// because of operationSignal.onabort = null
|
|
2359
|
+
// + abortCallbackList.clear() this won't re-call
|
|
2360
|
+
// callbacks
|
|
2361
|
+
if (!operationSignal.aborted) {
|
|
2362
|
+
isAbortAfterEnd = true;
|
|
2363
|
+
operationAbortController.abort();
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
|
|
2368
|
+
const addAbortSignal = (
|
|
2369
|
+
signal,
|
|
2370
|
+
{ onAbort = callbackNoop, onRemove = callbackNoop } = {},
|
|
2371
|
+
) => {
|
|
2372
|
+
const applyAbortEffects = () => {
|
|
2373
|
+
const onAbortCallback = onAbort;
|
|
2374
|
+
onAbort = callbackNoop;
|
|
2375
|
+
onAbortCallback();
|
|
2376
|
+
};
|
|
2377
|
+
const applyRemoveEffects = () => {
|
|
2378
|
+
const onRemoveCallback = onRemove;
|
|
2379
|
+
onRemove = callbackNoop;
|
|
2380
|
+
onAbort = callbackNoop;
|
|
2381
|
+
onRemoveCallback();
|
|
2382
|
+
};
|
|
2383
|
+
|
|
2384
|
+
if (operationSignal.aborted) {
|
|
2385
|
+
applyAbortEffects();
|
|
2386
|
+
applyRemoveEffects();
|
|
2387
|
+
return callbackNoop;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
if (signal.aborted) {
|
|
2391
|
+
operationAbortController.abort();
|
|
2392
|
+
applyAbortEffects();
|
|
2393
|
+
applyRemoveEffects();
|
|
2394
|
+
return callbackNoop;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
const cancelRace = raceCallbacks(
|
|
2398
|
+
{
|
|
2399
|
+
operation_abort: (cb) => {
|
|
2400
|
+
return addAbortCallback(cb);
|
|
2401
|
+
},
|
|
2402
|
+
operation_end: (cb) => {
|
|
2403
|
+
return addEndCallback(cb);
|
|
2404
|
+
},
|
|
2405
|
+
child_abort: (cb) => {
|
|
2406
|
+
return addEventListener(signal, "abort", cb);
|
|
2407
|
+
},
|
|
2408
|
+
},
|
|
2409
|
+
(winner) => {
|
|
2410
|
+
const raceEffects = {
|
|
2411
|
+
// Both "operation_abort" and "operation_end"
|
|
2412
|
+
// means we don't care anymore if the child aborts.
|
|
2413
|
+
// So we can:
|
|
2414
|
+
// - remove "abort" event listener on child (done by raceCallback)
|
|
2415
|
+
// - remove abort callback on operation (done by raceCallback)
|
|
2416
|
+
// - remove end callback on operation (done by raceCallback)
|
|
2417
|
+
// - call any custom cancel function
|
|
2418
|
+
operation_abort: () => {
|
|
2419
|
+
applyAbortEffects();
|
|
2420
|
+
applyRemoveEffects();
|
|
2421
|
+
},
|
|
2422
|
+
operation_end: () => {
|
|
2423
|
+
// Exists to
|
|
2424
|
+
// - remove abort callback on operation
|
|
2425
|
+
// - remove "abort" event listener on child
|
|
2426
|
+
// - call any custom cancel function
|
|
2427
|
+
applyRemoveEffects();
|
|
2428
|
+
},
|
|
2429
|
+
child_abort: () => {
|
|
2430
|
+
applyAbortEffects();
|
|
2431
|
+
operationAbortController.abort();
|
|
2432
|
+
},
|
|
2433
|
+
};
|
|
2434
|
+
raceEffects[winner.name](winner.value);
|
|
2435
|
+
},
|
|
2436
|
+
);
|
|
2437
|
+
|
|
2438
|
+
return () => {
|
|
2439
|
+
cancelRace();
|
|
2440
|
+
applyRemoveEffects();
|
|
2441
|
+
};
|
|
2442
|
+
};
|
|
2443
|
+
|
|
2444
|
+
const addAbortSource = (abortSourceCallback) => {
|
|
2445
|
+
const abortSource = {
|
|
2446
|
+
cleaned: false,
|
|
2447
|
+
signal: null,
|
|
2448
|
+
remove: callbackNoop,
|
|
2449
|
+
};
|
|
2450
|
+
const abortSourceController = new AbortController();
|
|
2451
|
+
const abortSourceSignal = abortSourceController.signal;
|
|
2452
|
+
abortSource.signal = abortSourceSignal;
|
|
2453
|
+
if (operationSignal.aborted) {
|
|
2454
|
+
return abortSource;
|
|
2455
|
+
}
|
|
2456
|
+
const returnValue = abortSourceCallback((value) => {
|
|
2457
|
+
abortSourceController.abort(value);
|
|
2458
|
+
});
|
|
2459
|
+
const removeAbortSignal = addAbortSignal(abortSourceSignal, {
|
|
2460
|
+
onRemove: () => {
|
|
2461
|
+
if (typeof returnValue === "function") {
|
|
2462
|
+
returnValue();
|
|
2463
|
+
}
|
|
2464
|
+
abortSource.cleaned = true;
|
|
2465
|
+
},
|
|
2466
|
+
});
|
|
2467
|
+
abortSource.remove = removeAbortSignal;
|
|
2468
|
+
return abortSource;
|
|
2469
|
+
};
|
|
2470
|
+
|
|
2471
|
+
const timeout = (ms) => {
|
|
2472
|
+
return addAbortSource((abort) => {
|
|
2473
|
+
const timeoutId = setTimeout(abort, ms);
|
|
2474
|
+
// an abort source return value is called when:
|
|
2475
|
+
// - operation is aborted (by an other source)
|
|
2476
|
+
// - operation ends
|
|
2477
|
+
return () => {
|
|
2478
|
+
clearTimeout(timeoutId);
|
|
2479
|
+
};
|
|
2480
|
+
});
|
|
2481
|
+
};
|
|
2482
|
+
|
|
2483
|
+
const wait = (ms) => {
|
|
2484
|
+
return new Promise((resolve) => {
|
|
2485
|
+
const timeoutId = setTimeout(() => {
|
|
2486
|
+
removeAbortCallback();
|
|
2487
|
+
resolve();
|
|
2488
|
+
}, ms);
|
|
2489
|
+
const removeAbortCallback = addAbortCallback(() => {
|
|
2490
|
+
clearTimeout(timeoutId);
|
|
2491
|
+
});
|
|
2492
|
+
});
|
|
2493
|
+
};
|
|
2494
|
+
|
|
2495
|
+
const withSignal = async (asyncCallback) => {
|
|
2496
|
+
const abortController = new AbortController();
|
|
2497
|
+
const signal = abortController.signal;
|
|
2498
|
+
const removeAbortSignal = addAbortSignal(signal, {
|
|
2499
|
+
onAbort: () => {
|
|
2500
|
+
abortController.abort();
|
|
2501
|
+
},
|
|
2502
|
+
});
|
|
2503
|
+
try {
|
|
2504
|
+
const value = await asyncCallback(signal);
|
|
2505
|
+
removeAbortSignal();
|
|
2506
|
+
return value;
|
|
2507
|
+
} catch (e) {
|
|
2508
|
+
removeAbortSignal();
|
|
2509
|
+
throw e;
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
|
|
2513
|
+
const withSignalSync = (callback) => {
|
|
2514
|
+
const abortController = new AbortController();
|
|
2515
|
+
const signal = abortController.signal;
|
|
2516
|
+
const removeAbortSignal = addAbortSignal(signal, {
|
|
2517
|
+
onAbort: () => {
|
|
2518
|
+
abortController.abort();
|
|
2519
|
+
},
|
|
2520
|
+
});
|
|
2521
|
+
try {
|
|
2522
|
+
const value = callback(signal);
|
|
2523
|
+
removeAbortSignal();
|
|
2524
|
+
return value;
|
|
2525
|
+
} catch (e) {
|
|
2526
|
+
removeAbortSignal();
|
|
2527
|
+
throw e;
|
|
2528
|
+
}
|
|
2529
|
+
};
|
|
2530
|
+
|
|
2531
|
+
const fork = () => {
|
|
2532
|
+
const forkedOperation = createOperation();
|
|
2533
|
+
forkedOperation.addAbortSignal(operationSignal);
|
|
2534
|
+
return forkedOperation;
|
|
2535
|
+
};
|
|
2536
|
+
|
|
2537
|
+
return {
|
|
2538
|
+
// We could almost hide the operationSignal
|
|
2539
|
+
// But it can be handy for 2 things:
|
|
2540
|
+
// - know if operation is aborted (operation.signal.aborted)
|
|
2541
|
+
// - forward the operation.signal directly (not using "withSignal" or "withSignalSync")
|
|
2542
|
+
signal: operationSignal,
|
|
2543
|
+
|
|
2544
|
+
throwIfAborted,
|
|
2545
|
+
addAbortCallback,
|
|
2546
|
+
addAbortSignal,
|
|
2547
|
+
addAbortSource,
|
|
2548
|
+
fork,
|
|
2549
|
+
timeout,
|
|
2550
|
+
wait,
|
|
2551
|
+
withSignal,
|
|
2552
|
+
withSignalSync,
|
|
2553
|
+
addEndCallback,
|
|
2554
|
+
end,
|
|
2555
|
+
};
|
|
2556
|
+
};
|
|
2557
|
+
|
|
2558
|
+
const callbackNoop = () => {};
|
|
2559
|
+
|
|
2560
|
+
const addEventListener = (target, eventName, cb) => {
|
|
2561
|
+
target.addEventListener(eventName, cb);
|
|
2562
|
+
return () => {
|
|
2563
|
+
target.removeEventListener(eventName, cb);
|
|
2564
|
+
};
|
|
2565
|
+
};
|
|
2566
|
+
|
|
2116
2567
|
/*
|
|
2117
2568
|
* Link to things doing pattern matching:
|
|
2118
2569
|
* https://git-scm.com/docs/gitignore
|
|
@@ -2716,6 +3167,43 @@ const URL_META = {
|
|
|
2716
3167
|
createFilter,
|
|
2717
3168
|
};
|
|
2718
3169
|
|
|
3170
|
+
const readDirectory = async (url, { emfileMaxWait = 1000 } = {}) => {
|
|
3171
|
+
const directoryUrl = assertAndNormalizeDirectoryUrl(url);
|
|
3172
|
+
const directoryUrlObject = new URL(directoryUrl);
|
|
3173
|
+
const startMs = Date.now();
|
|
3174
|
+
let attemptCount = 0;
|
|
3175
|
+
|
|
3176
|
+
const attempt = async () => {
|
|
3177
|
+
try {
|
|
3178
|
+
const names = await new Promise((resolve, reject) => {
|
|
3179
|
+
readdir(directoryUrlObject, (error, names) => {
|
|
3180
|
+
if (error) {
|
|
3181
|
+
reject(error);
|
|
3182
|
+
} else {
|
|
3183
|
+
resolve(names);
|
|
3184
|
+
}
|
|
3185
|
+
});
|
|
3186
|
+
});
|
|
3187
|
+
return names.map(encodeURIComponent);
|
|
3188
|
+
} catch (e) {
|
|
3189
|
+
// https://nodejs.org/dist/latest-v13.x/docs/api/errors.html#errors_common_system_errors
|
|
3190
|
+
if (e.code === "EMFILE" || e.code === "ENFILE") {
|
|
3191
|
+
attemptCount++;
|
|
3192
|
+
const nowMs = Date.now();
|
|
3193
|
+
const timeSpentWaiting = nowMs - startMs;
|
|
3194
|
+
if (timeSpentWaiting > emfileMaxWait) {
|
|
3195
|
+
throw e;
|
|
3196
|
+
}
|
|
3197
|
+
await new Promise((resolve) => setTimeout(resolve), attemptCount);
|
|
3198
|
+
return await attempt();
|
|
3199
|
+
}
|
|
3200
|
+
throw e;
|
|
3201
|
+
}
|
|
3202
|
+
};
|
|
3203
|
+
|
|
3204
|
+
return attempt();
|
|
3205
|
+
};
|
|
3206
|
+
|
|
2719
3207
|
const generateWindowsEPERMErrorMessage = (
|
|
2720
3208
|
error,
|
|
2721
3209
|
{ operation, path },
|
|
@@ -2737,6 +3225,209 @@ const generateWindowsEPERMErrorMessage = (
|
|
|
2737
3225
|
return message;
|
|
2738
3226
|
};
|
|
2739
3227
|
|
|
3228
|
+
const writeEntryPermissions = async (source, permissions) => {
|
|
3229
|
+
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
3230
|
+
|
|
3231
|
+
let binaryFlags;
|
|
3232
|
+
{
|
|
3233
|
+
binaryFlags = permissions;
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
return new Promise((resolve, reject) => {
|
|
3237
|
+
chmod(new URL(sourceUrl), binaryFlags, (error) => {
|
|
3238
|
+
if (error) {
|
|
3239
|
+
reject(error);
|
|
3240
|
+
} else {
|
|
3241
|
+
resolve();
|
|
3242
|
+
}
|
|
3243
|
+
});
|
|
3244
|
+
});
|
|
3245
|
+
};
|
|
3246
|
+
|
|
3247
|
+
/*
|
|
3248
|
+
* - stats object documentation on Node.js
|
|
3249
|
+
* https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
|
|
3250
|
+
*/
|
|
3251
|
+
|
|
3252
|
+
|
|
3253
|
+
const isWindows$2 = process.platform === "win32";
|
|
3254
|
+
|
|
3255
|
+
const readEntryStat = async (
|
|
3256
|
+
source,
|
|
3257
|
+
{ nullIfNotFound = false, followLink = true } = {},
|
|
3258
|
+
) => {
|
|
3259
|
+
let sourceUrl = assertAndNormalizeFileUrl(source);
|
|
3260
|
+
if (sourceUrl.endsWith("/")) sourceUrl = sourceUrl.slice(0, -1);
|
|
3261
|
+
|
|
3262
|
+
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
3263
|
+
|
|
3264
|
+
const handleNotFoundOption = nullIfNotFound
|
|
3265
|
+
? {
|
|
3266
|
+
handleNotFoundError: () => null,
|
|
3267
|
+
}
|
|
3268
|
+
: {};
|
|
3269
|
+
|
|
3270
|
+
return readStat(sourcePath, {
|
|
3271
|
+
followLink,
|
|
3272
|
+
...handleNotFoundOption,
|
|
3273
|
+
...(isWindows$2
|
|
3274
|
+
? {
|
|
3275
|
+
// Windows can EPERM on stat
|
|
3276
|
+
handlePermissionDeniedError: async (error) => {
|
|
3277
|
+
console.error(
|
|
3278
|
+
`trying to fix windows EPERM after stats on ${sourcePath}`,
|
|
3279
|
+
);
|
|
3280
|
+
|
|
3281
|
+
try {
|
|
3282
|
+
// unfortunately it means we mutate the permissions
|
|
3283
|
+
// without being able to restore them to the previous value
|
|
3284
|
+
// (because reading current permission would also throw)
|
|
3285
|
+
await writeEntryPermissions(sourceUrl, 0o666);
|
|
3286
|
+
const stats = await readStat(sourcePath, {
|
|
3287
|
+
followLink,
|
|
3288
|
+
...handleNotFoundOption,
|
|
3289
|
+
// could not fix the permission error, give up and throw original error
|
|
3290
|
+
handlePermissionDeniedError: () => {
|
|
3291
|
+
console.error(`still got EPERM after stats on ${sourcePath}`);
|
|
3292
|
+
throw error;
|
|
3293
|
+
},
|
|
3294
|
+
});
|
|
3295
|
+
return stats;
|
|
3296
|
+
} catch (e) {
|
|
3297
|
+
console.error(
|
|
3298
|
+
generateWindowsEPERMErrorMessage(e, {
|
|
3299
|
+
operation: "stats",
|
|
3300
|
+
path: sourcePath,
|
|
3301
|
+
}),
|
|
3302
|
+
);
|
|
3303
|
+
throw error;
|
|
3304
|
+
}
|
|
3305
|
+
},
|
|
3306
|
+
}
|
|
3307
|
+
: {}),
|
|
3308
|
+
});
|
|
3309
|
+
};
|
|
3310
|
+
|
|
3311
|
+
const readStat = (
|
|
3312
|
+
sourcePath,
|
|
3313
|
+
{
|
|
3314
|
+
followLink,
|
|
3315
|
+
handleNotFoundError = null,
|
|
3316
|
+
handlePermissionDeniedError = null,
|
|
3317
|
+
} = {},
|
|
3318
|
+
) => {
|
|
3319
|
+
const nodeMethod = followLink ? stat : lstat;
|
|
3320
|
+
|
|
3321
|
+
return new Promise((resolve, reject) => {
|
|
3322
|
+
nodeMethod(sourcePath, (error, statsObject) => {
|
|
3323
|
+
if (error) {
|
|
3324
|
+
if (handleNotFoundError && error.code === "ENOENT") {
|
|
3325
|
+
resolve(handleNotFoundError(error));
|
|
3326
|
+
} else if (
|
|
3327
|
+
handlePermissionDeniedError &&
|
|
3328
|
+
(error.code === "EPERM" || error.code === "EACCES")
|
|
3329
|
+
) {
|
|
3330
|
+
resolve(handlePermissionDeniedError(error));
|
|
3331
|
+
} else {
|
|
3332
|
+
reject(error);
|
|
3333
|
+
}
|
|
3334
|
+
} else {
|
|
3335
|
+
resolve(statsObject);
|
|
3336
|
+
}
|
|
3337
|
+
});
|
|
3338
|
+
});
|
|
3339
|
+
};
|
|
3340
|
+
|
|
3341
|
+
const collectFiles = async ({
|
|
3342
|
+
signal = new AbortController().signal,
|
|
3343
|
+
directoryUrl,
|
|
3344
|
+
associations,
|
|
3345
|
+
predicate,
|
|
3346
|
+
}) => {
|
|
3347
|
+
const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl);
|
|
3348
|
+
if (typeof predicate !== "function") {
|
|
3349
|
+
throw new TypeError(`predicate must be a function, got ${predicate}`);
|
|
3350
|
+
}
|
|
3351
|
+
associations = URL_META.resolveAssociations(associations, rootDirectoryUrl);
|
|
3352
|
+
|
|
3353
|
+
const collectOperation = Abort.startOperation();
|
|
3354
|
+
collectOperation.addAbortSignal(signal);
|
|
3355
|
+
|
|
3356
|
+
const matchingFileResultArray = [];
|
|
3357
|
+
const visitDirectory = async (directoryUrl) => {
|
|
3358
|
+
collectOperation.throwIfAborted();
|
|
3359
|
+
const directoryItems = await readDirectory(directoryUrl);
|
|
3360
|
+
|
|
3361
|
+
await Promise.all(
|
|
3362
|
+
directoryItems.map(async (directoryItem) => {
|
|
3363
|
+
const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`;
|
|
3364
|
+
collectOperation.throwIfAborted();
|
|
3365
|
+
const directoryChildNodeStats = await readEntryStat(
|
|
3366
|
+
directoryChildNodeUrl,
|
|
3367
|
+
{
|
|
3368
|
+
// we ignore symlink because recursively traversed
|
|
3369
|
+
// so symlinked file will be discovered.
|
|
3370
|
+
// Moreover if they lead outside of directoryPath it can become a problem
|
|
3371
|
+
// like infinite recursion of whatever.
|
|
3372
|
+
// that we could handle using an object of pathname already seen but it will be useless
|
|
3373
|
+
// because directoryPath is recursively traversed
|
|
3374
|
+
followLink: false,
|
|
3375
|
+
},
|
|
3376
|
+
);
|
|
3377
|
+
|
|
3378
|
+
if (directoryChildNodeStats.isDirectory()) {
|
|
3379
|
+
const subDirectoryUrl = `${directoryChildNodeUrl}/`;
|
|
3380
|
+
if (
|
|
3381
|
+
!URL_META.urlChildMayMatch({
|
|
3382
|
+
url: subDirectoryUrl,
|
|
3383
|
+
associations,
|
|
3384
|
+
predicate,
|
|
3385
|
+
})
|
|
3386
|
+
) {
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
await visitDirectory(subDirectoryUrl);
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
if (directoryChildNodeStats.isFile()) {
|
|
3394
|
+
const meta = URL_META.applyAssociations({
|
|
3395
|
+
url: directoryChildNodeUrl,
|
|
3396
|
+
associations,
|
|
3397
|
+
});
|
|
3398
|
+
if (!predicate(meta)) return;
|
|
3399
|
+
const relativeUrl = urlToRelativeUrl(
|
|
3400
|
+
directoryChildNodeUrl,
|
|
3401
|
+
rootDirectoryUrl,
|
|
3402
|
+
);
|
|
3403
|
+
matchingFileResultArray.push({
|
|
3404
|
+
url: new URL(relativeUrl, rootDirectoryUrl).href,
|
|
3405
|
+
relativeUrl: decodeURIComponent(relativeUrl),
|
|
3406
|
+
meta,
|
|
3407
|
+
fileStats: directoryChildNodeStats,
|
|
3408
|
+
});
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3411
|
+
}),
|
|
3412
|
+
);
|
|
3413
|
+
};
|
|
3414
|
+
|
|
3415
|
+
try {
|
|
3416
|
+
await visitDirectory(rootDirectoryUrl);
|
|
3417
|
+
|
|
3418
|
+
// When we operate on thoose files later it feels more natural
|
|
3419
|
+
// to perform operation in the same order they appear in the filesystem.
|
|
3420
|
+
// It also allow to get a predictable return value.
|
|
3421
|
+
// For that reason we sort matchingFileResultArray
|
|
3422
|
+
matchingFileResultArray.sort((leftFile, rightFile) => {
|
|
3423
|
+
return comparePathnames(leftFile.relativeUrl, rightFile.relativeUrl);
|
|
3424
|
+
});
|
|
3425
|
+
return matchingFileResultArray;
|
|
3426
|
+
} finally {
|
|
3427
|
+
await collectOperation.end();
|
|
3428
|
+
}
|
|
3429
|
+
};
|
|
3430
|
+
|
|
2740
3431
|
const writeEntryPermissionsSync = (source, permissions) => {
|
|
2741
3432
|
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
2742
3433
|
|
|
@@ -6671,4 +7362,4 @@ const isResponseEligibleForIntegrityValidation = (response) => {
|
|
|
6671
7362
|
return ["basic", "cors", "default"].includes(response.type);
|
|
6672
7363
|
};
|
|
6673
7364
|
|
|
6674
|
-
export { ANSI, CONTENT_TYPE, DATA_URL, JS_QUOTES, RUNTIME_COMPAT, URL_META, applyFileSystemMagicResolution, applyNodeEsmResolution, asSpecifierWithoutSearch, asUrlWithoutSearch, assertAndNormalizeDirectoryUrl, bufferToEtag, compareFileUrls, composeTwoImportMaps, createDetailedMessage$1 as createDetailedMessage, createLogger, createTaskLog, ensurePathnameTrailingSlash, ensureWindowsDriveLetter, errorToHTML, formatError, generateContentFrame, getCallerPosition, getExtensionsToTry, injectQueryParams, injectQueryParamsIntoSpecifier, isFileSystemPath, isSpecifierForNodeBuiltin, lookupPackageDirectory, moveUrl, normalizeImportMap, normalizeUrl, readCustomConditionsFromProcessArgs, readEntryStatSync, readPackageAtOrNull, registerDirectoryLifecycle, registerFileLifecycle, resolveImport, setUrlBasename, setUrlExtension, setUrlFilename, stringifyUrlSite, urlIsOrIsInsideOf, urlToBasename, urlToExtension$1 as urlToExtension, urlToFileSystemPath, urlToFilename$1 as urlToFilename, urlToPathname$1 as urlToPathname, urlToRelativeUrl, validateResponseIntegrity, writeFileSync };
|
|
7365
|
+
export { ANSI, CONTENT_TYPE, DATA_URL, JS_QUOTES, RUNTIME_COMPAT, URL_META, applyFileSystemMagicResolution, applyNodeEsmResolution, asSpecifierWithoutSearch, asUrlWithoutSearch, assertAndNormalizeDirectoryUrl, bufferToEtag, collectFiles, compareFileUrls, composeTwoImportMaps, createDetailedMessage$1 as createDetailedMessage, createLogger, createTaskLog, ensurePathnameTrailingSlash, ensureWindowsDriveLetter, errorToHTML, formatError, generateContentFrame, getCallerPosition, getExtensionsToTry, injectQueryParams, injectQueryParamsIntoSpecifier, isFileSystemPath, isSpecifierForNodeBuiltin, lookupPackageDirectory, moveUrl, normalizeImportMap, normalizeUrl, readCustomConditionsFromProcessArgs, readEntryStatSync, readPackageAtOrNull, registerDirectoryLifecycle, registerFileLifecycle, resolveImport, setUrlBasename, setUrlExtension, setUrlFilename, stringifyUrlSite, urlIsOrIsInsideOf, urlToBasename, urlToExtension$1 as urlToExtension, urlToFileSystemPath, urlToFilename$1 as urlToFilename, urlToPathname$1 as urlToPathname, urlToRelativeUrl, validateResponseIntegrity, writeFileSync };
|