- Enhanced `nss-fasteners-pb-import.json` to compute `selling_price` as `cost_price * (1 + 80%)` and included `markup` in the records. - Updated `nss-fasteners-pb-transform.ts` to allow configurable CSV and output file names via environment variables, improving flexibility for different product runs. - Added support for uploading photos in the repair detail page, including validation for file types and sizes, and implemented a dialog for creating customer quotes linked to repairs. - Introduced a new `customer_quote` field in the `Repair` type to link generated quotes back to their respective repairs. These changes streamline the pricing process and enhance the user experience in managing repairs and quotes.
81 lines
2.4 KiB
JavaScript
81 lines
2.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Pull the CSV from the headless browser via CDP and assemble it on disk.
|
|
*
|
|
* Usage:
|
|
* tsx scripts/_dump-browser-csv.ts <viewId> <expression> <out-path>
|
|
*
|
|
* Example:
|
|
* tsx scripts/_dump-browser-csv.ts stable-browser-session/xxx 'window.__hbCsv' scripts/nss-hex-bolts.csv
|
|
*/
|
|
import { writeFileSync } from "node:fs";
|
|
|
|
const [, , viewIdExpr, expression, outPath] = process.argv;
|
|
if (!viewIdExpr || !expression || !outPath) {
|
|
console.error("Usage: tsx _dump-browser-csv.ts <viewId> <expr> <out>");
|
|
process.exit(1);
|
|
}
|
|
|
|
const port = 9222;
|
|
const host = "127.0.0.1";
|
|
|
|
// Discover the page WebSocket URL.
|
|
async function getWsUrl(): Promise<string> {
|
|
const r = await fetch(`http://${host}:${port}/json`);
|
|
const tabs = (await r.json()) as Array<{ id: string; webSocketDebuggerUrl: string }>;
|
|
const target = tabs.find((t) => t.id.startsWith(viewIdExpr));
|
|
if (!target) throw new Error(`tab not found for viewId prefix ${viewIdExpr}`);
|
|
return target.webSocketDebuggerUrl;
|
|
}
|
|
|
|
async function evalChunked(ws: WebSocket, expression: string): Promise<string> {
|
|
const CHUNK = 6000;
|
|
const full = await evalOnce(ws, expression);
|
|
const out: string[] = [];
|
|
for (let i = 0; i < full.length; i += CHUNK) {
|
|
out.push(full.substring(i, i + CHUNK));
|
|
}
|
|
return out.join("");
|
|
}
|
|
|
|
function evalOnce(
|
|
ws: WebSocket,
|
|
expression: string,
|
|
): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
const id = Math.floor(Math.random() * 1e9);
|
|
const onMsg = (ev: MessageEvent) => {
|
|
const msg = JSON.parse(ev.data);
|
|
if (msg.id !== id) return;
|
|
ws.removeEventListener("message", onMsg);
|
|
if (msg.error) reject(new Error(msg.error.message));
|
|
else resolve(msg.result?.value ?? "");
|
|
};
|
|
ws.addEventListener("message", onMsg);
|
|
ws.send(
|
|
JSON.stringify({
|
|
id,
|
|
method: "Runtime.evaluate",
|
|
params: { expression, returnByValue: true },
|
|
}),
|
|
);
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const wsUrl = await getWsUrl();
|
|
const ws = new WebSocket(wsUrl);
|
|
await new Promise<void>((resolve, reject) => {
|
|
ws.addEventListener("open", () => resolve());
|
|
ws.addEventListener("error", (e) => reject(new Error(String(e))));
|
|
});
|
|
const csv = await evalChunked(ws, expression);
|
|
writeFileSync(outPath, csv);
|
|
console.log(`Wrote ${csv.length} bytes to ${outPath}`);
|
|
ws.close();
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error("FATAL:", e);
|
|
process.exit(1);
|
|
});
|