nx 23.1.1 → 23.1.3
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/release/changelog-renderer/index.js +4 -5
- package/dist/release/index.d.ts +1 -1
- package/dist/src/command-line/add/add.js +5 -0
- package/dist/src/command-line/affected/affected.js +1 -0
- package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +2 -2
- package/dist/src/command-line/import/import.js +1 -2
- package/dist/src/command-line/init/init-v2.js +4 -5
- package/dist/src/command-line/migrate/migrate.d.ts +4 -0
- package/dist/src/command-line/migrate/migrate.js +89 -26
- package/dist/src/command-line/release/changelog.js +5 -0
- package/dist/src/command-line/release/index.d.ts +1 -1
- package/dist/src/command-line/release/utils/git.js +1 -1
- package/dist/src/command-line/release/utils/release-graph.d.ts +17 -0
- package/dist/src/command-line/release/utils/release-graph.js +71 -19
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +6 -0
- package/dist/src/command-line/release/version/release-group-processor.d.ts +24 -0
- package/dist/src/command-line/release/version/release-group-processor.js +80 -6
- package/dist/src/command-line/release/version/version-actions.d.ts +27 -2
- package/dist/src/command-line/release/version/version-actions.js +9 -1
- package/dist/src/command-line/release/version.js +9 -5
- package/dist/src/command-line/reset/reset.js +6 -1
- package/dist/src/command-line/run/run-one.js +1 -0
- package/dist/src/command-line/run-many/run-many.js +1 -0
- package/dist/src/command-line/watch/watch.js +13 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/core/graph/styles.js +1 -1
- package/dist/src/daemon/client/client.d.ts +52 -1
- package/dist/src/daemon/client/client.js +160 -36
- package/dist/src/daemon/client/daemon-environment.d.ts +17 -0
- package/dist/src/daemon/client/daemon-environment.js +110 -2
- package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
- package/dist/src/daemon/message-types/daemon-message.d.ts +33 -0
- package/dist/src/daemon/message-types/daemon-message.js +48 -0
- package/dist/src/daemon/server/file-watching/file-watcher-sockets.d.ts +5 -0
- package/dist/src/daemon/server/file-watching/file-watcher-sockets.js +16 -0
- package/dist/src/daemon/server/latest-nx.js +5 -2
- package/dist/src/daemon/server/server.js +24 -0
- package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
- package/dist/src/daemon/server/shutdown-utils.js +11 -4
- package/dist/src/daemon/socket-utils.d.ts +1 -0
- package/dist/src/daemon/socket-utils.js +24 -3
- package/dist/src/daemon/tmp-dir.d.ts +43 -3
- package/dist/src/daemon/tmp-dir.js +437 -15
- package/dist/src/devkit-internals.d.ts +94 -4
- package/dist/src/devkit-internals.js +194 -5
- package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.d.ts +5 -0
- package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +9 -2
- package/dist/src/generators/tree.d.ts +4 -2
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +12 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +250 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +50 -0
- package/dist/src/native/index.d.ts +10 -0
- package/dist/src/native/index.js +71 -28
- package/dist/src/native/native-bindings.js +1 -0
- package/dist/src/native/native-file-cache-location.d.ts +17 -1
- package/dist/src/native/native-file-cache-location.js +86 -12
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/nx-cloud/utilities/get-cloud-options.js +5 -2
- package/dist/src/nx-cloud/utilities/onboarding.js +5 -2
- package/dist/src/plugins/js/lock-file/bun-parser.js +43 -10
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +60 -0
- package/dist/src/plugins/js/lock-file/project-graph-pruning.js +14 -8
- package/dist/src/plugins/js/project-graph/affected/npm-packages.js +51 -20
- package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +2 -0
- package/dist/src/project-graph/affected/affected-project-graph-models.d.ts +1 -1
- package/dist/src/project-graph/affected/affected-project-graph.d.ts +1 -1
- package/dist/src/project-graph/affected/affected-project-graph.js +2 -2
- package/dist/src/project-graph/affected/locators/project-glob-changes.js +5 -2
- package/dist/src/project-graph/error-types.d.ts +12 -1
- package/dist/src/project-graph/error-types.js +27 -2
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +18 -1
- package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
- package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
- package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
- package/dist/src/project-graph/project-graph.js +20 -0
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +5 -1
- package/dist/src/project-graph/utils/project-configuration/target-normalization.js +133 -3
- package/dist/src/tasks-runner/task-env.js +5 -2
- package/dist/src/tasks-runner/utils.js +2 -6
- package/dist/src/utils/assert-workspace-validity.js +7 -2
- package/dist/src/utils/fileutils.d.ts +5 -0
- package/dist/src/utils/fileutils.js +7 -2
- package/dist/src/utils/git-worktrees.d.ts +30 -0
- package/dist/src/utils/git-worktrees.js +212 -0
- package/dist/src/utils/ignore.d.ts +128 -1
- package/dist/src/utils/ignore.js +182 -12
- package/dist/src/utils/long-running-target.d.ts +10 -0
- package/dist/src/utils/long-running-target.js +19 -0
- package/dist/src/utils/min-release-age/behavior/bun.js +7 -11
- package/dist/src/utils/min-release-age/behavior/npm.js +5 -2
- package/dist/src/utils/min-release-age/packument.js +1 -1
- package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
- package/dist/src/utils/nx-tmp-dir.js +40 -0
- package/dist/src/utils/owned-private-dir.d.ts +132 -0
- package/dist/src/utils/owned-private-dir.js +323 -0
- package/dist/src/utils/package-manager-config/bunfig.d.ts +14 -0
- package/dist/src/utils/package-manager-config/bunfig.js +49 -0
- package/dist/src/utils/package-manager-config/npmrc.d.ts +23 -0
- package/dist/src/utils/package-manager-config/npmrc.js +124 -0
- package/dist/src/utils/package-manager-config/pnpm-config.d.ts +17 -0
- package/dist/src/utils/package-manager-config/pnpm-config.js +60 -0
- package/dist/src/utils/package-manager.d.ts +21 -2
- package/dist/src/utils/package-manager.js +244 -47
- package/dist/src/utils/provenance.js +6 -14
- package/dist/src/utils/registry-config/bun.d.ts +2 -0
- package/dist/src/utils/registry-config/bun.js +257 -0
- package/dist/src/utils/registry-config/index.d.ts +13 -0
- package/dist/src/utils/registry-config/index.js +102 -0
- package/dist/src/utils/registry-config/pnpm.d.ts +2 -0
- package/dist/src/utils/registry-config/pnpm.js +1551 -0
- package/dist/src/utils/registry-config/utils.d.ts +195 -0
- package/dist/src/utils/registry-config/utils.js +496 -0
- package/dist/src/utils/registry-config/yarn-berry.d.ts +2 -0
- package/dist/src/utils/registry-config/yarn-berry.js +612 -0
- package/dist/src/utils/registry-config/yarn-classic.d.ts +2 -0
- package/dist/src/utils/registry-config/yarn-classic.js +1001 -0
- package/dist/src/utils/safe-spawn.d.ts +24 -0
- package/dist/src/utils/safe-spawn.js +104 -0
- package/dist/src/utils/shell-quoting.d.ts +15 -0
- package/dist/src/utils/shell-quoting.js +35 -0
- package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
- package/dist/src/utils/wait-for-socket-connection.js +14 -8
- package/migrations.json +6 -0
- package/package.json +12 -12
- package/dist/src/utils/min-release-age/npmrc.d.ts +0 -15
- package/dist/src/utils/min-release-age/npmrc.js +0 -45
|
@@ -13,8 +13,10 @@ exports.copyPackageManagerConfigurationFiles = copyPackageManagerConfigurationFi
|
|
|
13
13
|
exports.createTempNpmDirectory = createTempNpmDirectory;
|
|
14
14
|
exports.resolvePackageVersionUsingRegistry = resolvePackageVersionUsingRegistry;
|
|
15
15
|
exports.resolvePackageVersionUsingInstallation = resolvePackageVersionUsingInstallation;
|
|
16
|
+
exports.getWorkspaceRegistryUrlForDisplay = getWorkspaceRegistryUrlForDisplay;
|
|
16
17
|
exports.packageRegistryView = packageRegistryView;
|
|
17
18
|
exports.packageRegistryPack = packageRegistryPack;
|
|
19
|
+
exports.clearPackageManagerVersionCache = clearPackageManagerVersionCache;
|
|
18
20
|
exports.getPackageWorkspaces = getPackageWorkspaces;
|
|
19
21
|
exports.addPackagePathToWorkspaces = addPackagePathToWorkspaces;
|
|
20
22
|
const child_process_1 = require("child_process");
|
|
@@ -30,9 +32,36 @@ const file_utils_1 = require("../project-graph/file-utils");
|
|
|
30
32
|
const catalog_1 = require("./catalog");
|
|
31
33
|
const fileutils_1 = require("./fileutils");
|
|
32
34
|
const installation_directory_1 = require("./installation-directory");
|
|
35
|
+
const logger_1 = require("./logger");
|
|
33
36
|
const package_json_1 = require("./package-json");
|
|
37
|
+
const shell_quoting_1 = require("./shell-quoting");
|
|
34
38
|
const workspace_root_1 = require("./workspace-root");
|
|
35
39
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
40
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
41
|
+
/**
|
|
42
|
+
* Shell-less spawn for the registry-bridged fetches: `exec` goes through
|
|
43
|
+
* /bin/sh, which is dash on Debian-family systems, and dash drops environment
|
|
44
|
+
* names that are not valid shell identifiers, i.e. every `//...:_authToken`
|
|
45
|
+
* credential and `@scope:registry` entry the overlay carries. Windows stays on
|
|
46
|
+
* `exec`, with every argument quoted, because Node refuses to execFile the
|
|
47
|
+
* package manager's `.cmd` shim without a shell.
|
|
48
|
+
*
|
|
49
|
+
* That quoting is the whole defence on Windows, so it goes through
|
|
50
|
+
* quoteShellArg, which throws on the one argument it cannot make safe.
|
|
51
|
+
*/
|
|
52
|
+
function execPackageManagerAsync(pm, args, options) {
|
|
53
|
+
if (process.platform === 'win32') {
|
|
54
|
+
return execAsync([pm, ...args].map(shell_quoting_1.quoteShellArg).join(' '), options);
|
|
55
|
+
}
|
|
56
|
+
return execFileAsync(pm, args, options);
|
|
57
|
+
}
|
|
58
|
+
/** Same split, and the same quoting, for a blocking caller. */
|
|
59
|
+
function execPackageManagerSync(pm, args, options) {
|
|
60
|
+
if (process.platform === 'win32') {
|
|
61
|
+
return (0, child_process_1.execSync)([pm, ...args].map(shell_quoting_1.quoteShellArg).join(' '), options);
|
|
62
|
+
}
|
|
63
|
+
return (0, child_process_1.execFileSync)(pm, args, options);
|
|
64
|
+
}
|
|
36
65
|
/**
|
|
37
66
|
* Detects which package manager is used in the workspace based on the lock file.
|
|
38
67
|
*/
|
|
@@ -408,6 +437,27 @@ function copyPackageManagerConfigurationFiles(root, destination) {
|
|
|
408
437
|
}
|
|
409
438
|
}
|
|
410
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* A non-JS workspace has no root package.json and keeps its package manager
|
|
442
|
+
* files under the Nx installation directory instead.
|
|
443
|
+
*/
|
|
444
|
+
function getPackageManagerConfigRoot() {
|
|
445
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(workspace_root_1.workspaceRoot, 'package.json'))) {
|
|
446
|
+
return workspace_root_1.workspaceRoot;
|
|
447
|
+
}
|
|
448
|
+
const installationPath = (0, installation_directory_1.getNxInstallationPath)(workspace_root_1.workspaceRoot);
|
|
449
|
+
// The installation directory can be missing or not a directory, and spawning
|
|
450
|
+
// with such a cwd fails outright (ENOENT/ENOTDIR).
|
|
451
|
+
try {
|
|
452
|
+
return (0, fs_1.statSync)(installationPath).isDirectory()
|
|
453
|
+
? installationPath
|
|
454
|
+
: workspace_root_1.workspaceRoot;
|
|
455
|
+
}
|
|
456
|
+
catch (e) {
|
|
457
|
+
logger_1.logger.verbose(`Failed to stat the Nx installation directory at "${installationPath}".`, e);
|
|
458
|
+
return workspace_root_1.workspaceRoot;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
411
461
|
/**
|
|
412
462
|
* Creates a temporary directory where you can run package manager commands safely.
|
|
413
463
|
*
|
|
@@ -424,8 +474,7 @@ function createTempNpmDirectory(skipCopy = false) {
|
|
|
424
474
|
// A package.json is needed for pnpm pack and for .npmrc to resolve
|
|
425
475
|
(0, fileutils_1.writeJsonFile)(`${dir}/package.json`, {});
|
|
426
476
|
if (!skipCopy) {
|
|
427
|
-
|
|
428
|
-
copyPackageManagerConfigurationFiles(isNonJs ? (0, installation_directory_1.getNxInstallationPath)(workspace_root_1.workspaceRoot) : workspace_root_1.workspaceRoot, dir);
|
|
477
|
+
copyPackageManagerConfigurationFiles(getPackageManagerConfigRoot(), dir);
|
|
429
478
|
}
|
|
430
479
|
const cleanup = async () => {
|
|
431
480
|
try {
|
|
@@ -444,7 +493,9 @@ function createTempNpmDirectory(skipCopy = false) {
|
|
|
444
493
|
async function resolvePackageVersionUsingRegistry(packageName, version) {
|
|
445
494
|
try {
|
|
446
495
|
const resolvedVersion = (0, catalog_1.resolveCatalogReferenceIfNeeded)(packageName, version);
|
|
447
|
-
const result = await packageRegistryView(packageName, resolvedVersion,
|
|
496
|
+
const result = await packageRegistryView(packageName, resolvedVersion, [
|
|
497
|
+
'version',
|
|
498
|
+
]);
|
|
448
499
|
if (!result) {
|
|
449
500
|
throw new Error(`Unable to resolve version ${packageName}@${resolvedVersion}.`);
|
|
450
501
|
}
|
|
@@ -466,10 +517,30 @@ async function resolvePackageVersionUsingRegistry(packageName, version) {
|
|
|
466
517
|
.replace(/'/g, '');
|
|
467
518
|
return finalResolvedVersion;
|
|
468
519
|
}
|
|
469
|
-
catch {
|
|
470
|
-
|
|
520
|
+
catch (e) {
|
|
521
|
+
// npm masks a URL credential only in the password position, so a bare token
|
|
522
|
+
// in the registry URL survives into the error kept as the cause.
|
|
523
|
+
throw new Error(`Unable to resolve version ${packageName}@${version}.`, {
|
|
524
|
+
cause: redactErrorCause(e),
|
|
525
|
+
});
|
|
471
526
|
}
|
|
472
527
|
}
|
|
528
|
+
// Masks the userinfo in a URL: `user`, `user:pass`, or a bare token.
|
|
529
|
+
function redactUrlCredentials(text) {
|
|
530
|
+
return text.replace(/([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, '$1***@');
|
|
531
|
+
}
|
|
532
|
+
function redactErrorCause(error) {
|
|
533
|
+
if (error && typeof error === 'object') {
|
|
534
|
+
const e = error;
|
|
535
|
+
// An exec error carries the command and both output streams as fields.
|
|
536
|
+
for (const field of ['message', 'stack', 'stderr', 'stdout', 'cmd']) {
|
|
537
|
+
if (typeof e[field] === 'string') {
|
|
538
|
+
e[field] = redactUrlCredentials(e[field]);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return error;
|
|
543
|
+
}
|
|
473
544
|
/**
|
|
474
545
|
* Return the resolved version for a given package and version tag using by
|
|
475
546
|
* installing it in a temporary directory and fetching the version from the
|
|
@@ -498,12 +569,86 @@ async function resolvePackageVersionUsingInstallation(packageName, version) {
|
|
|
498
569
|
await cleanup();
|
|
499
570
|
}
|
|
500
571
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
572
|
+
/**
|
|
573
|
+
* What every registry-bound npm spawn resolves the same way: where npm runs,
|
|
574
|
+
* and an environment reproducing the workspace package manager's own registry,
|
|
575
|
+
* auth and TLS resolution for `pkg`. `buildEnv` adds the caller's own entries.
|
|
576
|
+
*/
|
|
577
|
+
function createRegistrySpawnContext(pkg) {
|
|
578
|
+
// Deferred so the registry resolvers load only for the commands that spawn
|
|
579
|
+
// npm, not with every package-manager.ts import.
|
|
580
|
+
const { getNpmSpawnRegistryEnv, getPackageScope, ignoresNpmConfigEnv, mergeNpmConfigEnv, } = require('./registry-config');
|
|
581
|
+
const workspacePm = detectPackageManager();
|
|
582
|
+
const configRoot = getPackageManagerConfigRoot();
|
|
583
|
+
const workspacePmVersion = getPackageManagerVersionSafe(workspacePm, configRoot);
|
|
584
|
+
return {
|
|
585
|
+
workspacePm,
|
|
586
|
+
workspacePmVersion,
|
|
587
|
+
configRoot,
|
|
588
|
+
scope: getPackageScope(pkg),
|
|
589
|
+
buildEnv: (extra) => mergeNpmConfigEnv(process.env, {
|
|
590
|
+
...getNpmSpawnRegistryEnv(pkg, configRoot, workspacePm, workspacePmVersion),
|
|
591
|
+
...extra,
|
|
592
|
+
}, ignoresNpmConfigEnv(workspacePm, workspacePmVersion)),
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* The registry the fetch for `pkg` went to, with its userinfo masked because a
|
|
597
|
+
* registry URL can carry a bare token. The lookup is spawned the way
|
|
598
|
+
* `packageRegistryView` spawns the fetch this describes (same manager, same
|
|
599
|
+
* environment), so a registry the package manager keeps outside the .npmrc
|
|
600
|
+
* chain and a scope resolved for itself both land on the value that fetch used.
|
|
601
|
+
* Null where the manager yields no usable registry URL; throws where it cannot be run.
|
|
602
|
+
*/
|
|
603
|
+
function getWorkspaceRegistryUrlForDisplay(pkg) {
|
|
604
|
+
const { workspacePm, workspacePmVersion, configRoot, scope, buildEnv } = createRegistrySpawnContext(pkg);
|
|
605
|
+
const { pm, env, usesNativePnpm } = resolveRegistrySpawnTarget(workspacePm, workspacePmVersion, buildEnv);
|
|
606
|
+
// Ask for the package scope first. Native pnpm can keep the workspace
|
|
607
|
+
// `registries.default` separate from the flat `registry`, so it is queried
|
|
608
|
+
// in between.
|
|
609
|
+
const keys = scope ? [`${scope}:registry`] : [];
|
|
610
|
+
if (usesNativePnpm) {
|
|
611
|
+
keys.push('registries.default');
|
|
612
|
+
}
|
|
613
|
+
keys.push('registry');
|
|
614
|
+
for (const key of keys) {
|
|
615
|
+
const value = execPackageManagerSync(pm, ['config', 'get', key], {
|
|
616
|
+
cwd: configRoot,
|
|
617
|
+
timeout: 5000,
|
|
618
|
+
windowsHide: true,
|
|
619
|
+
encoding: 'utf-8',
|
|
620
|
+
// The downgraded pin warns on stderr, which is noise on a path that only
|
|
621
|
+
// decorates an error message.
|
|
622
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
623
|
+
env,
|
|
624
|
+
}).trim();
|
|
625
|
+
if (!value || value === 'undefined' || value === 'null') {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
// A present but unusable answer (a `registries:` map with a non-string
|
|
629
|
+
// default serializes as JSON; a non-HTTP(S) scheme) also aborted the fetch
|
|
630
|
+
// this describes, so a key below it was never contacted either.
|
|
631
|
+
if (!URL.canParse(value)) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
const protocol = new URL(value).protocol;
|
|
635
|
+
if (protocol !== 'http:' && protocol !== 'https:') {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
return redactUrlCredentials(value);
|
|
639
|
+
}
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Which manager answers a registry read for this workspace, and the environment
|
|
644
|
+
* it reads under. pnpm 11 reimplemented `view` natively, resolving registry and
|
|
645
|
+
* credentials (tokenHelper included) itself, so it runs on the untouched
|
|
646
|
+
* environment; pnpm 10 passed `view` through to the npm CLI, which needs the
|
|
647
|
+
* overlay. Shared so a lookup describing a fetch cannot resolve against a
|
|
648
|
+
* different environment than the fetch used.
|
|
649
|
+
*/
|
|
650
|
+
function resolveRegistrySpawnTarget(workspacePm, workspacePmVersion, buildEnv, options) {
|
|
651
|
+
let pm = workspacePm;
|
|
507
652
|
if (options?.forceNpm || pm === 'yarn' || pm === 'bun') {
|
|
508
653
|
/**
|
|
509
654
|
* yarn has `yarn info` but it behaves differently than (p)npm,
|
|
@@ -517,46 +662,98 @@ options) {
|
|
|
517
662
|
*/
|
|
518
663
|
pm = 'npm';
|
|
519
664
|
}
|
|
665
|
+
if (pm === 'pnpm' && ((0, semver_1.parse)(workspacePmVersion)?.major ?? 0) >= 11) {
|
|
666
|
+
return { pm, env: process.env, usesNativePnpm: true };
|
|
667
|
+
}
|
|
668
|
+
// npm_config_force downgrades npm's `devEngines.packageManager` enforcement,
|
|
669
|
+
// which otherwise aborts even a read-only lookup when the pin sets
|
|
670
|
+
// `onFail: error`. Only set for npm so a pnpm spawn is untouched.
|
|
671
|
+
return {
|
|
672
|
+
pm,
|
|
673
|
+
env: buildEnv(pm === 'npm' ? { npm_config_force: 'true' } : {}),
|
|
674
|
+
usesNativePnpm: false,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
async function packageRegistryView(pkg, version, args,
|
|
678
|
+
// `forceNpm` runs the view through npm even in a pnpm workspace: npm projects
|
|
679
|
+
// a field across every matched version, whereas `pnpm view <pkg>@<range>`
|
|
680
|
+
// collapses to the single highest match (breaks per-version field queries).
|
|
681
|
+
options) {
|
|
682
|
+
const { workspacePm, workspacePmVersion, configRoot, buildEnv } = createRegistrySpawnContext(pkg);
|
|
683
|
+
const { pm, env } = resolveRegistrySpawnTarget(workspacePm, workspacePmVersion, buildEnv, options);
|
|
520
684
|
// An empty version means we want the full packument; omit the trailing `@`.
|
|
521
|
-
// Quote the spec so range operators (e.g. `>=0.0.0`) are not parsed as shell
|
|
522
|
-
// redirections.
|
|
523
685
|
const spec = version ? `${pkg}@${version}` : pkg;
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
686
|
+
try {
|
|
687
|
+
const { stdout } = await execPackageManagerAsync(pm, ['view', spec, ...args], {
|
|
688
|
+
windowsHide: true,
|
|
689
|
+
cwd: configRoot,
|
|
690
|
+
env,
|
|
691
|
+
});
|
|
692
|
+
return stdout.toString().trim();
|
|
693
|
+
}
|
|
694
|
+
catch (e) {
|
|
695
|
+
throw redactErrorCause(e);
|
|
696
|
+
}
|
|
535
697
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
698
|
+
/**
|
|
699
|
+
* Only `npm pack` supports downloading a tarball of a specified remote
|
|
700
|
+
* package. `yarn` packs the active workspace, `pnpm pack` only packs
|
|
701
|
+
* the local project, and `bun` doesn't support pack.
|
|
702
|
+
*
|
|
703
|
+
* @param packDestination Directory passed to npm's `--pack-destination`, where
|
|
704
|
+
* the `.tgz` is written.
|
|
705
|
+
* @see https://github.com/nrwl/nx/pull/9667#discussion_r842553994
|
|
706
|
+
*/
|
|
707
|
+
async function packageRegistryPack(packDestination, pkg, version, options) {
|
|
544
708
|
const pm = 'npm';
|
|
545
|
-
const {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
709
|
+
const { configRoot, buildEnv } = createRegistrySpawnContext(pkg);
|
|
710
|
+
// Run from the config root, not the temp dir, so npm reads the workspace
|
|
711
|
+
// .npmrc natively; --pack-destination still writes the tarball to the temp
|
|
712
|
+
// dir. npm prints the tarball basename to stdout.
|
|
713
|
+
try {
|
|
714
|
+
const { stdout } = await execPackageManagerAsync(pm, ['pack', `${pkg}@${version}`, '--pack-destination', packDestination], {
|
|
715
|
+
cwd: configRoot,
|
|
716
|
+
windowsHide: true,
|
|
717
|
+
env: buildEnv({
|
|
718
|
+
// downgrade npm's devEngines.packageManager enforcement (onFail:
|
|
719
|
+
// error) to a warning so pack still runs in a non-npm workspace
|
|
720
|
+
npm_config_force: 'true',
|
|
721
|
+
...(options?.bypassMinReleaseAge
|
|
722
|
+
? { npm_config_min_release_age: '0' }
|
|
723
|
+
: {}),
|
|
724
|
+
}),
|
|
725
|
+
});
|
|
726
|
+
const tarballPath = stdout.trim();
|
|
727
|
+
return { tarballPath };
|
|
728
|
+
}
|
|
729
|
+
catch (e) {
|
|
730
|
+
throw redactErrorCause(e);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
// The version probe shells out when the packageManager field is absent, and
|
|
734
|
+
// packageRegistryView/packageRegistryPack run in tight resolution loops.
|
|
735
|
+
const packageManagerVersionCache = new Map();
|
|
736
|
+
/**
|
|
737
|
+
* A null version is tolerated per package manager: pnpm and yarn skip bridging,
|
|
738
|
+
* bun assumes a current version.
|
|
739
|
+
*/
|
|
740
|
+
function getPackageManagerVersionSafe(packageManager, root) {
|
|
741
|
+
const key = `${packageManager}:${root}`;
|
|
742
|
+
if (!packageManagerVersionCache.has(key)) {
|
|
743
|
+
let version = null;
|
|
744
|
+
try {
|
|
745
|
+
version = getPackageManagerVersion(packageManager, root);
|
|
746
|
+
}
|
|
747
|
+
catch (e) {
|
|
748
|
+
logger_1.logger.verbose(`Failed to determine the ${packageManager} version in "${root}".`, e);
|
|
749
|
+
}
|
|
750
|
+
packageManagerVersionCache.set(key, version);
|
|
751
|
+
}
|
|
752
|
+
return packageManagerVersionCache.get(key);
|
|
753
|
+
}
|
|
754
|
+
// Test-only: production never re-resolves a version mid-run.
|
|
755
|
+
function clearPackageManagerVersionCache() {
|
|
756
|
+
packageManagerVersionCache.clear();
|
|
560
757
|
}
|
|
561
758
|
/**
|
|
562
759
|
* Gets the workspaces defined in the package manager configuration.
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ProvenanceError = void 0;
|
|
4
4
|
exports.ensurePackageHasProvenance = ensurePackageHasProvenance;
|
|
5
5
|
exports.getNxPackageGroup = getNxPackageGroup;
|
|
6
|
-
const child_process_1 = require("child_process");
|
|
7
6
|
const module_1 = require("module");
|
|
8
7
|
const fileutils_1 = require("./fileutils");
|
|
9
8
|
const package_manager_1 = require("./package-manager");
|
|
@@ -21,7 +20,10 @@ async function ensurePackageHasProvenance(packageName, packageVersion) {
|
|
|
21
20
|
return;
|
|
22
21
|
}
|
|
23
22
|
try {
|
|
24
|
-
const result = await (0, package_manager_1.packageRegistryView)(packageName, packageVersion,
|
|
23
|
+
const result = await (0, package_manager_1.packageRegistryView)(packageName, packageVersion, [
|
|
24
|
+
'--json',
|
|
25
|
+
'--silent',
|
|
26
|
+
]);
|
|
25
27
|
const parsed = JSON.parse(result);
|
|
26
28
|
// `npm view <pkg>@<spec> --json` returns a bare object on npm <= 11 but an
|
|
27
29
|
// array on npm 12 and pnpm, even for a single resolved version. A version
|
|
@@ -77,19 +79,9 @@ class ProvenanceError extends Error {
|
|
|
77
79
|
constructor(packageName, packageVersion, error) {
|
|
78
80
|
let customRegistry = undefined;
|
|
79
81
|
try {
|
|
80
|
-
const
|
|
81
|
-
const commands = (0, package_manager_1.getPackageManagerCommand)(packageManager);
|
|
82
|
-
// Try to get registry from current package manager, fall back to npm
|
|
83
|
-
const registryCommand = commands.getRegistryUrl ?? 'npm config get registry';
|
|
84
|
-
const registry = (0, child_process_1.execSync)(registryCommand, {
|
|
85
|
-
timeout: 5000,
|
|
86
|
-
windowsHide: true,
|
|
87
|
-
encoding: 'utf-8',
|
|
88
|
-
}).trim();
|
|
82
|
+
const registry = (0, package_manager_1.getWorkspaceRegistryUrlForDisplay)(packageName);
|
|
89
83
|
// Only consider it custom if it's not the default npm registry
|
|
90
|
-
if (registry &&
|
|
91
|
-
registry !== 'undefined' &&
|
|
92
|
-
!registry.includes('registry.npmjs.org')) {
|
|
84
|
+
if (registry && !registry.includes('registry.npmjs.org')) {
|
|
93
85
|
customRegistry = registry;
|
|
94
86
|
}
|
|
95
87
|
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getBunSpawnRegistryEnv = getBunSpawnRegistryEnv;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const semver_1 = require("semver");
|
|
6
|
+
const bunfig_1 = require("../package-manager-config/bunfig");
|
|
7
|
+
const npmrc_1 = require("../package-manager-config/npmrc");
|
|
8
|
+
const utils_1 = require("./utils");
|
|
9
|
+
/*
|
|
10
|
+
* bun registry resolution (verified on 1.2.23 and 1.3.14, gates bisected):
|
|
11
|
+
*
|
|
12
|
+
* CLI --registry > BUN_CONFIG_REGISTRY > NPM_CONFIG_REGISTRY >
|
|
13
|
+
* npm_config_registry > project .npmrc > global ($XDG_CONFIG_HOME else
|
|
14
|
+
* $HOME)/.npmrc > project bunfig.toml > global bunfig > registry.npmjs.org.
|
|
15
|
+
* .npmrc beats bunfig at every level, including scoped keys. Env registry
|
|
16
|
+
* values must start with http(s):// or they are ignored. .npmrc support
|
|
17
|
+
* exists from bun 1.1.18; [install].ca/cafile from 1.1.31. When
|
|
18
|
+
* XDG_CONFIG_HOME is set, bun reads $XDG_CONFIG_HOME/.npmrc INSTEAD of
|
|
19
|
+
* ~/.npmrc, which npm always reads.
|
|
20
|
+
* See https://github.com/oven-sh/bun/blob/bun-v1.2.23/src/install/PackageManager.zig#L791
|
|
21
|
+
*
|
|
22
|
+
* The final default/scoped registry is always injected: bun does not read
|
|
23
|
+
* npm-only surfaces (e.g. $PREFIX/etc/npmrc), so npm must not fall back to
|
|
24
|
+
* them.
|
|
25
|
+
*/
|
|
26
|
+
const BUN_DEFAULT_REGISTRY = 'https://registry.npmjs.org/';
|
|
27
|
+
function getBunSpawnRegistryEnv(packageName, root, bunVersion) {
|
|
28
|
+
const env = {};
|
|
29
|
+
const scope = (0, utils_1.getPackageScope)(packageName);
|
|
30
|
+
const npmrcSupported = !bunVersion || (0, semver_1.gte)(bunVersion, '1.1.18');
|
|
31
|
+
const tlsSupported = !bunVersion || (0, semver_1.gte)(bunVersion, '1.1.31');
|
|
32
|
+
const globalConfigBase = (0, bunfig_1.getBunGlobalConfigBase)(process.env);
|
|
33
|
+
const projectNpmrc = npmrcSupported
|
|
34
|
+
? readBunNpmrcMap((0, path_1.join)(root, '.npmrc'))
|
|
35
|
+
: null;
|
|
36
|
+
const globalNpmrc = npmrcSupported && globalConfigBase
|
|
37
|
+
? readBunNpmrcMap((0, path_1.join)(globalConfigBase, '.npmrc'))
|
|
38
|
+
: null;
|
|
39
|
+
const projectBunfig = readBunfigInstall((0, path_1.join)(root, 'bunfig.toml'));
|
|
40
|
+
const globalBunfig = globalConfigBase
|
|
41
|
+
? readBunfigInstall((0, path_1.join)(globalConfigBase, '.bunfig.toml'))
|
|
42
|
+
: null;
|
|
43
|
+
// Swap npm's userconfig so the auth/TLS keys npm reads natively come from
|
|
44
|
+
// the same file bun would use.
|
|
45
|
+
if (npmrcSupported && process.env.XDG_CONFIG_HOME) {
|
|
46
|
+
env['npm_config_userconfig'] = (0, path_1.join)(process.env.XDG_CONFIG_HOME, '.npmrc');
|
|
47
|
+
}
|
|
48
|
+
const envRegistry = [
|
|
49
|
+
process.env.BUN_CONFIG_REGISTRY,
|
|
50
|
+
process.env.NPM_CONFIG_REGISTRY,
|
|
51
|
+
process.env.npm_config_registry,
|
|
52
|
+
].find((value) => value && /^https?:\/\//.test(value));
|
|
53
|
+
// An .npmrc registry goes through both of bun's expansions: its ini reader
|
|
54
|
+
// resolves `${VAR}` anywhere in the value, and the scope it then builds
|
|
55
|
+
// resolves a whole-value `$VAR`. Without the second, `registry=$MY_REGISTRY`
|
|
56
|
+
// stays a literal npm rejects as an invalid URL.
|
|
57
|
+
const npmrcRegistryValue = (key) => {
|
|
58
|
+
const raw = projectNpmrc?.get(key) ?? globalNpmrc?.get(key);
|
|
59
|
+
return raw === undefined
|
|
60
|
+
? undefined
|
|
61
|
+
: expandBunRegistryUrl((0, utils_1.expandEnvVars)(raw));
|
|
62
|
+
};
|
|
63
|
+
const bunfigValue = (read, fallbackUrl) => {
|
|
64
|
+
for (const install of [projectBunfig, globalBunfig]) {
|
|
65
|
+
if (!install) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const value = read(install);
|
|
69
|
+
if (value !== undefined) {
|
|
70
|
+
return normalizeBunRegistryValue(value, fallbackUrl);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
};
|
|
75
|
+
const npmrcRegistry = npmrcRegistryValue('registry');
|
|
76
|
+
const defaultPick = envRegistry
|
|
77
|
+
? { url: envRegistry }
|
|
78
|
+
: npmrcRegistry
|
|
79
|
+
? { url: npmrcRegistry }
|
|
80
|
+
: (bunfigValue((install) => install.registry, BUN_DEFAULT_REGISTRY) ?? {
|
|
81
|
+
url: BUN_DEFAULT_REGISTRY,
|
|
82
|
+
});
|
|
83
|
+
(0, utils_1.setRegistry)(env, defaultPick.url);
|
|
84
|
+
applyBunAuth(env, defaultPick);
|
|
85
|
+
if (scope) {
|
|
86
|
+
const npmrcScoped = npmrcRegistryValue(`${scope}:registry`);
|
|
87
|
+
// [install.scopes] keys are accepted with or without the leading @.
|
|
88
|
+
const scopedPick = npmrcScoped
|
|
89
|
+
? { url: npmrcScoped }
|
|
90
|
+
: (bunfigValue((install) => install.scopes?.[scope] ?? install.scopes?.[scope.slice(1)],
|
|
91
|
+
// A scope entry that declares only credentials keeps the default
|
|
92
|
+
// registry as its url, so its token still reaches the right host.
|
|
93
|
+
defaultPick.url) ?? defaultPick);
|
|
94
|
+
(0, utils_1.setScopedRegistry)(env, scope, scopedPick.url);
|
|
95
|
+
if (scopedPick !== defaultPick) {
|
|
96
|
+
applyBunAuth(env, scopedPick);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (tlsSupported) {
|
|
100
|
+
// .npmrc TLS keys beat bunfig's; npm reads the npmrc family natively.
|
|
101
|
+
const npmrcHasTls = projectNpmrc?.has('cafile') ||
|
|
102
|
+
projectNpmrc?.has('ca') ||
|
|
103
|
+
globalNpmrc?.has('cafile') ||
|
|
104
|
+
globalNpmrc?.has('ca');
|
|
105
|
+
if (!npmrcHasTls) {
|
|
106
|
+
const cafile = projectBunfig?.cafile ?? globalBunfig?.cafile;
|
|
107
|
+
if (cafile) {
|
|
108
|
+
// bun resolves cafile relative to the project dir.
|
|
109
|
+
(0, utils_1.setCafile)(env, (0, path_1.resolve)(root, cafile));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const ca = projectBunfig?.ca ?? globalBunfig?.ca;
|
|
113
|
+
if (ca) {
|
|
114
|
+
// npm reconstructs a config array from the env by splitting the
|
|
115
|
+
// value on a blank line.
|
|
116
|
+
env['npm_config_ca'] = Array.isArray(ca) ? ca.join('\n\n') : ca;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return env;
|
|
122
|
+
}
|
|
123
|
+
// bun silently resolves as though an .npmrc it cannot read were absent
|
|
124
|
+
// (verified on 1.3.13, EACCES and EISDIR both), so collapse that state the
|
|
125
|
+
// same way rather than warning or aborting.
|
|
126
|
+
function readBunNpmrcMap(path) {
|
|
127
|
+
const map = (0, npmrc_1.readNpmrcMap)(path);
|
|
128
|
+
return map === 'unreadable' ? null : map;
|
|
129
|
+
}
|
|
130
|
+
function normalizeBunRegistryValue(value, fallbackUrl) {
|
|
131
|
+
if (typeof value !== 'string') {
|
|
132
|
+
const result = {
|
|
133
|
+
url: value.url ? expandBunRegistryUrl(value.url) : fallbackUrl,
|
|
134
|
+
};
|
|
135
|
+
for (const field of ['token', 'username', 'password']) {
|
|
136
|
+
const expanded = expandBunAuthValue(value[field]);
|
|
137
|
+
if (expanded !== undefined) {
|
|
138
|
+
result[field] = expanded;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
const expanded = expandBunRegistryUrl(value);
|
|
144
|
+
try {
|
|
145
|
+
const url = new URL(expanded);
|
|
146
|
+
if (url.username || url.password) {
|
|
147
|
+
// bun treats a username with no password as no credentials at all.
|
|
148
|
+
const credentials = url.username && url.password
|
|
149
|
+
? { username: url.username, password: url.password }
|
|
150
|
+
: url.password
|
|
151
|
+
? // `https://:token@host/` carries a token as the bare password.
|
|
152
|
+
{ token: url.password }
|
|
153
|
+
: {};
|
|
154
|
+
url.username = '';
|
|
155
|
+
url.password = '';
|
|
156
|
+
return { url: url.toString(), ...credentials };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch { }
|
|
160
|
+
return { url: expanded };
|
|
161
|
+
}
|
|
162
|
+
// bun expands a whole-value `$VARNAME` (no braces) in bunfig credential fields
|
|
163
|
+
// via env.getAuto; an unset var keeps the literal, and `${VAR}` is expanded
|
|
164
|
+
// only in its .npmrc.
|
|
165
|
+
function expandBunAuthValue(value) {
|
|
166
|
+
if (value === undefined || value.length < 2 || value[0] !== '$') {
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
return process.env[value.slice(1)] ?? value;
|
|
170
|
+
}
|
|
171
|
+
// Bun expands a `$`-prefixed bunfig registry URL (Scope.fromAPI): it looks up
|
|
172
|
+
// the name with surrounding slashes trimmed and uses it only when the result is
|
|
173
|
+
// longer than one character, else keeps the literal.
|
|
174
|
+
function expandBunRegistryUrl(value) {
|
|
175
|
+
if (!value || value[0] !== '$') {
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
const name = value.slice(1).replace(/^\/+|\/+$/g, '');
|
|
179
|
+
const replaced = process.env[name];
|
|
180
|
+
return replaced && replaced.length > 1 ? replaced : value;
|
|
181
|
+
}
|
|
182
|
+
function applyBunAuth(env, value) {
|
|
183
|
+
if (value.token) {
|
|
184
|
+
(0, utils_1.setAuthToken)(env, value.url, value.token);
|
|
185
|
+
}
|
|
186
|
+
else if (value.username && value.password) {
|
|
187
|
+
const dart = (0, utils_1.requestNerfDart)(value.url);
|
|
188
|
+
if (dart) {
|
|
189
|
+
env[`npm_config_${dart}:username`] = value.username;
|
|
190
|
+
// npm expects _password base64-encoded.
|
|
191
|
+
env[`npm_config_${dart}:_password`] = Buffer.from(value.password).toString('base64');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function readBunfigInstall(path) {
|
|
196
|
+
const parsed = (0, bunfig_1.readBunfigRaw)(path);
|
|
197
|
+
if (parsed === null || parsed === 'unreadable') {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (parsed === 'invalid') {
|
|
201
|
+
// The throw reaches the caller's fall-open. Skipping the file instead would
|
|
202
|
+
// pin npm to the default registry as though the workspace configured none,
|
|
203
|
+
// overriding a registry npm resolves from a file of its own.
|
|
204
|
+
throw new Error(`The bunfig at ${path} could not be parsed.`);
|
|
205
|
+
}
|
|
206
|
+
const install = parsed.install;
|
|
207
|
+
if (!install || typeof install !== 'object' || Array.isArray(install)) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
validateBunfigInstall(install, path);
|
|
211
|
+
return install;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* bun type-checks the [install] table before it resolves anything and aborts on
|
|
215
|
+
* a value of the wrong shape. Reproduce that rather than carrying a number into
|
|
216
|
+
* a URL parse or a path join, where it would throw somewhere unrelated or
|
|
217
|
+
* bridge a value bun never accepted.
|
|
218
|
+
*/
|
|
219
|
+
function validateBunfigInstall(install, path) {
|
|
220
|
+
const fail = (what) => {
|
|
221
|
+
throw new Error(`The bunfig at ${path} declares ${what}.`);
|
|
222
|
+
};
|
|
223
|
+
if (install.registry !== undefined) {
|
|
224
|
+
validateBunRegistryValue(install.registry, 'install.registry', fail);
|
|
225
|
+
}
|
|
226
|
+
if (install.scopes !== undefined) {
|
|
227
|
+
if (typeof install.scopes !== 'object' || Array.isArray(install.scopes)) {
|
|
228
|
+
fail('an install.scopes that is not a table');
|
|
229
|
+
}
|
|
230
|
+
for (const [key, value] of Object.entries(install.scopes)) {
|
|
231
|
+
validateBunRegistryValue(value, `install.scopes["${key}"]`, fail);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (install.cafile !== undefined && typeof install.cafile !== 'string') {
|
|
235
|
+
fail('an install.cafile that is not a string');
|
|
236
|
+
}
|
|
237
|
+
if (install.ca !== undefined &&
|
|
238
|
+
typeof install.ca !== 'string' &&
|
|
239
|
+
!(Array.isArray(install.ca) &&
|
|
240
|
+
install.ca.every((entry) => typeof entry === 'string'))) {
|
|
241
|
+
fail('an install.ca that is neither a string nor an array of strings');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function validateBunRegistryValue(value, setting, fail) {
|
|
245
|
+
if (typeof value === 'string') {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
249
|
+
fail(`a ${setting} that is neither a URL string nor a table`);
|
|
250
|
+
}
|
|
251
|
+
for (const field of ['url', 'token', 'username', 'password']) {
|
|
252
|
+
const field_ = value[field];
|
|
253
|
+
if (field_ !== undefined && typeof field_ !== 'string') {
|
|
254
|
+
fail(`a ${setting}.${field} that is not a string`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|