@tgrv/void-cli 1.0.9 → 1.1.1

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.
package/README.md CHANGED
@@ -14,33 +14,37 @@ npm install -g @tgrv/void-cli
14
14
 
15
15
  ### 1. Initialize a new application project
16
16
  ```bash
17
- void init [path]
17
+ void init [app-path]
18
18
  ```
19
- Creates standard configuration templates and installs the `@tgrv/void-runtime` engine automatically.
19
+ Creates standard configuration templates and installs the `@tgrv/void-runtime` engine automatically. Defaults to the current folder (`.`).
20
20
 
21
21
  ### 2. Create a new Rust or Go plugin project
22
22
  ```bash
23
23
  void create [plugin-path]
24
24
  ```
25
- Prompts for language configuration and instantiates starting templates with pre-configured SDK targets.
25
+ Prompts for language configuration and instantiates starting templates with pre-configured SDK targets. Defaults to the current folder (`.`).
26
26
 
27
27
  ### 3. Build a plugin locally
28
28
  ```bash
29
29
  void build [plugin-path]
30
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 (`.`).
31
+ Compiles source files (Cargo/Go) and wraps the WebAssembly binary and configured assets under the build directory configured in `void.json`. Run from a plugin root folder (defaults to current folder `.`).
32
32
 
33
33
  ### 4. Publish a plugin
34
34
  ```bash
35
35
  void publish [plugin-path]
36
36
  ```
37
- Compiles and triggers `npm publish` natively from inside the scoped build directory. Defaults to current folder (`.`).
37
+ Compiles the plugin and runs `npm publish` natively from inside the built directory. Run from a plugin root folder (defaults to current folder `.`).
38
38
 
39
39
  ### 5. Install / Add a plugin
40
40
  ```bash
41
- void add <plugin-name>
41
+ void add <plugin-name-or-build-path>
42
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`.
43
+ Installs a plugin into the application:
44
+ - **Registry:** `void add <plugin-name>` downloads the plugin from the NPM registry.
45
+ - **Local Development / Testing:** `void add <build-folder-path>` (e.g. `void add ../plugins/math/@void/void-math`) copies the compiled local build output directory directly.
46
+
47
+ Adds the plugin dependency automatically to `void.config.json` and `package.json`.
44
48
 
45
49
  ### 6. Remove a plugin
46
50
  ```bash
package/bin/void.js CHANGED
@@ -72,6 +72,14 @@ function ensureDir(dir) {
72
72
  }
73
73
  }
74
74
 
75
+ function toJsFriendlyName(name) {
76
+ let clean = name.includes('/') ? name.split('/').pop() : name;
77
+ clean = clean.replace(/^[^a-zA-Z_$]+|[^a-zA-Z0-9_$]+$/g, '');
78
+ return clean.replace(/[-_]([a-z0-9])/gi, (match, group1) => {
79
+ return group1.toUpperCase();
80
+ });
81
+ }
82
+
75
83
  function copyFolderRecursive(src, dest, replacements = {}) {
76
84
  ensureDir(dest);
77
85
  const entries = fs.readdirSync(src, { withFileTypes: true });
@@ -100,48 +108,138 @@ function copyFolderRecursive(src, dest, replacements = {}) {
100
108
  }
101
109
  }
102
110
 
103
- function copyNonSourceFiles(src, dest, buildOutputDir) {
104
- ensureDir(dest);
105
- const entries = fs.readdirSync(src, { withFileTypes: true });
106
- for (const entry of entries) {
107
- const srcPath = path.join(src, entry.name);
108
- const destPath = path.join(dest, entry.name);
109
-
110
- // Prevent copying the build output directory into itself
111
- if (srcPath === buildOutputDir || srcPath.startsWith(buildOutputDir + path.sep)) {
112
- continue;
113
- }
114
-
115
- // Skip standard build/dev folders
116
- if (entry.name === "@void" ||
117
- entry.name === "@tgrv" ||
118
- entry.name === "target" ||
119
- entry.name === ".git" ||
120
- entry.name === "node_modules") {
121
- continue;
111
+ function matchGlob(filePath, pattern) {
112
+ const normalizedPath = filePath.replace(/\\/g, "/");
113
+ const normalizedPattern = pattern.replace(/\\/g, "/");
114
+
115
+ if (normalizedPath === normalizedPattern) {
116
+ return true;
117
+ }
118
+ const prefix = normalizedPattern.endsWith('/') ? normalizedPattern : normalizedPattern + '/';
119
+ if (normalizedPath.startsWith(prefix)) {
120
+ return true;
121
+ }
122
+
123
+ const globToRegex = (glob) => {
124
+ return '^' + glob
125
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
126
+ .replace(/\*\*/g, '___DOUBLE_STAR___')
127
+ .replace(/\*/g, '___SINGLE_STAR___')
128
+ .replace(/\?/g, '___QUESTION___')
129
+ .replace(/___DOUBLE_STAR___/g, '.*')
130
+ .replace(/___SINGLE_STAR___/g, '[^/]*')
131
+ .replace(/___QUESTION___/g, '.') + '$';
132
+ };
133
+
134
+ const regex = new RegExp(globToRegex(normalizedPattern));
135
+
136
+ if (!normalizedPattern.includes('/')) {
137
+ const baseName = path.basename(normalizedPath);
138
+ if (regex.test(baseName)) {
139
+ return true;
122
140
  }
123
-
124
- if (entry.isDirectory()) {
125
- copyNonSourceFiles(srcPath, destPath, buildOutputDir);
126
- } else {
127
- const ext = path.extname(entry.name).toLowerCase();
128
- const filename = entry.name.toLowerCase();
141
+ }
142
+
143
+ return regex.test(normalizedPath);
144
+ }
145
+
146
+ function copyNonSourceFiles(src, dest, buildOutputDir, filesConfig, wasmFilename) {
147
+ if (!filesConfig || !Array.isArray(filesConfig) || filesConfig.length === 0) {
148
+ return;
149
+ }
150
+
151
+ function traverse(currentDir) {
152
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
153
+ for (const entry of entries) {
154
+ const fullPath = path.join(currentDir, entry.name);
129
155
 
130
- // Exclude Go/Rust/Cargo files and build binaries/configs
131
- if (ext === ".go" ||
132
- ext === ".rs" ||
133
- filename === "cargo.toml" ||
134
- filename === "cargo.lock" ||
135
- filename === "go.mod" ||
136
- filename === "go.sum" ||
137
- filename === "void.json" ||
138
- filename === "plugin.wasm") {
156
+ // Prevent copying build output directory or skipped directories
157
+ if (fullPath === buildOutputDir || fullPath.startsWith(buildOutputDir + path.sep)) {
139
158
  continue;
140
159
  }
141
-
142
- fs.copyFileSync(srcPath, destPath);
160
+ if (entry.name === "@void" ||
161
+ entry.name === "@tgrv" ||
162
+ entry.name === "target" ||
163
+ entry.name === ".git" ||
164
+ entry.name === "node_modules" ||
165
+ entry.name === "void.json" ||
166
+ (wasmFilename && entry.name === wasmFilename)) {
167
+ continue;
168
+ }
169
+
170
+ if (entry.isDirectory()) {
171
+ traverse(fullPath);
172
+ } else {
173
+ const relativePath = path.relative(src, fullPath);
174
+
175
+ // Check if relativePath matches any glob in filesConfig
176
+ const matches = filesConfig.some(pattern => matchGlob(relativePath, pattern));
177
+ if (matches) {
178
+ const destPath = path.join(dest, relativePath);
179
+ ensureDir(path.dirname(destPath));
180
+ fs.copyFileSync(fullPath, destPath);
181
+ }
182
+ }
143
183
  }
144
184
  }
185
+
186
+ traverse(src);
187
+ }
188
+
189
+ function cleanupOutputDirectory(pluginDir, buildOutputDir, filesConfig, wasmFilename, typesFilename) {
190
+ if (!fs.existsSync(buildOutputDir)) return;
191
+
192
+ function traverse(currentDir) {
193
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
194
+ for (const entry of entries) {
195
+ const fullPath = path.join(currentDir, entry.name);
196
+ const relativePath = path.relative(buildOutputDir, fullPath);
197
+
198
+ if (entry.isDirectory()) {
199
+ traverse(fullPath);
200
+ // Clean up empty directories
201
+ if (fs.readdirSync(fullPath).length === 0) {
202
+ try {
203
+ fs.rmdirSync(fullPath);
204
+ } catch (e) {}
205
+ }
206
+ } else {
207
+ // Skip core files
208
+ if (entry.name === "index.js" ||
209
+ entry.name === "package.json" ||
210
+ entry.name === wasmFilename ||
211
+ (typesFilename && entry.name === typesFilename)) {
212
+ continue;
213
+ }
214
+
215
+ // Check if the file still exists in the source plugin root
216
+ const sourcePath = path.join(pluginDir, relativePath);
217
+ if (!fs.existsSync(sourcePath)) {
218
+ try {
219
+ fs.unlinkSync(fullPath);
220
+ } catch (e) {}
221
+ continue;
222
+ }
223
+
224
+ // Check if the file still matches the files configuration
225
+ if (!filesConfig || !Array.isArray(filesConfig) || filesConfig.length === 0) {
226
+ try {
227
+ fs.unlinkSync(fullPath);
228
+ } catch (e) {}
229
+ continue;
230
+ }
231
+
232
+ const matches = filesConfig.some(pattern => matchGlob(relativePath, pattern));
233
+ if (!matches) {
234
+ try {
235
+ fs.unlinkSync(fullPath);
236
+ } catch (e) {}
237
+ }
238
+ }
239
+ }
240
+ }
241
+
242
+ traverse(buildOutputDir);
145
243
  }
146
244
 
147
245
  const args = process.argv.slice(2);
@@ -164,7 +262,7 @@ ${colors.bold}Usage:${colors.reset}
164
262
  process.exit(0);
165
263
  }
166
264
 
167
- // Build core compilation wrapper function to resolve output directory using void.json manifest
265
+ // Build core compilation wrapper
168
266
  function runBuild(pluginPathArg) {
169
267
  const absolutePluginDir = path.resolve(process.cwd(), pluginPathArg);
170
268
  if (!fs.existsSync(absolutePluginDir)) {
@@ -192,10 +290,21 @@ function runBuild(pluginPathArg) {
192
290
  process.exit(1);
193
291
  }
194
292
 
293
+ if (manifest.types) {
294
+ const typesPath = path.resolve(absolutePluginDir, manifest.types);
295
+ if (!fs.existsSync(typesPath) || !fs.statSync(typesPath).isFile()) {
296
+ console.error(`${cross} ${colors.red}Error: Types file '${manifest.types}' specified in void.json does not exist.${colors.reset}`);
297
+ process.exit(1);
298
+ }
299
+ }
300
+
195
301
  const pluginName = manifest.name;
196
302
  const pluginType = manifest.type;
197
303
  const buildDir = manifest.buildDir;
198
304
  const buildOutputDir = path.join(absolutePluginDir, buildDir);
305
+ const rawExport = manifest.export || "plugin";
306
+ const exportName = toJsFriendlyName(rawExport);
307
+ const wasmFilename = `${exportName}.wasm`;
199
308
 
200
309
  if (pluginType === "sdk") {
201
310
  console.log(`\n${info} Packaging SDK '${colors.bold}${pluginName}${colors.reset}' to local build at: ${colors.bold}${buildOutputDir}${colors.reset}`);
@@ -227,9 +336,9 @@ function runBuild(pluginPathArg) {
227
336
  }
228
337
  // Go plugin compilation
229
338
  else if (pluginType === "go") {
230
- const outputWasm = path.join(absolutePluginDir, "plugin.wasm");
231
- console.log(`${info} Running: ${colors.blue}go build -o plugin.wasm${colors.reset}`);
232
- const compileSuccess = runCommand("go build -o plugin.wasm", {
339
+ const outputWasm = path.join(absolutePluginDir, wasmFilename);
340
+ console.log(`${info} Running: ${colors.blue}go build -o ${wasmFilename}${colors.reset}`);
341
+ const compileSuccess = runCommand(`go build -o ${wasmFilename}`, {
233
342
  cwd: absolutePluginDir,
234
343
  env: { ...process.env, GOOS: "wasip1", GOARCH: "wasm" },
235
344
  });
@@ -238,6 +347,45 @@ function runBuild(pluginPathArg) {
238
347
  process.exit(1);
239
348
  }
240
349
  builtWasmPath = outputWasm;
350
+ }
351
+ // C++ plugin compilation
352
+ else if (pluginType === "cpp") {
353
+ console.log(`${info} Running: ${colors.blue}emcmake cmake -B build -DCMAKE_BUILD_TYPE=Release${colors.reset}`);
354
+ const configSuccess = runCommand("emcmake cmake -B build -DCMAKE_BUILD_TYPE=Release", { cwd: absolutePluginDir });
355
+ if (!configSuccess) {
356
+ console.error(`${cross} ${colors.red}Emscripten CMake configuration failed. Make sure emscripten SDK is active.${colors.reset}`);
357
+ process.exit(1);
358
+ }
359
+
360
+ console.log(`${info} Running: ${colors.blue}cmake --build build --config Release${colors.reset}`);
361
+ const buildSuccess = runCommand("cmake --build build --config Release", { cwd: absolutePluginDir });
362
+ if (!buildSuccess) {
363
+ console.error(`${cross} ${colors.red}C++ compilation failed.${colors.reset}`);
364
+ process.exit(1);
365
+ }
366
+
367
+ const buildPath = path.join(absolutePluginDir, "build");
368
+ function findWasm(dir) {
369
+ if (!fs.existsSync(dir)) return null;
370
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
371
+ for (const entry of entries) {
372
+ const fullPath = path.join(dir, entry.name);
373
+ if (entry.isDirectory()) {
374
+ const found = findWasm(fullPath);
375
+ if (found) return found;
376
+ } else if (entry.name.endsWith(".wasm")) {
377
+ return fullPath;
378
+ }
379
+ }
380
+ return null;
381
+ }
382
+
383
+ const wasmFile = findWasm(buildPath);
384
+ if (!wasmFile) {
385
+ console.error(`${cross} ${colors.red}Could not find compiled .wasm file in C++ build directory${colors.reset}`);
386
+ process.exit(1);
387
+ }
388
+ builtWasmPath = wasmFile;
241
389
  } else {
242
390
  console.error(`${cross} ${colors.red}Unsupported plugin type: '${pluginType}' in void.json${colors.reset}`);
243
391
  process.exit(1);
@@ -246,8 +394,20 @@ function runBuild(pluginPathArg) {
246
394
  console.log(`${info} Placing build output to local folder at ${colors.bold}${buildOutputDir}${colors.reset}...`);
247
395
  ensureDir(buildOutputDir);
248
396
 
397
+ // Remove past builds' .wasm files from the build output directory
398
+ if (fs.existsSync(buildOutputDir)) {
399
+ const files = fs.readdirSync(buildOutputDir);
400
+ for (const file of files) {
401
+ if (file.endsWith(".wasm")) {
402
+ try {
403
+ fs.unlinkSync(path.join(buildOutputDir, file));
404
+ } catch (e) {}
405
+ }
406
+ }
407
+ }
408
+
249
409
  // Copy WASM
250
- fs.copyFileSync(builtWasmPath, path.join(buildOutputDir, "plugin.wasm"));
410
+ fs.copyFileSync(builtWasmPath, path.join(buildOutputDir, wasmFilename));
251
411
 
252
412
  // Clean up temporary compiled WASM
253
413
  if (path.dirname(builtWasmPath) === absolutePluginDir) {
@@ -258,7 +418,7 @@ function runBuild(pluginPathArg) {
258
418
  }
259
419
  }
260
420
 
261
- // Write package.json if it does not already exist, or check version if it does
421
+ // Write package.json if it does not already exist, or check version/types if it does
262
422
  const packageJsonPath = path.join(buildOutputDir, "package.json");
263
423
  if (!fs.existsSync(packageJsonPath)) {
264
424
  const pkgJson = {
@@ -268,25 +428,52 @@ function runBuild(pluginPathArg) {
268
428
  main: "index.js",
269
429
  dependencies: {
270
430
  "@tgrv/void-runtime": "^1.0.0",
431
+ ...(manifest.dependencies || {})
271
432
  },
272
433
  publishConfig: {
273
434
  access: "public"
274
435
  }
275
436
  };
437
+ if (manifest.types) {
438
+ pkgJson.types = manifest.types;
439
+ }
276
440
  fs.writeFileSync(packageJsonPath, JSON.stringify(pkgJson, null, 2));
277
441
  } else {
278
442
  try {
279
443
  const existingPkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
444
+ let changed = false;
280
445
  if (existingPkg.version !== manifest.version) {
281
446
  console.log(`${info} Version mismatch detected in ${packageJsonPath}. Updating from ${existingPkg.version} to ${manifest.version} to match void.json.`);
282
447
  existingPkg.version = manifest.version || "1.0.0";
448
+ changed = true;
449
+ }
450
+ if (manifest.types && existingPkg.types !== manifest.types) {
451
+ existingPkg.types = manifest.types;
452
+ changed = true;
453
+ }
454
+ if (manifest.dependencies) {
455
+ existingPkg.dependencies = {
456
+ ...existingPkg.dependencies,
457
+ ...manifest.dependencies
458
+ };
459
+ changed = true;
460
+ }
461
+ if (changed) {
283
462
  fs.writeFileSync(packageJsonPath, JSON.stringify(existingPkg, null, 2));
284
463
  }
285
464
  } catch (err) {
286
- console.warn(`${warning} Failed to check/update existing package.json version: ${err.message}`);
465
+ console.warn(`${warning} Failed to check/update existing package.json version/types: ${err.message}`);
287
466
  }
288
467
  }
289
468
 
469
+ // Copy types file if specified
470
+ if (manifest.types) {
471
+ const typesPath = path.resolve(absolutePluginDir, manifest.types);
472
+ const destTypesPath = path.join(buildOutputDir, manifest.types);
473
+ ensureDir(path.dirname(destTypesPath));
474
+ fs.copyFileSync(typesPath, destTypesPath);
475
+ }
476
+
290
477
  // Generate standard ESM index.js loader
291
478
  const indexJsContent = `import { runtime } from "@tgrv/void-runtime";
292
479
  import { fileURLToPath } from "url";
@@ -295,14 +482,15 @@ import { dirname, join } from "path";
295
482
  const __filename = fileURLToPath(import.meta.url);
296
483
  const __dirname = dirname(__filename);
297
484
 
298
- const plugin = await runtime.load(
299
- join(__dirname, "plugin.wasm")
485
+ const ${exportName} = await runtime.load(
486
+ join(__dirname, "${wasmFilename}")
300
487
  );
301
488
 
302
- export default plugin;
489
+ export default ${exportName};
303
490
  `;
304
491
  fs.writeFileSync(path.join(buildOutputDir, "index.js"), indexJsContent);
305
- copyNonSourceFiles(absolutePluginDir, buildOutputDir, buildOutputDir);
492
+ copyNonSourceFiles(absolutePluginDir, buildOutputDir, buildOutputDir, manifest.files, wasmFilename);
493
+ cleanupOutputDirectory(absolutePluginDir, buildOutputDir, manifest.files, wasmFilename, manifest.types);
306
494
  console.log(`${tick} ${colors.green}Successfully completed compilation & packaging!${colors.reset}`);
307
495
  return buildOutputDir;
308
496
  }
@@ -582,13 +770,13 @@ try {
582
770
  // Prompt for language
583
771
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
584
772
  const rawLanguage = await new Promise((resolve) => {
585
- rl.question(`${info} Select plugin language (go/rust) [default: rust]: `, (ans) => {
773
+ rl.question(`${info} Select plugin language (go/rust/cpp) [default: rust]: `, (ans) => {
586
774
  resolve(ans.trim().toLowerCase() || "rust");
587
775
  });
588
776
  });
589
777
  rl.close();
590
778
 
591
- if (rawLanguage !== "rust" && rawLanguage !== "go") {
779
+ if (rawLanguage !== "rust" && rawLanguage !== "go" && rawLanguage !== "cpp") {
592
780
  console.error(`${cross} ${colors.red}Error: Unsupported language: ${rawLanguage}${colors.reset}`);
593
781
  process.exit(1);
594
782
  }
@@ -610,7 +798,7 @@ try {
610
798
  "\\{\\{type\\}\\}": "rust",
611
799
  "\\{\\{workspace_dir\\}\\}": workspaceDir.replace(/\\/g, "/"),
612
800
  });
613
- } else {
801
+ } else if (rawLanguage === "go") {
614
802
  // Verify Go
615
803
  const goCheck = runCommand("go version");
616
804
  if (!goCheck) {
@@ -627,6 +815,23 @@ try {
627
815
  "\\{\\{type\\}\\}": "go",
628
816
  "\\{\\{workspace_dir\\}\\}": workspaceDir.replace(/\\/g, "/"),
629
817
  });
818
+ } else {
819
+ // Verify CMake
820
+ const cmakeCheck = runCommand("cmake --version");
821
+ if (!cmakeCheck) {
822
+ console.error(`\n${cross} ${colors.red}Error: 'cmake' is not installed on your machine.${colors.reset}`);
823
+ console.error(`${colors.yellow}Please install CMake from https://cmake.org/ first.${colors.reset}\n`);
824
+ process.exit(1);
825
+ }
826
+
827
+ console.log(`${info} Creating C++ plugin template at ${colors.bold}${targetDir}${colors.reset}...`);
828
+ ensureDir(targetDir);
829
+ const templateSrc = path.join(cliRoot, "templates", "cpp");
830
+ copyFolderRecursive(templateSrc, targetDir, {
831
+ "\\{\\{name\\}\\}": folderName,
832
+ "\\{\\{type\\}\\}": "cpp",
833
+ "\\{\\{workspace_dir\\}\\}": workspaceDir.replace(/\\/g, "/"),
834
+ });
630
835
  }
631
836
 
632
837
  console.log(`${tick} ${colors.green}Plugin created successfully under '${pluginPathArg}'!${colors.reset}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tgrv/void-cli",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "void": "./bin/void.js"
@@ -0,0 +1,27 @@
1
+ cmake_minimum_required(VERSION 3.14)
2
+ project({{name}} LANGUAGES CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+
7
+ # Configure output as a WebAssembly module
8
+ set(CMAKE_EXECUTABLE_SUFFIX ".wasm")
9
+
10
+ # Exclude entry point and export functions explicitly for Emscripten
11
+ if (EMSCRIPTEN)
12
+ set(EXPORT_FUNCTIONS "-sEXPORTED_FUNCTIONS=_void_malloc,_void_free,_void_free_string,_void_invoke,_void_init")
13
+ set(EXPORT_METHODS "-sEXPORTED_RUNTIME_METHODS=ccall,cwrap")
14
+ add_link_options(
15
+ "${EXPORT_FUNCTIONS}"
16
+ "${EXPORT_METHODS}"
17
+ "--no-entry"
18
+ "-sALLOW_MEMORY_GROWTH=1"
19
+ )
20
+ endif()
21
+
22
+ # Reference Void C++ SDK
23
+ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/@tgrv/void-sdk-cpp" void-sdk-cpp-build)
24
+
25
+
26
+ add_executable({{name}} src/plugin.cpp)
27
+ target_link_libraries({{name}} PRIVATE void-sdk-cpp)
@@ -0,0 +1,57 @@
1
+ # Void C++ Plugin Template
2
+
3
+ A template for building WebAssembly (WASM) plugins for the **Void** framework using C++.
4
+
5
+ ## 🚀 Getting Started
6
+
7
+ > [!IMPORTANT]
8
+ > Before building your plugin, run **`npm install`** (or `npm i`) in this root directory to download the C++ SDK dependency (`@tgrv/void-sdk-cpp`). The `package.json` file in this root folder is only used for local development setup and resolving CMake dependencies; it is **not** the package that gets published. The final publishable package (with its own generated `package.json`) will be built inside your output directory (configured in `void.json` as `buildDir`).
9
+
10
+
11
+ ### 1. Where to Start
12
+ Write your plugin logic inside [src/plugin.cpp](src/plugin.cpp). Define handlers matching the signature:
13
+ ```cpp
14
+ nlohmann::json MyHandler(const void_sdk::ArgsMap& args)
15
+ ```
16
+ And register them inside `init_handlers()`:
17
+ ```cpp
18
+ void init_handlers() {
19
+ void_sdk::register_handler("hello", hello);
20
+ }
21
+ VOID_PLUGIN(init_handlers);
22
+ ```
23
+
24
+ ### 2. Build the Plugin
25
+ Compile your plugin by running the build command in this directory:
26
+ ```bash
27
+ npx void build
28
+ ```
29
+ This runs `emcmake` to configure CMake and compiles the C++ code to a WebAssembly binary.
30
+
31
+ ### 3. Test/Install Locally
32
+ To test the built plugin in a local Void application:
33
+ 1. Initialize your application project: `npx void init`
34
+ 2. Add your compiled local plugin build output folder path:
35
+ ```bash
36
+ npx void add ./path/to/plugin/@void/<plugin-name>
37
+ ```
38
+
39
+ ### 4. Publish
40
+ To build and publish the plugin to the npm registry:
41
+ ```bash
42
+ npx void publish
43
+ ```
44
+
45
+ ---
46
+
47
+ ## ⚙️ Configuration (`void.json`)
48
+
49
+ Your plugin configuration is defined in `void.json`. Here are the available fields:
50
+
51
+ - **`name`**: The package name of your plugin (e.g. `my-plugin`).
52
+ - **`version`**: The current semantic version of the plugin.
53
+ - **`type`**: The compilation target language. Set to `"cpp"`.
54
+ - **`buildDir`**: The target directory where compilation and wrapping assets are generated (e.g. `@void/my-plugin`).
55
+ - **`types`**: Path to the TypeScript declaration file (e.g. `"types.d.ts"`).
56
+ - **`export`**: The name of the JavaScript variable used to load the WASM binary, which is also the default export name (e.g. `"myPlugin"`). **Note:** Ensure you use a JS-friendly name (e.g. alphanumeric/camelCase) to ensure the generated JS code does not contain syntax errors. The CLI will automatically sanitize special characters (like `-`, `_`, or `@scope/`) into camelCase/JS-friendly format for the variable and the generated WASM binary name.
57
+ - **`files`**: An array of glob patterns of non-source files (such as `*.md` or `*.d.ts`) to copy from the plugin root into the build output folder.
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "void-cpp-plugin",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "dependencies": {
7
+ "@tgrv/void-runtime": "^1.0.0",
8
+ "@tgrv/void-sdk-cpp": "^1.0.0"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "types": "types.d.ts"
14
+ }
@@ -0,0 +1,17 @@
1
+ #include <void/void.hpp>
2
+
3
+ using json = nlohmann::json;
4
+
5
+ // Define a starter function handler
6
+ json hello(const void_sdk::ArgsMap& args) {
7
+ std::string name = void_sdk::get_string(args, "name");
8
+ return json{{"message", "Hello, " + name + " from C++!"}};
9
+ }
10
+
11
+ // Register function handlers
12
+ void init_handlers() {
13
+ void_sdk::register_handler("hello", hello);
14
+ }
15
+
16
+ // Bind initialization entrypoint
17
+ VOID_PLUGIN(init_handlers);
@@ -0,0 +1,7 @@
1
+ declare module '{{name}}' {
2
+ interface CppPlugin {
3
+ hello(args: { name: string }): { message: string };
4
+ }
5
+ const plugin: CppPlugin;
6
+ export default plugin;
7
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "1.0.0",
4
+ "type": "cpp",
5
+ "buildDir": "@void/{{name}}",
6
+ "types": "types.d.ts",
7
+ "export": "{{name}}",
8
+ "dependencies": {
9
+ "@tgrv/void-sdk-cpp": "^1.0.0"
10
+ },
11
+ "files": [
12
+ "*.d.ts",
13
+ "*.md"
14
+ ]
15
+ }
@@ -0,0 +1,52 @@
1
+ # Void Go Plugin Template
2
+
3
+ A template for building WebAssembly (WASM) plugins for the **Void** framework using Go.
4
+
5
+ ## 🚀 Getting Started
6
+
7
+ ### 1. Where to Start
8
+ Write your plugin logic inside [main.go](file:///main.go). Exported functions must match the signature:
9
+ ```go
10
+ func MyFunction(args map[string]json.RawMessage) (any, error)
11
+ ```
12
+ And register them in `main()` using the Void SDK:
13
+ ```go
14
+ func main() {
15
+ void.Register("my_function", MyFunction)
16
+ }
17
+ ```
18
+
19
+ ### 2. Build the Plugin
20
+ Compile your plugin by running the build command in this directory:
21
+ ```bash
22
+ npx void build
23
+ ```
24
+ This compiles the Go code into a WebAssembly binary (`.wasm`) and packages it inside the build output directory configured in `void.json`.
25
+
26
+ ### 3. Test/Install Locally
27
+ To test the built plugin in a local Void application:
28
+ 1. Initialize your application project: `npx void init`
29
+ 2. Add your compiled local plugin build output folder path:
30
+ ```bash
31
+ npx void add ./path/to/plugin/@void/<plugin-name>
32
+ ```
33
+
34
+ ### 4. Publish
35
+ To build and publish the plugin to the npm registry:
36
+ ```bash
37
+ npx void publish
38
+ ```
39
+
40
+ ---
41
+
42
+ ## ⚙️ Configuration (`void.json`)
43
+
44
+ Your plugin configuration is defined in `void.json`. Here are the available fields:
45
+
46
+ - **`name`**: The package name of your plugin (e.g. `@void/my-plugin`).
47
+ - **`version`**: The current semantic version of the plugin.
48
+ - **`type`**: The compilation target language. Set to `"go"`.
49
+ - **`buildDir`**: The target directory where compilation and wrapping assets are generated (e.g. `@void/my-plugin`).
50
+ - **`types`**: Path to the TypeScript declaration file (e.g. `"types.d.ts"`). During the build process, the CLI validates its existence, copies it to the build output, and automatically sets the `"types"` entry in `package.json` for autocomplete and editor support.
51
+ - **`export`**: The name of the JavaScript variable used to load the WASM binary, which is also the default export name (e.g. `"myPlugin"`). **Note:** Ensure you use a JS-friendly name (e.g. alphanumeric/camelCase) to ensure the generated JS code does not contain syntax errors. The CLI will automatically sanitize special characters (like `-`, `_`, or `@scope/`) into camelCase/JS-friendly format for the variable and the generated WASM binary name.
52
+ - **`files`**: An array of glob patterns of non-source files (such as `*.md` or `*.d.ts`) to copy from the plugin root into the build output folder.
@@ -1,6 +1,11 @@
1
1
  {
2
- "name": "@void/{{name}}",
2
+ "name": "{{name}}",
3
3
  "version": "1.0.0",
4
4
  "type": "go",
5
- "buildDir": "@void/{{name}}"
5
+ "buildDir": "@void/{{name}}",
6
+ "export": "{{name}}",
7
+ "files": [
8
+ "*.d.ts",
9
+ "*.md"
10
+ ]
6
11
  }
@@ -0,0 +1,44 @@
1
+ # Void Rust Plugin Template
2
+
3
+ A template for building WebAssembly (WASM) plugins for the **Void** framework using Rust.
4
+
5
+ ## 🚀 Getting Started
6
+
7
+ ### 1. Where to Start
8
+ Write your plugin logic inside [src/lib.rs](file:///src/lib.rs). Functions must be annotated with the Void SDK macro and registered:
9
+ Refer to the Void Rust SDK documentation for macro registry instructions.
10
+
11
+ ### 2. Build the Plugin
12
+ Compile your plugin by running the build command in this directory:
13
+ ```bash
14
+ npx void build
15
+ ```
16
+ This compiles the Rust source code into a WebAssembly binary (`.wasm`) using `cargo build --target wasm32-unknown-unknown --release` and packages it under the build directory configured in `void.json`.
17
+
18
+ ### 3. Test/Install Locally
19
+ To test the built plugin in a local Void application:
20
+ 1. Initialize your application project: `npx void init`
21
+ 2. Add your compiled local plugin build output folder path:
22
+ ```bash
23
+ npx void add ./path/to/plugin/@void/<plugin-name>
24
+ ```
25
+
26
+ ### 4. Publish
27
+ To build and publish the plugin to the npm registry:
28
+ ```bash
29
+ npx void publish
30
+ ```
31
+
32
+ ---
33
+
34
+ ## ⚙️ Configuration (`void.json`)
35
+
36
+ Your plugin configuration is defined in `void.json`. Here are the available fields:
37
+
38
+ - **`name`**: The package name of your plugin (e.g. `@void/my-plugin`).
39
+ - **`version`**: The current semantic version of the plugin.
40
+ - **`type`**: The compilation target language. Set to `"rust"`.
41
+ - **`buildDir`**: The target directory where compilation and wrapping assets are generated (e.g. `@void/my-plugin`).
42
+ - **`types`**: Path to the TypeScript declaration file (e.g. `"types.d.ts"`). During the build process, the CLI validates its existence, copies it to the build output, and automatically sets the `"types"` entry in `package.json` for autocomplete and editor support.
43
+ - **`export`**: The name of the JavaScript variable used to load the WASM binary, which is also the default export name (e.g. `"myPlugin"`). **Note:** Ensure you use a JS-friendly name (e.g. alphanumeric/camelCase) to ensure the generated JS code does not contain syntax errors. The CLI will automatically sanitize special characters (like `-`, `_`, or `@scope/`) into camelCase/JS-friendly format for the variable and the generated WASM binary name.
44
+ - **`files`**: An array of glob patterns of non-source files (such as `*.md` or `*.d.ts`) to copy from the plugin root into the build output folder.
@@ -1,6 +1,11 @@
1
1
  {
2
- "name": "@void/{{name}}",
2
+ "name": "{{name}}",
3
3
  "version": "1.0.0",
4
4
  "type": "rust",
5
- "buildDir": "@void/{{name}}"
5
+ "buildDir": "@void/{{name}}",
6
+ "export": "{{name}}",
7
+ "files": [
8
+ "*.d.ts",
9
+ "*.md"
10
+ ]
6
11
  }