Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ on:

jobs:
claude-review:
# Fork PRs cannot receive the OIDC token required by this action.
if: github.event.pull_request.head.repo.full_name == github.repository
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
Expand Down Expand Up @@ -41,4 +43,3 @@ jobs:
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options

2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
cache: npm

- name: Install dependencies
run: npm ci
run: npm ci --force

- name: Run node test suite
run: npm test
217 changes: 217 additions & 0 deletions scripts/lib/script-command-runner-harness.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import { build } from 'esbuild';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const runnerPath = path.join(root, 'src/main/script-command-runner.ts');

function makeElectronMockSource(appPaths) {
return `
const paths = ${JSON.stringify(appPaths)};
export const app = {
getPath(name) {
return paths[name] || paths.userData;
},
};
`;
}

function makeSettingsMockSource(scriptCommandFolders) {
return `
export function loadSettings() {
return { scriptCommandFolders: ${JSON.stringify(scriptCommandFolders)} };
}
`;
}

function makeChildProcessMockSource(childProcessKey) {
return `
const mock = globalThis[${JSON.stringify(childProcessKey)}];

export function spawn(...args) {
return mock.spawn(...args);
}
`;
}

function makeInstrumentedFsSource(metricsKey) {
return `
import realFs from 'node:fs';

const metrics = globalThis[${JSON.stringify(metricsKey)}];

function countReadFile(value, options) {
if (Buffer.isBuffer(value)) return value.byteLength;
const encoding = typeof options === 'string'
? options
: options && typeof options === 'object' && options.encoding
? options.encoding
: 'utf8';
return Buffer.byteLength(String(value), encoding);
}

export const constants = realFs.constants;
export const existsSync = realFs.existsSync.bind(realFs);
export const mkdirSync = realFs.mkdirSync.bind(realFs);
export const readdirSync = realFs.readdirSync.bind(realFs);
export const writeFileSync = realFs.writeFileSync.bind(realFs);
export const chmodSync = realFs.chmodSync.bind(realFs);
export const unlinkSync = realFs.unlinkSync.bind(realFs);
export const openSync = (...args) => {
metrics.openSyncCalls += 1;
return realFs.openSync(...args);
};
export const closeSync = realFs.closeSync.bind(realFs);
export const accessSync = realFs.accessSync.bind(realFs);
export const readFileSync = (filePath, options) => {
const value = realFs.readFileSync(filePath, options);
metrics.readFileSyncCalls += 1;
metrics.readFileSyncBytes += countReadFile(value, options);
return value;
};
export const readSync = (...args) => {
const bytesRead = realFs.readSync(...args);
metrics.readSyncCalls += 1;
metrics.readSyncBytes += Math.max(0, Number(bytesRead) || 0);
return bytesRead;
};

export default {
...realFs,
constants,
existsSync,
mkdirSync,
readdirSync,
writeFileSync,
chmodSync,
unlinkSync,
openSync,
closeSync,
accessSync,
readFileSync,
readSync,
};
`;
}

function resetMetrics(metrics) {
for (const key of Object.keys(metrics)) {
metrics[key] = 0;
}
}

export async function loadScriptCommandRunner({
homeDir = os.homedir(),
tempDir = os.tmpdir(),
userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'supercmd-user-data-')),
scriptCommandFolders = [],
instrumentFs = false,
mockChildProcess = false,
} = {}) {
const metricsKey = `__supercmdScriptCommandFsMetrics_${Date.now()}_${Math.random()
.toString(36)
.slice(2)}`;
const childProcessKey = `__supercmdScriptCommandChildProcess_${Date.now()}_${Math.random()
.toString(36)
.slice(2)}`;
const metrics = {
readFileSyncCalls: 0,
readFileSyncBytes: 0,
readSyncCalls: 0,
readSyncBytes: 0,
openSyncCalls: 0,
};
const childProcess = {
spawn() {
throw new Error('Unexpected child_process.spawn call');
},
};
globalThis[metricsKey] = metrics;
if (mockChildProcess) {
globalThis[childProcessKey] = childProcess;
}

const plugins = [
{
name: 'supercmd-script-command-runner-mocks',
setup(pluginBuild) {
pluginBuild.onResolve({ filter: /^electron$/ }, () => ({
path: 'electron-mock',
namespace: 'supercmd-mocks',
}));
pluginBuild.onLoad({ filter: /^electron-mock$/, namespace: 'supercmd-mocks' }, () => ({
contents: makeElectronMockSource({ home: homeDir, temp: tempDir, userData: userDataDir }),
loader: 'js',
}));

pluginBuild.onResolve({ filter: /^\.\/settings-store$/ }, () => ({
path: 'settings-store-mock',
namespace: 'supercmd-mocks',
}));
pluginBuild.onLoad({ filter: /^settings-store-mock$/, namespace: 'supercmd-mocks' }, () => ({
contents: makeSettingsMockSource(scriptCommandFolders),
loader: 'js',
}));
},
},
];

if (mockChildProcess) {
plugins.push({
name: 'supercmd-script-command-child-process-mock',
setup(pluginBuild) {
pluginBuild.onResolve({ filter: /^child_process$/ }, () => ({
path: 'child-process-mock',
namespace: 'supercmd-child-process',
}));
pluginBuild.onLoad({ filter: /^child-process-mock$/, namespace: 'supercmd-child-process' }, () => ({
contents: makeChildProcessMockSource(childProcessKey),
loader: 'js',
}));
},
});
}

if (instrumentFs) {
plugins.push({
name: 'supercmd-script-command-fs-instrumentation',
setup(pluginBuild) {
pluginBuild.onResolve({ filter: /^fs$/ }, () => ({
path: 'fs-instrumented',
namespace: 'supercmd-fs',
}));
pluginBuild.onLoad({ filter: /^fs-instrumented$/, namespace: 'supercmd-fs' }, () => ({
contents: makeInstrumentedFsSource(metricsKey),
loader: 'js',
}));
},
});
}

const result = await build({
entryPoints: [runnerPath],
bundle: true,
platform: 'node',
format: 'esm',
write: false,
plugins,
});

const output = result.outputFiles[0]?.text;
if (!output) {
throw new Error('Failed to bundle script-command-runner.ts');
}

const moduleUrl = `data:text/javascript;base64,${Buffer.from(output).toString('base64')}#${metricsKey}`;
const module = await import(moduleUrl);

return {
module,
metrics,
childProcess,
resetMetrics: () => resetMetrics(metrics),
root,
};
}
56 changes: 44 additions & 12 deletions scripts/lib/ts-import.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
// Import a self-contained TypeScript module from a test by transpiling it with
// esbuild on the fly. This lets the recovery tests run the *real* production
// source (renderer-recovery.ts, reload-budget.ts) instead of grepping it.
//
// Only works for modules with no relative imports of their own — the recovery
// helpers are deliberately dependency-free for exactly this reason.
// Import a TypeScript module from a test by bundling it with esbuild on the fly.
// Tests may pass simple module stubs for Node built-ins such as `https`.

import { transform } from 'esbuild';
import fs from 'node:fs';
import { build } from 'esbuild';
import path from 'node:path';

export async function importTs(absPath) {
const src = fs.readFileSync(absPath, 'utf8');
const { code } = await transform(src, { loader: 'ts', format: 'esm' });
const dataUrl = 'data:text/javascript;base64,' + Buffer.from(code).toString('base64');
let importNonce = 0;

export async function importTs(absPath, options = {}) {
const resolvedPath = path.resolve(absPath);
const result = await build({
absWorkingDir: options.root || process.cwd(),
entryPoints: [resolvedPath],
bundle: true,
write: false,
format: 'esm',
platform: 'node',
target: options.target || 'node20',
logLevel: 'silent',
plugins: [stubPlugin(options.stubs || {})],
});
const code = result.outputFiles[0].text;
const dataUrl = [
'data:text/javascript;base64,',
Buffer.from(code).toString('base64'),
`#${encodeURIComponent(resolvedPath)}-${importNonce++}`,
].join('');
return import(dataUrl);
}

function stubPlugin(stubs) {
const stubSpecifiers = new Set(Object.keys(stubs));
return {
name: 'ts-import-stubs',
setup(esbuild) {
esbuild.onResolve({ filter: /.*/ }, (args) => {
if (stubSpecifiers.has(args.path)) {
return { path: args.path, namespace: 'ts-import-stub' };
}
return null;
});
esbuild.onLoad({ filter: /.*/, namespace: 'ts-import-stub' }, (args) => ({
contents: stubs[args.path],
loader: 'js',
}));
},
};
}
Loading
Loading