Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/vscode-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# VS Code Extension Draft

이 문서는 Maximus VS Code extension draft의 현재 계약을 설명합니다.

## 목표

첫 번째 버전은 아래 범위로 제한합니다.

- output channel 중심 실행
- CLI wrapper 제공
- `audit`, `doctor`, `fix` 명령만 우선 지원
- diagnostics provider, marketplace publish automation, telemetry 확장은 후속 작업으로 분리

## 실행 방식

확장은 VS Code command palette에서 실행됩니다.

- `Maximus: Run Audit`
- `Maximus: Run Doctor`
- `Maximus: Run Fix`

명령을 실행하면 output channel에 다음 정보를 남깁니다.

- 선택된 workspace 경로
- 실제로 실행된 CLI 명령
- stdout / stderr
- 종료 코드

## CLI wrapper 우선순위

1. workspace root에 `bin/maximus.js`가 있으면 `node`로 실행합니다.
2. 없으면 PATH 상의 `maximus` 실행 파일을 사용합니다.

이 순서는 로컬 저장소에서 직접 작업할 때와 설치된 CLI를 사용할 때를 모두 지원하기 위한 것입니다.

## 현재 상태

이 slice는 draft 수준의 소스와 문서만 추가합니다. 실제 VS Code 배포용 transpile / package 단계와 diagnostics 확장은 아직 포함하지 않습니다.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"description": "Canonical Rust runtime launcher for the Maximus CLI.",
"author": "JeremyDev87",
"type": "module",
"workspaces": [
"packages/*"
],
"bin": {
"maximus": "./bin/maximus.js"
},
Expand Down
26 changes: 26 additions & 0 deletions packages/vscode-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Maximus VS Code Extension

이 패키지는 Maximus CLI를 VS Code 안에서 실행하는 draft 확장입니다.

첫 버전의 기준은 다음과 같습니다.

- diagnostics provider 대신 output channel 중심으로 동작합니다.
- `audit`, `doctor`, `fix` 같은 CLI 명령을 그대로 감쌉니다.
- 로컬 저장소에서 `bin/maximus.js`가 있으면 그 경로를 우선 사용하고, 없으면 `maximus` 실행 파일을 찾습니다.

## 명령

- `Maximus: Run Audit`
- `Maximus: Run Doctor`
- `Maximus: Run Fix`

## 검증

패키지 루트에서 다음 검증을 돌릴 수 있습니다.

```bash
npm test
npm --prefix packages/vscode-extension test
```

이 draft는 아직 transpile / bundle 단계를 포함하지 않기 때문에, 실제 VS Code 배포 파이프라인은 후속 slice에서 마무리합니다.
47 changes: 47 additions & 0 deletions packages/vscode-extension/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "maximus-vscode-extension",
"displayName": "Maximus",
"description": "Output channel first VS Code draft for the Maximus CLI.",
"version": "0.1.0",
"publisher": "jeremydev87",
"private": true,
"engines": {
"vscode": "^1.90.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:maximus.runAudit",
"onCommand:maximus.runDoctor",
"onCommand:maximus.runFix"
],
"main": "./src/extension.ts",
"contributes": {
"commands": [
{
"command": "maximus.runAudit",
"title": "Maximus: Run Audit"
},
{
"command": "maximus.runDoctor",
"title": "Maximus: Run Doctor"
},
{
"command": "maximus.runFix",
"title": "Maximus: Run Fix"
}
]
},
"scripts": {
"test": "node --check ./src/extension.ts && node --test ./test/*.test.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/JeremyDev87/maximus.git"
},
"homepage": "https://github.com/JeremyDev87/maximus#readme",
"bugs": {
"url": "https://github.com/JeremyDev87/maximus/issues"
}
}
230 changes: 230 additions & 0 deletions packages/vscode-extension/src/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
const fs = require("node:fs");
const path = require("node:path");
const { spawn } = require("node:child_process");

const CHANNEL_NAME = "Maximus";
const COMMAND_IDS = {
audit: "maximus.runAudit",
doctor: "maximus.runDoctor",
fix: "maximus.runFix",
};

/**
* @param {import("vscode").ExtensionContext} context
*/
function activate(context) {
const vscode = getVscode();
const output = vscode.window.createOutputChannel(CHANNEL_NAME);

context.subscriptions.push(output);
context.subscriptions.push(
vscode.commands.registerCommand(COMMAND_IDS.audit, (resource) => {
return runMaximusCommand("audit", output, resource);
}),
vscode.commands.registerCommand(COMMAND_IDS.doctor, (resource) => {
return runMaximusCommand("doctor", output, resource);
}),
vscode.commands.registerCommand(COMMAND_IDS.fix, (resource) => {
return runMaximusCommand("fix", output, resource);
}),
);

output.appendLine("Maximus VS Code extension activated.");
}

function deactivate() {}

/**
* @param {"audit" | "doctor" | "fix"} mode
* @param {import("vscode").OutputChannel} output
* @param {unknown} resource
*/
async function runMaximusCommand(mode, output, resource) {
const vscode = getVscode();
const workspaceRoot = await resolveWorkspaceRoot(resource);

if (!workspaceRoot) {
await showMissingWorkspaceWarning(output, vscode);
return;
}

const invocation = resolveInvocation(workspaceRoot, mode);
output.clear();
output.show(true);
output.appendLine(`Workspace: ${workspaceRoot}`);
output.appendLine(`Command: ${formatCommand(invocation.command, invocation.args)}`);
output.appendLine("");

await new Promise((resolve) => {
const child = spawn(invocation.command, invocation.args, {
cwd: workspaceRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
...invocation.options,
});
let settled = false;

const finish = () => {
if (settled) {
return;
}

settled = true;
resolve();
};

child.stdout.on("data", (chunk) => {
output.append(chunk.toString());
});

child.stderr.on("data", (chunk) => {
output.append(chunk.toString());
});

child.on("error", async (error) => {
output.appendLine("");
output.appendLine(`실행 실패: ${error.message}`);
await vscode.window.showErrorMessage(`Maximus 실행에 실패했습니다: ${error.message}`);
finish();
});

child.on("close", (code) => {
output.appendLine("");
output.appendLine(`종료 코드: ${code ?? "unknown"}`);
finish();
});
});
}

/**
* @param {import("vscode").OutputChannel} output
* @param {typeof import("vscode")} vscode
*/
async function showMissingWorkspaceWarning(output, vscode = getVscode()) {
const message = "워크스페이스를 열고 다시 실행해 주세요.";
output.appendLine(message);
await vscode.window.showWarningMessage(message);
}

/**
* @param {unknown} resource
* @returns {string | undefined}
*/
async function resolveWorkspaceRoot(resource, vscode = getVscode()) {
const selectedFolder = resolveWorkspaceFolderFromResource(resource, vscode);
if (selectedFolder) {
return selectedFolder;
}

const activeEditorFolder = resolveActiveEditorWorkspaceRoot(vscode);
if (activeEditorFolder) {
return activeEditorFolder;
}

const workspaceFolders = vscode.workspace.workspaceFolders ?? [];
if (workspaceFolders.length === 1) {
return workspaceFolders[0].uri.fsPath;
}

if (workspaceFolders.length > 1) {
const pickedFolder = await vscode.window.showWorkspaceFolderPick({
placeHolder: "Maximus를 실행할 워크스페이스를 선택해 주세요.",
});

return pickedFolder?.uri.fsPath;
}

return undefined;
}

/**
* @param {unknown} resource
* @param {typeof import("vscode")} vscode
* @returns {string | undefined}
*/
function resolveWorkspaceFolderFromResource(resource, vscode = getVscode()) {
if (!resource || typeof resource !== "object") {
return undefined;
}

const uri = /** @type {{ fsPath?: string, scheme?: string }} */ (resource);
if (typeof uri.fsPath !== "string" || uri.fsPath.length === 0) {
return undefined;
}

const folder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(uri.fsPath));
if (folder) {
return folder.uri.fsPath;
}

return fs.existsSync(uri.fsPath) && fs.statSync(uri.fsPath).isDirectory()
? uri.fsPath
: path.dirname(uri.fsPath);
}

/**
* @param {typeof import("vscode")} vscode
* @returns {string | undefined}
*/
function resolveActiveEditorWorkspaceRoot(vscode = getVscode()) {
const activeEditorUri = vscode.window.activeTextEditor?.document?.uri;
if (!activeEditorUri) {
return undefined;
}

return vscode.workspace.getWorkspaceFolder(activeEditorUri)?.uri.fsPath;
}

/**
* @param {string} workspaceRoot
* @param {"audit" | "doctor" | "fix"} mode
* @param {NodeJS.Platform} platform
*/
function resolveInvocation(workspaceRoot, mode, platform = process.platform) {
const localCli = path.join(workspaceRoot, "bin", "maximus.js");
if (fs.existsSync(localCli)) {
return {
command: process.execPath,
args: [localCli, mode],
options: {},
};
}

if (platform === "win32") {
return {
command: process.env.comspec || "cmd.exe",
args: ["/d", "/s", "/c", "maximus", mode],
options: {
windowsHide: true,
},
};
}

return {
command: "maximus",
args: [mode],
options: {},
};
}

/**
* @param {string} command
* @param {string[]} args
*/
function formatCommand(command, args) {
return [command, ...args].join(" ");
}

function getVscode() {
return require("vscode");
}

module.exports = {
activate,
deactivate,
resolveActiveEditorWorkspaceRoot,
resolveInvocation,
resolveWorkspaceFolderFromResource,
resolveWorkspaceRoot,
showMissingWorkspaceWarning,
};
Loading