Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions src/commands/manifest/cmd-manifest-dotnet.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { describe, expect } from 'vitest'

import constants, {
FLAG_CONFIG,
FLAG_DRY_RUN,
FLAG_HELP,
} from '../../../src/constants.mts'
import { cmdit, spawnSocketCli } from '../../../test/utils.mts'

describe('socket manifest dotnet', async () => {
const { binCliPath } = constants

cmdit(
['manifest', 'dotnet', FLAG_HELP, FLAG_CONFIG, '{}'],
`should support ${FLAG_HELP}`,
async cmd => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
expect(stdout).toMatchInlineSnapshot(`
"[beta] Generate Socket facts files from .NET solutions and projects

Usage
$ socket manifest dotnet [options] [CWD=.|FILE]

Options
--bin Location of the dotnet binary to use, default: dotnet on PATH
--dotnet-opts Additional options to pass on to \`dotnet restore\`, e.g. \`-p:Configuration=Release --configfile nuget.config\`. MSBuild properties among them also apply to project evaluation
--exclude-configs Comma-separated glob patterns; target frameworks matching any pattern are skipped (applied after --include-configs)
--exclude-paths List of glob patterns to exclude from manifest/facts generation. Patterns are anchored micromatch globs matched relative to CWD (\`--cwd\` if set): \`tests\` matches only \`<cwd>/tests\`; use \`**/tests\` to match at any depth. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or multiple flags. An excluded JVM build module is not analysed as a project of its own, but where a project that is not excluded depends on it, its code is analysed as part of that project. A build module outside CWD cannot be excluded.
--ignore-unresolved Warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)
--include-configs Comma-separated glob patterns matched against target frameworks (case-sensitive; \`*\`, \`?\`, and \`[...]\` wildcards). Only target frameworks matching at least one pattern are read, e.g. \`net8.0\`. A runtime-specific target (\`net8.0/win-x64\`) also matches its framework. Default: every target framework
--verbose Print debug messages

Emits a \`<file>.socket.facts.json\` per solution or project describing its
resolved NuGet dependency graph. Given a directory, it builds every
solution (.sln, .slnx) and project (.csproj, .fsproj, .vbproj) at its top
level, solutions first, skipping projects a solution already covers; given
a file, just that one (a .slnf solution filter too).

Dependencies come from your own \`dotnet restore\` (same SDK, global.json,
nuget.config and credential providers), so a build that restores for you
restores here. PackageReference projects (central package management and
lock files included) and packages.config projects are supported. An
unresolved dependency is a fatal error unless --ignore-unresolved.

Support is beta. Please report issues or give us feedback on what's missing.

Examples

$ socket manifest dotnet .
$ socket manifest dotnet ./App.sln
$ socket manifest dotnet --dotnet-opts="-p:Configuration=Release" ."
`)
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
"
_____ _ _ /---------------
| __|___ ___| |_ ___| |_ | CLI: <redacted>
|__ | * | _| '_| -_| _| | token: <redacted>, org: <redacted>
|_____|___|___|_,_|___|_|.dev | Command: \`socket manifest dotnet\`, cwd: <redacted>"
`)

expect(code, 'explicit help should exit with code 0').toBe(0)
expect(stderr, 'banner includes base command').toContain(
'`socket manifest dotnet`',
)
},
)

cmdit(
['manifest', 'dotnet', FLAG_DRY_RUN, FLAG_CONFIG, '{}'],
'should require args with just dry-run',
async cmd => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
"
_____ _ _ /---------------
| __|___ ___| |_ ___| |_ | CLI: <redacted>
|__ | * | _| '_| -_| _| | token: <redacted>, org: <redacted>
|_____|___|___|_,_|___|_|.dev | Command: \`socket manifest dotnet\`, cwd: <redacted>"
`)

expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
},
)
})
96 changes: 61 additions & 35 deletions src/commands/manifest/detect-manifest-actions.test.mts
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'

import { afterEach, beforeEach, describe, expect, it } from 'vitest'

import { detectManifestActions } from './detect-manifest-actions.mts'
import {
detectManifestActions,
findDotnetEntries,
} from './detect-manifest-actions.mts'

import type { SocketJson } from '../../utils/socket-json.mts'

function mkTmp(): string {
return mkdtempSync(path.join(os.tmpdir(), 'detect-manifest-bazel-'))
}
let cwd: string

beforeEach(() => {
cwd = mkdtempSync(path.join(os.tmpdir(), 'detect-manifest-'))
})

afterEach(() => {
rmSync(cwd, { recursive: true, force: true })
})

function touch(dir: string, name: string): void {
writeFileSync(path.join(dir, name), '')
}

describe('detectManifestActions — bazel detector', () => {
let cwd: string

beforeEach(() => {
cwd = mkTmp()
})

afterEach(() => {
rmSync(cwd, { recursive: true, force: true })
})

it('skips bazel when defaults.manifest.bazel.disabled is true', async () => {
touch(cwd, 'MODULE.bazel')
const result = await detectManifestActions(
Expand Down Expand Up @@ -75,16 +74,6 @@ describe('detectManifestActions — bazel detector', () => {
})

describe('detectManifestActions — gradle detector', () => {
let cwd: string

beforeEach(() => {
cwd = mkTmp()
})

afterEach(() => {
rmSync(cwd, { recursive: true, force: true })
})

it('detects build.gradle', async () => {
touch(cwd, 'build.gradle')
const result = await detectManifestActions(null, cwd)
Expand Down Expand Up @@ -140,16 +129,6 @@ describe('detectManifestActions — gradle detector', () => {
})

describe('detectManifestActions — conda detector', () => {
let cwd: string

beforeEach(() => {
cwd = mkTmp()
})

afterEach(() => {
rmSync(cwd, { recursive: true, force: true })
})

it('detects environment.yml and reports it as the conda file', async () => {
touch(cwd, 'environment.yml')
const result = await detectManifestActions(null, cwd)
Expand Down Expand Up @@ -193,3 +172,50 @@ describe('detectManifestActions — conda detector', () => {
expect(result.count).toBe(0)
})
})

describe('detectManifestActions — dotnet detector', () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: this is the fourth copy of the temp-dir hooks in the file, and one file-level pair would replace them all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[agent] Fixed in 30d5794 - one file-level pair replaces all four.

it.each([
'App.sln',
'App.slnx',
'App.csproj',
'App.fsproj',
'App.vbproj',
'APP.SLN',
])('detects %s', async name => {
touch(cwd, name)
expect((await detectManifestActions(null, cwd)).dotnet).toBe(true)
})

it('ignores a solution filter, other files and directories named like projects', async () => {
touch(cwd, 'App.slnf')
touch(cwd, 'App.sqlproj')
mkdirSync(path.join(cwd, 'Lib.csproj'))
expect((await detectManifestActions(null, cwd)).dotnet).toBe(false)
})

it('skips dotnet when defaults.manifest.dotnet.disabled is true', async () => {
touch(cwd, 'App.sln')
const sockJson = {
defaults: { manifest: { dotnet: { disabled: true } } },
} as SocketJson
expect((await detectManifestActions(sockJson, cwd)).dotnet).toBe(false)
})

it('lists solutions before projects, each sorted by name', () => {
for (const name of [
'b.csproj',
'A.vbproj',
'Z.sln',
'M.slnx',
'notes.txt',
]) {
touch(cwd, name)
}
expect(findDotnetEntries(cwd)).toEqual([
'M.slnx',
'Z.sln',
'A.vbproj',
'b.csproj',
])
})
})
Loading
Loading