Files
Aislo/resources/tester/spreadsheet/helper_spreadsheet_d_bundle.cjs
T
eomsangdonandClaude Opus 5.5 a2011c8035 feat(spreadsheet): D 입력 — 고름 · 칸 편집기 · 한글 조합 · 수식 입력줄 · 참조 가리키기 · 단축키 · 채우기 끌기 · 잇기
- spreadsheet.ts — createSpreadsheet 잇기(문맥 · 명령 → 되돌림 → 엔진 → 격자 · onChange · onSelect · 시트마다 고름 기억) · parts 한 곳(시험 틀이 빈 몸 부품을 바꿔 끼움)
- spreadsheet_selection.ts — 병합으로 늘림 · Shift/Ctrl 방향 · 숨김 건너뜀 · 행열 머리 · 고름 안 Enter/Tab 돌기
- spreadsheet_editor.ts — 숨은 textarea 늘 초점 · compositionstart/input 로 편집 시작 · enter/edit 방식 · 수식 입력줄 · 주소 상자 · 참조 넣기 · 색 테두리 · 입력 글 규칙(' · % · 천 단위)
- spreadsheet_keys.ts · spreadsheet_mouse.ts — 엑셀 단축키 · 끌기 고르기 · 채우기 핸들(늘림 · 줄여 지움 · 두 번 누르면 아래로) · 우클릭 자리
- 시험 resources/tester/spreadsheet — 규칙 7 묶음 · 가짜 부품 번들 시험 틀 html(ORCA)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6KXAabDTenEU7hrDQmCKY
2026-09-27 20:22:02 +09:00

128 lines
4.8 KiB
JavaScript

/* 스프레드시트 D 시험 번들 도우미 — TS 를 그 자리에서 CommonJS 로 풀어 모음.
* 남의 몫 파일이 아직 빈 몸(「아직 없음」)이면 spreadsheet_d_fakes.cjs 의 가짜로 바꿔 끼움.
* load(entry) Node 안에서 모듈을 돌려 exports 를 줌(pytest 도우미가 씀)
* node 이 파일 harness 브라우저 시험 틀 번들 → tmp/spreadsheet_d/harness.js (git 밖)
* `.cjs` = 루트 package.json 의 "type":"module" 과 무관하게 CommonJS. */
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..", "..", "..");
const ts = require(path.join(ROOT, "config", "node_modules", "typescript"));
const SS = path.join(ROOT, "A00_Common", "spreadsheet");
const FAKES = path.join(__dirname, "spreadsheet_d_fakes.cjs");
const isStub = (file) => fs.readFileSync(file, "utf8").includes("아직 없음(");
/** 가져오기 글 → 모듈 id(파일 경로 · `fake:<이름>` · `css:<경로>`) */
function resolve(spec, from) {
if (spec === "@ui/ui_template_elements") return "fake:el";
let abs;
if (spec.startsWith("@ui/")) abs = path.join(ROOT, "ui_template", spec.slice(4));
else if (spec.startsWith(".")) abs = path.resolve(path.dirname(from), spec);
else throw new Error(`모르는 가져오기: ${spec} (${from})`);
if (/\.(css|json|cjs)$/.test(abs)) return (abs.endsWith(".css") ? "css:" : "") + abs;
if (!abs.endsWith(".ts")) abs += ".ts";
if (path.dirname(abs) === SS && isStub(abs))
return "fake:" + path.basename(abs, ".ts").replace(/^spreadsheet_/, "");
return abs;
}
/** 입구부터 딸린 모듈 전부 → id → {code, deps} */
function collect(entry, stubs = []) {
const mods = new Map();
const walk = (id) => {
if (mods.has(id)) return;
let code;
if (id.startsWith("fake:")) {
const name = id.slice(5);
if (name !== "el") stubs.push(name);
code = `module.exports = require("${FAKES.replace(/\\/g, "/")}")[${JSON.stringify(name)}];`;
mods.set(id, { code, deps: { [FAKES.replace(/\\/g, "/")]: FAKES } });
walk(FAKES);
return;
}
if (id.startsWith("css:")) {
const css = fs.readFileSync(id.slice(4), "utf8");
code = `if (typeof document !== "undefined") { const s = document.createElement("style"); s.textContent = ${JSON.stringify(css)}; document.head.append(s); }`;
mods.set(id, { code, deps: {} });
return;
}
const src = fs.readFileSync(id, "utf8");
if (id.endsWith(".json")) code = `module.exports = ${src};`;
else if (id.endsWith(".ts"))
code = ts.transpileModule(src, {
fileName: id,
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
}).outputText;
else code = src;
const deps = {};
mods.set(id, { code, deps });
for (const m of code.matchAll(/require\("([^"]+)"\)/g)) {
const dep = m[1] === FAKES.replace(/\\/g, "/") ? FAKES : resolve(m[1], id);
deps[m[1]] = dep;
walk(dep);
}
};
walk(entry);
return mods;
}
/** Node 안에서 돌림 */
function load(entry) {
const abs = path.resolve(entry);
const id = resolve("./" + path.basename(abs).replace(/\.ts$/, ""), abs); // 빈 몸이면 가짜
const mods = collect(id);
const cache = new Map();
const req = (id) => {
if (cache.has(id)) return cache.get(id).exports;
const m = mods.get(id);
const module = { exports: {} };
cache.set(id, module);
new Function("exports", "module", "require", m.code)(module.exports, module, (s) =>
req(m.deps[s]),
);
return module.exports;
};
return req(id);
}
/** 브라우저 한 파일 */
function bundle(entry, out) {
const stubs = [];
const mods = collect(path.resolve(entry), stubs);
const ids = [...mods.keys()];
const defs = ids
.map((id, i) => {
const m = mods.get(id);
const map = Object.fromEntries(Object.entries(m.deps).map(([s, d]) => [s, ids.indexOf(d)]));
return `[${JSON.stringify(map)}, function (exports, module, require) {\n${m.code}\n}]`;
})
.join(",\n");
const text = `(function () {
const defs = [${defs}];
const cache = [];
function req(i) {
if (cache[i]) return cache[i].exports;
const module = (cache[i] = { exports: {} });
defs[i][1](module.exports, module, (s) => req(defs[i][0][s]));
return module.exports;
}
window.__ssFakes = ${JSON.stringify([...new Set(stubs)])};
req(${ids.indexOf(path.resolve(entry))});
})();\n`;
fs.mkdirSync(path.dirname(out), { recursive: true });
fs.writeFileSync(out, text);
return { out, modules: ids.length, fakes: [...new Set(stubs)] };
}
module.exports = { load, bundle, ROOT, SS };
if (require.main === module && process.argv[2] === "harness") {
const r = bundle(
path.join(__dirname, "harness_spreadsheet_d.cjs"),
path.join(ROOT, "tmp", "spreadsheet_d", "harness.js"),
);
console.log(JSON.stringify(r));
}