Make command templates context type aware (#2176)
* Extend cmd customization for context types * Add unit tests for command template selection
This commit is contained in:
Родитель
33fd749ed1
Коммит
d7f9027de6
|
@ -48,9 +48,11 @@ export { DebugConfigurationBase } from './src/debugging/DockerDebugConfiguration
|
|||
export { ActivityMeasurementService } from './src/telemetry/ActivityMeasurementService';
|
||||
export { ExperimentationTelemetry } from './src/telemetry/ExperimentationTelemetry';
|
||||
export { DockerApiClient } from './src/docker/DockerApiClient';
|
||||
export { DockerContext, isNewContextType } from './src/docker/Contexts';
|
||||
export { DockerContainer } from './src/docker/Containers';
|
||||
export { DockerImage } from './src/docker/Images';
|
||||
export { DockerNetwork } from './src/docker/Networks';
|
||||
export { DockerVolume } from './src/docker/Volumes';
|
||||
export { CommandTemplate, selectCommandTemplate, defaultCommandTemplates } from './src/commands/selectCommandTemplate';
|
||||
|
||||
export * from 'vscode-azureextensionui';
|
||||
|
|
|
@ -11801,9 +11801,9 @@
|
|||
}
|
||||
},
|
||||
"vscode-azureextensiondev": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-azureextensiondev/-/vscode-azureextensiondev-0.4.0.tgz",
|
||||
"integrity": "sha512-2Ztr9UmO/AiY4Sy9nlXsQMUfVO6OZtN8eUyqQS+9krgGohPdNbdoOlYkwqWWwc/aAK6M263Lf6gZcv6NCqLxpQ==",
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-azureextensiondev/-/vscode-azureextensiondev-0.4.1.tgz",
|
||||
"integrity": "sha512-uQul8jKKOexMN7SJNTNm0YF93+xbKtxrgUm6fJFymk8iddE7R86K7dPOq9+VjvwUPMSQirZLYIE2PCRRCIXsoA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"azure-arm-resource": "^3.0.0-preview",
|
||||
|
@ -12184,9 +12184,9 @@
|
|||
}
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
|
||||
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
|
||||
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
|
@ -12201,9 +12201,9 @@
|
|||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
|
||||
"integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==",
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz",
|
||||
"integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
|
107
package.json
107
package.json
|
@ -1418,6 +1418,17 @@
|
|||
"match": {
|
||||
"type": "string",
|
||||
"description": "%vscode-docker.config.template.build.match%"
|
||||
},
|
||||
"contextTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"moby",
|
||||
"aci"
|
||||
]
|
||||
},
|
||||
"description": "%vscode-docker.config.template.contextTypes.description%"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -1450,6 +1461,17 @@
|
|||
"match": {
|
||||
"type": "string",
|
||||
"description": "%vscode-docker.config.template.run.match%"
|
||||
},
|
||||
"contextTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"moby",
|
||||
"aci"
|
||||
]
|
||||
},
|
||||
"description": "%vscode-docker.config.template.contextTypes.description%"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -1482,6 +1504,17 @@
|
|||
"match": {
|
||||
"type": "string",
|
||||
"description": "%vscode-docker.config.template.runInteractive.match%"
|
||||
},
|
||||
"contextTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"moby",
|
||||
"aci"
|
||||
]
|
||||
},
|
||||
"description": "%vscode-docker.config.template.contextTypes.description%"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -1514,6 +1547,17 @@
|
|||
"match": {
|
||||
"type": "string",
|
||||
"description": "%vscode-docker.config.template.attach.match%"
|
||||
},
|
||||
"contextTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"moby",
|
||||
"aci"
|
||||
]
|
||||
},
|
||||
"description": "%vscode-docker.config.template.contextTypes.description%"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -1546,6 +1590,17 @@
|
|||
"match": {
|
||||
"type": "string",
|
||||
"description": "%vscode-docker.config.template.logs.match%"
|
||||
},
|
||||
"contextTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"moby",
|
||||
"aci"
|
||||
]
|
||||
},
|
||||
"description": "%vscode-docker.config.template.contextTypes.description%"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -1578,6 +1633,17 @@
|
|||
"match": {
|
||||
"type": "string",
|
||||
"description": "%vscode-docker.config.template.composeUp.match%"
|
||||
},
|
||||
"contextTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"moby",
|
||||
"aci"
|
||||
]
|
||||
},
|
||||
"description": "%vscode-docker.config.template.contextTypes.description%"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -1590,7 +1656,19 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"default": "docker-compose ${configurationFile} up ${detached} ${build}",
|
||||
"default": [
|
||||
{
|
||||
"label": "Compose Up",
|
||||
"template": "docker-compose ${configurationFile} up ${detached} ${build}",
|
||||
"contextTypes": [
|
||||
"moby"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Compose Up",
|
||||
"template": "docker compose ${configurationFile} up ${detached}"
|
||||
}
|
||||
],
|
||||
"description": "%vscode-docker.config.template.composeUp.description%"
|
||||
},
|
||||
"docker.commands.composeDown": {
|
||||
|
@ -1610,6 +1688,17 @@
|
|||
"match": {
|
||||
"type": "string",
|
||||
"description": "%vscode-docker.config.template.composeDown.match%"
|
||||
},
|
||||
"contextTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"moby",
|
||||
"aci"
|
||||
]
|
||||
},
|
||||
"description": "%vscode-docker.config.template.contextTypes.description%"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -1622,7 +1711,19 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"default": "docker-compose ${configurationFile} down",
|
||||
"default": [
|
||||
{
|
||||
"label": "Compose Down",
|
||||
"template": "docker-compose ${configurationFile} down",
|
||||
"contextTypes": [
|
||||
"moby"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Compose Down",
|
||||
"template": "docker compose ${configurationFile} down"
|
||||
}
|
||||
],
|
||||
"description": "%vscode-docker.config.template.composeDown.description%"
|
||||
},
|
||||
"docker.containers.groupBy": {
|
||||
|
@ -2636,7 +2737,7 @@
|
|||
"typescript": "^3.9.7",
|
||||
"umd-compat-loader": "^2.1.2",
|
||||
"vsce": "^1.77.0",
|
||||
"vscode-azureextensiondev": "^0.4.0",
|
||||
"vscode-azureextensiondev": "^0.4.1",
|
||||
"vscode-nls-dev": "^3.3.2",
|
||||
"vscode-test": "^1.4.0",
|
||||
"webpack": "^4.43.0",
|
||||
|
|
|
@ -125,6 +125,7 @@
|
|||
"vscode-docker.config.template.composeDown.label": "The label displayed to the user.",
|
||||
"vscode-docker.config.template.composeDown.match": "The regular expression for choosing the right template. Checked against docker-compose YAML files, folder name, etc.",
|
||||
"vscode-docker.config.template.composeDown.description": "Command templates for `docker-compose down` commands.",
|
||||
"vscode-docker.config.template.contextTypes.description": "The context types in which the command template applies. If undefined or empty, the template applies in all context types.",
|
||||
"vscode-docker.config.docker.explorerRefreshInterval": "Docker view refresh interval (milliseconds)",
|
||||
"vscode-docker.config.docker.containers.groupBy": "The property to use to group containers in Docker view: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, Tag, or None",
|
||||
"vscode-docker.config.docker.containers.description": "Any secondary properties to display for a container (an array). Possible elements include: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, and Tag",
|
||||
|
|
|
@ -5,29 +5,39 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import { IActionContext, IAzureQuickPickItem } from 'vscode-azureextensionui';
|
||||
import { ContextType } from '../docker/Contexts';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { localize } from '../localize';
|
||||
import { resolveVariables } from '../utils/resolveVariables';
|
||||
|
||||
export type TemplateCommand = 'build' | 'run' | 'runInteractive' | 'attach' | 'logs' | 'composeUp' | 'composeDown';
|
||||
type TemplateCommand = 'build' | 'run' | 'runInteractive' | 'attach' | 'logs' | 'composeUp' | 'composeDown';
|
||||
|
||||
type CommandTemplate = {
|
||||
// Exported only for tests
|
||||
export type CommandTemplate = {
|
||||
template: string,
|
||||
label: string,
|
||||
match?: string,
|
||||
contextTypes?: ContextType[],
|
||||
};
|
||||
|
||||
// NOTE: the default templates are duplicated in package.json, since VSCode offers no way of looking up extension-level default settings
|
||||
// So, when modifying them here, be sure to modify them there as well!
|
||||
const defaults: { [key in TemplateCommand]: CommandTemplate } = {
|
||||
// Exported only for tests
|
||||
export const defaultCommandTemplates: { [key in TemplateCommand]: CommandTemplate[] } = {
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
'build': { label: 'Docker Build', template: 'docker build --pull --rm -f "${dockerfile}" -t ${tag} "${context}"' },
|
||||
'run': { label: 'Docker Run', template: 'docker run --rm -d ${exposedPorts} ${tag}' },
|
||||
'runInteractive': { label: 'Docker Run (Interactive)', template: 'docker run --rm -it ${exposedPorts} ${tag}' },
|
||||
'attach': { label: 'Docker Attach', template: 'docker exec -it ${containerId} ${shellCommand}' },
|
||||
'logs': { label: 'Docker Logs', template: 'docker logs -f ${containerId}' },
|
||||
'composeUp': { label: 'Compose Up', template: 'docker-compose ${configurationFile} up ${detached} ${build}' },
|
||||
'composeDown': { label: 'Compose Down', template: 'docker-compose ${configurationFile} down' },
|
||||
'build': [{ label: 'Docker Build', template: 'docker build --pull --rm -f "${dockerfile}" -t ${tag} "${context}"' }],
|
||||
'run': [{ label: 'Docker Run', template: 'docker run --rm -d ${exposedPorts} ${tag}' }],
|
||||
'runInteractive': [{ label: 'Docker Run (Interactive)', template: 'docker run --rm -it ${exposedPorts} ${tag}' }],
|
||||
'attach': [{ label: 'Docker Attach', template: 'docker exec -it ${containerId} ${shellCommand}' }],
|
||||
'logs': [{ label: 'Docker Logs', template: 'docker logs -f ${containerId}' }],
|
||||
'composeUp': [
|
||||
{ label: 'Compose Up', template: 'docker-compose ${configurationFile} up ${detached} ${build}', contextTypes: ['moby'] },
|
||||
{ label: 'Compose Up', template: 'docker compose ${configurationFile} up ${detached}' },
|
||||
],
|
||||
'composeDown': [
|
||||
{ label: 'Compose Down', template: 'docker-compose ${configurationFile} down', contextTypes: ['moby'] },
|
||||
{ label: 'Compose Down', template: 'docker compose ${configurationFile} down' },
|
||||
],
|
||||
/* eslint-enable no-template-curly-in-string */
|
||||
};
|
||||
|
||||
|
@ -88,53 +98,68 @@ export async function selectComposeCommand(context: IActionContext, folder: vsco
|
|||
);
|
||||
}
|
||||
|
||||
async function selectCommandTemplate(context: IActionContext, command: TemplateCommand, matchContext?: string[], folder?: vscode.WorkspaceFolder, additionalVariables?: { [key: string]: string }): Promise<string> {
|
||||
// Get the templates from settings
|
||||
// Exported only for tests
|
||||
export async function selectCommandTemplate(context: IActionContext, command: TemplateCommand, matchContext: string[], folder: vscode.WorkspaceFolder | undefined, additionalVariables: { [key: string]: string }): Promise<string> {
|
||||
// Get the current context type
|
||||
const currentContextType = (await ext.dockerContextManager.getCurrentContext()).Type;
|
||||
|
||||
// Get the configured settings values
|
||||
const config = vscode.workspace.getConfiguration('docker');
|
||||
const templateSetting: CommandTemplate[] | string = config.get(`commands.${command}`);
|
||||
let templates: CommandTemplate[];
|
||||
let settingsTemplates: CommandTemplate[];
|
||||
|
||||
// Get template(s) from settings
|
||||
// Get a template array from settings
|
||||
if (typeof (templateSetting) === 'string') {
|
||||
templates = [{ template: templateSetting }] as CommandTemplate[];
|
||||
settingsTemplates = [{ template: templateSetting }] as CommandTemplate[];
|
||||
} else if (!templateSetting) {
|
||||
// If templateSetting is some falsy value, make this an empty array so the hardcoded default above gets used
|
||||
templates = [];
|
||||
settingsTemplates = [];
|
||||
} else {
|
||||
templates = templateSetting;
|
||||
settingsTemplates = templateSetting;
|
||||
}
|
||||
|
||||
// Look for settings-defined template(s) with explicit match, that matches the context
|
||||
const matchedTemplates = templates.filter(template => {
|
||||
if (template.match) {
|
||||
try {
|
||||
const matcher = new RegExp(template.match, 'i');
|
||||
return matchContext.some(m => matcher.test(m));
|
||||
} catch {
|
||||
// Don't wait
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
ext.ui.showWarningMessage(localize('vscode-docker.commands.selectCommandTemplate.invalidMatch', 'Invalid match expression for template \'{0}\'. This template will be skipped.', template.label));
|
||||
}
|
||||
// Get a template array from hardcoded defaults
|
||||
const hardcodedTemplates = defaultCommandTemplates[command];
|
||||
|
||||
// Build the template selection matrix. Settings-defined values are preferred over hardcoded, and constrained over unconstrained.
|
||||
// Constrained templates have either `match` or `contextTypes`, and must match the constraints.
|
||||
// Unconstrained templates have neither `match` nor `contextTypes`.
|
||||
const templateMatrix: CommandTemplate[][] = [];
|
||||
|
||||
// 0. Settings-defined templates with either `match` or `contextTypes`, that satisfy the constraints
|
||||
templateMatrix.push(getConstrainedTemplates(settingsTemplates, matchContext, currentContextType));
|
||||
|
||||
// 1. Settings-defined templates with neither `match` nor `contextTypes`
|
||||
templateMatrix.push(getUnconstrainedTemplates(settingsTemplates));
|
||||
|
||||
// 2. Hardcoded templates with either `match` or `contextTypes`, that satisfy the constraints
|
||||
templateMatrix.push(getConstrainedTemplates(hardcodedTemplates, matchContext, currentContextType));
|
||||
|
||||
// 3. Hardcoded templates with neither `match` nor `contextTypes`
|
||||
templateMatrix.push(getUnconstrainedTemplates(hardcodedTemplates));
|
||||
|
||||
// Select the template to use
|
||||
let selectedTemplate: CommandTemplate;
|
||||
for (const templates of templateMatrix) {
|
||||
// Skip any empty group
|
||||
if (templates.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// Look for settings-defined template(s) with no explicit match
|
||||
const universalTemplates = templates.filter(template => !template.match);
|
||||
|
||||
// Select from explicit match templates, if none then from settings-defined universal templates, if none then hardcoded default
|
||||
let selectedTemplate: CommandTemplate;
|
||||
if (matchedTemplates.length > 0) {
|
||||
selectedTemplate = await quickPickTemplate(context, matchedTemplates);
|
||||
} else if (universalTemplates.length > 0) {
|
||||
selectedTemplate = await quickPickTemplate(context, universalTemplates);
|
||||
} else {
|
||||
selectedTemplate = defaults[command];
|
||||
// Choose a template from the first non-empty group
|
||||
// If only one matches there will be no prompt
|
||||
selectedTemplate = await quickPickTemplate(context, templates);
|
||||
break;
|
||||
}
|
||||
|
||||
context.telemetry.properties.isDefaultCommand = selectedTemplate.template === defaults[command].template ? 'true' : 'false';
|
||||
if (!selectedTemplate) {
|
||||
throw new Error(localize('vscode-docker.commands.selectCommandTemplate.noTemplate', 'No command template was found for command \'{0}\'', command));
|
||||
}
|
||||
|
||||
context.telemetry.properties.isDefaultCommand = hardcodedTemplates.some(t => t.template === selectedTemplate.template) ? 'true' : 'false';
|
||||
context.telemetry.properties.isCommandRegexMatched = selectedTemplate.match ? 'true' : 'false';
|
||||
context.telemetry.properties.commandContextType = `[${selectedTemplate.contextTypes?.join(', ') ?? ''}]`;
|
||||
context.telemetry.properties.currentContextType = currentContextType;
|
||||
|
||||
return resolveVariables(selectedTemplate.template, folder, additionalVariables);
|
||||
}
|
||||
|
@ -159,3 +184,48 @@ async function quickPickTemplate(context: IActionContext, templates: CommandTemp
|
|||
|
||||
return selection.data;
|
||||
}
|
||||
|
||||
function getConstrainedTemplates(templates: CommandTemplate[], matchContext: string[], currentContextType: ContextType): CommandTemplate[] {
|
||||
return templates.filter(template => {
|
||||
if (!template.contextTypes && !template.match) {
|
||||
// If neither contextTypes nor match is defined, this is an unconstrained template
|
||||
return false;
|
||||
}
|
||||
|
||||
return isContextTypeConstraintSatisfied(currentContextType, template.contextTypes) &&
|
||||
isMatchConstraintSatisfied(matchContext, template.match);
|
||||
});
|
||||
}
|
||||
|
||||
function getUnconstrainedTemplates(templates: CommandTemplate[]): CommandTemplate[] {
|
||||
return templates.filter(template => {
|
||||
// Both contextTypes and match must be falsy to make this an unconstrained template
|
||||
return !template.contextTypes && !template.match;
|
||||
});
|
||||
}
|
||||
|
||||
function isContextTypeConstraintSatisfied(currentContextType: ContextType, templateContextTypes: ContextType[] | undefined): boolean {
|
||||
if (!templateContextTypes) {
|
||||
// If templateContextTypes is undefined or empty, it is automatically satisfied
|
||||
return true;
|
||||
}
|
||||
|
||||
return templateContextTypes.some(tc => tc === currentContextType);
|
||||
}
|
||||
|
||||
function isMatchConstraintSatisfied(matchContext: string[], match: string | undefined): boolean {
|
||||
if (!match) {
|
||||
// If match is undefined or empty, it is automatically satisfied
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const matcher = new RegExp(match, 'i');
|
||||
return matchContext.some(m => matcher.test(m));
|
||||
} catch {
|
||||
// Don't wait
|
||||
void ext.ui.showWarningMessage(localize('vscode-docker.commands.selectCommandTemplate.invalidMatch', 'Invalid match expression \'{0}\'. This template will be skipped.', match));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import { LineSplitter } from '../debugging/coreclr/lineSplitter';
|
|||
import { ext } from '../extensionVariables';
|
||||
import { AsyncLazy } from '../utils/lazy';
|
||||
import { execAsync, spawnAsync } from '../utils/spawnAsync';
|
||||
import { DockerContext, DockerContextInspection } from './Contexts';
|
||||
import { DockerContext, DockerContextInspection, isNewContextType } from './Contexts';
|
||||
import { DockerodeApiClient } from './DockerodeApiClient/DockerodeApiClient';
|
||||
import { DockerServeClient } from './DockerServeClient/DockerServeClient';
|
||||
|
||||
|
@ -116,7 +116,7 @@ export class DockerContextManager implements ContextManager, Disposable {
|
|||
void ext.dockerClient?.dispose();
|
||||
|
||||
// Create a new client
|
||||
if (currentContext.Type === 'aci') {
|
||||
if (isNewContextType(currentContext.Type)) {
|
||||
// Currently vscode-docker:aciContext vscode-docker:newSdkContext mean the same thing
|
||||
// But that probably won't be true in the future, so define both as separate concepts now
|
||||
await this.setVsCodeContext('vscode-docker:aciContext', true);
|
||||
|
@ -258,8 +258,8 @@ export class DockerContextManager implements ContextManager, Disposable {
|
|||
let result: boolean = false;
|
||||
const contexts = await this.contextsCache.getValue();
|
||||
|
||||
if (contexts.some(c => c.Type === 'aci')) {
|
||||
// If there are any ACI contexts we automatically know it's the new CLI
|
||||
if (contexts.some(c => isNewContextType(c.Type))) {
|
||||
// If there are any new contexts we automatically know it's the new CLI
|
||||
result = true;
|
||||
} else {
|
||||
// Otherwise we look at the output of `docker serve --help`
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
|
||||
import { DockerObject } from './Common';
|
||||
|
||||
export type ContextType = 'aci' | 'moby';
|
||||
|
||||
export interface DockerContext extends DockerObject {
|
||||
readonly Description?: string;
|
||||
readonly DockerEndpoint: string;
|
||||
readonly Current: boolean;
|
||||
readonly Type: 'aci' | 'moby';
|
||||
readonly Type: ContextType;
|
||||
|
||||
readonly Id: string; // Will be equal to Name for contexts
|
||||
|
||||
|
@ -19,3 +21,13 @@ export interface DockerContext extends DockerObject {
|
|||
export interface DockerContextInspection {
|
||||
readonly [key: string]: unknown;
|
||||
}
|
||||
|
||||
export function isNewContextType(contextType: ContextType): boolean {
|
||||
switch (contextType) {
|
||||
case 'moby':
|
||||
return false;
|
||||
case 'aci': // ACI is new
|
||||
default: // Anything else is likely a new context type as well
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,616 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { runWithSetting } from '../runWithSetting';
|
||||
import { CommandTemplate, selectCommandTemplate, defaultCommandTemplates, ext, DockerContext, isNewContextType } from '../../extension.bundle';
|
||||
import { TestInput } from 'vscode-azureextensiondev';
|
||||
import { IActionContext } from 'vscode-azureextensionui';
|
||||
import { testUserInput } from '../global.test';
|
||||
import assert = require('assert');
|
||||
|
||||
suite("(unit) selectCommandTemplate", () => {
|
||||
test("One constrained from settings (match)", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// *Satisfied constraint (match)
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
match: 'test',
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (match)
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
},
|
||||
{
|
||||
// Unconstrained
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail3',
|
||||
template: 'fail',
|
||||
},
|
||||
{
|
||||
// Unconstrained hardcoded (value is test to assert isDefaultCommand == true)
|
||||
// (If we try to choose here it will fail due to prompting unexpectedly)
|
||||
label: 'fail4',
|
||||
template: 'test',
|
||||
}
|
||||
],
|
||||
[],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("One constrained from settings (contextTypes)", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// *Satisfied constraint (contextTypes + match)
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
match: 'test',
|
||||
contextTypes: ['moby', 'aci'],
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (match)
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
contextTypes: ['moby', 'aci'],
|
||||
},
|
||||
{
|
||||
// Unconstrained
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail3',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[moby, aci]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("Two constrained from settings", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// *Satisfied constraint (contextTypes)
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
contextTypes: ['moby'],
|
||||
},
|
||||
{
|
||||
// *Satisfied constraint (match)
|
||||
label: 'test2',
|
||||
template: 'test',
|
||||
match: 'test',
|
||||
},
|
||||
{
|
||||
// Unconstrained
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[TestInput.UseDefaultValue],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("One unconstrained from settings", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// Unsatisfied constraint (match)
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (contextTypes)
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
contextTypes: ['aci'],
|
||||
},
|
||||
{
|
||||
// *Unconstrained
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail3',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("Two unconstrained from settings", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// Unsatisfied constraint (match)
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (contextTypes)
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
contextTypes: ['aci'],
|
||||
},
|
||||
{
|
||||
// *Unconstrained
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
},
|
||||
{
|
||||
// *Unconstrained
|
||||
label: 'test2',
|
||||
template: 'test',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail3',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[TestInput.UseDefaultValue],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("One constrained from hardcoded (match)", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// Unsatisfied constraint (match)
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (contextTypes)
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
contextTypes: ['aci'],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// *Satisfied constraint (match) hardcoded
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
match: 'test',
|
||||
},
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail3',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("One constrained from hardcoded (contextTypes)", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// Unsatisfied constraint (match)
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (contextTypes)
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
contextTypes: ['aci'],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// *Satisfied constraint (contextTypes + match) hardcoded
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
match: 'test',
|
||||
contextTypes: ['moby'],
|
||||
},
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail3',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[moby]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("Two constrained from hardcoded", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// Unsatisfied constraint (match)
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (contextTypes)
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
contextTypes: ['aci'],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// *Satisfied constraint (contextTypes + match) hardcoded
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
match: 'test',
|
||||
contextTypes: ['moby'],
|
||||
},
|
||||
{
|
||||
// *Satisfied constraint (match) hardcoded
|
||||
label: 'test2',
|
||||
template: 'test',
|
||||
match: 'test',
|
||||
},
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail3',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[TestInput.UseDefaultValue],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("One unconstrained from hardcoded", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// Unsatisfied constraint (match)
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (contextTypes)
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
contextTypes: ['aci'],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// Unsatisfied constraint (match) hardcoded
|
||||
label: 'fail3',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
contextTypes: ['moby'],
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (contextTypes) hardcoded
|
||||
label: 'fail4',
|
||||
template: 'fail',
|
||||
contextTypes: ['aci']
|
||||
},
|
||||
{
|
||||
// *Unconstrained hardcoded
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
},
|
||||
],
|
||||
[],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("Two unconstrained from hardcoded", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// Unsatisfied constraint (match)
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (contextTypes)
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
contextTypes: ['aci'],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// Unsatisfied constraint (match) hardcoded
|
||||
label: 'fail3',
|
||||
template: 'fail',
|
||||
match: 'fail',
|
||||
contextTypes: ['moby'],
|
||||
},
|
||||
{
|
||||
// Unsatisfied constraint (contextTypes) hardcoded
|
||||
label: 'fail4',
|
||||
template: 'fail',
|
||||
contextTypes: ['aci']
|
||||
},
|
||||
{
|
||||
// *Unconstrained hardcoded
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
},
|
||||
{
|
||||
// *Unconstrained hardcoded
|
||||
label: 'test2',
|
||||
template: 'test',
|
||||
},
|
||||
],
|
||||
[TestInput.UseDefaultValue],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("Setting is a string", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
// *String setting
|
||||
'test',
|
||||
[
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("Setting is falsy", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[], // Falsy setting
|
||||
[
|
||||
{
|
||||
// *Unconstrained hardcoded
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
},
|
||||
],
|
||||
[],
|
||||
'moby',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("Unknown context constrained", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// *Satisfied constraint (match)
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
match: 'test',
|
||||
},
|
||||
{
|
||||
// Unconstrained
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail2',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[],
|
||||
'abc',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'abc', 'Wrong value for currentContextType');
|
||||
});
|
||||
|
||||
test("Unknown context unconstrained", async () => {
|
||||
const result = await runWithCommandSetting(
|
||||
[
|
||||
{
|
||||
// *Unconstrained
|
||||
label: 'test',
|
||||
template: 'test',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
// Unconstrained hardcoded
|
||||
label: 'fail',
|
||||
template: 'fail',
|
||||
},
|
||||
],
|
||||
[],
|
||||
'abc',
|
||||
['test']
|
||||
);
|
||||
|
||||
assert.equal(result.command, 'test', 'Incorrect command selected');
|
||||
|
||||
// Quick aside: validate that the context manager thinks an unknown context is new
|
||||
assert.equal(isNewContextType('abc' as any), true, 'Incorrect context type identification');
|
||||
assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand');
|
||||
assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched');
|
||||
assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType');
|
||||
assert.equal(result.context.telemetry.properties.currentContextType, 'abc', 'Wrong value for currentContextType');
|
||||
});
|
||||
});
|
||||
|
||||
async function runWithCommandSetting(
|
||||
settingsValues: CommandTemplate[] | string,
|
||||
hardcodedValues: CommandTemplate[],
|
||||
pickInputs: TestInput[],
|
||||
contextType: string,
|
||||
matchContext: string[]): Promise<{ command: string, context: IActionContext }> {
|
||||
|
||||
const oldDefaultTemplates = defaultCommandTemplates['build'];
|
||||
defaultCommandTemplates['build'] = hardcodedValues;
|
||||
|
||||
const oldContextManager = ext.dockerContextManager;
|
||||
ext.dockerContextManager = {
|
||||
onContextChanged: undefined,
|
||||
refresh: undefined,
|
||||
getContexts: undefined,
|
||||
inspect: undefined,
|
||||
use: undefined,
|
||||
remove: undefined,
|
||||
isNewCli: undefined,
|
||||
|
||||
// Only getCurrentContext is called by selectCommandTemplate
|
||||
// From it, only Type is used
|
||||
getCurrentContext: async () => {
|
||||
return {
|
||||
Type: contextType,
|
||||
} as DockerContext;
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const tempContext: IActionContext = {
|
||||
telemetry: { properties: {}, measurements: {}, },
|
||||
errorHandling: { issueProperties: {}, },
|
||||
};
|
||||
|
||||
const cmdResult: string = await runWithSetting('commands.build', settingsValues, async () => {
|
||||
return await testUserInput.runWithInputs(pickInputs, async () => {
|
||||
return await selectCommandTemplate(tempContext, 'build', matchContext, undefined, {});
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
command: cmdResult,
|
||||
context: tempContext,
|
||||
};
|
||||
} finally {
|
||||
defaultCommandTemplates['build'] = oldDefaultTemplates;
|
||||
ext.dockerContextManager = oldContextManager;
|
||||
}
|
||||
}
|
|
@ -6,13 +6,13 @@
|
|||
import { ConfigurationTarget, workspace, WorkspaceConfiguration } from "vscode";
|
||||
import { configPrefix } from "../extension.bundle";
|
||||
|
||||
export async function runWithSetting<T>(key: string, value: T | undefined, callback: () => Promise<void>): Promise<void> {
|
||||
export async function runWithSetting<TSetting, TCallback>(key: string, value: TSetting | undefined, callback: () => Promise<TCallback>): Promise<TCallback> {
|
||||
const config: WorkspaceConfiguration = workspace.getConfiguration(configPrefix);
|
||||
const result = config.inspect<T>(key);
|
||||
const oldValue: T | undefined = result && result.globalValue;
|
||||
const result = config.inspect<TSetting>(key);
|
||||
const oldValue: TSetting | undefined = result && result.globalValue;
|
||||
try {
|
||||
await config.update(key, value, ConfigurationTarget.Global);
|
||||
await callback();
|
||||
return await callback();
|
||||
} finally {
|
||||
await config.update(key, oldValue, ConfigurationTarget.Global);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче