Graduate Electron runtime to EveElectron
This commit is contained in:
parent
29515102c0
commit
3baf125b38
26 changed files with 141 additions and 2218 deletions
|
|
@ -1,116 +0,0 @@
|
|||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { EveElectronShell } from "../../runtimes/incubating/eve-electron/src/eve-electron-shell.mjs";
|
||||
import { buildElectronCaptureRequest, loadJsonFile, writeCaptureRequest } from "./eveelectron-capture-contract.mjs";
|
||||
|
||||
export function buildElectronCaptureArtifact({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
surfaceDocument,
|
||||
advertisementPath = "",
|
||||
capabilityManifestPath = "",
|
||||
captureSurfaceId = "",
|
||||
stamp = "latest",
|
||||
} = {}) {
|
||||
const request = buildElectronCaptureRequest({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
advertisementPath,
|
||||
capabilityManifestPath,
|
||||
captureSurfaceId,
|
||||
stamp,
|
||||
});
|
||||
const shell = new EveElectronShell();
|
||||
const projection = shell.lowerSurface(surfaceDocument, advertisement, request.surfaceId);
|
||||
validateProjectionArtifact(projection, request);
|
||||
|
||||
return {
|
||||
request,
|
||||
projection: {
|
||||
...projection,
|
||||
runtimeId: request.runtimeId,
|
||||
artifactKind: "json-projection",
|
||||
captureKind: "electron-shell-projection-json",
|
||||
sourceAdvertisementPath: request.sourceAdvertisementPath,
|
||||
sourceCapabilityManifestPath: request.sourceCapabilityManifestPath,
|
||||
issuedAtUtc: request.issuedAtUtc,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function writeElectronCaptureArtifact({ request, projection }, { outputPath, requestOutputPath = "" } = {}) {
|
||||
if (!outputPath) throw new Error("outputPath is required");
|
||||
mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
writeFileSync(outputPath, `${JSON.stringify(projection, null, 2)}\n`, "utf8");
|
||||
if (requestOutputPath) {
|
||||
writeCaptureRequest(request, requestOutputPath);
|
||||
}
|
||||
}
|
||||
|
||||
function validateProjectionArtifact(projection, request) {
|
||||
if (projection.schema !== "gamecult.eve.electron_shell_projection.v1") {
|
||||
throw new Error(`Unexpected Electron projection schema: ${projection.schema || ""}`);
|
||||
}
|
||||
for (const [field, expected] of [
|
||||
["providerId", request.providerId],
|
||||
["surfaceId", request.surfaceId],
|
||||
["projectionKind", request.projectionKind],
|
||||
["commandBoundary", request.commandBoundary],
|
||||
["receiptSchema", request.receiptSchema],
|
||||
]) {
|
||||
if ((projection[field] || "") !== (expected || "")) {
|
||||
throw new Error(`Electron projection ${field} expected ${expected || ""} got ${projection[field] || ""}`);
|
||||
}
|
||||
}
|
||||
if (!projection.root || !projection.root.id) {
|
||||
throw new Error("Electron projection artifact must contain a root shell node");
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const value = argv[index];
|
||||
if (!value.startsWith("--")) continue;
|
||||
const key = value.slice(2);
|
||||
const next = argv[index + 1];
|
||||
if (!next || next.startsWith("--")) {
|
||||
args[key] = true;
|
||||
} else {
|
||||
args[key] = next;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function runCli() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
for (const required of ["advertisement", "capability", "surface", "output"]) {
|
||||
if (!args[required]) {
|
||||
throw new Error(`missing required --${required}`);
|
||||
}
|
||||
}
|
||||
const stamp = args.stamp || "latest";
|
||||
const artifact = buildElectronCaptureArtifact({
|
||||
advertisement: loadJsonFile(args.advertisement),
|
||||
capabilityManifest: loadJsonFile(args.capability),
|
||||
surfaceDocument: loadJsonFile(args.surface),
|
||||
advertisementPath: args.advertisement,
|
||||
capabilityManifestPath: args.capability,
|
||||
captureSurfaceId: args["surface-id"] || "",
|
||||
stamp,
|
||||
});
|
||||
writeElectronCaptureArtifact(artifact, {
|
||||
outputPath: args.output,
|
||||
requestOutputPath: args["request-output"] || "",
|
||||
});
|
||||
process.stdout.write(`${args.output}\n`);
|
||||
}
|
||||
|
||||
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
if (invokedPath === modulePath) {
|
||||
runCli();
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { buildElectronCaptureArtifact } from "./eveelectron-capture-artifact.mjs";
|
||||
|
||||
const advertisement = JSON.parse(readFileSync(new URL("../../web/fixtures/aetheria.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const capabilityManifest = JSON.parse(readFileSync(new URL("../../runtimes/incubating/eve-electron/eve-runtime-capability.json", import.meta.url), "utf8"));
|
||||
const surfaceDocument = JSON.parse(readFileSync(new URL("../../web/fixtures/aetheria-world-surface.json", import.meta.url), "utf8"));
|
||||
const worldSmokeAdvertisement = JSON.parse(readFileSync(new URL("../../web/fixtures/eve-world-smoke.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const worldSmokeSurfaceDocument = JSON.parse(readFileSync(new URL("../../web/fixtures/eve-world-smoke-surface.json", import.meta.url), "utf8"));
|
||||
|
||||
test("builds Electron shell projection capture artifact from provider advertisement and surface", () => {
|
||||
const { request, projection } = buildElectronCaptureArtifact({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
surfaceDocument,
|
||||
advertisementPath: "web/fixtures/aetheria.provider-advertisement.json",
|
||||
capabilityManifestPath: "runtimes/incubating/eve-electron/eve-runtime-capability.json",
|
||||
stamp: "smoke",
|
||||
});
|
||||
|
||||
assert.equal(request.artifactPath, "artifacts/eveelectron-capture/smoke/electron-shell.png");
|
||||
assert.equal(projection.schema, "gamecult.eve.electron_shell_projection.v1");
|
||||
assert.equal(projection.runtimeId, "electron-shell");
|
||||
assert.equal(projection.providerId, "aetheria");
|
||||
assert.equal(projection.surfaceId, "aetheria.daemon.game");
|
||||
assert.equal(projection.commandBoundary, "aetheria.daemon.commands");
|
||||
assert.equal(projection.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
|
||||
assert.equal(projection.artifactKind, "json-projection");
|
||||
assert.equal(projection.captureKind, "electron-shell-projection-json");
|
||||
assert.equal(projection.root.id, "aetheria.daemon.game.root");
|
||||
});
|
||||
|
||||
test("builds Electron shell projection capture artifact from a generic world provider", () => {
|
||||
const { projection } = buildElectronCaptureArtifact({
|
||||
advertisement: worldSmokeAdvertisement,
|
||||
capabilityManifest,
|
||||
surfaceDocument: worldSmokeSurfaceDocument,
|
||||
advertisementPath: "web/fixtures/eve-world-smoke.provider-advertisement.json",
|
||||
capabilityManifestPath: "runtimes/incubating/eve-electron/eve-runtime-capability.json",
|
||||
stamp: "smoke",
|
||||
});
|
||||
|
||||
assert.equal(projection.schema, "gamecult.eve.electron_shell_projection.v1");
|
||||
assert.equal(projection.runtimeId, "electron-shell");
|
||||
assert.equal(projection.providerId, "eve.world-smoke");
|
||||
assert.equal(projection.surfaceId, "eve.world-smoke.surface");
|
||||
assert.equal(projection.commandBoundary, "eve.world-smoke.commands");
|
||||
assert.equal(projection.receiptSchema, "eve.world_smoke.command_receipt.v1");
|
||||
assert.equal(projection.root.id, "eve.world-smoke.root");
|
||||
});
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export function buildElectronCaptureRequest({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
advertisementPath = "",
|
||||
capabilityManifestPath = "",
|
||||
captureSurfaceId = "",
|
||||
stamp = new Date().toISOString().replace(/[:.]/g, "-"),
|
||||
} = {}) {
|
||||
if (!advertisement || typeof advertisement !== "object") {
|
||||
throw new Error("advertisement is required");
|
||||
}
|
||||
if (!capabilityManifest || typeof capabilityManifest !== "object") {
|
||||
throw new Error("capabilityManifest is required");
|
||||
}
|
||||
|
||||
const captureContract = capabilityManifest.lifecycle?.capture?.captureContract;
|
||||
if (!captureContract) {
|
||||
throw new Error("capability manifest is missing lifecycle.capture.captureContract");
|
||||
}
|
||||
|
||||
const captureSurface = selectCaptureSurface(captureContract, advertisement.providerId, captureSurfaceId);
|
||||
if (!captureSurface) {
|
||||
if (captureSurfaceId) {
|
||||
throw new Error(
|
||||
`capture surface mismatch: expected ${captureSurfaceList(captureContract)} got ${advertisement.providerId || "missing"}:${captureSurfaceId}`,
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
`capture provider mismatch: expected ${captureProviderList(captureContract)} got ${advertisement.providerId || "missing"}`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetId = captureContract.targetId || captureContract.runtimeId;
|
||||
const surface = (advertisement.surfaces || []).find(candidate => candidate.surfaceId === captureSurface.surfaceId);
|
||||
if (!surface) {
|
||||
throw new Error(`capture surface not advertised: ${captureSurface.surfaceId}`);
|
||||
}
|
||||
|
||||
const loweringTargets = surface.worldInteraction?.loweringTargets || [];
|
||||
if (!loweringTargets.includes(targetId)) {
|
||||
throw new Error(`surface ${surface.surfaceId} does not advertise lowering target ${targetId}`);
|
||||
}
|
||||
|
||||
const artifactPath = replaceStamp(captureContract.artifactPattern, stamp);
|
||||
|
||||
return {
|
||||
schema: captureContract.requestSchema || "gamecult.eve.runtime_capture_request.v1",
|
||||
runtimeId: capabilityManifest.runtimeId,
|
||||
ownerRepo: captureContract.ownerRepo,
|
||||
providerId: advertisement.providerId,
|
||||
surfaceId: surface.surfaceId,
|
||||
surfaceKind: surface.surfaceKind || "",
|
||||
targetId,
|
||||
projectionKind: surface.worldInteraction?.projectionKind || "",
|
||||
commandBoundary: surface.worldInteraction?.commandBoundary || "",
|
||||
receiptSchema: surface.worldInteraction?.receiptSchema || "",
|
||||
captureKind: captureContract.captureKind,
|
||||
artifactKind: captureContract.artifactKind,
|
||||
artifactPath,
|
||||
conformanceAttachment: captureContract.conformanceAttachment,
|
||||
authority: captureContract.authority,
|
||||
sourceAdvertisementPath: advertisementPath,
|
||||
sourceCapabilityManifestPath: capabilityManifestPath,
|
||||
issuedAtUtc: stamp,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadJsonFile(filePath) {
|
||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||
}
|
||||
|
||||
export function writeCaptureRequest(request, outputPath) {
|
||||
mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
writeFileSync(outputPath, `${JSON.stringify(request, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
function replaceStamp(pattern, stamp) {
|
||||
if (!pattern || !pattern.includes("{stamp}")) {
|
||||
throw new Error(`capture artifact pattern must include {stamp}: ${pattern || "missing"}`);
|
||||
}
|
||||
return pattern.replace("{stamp}", stamp);
|
||||
}
|
||||
|
||||
function selectCaptureSurface(captureContract, providerId, surfaceId = "") {
|
||||
const claims = captureSurfaceClaims(captureContract).filter(claim => claim.providerId === providerId);
|
||||
if (surfaceId) return claims.find(claim => claim.surfaceId === surfaceId) || null;
|
||||
return claims[0] || null;
|
||||
}
|
||||
|
||||
function captureProviderList(captureContract) {
|
||||
return captureSurfaceClaims(captureContract).map(claim => claim.providerId).filter(Boolean).join(", ") || "missing";
|
||||
}
|
||||
|
||||
function captureSurfaceList(captureContract) {
|
||||
return captureSurfaceClaims(captureContract).map(claim => `${claim.providerId}:${claim.surfaceId}`).join(", ") || "missing";
|
||||
}
|
||||
|
||||
function captureSurfaceClaims(captureContract) {
|
||||
return [
|
||||
{
|
||||
providerId: captureContract.requiredProvider || "",
|
||||
surfaceId: captureContract.requiredSurface || "",
|
||||
},
|
||||
...(Array.isArray(captureContract.additionalProviderSurfaces) ? captureContract.additionalProviderSurfaces : []),
|
||||
]
|
||||
.filter(claim => claim && typeof claim === "object")
|
||||
.map(claim => ({
|
||||
providerId: String(claim.providerId || ""),
|
||||
surfaceId: String(claim.surfaceId || ""),
|
||||
}))
|
||||
.filter(claim => claim.providerId && claim.surfaceId);
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const value = argv[index];
|
||||
if (!value.startsWith("--")) continue;
|
||||
const key = value.slice(2);
|
||||
const next = argv[index + 1];
|
||||
if (!next || next.startsWith("--")) {
|
||||
args[key] = true;
|
||||
} else {
|
||||
args[key] = next;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function runCli() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
for (const required of ["advertisement", "capability", "output"]) {
|
||||
if (!args[required]) {
|
||||
throw new Error(`missing required --${required}`);
|
||||
}
|
||||
}
|
||||
|
||||
const stamp = args.stamp || new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const request = buildElectronCaptureRequest({
|
||||
advertisement: loadJsonFile(args.advertisement),
|
||||
capabilityManifest: loadJsonFile(args.capability),
|
||||
advertisementPath: args.advertisement,
|
||||
capabilityManifestPath: args.capability,
|
||||
captureSurfaceId: args["surface-id"] || "",
|
||||
stamp,
|
||||
});
|
||||
writeCaptureRequest(request, args.output);
|
||||
process.stdout.write(`${args.output}\n`);
|
||||
}
|
||||
|
||||
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
if (invokedPath === modulePath) {
|
||||
runCli();
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { buildElectronCaptureRequest } from "./eveelectron-capture-contract.mjs";
|
||||
|
||||
const advertisement = JSON.parse(readFileSync(new URL("../../web/fixtures/aetheria.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const worldSmokeAdvertisement = JSON.parse(readFileSync(new URL("../../web/fixtures/eve-world-smoke.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const capabilityManifest = JSON.parse(readFileSync(new URL("../../runtimes/incubating/eve-electron/eve-runtime-capability.json", import.meta.url), "utf8"));
|
||||
|
||||
test("builds Electron shell capture request from provider advertisement", () => {
|
||||
const request = buildElectronCaptureRequest({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
advertisementPath: "web/fixtures/aetheria.provider-advertisement.json",
|
||||
capabilityManifestPath: "runtimes/incubating/eve-electron/eve-runtime-capability.json",
|
||||
stamp: "smoke",
|
||||
});
|
||||
|
||||
assert.equal(request.schema, "gamecult.eve.runtime_capture_request.v1");
|
||||
assert.equal(request.runtimeId, "electron-shell");
|
||||
assert.equal(request.ownerRepo, "EveElectron");
|
||||
assert.equal(request.providerId, "aetheria");
|
||||
assert.equal(request.surfaceId, "aetheria.daemon.game");
|
||||
assert.equal(request.targetId, "electron-shell");
|
||||
assert.equal(request.projectionKind, "provider-authored-world-surface");
|
||||
assert.equal(request.commandBoundary, "aetheria.daemon.commands");
|
||||
assert.equal(request.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
|
||||
assert.equal(request.artifactPath, "artifacts/eveelectron-capture/smoke/electron-shell.png");
|
||||
assert.match(request.authority, /provider state/);
|
||||
});
|
||||
|
||||
test("builds Electron shell capture request for Aetheria editor surface", () => {
|
||||
const request = buildElectronCaptureRequest({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
captureSurfaceId: "aetheria.daemon.editor",
|
||||
advertisementPath: "web/fixtures/aetheria.provider-advertisement.json",
|
||||
capabilityManifestPath: "runtimes/incubating/eve-electron/eve-runtime-capability.json",
|
||||
stamp: "smoke",
|
||||
});
|
||||
|
||||
assert.equal(request.runtimeId, "electron-shell");
|
||||
assert.equal(request.providerId, "aetheria");
|
||||
assert.equal(request.surfaceId, "aetheria.daemon.editor");
|
||||
assert.equal(request.surfaceKind, "interactive-world-editor");
|
||||
assert.equal(request.projectionKind, "provider-authored-world-editor-surface");
|
||||
assert.equal(request.commandBoundary, "aetheria.daemon.commands");
|
||||
assert.equal(request.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
|
||||
});
|
||||
|
||||
test("builds Electron shell capture request from a declared generic world provider", () => {
|
||||
const request = buildElectronCaptureRequest({
|
||||
advertisement: worldSmokeAdvertisement,
|
||||
capabilityManifest,
|
||||
advertisementPath: "web/fixtures/eve-world-smoke.provider-advertisement.json",
|
||||
capabilityManifestPath: "runtimes/incubating/eve-electron/eve-runtime-capability.json",
|
||||
stamp: "smoke",
|
||||
});
|
||||
|
||||
assert.equal(request.runtimeId, "electron-shell");
|
||||
assert.equal(request.providerId, "eve.world-smoke");
|
||||
assert.equal(request.surfaceId, "eve.world-smoke.surface");
|
||||
assert.equal(request.commandBoundary, "eve.world-smoke.commands");
|
||||
assert.equal(request.receiptSchema, "eve.world_smoke.command_receipt.v1");
|
||||
});
|
||||
|
||||
test("rejects capture request when the surface does not advertise Electron shell", () => {
|
||||
const withoutTarget = structuredClone(advertisement);
|
||||
const surface = withoutTarget.surfaces.find(candidate => candidate.surfaceId === "aetheria.daemon.game");
|
||||
surface.worldInteraction.loweringTargets = surface.worldInteraction.loweringTargets.filter(target => target !== "electron-shell");
|
||||
|
||||
assert.throws(
|
||||
() => buildElectronCaptureRequest({ advertisement: withoutTarget, capabilityManifest, stamp: "smoke" }),
|
||||
/does not advertise lowering target electron-shell/,
|
||||
);
|
||||
});
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export function buildElectronReleaseRequest({
|
||||
capabilityManifest,
|
||||
packageManifest,
|
||||
capabilityManifestPath = "",
|
||||
packageManifestPath = "",
|
||||
repository = "GameCult/EveElectron",
|
||||
} = {}) {
|
||||
if (!capabilityManifest || typeof capabilityManifest !== "object") {
|
||||
throw new Error("capabilityManifest is required");
|
||||
}
|
||||
if (!packageManifest || typeof packageManifest !== "object") {
|
||||
throw new Error("packageManifest is required");
|
||||
}
|
||||
|
||||
const releaseContract = capabilityManifest.lifecycle?.release?.releaseContract;
|
||||
if (!releaseContract) {
|
||||
throw new Error("capability manifest is missing lifecycle.release.releaseContract");
|
||||
}
|
||||
|
||||
if (packageManifest.name !== releaseContract.packageName) {
|
||||
throw new Error(`package name mismatch: expected ${releaseContract.packageName} got ${packageManifest.name || "missing"}`);
|
||||
}
|
||||
if (!packageManifest.version || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(packageManifest.version)) {
|
||||
throw new Error(`package version is not a SemVer-compatible release version: ${packageManifest.version || "missing"}`);
|
||||
}
|
||||
|
||||
const tagName = replaceVersion(releaseContract.tagPattern, packageManifest.version);
|
||||
const artifactPath = replaceVersion(
|
||||
releaseContract.artifactPattern || `artifacts/eveelectron-release/{version}/${releaseContract.packageName}-{version}.zip`,
|
||||
packageManifest.version,
|
||||
);
|
||||
|
||||
return {
|
||||
schema: releaseContract.requestSchema || "gamecult.eve.runtime_release_request.v1",
|
||||
ownerRepo: releaseContract.ownerRepo,
|
||||
repository,
|
||||
packageName: releaseContract.packageName,
|
||||
version: packageManifest.version,
|
||||
packageRoot: releaseContract.packageRoot,
|
||||
versionSource: releaseContract.versionSource,
|
||||
tagName,
|
||||
artifactKind: releaseContract.artifactKind,
|
||||
artifactPath,
|
||||
publishProof: releaseContract.publishProof,
|
||||
sourceCapabilityManifestPath: capabilityManifestPath,
|
||||
sourcePackageManifestPath: packageManifestPath,
|
||||
main: packageManifest.main || "",
|
||||
scripts: packageManifest.scripts || {},
|
||||
dependencies: packageManifest.dependencies || {},
|
||||
devDependencies: packageManifest.devDependencies || {},
|
||||
};
|
||||
}
|
||||
|
||||
export function loadJsonFile(filePath) {
|
||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||
}
|
||||
|
||||
export function writeReleaseRequest(request, outputPath) {
|
||||
mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
writeFileSync(outputPath, `${JSON.stringify(request, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
function replaceVersion(pattern, version) {
|
||||
if (!pattern || !pattern.includes("{version}")) {
|
||||
throw new Error(`release pattern must include {version}: ${pattern || "missing"}`);
|
||||
}
|
||||
return pattern.replaceAll("{version}", version);
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const value = argv[index];
|
||||
if (!value.startsWith("--")) continue;
|
||||
const key = value.slice(2);
|
||||
const next = argv[index + 1];
|
||||
if (!next || next.startsWith("--")) {
|
||||
args[key] = true;
|
||||
} else {
|
||||
args[key] = next;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function runCli() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
for (const required of ["capability", "package", "output"]) {
|
||||
if (!args[required]) {
|
||||
throw new Error(`missing required --${required}`);
|
||||
}
|
||||
}
|
||||
|
||||
const request = buildElectronReleaseRequest({
|
||||
capabilityManifest: loadJsonFile(args.capability),
|
||||
packageManifest: loadJsonFile(args.package),
|
||||
capabilityManifestPath: args.capability,
|
||||
packageManifestPath: args.package,
|
||||
repository: args.repository || "GameCult/EveElectron",
|
||||
});
|
||||
writeReleaseRequest(request, args.output);
|
||||
process.stdout.write(`${args.output}\n`);
|
||||
}
|
||||
|
||||
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
if (invokedPath === modulePath) {
|
||||
runCli();
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { buildElectronReleaseRequest } from "./eveelectron-release-contract.mjs";
|
||||
|
||||
const capabilityManifest = JSON.parse(readFileSync(new URL("../../runtimes/incubating/eve-electron/eve-runtime-capability.json", import.meta.url), "utf8"));
|
||||
const packageManifest = JSON.parse(readFileSync(new URL("../../runtimes/incubating/eve-electron/package.json", import.meta.url), "utf8"));
|
||||
|
||||
test("builds EveElectron release request from package manifest", () => {
|
||||
const request = buildElectronReleaseRequest({
|
||||
capabilityManifest,
|
||||
packageManifest,
|
||||
capabilityManifestPath: "runtimes/incubating/eve-electron/eve-runtime-capability.json",
|
||||
packageManifestPath: "runtimes/incubating/eve-electron/package.json",
|
||||
});
|
||||
|
||||
assert.equal(request.schema, "gamecult.eve.runtime_release_request.v1");
|
||||
assert.equal(request.ownerRepo, "EveElectron");
|
||||
assert.equal(request.repository, "GameCult/EveElectron");
|
||||
assert.equal(request.packageName, "eve-electron");
|
||||
assert.equal(request.version, "0.1.0");
|
||||
assert.equal(request.packageRoot, "runtimes/incubating/eve-electron");
|
||||
assert.equal(request.versionSource, "runtimes/incubating/eve-electron/package.json");
|
||||
assert.equal(request.tagName, "eveelectron-v0.1.0");
|
||||
assert.equal(request.artifactKind, "electron-app");
|
||||
assert.equal(request.artifactPath, "artifacts/eveelectron-release/0.1.0/eve-electron-0.1.0.zip");
|
||||
assert.equal(request.main, "src/eve-electron-shell.mjs");
|
||||
});
|
||||
|
||||
test("rejects package manifests that do not match the Electron release contract", () => {
|
||||
const wrongPackage = { ...packageManifest, name: "eve-wrong" };
|
||||
|
||||
assert.throws(
|
||||
() => buildElectronReleaseRequest({ capabilityManifest, packageManifest: wrongPackage }),
|
||||
/package name mismatch/,
|
||||
);
|
||||
});
|
||||
|
|
@ -658,45 +658,43 @@
|
|||
"id": "EveElectron",
|
||||
"ownerRepo": "EveElectron",
|
||||
"repoRole": "runtime-target",
|
||||
"status": "pending",
|
||||
"status": "graduated-runtime-capture-pending",
|
||||
"runtimes": ["electron-shell"],
|
||||
"requiredRuntimeStatuses": ["active"],
|
||||
"requiredFeatures": ["providerAdvertisements", "surfaceTreeProjection", "embeddedDocuments"],
|
||||
"requiredPlugins": [],
|
||||
"proofs": [
|
||||
{
|
||||
"description": "EveElectron split handoff is machine-readable.",
|
||||
"description": "EveElectron owner repository publishes the generic runtime package.",
|
||||
"status": "passed",
|
||||
"evidencePaths": [
|
||||
"runtimes/incubating/eve-electron/eveelectron-split-handoff.json",
|
||||
"scripts/run-eveelectron-split-handoff-smoke.ps1",
|
||||
"docs/eve-migration-roadmap.md",
|
||||
"docs/parity-testing-harness.md"
|
||||
"../EveElectron/package.json",
|
||||
"../EveElectron/eve-runtime-capability.json",
|
||||
"../EveElectron/src/index.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "EveElectron lifecycle contract is machine-readable and smoke-checked.",
|
||||
"description": "EveElectron owns secure window and preload lifecycle.",
|
||||
"status": "passed",
|
||||
"evidencePaths": [
|
||||
"runtimes/incubating/eve-electron/eve-runtime-capability.json",
|
||||
"scripts/run-eveelectron-lifecycle-smoke.ps1",
|
||||
"scripts/run-eveelectron-provider-shell-smoke.ps1"
|
||||
"../EveElectron/src/window-host.mjs",
|
||||
"../EveElectron/src/eve-provider-preload.cjs",
|
||||
"../EveElectron/test/window-host.test.mjs",
|
||||
"../EveElectron/test/preload.test.cjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "EveElectron provider-shell skeleton selects advertised surfaces, projects Eve surface trees, and emits Eve command intents.",
|
||||
"description": "Aetheria consumes EveElectron for generic window and preload authority while retaining provider transport and daemon lifecycle.",
|
||||
"status": "passed",
|
||||
"evidencePaths": [
|
||||
"runtimes/incubating/eve-electron/package.json",
|
||||
"runtimes/incubating/eve-electron/src/eve-electron-shell.mjs",
|
||||
"runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs",
|
||||
"scripts/run-eveelectron-provider-shell-smoke.ps1"
|
||||
"../Aetheria/Aetheria.Rts.Web/package.json",
|
||||
"../Aetheria/Aetheria.Rts.Web/Electron/main.ts",
|
||||
"../Aetheria/Aetheria.Rts.Web/Electron/preload.cjs",
|
||||
"../Aetheria/Aetheria.Rts.Web/Client/app.ts"
|
||||
]
|
||||
}
|
||||
],
|
||||
"pendingProofs": [
|
||||
"Electron shell runtime body graduates to EveElectron outside Aetheria product code.",
|
||||
"Electron renders embedded Eve documents in a packaged Electron window.",
|
||||
"Electron packaged window PNG capture artifacts are attached to conformance output."
|
||||
]
|
||||
},
|
||||
|
|
@ -750,19 +748,20 @@
|
|||
{
|
||||
"id": "electron-shell",
|
||||
"title": "Electron shell",
|
||||
"kind": "pending",
|
||||
"kind": "active",
|
||||
"ownerRepo": "EveElectron",
|
||||
"repoRole": "runtime-target",
|
||||
"splitTarget": "EveElectron",
|
||||
"graduationTrigger": "Split when Electron has a provider-agnostic shell body, provider advertisement consumption, command transport, packaged app lifecycle, and capture evidence independent of Aetheria product code.",
|
||||
"currentHostRepo": "EveElectron",
|
||||
"sourceRoot": "../EveElectron",
|
||||
"graduationTrigger": "Graduated: the runtime body and package are owned here.",
|
||||
"capabilityManifest": {
|
||||
"schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json",
|
||||
"manifestPath": "runtimes/incubating/eve-electron/eve-runtime-capability.json"
|
||||
"manifestPath": "eve-runtime-capability.json"
|
||||
},
|
||||
"splitHandoffPath": "runtimes/incubating/eve-electron/eveelectron-split-handoff.json",
|
||||
"requiredFixtures": ["embedded-surface", "aetheria-world", "eve-world-smoke"],
|
||||
"pluginFixtures": ["sai-vn"],
|
||||
"supportedFeatures": ["providerAdvertisements", "commandTransport", "surfaceTreeProjection", "embeddedDocuments", "pluginProjection"],
|
||||
"supportedFeatures": ["providerAdvertisements", "commandTransport", "surfaceTreeProjection", "embeddedDocuments", "pluginProjection", "securePreloadBridge", "windowLifecycle"],
|
||||
"supportedPlugins": [
|
||||
{
|
||||
"pluginId": "sai.vn",
|
||||
|
|
@ -783,94 +782,51 @@
|
|||
"targetId": "electron-shell",
|
||||
"surfaceKinds": ["interactive-world", "interactive-world-editor"],
|
||||
"projectionKinds": ["provider-authored-world-surface", "provider-authored-world-editor-surface"],
|
||||
"supportLevel": "electron-shell-surface-tree-command-surface",
|
||||
"supportLevel": "provider-shell-surface-tree-command-surface",
|
||||
"ownership": "runtime-lowers-provider-world-surface-without-owning-world-state",
|
||||
"evidencePaths": [
|
||||
"runtimes/incubating/eve-electron/src/eve-electron-shell.mjs",
|
||||
"runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs",
|
||||
"web/fixtures/sai-vn-surface.json",
|
||||
"web/fixtures/aetheria.provider-advertisement.json",
|
||||
"web/fixtures/eve-world-smoke.provider-advertisement.json"
|
||||
"src/eve-electron-shell.mjs",
|
||||
"src/window-host.mjs",
|
||||
"src/eve-provider-preload.cjs",
|
||||
"test/shell.test.mjs",
|
||||
"test/window-host.test.mjs",
|
||||
"test/preload.test.cjs"
|
||||
]
|
||||
}
|
||||
],
|
||||
"expectedPaths": [
|
||||
"runtimes/incubating/eve-electron/package.json",
|
||||
"runtimes/incubating/eve-electron/src/eve-electron-shell.mjs",
|
||||
"runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs",
|
||||
"runtimes/incubating/eve-electron/eveelectron-split-handoff.json",
|
||||
"runtimes/incubating/eve-electron/eve-runtime-capability.json",
|
||||
"scripts/run-eveelectron-lifecycle-smoke.ps1",
|
||||
"scripts/run-eveelectron-provider-shell-smoke.ps1",
|
||||
"scripts/run-eveelectron-release-contract-smoke.ps1",
|
||||
"scripts/run-eveelectron-capture-contract-smoke.ps1",
|
||||
"scripts/run-eveelectron-capture-smoke.ps1",
|
||||
"tools/eveelectron/eveelectron-release-contract.mjs",
|
||||
"tools/eveelectron/eveelectron-release-contract.test.mjs",
|
||||
"tools/eveelectron/eveelectron-capture-contract.mjs",
|
||||
"tools/eveelectron/eveelectron-capture-contract.test.mjs",
|
||||
"tools/eveelectron/eveelectron-capture-artifact.mjs",
|
||||
"tools/eveelectron/eveelectron-capture-artifact.test.mjs"
|
||||
"../EveElectron/package.json",
|
||||
"../EveElectron/package-lock.json",
|
||||
"../EveElectron/eve-runtime-capability.json",
|
||||
"../EveElectron/src/eve-electron-shell.mjs",
|
||||
"../EveElectron/src/window-host.mjs",
|
||||
"../EveElectron/src/eve-provider-preload.cjs",
|
||||
"../EveElectron/test/shell.test.mjs",
|
||||
"../EveElectron/test/window-host.test.mjs",
|
||||
"../EveElectron/test/preload.test.cjs"
|
||||
],
|
||||
"expectedSourceSymbols": [
|
||||
{
|
||||
"path": "runtimes/incubating/eve-electron/src/eve-electron-shell.mjs",
|
||||
"path": "../EveElectron/src/eve-electron-shell.mjs",
|
||||
"contains": ["EveElectronShell", "selectSurface", "lowerSurface", "createCommandIntent", "normalizeSurfaceDocument", "buildShellNode", "buildPluginProjection", "normalizeEmbeddedDocuments", "embeddedDocuments", "shellElementKind", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "worldInteraction", "commandBoundary", "receiptSchema", "electron-shell"]
|
||||
},
|
||||
{
|
||||
"path": "runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs",
|
||||
"contains": ["selects the active advertised provider surface", "lowers provider surface trees into an Electron shell projection", "lowers Sai, Norn, and TeX sidecar plugin shells without owning semantics", "embeddedDocuments", "norn.map", "sai-vn-stage-shell", "norn-graph-shell", "tex-math-shell", "optional-nested", "rejects surface documents that do not match the advertised target", "command intents carry provider-advertised boundaries", "aetheria.daemon.commands", "aetheria.eve_command_acceptance_status.v1"]
|
||||
"path": "../EveElectron/test/shell.test.mjs",
|
||||
"contains": ["lowers a non-product interactive world", "fixture.world", "provider-authored-world-surface", "fixture.world.commands"]
|
||||
},
|
||||
{
|
||||
"path": "tools/eveelectron/eveelectron-capture-contract.mjs",
|
||||
"contains": ["buildElectronCaptureRequest", "gamecult.eve.runtime_capture_request.v1", "loweringTargets", "artifactPattern", "writeCaptureRequest"]
|
||||
"path": "../EveElectron/src/window-host.mjs",
|
||||
"contains": ["createEveElectronWindow", "registerEveWindowControls", "contextIsolation", "nodeIntegration", "setWindowOpenHandler"]
|
||||
},
|
||||
{
|
||||
"path": "tools/eveelectron/eveelectron-capture-contract.test.mjs",
|
||||
"contains": ["builds Electron shell capture request from provider advertisement", "builds Electron shell capture request from a declared generic world provider", "rejects capture request when the surface does not advertise Electron shell", "electron-shell", "aetheria.daemon.commands", "eve.world-smoke.commands"]
|
||||
},
|
||||
{
|
||||
"path": "tools/eveelectron/eveelectron-capture-artifact.mjs",
|
||||
"contains": ["buildElectronCaptureArtifact", "EveElectronShell", "gamecult.eve.electron_shell_projection.v1", "json-projection", "writeElectronCaptureArtifact", "request-output"]
|
||||
},
|
||||
{
|
||||
"path": "tools/eveelectron/eveelectron-capture-artifact.test.mjs",
|
||||
"contains": ["builds Electron shell projection capture artifact", "builds Electron shell projection capture artifact from a generic world provider", "gamecult.eve.electron_shell_projection.v1", "electron-shell-projection-json", "eve.world-smoke.commands"]
|
||||
},
|
||||
{
|
||||
"path": "tools/eveelectron/eveelectron-release-contract.mjs",
|
||||
"contains": ["buildElectronReleaseRequest", "gamecult.eve.runtime_release_request.v1", "eveelectron-release", "writeReleaseRequest"]
|
||||
},
|
||||
{
|
||||
"path": "tools/eveelectron/eveelectron-release-contract.test.mjs",
|
||||
"contains": ["builds EveElectron release request from package manifest", "eveelectron-v0.1.0", "artifacts/eveelectron-release/0.1.0/eve-electron-0.1.0.zip"]
|
||||
"path": "../EveElectron/src/eve-provider-preload.cjs",
|
||||
"contains": ["installEveProviderBridge", "providerAdvertisement", "surface", "document", "submitCommand", "windowControl"]
|
||||
}
|
||||
],
|
||||
"capture": {
|
||||
"status": "json-projection",
|
||||
"note": "Electron attaches a provider-advertisement-backed gamecult.eve.electron_shell_projection.v1 JSON projection artifact; packaged window PNG capture remains an EveElectron split blocker.",
|
||||
"artifacts": [
|
||||
{
|
||||
"kind": "json-projection",
|
||||
"schema": "gamecult.eve.electron_shell_projection.v1",
|
||||
"path": "artifacts/eveelectron-capture/latest/electron-shell-projection.json",
|
||||
"runtimeId": "electron-shell",
|
||||
"providerId": "aetheria",
|
||||
"surfaceId": "aetheria.daemon.game",
|
||||
"sourceSurfacePath": "web/fixtures/aetheria-world-surface.json",
|
||||
"requestPath": "artifacts/eveelectron-capture/latest/capture-request.json"
|
||||
},
|
||||
{
|
||||
"kind": "json-projection",
|
||||
"schema": "gamecult.eve.electron_shell_projection.v1",
|
||||
"path": "artifacts/eveelectron-capture/latest/eve-world-smoke-electron-shell-projection.json",
|
||||
"runtimeId": "electron-shell",
|
||||
"providerId": "eve.world-smoke",
|
||||
"surfaceId": "eve.world-smoke.surface",
|
||||
"sourceSurfacePath": "web/fixtures/eve-world-smoke-surface.json",
|
||||
"requestPath": "artifacts/eveelectron-capture/latest/eve-world-smoke-capture-request.json"
|
||||
}
|
||||
]
|
||||
"status": "pending-owner-window-capture",
|
||||
"note": "EveElectron owns capture production; Eve core no longer manufactures runtime projection artifacts on its behalf.",
|
||||
"artifacts": []
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue