@cdxgen/cdxgen 13.2.0 → 13.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -14
- package/bin/cdxgen.js +310 -138
- package/bin/evinse.js +22 -0
- package/bin/repl.js +86 -0
- package/bin/sign.js +119 -15
- package/bin/tracebom.js +69 -5
- package/bin/validate.js +107 -19
- package/bin/verify.js +172 -63
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +85 -0
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +38 -6
- package/lib/cli/bomAssembly.js +105 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +156 -58
- package/lib/cli/jsBom.js +99 -53
- package/lib/cli/jvmBom.js +708 -147
- package/lib/cli/managedBom.js +415 -224
- package/lib/cli/nativeBom.js +264 -44
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +22 -15
- package/lib/core/fs.js +176 -7
- package/lib/core/httpClient.js +159 -24
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/runState.js +39 -0
- package/lib/core/toml.js +72 -0
- package/lib/ecosystems/asarutils.js +4 -2
- package/lib/ecosystems/bunutils.js +274 -108
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/caxa.js +434 -2
- package/lib/ecosystems/cmakeResolver.js +76 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +399 -51
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1983 -793
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +356 -37
- package/lib/ecosystems/jsEvidence.js +104 -44
- package/lib/ecosystems/kosi.js +31 -7
- package/lib/ecosystems/npmutils.js +174 -9
- package/lib/ecosystems/parsers-dotnet.js +616 -159
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +371 -70
- package/lib/ecosystems/parsers-js.js +263 -76
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +929 -216
- package/lib/ecosystems/parsers-python.js +167 -24
- package/lib/ecosystems/parsers-rust.js +28 -22
- package/lib/ecosystems/parsers-terraform.js +1573 -19
- package/lib/ecosystems/pythonutils.js +102 -6
- package/lib/ecosystems/remote/huggingface.js +3 -0
- package/lib/ecosystems/rubyutils.js +1492 -182
- package/lib/ecosystems/rusi.js +54 -10
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +342 -0
- package/lib/ecosystems/scalasem.js +2618 -0
- package/lib/ecosystems/terraformRegistry.js +288 -0
- package/lib/ecosystems/utils.js +2 -0
- package/lib/ecosystems/vendoredCode.js +156 -0
- package/lib/evinser/evinser.js +1008 -191
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +1101 -222
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +703 -43
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cdxrs.js +103 -12
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +798 -57
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +90 -6
- package/lib/inventory/evidenceUtils.js +166 -113
- package/lib/inventory/fetchBatch.js +690 -55
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/protobom.js +8 -1
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +45 -27
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +295 -25
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +467 -46
- package/lib/managers/oci.js +63 -3
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +466 -0
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/strictJson.js +322 -0
- package/lib/parsers/terraformConfig.js +1073 -0
- package/lib/parsers/terraformSource.js +652 -0
- package/lib/server/server.js +23 -0
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/postgen.js +83 -16
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +64 -11
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +2 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +23 -23
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +24 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +7 -4
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +3 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +9 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +13 -8
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/pluginRun.d.ts +97 -0
- package/types/lib/core/pluginRun.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +36 -0
- package/types/lib/core/processTree.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/runState.d.ts +29 -0
- package/types/lib/core/runState.d.ts.map +1 -0
- package/types/lib/core/toml.d.ts +20 -0
- package/types/lib/core/toml.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +12 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +28 -3
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
- package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +17 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +9 -3
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +62 -13
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +70 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +13 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +57 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +54 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +16 -5
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +70 -4
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +53 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +27 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +164 -3
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/sbtutils.d.ts +88 -2
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
- package/types/lib/ecosystems/scalasem.d.ts +311 -0
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +2 -2
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +132 -15
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +182 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +219 -5
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +3 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
- package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +2 -1
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +59 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +121 -9
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts +53 -7
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +10 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +19 -4
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +83 -14
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
- package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/parentSelection.d.ts +34 -0
- package/types/lib/inventory/parentSelection.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts +80 -0
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +9 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +59 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +42 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +17 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeCache.d.ts +16 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +77 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/cmakePresets.d.ts +78 -0
- package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
- package/types/lib/parsers/compileCommands.d.ts +115 -0
- package/types/lib/parsers/compileCommands.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/parsers/strictJson.d.ts +46 -0
- package/types/lib/parsers/strictJson.d.ts.map +1 -0
- package/types/lib/parsers/terraformConfig.d.ts +70 -0
- package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
- package/types/lib/parsers/terraformSource.d.ts +66 -0
- package/types/lib/parsers/terraformSource.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +18 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
package/lib/core/fs.js
CHANGED
|
@@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import {
|
|
5
5
|
accessSync,
|
|
6
|
+
closeSync,
|
|
6
7
|
copyFileSync,
|
|
7
8
|
createReadStream,
|
|
8
9
|
existsSync,
|
|
@@ -10,10 +11,14 @@ import {
|
|
|
10
11
|
lstatSync,
|
|
11
12
|
mkdirSync,
|
|
12
13
|
mkdtempSync,
|
|
14
|
+
openSync,
|
|
13
15
|
readFileSync,
|
|
16
|
+
renameSync,
|
|
14
17
|
rmSync,
|
|
18
|
+
statSync,
|
|
15
19
|
unlinkSync,
|
|
16
20
|
writeFileSync,
|
|
21
|
+
writeSync,
|
|
17
22
|
} from "node:fs";
|
|
18
23
|
import { tmpdir } from "node:os";
|
|
19
24
|
import path, { basename, dirname, join, resolve } from "node:path";
|
|
@@ -144,6 +149,53 @@ export function safeIsExecutableSync(filePath) {
|
|
|
144
149
|
* @returns {void}
|
|
145
150
|
*/
|
|
146
151
|
export function safeWriteSync(filePath, data, options) {
|
|
152
|
+
if (isWriteBlocked(filePath)) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
writeFileSync(filePath, data, options);
|
|
156
|
+
recordFilesystemActivity("write", filePath, "completed");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Permission- and dry-run-aware write of a sequence of chunks, for content too
|
|
161
|
+
* large to join into one string first. The chunks go to a temporary file next
|
|
162
|
+
* to the target, which is renamed into place once complete, so a chunk may be
|
|
163
|
+
* read from the file being replaced and a failed write leaves the target as it
|
|
164
|
+
* was.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} filePath File path to write.
|
|
167
|
+
* @param {Iterable<string|Buffer>} chunks Chunks written in order.
|
|
168
|
+
* @returns {void}
|
|
169
|
+
*/
|
|
170
|
+
export function safeWriteChunksSync(filePath, chunks) {
|
|
171
|
+
if (isWriteBlocked(filePath)) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
175
|
+
const fd = openSync(tempPath, "w");
|
|
176
|
+
try {
|
|
177
|
+
for (const chunk of chunks) {
|
|
178
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
179
|
+
let written = 0;
|
|
180
|
+
while (written < bytes.length) {
|
|
181
|
+
written += writeSync(fd, bytes, written);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
closeSync(fd);
|
|
185
|
+
renameSync(tempPath, filePath);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
try {
|
|
188
|
+
closeSync(fd);
|
|
189
|
+
} catch {
|
|
190
|
+
// Already closed before the rename failed.
|
|
191
|
+
}
|
|
192
|
+
rmSync(tempPath, { force: true });
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
195
|
+
recordFilesystemActivity("write", filePath, "completed");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function isWriteBlocked(filePath) {
|
|
147
199
|
if (isDryRun) {
|
|
148
200
|
recordFilesystemActivity(
|
|
149
201
|
"write",
|
|
@@ -151,7 +203,7 @@ export function safeWriteSync(filePath, data, options) {
|
|
|
151
203
|
"blocked",
|
|
152
204
|
"Dry run mode blocks filesystem writes.",
|
|
153
205
|
);
|
|
154
|
-
return
|
|
206
|
+
return true;
|
|
155
207
|
}
|
|
156
208
|
if (!hasWritePermission(filePath)) {
|
|
157
209
|
if (DEBUG_MODE) {
|
|
@@ -163,10 +215,9 @@ export function safeWriteSync(filePath, data, options) {
|
|
|
163
215
|
"blocked",
|
|
164
216
|
"cdxgen lacks write permission for this path.",
|
|
165
217
|
);
|
|
166
|
-
return
|
|
218
|
+
return true;
|
|
167
219
|
}
|
|
168
|
-
|
|
169
|
-
recordFilesystemActivity("write", filePath, "completed");
|
|
220
|
+
return false;
|
|
170
221
|
}
|
|
171
222
|
|
|
172
223
|
/**
|
|
@@ -483,6 +534,116 @@ const VERSION_PROBE_ARGS = new Set(["--version", "-version", "version"]);
|
|
|
483
534
|
const POSIX_SHELL_METACHARACTERS = /[;&|<>$`\\\n\r]/;
|
|
484
535
|
const WINDOWS_SHELL_METACHARACTERS = /[&|<>^%\n\r]/;
|
|
485
536
|
|
|
537
|
+
/**
|
|
538
|
+
* Quote one argument of a shell command line that is built as a single
|
|
539
|
+
* string. The shell flag exists so cmd.exe can resolve bare command names,
|
|
540
|
+
* `.cmd` shims and scripts, and every argument is data (paths, flags) that
|
|
541
|
+
* must arrive as one token whatever it contains: joined unquoted, an
|
|
542
|
+
* argument with spaces splits into several and hands the tool the wrong
|
|
543
|
+
* tokens.
|
|
544
|
+
*
|
|
545
|
+
* Windows follows the quoting rules cmd.exe and the MSVC runtimes share:
|
|
546
|
+
* wrap in double quotes, doubling the backslashes that precede a quote and
|
|
547
|
+
* escaping the quote itself. Inside double quotes cmd.exe already treats
|
|
548
|
+
* `&`, `|`, `<`, `>`, `^` and parentheses as literals, and the
|
|
549
|
+
* shell-metacharacter block above refuses `%` and the rest before this
|
|
550
|
+
* runs. A POSIX shell gets single quotes, which no expansion can leave.
|
|
551
|
+
*
|
|
552
|
+
* The same quoting is applied to the command itself when it names an
|
|
553
|
+
* existing file under a path with spaces; a command the shell must parse
|
|
554
|
+
* (a bare name, `sh -c`) never names a file as a whole and stays verbatim.
|
|
555
|
+
*
|
|
556
|
+
* @param {string} arg Argument to quote
|
|
557
|
+
* @param {boolean} [forWindows] Quote for cmd.exe rather than a POSIX shell; defaults to the current platform
|
|
558
|
+
* @returns {string} The argument, quoted when the shell needs it
|
|
559
|
+
*/
|
|
560
|
+
export function shellQuoteArgument(arg, forWindows = isWin) {
|
|
561
|
+
const value = `${arg}`;
|
|
562
|
+
if (forWindows) {
|
|
563
|
+
if (value === "") {
|
|
564
|
+
return '""';
|
|
565
|
+
}
|
|
566
|
+
if (!/[\s"]/.test(value)) {
|
|
567
|
+
return value;
|
|
568
|
+
}
|
|
569
|
+
// Every backslash run that precedes a quote doubles, each quote escapes,
|
|
570
|
+
// and the trailing run doubles for the closing quote this returns. A
|
|
571
|
+
// character scan keeps it linear; the equivalent `(\\*)"` pattern is
|
|
572
|
+
// polynomial on runs of backslashes that no quote ends.
|
|
573
|
+
let quoted = "";
|
|
574
|
+
let backslashes = 0;
|
|
575
|
+
for (const ch of value) {
|
|
576
|
+
if (ch === "\\") {
|
|
577
|
+
backslashes++;
|
|
578
|
+
quoted += ch;
|
|
579
|
+
} else if (ch === '"') {
|
|
580
|
+
quoted += `${"\\".repeat(backslashes)}\\"`;
|
|
581
|
+
backslashes = 0;
|
|
582
|
+
} else {
|
|
583
|
+
quoted += ch;
|
|
584
|
+
backslashes = 0;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
quoted += "\\".repeat(backslashes);
|
|
588
|
+
return `"${quoted}"`;
|
|
589
|
+
}
|
|
590
|
+
if (value === "") {
|
|
591
|
+
return "''";
|
|
592
|
+
}
|
|
593
|
+
if (!/[^-\w@%+=:,./]/.test(value)) {
|
|
594
|
+
return value;
|
|
595
|
+
}
|
|
596
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Whether the path names an existing regular file, without throwing on the
|
|
601
|
+
* many values that are not paths at all (a bare command name, a command with
|
|
602
|
+
* arguments, anything the shell is meant to parse).
|
|
603
|
+
*
|
|
604
|
+
* @param {string} file Candidate path
|
|
605
|
+
* @returns {boolean} True when a regular file exists at the path
|
|
606
|
+
*/
|
|
607
|
+
function isFileNoThrow(file) {
|
|
608
|
+
try {
|
|
609
|
+
return statSync(`${file}`).isFile();
|
|
610
|
+
} catch (_err) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Build the single command string a shell spawn runs. Every argument is
|
|
617
|
+
* quoted for the platform shell, so an argument containing spaces stays one
|
|
618
|
+
* token and cannot alter the command, and a command that is itself a file
|
|
619
|
+
* under a path with spaces (`C:\Program Files\...\node.exe`) is quoted too;
|
|
620
|
+
* a command the shell must parse (`sh -c`, a bare name) never names an
|
|
621
|
+
* existing file as a whole and is left verbatim.
|
|
622
|
+
*
|
|
623
|
+
* The one exception is a caller that composes shell fragments itself (sbt
|
|
624
|
+
* command lines embed their own quoting for the shell to strip): it passes
|
|
625
|
+
* `rawShellArgs` and keeps the verbatim join, under the shell-metacharacter
|
|
626
|
+
* block in safeSpawnSync like every other argument.
|
|
627
|
+
*
|
|
628
|
+
* @param {string} command The executable
|
|
629
|
+
* @param {string[]|undefined} args Arguments, when the caller passed any
|
|
630
|
+
* @param {boolean} [rawShellArgs] Join the arguments verbatim instead of quoting them
|
|
631
|
+
* @returns {string} The command line the shell will run
|
|
632
|
+
*/
|
|
633
|
+
export function joinShellCommand(command, args, rawShellArgs = false) {
|
|
634
|
+
let commandToken = `${command}`;
|
|
635
|
+
if (/\s/.test(commandToken) && isFileNoThrow(commandToken)) {
|
|
636
|
+
commandToken = shellQuoteArgument(commandToken);
|
|
637
|
+
}
|
|
638
|
+
if (!args?.length) {
|
|
639
|
+
return commandToken;
|
|
640
|
+
}
|
|
641
|
+
return [
|
|
642
|
+
commandToken,
|
|
643
|
+
...(rawShellArgs ? args : args.map((arg) => shellQuoteArgument(arg))),
|
|
644
|
+
].join(" ");
|
|
645
|
+
}
|
|
646
|
+
|
|
486
647
|
function hasShellMetacharacters(value) {
|
|
487
648
|
if (value === undefined || value === null) {
|
|
488
649
|
return false;
|
|
@@ -696,9 +857,12 @@ export function safeSpawnSync(command, args, options) {
|
|
|
696
857
|
}
|
|
697
858
|
}
|
|
698
859
|
}
|
|
860
|
+
// Read before the option is stripped below: a caller composing shell
|
|
861
|
+
// fragments itself opts out of argument quoting with this flag.
|
|
862
|
+
const rawShellArgs = options?.cdxgenRawShellArgs === true;
|
|
699
863
|
if (!options) {
|
|
700
864
|
options = {};
|
|
701
|
-
} else if (options.cdxgenActivity) {
|
|
865
|
+
} else if (options.cdxgenActivity || options.cdxgenRawShellArgs) {
|
|
702
866
|
options = {
|
|
703
867
|
...options,
|
|
704
868
|
};
|
|
@@ -706,6 +870,9 @@ export function safeSpawnSync(command, args, options) {
|
|
|
706
870
|
if (options.cdxgenActivity) {
|
|
707
871
|
delete options.cdxgenActivity;
|
|
708
872
|
}
|
|
873
|
+
if (options.cdxgenRawShellArgs) {
|
|
874
|
+
delete options.cdxgenRawShellArgs;
|
|
875
|
+
}
|
|
709
876
|
if (options.shell === true) {
|
|
710
877
|
const unsafeShellToken = getUnsafeShellToken(command, args);
|
|
711
878
|
if (unsafeShellToken !== undefined) {
|
|
@@ -817,8 +984,9 @@ export function safeSpawnSync(command, args, options) {
|
|
|
817
984
|
commandsExecuted.add(command);
|
|
818
985
|
// Fix for DEP0190 warning
|
|
819
986
|
if (options?.shell === true) {
|
|
987
|
+
const joined = joinShellCommand(command, args, rawShellArgs);
|
|
988
|
+
command = joined;
|
|
820
989
|
if (args?.length) {
|
|
821
|
-
command = `${command} ${args.join(" ")}`;
|
|
822
990
|
args = undefined;
|
|
823
991
|
}
|
|
824
992
|
}
|
|
@@ -857,7 +1025,8 @@ export const MAX_BUFFER =
|
|
|
857
1025
|
export function getAllFiles(dirPath, pattern, options = {}) {
|
|
858
1026
|
let ignoreList = [];
|
|
859
1027
|
if (!options.noIgnore) {
|
|
860
|
-
|
|
1028
|
+
// .chen holds chen's AST cache, which atom writes into a C/C++ source tree
|
|
1029
|
+
ignoreList.push("**/.hg/**", "**/.git/**", "**/.chen/**");
|
|
861
1030
|
// Only ignore node_modules if the caller is not looking for package.json
|
|
862
1031
|
if (!pattern.includes("package.json") && !options.includeNodeModulesDir) {
|
|
863
1032
|
ignoreList.push("**/node_modules/**");
|
package/lib/core/httpClient.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
import { brotliDecompressSync, gunzipSync, inflateSync } from "node:zlib";
|
|
4
5
|
|
|
@@ -9,6 +10,8 @@ import {
|
|
|
9
10
|
request as undiciRequest,
|
|
10
11
|
} from "undici";
|
|
11
12
|
|
|
13
|
+
import { registerRunReset } from "./runState.js";
|
|
14
|
+
|
|
12
15
|
// Environment variables here are read with raw `process.env` rather than
|
|
13
16
|
// `readEnvironmentVariable`: that helper lives in activity.js, which imports
|
|
14
17
|
// this module, so routing these through it would form an import cycle.
|
|
@@ -41,9 +44,8 @@ import {
|
|
|
41
44
|
* `Content-Encoding` header, matching `got`'s transparent decoding.
|
|
42
45
|
* - Proxy support via the standard `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`
|
|
43
46
|
* environment variables (through undici's `EnvHttpProxyAgent`).
|
|
44
|
-
* - An in-memory GET response cache
|
|
45
|
-
*
|
|
46
|
-
* cache cdxgen previously relied on.
|
|
47
|
+
* - An in-memory GET response cache that replaces the got + Keyv cache cdxgen
|
|
48
|
+
* previously relied on. A request opts out with `cache: false`.
|
|
47
49
|
* - Response objects exposing `statusCode`, `headers`, `body`, `rawBody`,
|
|
48
50
|
* `url` and `request.options`.
|
|
49
51
|
* - A lazily-resolved `.json()` method on the returned promise, mirroring
|
|
@@ -137,7 +139,12 @@ function mergeHeaders(base = {}, override = {}) {
|
|
|
137
139
|
* `beforeError` arrays.
|
|
138
140
|
*/
|
|
139
141
|
function mergeHooks(base = {}, override = {}) {
|
|
140
|
-
const hookNames = [
|
|
142
|
+
const hookNames = [
|
|
143
|
+
"beforeRequest",
|
|
144
|
+
"beforeNetwork",
|
|
145
|
+
"afterResponse",
|
|
146
|
+
"beforeError",
|
|
147
|
+
];
|
|
141
148
|
const merged = {};
|
|
142
149
|
for (const name of hookNames) {
|
|
143
150
|
merged[name] = [...(base?.[name] || []), ...(override?.[name] || [])];
|
|
@@ -253,6 +260,11 @@ function decompressBody(rawBody, contentEncoding) {
|
|
|
253
260
|
* Read and decode an undici response body according to the requested
|
|
254
261
|
* `responseType`.
|
|
255
262
|
*
|
|
263
|
+
* An error response often carries a body in another format than the one asked
|
|
264
|
+
* for, such as the HTML page a CDN sends with a 429. Its JSON body is decoded
|
|
265
|
+
* when it parses and kept as text otherwise, so the caller sees the status
|
|
266
|
+
* rather than a parse error.
|
|
267
|
+
*
|
|
256
268
|
* @param {Object} undiciResponse Response returned by `undici.request`.
|
|
257
269
|
* @param {string} [responseType] One of `"json"`, `"buffer"` or `"text"`.
|
|
258
270
|
* @returns {Promise<{body: any, rawBody: Buffer}>} Decoded body and raw bytes.
|
|
@@ -272,7 +284,17 @@ async function readBody(undiciResponse, responseType) {
|
|
|
272
284
|
body = rawBody;
|
|
273
285
|
} else if (responseType === "json") {
|
|
274
286
|
const text = rawBody.toString("utf-8");
|
|
275
|
-
|
|
287
|
+
if (!text.length) {
|
|
288
|
+
body = undefined;
|
|
289
|
+
} else if (undiciResponse.statusCode >= 400) {
|
|
290
|
+
try {
|
|
291
|
+
body = JSON.parse(text);
|
|
292
|
+
} catch (_err) {
|
|
293
|
+
body = text;
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
body = JSON.parse(text);
|
|
297
|
+
}
|
|
276
298
|
} else {
|
|
277
299
|
body = rawBody.toString("utf-8");
|
|
278
300
|
}
|
|
@@ -304,20 +326,100 @@ function resolveRequestBody(options) {
|
|
|
304
326
|
|
|
305
327
|
// In-memory cache of successful GET responses, mirroring the HTTP cache cdxgen
|
|
306
328
|
// historically kept via got + Keyv. Repeated metadata/license lookups during a
|
|
307
|
-
// scan are served from here instead of hitting the network again.
|
|
308
|
-
// CDXGEN_NO_CACHE
|
|
329
|
+
// scan are served from here instead of hitting the network again. It lasts for
|
|
330
|
+
// one run; `CDXGEN_NO_CACHE` and `--no-cache` turn off only the on-disk caches,
|
|
331
|
+
// because nothing gains from asking a registry the same question twice in one
|
|
332
|
+
// scan.
|
|
333
|
+
//
|
|
334
|
+
// Entries expire after an hour and the cache is bounded in size, least
|
|
335
|
+
// recently used first, so a long-running server neither serves stale registry
|
|
336
|
+
// documents nor grows without limit.
|
|
309
337
|
const responseCache = new Map();
|
|
338
|
+
const RESPONSE_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
339
|
+
const RESPONSE_CACHE_MAX_BYTES = 512 * 1024 * 1024;
|
|
340
|
+
let responseCacheLimits = {
|
|
341
|
+
ttlMs: RESPONSE_CACHE_TTL_MS,
|
|
342
|
+
maxBytes: RESPONSE_CACHE_MAX_BYTES,
|
|
343
|
+
};
|
|
344
|
+
let responseCacheBytes = 0;
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Approximate the memory a cached response holds. The raw bytes are kept,
|
|
348
|
+
* plus the decoded body for text and JSON responses.
|
|
349
|
+
*
|
|
350
|
+
* @param {Object} response Response object.
|
|
351
|
+
* @returns {number} Size in bytes.
|
|
352
|
+
*/
|
|
353
|
+
function cachedResponseBytes(response) {
|
|
354
|
+
const raw = response?.rawBody?.length || 0;
|
|
355
|
+
return response?.body === response?.rawBody ? raw : raw * 3;
|
|
356
|
+
}
|
|
310
357
|
|
|
311
358
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
* callers can toggle it at runtime.
|
|
359
|
+
* Read a cached response, dropping it when it has expired and marking it as
|
|
360
|
+
* recently used otherwise.
|
|
315
361
|
*
|
|
316
|
-
* @
|
|
362
|
+
* @param {string} key Cache key.
|
|
363
|
+
* @returns {Object|undefined} The cached response.
|
|
317
364
|
*/
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
365
|
+
function cacheGet(key) {
|
|
366
|
+
const entry = responseCache.get(key);
|
|
367
|
+
if (!entry) {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
responseCache.delete(key);
|
|
371
|
+
if (entry.expiresAt <= Date.now()) {
|
|
372
|
+
responseCacheBytes -= entry.bytes;
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
responseCache.set(key, entry);
|
|
376
|
+
return entry.response;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Store a response, evicting the least recently used entries beyond the size
|
|
381
|
+
* bound.
|
|
382
|
+
*
|
|
383
|
+
* @param {string} key Cache key.
|
|
384
|
+
* @param {Object} response Response object.
|
|
385
|
+
* @returns {void}
|
|
386
|
+
*/
|
|
387
|
+
function cacheSet(key, response) {
|
|
388
|
+
const bytes = cachedResponseBytes(response);
|
|
389
|
+
if (bytes > responseCacheLimits.maxBytes) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const previous = responseCache.get(key);
|
|
393
|
+
if (previous) {
|
|
394
|
+
responseCache.delete(key);
|
|
395
|
+
responseCacheBytes -= previous.bytes;
|
|
396
|
+
}
|
|
397
|
+
responseCache.set(key, {
|
|
398
|
+
response,
|
|
399
|
+
bytes,
|
|
400
|
+
expiresAt: Date.now() + responseCacheLimits.ttlMs,
|
|
401
|
+
});
|
|
402
|
+
responseCacheBytes += bytes;
|
|
403
|
+
for (const [oldestKey, oldest] of responseCache) {
|
|
404
|
+
if (responseCacheBytes <= responseCacheLimits.maxBytes) {
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
responseCache.delete(oldestKey);
|
|
408
|
+
responseCacheBytes -= oldest.bytes;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Override the response cache bounds. Returns the previous bounds so tests can
|
|
414
|
+
* restore them. Not part of the public API.
|
|
415
|
+
*
|
|
416
|
+
* @param {{ttlMs?: number, maxBytes?: number}} limits New bounds.
|
|
417
|
+
* @returns {{ttlMs: number, maxBytes: number}} Previous bounds.
|
|
418
|
+
*/
|
|
419
|
+
export function _setHttpCacheLimits(limits) {
|
|
420
|
+
const previous = responseCacheLimits;
|
|
421
|
+
responseCacheLimits = { ...responseCacheLimits, ...limits };
|
|
422
|
+
return previous;
|
|
321
423
|
}
|
|
322
424
|
|
|
323
425
|
/**
|
|
@@ -327,8 +429,11 @@ export function isCacheDisabled() {
|
|
|
327
429
|
*/
|
|
328
430
|
export function clearHttpCache() {
|
|
329
431
|
responseCache.clear();
|
|
432
|
+
responseCacheBytes = 0;
|
|
330
433
|
}
|
|
331
434
|
|
|
435
|
+
registerRunReset("http:responses", clearHttpCache);
|
|
436
|
+
|
|
332
437
|
// ---------------------------------------------------------------------------
|
|
333
438
|
// Test cassette interceptor — the single network seam.
|
|
334
439
|
//
|
|
@@ -397,15 +502,33 @@ export function clearHttpInterceptor() {
|
|
|
397
502
|
}
|
|
398
503
|
|
|
399
504
|
/**
|
|
400
|
-
* Build the cache key for a request.
|
|
401
|
-
*
|
|
505
|
+
* Build the cache key for a request.
|
|
506
|
+
*
|
|
507
|
+
* Besides the method and URL, the key carries the response type, which
|
|
508
|
+
* changes how the same bytes are decoded, and a digest of every request
|
|
509
|
+
* header. A response fetched with one credential is therefore never served to
|
|
510
|
+
* a request made with another credential or with none, and a JSON request
|
|
511
|
+
* never receives a text body. Header values only enter the key as a SHA-256
|
|
512
|
+
* digest, so no credential is held in it.
|
|
402
513
|
*
|
|
403
514
|
* @param {string} method Uppercased HTTP method.
|
|
404
515
|
* @param {URL} url Request URL.
|
|
516
|
+
* @param {Object} [headers] Request headers as sent.
|
|
517
|
+
* @param {string} [responseType] Requested body decoding.
|
|
405
518
|
* @returns {string} Cache key.
|
|
406
519
|
*/
|
|
407
|
-
function cacheKeyFor(method, url) {
|
|
408
|
-
|
|
520
|
+
function cacheKeyFor(method, url, headers = {}, responseType = undefined) {
|
|
521
|
+
const headerEntries = Object.entries(headers || {})
|
|
522
|
+
.filter(([, value]) => value !== undefined && value !== null)
|
|
523
|
+
.map(([name, value]) => [
|
|
524
|
+
name.toLowerCase(),
|
|
525
|
+
Array.isArray(value) ? value.join(", ") : String(value),
|
|
526
|
+
])
|
|
527
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
528
|
+
const headerDigest = headerEntries.length
|
|
529
|
+
? createHash("sha256").update(JSON.stringify(headerEntries)).digest("hex")
|
|
530
|
+
: "";
|
|
531
|
+
return `${method}:${responseType || "text"}:${url.toString()}:${headerDigest}`;
|
|
409
532
|
}
|
|
410
533
|
|
|
411
534
|
// The maximum number of redirects to follow when `followRedirect` is enabled.
|
|
@@ -587,11 +710,23 @@ async function doRequest(mergedOptions) {
|
|
|
587
710
|
const { body, headers } = resolveRequestBody(options);
|
|
588
711
|
const method = (options.method || "GET").toUpperCase();
|
|
589
712
|
|
|
590
|
-
// Serve cacheable GET requests from the in-memory cache when enabled.
|
|
591
|
-
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
713
|
+
// Serve cacheable GET requests from the in-memory cache when enabled. A
|
|
714
|
+
// request opts out with `cache: false`, as token exchanges do.
|
|
715
|
+
const cacheable = method === "GET" && options.cache !== false;
|
|
716
|
+
const cacheKey = cacheable
|
|
717
|
+
? cacheKeyFor(method, url, headers, options.responseType)
|
|
718
|
+
: undefined;
|
|
719
|
+
if (cacheable) {
|
|
720
|
+
const cached = cacheGet(cacheKey);
|
|
721
|
+
if (cached) {
|
|
722
|
+
return cached;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Runs only when the request is about to reach the network, so a caller
|
|
727
|
+
// that rate-limits a host does not wait for a response already cached.
|
|
728
|
+
for (const hook of options.hooks?.beforeNetwork || []) {
|
|
729
|
+
await hook(options);
|
|
595
730
|
}
|
|
596
731
|
|
|
597
732
|
const timeoutMs = resolveTimeout(options.timeout);
|
|
@@ -652,7 +787,7 @@ async function doRequest(mergedOptions) {
|
|
|
652
787
|
|
|
653
788
|
// Only successful responses are cached, matching HTTP cache semantics.
|
|
654
789
|
if (cacheable && finalResponse.statusCode < 400) {
|
|
655
|
-
|
|
790
|
+
cacheSet(cacheKey, finalResponse);
|
|
656
791
|
}
|
|
657
792
|
return finalResponse;
|
|
658
793
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The spellings under which a config file may list custom metadata
|
|
3
|
+
* properties: those of the `--metadata-property` flag, and the plural that
|
|
4
|
+
* the CDXGEN_METADATA_PROPERTIES environment variable maps to.
|
|
5
|
+
*/
|
|
6
|
+
export const METADATA_PROPERTY_CONFIG_KEYS = [
|
|
7
|
+
"metadata-property",
|
|
8
|
+
"metadataProperty",
|
|
9
|
+
"metadata-properties",
|
|
10
|
+
"metadataProperties",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
// Namespaces whose properties cdxgen itself asserts: `cdx:` is its registered
|
|
14
|
+
// taxonomy, and `internal:` carries its own bookkeeping
|
|
15
|
+
const RESERVED_METADATA_PROPERTY_PREFIXES = ["cdx:", "internal:"];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Report whether a property name belongs to a namespace cdxgen reserves for
|
|
19
|
+
* the facts it establishes itself, such as `cdx:bom:componentSrcFiles`.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} name Property name
|
|
22
|
+
* @returns {boolean} true for a reserved name
|
|
23
|
+
*/
|
|
24
|
+
export function isReservedMetadataPropertyName(name) {
|
|
25
|
+
return RESERVED_METADATA_PROPERTY_PREFIXES.some((prefix) =>
|
|
26
|
+
`${name}`.startsWith(prefix),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Normalize user-supplied metadata properties into `[{name, value}]`
|
|
32
|
+
* (discussion 4391).
|
|
33
|
+
*
|
|
34
|
+
* Accepted shapes:
|
|
35
|
+
* - a `name=value` string, or a JSON document holding any of the shapes below
|
|
36
|
+
* — an environment variable
|
|
37
|
+
* - an array of `name=value` strings or `{name, value}` objects — the
|
|
38
|
+
* `--metadata-property` flag and a JSON config file
|
|
39
|
+
* - a plain object mapping a name to a value, or to a list of values for a
|
|
40
|
+
* property that repeats — a JSON document or a YAML config file
|
|
41
|
+
*
|
|
42
|
+
* Values must be strings, numbers, or booleans. An entry without a name, a
|
|
43
|
+
* `name=value` string without a separator, and an empty or structured value
|
|
44
|
+
* are dropped rather than guessed at: a property with a mangled name or an
|
|
45
|
+
* `[object Object]` value is worse than none.
|
|
46
|
+
*
|
|
47
|
+
* @param {unknown} input Properties in any accepted shape
|
|
48
|
+
* @returns {{name: string, value: string}[]} Normalized properties
|
|
49
|
+
*/
|
|
50
|
+
export function parseMetadataProperties(input) {
|
|
51
|
+
const properties = [];
|
|
52
|
+
if (typeof input === "string") {
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(input);
|
|
56
|
+
} catch (_err) {
|
|
57
|
+
parsed = undefined;
|
|
58
|
+
}
|
|
59
|
+
input =
|
|
60
|
+
parsed && typeof parsed === "object"
|
|
61
|
+
? parsed
|
|
62
|
+
: [typeof parsed === "string" ? parsed : input];
|
|
63
|
+
}
|
|
64
|
+
const append = (name, value) => {
|
|
65
|
+
if (typeof name !== "string" && typeof name !== "number") {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const propName = `${name}`.trim();
|
|
69
|
+
if (
|
|
70
|
+
!propName.length ||
|
|
71
|
+
!["string", "number", "boolean"].includes(typeof value) ||
|
|
72
|
+
!`${value}`.length
|
|
73
|
+
) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
properties.push({ name: propName, value: `${value}` });
|
|
77
|
+
};
|
|
78
|
+
if (Array.isArray(input)) {
|
|
79
|
+
for (const entry of input) {
|
|
80
|
+
if (typeof entry === "string") {
|
|
81
|
+
const separator = entry.indexOf("=");
|
|
82
|
+
if (separator > 0) {
|
|
83
|
+
append(entry.slice(0, separator), entry.slice(separator + 1));
|
|
84
|
+
}
|
|
85
|
+
} else if (entry && typeof entry === "object") {
|
|
86
|
+
append(entry.name, entry.value);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} else if (input && typeof input === "object") {
|
|
90
|
+
for (const [name, value] of Object.entries(input)) {
|
|
91
|
+
for (const avalue of Array.isArray(value) ? value : [value]) {
|
|
92
|
+
append(name, avalue);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return properties;
|
|
97
|
+
}
|