Skip to content

Commit

Permalink
Add ability to create file tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
hverlin committed Nov 12, 2024
1 parent fd8ed57 commit 4865793
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 2 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ VS Code extension for [mise](https://mise.jdx.dev/)
## ✨ Features

### Task Management
- 🔍 Automatic detection of `mise` tasks
- 🔍 Automatic detection of [mise tasks](https://mise.jdx.dev/tasks/)
- ⚡ Run tasks directly from:
- `mise.toml` files
- Command palette
- Mise sidebar
- Arguments are supported!
- 📝 View task definitions
- ➕Create new file tasks

### Tool Management
- 🧰 View all [mise tools](https://mise.jdx.dev/dev-tools/) (python, node, jq, etc.) in the sidebar
- 📍 Quick navigation to tool definitions
- 📱 Show tools which are not installed or active
- 📦 Install/Remove/Use tools directly from the sidebar

### Environment Variables
- 🌍 View [mise environment variables](https://mise.jdx.dev/environments.html)
Expand Down
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "Mise VSCode",
"publisher": "hverlin",
"description": "VSCode extension for mise (manged dev tools, tasks and environment variables)",
"version": "0.0.18",
"version": "0.0.19",
"repository": {
"type": "git",
"url": "https://github.com/hverlin/mise-vscode"
Expand Down Expand Up @@ -83,6 +83,11 @@
"command": "mise.watchTask",
"title": "Run Mise Task in Watch Mode"
},
{
"command": "mise.createFileTask",
"title": "Create Mise File Task",
"icon": "$(new-file)"
},
{
"command": "mise.openToolDefinition",
"title": "Open Tool Definition"
Expand Down Expand Up @@ -121,6 +126,11 @@
],
"menus": {
"view/title": [
{
"command": "mise.createFileTask",
"when": "view == miseTasksView",
"group": "navigation@1"
},
{
"command": "mise.installAll",
"when": "view == miseToolsView",
Expand Down
67 changes: 67 additions & 0 deletions src/providers/tasksProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ import * as os from "node:os";
import * as toml from "@iarna/toml";
import * as vscode from "vscode";
import type { MiseService } from "../miseService";
import { setupTaskFile } from "../utils/fileUtils";
import { logger } from "../utils/logger";
import { execAsync } from "../utils/shell";
import type { MiseTaskInfo } from "../utils/taskInfoParser";

export const RUN_TASK_COMMAND = "mise.runTask";
export const WATCH_TASK_COMMAND = "mise.watchTask";
export const MISE_OPEN_TASK_DEFINITION = "mise.openTaskDefinition";
export const MISE_CREATE_FILE_TASK = "mise.createFileTask";

const allowedTaskDirs = [
"mise-tasks",
".mise-tasks",
"mise/tasks",
".mise/tasks",
".config/mise/tasks",
];

export class MiseTasksProvider implements vscode.TreeDataProvider<TreeNode> {
private _onDidChangeTreeData: vscode.EventEmitter<
Expand Down Expand Up @@ -363,5 +373,62 @@ export function registerMiseCommands(
}
},
),

vscode.commands.registerCommand(MISE_CREATE_FILE_TASK, async () => {
const taskName = await vscode.window.showInputBox({
prompt: "Enter the name of the task",
placeHolder: "task_name",
validateInput: (value) => {
if (!value) {
return "Task name is required";
}
return null;
},
});

if (!taskName) {
return;
}

const taskSource = await vscode.window.showQuickPick(allowedTaskDirs, {
title: "Select the task source directory",
placeHolder: "Select the task source directory",
});

if (!taskSource) {
return;
}
if (!allowedTaskDirs.includes(taskSource)) {
vscode.window.showErrorMessage(
`Invalid task source directory: ${taskSource}`,
);
return;
}

const rootPath = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
const taskDir = `${rootPath}/${taskSource}`;
const taskFile = vscode.Uri.file(`${taskDir}/${taskName}`);

await setupTaskFile(taskFile.fsPath);

const document = await vscode.workspace.openTextDocument(taskFile);
const editor = await vscode.window.showTextDocument(document);

const taskDefinition = [
"#!/usr/bin/env bash",
`#MISE description="Run ${taskName}"`,
"",
`echo "Running ${taskName}"`,
"",
"# See https://mise.jdx.dev/tasks/file-tasks.html for more information",
].join("\n");

await editor.edit((edit) => {
edit.insert(new vscode.Position(0, 0), taskDefinition);
});
await editor.document.save();
await vscode.commands.executeCommand("workbench.action.files.save");
taskProvider.refresh();
}),
);
}
78 changes: 78 additions & 0 deletions src/utils/fileUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";

async function makeDirectory(dirPath: string): Promise<void> {
try {
await fs.mkdir(dirPath, { recursive: true });
} catch (error) {
if (error instanceof Error && "code" in error && error.code !== "EEXIST") {
throw new Error(
`Failed to create directory ${dirPath}: ${error.message}`,
);
}
}
}

async function touchFile(filePath: string): Promise<void> {
try {
const parentDir = path.dirname(filePath);
await makeDirectory(parentDir);

try {
const handle = await fs.open(filePath, "wx");
await handle.close();
} catch (error) {
if (
error instanceof Error &&
"code" in error &&
error.code === "EEXIST"
) {
return;
}
throw error;
}
} catch (error) {
throw new Error(
`Failed to create file ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
);
}
}

async function setFilePermissions(filePath: string): Promise<void> {
try {
if (os.platform() === "win32") {
// On Windows, TODO?
return;
}

const mode = 0o755; // -rwxr-xr-x
await fs.chmod(filePath, mode);
} catch (error) {
throw new Error(
`Failed to set permissions on ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
);
}
}

export async function setupTaskFile(taskFilePath: string) {
try {
const normalizedDir = path.normalize(path.dirname(taskFilePath));
const normalizedPath = path.normalize(taskFilePath);

if (!path.isAbsolute(normalizedDir)) {
throw new Error("Task directory must be an absolute path");
}

if (!normalizedPath.startsWith(normalizedDir)) {
throw new Error("Task file must be within the task directory");
}

await makeDirectory(normalizedDir);
await touchFile(normalizedPath);
await setFilePermissions(normalizedPath);
} catch (error) {
console.error("Error setting up task file:", error);
throw error;
}
}

0 comments on commit 4865793

Please sign in to comment.