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
306 lines
9.9 KiB
JavaScript
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("");
|
|
}
|