mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-08 21:09:23 +08:00
feat: remove slop.
This commit is contained in:
@@ -79,10 +79,9 @@ Or with pnpm:
|
|||||||
```bash
|
```bash
|
||||||
pnpm add -g openclaw@latest
|
pnpm add -g openclaw@latest
|
||||||
pnpm approve-builds -g # approve openclaw, node-llama-cpp, sharp, etc.
|
pnpm approve-builds -g # approve openclaw, node-llama-cpp, sharp, etc.
|
||||||
pnpm add -g openclaw@latest # re-run to execute postinstall scripts
|
|
||||||
```
|
```
|
||||||
|
|
||||||
pnpm requires explicit approval for packages with build scripts. After the first install shows the "Ignored build scripts" warning, run `pnpm approve-builds -g` and select the listed packages, then re-run the install so postinstall scripts execute.
|
pnpm requires explicit approval for packages with build scripts. After the first install shows the "Ignored build scripts" warning, run `pnpm approve-builds -g` and select the listed packages.
|
||||||
|
|
||||||
Then:
|
Then:
|
||||||
|
|
||||||
|
|||||||
63
package.json
63
package.json
@@ -9,68 +9,16 @@
|
|||||||
"openclaw": "openclaw.mjs"
|
"openclaw": "openclaw.mjs"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
"assets/",
|
||||||
"CHANGELOG.md",
|
"CHANGELOG.md",
|
||||||
|
"dist/",
|
||||||
|
"docs/",
|
||||||
|
"extensions/",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"openclaw.mjs",
|
"openclaw.mjs",
|
||||||
"README-header.png",
|
"README-header.png",
|
||||||
"README.md",
|
"README.md",
|
||||||
"assets/**",
|
"skills/"
|
||||||
"dist/*.js",
|
|
||||||
"dist/*.json",
|
|
||||||
"docs/**",
|
|
||||||
"extensions/**",
|
|
||||||
"git-hooks/**",
|
|
||||||
"patches/**",
|
|
||||||
"scripts/format-staged.js",
|
|
||||||
"scripts/postinstall.js",
|
|
||||||
"scripts/setup-git-hooks.js",
|
|
||||||
"skills/**",
|
|
||||||
"dist/acp/**",
|
|
||||||
"dist/agents/**",
|
|
||||||
"dist/auto-reply/**",
|
|
||||||
"dist/browser/**",
|
|
||||||
"dist/canvas-host/**",
|
|
||||||
"dist/channels/**",
|
|
||||||
"dist/cli/**",
|
|
||||||
"dist/commands/**",
|
|
||||||
"dist/compat/**",
|
|
||||||
"dist/config/**",
|
|
||||||
"dist/control-ui/**",
|
|
||||||
"dist/cron/**",
|
|
||||||
"dist/daemon/**",
|
|
||||||
"dist/discord/**",
|
|
||||||
"dist/gateway/**",
|
|
||||||
"dist/hooks/**",
|
|
||||||
"dist/imessage/**",
|
|
||||||
"dist/infra/**",
|
|
||||||
"dist/line/**",
|
|
||||||
"dist/link-understanding/**",
|
|
||||||
"dist/logging/**",
|
|
||||||
"dist/macos/**",
|
|
||||||
"dist/markdown/**",
|
|
||||||
"dist/media-understanding/**",
|
|
||||||
"dist/media/**",
|
|
||||||
"dist/memory/**",
|
|
||||||
"dist/node-host/**",
|
|
||||||
"dist/pairing/**",
|
|
||||||
"dist/plugin-sdk/**",
|
|
||||||
"dist/plugins/**",
|
|
||||||
"dist/process/**",
|
|
||||||
"dist/providers/**",
|
|
||||||
"dist/routing/**",
|
|
||||||
"dist/security/**",
|
|
||||||
"dist/sessions/**",
|
|
||||||
"dist/shared/**",
|
|
||||||
"dist/signal/**",
|
|
||||||
"dist/slack/**",
|
|
||||||
"dist/telegram/**",
|
|
||||||
"dist/terminal/**",
|
|
||||||
"dist/tts/**",
|
|
||||||
"dist/tui/**",
|
|
||||||
"dist/utils/**",
|
|
||||||
"dist/web/**",
|
|
||||||
"dist/whatsapp/**",
|
|
||||||
"dist/wizard/**"
|
|
||||||
],
|
],
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -115,7 +63,6 @@
|
|||||||
"openclaw": "node scripts/run-node.mjs",
|
"openclaw": "node scripts/run-node.mjs",
|
||||||
"openclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json",
|
"openclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json",
|
||||||
"plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
|
"plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
|
||||||
"postinstall": "node scripts/postinstall.js",
|
|
||||||
"prepack": "pnpm build && pnpm ui:build",
|
"prepack": "pnpm build && pnpm ui:build",
|
||||||
"protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/OpenClawProtocol/GatewayModels.swift",
|
"protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/OpenClawProtocol/GatewayModels.swift",
|
||||||
"protocol:gen": "node --import tsx scripts/protocol-gen.ts",
|
"protocol:gen": "node --import tsx scripts/protocol-gen.ts",
|
||||||
|
|||||||
@@ -10,9 +10,6 @@
|
|||||||
".": "./index.js",
|
".": "./index.js",
|
||||||
"./cli-entry": "./bin/clawdbot.js"
|
"./cli-entry": "./bin/clawdbot.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
|
||||||
"postinstall": "node ./scripts/postinstall.js"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"openclaw": "workspace:*"
|
"openclaw": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,6 @@
|
|||||||
".": "./index.js",
|
".": "./index.js",
|
||||||
"./cli-entry": "./bin/moltbot.js"
|
"./cli-entry": "./bin/moltbot.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
|
||||||
"postinstall": "node ./scripts/postinstall.js"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"openclaw": "workspace:*"
|
"openclaw": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ RUN apt-get update \
|
|||||||
|
|
||||||
WORKDIR /repo
|
WORKDIR /repo
|
||||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||||
COPY scripts/postinstall.js ./scripts/postinstall.js
|
|
||||||
COPY scripts/setup-git-hooks.js ./scripts/setup-git-hooks.js
|
COPY scripts/setup-git-hooks.js ./scripts/setup-git-hooks.js
|
||||||
RUN corepack enable \
|
RUN corepack enable \
|
||||||
&& pnpm install --frozen-lockfile
|
&& pnpm install --frozen-lockfile
|
||||||
|
|||||||
@@ -1,361 +0,0 @@
|
|||||||
import { spawnSync } from "node:child_process";
|
|
||||||
import fs from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
import { setupGitHooks } from "./setup-git-hooks.js";
|
|
||||||
|
|
||||||
function detectPackageManager(ua = process.env.npm_config_user_agent ?? "") {
|
|
||||||
// Examples:
|
|
||||||
// - "pnpm/10.23.0 npm/? node/v22.21.1 darwin arm64"
|
|
||||||
// - "npm/10.9.4 node/v22.12.0 linux x64"
|
|
||||||
// - "bun/1.2.2"
|
|
||||||
const normalized = String(ua).trim();
|
|
||||||
if (normalized.startsWith("pnpm/")) {
|
|
||||||
return "pnpm";
|
|
||||||
}
|
|
||||||
if (normalized.startsWith("bun/")) {
|
|
||||||
return "bun";
|
|
||||||
}
|
|
||||||
if (normalized.startsWith("npm/")) {
|
|
||||||
return "npm";
|
|
||||||
}
|
|
||||||
if (normalized.startsWith("yarn/")) {
|
|
||||||
return "yarn";
|
|
||||||
}
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldApplyPnpmPatchedDependenciesFallback(pm = detectPackageManager()) {
|
|
||||||
// pnpm already applies pnpm.patchedDependencies itself; re-applying would fail.
|
|
||||||
return pm !== "pnpm";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRepoRoot() {
|
|
||||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
||||||
return path.resolve(here, "..");
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureExecutable(targetPath) {
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(targetPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const mode = fs.statSync(targetPath).mode & 0o777;
|
|
||||||
if (mode & 0o100) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fs.chmodSync(targetPath, 0o755);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(`[postinstall] chmod failed: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractPackageName(key) {
|
|
||||||
if (key.startsWith("@")) {
|
|
||||||
const idx = key.indexOf("@", 1);
|
|
||||||
if (idx === -1) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
return key.slice(0, idx);
|
|
||||||
}
|
|
||||||
const idx = key.lastIndexOf("@");
|
|
||||||
if (idx <= 0) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
return key.slice(0, idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripPrefix(p) {
|
|
||||||
if (p.startsWith("a/") || p.startsWith("b/")) {
|
|
||||||
return p.slice(2);
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseRange(segment) {
|
|
||||||
// segment: "-12,5" or "+7"
|
|
||||||
const [startRaw, countRaw] = segment.slice(1).split(",");
|
|
||||||
const start = Number.parseInt(startRaw, 10);
|
|
||||||
const count = countRaw ? Number.parseInt(countRaw, 10) : 1;
|
|
||||||
if (Number.isNaN(start) || Number.isNaN(count)) {
|
|
||||||
throw new Error(`invalid hunk range: ${segment}`);
|
|
||||||
}
|
|
||||||
return { start, count };
|
|
||||||
}
|
|
||||||
|
|
||||||
function parsePatch(patchText) {
|
|
||||||
const lines = patchText.split("\n");
|
|
||||||
const files = [];
|
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
while (i < lines.length) {
|
|
||||||
if (!lines[i].startsWith("diff --git ")) {
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = { oldPath: null, newPath: null, hunks: [] };
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
// Skip index line(s)
|
|
||||||
while (i < lines.length && lines[i].startsWith("index ")) {
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i < lines.length && lines[i].startsWith("--- ")) {
|
|
||||||
file.oldPath = stripPrefix(lines[i].slice(4).trim());
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
if (i < lines.length && lines[i].startsWith("+++ ")) {
|
|
||||||
file.newPath = stripPrefix(lines[i].slice(4).trim());
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (i < lines.length && lines[i].startsWith("@@")) {
|
|
||||||
const header = lines[i];
|
|
||||||
const match = /^@@\s+(-\d+(?:,\d+)?)\s+(\+\d+(?:,\d+)?)\s+@@/.exec(header);
|
|
||||||
if (!match) {
|
|
||||||
throw new Error(`invalid hunk header: ${header}`);
|
|
||||||
}
|
|
||||||
const oldRange = parseRange(match[1]);
|
|
||||||
const newRange = parseRange(match[2]);
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
const hunkLines = [];
|
|
||||||
while (i < lines.length) {
|
|
||||||
const line = lines[i];
|
|
||||||
if (line.startsWith("@@") || line.startsWith("diff --git ")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (line === "") {
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (line.startsWith("\\ No newline at end of file")) {
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
hunkLines.push(line);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
file.hunks.push({
|
|
||||||
oldStart: oldRange.start,
|
|
||||||
oldLines: oldRange.count,
|
|
||||||
newStart: newRange.start,
|
|
||||||
newLines: newRange.count,
|
|
||||||
lines: hunkLines,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.newPath && file.hunks.length > 0) {
|
|
||||||
files.push(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readFileLines(targetPath) {
|
|
||||||
if (!fs.existsSync(targetPath)) {
|
|
||||||
throw new Error(`target file missing: ${targetPath}`);
|
|
||||||
}
|
|
||||||
const raw = fs.readFileSync(targetPath, "utf-8");
|
|
||||||
const hasTrailingNewline = raw.endsWith("\n");
|
|
||||||
const parts = raw.split("\n");
|
|
||||||
if (hasTrailingNewline) {
|
|
||||||
parts.pop();
|
|
||||||
}
|
|
||||||
return { lines: parts, hasTrailingNewline };
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeFileLines(targetPath, lines, hadTrailingNewline) {
|
|
||||||
const content = lines.join("\n") + (hadTrailingNewline ? "\n" : "");
|
|
||||||
fs.writeFileSync(targetPath, content, "utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyHunk(lines, hunk, offset) {
|
|
||||||
let cursor = hunk.oldStart - 1 + offset;
|
|
||||||
const expected = [];
|
|
||||||
for (const raw of hunk.lines) {
|
|
||||||
const marker = raw[0];
|
|
||||||
if (marker === " " || marker === "+") {
|
|
||||||
expected.push(raw.slice(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cursor >= 0 && cursor + expected.length <= lines.length) {
|
|
||||||
let alreadyApplied = true;
|
|
||||||
for (let i = 0; i < expected.length; i += 1) {
|
|
||||||
if (lines[cursor + i] !== expected[i]) {
|
|
||||||
alreadyApplied = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (alreadyApplied) {
|
|
||||||
const delta = hunk.newLines - hunk.oldLines;
|
|
||||||
return offset + delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const raw of hunk.lines) {
|
|
||||||
const marker = raw[0];
|
|
||||||
const text = raw.slice(1);
|
|
||||||
if (marker === " ") {
|
|
||||||
if (lines[cursor] !== text) {
|
|
||||||
throw new Error(
|
|
||||||
`context mismatch at line ${cursor + 1}: expected "${text}", found "${lines[cursor] ?? "<eof>"}"`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
cursor += 1;
|
|
||||||
} else if (marker === "-") {
|
|
||||||
if (lines[cursor] !== text) {
|
|
||||||
throw new Error(
|
|
||||||
`delete mismatch at line ${cursor + 1}: expected "${text}", found "${lines[cursor] ?? "<eof>"}"`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
lines.splice(cursor, 1);
|
|
||||||
} else if (marker === "+") {
|
|
||||||
lines.splice(cursor, 0, text);
|
|
||||||
cursor += 1;
|
|
||||||
} else {
|
|
||||||
throw new Error(`unexpected hunk marker: ${marker}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const delta = hunk.newLines - hunk.oldLines;
|
|
||||||
return offset + delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyPatchToFile(targetDir, filePatch) {
|
|
||||||
if (filePatch.newPath === "/dev/null") {
|
|
||||||
// deletion not needed for our patches
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const relPath = stripPrefix(filePatch.newPath ?? filePatch.oldPath ?? "");
|
|
||||||
const targetPath = path.join(targetDir, relPath);
|
|
||||||
const { lines, hasTrailingNewline } = readFileLines(targetPath);
|
|
||||||
|
|
||||||
let offset = 0;
|
|
||||||
for (const hunk of filePatch.hunks) {
|
|
||||||
offset = applyHunk(lines, hunk, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileLines(targetPath, lines, hasTrailingNewline);
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyPatchSet({ patchText, targetDir }) {
|
|
||||||
let resolvedTarget = path.resolve(targetDir);
|
|
||||||
if (!fs.existsSync(resolvedTarget) || !fs.statSync(resolvedTarget).isDirectory()) {
|
|
||||||
console.warn(`[postinstall] skip missing target: ${resolvedTarget}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolvedTarget = fs.realpathSync(resolvedTarget);
|
|
||||||
|
|
||||||
const files = parsePatch(patchText);
|
|
||||||
if (files.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const filePatch of files) {
|
|
||||||
applyPatchToFile(resolvedTarget, filePatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyPatchFile({ patchPath, targetDir }) {
|
|
||||||
const absPatchPath = path.resolve(patchPath);
|
|
||||||
if (!fs.existsSync(absPatchPath)) {
|
|
||||||
throw new Error(`missing patch: ${absPatchPath}`);
|
|
||||||
}
|
|
||||||
const patchText = fs.readFileSync(absPatchPath, "utf-8");
|
|
||||||
applyPatchSet({ patchText, targetDir });
|
|
||||||
}
|
|
||||||
|
|
||||||
function trySetupCompletion(repoRoot) {
|
|
||||||
// Skip in CI or if explicitly disabled
|
|
||||||
if (process.env.CI || process.env.OPENCLAW_SKIP_COMPLETION_SETUP) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const binPath = path.join(repoRoot, "openclaw.mjs");
|
|
||||||
if (!fs.existsSync(binPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In development, dist might not exist yet during postinstall
|
|
||||||
const distEntry = path.join(repoRoot, "dist", "index.js");
|
|
||||||
if (!fs.existsSync(distEntry)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Run with OPENCLAW_SKIP_POSTINSTALL to avoid any weird recursion,
|
|
||||||
// though distinct from this script.
|
|
||||||
spawnSync(process.execPath, [binPath, "completion", "--install", "--yes", "--write-state"], {
|
|
||||||
cwd: repoRoot,
|
|
||||||
stdio: "inherit",
|
|
||||||
env: { ...process.env, OPENCLAW_SKIP_POSTINSTALL: "1" },
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// Ignore errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
const repoRoot = getRepoRoot();
|
|
||||||
process.chdir(repoRoot);
|
|
||||||
|
|
||||||
ensureExecutable(path.join(repoRoot, "dist", "entry.js"));
|
|
||||||
setupGitHooks({ repoRoot });
|
|
||||||
trySetupCompletion(repoRoot);
|
|
||||||
|
|
||||||
if (!shouldApplyPnpmPatchedDependenciesFallback()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pkgPath = path.join(repoRoot, "package.json");
|
|
||||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
||||||
const patched = pkg?.pnpm?.patchedDependencies ?? {};
|
|
||||||
|
|
||||||
// Bun does not support pnpm.patchedDependencies. Apply these patch files to
|
|
||||||
// node_modules packages as a best-effort compatibility layer.
|
|
||||||
for (const [key, relPatchPath] of Object.entries(patched)) {
|
|
||||||
if (typeof relPatchPath !== "string" || !relPatchPath.trim()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const pkgName = extractPackageName(String(key));
|
|
||||||
if (!pkgName) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
applyPatchFile({
|
|
||||||
targetDir: path.join("node_modules", ...pkgName.split("/")),
|
|
||||||
patchPath: relPatchPath,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const skip =
|
|
||||||
process.env.OPENCLAW_SKIP_POSTINSTALL === "1" ||
|
|
||||||
process.env.CLAWDBOT_SKIP_POSTINSTALL === "1" ||
|
|
||||||
process.env.VITEST === "true" ||
|
|
||||||
process.env.NODE_ENV === "test";
|
|
||||||
|
|
||||||
if (!skip) {
|
|
||||||
main();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(String(err));
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
applyPatchFile,
|
|
||||||
applyPatchSet,
|
|
||||||
applyPatchToFile,
|
|
||||||
detectPackageManager,
|
|
||||||
parsePatch,
|
|
||||||
shouldApplyPnpmPatchedDependenciesFallback,
|
|
||||||
};
|
|
||||||
@@ -208,7 +208,7 @@ async function tryWriteCompletionCache(root: string, jsonMode: boolean): Promise
|
|||||||
}
|
}
|
||||||
const result = spawnSync(resolveNodeRunner(), [binPath, "completion", "--write-state"], {
|
const result = spawnSync(resolveNodeRunner(), [binPath, "completion", "--write-state"], {
|
||||||
cwd: root,
|
cwd: root,
|
||||||
env: { ...process.env, OPENCLAW_SKIP_POSTINSTALL: "1" },
|
env: process.env,
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
});
|
});
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
import fs from "node:fs";
|
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
|
||||||
import { describe, expect, it } from "vitest";
|
|
||||||
import {
|
|
||||||
applyPatchSet,
|
|
||||||
detectPackageManager,
|
|
||||||
shouldApplyPnpmPatchedDependenciesFallback,
|
|
||||||
} from "../scripts/postinstall.js";
|
|
||||||
|
|
||||||
function makeTempDir() {
|
|
||||||
return fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-patch-"));
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("postinstall patcher", () => {
|
|
||||||
it("detects package manager from user agent", () => {
|
|
||||||
expect(detectPackageManager("pnpm/10.0.0 npm/? node/v22.0.0")).toBe("pnpm");
|
|
||||||
expect(detectPackageManager("npm/10.9.0 node/v22.0.0")).toBe("npm");
|
|
||||||
expect(detectPackageManager("bun/1.2.2")).toBe("bun");
|
|
||||||
expect(detectPackageManager("yarn/4.0.0 npm/? node/v22.0.0")).toBe("yarn");
|
|
||||||
expect(detectPackageManager("")).toBe("unknown");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("skips pnpm.patchedDependencies fallback for pnpm", () => {
|
|
||||||
expect(shouldApplyPnpmPatchedDependenciesFallback("pnpm")).toBe(false);
|
|
||||||
expect(shouldApplyPnpmPatchedDependenciesFallback("npm")).toBe(true);
|
|
||||||
expect(shouldApplyPnpmPatchedDependenciesFallback("bun")).toBe(true);
|
|
||||||
expect(shouldApplyPnpmPatchedDependenciesFallback("unknown")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("applies a simple patch", () => {
|
|
||||||
const dir = makeTempDir();
|
|
||||||
const target = path.join(dir, "lib");
|
|
||||||
fs.mkdirSync(target);
|
|
||||||
|
|
||||||
const filePath = path.join(target, "main.js");
|
|
||||||
const original = `${[
|
|
||||||
"var QRCode = require('./../vendor/QRCode'),",
|
|
||||||
" QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel'),",
|
|
||||||
' black = "\\033[40m \\033[0m",',
|
|
||||||
' white = "\\033[47m \\033[0m",',
|
|
||||||
" toCell = function (isBlack) {",
|
|
||||||
].join("\n")}\n`;
|
|
||||||
fs.writeFileSync(filePath, original, "utf-8");
|
|
||||||
|
|
||||||
const patchText = `diff --git a/lib/main.js b/lib/main.js
|
|
||||||
index 0000000..1111111 100644
|
|
||||||
--- a/lib/main.js
|
|
||||||
+++ b/lib/main.js
|
|
||||||
@@ -1,5 +1,5 @@
|
|
||||||
-var QRCode = require('./../vendor/QRCode'),
|
|
||||||
- QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel'),
|
|
||||||
+var QRCode = require('./../vendor/QRCode/index.js'),
|
|
||||||
+ QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel.js'),
|
|
||||||
black = "\\033[40m \\033[0m",
|
|
||||||
white = "\\033[47m \\033[0m",
|
|
||||||
toCell = function (isBlack) {
|
|
||||||
`;
|
|
||||||
|
|
||||||
applyPatchSet({ patchText, targetDir: dir });
|
|
||||||
|
|
||||||
const updated = fs.readFileSync(filePath, "utf-8");
|
|
||||||
expect(updated).toBe(
|
|
||||||
`${[
|
|
||||||
"var QRCode = require('./../vendor/QRCode/index.js'),",
|
|
||||||
" QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel.js'),",
|
|
||||||
' black = "\\033[40m \\033[0m",',
|
|
||||||
' white = "\\033[47m \\033[0m",',
|
|
||||||
" toCell = function (isBlack) {",
|
|
||||||
].join("\n")}\n`,
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.rmSync(dir, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("treats already-applied hunks as no-ops", () => {
|
|
||||||
const dir = makeTempDir();
|
|
||||||
const target = path.join(dir, "lib");
|
|
||||||
fs.mkdirSync(target);
|
|
||||||
|
|
||||||
const filePath = path.join(target, "main.js");
|
|
||||||
const original = `${[
|
|
||||||
"var QRCode = require('./../vendor/QRCode'),",
|
|
||||||
" QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel'),",
|
|
||||||
' black = "\\033[40m \\033[0m",',
|
|
||||||
' white = "\\033[47m \\033[0m",',
|
|
||||||
" toCell = function (isBlack) {",
|
|
||||||
].join("\n")}\n`;
|
|
||||||
fs.writeFileSync(filePath, original, "utf-8");
|
|
||||||
|
|
||||||
const patchText = `diff --git a/lib/main.js b/lib/main.js
|
|
||||||
index 0000000..1111111 100644
|
|
||||||
--- a/lib/main.js
|
|
||||||
+++ b/lib/main.js
|
|
||||||
@@ -1,5 +1,5 @@
|
|
||||||
-var QRCode = require('./../vendor/QRCode'),
|
|
||||||
- QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel'),
|
|
||||||
+var QRCode = require('./../vendor/QRCode/index.js'),
|
|
||||||
+ QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel.js'),
|
|
||||||
black = "\\033[40m \\033[0m",
|
|
||||||
white = "\\033[47m \\033[0m",
|
|
||||||
toCell = function (isBlack) {
|
|
||||||
`;
|
|
||||||
|
|
||||||
applyPatchSet({ patchText, targetDir: dir });
|
|
||||||
applyPatchSet({ patchText, targetDir: dir });
|
|
||||||
|
|
||||||
const updated = fs.readFileSync(filePath, "utf-8");
|
|
||||||
expect(updated).toBe(
|
|
||||||
`${[
|
|
||||||
"var QRCode = require('./../vendor/QRCode/index.js'),",
|
|
||||||
" QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel.js'),",
|
|
||||||
' black = "\\033[40m \\033[0m",',
|
|
||||||
' white = "\\033[47m \\033[0m",',
|
|
||||||
" toCell = function (isBlack) {",
|
|
||||||
].join("\n")}\n`,
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.rmSync(dir, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles multiple hunks with offsets", () => {
|
|
||||||
const dir = makeTempDir();
|
|
||||||
const filePath = path.join(dir, "file.txt");
|
|
||||||
fs.writeFileSync(
|
|
||||||
filePath,
|
|
||||||
`${["alpha", "beta", "gamma", "delta", "epsilon"].join("\n")}\n`,
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
|
|
||||||
const patchText = `diff --git a/file.txt b/file.txt
|
|
||||||
--- a/file.txt
|
|
||||||
+++ b/file.txt
|
|
||||||
@@ -1,3 +1,4 @@
|
|
||||||
alpha
|
|
||||||
beta
|
|
||||||
+beta2
|
|
||||||
gamma
|
|
||||||
@@ -3,3 +4,4 @@
|
|
||||||
gamma
|
|
||||||
-delta
|
|
||||||
+DELTA
|
|
||||||
epsilon
|
|
||||||
+zeta
|
|
||||||
`;
|
|
||||||
|
|
||||||
applyPatchSet({ patchText, targetDir: dir });
|
|
||||||
|
|
||||||
const updated = fs.readFileSync(filePath, "utf-8").trim().split("\n");
|
|
||||||
expect(updated).toEqual(["alpha", "beta", "beta2", "gamma", "DELTA", "epsilon", "zeta"]);
|
|
||||||
|
|
||||||
fs.rmSync(dir, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws on context mismatch", () => {
|
|
||||||
const dir = makeTempDir();
|
|
||||||
const filePath = path.join(dir, "file.txt");
|
|
||||||
fs.writeFileSync(filePath, "hello\nworld\n", "utf-8");
|
|
||||||
|
|
||||||
const patchText = `diff --git a/file.txt b/file.txt
|
|
||||||
--- a/file.txt
|
|
||||||
+++ b/file.txt
|
|
||||||
@@ -1,2 +1,2 @@
|
|
||||||
-hola
|
|
||||||
+hi
|
|
||||||
world
|
|
||||||
`;
|
|
||||||
|
|
||||||
expect(() => applyPatchSet({ patchText, targetDir: dir })).toThrow();
|
|
||||||
|
|
||||||
fs.rmSync(dir, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user