@cyclonedx/cdxgen 10.7.0 → 10.8.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 CHANGED
@@ -7,7 +7,6 @@
7
7
  [![SWH][badge-swh]][swh-cdxgen]
8
8
  [![Libraries.io dependency status][badge-libraries]][librariesio]
9
9
 
10
-
11
10
  # CycloneDX Generator (cdxgen)
12
11
 
13
12
  ![cdxgen logo](./docs/_media/cdxgen.png)
@@ -51,16 +50,6 @@ Sections include:
51
50
  - [Permissions][docs-permissions]
52
51
  - [Support (Enterprise & Community)][docs-support]
53
52
 
54
- ### Automatic usage detection
55
-
56
- For node.js projects, lock files are parsed initially, so the SBOM would include all dependencies, including dev ones. An AST parser powered by babel-parser is then used to detect packages that are imported and used by non-test code. Such imported packages would automatically set their scope property to `required` in the resulting SBOM. You can turn off this analysis by passing the argument `--no-babel`. Scope property would then be set based on the `dev` attribute in the lock file.
57
-
58
- This attribute can be later used for various purposes. For example, [dep-scan][depscan-github] uses this attribute to prioritize vulnerabilities. Unfortunately, tools such as dependency track, do not include this feature and might over-report the CVEs.
59
-
60
- With the argument `--required-only`, you can limit the SBOM only to include packages with the scope "required", commonly called production or non-dev dependencies. Combine with `--no-babel` to limit this list to only non-dev dependencies based on the `dev` attribute being false in the lock files.
61
-
62
- For go, `go mod why` command is used to identify required packages. For php, composer lock file is parsed to distinguish required (packages) from optional (packages-dev).
63
-
64
53
  ## Usage
65
54
 
66
55
  ## Installing
@@ -110,88 +99,68 @@ import { createBom, submitBom } from "npm:@cyclonedx/cdxgen@^9.0.1";
110
99
  ## Getting Help
111
100
 
112
101
  ```text
113
- $ cdxgen -h
102
+ cdxgen [command]
103
+
104
+ Commands:
105
+ cdxgen completion Generate bash/zsh completion
106
+
114
107
  Options:
115
- -o, --output Output file. Default bom.json
116
- [default: "bom.json"]
117
- -t, --type Project type. Please refer to https://cyclonedx.g
118
- ithub.io/cdxgen/#/PROJECT_TYPES for supported lan
119
- guages/platforms.
120
- -r, --recurse Recurse mode suitable for mono-repos. Defaults to
121
- true. Pass --no-recurse to disable.
122
- [boolean] [default: true]
123
- -p, --print Print the SBOM as a table with tree. [boolean]
124
- -c, --resolve-class Resolve class names for packages. jars only for n
125
- ow. [boolean]
126
- --deep Perform deep searches for components. Useful whil
127
- e scanning C/C++ apps, live OS and oci images.
128
- [boolean]
129
- --server-url Dependency track url. Eg: https://deptrack.cyclon
130
- edx.io
108
+ -o, --output Output file. Default bom.json [default: "bom.json"]
109
+ -t, --type Project type. Please refer to https://cyclonedx.github.io/cdxgen/#/PROJECT_TYPES for supp
110
+ orted languages/platforms. [array]
111
+ --exclude-type Project types to exclude. Please refer to https://cyclonedx.github.io/cdxgen/#/PROJECT_TY
112
+ PES for supported languages/platforms.
113
+ -r, --recurse Recurse mode suitable for mono-repos. Defaults to true. Pass --no-recurse to disable.
114
+ [boolean] [default: true]
115
+ -p, --print Print the SBOM as a table with tree. [boolean]
116
+ -c, --resolve-class Resolve class names for packages. jars only for now. [boolean]
117
+ --deep Perform deep searches for components. Useful while scanning C/C++ apps, live OS and oci i
118
+ mages. [boolean]
119
+ --server-url Dependency track url. Eg: https://deptrack.cyclonedx.io
131
120
  --api-key Dependency track api key
132
121
  --project-group Dependency track project group
133
- --project-name Dependency track project name. Default use the di
134
- rectory name
135
- --project-version Dependency track project version
136
- [string] [default: ""]
137
- --project-id Dependency track project id. Either provide the i
138
- d or the project name and version together
139
- [string]
140
- --parent-project-id Dependency track parent project id [string]
141
- --required-only Include only the packages with required scope on
142
- the SBOM. Would set compositions.aggregate to inc
143
- omplete unless --no-auto-compositions is passed.
144
- [boolean]
145
- --fail-on-error Fail if any dependency extractor fails. [boolean]
146
- --no-babel Do not use babel to perform usage analysis for Ja
147
- vaScript/TypeScript projects. [boolean]
148
- --generate-key-and-sign Generate an RSA public/private key pair and then
149
- sign the generated SBOM using JSON Web Signatures
150
- . [boolean]
151
- --server Run cdxgen as a server [boolean]
152
- --server-host Listen address [default: "127.0.0.1"]
153
- --server-port Listen port [default: "9090"]
154
- --install-deps Install dependencies automatically for some proje
155
- cts. Defaults to true but disabled for containers
156
- and oci scans. Use --no-install-deps to disable
157
- this feature. [boolean] [default: true]
158
- --validate Validate the generated SBOM using json schema. De
159
- faults to true. Pass --no-validate to disable.
160
- [boolean] [default: true]
161
- --evidence Generate SBOM with evidence for supported languag
162
- es. [boolean] [default: false]
163
- --spec-version CycloneDX Specification version to use. Defaults
164
- to 1.5 [number] [default: 1.5]
165
- --filter Filter components containing this word in purl or
166
- component.properties.value. Multiple values allo
167
- wed. [array]
168
- --only Include components only containing this word in p
169
- url. Useful to generate BOM with first party comp
170
- onents alone. Multiple values allowed. [array]
171
- --author The person(s) who created the BOM. Set this value
172
- if you're intending the modify the BOM and claim
173
- authorship.[array] [default: "OWASP Foundation"]
174
- --profile BOM profile to use for generation. Default generi
175
- c.
176
- [choices: "appsec", "research", "operational", "threat-modeling", "license-com
177
- pliance", "generic"] [default: "generic"]
178
- --exclude Additional glob pattern(s) to ignore [array]
179
- --include-formulation Generate formulation section using git metadata.
180
- [boolean] [default: false]
181
- --include-crypto Include crypto libraries found under formulation.
182
- [boolean] [default: false]
183
- --standard The list of standards which may consist of regula
184
- tions, industry or organizational-specific standa
185
- rds, maturity models, best practices, or any othe
186
- r requirements which can be evaluated against or
187
- attested to.
188
- [array] [choices: "asvs-4.0.3", "bsimm-v13", "masvs-2.0.0", "nist_ssdf-1.1", "
189
- pcissc-secure-slc-1.1", "scvs-1.0.0", "ssaf-DRAFT-2023-11"]
190
- --auto-compositions Automatically set compositions when the BOM was f
191
- iltered. Defaults to true
192
- [boolean] [default: true]
193
- -h, --help Show help [boolean]
194
- -v, --version Show version number [boolean]
122
+ --project-name Dependency track project name. Default use the directory name
123
+ --project-version Dependency track project version [string] [default: ""]
124
+ --project-id Dependency track project id. Either provide the id or the project name and version togeth
125
+ er [string]
126
+ --parent-project-id Dependency track parent project id [string]
127
+ --required-only Include only the packages with required scope on the SBOM. Would set compositions.aggrega
128
+ te to incomplete unless --no-auto-compositions is passed. [boolean]
129
+ --fail-on-error Fail if any dependency extractor fails. [boolean]
130
+ --no-babel Do not use babel to perform usage analysis for JavaScript/TypeScript projects. [boolean]
131
+ --generate-key-and-sign Generate an RSA public/private key pair and then sign the generated SBOM using JSON Web S
132
+ ignatures. [boolean]
133
+ --server Run cdxgen as a server [boolean]
134
+ --server-host Listen address [default: "127.0.0.1"]
135
+ --server-port Listen port [default: "9090"]
136
+ --install-deps Install dependencies automatically for some projects. Defaults to true but disabled for c
137
+ ontainers and oci scans. Use --no-install-deps to disable this feature. [boolean]
138
+ --validate Validate the generated SBOM using json schema. Defaults to true. Pass --no-validate to di
139
+ sable. [boolean] [default: true]
140
+ --evidence Generate SBOM with evidence for supported languages. [boolean] [default: false]
141
+ --spec-version CycloneDX Specification version to use. Defaults to 1.5 [number] [default: 1.5]
142
+ --filter Filter components containing this word in purl or component.properties.value. Multiple va
143
+ lues allowed. [array]
144
+ --only Include components only containing this word in purl. Useful to generate BOM with first p
145
+ arty components alone. Multiple values allowed. [array]
146
+ --author The person(s) who created the BOM. Set this value if you're intending the modify the BOM
147
+ and claim authorship. [array] [default: "OWASP Foundation"]
148
+ --profile BOM profile to use for generation. Default generic.
149
+ [choices: "appsec", "research", "operational", "threat-modeling", "license-compliance", "generic"] [default: "generic"
150
+ ]
151
+ --exclude Additional glob pattern(s) to ignore [array]
152
+ --include-formulation Generate formulation section with git metadata and build tools. Defaults to true. Invoke
153
+ with --no-include-formulation to disable. [boolean] [default: true]
154
+ --include-crypto Include crypto libraries found under formulation. [boolean] [default: false]
155
+ --standard The list of standards which may consist of regulations, industry or organizational-specif
156
+ ic standards, maturity models, best practices, or any other requirements which can be eva
157
+ luated against or attested to.
158
+ [array] [choices: "asvs-4.0.3", "bsimm-v13", "masvs-2.0.0", "nist_ssdf-1.1", "pcissc-secure-slc-1.1", "scvs-1.0.0", "s
159
+ saf-DRAFT-2023-11"]
160
+ --auto-compositions Automatically set compositions when the BOM was filtered. Defaults to true
161
+ [boolean] [default: true]
162
+ -h, --help Show help [boolean]
163
+ -v, --version Show version number [boolean]
195
164
  ```
196
165
 
197
166
  All boolean arguments accept `--no` prefix to toggle the behavior.
@@ -473,6 +442,16 @@ if (validationResult) {
473
442
  }
474
443
  ```
475
444
 
445
+ ## Automatic usage detection
446
+
447
+ For node.js projects, lock files are parsed initially, so the SBOM would include all dependencies, including dev ones. An AST parser powered by babel-parser is then used to detect packages that are imported and used by non-test code. Such imported packages would automatically set their scope property to `required` in the resulting SBOM. You can turn off this analysis by passing the argument `--no-babel`. Scope property would then be set based on the `dev` attribute in the lock file.
448
+
449
+ This attribute can be later used for various purposes. For example, [dep-scan][depscan-github] uses this attribute to prioritize vulnerabilities. Unfortunately, tools such as dependency track, do not include this feature and might over-report the CVEs.
450
+
451
+ With the argument `--required-only`, you can limit the SBOM only to include packages with the scope "required", commonly called production or non-dev dependencies. Combine with `--no-babel` to limit this list to only non-dev dependencies based on the `dev` attribute being false in the lock files.
452
+
453
+ For go, `go mod why` command is used to identify required packages. For php, composer lock file is parsed to distinguish required (packages) from optional (packages-dev).
454
+
476
455
  ## Automatic services detection
477
456
 
478
457
  cdxgen can automatically detect names of services from YAML manifests such as docker-compose, Kubernetes, or Skaffold manifests. These would be populated under the `services` attribute in the generated SBOM. With [evinse](./ADVANCED.md), additional services could be detected by parsing common annotations from the source code.
package/bin/cdxgen.js CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  printReachables,
18
18
  printServices,
19
19
  printSponsorBanner,
20
+ printSummary,
20
21
  printTable,
21
22
  } from "../display.js";
22
23
  import { createBom, submitBom } from "../index.js";
@@ -55,6 +56,10 @@ import { hideBin } from "yargs/helpers";
55
56
 
56
57
  const args = yargs(hideBin(process.argv))
57
58
  .env("CDXGEN")
59
+ .parserConfiguration({
60
+ "greedy-arrays": false,
61
+ "short-option-groups": false,
62
+ })
58
63
  .option("output", {
59
64
  alias: "o",
60
65
  description: "Output file. Default bom.json",
@@ -63,7 +68,6 @@ const args = yargs(hideBin(process.argv))
63
68
  .option("evinse-output", {
64
69
  description:
65
70
  "Create bom with evidence as a separate file. Default bom.json",
66
- default: "bom.json",
67
71
  hidden: true,
68
72
  })
69
73
  .option("type", {
@@ -71,6 +75,10 @@ const args = yargs(hideBin(process.argv))
71
75
  description:
72
76
  "Project type. Please refer to https://cyclonedx.github.io/cdxgen/#/PROJECT_TYPES for supported languages/platforms.",
73
77
  })
78
+ .option("exclude-type", {
79
+ description:
80
+ "Project types to exclude. Please refer to https://cyclonedx.github.io/cdxgen/#/PROJECT_TYPES for supported languages/platforms.",
81
+ })
74
82
  .option("recurse", {
75
83
  alias: "r",
76
84
  type: "boolean",
@@ -238,8 +246,9 @@ const args = yargs(hideBin(process.argv))
238
246
  })
239
247
  .option("include-formulation", {
240
248
  type: "boolean",
241
- default: false,
242
- description: "Generate formulation section using git metadata.",
249
+ default: true,
250
+ description:
251
+ "Generate formulation section with git metadata and build tools. Defaults to true. Invoke with --no-include-formulation to disable.",
243
252
  })
244
253
  .option("include-crypto", {
245
254
  type: "boolean",
@@ -262,10 +271,13 @@ const args = yargs(hideBin(process.argv))
262
271
  .option("no-banner", {
263
272
  type: "boolean",
264
273
  default: false,
274
+ hidden: true,
265
275
  description:
266
276
  "Do not show the donation banner. Set this attribute if you are an active sponsor for OWASP CycloneDX.",
267
277
  })
268
278
  .completion("completion", "Generate bash/zsh completion")
279
+ .array("type")
280
+ .array("excludeType")
269
281
  .array("filter")
270
282
  .array("only")
271
283
  .array("author")
@@ -279,6 +291,10 @@ const args = yargs(hideBin(process.argv))
279
291
  })
280
292
  .example([
281
293
  ["$0 -t java .", "Generate a Java SBOM for the current directory"],
294
+ [
295
+ "$0 -t java -t js .",
296
+ "Generate a SBOM for Java and JavaScript in the current directory",
297
+ ],
282
298
  ["$0 --server", "Run cdxgen as a server"],
283
299
  ])
284
300
  .epilogue("for documentation, visit https://cyclonedx.github.io/cdxgen")
@@ -369,7 +385,11 @@ const applyAdvancedOptions = (options) => {
369
385
  process.env.ASTGEN_IGNORE_FILE_PATTERN = "";
370
386
  break;
371
387
  case "operational":
372
- options.projectType = options.projectType || "os";
388
+ if (options?.projectType) {
389
+ options.projectType.push("os");
390
+ } else {
391
+ options.projectType = ["os"];
392
+ }
373
393
  break;
374
394
  case "threat-modeling":
375
395
  options.deep = true;
@@ -388,6 +408,8 @@ const applyAdvancedOptions = (options) => {
388
408
  case "post-build":
389
409
  if (
390
410
  !options.projectType ||
411
+ (Array.isArray(options.projectType) &&
412
+ options.projectType.length > 1) ||
391
413
  ![
392
414
  "csharp",
393
415
  "dotnet",
@@ -403,7 +425,7 @@ const applyAdvancedOptions = (options) => {
403
425
  "rust",
404
426
  "rust-lang",
405
427
  "cargo",
406
- ].includes(options.projectType)
428
+ ].includes(options.projectType[0])
407
429
  ) {
408
430
  console.log(
409
431
  "PREVIEW: post-build lifecycle SBOM generation is supported only for android, dotnet, go, and Rust projects. Please specify the type using the -t argument.",
@@ -473,14 +495,8 @@ const checkPermissions = (filePath) => {
473
495
  options.usagesSlicesFile = `${options.projectName}-usages.json`;
474
496
  }
475
497
  let bomNSData = (await createBom(filePath, options)) || {};
476
- if (
477
- options.requiredOnly ||
478
- options["filter"] ||
479
- options["only"] ||
480
- options.standard
481
- ) {
482
- bomNSData = postProcess(bomNSData, options);
483
- }
498
+ // Add extra metadata and annotations with post processing
499
+ bomNSData = postProcess(bomNSData, options);
484
500
  if (
485
501
  options.output &&
486
502
  (typeof options.output === "string" || options.output instanceof String)
@@ -496,15 +512,7 @@ const checkPermissions = (filePath) => {
496
512
  fs.writeFileSync(jsonFile, bomNSData.bomJson);
497
513
  jsonPayload = bomNSData.bomJson;
498
514
  } else {
499
- jsonPayload = JSON.stringify(
500
- bomNSData.bomJson,
501
- null,
502
- options.deep ||
503
- ["os", "docker", "universal"].includes(options.projectType) ||
504
- process.env.CI
505
- ? null
506
- : 2,
507
- );
515
+ jsonPayload = JSON.stringify(bomNSData.bomJson, null, null);
508
516
  fs.writeFileSync(jsonFile, jsonPayload);
509
517
  }
510
518
  if (
@@ -643,12 +651,17 @@ const checkPermissions = (filePath) => {
643
651
  }
644
652
  // Evidence generation
645
653
  if (options.evidence || options.includeCrypto) {
654
+ // Set the evinse output file to be the same as output file
655
+ if (!options.evinseOutput) {
656
+ options.evinseOutput = options.output;
657
+ }
646
658
  const evinserModule = await import("../evinser.js");
659
+ options.projectType = options.projectType || ["java"];
647
660
  const evinseOptions = {
648
661
  _: args._,
649
662
  input: options.output,
650
663
  output: options.evinseOutput,
651
- language: options.projectType || "java",
664
+ language: options.projectType,
652
665
  dbPath: process.env.ATOM_DB || ATOM_DB,
653
666
  skipMavenCollector: false,
654
667
  force: false,
@@ -699,6 +712,7 @@ const checkPermissions = (filePath) => {
699
712
  protobomModule.writeBinary(bomNSData.bomJson, options.protoBinFile);
700
713
  }
701
714
  if (options.print && bomNSData.bomJson && bomNSData.bomJson.components) {
715
+ printSummary(bomNSData.bomJson);
702
716
  printDependencyTree(bomNSData.bomJson);
703
717
  printTable(bomNSData.bomJson);
704
718
  // CBOM related print
package/bin/repl.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  printOSTable,
14
14
  printOccurrences,
15
15
  printServices,
16
+ printSummary,
16
17
  printTable,
17
18
  printVulnerabilities,
18
19
  } from "../display.js";
@@ -67,6 +68,7 @@ export const importSbom = (sbomOrPath) => {
67
68
  bomType = "VDR";
68
69
  }
69
70
  console.log(`✅ ${bomType} imported successfully from ${sbomOrPath}`);
71
+ printSummary(sbom);
70
72
  } catch (e) {
71
73
  console.log(`⚠ Unable to import the BOM from ${sbomOrPath} due to ${e}`);
72
74
  }
@@ -161,9 +163,15 @@ cdxgenRepl.defineCommand("search", {
161
163
  searchStr = `components[group ~> /${searchStr}/i or name ~> /${searchStr}/i or description ~> /${searchStr}/i or publisher ~> /${searchStr}/i or purl ~> /${searchStr}/i]`;
162
164
  }
163
165
  const expression = jsonata(searchStr);
164
- const components = await expression.evaluate(sbom);
166
+ let components = await expression.evaluate(sbom);
165
167
  const dexpression = jsonata(dependenciesSearchStr);
166
- const dependencies = await dexpression.evaluate(sbom);
168
+ let dependencies = await dexpression.evaluate(sbom);
169
+ if (components && !Array.isArray(components)) {
170
+ components = [components];
171
+ }
172
+ if (dependencies && !Array.isArray(dependencies)) {
173
+ dependencies = [dependencies];
174
+ }
167
175
  if (!components) {
168
176
  console.log("No results found!");
169
177
  } else {
package/binary.js CHANGED
@@ -249,6 +249,7 @@ const OS_DISTRO_ALIAS = {
249
249
  "debian-13.5": "trixie",
250
250
  "debian-12": "bookworm",
251
251
  "debian-12.5": "bookworm",
252
+ "debian-12.6": "bookworm",
252
253
  "debian-11": "bullseye",
253
254
  "debian-11.5": "bullseye",
254
255
  "debian-10": "buster",
@@ -17,9 +17,7 @@
17
17
  "Apache Public License 2.0",
18
18
  "Apache Software License - Version 2.0",
19
19
  "The Apache License, Version 2.0",
20
- "BSD or Apache License, Version 2.0",
21
20
  "Apache Software License",
22
- "Apache-2.0 OR MIT",
23
21
  "Apache2.0",
24
22
  "apache-2-0",
25
23
  "APL2",
@@ -241,7 +239,8 @@
241
239
  "GNU General Public License v2.0 or later",
242
240
  "GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version",
243
241
  "GNU GPLv2 or any later version",
244
- "GPLv2+"
242
+ "GPLv2+",
243
+ "GNU General Public License v2 or later (GPLv2+)"
245
244
  ]
246
245
  },
247
246
  {
@@ -287,6 +286,10 @@
287
286
  "GNU Affero General Public License v3.0"
288
287
  ]
289
288
  },
289
+ {
290
+ "exp": "MIT-0",
291
+ "names": ["MIT No Attribution License (MIT-0)", "MIT No Attribution"]
292
+ },
290
293
  {
291
294
  "exp": "MIT",
292
295
  "names": [
package/display.js CHANGED
@@ -405,3 +405,35 @@ export function printSponsorBanner(options) {
405
405
  console.log(table(data, config));
406
406
  }
407
407
  }
408
+
409
+ export const printSummary = (bomJson) => {
410
+ const config = {
411
+ header: {
412
+ alignment: "center",
413
+ content: "BOM summary",
414
+ },
415
+ };
416
+ const metadataProperties = bomJson?.metadata?.properties;
417
+ if (!metadataProperties) {
418
+ return;
419
+ }
420
+ let bomPkgTypes = [];
421
+ let bomPkgNamespaces = [];
422
+ for (const aprop of metadataProperties) {
423
+ if (aprop.name === "cdx:bom:componentTypes") {
424
+ bomPkgTypes = aprop?.value.split("\\n");
425
+ }
426
+ if (aprop.name === "cdx:bom:componentNamespaces") {
427
+ bomPkgNamespaces = aprop?.value.split("\\n");
428
+ }
429
+ }
430
+ if (!bomPkgTypes.length && !bomPkgNamespaces.length) {
431
+ return;
432
+ }
433
+ let message = `** Package Types (${bomPkgTypes.length}) **\n${bomPkgTypes.join("\n")}`;
434
+ if (bomPkgNamespaces.length) {
435
+ message = `${message}\n\n** Namespaces (${bomPkgNamespaces.length}) **\n${bomPkgNamespaces.join("\n")}`;
436
+ }
437
+ const data = [[message]];
438
+ console.log(table(data, config));
439
+ };
package/docker.js CHANGED
@@ -37,6 +37,7 @@ let isDockerRootless = false;
37
37
  let isContainerd = !!process.env.CONTAINERD_ADDRESS;
38
38
  const WIN_LOCAL_TLS = "http://localhost:2375";
39
39
  let isWinLocalTLS = false;
40
+ let isNerdctl = undefined;
40
41
 
41
42
  if (
42
43
  !process.env.DOCKER_HOST &&
@@ -49,6 +50,56 @@ if (
49
50
  isContainerd = true;
50
51
  }
51
52
 
53
+ /**
54
+ * Detect if Rancher desktop is running on a mac.
55
+ */
56
+ export function detectRancherDesktop() {
57
+ // Detect Rancher desktop and nerdctl on a mac
58
+ if (_platform() === "darwin") {
59
+ const limaHome = join(
60
+ homedir(),
61
+ "Library",
62
+ "Application Support",
63
+ "rancher-desktop",
64
+ "lima",
65
+ );
66
+ const limactl = join(
67
+ "/Applications",
68
+ "Rancher Desktop.app",
69
+ "Contents",
70
+ "Resources",
71
+ "resources",
72
+ "darwin",
73
+ "lima",
74
+ "bin",
75
+ "limactl",
76
+ );
77
+ // Is Rancher Desktop running
78
+ if (existsSync(limactl) || existsSync(limaHome)) {
79
+ const result = spawnSync("rdctl", ["list-settings"], {
80
+ encoding: "utf-8",
81
+ });
82
+ if (result.status !== 0 || result.error) {
83
+ if (
84
+ isNerdctl === undefined &&
85
+ result.stderr?.includes("connection refused")
86
+ ) {
87
+ console.warn(
88
+ "Ensure Rancher Desktop is running prior to invoking cdxgen. To start from the command line, type the command 'rdctl start'",
89
+ );
90
+ isNerdctl = false;
91
+ }
92
+ } else {
93
+ if (DEBUG_MODE) {
94
+ console.log("Rancher Desktop found!");
95
+ }
96
+ isNerdctl = true;
97
+ }
98
+ }
99
+ }
100
+ return isNerdctl;
101
+ }
102
+
52
103
  // Cache the registry auth keys
53
104
  const registry_auth_keys = {};
54
105
 
@@ -288,7 +339,7 @@ const getDefaultOptions = (forRegistry) => {
288
339
  };
289
340
 
290
341
  export const getConnection = async (options, forRegistry) => {
291
- if (isContainerd) {
342
+ if (isContainerd || isNerdctl) {
292
343
  return undefined;
293
344
  }
294
345
  if (!dockerConn) {
@@ -368,10 +419,15 @@ export const getConnection = async (options, forRegistry) => {
368
419
  "Ensure Docker for Desktop is running as an administrator with 'Exposing daemon on TCP without TLS' setting turned on.",
369
420
  opts,
370
421
  );
371
- } else if (_platform() === "darwin") {
372
- console.warn(
373
- "Ensure Podman Desktop (open-source) or Docker for Desktop (May require subscription) is running.",
374
- );
422
+ } else if (_platform() === "darwin" && !isNerdctl) {
423
+ if (detectRancherDesktop()) {
424
+ return undefined;
425
+ }
426
+ if (isNerdctl === undefined) {
427
+ console.warn(
428
+ "Ensure Podman Desktop (open-source) or Docker for Desktop (May require subscription) is running.",
429
+ );
430
+ }
375
431
  } else {
376
432
  console.warn(
377
433
  "Ensure docker/podman service or Docker for Desktop is running.",
@@ -497,11 +553,14 @@ export const parseImageName = (fullImageName) => {
497
553
  };
498
554
 
499
555
  /**
500
- * Prefer cli on windows or when using tcp/ssh based host.
556
+ * Prefer cli on windows, nerdctl on mac, or when using tcp/ssh based host.
501
557
  *
502
558
  * @returns boolean true if we should use the cli. false otherwise
503
559
  */
504
560
  const needsCliFallback = () => {
561
+ if (_platform() === "darwin" && detectRancherDesktop()) {
562
+ return true;
563
+ }
505
564
  return (
506
565
  isWin ||
507
566
  (process.env.DOCKER_HOST &&
@@ -532,7 +591,10 @@ export const getImage = async (fullImageName) => {
532
591
  return undefined;
533
592
  }
534
593
  if (needsCliFallback()) {
535
- const dockerCmd = process.env.DOCKER_CMD || "docker";
594
+ let dockerCmd = process.env.DOCKER_CMD || "docker";
595
+ if (!process.env.DOCKER_CMD && detectRancherDesktop()) {
596
+ dockerCmd = "nerdctl";
597
+ }
536
598
  let result = spawnSync(dockerCmd, ["pull", fullImageName], {
537
599
  encoding: "utf-8",
538
600
  });
@@ -956,14 +1018,18 @@ export const exportImage = async (fullImageName) => {
956
1018
  let manifestFile = join(tempDir, "manifest.json");
957
1019
  // Windows containers use index.json
958
1020
  const manifestIndexFile = join(tempDir, "index.json");
959
- // On Windows, fallback to invoking cli
1021
+ // On Windows or on mac with Rancher Desktop, fallback to invoking cli
960
1022
  if (needsCliFallback()) {
961
1023
  const imageTarFile = join(tempDir, "image.tar");
1024
+ let dockerCmd = process.env.DOCKER_CMD || "docker";
1025
+ if (!process.env.DOCKER_CMD && detectRancherDesktop()) {
1026
+ dockerCmd = "nerdctl";
1027
+ }
962
1028
  console.log(
963
- `About to export image ${fullImageName} to ${imageTarFile} using docker cli`,
1029
+ `About to export image ${fullImageName} to ${imageTarFile} using ${dockerCmd} cli`,
964
1030
  );
965
1031
  const result = spawnSync(
966
- "docker",
1032
+ dockerCmd,
967
1033
  ["save", "-o", imageTarFile, fullImageName],
968
1034
  {
969
1035
  encoding: "utf-8",
package/evinser.js CHANGED
@@ -202,7 +202,7 @@ export const createAndStoreSlice = async (
202
202
  };
203
203
 
204
204
  export const createSlice = (
205
- purlOrLanguage,
205
+ purlOrLanguages,
206
206
  filePath,
207
207
  sliceType = "usages",
208
208
  options = {},
@@ -215,9 +215,12 @@ export const createSlice = (
215
215
  filePath,
216
216
  )}. Please wait ...`,
217
217
  );
218
- const language = purlOrLanguage.startsWith("pkg:")
219
- ? purlToLanguage(purlOrLanguage, filePath)
220
- : purlOrLanguage;
218
+ const firstLanguage = Array.isArray(purlOrLanguages)
219
+ ? purlOrLanguages[0]
220
+ : purlOrLanguages;
221
+ const language = firstLanguage.startsWith("pkg:")
222
+ ? purlToLanguage(firstLanguage, filePath)
223
+ : firstLanguage;
221
224
  if (!language) {
222
225
  return undefined;
223
226
  }