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.
- package/CHANGELOG.md +24 -0
- package/LICENSE +191 -0
- package/README.md +85 -0
- package/dist/brepjs-verify.cjs +14 -0
- package/dist/brepjs-verify.js +2 -0
- package/dist/chunk-D6vf50IK.cjs +28 -0
- package/dist/cli/exportPart.d.ts +13 -0
- package/dist/cli/main.cjs +325 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.js +323 -0
- package/dist/cli/scaffold.d.ts +9 -0
- package/dist/cli/watch.d.ts +5 -0
- package/dist/diff-CZ4mLtrf.cjs +869 -0
- package/dist/diff-D7ZBNRJG.js +778 -0
- package/dist/disposeShape.d.ts +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/loader/brepjsResolve.mjs +57 -0
- package/dist/snapshot/registry.cjs +50 -0
- package/dist/snapshot/registry.d.ts +12 -0
- package/dist/snapshot/registry.js +48 -0
- package/dist/snapshot/serve.cjs +27 -0
- package/dist/snapshot/serve.d.ts +12 -0
- package/dist/snapshot/serve.js +26 -0
- package/dist/snapshot/shoot.cjs +64 -0
- package/dist/snapshot/shoot.d.ts +14 -0
- package/dist/snapshot/shoot.js +61 -0
- package/dist/snapshot/static.cjs +100 -0
- package/dist/snapshot/static.d.ts +16 -0
- package/dist/snapshot/static.js +98 -0
- package/dist/verify/brepjsRuntime.d.ts +4 -0
- package/dist/verify/checks.d.ts +4 -0
- package/dist/verify/diff.d.ts +2 -0
- package/dist/verify/expected.d.ts +26 -0
- package/dist/verify/measure.d.ts +6 -0
- package/dist/verify/report.d.ts +75 -0
- package/dist/verify/runPart.d.ts +23 -0
- package/dist/verify/typecheck.d.ts +17 -0
- package/package.json +78 -0
- package/viewer/dist/assets/brepjs-CDZqKweN.js +57 -0
- package/viewer/dist/assets/index-B8QUQDqM.js +4167 -0
- package/viewer/dist/assets/kernelWorker-C6s5i9JH.js +1 -0
- package/viewer/dist/index.html +22 -0
- package/viewer/dist/wasm/occt-wasm.js +2 -0
- 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;
|
package/dist/cli/main.js
ADDED
|
@@ -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 };
|