mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-10-16 15:34:08 +08:00
699 lines
25 KiB
JavaScript
699 lines
25 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import legacySurfaceCatalog from "../../src/lib/governance/legacySurfaceCatalog.json" with { type: "json" };
|
|
import { findPermissionOwnerViolations } from "./lib/permission-owner-guard.mjs";
|
|
|
|
const bundledElectronRuntimePackages = new Set([
|
|
"@limecloud/app-server-client",
|
|
]);
|
|
|
|
function readPackageJson() {
|
|
return JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
}
|
|
|
|
function readPackageScripts() {
|
|
return readPackageJson().scripts ?? {};
|
|
}
|
|
|
|
function readFile(filePath) {
|
|
return fs.readFileSync(filePath, "utf8");
|
|
}
|
|
|
|
function retiredHostTopic() {
|
|
return ["ta", "uri"].join("");
|
|
}
|
|
|
|
function retiredHostConfigStem() {
|
|
return ["ta", "uri.conf"].join("");
|
|
}
|
|
|
|
function retiredRootDesktopHostFiles() {
|
|
const configStem = retiredHostConfigStem();
|
|
return [
|
|
"lime-rs/build.rs",
|
|
"lime-rs/src/main.rs",
|
|
"lime-rs/src/lib.rs",
|
|
["lime-rs/", configStem, ".json"].join(""),
|
|
["lime-rs/", configStem, ".headless.json"].join(""),
|
|
["lime-rs/", retiredHostTopic(), ".windows.conf.json"].join(""),
|
|
];
|
|
}
|
|
|
|
function retiredHostCleanupCandidateFiles() {
|
|
return [];
|
|
}
|
|
|
|
function retiredHostBuildInputTerms() {
|
|
return [
|
|
retiredHostConfigStem(),
|
|
["lime-rs/", retiredHostConfigStem()].join(""),
|
|
["lime-rs/", retiredHostTopic(), ".windows.conf"].join(""),
|
|
["src-", "ta", "uri"].join(""),
|
|
["@", "ta", "uri-apps/cli"].join(""),
|
|
["npm run ", "ta", "uri"].join(""),
|
|
["node_modules/.bin/", "ta", "uri"].join(""),
|
|
];
|
|
}
|
|
|
|
function retiredHostRuntimeTerms() {
|
|
return [
|
|
...retiredHostBuildInputTerms(),
|
|
["__", "TA", "URI__"].join(""),
|
|
["__", "TA", "URI_INTERNALS__"].join(""),
|
|
["TA", "URI_"].join(""),
|
|
["ta", "uri::"].join(""),
|
|
["#[", "ta", "uri::command]"].join(""),
|
|
];
|
|
}
|
|
|
|
function expectNoRetiredHostBuildInput(content, label) {
|
|
for (const term of retiredHostBuildInputTerms()) {
|
|
expect(content, label).not.toContain(term);
|
|
}
|
|
}
|
|
|
|
function expectNoRetiredHostRuntimeInput(content, label) {
|
|
for (const term of retiredHostRuntimeTerms()) {
|
|
expect(content, label).not.toContain(term);
|
|
}
|
|
}
|
|
|
|
function expectNoElectronRuntimeEsmImport(content, label) {
|
|
const runtimeImportLines = content
|
|
.split("\n")
|
|
.filter((line) => /^\s*import\s+(?!type\b).*from "electron";/.test(line));
|
|
|
|
expect(runtimeImportLines, label).toEqual([]);
|
|
}
|
|
|
|
function barePackageName(specifier) {
|
|
if (
|
|
specifier.startsWith(".") ||
|
|
specifier.startsWith("/") ||
|
|
specifier.startsWith("#") ||
|
|
specifier.startsWith("node:") ||
|
|
specifier === "electron"
|
|
) {
|
|
return null;
|
|
}
|
|
if (specifier.startsWith("@")) {
|
|
return specifier.split("/").slice(0, 2).join("/");
|
|
}
|
|
return specifier.split("/")[0] ?? null;
|
|
}
|
|
|
|
function collectRuntimeBareImports(content) {
|
|
const imports = new Set();
|
|
const importFromPattern =
|
|
/^\s*import\s+(?!type\b)[^;]*?\s+from\s+["']([^"']+)["']/gm;
|
|
const sideEffectImportPattern = /^\s*import\s+["']([^"']+)["']/gm;
|
|
const requirePattern = /\brequire\(\s*["']([^"']+)["']\s*\)/g;
|
|
|
|
for (const pattern of [
|
|
importFromPattern,
|
|
sideEffectImportPattern,
|
|
requirePattern,
|
|
]) {
|
|
for (const match of content.matchAll(pattern)) {
|
|
const packageName = barePackageName(match[1]);
|
|
if (packageName) {
|
|
imports.add(packageName);
|
|
}
|
|
}
|
|
}
|
|
|
|
return [...imports].sort();
|
|
}
|
|
|
|
function listFiles(root, predicate = () => true) {
|
|
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
const files = [];
|
|
for (const entry of entries) {
|
|
const filePath = path.join(root, entry.name);
|
|
if (entry.isDirectory()) {
|
|
files.push(...listFiles(filePath, predicate));
|
|
continue;
|
|
}
|
|
if (entry.isFile() && predicate(filePath)) {
|
|
files.push(filePath);
|
|
}
|
|
}
|
|
return files.sort();
|
|
}
|
|
|
|
function currentElectronEntrypointFiles() {
|
|
const explicitFiles = [
|
|
"package.json",
|
|
"forge.config.mjs",
|
|
"scripts/electron/brand-mac-helper-apps.mjs",
|
|
"scripts/check-app-version-consistency.mjs",
|
|
"scripts/electron/build-host.mjs",
|
|
"scripts/electron/build-renderer.mjs",
|
|
"scripts/electron/build-renderer-smoke.mjs",
|
|
"scripts/electron/renderer-build-env.mjs",
|
|
"scripts/electron/copy-desktop-assets.mjs",
|
|
"scripts/electron/smoke.mjs",
|
|
"scripts/electron/make-zip-local-feed.mjs",
|
|
"scripts/electron/update-feed-r2-upload-plan.mjs",
|
|
"scripts/electron/prepare-app-server-assets.mjs",
|
|
"scripts/electron/run-dev.mjs",
|
|
"scripts/electron/run-package-dir.mjs",
|
|
"scripts/electron/run-preview.mjs",
|
|
"scripts/electron/stage-release-assets.mjs",
|
|
"scripts/electron/verify-package-resources.mjs",
|
|
"scripts/electron/macos-native-host-gate-b.mjs",
|
|
"scripts/electron/lib/macos-native-host-electron-gate-b.mjs",
|
|
"scripts/electron/macos-window-fixture.swift",
|
|
"scripts/electron/windows-native-host-gate-b.mjs",
|
|
"scripts/electron/windows-packaged-evidence.mjs",
|
|
"scripts/lib/electron-desktop-resources.mjs",
|
|
"electron/native/macos/macos-native-host.swift",
|
|
"electron/native/macos/macos-screen-capture.swift",
|
|
"electron/native/macos/macos-security.swift",
|
|
"electron/native/macos/macos-window-orchestration.swift",
|
|
"electron/native/macos/macos-accessibility-tree.swift",
|
|
"electron/native/macos/macos-display-watcher.swift",
|
|
"electron/native/macos/macos-media-permissions.swift",
|
|
"electron/native/macos/macos-apple-events.swift",
|
|
"electron/native/windows/windows-native-host.cpp",
|
|
];
|
|
return [
|
|
...explicitFiles,
|
|
...listFiles(".github", (filePath) =>
|
|
/\.(ya?ml|json|md|sh|mjs|js|ts)$/i.test(filePath),
|
|
),
|
|
...listFiles("electron", (filePath) =>
|
|
/\.(ya?ml|json|md|mjs|js|ts|tsx)$/i.test(filePath),
|
|
),
|
|
];
|
|
}
|
|
|
|
function isElectronTestOnlyPath(filePath) {
|
|
return /\.(?:test|testHarness|test-support)(?:\.(?:ts|mjs|js))?$/i.test(
|
|
filePath,
|
|
);
|
|
}
|
|
|
|
function isElectronProductionSourceFile(filePath) {
|
|
const normalized = filePath.replace(/\\/g, "/");
|
|
return (
|
|
/\.(?:mjs|js|ts)$/i.test(normalized) &&
|
|
!normalized.endsWith(".d.ts") &&
|
|
!isElectronTestOnlyPath(normalized)
|
|
);
|
|
}
|
|
|
|
function unpackagedRuntimeImports(filePath, content, runtimeDependencies) {
|
|
if (!isElectronProductionSourceFile(filePath)) return [];
|
|
return collectRuntimeBareImports(content).filter(
|
|
(packageName) =>
|
|
!runtimeDependencies.has(packageName) &&
|
|
!bundledElectronRuntimePackages.has(packageName),
|
|
);
|
|
}
|
|
|
|
function productionTestImports(filePath, content) {
|
|
if (!isElectronProductionSourceFile(filePath)) return [];
|
|
return [
|
|
...content.matchAll(
|
|
/\b(?:from\s+|import\s*(?:\(\s*)?|require\s*\(\s*)(["'])([^"']+)\1/g,
|
|
),
|
|
]
|
|
.map((match) => match[2])
|
|
.filter(isElectronTestOnlyPath);
|
|
}
|
|
|
|
function electronProductionSourceFiles() {
|
|
return listFiles("electron", isElectronProductionSourceFile);
|
|
}
|
|
|
|
describe("Electron current package entrypoints", () => {
|
|
it("default desktop commands point to Electron current", () => {
|
|
const scripts = readPackageScripts();
|
|
|
|
expect(scripts.dev).toBe("npm run electron:dev");
|
|
expect(scripts.build).toBe("npm run electron:build");
|
|
expect(scripts.preview).toBe("npm run electron:start");
|
|
expect(scripts["verify:gui-smoke"]).toBe("npm run smoke:electron");
|
|
expect(scripts["smoke:electron"]).toContain("scripts/electron/smoke.mjs");
|
|
expect(scripts["electron:make:zip-local-feed"]).toBe(
|
|
"node scripts/electron/make-zip-local-feed.mjs",
|
|
);
|
|
});
|
|
|
|
it("package scripts do not expose retired desktop host as a GUI current path", () => {
|
|
const scripts = readPackageScripts();
|
|
const retiredHostScripts = Object.entries(scripts).filter(
|
|
([name, command]) =>
|
|
`${name} ${command}`.toLowerCase().includes("retired-host"),
|
|
);
|
|
|
|
expect(retiredHostScripts).toEqual([]);
|
|
});
|
|
|
|
it("does not keep retired root desktop host app entry files", () => {
|
|
for (const filePath of retiredRootDesktopHostFiles()) {
|
|
expect(fs.existsSync(filePath), filePath).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("keeps one current main-window permission owner and forbids retired media owners", () => {
|
|
const retired = legacySurfaceCatalog.imports.find(
|
|
(entry) => entry.id === "retired-main-window-media-permissions",
|
|
);
|
|
expect(retired).toMatchObject({ classification: "dead", allowedPaths: [] });
|
|
expect(
|
|
findPermissionOwnerViolations(process.cwd(), retired.targets),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("keeps retired host cleanup candidates out of current entrypoints", () => {
|
|
const currentEntrypointContent = currentElectronEntrypointFiles()
|
|
.map((filePath) => readFile(filePath))
|
|
.join("\n");
|
|
|
|
for (const filePath of retiredHostCleanupCandidateFiles()) {
|
|
expect(currentEntrypointContent, filePath).not.toContain(filePath);
|
|
}
|
|
});
|
|
|
|
it("package scripts, Electron Forge, and version checks do not consume retired host config", () => {
|
|
const scripts = readPackageScripts();
|
|
const scriptEntrypoints = Object.entries(scripts)
|
|
.map(([name, command]) => `${name} ${command}`)
|
|
.join("\n");
|
|
const forgeConfig = readFile("forge.config.mjs");
|
|
const appVersionCheck = readFile(
|
|
"scripts/check-app-version-consistency.mjs",
|
|
);
|
|
|
|
expectNoRetiredHostBuildInput(scriptEntrypoints, "package.json scripts");
|
|
expectNoRetiredHostBuildInput(forgeConfig, "forge.config.mjs");
|
|
expectNoRetiredHostBuildInput(
|
|
appVersionCheck,
|
|
"scripts/check-app-version-consistency.mjs",
|
|
);
|
|
|
|
expect(forgeConfig).toContain('const PRODUCT_NAME = "Lime"');
|
|
expect(forgeConfig).toContain('const APP_ID = "com.limecloud.lime"');
|
|
expect(forgeConfig).toContain("app-server.release.json");
|
|
expect(forgeConfig).toContain("new MakerDMG");
|
|
expect(forgeConfig).toContain("new MakerZIP");
|
|
expect(forgeConfig).toContain("new MakerSquirrel");
|
|
expect(scriptEntrypoints).toContain(
|
|
"electron:make:zip-local-feed node scripts/electron/make-zip-local-feed.mjs",
|
|
);
|
|
expect(appVersionCheck).toContain("Cargo.toml");
|
|
expect(appVersionCheck).toContain("packages");
|
|
expect(appVersionCheck).toContain("packages/cli");
|
|
});
|
|
|
|
it("Electron current build, release, CI, and host files stay free of retired host runtime inputs", () => {
|
|
for (const filePath of currentElectronEntrypointFiles()) {
|
|
expectNoRetiredHostRuntimeInput(readFile(filePath), filePath);
|
|
}
|
|
});
|
|
|
|
it("Electron main process avoids ESM runtime imports from electron", () => {
|
|
const mainProcessFiles = [
|
|
"electron/main.ts",
|
|
"electron/updateHost.ts",
|
|
"electron/appServerHost.ts",
|
|
"electron/hostCommands.ts",
|
|
];
|
|
for (const filePath of mainProcessFiles) {
|
|
expectNoElectronRuntimeEsmImport(readFile(filePath), filePath);
|
|
expect(readFile(filePath), filePath).toContain(
|
|
'from "./electronRuntime"',
|
|
);
|
|
}
|
|
|
|
const runtime = readFile("electron/electronRuntime.ts");
|
|
expect(runtime).toContain("createRequire(import.meta.url)");
|
|
expect(runtime).toContain('requireElectron("electron")');
|
|
expectNoElectronRuntimeEsmImport(runtime, "electron/electronRuntime.ts");
|
|
});
|
|
|
|
it("Electron production runtime bare imports stay in packaged dependencies", () => {
|
|
const packageJson = readPackageJson();
|
|
const runtimeDependencies = new Set(
|
|
Object.keys(packageJson.dependencies ?? {}),
|
|
);
|
|
const missing = [];
|
|
|
|
for (const filePath of electronProductionSourceFiles()) {
|
|
for (const packageName of unpackagedRuntimeImports(
|
|
filePath,
|
|
readFile(filePath),
|
|
runtimeDependencies,
|
|
)) {
|
|
missing.push(`${filePath}: ${packageName}`);
|
|
}
|
|
}
|
|
|
|
expect(
|
|
missing,
|
|
"Forge package uses prune=true, so Electron production runtime imports must be root dependencies.",
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("test harness classification does not admit test dependencies in production sources", () => {
|
|
const runtimeDependencies = new Set(["electron"]);
|
|
const content = 'import { vi } from "vitest";';
|
|
expect(
|
|
unpackagedRuntimeImports(
|
|
"electron/browserTabHost.testHarness.ts",
|
|
content,
|
|
runtimeDependencies,
|
|
),
|
|
).toEqual([]);
|
|
expect(
|
|
unpackagedRuntimeImports(
|
|
"electron/browserTabHost.ts",
|
|
content,
|
|
runtimeDependencies,
|
|
),
|
|
).toEqual(["vitest"]);
|
|
expect(
|
|
unpackagedRuntimeImports(
|
|
"electron/browserTabHost.js",
|
|
'const test = require("vitest");',
|
|
runtimeDependencies,
|
|
),
|
|
).toEqual(["vitest"]);
|
|
expect(
|
|
productionTestImports(
|
|
"electron/main.ts",
|
|
'import { test } from "./browserTabHost.testHarness";',
|
|
),
|
|
).toEqual(["./browserTabHost.testHarness"]);
|
|
expect(
|
|
productionTestImports(
|
|
"electron/browserTabHost.test.ts",
|
|
'import { test } from "./browserTabHost.testHarness";',
|
|
),
|
|
).toEqual([]);
|
|
expect(
|
|
electronProductionSourceFiles().flatMap((filePath) =>
|
|
productionTestImports(filePath, readFile(filePath)).map(
|
|
(specifier) => `${filePath}: ${specifier}`,
|
|
),
|
|
),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it.each(["ts", "mjs", "js"])(
|
|
"classifies .test-support.%s as a test-only leaf without exempting production siblings",
|
|
(extension) => {
|
|
const runtimeDependencies = new Set(["electron"]);
|
|
const content = 'import { vi } from "vitest";';
|
|
for (const filePath of [
|
|
`electron/persistedAtomStore.test-support.${extension}`,
|
|
`electron\\persistedAtomStore.test-support.${extension}`,
|
|
]) {
|
|
expect(
|
|
unpackagedRuntimeImports(filePath, content, runtimeDependencies),
|
|
filePath,
|
|
).toEqual([]);
|
|
expect(
|
|
productionTestImports(
|
|
filePath,
|
|
'import { put } from "./persistedAtomStore.test-support";',
|
|
),
|
|
filePath,
|
|
).toEqual([]);
|
|
}
|
|
for (const filePath of [
|
|
`electron/persistedAtomStore.${extension}`,
|
|
`electron/test-supportOwner.${extension}`,
|
|
`electron/persistedAtomStore.test-supporting.${extension}`,
|
|
]) {
|
|
expect(
|
|
unpackagedRuntimeImports(filePath, content, runtimeDependencies),
|
|
filePath,
|
|
).toEqual(["vitest"]);
|
|
}
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
"static import",
|
|
'import { put } from "./persistedAtomStore.test-support";',
|
|
"./persistedAtomStore.test-support",
|
|
],
|
|
[
|
|
"type import",
|
|
'import type { Fixture } from "./persistedAtomStore.test-support.ts";',
|
|
"./persistedAtomStore.test-support.ts",
|
|
],
|
|
[
|
|
"side-effect import",
|
|
'import "./persistedAtomStore.test-support.js";',
|
|
"./persistedAtomStore.test-support.js",
|
|
],
|
|
[
|
|
"re-export",
|
|
'export { put } from "./persistedAtomStore.test-support.mjs";',
|
|
"./persistedAtomStore.test-support.mjs",
|
|
],
|
|
[
|
|
"dynamic import",
|
|
'const support = await import( "./persistedAtomStore.test-support" );',
|
|
"./persistedAtomStore.test-support",
|
|
],
|
|
[
|
|
"require",
|
|
'const support = require( "./persistedAtomStore.test-support.ts" );',
|
|
"./persistedAtomStore.test-support.ts",
|
|
],
|
|
])(
|
|
"rejects production %s of test-only support",
|
|
(_form, content, specifier) => {
|
|
expect(productionTestImports("electron/main.ts", content)).toEqual([
|
|
specifier,
|
|
]);
|
|
expect(
|
|
productionTestImports("electron/persistedAtomStore.test.ts", content),
|
|
).toEqual([]);
|
|
expect(
|
|
productionTestImports(
|
|
"electron/persistedAtomStore.test-support.ts",
|
|
content,
|
|
),
|
|
).toEqual([]);
|
|
},
|
|
);
|
|
|
|
it("does not mistake a production import for a test-only support leaf", () => {
|
|
const content = [
|
|
'import { PersistedAtomStore } from "./persistedAtomStore";',
|
|
'import { support } from "./test-supportOwner";',
|
|
'import "./persistedAtomStore.test-supporting";',
|
|
].join("\n");
|
|
expect(productionTestImports("electron/main.ts", content)).toEqual([]);
|
|
});
|
|
|
|
it("legacy verify-gui-smoke script delegates to Electron smoke", () => {
|
|
const content = readFile("scripts/verify-gui-smoke.mjs");
|
|
|
|
expect(content).toContain('"smoke:electron"');
|
|
expect(content).toContain("Electron Desktop Host current GUI smoke");
|
|
expect(content).not.toMatch(/retired-host/i);
|
|
expect(content).not.toContain("headless");
|
|
expect(content).not.toContain("retired-host.conf");
|
|
expect(content).not.toContain("node_modules/.bin");
|
|
});
|
|
|
|
it("Electron smoke gates Claw workbench shell and composer readiness", () => {
|
|
const mainContent = readFile("electron/main.ts");
|
|
const memorySmokeContent = readFile("electron/smokeMemorySettings.ts");
|
|
const smokeChecksContent = readFile("electron/smokeChecks.ts");
|
|
const smokeEvidenceContent = readFile("electron/smokeEvidence.ts");
|
|
const smokeScript = readFile("scripts/electron/smoke.mjs");
|
|
|
|
expect(mainContent).toContain("createElectronSmokeRunner");
|
|
expect(mainContent).not.toContain("function runElectronSmokeChecks");
|
|
expect(smokeChecksContent).toContain("waitForElectronSmokeWorkbenchReady");
|
|
expect(smokeChecksContent).toContain(
|
|
"waitForElectronSmokeMemorySettingsReady",
|
|
);
|
|
expect(mainContent).toContain("showMainWindowDuringStartup");
|
|
expect(mainContent).toContain("shouldShowMainWindowDuringStartup");
|
|
expect(mainContent).toContain("LIME_ELECTRON_SMOKE_VISIBLE");
|
|
expect(mainContent).toContain("window.showInactive()");
|
|
expect(smokeChecksContent).toContain(
|
|
'[data-testid="workspace-shell-scene"]',
|
|
);
|
|
expect(smokeChecksContent).toContain(
|
|
'[data-testid="inputbar-core-container"]',
|
|
);
|
|
expect(smokeChecksContent).toContain(
|
|
'[data-testid="composer-editor"][role="textbox"]',
|
|
);
|
|
expect(smokeChecksContent).toContain("claw workbench shell ready");
|
|
expect(smokeChecksContent).toContain("memory settings ready");
|
|
expect(smokeChecksContent).toContain("reloadElectronSmokeRenderer");
|
|
expect(smokeChecksContent).toContain(
|
|
"claw workbench shell ready after reload",
|
|
);
|
|
expect(smokeChecksContent).toContain("collectRendererPageErrorCount");
|
|
expect(smokeChecksContent).toContain("collectElectronSmokeLayoutEvidence");
|
|
expect(smokeChecksContent).toContain("ELECTRON_SMOKE_LAYOUT_VIEWPORTS");
|
|
expect(smokeChecksContent).toContain("window.setSize");
|
|
expect(smokeChecksContent).toContain("window.getSize");
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="app-sidebar-nav-settings"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-sidebar-tab-memory"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-floating-nav-button"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-floating-tab-memory"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-memory-store-panel"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-memory-review-refresh"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-memory-index-rebuild"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-memory-consolidate"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-memory-rollout-refresh"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-memory-rollout-consolidate"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-memory-soul-panel"]',
|
|
);
|
|
expect(memorySmokeContent).toContain(
|
|
'[data-testid="settings-memory-advanced-panel"]',
|
|
);
|
|
expect(memorySmokeContent).toContain("/灵感库/");
|
|
expect(memorySmokeContent).toContain("/MemoryPage/");
|
|
expect(mainContent).not.toContain('"turn/start"');
|
|
expect(mainContent).not.toContain('"test_api_key_provider_chat"');
|
|
expect(smokeChecksContent).toContain("LIME_GATE_RUN_ID");
|
|
expect(smokeChecksContent).toContain("LIME_ELECTRON_SMOKE_EVIDENCE_DIR");
|
|
expect(smokeChecksContent).toContain("app_server_handle_json_lines");
|
|
expect(smokeChecksContent).toContain("electron-ipc");
|
|
expect(smokeChecksContent).toContain("capturePage");
|
|
expect(smokeChecksContent).toContain("legacyCommandHitCount");
|
|
expect(smokeChecksContent).toContain("mockFallbackHitCount");
|
|
expect(smokeChecksContent).toContain("pageErrorCount");
|
|
expect(smokeChecksContent).toContain("rendererCrashCount");
|
|
expect(smokeEvidenceContent).toContain(
|
|
'ELECTRON_SMOKE_PROOF_LEVEL = "Gate B-F"',
|
|
);
|
|
expect(smokeEvidenceContent).toContain("currentAppServerMethodObserved");
|
|
expect(smokeEvidenceContent).toContain("noMockFallbackHits");
|
|
expect(smokeEvidenceContent).toContain("screenshotCaptured");
|
|
expect(smokeEvidenceContent).toContain('surfaceId: "SHELL-01"');
|
|
expect(smokeEvidenceContent).toContain('proof: "gate-b-f"');
|
|
expect(smokeEvidenceContent).toContain('complete: result === "pass"');
|
|
expect(smokeEvidenceContent).toContain("workbenchReloadReady");
|
|
expect(smokeEvidenceContent).toContain("ELECTRON_SMOKE_LAYOUT_PROOF_LEVEL");
|
|
expect(smokeEvidenceContent).toContain(
|
|
'"electron-responsive-layout-contract"',
|
|
);
|
|
expect(smokeEvidenceContent).toContain("BrowserWindow setSize/getSize");
|
|
expect(smokeEvidenceContent).toContain("layoutGeometryStable");
|
|
expect(smokeScript).toContain("mkdtempSync");
|
|
expect(smokeScript).toContain("ELECTRON_E2E_USER_DATA_DIR");
|
|
expect(smokeScript).toContain('LIME_ELECTRON_E2E: "1"');
|
|
expect(smokeScript).toContain(
|
|
'packagedExecutable ? { LIME_ELECTRON_BRAND_DEV_APP: "0" } : {}',
|
|
);
|
|
expect(smokeScript).toContain("LIME_ELECTRON_SMOKE_VISIBLE");
|
|
expect(smokeScript).toContain("LIME_GATE_RUN_ID");
|
|
expect(smokeScript).toContain("LIME_ELECTRON_SMOKE_EVIDENCE_DIR");
|
|
expect(smokeScript).toContain('path.join(evidenceDir, "summary.json")');
|
|
expect(smokeScript).toContain("timed out waiting for renderer/workbench");
|
|
});
|
|
|
|
it("dev launcher can expose a guarded Electron CDP port for real GUI续测", () => {
|
|
const runDev = readFile("scripts/electron/run-dev.mjs");
|
|
|
|
expect(runDev).toContain("LIME_ELECTRON_REMOTE_DEBUGGING_PORT");
|
|
expect(runDev).toContain("--remote-debugging-port=");
|
|
expect(runDev).toContain("normalizeRemoteDebuggingPort");
|
|
expect(runDev).toContain("must be between 1 and 65535");
|
|
expect(runDev).toContain("resolveElectronDevLaunchEnv");
|
|
expect(runDev).toContain("ELECTRON_E2E_USER_DATA_DIR");
|
|
expect(runDev).toContain("LIME_ELECTRON_E2E");
|
|
expect(runDev).toContain("LIME_ELECTRON_DEV_HTTP_BRIDGE");
|
|
});
|
|
|
|
it("Electron packaged renderer uses relative assets under file URLs", () => {
|
|
const viteConfig = readFile("vite.config.ts");
|
|
const buildRenderer = readFile("scripts/electron/build-renderer.mjs");
|
|
const smokeBuildRenderer = readFile(
|
|
"scripts/electron/build-renderer-smoke.mjs",
|
|
);
|
|
const rendererBuildEnv = readFile(
|
|
"scripts/electron/renderer-build-env.mjs",
|
|
);
|
|
|
|
expect(buildRenderer).toContain("LIME_ELECTRON_RENDERER");
|
|
expect(buildRenderer).toContain("acquireRendererBuildLock");
|
|
expect(buildRenderer).toContain("rendererBuildEnv");
|
|
expect(buildRenderer).toContain("startRendererBuildHeartbeat");
|
|
expect(smokeBuildRenderer).toContain("LIME_ELECTRON_RENDERER");
|
|
expect(smokeBuildRenderer).toContain('LIME_VITE_EMPTY_OUT_DIR = "0"');
|
|
expect(smokeBuildRenderer).toContain("acquireRendererBuildLock");
|
|
expect(smokeBuildRenderer).toContain("rendererBuildEnv");
|
|
expect(smokeBuildRenderer).toContain("startRendererBuildHeartbeat");
|
|
expect(rendererBuildEnv).toContain("--max-old-space-size=8192");
|
|
expect(rendererBuildEnv).toContain("NODE_OPTIONS");
|
|
expect(rendererBuildEnv).toContain("still running after");
|
|
expect(viteConfig).toContain("base:");
|
|
expect(viteConfig).toContain("emptyOutDir:");
|
|
expect(viteConfig).toContain("keepExistingOutDir ? false : undefined");
|
|
expect(viteConfig).toContain('isElectronRenderer ? "./" : undefined');
|
|
expect(viteConfig).toContain('find: "@limecloud/app-server-client"');
|
|
expect(viteConfig).toContain("./packages/app-server-client/src/browser.ts");
|
|
});
|
|
|
|
it("build monitor observes Electron package output instead of retired host bundles", () => {
|
|
const content = readFile("scripts/monitor-build.sh");
|
|
|
|
expect(content).toContain("Electron 打包进度监控");
|
|
expect(content).toContain("/tmp/electron-build.log");
|
|
expect(content).toContain("release-electron");
|
|
expect(content).toContain(
|
|
"electron-forge|electron:package:dir|electron:dist",
|
|
);
|
|
expect(content).not.toMatch(/retired-host/i);
|
|
expect(content).not.toContain("lime-rs/target/release/bundle");
|
|
expect(content).not.toContain("/tmp/retired-host-build.log");
|
|
});
|
|
|
|
it("local install script advertises Electron current desktop commands", () => {
|
|
const content = readFile("lime-rs/install-local.sh");
|
|
|
|
expect(content).toContain("Rust CLI");
|
|
expect(content).toContain("Electron 命令接管");
|
|
expect(content).toContain("npm run electron:dev");
|
|
expect(content).toContain("npm run electron:build");
|
|
expect(content).toContain("npm run verify:gui-smoke");
|
|
expectNoRetiredHostBuildInput(content, "lime-rs/install-local.sh");
|
|
});
|
|
|
|
it("repository discovery metadata advertises Electron current host", () => {
|
|
const content = readFile(".github/repository-metadata.md");
|
|
|
|
expect(content).toContain("electron");
|
|
expect(content).not.toContain(retiredHostTopic());
|
|
});
|
|
});
|