/* 623 헬퍼 — 서버가 새로 만든 관 옵션(`_apply_new_defaults`)을 공용 명세 폼 `write → read` 에 돌리고 * 같은 종류 병합(`mergeFacilityOptions`)까지 — 폼을 처음 거칠 때 파일이 바뀌는 칸(지워짐 · 더함 · 바뀜). * 입력 = 표준입력 JSON(옵션 목록) · 출력 = 표준출력 JSON 목록 · 짝 = test_623_inlet_defaults.py. * 가짜 DOM 틀 = helper_173_7_inlet_animal.cjs. */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); class FakeNode { constructor(tag) { this.tagName = tag.toUpperCase(); this.children = []; this.dataset = {}; this.style = {}; this._classes = new Set(); this._on = {}; this._text = ""; this.value = ""; const self = this; this.classList = { add: (...n) => n.forEach((c) => self._classes.add(c)), remove: (...n) => n.forEach((c) => self._classes.delete(c)), toggle: (c, f) => (f ?? !self._classes.has(c)) ? self._classes.add(c) : self._classes.delete(c), contains: (c) => self._classes.has(c), }; } set className(v) { this._classes = new Set(String(v).split(/\s+/).filter(Boolean)); } set textContent(v) { this._text = String(v); this.children = []; } get textContent() { return this._text + this.children.map((c) => c.textContent ?? "").join(""); } append(...kids) { kids.forEach((k) => this.children.push(typeof k === "string" ? { textContent: k } : k)); } replaceChildren(...kids) { this.children = []; this.append(...kids); } add(kid) { this.children.push(kid); } setAttribute(n, v) { this[n] = v; } addEventListener(type, fn) { (this._on[type] ??= []).push(fn); } fire(type) { (this._on[type] ?? []).forEach((fn) => fn({ target: this })); } closest() { return null; } matches(sel) { return sel.split(",").some((s) => { s = s.trim(); return s.startsWith(".") ? this._classes.has(s.slice(1)) : this.tagName === s.toUpperCase(); }); } querySelectorAll(sel) { const found = []; const walk = (n) => (n.children ?? []).forEach((k) => { if (k.matches?.(sel)) found.push(k); walk(k); }); walk(this); return found; } querySelector(sel) { return this.querySelectorAll(sel)[0] ?? null; } } globalThis.document = { createElement: (t) => new FakeNode(t) }; globalThis.queueMicrotask = (fn) => fn(); globalThis.Option = function (text, value) { const o = new FakeNode("option"); o.textContent = text; o.value = value; return o; }; const STUBS = { "@ui/ui_template_elements": { showToast: () => {} }, "@config/config_frontend": { FORD_MANNING_N: 0.03, FORD_SLOPE: 0.02 }, "./ui_template_checkbox.css": {}, "../../B05_Profile/B05_Profile_UI_Structures_Summary_Row": { buildSummaryRow: () => null }, }; const { loadTs } = makeLoader({ root: ROOT, requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (STUBS[request]) return STUBS[request]; if (request.startsWith("@util/")) return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`)); return loadTs(path.join(path.dirname(full), `${request}.ts`)); }, }); const { createSpecForm } = loadTs( path.join(ROOT, "A00_Common", "design", "design_structure_form.ts"), ); const { PIPE_SPEC } = loadTs( path.join(ROOT, "A00_Common", "design", "logic", "design_structure_pipe.ts"), ); const { mergeFacilityOptions } = loadTs( path.join(ROOT, "B05_Profile", "logic", "B05_Profile_UI_Drainage_Facility_Merge.ts"), ); const same = (a, b) => JSON.stringify(a) === JSON.stringify(b); const inputs = JSON.parse(fs.readFileSync(0, "utf-8")); const out = inputs.map((options) => { const form = createSpecForm(null, {}); form.write(PIPE_SPEC, options); const merged = mergeFacilityOptions({ facility: "pipe", options }, { facility: "pipe", options: form.read() }) .options ?? {}; return { removed: Object.keys(options).filter((k) => !(k in merged)), added: Object.keys(merged).filter((k) => !(k in options)), changed: Object.keys(options).filter((k) => k in merged && !same(options[k], merged[k])), }; }); process.stdout.write(JSON.stringify(out));