brepjs-verify 0.2.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +191 -0
  3. package/README.md +85 -0
  4. package/dist/brepjs-verify.cjs +14 -0
  5. package/dist/brepjs-verify.js +2 -0
  6. package/dist/chunk-D6vf50IK.cjs +28 -0
  7. package/dist/cli/exportPart.d.ts +13 -0
  8. package/dist/cli/main.cjs +325 -0
  9. package/dist/cli/main.d.ts +3 -0
  10. package/dist/cli/main.js +323 -0
  11. package/dist/cli/scaffold.d.ts +9 -0
  12. package/dist/cli/watch.d.ts +5 -0
  13. package/dist/diff-CZ4mLtrf.cjs +869 -0
  14. package/dist/diff-D7ZBNRJG.js +778 -0
  15. package/dist/disposeShape.d.ts +1 -0
  16. package/dist/index.d.ts +7 -0
  17. package/dist/loader/brepjsResolve.mjs +57 -0
  18. package/dist/snapshot/registry.cjs +50 -0
  19. package/dist/snapshot/registry.d.ts +12 -0
  20. package/dist/snapshot/registry.js +48 -0
  21. package/dist/snapshot/serve.cjs +27 -0
  22. package/dist/snapshot/serve.d.ts +12 -0
  23. package/dist/snapshot/serve.js +26 -0
  24. package/dist/snapshot/shoot.cjs +64 -0
  25. package/dist/snapshot/shoot.d.ts +14 -0
  26. package/dist/snapshot/shoot.js +61 -0
  27. package/dist/snapshot/static.cjs +100 -0
  28. package/dist/snapshot/static.d.ts +16 -0
  29. package/dist/snapshot/static.js +98 -0
  30. package/dist/verify/brepjsRuntime.d.ts +4 -0
  31. package/dist/verify/checks.d.ts +4 -0
  32. package/dist/verify/diff.d.ts +2 -0
  33. package/dist/verify/expected.d.ts +26 -0
  34. package/dist/verify/measure.d.ts +6 -0
  35. package/dist/verify/report.d.ts +75 -0
  36. package/dist/verify/runPart.d.ts +23 -0
  37. package/dist/verify/typecheck.d.ts +17 -0
  38. package/package.json +78 -0
  39. package/viewer/dist/assets/brepjs-CDZqKweN.js +57 -0
  40. package/viewer/dist/assets/index-B8QUQDqM.js +4167 -0
  41. package/viewer/dist/assets/kernelWorker-C6s5i9JH.js +1 -0
  42. package/viewer/dist/index.html +22 -0
  43. package/viewer/dist/wasm/occt-wasm.js +2 -0
  44. package/viewer/dist/wasm/occt-wasm.wasm +0 -0
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env node
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_diff = require("../diff-CZ4mLtrf.cjs");
4
+ let node_url = require("node:url");
5
+ let node_fs = require("node:fs");
6
+ let node_path = require("node:path");
7
+ let commander = require("commander");
8
+ let node_os = require("node:os");
9
+ //#region src/cli/scaffold.ts
10
+ function partTemplate(name) {
11
+ return `import { box, cut, unwrap } from 'brepjs';
12
+
13
+ const width = 40;
14
+ const depth = 20;
15
+ const height = 10;
16
+ const holeSize = 6;
17
+
18
+ // ${name}: a parameterized starter part — edit the consts above, then re-verify.
19
+ export default () => {
20
+ const body = box(width, depth, height, { centered: true });
21
+ const hole = box(holeSize, holeSize, height + 2, { centered: true });
22
+ return unwrap(cut(body, hole));
23
+ };
24
+ `;
25
+ }
26
+ function tsconfigTemplate() {
27
+ return `${JSON.stringify({
28
+ compilerOptions: {
29
+ target: "ES2022",
30
+ module: "ESNext",
31
+ moduleResolution: "bundler",
32
+ strict: true,
33
+ noEmit: true,
34
+ skipLibCheck: true
35
+ },
36
+ include: ["*.brep.ts"]
37
+ }, null, 2)}\n`;
38
+ }
39
+ function readmeTemplate(name) {
40
+ return `# ${name}
41
+
42
+ A parametric brepjs CAD part. Units are millimetres.
43
+
44
+ ## Verify
45
+
46
+ \`\`\`sh
47
+ npx -y brepjs-verify ${name}.brep.ts --json report.json
48
+ \`\`\`
49
+
50
+ ## Iterate
51
+
52
+ \`\`\`sh
53
+ npx -y brepjs-verify watch ${name}.brep.ts
54
+ \`\`\`
55
+
56
+ ## Export artifacts
57
+
58
+ \`\`\`sh
59
+ npx -y brepjs-verify export ${name}.brep.ts --all --out out/
60
+ \`\`\`
61
+ `;
62
+ }
63
+ function scaffoldPart(name, dir) {
64
+ if (!(0, node_fs.existsSync)(dir)) (0, node_fs.mkdirSync)(dir, { recursive: true });
65
+ const targets = [
66
+ {
67
+ path: (0, node_path.join)(dir, `${name}.brep.ts`),
68
+ content: partTemplate(name)
69
+ },
70
+ {
71
+ path: (0, node_path.join)(dir, "tsconfig.json"),
72
+ content: tsconfigTemplate()
73
+ },
74
+ {
75
+ path: (0, node_path.join)(dir, "README.md"),
76
+ content: readmeTemplate(name)
77
+ }
78
+ ];
79
+ const files = [];
80
+ for (const t of targets) {
81
+ if ((0, node_fs.existsSync)(t.path)) {
82
+ files.push({
83
+ path: t.path,
84
+ created: false
85
+ });
86
+ continue;
87
+ }
88
+ (0, node_fs.writeFileSync)(t.path, t.content);
89
+ files.push({
90
+ path: t.path,
91
+ created: true
92
+ });
93
+ }
94
+ return {
95
+ dir,
96
+ files
97
+ };
98
+ }
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
+ //#endregion
120
+ //#region src/disposeShape.ts
121
+ function disposeShape(shape) {
122
+ const disposer = shape?.[Symbol.dispose];
123
+ if (typeof disposer === "function") disposer.call(shape);
124
+ }
125
+ //#endregion
126
+ //#region src/cli/exportPart.ts
127
+ function stem(file) {
128
+ return (0, node_path.basename)(file).replace(/\.brep\.ts$/, "").replace(/\.ts$/, "");
129
+ }
130
+ async function exportPart(modulePath, formats, outDir) {
131
+ const { shape, report, step, glb } = await require_diff.runPart(modulePath, {
132
+ step: Boolean(formats.step),
133
+ glb: Boolean(formats.glb)
134
+ });
135
+ const errors = [];
136
+ const written = [];
137
+ if (!(require_diff.reportOk(report) && shape !== null)) {
138
+ disposeShape(shape);
139
+ const failures = report.errorInfos.map((e) => e.message);
140
+ return {
141
+ ok: false,
142
+ report,
143
+ written,
144
+ errors: failures.length > 0 ? failures : report.errors
145
+ };
146
+ }
147
+ try {
148
+ if (!(0, node_fs.existsSync)(outDir)) (0, node_fs.mkdirSync)(outDir, { recursive: true });
149
+ const base = stem(modulePath);
150
+ if (formats.step) if (step) {
151
+ const p = (0, node_path.join)(outDir, `${base}.step`);
152
+ (0, node_fs.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 = (0, node_path.join)(outDir, `${base}.glb`);
157
+ (0, node_fs.writeFileSync)(p, Buffer.from(glb));
158
+ written.push(p);
159
+ } else errors.push("GLB export produced no data");
160
+ if (formats.stl) {
161
+ const { exportSTL, isOk } = await require_diff.loadBrep();
162
+ const r = exportSTL(shape);
163
+ if (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);
171
+ }
172
+ return {
173
+ ok: errors.length === 0,
174
+ report,
175
+ written,
176
+ errors
177
+ };
178
+ }
179
+ //#endregion
180
+ //#region src/cli/main.ts
181
+ console.log = (...args) => {
182
+ process.stderr.write(args.map(String).join(" ") + "\n");
183
+ };
184
+ async function loadSnapshotShoot() {
185
+ try {
186
+ return (await Promise.resolve().then(() => require("../snapshot/shoot.cjs"))).shoot;
187
+ } catch {
188
+ process.stderr.write("snapshots need puppeteer/Chrome — run: npm i puppeteer\n");
189
+ process.exitCode = 1;
190
+ return;
191
+ }
192
+ }
193
+ var program = new commander.Command();
194
+ program.name("brepjs-verify");
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("--check", "type-check the part (against brepjs types) before running; skip execution on type errors").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) => {
196
+ const wantStep = Boolean(opts.step) || Boolean(opts.snapshot) || Boolean(opts.serve);
197
+ const { report, step, glb, shape } = await require_diff.runPart((0, node_path.resolve)(file), {
198
+ step: wantStep,
199
+ glb: Boolean(opts.glb),
200
+ check: Boolean(opts.check)
201
+ });
202
+ let stepPath = opts.step;
203
+ try {
204
+ if (opts.glb && glb) (0, node_fs.writeFileSync)(opts.glb, Buffer.from(glb));
205
+ if (wantStep && step) {
206
+ stepPath = opts.step ?? (0, node_path.join)((0, node_os.tmpdir)(), `brepjs-verify-${(0, node_path.basename)(file)}.step`);
207
+ (0, node_fs.writeFileSync)(stepPath, Buffer.from(step));
208
+ }
209
+ if (opts.snapshot && stepPath) {
210
+ const shoot = await loadSnapshotShoot();
211
+ if (shoot) {
212
+ const { pngs } = await shoot({
213
+ file: stepPath,
214
+ outDir: opts.snapshot
215
+ });
216
+ for (const p of pngs) process.stderr.write(`snapshot: ${p}\n`);
217
+ }
218
+ } else if (opts.snapshot) process.stderr.write("snapshot skipped: STEP export produced no artifact\n");
219
+ } catch (e) {
220
+ require_diff.pushError(report, { message: `artifact write failed: ${e.message}` });
221
+ } finally {
222
+ disposeShape(shape);
223
+ }
224
+ const json = require_diff.serializeReport(report);
225
+ if (opts.json) (0, node_fs.writeFileSync)(opts.json, json);
226
+ process.stdout.write(json + "\n");
227
+ if (!require_diff.reportOk(report)) process.exitCode = 1;
228
+ if (Boolean(opts.serve) && stepPath !== void 0 && require_diff.reportOk(report) && stepPath) {
229
+ const { serve } = await Promise.resolve().then(() => require("../snapshot/serve.cjs"));
230
+ const { url } = await serve({ file: stepPath });
231
+ process.stderr.write(`viewer: ${url}\n`);
232
+ }
233
+ });
234
+ 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) => {
235
+ const result = await require_diff.runMeasure((0, node_path.resolve)(a), b === void 0 ? void 0 : (0, node_path.resolve)(b));
236
+ process.stdout.write(JSON.stringify({
237
+ ok: result.errors.length === 0,
238
+ ...result
239
+ }, null, 2) + "\n");
240
+ if (result.errors.length > 0) process.exitCode = 1;
241
+ });
242
+ program.command("diff").argument("<a>", "path to the baseline .brep.ts module").argument("<b>", "path to the comparison .brep.ts module").action(async (a, b) => {
243
+ const result = await require_diff.runDiff((0, node_path.resolve)(a), (0, node_path.resolve)(b));
244
+ process.stdout.write(JSON.stringify({
245
+ ok: result.errors.length === 0,
246
+ ...result
247
+ }, null, 2) + "\n");
248
+ if (result.errors.length > 0) process.exitCode = 1;
249
+ });
250
+ 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) => {
251
+ const result = scaffoldPart(name, (0, node_path.resolve)(opts.out ?? name));
252
+ for (const f of result.files) {
253
+ const tag = f.created ? "created" : "exists (kept)";
254
+ process.stderr.write(`${tag}: ${f.path}\n`);
255
+ }
256
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
257
+ });
258
+ program.command("watch").argument("<file>", "path to a .brep.ts module; re-verifies on each save until Ctrl-C").action((file) => {
259
+ const path = (0, node_path.resolve)(file);
260
+ const run = async () => {
261
+ try {
262
+ const { report, shape } = await require_diff.runPart(path);
263
+ try {
264
+ process.stdout.write(require_diff.serializeReport(report) + "\n");
265
+ } finally {
266
+ disposeShape(shape);
267
+ }
268
+ } catch (e) {
269
+ process.stderr.write(`watch run failed: ${e.message}\n`);
270
+ }
271
+ };
272
+ const { trigger } = debounce(run, 150);
273
+ process.stderr.write(`watching ${path} (Ctrl-C to stop)\n`);
274
+ run();
275
+ const watcher = (0, node_fs.watch)((0, node_path.dirname)(path), (_event, filename) => {
276
+ if (filename === void 0 || filename === null) {
277
+ trigger();
278
+ return;
279
+ }
280
+ if ((0, node_path.basename)(path) === filename.toString()) trigger();
281
+ });
282
+ const stop = () => {
283
+ watcher.close();
284
+ process.exit(0);
285
+ };
286
+ process.on("SIGINT", stop);
287
+ process.on("SIGTERM", stop);
288
+ });
289
+ 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) => {
290
+ const formats = opts.all ? {
291
+ step: true,
292
+ glb: true,
293
+ stl: true
294
+ } : {
295
+ step: Boolean(opts.step),
296
+ glb: Boolean(opts.glb),
297
+ stl: Boolean(opts.stl)
298
+ };
299
+ if (!formats.step && !formats.glb && !formats.stl) {
300
+ process.stderr.write("no formats requested — pass --step/--glb/--stl or --all\n");
301
+ process.exitCode = 1;
302
+ return;
303
+ }
304
+ const result = await exportPart((0, node_path.resolve)(file), formats, (0, node_path.resolve)(opts.out));
305
+ for (const p of result.written) process.stderr.write(`wrote: ${p}\n`);
306
+ for (const e of result.errors) process.stderr.write(`error: ${e}\n`);
307
+ process.stdout.write(JSON.stringify({
308
+ ok: result.ok,
309
+ written: result.written,
310
+ errors: result.errors
311
+ }, null, 2) + "\n");
312
+ if (!result.ok) process.exitCode = 1;
313
+ });
314
+ function isEntrypoint(argv1, moduleUrl) {
315
+ if (!argv1) return false;
316
+ try {
317
+ return (0, node_fs.realpathSync)(argv1) === (0, node_fs.realpathSync)((0, node_url.fileURLToPath)(moduleUrl));
318
+ } catch {
319
+ return false;
320
+ }
321
+ }
322
+ if (isEntrypoint(process.argv[1], {}.url)) program.parseAsync();
323
+ //#endregion
324
+ exports.isEntrypoint = isEntrypoint;
325
+ exports.loadSnapshotShoot = loadSnapshotShoot;
@@ -0,0 +1,3 @@
1
+ import { shoot as ShootFn } from '../snapshot/shoot.js';
2
+ export declare function loadSnapshotShoot(): Promise<typeof ShootFn | undefined>;
3
+ export declare function isEntrypoint(argv1: string | undefined, moduleUrl: string): boolean;
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env node
2
+ import { f as pushError, h as loadBrep, m as serializeReport, n as runMeasure, p as reportOk, r as runPart, t as runDiff } from "../diff-D7ZBNRJG.js";
3
+ import { fileURLToPath } from "node:url";
4
+ import { existsSync, mkdirSync, realpathSync, watch, writeFileSync } from "node:fs";
5
+ import { basename, dirname, join, resolve } from "node:path";
6
+ import { Command } from "commander";
7
+ import { tmpdir } from "node:os";
8
+ //#region src/cli/scaffold.ts
9
+ function partTemplate(name) {
10
+ return `import { box, cut, unwrap } from 'brepjs';
11
+
12
+ const width = 40;
13
+ const depth = 20;
14
+ const height = 10;
15
+ const holeSize = 6;
16
+
17
+ // ${name}: a parameterized starter part — edit the consts above, then re-verify.
18
+ export default () => {
19
+ const body = box(width, depth, height, { centered: true });
20
+ const hole = box(holeSize, holeSize, height + 2, { centered: true });
21
+ return unwrap(cut(body, hole));
22
+ };
23
+ `;
24
+ }
25
+ function tsconfigTemplate() {
26
+ return `${JSON.stringify({
27
+ compilerOptions: {
28
+ target: "ES2022",
29
+ module: "ESNext",
30
+ moduleResolution: "bundler",
31
+ strict: true,
32
+ noEmit: true,
33
+ skipLibCheck: true
34
+ },
35
+ include: ["*.brep.ts"]
36
+ }, null, 2)}\n`;
37
+ }
38
+ function readmeTemplate(name) {
39
+ return `# ${name}
40
+
41
+ A parametric brepjs CAD part. Units are millimetres.
42
+
43
+ ## Verify
44
+
45
+ \`\`\`sh
46
+ npx -y brepjs-verify ${name}.brep.ts --json report.json
47
+ \`\`\`
48
+
49
+ ## Iterate
50
+
51
+ \`\`\`sh
52
+ npx -y brepjs-verify watch ${name}.brep.ts
53
+ \`\`\`
54
+
55
+ ## Export artifacts
56
+
57
+ \`\`\`sh
58
+ npx -y brepjs-verify export ${name}.brep.ts --all --out out/
59
+ \`\`\`
60
+ `;
61
+ }
62
+ function scaffoldPart(name, dir) {
63
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
64
+ const targets = [
65
+ {
66
+ path: join(dir, `${name}.brep.ts`),
67
+ content: partTemplate(name)
68
+ },
69
+ {
70
+ path: join(dir, "tsconfig.json"),
71
+ content: tsconfigTemplate()
72
+ },
73
+ {
74
+ path: join(dir, "README.md"),
75
+ content: readmeTemplate(name)
76
+ }
77
+ ];
78
+ const files = [];
79
+ for (const t of targets) {
80
+ if (existsSync(t.path)) {
81
+ files.push({
82
+ path: t.path,
83
+ created: false
84
+ });
85
+ continue;
86
+ }
87
+ writeFileSync(t.path, t.content);
88
+ files.push({
89
+ path: t.path,
90
+ created: true
91
+ });
92
+ }
93
+ return {
94
+ dir,
95
+ files
96
+ };
97
+ }
98
+ function debounce(fn, delayMs = 150) {
99
+ let timer;
100
+ const cancel = () => {
101
+ if (timer) {
102
+ clearTimeout(timer);
103
+ timer = void 0;
104
+ }
105
+ };
106
+ const trigger = () => {
107
+ cancel();
108
+ timer = setTimeout(() => {
109
+ timer = void 0;
110
+ fn();
111
+ }, delayMs);
112
+ };
113
+ return {
114
+ trigger,
115
+ cancel
116
+ };
117
+ }
118
+ //#endregion
119
+ //#region src/disposeShape.ts
120
+ function disposeShape(shape) {
121
+ const disposer = shape?.[Symbol.dispose];
122
+ if (typeof disposer === "function") disposer.call(shape);
123
+ }
124
+ //#endregion
125
+ //#region src/cli/exportPart.ts
126
+ function stem(file) {
127
+ return basename(file).replace(/\.brep\.ts$/, "").replace(/\.ts$/, "");
128
+ }
129
+ async function exportPart(modulePath, formats, outDir) {
130
+ const { shape, report, step, glb } = await runPart(modulePath, {
131
+ step: Boolean(formats.step),
132
+ glb: Boolean(formats.glb)
133
+ });
134
+ const errors = [];
135
+ const written = [];
136
+ if (!(reportOk(report) && shape !== null)) {
137
+ disposeShape(shape);
138
+ const failures = report.errorInfos.map((e) => e.message);
139
+ return {
140
+ ok: false,
141
+ report,
142
+ written,
143
+ errors: failures.length > 0 ? failures : report.errors
144
+ };
145
+ }
146
+ try {
147
+ if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
148
+ const base = stem(modulePath);
149
+ if (formats.step) if (step) {
150
+ const p = join(outDir, `${base}.step`);
151
+ writeFileSync(p, Buffer.from(step));
152
+ written.push(p);
153
+ } else errors.push("STEP export produced no data");
154
+ if (formats.glb) if (glb) {
155
+ const p = join(outDir, `${base}.glb`);
156
+ writeFileSync(p, Buffer.from(glb));
157
+ written.push(p);
158
+ } else errors.push("GLB export produced no data");
159
+ if (formats.stl) {
160
+ const { exportSTL, isOk } = await loadBrep();
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);
170
+ }
171
+ return {
172
+ ok: errors.length === 0,
173
+ report,
174
+ written,
175
+ errors
176
+ };
177
+ }
178
+ //#endregion
179
+ //#region src/cli/main.ts
180
+ console.log = (...args) => {
181
+ process.stderr.write(args.map(String).join(" ") + "\n");
182
+ };
183
+ async function loadSnapshotShoot() {
184
+ try {
185
+ return (await import("../snapshot/shoot.js")).shoot;
186
+ } catch {
187
+ process.stderr.write("snapshots need puppeteer/Chrome — run: npm i puppeteer\n");
188
+ process.exitCode = 1;
189
+ return;
190
+ }
191
+ }
192
+ var program = new Command();
193
+ program.name("brepjs-verify");
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("--check", "type-check the part (against brepjs types) before running; skip execution on type errors").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) => {
195
+ const wantStep = Boolean(opts.step) || Boolean(opts.snapshot) || Boolean(opts.serve);
196
+ const { report, step, glb, shape } = await runPart(resolve(file), {
197
+ step: wantStep,
198
+ glb: Boolean(opts.glb),
199
+ check: Boolean(opts.check)
200
+ });
201
+ let stepPath = opts.step;
202
+ try {
203
+ if (opts.glb && glb) writeFileSync(opts.glb, Buffer.from(glb));
204
+ if (wantStep && step) {
205
+ stepPath = opts.step ?? join(tmpdir(), `brepjs-verify-${basename(file)}.step`);
206
+ writeFileSync(stepPath, Buffer.from(step));
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
+ pushError(report, { message: `artifact write failed: ${e.message}` });
220
+ } finally {
221
+ disposeShape(shape);
222
+ }
223
+ const json = serializeReport(report);
224
+ if (opts.json) writeFileSync(opts.json, json);
225
+ process.stdout.write(json + "\n");
226
+ if (!reportOk(report)) process.exitCode = 1;
227
+ if (Boolean(opts.serve) && stepPath !== void 0 && reportOk(report) && stepPath) {
228
+ const { serve } = await import("../snapshot/serve.js");
229
+ const { url } = await serve({ file: stepPath });
230
+ process.stderr.write(`viewer: ${url}\n`);
231
+ }
232
+ });
233
+ 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) => {
234
+ const result = await runMeasure(resolve(a), b === void 0 ? void 0 : resolve(b));
235
+ process.stdout.write(JSON.stringify({
236
+ ok: result.errors.length === 0,
237
+ ...result
238
+ }, null, 2) + "\n");
239
+ if (result.errors.length > 0) process.exitCode = 1;
240
+ });
241
+ program.command("diff").argument("<a>", "path to the baseline .brep.ts module").argument("<b>", "path to the comparison .brep.ts module").action(async (a, b) => {
242
+ const result = await runDiff(resolve(a), resolve(b));
243
+ process.stdout.write(JSON.stringify({
244
+ ok: result.errors.length === 0,
245
+ ...result
246
+ }, null, 2) + "\n");
247
+ if (result.errors.length > 0) process.exitCode = 1;
248
+ });
249
+ 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) => {
250
+ const result = scaffoldPart(name, resolve(opts.out ?? name));
251
+ for (const f of result.files) {
252
+ const tag = f.created ? "created" : "exists (kept)";
253
+ process.stderr.write(`${tag}: ${f.path}\n`);
254
+ }
255
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
256
+ });
257
+ program.command("watch").argument("<file>", "path to a .brep.ts module; re-verifies on each save until Ctrl-C").action((file) => {
258
+ const path = resolve(file);
259
+ const run = async () => {
260
+ try {
261
+ const { report, shape } = await runPart(path);
262
+ try {
263
+ process.stdout.write(serializeReport(report) + "\n");
264
+ } finally {
265
+ disposeShape(shape);
266
+ }
267
+ } catch (e) {
268
+ process.stderr.write(`watch run failed: ${e.message}\n`);
269
+ }
270
+ };
271
+ const { trigger } = debounce(run, 150);
272
+ process.stderr.write(`watching ${path} (Ctrl-C to stop)\n`);
273
+ run();
274
+ const watcher = watch(dirname(path), (_event, filename) => {
275
+ if (filename === void 0 || filename === null) {
276
+ trigger();
277
+ return;
278
+ }
279
+ if (basename(path) === filename.toString()) trigger();
280
+ });
281
+ const stop = () => {
282
+ watcher.close();
283
+ process.exit(0);
284
+ };
285
+ process.on("SIGINT", stop);
286
+ process.on("SIGTERM", stop);
287
+ });
288
+ 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) => {
289
+ const formats = opts.all ? {
290
+ step: true,
291
+ glb: true,
292
+ stl: true
293
+ } : {
294
+ step: Boolean(opts.step),
295
+ glb: Boolean(opts.glb),
296
+ stl: Boolean(opts.stl)
297
+ };
298
+ if (!formats.step && !formats.glb && !formats.stl) {
299
+ process.stderr.write("no formats requested — pass --step/--glb/--stl or --all\n");
300
+ process.exitCode = 1;
301
+ return;
302
+ }
303
+ const result = await exportPart(resolve(file), formats, resolve(opts.out));
304
+ for (const p of result.written) process.stderr.write(`wrote: ${p}\n`);
305
+ for (const e of result.errors) process.stderr.write(`error: ${e}\n`);
306
+ process.stdout.write(JSON.stringify({
307
+ ok: result.ok,
308
+ written: result.written,
309
+ errors: result.errors
310
+ }, null, 2) + "\n");
311
+ if (!result.ok) process.exitCode = 1;
312
+ });
313
+ function isEntrypoint(argv1, moduleUrl) {
314
+ if (!argv1) return false;
315
+ try {
316
+ return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
317
+ } catch {
318
+ return false;
319
+ }
320
+ }
321
+ if (isEntrypoint(process.argv[1], import.meta.url)) program.parseAsync();
322
+ //#endregion
323
+ export { isEntrypoint, loadSnapshotShoot };
@@ -0,0 +1,9 @@
1
+ export interface ScaffoldFile {
2
+ path: string;
3
+ created: boolean;
4
+ }
5
+ export interface ScaffoldResult {
6
+ dir: string;
7
+ files: ScaffoldFile[];
8
+ }
9
+ export declare function scaffoldPart(name: string, dir: string): ScaffoldResult;
@@ -0,0 +1,5 @@
1
+ export declare const DEFAULT_DEBOUNCE_MS = 150;
2
+ export declare function debounce(fn: () => void | Promise<void>, delayMs?: number): {
3
+ trigger: () => void;
4
+ cancel: () => void;
5
+ };