mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
e832325e87
* refactor: split generic host mechanics into @agent-device/host-kit (#2082 W1) The shared src/utils closure that blocked the platform-family moves lands on declared owners: generic host mechanics form a new private @agent-device/host-kit package between kernel and capture-kit, and capture-kit keeps capture, snapshot, and recording behavior, depending on host-kit for the mechanics it needs. tar-stream and yauzl move with the archive code. Every seam's exported subpaths are pinned in package-boundaries.test.ts, the layering model ranks the new zone, R13's allow-list names it, and each seam carries an exact eager-closure row. ADR-0019's substrate amendment describes the layout. Tests that mocked two of the moved modules separately became duplicate same-seam vi.mock factories, where the second silently replaced the first; those are merged, and the mocks that production code reaches past are pinned at their injection points instead. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH * refactor(host-kit): one narrow capability port per export The four technical barrels (exec/fs/values/request) grouped by category rather than by capability, so a consumer needing one mechanic evaluated unrelated ones. Each export is now a single capability over the host machine: command, process, diagnostics, retry, archive, file, request, version. A port re-exports only what a consumer of that capability uses, and every port carries its own eager-closure row. Most of the old values barrel was never host mechanics. Pure record readers, config-source values, result text, memoization, async scoping, coordinate validation, and device-scope parsing touch no process, file, or environment, so they join kernel's other primitives instead. Closures fall accordingly: capture-kit's png-worker-client from 20 to 10, png-resize from 28 to 18, session-teardown from 79 to 68, and the CLI from 386 to 380. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH * chore: drop the migration inventories and trim the touched comments Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH * docs: trim the touched host-kit and mutation-lane comments Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH * docs: keep tool directives only in the touched files Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH * docs: keep tool directives only across the touched tree Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH * fix: point the Swift parity comment at the real TS twin and test The W1 move rewrote this citation to packages/contracts/src/mobile-snapshot-semantics.ts, which does not exist: the module went to capture-kit while isTapPointInsideViewport itself went to packages/contracts/src/snapshot-visibility.ts. The TS test line was left pointing at the pre-move path. Both now resolve. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH * fix: repoint comment citations at the homes this refactor moved them to The W1 move left ~20 comment citations pointing at src/utils/*.ts and src/request/*.ts paths that no longer exist. Each now names the capability port that owns the symbol, which survives further file moves: exec -> host-kit/command host-process, owner-identity -> host-kit/process diagnostics -> host-kit/diagnostics atomic-file, process-lock -> host-kit/file retry -> host-kit/retry request progress/cancel -> host-kit/request version -> host-kit/version ttl-memo, source-value, parsing, device-isolation, keyed-lock, success-text -> kernel subpaths Comment-only; no closure, budget, or behavior change. ADR citations are left as written, being dated records of the decision rather than live references. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH --------- Co-authored-by: Claude <noreply@anthropic.com>
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/tmp-dir.ts';
|
|
import { runCmd } from '@agent-device/host-kit/command';
|
|
|
|
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, ['*']);
|
|
});
|