brepjs-cad 0.175.0 → 0.176.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.
- package/CHANGELOG.md +7 -0
- package/dist/brepjs-cad.cjs +12 -11
- package/dist/brepjs-cad.js +2 -1
- package/dist/cli/main.cjs +40 -179
- package/dist/cli/main.js +17 -156
- package/dist/cli/watch.d.ts +9 -0
- package/dist/diff-C2vDUt9F.js +143 -0
- package/dist/diff-CmIV_qOi.cjs +154 -0
- package/dist/mcp/server.cjs +2 -2
- package/dist/mcp/server.js +2 -2
- package/dist/openBrowser-BBYw6VVb.js +166 -0
- package/dist/openBrowser-C1AiX6cC.cjs +213 -0
- package/dist/preview/evaluate.d.ts +26 -0
- package/dist/preview/preview.cjs +11957 -0
- package/dist/preview/preview.d.ts +9 -0
- package/dist/preview/preview.js +11957 -0
- package/dist/preview/server.d.ts +17 -0
- package/dist/{rolldown-runtime-VH7oDXx4.cjs → rolldown-runtime-y5ARvAq5.cjs} +7 -0
- package/dist/snapshot/shoot.cjs +1 -1
- package/dist/snapshot/static.cjs +3 -0
- package/dist/snapshot/static.d.ts +4 -0
- package/dist/snapshot/static.js +1 -1
- package/dist/{diff-DX9zgrss.cjs → usingCtx-DNbyRoEg.cjs} +25 -154
- package/dist/{diff-B1sSWjLa.js → usingCtx-DOzK2eJh.js} +1 -142
- package/dist/verify/runPart.d.ts +5 -0
- package/dist/verify/typecheck.d.ts +1 -0
- package/package.json +1 -1
- package/viewer/dist/assets/index-Cq6KRusM.js +4208 -0
- package/viewer/dist/index.html +1 -1
- package/viewer/dist/assets/index-CH09YZ3K.js +0 -4208
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.176.0](https://github.com/andymai/brepjs/compare/brepjs-cad-v0.175.0...brepjs-cad-v0.176.0) (2026-08-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **brepjs-cad:** brep preview — element-aware model viewer with live watch ([#2231](https://github.com/andymai/brepjs/issues/2231)) ([002a20f](https://github.com/andymai/brepjs/commit/002a20f9ad8d38cb812f00a875583bdb850a004d))
|
|
9
|
+
|
|
3
10
|
## [0.175.0](https://github.com/andymai/brepjs/compare/brepjs-cad-v0.174.0...brepjs-cad-v0.175.0) (2026-08-26)
|
|
4
11
|
|
|
5
12
|
|
package/dist/brepjs-cad.cjs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
exports.
|
|
5
|
-
exports.
|
|
6
|
-
exports.
|
|
7
|
-
exports.
|
|
8
|
-
exports.
|
|
9
|
-
exports.
|
|
2
|
+
const require_usingCtx = require("./usingCtx-DNbyRoEg.cjs");
|
|
3
|
+
const require_diff = require("./diff-CmIV_qOi.cjs");
|
|
4
|
+
exports.DEFAULT_TOLERANCE_PCT = require_usingCtx.DEFAULT_TOLERANCE_PCT;
|
|
5
|
+
exports.TYPECHECK_CODE = require_usingCtx.TYPECHECK_CODE;
|
|
6
|
+
exports.emptyReport = require_usingCtx.emptyReport;
|
|
7
|
+
exports.evaluateExpected = require_usingCtx.evaluateExpected;
|
|
8
|
+
exports.isExpectedDims = require_usingCtx.isExpectedDims;
|
|
9
|
+
exports.pctDelta = require_usingCtx.pctDelta;
|
|
10
|
+
exports.runChecks = require_usingCtx.runChecks;
|
|
10
11
|
exports.runDiff = require_diff.runDiff;
|
|
11
12
|
exports.runMeasure = require_diff.runMeasure;
|
|
12
|
-
exports.runPart =
|
|
13
|
-
exports.serializeReport =
|
|
14
|
-
exports.typecheckPart =
|
|
13
|
+
exports.runPart = require_usingCtx.runPart;
|
|
14
|
+
exports.serializeReport = require_usingCtx.serializeReport;
|
|
15
|
+
exports.typecheckPart = require_usingCtx.typecheckPart;
|
package/dist/brepjs-cad.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as evaluateExpected, d as runChecks, f as emptyReport, h as serializeReport, i as TYPECHECK_CODE, l as isExpectedDims, o as typecheckPart, r as runPart, s as DEFAULT_TOLERANCE_PCT, u as pctDelta } from "./usingCtx-DOzK2eJh.js";
|
|
2
|
+
import { n as runMeasure, t as runDiff } from "./diff-C2vDUt9F.js";
|
|
2
3
|
export { DEFAULT_TOLERANCE_PCT, TYPECHECK_CODE, emptyReport, evaluateExpected, isExpectedDims, pctDelta, runChecks, runDiff, runMeasure, runPart, serializeReport, typecheckPart };
|
package/dist/cli/main.cjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const
|
|
3
|
+
const require_usingCtx = require("../usingCtx-DNbyRoEg.cjs");
|
|
4
|
+
const require_diff = require("../diff-CmIV_qOi.cjs");
|
|
5
|
+
const require_openBrowser = require("../openBrowser-C1AiX6cC.cjs");
|
|
4
6
|
let node_url = require("node:url");
|
|
5
7
|
let node_fs = require("node:fs");
|
|
6
8
|
let node_path = require("node:path");
|
|
7
9
|
let commander = require("commander");
|
|
8
10
|
let node_os = require("node:os");
|
|
9
|
-
let node_child_process = require("node:child_process");
|
|
10
11
|
//#region src/cli/scaffold.ts
|
|
11
12
|
function partTemplate(name) {
|
|
12
13
|
return `import { box, cut, unwrap } from 'brepjs';
|
|
@@ -97,114 +98,20 @@ function scaffoldPart(name, dir) {
|
|
|
97
98
|
files
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
|
-
function debounce(fn, delayMs = 150) {
|
|
101
|
-
let timer;
|
|
102
|
-
const cancel = () => {
|
|
103
|
-
if (timer) {
|
|
104
|
-
clearTimeout(timer);
|
|
105
|
-
timer = void 0;
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
const trigger = () => {
|
|
109
|
-
cancel();
|
|
110
|
-
timer = setTimeout(() => {
|
|
111
|
-
timer = void 0;
|
|
112
|
-
fn();
|
|
113
|
-
}, delayMs);
|
|
114
|
-
};
|
|
115
|
-
return {
|
|
116
|
-
trigger,
|
|
117
|
-
cancel
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
var SOURCE_FILE_RE = /\.(?:m?[jt]s|[jt]sx)$/;
|
|
121
|
-
function isWatchRelevant(filename) {
|
|
122
|
-
if (filename === void 0 || filename === null) return true;
|
|
123
|
-
const name = filename.toString();
|
|
124
|
-
if (/(?:^|[\\/])tsconfig(?:\..+)?\.json$/.test(name)) return true;
|
|
125
|
-
return SOURCE_FILE_RE.test(name);
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Root the watcher at the project, not the entry's own directory: with the common
|
|
129
|
-
* `src/main.ts` layout, tsconfig.json (whose JSX options change how sources transpile)
|
|
130
|
-
* and `../` imports live one level up. Nearest package.json or tsconfig.json walking up
|
|
131
|
-
* wins; entries with no project marker fall back to their own directory.
|
|
132
|
-
*/
|
|
133
|
-
function watchRootFor(entryPath) {
|
|
134
|
-
const start = (0, node_path.dirname)(entryPath);
|
|
135
|
-
let dir = start;
|
|
136
|
-
for (;;) {
|
|
137
|
-
if ((0, node_fs.existsSync)((0, node_path.join)(dir, "package.json")) || (0, node_fs.existsSync)((0, node_path.join)(dir, "tsconfig.json"))) return dir;
|
|
138
|
-
const parent = (0, node_path.dirname)(dir);
|
|
139
|
-
if (parent === dir) return start;
|
|
140
|
-
dir = parent;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
var IGNORED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
144
|
-
"node_modules",
|
|
145
|
-
"dist",
|
|
146
|
-
".git"
|
|
147
|
-
]);
|
|
148
|
-
/**
|
|
149
|
-
* Watch a directory tree with one non-recursive `fs.watch` per directory, skipping
|
|
150
|
-
* node_modules/dist/.git and never following symlinks. `fs.watch({recursive})` has no
|
|
151
|
-
* exclusion API, so on a real project it would descend into node_modules — tens of
|
|
152
|
-
* thousands of inotify watches. Whenever an event fires in a directory it is re-scanned,
|
|
153
|
-
* which picks up newly created subdirectories. Returns a stop function.
|
|
154
|
-
*/
|
|
155
|
-
function watchTree(root, onEvent) {
|
|
156
|
-
const watchers = /* @__PURE__ */ new Map();
|
|
157
|
-
const add = (dir) => {
|
|
158
|
-
if (watchers.has(dir)) return;
|
|
159
|
-
try {
|
|
160
|
-
watchers.set(dir, (0, node_fs.watch)(dir, (_event, filename) => {
|
|
161
|
-
scan(dir);
|
|
162
|
-
onEvent(filename);
|
|
163
|
-
}));
|
|
164
|
-
} catch {}
|
|
165
|
-
};
|
|
166
|
-
const scan = (dir) => {
|
|
167
|
-
let entries;
|
|
168
|
-
try {
|
|
169
|
-
entries = (0, node_fs.readdirSync)(dir, { withFileTypes: true });
|
|
170
|
-
} catch {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
for (const entry of entries) {
|
|
174
|
-
if (!entry.isDirectory() || IGNORED_DIR_NAMES.has(entry.name)) continue;
|
|
175
|
-
const child = (0, node_path.join)(dir, entry.name);
|
|
176
|
-
if (watchers.has(child)) continue;
|
|
177
|
-
add(child);
|
|
178
|
-
scan(child);
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
add(root);
|
|
182
|
-
scan(root);
|
|
183
|
-
return () => {
|
|
184
|
-
for (const watcher of watchers.values()) watcher.close();
|
|
185
|
-
watchers.clear();
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
//#endregion
|
|
189
|
-
//#region src/disposeShape.ts
|
|
190
|
-
function disposeShape(shape) {
|
|
191
|
-
const disposer = shape?.[Symbol.dispose];
|
|
192
|
-
if (typeof disposer === "function") disposer.call(shape);
|
|
193
|
-
}
|
|
194
101
|
//#endregion
|
|
195
102
|
//#region src/cli/exportPart.ts
|
|
196
103
|
function stem(file) {
|
|
197
104
|
return (0, node_path.basename)(file).replace(/\.brep\.ts$/, "").replace(/\.ts$/, "");
|
|
198
105
|
}
|
|
199
106
|
async function exportPart(modulePath, formats, outDir) {
|
|
200
|
-
const { shape, report, step, glb } = await
|
|
107
|
+
const { shape, report, step, glb } = await require_usingCtx.runPart(modulePath, {
|
|
201
108
|
step: Boolean(formats.step),
|
|
202
109
|
glb: Boolean(formats.glb)
|
|
203
110
|
});
|
|
204
111
|
const errors = [];
|
|
205
112
|
const written = [];
|
|
206
|
-
if (!(
|
|
207
|
-
disposeShape(shape);
|
|
113
|
+
if (!(require_usingCtx.reportOk(report) && shape !== null)) {
|
|
114
|
+
require_openBrowser.disposeShape(shape);
|
|
208
115
|
const failures = report.errorInfos.map((e) => e.message);
|
|
209
116
|
return {
|
|
210
117
|
ok: false,
|
|
@@ -231,7 +138,7 @@ async function exportPart(modulePath, formats, outDir) {
|
|
|
231
138
|
} else errors.push("GLB export produced no data");
|
|
232
139
|
}
|
|
233
140
|
if (formats.stl) {
|
|
234
|
-
const { exportSTL, isOk } = await
|
|
141
|
+
const { exportSTL, isOk } = await require_usingCtx.loadBrep();
|
|
235
142
|
const r = exportSTL(shape);
|
|
236
143
|
if (isOk(r)) {
|
|
237
144
|
const p = (0, node_path.join)(outDir, `${base}.stl`);
|
|
@@ -240,7 +147,7 @@ async function exportPart(modulePath, formats, outDir) {
|
|
|
240
147
|
} else errors.push(`STL export: ${r.error.message}`);
|
|
241
148
|
}
|
|
242
149
|
} finally {
|
|
243
|
-
disposeShape(shape);
|
|
150
|
+
require_openBrowser.disposeShape(shape);
|
|
244
151
|
}
|
|
245
152
|
return {
|
|
246
153
|
ok: errors.length === 0,
|
|
@@ -250,43 +157,6 @@ async function exportPart(modulePath, formats, outDir) {
|
|
|
250
157
|
};
|
|
251
158
|
}
|
|
252
159
|
//#endregion
|
|
253
|
-
//#region src/cli/openBrowser.ts
|
|
254
|
-
/**
|
|
255
|
-
* Whether `--serve` should auto-open the browser for the current environment.
|
|
256
|
-
*
|
|
257
|
-
* Opens only for an interactive session — suppressed under CI, when stderr is
|
|
258
|
-
* not a TTY (agent/piped runs), or on Linux without a display server — so
|
|
259
|
-
* automation never spawns a browser unexpectedly. An explicit `--no-open` is a
|
|
260
|
-
* separate, always-on override handled by the caller.
|
|
261
|
-
*/
|
|
262
|
-
function shouldAutoOpen({ env = process.env, platform = process.platform, isTTY = Boolean(process.stderr.isTTY) } = {}) {
|
|
263
|
-
if (env["CI"]) return false;
|
|
264
|
-
if (!isTTY) return false;
|
|
265
|
-
if (platform === "linux" && !env["DISPLAY"] && !env["WAYLAND_DISPLAY"]) return false;
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
/** The platform-specific command that opens `url` in the default browser. */
|
|
269
|
-
function browserCommand(url, platform) {
|
|
270
|
-
if (platform === "darwin") return ["open", [url]];
|
|
271
|
-
if (platform === "win32") return ["rundll32", ["url.dll,FileProtocolHandler", url]];
|
|
272
|
-
return ["xdg-open", [url]];
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Best-effort open of `url` in the default browser. Never throws and never
|
|
276
|
-
* blocks — a missing opener just leaves the printed URL as the fallback.
|
|
277
|
-
*/
|
|
278
|
-
function openBrowser(url, platform = process.platform) {
|
|
279
|
-
try {
|
|
280
|
-
const [cmd, args] = browserCommand(url, platform);
|
|
281
|
-
const child = (0, node_child_process.spawn)(cmd, args, {
|
|
282
|
-
stdio: "ignore",
|
|
283
|
-
detached: true
|
|
284
|
-
});
|
|
285
|
-
child.on("error", () => {});
|
|
286
|
-
child.unref();
|
|
287
|
-
} catch {}
|
|
288
|
-
}
|
|
289
|
-
//#endregion
|
|
290
160
|
//#region src/snapshot/aiming.ts
|
|
291
161
|
/**
|
|
292
162
|
* Kernel-anchored marks for the judge: `B<index>` at each body's bbox centroid (only for multi-body
|
|
@@ -372,14 +242,14 @@ program.command("verify", { isDefault: true }).argument("<files...>", "path(s) o
|
|
|
372
242
|
if (opts.step || opts.serve || opts.snapshot || opts.glb || opts.expectCode || opts.expectInvalid) process.stderr.write("batch mode: --step/--glb/--serve/--snapshot/--expect-code/--expect-invalid ignored (single-file only)\n");
|
|
373
243
|
const results = [];
|
|
374
244
|
for (const f of targets) {
|
|
375
|
-
const { report, shape } = await
|
|
245
|
+
const { report, shape } = await require_usingCtx.runPart(f, {
|
|
376
246
|
check: Boolean(opts.check),
|
|
377
247
|
metrics: Boolean(opts.metrics)
|
|
378
248
|
});
|
|
379
|
-
disposeShape(shape);
|
|
249
|
+
require_openBrowser.disposeShape(shape);
|
|
380
250
|
results.push({
|
|
381
251
|
file: f,
|
|
382
|
-
ok:
|
|
252
|
+
ok: require_usingCtx.reportOk(report),
|
|
383
253
|
report
|
|
384
254
|
});
|
|
385
255
|
}
|
|
@@ -391,7 +261,7 @@ program.command("verify", { isDefault: true }).argument("<files...>", "path(s) o
|
|
|
391
261
|
}
|
|
392
262
|
const file = targets[0] ?? files[0] ?? "";
|
|
393
263
|
const wantStep = Boolean(opts.step) || Boolean(opts.snapshot) || Boolean(opts.serve);
|
|
394
|
-
const { report, step, glb, shape } = await
|
|
264
|
+
const { report, step, glb, shape } = await require_usingCtx.runPart((0, node_path.resolve)(file), {
|
|
395
265
|
step: wantStep,
|
|
396
266
|
glb: Boolean(opts.glb),
|
|
397
267
|
check: Boolean(opts.check),
|
|
@@ -419,27 +289,27 @@ program.command("verify", { isDefault: true }).argument("<files...>", "path(s) o
|
|
|
419
289
|
}
|
|
420
290
|
} else if (opts.snapshot) process.stderr.write("snapshot skipped: STEP export produced no artifact\n");
|
|
421
291
|
} catch (e) {
|
|
422
|
-
|
|
292
|
+
require_usingCtx.pushError(report, { message: `artifact write failed: ${e.message}` });
|
|
423
293
|
} finally {
|
|
424
|
-
disposeShape(shape);
|
|
294
|
+
require_openBrowser.disposeShape(shape);
|
|
425
295
|
}
|
|
426
|
-
const json =
|
|
296
|
+
const json = require_usingCtx.serializeReport(report);
|
|
427
297
|
if (opts.json && opts.json !== "-") (0, node_fs.writeFileSync)(opts.json, json);
|
|
428
298
|
process.stdout.write(json + "\n");
|
|
429
|
-
if (!
|
|
299
|
+
if (!require_usingCtx.reportOk(report)) process.exitCode = 1;
|
|
430
300
|
if (opts.expectInvalid || opts.expectCode) {
|
|
431
301
|
const codes = [...new Set(report.errorInfos.map((e) => e.code).filter((c) => Boolean(c)))];
|
|
432
|
-
const valid =
|
|
302
|
+
const valid = require_usingCtx.reportOk(report);
|
|
433
303
|
const pass = (!opts.expectInvalid || !valid) && (!opts.expectCode || codes.includes(opts.expectCode));
|
|
434
304
|
process.exitCode = pass ? 0 : 1;
|
|
435
305
|
process.stderr.write(`expect: ${pass ? "PASS" : "FAIL"}` + (opts.expectCode ? ` code=${opts.expectCode}` : "") + (opts.expectInvalid ? " invalid" : "") + ` (ok=${valid}, codes=${codes.join(",") || "none"})\n`);
|
|
436
306
|
return;
|
|
437
307
|
}
|
|
438
|
-
if (Boolean(opts.serve) && stepPath !== void 0 &&
|
|
308
|
+
if (Boolean(opts.serve) && stepPath !== void 0 && require_usingCtx.reportOk(report) && stepPath) {
|
|
439
309
|
const { serve } = await Promise.resolve().then(() => require("../snapshot/serve.cjs"));
|
|
440
310
|
const { url, reused } = await serve({ file: stepPath });
|
|
441
311
|
process.stderr.write(`viewer: ${url}\n`);
|
|
442
|
-
if (!reused && opts.open && shouldAutoOpen()) openBrowser(url);
|
|
312
|
+
if (!reused && opts.open && require_openBrowser.shouldAutoOpen()) require_openBrowser.openBrowser(url);
|
|
443
313
|
}
|
|
444
314
|
});
|
|
445
315
|
program.command("measure").argument("<a>", "path to a .brep.ts module").argument("[b]", "optional second module; if given, measures distance between the two parts").action(async (a, b) => {
|
|
@@ -462,14 +332,14 @@ program.command("snapshot").argument("<file>", "path to a .brep.ts module; rende
|
|
|
462
332
|
const resolved = (0, node_path.resolve)(file);
|
|
463
333
|
const base = opts.out ? (0, node_path.resolve)(opts.out) : `${resolved}-shots`;
|
|
464
334
|
const outDir = opts.label ? (0, node_path.join)(base, opts.label) : base;
|
|
465
|
-
const { report, step, shape } = await
|
|
335
|
+
const { report, step, shape } = await require_usingCtx.runPart(resolved, {
|
|
466
336
|
step: true,
|
|
467
337
|
metrics: true
|
|
468
338
|
});
|
|
469
339
|
try {
|
|
470
340
|
if (!step) {
|
|
471
341
|
process.stderr.write("snapshot: part produced no STEP — nothing to render\n");
|
|
472
|
-
process.stdout.write(
|
|
342
|
+
process.stdout.write(require_usingCtx.serializeReport(report) + "\n");
|
|
473
343
|
process.exitCode = 1;
|
|
474
344
|
return;
|
|
475
345
|
}
|
|
@@ -477,7 +347,7 @@ program.command("snapshot").argument("<file>", "path to a .brep.ts module; rende
|
|
|
477
347
|
(0, node_fs.writeFileSync)(stepPath, Buffer.from(step));
|
|
478
348
|
const shoot = await loadSnapshotShoot();
|
|
479
349
|
if (!shoot) {
|
|
480
|
-
process.stdout.write(
|
|
350
|
+
process.stdout.write(require_usingCtx.serializeReport(report) + "\n");
|
|
481
351
|
return;
|
|
482
352
|
}
|
|
483
353
|
const section = aimedSection(report.manufacturability?.bores ?? [], report.measurements.bounds);
|
|
@@ -491,7 +361,7 @@ program.command("snapshot").argument("<file>", "path to a .brep.ts module; rende
|
|
|
491
361
|
});
|
|
492
362
|
for (const p of pngs) process.stdout.write(`${p}\n`);
|
|
493
363
|
} finally {
|
|
494
|
-
disposeShape(shape);
|
|
364
|
+
require_openBrowser.disposeShape(shape);
|
|
495
365
|
}
|
|
496
366
|
});
|
|
497
367
|
program.command("init").argument("<name>", "part name; scaffolds <name>.brep.ts + tsconfig.json + README.md").option("--out <dir>", "target directory (defaults to ./<name>)").action((name, opts) => {
|
|
@@ -506,40 +376,22 @@ program.command("watch").argument("<file>", "path to a .brep.ts module; re-verif
|
|
|
506
376
|
const path = (0, node_path.resolve)(file);
|
|
507
377
|
const run = async (fresh) => {
|
|
508
378
|
try {
|
|
509
|
-
const { report, shape } = await
|
|
379
|
+
const { report, shape } = await require_usingCtx.runPart(path, { freshImport: fresh });
|
|
510
380
|
try {
|
|
511
|
-
process.stdout.write(
|
|
381
|
+
process.stdout.write(require_usingCtx.serializeReport(report) + "\n");
|
|
512
382
|
} finally {
|
|
513
|
-
disposeShape(shape);
|
|
383
|
+
require_openBrowser.disposeShape(shape);
|
|
514
384
|
}
|
|
515
385
|
} catch (e) {
|
|
516
386
|
process.stderr.write(`watch run failed: ${e.message}\n`);
|
|
517
387
|
}
|
|
518
388
|
};
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const start = (fresh) => {
|
|
522
|
-
inFlight = true;
|
|
523
|
-
run(fresh).finally(() => {
|
|
524
|
-
inFlight = false;
|
|
525
|
-
if (rerunQueued) {
|
|
526
|
-
rerunQueued = false;
|
|
527
|
-
start(true);
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
};
|
|
531
|
-
const schedule = () => {
|
|
532
|
-
if (inFlight) {
|
|
533
|
-
rerunQueued = true;
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
start(true);
|
|
537
|
-
};
|
|
538
|
-
const { trigger } = debounce(schedule, 150);
|
|
389
|
+
const runner = require_openBrowser.createSerialRunner(run);
|
|
390
|
+
const { trigger } = require_openBrowser.debounce(runner.trigger, 150);
|
|
539
391
|
process.stderr.write(`watching ${path} (Ctrl-C to stop)\n`);
|
|
540
|
-
start(false);
|
|
541
|
-
const stopWatching = watchTree(watchRootFor(path), (filename) => {
|
|
542
|
-
if (isWatchRelevant(filename)) trigger();
|
|
392
|
+
runner.start(false);
|
|
393
|
+
const stopWatching = require_openBrowser.watchTree(require_openBrowser.watchRootFor(path), (filename) => {
|
|
394
|
+
if (require_openBrowser.isWatchRelevant(filename)) trigger();
|
|
543
395
|
});
|
|
544
396
|
const stop = () => {
|
|
545
397
|
stopWatching();
|
|
@@ -548,6 +400,15 @@ program.command("watch").argument("<file>", "path to a .brep.ts module; re-verif
|
|
|
548
400
|
process.on("SIGINT", stop);
|
|
549
401
|
process.on("SIGTERM", stop);
|
|
550
402
|
});
|
|
403
|
+
program.command("preview").argument("<file>", "path to a model module (.ts/.tsx); a families element tree renders per element").option("--watch", "re-evaluate on save and push updates to the open viewer tab").option("--write-only", "write artifacts and exit without starting the viewer server").option("--glb <out>", "write a merged GLB of the whole model").option("--json <out>", "write the JSON summary to this path").option("--port <n>", "viewer server port (default: OS-assigned)", (v) => parseInt(v, 10)).option("--no-open", "do not auto-open the browser").action(async (file, opts) => {
|
|
404
|
+
const { runPreview } = await Promise.resolve().then(() => require("../preview/preview.cjs"));
|
|
405
|
+
try {
|
|
406
|
+
await runPreview((0, node_path.resolve)(file), opts);
|
|
407
|
+
} catch (e) {
|
|
408
|
+
process.stderr.write(`preview failed: ${e.message}\n`);
|
|
409
|
+
process.exitCode = 1;
|
|
410
|
+
}
|
|
411
|
+
});
|
|
551
412
|
program.command("export").argument("<file>", "path to a .brep.ts module").option("--step", "write a STEP artifact").option("--glb", "write a GLB artifact").option("--stl", "write an STL artifact").option("--all", "write STEP + GLB + STL").option("--out <dir>", "output directory", ".").action(async (file, opts) => {
|
|
552
413
|
const formats = opts.all ? {
|
|
553
414
|
step: true,
|
package/dist/cli/main.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { _ as loadBrep, h as serializeReport, m as reportOk, p as pushError, r as runPart } from "../usingCtx-DOzK2eJh.js";
|
|
3
|
+
import { n as runMeasure, t as runDiff } from "../diff-C2vDUt9F.js";
|
|
4
|
+
import { a as debounce, c as watchTree, i as createSerialRunner, n as shouldAutoOpen, o as isWatchRelevant, r as disposeShape, s as watchRootFor, t as openBrowser } from "../openBrowser-BBYw6VVb.js";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { existsSync, globSync, mkdirSync,
|
|
5
|
-
import { basename,
|
|
6
|
+
import { existsSync, globSync, mkdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { basename, join, resolve } from "node:path";
|
|
6
8
|
import { Command } from "commander";
|
|
7
9
|
import { tmpdir } from "node:os";
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
10
|
//#region src/cli/scaffold.ts
|
|
10
11
|
function partTemplate(name) {
|
|
11
12
|
return `import { box, cut, unwrap } from 'brepjs';
|
|
@@ -96,100 +97,6 @@ function scaffoldPart(name, dir) {
|
|
|
96
97
|
files
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
|
-
function debounce(fn, delayMs = 150) {
|
|
100
|
-
let timer;
|
|
101
|
-
const cancel = () => {
|
|
102
|
-
if (timer) {
|
|
103
|
-
clearTimeout(timer);
|
|
104
|
-
timer = void 0;
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
const trigger = () => {
|
|
108
|
-
cancel();
|
|
109
|
-
timer = setTimeout(() => {
|
|
110
|
-
timer = void 0;
|
|
111
|
-
fn();
|
|
112
|
-
}, delayMs);
|
|
113
|
-
};
|
|
114
|
-
return {
|
|
115
|
-
trigger,
|
|
116
|
-
cancel
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
var SOURCE_FILE_RE = /\.(?:m?[jt]s|[jt]sx)$/;
|
|
120
|
-
function isWatchRelevant(filename) {
|
|
121
|
-
if (filename === void 0 || filename === null) return true;
|
|
122
|
-
const name = filename.toString();
|
|
123
|
-
if (/(?:^|[\\/])tsconfig(?:\..+)?\.json$/.test(name)) return true;
|
|
124
|
-
return SOURCE_FILE_RE.test(name);
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Root the watcher at the project, not the entry's own directory: with the common
|
|
128
|
-
* `src/main.ts` layout, tsconfig.json (whose JSX options change how sources transpile)
|
|
129
|
-
* and `../` imports live one level up. Nearest package.json or tsconfig.json walking up
|
|
130
|
-
* wins; entries with no project marker fall back to their own directory.
|
|
131
|
-
*/
|
|
132
|
-
function watchRootFor(entryPath) {
|
|
133
|
-
const start = dirname(entryPath);
|
|
134
|
-
let dir = start;
|
|
135
|
-
for (;;) {
|
|
136
|
-
if (existsSync(join(dir, "package.json")) || existsSync(join(dir, "tsconfig.json"))) return dir;
|
|
137
|
-
const parent = dirname(dir);
|
|
138
|
-
if (parent === dir) return start;
|
|
139
|
-
dir = parent;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
var IGNORED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
143
|
-
"node_modules",
|
|
144
|
-
"dist",
|
|
145
|
-
".git"
|
|
146
|
-
]);
|
|
147
|
-
/**
|
|
148
|
-
* Watch a directory tree with one non-recursive `fs.watch` per directory, skipping
|
|
149
|
-
* node_modules/dist/.git and never following symlinks. `fs.watch({recursive})` has no
|
|
150
|
-
* exclusion API, so on a real project it would descend into node_modules — tens of
|
|
151
|
-
* thousands of inotify watches. Whenever an event fires in a directory it is re-scanned,
|
|
152
|
-
* which picks up newly created subdirectories. Returns a stop function.
|
|
153
|
-
*/
|
|
154
|
-
function watchTree(root, onEvent) {
|
|
155
|
-
const watchers = /* @__PURE__ */ new Map();
|
|
156
|
-
const add = (dir) => {
|
|
157
|
-
if (watchers.has(dir)) return;
|
|
158
|
-
try {
|
|
159
|
-
watchers.set(dir, watch(dir, (_event, filename) => {
|
|
160
|
-
scan(dir);
|
|
161
|
-
onEvent(filename);
|
|
162
|
-
}));
|
|
163
|
-
} catch {}
|
|
164
|
-
};
|
|
165
|
-
const scan = (dir) => {
|
|
166
|
-
let entries;
|
|
167
|
-
try {
|
|
168
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
169
|
-
} catch {
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
for (const entry of entries) {
|
|
173
|
-
if (!entry.isDirectory() || IGNORED_DIR_NAMES.has(entry.name)) continue;
|
|
174
|
-
const child = join(dir, entry.name);
|
|
175
|
-
if (watchers.has(child)) continue;
|
|
176
|
-
add(child);
|
|
177
|
-
scan(child);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
add(root);
|
|
181
|
-
scan(root);
|
|
182
|
-
return () => {
|
|
183
|
-
for (const watcher of watchers.values()) watcher.close();
|
|
184
|
-
watchers.clear();
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
//#endregion
|
|
188
|
-
//#region src/disposeShape.ts
|
|
189
|
-
function disposeShape(shape) {
|
|
190
|
-
const disposer = shape?.[Symbol.dispose];
|
|
191
|
-
if (typeof disposer === "function") disposer.call(shape);
|
|
192
|
-
}
|
|
193
100
|
//#endregion
|
|
194
101
|
//#region src/cli/exportPart.ts
|
|
195
102
|
function stem(file) {
|
|
@@ -249,43 +156,6 @@ async function exportPart(modulePath, formats, outDir) {
|
|
|
249
156
|
};
|
|
250
157
|
}
|
|
251
158
|
//#endregion
|
|
252
|
-
//#region src/cli/openBrowser.ts
|
|
253
|
-
/**
|
|
254
|
-
* Whether `--serve` should auto-open the browser for the current environment.
|
|
255
|
-
*
|
|
256
|
-
* Opens only for an interactive session — suppressed under CI, when stderr is
|
|
257
|
-
* not a TTY (agent/piped runs), or on Linux without a display server — so
|
|
258
|
-
* automation never spawns a browser unexpectedly. An explicit `--no-open` is a
|
|
259
|
-
* separate, always-on override handled by the caller.
|
|
260
|
-
*/
|
|
261
|
-
function shouldAutoOpen({ env = process.env, platform = process.platform, isTTY = Boolean(process.stderr.isTTY) } = {}) {
|
|
262
|
-
if (env["CI"]) return false;
|
|
263
|
-
if (!isTTY) return false;
|
|
264
|
-
if (platform === "linux" && !env["DISPLAY"] && !env["WAYLAND_DISPLAY"]) return false;
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
|
-
/** The platform-specific command that opens `url` in the default browser. */
|
|
268
|
-
function browserCommand(url, platform) {
|
|
269
|
-
if (platform === "darwin") return ["open", [url]];
|
|
270
|
-
if (platform === "win32") return ["rundll32", ["url.dll,FileProtocolHandler", url]];
|
|
271
|
-
return ["xdg-open", [url]];
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Best-effort open of `url` in the default browser. Never throws and never
|
|
275
|
-
* blocks — a missing opener just leaves the printed URL as the fallback.
|
|
276
|
-
*/
|
|
277
|
-
function openBrowser(url, platform = process.platform) {
|
|
278
|
-
try {
|
|
279
|
-
const [cmd, args] = browserCommand(url, platform);
|
|
280
|
-
const child = spawn(cmd, args, {
|
|
281
|
-
stdio: "ignore",
|
|
282
|
-
detached: true
|
|
283
|
-
});
|
|
284
|
-
child.on("error", () => {});
|
|
285
|
-
child.unref();
|
|
286
|
-
} catch {}
|
|
287
|
-
}
|
|
288
|
-
//#endregion
|
|
289
159
|
//#region src/snapshot/aiming.ts
|
|
290
160
|
/**
|
|
291
161
|
* Kernel-anchored marks for the judge: `B<index>` at each body's bbox centroid (only for multi-body
|
|
@@ -515,28 +385,10 @@ program.command("watch").argument("<file>", "path to a .brep.ts module; re-verif
|
|
|
515
385
|
process.stderr.write(`watch run failed: ${e.message}\n`);
|
|
516
386
|
}
|
|
517
387
|
};
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const start = (fresh) => {
|
|
521
|
-
inFlight = true;
|
|
522
|
-
run(fresh).finally(() => {
|
|
523
|
-
inFlight = false;
|
|
524
|
-
if (rerunQueued) {
|
|
525
|
-
rerunQueued = false;
|
|
526
|
-
start(true);
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
|
-
};
|
|
530
|
-
const schedule = () => {
|
|
531
|
-
if (inFlight) {
|
|
532
|
-
rerunQueued = true;
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
start(true);
|
|
536
|
-
};
|
|
537
|
-
const { trigger } = debounce(schedule, 150);
|
|
388
|
+
const runner = createSerialRunner(run);
|
|
389
|
+
const { trigger } = debounce(runner.trigger, 150);
|
|
538
390
|
process.stderr.write(`watching ${path} (Ctrl-C to stop)\n`);
|
|
539
|
-
start(false);
|
|
391
|
+
runner.start(false);
|
|
540
392
|
const stopWatching = watchTree(watchRootFor(path), (filename) => {
|
|
541
393
|
if (isWatchRelevant(filename)) trigger();
|
|
542
394
|
});
|
|
@@ -547,6 +399,15 @@ program.command("watch").argument("<file>", "path to a .brep.ts module; re-verif
|
|
|
547
399
|
process.on("SIGINT", stop);
|
|
548
400
|
process.on("SIGTERM", stop);
|
|
549
401
|
});
|
|
402
|
+
program.command("preview").argument("<file>", "path to a model module (.ts/.tsx); a families element tree renders per element").option("--watch", "re-evaluate on save and push updates to the open viewer tab").option("--write-only", "write artifacts and exit without starting the viewer server").option("--glb <out>", "write a merged GLB of the whole model").option("--json <out>", "write the JSON summary to this path").option("--port <n>", "viewer server port (default: OS-assigned)", (v) => parseInt(v, 10)).option("--no-open", "do not auto-open the browser").action(async (file, opts) => {
|
|
403
|
+
const { runPreview } = await import("../preview/preview.js");
|
|
404
|
+
try {
|
|
405
|
+
await runPreview(resolve(file), opts);
|
|
406
|
+
} catch (e) {
|
|
407
|
+
process.stderr.write(`preview failed: ${e.message}\n`);
|
|
408
|
+
process.exitCode = 1;
|
|
409
|
+
}
|
|
410
|
+
});
|
|
550
411
|
program.command("export").argument("<file>", "path to a .brep.ts module").option("--step", "write a STEP artifact").option("--glb", "write a GLB artifact").option("--stl", "write an STL artifact").option("--all", "write STEP + GLB + STL").option("--out <dir>", "output directory", ".").action(async (file, opts) => {
|
|
551
412
|
const formats = opts.all ? {
|
|
552
413
|
step: true,
|
package/dist/cli/watch.d.ts
CHANGED
|
@@ -19,3 +19,12 @@ export declare function watchRootFor(entryPath: string): string;
|
|
|
19
19
|
* which picks up newly created subdirectories. Returns a stop function.
|
|
20
20
|
*/
|
|
21
21
|
export declare function watchTree(root: string, onEvent: (filename: string | Buffer | null | undefined) => void): () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Serialize an async job: while one run is in flight, triggers coalesce into exactly
|
|
24
|
+
* one trailing rerun (verify/preview runs share one kernel — overlapping runs would
|
|
25
|
+
* interleave output and reorder reports).
|
|
26
|
+
*/
|
|
27
|
+
export declare function createSerialRunner(run: (fresh: boolean) => Promise<void>): {
|
|
28
|
+
start: (fresh: boolean) => void;
|
|
29
|
+
trigger: () => void;
|
|
30
|
+
};
|