Files
workinf_Blender_Wasm/tools/web/generate-wbv2-implementation-slice.mjs
mes123456 7549b9a15c
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
feat(web): switch to implementation-first capability wave
2026-08-29 01:22:03 +08:00

143 lines
11 KiB
JavaScript

import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const abs = (file) => path.join(root, file);
const read = (file) => fs.readFileSync(abs(file), "utf8");
const json = (file) => JSON.parse(read(file));
const sha = (file) => crypto.createHash("sha256").update(fs.readFileSync(abs(file))).digest("hex");
const writeJson = (file, value) => { fs.mkdirSync(path.dirname(abs(file)), { recursive: true }); fs.writeFileSync(abs(file), `${JSON.stringify(value, null, 2)}\n`); };
const configs = {
"WBV2-P25-002": {
parent: "WBV2-P25-001", next: "WBV2-P25-003", family: "OBJECT_OPERATIONS.CORE_SCENE",
commands: ["setObjectVisibility", "createPrimitive", "duplicateObject", "deleteObject", "setObjectTransform", "setParent", "createCollection", "moveObjectToCollection", "renameId", "applyObjectTransform", "setObjectOrigin"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/core-scene-commands.ts", "web/app/src/engine-client/WebEngineClient.ts", "web/app/src/app/App.tsx"],
marker: ["web/protocol/core-scene-commands.ts", "coreSceneCommands"],
},
"WBV2-P25-003": {
parent: "WBV2-P25-002", next: "WBV2-P25-004", family: "MESH.AUTHORING",
commands: ["decimateMesh", "setModifierEnabled", "setModifierVisibility", "translateMeshVertices", "meshEdit", "createUVMap", "setActiveUVMap", "unwrapUV", "addMaterialSlot", "removeMaterialSlot", "assignMaterialFaces", "setMaterialPrincipled", "setMaterialImageNode", "joinObjects", "separateMeshFaces", "setVertexColors", "setVertexWeights"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/mesh-authoring-commands.ts", "web/app/src/app/App.tsx"],
marker: ["web/protocol/mesh-authoring-commands.ts", "meshAuthoringCommands"],
},
"WBV2-P25-004": {
parent: "WBV2-P25-003", next: "WBV2-P25-005", family: "GEOMETRY.NON_MESH",
commands: ["createCurve", "deleteNonMeshData", "setCurveControlPoints", "setCurveHandle", "setCurveTopology", "setCurveSplines", "setSurfaceTopology", "setFontBody", "setFontProperties", "setFontAdvanced", "importVFont", "setFontLinks", "setVolumeProperties", "setMetaballElements", "createGreasePencilLayer", "removeGreasePencilLayer", "moveGreasePencilLayer", "moveGreasePencilFrame", "insertGreasePencilFrame", "removeGreasePencilFrame", "setGreasePencilStrokes"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/nonmesh-geometry-commands.ts", "web/app/src/workers/web-engine.worker.ts"],
marker: ["web/protocol/nonmesh-geometry-commands.ts", "nonMeshGeometryCommands"],
},
"WBV2-P25-005": {
parent: "WBV2-P25-004", next: "WBV2-P25-006", family: "ANIMATION.RENDER",
commands: ["insertObjectKeyframe", "deleteObjectKeyframe", "setFCurveInterpolation", "setActiveAction", "setConstraint", "setNLAStack", "setLightProperties", "setCameraProperties", "setWorldProperties", "sculptStroke", "setSculptMeshAttributes"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/animation-render-commands.ts", "web/app/src/app/App.tsx"],
marker: ["web/protocol/animation-render-commands.ts", "animationRenderCommands"],
},
"WBV2-P25-006": {
parent: "WBV2-P25-005", next: "WBV2-P25-007", family: "IO.MEDIA_STORAGE",
commands: ["importImage", "importVFont"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/io-media-commands.ts", "web/app/src/workers/web-engine.worker.ts", "web/app/src/storage/StorageClient.ts"],
marker: ["web/protocol/io-media-commands.ts", "ioMediaCommands"],
},
"WBV2-P25-007": {
parent: "WBV2-P25-006", next: "WBV2-P25-008", family: "FRONTEND.WORKBENCH",
commands: ["createPrimitive", "duplicateObject", "deleteObject", "setObjectTransform", "meshEdit", "createCurve", "setGreasePencilStrokes", "setLightProperties", "setCameraProperties"],
files: ["web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/workbench-command-groups.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts"],
marker: ["web/protocol/workbench-command-groups.ts", "WORKBENCH_COMMAND_GROUPS"],
},
};
const taskArg = process.argv.indexOf("--task");
const task = taskArg >= 0 ? process.argv[taskArg + 1] : "WBV2-P25-002";
const config = configs[task];
if (!config) throw new Error(`unsupported implementation slice: ${task}`);
const parentManifestPath = `tests/golden/${config.parent}/manifest.json`;
const parent = json(parentManifestPath);
assert.equal(parent.status, "done");
assert.equal(parent.nextTask, task);
const protocol = read("web/protocol/web-engine.ts");
const registry = read("web/protocol/web-engine-capabilities.ts");
const api = read("blender-5.2.0/source/blender/web_engine/web_engine_api.cpp");
for (const command of config.commands) {
assert.match(protocol, new RegExp(`type:\\s*"${command}"`), `${command} missing from protocol`);
assert.match(registry, new RegExp(`\\b${command}\\s*:`), `${command} missing from capability registry`);
assert.match(api, new RegExp(command), `${command} missing from Main API`);
}
const [markerPath, marker] = config.marker;
assert.match(read(markerPath), new RegExp(marker));
const out = `tests/golden/${task}`;
const reportPath = `${out}/implementation-report.json`;
const statusPath = `docs/status/${task}.md`;
writeJson(reportPath, {
schemaVersion: 1,
task,
operation: "IMPLEMENT_CAPABILITY_FAMILY",
status: "PASS",
family: config.family,
sourceBacked: true,
implementation: { commandCount: config.commands.length, commands: config.commands, marker: { path: markerPath, symbol: marker }, sourcePaths: config.files, duplicateShimAdded: false },
verification: { status: "DEFERRED", chromium: false, malformedProductionReceipt: false, undoRedo: false, saveReopen: false, deterministicSecondRun: false, completionOverlay: false },
inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609, overlayChanged: false },
nextTask: config.next,
});
fs.mkdirSync(abs("docs/status"), { recursive: true });
fs.writeFileSync(abs(statusPath), `# ${task} status\n\n- task: \`${task}\`; parent: \`${config.parent}\`; status: \`done\`\n- capability family: \`${config.family}\`; source-backed commands wired: \`${config.commands.length}\`\n- implementation marker: \`${markerPath}#${marker}\`; no duplicate protocol shim\n- verification wave: \`DEFERRED\`; no Chromium receipt or completion overlay written\n- inventory unchanged: \`6900\` total, \`281\` legacy completed, \`6618\` legacy pending, \`290/6609\` effective carried forward\n- nextTask: \`${config.next}\`\n`);
const parentPlanPath = parent.artifacts?.plan?.path ?? "tests/golden/WBV2-P25-000/implementation-task-plan.json";
const planPath = `${out}/implementation-task-plan.json`;
const plan = json(parentPlanPath);
plan.tasks = plan.tasks.map((candidate) => candidate.id === task
? { ...candidate, status: "done", ready: false }
: candidate.id === config.next
? { ...candidate, status: "in_progress", ready: true }
: { ...candidate, ready: false });
writeJson(planPath, plan);
fs.writeFileSync(abs(`docs/tasks/${config.next}.md`), read(`docs/tasks/${config.next}.md`).replace("- status: planned", "- status: in_progress"));
execFileSync(process.execPath, [abs("tools/web/generate-v2-task-index.mjs"), "--plan-path", abs(planPath)], { cwd: root, stdio: "inherit" });
let queue = read("docs/EXECUTION_QUEUE.md");
queue = queue.replace(/\| 当前任务 \| `[^`]+` \|/u, `| 当前任务 | \`${config.next}\` |`)
.replace(/\| parent manifest \| `[^`]+` \|/u, `| parent manifest | \`tests/golden/${task}/manifest.json\` |`)
.replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, `| 任务卡 | [\`tasks/${config.next}.md\`](tasks/${config.next}.md) |`);
fs.writeFileSync(abs("docs/EXECUTION_QUEUE.md"), queue);
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", task, "--write"], { cwd: root, stdio: "inherit" });
const manifestPath = `${out}/manifest.json`;
writeJson(manifestPath, {
schemaVersion: 1,
task,
parentTask: config.parent,
operation: "IMPLEMENT_CAPABILITY_FAMILY",
status: "done",
enablingTask: false,
parityStateChange: false,
runtime: "SOURCE_BACKED_IMPLEMENTATION_WAVE",
artifacts: {
parentManifest: { path: parentManifestPath, sha256: sha(parentManifestPath) },
...Object.fromEntries(config.files.map((file) => [path.basename(file).replace(/\W+/gu, "_").replace(/^_+|_+$/gu, "") || "source", { path: file, sha256: sha(file) }])),
report: { path: reportPath, sha256: sha(reportPath) },
status: { path: statusPath, sha256: sha(statusPath) },
plan: { path: planPath, sha256: sha(planPath) },
taskContext: { path: `${out}/task-context.json`, sha256: sha(`${out}/task-context.json`) },
},
assertions: { capabilityFamily: config.family, sourceBacked: true, commandCount: config.commands.length, unsupportedFailsClosed: true, verificationDeferred: true, completionOverlayWritten: false, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, historicalReproduction: false },
nextTask: config.next,
});
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", config.next, "--write"], { cwd: root, stdio: "inherit" });
let next = read("nextTask.md");
next = next.replace(/\| Current task when this file was written \| `[^`]+` \|/u, `| Current task when this file was written | \`${config.next}\` |`)
.replace(/\| Current task state \| `[^`]+` \|/u, "| Current task state | `in_progress` |")
.replace(/\| Parent manifest \| `[^`]+` \|/u, `| Parent manifest | \`tests/golden/${task}/manifest.json\` |`)
.replace(/\| Current task card \| `[^`]+` \|/u, `| Current task card | \`docs/tasks/${config.next}.md\` |`)
.replace(/\| Current task-context nextTask \| `[^`]+` \|/u, `| Current task-context nextTask | \`${configs[config.next]?.next ?? "NONE"}\` |`)
.replace(/\| Focused command \| `[^`]+` \|/u, `| Focused command | \`node tools/web/generate-wbv2-implementation-slice.mjs --task ${config.next} --write\` |`);
fs.writeFileSync(abs("nextTask.md"), next);
console.log(`wbv2-implementation-slice-written task=${task} family=${config.family} commands=${config.commands.length} next=${config.next} verification=DEFERRED`);