Skip to content

Commit ee925e3

Browse files
committed
feat(manifest): add socket manifest dotnet (1.1.126, Coana 15.5.6)
Add a `socket manifest dotnet` command that generates a Socket facts file (`.socket.facts.json`) from a .NET project by delegating to the Coana CLI's `manifest dotnet` command (which runs a bundled NuGet/MSBuild resolver for SDK-style and legacy `packages.config` projects), mirroring the existing gradle/sbt/maven facts flows. Includes detection of `*.csproj`/`*.fsproj`/ `*.vbproj`/`*.sln` project/solution files, `socket manifest auto` wiring, the `socket manifest setup` configurator, socket.json defaults, and `--dotnet-opts` / `--bin` pass-through. Unlike the JVM tools, the .NET resolver has no configuration filters, so `--include-configs`/`--exclude-configs` are intentionally not exposed; only `--ignore-unresolved` and `--dotnet-opts` apply. Bump Coana CLI to 15.5.6, which adds the `manifest dotnet` command this delegates to.
1 parent b0c243b commit ee925e3

14 files changed

Lines changed: 477 additions & 16 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [1.1.126](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.126) - 2026-06-22
8+
9+
### Added
10+
- New `socket manifest dotnet` command generates a Socket facts file (`.socket.facts.json`) from a .NET project (`.csproj`/`.fsproj`/`.vbproj`/`.sln`). It runs the `dotnet` host on PATH to resolve NuGet/MSBuild dependencies (SDK-style and legacy `packages.config` projects), auto-detects your project, and plugs into `socket manifest auto` and the `socket manifest setup` configurator. Use `--bin` to point at a specific dotnet host and `--dotnet-opts` to pass options through.
11+
12+
### Changed
13+
- Updated the Coana CLI to v `15.5.6`.
14+
715
## [1.1.125](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.125) - 2026-06-22
816

917
### Added

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "socket",
3-
"version": "1.1.125",
3+
"version": "1.1.126",
44
"description": "CLI for Socket.dev",
55
"homepage": "https://github.com/SocketDev/socket-cli",
66
"license": "MIT",
@@ -96,7 +96,7 @@
9696
"@babel/preset-typescript": "7.27.1",
9797
"@babel/runtime": "7.28.4",
9898
"@biomejs/biome": "2.2.4",
99-
"@coana-tech/cli": "15.5.5",
99+
"@coana-tech/cli": "15.5.6",
100100
"@cyclonedx/cdxgen": "12.1.2",
101101
"@dotenvx/dotenvx": "1.49.0",
102102
"@eslint/compat": "1.3.2",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/commands/manifest/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ have a working cdxgen configuration.
140140
Converts a Conda `environment.yml` file to a Python `requirements.txt` so the
141141
Socket scan pipeline can consume the resulting manifest.
142142

143+
## socket manifest dotnet [beta]
144+
145+
Generates a Socket facts file (`.socket.facts.json`) from a .NET project
146+
(`*.csproj` / `*.fsproj` / `*.vbproj` / `*.sln`), using the `dotnet` host on
147+
PATH to run a bundled NuGet/MSBuild resolver (SDK-style and legacy
148+
`packages.config` projects are both supported). Override the host with `--bin`
149+
and pass extra options through with `--dotnet-opts`. Unlike the JVM generators
150+
there are no configuration filters (`--include-configs` / `--exclude-configs`
151+
do not apply).
152+
143153
## socket manifest gradle [beta]
144154

145155
Uses Gradle (via the project's `gradlew`) to emit a `pom.xml` per subproject,
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import path from 'node:path'
2+
3+
import { debugFn } from '@socketsecurity/registry/lib/debug'
4+
import { logger } from '@socketsecurity/registry/lib/logger'
5+
6+
import { convertDotnetToFacts } from './convert-dotnet-to-facts.mts'
7+
import { parseBuildToolOpts } from './parse-build-tool-opts.mts'
8+
import constants, { SOCKET_JSON } from '../../constants.mts'
9+
import { commonFlags } from '../../flags.mts'
10+
import { checkCommandInput } from '../../utils/check-input.mts'
11+
import { getOutputKind } from '../../utils/get-output-kind.mts'
12+
import { meowOrExit } from '../../utils/meow-with-subcommands.mts'
13+
import { getFlagListOutput } from '../../utils/output-formatting.mts'
14+
import { readOrDefaultSocketJson } from '../../utils/socket-json.mts'
15+
16+
import type {
17+
CliCommandConfig,
18+
CliCommandContext,
19+
} from '../../utils/meow-with-subcommands.mts'
20+
21+
const config: CliCommandConfig = {
22+
commandName: 'dotnet',
23+
description:
24+
'[beta] Generate a Socket facts file from a .NET project (`.csproj`/`.sln`/etc)',
25+
hidden: false,
26+
flags: {
27+
...commonFlags,
28+
bin: {
29+
type: 'string',
30+
description:
31+
'Location of the dotnet host to use, default: dotnet on PATH',
32+
},
33+
ignoreUnresolved: {
34+
type: 'boolean',
35+
description:
36+
'Warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)',
37+
},
38+
dotnetOpts: {
39+
type: 'string',
40+
description: 'Additional options to pass on to the bundled dotnet tool',
41+
},
42+
verbose: {
43+
type: 'boolean',
44+
description: 'Print debug messages',
45+
},
46+
},
47+
help: (command, config) => `
48+
Usage
49+
$ ${command} [options] [CWD=.]
50+
51+
Options
52+
${getFlagListOutput(config.flags)}
53+
54+
Emits a single \`.socket.facts.json\` describing the resolved dependency
55+
graph of your .NET project, using the \`dotnet\` host on PATH to run a
56+
bundled NuGet/MSBuild resolver (SDK-style projects and legacy
57+
\`packages.config\` are both supported). An unresolved dependency is a fatal
58+
error; pass --ignore-unresolved to warn and continue instead.
59+
60+
Unlike the JVM generators there are no configuration filters: .NET
61+
resolution has no equivalent of Gradle/Maven configurations, so
62+
--include-configs / --exclude-configs do not apply.
63+
64+
You can specify --bin to override the path to the \`dotnet\` host to invoke,
65+
and --dotnet-opts to pass extra options through to the bundled tool.
66+
67+
Support is beta. Please report issues or give us feedback on what's missing.
68+
69+
Examples
70+
71+
$ ${command} .
72+
$ ${command} --bin=/usr/local/share/dotnet/dotnet .
73+
`,
74+
}
75+
76+
export const cmdManifestDotnet = {
77+
description: config.description,
78+
hidden: config.hidden,
79+
run,
80+
}
81+
82+
async function run(
83+
argv: string[] | readonly string[],
84+
importMeta: ImportMeta,
85+
{ parentName }: CliCommandContext,
86+
): Promise<void> {
87+
const cli = meowOrExit({
88+
argv,
89+
config,
90+
importMeta,
91+
parentName,
92+
})
93+
94+
const { json = false, markdown = false } = cli.flags
95+
96+
const dryRun = !!cli.flags['dryRun']
97+
98+
// TODO: Implement json/md further.
99+
const outputKind = getOutputKind(json, markdown)
100+
101+
let [cwd = '.'] = cli.input
102+
// Note: path.resolve vs .join:
103+
// If given path is absolute then cwd should not affect it.
104+
cwd = path.resolve(process.cwd(), cwd)
105+
106+
const sockJson = readOrDefaultSocketJson(cwd)
107+
108+
debugFn(
109+
'inspect',
110+
`override: ${SOCKET_JSON} dotnet`,
111+
sockJson?.defaults?.manifest?.dotnet,
112+
)
113+
114+
let { bin, dotnetOpts, ignoreUnresolved, verbose } = cli.flags
115+
116+
// Set defaults for any flag/arg that is not given. Check socket.json first.
117+
if (!bin) {
118+
if (sockJson.defaults?.manifest?.dotnet?.bin) {
119+
bin = sockJson.defaults?.manifest?.dotnet?.bin
120+
logger.info(`Using default --bin from ${SOCKET_JSON}:`, bin)
121+
} else {
122+
bin = 'dotnet'
123+
}
124+
}
125+
if (!dotnetOpts) {
126+
if (sockJson.defaults?.manifest?.dotnet?.dotnetOpts) {
127+
dotnetOpts = sockJson.defaults?.manifest?.dotnet?.dotnetOpts
128+
logger.info(
129+
`Using default --dotnet-opts from ${SOCKET_JSON}:`,
130+
dotnetOpts,
131+
)
132+
} else {
133+
dotnetOpts = ''
134+
}
135+
}
136+
if (ignoreUnresolved === undefined) {
137+
if (sockJson.defaults?.manifest?.dotnet?.ignoreUnresolved !== undefined) {
138+
ignoreUnresolved = sockJson.defaults?.manifest?.dotnet?.ignoreUnresolved
139+
logger.info(
140+
`Using default --ignore-unresolved from ${SOCKET_JSON}:`,
141+
ignoreUnresolved,
142+
)
143+
} else {
144+
ignoreUnresolved = false
145+
}
146+
}
147+
if (verbose === undefined) {
148+
if (sockJson.defaults?.manifest?.dotnet?.verbose !== undefined) {
149+
verbose = sockJson.defaults?.manifest?.dotnet?.verbose
150+
logger.info(`Using default --verbose from ${SOCKET_JSON}:`, verbose)
151+
} else {
152+
verbose = false
153+
}
154+
}
155+
156+
if (verbose) {
157+
logger.group('- ', parentName, config.commandName, ':')
158+
logger.group('- flags:', cli.flags)
159+
logger.groupEnd()
160+
logger.log('- input:', cli.input)
161+
logger.groupEnd()
162+
}
163+
164+
const wasValidInput = checkCommandInput(outputKind, {
165+
nook: true,
166+
test: cli.input.length <= 1,
167+
message: 'Can only accept one DIR (make sure to escape spaces!)',
168+
fail: 'received ' + cli.input.length,
169+
})
170+
if (!wasValidInput) {
171+
return
172+
}
173+
174+
if (verbose) {
175+
logger.group()
176+
logger.info('- cwd:', cwd)
177+
logger.info('- dotnet bin:', bin)
178+
logger.groupEnd()
179+
}
180+
181+
if (dryRun) {
182+
logger.log(constants.DRY_RUN_BAILING_NOW)
183+
return
184+
}
185+
186+
const parsedDotnetOpts = parseBuildToolOpts(String(dotnetOpts || ''))
187+
188+
await convertDotnetToFacts({
189+
bin: String(bin),
190+
cwd,
191+
dotnetOpts: parsedDotnetOpts,
192+
ignoreUnresolved: Boolean(ignoreUnresolved),
193+
verbose: Boolean(verbose),
194+
})
195+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { describe, expect } from 'vitest'
2+
3+
import constants, {
4+
FLAG_CONFIG,
5+
FLAG_DRY_RUN,
6+
FLAG_HELP,
7+
} from '../../../src/constants.mts'
8+
import { cmdit, spawnSocketCli } from '../../../test/utils.mts'
9+
10+
describe('socket manifest dotnet', async () => {
11+
const { binCliPath } = constants
12+
13+
cmdit(
14+
['manifest', 'dotnet', FLAG_HELP, FLAG_CONFIG, '{}'],
15+
`should support ${FLAG_HELP}`,
16+
async cmd => {
17+
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
18+
expect(stdout).toMatchInlineSnapshot(`
19+
"[beta] Generate a Socket facts file from a .NET project (\`.csproj\`/\`.sln\`/etc)
20+
21+
Usage
22+
$ socket manifest dotnet [options] [CWD=.]
23+
24+
Options
25+
--bin Location of the dotnet host to use, default: dotnet on PATH
26+
--dotnet-opts Additional options to pass on to the bundled dotnet tool
27+
--ignore-unresolved Warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)
28+
--verbose Print debug messages
29+
30+
Emits a single \`.socket.facts.json\` describing the resolved dependency
31+
graph of your .NET project, using the \`dotnet\` host on PATH to run a
32+
bundled NuGet/MSBuild resolver (SDK-style projects and legacy
33+
\`packages.config\` are both supported). An unresolved dependency is a fatal
34+
error; pass --ignore-unresolved to warn and continue instead.
35+
36+
Unlike the JVM generators there are no configuration filters: .NET
37+
resolution has no equivalent of Gradle/Maven configurations, so
38+
--include-configs / --exclude-configs do not apply.
39+
40+
You can specify --bin to override the path to the \`dotnet\` host to invoke,
41+
and --dotnet-opts to pass extra options through to the bundled tool.
42+
43+
Support is beta. Please report issues or give us feedback on what's missing.
44+
45+
Examples
46+
47+
$ socket manifest dotnet .
48+
$ socket manifest dotnet --bin=/usr/local/share/dotnet/dotnet ."
49+
`)
50+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
51+
"
52+
_____ _ _ /---------------
53+
| __|___ ___| |_ ___| |_ | CLI: <redacted>
54+
|__ | * | _| '_| -_| _| | token: <redacted>, org: <redacted>
55+
|_____|___|___|_,_|___|_|.dev | Command: \`socket manifest dotnet\`, cwd: <redacted>"
56+
`)
57+
58+
expect(code, 'explicit help should exit with code 0').toBe(0)
59+
expect(stderr, 'banner includes base command').toContain(
60+
'`socket manifest dotnet`',
61+
)
62+
},
63+
)
64+
65+
cmdit(
66+
['manifest', 'dotnet', FLAG_DRY_RUN, FLAG_CONFIG, '{}'],
67+
'should require args with just dry-run',
68+
async cmd => {
69+
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
70+
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
71+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
72+
"
73+
_____ _ _ /---------------
74+
| __|___ ___| |_ ___| |_ | CLI: <redacted>
75+
|__ | * | _| '_| -_| _| | token: <redacted>, org: <redacted>
76+
|_____|___|___|_,_|___|_|.dev | Command: \`socket manifest dotnet\`, cwd: <redacted>"
77+
`)
78+
79+
expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
80+
},
81+
)
82+
})

src/commands/manifest/cmd-manifest.mts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { cmdManifestBazel } from './bazel/cmd-manifest-bazel.mts'
22
import { cmdManifestAuto } from './cmd-manifest-auto.mts'
33
import { cmdManifestCdxgen } from './cmd-manifest-cdxgen.mts'
44
import { cmdManifestConda } from './cmd-manifest-conda.mts'
5+
import { cmdManifestDotnet } from './cmd-manifest-dotnet.mts'
56
import { cmdManifestGradle } from './cmd-manifest-gradle.mts'
67
import { cmdManifestKotlin } from './cmd-manifest-kotlin.mts'
78
import { cmdManifestMaven } from './cmd-manifest-maven.mts'
@@ -39,8 +40,8 @@ const config: CliCommandConfig = {
3940
configurations available. See \`manifest <language> --help\` for usage details
4041
per language.
4142
42-
Currently supported language: bazel [beta], gradle [beta], kotlin (through
43-
gradle) [beta], maven [beta], scala [beta].
43+
Currently supported language: bazel [beta], dotnet [beta], gradle [beta],
44+
kotlin (through gradle) [beta], maven [beta], scala [beta].
4445
4546
Examples
4647
@@ -73,6 +74,7 @@ async function run(
7374
bazel: cmdManifestBazel,
7475
cdxgen: cmdManifestCdxgen,
7576
conda: cmdManifestConda,
77+
dotnet: cmdManifestDotnet,
7678
gradle: cmdManifestGradle,
7779
kotlin: cmdManifestKotlin,
7880
maven: cmdManifestMaven,

src/commands/manifest/cmd-manifest.test.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('socket manifest', async () => {
2727
bazel [beta] Bazel SBOM support \\u2014 generate manifest files for a Bazel project (Maven, PyPI)
2828
cdxgen Run cdxgen for SBOM generation
2929
conda [beta] Convert a Conda environment.yml file to a python requirements.txt
30+
dotnet [beta] Generate a Socket facts file from a .NET project (\`.csproj\`/\`.sln\`/etc)
3031
gradle [beta] Generate a Socket facts file (or \`pom.xml\` with --pom) for a Gradle/Java/Kotlin/etc project
3132
kotlin [beta] Generate a Socket facts file (or \`pom.xml\` with --pom) for a Kotlin project
3233
maven [beta] Generate a Socket facts file from a Maven \`pom.xml\` project

0 commit comments

Comments
 (0)