Files
maka/apps/desktop/scripts/check-renderer-architecture.test.mjs
agent-radar 7be535059e
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 19:05:14 +08:00

2308 lines
84 KiB
JavaScript

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { strict as assert } from 'node:assert';
import { spawnSync } from 'node:child_process';
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { describe, it } from 'node:test';
import { fileURLToPath } from 'node:url';
import {
analyzeRendererSource,
checkRendererArchitecture,
generateArchitectureConfig,
} from './check-renderer-architecture.mjs';
import {
assertRendererEntryHtml,
rendererEntryContractPlugin,
} from './vite-renderer-entry-contract.ts';
const TRANSITIVE_APP_SHELL_PATH = 'src/renderer/app-shell.ts';
const TRANSITIVE_LEGACY_HELPER_PATH = 'src/renderer/legacy-session-helper.ts';
const RENDERER_ENTRY_PATH = 'src/renderer/main.tsx';
function emptyDebt(overrides = {}) {
return {
importDeclarations: 0,
importSpecifiers: 0,
nonTriviaTokens: 0,
dependencyPaths: {},
bridgePaths: {},
environmentCapabilities: {},
hookCalls: {},
lifecycleMethods: {},
unresolvedDependencies: 0,
actionFactories: [],
...overrides,
};
}
function architectureConfig({
legacyAppShellClosureDebt,
legacyFeatureImports = [],
legacyFiles = {},
legacyGrowthDirectories = [],
legacyPlatformImports = [],
rootDebt = {},
rootDebtClosure = {},
legacyRendererFiles = Object.keys(rootDebt),
ownership = [],
} = {}) {
return {
version: 1,
legacyRendererFiles: [...legacyRendererFiles].sort(),
legacyGrowthDirectories: [...legacyGrowthDirectories].sort(),
legacyFeatureImports: [...legacyFeatureImports].sort(),
legacyPlatformImports: [...legacyPlatformImports].sort(),
legacyAppShell: {
files: legacyFiles,
closure: legacyAppShellClosureDebt ?? {},
},
rootDebt,
rootDebtClosure,
ownership,
};
}
function debtForSource(source, path) {
const analysis = analyzeRendererSource(source, path);
return {
importDeclarations: analysis.importDeclarations,
importSpecifiers: analysis.importSpecifiers,
nonTriviaTokens: analysis.nonTriviaTokens,
dependencyPaths: analysis.dependencyPaths,
bridgePaths: analysis.bridgePaths,
environmentCapabilities: analysis.environmentCapabilities,
hookCalls: analysis.hookCalls,
lifecycleMethods: analysis.lifecycleMethods,
unresolvedDependencies: analysis.unresolvedDependencies,
actionFactories: analysis.actionFactories,
};
}
function capabilityDebtForSource(source, path) {
const debt = debtForSource(source, path);
return {
actionFactories: debt.actionFactories,
bridgePaths: debt.bridgePaths,
dependencyPaths: debt.dependencyPaths,
environmentCapabilities: debt.environmentCapabilities,
hookCalls: debt.hookCalls,
lifecycleMethods: debt.lifecycleMethods,
unresolvedDependencies: debt.unresolvedDependencies,
};
}
async function withDesktopFixture(files, run) {
const desktopRoot = await mkdtemp(join(tmpdir(), 'maka-renderer-architecture-'));
try {
for (const [path, source] of Object.entries(files)) {
const absolutePath = join(desktopRoot, path);
await mkdir(dirname(absolutePath), { recursive: true });
await writeFile(absolutePath, source, 'utf8');
}
return await run(desktopRoot);
} finally {
await rm(desktopRoot, { force: true, recursive: true });
}
}
function violationsFor(desktopRoot, config = architectureConfig(), baseConfig) {
return checkRendererArchitecture({
baseConfig,
config,
desktopRoot,
enforceRendererEntryContract: false,
});
}
function assertHasViolation(violations, pattern) {
assert.ok(
violations.some((violation) => pattern.test(violation)),
`Expected a violation matching ${pattern}, received:\n${violations.join('\n')}`,
);
}
function transitiveAppShellFiles(helperSource, extraFiles = {}) {
return {
[TRANSITIVE_APP_SHELL_PATH]: `
import { legacySessionHelper } from './legacy-session-helper.js';
export const AppShell = legacySessionHelper;
`,
[TRANSITIVE_LEGACY_HELPER_PATH]: helperSource,
...extraFiles,
};
}
function transitiveAppShellSeedConfig() {
return architectureConfig({
ownership: [
{
capability: 'fixture-app-shell',
targetZone: 'shell',
legacyPaths: [TRANSITIVE_APP_SHELL_PATH],
},
],
});
}
function rendererEntrySeedConfig() {
return architectureConfig({
rootDebt: { [RENDERER_ENTRY_PATH]: emptyDebt() },
ownership: [
{
capability: 'fixture-root',
targetZone: 'bootstrap',
legacyPaths: [RENDERER_ENTRY_PATH],
},
],
});
}
function rendererEntryContractFiles(overrides = {}) {
return {
'src/renderer/index.html': `
<!doctype html>
<html><body><script type="module" src="/main.tsx"></script></body></html>
`,
[RENDERER_ENTRY_PATH]: `export const main = true;`,
'src/main/main-window.ts': `
import { loadMainRenderer, resolveMainRendererEntry } from './main-renderer-loader.js';
async function createWindow() {
const rendererEntry = resolveMainRendererEntry(import.meta.dirname, process.env.VITE_DEV_SERVER_URL);
await loadMainRenderer(mainWindow, rendererEntry);
}
`,
'src/main/main-renderer-loader.ts': `
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
interface MainRendererWindow {
loadFile(path: string): Promise<void>;
loadURL(url: string): Promise<void>;
}
interface MainRendererEntry {
readonly filePath: string;
readonly url: string;
readonly useDevServer: boolean;
}
export function resolveMainRendererEntry(
mainModuleDirectory: string,
viteDevServerUrl: string | undefined,
): MainRendererEntry {
const rendererEntryPath = join(mainModuleDirectory, '..', '..', 'dist-renderer', 'index.html');
const rendererEntryUrl = viteDevServerUrl ?? pathToFileURL(rendererEntryPath).href;
return Object.freeze({
filePath: rendererEntryPath,
url: rendererEntryUrl,
useDevServer: !!viteDevServerUrl,
});
}
export async function loadMainRenderer(
mainWindow: MainRendererWindow,
rendererEntry: MainRendererEntry,
): Promise<void> {
if (rendererEntry.useDevServer) {
await mainWindow.loadURL(rendererEntry.url);
} else {
await mainWindow.loadFile(rendererEntry.filePath);
}
}
`,
'vite.config.ts': `
import react from '@vitejs/plugin-react';
import { resolve } from 'node:path';
import { defineConfig } from 'vite';
import { rendererEntryContractPlugin } from './scripts/vite-renderer-entry-contract.js';
import { bundledNpmPackagesPlugin } from './vite-bundled-packages.js';
import { dependencyPatchesCachePlugin } from './vite-dependency-patches.js';
const REPO_ROOT = '/fixture';
export default defineConfig({
root: 'src/renderer',
plugins: [
react(),
dependencyPatchesCachePlugin(REPO_ROOT),
bundledNpmPackagesPlugin(),
rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer')),
],
build: { outDir: '../../dist-renderer' },
});
`,
'package.json': JSON.stringify({
scripts: {
'build:renderer':
'vite build && node scripts/check-renderer-entry-output.mjs && node ../../scripts/check-third-party-notices.mjs',
},
}),
...overrides,
};
}
function runRendererEntryBundleContract(directImports) {
const plugin = rendererEntryContractPlugin('/fixture/src/renderer');
plugin.configResolved({ root: '/fixture/src/renderer' });
assert.equal(typeof plugin.generateBundle, 'function');
plugin.generateBundle.call(
{
emitFile() {},
error(message) {
throw new Error(message);
},
getModuleInfo(id) {
return id === '/fixture/src/renderer/index.html' ? { importedIds: directImports } : null;
},
},
{},
{
'assets/index.js': {
type: 'chunk',
isEntry: true,
facadeModuleId: '/fixture/src/renderer/index.html',
},
},
);
}
function canonicalRendererEntryHtml(extraBody = '', policy = "script-src 'self'") {
return `
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; ${policy}; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self'"
/>
<title>Maka</title>
<style>body { margin: 0; }</style>
</head>
<body>
<div id="root"></div>
${extraBody}
<script type="module" src="/main.tsx"></script>
</body>
</html>
`;
}
describe('renderer architecture checker fixtures', () => {
it('accepts the legal inward dependency graph and testing entry boundary', async () => {
await withDesktopFixture(
{
'src/renderer/application/contracts/regions.ts': `
export interface RegionModel { readonly id: string }
export const fetch = () => 'injected-contract';
`,
'src/renderer/application/session/session-scope.ts': `
import type { RegionModel } from '../contracts/regions.js';
export type SessionScope = RegionModel;
`,
'src/renderer/shell/shell-frame.tsx': `
import type { RegionModel } from '../application/contracts/regions.js';
import { fetch } from '../application/contracts/regions.js';
export function ShellFrame(props: { readonly region: RegionModel }) {
return <main data-region={props.region.id} data-contract={fetch()} />;
}
`,
'src/renderer/features/alpha/index.ts': `
export { AlphaHost } from './ui/alpha-host.js';
export type { AlphaServices } from './ports.js';
`,
'src/renderer/features/alpha/ports.ts': `
export interface AlphaServices { readonly read: () => Promise<string> }
`,
'src/renderer/features/alpha/testing.ts': `
export function createFakeAlphaServices() {
return { read: async () => 'fixture' };
}
`,
'src/renderer/features/alpha/stories.ts': `
export const alphaStoryModel = { id: 'alpha-story' };
`,
'src/renderer/features/alpha/ui/alpha-host.tsx': `
import type { RegionModel } from '../../../application/contracts/regions.js';
export function AlphaHost(props: RegionModel) {
return <section>{props.id}</section>;
}
`,
'src/renderer/platform/desktop/create-alpha-services.ts': `
import type { AlphaServices } from '../../features/alpha/index.js';
export function createAlphaServices(): AlphaServices {
return { read: async () => 'desktop' };
}
`,
'src/renderer/composition/desktop-application.tsx': `
import { AlphaHost } from '../features/alpha/index.js';
import { createAlphaServices } from '../platform/desktop/create-alpha-services.js';
import { ShellFrame } from '../shell/shell-frame.js';
void createAlphaServices;
export function DesktopApplication() {
return <><ShellFrame region={{ id: 'primary' }} /><AlphaHost id="alpha" /></>;
}
`,
'src/main/__tests__/alpha-boundary.test.ts': `
import { createFakeAlphaServices } from '../../renderer/features/alpha/testing.js';
void createFakeAlphaServices;
`,
'stories/alpha.stories.tsx': `
import { createFakeAlphaServices } from '../src/renderer/features/alpha/testing.js';
import { alphaStoryModel } from '../src/renderer/features/alpha/stories.js';
export const services = createFakeAlphaServices();
export const model = alphaStoryModel;
`,
},
(desktopRoot) => {
assert.deepEqual(violationsFor(desktopRoot), []);
},
);
});
it('resolves @maka/desktop self-imports before enforcing zone boundaries', async () => {
await withDesktopFixture(
{
'src/renderer/features/alpha/controller.ts': `
import { desktopOnly } from '@maka/desktop/src/renderer/platform/desktop/private.js';
export const alpha = desktopOnly;
`,
'src/renderer/shell/shell-frame.ts': `
import { BetaHost } from '@maka/desktop/src/renderer/features/beta/index.js';
export const frame = BetaHost;
`,
'src/renderer/application/session/session-scope.ts': `
import { BetaHost } from '@maka/desktop/src/renderer/features/beta/index.js';
export const scope = BetaHost;
`,
'src/renderer/features/beta/index.ts': 'export const BetaHost = true;',
'src/renderer/platform/desktop/private.ts': 'export const desktopOnly = true;',
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(
violations,
/features\/alpha\/controller\.ts: feature imports a forbidden Desktop\/shell module/u,
);
assertHasViolation(
violations,
/shell\/shell-frame\.ts: shell imports forbidden implementation module/u,
);
assertHasViolation(
violations,
/application\/session\/session-scope\.ts: application authority imports an outer implementation/u,
);
},
);
});
it('rejects direct browser environment access in shell and composition files', async () => {
await withDesktopFixture(
{
'src/renderer/shell/local-storage.ts': `
export const persisted = localStorage.getItem('shell-layout');
`,
'src/renderer/shell/window-listener.ts': `
window.addEventListener('resize', () => undefined);
`,
'src/renderer/composition/fetch.ts': `
export const request = fetch('/renderer-bootstrap');
`,
'src/renderer/composition/timers.ts': `
setTimeout(() => undefined, 1);
requestAnimationFrame(() => undefined);
`,
'src/renderer/shell/aliased-environment.ts': `
const later = setTimeout;
const retrieve = fetch;
later(() => undefined, 1);
void retrieve('/shell-data');
`,
'src/renderer/composition/browser-globals.ts': `
void indexedDB.open('maka');
history.pushState({}, '', '/');
void new FileReader();
addEventListener('online', () => undefined);
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
for (const path of [
'src/renderer/shell/local-storage.ts',
'src/renderer/shell/window-listener.ts',
'src/renderer/composition/fetch.ts',
'src/renderer/composition/timers.ts',
'src/renderer/shell/aliased-environment.ts',
'src/renderer/composition/browser-globals.ts',
]) {
assertHasViolation(
violations,
new RegExp(
`^${path.replaceAll('/', '\\/').replaceAll('.', '\\.')}.*directly accesses browser environment capabilities$`,
'u',
),
);
}
},
);
});
it('resolves browser-global shadowing lexically instead of masking the whole file', () => {
const analysis = analyzeRendererSource(
`
function useInjected(fetch, history, setTimeout) {
fetch('/injected');
history.pushState({}, '', '/injected');
setTimeout(() => undefined, 1);
}
void useInjected;
fetch('/global');
history.pushState({}, '', '/global');
setTimeout(() => undefined, 1);
`,
'src/renderer/shell/lexical-browser-globals.ts',
);
assert.equal(analysis.environmentCapabilities.fetch, 1);
assert.equal(analysis.environmentCapabilities['history.pushState'], 1);
assert.equal(analysis.environmentCapabilities.setTimeout, 1);
});
it('rejects computed and optional access to the Desktop bridge in strict zones', async () => {
await withDesktopFixture(
{
'src/renderer/application/computed-bridge.ts': `
void window['maka'].sessions.list();
`,
'src/renderer/application/optional-bridge.ts': `
void window?.maka?.sessions?.subscribeEvents?.(() => undefined);
`,
'src/renderer/application/dynamic-global.ts': `
const bridgeName = 'maka';
void window[bridgeName];
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(
violations,
/^src\/renderer\/application\/computed-bridge\.ts: application code accesses the Desktop global bridge$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/application\/optional-bridge\.ts: application code accesses the Desktop global bridge$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/application\/dynamic-global\.ts: application code contains non-static global environment access$/u,
);
},
);
});
it('rejects cross-feature, deep feature, and production testing imports', async () => {
await withDesktopFixture(
{
'src/renderer/features/alpha/controller.ts': `
export async function loadBeta() {
return import('../beta/index.js');
}
`,
'src/renderer/features/beta/index.ts': `
export const beta = true;
`,
'src/renderer/composition/deep-feature.ts': `
const alphaController = require('../features/alpha/controller/use-alpha.js');
void alphaController;
`,
'src/renderer/composition/testing-in-production.ts': `
import { createFakeAlphaServices } from '../features/alpha/testing.js';
void createFakeAlphaServices;
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(
violations,
/^src\/renderer\/features\/alpha\/controller\.ts: feature alpha imports feature beta:/u,
);
assertHasViolation(
violations,
/^src\/renderer\/composition\/deep-feature\.ts: feature imports must use index:/u,
);
assertHasViolation(
violations,
/^src\/renderer\/composition\/testing-in-production\.ts: production code imports a feature testing entry:/u,
);
},
);
});
it('rejects a feature production module importing its own testing entry', async () => {
await withDesktopFixture(
{
'src/renderer/features/alpha/controller.ts': `
import { fakeAlpha } from './testing.js';
export const alpha = fakeAlpha;
`,
'src/renderer/features/alpha/testing.ts': 'export const fakeAlpha = true;',
},
(desktopRoot) => {
assertHasViolation(
violationsFor(desktopRoot),
/features\/alpha\/controller\.ts:.*testing entry/u,
);
},
);
});
it('keeps feature stories entries test-only', async () => {
await withDesktopFixture(
{
'src/renderer/features/alpha/stories.ts': 'export const storyModel = true;',
'src/renderer/composition/stories-in-production.ts': `
import { storyModel } from '../features/alpha/stories.js';
export const leakedStoryModel = storyModel;
`,
},
(desktopRoot) => {
assertHasViolation(
violationsFor(desktopRoot),
/composition\/stories-in-production\.ts: production code imports a feature stories entry/u,
);
},
);
});
it('rejects an application contract re-exporting application implementation', async () => {
await withDesktopFixture(
{
'src/renderer/application/contracts/index.ts': `
export { sessionStore } from '../session/session-store.js';
`,
'src/renderer/application/session/session-store.ts': `
export const sessionStore = true;
`,
},
(desktopRoot) => {
assertHasViolation(
violationsFor(desktopRoot),
/application\/contracts\/index\.ts:.*(?:contract|contracts).*implementation/iu,
);
},
);
});
it('rejects composition and Desktop adapters importing deep application implementation', async () => {
await withDesktopFixture(
{
'src/renderer/application/session/session-store.ts': `export const sessionStore = true;`,
'src/renderer/composition/deep-application.ts': `
import { sessionStore } from '../application/session/session-store.js';
export const composed = sessionStore;
`,
'src/renderer/platform/desktop/deep-application.ts': `
import { sessionStore } from '../../application/session/session-store.js';
export const adapted = sessionStore;
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(
violations,
/composition\/deep-application\.ts: composition imports application implementation instead of a public entry/u,
);
assertHasViolation(
violations,
/platform\/desktop\/deep-application\.ts: Desktop adapter imports application implementation instead of a public entry/u,
);
},
);
});
it('rejects Electron and node built-in imports from strict feature code', async () => {
await withDesktopFixture(
{
'src/renderer/features/alpha/electron-import.ts': `
import { ipcRenderer } from 'electron';
export const ipc = ipcRenderer;
`,
'src/renderer/features/alpha/node-import.ts': `
import { readFile } from 'node:fs/promises';
export const read = readFile;
`,
'src/renderer/features/alpha/controller.test.ts': `
import { strict as assert } from 'node:assert';
assert.equal(1, 1);
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(
violations,
/features\/alpha\/electron-import\.ts:.*feature.*electron/iu,
);
assertHasViolation(
violations,
/features\/alpha\/node-import\.ts:.*feature.*node:fs/iu,
);
assert.ok(
!violations.some((violation) => violation.includes('controller.test.ts')),
`Feature tests may use the Node test environment:\n${violations.join('\n')}`,
);
},
);
});
it('keeps Desktop platform adapters free of UI lifecycle and runtime module loading', async () => {
await withDesktopFixture(
{
'src/renderer/platform/desktop/stateful-view.tsx': `
import * as React from 'react';
import { useState } from 'react';
export function StatefulView() {
const [open] = useState(false);
return <div>{String(open)}</div>;
}
export class StatefulAdapter extends React.Component {
componentDidMount() {}
render() { return null; }
}
`,
'src/renderer/platform/desktop/dynamic-adapter.ts': `
export function loadAdapter(path: string) { return import(path); }
`,
'src/renderer/platform/desktop/electron-adapter.ts': `
import { ipcRenderer } from 'electron';
export const ipc = ipcRenderer;
`,
'src/renderer/platform/desktop/node-adapter.ts': `
import { readFile } from 'node:fs/promises';
export const read = readFile;
`,
'src/renderer/platform/desktop/allowed-capabilities.ts': `
export function createAllowedCapabilities() {
localStorage.getItem('desktop-adapter');
return window.maka.sessions.list();
}
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(
violations,
/platform\/desktop\/stateful-view\.tsx: platform code owns stateful React hooks/u,
);
assertHasViolation(
violations,
/platform\/desktop\/stateful-view\.tsx: platform code owns React class lifecycle methods/u,
);
assertHasViolation(
violations,
/platform\/desktop\/stateful-view\.tsx: Desktop adapters cannot own React UI/u,
);
assertHasViolation(
violations,
/platform\/desktop\/dynamic-adapter\.ts: platform code contains a non-static import or require/u,
);
assertHasViolation(
violations,
/platform\/desktop\/electron-adapter\.ts: platform code imports forbidden environment module: electron/u,
);
assertHasViolation(
violations,
/platform\/desktop\/node-adapter\.ts: platform code imports forbidden environment module: node:fs\/promises/u,
);
assert.ok(
!violations.some((violation) => violation.includes('allowed-capabilities.ts')),
`Desktop adapters may own the bridge and browser environment:\n${violations.join('\n')}`,
);
},
);
});
it('rejects new AppShell-family helper files outside the debt ledger', async () => {
await withDesktopFixture(
{
'src/renderer/app-shell-fresh-owner.ts': 'export const owner = true;',
'src/renderer/use-app-shell-fresh-owner.ts': 'export const owner = true;',
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(
violations,
/^legacy AppShell file set changed;.*app-shell-fresh-owner\.ts.*use-app-shell-fresh-owner\.ts/u,
);
},
);
});
it('generates the transitive legacy closure owned directly by AppShell', async () => {
const helperSource = `
export const legacySessionHelper = 'legacy-session';
`;
await withDesktopFixture(
transitiveAppShellFiles(helperSource),
(desktopRoot) => {
const generated = generateArchitectureConfig(
desktopRoot,
transitiveAppShellSeedConfig(),
);
assert.deepEqual(
Object.keys(generated.legacyAppShell.closure),
[TRANSITIVE_LEGACY_HELPER_PATH],
);
assert.deepEqual(
generated.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH],
capabilityDebtForSource(helperSource, TRANSITIVE_LEGACY_HELPER_PATH),
);
},
);
});
it('prefers runtime source over a same-stem declaration in the AppShell closure', async () => {
const runtimePath = 'src/renderer/legacy-session-helper.js';
const runtimeSource = `
export function legacySessionHelper() {
return window.maka.sessions.list();
}
`;
await withDesktopFixture(
{
[TRANSITIVE_APP_SHELL_PATH]: `
import { legacySessionHelper } from './legacy-session-helper.js';
export const AppShell = legacySessionHelper;
`,
'src/renderer/legacy-session-helper.d.ts': `
export declare function legacySessionHelper(): Promise<unknown>;
`,
[runtimePath]: runtimeSource,
},
(desktopRoot) => {
const generated = generateArchitectureConfig(
desktopRoot,
transitiveAppShellSeedConfig(),
);
assert.deepEqual(Object.keys(generated.legacyAppShell.closure), [runtimePath]);
assert.deepEqual(
generated.legacyAppShell.closure[runtimePath],
capabilityDebtForSource(runtimeSource, runtimePath),
);
},
);
});
it('ratchets legacy helpers reachable through a feature public ownership boundary', async () => {
await withDesktopFixture(
{
[TRANSITIVE_APP_SHELL_PATH]: `
import { AlphaHost } from './features/alpha/index.js';
export const AppShell = AlphaHost;
`,
'src/renderer/features/alpha/index.ts': `
import { legacySessionHelper } from '../../legacy-session-helper.js';
export const AlphaHost = legacySessionHelper;
`,
[TRANSITIVE_LEGACY_HELPER_PATH]: `
import { useState } from 'react';
export function legacySessionHelper() {
void window.maka.sessions.list();
localStorage.getItem('feature-owned');
return useState(false);
}
`,
},
(desktopRoot) => {
const currentConfig = generateArchitectureConfig(
desktopRoot,
transitiveAppShellSeedConfig(),
);
assert.deepEqual(
Object.keys(currentConfig.legacyAppShell.closure),
[TRANSITIVE_LEGACY_HELPER_PATH],
);
const baseConfig = structuredClone(currentConfig);
const baseHelperDebt = baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH];
baseHelperDebt.hookCalls = {};
baseHelperDebt.bridgePaths = {};
baseHelperDebt.environmentCapabilities = {};
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/^src\/renderer\/legacy-session-helper\.ts: new or increased hookCalls debt useState$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/legacy-session-helper\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/legacy-session-helper\.ts: new or increased environmentCapabilities debt localStorage\.getItem$/u,
);
},
);
});
it('ratchets legacy helpers reachable through a shared Desktop boundary', async () => {
await withDesktopFixture(
{
[TRANSITIVE_APP_SHELL_PATH]: `
import { rootBridge } from '../shared/root-bridge.js';
export const AppShell = rootBridge;
`,
'src/shared/root-bridge.ts': `
import { legacySessionHelper } from '../renderer/legacy-session-helper.js';
export const rootBridge = legacySessionHelper;
`,
[TRANSITIVE_LEGACY_HELPER_PATH]: `
export function legacySessionHelper() {
return window.maka.sessions.list();
}
`,
},
(desktopRoot) => {
const currentConfig = generateArchitectureConfig(
desktopRoot,
transitiveAppShellSeedConfig(),
);
assert.deepEqual(
Object.keys(currentConfig.legacyAppShell.closure),
[TRANSITIVE_LEGACY_HELPER_PATH, 'src/shared/root-bridge.ts'],
);
const baseConfig = structuredClone(currentConfig);
baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH].bridgePaths = {};
assertHasViolation(
violationsFor(desktopRoot, currentConfig, baseConfig),
/^src\/renderer\/legacy-session-helper\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u,
);
},
);
});
it('ratchets capability debt owned directly by a shared Desktop intermediary', async () => {
const sharedPath = 'src/shared/root-bridge.ts';
await withDesktopFixture(
{
[TRANSITIVE_APP_SHELL_PATH]: `
import { rootBridge } from '../shared/root-bridge.js';
export const AppShell = rootBridge;
`,
[sharedPath]: `
import { useState } from 'react';
export function rootBridge() {
void window.maka.sessions.list();
localStorage.getItem('shared-root-state');
return useState(false);
}
`,
},
(desktopRoot) => {
const currentConfig = generateArchitectureConfig(
desktopRoot,
transitiveAppShellSeedConfig(),
);
assert.deepEqual(Object.keys(currentConfig.legacyAppShell.closure), [sharedPath]);
const baseConfig = structuredClone(currentConfig);
const baseSharedDebt = baseConfig.legacyAppShell.closure[sharedPath];
baseSharedDebt.hookCalls = {};
baseSharedDebt.bridgePaths = {};
baseSharedDebt.environmentCapabilities = {};
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/^src\/shared\/root-bridge\.ts: new or increased hookCalls debt useState$/u,
);
assertHasViolation(
violations,
/^src\/shared\/root-bridge\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u,
);
assertHasViolation(
violations,
/^src\/shared\/root-bridge\.ts: new or increased environmentCapabilities debt localStorage\.getItem$/u,
);
},
);
});
it('allows support debt to leave the AppShell closure but rejects the reverse ownership regression', async () => {
const mainPath = 'src/renderer/main.tsx';
const compositionPath = 'src/renderer/composition/desktop-application.ts';
const sharedPath = 'src/shared/root-bridge.ts';
const appShellOwnsSupport = `
import { rootBridge } from '../shared/root-bridge.js';
export const AppShell = rootBridge;
`;
const compositionUsesAppShell = `
import { AppShell } from '../app-shell.js';
export const DesktopApplication = AppShell;
`;
const compositionOwnsSupport = `
import { rootBridge } from '../../shared/root-bridge.js';
export const DesktopApplication = rootBridge;
`;
await withDesktopFixture(
{
[mainPath]: `
import { DesktopApplication } from './composition/desktop-application.js';
export const main = DesktopApplication;
`,
[compositionPath]: compositionUsesAppShell,
[TRANSITIVE_APP_SHELL_PATH]: appShellOwnsSupport,
[sharedPath]: `export const rootBridge = window.maka.sessions.list();`,
},
async (desktopRoot) => {
const seedConfig = architectureConfig({
rootDebt: { [mainPath]: emptyDebt() },
ownership: [
{
capability: 'fixture-app-shell',
targetZone: 'shell',
legacyPaths: [TRANSITIVE_APP_SHELL_PATH],
},
{
capability: 'fixture-root',
targetZone: 'bootstrap',
legacyPaths: [mainPath],
},
],
});
const appShellOwnedConfig = generateArchitectureConfig(desktopRoot, seedConfig);
assert.deepEqual(Object.keys(appShellOwnedConfig.legacyAppShell.closure), [sharedPath]);
assert.deepEqual(Object.keys(appShellOwnedConfig.rootDebtClosure), []);
await writeFile(join(desktopRoot, compositionPath), compositionOwnsSupport, 'utf8');
await writeFile(
join(desktopRoot, TRANSITIVE_APP_SHELL_PATH),
`export const AppShell = true;`,
'utf8',
);
const rootOwnedConfig = generateArchitectureConfig(desktopRoot, appShellOwnedConfig);
assert.deepEqual(Object.keys(rootOwnedConfig.legacyAppShell.closure), []);
assert.deepEqual(Object.keys(rootOwnedConfig.rootDebtClosure), [sharedPath]);
assert.deepEqual(
violationsFor(desktopRoot, rootOwnedConfig, appShellOwnedConfig),
[],
);
await writeFile(join(desktopRoot, compositionPath), compositionUsesAppShell, 'utf8');
await writeFile(
join(desktopRoot, TRANSITIVE_APP_SHELL_PATH),
appShellOwnsSupport,
'utf8',
);
const regressedConfig = generateArchitectureConfig(desktopRoot, rootOwnedConfig);
assertHasViolation(
violationsFor(desktopRoot, regressedConfig, rootOwnedConfig),
/^src\/shared\/root-bridge\.ts: new legacyAppShellClosure debt entries are forbidden$/u,
);
},
);
});
it('rejects stateful hook growth inside a transitive legacy AppShell helper', async () => {
await withDesktopFixture(
transitiveAppShellFiles(`
import { useState } from 'react';
export function legacySessionHelper() {
const [session] = useState('legacy-session');
return session;
}
`),
(desktopRoot) => {
const currentConfig = generateArchitectureConfig(
desktopRoot,
transitiveAppShellSeedConfig(),
);
const baseConfig = structuredClone(currentConfig);
baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH].hookCalls = {};
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/^src\/renderer\/legacy-session-helper\.ts: hookCalls debt increased from 0 to 1$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/legacy-session-helper\.ts: new or increased hookCalls debt useState$/u,
);
},
);
});
it('rejects a feature controller Hook returning to AppShell after provider migration', async () => {
const providerOwnedAppShell = `
import { GoalProvider } from './features/goals/index.js';
export const AppShell = GoalProvider;
`;
await withDesktopFixture(
{
[TRANSITIVE_APP_SHELL_PATH]: providerOwnedAppShell,
'src/renderer/features/goals/index.ts': `
export const GoalProvider = true;
export function useGoalController() { return true; }
`,
},
async (desktopRoot) => {
const seedConfig = transitiveAppShellSeedConfig();
const providerOwnedConfig = generateArchitectureConfig(desktopRoot, seedConfig);
await writeFile(
join(desktopRoot, TRANSITIVE_APP_SHELL_PATH),
`
import { GoalProvider, useGoalController } from './features/goals/index.js';
export const AppShell = [GoalProvider, useGoalController()];
`,
'utf8',
);
const regressedConfig = generateArchitectureConfig(
desktopRoot,
providerOwnedConfig,
);
const violations = violationsFor(
desktopRoot,
regressedConfig,
providerOwnedConfig,
);
assertHasViolation(
violations,
/^src\/renderer\/app-shell\.ts: hookCalls debt increased from 0 to 1$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/app-shell\.ts: new or increased hookCalls debt useGoalController$/u,
);
},
);
});
it('rejects bridge and environment capability growth inside a transitive legacy AppShell helper', async () => {
await withDesktopFixture(
transitiveAppShellFiles(`
export function legacySessionHelper() {
void window.maka.sessions.list();
return localStorage.getItem('legacy-session');
}
`),
(desktopRoot) => {
const currentConfig = generateArchitectureConfig(
desktopRoot,
transitiveAppShellSeedConfig(),
);
const baseConfig = structuredClone(currentConfig);
const baseHelperDebt = baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH];
baseHelperDebt.bridgePaths = {};
baseHelperDebt.environmentCapabilities = {};
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/^src\/renderer\/legacy-session-helper\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/legacy-session-helper\.ts: new or increased environmentCapabilities debt localStorage\.getItem$/u,
);
},
);
});
it('rejects dependency growth inside a transitive legacy AppShell helper', async () => {
const dependencyPath = 'src/renderer/legacy-session-store.ts';
await withDesktopFixture(
transitiveAppShellFiles(
`
import { legacySessionStore } from './legacy-session-store.js';
export const legacySessionHelper = legacySessionStore;
`,
{
[dependencyPath]: `export const legacySessionStore = 'legacy-session';`,
},
),
(desktopRoot) => {
const currentConfig = generateArchitectureConfig(
desktopRoot,
transitiveAppShellSeedConfig(),
);
const baseConfig = structuredClone(currentConfig);
baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH].dependencyPaths = {};
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/^src\/renderer\/legacy-session-helper\.ts: dependencyPaths debt increased from 0 to 1$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/legacy-session-helper\.ts: new dependency debt \.\/legacy-session-store\.js$/u,
);
},
);
});
it('rejects a stale AppShell closure ledger when another legacy file becomes reachable', async () => {
const newlyReachablePath = 'src/renderer/legacy-session-store.ts';
await withDesktopFixture(
transitiveAppShellFiles(`
export const legacySessionHelper = 'legacy-session';
`),
async (desktopRoot) => {
const staleConfig = generateArchitectureConfig(
desktopRoot,
transitiveAppShellSeedConfig(),
);
const expandedHelperSource = `
import { legacySessionStore } from './legacy-session-store.js';
export const legacySessionHelper = legacySessionStore;
`;
await writeFile(
join(desktopRoot, TRANSITIVE_LEGACY_HELPER_PATH),
expandedHelperSource,
'utf8',
);
await writeFile(
join(desktopRoot, newlyReachablePath),
`export const legacySessionStore = 'legacy-session';`,
'utf8',
);
staleConfig.legacyRendererFiles = [
...staleConfig.legacyRendererFiles,
newlyReachablePath,
].sort();
staleConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH] = capabilityDebtForSource(
expandedHelperSource,
TRANSITIVE_LEGACY_HELPER_PATH,
);
assertHasViolation(
violationsFor(desktopRoot, staleConfig),
/legacy AppShell transitive renderer closure changed;.*legacy-session-store\.ts/u,
);
},
);
});
it('rejects non-static dependency escape hatches inside the AppShell legacy closure', async () => {
await withDesktopFixture(
transitiveAppShellFiles(
`
export const hiddenLegacyOwners = import.meta.glob('./legacy-session-store.ts');
`,
{
'src/renderer/legacy-session-store.ts': `
void window.maka.sessions.list();
`,
},
),
(desktopRoot) => {
assert.throws(
() => generateArchitectureConfig(desktopRoot, transitiveAppShellSeedConfig()),
/legacy-session-helper\.ts: AppShell closure contains a non-static import, require, or import\.meta glob/u,
);
},
);
});
it('rejects stateful shell hooks and imports from legacy implementation', async () => {
await withDesktopFixture(
{
'src/renderer/legacy-session-owner.ts': 'export const legacySessionOwner = true;',
'src/renderer/shell/shell-frame.tsx': `
import * as React from 'react';
import { legacySessionOwner } from '../legacy-session-owner.js';
export function ShellFrame() {
const [open] = React.useState(legacySessionOwner);
return <main data-open={open} />;
}
`,
'src/renderer/shell/custom-hook.ts': `
import { useExternalSession } from '@example/session-hooks';
export const session = useExternalSession();
`,
'src/renderer/shell/namespace-hook.ts': `
import * as hooks from '@example/session-hooks';
export const session = hooks.useExternalSession();
`,
'src/renderer/shell/react-namespace-alias.ts': `
import * as React from 'react';
const R = React;
export const state = R.useState(false);
`,
'src/renderer/shell/react-19-hooks.tsx': `
import { useActionState, useInsertionEffect, useOptimistic, useTransition } from 'react';
export function StatefulFrame() {
useActionState(async (state: number) => state, 0);
useInsertionEffect(() => undefined, []);
useOptimistic(0);
useTransition();
return null;
}
`,
'src/renderer/composition/class-lifecycle.tsx': `
import * as React from 'react';
export class DesktopComposition extends React.Component {
componentDidMount() {}
render() { return null; }
}
`,
'src/renderer/composition/class-field-lifecycle.tsx': `
import * as React from 'react';
export class DesktopComposition extends React.Component {
componentDidMount = () => {};
render() { return null; }
}
`,
'src/renderer/composition/class-state.tsx': `
import * as React from 'react';
export class StatefulComposition extends React.Component {
constructor() {
super({});
this.state = { mounted: false };
}
render() { return null; }
}
`,
'src/renderer/composition/feature-namespace-hook.ts': `
import * as session from '../features/alpha/index.js';
export const controller = session.useSessionController();
`,
'src/renderer/features/alpha/index.ts': `
export function useSessionController() { return {}; }
`,
'src/renderer/composition/plain-class.ts': `
export class DomainLifecycleName {
componentDidMount() { return 'not React'; }
}
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(
violations,
/^src\/renderer\/shell\/shell-frame\.tsx: shell code owns stateful React hooks$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/shell\/shell-frame\.tsx: shell imports forbidden implementation module:/u,
);
assertHasViolation(
violations,
/^src\/renderer\/shell\/custom-hook\.ts: shell code owns stateful React hooks$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/shell\/namespace-hook\.ts: shell code owns stateful React hooks$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/shell\/react-namespace-alias\.ts: shell code owns stateful React hooks$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/shell\/react-19-hooks\.tsx: shell code owns stateful React hooks$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/composition\/class-lifecycle\.tsx: composition code owns React class lifecycle methods$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/composition\/class-field-lifecycle\.tsx: composition code owns React class lifecycle methods$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/composition\/class-state\.tsx: composition code owns React class lifecycle methods$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/composition\/feature-namespace-hook\.ts: composition code owns stateful React hooks$/u,
);
assert.ok(
!violations.some((violation) => violation.includes('plain-class.ts')),
`Non-React classes must not be treated as React lifecycle owners:\n${violations.join('\n')}`,
);
},
);
});
it('rejects bridge debt that grows relative to the base ledger', async () => {
const debtPath = 'src/renderer/legacy-root.ts';
const currentConfig = architectureConfig({
rootDebt: {
[debtPath]: emptyDebt({ bridgePaths: { 'window.maka.sessions.list': 1 } }),
},
});
const baseConfig = architectureConfig({
rootDebt: {
[debtPath]: emptyDebt(),
},
});
await withDesktopFixture(
{
[debtPath]: 'void window.maka.sessions.list();',
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/^src\/renderer\/legacy-root\.ts: bridgePaths debt increased from 0 to 1$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/legacy-root\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u,
);
},
);
});
it('accepts the single pinned renderer module entry', async () => {
await withDesktopFixture(
rendererEntryContractFiles(),
(desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assert.deepEqual(checkRendererArchitecture({ config, desktopRoot }), []);
},
);
});
it('rejects replacing the pinned renderer module entry with an alternate source', async () => {
await withDesktopFixture(
rendererEntryContractFiles({
'src/renderer/index.html': `
<!doctype html>
<html><body><script type="module" src="/settings/alternate-entry.tsx"></script></body></html>
`,
'src/renderer/settings/alternate-entry.tsx': `
void window.maka.sessions.list();
export const alternateEntry = true;
`,
}),
(desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^src\/renderer\/index\.html: renderer entry contract requires exactly one empty external module script for \/main\.tsx$/u,
);
},
);
});
it('rejects adding another renderer module entry beside the pinned source', async () => {
await withDesktopFixture(
rendererEntryContractFiles({
'src/renderer/index.html': `
<!doctype html>
<html><body>
<script type="module" src="/main.tsx"></script>
<script type="module" src="/settings/alternate-entry.tsx"></script>
</body></html>
`,
'src/renderer/settings/alternate-entry.tsx': `export const alternateEntry = true;`,
}),
(desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^src\/renderer\/index\.html: renderer entry contract requires exactly one empty external module script for \/main\.tsx$/u,
);
},
);
});
it('rejects changing the packaged main-window loader to an alternate renderer document', async () => {
await withDesktopFixture(
rendererEntryContractFiles({
'src/main/main-renderer-loader.ts': `
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
interface MainRendererWindow {
loadFile(path: string): Promise<void>;
loadURL(url: string): Promise<void>;
}
interface MainRendererEntry {
readonly filePath: string;
readonly url: string;
readonly useDevServer: boolean;
}
export function resolveMainRendererEntry(
mainModuleDirectory: string,
viteDevServerUrl: string | undefined,
): MainRendererEntry {
const rendererEntryPath = join(mainModuleDirectory, '..', '..', 'dist-renderer', 'alternate.html');
const rendererEntryUrl = viteDevServerUrl ?? pathToFileURL(rendererEntryPath).href;
return Object.freeze({
filePath: rendererEntryPath,
url: rendererEntryUrl,
useDevServer: !!viteDevServerUrl,
});
}
export async function loadMainRenderer(
mainWindow: MainRendererWindow,
rendererEntry: MainRendererEntry,
): Promise<void> {
if (rendererEntry.useDevServer) {
await mainWindow.loadURL(rendererEntry.url);
} else {
await mainWindow.loadFile(rendererEntry.filePath);
}
}
`,
}),
(desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^src\/main\/main-renderer-loader\.ts: renderer loader must load only the pinned dist-renderer\/index\.html entry$/u,
);
},
);
});
it('rejects shadowing the pinned renderer path helpers behind local bindings', async () => {
const files = rendererEntryContractFiles();
files['src/main/main-renderer-loader.ts'] = files['src/main/main-renderer-loader.ts']
.replace("import { join } from 'node:path';", "import { join as pathJoin } from 'node:path';")
.replace(
"import { pathToFileURL } from 'node:url';",
`
import { pathToFileURL as nodePathToFileURL } from 'node:url';
function join(...parts) {
return pathJoin(...parts.slice(0, -1), 'alternate.html');
}
function pathToFileURL(path) {
return nodePathToFileURL(path);
}
`,
);
await withDesktopFixture(files, (desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^src\/main\/main-renderer-loader\.ts: renderer loader must load only the pinned dist-renderer\/index\.html entry$/u,
);
});
});
it('rejects reassigning the resolved renderer entry before loading it', async () => {
const files = rendererEntryContractFiles();
files['src/main/main-window.ts'] = files['src/main/main-window.ts']
.replace('const rendererEntry =', 'let rendererEntry =')
.replace(
'await loadMainRenderer(mainWindow, rendererEntry);',
`
rendererEntry = {
filePath: '/alternate.html',
url: 'file:///alternate.html',
useDevServer: false,
};
await loadMainRenderer(mainWindow, rendererEntry);
`,
);
await withDesktopFixture(files, (desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^src\/main\/main-window\.ts: main window must delegate exactly once to the pinned renderer loader$/u,
);
});
});
it('rejects mutating the frozen renderer entry before loading it', async () => {
const files = rendererEntryContractFiles();
files['src/main/main-window.ts'] = files['src/main/main-window.ts'].replace(
'await loadMainRenderer(mainWindow, rendererEntry);',
`
rendererEntry.filePath = '/alternate.html';
await loadMainRenderer(mainWindow, rendererEntry);
`,
);
await withDesktopFixture(files, (desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^src\/main\/main-window\.ts: main window must delegate exactly once to the pinned renderer loader$/u,
);
});
});
it('rejects aliasing the main window navigation API around the dedicated loader', async () => {
await withDesktopFixture(
rendererEntryContractFiles({
'src/main/main-window.ts': `
import { loadMainRenderer, resolveMainRendererEntry } from './main-renderer-loader.js';
async function createWindow() {
const rendererEntry = resolveMainRendererEntry(import.meta.dirname, process.env.VITE_DEV_SERVER_URL);
await loadMainRenderer(mainWindow, rendererEntry);
const alternateEntryPath = join(import.meta.dirname, '..', '..', 'dist-renderer', 'alternate.html');
const loadAlternateEntry = mainWindow.loadFile.bind(mainWindow);
await loadAlternateEntry(alternateEntryPath);
}
`,
}),
(desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^src\/main\/main-window\.ts: main window must delegate exactly once to the pinned renderer loader$/u,
);
},
);
});
it('rejects a computed second navigation inside the dedicated loader branch', async () => {
const files = rendererEntryContractFiles();
files['src/main/main-renderer-loader.ts'] = files['src/main/main-renderer-loader.ts'].replace(
'await mainWindow.loadFile(rendererEntry.filePath);',
`
await mainWindow.loadFile(rendererEntry.filePath);
await mainWindow['load' + 'File'](
rendererEntry.filePath.replace('index.html', 'alternate.html'),
);
`,
);
await withDesktopFixture(files, (desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^src\/main\/main-renderer-loader\.ts: renderer loader must load only the pinned dist-renderer\/index\.html entry$/u,
);
});
});
it('rejects remapping the Vite renderer root to an alternate source tree', async () => {
await withDesktopFixture(
rendererEntryContractFiles({
'vite.config.ts': `
export default defineConfig({
root: 'src/renderer/settings/alternate-root',
build: { outDir: '../../../../dist-renderer' },
});
`,
}),
(desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^vite\.config\.ts: Vite must build src\/renderer\/index\.html into dist-renderer without an input override$/u,
);
},
);
});
it('rejects shadowing the pinned Vite config factory behind a local binding', async () => {
const files = rendererEntryContractFiles();
files['vite.config.ts'] = files['vite.config.ts']
.replace("import { defineConfig } from 'vite';", "import { defineConfig as viteDefineConfig } from 'vite';")
.replace(
"const REPO_ROOT = '/fixture';",
`
const REPO_ROOT = '/fixture';
function defineConfig(config) {
return viteDefineConfig(config);
}
`,
);
await withDesktopFixture(files, (desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^vite\.config\.ts: Vite must build src\/renderer\/index\.html into dist-renderer without an input override$/u,
);
});
});
it('rejects pointing the Vite entry guard at a different renderer root', async () => {
const files = rendererEntryContractFiles();
files['vite.config.ts'] = files['vite.config.ts'].replace(
"rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer'))",
"rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer/settings/alternate-root'))",
);
await withDesktopFixture(files, (desktopRoot) => {
const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig());
assertHasViolation(
checkRendererArchitecture({ config, desktopRoot }),
/^vite\.config\.ts: Vite must build src\/renderer\/index\.html into dist-renderer without an input override$/u,
);
});
});
it('attests the canonical main source in the final Vite entry graph', () => {
assert.doesNotThrow(() =>
runRendererEntryBundleContract([
'\0vite/modulepreload-polyfill.js',
'/fixture/src/renderer/index.html?html-proxy&inline-css&index=0.css',
'/fixture/src/renderer/main.tsx',
]),
);
});
it('does not apply the renderer entry contract to Storybook builds', () => {
const plugin = rendererEntryContractPlugin('/fixture/src/renderer');
plugin.configResolved({ root: '/fixture/storybook' });
assert.doesNotThrow(() =>
plugin.generateBundle.call(
{
emitFile() {
assert.fail('a non-renderer build must not emit a renderer attestation');
},
},
{},
{},
),
);
});
it('rejects a Vite HTML transform that swaps the final entry graph', () => {
assert.throws(
() =>
runRendererEntryBundleContract([
'\0vite/modulepreload-polyfill.js',
'/fixture/src/renderer/settings/alternate-entry.tsx',
]),
/renderer entry contract requires src\/renderer\/index\.html to import only src\/renderer\/main\.tsx/u,
);
});
it('rejects executable HTML injected around the canonical module entry', () => {
assert.doesNotThrow(() => assertRendererEntryHtml(canonicalRendererEntryHtml()));
const emittedHtml = canonicalRendererEntryHtml().replace(
'/main.tsx',
'./assets/index-canonical.js',
);
assert.doesNotThrow(() =>
assertRendererEntryHtml(emittedHtml, './assets/index-canonical.js'),
);
assert.throws(
() => assertRendererEntryHtml(emittedHtml, './assets/index-other.js'),
/renderer entry HTML contract forbids transformed executable or navigation surfaces/u,
);
assert.throws(
() =>
assertRendererEntryHtml(
canonicalRendererEntryHtml(
'<script>window.maka.sessions.list()</script>',
"script-src 'self' 'unsafe-inline'",
),
),
/renderer entry HTML contract forbids transformed executable or navigation surfaces/u,
);
assert.throws(
() =>
assertRendererEntryHtml(
canonicalRendererEntryHtml('<iframe src="/settings/alternate.html"></iframe>'),
),
/renderer entry HTML contract forbids transformed executable or navigation surfaces/u,
);
});
it('rejects a newly allowlisted root debt file relative to the base ledger', async () => {
const debtPath = 'src/renderer/new-root-debt.ts';
const currentConfig = architectureConfig({
rootDebt: { [debtPath]: emptyDebt() },
});
const baseConfig = architectureConfig();
await withDesktopFixture(
{
[debtPath]: 'export const newRootDebt = true;',
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/^src\/renderer\/new-root-debt\.ts: new rootDebt debt entries are forbidden$/u,
);
},
);
});
it('rejects deleting root debt while the existing root entry is still dirty', async () => {
const debtPath = 'src/renderer/main.tsx';
const currentConfig = architectureConfig({ legacyRendererFiles: [debtPath] });
const baseConfig = architectureConfig({
legacyRendererFiles: [debtPath],
rootDebt: {
[debtPath]: emptyDebt({
bridgePaths: { 'window.maka.sessions.list': 1 },
hookCalls: { useEffect: 1 },
}),
},
});
await withDesktopFixture(
{
[debtPath]: `
import { useEffect } from 'react';
void window.maka.sessions.list();
export function RendererEntry() { useEffect(() => undefined, []); return null; }
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/^src\/renderer\/main\.tsx: permanent root entry guard cannot be removed while the source exists$/u,
);
},
);
});
it('rejects removing the permanent root guard while a clean thin entry still exists', async () => {
const debtPath = 'src/renderer/main.tsx';
const currentConfig = architectureConfig({ legacyRendererFiles: [debtPath] });
const baseConfig = architectureConfig({
legacyRendererFiles: [debtPath],
rootDebt: {
[debtPath]: emptyDebt({
bridgePaths: { 'window.maka.onboarding.getSnapshot': 1 },
hookCalls: { useEffect: 1 },
}),
},
});
await withDesktopFixture(
{
[debtPath]: `
import { mountDesktopApplication } from './bootstrap/mount-desktop-application.js';
mountDesktopApplication();
`,
'src/renderer/bootstrap/mount-desktop-application.ts': `
export function mountDesktopApplication() {}
`,
},
(desktopRoot) => {
assertHasViolation(
violationsFor(desktopRoot, currentConfig, baseConfig),
/^src\/renderer\/main\.tsx: permanent root entry guard cannot be removed while the source exists$/u,
);
},
);
});
it('rejects bridge debt returning to a retained clean root entry', async () => {
const debtPath = 'src/renderer/main.tsx';
const cleanSource = `
import { mountDesktopApplication } from './bootstrap/mount-desktop-application.js';
mountDesktopApplication();
`;
const pollutedSource = `
import { mountDesktopApplication } from './bootstrap/mount-desktop-application.js';
void window.maka.sessions.list();
mountDesktopApplication();
`;
const baseConfig = architectureConfig({
legacyRendererFiles: [debtPath],
rootDebt: { [debtPath]: debtForSource(cleanSource, debtPath) },
});
const currentConfig = architectureConfig({
legacyRendererFiles: [debtPath],
rootDebt: { [debtPath]: debtForSource(pollutedSource, debtPath) },
});
await withDesktopFixture(
{
[debtPath]: pollutedSource,
'src/renderer/bootstrap/mount-desktop-application.ts': `
export function mountDesktopApplication() {}
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/^src\/renderer\/main\.tsx: bridgePaths debt increased from 0 to 1$/u,
);
assertHasViolation(
violations,
/^src\/renderer\/main\.tsx: new or increased bridgePaths debt window\.maka\.sessions\.list$/u,
);
},
);
});
it('allows removing the permanent root guard after its source is deleted', async () => {
const debtPath = 'src/renderer/main.tsx';
const currentConfig = architectureConfig();
const baseConfig = architectureConfig({
legacyRendererFiles: [debtPath],
rootDebt: { [debtPath]: emptyDebt() },
});
await withDesktopFixture(
{
'src/renderer/bootstrap/mount-desktop-application.ts': `
export function mountDesktopApplication() {}
`,
},
(desktopRoot) => {
assert.deepEqual(violationsFor(desktopRoot, currentConfig, baseConfig), []);
},
);
});
it('rejects retiring root debt by moving state and storage ownership into bootstrap', async () => {
const debtPath = 'src/renderer/main.tsx';
const currentConfig = architectureConfig({ legacyRendererFiles: [debtPath] });
const baseConfig = architectureConfig({
legacyRendererFiles: [debtPath],
rootDebt: { [debtPath]: emptyDebt() },
});
await withDesktopFixture(
{
[debtPath]: `
import { PollutedBootstrap } from './bootstrap/polluted-bootstrap.js';
void PollutedBootstrap;
`,
'src/renderer/bootstrap/polluted-bootstrap.tsx': `
import { useState } from 'react';
export function PollutedBootstrap() {
const [value] = useState(() => localStorage.getItem('root-state'));
return <main>{value}</main>;
}
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/bootstrap\/polluted-bootstrap\.tsx: bootstrap code owns stateful React hooks/u,
);
assertHasViolation(
violations,
/bootstrap\/polluted-bootstrap\.tsx: bootstrap code directly accesses browser environment capabilities/u,
);
assertHasViolation(
violations,
/^src\/renderer\/main\.tsx: permanent root entry guard cannot be removed while the source exists$/u,
);
},
);
});
it('allows new legacy files inside an explicitly approved growth directory', async () => {
const growthDirectory = 'src/renderer/settings';
const path = `${growthDirectory}/new-preference.ts`;
const currentConfig = architectureConfig({
legacyGrowthDirectories: [growthDirectory],
legacyRendererFiles: [path],
});
const baseConfig = architectureConfig({
legacyGrowthDirectories: [growthDirectory],
});
await withDesktopFixture(
{
[path]: 'export const newPreference = true;',
},
(desktopRoot) => {
assert.deepEqual(violationsFor(desktopRoot, currentConfig, baseConfig), []);
},
);
});
it('rejects arbitrary new unclassified renderer source names relative to the base ledger', async () => {
const path = 'src/renderer/session-coordinator.mts';
const currentConfig = architectureConfig({ legacyRendererFiles: [path] });
const baseConfig = architectureConfig();
await withDesktopFixture(
{
[path]: 'export const coordinator = true;',
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot, currentConfig, baseConfig);
assertHasViolation(
violations,
/^src\/renderer\/session-coordinator\.mts: new unclassified renderer source files are forbidden outside approved legacy directories$/u,
);
},
);
});
it('rejects unbudgeted legacy imports from every strict ownership zone', async () => {
await withDesktopFixture(
{
'src/renderer/legacy-helper.ts': 'export const legacy = true;',
'src/renderer/features/alpha/controller.ts': `
import { legacy } from '../../legacy-helper.js';
export const featureValue = legacy;
`,
'src/renderer/application/session/session-scope.ts': `
import { legacy } from '../../legacy-helper.js';
export const applicationValue = legacy;
`,
'src/renderer/composition/desktop-application.ts': `
import { legacy } from '../legacy-helper.js';
export const compositionValue = legacy;
`,
'src/renderer/platform/desktop/create-services.ts': `
import { legacy } from '../../legacy-helper.js';
export const platformValue = legacy;
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(violations, /feature imports unbudgeted renderer legacy code/u);
assertHasViolation(violations, /application authority imports an outer implementation/u);
assertHasViolation(violations, /composition imports a forbidden implementation module/u);
assertHasViolation(violations, /Desktop adapter imports unbudgeted renderer legacy code/u);
},
);
});
it('detects bridge aliases, TypeScript wrappers, and aliased React hooks', async () => {
await withDesktopFixture(
{
'src/renderer/application/bridge-aliases.ts': `
const desktopWindow = window;
void desktopWindow.maka.sessions.list();
void globalThis.maka.sessions.list();
void self['maka'].sessions.list();
void (window as Window).maka.sessions.list();
void window!.maka.sessions.list();
const { maka } = window;
void maka.sessions.list();
`,
'src/renderer/shell/aliased-hook.ts': `
import { useState as state } from 'react';
export function useFrameState() { return state(false); }
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(violations, /bridge-aliases\.ts: application code accesses the Desktop global bridge/u);
assertHasViolation(violations, /aliased-hook\.ts: shell code owns stateful React hooks/u);
},
);
});
it('retains full bridge method paths after the Desktop bridge is aliased', () => {
const analysis = analyzeRendererSource(
`
const bridge = window.maka;
void bridge.sessions.list();
const { maka: desktop } = window;
void desktop.transcripts.open('session-a');
`,
'src/renderer/application/bridge-method-aliases.ts',
);
assert.equal(analysis.bridgePaths['window.maka.sessions.list'], 1);
assert.equal(analysis.bridgePaths['window.maka.transcripts.open'], 1);
});
it('retains stateful hook ownership through local aliases', () => {
const analysis = analyzeRendererSource(
`
import { useState as state } from 'react';
const localState = state;
localState(false);
`,
'src/renderer/shell/hook-method-aliases.ts',
);
assert.equal(analysis.hookCalls.useState, 1);
const reassigned = analyzeRendererSource(
`
import { useEffect, useState } from 'react';
let hook = useState;
hook = useEffect;
hook(false);
`,
'src/renderer/shell/reassigned-hook-alias.ts',
);
assert.equal(reassigned.hookCalls.useState, 1);
assert.equal(reassigned.hookCalls.useEffect, 1);
const repeatedVar = analyzeRendererSource(
`
import React, { useState } from 'react';
function legacyAliases() {
var hook;
var hook = useState;
var R;
var R = React;
var NestedReact;
if (true) {
var NestedReact = React;
}
hook(false);
R.useState(false);
NestedReact.useState(false);
}
`,
'src/renderer/shell/repeated-var-hook-alias.ts',
);
assert.equal(repeatedVar.hookCalls.useState, 3);
});
it('recognizes React 19 use and namespace-destructured custom Hooks', async () => {
const analysis = analyzeRendererSource(
`
import { use as read } from 'react';
import * as React from 'react';
import * as hooks from '@astryxdesign/core/hooks';
const R = React;
const { use: readFromNamespace } = R;
const hookNamespace = hooks;
const { useHotkeys: hotkeys } = hookNamespace;
const { useHotkeys } = hooks as typeof hooks;
const { useHotkeys: defaultHotkeys = () => undefined } = hooks as typeof hooks;
read(Promise.resolve('direct'));
React.use(Promise.resolve('member'));
readFromNamespace(Promise.resolve('destructured'));
hotkeys([]);
useHotkeys([]);
defaultHotkeys([]);
function shadowedReact(React: { use(value: unknown): unknown }) {
return React.use(Promise.resolve('shadowed'));
}
function shadowedHook(useHotkeys: (bindings: unknown[]) => unknown) {
return useHotkeys([]);
}
function blockShadowedReact() {
const React = { use: (value: unknown) => value };
return React.use(Promise.resolve('block-shadowed'));
}
function blockShadowedHook() {
const useHotkeys = (bindings: unknown[]) => bindings;
return useHotkeys([]);
}
function lateDestructuredAlias() {
const lateHotkeys = lateUseHotkeys;
return lateHotkeys([]);
}
const { useHotkeys: lateUseHotkeys } = hooks as typeof hooks;
`,
'src/renderer/shell/react-use-aliases.tsx',
);
assert.equal(analysis.hookCalls.use, 3);
assert.equal(analysis.hookCalls.useHotkeys, 4);
const nonReactUse = analyzeRendererSource(
`
import { use } from './plain-helper.js';
use('not-react');
`,
'src/renderer/shell/plain-use.ts',
);
assert.deepEqual(nonReactUse.hookCalls, {});
await withDesktopFixture(
{
'src/renderer/shell/react-use.tsx': `
import { use } from 'react';
export function ShellRead() { return use(Promise.resolve('shell')); }
`,
'src/renderer/composition/react-namespace-use.tsx': `
import * as React from 'react';
export function CompositionRead() { return React.use(Promise.resolve('composition')); }
`,
'src/renderer/platform/desktop/destructured-hook.ts': `
import * as hooks from '@astryxdesign/core/hooks';
const { useHotkeys = () => undefined } = hooks as typeof hooks;
export function installHotkeys() { return useHotkeys([]); }
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(violations, /shell\/react-use\.tsx: shell code owns stateful React hooks/u);
assertHasViolation(
violations,
/composition\/react-namespace-use\.tsx: composition code owns stateful React hooks/u,
);
assertHasViolation(
violations,
/platform\/desktop\/destructured-hook\.ts: platform code owns stateful React hooks/u,
);
},
);
});
it('parses TypeScript and static-template dependencies and fails closed on dynamic paths', async () => {
await withDesktopFixture(
{
'src/renderer/features/alpha/import-equals.ts': `
import beta = require('../beta/private.js');
export const value = beta;
`,
'src/renderer/features/alpha/dynamic.ts': `
export function load(path: string) { return import(path); }
`,
'src/renderer/features/beta/private.ts': 'export const beta = true;',
'src/renderer/application/contracts/deep-type.ts': `
export type Deep = import('../../features/alpha/private.js').Deep;
`,
'src/renderer/composition/static-template.ts': `
export const deep = import(\`../features/alpha/private.js\`);
`,
'src/renderer/composition/import-meta-glob.ts': `
export const privateFeatures = import.meta.glob('../features/alpha/private.ts');
`,
},
(desktopRoot) => {
const violations = violationsFor(desktopRoot);
assertHasViolation(violations, /import-equals\.ts: feature alpha imports feature beta/u);
assertHasViolation(violations, /dynamic\.ts: feature code contains a non-static import or require/u);
assertHasViolation(violations, /deep-type\.ts: feature imports must use index/u);
assertHasViolation(violations, /static-template\.ts: feature imports must use index/u);
assertHasViolation(violations, /import-meta-glob\.ts: composition code contains a non-static import or require/u);
},
);
});
it('forbids exposing a feature testing entry through its public index', async () => {
await withDesktopFixture(
{
'src/renderer/features/alpha/index.ts': `export * from './testing.js';`,
'src/renderer/features/alpha/testing.ts': 'export const fake = true;',
},
(desktopRoot) => {
assertHasViolation(
violationsFor(desktopRoot),
/feature .*testing entry/u,
);
},
);
});
it('allows legacy AppShell dependency replacement with feature public APIs and application contracts', async () => {
const appShellPath = 'src/renderer/app-shell.tsx';
const appShellSource = `
import { AlphaHost } from './features/alpha/index.js';
import type { SessionScope } from './application/contracts/session-scope.js';
export function AppShell(props: { readonly scope: SessionScope }) {
return <AlphaHost scope={props.scope} />;
}
`;
const currentDebt = debtForSource(appShellSource, appShellPath);
const baseDebt = {
...currentDebt,
dependencyPaths: {
'./legacy-alpha-owner.js': 1,
'./legacy-session-owner.js': 1,
},
};
const ownership = [
{
capability: 'fixture-app-shell',
targetZone: 'shell',
legacyPaths: [appShellPath],
},
];
const currentConfig = architectureConfig({
legacyFiles: { [appShellPath]: currentDebt },
legacyRendererFiles: [appShellPath],
ownership,
});
const baseConfig = architectureConfig({
legacyFiles: { [appShellPath]: baseDebt },
legacyRendererFiles: [appShellPath],
ownership,
});
await withDesktopFixture(
{
[appShellPath]: appShellSource,
'src/renderer/features/alpha/index.ts': `
export function AlphaHost(_props: unknown) { return null; }
`,
'src/renderer/application/contracts/session-scope.ts': `
export interface SessionScope { readonly sessionId?: string }
`,
},
(desktopRoot) => {
assert.deepEqual(violationsFor(desktopRoot, currentConfig, baseConfig), []);
},
);
});
it('rejects replacing legacy AppShell debt with a feature private import', async () => {
const appShellPath = 'src/renderer/app-shell.tsx';
const appShellSource = `
import { alphaController } from './features/alpha/controller.js';
export const AppShell = alphaController;
`;
const currentDebt = debtForSource(appShellSource, appShellPath);
const baseDebt = {
...currentDebt,
dependencyPaths: { './legacy-alpha-owner.js': 1 },
};
const ownership = [
{
capability: 'fixture-app-shell',
targetZone: 'shell',
legacyPaths: [appShellPath],
},
];
const currentConfig = architectureConfig({
legacyFiles: { [appShellPath]: currentDebt },
legacyRendererFiles: [appShellPath],
ownership,
});
const baseConfig = architectureConfig({
legacyFiles: { [appShellPath]: baseDebt },
legacyRendererFiles: [appShellPath],
ownership,
});
await withDesktopFixture(
{
[appShellPath]: appShellSource,
'src/renderer/features/alpha/controller.ts': 'export const alphaController = true;',
},
(desktopRoot) => {
assertHasViolation(
violationsFor(desktopRoot, currentConfig, baseConfig),
/^src\/renderer\/app-shell\.tsx: new dependency debt \.\/features\/alpha\/controller\.js$/u,
);
},
);
});
it('fails closed when the CLI base argument is missing or invalid', () => {
const checker = fileURLToPath(new URL('./check-renderer-architecture.mjs', import.meta.url));
const repoRoot = fileURLToPath(new URL('../../..', import.meta.url));
const missing = spawnSync(process.execPath, [checker, '--base'], {
cwd: repoRoot,
encoding: 'utf8',
});
const invalid = spawnSync(process.execPath, [checker, '--base', 'definitely-not-a-renderer-architecture-ref'], {
cwd: repoRoot,
encoding: 'utf8',
});
assert.notEqual(missing.status, 0);
assert.match(missing.stderr, /usage: check-renderer-architecture/u);
assert.notEqual(invalid.status, 0);
assert.match(invalid.stderr, /base ref does not resolve to a commit/u);
});
});