Skip to content

Commit

Permalink
fix(dotnet): should work with paths that contain spaces (#392)
Browse files Browse the repository at this point in the history
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
  • Loading branch information
asinino and AgentEnder authored Mar 3, 2022
1 parent 9b3b043 commit fa86355
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 75 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@

<p align="center">
<image src="https://user-images.githubusercontent.com/6933928/135131740-66939491-dc3e-4982-82ac-e6584530bb1b.png" alt="nx-dotnet logo"/>
</p>

# NxDotnet

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->

[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-)

<!-- ALL-CONTRIBUTORS-BADGE:END -->

[![Join the chat at https://gitter.im/nx-dotnet-plugin/community](https://badges.gitter.im/nx-dotnet-plugin/community.svg)](https://gitter.im/nx-dotnet-plugin/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Run CI checks](https://github.com/nx-dotnet/nx-dotnet/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/nx-dotnet/nx-dotnet/actions/workflows/main.yml)
Expand Down
2 changes: 1 addition & 1 deletion docs/core/guides/handling-solutions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ To add projects to a solution file by default, you can set the generator default
},
},
}
```

> Note that the generator names in `nx.json` must be the full name. Alias's like `app`, `lib` and so on will not be recognized. Aliases that work on the command line for options, like --solution, are also not supported currently.
Expand All @@ -41,3 +40,4 @@ One option would be totally separated solution files for project graphs that are
### Solution filters
Some IDEs such as Visual Studio support solution filters. These filters would allow for all projects to be visible to the IDE, but can have some performance benefits. The caveats to using separate files can still exist though, but these could be easier to maintain in the long run. Here is a link to the [msdn docs for solution filters](https://docs.microsoft.com/en-us/visualstudio/ide/filtered-solutions?view=vs-2022).
```
57 changes: 50 additions & 7 deletions e2e/core-e2e/tests/nx-dotnet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { joinPathFragments, names } from '@nrwl/devkit';
import {
getPackageManagerCommand,
joinPathFragments,
names,
} from '@nrwl/devkit';
import {
checkFilesExist,
ensureNxProject,
listFiles,
readFile,
runCommand,
runNxCommand,
runNxCommandAsync,
runPackageManagerInstall,
tmpProjPath,
uniq,
Expand All @@ -19,7 +22,7 @@ import { XmlDocument } from 'xmldoc';

import { findProjectFileInPathSync } from '@nx-dotnet/utils';
import { readDependenciesFromNxDepGraph } from '@nx-dotnet/utils/e2e';
import { execSync } from 'child_process';
import { exec, execSync } from 'child_process';
import { ensureDirSync } from 'fs-extra';
import { Workspaces } from '@nrwl/tao/src/shared/workspace';

Expand Down Expand Up @@ -158,9 +161,7 @@ describe('nx-dotnet e2e', () => {
await runNxCommandAsync(
`generate @nx-dotnet/core:app ${app} --language="C#" --template="webapi"`,
);
const promise = runNxCommandAsync(`lint ${app}`, {
silenceError: true,
}).then((x) => x.stderr);
const promise = runNxCommandAsync(`lint ${app}`).then((x) => x.stderr);
await expect(promise).resolves.toContain('WHITESPACE');
});
});
Expand Down Expand Up @@ -269,8 +270,9 @@ describe('nx-dotnet e2e', () => {
// For solution handling, defaults fall back to if a file exists.
// This ensures that the tests are ran in a clean state, without previous
// test projects interfering with the test.
beforeEach(() => {
beforeAll(() => {
ensureNxProject('@nx-dotnet/core', 'dist/packages/core');
initializeGitRepo(e2eDir);
}, 1500000);

it("shouldn't create a solution by default if not specified", async () => {
Expand Down Expand Up @@ -394,3 +396,44 @@ function initializeGitRepo(cwd: string) {
runCommand('git add .');
runCommand('git commit -m "initial commit"');
}

function runCommandAsync(
command: string,
opts = {
silenceError: false,
nxVerboseLogging: true,
},
) {
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
exec(
command,
{
cwd: e2eDir,
env: opts.nxVerboseLogging
? { ...process.env, NX_VERBOSE_LOGGING: 'true' }
: process.env,
},
(err, stdout, stderr) => {
if (!opts.silenceError && err) {
reject(err);
}
resolve({ stdout, stderr });
},
);
});
}
/**
* Run a nx command asynchronously inside the e2e directory
* @param command
* @param opts
*/
function runNxCommandAsync(
command: string,
opts = {
silenceError: false,
nxVerboseLogging: true,
},
) {
const pmc = getPackageManagerCommand();
return runCommandAsync(`${pmc.exec} nx ${command}`, opts);
}
134 changes: 68 additions & 66 deletions packages/dotnet/src/lib/core/dotnet.client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ChildProcess, execSync, spawn } from 'child_process';

import { getParameterString, swapKeysUsingMap } from '@nx-dotnet/utils';
import { getSpawnParameterArray, swapKeysUsingMap } from '@nx-dotnet/utils';
import { ChildProcess, spawn, spawnSync } from 'child_process';

import {
addPackageKeyMap,
Expand All @@ -25,70 +24,57 @@ export class DotNetClient {
constructor(private cliCommand: LoadedCLI, public cwd?: string) {}

new(template: dotnetTemplate, parameters?: dotnetNewOptions): void {
let cmd = `${this.cliCommand.command} new ${template}`;
const params = [`new`, template];
if (parameters) {
parameters = swapKeysUsingMap(parameters, newKeyMap);
const paramString = parameters ? getParameterString(parameters) : '';
cmd = `${cmd} ${paramString}`;
params.push(...getSpawnParameterArray(parameters));
}
return this.logAndExecute(cmd);
return this.logAndExecute(params);
}

build(project: string, parameters?: dotnetBuildOptions): void {
let cmd = `${this.cliCommand.command} build "${project}"`;
const params = [`build`, project];
if (parameters) {
parameters = swapKeysUsingMap(parameters, buildKeyMap);
const paramString = parameters ? getParameterString(parameters) : '';
cmd = `${cmd} ${paramString}`;
params.push(...getSpawnParameterArray(parameters));
}
return this.logAndExecute(cmd);
return this.logAndExecute(params);
}

run(
project: string,
watch = false,
parameters?: dotnetRunOptions,
): ChildProcess {
let cmd = watch
? `watch --project "${project}" run`
: `run --project "${project}"`;
const params = watch
? [`watch`, `--project`, project, `run`]
: [`run`, `--project`, project];
if (parameters) {
parameters = swapKeysUsingMap(parameters, runKeyMap);
const paramString = parameters ? getParameterString(parameters) : '';
cmd = `${cmd} ${paramString}`;
params.push(...getSpawnParameterArray(parameters));
}
console.log(`Executing Command: dotnet ${cmd}`);
return spawn(this.cliCommand.command, cmd.split(' '), {
stdio: 'inherit',
cwd: this.cwd,
});

return this.logAndSpawn(params);
}

test(
project: string,
watch?: boolean,
parameters?: dotnetTestOptions,
): void | ChildProcess {
let cmd = watch ? `watch --project "${project}" test` : `test "${project}"`;
cmd = `${this.cliCommand.command} ${cmd}`;
const params = watch
? [`watch`, `--project`, project, `test`]
: [`test`, project];

if (parameters) {
const mappedParameters = swapKeysUsingMap(parameters, testKeyMap);
const paramString = getParameterString(mappedParameters);
cmd = `${cmd} ${paramString}`;
parameters = swapKeysUsingMap(parameters, testKeyMap);
params.push(...getSpawnParameterArray(parameters));
}
if (!watch) {
return this.logAndExecute(cmd);
return this.logAndExecute(params);
} else {
console.log(`Executing Command: ${cmd}`);
const params = cmd
.split(' ')
.slice(1)
.filter((x) => x.length);
return spawn(this.cliCommand.command, params, {
stdio: 'inherit',
cwd: this.cwd,
});
const slicedParams = params.slice(1).filter((x) => x.length);
return this.logAndSpawn(slicedParams);
}
}

Expand All @@ -97,19 +83,16 @@ export class DotNetClient {
pkg: string,
parameters?: dotnetAddPackageOptions,
): void {
let cmd = `${this.cliCommand.command} add "${project}" package ${pkg}`;
const params = [`add`, project, `package`, pkg];
if (parameters) {
parameters = swapKeysUsingMap(parameters, addPackageKeyMap);
const paramString = parameters ? getParameterString(parameters) : '';
cmd = `${cmd} ${paramString}`;
params.push(...getSpawnParameterArray(parameters));
}
return this.logAndExecute(cmd);
return this.logAndExecute(params);
}

addProjectReference(hostCsProj: string, targetCsProj: string): void {
return this.logAndExecute(
`${this.cliCommand.command} add ${hostCsProj} reference ${targetCsProj}`,
);
return this.logAndExecute([`add`, hostCsProj, `reference`, targetCsProj]);
}

publish(
Expand All @@ -118,66 +101,85 @@ export class DotNetClient {
publishProfile?: string,
extraParameters?: string,
): void {
let cmd = `${this.cliCommand.command} publish "${project}"`;
const params = [`publish`, `"${project}"`];
if (parameters) {
parameters = swapKeysUsingMap(parameters, publishKeyMap);
const paramString = parameters ? getParameterString(parameters) : '';
cmd = `${cmd} ${paramString}`;
params.push(...getSpawnParameterArray(parameters));
}
if (publishProfile) {
cmd = `${cmd} -p:PublishProfile=${publishProfile}`;
params.push(`-p:PublishProfile=${publishProfile}`);
}
if (extraParameters) {
cmd = `${cmd} ${extraParameters}`;
params.push(`${extraParameters}`);
}
return this.logAndExecute(cmd);
return this.logAndExecute(params);
}

installTool(tool: string): void {
const cmd = `${this.cliCommand.command} tool install ${tool}`;
const cmd = [`tool`, `install`, tool];
return this.logAndExecute(cmd);
}

restorePackages(project: string): void {
const cmd = `${this.cliCommand.command} restore "${project}"`;
const cmd = [`restore`, project];
return this.logAndExecute(cmd);
}

restoreTools(): void {
const cmd = `${this.cliCommand.command} tool restore`;
const cmd = [`tool`, `restore`];
return this.logAndExecute(cmd);
}

format(project: string, parameters?: dotnetFormatOptions): void {
let cmd = `${this.cliCommand.command} format "${project}"`;
const params = [`format`, project];
if (parameters) {
parameters = swapKeysUsingMap(parameters, formatKeyMap);
const paramString = parameters ? getParameterString(parameters) : '';
cmd = `${cmd} ${paramString}`;
params.push(...getSpawnParameterArray(parameters));
}
return this.logAndExecute(cmd);
return this.logAndExecute(params);
}

addProjectToSolution(solutionFile: string, project: string) {
const cmd = `${this.cliCommand.command} sln "${solutionFile}" add "${project}"`;
this.logAndExecute(cmd);
const params = [`sln`, solutionFile, `add`, project];
this.logAndExecute(params);
}

getSdkVersion(): Buffer {
const cmd = 'dotnet --version';
return this.execute(cmd);
return this.execute(['--version']);
}

printSdkVersion(): void {
this.logAndExecute('dotnet --version');
this.logAndExecute(['--version']);
}

private logAndExecute(cmd: string): void {
console.log(`Executing Command: ${cmd}`);
execSync(cmd, { stdio: 'inherit', cwd: this.cwd || process.cwd() });
private logAndExecute(params: string[]): void {
console.log(
`Executing Command: ${this.cliCommand.command} "${params.join('" "')}"`,
);
spawnSync(this.cliCommand.command, params, {
cwd: this.cwd || process.cwd(),
stdio: 'inherit',
});
}

private execute(cmd: string): Buffer {
return execSync(cmd, { cwd: this.cwd || process.cwd() });
private execute(params: string[]): Buffer {
return spawnSync(this.cliCommand.command, params, {
cwd: this.cwd || process.cwd(),
})
.output.filter((buf) => buf !== null)
.reduce(
(acc, buf) => Buffer.concat([acc as Buffer, buf as Buffer]),
Buffer.from(''),
) as Buffer;
}

private logAndSpawn(params: string[]): ChildProcess {
console.log(
`Executing Command: ${this.cliCommand.command} "${params.join('" "')}"`,
);
return spawn(this.cliCommand.command, params, {
stdio: 'inherit',
cwd: this.cwd || process.cwd(),
});
}
}
21 changes: 21 additions & 0 deletions packages/utils/src/lib/utility-functions/parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,24 @@ export function getParameterString(
}
}, '');
}

/**
* Transforms an object of parameters into an array of strings with key followed by the corresponding value.
* @param parameters Parameters to transform into CLI arguments
* @returns Parameter string to be appended to CLI command
*/
export function getSpawnParameterArray(
parameters: Record<string, boolean | string>,
): string[] {
return Object.entries(parameters).reduce((acc, [flag, value]) => {
if (typeof value === 'boolean' || !value) {
if (value) {
return [...acc, `--${flag}`];
} else {
return acc;
}
} else {
return [...acc, `--${flag}`, value.toString()];
}
}, new Array<string>());
}

0 comments on commit fa86355

Please sign in to comment.