@socketsecurity/cli 1.1.153 → 1.1.155
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/CHANGELOG.md +18 -0
- package/README.md +28 -23
- package/dist/cli.js +4135 -3974
- package/dist/cli.js.map +1 -1
- package/dist/constants.js +4 -4
- package/dist/constants.js.map +1 -1
- package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
- package/dist/shadow-npm-bin2.js +27 -2
- package/dist/shadow-npm-bin2.js.map +1 -1
- package/dist/tsconfig.dts.tsbuildinfo +1 -1
- package/dist/types/commands/ci/handle-ci.d.mts +7 -0
- package/dist/types/commands/ci/handle-ci.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-dynamic-sbom-inference.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-setup.d.mts.map +1 -1
- package/dist/types/commands/manifest/discover-manifest-roots.d.mts +2 -1
- package/dist/types/commands/manifest/discover-manifest-roots.d.mts.map +1 -1
- package/dist/types/commands/manifest/generate-recursive-manifests.d.mts +24 -6
- package/dist/types/commands/manifest/generate-recursive-manifests.d.mts.map +1 -1
- package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
- package/dist/types/commands/manifest/handle-manifest-dynamic-sbom-inference.d.mts.map +1 -1
- package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/sidecar.d.mts +47 -21
- package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -1
- package/dist/types/commands/manifest/setup-recursive-manifest-config.d.mts +8 -6
- package/dist/types/commands/manifest/setup-recursive-manifest-config.d.mts.map +1 -1
- package/dist/types/commands/scan/cmd-scan-reach.d.mts.map +1 -1
- package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/perform-reachability-analysis.d.mts.map +1 -1
- package/dist/types/commands/scan/reachability-flags.d.mts.map +1 -1
- package/dist/types/shadow/npm-base.d.mts +18 -0
- package/dist/types/shadow/npm-base.d.mts.map +1 -1
- package/dist/types/utils/fs.d.mts +4 -0
- package/dist/types/utils/fs.d.mts.map +1 -1
- package/dist/types/utils/git.d.mts +6 -0
- package/dist/types/utils/git.d.mts.map +1 -1
- package/dist/types/utils/github-errors.d.mts +2 -1
- package/dist/types/utils/github-errors.d.mts.map +1 -1
- package/dist/types/utils/glob.d.mts.map +1 -1
- package/dist/types/utils/npm-paths.d.mts +17 -0
- package/dist/types/utils/npm-paths.d.mts.map +1 -1
- package/dist/utils.js +348 -120
- package/dist/utils.js.map +1 -1
- package/package.json +18 -13
- package/logo-dark.png +0 -0
- package/logo-light.png +0 -0
package/dist/utils.js
CHANGED
|
@@ -15,6 +15,7 @@ var flags = require('./flags.js');
|
|
|
15
15
|
var path = require('node:path');
|
|
16
16
|
var regexps = require('../external/@socketsecurity/registry/lib/regexps');
|
|
17
17
|
var prompts = require('../external/@socketsecurity/registry/lib/prompts');
|
|
18
|
+
var env = require('../external/@socketsecurity/registry/lib/env');
|
|
18
19
|
var spawn = require('../external/@socketsecurity/registry/lib/spawn');
|
|
19
20
|
var fs = require('node:fs');
|
|
20
21
|
var fs$2 = require('node:fs/promises');
|
|
@@ -27,16 +28,17 @@ var url = require('../external/@socketsecurity/registry/lib/url');
|
|
|
27
28
|
var agent = require('../external/@socketsecurity/registry/lib/agent');
|
|
28
29
|
var bin = require('../external/@socketsecurity/registry/lib/bin');
|
|
29
30
|
var packages = require('../external/@socketsecurity/registry/lib/packages');
|
|
31
|
+
var process$1 = require('node:process');
|
|
32
|
+
var require$$7 = require('node:timers/promises');
|
|
33
|
+
var promises = require('../external/@socketsecurity/registry/lib/promises');
|
|
30
34
|
var require$$13 = require('node:https');
|
|
31
35
|
var web = require('node:stream/web');
|
|
32
36
|
var require$$0$1 = require('node:url');
|
|
33
37
|
var globs = require('../external/@socketsecurity/registry/lib/globs');
|
|
34
38
|
var streams = require('../external/@socketsecurity/registry/lib/streams');
|
|
35
|
-
var require$$7 = require('node:timers/promises');
|
|
36
39
|
var require$$10 = require('node:util');
|
|
37
40
|
var require$$12 = require('node:http');
|
|
38
41
|
var node_tls = require('node:tls');
|
|
39
|
-
var process$1 = require('node:process');
|
|
40
42
|
var require$$0 = require('node:crypto');
|
|
41
43
|
|
|
42
44
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -4003,6 +4005,7 @@ function extractOwner(owner) {
|
|
|
4003
4005
|
* Repository Information:
|
|
4004
4006
|
* - detectDefaultBranch: Find default branch (main/master/develop/etc)
|
|
4005
4007
|
* - getBaseBranch: Determine base branch (respects GitHub Actions env)
|
|
4008
|
+
* - getCiBranch: Branch name a GitHub Actions run is on
|
|
4006
4009
|
* - getRepoInfo: Extract owner/repo from git remote URL
|
|
4007
4010
|
* - gitBranch: Get current branch or commit hash
|
|
4008
4011
|
*/
|
|
@@ -4048,6 +4051,29 @@ async function getBaseBranch(cwd = process.cwd()) {
|
|
|
4048
4051
|
// https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-branches#about-the-default-branch
|
|
4049
4052
|
return 'main';
|
|
4050
4053
|
}
|
|
4054
|
+
|
|
4055
|
+
/**
|
|
4056
|
+
* The branch a GitHub Actions workflow run is on, or undefined when the
|
|
4057
|
+
* environment does not identify one. Read straight from process.env because
|
|
4058
|
+
* GITHUB_HEAD_REF is not part of the constants.ENV snapshot.
|
|
4059
|
+
*/
|
|
4060
|
+
function getCiBranch() {
|
|
4061
|
+
// The head branch of a pull request, only set for pull_request and
|
|
4062
|
+
// pull_request_target events. Checked first because GITHUB_REF_NAME is
|
|
4063
|
+
// '<pr_number>/merge' on those events, which is not a branch name.
|
|
4064
|
+
// https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables
|
|
4065
|
+
const githubHeadRef = env.envAsString(process.env['GITHUB_HEAD_REF']);
|
|
4066
|
+
if (githubHeadRef) {
|
|
4067
|
+
return githubHeadRef;
|
|
4068
|
+
}
|
|
4069
|
+
// The pushed ref. GITHUB_REF_TYPE tells branches and tags apart, and a tag
|
|
4070
|
+
// is not a branch name.
|
|
4071
|
+
const githubRefName = env.envAsString(process.env['GITHUB_REF_NAME']);
|
|
4072
|
+
if (env.envAsString(process.env['GITHUB_REF_TYPE']) === 'branch' && githubRefName) {
|
|
4073
|
+
return githubRefName;
|
|
4074
|
+
}
|
|
4075
|
+
return undefined;
|
|
4076
|
+
}
|
|
4051
4077
|
async function getRepoInfo(cwd = process.cwd()) {
|
|
4052
4078
|
let info;
|
|
4053
4079
|
try {
|
|
@@ -4094,6 +4120,14 @@ async function gitBranch(cwd = process.cwd()) {
|
|
|
4094
4120
|
error: e
|
|
4095
4121
|
});
|
|
4096
4122
|
}
|
|
4123
|
+
// Detached HEAD is the normal state in CI checkouts (actions/checkout),
|
|
4124
|
+
// where the commit-SHA fallback below would mislabel the scan's branch — a
|
|
4125
|
+
// SHA can never match the repo's default branch, so scans vanish from the
|
|
4126
|
+
// Main/PR tabs. Prefer the branch the CI run says it is on.
|
|
4127
|
+
const ciBranch = getCiBranch();
|
|
4128
|
+
if (ciBranch) {
|
|
4129
|
+
return ciBranch;
|
|
4130
|
+
}
|
|
4097
4131
|
// Fallback to using rev-parse to get the short commit hash in a
|
|
4098
4132
|
// detached HEAD state.
|
|
4099
4133
|
try {
|
|
@@ -4579,6 +4613,67 @@ function getSupportedFilePatterns(supportedFiles) {
|
|
|
4579
4613
|
}
|
|
4580
4614
|
return patterns;
|
|
4581
4615
|
}
|
|
4616
|
+
// Whether `targetPath` is ignored by the gitignore matchers on its ancestor
|
|
4617
|
+
// directories, root to leaf, last match wins. Each matcher tests the path
|
|
4618
|
+
// relative to its own directory (how git stores patterns); `isDir` appends a
|
|
4619
|
+
// trailing slash so a directory-only rule like `build/` matches the directory.
|
|
4620
|
+
// `targetPath` is POSIX, relative to cwd.
|
|
4621
|
+
function pathIgnoredByChain(targetPath, isDir, matchersByDir) {
|
|
4622
|
+
let ignored = false;
|
|
4623
|
+
// Ancestor directories of targetPath: '', then each parent dir prefix.
|
|
4624
|
+
const segments = targetPath.split('/');
|
|
4625
|
+
segments.pop();
|
|
4626
|
+
const dirs = [''];
|
|
4627
|
+
let prefix = '';
|
|
4628
|
+
for (let i = 0, {
|
|
4629
|
+
length
|
|
4630
|
+
} = segments; i < length; i += 1) {
|
|
4631
|
+
prefix = prefix ? `${prefix}/${segments[i]}` : segments[i];
|
|
4632
|
+
dirs.push(prefix);
|
|
4633
|
+
}
|
|
4634
|
+
for (let i = 0, {
|
|
4635
|
+
length
|
|
4636
|
+
} = dirs; i < length; i += 1) {
|
|
4637
|
+
const dir = dirs[i];
|
|
4638
|
+
const matchers = matchersByDir.get(dir);
|
|
4639
|
+
if (!matchers) {
|
|
4640
|
+
continue;
|
|
4641
|
+
}
|
|
4642
|
+
const relToDir = dir === '' ? targetPath : targetPath.slice(dir.length + 1);
|
|
4643
|
+
const probe = isDir ? `${relToDir}/` : relToDir;
|
|
4644
|
+
for (let j = 0, len = matchers.length; j < len; j += 1) {
|
|
4645
|
+
const result = matchers[j].test(probe);
|
|
4646
|
+
if (result.ignored) {
|
|
4647
|
+
ignored = true;
|
|
4648
|
+
} else if (result.unignored) {
|
|
4649
|
+
ignored = false;
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
return ignored;
|
|
4654
|
+
}
|
|
4655
|
+
|
|
4656
|
+
// Whether `relPath` is ignored, honoring git's rule that an excluded directory
|
|
4657
|
+
// is never descended into: a file is ignored if any ancestor directory is, and a
|
|
4658
|
+
// deeper `!` cannot re-include a file under an excluded parent. Walks ancestors
|
|
4659
|
+
// top-down, short-circuiting on the first excluded one. POSIX, relative to cwd.
|
|
4660
|
+
function isIgnoredAlongChain(relPath, matchersByDir) {
|
|
4661
|
+
// Outside cwd (a `..` prefix) or empty: outside every gitignore's domain, and
|
|
4662
|
+
// the `ignore` package throws on such input, so report not-ignored.
|
|
4663
|
+
if (!relPath || relPath === '..' || relPath.startsWith('../')) {
|
|
4664
|
+
return false;
|
|
4665
|
+
}
|
|
4666
|
+
const segments = relPath.split('/');
|
|
4667
|
+
const last = segments.length - 1;
|
|
4668
|
+
let prefix = '';
|
|
4669
|
+
for (let i = 0; i <= last; i += 1) {
|
|
4670
|
+
prefix = prefix ? `${prefix}/${segments[i]}` : segments[i];
|
|
4671
|
+
if (pathIgnoredByChain(prefix, i < last, matchersByDir)) {
|
|
4672
|
+
return true;
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
return false;
|
|
4676
|
+
}
|
|
4582
4677
|
async function globWithGitIgnore(patterns, options) {
|
|
4583
4678
|
const {
|
|
4584
4679
|
additionalIgnores,
|
|
@@ -4590,33 +4685,36 @@ async function globWithGitIgnore(patterns, options) {
|
|
|
4590
4685
|
__proto__: null,
|
|
4591
4686
|
...options
|
|
4592
4687
|
};
|
|
4688
|
+
|
|
4689
|
+
// Anchored minimatch patterns for fast-glob: built-in ignored dirs, venv
|
|
4690
|
+
// markers, projectIgnorePaths, and every discovered `.gitignore`, translated
|
|
4691
|
+
// and anchored from cwd. When no pattern is negated, fast-glob does the whole
|
|
4692
|
+
// gitignore match from this set.
|
|
4593
4693
|
const ignores = new Set(IGNORED_DIR_PATTERNS);
|
|
4594
4694
|
|
|
4595
|
-
// CLI-supplied `additionalIgnores` are already anchored minimatch
|
|
4596
|
-
//
|
|
4597
|
-
// anywhere" semantics would re-interpret a bare `tests`
|
|
4598
|
-
// `subdir/tests/foo.json`). Keep them in fast-glob's ignore list across
|
|
4599
|
-
// both paths; only gitignore-translated entries go into the `ig` matcher.
|
|
4695
|
+
// CLI-supplied `additionalIgnores` are already anchored minimatch from cwd, so
|
|
4696
|
+
// they go straight to fast-glob and never enter the gitignore matchers (whose
|
|
4697
|
+
// "match anywhere" semantics would re-interpret a bare `tests` as `**/tests`).
|
|
4600
4698
|
const cliMinimatchIgnores = additionalIgnores ?? [];
|
|
4601
4699
|
const projectIgnorePaths = socketConfig?.projectIgnorePaths;
|
|
4602
|
-
const
|
|
4700
|
+
const projectIgnoreLines = Array.isArray(projectIgnorePaths) ? projectIgnorePaths : [];
|
|
4701
|
+
const projectIgnoreGlobs = projectIgnoreLines.length ? ignoreFileLinesToGlobPatterns(projectIgnoreLines, path.join(cwd, '.gitignore'), cwd) : [];
|
|
4603
4702
|
for (const pattern of projectIgnoreGlobs) {
|
|
4604
4703
|
ignores.add(pattern);
|
|
4605
4704
|
}
|
|
4606
4705
|
|
|
4607
|
-
//
|
|
4608
|
-
//
|
|
4609
|
-
|
|
4610
|
-
//
|
|
4611
|
-
//
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
//
|
|
4615
|
-
//
|
|
4616
|
-
//
|
|
4617
|
-
//
|
|
4618
|
-
// ignore entries inconsistently.
|
|
4619
|
-
// walk avoids a second full-tree traversal.
|
|
4706
|
+
// Raw per-directory `.gitignore` contents from discovery. Matchers are built
|
|
4707
|
+
// from these only when a pattern is negated (see below).
|
|
4708
|
+
const gitignoreFiles = [];
|
|
4709
|
+
// Directory excludes from discovered pyvenv.cfg markers, so virtualenvs with
|
|
4710
|
+
// non-conventional names are pruned. Fed to fast-glob's ignore on every path.
|
|
4711
|
+
const venvGlobs = [];
|
|
4712
|
+
|
|
4713
|
+
// Discover `.gitignore` files and `pyvenv.cfg` venv markers in one walk,
|
|
4714
|
+
// honoring the same dir exclusions as the package walk so an unreadable subtree
|
|
4715
|
+
// (EACCES, foreign-uid pgdata, Docker mount) can't abort it. `suppressErrors`
|
|
4716
|
+
// skips dirs the user cannot read; negated patterns are dropped here because
|
|
4717
|
+
// fast-glob treats `!` ignore entries inconsistently.
|
|
4620
4718
|
const discoveryStream = vendor.outExports.globStream(['**/.gitignore', `**/${PYVENV_CFG}`], {
|
|
4621
4719
|
absolute: true,
|
|
4622
4720
|
cwd,
|
|
@@ -4624,21 +4722,40 @@ async function globWithGitIgnore(patterns, options) {
|
|
|
4624
4722
|
ignore: [...DEFAULT_IGNORE_FOR_GIT_IGNORE, ...projectIgnoreGlobs, ...cliMinimatchIgnores].filter(p => p.charCodeAt(0) !== 33 /*'!'*/).map(stripTrailingSlash),
|
|
4625
4723
|
suppressErrors: true
|
|
4626
4724
|
});
|
|
4627
|
-
for await (const
|
|
4725
|
+
for await (const found of streams.transform(discoveryStream, async filepath => {
|
|
4726
|
+
const dirRel = path$1.normalizePath(path.relative(cwd, path.dirname(filepath)));
|
|
4628
4727
|
if (path.basename(filepath) === PYVENV_CFG) {
|
|
4629
|
-
// A pyvenv.cfg sits at the venv root, so exclude the whole directory.
|
|
4630
|
-
|
|
4631
|
-
//
|
|
4632
|
-
|
|
4633
|
-
|
|
4728
|
+
// A pyvenv.cfg sits at the venv root, so exclude the whole directory. An
|
|
4729
|
+
// empty dirRel means the scan target itself is a venv root; don't emit
|
|
4730
|
+
// `/**`, which would exclude everything the user explicitly targeted.
|
|
4731
|
+
return {
|
|
4732
|
+
content: '',
|
|
4733
|
+
dir: dirRel,
|
|
4734
|
+
patterns: dirRel ? [`${dirRel}/**`] : [],
|
|
4735
|
+
venv: true
|
|
4736
|
+
};
|
|
4634
4737
|
}
|
|
4635
|
-
|
|
4738
|
+
const content = (await fs$1.safeReadFile(filepath)) ?? '';
|
|
4739
|
+
return {
|
|
4740
|
+
content,
|
|
4741
|
+
dir: dirRel,
|
|
4742
|
+
patterns: ignoreFileToGlobPatterns(content, filepath, cwd),
|
|
4743
|
+
venv: false
|
|
4744
|
+
};
|
|
4636
4745
|
}, {
|
|
4637
4746
|
concurrency: 8
|
|
4638
4747
|
})) {
|
|
4639
|
-
for (const p of
|
|
4748
|
+
for (const p of found.patterns) {
|
|
4640
4749
|
ignores.add(p);
|
|
4641
4750
|
}
|
|
4751
|
+
if (found.venv) {
|
|
4752
|
+
venvGlobs.push(...found.patterns);
|
|
4753
|
+
} else if (found.content) {
|
|
4754
|
+
gitignoreFiles.push({
|
|
4755
|
+
content: found.content,
|
|
4756
|
+
dir: found.dir
|
|
4757
|
+
});
|
|
4758
|
+
}
|
|
4642
4759
|
}
|
|
4643
4760
|
let hasNegatedPattern = false;
|
|
4644
4761
|
for (const p of ignores) {
|
|
@@ -4652,41 +4769,72 @@ async function globWithGitIgnore(patterns, options) {
|
|
|
4652
4769
|
absolute: true,
|
|
4653
4770
|
cwd,
|
|
4654
4771
|
dot: true,
|
|
4655
|
-
|
|
4772
|
+
// With a negation, the per-dir matcher chain (below) covers only gitignore
|
|
4773
|
+
// and projectIgnore patterns, so fast-glob still prunes the built-in ignored
|
|
4774
|
+
// dirs and discovered venvs. Without one, the full anchored set goes to
|
|
4775
|
+
// fast-glob, which does the whole match.
|
|
4776
|
+
ignore: hasNegatedPattern ? [...globs.defaultIgnore, ...IGNORED_DIR_PATTERNS, ...venvGlobs, ...cliMinimatchIgnores] : [...ignores, ...cliMinimatchIgnores].map(stripTrailingSlash),
|
|
4656
4777
|
...additionalOptions,
|
|
4657
|
-
// Skip
|
|
4658
|
-
//
|
|
4659
|
-
//
|
|
4660
|
-
// caller's options bag cannot accidentally flip it back to `false` and
|
|
4661
|
-
// re-introduce the crash — `suppressErrors` is a safety invariant here, not
|
|
4662
|
-
// a tunable.
|
|
4778
|
+
// Skip dirs the running user cannot read instead of aborting on the first
|
|
4779
|
+
// `EACCES`. Pinned after `...additionalOptions` so a caller's options bag
|
|
4780
|
+
// cannot flip it off; it is a safety invariant, not a tunable.
|
|
4663
4781
|
suppressErrors: true
|
|
4664
4782
|
};
|
|
4665
4783
|
|
|
4666
|
-
//
|
|
4784
|
+
// No negation and no filter: fast-glob's anchored ignore set is authoritative.
|
|
4667
4785
|
if (!hasNegatedPattern && !filter) {
|
|
4668
4786
|
return await vendor.outExports.glob(patterns, globOptions);
|
|
4669
4787
|
}
|
|
4670
|
-
|
|
4671
|
-
//
|
|
4672
|
-
//
|
|
4673
|
-
//
|
|
4788
|
+
|
|
4789
|
+
// When a pattern is negated, match each candidate against the gitignore
|
|
4790
|
+
// ancestor chain. One matcher per `.gitignore` is built from its raw lines and
|
|
4791
|
+
// deduped by content via `igByContent`, keeping compiled-regex memory bounded
|
|
4792
|
+
// by the number of DISTINCT `.gitignore` contents, not by file count (a single
|
|
4793
|
+
// matcher over every anchored pattern can exhaust V8 code space on big repos).
|
|
4794
|
+
let matchersByDir;
|
|
4795
|
+
if (hasNegatedPattern) {
|
|
4796
|
+
const byDir = new Map();
|
|
4797
|
+
const igByContent = new Map();
|
|
4798
|
+
const addMatcher = (dirRel, content) => {
|
|
4799
|
+
let ig = igByContent.get(content);
|
|
4800
|
+
if (!ig) {
|
|
4801
|
+
ig = vendor.ignoreExports().add(content.split(/\r?\n/));
|
|
4802
|
+
igByContent.set(content, ig);
|
|
4803
|
+
}
|
|
4804
|
+
const existing = byDir.get(dirRel);
|
|
4805
|
+
if (existing) {
|
|
4806
|
+
existing.push(ig);
|
|
4807
|
+
} else {
|
|
4808
|
+
byDir.set(dirRel, [ig]);
|
|
4809
|
+
}
|
|
4810
|
+
};
|
|
4811
|
+
// projectIgnorePaths act as a root-level gitignore.
|
|
4812
|
+
if (projectIgnoreLines.length) {
|
|
4813
|
+
addMatcher('', projectIgnoreLines.join('\n'));
|
|
4814
|
+
}
|
|
4815
|
+
for (let i = 0, {
|
|
4816
|
+
length
|
|
4817
|
+
} = gitignoreFiles; i < length; i += 1) {
|
|
4818
|
+
addMatcher(gitignoreFiles[i].dir, gitignoreFiles[i].content);
|
|
4819
|
+
}
|
|
4820
|
+
matchersByDir = byDir;
|
|
4821
|
+
}
|
|
4822
|
+
|
|
4823
|
+
// Stream so memory stays bounded on large monorepos with 100k+ files: the
|
|
4824
|
+
// optional caller filter drops non-matches before they accumulate. On the slow
|
|
4825
|
+
// path each surviving entry is also re-checked against its gitignore ancestor
|
|
4826
|
+
// chain, which carries the full negation support fast-glob lacks.
|
|
4674
4827
|
const results = [];
|
|
4675
|
-
const ig = hasNegatedPattern ? vendor.ignoreExports().add([...ignores]) : null;
|
|
4676
4828
|
const stream = vendor.outExports.globStream(patterns, globOptions);
|
|
4677
4829
|
for await (const p of stream) {
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
//
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
if (ig.ignores(relPath)) {
|
|
4830
|
+
if (matchersByDir) {
|
|
4831
|
+
// Patterns are forward-slash anchored and tested relative to each
|
|
4832
|
+
// gitignore's directory; normalize so a Windows backslash path matches.
|
|
4833
|
+
const relPath = path$1.normalizePath(globOptions.absolute ? path.relative(cwd, p) : p);
|
|
4834
|
+
if (isIgnoredAlongChain(relPath, matchersByDir)) {
|
|
4684
4835
|
continue;
|
|
4685
4836
|
}
|
|
4686
4837
|
}
|
|
4687
|
-
// Apply the optional filter to reduce memory usage.
|
|
4688
|
-
// When scanning large monorepos, this filters early (e.g., to manifest files only)
|
|
4689
|
-
// instead of accumulating all 100k+ files and filtering later.
|
|
4690
4838
|
if (filter && !filter(p)) {
|
|
4691
4839
|
continue;
|
|
4692
4840
|
}
|
|
@@ -5087,6 +5235,17 @@ async function withTmpDir(prefix, fn) {
|
|
|
5087
5235
|
}
|
|
5088
5236
|
}
|
|
5089
5237
|
|
|
5238
|
+
// Symlink-resolved absolute path when the target exists (e.g. macOS symlinks
|
|
5239
|
+
// /tmp -> /private/tmp); falls back to a plain resolve so a not-yet-existing
|
|
5240
|
+
// path still gets a usable absolute value instead of throwing.
|
|
5241
|
+
async function realpathOrResolved(target) {
|
|
5242
|
+
try {
|
|
5243
|
+
return await fs.promises.realpath(target);
|
|
5244
|
+
} catch {
|
|
5245
|
+
return path.resolve(target);
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
|
|
5090
5249
|
function findBinPathDetailsSync(binName) {
|
|
5091
5250
|
const rawBinPaths = bin.whichBinSync(binName, {
|
|
5092
5251
|
all: true,
|
|
@@ -6725,6 +6884,35 @@ function findBinNextToNode(binName) {
|
|
|
6725
6884
|
}
|
|
6726
6885
|
return undefined;
|
|
6727
6886
|
}
|
|
6887
|
+
const JS_ENTRY_EXTENSIONS = new Set(['.cjs', '.js', '.mjs']);
|
|
6888
|
+
|
|
6889
|
+
/**
|
|
6890
|
+
* Resolve a package manager bin path to a JavaScript entry `node` can run.
|
|
6891
|
+
*
|
|
6892
|
+
* Some version managers ship `npm` as a shell script instead of the usual shim
|
|
6893
|
+
* that points at npm's JavaScript entry. mise writes a bash wrapper so it can
|
|
6894
|
+
* run `mise reshim` after a global install. resolveBinPathSync only recognizes
|
|
6895
|
+
* npm's own shim, by the `NPM_CLI_JS=` assignment inside it, and passes anything
|
|
6896
|
+
* else through untouched, so the wrapper reaches us unchanged. We then hand it
|
|
6897
|
+
* to `node` as the main module and the shell syntax throws a SyntaxError before
|
|
6898
|
+
* npm ever starts (issue #946).
|
|
6899
|
+
*
|
|
6900
|
+
* When the resolved path is not JavaScript, fall back to the CLI entry inside
|
|
6901
|
+
* npm's install directory, which findNpmDirPathSync already knows how to locate
|
|
6902
|
+
* for this layout. A path we cannot map keeps its current value, so an
|
|
6903
|
+
* unrecognized layout behaves exactly as it does today.
|
|
6904
|
+
*/
|
|
6905
|
+
function resolveBinPathToJsEntry(binPath, cliEntryName) {
|
|
6906
|
+
if (JS_ENTRY_EXTENSIONS.has(path.extname(binPath).toLowerCase())) {
|
|
6907
|
+
return binPath;
|
|
6908
|
+
}
|
|
6909
|
+
const npmDirPath = findNpmDirPathSync(binPath);
|
|
6910
|
+
if (!npmDirPath) {
|
|
6911
|
+
return binPath;
|
|
6912
|
+
}
|
|
6913
|
+
const jsEntryPath = path.join(npmDirPath, 'bin', cliEntryName);
|
|
6914
|
+
return fs.existsSync(jsEntryPath) ? jsEntryPath : binPath;
|
|
6915
|
+
}
|
|
6728
6916
|
let _npmBinPath;
|
|
6729
6917
|
function getNpmBinPath() {
|
|
6730
6918
|
if (_npmBinPath === undefined) {
|
|
@@ -6741,15 +6929,15 @@ function getNpmBinPathDetails() {
|
|
|
6741
6929
|
// First try to find npm next to the node binary to avoid picking up
|
|
6742
6930
|
// a project-local npm from node_modules/.bin on PATH.
|
|
6743
6931
|
const npmNextToNode = findBinNextToNode(constants.NPM);
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
}
|
|
6932
|
+
const details = npmNextToNode ? {
|
|
6933
|
+
name: constants.NPM,
|
|
6934
|
+
path: npmNextToNode,
|
|
6935
|
+
shadowed: false
|
|
6936
|
+
} : findBinPathDetailsSync(constants.NPM);
|
|
6937
|
+
_npmBinPathDetails = details.path ? {
|
|
6938
|
+
...details,
|
|
6939
|
+
path: resolveBinPathToJsEntry(details.path, 'npm-cli.js')
|
|
6940
|
+
} : details;
|
|
6753
6941
|
}
|
|
6754
6942
|
return _npmBinPathDetails;
|
|
6755
6943
|
}
|
|
@@ -6806,15 +6994,15 @@ function getNpxBinPathDetails() {
|
|
|
6806
6994
|
// standalone npx package which bundles npm@5.1.0, incompatible
|
|
6807
6995
|
// with Node 22+).
|
|
6808
6996
|
const npxNextToNode = findBinNextToNode('npx');
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
}
|
|
6997
|
+
const details = npxNextToNode ? {
|
|
6998
|
+
name: 'npx',
|
|
6999
|
+
path: npxNextToNode,
|
|
7000
|
+
shadowed: false
|
|
7001
|
+
} : findBinPathDetailsSync('npx');
|
|
7002
|
+
_npxBinPathDetails = details.path ? {
|
|
7003
|
+
...details,
|
|
7004
|
+
path: resolveBinPathToJsEntry(details.path, 'npx-cli.js')
|
|
7005
|
+
} : details;
|
|
6818
7006
|
}
|
|
6819
7007
|
return _npxBinPathDetails;
|
|
6820
7008
|
}
|
|
@@ -7420,20 +7608,20 @@ async function detectAndValidatePackageEnvironment(cwd, options) {
|
|
|
7420
7608
|
* - Classifying a GitHub response as a blocking error (rate limit / abuse
|
|
7421
7609
|
* detection / auth) with a clear, actionable message.
|
|
7422
7610
|
* - A bounded-retry request wrapper that respects `Retry-After` /
|
|
7423
|
-
* `x-ratelimit-reset` for short reset windows and
|
|
7424
|
-
* network failures
|
|
7611
|
+
* `x-ratelimit-reset` for short reset windows and hands transient 5xx /
|
|
7612
|
+
* network failures to the fleet's shared `pRetry` for backoff.
|
|
7425
7613
|
*/
|
|
7426
7614
|
|
|
7427
7615
|
// GitHub returns 429 for some secondary rate limits; there is no shared
|
|
7428
7616
|
// constant for it in constants.mts, so define it locally.
|
|
7429
7617
|
const HTTP_STATUS_TOO_MANY_REQUESTS = 429;
|
|
7430
7618
|
|
|
7431
|
-
//
|
|
7432
|
-
//
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
//
|
|
7436
|
-
const
|
|
7619
|
+
// Base delay before the first transient retry. Mirrors the default in
|
|
7620
|
+
// @socketsecurity/lib's releases/github-retry-config, including the env var
|
|
7621
|
+
// name, so both socket-cli lines back off against the GitHub API on the same
|
|
7622
|
+
// schedule. Read live rather than captured at import so a test or a CI job can
|
|
7623
|
+
// set it to 0 and skip the real wallclock wait.
|
|
7624
|
+
const DEFAULT_RETRY_BASE_DELAY_MS = 5000;
|
|
7437
7625
|
|
|
7438
7626
|
// Only wait-and-retry a rate-limited response when the reset window is at
|
|
7439
7627
|
// most this many seconds. The usual primary-limit reset is up to an hour
|
|
@@ -7525,13 +7713,34 @@ function classifyGitHubResponse(status, headers, bodyText, context) {
|
|
|
7525
7713
|
}
|
|
7526
7714
|
return undefined;
|
|
7527
7715
|
}
|
|
7528
|
-
|
|
7529
|
-
|
|
7716
|
+
|
|
7717
|
+
/**
|
|
7718
|
+
* Retry policy for transient GitHub failures. Same values as the shared
|
|
7719
|
+
* GITHUB_RETRY_CONFIG in @socketsecurity/lib: two retries on top of the initial
|
|
7720
|
+
* attempt, delay doubling each time, capped at 10 seconds. Built per call
|
|
7721
|
+
* rather than at import so the env override is read live.
|
|
7722
|
+
*/
|
|
7723
|
+
function githubRetryOptions() {
|
|
7724
|
+
return {
|
|
7725
|
+
backoffFactor: 2,
|
|
7726
|
+
baseDelayMs: env.envAsNumber(process$1.env['SOCKET_GITHUB_RETRY_BASE_DELAY_MS'], DEFAULT_RETRY_BASE_DELAY_MS),
|
|
7727
|
+
maxDelayMs: 10_000,
|
|
7728
|
+
retries: 2
|
|
7729
|
+
};
|
|
7530
7730
|
}
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7731
|
+
|
|
7732
|
+
/**
|
|
7733
|
+
* Thrown by one request attempt to tell `pRetry` what happened. `retryable`
|
|
7734
|
+
* false means another attempt cannot help, so the retry loop stops early
|
|
7735
|
+
* instead of burning its budget. The CResult is what the caller sees.
|
|
7736
|
+
*/
|
|
7737
|
+
class GitHubRequestFailure extends Error {
|
|
7738
|
+
constructor(result, retryable) {
|
|
7739
|
+
super(result.message);
|
|
7740
|
+
this.name = 'GitHubRequestFailure';
|
|
7741
|
+
this.result = result;
|
|
7742
|
+
this.retryable = retryable;
|
|
7743
|
+
}
|
|
7535
7744
|
}
|
|
7536
7745
|
|
|
7537
7746
|
/**
|
|
@@ -7549,7 +7758,8 @@ function sleep(ms) {
|
|
|
7549
7758
|
* short (<= CHEAP_RATE_LIMIT_WAIT_MAX_SECONDS); otherwise surface the error
|
|
7550
7759
|
* immediately. Long primary-limit resets are not worth blocking on.
|
|
7551
7760
|
* - Auth: never retried.
|
|
7552
|
-
* - 5xx / network:
|
|
7761
|
+
* - 5xx / network: handed to the fleet's shared `pRetry` for exponential
|
|
7762
|
+
* backoff, on the policy in `githubRetryOptions`.
|
|
7553
7763
|
*/
|
|
7554
7764
|
async function githubApiRequest(url, init, context,
|
|
7555
7765
|
// Injectable request implementation. Defaults to the real `apiFetch`;
|
|
@@ -7558,72 +7768,89 @@ async function githubApiRequest(url, init, context,
|
|
|
7558
7768
|
fetchImpl = apiFetch) {
|
|
7559
7769
|
const method = init.method || 'GET';
|
|
7560
7770
|
let rateLimitWaitUsed = false;
|
|
7561
|
-
|
|
7771
|
+
// pRetry rethrows whichever error it stored first. Track the newest one
|
|
7772
|
+
// ourselves so the caller always sees the failure that actually ended the
|
|
7773
|
+
// run, not an earlier one it had already recovered past.
|
|
7774
|
+
let lastFailure;
|
|
7775
|
+
const fail = (result, retryable) => {
|
|
7776
|
+
const failure = new GitHubRequestFailure(result, retryable);
|
|
7777
|
+
lastFailure = failure;
|
|
7778
|
+
return failure;
|
|
7779
|
+
};
|
|
7780
|
+
const attempt = async () => {
|
|
7562
7781
|
debugApiRequest(method, url);
|
|
7563
7782
|
let response;
|
|
7564
7783
|
try {
|
|
7565
|
-
// eslint-disable-next-line no-await-in-loop
|
|
7566
7784
|
response = await fetchImpl(url, init);
|
|
7567
7785
|
debugApiResponse(method, url, response.status);
|
|
7568
7786
|
} catch (e) {
|
|
7569
7787
|
debugApiResponse(method, url, undefined, e);
|
|
7570
|
-
// Network-level failure (DNS, connection reset, timeout).
|
|
7571
|
-
|
|
7572
|
-
if (attempt < MAX_TRANSIENT_ATTEMPTS) {
|
|
7573
|
-
debug$2.debugFn('notice', `retry: network error while ${context}`, attempt);
|
|
7574
|
-
// eslint-disable-next-line no-await-in-loop
|
|
7575
|
-
await sleep(backoffMs(attempt));
|
|
7576
|
-
continue;
|
|
7577
|
-
}
|
|
7578
|
-
return {
|
|
7788
|
+
// Network-level failure (DNS, connection reset, timeout).
|
|
7789
|
+
throw fail({
|
|
7579
7790
|
ok: false,
|
|
7580
7791
|
message: 'Network error connecting to GitHub',
|
|
7581
7792
|
cause: formatErrorWithDetail(`Network error while ${context}`, e)
|
|
7582
|
-
};
|
|
7793
|
+
}, true);
|
|
7583
7794
|
}
|
|
7584
|
-
|
|
7585
|
-
// eslint-disable-next-line no-await-in-loop
|
|
7586
7795
|
const bodyText = await response.text();
|
|
7587
7796
|
const blocking = classifyGitHubResponse(response.status, response.headers, bodyText, context);
|
|
7588
7797
|
if (blocking) {
|
|
7589
7798
|
// Auth failures never succeed on retry.
|
|
7590
7799
|
if (blocking.message === GITHUB_ERR_AUTH_FAILED) {
|
|
7591
|
-
|
|
7800
|
+
throw fail(blocking, false);
|
|
7592
7801
|
}
|
|
7593
|
-
// Rate limit / abuse:
|
|
7594
|
-
//
|
|
7802
|
+
// Rate limit / abuse: wait once, but only when the reset window is short
|
|
7803
|
+
// enough to be worth waiting on.
|
|
7595
7804
|
const waitSeconds = getRateLimitWaitSeconds(response.headers);
|
|
7596
7805
|
if (!rateLimitWaitUsed && waitSeconds !== undefined && waitSeconds <= CHEAP_RATE_LIMIT_WAIT_MAX_SECONDS) {
|
|
7597
7806
|
rateLimitWaitUsed = true;
|
|
7598
7807
|
logger.logger.info(`GitHub rate limit hit while ${context}; waiting ${waitSeconds}s before one retry...`);
|
|
7599
|
-
//
|
|
7600
|
-
//
|
|
7601
|
-
|
|
7602
|
-
|
|
7808
|
+
// GitHub told us exactly when the quota comes back, so this wait is
|
|
7809
|
+
// honoring a server instruction rather than backing off. Backoff is
|
|
7810
|
+
// pRetry's job and its delay is capped well below a reset window.
|
|
7811
|
+
// A second of slack lands the retry just past the reset boundary.
|
|
7812
|
+
await require$$7.setTimeout((waitSeconds + 1) * 1000);
|
|
7813
|
+
throw fail(blocking, true);
|
|
7603
7814
|
}
|
|
7604
|
-
|
|
7815
|
+
throw fail(blocking, false);
|
|
7605
7816
|
}
|
|
7606
7817
|
|
|
7607
|
-
// Transient server errors
|
|
7818
|
+
// Transient server errors.
|
|
7608
7819
|
if (response.status >= constants.HTTP_STATUS_INTERNAL_SERVER_ERROR) {
|
|
7609
|
-
|
|
7610
|
-
debug$2.debugFn('notice', `retry: GitHub ${response.status} while ${context}`, attempt);
|
|
7611
|
-
// eslint-disable-next-line no-await-in-loop
|
|
7612
|
-
await sleep(backoffMs(attempt));
|
|
7613
|
-
continue;
|
|
7614
|
-
}
|
|
7615
|
-
return {
|
|
7820
|
+
throw fail({
|
|
7616
7821
|
ok: false,
|
|
7617
7822
|
message: 'GitHub server error',
|
|
7618
7823
|
cause: `GitHub server error (${response.status}) while ${context}. ` + 'GitHub may be experiencing issues; try again shortly.'
|
|
7619
|
-
};
|
|
7824
|
+
}, true);
|
|
7620
7825
|
}
|
|
7826
|
+
return {
|
|
7827
|
+
response,
|
|
7828
|
+
bodyText
|
|
7829
|
+
};
|
|
7830
|
+
};
|
|
7831
|
+
try {
|
|
7832
|
+
const data = await promises.pRetry(attempt, {
|
|
7833
|
+
...githubRetryOptions(),
|
|
7834
|
+
onRetry(attemptNumber, e) {
|
|
7835
|
+
if (e instanceof GitHubRequestFailure && !e.retryable) {
|
|
7836
|
+
// Stop now; another attempt cannot change the answer.
|
|
7837
|
+
return false;
|
|
7838
|
+
}
|
|
7839
|
+
debug$2.debugFn('notice', `retry: ${e instanceof Error ? e.message : 'failure'} while ${context}`, attemptNumber);
|
|
7840
|
+
return undefined;
|
|
7841
|
+
},
|
|
7842
|
+
onRetryCancelOnFalse: true
|
|
7843
|
+
});
|
|
7621
7844
|
return {
|
|
7622
7845
|
ok: true,
|
|
7623
|
-
data
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7846
|
+
data
|
|
7847
|
+
};
|
|
7848
|
+
} catch {
|
|
7849
|
+
/* c8 ignore next - `lastFailure` is set on every throw out of `attempt`. */
|
|
7850
|
+
return lastFailure?.result ?? {
|
|
7851
|
+
ok: false,
|
|
7852
|
+
message: 'GitHub request failed',
|
|
7853
|
+
cause: `GitHub request failed while ${context}.`
|
|
7627
7854
|
};
|
|
7628
7855
|
}
|
|
7629
7856
|
}
|
|
@@ -8831,6 +9058,7 @@ exports.readOrDefaultSocketJsonUp = readOrDefaultSocketJsonUp;
|
|
|
8831
9058
|
exports.readPnpmLockfile = readPnpmLockfile;
|
|
8832
9059
|
exports.readSocketJsonCascade = readSocketJsonCascade;
|
|
8833
9060
|
exports.readSocketJsonSync = readSocketJsonSync;
|
|
9061
|
+
exports.realpathOrResolved = realpathOrResolved;
|
|
8834
9062
|
exports.runAgentInstall = runAgentInstall;
|
|
8835
9063
|
exports.runSelfUpdateCheck = runSelfUpdateCheck;
|
|
8836
9064
|
exports.safeNpa = safeNpa$1;
|
|
@@ -8860,5 +9088,5 @@ exports.walkNestedMap = walkNestedMap;
|
|
|
8860
9088
|
exports.webLink = webLink;
|
|
8861
9089
|
exports.withTmpDir = withTmpDir;
|
|
8862
9090
|
exports.writeSocketJson = writeSocketJson;
|
|
8863
|
-
//# debugId=
|
|
9091
|
+
//# debugId=e72c7655-2d5e-4e48-bfdb-c488b68167b6
|
|
8864
9092
|
//# sourceMappingURL=utils.js.map
|