esque-bridge 0.6.6 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +86 -46
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -687,58 +687,98 @@ async function probeBuildError(port) {
|
|
|
687
687
|
return scanOutputForError(preview && preview.output);
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
-
//
|
|
691
|
-
//
|
|
692
|
-
//
|
|
693
|
-
//
|
|
694
|
-
|
|
695
|
-
// with its own generous timeout so it never collides with the 60s port-wait.
|
|
696
|
-
// Idempotent: skipped as soon as node_modules exists.
|
|
697
|
-
function ensureDeps() {
|
|
690
|
+
// Run one `sh -c <cmd>` inside the workdir, streaming a labelled copy of its
|
|
691
|
+
// output and keeping a rolling tail we can scan afterwards. Self-kills after
|
|
692
|
+
// timeoutMs so a wedged install can't hang the preview forever. Resolves
|
|
693
|
+
// { code, out } (code -1 on timeout/spawn error).
|
|
694
|
+
function runInWorkdir(cmd, label, timeoutMs) {
|
|
698
695
|
return new Promise((resolve) => {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
fs.existsSync(path.join(WORKDIR, 'package.json')) &&
|
|
703
|
-
!fs.existsSync(path.join(WORKDIR, 'node_modules'));
|
|
704
|
-
} catch {
|
|
705
|
-
needs = false;
|
|
706
|
-
}
|
|
707
|
-
if (!needs) return resolve(true);
|
|
708
|
-
|
|
709
|
-
// `--legacy-peer-deps`: AI-scaffolded Expo/RN projects routinely pin a
|
|
710
|
-
// React-18 app alongside test tooling that peer-wants React 19, which makes
|
|
711
|
-
// a strict `npm install` abort with ERESOLVE. The app runtime only needs
|
|
712
|
-
// the React it pins, so we tell npm to install the tree as written rather
|
|
713
|
-
// than fail the whole preview over a test-only peer mismatch.
|
|
714
|
-
console.log('[preview] installing dependencies (npm install) — first preview, this can take a few minutes…');
|
|
715
|
-
const proc = spawn('sh', ['-c', 'npm install --legacy-peer-deps --no-audit --no-fund'], {
|
|
716
|
-
cwd: WORKDIR,
|
|
717
|
-
env: process.env,
|
|
718
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
719
|
-
});
|
|
720
|
-
const onData = (d) => process.stdout.write(`[npm] ${d}`);
|
|
696
|
+
const proc = spawn('sh', ['-c', cmd], { cwd: WORKDIR, env: process.env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
697
|
+
let out = '';
|
|
698
|
+
const onData = (d) => { out = (out + d).slice(-20000); process.stdout.write(`[${label}] ${d}`); };
|
|
721
699
|
proc.stdout.on('data', onData);
|
|
722
700
|
proc.stderr.on('data', onData);
|
|
723
|
-
const timer = setTimeout(() => {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
resolve(false);
|
|
727
|
-
}, 360000);
|
|
728
|
-
proc.on('exit', (code) => {
|
|
729
|
-
clearTimeout(timer);
|
|
730
|
-
if (code === 0) console.log('[preview] dependencies installed');
|
|
731
|
-
else console.error(`[preview] npm install exited ${code} — preview may fail`);
|
|
732
|
-
resolve(code === 0);
|
|
733
|
-
});
|
|
734
|
-
proc.on('error', (e) => {
|
|
735
|
-
clearTimeout(timer);
|
|
736
|
-
console.error('[preview] npm install error:', e.message);
|
|
737
|
-
resolve(false);
|
|
738
|
-
});
|
|
701
|
+
const timer = setTimeout(() => { try { proc.kill('SIGKILL'); } catch { /* already gone */ } resolve({ code: -1, out }); }, timeoutMs);
|
|
702
|
+
proc.on('exit', (code) => { clearTimeout(timer); resolve({ code, out }); });
|
|
703
|
+
proc.on('error', (e) => { clearTimeout(timer); resolve({ code: -1, out: `${out}\n${e.message}` }); });
|
|
739
704
|
});
|
|
740
705
|
}
|
|
741
706
|
|
|
707
|
+
// The agent sometimes invents a dependency version that does not exist on the
|
|
708
|
+
// registry (e.g. `react-native-worklets@0.0.0`), and a single bad pin makes the
|
|
709
|
+
// whole `npm install` abort with ETARGET/E404. Pull the offending package name
|
|
710
|
+
// out of npm's error text and delete it from package.json (backing the original
|
|
711
|
+
// up once) so the next attempt can get past it. Returns the dropped name or null.
|
|
712
|
+
function dropUnresolvableDep(npmOutput) {
|
|
713
|
+
const m =
|
|
714
|
+
npmOutput.match(/No matching version found for (@?[\w.\/-]+)@/i) ||
|
|
715
|
+
npmOutput.match(/['"]?(@?[\w.\/-]+)@[^'"\s]+['"]? is not in this registry/i);
|
|
716
|
+
if (!m) return null;
|
|
717
|
+
const name = m[1];
|
|
718
|
+
const pkgPath = path.join(WORKDIR, 'package.json');
|
|
719
|
+
try {
|
|
720
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
721
|
+
let dropped = false;
|
|
722
|
+
for (const sec of ['dependencies', 'devDependencies']) {
|
|
723
|
+
if (pkg[sec] && Object.prototype.hasOwnProperty.call(pkg[sec], name)) { delete pkg[sec][name]; dropped = true; }
|
|
724
|
+
}
|
|
725
|
+
if (!dropped) return null;
|
|
726
|
+
const bak = `${pkgPath}.esque-bak`;
|
|
727
|
+
if (!fs.existsSync(bak)) fs.copyFileSync(pkgPath, bak);
|
|
728
|
+
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
729
|
+
return name;
|
|
730
|
+
} catch { return null; }
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Make sure the project's dependencies are on disk before a preview tries to
|
|
734
|
+
// boot its dev server. The agent scaffolds package.json but deliberately leaves
|
|
735
|
+
// the slow install to Esque (the blueprint says so), so the first preview would
|
|
736
|
+
// otherwise die with "module 'expo' is not installed" / "next: not found".
|
|
737
|
+
// Idempotent — skipped as soon as node_modules exists.
|
|
738
|
+
async function ensureDeps() {
|
|
739
|
+
let isNode = false, hasModules = false, isExpo = false;
|
|
740
|
+
try {
|
|
741
|
+
const pkgPath = path.join(WORKDIR, 'package.json');
|
|
742
|
+
isNode = fs.existsSync(pkgPath);
|
|
743
|
+
hasModules = fs.existsSync(path.join(WORKDIR, 'node_modules'));
|
|
744
|
+
if (isNode) {
|
|
745
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
746
|
+
isExpo = !!(pkg.dependencies && pkg.dependencies.expo);
|
|
747
|
+
}
|
|
748
|
+
} catch { isNode = false; }
|
|
749
|
+
if (!isNode || hasModules) return true;
|
|
750
|
+
|
|
751
|
+
console.log('[preview] installing dependencies — first preview, this can take a few minutes…');
|
|
752
|
+
// `--legacy-peer-deps`: AI-scaffolded RN projects routinely pin a React-18 app
|
|
753
|
+
// beside test tooling that peer-wants React 19, which a strict install rejects.
|
|
754
|
+
const INSTALL = 'npm install --legacy-peer-deps --no-audit --no-fund';
|
|
755
|
+
|
|
756
|
+
let ok = false;
|
|
757
|
+
// Each failed pass may expose one invented version we can drop, then retry.
|
|
758
|
+
// Bounded so a genuinely unfixable project can't loop forever.
|
|
759
|
+
for (let attempt = 0; attempt < 6; attempt++) {
|
|
760
|
+
const { code, out } = await runInWorkdir(INSTALL, 'npm', 360000);
|
|
761
|
+
if (code === 0) { ok = true; break; }
|
|
762
|
+
const dropped = dropUnresolvableDep(out);
|
|
763
|
+
if (dropped) { console.log(`[preview] "${dropped}" has no installable version — removed it and retrying…`); continue; }
|
|
764
|
+
console.error(`[preview] npm install failed (exit ${code}) — preview may fail`);
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
if (!ok) return false;
|
|
768
|
+
console.log('[preview] dependencies installed');
|
|
769
|
+
|
|
770
|
+
// For Expo apps, realign every SDK-managed package to the version the SDK
|
|
771
|
+
// expects. The agent often mixes incompatible pins (e.g. Expo 56 with
|
|
772
|
+
// react-native 0.76), which compiles to a blank/broken web bundle; this makes
|
|
773
|
+
// the tree coherent. Non-fatal — keep the installed versions if it can't run.
|
|
774
|
+
if (isExpo) {
|
|
775
|
+
const { code } = await runInWorkdir('npx --yes expo install --fix', 'expo', 240000);
|
|
776
|
+
if (code === 0) console.log('[preview] aligned dependency versions to the Expo SDK');
|
|
777
|
+
else console.warn('[preview] could not run expo install --fix — continuing with installed versions');
|
|
778
|
+
}
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
|
|
742
782
|
async function startPreview(cmd, port) {
|
|
743
783
|
killPreview();
|
|
744
784
|
// Make sure node_modules exists before the dev server tries to boot.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esque-bridge",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "Desktop-side receiver for the Esque Agent mobile app. Pairs your phone with a local coding-agent CLI (Claude Code, Codex, Aider, or any custom command) via a tunnel + QR code, so prompts run through your subscription instead of per-token API billing.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"esque-bridge": "index.js"
|