@twin.org/node-core 0.0.2-next.25 → 0.0.2-next.26
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/README.md +21 -7
- package/dist/cjs/index.cjs +387 -49
- package/dist/esm/index.mjs +382 -53
- package/dist/types/index.d.ts +4 -0
- package/dist/types/models/ICacheMetadata.d.ts +17 -0
- package/dist/types/models/IModuleProtocol.d.ts +18 -0
- package/dist/types/models/INodeEnvironmentVariables.d.ts +25 -0
- package/dist/types/models/IProtocolHandlerResult.d.ts +13 -0
- package/dist/types/models/moduleProtocol.d.ts +29 -0
- package/dist/types/node.d.ts +3 -2
- package/dist/types/utils.d.ts +67 -0
- package/docs/changelog.md +7 -0
- package/docs/detailed-guide.md +129 -0
- package/docs/reference/functions/createModuleImportUrl.md +21 -0
- package/docs/reference/functions/getExtensionsCacheDir.md +31 -0
- package/docs/reference/functions/handleHttpsProtocol.md +49 -0
- package/docs/reference/functions/handleNpmProtocol.md +31 -0
- package/docs/reference/functions/hashUrl.md +19 -0
- package/docs/reference/functions/isCacheExpired.md +31 -0
- package/docs/reference/functions/overrideModuleImport.md +8 -2
- package/docs/reference/functions/parseModuleProtocol.md +19 -0
- package/docs/reference/functions/resolvePackageEntryPoint.md +32 -0
- package/docs/reference/index.md +13 -0
- package/docs/reference/interfaces/ICacheMetadata.md +27 -0
- package/docs/reference/interfaces/IModuleProtocol.md +27 -0
- package/docs/reference/interfaces/INodeEnvironmentVariables.md +70 -0
- package/docs/reference/interfaces/IProtocolHandlerResult.md +19 -0
- package/docs/reference/type-aliases/ModuleProtocol.md +5 -0
- package/docs/reference/variables/ModuleProtocol.md +37 -0
- package/locales/en.json +11 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TWIN Node Core
|
|
2
2
|
|
|
3
|
-
TWIN
|
|
3
|
+
Core components for running TWIN nodes with dynamic extension loading and protocol-based module resolution.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,14 +8,28 @@ TWIN Node Core for serving APIs using the specified configuration
|
|
|
8
8
|
npm install @twin.org/node-core
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Quick Start
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
```javascript
|
|
14
|
+
import { start } from '@twin.org/node-core';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
// Start a TWIN node
|
|
17
|
+
await start({
|
|
18
|
+
extensions: ['./my-extension.mjs']
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Key Features
|
|
23
|
+
|
|
24
|
+
- **Protocol-based extension loading** - Load from local files, npm packages, or HTTPS URLs
|
|
25
|
+
- **Dynamic module resolution** - Automatic installation and caching
|
|
26
|
+
- **Extension lifecycle hooks** - Customize node behavior at different stages
|
|
27
|
+
- **Security controls** - Size limits, HTTPS-only, and cache TTL
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
16
30
|
|
|
17
|
-
|
|
31
|
+
For detailed documentation, configuration options, and examples, see [docs/detailed-guide.md](docs/detailed-guide.md).
|
|
18
32
|
|
|
19
|
-
##
|
|
33
|
+
## License
|
|
20
34
|
|
|
21
|
-
|
|
35
|
+
Apache-2.0
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -9,13 +9,14 @@ var entityStorageModels = require('@twin.org/entity-storage-models');
|
|
|
9
9
|
var identityModels = require('@twin.org/identity-models');
|
|
10
10
|
var vaultModels = require('@twin.org/vault-models');
|
|
11
11
|
var walletModels = require('@twin.org/wallet-models');
|
|
12
|
+
var node_child_process = require('node:child_process');
|
|
12
13
|
var promises = require('node:fs/promises');
|
|
14
|
+
var node_https = require('node:https');
|
|
13
15
|
var path = require('node:path');
|
|
14
16
|
var cliCore = require('@twin.org/cli-core');
|
|
17
|
+
var modules = require('@twin.org/modules');
|
|
15
18
|
var rightsManagementRestClient = require('@twin.org/rights-management-rest-client');
|
|
16
19
|
var engineServer = require('@twin.org/engine-server');
|
|
17
|
-
var modules = require('@twin.org/modules');
|
|
18
|
-
var node_child_process = require('node:child_process');
|
|
19
20
|
var dotenv = require('dotenv');
|
|
20
21
|
var engine = require('@twin.org/engine');
|
|
21
22
|
var engineCore = require('@twin.org/engine-core');
|
|
@@ -70,6 +71,35 @@ const NodeFeatures = {
|
|
|
70
71
|
NodeWallet: "node-wallet"
|
|
71
72
|
};
|
|
72
73
|
|
|
74
|
+
// Copyright 2024 IOTA Stiftung.
|
|
75
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
76
|
+
/**
|
|
77
|
+
* The protocol types for modules.
|
|
78
|
+
*/
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
80
|
+
const ModuleProtocol = {
|
|
81
|
+
/**
|
|
82
|
+
* Local module (starts with . or / or file://).
|
|
83
|
+
*/
|
|
84
|
+
Local: "local",
|
|
85
|
+
/**
|
|
86
|
+
* NPM package (starts with npm:).
|
|
87
|
+
*/
|
|
88
|
+
Npm: "npm",
|
|
89
|
+
/**
|
|
90
|
+
* HTTPS URL (starts with https://).
|
|
91
|
+
*/
|
|
92
|
+
Https: "https",
|
|
93
|
+
/**
|
|
94
|
+
* HTTP URL (starts with http://).
|
|
95
|
+
*/
|
|
96
|
+
Http: "http",
|
|
97
|
+
/**
|
|
98
|
+
* Default/standard module resolution.
|
|
99
|
+
*/
|
|
100
|
+
Default: "default"
|
|
101
|
+
};
|
|
102
|
+
|
|
73
103
|
// Copyright 2024 IOTA Stiftung.
|
|
74
104
|
// SPDX-License-Identifier: Apache-2.0.
|
|
75
105
|
/**
|
|
@@ -203,6 +233,280 @@ function getFeatures(env) {
|
|
|
203
233
|
}
|
|
204
234
|
return features;
|
|
205
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Parse the protocol from a module name.
|
|
238
|
+
* @param moduleName The module name to parse.
|
|
239
|
+
* @returns The parsed protocol information.
|
|
240
|
+
*/
|
|
241
|
+
function parseModuleProtocol(moduleName) {
|
|
242
|
+
const trimmed = moduleName.trim();
|
|
243
|
+
if (trimmed.startsWith("npm:")) {
|
|
244
|
+
return {
|
|
245
|
+
protocol: ModuleProtocol.Npm,
|
|
246
|
+
identifier: trimmed.slice(4),
|
|
247
|
+
original: trimmed
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (trimmed.startsWith("https://")) {
|
|
251
|
+
return {
|
|
252
|
+
protocol: ModuleProtocol.Https,
|
|
253
|
+
identifier: trimmed,
|
|
254
|
+
original: trimmed
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
if (trimmed.startsWith("http://")) {
|
|
258
|
+
return {
|
|
259
|
+
protocol: ModuleProtocol.Http,
|
|
260
|
+
identifier: trimmed,
|
|
261
|
+
original: trimmed
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (trimmed.startsWith("file://")) {
|
|
265
|
+
return {
|
|
266
|
+
protocol: ModuleProtocol.Local,
|
|
267
|
+
identifier: trimmed,
|
|
268
|
+
original: trimmed
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (modules.ModuleHelper.isLocalModule(trimmed)) {
|
|
272
|
+
return {
|
|
273
|
+
protocol: ModuleProtocol.Local,
|
|
274
|
+
identifier: trimmed,
|
|
275
|
+
original: trimmed
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
protocol: ModuleProtocol.Default,
|
|
280
|
+
identifier: trimmed,
|
|
281
|
+
original: trimmed
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Hash a URL to create a safe filename.
|
|
286
|
+
* @param url The URL to hash.
|
|
287
|
+
* @returns A hashed filename safe for the filesystem.
|
|
288
|
+
*/
|
|
289
|
+
function hashUrl(url) {
|
|
290
|
+
const urlBytes = core.Converter.utf8ToBytes(url);
|
|
291
|
+
const hashBytes = crypto.Sha256.sum256(urlBytes);
|
|
292
|
+
const hash = core.Converter.bytesToHex(hashBytes);
|
|
293
|
+
const ext = path.extname(new URL(url).pathname);
|
|
294
|
+
return `${hash}${ext}`;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get the extensions cache directory.
|
|
298
|
+
* @param executionDirectory The execution directory.
|
|
299
|
+
* @param protocol The protocol type for subdirectory organization.
|
|
300
|
+
* @param cacheDirectory The cache directory base path.
|
|
301
|
+
* @returns The cache directory path.
|
|
302
|
+
*/
|
|
303
|
+
function getExtensionsCacheDir(executionDirectory, protocol, cacheDirectory) {
|
|
304
|
+
// Resolve to absolute path to ensure consistent behavior
|
|
305
|
+
const absoluteDir = path.resolve(executionDirectory);
|
|
306
|
+
const baseDir = cacheDirectory ?? ".tmp";
|
|
307
|
+
return path.join(absoluteDir, baseDir, "extensions", protocol);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Handle the npm: protocol by installing the package if needed.
|
|
311
|
+
* @param packageName The npm package name (without npm: prefix).
|
|
312
|
+
* @param executionDirectory The execution directory.
|
|
313
|
+
* @param cacheDirectory The cache directory base path.
|
|
314
|
+
* @returns The resolved path to the installed module.
|
|
315
|
+
*/
|
|
316
|
+
async function handleNpmProtocol(packageName, executionDirectory, cacheDirectory) {
|
|
317
|
+
const cacheDir = getExtensionsCacheDir(executionDirectory, ModuleProtocol.Npm, cacheDirectory);
|
|
318
|
+
// Extract just the package name (without version) for the directory
|
|
319
|
+
// e.g. "picocolors@1.0.0" becomes "picocolors"
|
|
320
|
+
// e.g. "@scope/package@1.0.0" becomes "@scope/package"
|
|
321
|
+
const lastAtIndex = packageName.lastIndexOf("@");
|
|
322
|
+
const packageNameOnly = lastAtIndex > 0 ? packageName.slice(0, lastAtIndex) : packageName;
|
|
323
|
+
const packageDir = path.join(cacheDir, "node_modules", packageNameOnly);
|
|
324
|
+
const packageJsonPath = path.join(packageDir, "package.json");
|
|
325
|
+
const exists = await fileExists(packageJsonPath);
|
|
326
|
+
if (exists) {
|
|
327
|
+
const mainFile = await resolvePackageEntryPoint(packageDir, packageNameOnly);
|
|
328
|
+
const modulePath = path.join(packageDir, mainFile);
|
|
329
|
+
return {
|
|
330
|
+
resolvedPath: modulePath,
|
|
331
|
+
cached: true
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
await promises.mkdir(cacheDir, { recursive: true });
|
|
335
|
+
cliCore.CLIDisplay.task(core.I18n.formatMessage("node.extensionNpmInstalling"), packageName);
|
|
336
|
+
try {
|
|
337
|
+
// Always pipe stdio to comply with env access restrictions in tests/lint
|
|
338
|
+
const stdio = "pipe";
|
|
339
|
+
node_child_process.execSync(`npm install ${packageName} --prefix "${cacheDir}" --no-save --no-package-lock`, {
|
|
340
|
+
cwd: cacheDir,
|
|
341
|
+
stdio
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
throw new core.GeneralError("node", "extensionNpmInstallFailed", {
|
|
346
|
+
package: packageName
|
|
347
|
+
}, core.BaseError.fromError(err));
|
|
348
|
+
}
|
|
349
|
+
const mainFile = await resolvePackageEntryPoint(packageDir, packageNameOnly);
|
|
350
|
+
const modulePath = path.join(packageDir, mainFile);
|
|
351
|
+
return {
|
|
352
|
+
resolvedPath: modulePath,
|
|
353
|
+
cached: false
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Check if a cached file has expired based on TTL and force refresh settings.
|
|
358
|
+
* @param metadataPath Path to the cache metadata file.
|
|
359
|
+
* @param ttlHours Time to live in hours.
|
|
360
|
+
* @param forceRefresh Whether to force refresh regardless of TTL.
|
|
361
|
+
* @returns True if the cache is expired or should be refreshed.
|
|
362
|
+
*/
|
|
363
|
+
async function isCacheExpired(metadataPath, ttlHours, forceRefresh) {
|
|
364
|
+
if (forceRefresh) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
const metadata = await loadJsonFile(metadataPath);
|
|
369
|
+
const ttlMillis = ttlHours * 60 * 60 * 1000;
|
|
370
|
+
const expireTime = metadata.downloadedAt + ttlMillis;
|
|
371
|
+
return Date.now() > expireTime;
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// If metadata doesn't exist or is corrupted, consider expired
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Handle the https: protocol by downloading the module if needed.
|
|
380
|
+
* @param url The HTTPS URL to download from.
|
|
381
|
+
* @param executionDirectory The execution directory.
|
|
382
|
+
* @param maxSizeMb The maximum size in MB for the download.
|
|
383
|
+
* @param cacheDirectory The cache directory base path.
|
|
384
|
+
* @param ttlHours TTL in hours for cache expiration.
|
|
385
|
+
* @param forceRefresh Whether to force refresh the cache.
|
|
386
|
+
* @returns The resolved path to the downloaded module.
|
|
387
|
+
*/
|
|
388
|
+
async function handleHttpsProtocol(url, executionDirectory, maxSizeMb, cacheDirectory, ttlHours, forceRefresh) {
|
|
389
|
+
const effectiveTtlHours = ttlHours ?? 24;
|
|
390
|
+
const effectiveForceRefresh = forceRefresh ?? false;
|
|
391
|
+
const cacheDir = getExtensionsCacheDir(executionDirectory, ModuleProtocol.Https, cacheDirectory);
|
|
392
|
+
const filename = hashUrl(url);
|
|
393
|
+
const cachedPath = path.join(cacheDir, filename);
|
|
394
|
+
const metadataPath = `${cachedPath}.meta`;
|
|
395
|
+
const exists = await fileExists(cachedPath);
|
|
396
|
+
if (exists) {
|
|
397
|
+
const expired = await isCacheExpired(metadataPath, effectiveTtlHours, effectiveForceRefresh);
|
|
398
|
+
if (!expired) {
|
|
399
|
+
return {
|
|
400
|
+
resolvedPath: cachedPath,
|
|
401
|
+
cached: true
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (effectiveForceRefresh) {
|
|
405
|
+
cliCore.CLIDisplay.warning(core.I18n.formatMessage("node.extensionForceRefresh", { url }));
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
cliCore.CLIDisplay.task(core.I18n.formatMessage("node.extensionCacheExpired", { url }));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
cliCore.CLIDisplay.warning(core.I18n.formatMessage("node.extensionSecurityWarning", { url }));
|
|
412
|
+
cliCore.CLIDisplay.task(core.I18n.formatMessage("node.extensionHttpsDownloading"), url);
|
|
413
|
+
await promises.mkdir(cacheDir, { recursive: true });
|
|
414
|
+
const maxSizeBytes = maxSizeMb * 1024 * 1024;
|
|
415
|
+
let downloadedSize = 0;
|
|
416
|
+
const chunks = [];
|
|
417
|
+
try {
|
|
418
|
+
await new Promise((resolve, reject) => {
|
|
419
|
+
node_https.get(url, response => {
|
|
420
|
+
if (response.statusCode !== 200) {
|
|
421
|
+
reject(new core.GeneralError("node", "extensionDownloadFailed", {
|
|
422
|
+
url,
|
|
423
|
+
status: response.statusCode ?? 0
|
|
424
|
+
}));
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
response.on("data", (chunk) => {
|
|
428
|
+
downloadedSize += chunk.length;
|
|
429
|
+
if (downloadedSize > maxSizeBytes) {
|
|
430
|
+
response.destroy();
|
|
431
|
+
reject(new core.GeneralError("node", "extensionSizeLimitExceeded", {
|
|
432
|
+
size: downloadedSize,
|
|
433
|
+
limit: maxSizeBytes
|
|
434
|
+
}));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
chunks.push(chunk);
|
|
438
|
+
});
|
|
439
|
+
response.on("end", () => {
|
|
440
|
+
resolve();
|
|
441
|
+
});
|
|
442
|
+
response.on("error", err => {
|
|
443
|
+
reject(core.BaseError.fromError(err));
|
|
444
|
+
});
|
|
445
|
+
}).on("error", err => {
|
|
446
|
+
reject(core.BaseError.fromError(err));
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
catch (err) {
|
|
451
|
+
throw new core.GeneralError("node", "extensionDownloadFailed", {
|
|
452
|
+
url
|
|
453
|
+
}, core.BaseError.fromError(err));
|
|
454
|
+
}
|
|
455
|
+
const tempPath = `${cachedPath}.tmp`;
|
|
456
|
+
await promises.writeFile(tempPath, Buffer.concat(chunks));
|
|
457
|
+
// Atomic move from temp to final location
|
|
458
|
+
await promises.rename(tempPath, cachedPath);
|
|
459
|
+
// Save metadata for TTL tracking
|
|
460
|
+
const metadata = {
|
|
461
|
+
downloadedAt: Date.now(),
|
|
462
|
+
url,
|
|
463
|
+
size: Buffer.concat(chunks).length
|
|
464
|
+
};
|
|
465
|
+
await promises.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
|
|
466
|
+
return {
|
|
467
|
+
resolvedPath: cachedPath,
|
|
468
|
+
cached: false
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Resolve the main entry point from a package directory using Node.js resolution with fallback.
|
|
473
|
+
* Uses require.resolve() when possible for standard Node.js behavior, with manual fallback.
|
|
474
|
+
* @param packagePath The absolute path to the package directory.
|
|
475
|
+
* @param packageName The package name for require.resolve().
|
|
476
|
+
* @param fallback The fallback file name if no entry point is found.
|
|
477
|
+
* @returns The resolved entry point file name (relative to package directory).
|
|
478
|
+
*/
|
|
479
|
+
async function resolvePackageEntryPoint(packagePath, packageName, fallback = "index.js") {
|
|
480
|
+
try {
|
|
481
|
+
// Try require.resolve() first - handles exports, main, module automatically
|
|
482
|
+
const resolvedPath = require.resolve(packageName, { paths: [path.dirname(packagePath)] });
|
|
483
|
+
// Convert absolute path back to relative filename within package
|
|
484
|
+
const relativePath = path.relative(packagePath, resolvedPath);
|
|
485
|
+
return relativePath ?? fallback;
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// Fallback to manual package.json parsing if require.resolve fails
|
|
489
|
+
try {
|
|
490
|
+
const packageJsonPath = path.join(packagePath, "package.json");
|
|
491
|
+
const packageJsonContent = await loadJsonFile(packageJsonPath);
|
|
492
|
+
// Future: Could expand exports field support here
|
|
493
|
+
return packageJsonContent.module ?? packageJsonContent.main ?? fallback;
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return fallback;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Convert a file path to an import-compatible URL for cross-platform module loading.
|
|
502
|
+
* On Windows, adds the 'file://' protocol prefix required for dynamic imports.
|
|
503
|
+
* On other platforms, returns the path unchanged.
|
|
504
|
+
* @param filePath The absolute file path to convert.
|
|
505
|
+
* @returns A URL string compatible with dynamic import().
|
|
506
|
+
*/
|
|
507
|
+
function createModuleImportUrl(filePath) {
|
|
508
|
+
return process.platform === "win32" ? `file://${filePath}` : filePath;
|
|
509
|
+
}
|
|
206
510
|
|
|
207
511
|
// Copyright 2024 IOTA Stiftung.
|
|
208
512
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -1994,7 +2298,7 @@ async function run(nodeOptions) {
|
|
|
1994
2298
|
nodeOptions ??= {};
|
|
1995
2299
|
const serverInfo = {
|
|
1996
2300
|
name: nodeOptions?.serverName ?? "TWIN Node Server",
|
|
1997
|
-
version: nodeOptions?.serverVersion ?? "0.0.2-next.
|
|
2301
|
+
version: nodeOptions?.serverVersion ?? "0.0.2-next.26" // x-release-please-version
|
|
1998
2302
|
};
|
|
1999
2303
|
cliCore.CLIDisplay.header(serverInfo.name, serverInfo.version, "🌩️ ");
|
|
2000
2304
|
if (!core.Is.stringValue(nodeOptions?.executionDirectory)) {
|
|
@@ -2132,10 +2436,13 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
2132
2436
|
return { nodeEngineConfig, nodeEnvVars: envVars };
|
|
2133
2437
|
}
|
|
2134
2438
|
/**
|
|
2135
|
-
* Override module imports to
|
|
2439
|
+
* Override module imports to support protocol-based loading (npm:, https:) and local files.
|
|
2136
2440
|
* @param executionDirectory The execution directory for resolving local module paths.
|
|
2441
|
+
* @param envVars The environment variables containing extension configuration (optional, uses defaults if not provided).
|
|
2137
2442
|
*/
|
|
2138
|
-
function overrideModuleImport(executionDirectory) {
|
|
2443
|
+
function overrideModuleImport(executionDirectory, envVars) {
|
|
2444
|
+
const maxSizeMb = core.Coerce.number(envVars?.extensionsMaxSizeMb) ?? 10;
|
|
2445
|
+
const cacheDirectory = envVars?.extensionsCacheDirectory;
|
|
2139
2446
|
modules.ModuleHelper.overrideImport(async (moduleName) => {
|
|
2140
2447
|
if (moduleCache[moduleName]) {
|
|
2141
2448
|
return {
|
|
@@ -2143,55 +2450,77 @@ function overrideModuleImport(executionDirectory) {
|
|
|
2143
2450
|
useDefault: false
|
|
2144
2451
|
};
|
|
2145
2452
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
let exists = await fileExists(localFilename);
|
|
2154
|
-
if (!exists) {
|
|
2155
|
-
// Doesn't exist in the current directory, try the execution directory
|
|
2156
|
-
localFilename = path.resolve(executionDirectory, moduleName);
|
|
2157
|
-
exists = await fileExists(localFilename);
|
|
2158
|
-
}
|
|
2159
|
-
if (exists) {
|
|
2160
|
-
// If the module exists then we can load it, otherwise
|
|
2161
|
-
// we fallback to regular handling to see if that can import it
|
|
2162
|
-
const module = await import(process.platform === "win32" ? `file://${localFilename}` : localFilename);
|
|
2163
|
-
moduleCache[moduleName] = module;
|
|
2164
|
-
return {
|
|
2165
|
-
module,
|
|
2166
|
-
useDefault: false
|
|
2167
|
-
};
|
|
2453
|
+
const parsed = parseModuleProtocol(moduleName);
|
|
2454
|
+
let resolvedPath;
|
|
2455
|
+
switch (parsed.protocol) {
|
|
2456
|
+
case ModuleProtocol.Npm: {
|
|
2457
|
+
const result = await handleNpmProtocol(parsed.identifier, executionDirectory, cacheDirectory);
|
|
2458
|
+
resolvedPath = result.resolvedPath;
|
|
2459
|
+
break;
|
|
2168
2460
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2461
|
+
case ModuleProtocol.Https: {
|
|
2462
|
+
const result = await handleHttpsProtocol(parsed.identifier, executionDirectory, maxSizeMb, cacheDirectory, envVars?.extensionsCacheTtlHours, envVars?.extensionsForceRefresh);
|
|
2463
|
+
resolvedPath = result.resolvedPath;
|
|
2464
|
+
break;
|
|
2465
|
+
}
|
|
2466
|
+
case ModuleProtocol.Http: {
|
|
2467
|
+
throw new core.GeneralError("node", "insecureProtocol", { protocol: ModuleProtocol.Http });
|
|
2468
|
+
}
|
|
2469
|
+
case ModuleProtocol.Local: {
|
|
2470
|
+
let localFilename = path.resolve(moduleName);
|
|
2471
|
+
let exists = await fileExists(localFilename);
|
|
2472
|
+
if (!exists) {
|
|
2473
|
+
localFilename = path.resolve(executionDirectory, moduleName);
|
|
2474
|
+
exists = await fileExists(localFilename);
|
|
2475
|
+
}
|
|
2476
|
+
if (exists) {
|
|
2477
|
+
resolvedPath = localFilename;
|
|
2478
|
+
}
|
|
2479
|
+
break;
|
|
2480
|
+
}
|
|
2481
|
+
case ModuleProtocol.Default: {
|
|
2482
|
+
try {
|
|
2483
|
+
const npmRoot = node_child_process.execSync("npm root").toString().trim().replace(/\\/g, "/");
|
|
2484
|
+
const packagePath = path.resolve(npmRoot, moduleName);
|
|
2485
|
+
const mainFile = await resolvePackageEntryPoint(packagePath, moduleName);
|
|
2486
|
+
const modulePath = path.resolve(packagePath, mainFile);
|
|
2487
|
+
const exists = await fileExists(modulePath);
|
|
2488
|
+
if (exists) {
|
|
2489
|
+
resolvedPath = modulePath;
|
|
2490
|
+
break;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
catch {
|
|
2494
|
+
// Continue to fallback resolution
|
|
2495
|
+
}
|
|
2496
|
+
// Fallback: resolve from npm protocol cache directory (installed via handleNpmProtocol)
|
|
2497
|
+
try {
|
|
2498
|
+
const cacheNpmRoot = path.resolve(getExtensionsCacheDir(executionDirectory, ModuleProtocol.Npm, cacheDirectory), "node_modules");
|
|
2499
|
+
const packagePath = path.resolve(cacheNpmRoot, moduleName);
|
|
2500
|
+
const mainFile = await resolvePackageEntryPoint(packagePath, moduleName);
|
|
2501
|
+
const modulePath = path.resolve(packagePath, mainFile);
|
|
2502
|
+
const exists = await fileExists(modulePath);
|
|
2503
|
+
if (exists) {
|
|
2504
|
+
resolvedPath = modulePath;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
catch {
|
|
2508
|
+
// No cached resolution either; fall through
|
|
2509
|
+
}
|
|
2510
|
+
break;
|
|
2186
2511
|
}
|
|
2187
2512
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
2513
|
+
// Common module loading and caching logic
|
|
2514
|
+
if (resolvedPath) {
|
|
2515
|
+
const module = await import(createModuleImportUrl(resolvedPath));
|
|
2516
|
+
moduleCache[moduleName] = module;
|
|
2517
|
+
return {
|
|
2518
|
+
module,
|
|
2519
|
+
useDefault: false
|
|
2520
|
+
};
|
|
2190
2521
|
}
|
|
2191
|
-
// We don't appear to be able to manually resolve this module
|
|
2192
|
-
// So we let the default handling take care of it
|
|
2193
|
-
// This will allow built-in modules and regular node_modules to load as normal
|
|
2194
2522
|
return {
|
|
2523
|
+
module: undefined,
|
|
2195
2524
|
useDefault: true
|
|
2196
2525
|
};
|
|
2197
2526
|
});
|
|
@@ -2201,6 +2530,7 @@ exports.ATTESTATION_VERIFICATION_METHOD_ID = ATTESTATION_VERIFICATION_METHOD_ID;
|
|
|
2201
2530
|
exports.AUTH_SIGNING_KEY_ID = AUTH_SIGNING_KEY_ID;
|
|
2202
2531
|
exports.BLOB_STORAGE_ENCRYPTION_KEY_ID = BLOB_STORAGE_ENCRYPTION_KEY_ID;
|
|
2203
2532
|
exports.IMMUTABLE_PROOF_VERIFICATION_METHOD_ID = IMMUTABLE_PROOF_VERIFICATION_METHOD_ID;
|
|
2533
|
+
exports.ModuleProtocol = ModuleProtocol;
|
|
2204
2534
|
exports.NodeFeatures = NodeFeatures;
|
|
2205
2535
|
exports.SYNCHRONISED_STORAGE_BLOB_STORAGE_ENCRYPTION_KEY_ID = SYNCHRONISED_STORAGE_BLOB_STORAGE_ENCRYPTION_KEY_ID;
|
|
2206
2536
|
exports.VC_AUTHENTICATION_VERIFICATION_METHOD_ID = VC_AUTHENTICATION_VERIFICATION_METHOD_ID;
|
|
@@ -2214,19 +2544,27 @@ exports.bootstrapSynchronisedStorage = bootstrapSynchronisedStorage;
|
|
|
2214
2544
|
exports.buildConfiguration = buildConfiguration;
|
|
2215
2545
|
exports.buildEngineConfiguration = buildEngineConfiguration;
|
|
2216
2546
|
exports.buildEngineServerConfiguration = buildEngineServerConfiguration;
|
|
2547
|
+
exports.createModuleImportUrl = createModuleImportUrl;
|
|
2217
2548
|
exports.directoryExists = directoryExists;
|
|
2218
2549
|
exports.extensionsConfiguration = extensionsConfiguration;
|
|
2219
2550
|
exports.extensionsInitialiseEngine = extensionsInitialiseEngine;
|
|
2220
2551
|
exports.extensionsInitialiseEngineServer = extensionsInitialiseEngineServer;
|
|
2221
2552
|
exports.fileExists = fileExists;
|
|
2222
2553
|
exports.getExecutionDirectory = getExecutionDirectory;
|
|
2554
|
+
exports.getExtensionsCacheDir = getExtensionsCacheDir;
|
|
2223
2555
|
exports.getFeatures = getFeatures;
|
|
2224
2556
|
exports.getFiles = getFiles;
|
|
2225
2557
|
exports.getSubFolders = getSubFolders;
|
|
2558
|
+
exports.handleHttpsProtocol = handleHttpsProtocol;
|
|
2559
|
+
exports.handleNpmProtocol = handleNpmProtocol;
|
|
2560
|
+
exports.hashUrl = hashUrl;
|
|
2226
2561
|
exports.initialiseLocales = initialiseLocales;
|
|
2562
|
+
exports.isCacheExpired = isCacheExpired;
|
|
2227
2563
|
exports.loadJsonFile = loadJsonFile;
|
|
2228
2564
|
exports.loadTextFile = loadTextFile;
|
|
2229
2565
|
exports.overrideModuleImport = overrideModuleImport;
|
|
2566
|
+
exports.parseModuleProtocol = parseModuleProtocol;
|
|
2567
|
+
exports.resolvePackageEntryPoint = resolvePackageEntryPoint;
|
|
2230
2568
|
exports.run = run;
|
|
2231
2569
|
exports.shutdownExtensions = shutdownExtensions;
|
|
2232
2570
|
exports.start = start;
|