From 4e495c2010284e44df8603359474918588061951 Mon Sep 17 00:00:00 2001 From: Gabriel Terwesten Date: Sat, 11 Dec 2021 21:22:40 +0100 Subject: [PATCH] feat: add support for showing package graph Closes #5 --- README.md | 1 + package.json | 9 +++- src/commands.ts | 63 +++++++++++++++++++-------- src/execute.ts | 52 ++++++++++++++++++++++ src/package_graph_view.ts | 57 ++++++++++++++++++++++++ src/test/suite/melos-commands.test.ts | 2 +- src/test/suite/package-graph.test.ts | 14 ++++++ 7 files changed, 178 insertions(+), 20 deletions(-) create mode 100644 src/package_graph_view.ts create mode 100644 src/test/suite/package-graph.test.ts diff --git a/README.md b/README.md index 2453fd0..443ff13 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,6 @@ This extension adds support for using [Melos] with Visual Studio Code. - Provide scripts in `melos.yaml` as tasks - Apply defaults for Dart VS Code extension settings - Run `bootstrap` and `clean` commands as VS Code commands +- Show package graph [melos]: https://pub.dev/packages/melos diff --git a/package.json b/package.json index d98da2a..e348f58 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "onLanguage:yaml", "workspaceContains:melos.yaml", "onCommand:melos.bootstrap", - "onCommand:melos.clean" + "onCommand:melos.clean", + "onCommand:melos.showPackageGraph" ], "contributes": { "yamlValidation": [ @@ -71,6 +72,12 @@ "title": "Clean", "category": "Melos", "enablement": "shellExecutionSupported && workspaceFolderCount > 0" + }, + { + "command": "melos.showPackageGraph", + "title": "Show package graph", + "category": "Melos", + "enablement": "shellExecutionSupported && workspaceFolderCount > 0" } ] }, diff --git a/src/commands.ts b/src/commands.ts index ccb7e5c..601480b 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode' -import { executeMelosCommand } from './execute' +import { executeMelosCommand, melosList, MelosListFormat } from './execute' import { debug } from './logging' +import { showPackageGraphView } from './package_graph_view' import { buildMelosScriptTask } from './script-task-provider' import { resolveWorkspaceFolder } from './utils/vscode-utils' @@ -14,6 +15,34 @@ export function registerMelosCommands(context: vscode.ExtensionContext) { runScriptCommandHandler() ) ) + + context.subscriptions.push( + vscode.commands.registerCommand( + 'melos.showPackageGraph', + showPackageGraphCommandHandler() + ) + ) +} + +function registerMelosToolCommand( + context: vscode.ExtensionContext, + options: { name: string } +) { + context.subscriptions.push( + vscode.commands.registerCommand(`melos.${options.name}`, async () => { + debug(`command:${options.name}`) + + const workspaceFolder = await resolveWorkspaceFolder() + if (!workspaceFolder) { + return + } + + return executeMelosCommand({ + name: options.name, + folder: workspaceFolder, + }) + }) + ) } /** @@ -50,23 +79,21 @@ function runScriptCommandHandler() { } } -function registerMelosToolCommand( - context: vscode.ExtensionContext, - options: { name: string } -) { - context.subscriptions.push( - vscode.commands.registerCommand(`melos.${options.name}`, async () => { - debug(`command:${options.name}`) - - const workspaceFolder = await resolveWorkspaceFolder() - if (!workspaceFolder) { - return - } +function showPackageGraphCommandHandler() { + return async () => { + // Get melos workspace folder. + const workspaceFolder = await resolveWorkspaceFolder() + if (!workspaceFolder) { + return + } - return executeMelosCommand({ - name: options.name, - folder: workspaceFolder, - }) + // Get package graph data. + const dotGraph = await melosList({ + format: MelosListFormat.gviz, + folder: workspaceFolder, }) - ) + + // Show package graph. + return showPackageGraphView({ dotGraph, folder: workspaceFolder }) + } } diff --git a/src/execute.ts b/src/execute.ts index 1474954..973ce30 100644 --- a/src/execute.ts +++ b/src/execute.ts @@ -1,7 +1,11 @@ +import { exec } from 'child_process' +import { promisify } from 'util' import * as vscode from 'vscode' import { melosExecutableName } from './env' import { info } from './logging' +const execAsync = promisify(exec) + /** * Executes a melos command as a VS Code task. * @@ -60,3 +64,51 @@ export async function executeMelosCommand(options: { return exitCode } + +export async function executeMelosCommandForResult(options: { + args: string[] + folder: vscode.WorkspaceFolder +}): Promise { + const commandLine = `${melosExecutableName} ${options.args.join(' ')}` + const result = execAsync(commandLine, { + encoding: 'utf8', + cwd: options.folder.uri.fsPath, + }) + const output = await result + const exitCode = result.child.exitCode + if (exitCode !== 0) { + throw new Error( + `Expected to get exit code 0 but got ${exitCode}, when executing:\n'${commandLine}'` + ) + } + return output.stdout +} + +export enum MelosListFormat { + json = 'json', + graph = 'graph', + gviz = 'gviz', +} + +export function melosList(options: { + format: MelosListFormat.gviz + folder: vscode.WorkspaceFolder +}): Promise + +export async function melosList(options: { + format: MelosListFormat + folder: vscode.WorkspaceFolder +}): Promise { + const rawResult = await executeMelosCommandForResult({ + args: ['list', `--${options.format}`], + folder: options.folder, + }) + + switch (options.format) { + case MelosListFormat.json: + case MelosListFormat.graph: + return JSON.parse(rawResult) + case MelosListFormat.gviz: + return rawResult + } +} diff --git a/src/package_graph_view.ts b/src/package_graph_view.ts new file mode 100644 index 0000000..fff7c07 --- /dev/null +++ b/src/package_graph_view.ts @@ -0,0 +1,57 @@ +import * as vscode from 'vscode' + +export function showPackageGraphView(options: { + dotGraph: string + folder: vscode.WorkspaceFolder +}) { + const panel = vscode.window.createWebviewPanel( + 'melos.packageGraph', + `Package graph - ${options.folder.name}`, + vscode.ViewColumn.Beside, + { + enableScripts: true, + } + ) + + panel.webview.html = packageGraphWebviewContent(options.dotGraph) + + return panel +} + +function packageGraphWebviewContent(graph: string) { + return ` + + + + + + + + + + + +
+ + + +` +} diff --git a/src/test/suite/melos-commands.test.ts b/src/test/suite/melos-commands.test.ts index ed0447f..c856e9f 100644 --- a/src/test/suite/melos-commands.test.ts +++ b/src/test/suite/melos-commands.test.ts @@ -1,4 +1,4 @@ -import assert = require('assert') +import * as assert from 'assert' import * as vscode from 'vscode' import { melosExecutableName } from '../../env' import { workspaceFolder } from '../utils/vscode-workspace-utils' diff --git a/src/test/suite/package-graph.test.ts b/src/test/suite/package-graph.test.ts new file mode 100644 index 0000000..690c969 --- /dev/null +++ b/src/test/suite/package-graph.test.ts @@ -0,0 +1,14 @@ +import * as assert from 'assert' +import * as vscode from 'vscode' +import { workspaceFolder } from '../utils/vscode-workspace-utils' + +suite('Melos package graph', () => { + test('melos.showPackageGraph command shows package graph in webview', async () => { + const panel = (await vscode.commands.executeCommand( + 'melos.showPackageGraph' + )) as vscode.WebviewPanel + + assert.strictEqual(panel.title, `Package graph - ${workspaceFolder().name}`) + assert.notStrictEqual(panel.webview.html, '') + }) +})