diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..673aaac7e --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,28 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node +{ + "name": "Node.js & TypeScript", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-16-bullseye" + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "yarn install", + // Configure tool-specific properties. + // "customizations": {}, + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" + , + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.vscode-typescript-tslint-plugin", + "esbenp.prettier-vscode", + "joelday.docthis", + "mike-co.import-sorter" + ] + } + } +} \ No newline at end of file diff --git a/ChangeLog.md b/ChangeLog.md index 1d873e9ae..2a806b544 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,6 +4,10 @@ ## Upcoming Release +General: + +- Expose start/stop/clean commands as tasks in VS Code extension. + ## 2023.10 Version 3.27.0 General: diff --git a/README.md b/README.md index 559d16c3e..b96d2d140 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,40 @@ Following extension configurations are supported: - `azurite.skipApiVersionCheck` Skip the request API version check, by default false. - `azurite.disableProductStyleUrl` Force parsing storage account name from request Uri path, instead of from request Uri host. +The following custom tasks are provided and can be used in `tasks.json` (e.g. to automatically start Azurite when launching a debug session or opening a workspace): + +- `azurite: start` Start all Azurite services +- `azurite: close` Close all Azurite services +- `azurite: clean` Reset all Azurite services persistency data +- `azurite: blob.start` Start blob service +- `azurite: blob.close` Close blob service +- `azurite: blob.clean` Clean blob service +- `azurite: queue.start` Start queue service +- `azurite: queue.close` Close queue service +- `azurite: queue.clean` Clean queue service +- `azurite: table.start` Start table service +- `azurite: table.close` Close table service +- `azurite: table.clean` Clean table service + +To ensure that all services are started before launching a debug configuration, add `"preLaunchTask": "azurite: start"` to the configuration in `launch.json` (see [the docs](https://code.visualstudio.com/Docs/editor/debugging#_launchjson-attributes) for more details). + +To auto-start the blob service when opening a workspace, use the `runOptions` in `task.json` as shown in the example below: + +```json +{ + "tasks" : [ + { + "type": "azurite", + "action": "blob.start", + "problemMatcher": [], + "runOptions": { + "runOn": "folderOpen" + } + } + ] +} +``` + ### [DockerHub](https://hub.docker.com/_/microsoft-azure-storage-azurite) #### Run Azurite V3 docker image diff --git a/package.json b/package.json index 4704c8347..5df36cefe 100644 --- a/package.json +++ b/package.json @@ -251,6 +251,20 @@ } } } + ], + "taskDefinitions": [ + { + "type": "azurite", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string", + "description": "Action to perform. Should be one of: start_blob, stop_blob, clean_blob" + } + } + } ] }, "scripts": { diff --git a/src/extension.ts b/src/extension.ts index 84bf64b26..a9d704917 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,4 +1,10 @@ -import { commands, ExtensionContext, StatusBarAlignment, window } from "vscode"; +import { + commands, + ExtensionContext, + StatusBarAlignment, + tasks, + window +} from "vscode"; import VSCAccessLog from "./common/VSCAccessLog"; import VSCNotification from "./common/VSCNotification"; @@ -7,6 +13,7 @@ import VSCServerManagerBlob from "./common/VSCServerManagerBlob"; import VSCServerManagerQueue from "./common/VSCServerManagerQueue"; import VSCServerManagerTable from "./common/VSCServerManagerTable"; import VSCStatusBarItem from "./common/VSCStatusBarItem"; +import { AzuriteTaskProvider } from "./tasks"; export function activate(context: ExtensionContext) { // Initialize server managers @@ -54,6 +61,15 @@ export function activate(context: ExtensionContext) { new VSCAccessLog(tableServerManager.accessChannelStream) ); + tasks.registerTaskProvider( + AzuriteTaskProvider.AzuriteTaskType, + new AzuriteTaskProvider( + blobServerManager, + queueServerManager, + tableServerManager + ) + ); + context.subscriptions.push( commands.registerCommand("azurite.start", () => { blobServerManager.start(); diff --git a/src/tasks.ts b/src/tasks.ts new file mode 100644 index 000000000..364562de1 --- /dev/null +++ b/src/tasks.ts @@ -0,0 +1,204 @@ +import * as vscode from "vscode"; + +import VSCServerManagerBlob from "./common/VSCServerManagerBlob"; +import VSCServerManagerQueue from "./common/VSCServerManagerQueue"; +import VSCServerManagerTable from "./common/VSCServerManagerTable"; +import { ServerStatus } from "./common/ServerBase"; +import VSCServerManagerBase from "./common/VSCServerManagerBase"; + +interface Action { + name: string; + command: string; + preCheck?: (writeEmitter: vscode.EventEmitter) => Promise; +} + +interface AzuriteTaskDefinition extends vscode.TaskDefinition { + action: string; +} + +export class AzuriteTaskProvider implements vscode.TaskProvider { + static AzuriteTaskType = "azurite"; + + private tasks: vscode.Task[] | undefined; + private actions: Record; + + constructor( + blobManager: VSCServerManagerBlob, + queueManager: VSCServerManagerQueue, + tableManager: VSCServerManagerTable + ) { + this.actions = { + start: { + name: "Start All", + command: "azurite.start" + // NOTE no pre-check here as starting all services doesn't give an error if they're already running + }, + close: { + name: "Close All", + command: "azurite.close" + }, + clean: { + name: "Clean All", + command: "azurite.clean" + }, + "blob.start": { + name: "Start Blob Server", + command: blobManager.getStartCommand(), + preCheck: this.createPreCheck( + blobManager, + ServerStatus.Running, + "Blob server is already running.\r\n" + ) + }, + "blob.close": { + name: "Close Blob Server", + command: blobManager.getCloseCommand(), + preCheck: this.createPreCheck( + blobManager, + ServerStatus.Closed, + "Blob server is already closed.\r\n" + ) + }, + "blob.clean": { + name: "Clean Blob Server", + command: blobManager.getCleanCommand() + }, + "queue.start": { + name: "Start Queue Server", + command: queueManager.getStartCommand(), + preCheck: this.createPreCheck( + queueManager, + ServerStatus.Running, + "Queue server is already running.\r\n" + ) + }, + "queue.close": { + name: "Close Queue Server", + command: queueManager.getCloseCommand(), + preCheck: this.createPreCheck( + queueManager, + ServerStatus.Closed, + "Queue server is already closed.\r\n" + ) + }, + "queue.clean": { + name: "Clean Queue Server", + command: queueManager.getCleanCommand() + }, + "table.start": { + name: "Start Table Server", + command: tableManager.getStartCommand(), + preCheck: this.createPreCheck( + tableManager, + ServerStatus.Running, + "Table server is already running.\r\n" + ) + }, + "table.close": { + name: "Close Table Server", + command: tableManager.getCloseCommand(), + preCheck: this.createPreCheck( + tableManager, + ServerStatus.Closed, + "Table server is already closed.\r\n" + ) + }, + "table.clean": { + name: "Clean Table Server", + command: tableManager.getCleanCommand() + } + }; + } + + /** + * Create a pre-check function for a task. Used to avoid showing an error when starting a service that is already running. + * @param manager + * @param checkStatus + * @param message + * @returns + */ + private createPreCheck( + manager: VSCServerManagerBase, + checkStatus: ServerStatus, + message: string + ) { + return async (writeEmitter: vscode.EventEmitter) => { + const server = manager.getServer(); + if (server?.getStatus() === checkStatus) { + writeEmitter.fire(message); + return false; + } + return true; + }; + } + + provideTasks(): vscode.ProviderResult { + return this.getTasks(); + } + private getTasks() { + if (!this.tasks) { + this.tasks = Object.keys(this.actions).map((actionKey) => { + const definition: AzuriteTaskDefinition = { + type: AzuriteTaskProvider.AzuriteTaskType, + action: actionKey + }; + const action = this.actions[actionKey]; + return new vscode.Task( + definition, + vscode.TaskScope.Workspace, + actionKey, + AzuriteTaskProvider.AzuriteTaskType, + new vscode.CustomExecution( + async (): Promise => { + return new AzuriteTaskTerminal(action); + } + ) + ); + }); + } + return this.tasks; + } + + resolveTask(task: vscode.Task): vscode.ProviderResult { + const actionName = task.definition.action as string; + if (!actionName) { + return undefined; + } + const resolvedTask = this.getTasks().find( + (t) => t.definition.action === actionName + ); + return resolvedTask; + } +} + +class AzuriteTaskTerminal implements vscode.Pseudoterminal { + private writeEmitter = new vscode.EventEmitter(); + onDidWrite: vscode.Event = this.writeEmitter.event; + private closeEmitter = new vscode.EventEmitter(); + onDidClose?: vscode.Event = this.closeEmitter.event; + + private action: Action; + + constructor(action: Action) { + this.action = action; + } + open(initialDimensions: vscode.TerminalDimensions | undefined): void { + this.start(); + } + // eslint-disable-next-line @typescript-eslint/no-empty-function + close(): void {} + + private async start(): Promise { + if (this.action.preCheck) { + const shouldContinue = await this.action.preCheck(this.writeEmitter); + if (!shouldContinue) { + this.closeEmitter.fire(0); + return; + } + } + this.writeEmitter.fire(this.action.name + "\r\n"); + await vscode.commands.executeCommand(this.action.command); + this.writeEmitter.fire("Done!\r\n"); + this.closeEmitter.fire(0); + } +} diff --git a/tsconfig.json b/tsconfig.json index 42a2e39db..0e57604b2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,13 +16,25 @@ "declarationMap": true, "importHelpers": true, "declarationDir": "./typings", - "lib": ["es5", "es6", "es7", "esnext", "dom"], + "lib": [ + "es5", + "es6", + "es7", + "esnext", + "dom" + ], "esModuleInterop": true, "downlevelIteration": true, "useUnknownInCatchVariables": false, "skipLibCheck": true, }, "compileOnSave": true, - "exclude": ["node_modules"], - "include": ["./src/**/*.ts", "./tests/**/*.ts"] -} + "exclude": [ + "node_modules" + ], + "include": [ + "./src/**/*.ts", + "./tests/**/*.ts", + "src/tasks.ts.tmp" + ] +} \ No newline at end of file