Link bundled runtime dependencies for core packages
This commit is contained in:
@@ -25,7 +25,7 @@ curl -fsSL https://feynman.is/install | bash
|
|||||||
irm https://feynman.is/install.ps1 | iex
|
irm https://feynman.is/install.ps1 | iex
|
||||||
```
|
```
|
||||||
|
|
||||||
The one-line installer fetches the latest tagged release. To pin a version, pass it explicitly, for example `curl -fsSL https://feynman.is/install | bash -s -- 0.2.23`.
|
The one-line installer fetches the latest tagged release. To pin a version, pass it explicitly, for example `curl -fsSL https://feynman.is/install | bash -s -- 0.2.24`.
|
||||||
|
|
||||||
The installer downloads a standalone native bundle with its own Node.js runtime.
|
The installer downloads a standalone native bundle with its own Node.js runtime.
|
||||||
|
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.23",
|
"version": "0.2.24",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.23",
|
"version": "0.2.24",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.23",
|
"version": "0.2.24",
|
||||||
"description": "Research-first CLI agent built on Pi and alphaXiv",
|
"description": "Research-first CLI agent built on Pi and alphaXiv",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ This usually means the release exists, but not all platform bundles were uploade
|
|||||||
Workarounds:
|
Workarounds:
|
||||||
- try again after the release finishes publishing
|
- try again after the release finishes publishing
|
||||||
- pass the latest published version explicitly, e.g.:
|
- pass the latest published version explicitly, e.g.:
|
||||||
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.23
|
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.24
|
||||||
"@
|
"@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ This usually means the release exists, but not all platform bundles were uploade
|
|||||||
Workarounds:
|
Workarounds:
|
||||||
- try again after the release finishes publishing
|
- try again after the release finishes publishing
|
||||||
- pass the latest published version explicitly, e.g.:
|
- pass the latest published version explicitly, e.g.:
|
||||||
curl -fsSL https://feynman.is/install | bash -s -- 0.2.23
|
curl -fsSL https://feynman.is/install | bash -s -- 0.2.24
|
||||||
EOF
|
EOF
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
||||||
import { createRequire } from "node:module";
|
import { createRequire } from "node:module";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { delimiter, dirname, resolve } from "node:path";
|
import { delimiter, dirname, resolve } from "node:path";
|
||||||
@@ -286,28 +286,53 @@ function linkPointsTo(linkPath, targetPath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listWorkspacePackageNames(root) {
|
||||||
|
if (!existsSync(root)) return [];
|
||||||
|
const names = [];
|
||||||
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
||||||
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
||||||
|
if (entry.name.startsWith(".")) continue;
|
||||||
|
if (entry.name.startsWith("@")) {
|
||||||
|
const scopeRoot = resolve(root, entry.name);
|
||||||
|
for (const scopedEntry of readdirSync(scopeRoot, { withFileTypes: true })) {
|
||||||
|
if (!scopedEntry.isDirectory() && !scopedEntry.isSymbolicLink()) continue;
|
||||||
|
names.push(`${entry.name}/${scopedEntry.name}`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
names.push(entry.name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkBundledPackage(packageName) {
|
||||||
|
const sourcePath = resolve(workspaceRoot, packageName);
|
||||||
|
const targetPath = resolve(globalNodeModulesRoot, packageName);
|
||||||
|
if (!existsSync(sourcePath)) return false;
|
||||||
|
if (linkPointsTo(targetPath, sourcePath)) return false;
|
||||||
|
try {
|
||||||
|
if (lstatSync(targetPath).isSymbolicLink()) {
|
||||||
|
rmSync(targetPath, { force: true });
|
||||||
|
} else if (!installedPackageLooksUsable(targetPath, globalNodeModulesRoot)) {
|
||||||
|
rmSync(targetPath, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
if (existsSync(targetPath)) return false;
|
||||||
|
|
||||||
|
ensureParentDir(targetPath);
|
||||||
|
try {
|
||||||
|
symlinkSync(sourcePath, targetPath, process.platform === "win32" ? "junction" : "dir");
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ensureBundledPackageLinks(packageSpecs) {
|
function ensureBundledPackageLinks(packageSpecs) {
|
||||||
if (!workspaceMatchesRuntime(packageSpecs)) return;
|
if (!workspaceMatchesRuntime(packageSpecs)) return;
|
||||||
|
|
||||||
for (const spec of packageSpecs) {
|
for (const packageName of listWorkspacePackageNames(workspaceRoot)) {
|
||||||
const packageName = parsePackageName(spec);
|
linkBundledPackage(packageName);
|
||||||
const sourcePath = resolve(workspaceRoot, packageName);
|
|
||||||
const targetPath = resolve(globalNodeModulesRoot, packageName);
|
|
||||||
if (!existsSync(sourcePath)) continue;
|
|
||||||
if (linkPointsTo(targetPath, sourcePath)) continue;
|
|
||||||
try {
|
|
||||||
if (lstatSync(targetPath).isSymbolicLink()) {
|
|
||||||
rmSync(targetPath, { force: true });
|
|
||||||
} else if (!installedPackageLooksUsable(targetPath, globalNodeModulesRoot)) {
|
|
||||||
rmSync(targetPath, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
if (existsSync(targetPath)) continue;
|
|
||||||
|
|
||||||
ensureParentDir(targetPath);
|
|
||||||
try {
|
|
||||||
symlinkSync(sourcePath, targetPath, process.platform === "win32" ? "junction" : "dir");
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { dirname, join, resolve } from "node:path";
|
import { dirname, join, resolve } from "node:path";
|
||||||
|
|
||||||
@@ -427,6 +427,28 @@ function packageNameToPath(root: string, packageName: string): string {
|
|||||||
return resolve(root, packageName);
|
return resolve(root, packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listBundledWorkspacePackageNames(root: string): string[] {
|
||||||
|
if (!existsSync(root)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const names: string[] = [];
|
||||||
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
||||||
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
||||||
|
if (entry.name.startsWith(".")) continue;
|
||||||
|
if (entry.name.startsWith("@")) {
|
||||||
|
const scopeRoot = resolve(root, entry.name);
|
||||||
|
for (const scopedEntry of readdirSync(scopeRoot, { withFileTypes: true })) {
|
||||||
|
if (!scopedEntry.isDirectory() && !scopedEntry.isSymbolicLink()) continue;
|
||||||
|
names.push(`${entry.name}/${scopedEntry.name}`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
names.push(entry.name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
function packageDependencyExists(packagePath: string, globalNodeModulesRoot: string, dependency: string): boolean {
|
function packageDependencyExists(packagePath: string, globalNodeModulesRoot: string, dependency: string): boolean {
|
||||||
return existsSync(packageNameToPath(resolve(packagePath, "node_modules"), dependency)) ||
|
return existsSync(packageNameToPath(resolve(packagePath, "node_modules"), dependency)) ||
|
||||||
existsSync(packageNameToPath(globalNodeModulesRoot, dependency));
|
existsSync(packageNameToPath(globalNodeModulesRoot, dependency));
|
||||||
@@ -464,6 +486,23 @@ function replaceBrokenPackageWithBundledCopy(targetPath: string, bundledPackageP
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function seedBundledPackage(globalNodeModulesRoot: string, bundledNodeModulesRoot: string, packageName: string): boolean {
|
||||||
|
const bundledPackagePath = resolve(bundledNodeModulesRoot, packageName);
|
||||||
|
if (!existsSync(bundledPackagePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPath = resolve(globalNodeModulesRoot, packageName);
|
||||||
|
if (replaceBrokenPackageWithBundledCopy(targetPath, bundledPackagePath, globalNodeModulesRoot)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!existsSync(targetPath)) {
|
||||||
|
linkDirectory(targetPath, bundledPackagePath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function seedBundledWorkspacePackages(
|
export function seedBundledWorkspacePackages(
|
||||||
agentDir: string,
|
agentDir: string,
|
||||||
appRoot: string,
|
appRoot: string,
|
||||||
@@ -476,6 +515,10 @@ export function seedBundledWorkspacePackages(
|
|||||||
|
|
||||||
const globalNodeModulesRoot = resolve(getFeynmanNpmPrefixPath(agentDir), "lib", "node_modules");
|
const globalNodeModulesRoot = resolve(getFeynmanNpmPrefixPath(agentDir), "lib", "node_modules");
|
||||||
const seeded: string[] = [];
|
const seeded: string[] = [];
|
||||||
|
const bundledPackageNames = listBundledWorkspacePackageNames(bundledNodeModulesRoot);
|
||||||
|
for (const packageName of bundledPackageNames) {
|
||||||
|
seedBundledPackage(globalNodeModulesRoot, bundledNodeModulesRoot, packageName);
|
||||||
|
}
|
||||||
|
|
||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
if (shouldSkipNativeSource(source)) continue;
|
if (shouldSkipNativeSource(source)) continue;
|
||||||
@@ -483,16 +526,8 @@ export function seedBundledWorkspacePackages(
|
|||||||
const parsed = parseNpmSource(source);
|
const parsed = parseNpmSource(source);
|
||||||
if (!parsed) continue;
|
if (!parsed) continue;
|
||||||
|
|
||||||
const bundledPackagePath = resolve(bundledNodeModulesRoot, parsed.name);
|
|
||||||
if (!existsSync(bundledPackagePath)) continue;
|
|
||||||
|
|
||||||
const targetPath = resolve(globalNodeModulesRoot, parsed.name);
|
const targetPath = resolve(globalNodeModulesRoot, parsed.name);
|
||||||
if (replaceBrokenPackageWithBundledCopy(targetPath, bundledPackagePath, globalNodeModulesRoot)) {
|
if (pathsMatchSymlinkTarget(targetPath, resolve(bundledNodeModulesRoot, parsed.name))) {
|
||||||
seeded.push(source);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!existsSync(targetPath)) {
|
|
||||||
linkDirectory(targetPath, bundledPackagePath);
|
|
||||||
seeded.push(source);
|
seeded.push(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ test("seedBundledWorkspacePackages repairs broken existing bundled packages", ()
|
|||||||
|
|
||||||
assert.deepEqual(seeded, ["npm:pi-markdown-preview"]);
|
assert.deepEqual(seeded, ["npm:pi-markdown-preview"]);
|
||||||
assert.equal(lstatSync(existingPackageDir).isSymbolicLink(), true);
|
assert.equal(lstatSync(existingPackageDir).isSymbolicLink(), true);
|
||||||
|
assert.equal(lstatSync(resolve(homeRoot, "npm-global", "lib", "node_modules", "puppeteer-core")).isSymbolicLink(), true);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
readFileSync(resolve(existingPackageDir, "package.json"), "utf8").includes('"version": "1.0.0"'),
|
readFileSync(resolve(existingPackageDir, "package.json"), "utf8").includes('"version": "1.0.0"'),
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ This usually means the release exists, but not all platform bundles were uploade
|
|||||||
Workarounds:
|
Workarounds:
|
||||||
- try again after the release finishes publishing
|
- try again after the release finishes publishing
|
||||||
- pass the latest published version explicitly, e.g.:
|
- pass the latest published version explicitly, e.g.:
|
||||||
curl -fsSL https://feynman.is/install | bash -s -- 0.2.23
|
curl -fsSL https://feynman.is/install | bash -s -- 0.2.24
|
||||||
EOF
|
EOF
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ This usually means the release exists, but not all platform bundles were uploade
|
|||||||
Workarounds:
|
Workarounds:
|
||||||
- try again after the release finishes publishing
|
- try again after the release finishes publishing
|
||||||
- pass the latest published version explicitly, e.g.:
|
- pass the latest published version explicitly, e.g.:
|
||||||
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.23
|
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.24
|
||||||
"@
|
"@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,13 +117,13 @@ These installers download the bundled `skills/` and `prompts/` trees plus the re
|
|||||||
The one-line installer already targets the latest tagged release. To pin an exact version, pass it explicitly:
|
The one-line installer already targets the latest tagged release. To pin an exact version, pass it explicitly:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://feynman.is/install | bash -s -- 0.2.23
|
curl -fsSL https://feynman.is/install | bash -s -- 0.2.24
|
||||||
```
|
```
|
||||||
|
|
||||||
On Windows:
|
On Windows:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.23
|
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.24
|
||||||
```
|
```
|
||||||
|
|
||||||
## Post-install setup
|
## Post-install setup
|
||||||
|
|||||||
Reference in New Issue
Block a user