Files
tutti/tools/scripts/generate-workbench-go-contract.mjs
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

306 lines
9.9 KiB
JavaScript

import { execFileSync } from "node:child_process";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(__dirname, "../..");
const schemaPath = resolve(
repoRoot,
"packages/workbench/snapshot/src/schema.json"
);
const limitsPath = resolve(
repoRoot,
"packages/workbench/snapshot/src/limits.ts"
);
const outputPath = resolve(
repoRoot,
"packages/workbench/service/workbench_snapshot_contract.gen.go"
);
const checkOnly = process.argv.includes("--check");
const schema = JSON.parse(readFileSync(schemaPath, "utf8"));
const limits = parseLimits(readFileSync(limitsPath, "utf8"));
const nextSource = formatGoSource(renderGoContract(schema, limits));
const currentSource = readExistingText(outputPath);
if (checkOnly) {
if (currentSource !== nextSource) {
throw new Error(
"Go workbench snapshot contract is out of sync with packages/workbench/snapshot sources. Run pnpm sync:workbench-go-contract."
);
}
} else if (currentSource !== nextSource) {
writeFileSync(outputPath, nextSource, "utf8");
}
function renderGoContract(schema, limits) {
const schemaVersion = readRequiredNumber(
schema.properties?.schemaVersion?.const,
"schemaVersion"
);
const maxNodes = readRequiredNumber(
schema.properties?.nodes?.maxItems,
"nodes.maxItems"
);
const maxNodeIDLength = readRequiredNumber(
schema.$defs?.node?.properties?.id?.maxLength,
"$defs.node.properties.id.maxLength"
);
const maxKindLength = readRequiredNumber(
schema.$defs?.node?.properties?.kind?.maxLength,
"$defs.node.properties.kind.maxLength"
);
const maxTitleLength = readRequiredNumber(
schema.$defs?.node?.properties?.title?.maxLength,
"$defs.node.properties.title.maxLength"
);
const minFrameWidth = readRequiredNumber(
schema.$defs?.frame?.properties?.width?.minimum,
"$defs.frame.properties.width.minimum"
);
const minFrameHeight = readRequiredNumber(
schema.$defs?.frame?.properties?.height?.minimum,
"$defs.frame.properties.height.minimum"
);
const displayModes = readRequiredStringArray(
schema.$defs?.node?.properties?.displayMode?.enum,
"$defs.node.properties.displayMode.enum"
);
const layoutPresetKinds = readRequiredStringArray(
schema.$defs?.layoutPreset?.properties?.kind?.enum,
"$defs.layoutPreset.properties.kind.enum"
);
const maxSerializedBytes = readRequiredNumber(
limits.maxSerializedBytes,
"workbenchSnapshotLimits.maxSerializedBytes"
);
const snapshotStruct = renderSnapshotStruct();
const nodeStruct = renderNodeStruct();
const spaceStruct = renderSpaceStruct();
const frameStruct = renderFrameStruct();
const layoutBasisStructs = renderLayoutBasisStructs();
const lockedLayoutStructs = renderLockedLayoutStructs();
const displayModeType = renderDisplayModeType(displayModes);
const layoutPresetType = renderLayoutPresetType(layoutPresetKinds);
return `// Code generated by tools/scripts/generate-workbench-go-contract.mjs. DO NOT EDIT.
package workbenchservice
const (
\tworkbenchSnapshotContractSchemaVersion = ${schemaVersion}
\tworkbenchSnapshotContractMaxSerializedBytes = ${maxSerializedBytes}
\tworkbenchSnapshotContractMaxNodes = ${maxNodes}
\tworkbenchSnapshotContractMaxNodeIDLength = ${maxNodeIDLength}
\tworkbenchSnapshotContractMaxKindLength = ${maxKindLength}
\tworkbenchSnapshotContractMaxTitleLength = ${maxTitleLength}
\tworkbenchSnapshotContractMinFrameWidth = ${minFrameWidth}
\tworkbenchSnapshotContractMinFrameHeight = ${minFrameHeight}
)
${snapshotStruct}
${nodeStruct}
${spaceStruct}
${frameStruct}
${layoutBasisStructs}
${lockedLayoutStructs}
${displayModeType}
${layoutPresetType}
var workbenchSnapshotContractDisplayModes = map[WorkbenchSnapshotDisplayMode]struct{}{
${displayModes.map((value) => `\tWorkbenchSnapshotDisplayMode${toGoExport(value)}: {},`).join("\n")}
}
`;
}
function formatGoSource(source) {
return execFileSync("gofmt", { encoding: "utf8", input: source });
}
function renderSnapshotStruct() {
return `type WorkbenchSnapshot struct {
\tSchemaVersion int \`json:"schemaVersion"\`
\tNodes []WorkbenchSnapshotNode \`json:"nodes"\`
\tNodeStack *[]string \`json:"nodeStack,omitempty"\`
\tActiveNodeID *string \`json:"activeNodeId,omitempty"\`
\tSpaces *[]WorkbenchSnapshotSpace \`json:"spaces,omitempty"\`
\tActiveSpaceID *string \`json:"activeSpaceId,omitempty"\`
\tLayoutBasis *WorkbenchSnapshotLayoutBasis \`json:"layoutBasis,omitempty"\`
\tLockedLayout *WorkbenchSnapshotLockedLayout \`json:"lockedLayout,omitempty"\`
\tMetadata map[string]interface{} \`json:"metadata,omitempty"\`
}`;
}
function renderNodeStruct() {
return `type WorkbenchSnapshotNode struct {
\tID string \`json:"id"\`
\tKind string \`json:"kind"\`
\tTitle string \`json:"title"\`
\tFrame WorkbenchSnapshotFrame \`json:"frame"\`
\tDisplayMode *WorkbenchSnapshotDisplayMode \`json:"displayMode,omitempty"\`
\tRestoreFrame *WorkbenchSnapshotFrame \`json:"restoreFrame,omitempty"\`
\tIsMinimized *bool \`json:"isMinimized,omitempty"\`
\tMinimizedAtUnixMs *int64 \`json:"minimizedAtUnixMs,omitempty"\`
\tData interface{} \`json:"data,omitempty"\`
\tAdapterState map[string]interface{} \`json:"adapterState,omitempty"\`
}`;
}
function renderSpaceStruct() {
return `type WorkbenchSnapshotSpace struct {
\tID string \`json:"id"\`
\tName string \`json:"name"\`
\tNodeIDs []string \`json:"nodeIds"\`
\tFrame *WorkbenchSnapshotFrame \`json:"frame,omitempty"\`
\tData interface{} \`json:"data,omitempty"\`
}`;
}
function renderFrameStruct() {
return `type WorkbenchSnapshotFrame struct {
\tX float64 \`json:"x"\`
\tY float64 \`json:"y"\`
\tWidth float64 \`json:"width"\`
\tHeight float64 \`json:"height"\`
}`;
}
function renderLayoutBasisStructs() {
return `type WorkbenchSnapshotSize struct {
\tWidth float64 \`json:"width"\`
\tHeight float64 \`json:"height"\`
}
type WorkbenchSnapshotSafeArea struct {
\tTop float64 \`json:"top"\`
\tRight float64 \`json:"right"\`
\tBottom float64 \`json:"bottom"\`
\tLeft float64 \`json:"left"\`
}
type WorkbenchSnapshotLayoutConstraints struct {
\tMinWidth float64 \`json:"minWidth"\`
\tMinHeight float64 \`json:"minHeight"\`
\tSurfacePadding float64 \`json:"surfacePadding"\`
\tSafeArea WorkbenchSnapshotSafeArea \`json:"safeArea"\`
}
type WorkbenchSnapshotLayoutBasis struct {
\tSurfaceSize WorkbenchSnapshotSize \`json:"surfaceSize"\`
\tLayoutConstraints WorkbenchSnapshotLayoutConstraints \`json:"layoutConstraints"\`
}`;
}
function renderLockedLayoutStructs() {
return `type WorkbenchSnapshotNormalizedFrame struct {
\tX float64 \`json:"x"\`
\tY float64 \`json:"y"\`
\tWidth float64 \`json:"width"\`
\tHeight float64 \`json:"height"\`
}
type WorkbenchSnapshotLayoutPreset struct {
\tKind WorkbenchSnapshotLayoutPresetKind \`json:"kind"\`
}
type WorkbenchSnapshotLockedLayout struct {
\tPreset WorkbenchSnapshotLayoutPreset \`json:"preset"\`
\tNodeIDs []string \`json:"nodeIDs"\`
\tNormalizedFrames map[string]WorkbenchSnapshotNormalizedFrame \`json:"normalizedFrames,omitempty"\`
}`;
}
function renderDisplayModeType(displayModes) {
return `type WorkbenchSnapshotDisplayMode string
const (
${displayModes.map((value) => `\tWorkbenchSnapshotDisplayMode${toGoExport(value)} WorkbenchSnapshotDisplayMode = "${value}"`).join("\n")}
)`;
}
function renderLayoutPresetType(layoutPresetKinds) {
return `type WorkbenchSnapshotLayoutPresetKind string
const (
${layoutPresetKinds.map((value) => `\tWorkbenchSnapshotLayoutPresetKind${toGoExport(value)} WorkbenchSnapshotLayoutPresetKind = "${value}"`).join("\n")}
)
var workbenchSnapshotContractLayoutPresetKinds = map[WorkbenchSnapshotLayoutPresetKind]struct{}{
${layoutPresetKinds.map((value) => `\tWorkbenchSnapshotLayoutPresetKind${toGoExport(value)}: {},`).join("\n")}
}`;
}
function parseLimits(source) {
const objectMatch = source.match(
/workbenchSnapshotLimits\s*=\s*\{(?<body>[\s\S]*?)\}\s*as const/
);
if (!objectMatch?.groups?.body) {
throw new Error(
`Unable to locate workbenchSnapshotLimits in ${limitsPath}`
);
}
const limits = {};
for (const match of objectMatch.groups.body.matchAll(
/(?<key>[A-Za-z0-9_]+)\s*:\s*(?<value>[^,\n]+),?/g
)) {
const key = match.groups?.key;
const value = match.groups?.value;
if (!key || !value) {
continue;
}
limits[key] = evaluateNumericExpression(value);
}
return limits;
}
function evaluateNumericExpression(source) {
const expression = source.replaceAll("_", "").trim();
if (!/^[0-9+\-*/ ().]+$/.test(expression)) {
throw new Error(`Unsupported numeric expression: ${source}`);
}
return Function(`"use strict"; return (${expression});`)();
}
function readRequiredNumber(value, path) {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new Error(`Expected finite number at ${path}`);
}
return value;
}
function readRequiredStringArray(value, path) {
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
throw new Error(`Expected string array at ${path}`);
}
return value;
}
function readExistingText(path) {
if (!existsSync(path)) {
return "";
}
return readFileSync(path, "utf8");
}
function toGoExport(value) {
return value
.split(/[^A-Za-z0-9]+/)
.filter(Boolean)
.map((segment) => segment[0].toUpperCase() + segment.slice(1))
.join("");
}