@tgrv/void-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +59 -0
  2. package/bin/void.js +567 -0
  3. package/package.json +9 -0
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # Void CLI (`@tgrv/void-cli`)
2
+
3
+ The official developer command-line toolkit for initializing, building, and publishing WebAssembly plugins for the **Void** framework.
4
+
5
+ ## Installation
6
+
7
+ Install globally or use on-demand via `npx`:
8
+
9
+ ```bash
10
+ npm install -g @tgrv/void-cli
11
+ ```
12
+
13
+ ## CLI Usage
14
+
15
+ ### 1. Initialize a new application project
16
+ ```bash
17
+ void init [path]
18
+ ```
19
+ Creates standard configuration templates and installs the `@tgrv/void-runtime` engine automatically.
20
+
21
+ ### 2. Create a new Rust or Go plugin project
22
+ ```bash
23
+ void create [plugin-path]
24
+ ```
25
+ Prompts for language configuration and instantiates starting templates with pre-configured SDK targets.
26
+
27
+ ### 3. Build a plugin locally
28
+ ```bash
29
+ void build [plugin-path]
30
+ ```
31
+ Compiles source files (Cargo/Go) and wraps the WASM binary under a local scoped output folder configured in `void.json`. Defaults to current folder (`.`).
32
+
33
+ ### 4. Publish a plugin
34
+ ```bash
35
+ void publish [plugin-path]
36
+ ```
37
+ Compiles and triggers `npm publish` natively from inside the scoped build directory. Defaults to current folder (`.`).
38
+
39
+ ### 5. Install / Add a plugin
40
+ ```bash
41
+ void add <plugin-name>
42
+ ```
43
+ Downloads the plugin from the NPM registry, or links local build outputs during development, adding dependencies to `void.config.json` and `package.json`.
44
+
45
+ ### 6. Remove a plugin
46
+ ```bash
47
+ void remove <plugin-name>
48
+ ```
49
+ Uninstalls the package, deletes the local `node_modules` subfolder, and cleans configurations.
50
+
51
+ ### 7. Inspect a plugin
52
+ ```bash
53
+ void view <plugin-name>
54
+ ```
55
+ Queries function metadata directly from the compiled WASM binary.
56
+
57
+ ## License
58
+
59
+ ISC License. See `LICENSE` for details.
package/bin/void.js ADDED
@@ -0,0 +1,567 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { execSync } from "child_process";
6
+ import { fileURLToPath, pathToFileURL } from "url";
7
+ import readline from "readline";
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const workspaceDir = path.resolve(__dirname, "../../..");
12
+
13
+ // ANSI Color formatting codes
14
+ const colors = {
15
+ reset: "\x1b[0m",
16
+ bold: "\x1b[1m",
17
+ green: "\x1b[32m",
18
+ red: "\x1b[31m",
19
+ cyan: "\x1b[36m",
20
+ yellow: "\x1b[33m",
21
+ blue: "\x1b[34m",
22
+ magenta: "\x1b[35m"
23
+ };
24
+
25
+ const tick = `\x1b[32m✔\x1b[0m`;
26
+ const cross = `\x1b[31m✘\x1b[0m`;
27
+ const info = `\x1b[36mℹ\x1b[0m`;
28
+ const warning = `\x1b[33m⚠\x1b[0m`;
29
+
30
+ // Helper to find the root void.config.json by scanning upward
31
+ function findConfig(dir) {
32
+ const configPath = path.join(dir, "void.config.json");
33
+ if (fs.existsSync(configPath)) {
34
+ return { configPath, configDir: dir };
35
+ }
36
+ const parent = path.dirname(dir);
37
+ if (parent === dir) {
38
+ return null;
39
+ }
40
+ return findConfig(parent);
41
+ }
42
+
43
+ function runCommand(cmd, options = {}) {
44
+ try {
45
+ execSync(cmd, { stdio: "inherit", ...options });
46
+ return true;
47
+ } catch (error) {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ function ensureDir(dir) {
53
+ if (!fs.existsSync(dir)) {
54
+ fs.mkdirSync(dir, { recursive: true });
55
+ }
56
+ }
57
+
58
+ function copyFolderRecursive(src, dest, replacements = {}) {
59
+ ensureDir(dest);
60
+ const entries = fs.readdirSync(src, { withFileTypes: true });
61
+ for (const entry of entries) {
62
+ const srcPath = path.join(src, entry.name);
63
+ const destPath = path.join(dest, entry.name);
64
+ if (entry.isDirectory()) {
65
+ // Avoid copying build output folder back into itself
66
+ if (entry.name === "@void" || entry.name === "@tgrv") {
67
+ continue;
68
+ }
69
+ copyFolderRecursive(srcPath, destPath, replacements);
70
+ } else {
71
+ const ext = path.extname(entry.name).toLowerCase();
72
+ const isBinary = [".wasm", ".png", ".jpg", ".jpeg", ".gif", ".ico"].includes(ext);
73
+ if (isBinary) {
74
+ fs.copyFileSync(srcPath, destPath);
75
+ } else {
76
+ let content = fs.readFileSync(srcPath, "utf8");
77
+ for (const [key, value] of Object.entries(replacements)) {
78
+ content = content.replace(new RegExp(key, "g"), value);
79
+ }
80
+ fs.writeFileSync(destPath, content);
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ const args = process.argv.slice(2);
87
+ const command = args[0];
88
+
89
+ if (!command) {
90
+ console.log(`
91
+ ${colors.bold}${colors.cyan}Void CLI V2 - WASM Plugin Manager${colors.reset}
92
+
93
+ ${colors.bold}Usage:${colors.reset}
94
+ ${colors.green}void init [path]${colors.reset} - Initialize a new application project at path (default: .)
95
+ ${colors.green}void create [plugin-path]${colors.reset} - Create a new Rust or Go plugin project template (default: .)
96
+ ${colors.green}void build [plugin-path]${colors.reset} - Compile and place builds inside the plugin folder (default: .)
97
+ ${colors.green}void publish [plugin-path]${colors.reset} - Build and publish the plugin to npm registry (default: .)
98
+ ${colors.green}void add <plugin-name>${colors.reset} - Add a plugin from registry/local build into application
99
+ ${colors.green}void remove <plugin-name>${colors.reset} - Remove a plugin from application and configuration
100
+ ${colors.green}void view <plugin-name>${colors.reset} - Inspect an installed plugin and list all its exposed functions
101
+ `);
102
+ process.exit(0);
103
+ }
104
+
105
+ // Build core compilation wrapper function to resolve output directory using void.json manifest
106
+ function runBuild(pluginPathArg) {
107
+ const absolutePluginDir = path.resolve(process.cwd(), pluginPathArg);
108
+ if (!fs.existsSync(absolutePluginDir)) {
109
+ console.error(`${cross} ${colors.red}Error: Directory does not exist: ${absolutePluginDir}${colors.reset}`);
110
+ process.exit(1);
111
+ }
112
+
113
+ // Load and parse void.json manifest file
114
+ const manifestPath = path.join(absolutePluginDir, "void.json");
115
+ if (!fs.existsSync(manifestPath)) {
116
+ console.error(`${cross} ${colors.red}Error: void.json manifest not found at: ${manifestPath}${colors.reset}`);
117
+ process.exit(1);
118
+ }
119
+
120
+ let manifest;
121
+ try {
122
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
123
+ } catch (err) {
124
+ console.error(`${cross} ${colors.red}Error: Failed to parse void.json manifest: ${err.message}${colors.reset}`);
125
+ process.exit(1);
126
+ }
127
+
128
+ if (!manifest.name || !manifest.type || !manifest.buildDir) {
129
+ console.error(`${cross} ${colors.red}Error: void.json must specify 'name', 'type', and 'buildDir'${colors.reset}`);
130
+ process.exit(1);
131
+ }
132
+
133
+ const pluginName = manifest.name;
134
+ const pluginType = manifest.type;
135
+ const buildDir = manifest.buildDir;
136
+ const buildOutputDir = path.join(absolutePluginDir, buildDir);
137
+
138
+ if (pluginType === "sdk") {
139
+ console.log(`\n${info} Packaging SDK '${colors.bold}${pluginName}${colors.reset}' to local build at: ${colors.bold}${buildOutputDir}${colors.reset}`);
140
+ ensureDir(buildOutputDir);
141
+ copyFolderRecursive(absolutePluginDir, buildOutputDir);
142
+ console.log(`${tick} ${colors.green}Successfully completed SDK packaging!${colors.reset}`);
143
+ return buildOutputDir;
144
+ }
145
+
146
+ console.log(`\n${info} Compiling plugin '${colors.bold}${pluginName}${colors.reset}'...`);
147
+ let builtWasmPath = null;
148
+
149
+ // Rust plugin compilation
150
+ if (pluginType === "rust") {
151
+ console.log(`${info} Running: ${colors.blue}cargo build --target wasm32-unknown-unknown --release${colors.reset}`);
152
+ const compileSuccess = runCommand("cargo build --target wasm32-unknown-unknown --release", { cwd: absolutePluginDir });
153
+ if (!compileSuccess) {
154
+ console.error(`${cross} ${colors.red}Rust compilation failed.${colors.reset}`);
155
+ process.exit(1);
156
+ }
157
+
158
+ const targetDir = path.join(absolutePluginDir, "target", "wasm32-unknown-unknown", "release");
159
+ const wasmFiles = fs.readdirSync(targetDir).filter((file) => file.endsWith(".wasm"));
160
+ if (wasmFiles.length === 0) {
161
+ console.error(`${cross} ${colors.red}Could not find compiled .wasm file in Rust target directory${colors.reset}`);
162
+ process.exit(1);
163
+ }
164
+ builtWasmPath = path.join(targetDir, wasmFiles[0]);
165
+ }
166
+ // Go plugin compilation
167
+ else if (pluginType === "go") {
168
+ const outputWasm = path.join(absolutePluginDir, "plugin.wasm");
169
+ console.log(`${info} Running: ${colors.blue}go build -o plugin.wasm${colors.reset}`);
170
+ const compileSuccess = runCommand("go build -o plugin.wasm", {
171
+ cwd: absolutePluginDir,
172
+ env: { ...process.env, GOOS: "wasip1", GOARCH: "wasm" },
173
+ });
174
+ if (!compileSuccess) {
175
+ console.error(`${cross} ${colors.red}Go compilation failed.${colors.reset}`);
176
+ process.exit(1);
177
+ }
178
+ builtWasmPath = outputWasm;
179
+ } else {
180
+ console.error(`${cross} ${colors.red}Unsupported plugin type: '${pluginType}' in void.json${colors.reset}`);
181
+ process.exit(1);
182
+ }
183
+
184
+ console.log(`${info} Placing build output to local folder at ${colors.bold}${buildOutputDir}${colors.reset}...`);
185
+ ensureDir(buildOutputDir);
186
+
187
+ // Copy WASM
188
+ fs.copyFileSync(builtWasmPath, path.join(buildOutputDir, "plugin.wasm"));
189
+
190
+ // Write package.json
191
+ const pkgJson = {
192
+ name: pluginName,
193
+ version: "1.0.0",
194
+ type: "module",
195
+ main: "index.js",
196
+ dependencies: {
197
+ "@tgrv/void-runtime": "^1.0.0",
198
+ },
199
+ };
200
+ fs.writeFileSync(path.join(buildOutputDir, "package.json"), JSON.stringify(pkgJson, null, 2));
201
+
202
+ // Generate standard ESM index.js loader
203
+ const indexJsContent = `import { runtime } from "@tgrv/void-runtime";
204
+ import { fileURLToPath } from "url";
205
+ import { dirname, join } from "path";
206
+
207
+ const __filename = fileURLToPath(import.meta.url);
208
+ const __dirname = dirname(__filename);
209
+
210
+ const plugin = await runtime.load(
211
+ join(__dirname, "plugin.wasm")
212
+ );
213
+
214
+ export default plugin;
215
+ `;
216
+ fs.writeFileSync(path.join(buildOutputDir, "index.js"), indexJsContent);
217
+ console.log(`${tick} ${colors.green}Successfully completed compilation & packaging!${colors.reset}`);
218
+ return buildOutputDir;
219
+ }
220
+
221
+ async function main() {
222
+ switch (command) {
223
+ case "init": {
224
+ const targetPathArg = args[1] || ".";
225
+ const targetDir = path.resolve(process.cwd(), targetPathArg);
226
+ ensureDir(targetDir);
227
+
228
+ console.log(`\n${info} Initializing Void project at: ${colors.bold}${targetDir}${colors.reset}`);
229
+
230
+ // Initialize package.json if not exists
231
+ const pkgPath = path.join(targetDir, "package.json");
232
+ if (!fs.existsSync(pkgPath)) {
233
+ const defaultPkg = {
234
+ name: path.basename(targetDir),
235
+ version: "1.0.0",
236
+ type: "module",
237
+ dependencies: {},
238
+ };
239
+ fs.writeFileSync(pkgPath, JSON.stringify(defaultPkg, null, 2));
240
+ }
241
+
242
+ // Initialize void.config.json
243
+ const configPath = path.join(targetDir, "void.config.json");
244
+ if (!fs.existsSync(configPath)) {
245
+ const config = {
246
+ registry: "./registry",
247
+ plugins: {},
248
+ };
249
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
250
+ }
251
+
252
+ // Write default application app.js
253
+ const appPath = path.join(targetDir, "app.js");
254
+ if (!fs.existsSync(appPath)) {
255
+ const appContent = `import math from "@tgrv/void-math";
256
+
257
+ console.log("=== Void Application ===");
258
+
259
+ try {
260
+ console.log("Math sum (40 + 2):", math.add({ a: 40, b: 2 }));
261
+ } catch (e) {
262
+ console.error("Error running application:", e.message);
263
+ }
264
+ `;
265
+ fs.writeFileSync(appPath, appContent);
266
+ }
267
+
268
+ // Install void-runtime via NPM
269
+ console.log(`${info} Running: ${colors.blue}npm install @tgrv/void-runtime${colors.reset}`);
270
+ const npmSuccess = runCommand("npm install @tgrv/void-runtime", { cwd: targetDir });
271
+ if (!npmSuccess) {
272
+ console.error(`${cross} ${colors.red}Failed to install @tgrv/void-runtime via npm.${colors.reset}`);
273
+ }
274
+
275
+ console.log(`${tick} ${colors.green}Successfully initialized Void project!${colors.reset}`);
276
+ console.log(`\nTo add plugins, you can run:`);
277
+ console.log(` ${colors.cyan}npx void add <plugin-name>${colors.reset}`);
278
+ console.log(`For example:`);
279
+ console.log(` ${colors.cyan}npx void add @tgrv/void-math${colors.reset}\n`);
280
+ break;
281
+ }
282
+
283
+ case "create": {
284
+ const pluginPathArg = args[1] || ".";
285
+ const targetDir = path.resolve(process.cwd(), pluginPathArg);
286
+ const folderName = path.basename(targetDir);
287
+
288
+ // Prompt for language
289
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
290
+ const rawLanguage = await new Promise((resolve) => {
291
+ rl.question(`${info} Select plugin language (go/rust) [default: rust]: `, (ans) => {
292
+ resolve(ans.trim().toLowerCase() || "rust");
293
+ });
294
+ });
295
+ rl.close();
296
+
297
+ if (rawLanguage !== "rust" && rawLanguage !== "go") {
298
+ console.error(`${cross} ${colors.red}Error: Unsupported language: ${rawLanguage}${colors.reset}`);
299
+ process.exit(1);
300
+ }
301
+
302
+ if (rawLanguage === "rust") {
303
+ // Verify Cargo
304
+ const cargoCheck = runCommand("cargo --version");
305
+ if (!cargoCheck) {
306
+ console.error(`\n${cross} ${colors.red}Error: 'rustc' or 'cargo' is not installed on your machine.${colors.reset}`);
307
+ console.error(`${colors.yellow}Please install Rust from https://rustup.rs/ first.${colors.reset}\n`);
308
+ process.exit(1);
309
+ }
310
+
311
+ console.log(`${info} Creating Rust plugin template at ${colors.bold}${targetDir}${colors.reset}...`);
312
+ ensureDir(targetDir);
313
+ const templateSrc = path.join(workspaceDir, "templates", "rust");
314
+ copyFolderRecursive(templateSrc, targetDir, {
315
+ "\\{\\{name\\}\\}": folderName,
316
+ "\\{\\{type\\}\\}": "rust",
317
+ "\\{\\{workspace_dir\\}\\}": workspaceDir.replace(/\\/g, "/"),
318
+ });
319
+ } else {
320
+ // Verify Go
321
+ const goCheck = runCommand("go version");
322
+ if (!goCheck) {
323
+ console.error(`\n${cross} ${colors.red}Error: 'go' is not installed on your machine.${colors.reset}`);
324
+ console.error(`${colors.yellow}Please install Go from https://go.dev/doc/install first.${colors.reset}\n`);
325
+ process.exit(1);
326
+ }
327
+
328
+ console.log(`${info} Creating Go plugin template at ${colors.bold}${targetDir}${colors.reset}...`);
329
+ ensureDir(targetDir);
330
+ const templateSrc = path.join(workspaceDir, "templates", "go");
331
+ copyFolderRecursive(templateSrc, targetDir, {
332
+ "\\{\\{name\\}\\}": folderName,
333
+ "\\{\\{type\\}\\}": "go",
334
+ "\\{\\{workspace_dir\\}\\}": workspaceDir.replace(/\\/g, "/"),
335
+ });
336
+ }
337
+
338
+ console.log(`${tick} ${colors.green}Plugin created successfully under '${pluginPathArg}'!${colors.reset}\n`);
339
+ break;
340
+ }
341
+
342
+ case "build": {
343
+ const pluginPathArg = args[1] || ".";
344
+ runBuild(pluginPathArg);
345
+ break;
346
+ }
347
+
348
+ case "publish": {
349
+ const pluginPathArg = args[1] || ".";
350
+ // 1. Run build
351
+ const buildOutputDir = runBuild(pluginPathArg);
352
+
353
+ // 2. Call npm publish inside the output build folder
354
+ console.log(`\n${info} Running 'npm publish' inside: ${colors.bold}${buildOutputDir}${colors.reset}`);
355
+ const publishSuccess = runCommand("npm publish", { cwd: buildOutputDir });
356
+ if (!publishSuccess) {
357
+ console.log(`\n${warning} ${colors.yellow}NPM publish failed or completed as dry run (check NPM login credentials).${colors.reset}`);
358
+ } else {
359
+ console.log(`${tick} ${colors.green}Successfully published package to NPM!${colors.reset}\n`);
360
+ }
361
+ break;
362
+ }
363
+
364
+ case "add": {
365
+ const pluginName = args[1];
366
+ if (!pluginName) {
367
+ console.error(`${cross} ${colors.red}Error: Please specify the plugin to add. e.g. void add @tgrv/void-math${colors.reset}`);
368
+ process.exit(1);
369
+ }
370
+
371
+ const configInfo = findConfig(process.cwd());
372
+ if (!configInfo) {
373
+ console.error(`${cross} ${colors.red}Error: void.config.json not found. Run 'void init' first.${colors.reset}`);
374
+ process.exit(1);
375
+ }
376
+
377
+ const config = JSON.parse(fs.readFileSync(configInfo.configPath, "utf8"));
378
+
379
+ // Look up local build directory inside the plugins/ or packages/ directory tree
380
+ let localBuildDir = null;
381
+
382
+ const scanLocations = [
383
+ path.join(workspaceDir, "plugins"),
384
+ path.join(workspaceDir, "packages")
385
+ ];
386
+
387
+ for (const root of scanLocations) {
388
+ if (fs.existsSync(root)) {
389
+ const subdirs = fs.readdirSync(root);
390
+ for (const subdir of subdirs) {
391
+ const pPath = path.join(root, subdir);
392
+ if (fs.statSync(pPath).isDirectory()) {
393
+ // Scans nested scopes (e.g. plugins/math/@tgrv/void-math)
394
+ const entries = fs.readdirSync(pPath);
395
+ for (const entry of entries) {
396
+ if (entry.startsWith("@")) {
397
+ const scopePath = path.join(pPath, entry);
398
+ const subfolders = fs.readdirSync(scopePath);
399
+ for (const name of subfolders) {
400
+ const pkgPath = path.join(scopePath, name, "package.json");
401
+ if (fs.existsSync(pkgPath)) {
402
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
403
+ if (pkg.name === pluginName) {
404
+ localBuildDir = path.join(scopePath, name);
405
+ break;
406
+ }
407
+ }
408
+ }
409
+ }
410
+ if (localBuildDir) break;
411
+ }
412
+ }
413
+ if (localBuildDir) break;
414
+ }
415
+ }
416
+ if (localBuildDir) break;
417
+ }
418
+
419
+ const targetPluginDir = path.join(process.cwd(), "node_modules", pluginName);
420
+ ensureDir(path.dirname(targetPluginDir));
421
+
422
+ if (localBuildDir) {
423
+ console.log(`${info} Installing local build of '${colors.bold}${pluginName}${colors.reset}' from ${localBuildDir} to ${targetPluginDir}...`);
424
+ if (fs.existsSync(targetPluginDir)) {
425
+ fs.rmSync(targetPluginDir, { recursive: true, force: true });
426
+ }
427
+ copyFolderRecursive(localBuildDir, targetPluginDir);
428
+ } else {
429
+ console.log(`${info} Installing '${colors.bold}${pluginName}${colors.reset}' from NPM registry...`);
430
+ const npmSuccess = runCommand(`npm install ${pluginName}`, { cwd: process.cwd() });
431
+ if (!npmSuccess) {
432
+ console.error(`${cross} ${colors.red}Error: Failed to install package '${pluginName}' via npm.${colors.reset}`);
433
+ process.exit(1);
434
+ }
435
+ }
436
+
437
+ // Update package.json of the application
438
+ const pkgPath = path.join(process.cwd(), "package.json");
439
+ if (fs.existsSync(pkgPath)) {
440
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
441
+ pkg.dependencies = pkg.dependencies || {};
442
+ pkg.dependencies[pluginName] = "1.0.0";
443
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
444
+ }
445
+
446
+ // Update void.config.json
447
+ config.plugins = config.plugins || {};
448
+ config.plugins[pluginName] = "^1.0.0";
449
+ fs.writeFileSync(configInfo.configPath, JSON.stringify(config, null, 2));
450
+
451
+ console.log(`${tick} ${colors.green}Successfully added '${pluginName}'!${colors.reset}\n`);
452
+ break;
453
+ }
454
+
455
+ case "remove": {
456
+ const pluginName = args[1];
457
+ if (!pluginName) {
458
+ console.error(`${cross} ${colors.red}Error: Please specify the plugin to remove. e.g. void remove @tgrv/void-math${colors.reset}`);
459
+ process.exit(1);
460
+ }
461
+
462
+ const configInfo = findConfig(process.cwd());
463
+ if (!configInfo) {
464
+ console.error(`${cross} ${colors.red}Error: void.config.json not found.${colors.reset}`);
465
+ process.exit(1);
466
+ }
467
+
468
+ const config = JSON.parse(fs.readFileSync(configInfo.configPath, "utf8"));
469
+
470
+ console.log(`${info} Removing '${colors.bold}${pluginName}${colors.reset}'...`);
471
+
472
+ // Run npm uninstall
473
+ const npmSuccess = runCommand(`npm uninstall ${pluginName}`, { cwd: process.cwd() });
474
+ if (!npmSuccess) {
475
+ console.error(`${cross} ${colors.red}Error: Failed to uninstall package '${pluginName}' via npm.${colors.reset}`);
476
+ }
477
+
478
+ // Explicitly delete folder from node_modules if it exists
479
+ const targetPluginDir = path.join(process.cwd(), "node_modules", pluginName);
480
+ if (fs.existsSync(targetPluginDir)) {
481
+ fs.rmSync(targetPluginDir, { recursive: true, force: true });
482
+ }
483
+
484
+ // Update package.json of the application
485
+ const pkgPath = path.join(process.cwd(), "package.json");
486
+ if (fs.existsSync(pkgPath)) {
487
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
488
+ if (pkg.dependencies && pkg.dependencies[pluginName]) {
489
+ delete pkg.dependencies[pluginName];
490
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
491
+ }
492
+ }
493
+
494
+ // Update void.config.json
495
+ if (config.plugins && config.plugins[pluginName]) {
496
+ delete config.plugins[pluginName];
497
+ fs.writeFileSync(configInfo.configPath, JSON.stringify(config, null, 2));
498
+ }
499
+
500
+ console.log(`${tick} ${colors.green}Successfully removed '${pluginName}'!${colors.reset}\n`);
501
+ break;
502
+ }
503
+
504
+ case "view": {
505
+ const pluginName = args[1];
506
+ if (!pluginName) {
507
+ console.error(`${cross} ${colors.red}Error: Please specify the plugin to view, e.g. void view @tgrv/void-math${colors.reset}`);
508
+ process.exit(1);
509
+ }
510
+
511
+ const configInfo = findConfig(process.cwd());
512
+ if (!configInfo) {
513
+ console.error(`${cross} ${colors.red}Error: void.config.json not found.${colors.reset}`);
514
+ process.exit(1);
515
+ }
516
+
517
+ const pluginDir = path.join(process.cwd(), "node_modules", pluginName);
518
+ if (!fs.existsSync(pluginDir)) {
519
+ console.error(`${cross} ${colors.red}Error: Plugin '${pluginName}' is not installed in this project.${colors.reset}`);
520
+ process.exit(1);
521
+ }
522
+
523
+ const wasmPath = path.join(pluginDir, "plugin.wasm");
524
+ if (!fs.existsSync(wasmPath)) {
525
+ console.error(`${cross} ${colors.red}Error: WASM binary not found at: ${wasmPath}${colors.reset}`);
526
+ process.exit(1);
527
+ }
528
+
529
+ // Dynamically load the plugin and query its functions
530
+ try {
531
+ const runtimePath = path.join(process.cwd(), "node_modules", "@tgrv", "void-runtime", "index.js");
532
+ const { runtime } = await import(pathToFileURL(path.resolve(runtimePath)).toString());
533
+ const plugin = await runtime.load(wasmPath);
534
+
535
+ // Query reflected functions list
536
+ const functions = plugin.callInternal("__list_functions__", {});
537
+
538
+ console.log(`\n${colors.bold}${colors.magenta}=========================================${colors.reset}`);
539
+ console.log(`${colors.bold}${colors.cyan}Plugin '${pluginName}' Details:${colors.reset}`);
540
+ console.log(`${colors.bold}${colors.magenta}=========================================${colors.reset}`);
541
+ console.log(`${colors.bold}Location:${colors.reset} ${pluginDir}`);
542
+ console.log(`${colors.bold}Functions:${colors.reset}`);
543
+ if (Array.isArray(functions) && functions.length > 0) {
544
+ for (const f of functions) {
545
+ console.log(" " + tick + " " + f);
546
+ }
547
+ } else {
548
+ console.log(` (no functions registered)`);
549
+ }
550
+ console.log(`${colors.bold}${colors.magenta}=========================================${colors.reset}\n`);
551
+ } catch (err) {
552
+ console.error(`${cross} ${colors.red}Error loading plugin details: ${err.message}${colors.reset}`);
553
+ process.exit(1);
554
+ }
555
+ break;
556
+ }
557
+
558
+ default:
559
+ console.error(`${cross} ${colors.red}Unknown command: ${command}${colors.reset}`);
560
+ process.exit(1);
561
+ }
562
+ }
563
+
564
+ main().catch((err) => {
565
+ console.error("CLI error:", err);
566
+ process.exit(1);
567
+ });
package/package.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "@tgrv/void-cli",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "void": "./bin/void.js"
7
+ },
8
+ "dependencies": {}
9
+ }