Skip to content
This repository was archived by the owner on Jul 20, 2026. It is now read-only.

Commit 4799004

Browse files
authored
Convert default command template values into ShellQuotedString format (#4433)
* Convert default command template values into ShellQuotedString format * Cmd doesn't like escaped strings, so switch to strong quotes * Handle build image command * Remove some duplicated code
1 parent c64c35d commit 4799004

2 files changed

Lines changed: 56 additions & 9 deletions

File tree

src/commands/images/buildImage.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IActionContext, UserCancelledError } from "@microsoft/vscode-azext-utils";
7+
import { quoted } from "@microsoft/vscode-container-client";
78
import * as path from "path";
89
import * as vscode from "vscode";
910
import { ext } from "../../extensionVariables";
@@ -48,10 +49,10 @@ export async function buildImage(context: IActionContext, dockerFileUri: vscode.
4849
contextPath
4950
);
5051

51-
// Replace '${tag}' if needed. Tag is a special case because we don't want to prompt unless necessary, so must manually replace it.
52-
if (tagRegex.test(terminalCommand.command)) {
52+
const getImageName = async (): Promise<string> => {
5353
const absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath);
5454
const dockerFileKey = `buildTag_${absFilePath}`;
55+
5556
const prevImageName: string | undefined = ext.context.workspaceState.get(dockerFileKey);
5657

5758
// Get imageName based previous entries, else on name of subfolder containing the Dockerfile
@@ -65,7 +66,32 @@ export async function buildImage(context: IActionContext, dockerFileUri: vscode.
6566
addImageTaggingTelemetry(context, imageName, '.after');
6667

6768
await ext.context.workspaceState.update(dockerFileKey, imageName);
68-
terminalCommand.command = terminalCommand.command.replace(tagRegex, imageName);
69+
70+
return imageName;
71+
};
72+
73+
// Replace '${tag}' if needed. Tag is a special case because we don't want to prompt unless necessary, so must manually replace it.
74+
if (!terminalCommand.args || terminalCommand.args.length === 0) {
75+
// This is a customized command, so parse the tag from the command
76+
if (tagRegex.test(terminalCommand.command)) {
77+
const imageName = await getImageName();
78+
terminalCommand.command = terminalCommand.command.replace(tagRegex, imageName);
79+
}
80+
} else if (terminalCommand.args.some(arg => tagRegex.test(typeof (arg) === 'string' ? arg : arg.value))) {
81+
// This is a default command, so look for ${tag} in the args
82+
const imageName = await getImageName();
83+
84+
terminalCommand.args = terminalCommand.args.map(arg => {
85+
if (typeof (arg) === 'string') {
86+
if (tagRegex.test(arg)) {
87+
arg = quoted(arg.replace(tagRegex, imageName));
88+
}
89+
} else if (tagRegex.test(arg.value)) {
90+
arg = quoted(arg.value.replace(tagRegex, imageName));
91+
}
92+
93+
return arg;
94+
});
6995
}
7096

7197
const client = await ext.runtimeManager.getClient();

src/commands/selectCommandTemplate.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IActionContext, IAzureQuickPickItem, IAzureQuickPickOptions, UserCancelledError } from '@microsoft/vscode-azext-utils';
7-
import { PortBinding, VoidCommandResponse } from '@microsoft/vscode-container-client';
7+
import { PortBinding, quoted, VoidCommandResponse } from '@microsoft/vscode-container-client';
88
import * as vscode from 'vscode';
99
import { ext } from '../extensionVariables';
1010
import { isDockerComposeClient } from '../runtimes/OrchestratorRuntimeManager';
@@ -168,14 +168,35 @@ export async function selectCommandTemplate(
168168
throw new Error(vscode.l10n.t('No command template was found for command \'{0}\'', command));
169169
}
170170

171-
actionContext.telemetry.properties.isDefaultCommand = defaultTemplates.some(t => t.template === selectedTemplate.template) ? 'true' : 'false';
171+
const isDefault = defaultTemplates.some(t => t.template === selectedTemplate.template);
172+
actionContext.telemetry.properties.isDefaultCommand = isDefault ? 'true' : 'false';
172173
actionContext.telemetry.properties.isCommandRegexMatched = selectedTemplate.match ? 'true' : 'false';
173174

174-
// This is not really ideal (putting the full command line into `command` instead of `command` + `args`), but parsing a string into a command + args like that is really hard
175-
// Fortunately, `TaskCommandRunnerFactory` does not really care
175+
176+
let resolvedCommand = resolveVariables(selectedTemplate.template, folder, additionalVariables);
177+
178+
if (!isDefault) {
179+
// This is not really ideal (putting the full command line into `command` instead of `command` + `args`), but parsing a string into a command + args like that is really hard
180+
// Fortunately, `TaskCommandRunnerFactory` does not really care
181+
return {
182+
command: resolvedCommand,
183+
args: undefined,
184+
};
185+
}
186+
187+
// For the default command, we can make assumptions that allow us to parse into command + args for better shell support
188+
189+
const argsRegex = /(?:"[^"]*")|(?:[^"\s]*)/g;
190+
const commandAndArgs = resolvedCommand.match(argsRegex).filter(arg => arg.length !== 0);
191+
if (commandAndArgs.length > 0) {
192+
resolvedCommand = commandAndArgs[0].replace(/"/g, '');
193+
}
194+
195+
const resolvedArgs = commandAndArgs.slice(1).map(arg => arg.startsWith('"') ? quoted(arg.replace(/"/g, '')) : arg);
196+
176197
return {
177-
command: resolveVariables(selectedTemplate.template, folder, additionalVariables),
178-
args: undefined,
198+
command: resolvedCommand,
199+
args: resolvedArgs,
179200
};
180201
}
181202

0 commit comments

Comments
 (0)