feat: military grade commands synchronizer as esbuild plugin
Some checks failed
CI / ci (push) Failing after 2m17s

This commit is contained in:
2026-09-28 21:17:34 +02:00
parent 58f4bda910
commit cdb725e9a6
8 changed files with 151 additions and 37 deletions

View File

@@ -1,8 +1,81 @@
const esbuild = require("esbuild");
const fs = require("node:fs");
const path = require("node:path");
const production = process.argv.includes("--production");
const watch = process.argv.includes("--watch");
/**
* Needed by `syncCommandsPlugin`, which evaluates the commands, because `vscode` is not
* a real package. `vscode` imports only make sense in the extension host context. We can't
* just do `external: ["vscode"]` because we aren't bundling the extension in its host context.
*
* @type {import("esbuild").Plugin}
*/
const vscodeStubPlugin = {
name: "stub-vscode",
setup(build) {
build.onResolve({ filter: /^vscode$/ }, (args) => ({ path: args.path, namespace: "vscode-stub" }));
build.onLoad({ filter: /.*/, namespace: "vscode-stub" }, () => ({
loader: "js",
contents: `
function stub() {
return new Proxy(function () {}, { get: stub, apply: stub, construct: stub });
}
module.exports = stub();
`,
}));
},
};
/**
* Rewrites package.json's `contributes.commands` based on `src/commands/index.ts`, to
* have a single source of truth.
*
* @type {import("esbuild").Plugin}
*/
const syncCommandsPlugin = {
name: "sync-commands",
setup(build) {
build.onStart(async () => {
const packageJsonPath = path.join(__dirname, "package.json");
const result = await esbuild.build({
entryPoints: [path.join(__dirname, "src/commands/index.ts")],
bundle: true,
write: false,
format: "cjs",
platform: "node",
logLevel: "silent",
plugins: [vscodeStubPlugin],
});
// any command file has `export default defineCommand`
const mod = { exports: {} };
const runModule = new Function("module", "exports", result.outputFiles[0].text);
runModule(mod, mod.exports);
const { commands } = mod.exports;
const currentJSON = fs.readFileSync(packageJsonPath, "utf8");
const package = JSON.parse(currentJSON);
package.contributes ??= {};
package.contributes.commands = commands
.map(({ id, title }) => ({
command: `tatr-vsc.${id}`,
title,
}));
const newJSON = `${JSON.stringify(package, null, 2)}\n`;
if (newJSON !== currentJSON) {
fs.writeFileSync(packageJsonPath, newJSON);
console.log("Updated package.json contributes.commands.");
}
});
},
};
/**
* @type {import("esbuild").Plugin}
*/
@@ -38,6 +111,7 @@ async function main() {
external: ["vscode"],
logLevel: "silent",
plugins: [
syncCommandsPlugin,
esbuildProblemMatcherPlugin,
],
});

View File

@@ -27,6 +27,10 @@
{
"command": "tatr-vsc.init",
"title": "Tatr: Initialize Tasks Folder"
},
{
"command": "tatr-vsc.new",
"title": "Tatr: New Task"
}
]
},
@@ -38,6 +42,7 @@
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"package": "pnpm run check-types && pnpm run lint && node esbuild.js --production",
"package:vsix": "vsce package --no-dependencies",
"install:vsix": "vsce package --no-dependencies --out tatr-vsc-dev.vsix && codium --install-extension tatr-vsc-dev.vsix --force",
"compile-tests": "tsc -p . --outDir out",
"watch-tests": "tsc -p . -w --outDir out",
"pretest": "pnpm run compile-tests && pnpm run compile && pnpm run lint",

View File

@@ -1,5 +1,10 @@
import { window } from "vscode";
import { defineCommand } from "../utils/command";
export const healthCheckCommand = () => {
window.showInformationMessage("TBD");
};
export default defineCommand({
id: "healthCheck",
title: "Tatr: Health Check",
action: () => {
window.showInformationMessage("TODO: health check");
},
});

10
src/commands/index.ts Normal file
View File

@@ -0,0 +1,10 @@
import healthCheckCommand from "./health-check";
import initCommand from "./init";
import newCommand from "./new";
import type { Command } from "../utils/command";
export const commands: Command[] = [
healthCheckCommand,
initCommand,
newCommand,
];

View File

@@ -1,7 +1,11 @@
import { FileSystemError, FileType, Uri, window, workspace, type FileStat } from "vscode";
import { pickWorkspaceFolder } from "../utils/workspace";
import { defineCommand } from "../utils/command";
export const initCommand = async () => {
export default defineCommand({
id: "init",
title: "Tatr: Initialize Tasks Folder",
action: async () => {
const folder = await pickWorkspaceFolder("Select the folder to create tasks/ in");
if (!folder) { return; }
@@ -33,4 +37,5 @@ export const initCommand = async () => {
return;
}
window.showInformationMessage(`Tatr: created ${tasksUri.fsPath}`);
};
},
});

10
src/commands/new.ts Normal file
View File

@@ -0,0 +1,10 @@
import { window } from "vscode";
import { defineCommand } from "../utils/command";
export default defineCommand({
id: "new",
title: "Tatr: New Task",
action: () => {
window.showInformationMessage("TODO: new");
},
});

View File

@@ -1,12 +1,10 @@
import { commands, type ExtensionContext } from "vscode";
import { healthCheckCommand } from "./commands/health-check";
import { initCommand } from "./commands/init";
import { commands as vscodeCommands, type ExtensionContext } from "vscode";
import { commands as tatrCommands } from "./commands";
export const activate = (context: ExtensionContext) => {
context.subscriptions.push(
commands.registerCommand("tatr-vsc.healthCheck", healthCheckCommand),
commands.registerCommand("tatr-vsc.init", initCommand),
);
for (const { id, action } of tatrCommands) {
context.subscriptions.push(vscodeCommands.registerCommand(`tatr-vsc.${id}`, action));
}
console.log("Extension ready");
};

7
src/utils/command.ts Normal file
View File

@@ -0,0 +1,7 @@
export type Command = {
id: string;
title: string;
action: () => void | Promise<void>;
};
export const defineCommand = (command: Command): Command => command;