diff --git a/.github/skills/azure-pipelines/SKILL.md b/.github/skills/azure-pipelines/SKILL.md index 97904019952..8903496983c 100644 --- a/.github/skills/azure-pipelines/SKILL.md +++ b/.github/skills/azure-pipelines/SKILL.md @@ -92,8 +92,8 @@ node .github/skills/azure-pipelines/azure-pipeline.ts queue --parameter "VSCODE_ |--------|-------------| | `--branch ` | Source branch to build (default: current git branch) | | `--definition ` | Pipeline definition ID (default: 111) | -| `--parameter ` | Pipeline parameter in `KEY=value` format (repeatable) | -| `--parameters ` | Space-separated parameters in `KEY=value KEY2=value2` format | +| `--parameter ` | Pipeline parameter in `KEY=value` format (repeatable); **use this when the value contains spaces** | +| `--parameters ` | Space-separated parameters in `KEY=value KEY2=value2` format; values **must not** contain spaces | | `--dry-run` | Print the command without executing | ### Product Build Queue Parameters (`build/azure-pipelines/product-build.yml`) diff --git a/.github/skills/azure-pipelines/azure-pipeline.ts b/.github/skills/azure-pipelines/azure-pipeline.ts index fbb74b5dd4a..3032f01c6fd 100644 --- a/.github/skills/azure-pipelines/azure-pipeline.ts +++ b/.github/skills/azure-pipelines/azure-pipeline.ts @@ -556,7 +556,11 @@ class AzureDevOpsClient { protected runAzCommand(args: string[]): Promise { return new Promise((resolve, reject) => { - const proc = spawn('az', args, { shell: true }); + // Use shell: false so that argument values with spaces are passed verbatim + // to the process without shell word-splitting. On Windows, az is a .cmd + // file and cannot be executed directly, so we must use az.cmd. + const azBin = process.platform === 'win32' ? 'az.cmd' : 'az'; + const proc = spawn(azBin, args, { shell: false }); let stdout = ''; let stderr = ''; @@ -778,8 +782,8 @@ function printQueueUsage(): void { console.log('Options:'); console.log(' --branch Source branch to build (default: current git branch)'); console.log(' --definition Pipeline definition ID (default: 111)'); - console.log(' --parameter Pipeline parameter in "KEY=value" format (repeatable)'); - console.log(' --parameters Space-separated parameter list in "KEY=value KEY2=value2" format'); + console.log(' --parameter Pipeline parameter in "KEY=value" format (repeatable); use this for values with spaces'); + console.log(' --parameters Space-separated parameter list in "KEY=value KEY2=value2" format (values must not contain spaces)'); console.log(' --dry-run Print the command without executing'); console.log(' --help Show this help message'); console.log(''); @@ -887,7 +891,8 @@ async function runQueueCommand(args: string[]): Promise { cmdArgs.push('--parameters', ...parsedArgs.parameters); } cmdArgs.push('--output', 'json'); - console.log(`az ${cmdArgs.join(' ')}`); + const displayArgs = cmdArgs.map(a => a.includes(' ') ? `"${a}"` : a); + console.log(`az ${displayArgs.join(' ')}`); process.exit(0); } @@ -1539,6 +1544,15 @@ async function runAllTests(): Promise { assert.ok(cmd.includes('OTHER=test')); }); + it('queueBuild passes parameter values with spaces verbatim', async () => { + const client = new TestableAzureDevOpsClient(ORGANIZATION, PROJECT); + await client.queueBuild('111', 'main', ['VSCODE_BUILD_TYPE=Product Build']); + + const cmd = client.capturedCommands[0]; + assert.ok(cmd.includes('--parameters')); + assert.deepStrictEqual(cmd[cmd.indexOf('--parameters') + 1], 'VSCODE_BUILD_TYPE=Product Build'); + }); + it('getBuild constructs correct az command', async () => { const client = new TestableAzureDevOpsClient(ORGANIZATION, PROJECT); await client.getBuild('12345');