Files
callstack__agent-device/scripts/package-apple-runner-source.mjs
Michał Pierzchała c0fc822e80 chore: remove dead code and tune fallow's dead-code rules (#1363)
Evaluated knip (webpro-nl/knip) against the fallow setup already in the
repo, cleaned up everything it surfaced, then removed knip again: measured
head-to-head on the same tree, fallow is a strict superset once two
switches it already supports are flipped.

Dead code removed:
- `daemon/artifact-materialization.ts` (224 lines) had no production
  caller, only its own test. Removing it exposed that
  `downloadArtifactToTempDir` and the whole URL-fetch-with-redirects path
  in `artifact-download.ts` were reachable only through it — the live
  upload paths use the incoming-request helpers instead. That file goes
  348 -> 123 lines. `readZipEntries` then fell out of `artifact-archive.ts`.
- Dead test-helper exports: 12 unused re-exports and 6 needlessly-exported
  mocks in `session-test-harness.ts`, dead barrel entries in
  `__tests__/test-utils/index.ts`, plus `withMockedXcrun`, `matchesSchema`,
  `IOS_FRAME`, `IOS_TAB_FRAME`, `snapshotWithOffscreenContent`.
- `androidSnapshotHelperOutput` was duplicated byte-for-byte in
  `provider-scenarios/android-world.ts`; it now imports the shared copy.
- 8 unreferenced type aliases, and 17 redundant type re-export lines in
  `client/client-types.ts`. The published `.d.ts` is byte-identical before
  and after all 19 files: those types already reach consumers through
  `contracts/*` via `CommandResult<...>`, so this is not an API change.

Tooling:
- `.fallowrc.json` gains `includeEntryExports`,
  `ignoreExportsUsedInFile: {type, interface}` and `unused-types: warn`.
  That combination is what made the findings above visible; the previous
  config was quiet mainly because of its own suppression list.
- Dropped 2 now-obsolete `ignoreExports` suppressions, added 3 documented
  ones (published `sdk/*` surface, tool-config `default` exports, and the
  `AssertTrue<...>` totality guards that exist only to satisfy
  `noUnusedLocals`).

`unused-types` stays at `warn`: 72 pre-existing type re-export lines across
27 files remain, tracked separately. Every other fallow detector is at zero.
2026-07-22 17:16:02 +02:00

207 lines
5.9 KiB
JavaScript

#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const UNIT_TEST_CONDITION = 'AGENT_DEVICE_RUNNER_UNIT_TESTS';
const SOURCE_DIR = path.join('apple', 'runner');
const OUTPUT_DIR = path.join('dist', 'apple', 'runner');
// Packaged-runner locations from before the apple-runner/ -> apple/runner/ move. `dist` ships
// wholesale, so a stale tree left by an older build/checkout would double-ship into the npm
// package (and inflate the bundle-size diff, which packages the base then the PR into one dist).
// Always remove them so only the current OUTPUT_DIR survives.
const LEGACY_OUTPUT_DIRS = [
path.join('dist', 'apple-runner'),
path.join('dist', 'apple', 'apple-runner'),
];
const SKIPPED_DIR_NAMES = new Set(['.build', '.swiftpm', 'xcuserdata']);
const SKIPPED_ROOT_FILES = new Set(['README.md', 'RUNNER_PROTOCOL.md']);
function packageAppleRunnerSource(options = {}) {
const root = path.resolve(options.root ?? process.cwd());
const sourceRoot = path.join(root, SOURCE_DIR);
const outputRoot = path.join(root, OUTPUT_DIR);
if (!fs.existsSync(sourceRoot)) {
throw new Error(`Apple runner source not found at ${sourceRoot}`);
}
fs.rmSync(outputRoot, { recursive: true, force: true });
for (const legacyDir of LEGACY_OUTPUT_DIRS) {
fs.rmSync(path.join(root, legacyDir), { recursive: true, force: true });
}
const summary = {
outputRoot,
copiedFiles: 0,
strippedFiles: 0,
strippedBlocks: 0,
};
copyDirectory(sourceRoot, outputRoot, '', summary);
return summary;
}
function stripRunnerUnitTestBlocks(source, filePath = '<swift source>') {
const lines = source.match(/[^\n]*\n|[^\n]+/g) ?? [];
const state = {
output: [],
strippedBlocks: 0,
skippedDepth: 0,
};
for (const line of lines) {
consumeSwiftLine(state, line);
}
if (state.skippedDepth !== 0) {
throw new Error(`Unterminated ${UNIT_TEST_CONDITION} block in ${filePath}`);
}
return {
contents: state.output.join(''),
strippedBlocks: state.strippedBlocks,
};
}
function consumeSwiftLine(state, line) {
if (state.skippedDepth > 0) {
consumeSkippedConditionalLine(state, line);
return;
}
if (isRunnerUnitTestBlockStart(line)) {
state.skippedDepth = 1;
state.strippedBlocks += 1;
return;
}
state.output.push(line);
}
function consumeSkippedConditionalLine(state, line) {
if (isConditionalStart(line)) {
state.skippedDepth += 1;
}
if (isConditionalEnd(line)) {
state.skippedDepth -= 1;
}
}
function copyDirectory(sourceDir, outputDir, relativeDir, summary) {
fs.mkdirSync(outputDir, { recursive: true });
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
for (const entry of entries) {
copyDirectoryEntry(entry, sourceDir, outputDir, relativeDir, summary);
}
}
function copyDirectoryEntry(entry, sourceDir, outputDir, relativeDir, summary) {
const relativePath = path.join(relativeDir, entry.name);
if (shouldSkipEntry(entry, relativePath)) {
return;
}
const sourcePath = path.join(sourceDir, entry.name);
const outputPath = path.join(outputDir, entry.name);
if (entry.isDirectory()) {
copyDirectory(sourcePath, outputPath, relativePath, summary);
return;
}
if (entry.isFile()) {
copyFile(sourcePath, outputPath, summary);
}
}
function copyFile(sourcePath, outputPath, summary) {
if (path.extname(sourcePath) !== '.swift') {
fs.copyFileSync(sourcePath, outputPath);
summary.copiedFiles += 1;
return;
}
const source = fs.readFileSync(sourcePath, 'utf8');
const stripped = stripRunnerUnitTestBlocks(source, sourcePath);
fs.writeFileSync(outputPath, stripped.contents);
summary.copiedFiles += 1;
if (stripped.strippedBlocks > 0) {
summary.strippedFiles += 1;
summary.strippedBlocks += stripped.strippedBlocks;
}
}
function shouldSkipEntry(entry, relativePath) {
return shouldSkipDirectory(entry) || shouldSkipFile(entry, relativePath);
}
function shouldSkipDirectory(entry) {
return entry.isDirectory() && SKIPPED_DIR_NAMES.has(entry.name);
}
function shouldSkipFile(entry, relativePath) {
return entry.isFile() && (isXcodeUserStateFile(entry) || isSkippedRootFile(entry, relativePath));
}
function isXcodeUserStateFile(entry) {
return entry.name.endsWith('.xcuserstate');
}
function isSkippedRootFile(entry, relativePath) {
return !relativePath.includes(path.sep) && SKIPPED_ROOT_FILES.has(entry.name);
}
function isRunnerUnitTestBlockStart(line) {
return new RegExp(`^\\s*#if\\s+${UNIT_TEST_CONDITION}(?:\\b|$)`).test(line);
}
function isConditionalStart(line) {
return /^\s*#if\b/.test(line);
}
function isConditionalEnd(line) {
return /^\s*#endif\b/.test(line);
}
function parseArgs(argv) {
const parsed = { root: process.cwd(), quiet: false };
let index = 0;
while (index < argv.length) {
index = parseArg(argv, index, parsed);
}
return parsed;
}
function parseArg(argv, index, parsed) {
const arg = argv[index];
if (arg === '--quiet') {
parsed.quiet = true;
return index + 1;
}
if (arg === '--root') {
return parseRootArg(argv, index, parsed);
}
throw new Error(`Unknown argument: ${arg}`);
}
function parseRootArg(argv, index, parsed) {
const value = argv[index + 1];
if (!value || value.startsWith('--')) {
throw new Error('--root requires a path');
}
parsed.root = value;
return index + 2;
}
function isMainModule() {
return process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
}
if (isMainModule()) {
const options = parseArgs(process.argv.slice(2));
const summary = packageAppleRunnerSource(options);
if (!options.quiet) {
const relativeOutput = path.relative(path.resolve(options.root), summary.outputRoot);
console.log(
`Packaged Apple runner source at ${relativeOutput} ` +
`(${summary.copiedFiles} files, stripped ${summary.strippedBlocks} unit-test blocks).`,
);
}
}