brepjs-cad 0.1.0 → 0.2.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 CHANGED
@@ -1 +1,17 @@
1
1
  # Changelog
2
+
3
+ ## [0.2.0](https://github.com/andymai/brepjs/compare/brepjs-cad-v0.1.0...brepjs-cad-v0.2.0) (2026-06-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * **brepjs-cad:** CLI subcommands + verify hints + gridfinity examples + eval harness ([#1204](https://github.com/andymai/brepjs/issues/1204)) ([4d57198](https://github.com/andymai/brepjs/commit/4d5719874b5f5e685a4f909fd2d2363c0331770b))
9
+ * **brepjs-cad:** rename from brepjs-agent + make npm-publishable (publish held) ([#1201](https://github.com/andymai/brepjs/issues/1201)) ([630bbba](https://github.com/andymai/brepjs/commit/630bbbab4885604bd4d5fb2148584a6572c8d99c))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **brepjs-cad:** run CLI via bin symlink + quality pass ([#1206](https://github.com/andymai/brepjs/issues/1206)) ([ac5b1fe](https://github.com/andymai/brepjs/commit/ac5b1feee3c5b424c37716ca06c397f7898838f1))
15
+ * **opencascade:** prevent LTO stripping of custom bindings ([#666](https://github.com/andymai/brepjs/issues/666)) ([977dd75](https://github.com/andymai/brepjs/commit/977dd757e162d6fa47152b14aa31bac4edd9ae82))
16
+
17
+ ## Changelog
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_diff = require("./diff-4UNdqx4k.cjs");
2
+ const require_diff = require("./diff-DyOhTrZ3.cjs");
3
3
  exports.emptyReport = require_diff.emptyReport;
4
4
  exports.runChecks = require_diff.runChecks;
5
5
  exports.runDiff = require_diff.runDiff;
@@ -1,2 +1,2 @@
1
- import { a as emptyReport, i as runChecks, n as runMeasure, r as runPart, s as serializeReport, t as runDiff } from "./diff-ByiVwVrr.js";
1
+ import { a as emptyReport, c as serializeReport, i as runChecks, n as runMeasure, r as runPart, t as runDiff } from "./diff-DhKrjOVB.js";
2
2
  export { emptyReport, runChecks, runDiff, runMeasure, runPart, serializeReport };
package/dist/cli/main.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_diff = require("../diff-4UNdqx4k.cjs");
3
+ const require_diff = require("../diff-DyOhTrZ3.cjs");
4
4
  let brepjs = require("brepjs");
5
5
  let commander = require("commander");
6
6
  let node_fs = require("node:fs");
@@ -97,11 +97,6 @@ function scaffoldPart(name, dir) {
97
97
  files
98
98
  };
99
99
  }
100
- /**
101
- * Wraps a handler so bursts of rapid calls collapse into a single trailing
102
- * invocation after `delayMs` of quiet — fs.watch fires twice per save on many
103
- * platforms, so the model is re-run once rather than per raw event.
104
- */
105
100
  function debounce(fn, delayMs = 150) {
106
101
  let timer;
107
102
  const cancel = () => {
@@ -124,11 +119,6 @@ function debounce(fn, delayMs = 150) {
124
119
  }
125
120
  //#endregion
126
121
  //#region src/disposeShape.ts
127
- /**
128
- * Release a live WASM-backed kernel shape handle returned by `runPart`.
129
- * No-op for null/undefined or shapes without a disposer, so callers can pass
130
- * `result.shape` unconditionally. WASM memory accumulates without this.
131
- */
132
122
  function disposeShape(shape) {
133
123
  const disposer = shape?.[Symbol.dispose];
134
124
  if (typeof disposer === "function") disposer.call(shape);
@@ -139,7 +129,6 @@ function stem(file) {
139
129
  return (0, node_path.basename)(file).replace(/\.brep\.ts$/, "").replace(/\.ts$/, "");
140
130
  }
141
131
  async function exportPart(modulePath, formats, outDir) {
142
- const wantStl = Boolean(formats.stl);
143
132
  const { shape, report, step, glb } = await require_diff.runPart(modulePath, {
144
133
  step: Boolean(formats.step),
145
134
  glb: Boolean(formats.glb)
@@ -148,34 +137,38 @@ async function exportPart(modulePath, formats, outDir) {
148
137
  const written = [];
149
138
  if (!(require_diff.reportOk(report) && shape !== null)) {
150
139
  disposeShape(shape);
140
+ const failures = report.errorInfos.map((e) => e.message);
151
141
  return {
152
142
  ok: false,
153
143
  report,
154
144
  written,
155
- errors: report.errors
145
+ errors: failures.length > 0 ? failures : report.errors
156
146
  };
157
147
  }
158
- if (!(0, node_fs.existsSync)(outDir)) (0, node_fs.mkdirSync)(outDir, { recursive: true });
159
- const base = stem(modulePath);
160
- if (formats.step) if (step) {
161
- const p = (0, node_path.join)(outDir, `${base}.step`);
162
- (0, node_fs.writeFileSync)(p, Buffer.from(step));
163
- written.push(p);
164
- } else errors.push("STEP export produced no data");
165
- if (formats.glb) if (glb) {
166
- const p = (0, node_path.join)(outDir, `${base}.glb`);
167
- (0, node_fs.writeFileSync)(p, Buffer.from(glb));
168
- written.push(p);
169
- } else errors.push("GLB export produced no data");
170
- if (wantStl) {
171
- const r = (0, brepjs.exportSTL)(shape);
172
- if ((0, brepjs.isOk)(r)) {
173
- const p = (0, node_path.join)(outDir, `${base}.stl`);
174
- (0, node_fs.writeFileSync)(p, Buffer.from(await r.value.arrayBuffer()));
148
+ try {
149
+ if (!(0, node_fs.existsSync)(outDir)) (0, node_fs.mkdirSync)(outDir, { recursive: true });
150
+ const base = stem(modulePath);
151
+ if (formats.step) if (step) {
152
+ const p = (0, node_path.join)(outDir, `${base}.step`);
153
+ (0, node_fs.writeFileSync)(p, Buffer.from(step));
154
+ written.push(p);
155
+ } else errors.push("STEP export produced no data");
156
+ if (formats.glb) if (glb) {
157
+ const p = (0, node_path.join)(outDir, `${base}.glb`);
158
+ (0, node_fs.writeFileSync)(p, Buffer.from(glb));
175
159
  written.push(p);
176
- } else errors.push(`STL export: ${r.error.message}`);
160
+ } else errors.push("GLB export produced no data");
161
+ if (formats.stl) {
162
+ const r = (0, brepjs.exportSTL)(shape);
163
+ if ((0, brepjs.isOk)(r)) {
164
+ const p = (0, node_path.join)(outDir, `${base}.stl`);
165
+ (0, node_fs.writeFileSync)(p, Buffer.from(await r.value.arrayBuffer()));
166
+ written.push(p);
167
+ } else errors.push(`STL export: ${r.error.message}`);
168
+ }
169
+ } finally {
170
+ disposeShape(shape);
177
171
  }
178
- disposeShape(shape);
179
172
  return {
180
173
  ok: errors.length === 0,
181
174
  report,
@@ -201,32 +194,37 @@ var program = new commander.Command();
201
194
  program.name("brepjs");
202
195
  program.command("verify", { isDefault: true }).argument("<file>", "path to a .brep.ts module with a default-exported part function").option("--step <out>", "write the primary STEP artifact to this path").option("--glb <out>", "write a derived GLB preview to this path").option("--json <out>", "write the JSON report to this path").option("--snapshot <dir>", "render iso/front/top/right PNGs to this dir (requires built viewer)").option("--serve", "after verifying, start a preview server and print a ?dir=&file= deep link (stays running)").action(async (file, opts) => {
203
196
  const wantStep = Boolean(opts.step) || Boolean(opts.snapshot) || Boolean(opts.serve);
204
- const { report, step, glb } = await require_diff.runPart((0, node_path.resolve)(file), {
197
+ const { report, step, glb, shape } = await require_diff.runPart((0, node_path.resolve)(file), {
205
198
  step: wantStep,
206
199
  glb: Boolean(opts.glb)
207
200
  });
208
- const json = require_diff.serializeReport(report);
209
- if (opts.json) (0, node_fs.writeFileSync)(opts.json, json);
210
- if (opts.glb && glb) (0, node_fs.writeFileSync)(opts.glb, Buffer.from(glb));
211
201
  let stepPath = opts.step;
212
- if (wantStep && step) {
213
- stepPath = opts.step ?? (0, node_path.join)((0, node_os.tmpdir)(), `brepjs-cad-${(0, node_path.basename)(file)}.step`);
214
- (0, node_fs.writeFileSync)(stepPath, Buffer.from(step));
215
- }
216
- if (opts.snapshot && stepPath) {
217
- const shoot = await loadSnapshotShoot();
218
- if (shoot) {
219
- const { pngs } = await shoot({
220
- file: stepPath,
221
- outDir: opts.snapshot
222
- });
223
- for (const p of pngs) process.stderr.write(`snapshot: ${p}\n`);
202
+ try {
203
+ if (opts.glb && glb) (0, node_fs.writeFileSync)(opts.glb, Buffer.from(glb));
204
+ if (wantStep && step) {
205
+ stepPath = opts.step ?? (0, node_path.join)((0, node_os.tmpdir)(), `brepjs-cad-${(0, node_path.basename)(file)}.step`);
206
+ (0, node_fs.writeFileSync)(stepPath, Buffer.from(step));
224
207
  }
208
+ if (opts.snapshot && stepPath) {
209
+ const shoot = await loadSnapshotShoot();
210
+ if (shoot) {
211
+ const { pngs } = await shoot({
212
+ file: stepPath,
213
+ outDir: opts.snapshot
214
+ });
215
+ for (const p of pngs) process.stderr.write(`snapshot: ${p}\n`);
216
+ }
217
+ } else if (opts.snapshot) process.stderr.write("snapshot skipped: STEP export produced no artifact\n");
218
+ } catch (e) {
219
+ require_diff.pushError(report, { message: `artifact write failed: ${e.message}` });
220
+ } finally {
221
+ disposeShape(shape);
225
222
  }
223
+ const json = require_diff.serializeReport(report);
224
+ if (opts.json) (0, node_fs.writeFileSync)(opts.json, json);
226
225
  process.stdout.write(json + "\n");
227
- const parsed = JSON.parse(json);
228
- if (!opts.serve && parsed.ok !== true) process.exitCode = 1;
229
- if (opts.serve && stepPath) {
226
+ if (!require_diff.reportOk(report)) process.exitCode = 1;
227
+ if (Boolean(opts.serve) && stepPath !== void 0 && require_diff.reportOk(report) && stepPath) {
230
228
  const { serve } = await Promise.resolve().then(() => require("../snapshot/serve.cjs"));
231
229
  const { url } = await serve({ file: stepPath });
232
230
  process.stderr.write(`viewer: ${url}\n`);
@@ -312,6 +310,15 @@ program.command("export").argument("<file>", "path to a .brep.ts module").option
312
310
  }, null, 2) + "\n");
313
311
  if (!result.ok) process.exitCode = 1;
314
312
  });
315
- if (process.argv[1] && {}.url === (0, node_url.pathToFileURL)(process.argv[1]).href) program.parseAsync();
313
+ function isEntrypoint(argv1, moduleUrl) {
314
+ if (!argv1) return false;
315
+ try {
316
+ return (0, node_fs.realpathSync)(argv1) === (0, node_fs.realpathSync)((0, node_url.fileURLToPath)(moduleUrl));
317
+ } catch {
318
+ return false;
319
+ }
320
+ }
321
+ if (isEntrypoint(process.argv[1], {}.url)) program.parseAsync();
316
322
  //#endregion
323
+ exports.isEntrypoint = isEntrypoint;
317
324
  exports.loadSnapshotShoot = loadSnapshotShoot;
@@ -1,2 +1,3 @@
1
1
  import { shoot as ShootFn } from '../snapshot/shoot.js';
2
2
  export declare function loadSnapshotShoot(): Promise<typeof ShootFn | undefined>;
3
+ export declare function isEntrypoint(argv1: string | undefined, moduleUrl: string): boolean;
package/dist/cli/main.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { n as runMeasure, o as reportOk, r as runPart, s as serializeReport, t as runDiff } from "../diff-ByiVwVrr.js";
2
+ import { c as serializeReport, n as runMeasure, o as pushError, r as runPart, s as reportOk, t as runDiff } from "../diff-DhKrjOVB.js";
3
3
  import { exportSTL, isOk } from "brepjs";
4
4
  import { Command } from "commander";
5
- import { existsSync, mkdirSync, watch, writeFileSync } from "node:fs";
5
+ import { existsSync, mkdirSync, realpathSync, watch, writeFileSync } from "node:fs";
6
6
  import { basename, dirname, join, resolve } from "node:path";
7
7
  import { tmpdir } from "node:os";
8
- import { pathToFileURL } from "node:url";
8
+ import { fileURLToPath } from "node:url";
9
9
  //#region src/cli/scaffold.ts
10
10
  function partTemplate(name) {
11
11
  return `import { box, cut, unwrap } from 'brepjs';
@@ -96,11 +96,6 @@ function scaffoldPart(name, dir) {
96
96
  files
97
97
  };
98
98
  }
99
- /**
100
- * Wraps a handler so bursts of rapid calls collapse into a single trailing
101
- * invocation after `delayMs` of quiet — fs.watch fires twice per save on many
102
- * platforms, so the model is re-run once rather than per raw event.
103
- */
104
99
  function debounce(fn, delayMs = 150) {
105
100
  let timer;
106
101
  const cancel = () => {
@@ -123,11 +118,6 @@ function debounce(fn, delayMs = 150) {
123
118
  }
124
119
  //#endregion
125
120
  //#region src/disposeShape.ts
126
- /**
127
- * Release a live WASM-backed kernel shape handle returned by `runPart`.
128
- * No-op for null/undefined or shapes without a disposer, so callers can pass
129
- * `result.shape` unconditionally. WASM memory accumulates without this.
130
- */
131
121
  function disposeShape(shape) {
132
122
  const disposer = shape?.[Symbol.dispose];
133
123
  if (typeof disposer === "function") disposer.call(shape);
@@ -138,7 +128,6 @@ function stem(file) {
138
128
  return basename(file).replace(/\.brep\.ts$/, "").replace(/\.ts$/, "");
139
129
  }
140
130
  async function exportPart(modulePath, formats, outDir) {
141
- const wantStl = Boolean(formats.stl);
142
131
  const { shape, report, step, glb } = await runPart(modulePath, {
143
132
  step: Boolean(formats.step),
144
133
  glb: Boolean(formats.glb)
@@ -147,34 +136,38 @@ async function exportPart(modulePath, formats, outDir) {
147
136
  const written = [];
148
137
  if (!(reportOk(report) && shape !== null)) {
149
138
  disposeShape(shape);
139
+ const failures = report.errorInfos.map((e) => e.message);
150
140
  return {
151
141
  ok: false,
152
142
  report,
153
143
  written,
154
- errors: report.errors
144
+ errors: failures.length > 0 ? failures : report.errors
155
145
  };
156
146
  }
157
- if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
158
- const base = stem(modulePath);
159
- if (formats.step) if (step) {
160
- const p = join(outDir, `${base}.step`);
161
- writeFileSync(p, Buffer.from(step));
162
- written.push(p);
163
- } else errors.push("STEP export produced no data");
164
- if (formats.glb) if (glb) {
165
- const p = join(outDir, `${base}.glb`);
166
- writeFileSync(p, Buffer.from(glb));
167
- written.push(p);
168
- } else errors.push("GLB export produced no data");
169
- if (wantStl) {
170
- const r = exportSTL(shape);
171
- if (isOk(r)) {
172
- const p = join(outDir, `${base}.stl`);
173
- writeFileSync(p, Buffer.from(await r.value.arrayBuffer()));
147
+ try {
148
+ if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
149
+ const base = stem(modulePath);
150
+ if (formats.step) if (step) {
151
+ const p = join(outDir, `${base}.step`);
152
+ writeFileSync(p, Buffer.from(step));
153
+ written.push(p);
154
+ } else errors.push("STEP export produced no data");
155
+ if (formats.glb) if (glb) {
156
+ const p = join(outDir, `${base}.glb`);
157
+ writeFileSync(p, Buffer.from(glb));
174
158
  written.push(p);
175
- } else errors.push(`STL export: ${r.error.message}`);
159
+ } else errors.push("GLB export produced no data");
160
+ if (formats.stl) {
161
+ const r = exportSTL(shape);
162
+ if (isOk(r)) {
163
+ const p = join(outDir, `${base}.stl`);
164
+ writeFileSync(p, Buffer.from(await r.value.arrayBuffer()));
165
+ written.push(p);
166
+ } else errors.push(`STL export: ${r.error.message}`);
167
+ }
168
+ } finally {
169
+ disposeShape(shape);
176
170
  }
177
- disposeShape(shape);
178
171
  return {
179
172
  ok: errors.length === 0,
180
173
  report,
@@ -200,32 +193,37 @@ var program = new Command();
200
193
  program.name("brepjs");
201
194
  program.command("verify", { isDefault: true }).argument("<file>", "path to a .brep.ts module with a default-exported part function").option("--step <out>", "write the primary STEP artifact to this path").option("--glb <out>", "write a derived GLB preview to this path").option("--json <out>", "write the JSON report to this path").option("--snapshot <dir>", "render iso/front/top/right PNGs to this dir (requires built viewer)").option("--serve", "after verifying, start a preview server and print a ?dir=&file= deep link (stays running)").action(async (file, opts) => {
202
195
  const wantStep = Boolean(opts.step) || Boolean(opts.snapshot) || Boolean(opts.serve);
203
- const { report, step, glb } = await runPart(resolve(file), {
196
+ const { report, step, glb, shape } = await runPart(resolve(file), {
204
197
  step: wantStep,
205
198
  glb: Boolean(opts.glb)
206
199
  });
207
- const json = serializeReport(report);
208
- if (opts.json) writeFileSync(opts.json, json);
209
- if (opts.glb && glb) writeFileSync(opts.glb, Buffer.from(glb));
210
200
  let stepPath = opts.step;
211
- if (wantStep && step) {
212
- stepPath = opts.step ?? join(tmpdir(), `brepjs-cad-${basename(file)}.step`);
213
- writeFileSync(stepPath, Buffer.from(step));
214
- }
215
- if (opts.snapshot && stepPath) {
216
- const shoot = await loadSnapshotShoot();
217
- if (shoot) {
218
- const { pngs } = await shoot({
219
- file: stepPath,
220
- outDir: opts.snapshot
221
- });
222
- for (const p of pngs) process.stderr.write(`snapshot: ${p}\n`);
201
+ try {
202
+ if (opts.glb && glb) writeFileSync(opts.glb, Buffer.from(glb));
203
+ if (wantStep && step) {
204
+ stepPath = opts.step ?? join(tmpdir(), `brepjs-cad-${basename(file)}.step`);
205
+ writeFileSync(stepPath, Buffer.from(step));
223
206
  }
207
+ if (opts.snapshot && stepPath) {
208
+ const shoot = await loadSnapshotShoot();
209
+ if (shoot) {
210
+ const { pngs } = await shoot({
211
+ file: stepPath,
212
+ outDir: opts.snapshot
213
+ });
214
+ for (const p of pngs) process.stderr.write(`snapshot: ${p}\n`);
215
+ }
216
+ } else if (opts.snapshot) process.stderr.write("snapshot skipped: STEP export produced no artifact\n");
217
+ } catch (e) {
218
+ pushError(report, { message: `artifact write failed: ${e.message}` });
219
+ } finally {
220
+ disposeShape(shape);
224
221
  }
222
+ const json = serializeReport(report);
223
+ if (opts.json) writeFileSync(opts.json, json);
225
224
  process.stdout.write(json + "\n");
226
- const parsed = JSON.parse(json);
227
- if (!opts.serve && parsed.ok !== true) process.exitCode = 1;
228
- if (opts.serve && stepPath) {
225
+ if (!reportOk(report)) process.exitCode = 1;
226
+ if (Boolean(opts.serve) && stepPath !== void 0 && reportOk(report) && stepPath) {
229
227
  const { serve } = await import("../snapshot/serve.js");
230
228
  const { url } = await serve({ file: stepPath });
231
229
  process.stderr.write(`viewer: ${url}\n`);
@@ -311,6 +309,14 @@ program.command("export").argument("<file>", "path to a .brep.ts module").option
311
309
  }, null, 2) + "\n");
312
310
  if (!result.ok) process.exitCode = 1;
313
311
  });
314
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) program.parseAsync();
312
+ function isEntrypoint(argv1, moduleUrl) {
313
+ if (!argv1) return false;
314
+ try {
315
+ return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
316
+ } catch {
317
+ return false;
318
+ }
319
+ }
320
+ if (isEntrypoint(process.argv[1], import.meta.url)) program.parseAsync();
315
321
  //#endregion
316
- export { loadSnapshotShoot };
322
+ export { isEntrypoint, loadSnapshotShoot };
@@ -1,9 +1,4 @@
1
1
  export declare const DEFAULT_DEBOUNCE_MS = 150;
2
- /**
3
- * Wraps a handler so bursts of rapid calls collapse into a single trailing
4
- * invocation after `delayMs` of quiet — fs.watch fires twice per save on many
5
- * platforms, so the model is re-run once rather than per raw event.
6
- */
7
2
  export declare function debounce(fn: () => void | Promise<void>, delayMs?: number): {
8
3
  trigger: () => void;
9
4
  cancel: () => void;
@@ -10,7 +10,6 @@ function emptyReport() {
10
10
  hints: []
11
11
  };
12
12
  }
13
- /** Record a failure on the report, keeping the flat `errors` string list and structured `errorInfos` in sync. */
14
13
  function pushError(r, info) {
15
14
  r.errors.push(info.message);
16
15
  r.errorInfos.push(info);
@@ -246,7 +245,6 @@ function isBrepError(v) {
246
245
  const rec = v;
247
246
  return typeof rec["code"] === "string" && typeof rec["message"] === "string";
248
247
  }
249
- /** Pull structured `{ message, code, suggestion }` out of a `BrepError`, a thrown `Error`, or anything. */
250
248
  function toErrorInfo(prefix, e) {
251
249
  if (isBrepError(e)) return {
252
250
  message: `${prefix}: ${e.message}`,
@@ -261,8 +259,16 @@ function finalize(result) {
261
259
  return result;
262
260
  }
263
261
  async function runPart(modulePath, opts = {}) {
264
- await init();
265
262
  const report = emptyReport();
263
+ try {
264
+ await init();
265
+ } catch (e) {
266
+ pushError(report, toErrorInfo("kernel init failed", e));
267
+ return finalize({
268
+ shape: null,
269
+ report
270
+ });
271
+ }
266
272
  let mod;
267
273
  try {
268
274
  mod = await import(modulePath);
@@ -291,15 +297,16 @@ async function runPart(modulePath, opts = {}) {
291
297
  });
292
298
  }
293
299
  let shape;
294
- if (isResult(out)) if (isOk(out)) shape = out.value;
295
- else {
296
- pushError(report, toErrorInfo("part returned Err", out.error));
297
- return finalize({
298
- shape: null,
299
- report
300
- });
301
- }
302
- else shape = out;
300
+ if (isResult(out)) {
301
+ if (!isOk(out)) {
302
+ pushError(report, toErrorInfo("part returned Err", out.error));
303
+ return finalize({
304
+ shape: null,
305
+ report
306
+ });
307
+ }
308
+ shape = out.value;
309
+ } else shape = out;
303
310
  if (!shape) {
304
311
  pushError(report, { message: "part produced no shape" });
305
312
  return finalize({
@@ -449,6 +456,30 @@ function areaOf(shape, errors) {
449
456
  errors.push(`measureArea: ${a.error.message}`);
450
457
  return 0;
451
458
  }
459
+ function boundsDelta(a, b, errors) {
460
+ try {
461
+ const ba = getBounds(a);
462
+ const bb = getBounds(b);
463
+ return {
464
+ xMin: bb.xMin - ba.xMin,
465
+ xMax: bb.xMax - ba.xMax,
466
+ yMin: bb.yMin - ba.yMin,
467
+ yMax: bb.yMax - ba.yMax,
468
+ zMin: bb.zMin - ba.zMin,
469
+ zMax: bb.zMax - ba.zMax
470
+ };
471
+ } catch (e) {
472
+ errors.push(`getBounds: ${e.message}`);
473
+ return {
474
+ xMin: 0,
475
+ xMax: 0,
476
+ yMin: 0,
477
+ yMax: 0,
478
+ zMin: 0,
479
+ zMax: 0
480
+ };
481
+ }
482
+ }
452
483
  function cutVolume(x, y, errors) {
453
484
  try {
454
485
  var _usingCtx$1 = _usingCtx();
@@ -470,21 +501,13 @@ async function runDiff(aPath, bPath) {
470
501
  const errors = [];
471
502
  const a = await runPart(aPath);
472
503
  errors.push(...a.report.errors);
504
+ if (!a.shape) return emptyDiff(errors);
505
+ const sa = _usingCtx3.u(a.shape);
473
506
  const b = await runPart(bPath);
474
507
  errors.push(...b.report.errors);
475
- if (!a.shape || !b.shape) return emptyDiff(errors);
476
- const sa = _usingCtx3.u(a.shape);
508
+ if (!b.shape) return emptyDiff(errors);
477
509
  const sb = _usingCtx3.u(b.shape);
478
- const ba = getBounds(sa);
479
- const bb = getBounds(sb);
480
- const bboxDelta = {
481
- xMin: bb.xMin - ba.xMin,
482
- xMax: bb.xMax - ba.xMax,
483
- yMin: bb.yMin - ba.yMin,
484
- yMax: bb.yMax - ba.yMax,
485
- zMin: bb.zMin - ba.zMin,
486
- zMax: bb.zMax - ba.zMax
487
- };
510
+ const bboxDelta = boundsDelta(sa, sb, errors);
488
511
  const areaDelta = areaOf(sb, errors) - areaOf(sa, errors);
489
512
  let volumeDelta = 0;
490
513
  let symmetricDifferenceVolume = 0;
@@ -506,4 +529,4 @@ async function runDiff(aPath, bPath) {
506
529
  }
507
530
  }
508
531
  //#endregion
509
- export { emptyReport as a, runChecks as i, runMeasure as n, reportOk as o, runPart as r, serializeReport as s, runDiff as t };
532
+ export { emptyReport as a, serializeReport as c, runChecks as i, runMeasure as n, pushError as o, runPart as r, reportOk as s, runDiff as t };
@@ -1,6 +1 @@
1
- /**
2
- * Release a live WASM-backed kernel shape handle returned by `runPart`.
3
- * No-op for null/undefined or shapes without a disposer, so callers can pass
4
- * `result.shape` unconditionally. WASM memory accumulates without this.
5
- */
6
1
  export declare function disposeShape(shape: unknown): void;
@@ -31,7 +31,7 @@ async function acquireServer(opts = {}) {
31
31
  server = await require_snapshot_static.startStaticServer({ port });
32
32
  break;
33
33
  } catch {}
34
- if (!server) throw new Error(`no free port in ${ports[0]}..${ports[ports.length - 1]}`);
34
+ if (!server) throw new Error(`no free port in ${ports[0]}..${ports.at(-1)}`);
35
35
  const timer = setTimeout(() => void server?.close(), opts.shutdownAfterMs ?? 432e5);
36
36
  timer.unref();
37
37
  const started = server;
@@ -30,7 +30,7 @@ async function acquireServer(opts = {}) {
30
30
  server = await startStaticServer({ port });
31
31
  break;
32
32
  } catch {}
33
- if (!server) throw new Error(`no free port in ${ports[0]}..${ports[ports.length - 1]}`);
33
+ if (!server) throw new Error(`no free port in ${ports[0]}..${ports.at(-1)}`);
34
34
  const timer = setTimeout(() => void server?.close(), opts.shutdownAfterMs ?? 432e5);
35
35
  timer.unref();
36
36
  const started = server;
@@ -38,7 +38,8 @@ async function sendFile(res, absPath) {
38
38
  }
39
39
  function safeJoin(root, rel) {
40
40
  const abs = (0, node_path.resolve)(root, (0, node_path.normalize)(decodeURIComponent(rel.replace(/^\/+/, ""))));
41
- return abs !== root && !abs.startsWith(root + node_path.sep) ? null : abs;
41
+ if (abs !== root && !abs.startsWith(root + node_path.sep)) return null;
42
+ return abs;
42
43
  }
43
44
  async function handle(req, res, port) {
44
45
  const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
@@ -37,7 +37,8 @@ async function sendFile(res, absPath) {
37
37
  }
38
38
  function safeJoin(root, rel) {
39
39
  const abs = resolve(root, normalize(decodeURIComponent(rel.replace(/^\/+/, ""))));
40
- return abs !== root && !abs.startsWith(root + sep) ? null : abs;
40
+ if (abs !== root && !abs.startsWith(root + sep)) return null;
41
+ return abs;
41
42
  }
42
43
  async function handle(req, res, port) {
43
44
  const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
@@ -53,7 +53,6 @@ export interface DiffReport {
53
53
  errors: string[];
54
54
  }
55
55
  export declare function emptyReport(): VerifyReport;
56
- /** Record a failure on the report, keeping the flat `errors` string list and structured `errorInfos` in sync. */
57
56
  export declare function pushError(r: VerifyReport, info: ErrorInfo): void;
58
57
  export declare function reportOk(r: VerifyReport): boolean;
59
58
  /** Synthetic code attached to validity-check failures (validSolid returns a plain string error). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brepjs-cad",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Agent skill + verify/preview tooling for authoring parametric brepjs CAD code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",