@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 +11 -7
- package/bin/void.js +255 -50
- package/package.json +1 -1
- package/templates/cpp/CMakeLists.txt +27 -0
- package/templates/cpp/README.md +57 -0
- package/templates/cpp/package.json +14 -0
- package/templates/cpp/src/plugin.cpp +17 -0
- package/templates/cpp/types.d.ts +7 -0
- package/templates/cpp/void.json +15 -0
- package/templates/go/README.md +52 -0
- package/templates/go/void.json +7 -2
- package/templates/rust/README.md +44 -0
- package/templates/rust/void.json +7 -2
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
|
|
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
|
|
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
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
//
|
|
131
|
-
if (
|
|
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
|
-
|
|
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
|
|
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,
|
|
231
|
-
console.log(`${info} Running: ${colors.blue}go build -o
|
|
232
|
-
const compileSuccess = runCommand(
|
|
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,
|
|
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
|
|
299
|
-
join(__dirname, "
|
|
485
|
+
const ${exportName} = await runtime.load(
|
|
486
|
+
join(__dirname, "${wasmFilename}")
|
|
300
487
|
);
|
|
301
488
|
|
|
302
|
-
export default
|
|
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
|
@@ -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,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.
|
package/templates/go/void.json
CHANGED
|
@@ -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.
|
package/templates/rust/void.json
CHANGED