mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
7f5dbd2e50
* chore: drive unused production exports to zero `pnpm check:production-exports` has been failing on main with 21 findings. Each was investigated rather than blanket-suppressed; they split three ways. Genuinely dead, deleted: - `androidDeviceForSerial` (android/adb.ts) had zero references anywhere, tests included. - `streamAndroidLogcatWithAdb` (android/logcat.ts) had no production consumer and only a guard-clause test; its `captureAndroidLogcatWithAdb` sibling is the published SDK surface. Removed with its options type and test. Test-only aliases over live siblings, collapsed: - app-log-resource-store re-exposed four bound store methods; production used only `resolvePath`, tests used the other three. The sibling screen-recording-resource-store exports just the store, so this now matches: one export, all consumers call `appLogResourceStore.x`. - device-claims re-exported `canonicalLocalDeviceKey` for a single test, while production imports it from device-claim-paths directly. Dropped the re-export and pointed the test at the canonical module. Real consumers the analysis cannot see, exempted with the reason: - The nine remaining `src/cli/commands/*Command` handlers are reached only through `dedicatedCliCommandHandlerLoaders`, the dynamic import() table in router.ts. `deviceCommand` already carried an inline suppression for exactly this; replaced it with one config entry naming the table that enumerates all ten, matching the existing daemon route-handler entry. - `resolveVitestMaxWorkers` (vitest.config.ts), `DEVICE_CLAIM_IN_USE_SAMPLE` (bench sample producers) and the capture-kit `createAppLogLiveHandle` facade export joined the existing entries that already record their exact shape. - `**/*.fixtures.ts` is now an ignorePattern: all 15 build doubles for co-located tests, several import `vi`, and none is imported by production source. Pattern-matching them as test infrastructure also keeps this class of finding from recurring. Gate now reports zero. Unit suite, layering, fallow audit, MCP metadata, build, bundle-owner and package checks all pass. * chore: scope the fixture exemption to unused exports Review feedback on #1743: `ignorePatterns` removes a file from every Fallow mode and rule, but the false positive here is only production-unused-exports. Moved *.fixtures.ts to an ignoreExports entry so fixtures stay inside health, dead-code and cycle analysis. Kept `exports: ["*"]` rather than today's three symbols because the property is per-file — no fixture module has a production consumer — so a new fixture symbol should not reopen the finding. check:production-exports still reports zero, and a full `fallow --summary` returns identical totals (2 dead-code / 4 dupes / 123 health) with and without the change, so nothing was newly surfaced or newly hidden. * test(fallow): prove fixture policy scope
114 lines
3.8 KiB
TypeScript
114 lines
3.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { test } from 'vitest';
|
|
import { mkdtempForTest } from '../../src/__tests__/test-utils/index.ts';
|
|
import { runCmd } from '../../src/utils/exec.ts';
|
|
|
|
const REPOSITORY_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
const FALLOW_CLI = path.join(REPOSITORY_ROOT, 'node_modules', 'fallow', 'bin', 'fallow');
|
|
const FIXTURE_GLOB = '**/*.fixtures.ts';
|
|
|
|
type IgnoreExportPolicy = {
|
|
file: string;
|
|
exports: string[];
|
|
};
|
|
|
|
type ResolvedFallowConfig = {
|
|
ignorePatterns: string[];
|
|
ignoreExports: IgnoreExportPolicy[];
|
|
};
|
|
|
|
type DeadCodeReport = {
|
|
summary: { unused_exports: number };
|
|
unused_exports: Array<{ path: string; export_name: string }>;
|
|
};
|
|
|
|
type HealthReport = {
|
|
findings: Array<{ path: string; name: string }>;
|
|
};
|
|
|
|
function parseJsonEnvelope<T>(stdout: string): T {
|
|
const jsonStart = stdout.indexOf('{');
|
|
assert.notEqual(jsonStart, -1, `expected JSON output, received: ${stdout}`);
|
|
return JSON.parse(stdout.slice(jsonStart)) as T;
|
|
}
|
|
|
|
async function runFallow(root: string, args: string[]) {
|
|
return await runCmd(process.execPath, [FALLOW_CLI, ...args], {
|
|
cwd: root,
|
|
allowFailure: true,
|
|
timeoutMs: 30_000,
|
|
});
|
|
}
|
|
|
|
test('fixture exports stay exempt while fixture health remains audited', async () => {
|
|
const configResult = await runFallow(REPOSITORY_ROOT, ['config', '--format', 'json', '--quiet']);
|
|
assert.equal(configResult.exitCode, 0, configResult.stderr);
|
|
const resolved = parseJsonEnvelope<ResolvedFallowConfig>(configResult.stdout);
|
|
const fixturePolicy = resolved.ignoreExports.find(({ file }) => file === FIXTURE_GLOB);
|
|
|
|
const root = await mkdtempForTest('fallow-fixture-policy-');
|
|
await fs.mkdir(path.join(root, 'src'));
|
|
await Promise.all([
|
|
fs.writeFile(path.join(root, 'package.json'), '{"type":"module"}\n'),
|
|
fs.writeFile(
|
|
path.join(root, 'tsconfig.json'),
|
|
'{"compilerOptions":{"target":"ES2022","module":"NodeNext","moduleResolution":"NodeNext"},"include":["src/**/*.ts"]}\n',
|
|
),
|
|
fs.writeFile(
|
|
path.join(root, 'src', 'index.ts'),
|
|
"import './sample.fixtures.ts';\nexport const entry = true;\n",
|
|
),
|
|
fs.writeFile(
|
|
path.join(root, 'src', 'sample.fixtures.ts'),
|
|
'export function unusedFixture(value: boolean): number {\n if (value) return 1;\n return 0;\n}\n',
|
|
),
|
|
fs.writeFile(
|
|
path.join(root, '.fallowrc.json'),
|
|
`${JSON.stringify({
|
|
entry: ['src/index.ts'],
|
|
ignorePatterns: resolved.ignorePatterns,
|
|
ignoreExports: fixturePolicy ? [fixturePolicy] : [],
|
|
health: { maxCyclomatic: 1, maxCognitive: 1 },
|
|
})}\n`,
|
|
),
|
|
]);
|
|
|
|
const deadCodeResult = await runFallow(root, [
|
|
'dead-code',
|
|
'--unused-exports',
|
|
'--format',
|
|
'json',
|
|
'--quiet',
|
|
'--no-cache',
|
|
]);
|
|
assert.equal(deadCodeResult.exitCode, 0, deadCodeResult.stderr);
|
|
const deadCode = parseJsonEnvelope<DeadCodeReport>(deadCodeResult.stdout);
|
|
assert.equal(deadCode.summary.unused_exports, 0);
|
|
assert.deepEqual(deadCode.unused_exports, []);
|
|
|
|
const healthResult = await runFallow(root, [
|
|
'health',
|
|
'--complexity',
|
|
'--format',
|
|
'json',
|
|
'--quiet',
|
|
'--no-cache',
|
|
]);
|
|
assert.equal(healthResult.exitCode, 1, 'the planted fixture complexity must fail health');
|
|
const health = parseJsonEnvelope<HealthReport>(healthResult.stdout);
|
|
assert.ok(
|
|
health.findings.some(
|
|
({ path: findingPath, name }) =>
|
|
findingPath === 'src/sample.fixtures.ts' && name === 'unusedFixture',
|
|
),
|
|
'Fallow health did not analyze the planted fixture',
|
|
);
|
|
|
|
assert.equal(resolved.ignorePatterns.includes(FIXTURE_GLOB), false);
|
|
assert.ok(fixturePolicy, `missing ${FIXTURE_GLOB} unused-export policy`);
|
|
assert.deepEqual(fixturePolicy.exports, ['*']);
|
|
});
|