mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
fix: helpers honor --target for multi-app disambiguation
greptile-apps[bot] repro: the multi-app warning recommended --target, but the helper CLIs never parsed it, so live-poll --target appB still re-anchored onto the pointer's first choice. enterLiveRoot now consumes a --target argument (removing it from argv so downstream flag parsers never see it) and resolves roots against it, making the documented escape hatch real on every helper. Regression test drives a two-live-app repo through a child process and asserts both the chdir target and the argv scrubbing. This work was produced with AI assistance (Claude Code). Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Code
parent
f1d450e6ab
commit
baed04a52b
@@ -393,14 +393,41 @@ export function resolveLiveRoots(cwd = process.cwd(), { targetPath = null } = {}
|
||||
return { manifest: fresh.manifest, source: 'fresh' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume a `--target <path>` / `--target=<path>` pair from an argv array,
|
||||
* returning the value and removing the tokens so downstream flag parsers
|
||||
* (which do not know the option) never see them.
|
||||
*/
|
||||
export function consumeTargetArg(argv = process.argv) {
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
if (arg === '--target' && typeof argv[i + 1] === 'string') {
|
||||
const value = argv[i + 1];
|
||||
argv.splice(i, 2);
|
||||
return value;
|
||||
}
|
||||
if (typeof arg === 'string' && arg.startsWith('--target=')) {
|
||||
const value = arg.slice('--target='.length);
|
||||
argv.splice(i, 1);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry-point guard for live CLI scripts: resolve the governing roots and
|
||||
* make appRoot the process cwd so every downstream path derivation agrees
|
||||
* with the boot. Returns the manifest. Never throws; on selection ambiguity
|
||||
* it stays in the current directory (the boot flow handles prompting).
|
||||
* with the boot. An explicit `--target <path>` on the helper's command line
|
||||
* overrides pointer resolution, which is what disambiguates a repo with
|
||||
* several live apps (the multi-app warning names this escape hatch, so it
|
||||
* has to actually work on every helper). Returns the manifest. Never
|
||||
* throws; on selection ambiguity it stays in the current directory (the
|
||||
* boot flow handles prompting).
|
||||
*/
|
||||
export function enterLiveRoot(cwd = process.cwd()) {
|
||||
const resolved = resolveLiveRoots(cwd);
|
||||
const targetPath = consumeTargetArg(process.argv);
|
||||
const resolved = resolveLiveRoots(cwd, targetPath ? { targetPath } : {});
|
||||
if (!resolved.manifest) return null;
|
||||
const appRoot = resolved.manifest.appRoot;
|
||||
if (path.resolve(cwd) !== path.resolve(appRoot) && isDir(appRoot)) {
|
||||
|
||||
@@ -267,3 +267,38 @@ describe('review regressions: stopped-session recovery', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('review regressions: helper --target', () => {
|
||||
it('enterLiveRoot honors --target and strips it from argv', () => {
|
||||
const repo = realpathSync(mkdtempSync(join(tmpdir(), 'impeccable-roots-target-')));
|
||||
try {
|
||||
mkdirSync(join(repo, '.git'), { recursive: true });
|
||||
for (const name of ['appA', 'appB']) {
|
||||
write(repo, `${name}/vite.config.js`, 'export default {};');
|
||||
}
|
||||
const a = resolveRoots({ cwd: repo, targetPath: join(repo, 'appA/vite.config.js') }).manifest;
|
||||
const b = resolveRoots({ cwd: repo, targetPath: join(repo, 'appB/vite.config.js') }).manifest;
|
||||
writeRootsManifest(a);
|
||||
writeRootsManifest(b);
|
||||
// Both alive: pointer resolution alone is ambiguous (A? B?); --target
|
||||
// must decide, and downstream flag parsing must not see the tokens.
|
||||
write(repo, 'appA/.impeccable/live/server.json', JSON.stringify({ pid: process.pid, port: 1, token: 't' }));
|
||||
write(repo, 'appB/.impeccable/live/server.json', JSON.stringify({ pid: process.pid, port: 2, token: 't' }));
|
||||
|
||||
const res = spawnSync(process.execPath, [
|
||||
'-e',
|
||||
`import(${JSON.stringify(ROOTS_MODULE)}).then((m) => {
|
||||
process.argv.push('--target', ${JSON.stringify(join(repo, 'appB'))});
|
||||
m.enterLiveRoot();
|
||||
console.log(JSON.stringify({ cwd: process.cwd(), argvHasTarget: process.argv.includes('--target') }));
|
||||
});`,
|
||||
], { cwd: repo, encoding: 'utf-8' });
|
||||
assert.equal(res.status, 0, res.stderr);
|
||||
const out = JSON.parse(res.stdout.trim().split('\n').pop());
|
||||
assert.equal(realpathSync(out.cwd), join(repo, 'appB'));
|
||||
assert.equal(out.argvHasTarget, false);
|
||||
} finally {
|
||||
rmSync(repo, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user