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 @@
1
+ export declare function disposeShape(shape: unknown): void;
@@ -0,0 +1,7 @@
1
+ export { runPart, type RunPartOptions, type RunPartResult } from './verify/runPart.js';
2
+ export { runChecks } from './verify/checks.js';
3
+ export { runMeasure, type MeasureReport } from './verify/measure.js';
4
+ export { runDiff } from './verify/diff.js';
5
+ export { serializeReport, emptyReport, type VerifyReport, type VerifyCheck, type VerifyMeasurements, type VerifyAssertion, type DiffReport, type BoundsDelta, } from './verify/report.js';
6
+ export { typecheckPart, TYPECHECK_CODE, type TypecheckResult } from './verify/typecheck.js';
7
+ export { evaluateExpected, isExpectedDims, pctDelta, DEFAULT_TOLERANCE_PCT, type ExpectedDims, type ExpectedBounds, } from './verify/expected.js';
@@ -0,0 +1,57 @@
1
+ import { pathToFileURL } from 'node:url';
2
+
3
+ // Prefer-local resolution hook for the bundled `brepjs-verify` CLI.
4
+ //
5
+ // The tool bundles its own `brepjs` + `occt-wasm` so it runs standalone, but a part
6
+ // authored inside a real project should bind to THAT project's installed `brepjs`
7
+ // (matching the kernel/types the author develops against). So for every `brepjs` /
8
+ // `occt-wasm` specifier we FIRST try default resolution (which walks the importing
9
+ // module's node_modules and finds a local copy if present); only when that fails do we
10
+ // fall back to the tool's own bundled copy.
11
+ //
12
+ // Critical single-instance property: the bundled fallback always resolves against the
13
+ // SAME tool directory regardless of who imports, so the CLI's own `brepjs` and the
14
+ // part's `import 'brepjs'` collapse to one module URL — one initialized kernel realm.
15
+
16
+ let toolBaseUrl;
17
+
18
+ export async function initialize(data) {
19
+ // `data.toolDir` is the brepjs-verify package root; resolve bundled deps relative to a
20
+ // file inside it so Node's resolver walks the tool's node_modules.
21
+ const dir = data && typeof data.toolDir === 'string' ? data.toolDir : undefined;
22
+ if (dir) {
23
+ toolBaseUrl = pathToFileURL(
24
+ dir.endsWith('/') ? dir + 'package.json' : dir + '/package.json'
25
+ ).href;
26
+ }
27
+ }
28
+
29
+ function isManagedSpecifier(specifier) {
30
+ return (
31
+ specifier === 'brepjs' ||
32
+ specifier.startsWith('brepjs/') ||
33
+ specifier === 'occt-wasm' ||
34
+ specifier.startsWith('occt-wasm/')
35
+ );
36
+ }
37
+
38
+ export async function resolve(specifier, context, nextResolve) {
39
+ if (!isManagedSpecifier(specifier)) {
40
+ return nextResolve(specifier, context);
41
+ }
42
+ try {
43
+ // Default resolution: finds a LOCAL brepjs/occt-wasm in the consumer's project.
44
+ return await nextResolve(specifier, context);
45
+ } catch (err) {
46
+ if (!toolBaseUrl) throw err;
47
+ // Re-run the DEFAULT resolver, but as if the import came from inside the tool's package
48
+ // (parentURL = the tool's package.json) so it walks the tool's node_modules and honors
49
+ // package "exports" for subpaths. `import.meta.resolve` is unavailable in the hooks
50
+ // thread, so this nextResolve-with-rebased-parent is the resolver we have.
51
+ try {
52
+ return await nextResolve(specifier, { ...context, parentURL: toolBaseUrl });
53
+ } catch {
54
+ throw err;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,50 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_snapshot_static = require("./static.cjs");
3
+ //#region src/snapshot/registry.ts
4
+ var DEFAULT_PORT = 7373;
5
+ var PROBE_SPAN = 8;
6
+ var DEFAULT_SHUTDOWN_AFTER_MS = 720 * 60 * 1e3;
7
+ async function probe(port) {
8
+ const ctrl = new AbortController();
9
+ const timer = setTimeout(() => ctrl.abort(), 400);
10
+ try {
11
+ const res = await fetch(`http://127.0.0.1:${port}/__cad/server`, { signal: ctrl.signal });
12
+ if (!res.ok) return false;
13
+ const d = await res.json();
14
+ return d.app === "brepjs-cad-viewer" && d.dynamicRoot === true && typeof d.serverApiVersion === "number" && d.serverApiVersion >= 1;
15
+ } catch {
16
+ return false;
17
+ } finally {
18
+ clearTimeout(timer);
19
+ }
20
+ }
21
+ async function acquireServer(opts = {}) {
22
+ const ports = opts.port !== void 0 ? [opts.port] : Array.from({ length: PROBE_SPAN }, (_, i) => DEFAULT_PORT + i);
23
+ for (const port of ports) if (await probe(port)) return {
24
+ port,
25
+ url: `http://127.0.0.1:${port}`,
26
+ reused: true,
27
+ close: () => Promise.resolve()
28
+ };
29
+ let server;
30
+ for (const port of ports) try {
31
+ server = await require_snapshot_static.startStaticServer({ port });
32
+ break;
33
+ } catch {}
34
+ if (!server) throw new Error(`no free port in ${ports[0]}..${ports.at(-1)}`);
35
+ const timer = setTimeout(() => void server?.close(), opts.shutdownAfterMs ?? 432e5);
36
+ timer.unref();
37
+ const started = server;
38
+ return {
39
+ port: started.port,
40
+ url: started.url,
41
+ reused: false,
42
+ close: async () => {
43
+ clearTimeout(timer);
44
+ await started.close();
45
+ }
46
+ };
47
+ }
48
+ //#endregion
49
+ exports.DEFAULT_SHUTDOWN_AFTER_MS = DEFAULT_SHUTDOWN_AFTER_MS;
50
+ exports.acquireServer = acquireServer;
@@ -0,0 +1,12 @@
1
+ export declare const DEFAULT_SHUTDOWN_AFTER_MS: number;
2
+ export interface AcquireOptions {
3
+ port?: number;
4
+ shutdownAfterMs?: number;
5
+ }
6
+ export interface AcquiredServer {
7
+ port: number;
8
+ url: string;
9
+ reused: boolean;
10
+ close(): Promise<void>;
11
+ }
12
+ export declare function acquireServer(opts?: AcquireOptions): Promise<AcquiredServer>;
@@ -0,0 +1,48 @@
1
+ import { startStaticServer } from "./static.js";
2
+ //#region src/snapshot/registry.ts
3
+ var DEFAULT_PORT = 7373;
4
+ var PROBE_SPAN = 8;
5
+ var DEFAULT_SHUTDOWN_AFTER_MS = 720 * 60 * 1e3;
6
+ async function probe(port) {
7
+ const ctrl = new AbortController();
8
+ const timer = setTimeout(() => ctrl.abort(), 400);
9
+ try {
10
+ const res = await fetch(`http://127.0.0.1:${port}/__cad/server`, { signal: ctrl.signal });
11
+ if (!res.ok) return false;
12
+ const d = await res.json();
13
+ return d.app === "brepjs-cad-viewer" && d.dynamicRoot === true && typeof d.serverApiVersion === "number" && d.serverApiVersion >= 1;
14
+ } catch {
15
+ return false;
16
+ } finally {
17
+ clearTimeout(timer);
18
+ }
19
+ }
20
+ async function acquireServer(opts = {}) {
21
+ const ports = opts.port !== void 0 ? [opts.port] : Array.from({ length: PROBE_SPAN }, (_, i) => DEFAULT_PORT + i);
22
+ for (const port of ports) if (await probe(port)) return {
23
+ port,
24
+ url: `http://127.0.0.1:${port}`,
25
+ reused: true,
26
+ close: () => Promise.resolve()
27
+ };
28
+ let server;
29
+ for (const port of ports) try {
30
+ server = await startStaticServer({ port });
31
+ break;
32
+ } catch {}
33
+ if (!server) throw new Error(`no free port in ${ports[0]}..${ports.at(-1)}`);
34
+ const timer = setTimeout(() => void server?.close(), opts.shutdownAfterMs ?? 432e5);
35
+ timer.unref();
36
+ const started = server;
37
+ return {
38
+ port: started.port,
39
+ url: started.url,
40
+ reused: false,
41
+ close: async () => {
42
+ clearTimeout(timer);
43
+ await started.close();
44
+ }
45
+ };
46
+ }
47
+ //#endregion
48
+ export { DEFAULT_SHUTDOWN_AFTER_MS, acquireServer };
@@ -0,0 +1,27 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_snapshot_registry = require("./registry.cjs");
3
+ let node_path = require("node:path");
4
+ //#region src/snapshot/serve.ts
5
+ function viewerUrl(base, file) {
6
+ if (!file) return base;
7
+ const abs = (0, node_path.resolve)(file);
8
+ return `${base}/?dir=${encodeURIComponent((0, node_path.dirname)(abs))}&file=${encodeURIComponent((0, node_path.basename)(abs))}`;
9
+ }
10
+ /** Acquire (reuse or start) the persistent server and return its viewer URL. */
11
+ async function serve(opts = {}) {
12
+ const server = await require_snapshot_registry.acquireServer(opts);
13
+ const url = viewerUrl(server.url, opts.file);
14
+ if (!server.reused) {
15
+ const onSig = () => void server.close().then(() => process.exit(0));
16
+ process.once("SIGINT", onSig);
17
+ process.once("SIGTERM", onSig);
18
+ }
19
+ return {
20
+ port: server.port,
21
+ url,
22
+ reused: server.reused,
23
+ close: () => server.close()
24
+ };
25
+ }
26
+ //#endregion
27
+ exports.serve = serve;
@@ -0,0 +1,12 @@
1
+ import { AcquireOptions } from './registry.js';
2
+ export interface ServeOptions extends AcquireOptions {
3
+ file?: string;
4
+ }
5
+ export interface ServeHandle {
6
+ port: number;
7
+ url: string;
8
+ reused: boolean;
9
+ close(): Promise<void>;
10
+ }
11
+ /** Acquire (reuse or start) the persistent server and return its viewer URL. */
12
+ export declare function serve(opts?: ServeOptions): Promise<ServeHandle>;
@@ -0,0 +1,26 @@
1
+ import { acquireServer } from "./registry.js";
2
+ import { basename, dirname, resolve } from "node:path";
3
+ //#region src/snapshot/serve.ts
4
+ function viewerUrl(base, file) {
5
+ if (!file) return base;
6
+ const abs = resolve(file);
7
+ return `${base}/?dir=${encodeURIComponent(dirname(abs))}&file=${encodeURIComponent(basename(abs))}`;
8
+ }
9
+ /** Acquire (reuse or start) the persistent server and return its viewer URL. */
10
+ async function serve(opts = {}) {
11
+ const server = await acquireServer(opts);
12
+ const url = viewerUrl(server.url, opts.file);
13
+ if (!server.reused) {
14
+ const onSig = () => void server.close().then(() => process.exit(0));
15
+ process.once("SIGINT", onSig);
16
+ process.once("SIGTERM", onSig);
17
+ }
18
+ return {
19
+ port: server.port,
20
+ url,
21
+ reused: server.reused,
22
+ close: () => server.close()
23
+ };
24
+ }
25
+ //#endregion
26
+ export { serve };
@@ -0,0 +1,64 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_chunk = require("../chunk-D6vf50IK.cjs");
3
+ const require_snapshot_registry = require("./registry.cjs");
4
+ let node_path = require("node:path");
5
+ let node_fs_promises = require("node:fs/promises");
6
+ let puppeteer = require("puppeteer");
7
+ puppeteer = require_chunk.__toESM(puppeteer, 1);
8
+ //#region src/snapshot/shoot.ts
9
+ var VIEWS = [
10
+ "iso",
11
+ "front",
12
+ "top",
13
+ "right"
14
+ ];
15
+ var READY_TIMEOUT_MS = 9e4;
16
+ async function shoot(opts) {
17
+ const absFile = (0, node_path.resolve)(opts.file);
18
+ const dir = (0, node_path.dirname)(absFile);
19
+ const rel = (0, node_path.basename)(absFile);
20
+ const views = opts.views ?? VIEWS;
21
+ await (0, node_fs_promises.mkdir)(opts.outDir, { recursive: true });
22
+ const server = await require_snapshot_registry.acquireServer(opts);
23
+ const browser = await puppeteer.default.launch({
24
+ headless: true,
25
+ args: [
26
+ "--no-sandbox",
27
+ "--use-gl=angle",
28
+ "--use-angle=swiftshader",
29
+ "--enable-unsafe-swiftshader"
30
+ ]
31
+ });
32
+ const pngs = [];
33
+ try {
34
+ const page = await browser.newPage();
35
+ await page.setViewport({
36
+ width: 1200,
37
+ height: 900
38
+ });
39
+ const target = `${server.url}/?dir=${encodeURIComponent(dir)}&file=${encodeURIComponent(rel)}`;
40
+ await page.goto(target, {
41
+ waitUntil: "domcontentloaded",
42
+ timeout: 3e4
43
+ });
44
+ await page.waitForFunction("window.__ready === true", { timeout: READY_TIMEOUT_MS });
45
+ for (const view of views) {
46
+ await page.evaluate((v) => {
47
+ globalThis.__renderView(v);
48
+ }, view);
49
+ await new Promise((r) => setTimeout(r, opts.settleMs ?? 400));
50
+ const path = (0, node_path.resolve)(opts.outDir, `${view}.png`);
51
+ await page.screenshot({ path });
52
+ pngs.push(path);
53
+ }
54
+ } finally {
55
+ await browser.close();
56
+ await server.close();
57
+ }
58
+ return {
59
+ outDir: opts.outDir,
60
+ pngs
61
+ };
62
+ }
63
+ //#endregion
64
+ exports.shoot = shoot;
@@ -0,0 +1,14 @@
1
+ import { AcquireOptions } from './registry.js';
2
+ export type ViewName = 'iso' | 'front' | 'top' | 'right';
3
+ export interface ShootOptions extends AcquireOptions {
4
+ file: string;
5
+ outDir: string;
6
+ views?: readonly ViewName[];
7
+ /** Wall-clock ms to let the camera settle before each capture (default 400; raise on slow CI). */
8
+ settleMs?: number;
9
+ }
10
+ export interface ShootResult {
11
+ outDir: string;
12
+ pngs: string[];
13
+ }
14
+ export declare function shoot(opts: ShootOptions): Promise<ShootResult>;
@@ -0,0 +1,61 @@
1
+ import { acquireServer } from "./registry.js";
2
+ import { basename, dirname, resolve } from "node:path";
3
+ import { mkdir } from "node:fs/promises";
4
+ import puppeteer from "puppeteer";
5
+ //#region src/snapshot/shoot.ts
6
+ var VIEWS = [
7
+ "iso",
8
+ "front",
9
+ "top",
10
+ "right"
11
+ ];
12
+ var READY_TIMEOUT_MS = 9e4;
13
+ async function shoot(opts) {
14
+ const absFile = resolve(opts.file);
15
+ const dir = dirname(absFile);
16
+ const rel = basename(absFile);
17
+ const views = opts.views ?? VIEWS;
18
+ await mkdir(opts.outDir, { recursive: true });
19
+ const server = await acquireServer(opts);
20
+ const browser = await puppeteer.launch({
21
+ headless: true,
22
+ args: [
23
+ "--no-sandbox",
24
+ "--use-gl=angle",
25
+ "--use-angle=swiftshader",
26
+ "--enable-unsafe-swiftshader"
27
+ ]
28
+ });
29
+ const pngs = [];
30
+ try {
31
+ const page = await browser.newPage();
32
+ await page.setViewport({
33
+ width: 1200,
34
+ height: 900
35
+ });
36
+ const target = `${server.url}/?dir=${encodeURIComponent(dir)}&file=${encodeURIComponent(rel)}`;
37
+ await page.goto(target, {
38
+ waitUntil: "domcontentloaded",
39
+ timeout: 3e4
40
+ });
41
+ await page.waitForFunction("window.__ready === true", { timeout: READY_TIMEOUT_MS });
42
+ for (const view of views) {
43
+ await page.evaluate((v) => {
44
+ globalThis.__renderView(v);
45
+ }, view);
46
+ await new Promise((r) => setTimeout(r, opts.settleMs ?? 400));
47
+ const path = resolve(opts.outDir, `${view}.png`);
48
+ await page.screenshot({ path });
49
+ pngs.push(path);
50
+ }
51
+ } finally {
52
+ await browser.close();
53
+ await server.close();
54
+ }
55
+ return {
56
+ outDir: opts.outDir,
57
+ pngs
58
+ };
59
+ }
60
+ //#endregion
61
+ export { shoot };
@@ -0,0 +1,100 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_url = require("node:url");
3
+ let node_fs = require("node:fs");
4
+ let node_path = require("node:path");
5
+ let node_http = require("node:http");
6
+ let node_fs_promises = require("node:fs/promises");
7
+ //#region src/snapshot/static.ts
8
+ var SERVER_API_VERSION = 1;
9
+ var VIEWER_DIST = (0, node_path.resolve)((0, node_path.dirname)((0, node_url.fileURLToPath)({}.url)), "../../viewer/dist");
10
+ var MIME = {
11
+ ".html": "text/html; charset=utf-8",
12
+ ".js": "text/javascript; charset=utf-8",
13
+ ".mjs": "text/javascript; charset=utf-8",
14
+ ".css": "text/css; charset=utf-8",
15
+ ".json": "application/json; charset=utf-8",
16
+ ".wasm": "application/wasm",
17
+ ".png": "image/png",
18
+ ".svg": "image/svg+xml",
19
+ ".glb": "model/gltf-binary",
20
+ ".gltf": "model/gltf+json",
21
+ ".step": "application/step",
22
+ ".stp": "application/step",
23
+ ".stl": "model/stl",
24
+ ".obj": "text/plain; charset=utf-8"
25
+ };
26
+ var mimeFor = (p) => MIME[(0, node_path.extname)(p).toLowerCase()] ?? "application/octet-stream";
27
+ async function sendFile(res, absPath) {
28
+ const info = await (0, node_fs_promises.stat)(absPath);
29
+ if (!info.isFile()) {
30
+ res.writeHead(404).end("not found");
31
+ return;
32
+ }
33
+ res.writeHead(200, {
34
+ "content-type": mimeFor(absPath),
35
+ "content-length": info.size
36
+ });
37
+ (0, node_fs.createReadStream)(absPath).pipe(res);
38
+ }
39
+ function safeJoin(root, rel) {
40
+ const abs = (0, node_path.resolve)(root, (0, node_path.normalize)(decodeURIComponent(rel.replace(/^\/+/, ""))));
41
+ if (abs !== root && !abs.startsWith(root + node_path.sep)) return null;
42
+ return abs;
43
+ }
44
+ async function handle(req, res, port) {
45
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
46
+ if (url.pathname === "/__cad/server") {
47
+ const d = {
48
+ app: "brepjs-cad-viewer",
49
+ port,
50
+ dynamicRoot: true,
51
+ serverApiVersion: 1
52
+ };
53
+ res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
54
+ res.end(JSON.stringify(d));
55
+ return;
56
+ }
57
+ if (url.pathname.startsWith("/__model/")) {
58
+ const root = url.searchParams.get("dir");
59
+ if (!root) {
60
+ res.writeHead(400).end("missing dir");
61
+ return;
62
+ }
63
+ const abs = safeJoin((0, node_path.resolve)(root), url.pathname.slice(8));
64
+ if (!abs) {
65
+ res.writeHead(403).end("forbidden");
66
+ return;
67
+ }
68
+ await sendFile(res, abs).catch(() => res.writeHead(404).end("not found"));
69
+ return;
70
+ }
71
+ if (url.searchParams.has("dir") && url.pathname !== "/") {
72
+ res.writeHead(403).end("forbidden");
73
+ return;
74
+ }
75
+ const abs = safeJoin(VIEWER_DIST, url.pathname === "/" ? "index.html" : url.pathname);
76
+ if (!abs) {
77
+ res.writeHead(403).end("forbidden");
78
+ return;
79
+ }
80
+ await sendFile(res, abs).catch(() => sendFile(res, (0, node_path.join)(VIEWER_DIST, "index.html")).catch(() => res.writeHead(404).end("not found")));
81
+ }
82
+ function startStaticServer(opts = {}) {
83
+ return new Promise((resolvePromise, reject) => {
84
+ const server = (0, node_http.createServer)((req, res) => {
85
+ handle(req, res, server.address().port);
86
+ });
87
+ server.on("error", reject);
88
+ server.listen(opts.port ?? 0, "127.0.0.1", () => {
89
+ const port = server.address().port;
90
+ resolvePromise({
91
+ port,
92
+ url: `http://127.0.0.1:${port}`,
93
+ close: () => new Promise((done, fail) => server.close((e) => e ? fail(e) : done()))
94
+ });
95
+ });
96
+ });
97
+ }
98
+ //#endregion
99
+ exports.SERVER_API_VERSION = SERVER_API_VERSION;
100
+ exports.startStaticServer = startStaticServer;
@@ -0,0 +1,16 @@
1
+ export declare const SERVER_API_VERSION = 1;
2
+ export interface StaticServerOptions {
3
+ port?: number;
4
+ }
5
+ export interface StaticServer {
6
+ port: number;
7
+ url: string;
8
+ close(): Promise<void>;
9
+ }
10
+ export interface ServerDescriptor {
11
+ app: 'brepjs-cad-viewer';
12
+ port: number;
13
+ dynamicRoot: true;
14
+ serverApiVersion: number;
15
+ }
16
+ export declare function startStaticServer(opts?: StaticServerOptions): Promise<StaticServer>;
@@ -0,0 +1,98 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { createReadStream } from "node:fs";
3
+ import { dirname, extname, join, normalize, resolve, sep } from "node:path";
4
+ import { createServer } from "node:http";
5
+ import { stat } from "node:fs/promises";
6
+ //#region src/snapshot/static.ts
7
+ var SERVER_API_VERSION = 1;
8
+ var VIEWER_DIST = resolve(dirname(fileURLToPath(import.meta.url)), "../../viewer/dist");
9
+ var MIME = {
10
+ ".html": "text/html; charset=utf-8",
11
+ ".js": "text/javascript; charset=utf-8",
12
+ ".mjs": "text/javascript; charset=utf-8",
13
+ ".css": "text/css; charset=utf-8",
14
+ ".json": "application/json; charset=utf-8",
15
+ ".wasm": "application/wasm",
16
+ ".png": "image/png",
17
+ ".svg": "image/svg+xml",
18
+ ".glb": "model/gltf-binary",
19
+ ".gltf": "model/gltf+json",
20
+ ".step": "application/step",
21
+ ".stp": "application/step",
22
+ ".stl": "model/stl",
23
+ ".obj": "text/plain; charset=utf-8"
24
+ };
25
+ var mimeFor = (p) => MIME[extname(p).toLowerCase()] ?? "application/octet-stream";
26
+ async function sendFile(res, absPath) {
27
+ const info = await stat(absPath);
28
+ if (!info.isFile()) {
29
+ res.writeHead(404).end("not found");
30
+ return;
31
+ }
32
+ res.writeHead(200, {
33
+ "content-type": mimeFor(absPath),
34
+ "content-length": info.size
35
+ });
36
+ createReadStream(absPath).pipe(res);
37
+ }
38
+ function safeJoin(root, rel) {
39
+ const abs = resolve(root, normalize(decodeURIComponent(rel.replace(/^\/+/, ""))));
40
+ if (abs !== root && !abs.startsWith(root + sep)) return null;
41
+ return abs;
42
+ }
43
+ async function handle(req, res, port) {
44
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
45
+ if (url.pathname === "/__cad/server") {
46
+ const d = {
47
+ app: "brepjs-cad-viewer",
48
+ port,
49
+ dynamicRoot: true,
50
+ serverApiVersion: 1
51
+ };
52
+ res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
53
+ res.end(JSON.stringify(d));
54
+ return;
55
+ }
56
+ if (url.pathname.startsWith("/__model/")) {
57
+ const root = url.searchParams.get("dir");
58
+ if (!root) {
59
+ res.writeHead(400).end("missing dir");
60
+ return;
61
+ }
62
+ const abs = safeJoin(resolve(root), url.pathname.slice(8));
63
+ if (!abs) {
64
+ res.writeHead(403).end("forbidden");
65
+ return;
66
+ }
67
+ await sendFile(res, abs).catch(() => res.writeHead(404).end("not found"));
68
+ return;
69
+ }
70
+ if (url.searchParams.has("dir") && url.pathname !== "/") {
71
+ res.writeHead(403).end("forbidden");
72
+ return;
73
+ }
74
+ const abs = safeJoin(VIEWER_DIST, url.pathname === "/" ? "index.html" : url.pathname);
75
+ if (!abs) {
76
+ res.writeHead(403).end("forbidden");
77
+ return;
78
+ }
79
+ await sendFile(res, abs).catch(() => sendFile(res, join(VIEWER_DIST, "index.html")).catch(() => res.writeHead(404).end("not found")));
80
+ }
81
+ function startStaticServer(opts = {}) {
82
+ return new Promise((resolvePromise, reject) => {
83
+ const server = createServer((req, res) => {
84
+ handle(req, res, server.address().port);
85
+ });
86
+ server.on("error", reject);
87
+ server.listen(opts.port ?? 0, "127.0.0.1", () => {
88
+ const port = server.address().port;
89
+ resolvePromise({
90
+ port,
91
+ url: `http://127.0.0.1:${port}`,
92
+ close: () => new Promise((done, fail) => server.close((e) => e ? fail(e) : done()))
93
+ });
94
+ });
95
+ });
96
+ }
97
+ //#endregion
98
+ export { SERVER_API_VERSION, startStaticServer };
@@ -0,0 +1,4 @@
1
+ import type * as Brep from 'brepjs';
2
+ export type BrepNs = typeof Brep;
3
+ export declare function toolDir(): string;
4
+ export declare function loadBrep(): Promise<BrepNs>;
@@ -0,0 +1,4 @@
1
+ import { AnyShape } from 'brepjs';
2
+ import { BrepNs } from './brepjsRuntime.js';
3
+ import { VerifyReport } from './report.js';
4
+ export declare function runChecks(brep: BrepNs, shape: AnyShape): VerifyReport;
@@ -0,0 +1,2 @@
1
+ import { DiffReport } from './report.js';
2
+ export declare function runDiff(aPath: string, bPath: string): Promise<DiffReport>;
@@ -0,0 +1,26 @@
1
+ import { VerifyAssertion, VerifyMeasurements } from './report.js';
2
+ export interface ExpectedBounds {
3
+ xMin?: number;
4
+ xMax?: number;
5
+ yMin?: number;
6
+ yMax?: number;
7
+ zMin?: number;
8
+ zMax?: number;
9
+ }
10
+ /** Asserted dimensions a part may `export const expected` to gate its own verification. */
11
+ export interface ExpectedDims {
12
+ volume?: number;
13
+ area?: number;
14
+ bounds?: ExpectedBounds;
15
+ /** Allowed percent deviation for each numeric comparison; defaults to 0.5%. */
16
+ tolerancePct?: number;
17
+ }
18
+ export declare const DEFAULT_TOLERANCE_PCT = 0.5;
19
+ /** Percent deviation of `actual` from `expected`; 0 expected matches only 0 actual. */
20
+ export declare function pctDelta(actual: number, expected: number): number;
21
+ export declare function isExpectedDims(v: unknown): v is ExpectedDims;
22
+ /**
23
+ * Compare measured dimensions against a part's `expected` export. Each declared field becomes one
24
+ * assertion; a missing measurement for a declared expectation is a failing assertion (`actual:null`).
25
+ */
26
+ export declare function evaluateExpected(expected: ExpectedDims, measurements: VerifyMeasurements): VerifyAssertion[];
@@ -0,0 +1,6 @@
1
+ export interface MeasureReport {
2
+ length?: number;
3
+ distance?: number;
4
+ errors: string[];
5
+ }
6
+ export declare function runMeasure(aPath: string, bPath?: string): Promise<MeasureReport>;