@helix3/helix-cli 0.1.18-helix3.367 → 0.1.18-helix3.368
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/dist/bridge/zipSource.d.ts +27 -0
- package/dist/bridge/zipSource.js +90 -0
- package/dist/bridge/zipSource.js.map +1 -0
- package/dist/index.js +26 -12
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/bridge.md +3 -1
- package/package.json +1 -1
- package/plugins/beamng-vehicle/README.md +22 -2
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_audio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_engine.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/test_beamng_engine.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/beamng-vehicle.mjs +13 -2
- package/plugins/beamng-vehicle/bin/beamng_engine.py +184 -0
- package/plugins/beamng-vehicle/bin/beamng_jbeam.py +19 -1
- package/plugins/beamng-vehicle/bin/lower-beamng.py +29 -8
- package/plugins/beamng-vehicle/bin/test_beamng_engine.py +124 -0
- package/plugins/beamng-vehicle/bridge-plugin.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ArchiveLimits } from '../sceneImport/archive';
|
|
2
|
+
/**
|
|
3
|
+
* Caps for a mod archive downloaded from a repository. Entry count matches the BeamNG importer's own
|
|
4
|
+
* 50,000-file source ceiling; the byte ceilings are per entry and in total, and the compression-ratio
|
|
5
|
+
* guard from the shared archive reader still applies.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ZIP_SOURCE_LIMITS: ArchiveLimits;
|
|
8
|
+
export type PreparedBridgeSource = {
|
|
9
|
+
/** Directory (or the original file) the Bridge should read. */
|
|
10
|
+
path: string;
|
|
11
|
+
/** Set when `path` is a temporary extraction of this ZIP. */
|
|
12
|
+
extractedFrom?: {
|
|
13
|
+
archive: string;
|
|
14
|
+
sha256: string;
|
|
15
|
+
entries: number;
|
|
16
|
+
uncompressedBytes: number;
|
|
17
|
+
};
|
|
18
|
+
cleanup(): Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
export declare function isZipSourcePath(source: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Safely extract a `.zip` source to a private temporary directory. The archive is rejected as a whole
|
|
23
|
+
* if any entry is absolute, climbs out with `..`, is duplicated, exceeds the size caps, or looks like a
|
|
24
|
+
* decompression bomb; members are written with exclusive-create into an empty real directory that is
|
|
25
|
+
* removed by `cleanup()` (and on process exit). A non-zip source is returned unchanged.
|
|
26
|
+
*/
|
|
27
|
+
export declare function prepareBridgeSource(source: string, onProgress?: (message: string) => void, limits?: ArchiveLimits): Promise<PreparedBridgeSource>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ZIP_SOURCE_LIMITS = void 0;
|
|
4
|
+
exports.isZipSourcePath = isZipSourcePath;
|
|
5
|
+
exports.prepareBridgeSource = prepareBridgeSource;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_os_1 = require("node:os");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const archive_1 = require("../sceneImport/archive");
|
|
11
|
+
/**
|
|
12
|
+
* Caps for a mod archive downloaded from a repository. Entry count matches the BeamNG importer's own
|
|
13
|
+
* 50,000-file source ceiling; the byte ceilings are per entry and in total, and the compression-ratio
|
|
14
|
+
* guard from the shared archive reader still applies.
|
|
15
|
+
*/
|
|
16
|
+
exports.ZIP_SOURCE_LIMITS = {
|
|
17
|
+
maxEntries: 50_000,
|
|
18
|
+
maxEntryBytes: 1_073_741_824,
|
|
19
|
+
maxTotalBytes: 8 * 1_073_741_824,
|
|
20
|
+
maxCompressionRatio: 1_000,
|
|
21
|
+
};
|
|
22
|
+
/** Archive bookkeeping that is never part of a mod's content. */
|
|
23
|
+
const JUNK = /(?:^|\/)(?:__MACOSX|\.DS_Store|Thumbs\.db)(?:\/|$)/;
|
|
24
|
+
function isZipSourcePath(source) {
|
|
25
|
+
const path = (0, node_path_1.resolve)(source);
|
|
26
|
+
if ((0, node_path_1.extname)(path).toLowerCase() !== '.zip')
|
|
27
|
+
return false;
|
|
28
|
+
try {
|
|
29
|
+
return (0, node_fs_1.statSync)(path).isFile();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** A mod zipped with one wrapper folder (`MyMod/vehicles/...`) is read from inside that folder. */
|
|
36
|
+
function unwrapSingleFolder(root) {
|
|
37
|
+
const entries = (0, node_fs_1.readdirSync)(root, { withFileTypes: true });
|
|
38
|
+
if (entries.some((entry) => entry.isDirectory() && entry.name === 'vehicles'))
|
|
39
|
+
return root;
|
|
40
|
+
const folders = entries.filter((entry) => entry.isDirectory());
|
|
41
|
+
if (folders.length === 1 && entries.length === 1) {
|
|
42
|
+
return unwrapSingleFolder((0, node_path_1.join)(root, folders[0].name));
|
|
43
|
+
}
|
|
44
|
+
return root;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Safely extract a `.zip` source to a private temporary directory. The archive is rejected as a whole
|
|
48
|
+
* if any entry is absolute, climbs out with `..`, is duplicated, exceeds the size caps, or looks like a
|
|
49
|
+
* decompression bomb; members are written with exclusive-create into an empty real directory that is
|
|
50
|
+
* removed by `cleanup()` (and on process exit). A non-zip source is returned unchanged.
|
|
51
|
+
*/
|
|
52
|
+
async function prepareBridgeSource(source, onProgress, limits = exports.ZIP_SOURCE_LIMITS) {
|
|
53
|
+
if (!isZipSourcePath(source))
|
|
54
|
+
return { path: source, cleanup: async () => { } };
|
|
55
|
+
const archive = (0, node_path_1.resolve)(source);
|
|
56
|
+
const sha256 = await (0, archive_1.sha256File)(archive);
|
|
57
|
+
const inventory = await (0, archive_1.inventorySourceArchive)(archive, sha256, limits);
|
|
58
|
+
const members = inventory.entries
|
|
59
|
+
.filter((entry) => !entry.directory && !JUNK.test(entry.path))
|
|
60
|
+
.map((entry) => entry.path);
|
|
61
|
+
if (members.length === 0)
|
|
62
|
+
throw new Error(`Bridge source archive is empty: ${archive}`);
|
|
63
|
+
const scratch = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'helix-zip-source-'));
|
|
64
|
+
const exitCleanup = () => { try {
|
|
65
|
+
(0, node_fs_1.rmSync)(scratch, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
catch { /* best effort */ } };
|
|
68
|
+
process.once('exit', exitCleanup);
|
|
69
|
+
const cleanup = async () => {
|
|
70
|
+
process.removeListener('exit', exitCleanup);
|
|
71
|
+
await (0, promises_1.rm)(scratch, { recursive: true, force: true });
|
|
72
|
+
};
|
|
73
|
+
try {
|
|
74
|
+
const output = (0, node_path_1.join)(scratch, 'source');
|
|
75
|
+
onProgress?.(`extracting ${members.length} file(s) from ${archive} (${inventory.uncompressedBytes} bytes)`);
|
|
76
|
+
await (0, archive_1.extractArchiveMembers)({ archivePath: archive, expectedSha256: sha256, members, outputDir: output, limits });
|
|
77
|
+
return {
|
|
78
|
+
path: unwrapSingleFolder(output),
|
|
79
|
+
extractedFrom: {
|
|
80
|
+
archive, sha256, entries: members.length, uncompressedBytes: inventory.uncompressedBytes,
|
|
81
|
+
},
|
|
82
|
+
cleanup,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
await cleanup();
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=zipSource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zipSource.js","sourceRoot":"","sources":["../../src/bridge/zipSource.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -142,6 +142,20 @@ function bridgeInputs(values) {
|
|
|
142
142
|
}
|
|
143
143
|
return inputs;
|
|
144
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Bridge sources may be a `.zip` as downloaded (a BeamNG mod from the repository, say): it is extracted
|
|
147
|
+
* safely to a temporary directory for the duration of the command and removed afterwards.
|
|
148
|
+
*/
|
|
149
|
+
async function withBridgeSource(source, run) {
|
|
150
|
+
const { prepareBridgeSource } = await Promise.resolve().then(() => __importStar(require('./bridge/zipSource')));
|
|
151
|
+
const prepared = await prepareBridgeSource(source, (message) => console.error(` ${message}`));
|
|
152
|
+
try {
|
|
153
|
+
return await run(prepared.path);
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
await prepared.cleanup();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
145
159
|
function bridgeTerminalText(value) {
|
|
146
160
|
return value.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu, (character) => {
|
|
147
161
|
if (character === '\n')
|
|
@@ -178,12 +192,12 @@ const bridge = program
|
|
|
178
192
|
bridge
|
|
179
193
|
.command('detect')
|
|
180
194
|
.description('Fingerprint a source and identify its engine, format, and likely content domain')
|
|
181
|
-
.argument('<source>', 'file or
|
|
195
|
+
.argument('<source>', 'file, project directory, or a .zip of one (extracted safely to a temp dir)')
|
|
182
196
|
.option('--json', 'print the machine-readable detection record')
|
|
183
197
|
.action(async (source, opts) => {
|
|
184
198
|
try {
|
|
185
199
|
const { detectBridgeSource } = await Promise.resolve().then(() => __importStar(require('./bridge')));
|
|
186
|
-
const detection = await
|
|
200
|
+
const detection = await withBridgeSource(source, (resolved) => detectBridgeSource(resolved));
|
|
187
201
|
if (opts.json)
|
|
188
202
|
console.log(JSON.stringify(detection, null, 2));
|
|
189
203
|
else {
|
|
@@ -201,13 +215,13 @@ bridge
|
|
|
201
215
|
bridge
|
|
202
216
|
.command('inspect')
|
|
203
217
|
.description('Inspect a source and show its detected structure plus viable import goals')
|
|
204
|
-
.argument('<source>', 'file or
|
|
218
|
+
.argument('<source>', 'file, project directory, or a .zip of one (extracted safely to a temp dir)')
|
|
205
219
|
.option('--plugin-dir <paths...>', 'also discover local plugins from these directories')
|
|
206
220
|
.option('--json', 'print machine-readable inspection and plans')
|
|
207
221
|
.action(async (source, opts) => {
|
|
208
222
|
try {
|
|
209
223
|
const { buildBridgePlan, detectBridgeSource, loadBridgePlugins } = await Promise.resolve().then(() => __importStar(require('./bridge')));
|
|
210
|
-
const detection = await
|
|
224
|
+
const detection = await withBridgeSource(source, (resolved) => detectBridgeSource(resolved));
|
|
211
225
|
const plugins = loadBridgePlugins(opts.pluginDir);
|
|
212
226
|
const plans = ['asset', 'character', 'addon', 'scene', 'animation']
|
|
213
227
|
.map((type) => buildBridgePlan(detection, { type, target: 'web' }, plugins))
|
|
@@ -229,7 +243,7 @@ bridge
|
|
|
229
243
|
bridge
|
|
230
244
|
.command('plan')
|
|
231
245
|
.description('Resolve installed plugin capabilities into a deterministic conversion graph')
|
|
232
|
-
.argument('<source>', 'file or
|
|
246
|
+
.argument('<source>', 'file, project directory, or a .zip of one (e.g. a BeamNG mod as downloaded; extracted safely to a temp dir)')
|
|
233
247
|
.option('--type <type>', 'auto, asset, character, addon, scene, animation, or extract', 'auto')
|
|
234
248
|
.option('--target <target>', 'web, unreal, mobile, or canonical', 'web')
|
|
235
249
|
.option('--plugin <ids...>', 'restrict non-built-in plugins to these installed ids')
|
|
@@ -238,12 +252,12 @@ bridge
|
|
|
238
252
|
.option('--eye-texture <image>', 'MetaHuman baked eye texture override (default: packaged canonical bake from PR #99)')
|
|
239
253
|
.option('--canonical <glb>', 'canonical helix-humanoid body override')
|
|
240
254
|
.option('--map <json>', 'custom skeleton/bone map')
|
|
241
|
-
.option('--input <name=path...>', 'immutable named plugin input; repeat for multiple inputs')
|
|
255
|
+
.option('--input <name=path...>', 'immutable named plugin input; repeat for multiple inputs (BeamNG: palette=<palette.json>, beamngCommon=<index.json>, beamngEngine=<engine.json> with {"cylinders": N} to override the derived cylinder count)')
|
|
242
256
|
.option('--json', 'print the machine-readable plan')
|
|
243
257
|
.action(async (source, opts) => {
|
|
244
258
|
try {
|
|
245
259
|
const { resolveBridgePlan } = await Promise.resolve().then(() => __importStar(require('./bridge')));
|
|
246
|
-
const plan = await
|
|
260
|
+
const plan = await withBridgeSource(source, (resolved) => resolveBridgePlan(resolved, {
|
|
247
261
|
type: bridgeType(opts.type),
|
|
248
262
|
target: bridgeTarget(opts.target),
|
|
249
263
|
plugin: opts.plugin,
|
|
@@ -253,7 +267,7 @@ bridge
|
|
|
253
267
|
canonical: opts.canonical,
|
|
254
268
|
map: opts.map,
|
|
255
269
|
inputs: bridgeInputs(opts.input),
|
|
256
|
-
});
|
|
270
|
+
}));
|
|
257
271
|
console.log(opts.json ? JSON.stringify(plan, null, 2) : renderBridgePlan(plan));
|
|
258
272
|
if (plan.blockers.length > 0)
|
|
259
273
|
process.exitCode = 2;
|
|
@@ -265,7 +279,7 @@ bridge
|
|
|
265
279
|
bridge
|
|
266
280
|
.command('import')
|
|
267
281
|
.description('Detect, plan, convert, validate, package, and write deterministic provenance')
|
|
268
|
-
.argument('<source>', 'file or
|
|
282
|
+
.argument('<source>', 'file, project directory, or a .zip of one (e.g. a BeamNG mod as downloaded; extracted safely to a temp dir)')
|
|
269
283
|
.option('--type <type>', 'auto, asset, character, addon, scene, animation, or extract', 'auto')
|
|
270
284
|
.option('--target <target>', 'web, unreal, mobile, or canonical', 'web')
|
|
271
285
|
.option('-o, --output <directory>', 'empty output directory', './bridge-out')
|
|
@@ -275,7 +289,7 @@ bridge
|
|
|
275
289
|
.option('--eye-texture <image>', 'MetaHuman baked eye texture override (default: packaged canonical bake from PR #99)')
|
|
276
290
|
.option('--canonical <glb>', 'canonical helix-humanoid body override')
|
|
277
291
|
.option('--map <json>', 'custom skeleton/bone map')
|
|
278
|
-
.option('--input <name=path...>', 'immutable named plugin input; repeat for multiple inputs')
|
|
292
|
+
.option('--input <name=path...>', 'immutable named plugin input; repeat for multiple inputs (BeamNG: palette=<palette.json>, beamngCommon=<index.json>, beamngEngine=<engine.json> with {"cylinders": N} to override the derived cylinder count)')
|
|
279
293
|
.option('--expect-plan-key <sha256>', 'execute only if the freshly resolved plan has this exact key')
|
|
280
294
|
.option('--allow-permission <permissions...>', 'permissions approved for community/local plugins; repeat or comma-separate')
|
|
281
295
|
.option('--dry-run', 'plan only; write and execute nothing')
|
|
@@ -285,7 +299,7 @@ bridge
|
|
|
285
299
|
.action(async (source, opts) => {
|
|
286
300
|
try {
|
|
287
301
|
const { importWithBridge } = await Promise.resolve().then(() => __importStar(require('./bridge')));
|
|
288
|
-
const result = await
|
|
302
|
+
const result = await withBridgeSource(source, (resolved) => importWithBridge(resolved, {
|
|
289
303
|
type: bridgeType(opts.type),
|
|
290
304
|
target: bridgeTarget(opts.target),
|
|
291
305
|
output: opts.output,
|
|
@@ -302,7 +316,7 @@ bridge
|
|
|
302
316
|
keepStage: opts.keepStage,
|
|
303
317
|
allowUntexturedBodyGeometry: opts.allowUntexturedBodyGeometry,
|
|
304
318
|
onProgress: (message) => console.error(` ${message}`),
|
|
305
|
-
});
|
|
319
|
+
}));
|
|
306
320
|
if (opts.json)
|
|
307
321
|
console.log(JSON.stringify(result, null, 2));
|
|
308
322
|
else if (opts.dryRun)
|