feat: military grade commands synchronizer as esbuild plugin
Some checks failed
CI / ci (push) Failing after 2m17s
Some checks failed
CI / ci (push) Failing after 2m17s
This commit is contained in:
74
esbuild.js
74
esbuild.js
@@ -1,8 +1,81 @@
|
|||||||
const esbuild = require("esbuild");
|
const esbuild = require("esbuild");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
const production = process.argv.includes("--production");
|
const production = process.argv.includes("--production");
|
||||||
const watch = process.argv.includes("--watch");
|
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}
|
* @type {import("esbuild").Plugin}
|
||||||
*/
|
*/
|
||||||
@@ -38,6 +111,7 @@ async function main() {
|
|||||||
external: ["vscode"],
|
external: ["vscode"],
|
||||||
logLevel: "silent",
|
logLevel: "silent",
|
||||||
plugins: [
|
plugins: [
|
||||||
|
syncCommandsPlugin,
|
||||||
esbuildProblemMatcherPlugin,
|
esbuildProblemMatcherPlugin,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,6 +27,10 @@
|
|||||||
{
|
{
|
||||||
"command": "tatr-vsc.init",
|
"command": "tatr-vsc.init",
|
||||||
"title": "Tatr: Initialize Tasks Folder"
|
"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",
|
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||||
"package": "pnpm run check-types && pnpm run lint && node esbuild.js --production",
|
"package": "pnpm run check-types && pnpm run lint && node esbuild.js --production",
|
||||||
"package:vsix": "vsce package --no-dependencies",
|
"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",
|
"compile-tests": "tsc -p . --outDir out",
|
||||||
"watch-tests": "tsc -p . -w --outDir out",
|
"watch-tests": "tsc -p . -w --outDir out",
|
||||||
"pretest": "pnpm run compile-tests && pnpm run compile && pnpm run lint",
|
"pretest": "pnpm run compile-tests && pnpm run compile && pnpm run lint",
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { window } from "vscode";
|
import { window } from "vscode";
|
||||||
|
import { defineCommand } from "../utils/command";
|
||||||
|
|
||||||
export const healthCheckCommand = () => {
|
export default defineCommand({
|
||||||
window.showInformationMessage("TBD");
|
id: "healthCheck",
|
||||||
};
|
title: "Tatr: Health Check",
|
||||||
|
action: () => {
|
||||||
|
window.showInformationMessage("TODO: health check");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
10
src/commands/index.ts
Normal file
10
src/commands/index.ts
Normal 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,
|
||||||
|
];
|
||||||
@@ -1,36 +1,41 @@
|
|||||||
import { FileSystemError, FileType, Uri, window, workspace, type FileStat } from "vscode";
|
import { FileSystemError, FileType, Uri, window, workspace, type FileStat } from "vscode";
|
||||||
import { pickWorkspaceFolder } from "../utils/workspace";
|
import { pickWorkspaceFolder } from "../utils/workspace";
|
||||||
|
import { defineCommand } from "../utils/command";
|
||||||
|
|
||||||
export const initCommand = async () => {
|
export default defineCommand({
|
||||||
const folder = await pickWorkspaceFolder("Select the folder to create tasks/ in");
|
id: "init",
|
||||||
if (!folder) { return; }
|
title: "Tatr: Initialize Tasks Folder",
|
||||||
|
action: async () => {
|
||||||
|
const folder = await pickWorkspaceFolder("Select the folder to create tasks/ in");
|
||||||
|
if (!folder) { return; }
|
||||||
|
|
||||||
const tasksUri = Uri.joinPath(folder.uri, "tasks");
|
const tasksUri = Uri.joinPath(folder.uri, "tasks");
|
||||||
|
|
||||||
let stat: FileStat | undefined;
|
let stat: FileStat | undefined;
|
||||||
try {
|
try {
|
||||||
stat = await workspace.fs.stat(tasksUri);
|
stat = await workspace.fs.stat(tasksUri);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!(e instanceof FileSystemError && e.code === "FileNotFound")) {
|
if (!(e instanceof FileSystemError && e.code === "FileNotFound")) {
|
||||||
window.showErrorMessage(`Tatr: failed to access ${tasksUri.fsPath}: ${e}`);
|
window.showErrorMessage(`Tatr: failed to access ${tasksUri.fsPath}: ${e}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat) {
|
||||||
|
if (stat.type & FileType.Directory) {
|
||||||
|
window.showInformationMessage("Tatr: tasks/ already exists");
|
||||||
|
} else {
|
||||||
|
window.showErrorMessage(`Tatr: ${tasksUri.fsPath} exists but is not a directory`);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (stat) {
|
try {
|
||||||
if (stat.type & FileType.Directory) {
|
await workspace.fs.createDirectory(tasksUri);
|
||||||
window.showInformationMessage("Tatr: tasks/ already exists");
|
} catch (e) {
|
||||||
} else {
|
window.showErrorMessage(`Tatr: failed to create ${tasksUri.fsPath}: ${e}`);
|
||||||
window.showErrorMessage(`Tatr: ${tasksUri.fsPath} exists but is not a directory`);
|
return;
|
||||||
}
|
}
|
||||||
return;
|
window.showInformationMessage(`Tatr: created ${tasksUri.fsPath}`);
|
||||||
}
|
},
|
||||||
|
});
|
||||||
try {
|
|
||||||
await workspace.fs.createDirectory(tasksUri);
|
|
||||||
} catch (e) {
|
|
||||||
window.showErrorMessage(`Tatr: failed to create ${tasksUri.fsPath}: ${e}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.showInformationMessage(`Tatr: created ${tasksUri.fsPath}`);
|
|
||||||
};
|
|
||||||
|
|||||||
10
src/commands/new.ts
Normal file
10
src/commands/new.ts
Normal 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");
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import { commands, type ExtensionContext } from "vscode";
|
import { commands as vscodeCommands, type ExtensionContext } from "vscode";
|
||||||
import { healthCheckCommand } from "./commands/health-check";
|
import { commands as tatrCommands } from "./commands";
|
||||||
import { initCommand } from "./commands/init";
|
|
||||||
|
|
||||||
export const activate = (context: ExtensionContext) => {
|
export const activate = (context: ExtensionContext) => {
|
||||||
context.subscriptions.push(
|
for (const { id, action } of tatrCommands) {
|
||||||
commands.registerCommand("tatr-vsc.healthCheck", healthCheckCommand),
|
context.subscriptions.push(vscodeCommands.registerCommand(`tatr-vsc.${id}`, action));
|
||||||
commands.registerCommand("tatr-vsc.init", initCommand),
|
}
|
||||||
);
|
|
||||||
|
|
||||||
console.log("Extension ready");
|
console.log("Extension ready");
|
||||||
};
|
};
|
||||||
|
|||||||
7
src/utils/command.ts
Normal file
7
src/utils/command.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export type Command = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
action: () => void | Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defineCommand = (command: Command): Command => command;
|
||||||
Reference in New Issue
Block a user