mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
build(pnpm): upgrade workspace to pnpm 11
This commit is contained in:
6
.npmrc
6
.npmrc
@@ -1,4 +1,2 @@
|
|||||||
# pnpm build-script allowlist lives in package.json -> pnpm.onlyBuiltDependencies.
|
# pnpm v11 reads project settings from pnpm-workspace.yaml.
|
||||||
# TS 7 native-preview fails to resolve packages reliably from pnpm's isolated linker.
|
# Keep this file for registry/auth-only npmrc entries so Docker COPY steps stay stable.
|
||||||
# Keep the workspace on a hoisted layout so pnpm check/build stay stable.
|
|
||||||
node-linker=hoisted
|
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ Package Acceptance has bounded legacy-compatibility windows for already-publishe
|
|||||||
|
|
||||||
- known private QA entries in `dist/postinstall-inventory.json` may point at tarball-omitted files;
|
- known private QA entries in `dist/postinstall-inventory.json` may point at tarball-omitted files;
|
||||||
- `doctor-switch` may skip the `gateway install --wrapper` persistence subcase when the package does not expose that flag;
|
- `doctor-switch` may skip the `gateway install --wrapper` persistence subcase when the package does not expose that flag;
|
||||||
- `update-channel-switch` may prune missing `pnpm.patchedDependencies` from the tarball-derived fake git fixture and may log missing persisted `update.channel`;
|
- `update-channel-switch` may prune missing pnpm `patchedDependencies` from the tarball-derived fake git fixture and may log missing persisted `update.channel`;
|
||||||
- plugin smokes may read legacy install-record locations or accept missing marketplace install-record persistence;
|
- plugin smokes may read legacy install-record locations or accept missing marketplace install-record persistence;
|
||||||
- `plugin-update` may allow config metadata migration while still requiring the install record and no-reinstall behavior to stay unchanged.
|
- `plugin-update` may allow config metadata migration while still requiring the install record and no-reinstall behavior to stay unchanged.
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ manually.
|
|||||||
Rebases onto the selected commit (dev only).
|
Rebases onto the selected commit (dev only).
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Install dependencies">
|
<Step title="Install dependencies">
|
||||||
Uses the repo package manager. For pnpm checkouts, the updater bootstraps `pnpm` on demand (via `corepack` first, then a temporary `npm install pnpm@10` fallback) instead of running `npm run build` inside a pnpm workspace.
|
Uses the repo package manager. For pnpm checkouts, the updater bootstraps `pnpm` on demand (via `corepack` first, then a temporary `npm install pnpm@11` fallback) instead of running `npm run build` inside a pnpm workspace.
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Build Control UI">
|
<Step title="Build Control UI">
|
||||||
Builds the gateway and the Control UI.
|
Builds the gateway and the Control UI.
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ Use this flow when OpenClaw needs unreleased ACPX changes before the ACPX versio
|
|||||||
|
|
||||||
1. Make the ACPX code change in the `openclaw/acpx` repo first.
|
1. Make the ACPX code change in the `openclaw/acpx` repo first.
|
||||||
2. In OpenClaw, temporarily point `extensions/acpx/package.json` at the ACPX GitHub commit you need.
|
2. In OpenClaw, temporarily point `extensions/acpx/package.json` at the ACPX GitHub commit you need.
|
||||||
3. If pnpm blocks ACPX lifecycle/build scripts for that temporary GitHub-sourced package, temporarily add `acpx` to `onlyBuiltDependencies` in both `package.json` and `pnpm-workspace.yaml`.
|
3. If pnpm blocks ACPX lifecycle/build scripts for that temporary GitHub-sourced package, temporarily add `acpx: true` to `allowBuilds` in `pnpm-workspace.yaml`.
|
||||||
4. Refresh the root workspace lock:
|
4. Refresh the root workspace lock:
|
||||||
- `pnpm install --lockfile-only --filter ./extensions/acpx`
|
- `pnpm install --lockfile-only --filter ./extensions/acpx`
|
||||||
5. Refresh the extension-local npm lock for install metadata:
|
5. Refresh the extension-local npm lock for install metadata:
|
||||||
- `cd extensions/acpx && npm install --package-lock-only --ignore-scripts`
|
- `cd extensions/acpx && npm install --package-lock-only --ignore-scripts`
|
||||||
6. Rebuild OpenClaw and restart the gateway before doing live ACP validation.
|
6. Rebuild OpenClaw and restart the gateway before doing live ACP validation.
|
||||||
7. Once ACPX is released, switch `extensions/acpx/package.json` back to the published npm version and refresh the same lockfiles again.
|
7. Once ACPX is released, switch `extensions/acpx/package.json` back to the published npm version and refresh the same lockfiles again.
|
||||||
8. Remove any temporary `acpx` build-script allowlist entries that were only needed for the GitHub-sourced development pin.
|
8. Remove any temporary `acpx` build-script allowlist entry that was only needed for the GitHub-sourced development pin.
|
||||||
|
|
||||||
## Lockfile Notes
|
## Lockfile Notes
|
||||||
|
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ qa_status=0
|
|||||||
{
|
{
|
||||||
set -e
|
set -e
|
||||||
echo "remote pwd: $(pwd)"
|
echo "remote pwd: $(pwd)"
|
||||||
sudo corepack enable || sudo npm install -g pnpm@10.33.2
|
sudo corepack enable || sudo npm install -g pnpm@11
|
||||||
if [ "$hydrate_mode" = "source" ]; then
|
if [ "$hydrate_mode" = "source" ]; then
|
||||||
if ! command -v make >/dev/null 2>&1 || ! command -v python3 >/dev/null 2>&1; then
|
if ! command -v make >/dev/null 2>&1 || ! command -v python3 >/dev/null 2>&1; then
|
||||||
sudo apt-get update -y >>"$out/apt.log" 2>&1 || true
|
sudo apt-get update -y >>"$out/apt.log" 2>&1 || true
|
||||||
|
|||||||
68
package.json
68
package.json
@@ -24,6 +24,7 @@
|
|||||||
"CHANGELOG.md",
|
"CHANGELOG.md",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"openclaw.mjs",
|
"openclaw.mjs",
|
||||||
|
"pnpm-workspace.yaml",
|
||||||
"README.md",
|
"README.md",
|
||||||
"dist/",
|
"dist/",
|
||||||
"!dist/.buildstamp",
|
"!dist/.buildstamp",
|
||||||
@@ -1814,70 +1815,5 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.16.0"
|
"node": ">=22.16.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8",
|
"packageManager": "pnpm@11.0.8+sha512.4c4097e1dd2d42372c4e7fa5a791ff28fc75a484c7ac192e64b1df0fdef17594ba982f9b4fed9adfb3c757846f565b799b2763fb3733d1de1bcb82cf46684912"
|
||||||
"pnpm": {
|
|
||||||
"overrides": {
|
|
||||||
"@anthropic-ai/sdk": "0.95.1",
|
|
||||||
"hono": "4.12.18",
|
|
||||||
"@hono/node-server": "1.19.14",
|
|
||||||
"@aws-sdk/client-bedrock-runtime": "3.1045.0",
|
|
||||||
"axios": "1.16.0",
|
|
||||||
"fast-uri": "3.1.2",
|
|
||||||
"follow-redirects": "1.16.0",
|
|
||||||
"defu": "6.1.5",
|
|
||||||
"fast-xml-parser": "5.7.0",
|
|
||||||
"request": "npm:@cypress/request@3.0.10",
|
|
||||||
"request-promise": "npm:@cypress/request-promise@5.0.0",
|
|
||||||
"basic-ftp": "6.0.1",
|
|
||||||
"file-type": "22.0.1",
|
|
||||||
"form-data": "2.5.4",
|
|
||||||
"ip-address": "10.2.0",
|
|
||||||
"minimatch": "10.2.5",
|
|
||||||
"path-to-regexp": "8.4.0",
|
|
||||||
"qs": "6.14.2",
|
|
||||||
"node-domexception": "npm:@nolyfill/domexception@1.0.28",
|
|
||||||
"typebox": "1.1.38",
|
|
||||||
"tar": "7.5.15",
|
|
||||||
"tough-cookie": "4.1.3",
|
|
||||||
"yauzl": "3.2.1",
|
|
||||||
"protobufjs": "7.5.5",
|
|
||||||
"uuid": "14.0.0"
|
|
||||||
},
|
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"@openclaw/fs-safe",
|
|
||||||
"@google/genai",
|
|
||||||
"@lydell/node-pty",
|
|
||||||
"@matrix-org/matrix-sdk-crypto-nodejs",
|
|
||||||
"@tloncorp/api",
|
|
||||||
"@tloncorp/tlon-skill",
|
|
||||||
"baileys",
|
|
||||||
"@whiskeysockets/libsignal-node",
|
|
||||||
"authenticate-pam",
|
|
||||||
"esbuild",
|
|
||||||
"node-llama-cpp",
|
|
||||||
"protobufjs",
|
|
||||||
"sharp"
|
|
||||||
],
|
|
||||||
"ignoredBuiltDependencies": [
|
|
||||||
"@discordjs/opus",
|
|
||||||
"koffi",
|
|
||||||
"tree-sitter-bash"
|
|
||||||
],
|
|
||||||
"packageExtensions": {
|
|
||||||
"@mariozechner/pi-coding-agent": {
|
|
||||||
"dependencies": {
|
|
||||||
"strip-ansi": "^7.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peerDependencyRules": {
|
|
||||||
"allowedVersions": {
|
|
||||||
"prism-media>opusscript": "^0.0.8 || ^0.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"patchedDependencies": {
|
|
||||||
"baileys@7.0.0-rc10": "patches/baileys@7.0.0-rc10.patch",
|
|
||||||
"@agentclientprotocol/claude-agent-acp@0.33.1": "patches/@agentclientprotocol__claude-agent-acp@0.33.1.patch"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@@ -3360,7 +3360,7 @@ packages:
|
|||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@openclaw/fs-safe@https://codeload.github.com/openclaw/fs-safe/tar.gz/c7ccb99d3058f2acf2ad2758ad2470c7e113a53c':
|
'@openclaw/fs-safe@https://codeload.github.com/openclaw/fs-safe/tar.gz/c7ccb99d3058f2acf2ad2758ad2470c7e113a53c':
|
||||||
resolution: {tarball: https://codeload.github.com/openclaw/fs-safe/tar.gz/c7ccb99d3058f2acf2ad2758ad2470c7e113a53c}
|
resolution: {gitHosted: true, tarball: https://codeload.github.com/openclaw/fs-safe/tar.gz/c7ccb99d3058f2acf2ad2758ad2470c7e113a53c}
|
||||||
version: 0.2.0
|
version: 0.2.0
|
||||||
engines: {node: '>=20.11'}
|
engines: {node: '>=20.11'}
|
||||||
|
|
||||||
@@ -4848,7 +4848,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-58rWEqDGg+CKCyEeKm2KoxxSwTWtHh/NLTW9ObR4K8CGF6VwuuGudEI1CtniS/oSRmL1nJq/eh8MKARiluw4DQ==}
|
resolution: {integrity: sha512-58rWEqDGg+CKCyEeKm2KoxxSwTWtHh/NLTW9ObR4K8CGF6VwuuGudEI1CtniS/oSRmL1nJq/eh8MKARiluw4DQ==}
|
||||||
|
|
||||||
'@whiskeysockets/libsignal-node@https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67':
|
'@whiskeysockets/libsignal-node@https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67':
|
||||||
resolution: {tarball: https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67}
|
resolution: {gitHosted: true, tarball: https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67}
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
|
|
||||||
'@zed-industries/codex-acp-darwin-arm64@0.14.0':
|
'@zed-industries/codex-acp-darwin-arm64@0.14.0':
|
||||||
|
|||||||
@@ -32,22 +32,63 @@ minimumReleaseAgeExclude:
|
|||||||
- "sqlite-vec"
|
- "sqlite-vec"
|
||||||
- "sqlite-vec-*"
|
- "sqlite-vec-*"
|
||||||
|
|
||||||
onlyBuiltDependencies:
|
nodeLinker: hoisted
|
||||||
- "@openclaw/fs-safe"
|
|
||||||
- "@google/genai"
|
|
||||||
- "@lydell/node-pty"
|
|
||||||
- "@matrix-org/matrix-sdk-crypto-nodejs"
|
|
||||||
- "@napi-rs/canvas"
|
|
||||||
- "@tloncorp/api"
|
|
||||||
- "baileys"
|
|
||||||
- "@whiskeysockets/libsignal-node"
|
|
||||||
- authenticate-pam
|
|
||||||
- esbuild
|
|
||||||
- node-llama-cpp
|
|
||||||
- protobufjs
|
|
||||||
- sharp
|
|
||||||
|
|
||||||
ignoredBuiltDependencies:
|
overrides:
|
||||||
- "@discordjs/opus"
|
"@anthropic-ai/sdk": 0.95.1
|
||||||
- koffi
|
hono: 4.12.18
|
||||||
- tree-sitter-bash
|
"@hono/node-server": 1.19.14
|
||||||
|
"@aws-sdk/client-bedrock-runtime": 3.1045.0
|
||||||
|
axios: 1.16.0
|
||||||
|
fast-uri: 3.1.2
|
||||||
|
follow-redirects: 1.16.0
|
||||||
|
defu: 6.1.5
|
||||||
|
fast-xml-parser: 5.7.0
|
||||||
|
request: "npm:@cypress/request@3.0.10"
|
||||||
|
request-promise: "npm:@cypress/request-promise@5.0.0"
|
||||||
|
basic-ftp: 6.0.1
|
||||||
|
file-type: 22.0.1
|
||||||
|
form-data: 2.5.4
|
||||||
|
ip-address: 10.2.0
|
||||||
|
minimatch: 10.2.5
|
||||||
|
path-to-regexp: 8.4.0
|
||||||
|
qs: 6.14.2
|
||||||
|
node-domexception: "npm:@nolyfill/domexception@1.0.28"
|
||||||
|
typebox: 1.1.38
|
||||||
|
tar: 7.5.15
|
||||||
|
tough-cookie: 4.1.3
|
||||||
|
yauzl: 3.2.1
|
||||||
|
protobufjs: 7.5.5
|
||||||
|
uuid: 14.0.0
|
||||||
|
|
||||||
|
allowBuilds:
|
||||||
|
"@openclaw/fs-safe": true
|
||||||
|
"@google/genai": true
|
||||||
|
"@lydell/node-pty": true
|
||||||
|
"@matrix-org/matrix-sdk-crypto-nodejs": true
|
||||||
|
"@napi-rs/canvas": true
|
||||||
|
"@tloncorp/api": true
|
||||||
|
"@tloncorp/tlon-skill": true
|
||||||
|
baileys: true
|
||||||
|
"@whiskeysockets/libsignal-node": true
|
||||||
|
authenticate-pam: true
|
||||||
|
"@discordjs/opus": false
|
||||||
|
esbuild: true
|
||||||
|
koffi: false
|
||||||
|
node-llama-cpp: true
|
||||||
|
protobufjs: true
|
||||||
|
sharp: true
|
||||||
|
tree-sitter-bash: false
|
||||||
|
|
||||||
|
packageExtensions:
|
||||||
|
"@mariozechner/pi-coding-agent":
|
||||||
|
dependencies:
|
||||||
|
strip-ansi: ^7.2.0
|
||||||
|
|
||||||
|
peerDependencyRules:
|
||||||
|
allowedVersions:
|
||||||
|
"prism-media>opusscript": "^0.0.8 || ^0.1.1"
|
||||||
|
|
||||||
|
patchedDependencies:
|
||||||
|
"baileys@7.0.0-rc10": "patches/baileys@7.0.0-rc10.patch"
|
||||||
|
"@agentclientprotocol/claude-agent-acp@0.33.1": "patches/@agentclientprotocol__claude-agent-acp@0.33.1.patch"
|
||||||
|
|||||||
@@ -16,6 +16,80 @@ function readJson(file) {
|
|||||||
return JSON.parse(fs.readFileSync(file, "utf8"));
|
return JSON.parse(fs.readFileSync(file, "utf8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs inside the bare Docker E2E image, before package dependencies are installed.
|
||||||
|
// Keep this to the small pnpm-workspace.yaml surface the fixture mutates.
|
||||||
|
function findTopLevelBlock(lines, key) {
|
||||||
|
const start = lines.findIndex((line) => new RegExp(`^${key}:\\s*(?:#.*)?$`).test(line));
|
||||||
|
if (start === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let end = start + 1;
|
||||||
|
while (end < lines.length && !/^[A-Za-z0-9_-]+:\s*/.test(lines[end])) {
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
return { start, end };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseYamlScalar(raw) {
|
||||||
|
const trimmed = raw.trim();
|
||||||
|
const withoutComment = trimmed.replace(/\s+#.*$/, "");
|
||||||
|
if (withoutComment.startsWith('"') && withoutComment.endsWith('"')) {
|
||||||
|
return withoutComment.slice(1, -1);
|
||||||
|
}
|
||||||
|
if (withoutComment.startsWith("'") && withoutComment.endsWith("'")) {
|
||||||
|
return withoutComment.slice(1, -1);
|
||||||
|
}
|
||||||
|
return withoutComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readWorkspacePatchedDependencies(file) {
|
||||||
|
const lines = fs.readFileSync(file, "utf8").split("\n");
|
||||||
|
const block = findTopLevelBlock(lines, "patchedDependencies");
|
||||||
|
if (!block) {
|
||||||
|
return { patches: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
const patches = {};
|
||||||
|
for (const line of lines.slice(block.start + 1, block.end)) {
|
||||||
|
const match = line.match(/^\s+(.+?):\s+(.+?)\s*$/);
|
||||||
|
if (!match) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
patches[parseYamlScalar(match[1])] = parseYamlScalar(match[2]);
|
||||||
|
}
|
||||||
|
return { patches };
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeWorkspacePnpmConfig(file, keptPatches) {
|
||||||
|
const original = fs.readFileSync(file, "utf8");
|
||||||
|
const hadTrailingNewline = original.endsWith("\n");
|
||||||
|
const lines = original.replace(/\n$/, "").split("\n");
|
||||||
|
const patchBlock = findTopLevelBlock(lines, "patchedDependencies");
|
||||||
|
|
||||||
|
if (patchBlock) {
|
||||||
|
const nextLines = [];
|
||||||
|
nextLines.push(...lines.slice(0, patchBlock.start));
|
||||||
|
if (Object.keys(keptPatches).length > 0) {
|
||||||
|
nextLines.push("patchedDependencies:");
|
||||||
|
for (const [dependency, patchFile] of Object.entries(keptPatches)) {
|
||||||
|
nextLines.push(` ${JSON.stringify(dependency)}: ${JSON.stringify(patchFile)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextLines.push(...lines.slice(patchBlock.end));
|
||||||
|
lines.length = 0;
|
||||||
|
lines.push(...nextLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowUnusedIndex = lines.findIndex((line) => /^allowUnusedPatches:\s*/.test(line));
|
||||||
|
if (allowUnusedIndex === -1) {
|
||||||
|
lines.push("allowUnusedPatches: true");
|
||||||
|
} else {
|
||||||
|
lines[allowUnusedIndex] = "allowUnusedPatches: true";
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(file, `${lines.join("\n")}${hadTrailingNewline ? "\n" : ""}`);
|
||||||
|
}
|
||||||
|
|
||||||
function writeControlUi(root) {
|
function writeControlUi(root) {
|
||||||
const file = path.join(root, "dist", "control-ui", "index.html");
|
const file = path.join(root, "dist", "control-ui", "index.html");
|
||||||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||||
@@ -25,31 +99,41 @@ function writeControlUi(root) {
|
|||||||
function prepareGitFixture(root) {
|
function prepareGitFixture(root) {
|
||||||
const packageJsonPath = path.join(root, "package.json");
|
const packageJsonPath = path.join(root, "package.json");
|
||||||
const packageJson = readJson(packageJsonPath);
|
const packageJson = readJson(packageJsonPath);
|
||||||
packageJson.pnpm = { ...packageJson.pnpm, allowUnusedPatches: true };
|
const pnpmWorkspacePath = path.join(root, "pnpm-workspace.yaml");
|
||||||
const patches = packageJson.pnpm.patchedDependencies;
|
const workspaceConfig = fs.existsSync(pnpmWorkspacePath)
|
||||||
|
? readWorkspacePatchedDependencies(pnpmWorkspacePath)
|
||||||
|
: undefined;
|
||||||
|
const pnpmConfig = workspaceConfig ? {} : { ...packageJson.pnpm };
|
||||||
|
const patches = workspaceConfig?.patches ?? pnpmConfig.patchedDependencies;
|
||||||
|
const keptPatches = {};
|
||||||
if (patches && typeof patches === "object" && !Array.isArray(patches)) {
|
if (patches && typeof patches === "object" && !Array.isArray(patches)) {
|
||||||
const kept = {};
|
|
||||||
const missing = [];
|
const missing = [];
|
||||||
for (const [dependency, patchFile] of Object.entries(patches)) {
|
for (const [dependency, patchFile] of Object.entries(patches)) {
|
||||||
const exists =
|
const exists =
|
||||||
typeof patchFile === "string" &&
|
typeof patchFile === "string" &&
|
||||||
fs.existsSync(path.resolve(path.dirname(packageJsonPath), patchFile));
|
fs.existsSync(path.resolve(path.dirname(packageJsonPath), patchFile));
|
||||||
if (exists) {
|
if (exists) {
|
||||||
kept[dependency] = patchFile;
|
keptPatches[dependency] = patchFile;
|
||||||
} else {
|
} else {
|
||||||
missing.push(`${dependency} -> ${String(patchFile)}`);
|
missing.push(`${dependency} -> ${String(patchFile)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (missing.length > 0 && !legacyPackageAcceptanceCompat(packageJson.version)) {
|
if (missing.length > 0 && !legacyPackageAcceptanceCompat(packageJson.version)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`package ${packageJson.version} has missing pnpm.patchedDependencies in package fixture: ${missing.join(", ")}`,
|
`package ${packageJson.version} has missing pnpm patchedDependencies in package fixture: ${missing.join(", ")}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (Object.keys(kept).length > 0) {
|
}
|
||||||
packageJson.pnpm.patchedDependencies = kept;
|
if (workspaceConfig) {
|
||||||
|
writeWorkspacePnpmConfig(pnpmWorkspacePath, keptPatches);
|
||||||
|
} else {
|
||||||
|
pnpmConfig.allowUnusedPatches = true;
|
||||||
|
if (Object.keys(keptPatches).length > 0) {
|
||||||
|
pnpmConfig.patchedDependencies = keptPatches;
|
||||||
} else {
|
} else {
|
||||||
delete packageJson.pnpm.patchedDependencies;
|
delete pnpmConfig.patchedDependencies;
|
||||||
}
|
}
|
||||||
|
packageJson.pnpm = pnpmConfig;
|
||||||
}
|
}
|
||||||
const fixtureUiBuildSource = `const fs=require("node:fs");fs.mkdirSync("dist/control-ui",{recursive:true});fs.writeFileSync("dist/control-ui/index.html",${JSON.stringify(controlUiHtml)})`;
|
const fixtureUiBuildSource = `const fs=require("node:fs");fs.mkdirSync("dist/control-ui",{recursive:true});fs.writeFileSync("dist/control-ui/index.html",${JSON.stringify(controlUiHtml)})`;
|
||||||
packageJson.scripts = {
|
packageJson.scripts = {
|
||||||
|
|||||||
@@ -851,7 +851,7 @@ fi
|
|||||||
echo "bootstrap-pnpm: install"
|
echo "bootstrap-pnpm: install"
|
||||||
rm -rf "$bootstrap_root"
|
rm -rf "$bootstrap_root"
|
||||||
mkdir -p "$bootstrap_root"
|
mkdir -p "$bootstrap_root"
|
||||||
/opt/homebrew/bin/node /opt/homebrew/bin/npm install --prefix "$bootstrap_root" --no-save pnpm@10
|
/opt/homebrew/bin/node /opt/homebrew/bin/npm install --prefix "$bootstrap_root" --no-save pnpm@11
|
||||||
"$bootstrap_bin/pnpm" --version`);
|
"$bootstrap_bin/pnpm" --version`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1839,7 +1839,7 @@ ensure_pnpm() {
|
|||||||
if command -v corepack &> /dev/null; then
|
if command -v corepack &> /dev/null; then
|
||||||
ui_info "Configuring pnpm via Corepack"
|
ui_info "Configuring pnpm via Corepack"
|
||||||
corepack enable >/dev/null 2>&1 || true
|
corepack enable >/dev/null 2>&1 || true
|
||||||
if ! run_quiet_step "Activating pnpm" corepack prepare pnpm@10 --activate; then
|
if ! run_quiet_step "Activating pnpm" corepack prepare pnpm@11 --activate; then
|
||||||
ui_warn "Corepack pnpm activation failed; falling back"
|
ui_warn "Corepack pnpm activation failed; falling back"
|
||||||
fi
|
fi
|
||||||
refresh_shell_command_cache
|
refresh_shell_command_cache
|
||||||
@@ -1854,7 +1854,7 @@ ensure_pnpm() {
|
|||||||
|
|
||||||
ui_info "Installing pnpm via npm"
|
ui_info "Installing pnpm via npm"
|
||||||
fix_npm_permissions
|
fix_npm_permissions
|
||||||
run_quiet_step "Installing pnpm" npm install -g pnpm@10
|
run_quiet_step "Installing pnpm" npm install -g pnpm@11
|
||||||
refresh_shell_command_cache
|
refresh_shell_command_cache
|
||||||
if detect_pnpm_cmd && pnpm_cmd_is_ready; then
|
if detect_pnpm_cmd && pnpm_cmd_is_ready; then
|
||||||
ui_success "pnpm ready ($(pnpm_cmd_pretty))"
|
ui_success "pnpm ready ($(pnpm_cmd_pretty))"
|
||||||
@@ -1873,7 +1873,7 @@ ensure_pnpm_binary_for_scripts() {
|
|||||||
if command -v corepack >/dev/null 2>&1; then
|
if command -v corepack >/dev/null 2>&1; then
|
||||||
ui_info "Ensuring pnpm command is available"
|
ui_info "Ensuring pnpm command is available"
|
||||||
corepack enable >/dev/null 2>&1 || true
|
corepack enable >/dev/null 2>&1 || true
|
||||||
corepack prepare pnpm@10 --activate >/dev/null 2>&1 || true
|
corepack prepare pnpm@11 --activate >/dev/null 2>&1 || true
|
||||||
refresh_shell_command_cache
|
refresh_shell_command_cache
|
||||||
if command -v pnpm >/dev/null 2>&1; then
|
if command -v pnpm >/dev/null 2>&1; then
|
||||||
ui_success "pnpm command enabled via Corepack"
|
ui_success "pnpm command enabled via Corepack"
|
||||||
@@ -1899,7 +1899,7 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
ui_error "pnpm command not available on PATH"
|
ui_error "pnpm command not available on PATH"
|
||||||
ui_info "Install pnpm globally (npm install -g pnpm@10) and retry"
|
ui_info "Install pnpm globally (npm install -g pnpm@11) and retry"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
48
src/commands/doctor-install.test.ts
Normal file
48
src/commands/doctor-install.test.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { note } from "../terminal/note.js";
|
||||||
|
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||||
|
import { noteSourceInstallIssues } from "./doctor-install.js";
|
||||||
|
|
||||||
|
vi.mock("../terminal/note.js", () => ({
|
||||||
|
note: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
async function writeFile(root: string, relativePath: string, content = "") {
|
||||||
|
const file = path.join(root, relativePath);
|
||||||
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
||||||
|
await fs.writeFile(file, content, "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("noteSourceInstallIssues", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.mocked(note).mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not treat a packaged workspace config as a source checkout", async () => {
|
||||||
|
await withTempDir({ prefix: "openclaw-doctor-install-" }, async (root) => {
|
||||||
|
await fs.mkdir(path.join(root, "node_modules"), { recursive: true });
|
||||||
|
await writeFile(root, "pnpm-workspace.yaml", "packages:\n - .\n");
|
||||||
|
|
||||||
|
noteSourceInstallIssues(root);
|
||||||
|
|
||||||
|
expect(note).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("warns source checkouts when node_modules was not installed by pnpm", async () => {
|
||||||
|
await withTempDir({ prefix: "openclaw-doctor-install-" }, async (root) => {
|
||||||
|
await fs.mkdir(path.join(root, "node_modules"), { recursive: true });
|
||||||
|
await writeFile(root, "pnpm-workspace.yaml", "packages:\n - .\n");
|
||||||
|
await writeFile(root, "src/entry.ts", "export {};\n");
|
||||||
|
|
||||||
|
noteSourceInstallIssues(root);
|
||||||
|
|
||||||
|
expect(note).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("node_modules was not installed by pnpm"),
|
||||||
|
"Install",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,8 +7,9 @@ export function noteSourceInstallIssues(root: string | null) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const srcEntry = path.join(root, "src", "entry.ts");
|
||||||
const workspaceMarker = path.join(root, "pnpm-workspace.yaml");
|
const workspaceMarker = path.join(root, "pnpm-workspace.yaml");
|
||||||
if (!fs.existsSync(workspaceMarker)) {
|
if (!fs.existsSync(workspaceMarker) || !fs.existsSync(srcEntry)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,7 +17,6 @@ export function noteSourceInstallIssues(root: string | null) {
|
|||||||
const nodeModules = path.join(root, "node_modules");
|
const nodeModules = path.join(root, "node_modules");
|
||||||
const pnpmStore = path.join(nodeModules, ".pnpm");
|
const pnpmStore = path.join(nodeModules, ".pnpm");
|
||||||
const tsxBin = path.join(nodeModules, ".bin", "tsx");
|
const tsxBin = path.join(nodeModules, ".bin", "tsx");
|
||||||
const srcEntry = path.join(root, "src", "entry.ts");
|
|
||||||
|
|
||||||
if (fs.existsSync(nodeModules) && !fs.existsSync(pnpmStore)) {
|
if (fs.existsSync(nodeModules) && !fs.existsSync(pnpmStore)) {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { join, resolve } from "node:path";
|
|||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { BUNDLED_PLUGIN_ROOT_DIR } from "openclaw/plugin-sdk/test-fixtures";
|
import { BUNDLED_PLUGIN_ROOT_DIR } from "openclaw/plugin-sdk/test-fixtures";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import YAML from "yaml";
|
||||||
|
|
||||||
const repoRoot = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
|
const repoRoot = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
|
||||||
const dockerfilePath = join(repoRoot, "Dockerfile");
|
const dockerfilePath = join(repoRoot, "Dockerfile");
|
||||||
const packageJsonPath = join(repoRoot, "package.json");
|
const pnpmWorkspacePath = join(repoRoot, "pnpm-workspace.yaml");
|
||||||
|
|
||||||
function collapseDockerContinuations(dockerfile: string): string {
|
function collapseDockerContinuations(dockerfile: string): string {
|
||||||
return dockerfile.replace(/\\\r?\n[ \t]*/g, " ");
|
return dockerfile.replace(/\\\r?\n[ \t]*/g, " ");
|
||||||
@@ -140,11 +141,11 @@ describe("Dockerfile", () => {
|
|||||||
|
|
||||||
it("keeps package manager patch files in runtime images", async () => {
|
it("keeps package manager patch files in runtime images", async () => {
|
||||||
const dockerfile = await readFile(dockerfilePath, "utf8");
|
const dockerfile = await readFile(dockerfilePath, "utf8");
|
||||||
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")) as {
|
const pnpmWorkspace = YAML.parse(await readFile(pnpmWorkspacePath, "utf8")) as {
|
||||||
pnpm?: { patchedDependencies?: Record<string, string> };
|
patchedDependencies?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(Object.keys(packageJson.pnpm?.patchedDependencies ?? {})).not.toHaveLength(0);
|
expect(Object.keys(pnpmWorkspace.patchedDependencies ?? {})).not.toHaveLength(0);
|
||||||
expect(dockerfile).toContain(
|
expect(dockerfile).toContain(
|
||||||
"COPY --from=runtime-assets --chown=node:node /app/patches ./patches",
|
"COPY --from=runtime-assets --chown=node:node /app/patches ./patches",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe("resolveUpdateBuildManager", () => {
|
|||||||
const envPath = options.env?.PATH ?? options.env?.Path ?? "";
|
const envPath = options.env?.PATH ?? options.env?.Path ?? "";
|
||||||
if (envPath.includes("openclaw-update-pnpm-")) {
|
if (envPath.includes("openclaw-update-pnpm-")) {
|
||||||
paths.push(envPath);
|
paths.push(envPath);
|
||||||
return { stdout: "10.0.0", stderr: "", code: 0 };
|
return { stdout: "11.0.0", stderr: "", code: 0 };
|
||||||
}
|
}
|
||||||
throw new Error("spawn pnpm ENOENT");
|
throw new Error("spawn pnpm ENOENT");
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ describe("resolveUpdateBuildManager", () => {
|
|||||||
if (key === "npm --version") {
|
if (key === "npm --version") {
|
||||||
return { stdout: "10.0.0", stderr: "", code: 0 };
|
return { stdout: "10.0.0", stderr: "", code: 0 };
|
||||||
}
|
}
|
||||||
if (key.startsWith("npm install --prefix ") && key.endsWith(" pnpm@10")) {
|
if (key.startsWith("npm install --prefix ") && key.endsWith(" pnpm@11")) {
|
||||||
return { stdout: "added 1 package", stderr: "", code: 0 };
|
return { stdout: "added 1 package", stderr: "", code: 0 };
|
||||||
}
|
}
|
||||||
return { stdout: "", stderr: "", code: 0 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
@@ -53,7 +53,7 @@ describe("resolveUpdateBuildManager", () => {
|
|||||||
if (key === "npm --version") {
|
if (key === "npm --version") {
|
||||||
return { stdout: "10.0.0", stderr: "", code: 0 };
|
return { stdout: "10.0.0", stderr: "", code: 0 };
|
||||||
}
|
}
|
||||||
if (key.startsWith("npm install --prefix ") && key.endsWith(" pnpm@10")) {
|
if (key.startsWith("npm install --prefix ") && key.endsWith(" pnpm@11")) {
|
||||||
return { stdout: "", stderr: "network exploded", code: 1 };
|
return { stdout: "", stderr: "network exploded", code: 1 };
|
||||||
}
|
}
|
||||||
return { stdout: "", stderr: "", code: 0 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ type ResolvedBuildManager =
|
|||||||
reason: UpdatePackageManagerFailureReason;
|
reason: UpdatePackageManagerFailureReason;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PNPM_NPM_FALLBACK_SPEC = "pnpm@11";
|
||||||
|
|
||||||
async function detectBuildManager(root: string): Promise<BuildManager> {
|
async function detectBuildManager(root: string): Promise<BuildManager> {
|
||||||
return (await detectPackageManagerImpl(root)) ?? "npm";
|
return (await detectPackageManagerImpl(root)) ?? "npm";
|
||||||
}
|
}
|
||||||
@@ -124,7 +126,7 @@ async function bootstrapPnpmViaNpm(params: {
|
|||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const installResult = await params.runCommand(
|
const installResult = await params.runCommand(
|
||||||
["npm", "install", "--prefix", tempRoot, "pnpm@10"],
|
["npm", "install", "--prefix", tempRoot, PNPM_NPM_FALLBACK_SPEC],
|
||||||
{
|
{
|
||||||
timeoutMs: params.timeoutMs,
|
timeoutMs: params.timeoutMs,
|
||||||
env: params.baseEnv,
|
env: params.baseEnv,
|
||||||
|
|||||||
@@ -438,7 +438,7 @@ describe("runGatewayUpdate", () => {
|
|||||||
if (key === "pnpm --version") {
|
if (key === "pnpm --version") {
|
||||||
const envPath = options?.env?.PATH ?? options?.env?.Path ?? "";
|
const envPath = options?.env?.PATH ?? options?.env?.Path ?? "";
|
||||||
if (envPath.includes("openclaw-update-pnpm-")) {
|
if (envPath.includes("openclaw-update-pnpm-")) {
|
||||||
return { stdout: "10.0.0" };
|
return { stdout: "11.0.0" };
|
||||||
}
|
}
|
||||||
throw new Error("spawn pnpm ENOENT");
|
throw new Error("spawn pnpm ENOENT");
|
||||||
}
|
}
|
||||||
@@ -448,7 +448,7 @@ describe("runGatewayUpdate", () => {
|
|||||||
if (key === "npm --version") {
|
if (key === "npm --version") {
|
||||||
return { stdout: "10.0.0" };
|
return { stdout: "10.0.0" };
|
||||||
}
|
}
|
||||||
if (key.startsWith("npm install --prefix ") && key.endsWith(" pnpm@10")) {
|
if (key.startsWith("npm install --prefix ") && key.endsWith(" pnpm@11")) {
|
||||||
return { stdout: "added 1 package" };
|
return { stdout: "added 1 package" };
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -550,7 +550,7 @@ describe("runGatewayUpdate", () => {
|
|||||||
const envPath = options?.env?.PATH ?? options?.env?.Path ?? "";
|
const envPath = options?.env?.PATH ?? options?.env?.Path ?? "";
|
||||||
if (envPath.includes("openclaw-update-pnpm-")) {
|
if (envPath.includes("openclaw-update-pnpm-")) {
|
||||||
pnpmEnvPaths.push(envPath);
|
pnpmEnvPaths.push(envPath);
|
||||||
return { stdout: "10.0.0", stderr: "", code: 0 };
|
return { stdout: "11.0.0", stderr: "", code: 0 };
|
||||||
}
|
}
|
||||||
throw new Error("spawn pnpm ENOENT");
|
throw new Error("spawn pnpm ENOENT");
|
||||||
}
|
}
|
||||||
@@ -560,7 +560,7 @@ describe("runGatewayUpdate", () => {
|
|||||||
if (key === "npm --version") {
|
if (key === "npm --version") {
|
||||||
return { stdout: "10.0.0", stderr: "", code: 0 };
|
return { stdout: "10.0.0", stderr: "", code: 0 };
|
||||||
}
|
}
|
||||||
if (key.startsWith("npm install --prefix ") && key.endsWith(" pnpm@10")) {
|
if (key.startsWith("npm install --prefix ") && key.endsWith(" pnpm@11")) {
|
||||||
return { stdout: "added 1 package", stderr: "", code: 0 };
|
return { stdout: "added 1 package", stderr: "", code: 0 };
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -1412,7 +1412,7 @@ describe("runGatewayUpdate", () => {
|
|||||||
if (key === "npm --version") {
|
if (key === "npm --version") {
|
||||||
return { stdout: "10.0.0", stderr: "", code: 0 };
|
return { stdout: "10.0.0", stderr: "", code: 0 };
|
||||||
}
|
}
|
||||||
if (key.startsWith("npm install --prefix ") && key.endsWith(" pnpm@10")) {
|
if (key.startsWith("npm install --prefix ") && key.endsWith(" pnpm@11")) {
|
||||||
return { stdout: "", stderr: "network exploded", code: 1 };
|
return { stdout: "", stderr: "network exploded", code: 1 };
|
||||||
}
|
}
|
||||||
return { stdout: "", stderr: "", code: 0 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import YAML from "yaml";
|
||||||
import {
|
import {
|
||||||
blockedInstallDependencyPackageNames,
|
blockedInstallDependencyPackageNames,
|
||||||
findBlockedPackageDirectoryInPath,
|
findBlockedPackageDirectoryInPath,
|
||||||
@@ -15,9 +16,10 @@ type RootPackageManifest = {
|
|||||||
optionalDependencies?: Record<string, string>;
|
optionalDependencies?: Record<string, string>;
|
||||||
overrides?: Record<string, string | Record<string, string>>;
|
overrides?: Record<string, string | Record<string, string>>;
|
||||||
peerDependencies?: Record<string, string>;
|
peerDependencies?: Record<string, string>;
|
||||||
pnpm?: {
|
};
|
||||||
overrides?: Record<string, string>;
|
|
||||||
};
|
type PnpmWorkspaceConfig = {
|
||||||
|
overrides?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function readRootManifest(): RootPackageManifest {
|
function readRootManifest(): RootPackageManifest {
|
||||||
@@ -26,6 +28,12 @@ function readRootManifest(): RootPackageManifest {
|
|||||||
) as RootPackageManifest;
|
) as RootPackageManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readPnpmWorkspaceConfig(): PnpmWorkspaceConfig {
|
||||||
|
return YAML.parse(
|
||||||
|
fs.readFileSync(path.resolve(process.cwd(), "pnpm-workspace.yaml"), "utf8"),
|
||||||
|
) as PnpmWorkspaceConfig;
|
||||||
|
}
|
||||||
|
|
||||||
function readRootLockfile(): string {
|
function readRootLockfile(): string {
|
||||||
return fs.readFileSync(path.resolve(process.cwd(), "pnpm-lock.yaml"), "utf8");
|
return fs.readFileSync(path.resolve(process.cwd(), "pnpm-lock.yaml"), "utf8");
|
||||||
}
|
}
|
||||||
@@ -84,8 +92,9 @@ describe("dependency denylist guardrails", () => {
|
|||||||
|
|
||||||
it("pins the axios override to an exact version", () => {
|
it("pins the axios override to an exact version", () => {
|
||||||
const manifest = readRootManifest();
|
const manifest = readRootManifest();
|
||||||
|
const pnpmWorkspace = readPnpmWorkspaceConfig();
|
||||||
expect(manifest.overrides?.axios).toMatch(/^\d+\.\d+\.\d+$/);
|
expect(manifest.overrides?.axios).toMatch(/^\d+\.\d+\.\d+$/);
|
||||||
expect(manifest.pnpm?.overrides?.axios).toMatch(/^\d+\.\d+\.\d+$/);
|
expect(pnpmWorkspace.overrides?.axios).toMatch(/^\d+\.\d+\.\d+$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("finds blocked package directories under node_modules regardless of node_modules casing", () => {
|
it("finds blocked package directories under node_modules regardless of node_modules casing", () => {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import YAML from "yaml";
|
||||||
|
|
||||||
type RootPackageManifest = {
|
type RootPackageManifest = {
|
||||||
dependencies?: Record<string, string>;
|
dependencies?: Record<string, string>;
|
||||||
pnpm?: {
|
};
|
||||||
overrides?: Record<string, string>;
|
|
||||||
};
|
type PnpmWorkspaceConfig = {
|
||||||
|
overrides?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PI_PACKAGE_NAMES = [
|
const PI_PACKAGE_NAMES = [
|
||||||
@@ -21,6 +23,11 @@ function readRootManifest(): RootPackageManifest {
|
|||||||
return JSON.parse(fs.readFileSync(manifestPath, "utf8")) as RootPackageManifest;
|
return JSON.parse(fs.readFileSync(manifestPath, "utf8")) as RootPackageManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readPnpmWorkspaceConfig(): PnpmWorkspaceConfig {
|
||||||
|
const workspacePath = path.resolve(process.cwd(), "pnpm-workspace.yaml");
|
||||||
|
return YAML.parse(fs.readFileSync(workspacePath, "utf8")) as PnpmWorkspaceConfig;
|
||||||
|
}
|
||||||
|
|
||||||
function isExactPinnedVersion(spec: string): boolean {
|
function isExactPinnedVersion(spec: string): boolean {
|
||||||
return !spec.startsWith("^") && !spec.startsWith("~");
|
return !spec.startsWith("^") && !spec.startsWith("~");
|
||||||
}
|
}
|
||||||
@@ -76,8 +83,8 @@ describe("pi package graph guardrails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("forbids pnpm overrides that target Pi packages", () => {
|
it("forbids pnpm overrides that target Pi packages", () => {
|
||||||
const manifest = readRootManifest();
|
const pnpmWorkspace = readPnpmWorkspaceConfig();
|
||||||
const overrides = manifest.pnpm?.overrides ?? {};
|
const overrides = pnpmWorkspace.overrides ?? {};
|
||||||
const piOverrides = Object.keys(overrides).filter(isPiOverrideKey);
|
const piOverrides = Object.keys(overrides).filter(isPiOverrideKey);
|
||||||
|
|
||||||
expectNoGraphViolations(
|
expectNoGraphViolations(
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { readFileSync } from "node:fs";
|
import { execFileSync } from "node:child_process";
|
||||||
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
const HELPER_PATH = "scripts/lib/docker-build.sh";
|
const HELPER_PATH = "scripts/lib/docker-build.sh";
|
||||||
@@ -225,6 +228,50 @@ describe("docker build helper", () => {
|
|||||||
expect(pluginsAssertions).toContain("expected modern installRecords in installed plugin index");
|
expect(pluginsAssertions).toContain("expected modern installRecords in installed plugin index");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prepares pnpm workspace package fixtures without package dependencies", () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), "openclaw-update-channel-fixture-"));
|
||||||
|
try {
|
||||||
|
mkdirSync(join(root, "patches"));
|
||||||
|
writeFileSync(
|
||||||
|
join(root, "package.json"),
|
||||||
|
`${JSON.stringify({ name: "openclaw", version: "2026.5.6", scripts: {} }, null, 2)}\n`,
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
writeFileSync(
|
||||||
|
join(root, "pnpm-workspace.yaml"),
|
||||||
|
[
|
||||||
|
"packages:",
|
||||||
|
" - .",
|
||||||
|
"",
|
||||||
|
"patchedDependencies:",
|
||||||
|
' "kept@1.0.0": "patches/kept.patch"',
|
||||||
|
"allowBuilds:",
|
||||||
|
" esbuild: true",
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
writeFileSync(join(root, "patches", "kept.patch"), "", "utf8");
|
||||||
|
|
||||||
|
execFileSync(process.execPath, [
|
||||||
|
UPDATE_CHANNEL_SWITCH_ASSERTIONS_PATH,
|
||||||
|
"prepare-git-fixture",
|
||||||
|
root,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const workspace = readFileSync(join(root, "pnpm-workspace.yaml"), "utf8");
|
||||||
|
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
|
||||||
|
pnpm?: unknown;
|
||||||
|
};
|
||||||
|
expect(workspace).toContain(' "kept@1.0.0": "patches/kept.patch"');
|
||||||
|
expect(workspace).toContain("allowUnusedPatches: true");
|
||||||
|
expect(workspace).toContain("allowBuilds:");
|
||||||
|
expect(manifest.pnpm).toBeUndefined();
|
||||||
|
} finally {
|
||||||
|
rmSync(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps bundled plugin install/uninstall sweep chunkable", () => {
|
it("keeps bundled plugin install/uninstall sweep chunkable", () => {
|
||||||
const runner = readFileSync(BUNDLED_PLUGIN_INSTALL_UNINSTALL_E2E_PATH, "utf8");
|
const runner = readFileSync(BUNDLED_PLUGIN_INSTALL_UNINSTALL_E2E_PATH, "utf8");
|
||||||
const sweep = readFileSync(BUNDLED_PLUGIN_INSTALL_UNINSTALL_SWEEP_PATH, "utf8");
|
const sweep = readFileSync(BUNDLED_PLUGIN_INSTALL_UNINSTALL_SWEEP_PATH, "utf8");
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import YAML from "yaml";
|
||||||
|
|
||||||
type RootPackageManifest = {
|
type RootPackageManifest = {
|
||||||
dependencies?: Record<string, string>;
|
dependencies?: Record<string, string>;
|
||||||
overrides?: Record<string, string>;
|
overrides?: Record<string, string>;
|
||||||
pnpm?: {
|
};
|
||||||
overrides?: Record<string, string>;
|
|
||||||
};
|
type PnpmWorkspaceConfig = {
|
||||||
|
overrides?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function readRootManifest(): RootPackageManifest {
|
function readRootManifest(): RootPackageManifest {
|
||||||
@@ -15,13 +17,19 @@ function readRootManifest(): RootPackageManifest {
|
|||||||
return JSON.parse(fs.readFileSync(manifestPath, "utf8")) as RootPackageManifest;
|
return JSON.parse(fs.readFileSync(manifestPath, "utf8")) as RootPackageManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readPnpmWorkspaceConfig(): PnpmWorkspaceConfig {
|
||||||
|
const workspacePath = path.resolve(process.cwd(), "pnpm-workspace.yaml");
|
||||||
|
return YAML.parse(fs.readFileSync(workspacePath, "utf8")) as PnpmWorkspaceConfig;
|
||||||
|
}
|
||||||
|
|
||||||
describe("root package override guardrails", () => {
|
describe("root package override guardrails", () => {
|
||||||
it("pins the Bedrock runtime below the Windows ARM Node 24 npm resolver failure", () => {
|
it("pins the Bedrock runtime below the Windows ARM Node 24 npm resolver failure", () => {
|
||||||
const manifest = readRootManifest();
|
const manifest = readRootManifest();
|
||||||
|
const pnpmWorkspace = readPnpmWorkspaceConfig();
|
||||||
const packageName = "@aws-sdk/client-bedrock-runtime";
|
const packageName = "@aws-sdk/client-bedrock-runtime";
|
||||||
const dependencyVersion = manifest.dependencies?.[packageName];
|
const dependencyVersion = manifest.dependencies?.[packageName];
|
||||||
const npmOverride = manifest.overrides?.[packageName];
|
const npmOverride = manifest.overrides?.[packageName];
|
||||||
const pnpmOverride = manifest.pnpm?.overrides?.["@aws-sdk/client-bedrock-runtime"];
|
const pnpmOverride = pnpmWorkspace.overrides?.["@aws-sdk/client-bedrock-runtime"];
|
||||||
|
|
||||||
expect(manifest.dependencies).toHaveProperty(packageName);
|
expect(manifest.dependencies).toHaveProperty(packageName);
|
||||||
expect(pnpmOverride).toBe(dependencyVersion);
|
expect(pnpmOverride).toBe(dependencyVersion);
|
||||||
@@ -30,7 +38,8 @@ describe("root package override guardrails", () => {
|
|||||||
|
|
||||||
it("pins the node-domexception alias exactly in npm and pnpm overrides", () => {
|
it("pins the node-domexception alias exactly in npm and pnpm overrides", () => {
|
||||||
const manifest = readRootManifest();
|
const manifest = readRootManifest();
|
||||||
const pnpmOverride = manifest.pnpm?.overrides?.["node-domexception"];
|
const pnpmWorkspace = readPnpmWorkspaceConfig();
|
||||||
|
const pnpmOverride = pnpmWorkspace.overrides?.["node-domexception"];
|
||||||
const npmOverride = manifest.overrides?.["node-domexception"];
|
const npmOverride = manifest.overrides?.["node-domexception"];
|
||||||
|
|
||||||
expect(pnpmOverride).toBe("npm:@nolyfill/domexception@1.0.28");
|
expect(pnpmOverride).toBe("npm:@nolyfill/domexception@1.0.28");
|
||||||
|
|||||||
Reference in New Issue
Block a user