mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
5a362ec752
Follow-ups from the bundler/CI speed work, re-validated against latest main. The typescript package is gone from the toolchain: - pnpm typecheck stays on tsgo; the typecheck:tsc escape hatch is removed along with the typescript devDependency. - args.test.ts extracted cli.ts dispatch literals through the TypeScript compiler API - the only remaining consumer. It now walks the same AST via oxc-parser (matching the OXC lint/format/build stack); both implementations extract an identical 14-literal set from cli.ts, verified side by side before the swap. The substitution-free template case ts.isStringLiteralLike covered is preserved. - dts bundling is unaffected: the tsdown build uses the tsgo backend and builds green with no typescript package installed. Test fixes for containerized agent environments: - The missing-binary doctor-guidance web provider test pins Node 24 via the file's existing withNodeRuntimeVersion helper, so it asserts the setup hint instead of inheriting the host Node and failing on Node 22 (the supported engines floor). - The clean-xcuitest cleanup-failure smoke test skips as root: chmod 0o500 cannot force a removal failure when the process bypasses directory permissions. AGENTS.md toolchain notes updated to match. Claude-Session: https://claude.ai/code/session_01FqeW8sA2ZnvnftdvpCqFMS Co-authored-by: Claude <noreply@anthropic.com>
133 lines
5.5 KiB
TypeScript
133 lines
5.5 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { runCmdSync } from '../../src/utils/exec.ts';
|
|
|
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
const scriptPath = path.join(repoRoot, 'scripts', 'clean-xcuitest-derived.mjs');
|
|
|
|
test('clean-xcuitest ios removes only transient root entries and reports preserved cache entries', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-clean-xcuitest-ios-'));
|
|
try {
|
|
const derivedRoot = path.join(homeDir, '.agent-device', 'apple-runner', 'derived');
|
|
fs.mkdirSync(path.join(derivedRoot, 'Build'), { recursive: true });
|
|
fs.mkdirSync(path.join(derivedRoot, 'Logs'), { recursive: true });
|
|
fs.mkdirSync(path.join(derivedRoot, 'cache-warm-runner'), { recursive: true });
|
|
fs.mkdirSync(path.join(derivedRoot, 'macos'), { recursive: true });
|
|
|
|
const result = runCleanXcuitest(homeDir, 'ios');
|
|
assert.equal(result.exitCode, 0, result.stderr);
|
|
assert.equal(
|
|
result.stdout.trim(),
|
|
`Removed iOS XCTest transient entries under ${derivedRoot}: Build, Logs; kept cache-warm-runner, macos`,
|
|
);
|
|
assert.equal(fs.existsSync(path.join(derivedRoot, 'Build')), false);
|
|
assert.equal(fs.existsSync(path.join(derivedRoot, 'Logs')), false);
|
|
assert.equal(fs.existsSync(path.join(derivedRoot, 'cache-warm-runner')), true);
|
|
assert.equal(fs.existsSync(path.join(derivedRoot, 'macos')), true);
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('clean-xcuitest ios reports a no-op when only preserved entries remain', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-clean-xcuitest-ios-noop-'));
|
|
try {
|
|
const derivedRoot = path.join(homeDir, '.agent-device', 'apple-runner', 'derived');
|
|
fs.mkdirSync(path.join(derivedRoot, 'cache-warm-runner'), { recursive: true });
|
|
fs.mkdirSync(path.join(derivedRoot, 'tvos'), { recursive: true });
|
|
|
|
const result = runCleanXcuitest(homeDir, 'ios');
|
|
assert.equal(result.exitCode, 0, result.stderr);
|
|
assert.equal(
|
|
result.stdout.trim(),
|
|
`Skipped iOS XCTest cleanup under ${derivedRoot}: no transient entries found; kept cache-warm-runner, tvos`,
|
|
);
|
|
assert.equal(fs.existsSync(path.join(derivedRoot, 'cache-warm-runner')), true);
|
|
assert.equal(fs.existsSync(path.join(derivedRoot, 'tvos')), true);
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('clean-xcuitest macos skips a missing derived directory', () => {
|
|
const homeDir = fs.mkdtempSync(
|
|
path.join(os.tmpdir(), 'agent-device-clean-xcuitest-macos-missing-'),
|
|
);
|
|
try {
|
|
const derivedPath = path.join(homeDir, '.agent-device', 'apple-runner', 'derived', 'macos');
|
|
|
|
const result = runCleanXcuitest(homeDir, 'macos');
|
|
assert.equal(result.exitCode, 0, result.stderr);
|
|
assert.equal(result.stdout.trim(), `Skipped macOS XCTest cleanup: ${derivedPath} not found`);
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('clean-xcuitest macos removes the entire platform directory when present', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-clean-xcuitest-macos-'));
|
|
try {
|
|
const derivedPath = path.join(homeDir, '.agent-device', 'apple-runner', 'derived', 'macos');
|
|
fs.mkdirSync(derivedPath, { recursive: true });
|
|
fs.writeFileSync(path.join(derivedPath, 'marker.txt'), 'ok', 'utf8');
|
|
|
|
const result = runCleanXcuitest(homeDir, 'macos');
|
|
assert.equal(result.exitCode, 0, result.stderr);
|
|
assert.equal(result.stdout.trim(), `Removed macOS XCTest derived data: ${derivedPath}`);
|
|
assert.equal(fs.existsSync(derivedPath), false);
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
// Root bypasses directory permissions, so chmod cannot force the cleanup
|
|
// failure this test provokes (common in containerized agent environments).
|
|
const isRoot = process.getuid?.() === 0;
|
|
|
|
test(
|
|
'clean-xcuitest reports cleanup failures directly',
|
|
{ skip: isRoot && 'running as root: chmod cannot force a cleanup failure' },
|
|
() => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-clean-xcuitest-failure-'));
|
|
const derivedRoot = path.join(homeDir, '.agent-device', 'apple-runner', 'derived');
|
|
try {
|
|
fs.mkdirSync(path.join(derivedRoot, 'Build'), { recursive: true });
|
|
fs.chmodSync(derivedRoot, 0o500);
|
|
|
|
const result = runCleanXcuitest(homeDir, 'ios', { allowFailure: true });
|
|
assert.equal(result.exitCode, 1);
|
|
assert.equal(result.stdout, '');
|
|
assert.match(
|
|
result.stderr.trim(),
|
|
new RegExp(`^Failed to clean iOS XCTest derived data under ${escapeRegExp(derivedRoot)}: `),
|
|
);
|
|
} finally {
|
|
fs.chmodSync(derivedRoot, 0o700);
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
|
|
function runCleanXcuitest(homeDir: string, ...args: Array<string | { allowFailure?: boolean }>) {
|
|
const lastArg = args.at(-1);
|
|
const options = typeof lastArg === 'object' ? lastArg : {};
|
|
const platforms = (options === lastArg ? args.slice(0, -1) : args) as string[];
|
|
return runCmdSync(process.execPath, [scriptPath, ...platforms], {
|
|
cwd: repoRoot,
|
|
env: {
|
|
...process.env,
|
|
HOME: homeDir,
|
|
},
|
|
allowFailure: options?.allowFailure,
|
|
timeoutMs: 30_000,
|
|
});
|
|
}
|
|
|
|
function escapeRegExp(value: string) {
|
|
return value.replaceAll(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
|
}
|