mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 01:13:03 +00:00
fix(update): reject openclaw source package targets
This commit is contained in:
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- Agents/subagents: surface blocked child-run completions as errors instead of successful subagent finishes. (#80886) Thanks @TurboTheTurtle.
|
||||
- WhatsApp: update Baileys to `7.0.0-rc13` and drop the obsolete logger type patch.
|
||||
- Install/update: reject OpenClaw GitHub source package targets early and point moving-main users at the dev/git install path instead of the broken npm source-install flow.
|
||||
- Infra/json: retry transient `File changed during read` races while loading JSON state so config and state reads recover instead of failing the turn. (#84285)
|
||||
- Providers/Ollama: resolve configured Ollama Cloud `OLLAMA_API_KEY` markers to the real discovery key so cloud provider entries keep authenticated model catalog access. (#85037)
|
||||
- Discord: keep persistent component registry fallback warnings actionable by forwarding structured error and cause metadata through the runtime logger. Fixes #84185. (#84190) Thanks @100menotu001.
|
||||
|
||||
@@ -23,7 +23,6 @@ openclaw update wizard
|
||||
openclaw update --channel beta
|
||||
openclaw update --channel dev
|
||||
openclaw update --tag beta
|
||||
openclaw update --tag main
|
||||
openclaw update --dry-run
|
||||
openclaw update --no-restart
|
||||
openclaw update --yes
|
||||
@@ -35,7 +34,7 @@ openclaw --update
|
||||
|
||||
- `--no-restart`: skip restarting the Gateway service after a successful update. Package-manager updates that do restart the Gateway verify the restarted service reports the expected updated version before the command succeeds.
|
||||
- `--channel <stable|beta|dev>`: set the update channel (git + npm; persisted in config).
|
||||
- `--tag <dist-tag|version|spec>`: override the package target for this update only. For package installs, `main` maps to `github:openclaw/openclaw#main`.
|
||||
- `--tag <dist-tag|version|spec>`: override the package target for this update only. Use `--channel dev`, not `--tag main`, for the moving GitHub `main` checkout.
|
||||
- `--dry-run`: preview planned update actions (channel/tag/target/restart flow) without writing config, installing, syncing plugins, or restarting.
|
||||
- `--json`: print machine-readable `UpdateRunResult` JSON, including
|
||||
`postUpdate.plugins.warnings` when corrupt or unloadable managed plugins need
|
||||
|
||||
@@ -60,8 +60,8 @@ openclaw update --tag 2026.4.1-beta.1
|
||||
# Install from the beta dist-tag (one-off, does not persist)
|
||||
openclaw update --tag beta
|
||||
|
||||
# Install from GitHub main branch (npm tarball)
|
||||
openclaw update --tag main
|
||||
# Switch to the moving GitHub main checkout
|
||||
openclaw update --channel dev
|
||||
|
||||
# Install a specific npm package spec
|
||||
openclaw update --tag openclaw@2026.4.1-beta.1
|
||||
@@ -72,6 +72,9 @@ Notes:
|
||||
- `--tag` applies to **package (npm) installs only**. Git installs ignore it.
|
||||
- The tag is not persisted. Your next `openclaw update` uses your configured
|
||||
channel as usual.
|
||||
- OpenClaw does not support npm GitHub source installs for `openclaw/openclaw`.
|
||||
Use `--channel dev` or `--install-method git --version main` for the moving
|
||||
`main` checkout.
|
||||
- Downgrade protection: if the target version is older than your current version,
|
||||
OpenClaw prompts for confirmation (skip with `--yes`).
|
||||
- `--channel beta` is different from `--tag beta`: the channel flow can fall back
|
||||
|
||||
@@ -131,10 +131,10 @@ openclaw onboard --install-daemon
|
||||
|
||||
Or skip the link and use `pnpm openclaw ...` from inside the repo. See [Setup](/start/setup) for full development workflows.
|
||||
|
||||
### Install from GitHub main
|
||||
### Install from the GitHub main checkout
|
||||
|
||||
```bash
|
||||
npm install -g github:openclaw/openclaw#main
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --install-method git --version main
|
||||
```
|
||||
|
||||
### Containers and package managers
|
||||
|
||||
@@ -119,9 +119,9 @@ The script exits with code `2` for invalid method selection or invalid `--instal
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --install-method git
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="GitHub main via npm">
|
||||
<Tab title="GitHub main checkout">
|
||||
```bash
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --version main
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --install-method git --version main
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Dry run">
|
||||
@@ -157,7 +157,7 @@ The script exits with code `2` for invalid method selection or invalid `--instal
|
||||
| Variable | Description |
|
||||
| ------------------------------------------------------- | --------------------------------------------- |
|
||||
| `OPENCLAW_INSTALL_METHOD=git\|npm` | Install method |
|
||||
| `OPENCLAW_VERSION=latest\|next\|main\|<semver>\|<spec>` | npm version, dist-tag, or package spec |
|
||||
| `OPENCLAW_VERSION=latest\|next\|<semver>\|<spec>` | npm version, dist-tag, or package spec |
|
||||
| `OPENCLAW_BETA=0\|1` | Use beta if available |
|
||||
| `OPENCLAW_GIT_DIR=<path>` | Checkout directory |
|
||||
| `OPENCLAW_GIT_UPDATE=0\|1` | Toggle git updates |
|
||||
@@ -315,9 +315,9 @@ by default, plus git-checkout installs under the same prefix flow.
|
||||
& ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -InstallMethod git
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="GitHub main via npm">
|
||||
<Tab title="GitHub main checkout">
|
||||
```powershell
|
||||
& ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -Tag main
|
||||
& ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -InstallMethod git -Tag main
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Custom git directory">
|
||||
|
||||
@@ -21,7 +21,6 @@ To switch channels or target a specific version:
|
||||
```bash
|
||||
openclaw update --channel beta
|
||||
openclaw update --channel dev
|
||||
openclaw update --tag main
|
||||
openclaw update --dry-run # preview without applying
|
||||
```
|
||||
|
||||
@@ -35,6 +34,10 @@ installer has its own `--verbose` flag, but that flag is not part of
|
||||
the beta tag is missing or older than the latest stable release. Use `--tag beta`
|
||||
if you want the raw npm beta dist-tag for a one-off package update.
|
||||
|
||||
Use `--channel dev` for the moving GitHub `main` checkout. Package updates do
|
||||
not support npm GitHub source installs for `openclaw/openclaw`; target a
|
||||
published dist-tag, exact version, or built tarball instead.
|
||||
|
||||
For managed plugins, beta-channel fallback is a warning: the core update can
|
||||
still succeed while a plugin uses its recorded default/latest release because no
|
||||
plugin beta is available.
|
||||
|
||||
@@ -364,6 +364,27 @@ run_pnpm() {
|
||||
"${PNPM_CMD[@]}" "$@"
|
||||
}
|
||||
|
||||
to_lowercase_ascii() {
|
||||
printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]'
|
||||
}
|
||||
|
||||
is_openclaw_source_package_install_spec() {
|
||||
local value="${1:-}"
|
||||
local normalized_value=""
|
||||
normalized_value="$(to_lowercase_ascii "$value")"
|
||||
normalized_value="${normalized_value#openclaw@}"
|
||||
|
||||
[[ "$normalized_value" == "main" ]] && return 0
|
||||
[[ "$normalized_value" =~ ^github:openclaw/openclaw($|[#/]) ]] && return 0
|
||||
|
||||
normalized_value="${normalized_value#git+}"
|
||||
[[ "$normalized_value" =~ ^https?://github\.com/openclaw/openclaw(\.git)?($|[?#]) ]] && return 0
|
||||
[[ "$normalized_value" =~ ^ssh://git@github\.com[:/]openclaw/openclaw(\.git)?($|[?#]) ]] && return 0
|
||||
[[ "$normalized_value" =~ ^git://github\.com/openclaw/openclaw(\.git)?($|[?#]) ]] && return 0
|
||||
[[ "$normalized_value" =~ ^git@github\.com:openclaw/openclaw(\.git)?($|[?#]) ]] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
resolve_git_openclaw_ref() {
|
||||
local requested="${OPENCLAW_VERSION:-latest}"
|
||||
local resolved_version=""
|
||||
@@ -624,6 +645,9 @@ fix_npm_prefix_if_needed() {
|
||||
|
||||
install_openclaw() {
|
||||
local requested="${OPENCLAW_VERSION:-latest}"
|
||||
if is_openclaw_source_package_install_spec "$requested"; then
|
||||
fail "npm installs do not support OpenClaw GitHub source targets like '${requested}'. Use --install-method git --version main, latest, beta, an exact version, or a built .tgz package."
|
||||
fi
|
||||
local freshness_flag="--min-release-age=0"
|
||||
local min_release_age=""
|
||||
min_release_age="$(env -u NPM_CONFIG_BEFORE -u npm_config_before "$(npm_bin)" config get min-release-age 2>/dev/null || true)"
|
||||
|
||||
@@ -511,10 +511,45 @@ function Resolve-NpmOpenClawInstallSpec {
|
||||
return "$PackageName@$trimmedTag"
|
||||
}
|
||||
|
||||
function Test-OpenClawSourcePackageInstallSpec {
|
||||
param([string]$RequestedTag)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($RequestedTag)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$normalizedTag = $RequestedTag.Trim().ToLowerInvariant()
|
||||
if ($normalizedTag.StartsWith("openclaw@")) {
|
||||
$normalizedTag = $normalizedTag.Substring("openclaw@".Length)
|
||||
}
|
||||
|
||||
if ($normalizedTag -eq "main") {
|
||||
return $true
|
||||
}
|
||||
if ($normalizedTag -match '^github:openclaw/openclaw($|[#/])') {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($normalizedTag.StartsWith("git+")) {
|
||||
$normalizedTag = $normalizedTag.Substring("git+".Length)
|
||||
}
|
||||
return (
|
||||
$normalizedTag -match '^https?://github\.com/openclaw/openclaw(\.git)?($|[?#])' -or
|
||||
$normalizedTag -match '^ssh://git@github\.com[:/]openclaw/openclaw(\.git)?($|[?#])' -or
|
||||
$normalizedTag -match '^git://github\.com/openclaw/openclaw(\.git)?($|[?#])' -or
|
||||
$normalizedTag -match '^git@github\.com:openclaw/openclaw(\.git)?($|[?#])'
|
||||
)
|
||||
}
|
||||
|
||||
function Install-OpenClaw {
|
||||
if ([string]::IsNullOrWhiteSpace($Tag)) {
|
||||
$Tag = "latest"
|
||||
}
|
||||
if (Test-OpenClawSourcePackageInstallSpec -RequestedTag $Tag) {
|
||||
Write-Host "Error: npm installs do not support OpenClaw GitHub source targets like '$Tag'." -ForegroundColor Red
|
||||
Write-Host "Use -InstallMethod git -Tag main for the moving main checkout, or use latest, beta, an exact version, or a built .tgz package." -ForegroundColor Yellow
|
||||
return $false
|
||||
}
|
||||
if (-not (Ensure-Git)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
@@ -1047,7 +1047,7 @@ Options:
|
||||
--install-method, --method npm|git Install via npm (default) or from a git checkout
|
||||
--npm Shortcut for --install-method npm
|
||||
--git, --github Shortcut for --install-method git
|
||||
--version <version|dist-tag|spec> npm install target (default: latest; use "main" for GitHub main)
|
||||
--version <version|dist-tag|spec> npm install target (default: latest)
|
||||
--beta Use beta if available, else latest
|
||||
--git-dir, --dir <path> Checkout directory (default: ~/openclaw)
|
||||
--no-git-update Skip git pull for existing checkout
|
||||
@@ -1060,7 +1060,7 @@ Options:
|
||||
|
||||
Environment variables:
|
||||
OPENCLAW_INSTALL_METHOD=git|npm
|
||||
OPENCLAW_VERSION=latest|next|main|<semver>|<spec>
|
||||
OPENCLAW_VERSION=latest|next|<semver>|<spec>
|
||||
OPENCLAW_BETA=0|1
|
||||
OPENCLAW_GIT_DIR=...
|
||||
OPENCLAW_GIT_UPDATE=0|1
|
||||
@@ -1076,7 +1076,7 @@ Examples:
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard --verify
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --version main
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --install-method git --version main
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --install-method git --no-onboard
|
||||
EOF
|
||||
}
|
||||
@@ -2444,6 +2444,23 @@ is_explicit_package_install_spec() {
|
||||
[[ "$value" == *"://"* || "$value" == *"#"* || "$value" =~ ^(file|github|git\+ssh|git\+https|git\+http|git\+file|npm): ]]
|
||||
}
|
||||
|
||||
is_openclaw_source_package_install_spec() {
|
||||
local value="${1:-}"
|
||||
local normalized_value=""
|
||||
normalized_value="$(to_lowercase_ascii "$value")"
|
||||
normalized_value="${normalized_value#openclaw@}"
|
||||
|
||||
[[ "$normalized_value" == "main" ]] && return 0
|
||||
[[ "$normalized_value" =~ ^github:openclaw/openclaw($|[#/]) ]] && return 0
|
||||
|
||||
normalized_value="${normalized_value#git+}"
|
||||
[[ "$normalized_value" =~ ^https?://github\.com/openclaw/openclaw(\.git)?($|[?#]) ]] && return 0
|
||||
[[ "$normalized_value" =~ ^ssh://git@github\.com[:/]openclaw/openclaw(\.git)?($|[?#]) ]] && return 0
|
||||
[[ "$normalized_value" =~ ^git://github\.com/openclaw/openclaw(\.git)?($|[?#]) ]] && return 0
|
||||
[[ "$normalized_value" =~ ^git@github\.com:openclaw/openclaw(\.git)?($|[?#]) ]] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
can_resolve_registry_package_version() {
|
||||
local value="${1:-}"
|
||||
local normalized_value=""
|
||||
@@ -2499,6 +2516,12 @@ install_openclaw() {
|
||||
OPENCLAW_VERSION="latest"
|
||||
fi
|
||||
|
||||
if is_openclaw_source_package_install_spec "${OPENCLAW_VERSION}"; then
|
||||
ui_error "npm installs do not support OpenClaw GitHub source targets like '${OPENCLAW_VERSION}'."
|
||||
ui_info "Use --install-method git --version main for the moving main checkout, or use latest, beta, an exact version, or a built .tgz package."
|
||||
return 1
|
||||
fi
|
||||
|
||||
local resolved_version=""
|
||||
if can_resolve_registry_package_version "${OPENCLAW_VERSION}"; then
|
||||
resolved_version="$(npm view "${package_name}@${OPENCLAW_VERSION}" version 2>/dev/null || true)"
|
||||
|
||||
@@ -2074,22 +2074,6 @@ describe("update-cli", () => {
|
||||
},
|
||||
expectedSpec: "openclaw@next",
|
||||
},
|
||||
{
|
||||
name: "main shorthand",
|
||||
run: async () => {
|
||||
mockPackageInstallStatus(createCaseDir("openclaw-update"));
|
||||
await updateCommand({ yes: true, tag: "main" });
|
||||
},
|
||||
expectedSpec: "github:openclaw/openclaw#main",
|
||||
},
|
||||
{
|
||||
name: "explicit git package spec",
|
||||
run: async () => {
|
||||
mockPackageInstallStatus(createCaseDir("openclaw-update"));
|
||||
await updateCommand({ yes: true, tag: "github:openclaw/openclaw#main" });
|
||||
},
|
||||
expectedSpec: "github:openclaw/openclaw#main",
|
||||
},
|
||||
{
|
||||
name: "OPENCLAW_UPDATE_PACKAGE_SPEC override",
|
||||
run: async () => {
|
||||
@@ -2116,6 +2100,22 @@ describe("update-cli", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["main", "github:openclaw/openclaw#main", "openclaw@github:openclaw/openclaw#main"])(
|
||||
"rejects OpenClaw GitHub source package updates: %s",
|
||||
async (tag) => {
|
||||
mockPackageInstallStatus(createCaseDir("openclaw-update"));
|
||||
|
||||
await updateCommand({ yes: true, tag });
|
||||
|
||||
expect(packageInstallCommandCall()).toBeUndefined();
|
||||
expect(defaultRuntime.exit).toHaveBeenCalledWith(1);
|
||||
const errors = vi.mocked(defaultRuntime.error).mock.calls.map((call) => String(call[0]));
|
||||
expect(errors.join("\n")).toContain("Unsupported package update target");
|
||||
expect(errors.join("\n")).toContain("openclaw/openclaw");
|
||||
expect(errors.join("\n")).toContain("openclaw update --channel dev");
|
||||
},
|
||||
);
|
||||
|
||||
it("fails package updates when the installed correction version does not match the requested target", async () => {
|
||||
const tempDir = createCaseDir("openclaw-update");
|
||||
const nodeModules = path.join(tempDir, "node_modules");
|
||||
|
||||
@@ -58,7 +58,6 @@ export function registerUpdateCli(program: Command) {
|
||||
["openclaw update --channel beta", "Switch to beta channel (git + npm)"],
|
||||
["openclaw update --channel dev", "Switch to dev channel (git + npm)"],
|
||||
["openclaw update --tag beta", "One-off update to a dist-tag or version"],
|
||||
["openclaw update --tag main", "One-off package install from GitHub main"],
|
||||
["openclaw update --dry-run", "Preview actions without changing anything"],
|
||||
["openclaw update --no-restart", "Update without restarting the service"],
|
||||
["openclaw update --json", "Output result as JSON"],
|
||||
@@ -78,6 +77,7 @@ ${theme.heading("Switch channels:")}
|
||||
- Use --channel stable|beta|dev to persist the update channel in config
|
||||
- Run openclaw update status to see the active channel and source
|
||||
- Use --tag <dist-tag|version|spec> for a one-off package update without persisting
|
||||
- Use --channel dev, not --tag main, for the moving GitHub main checkout
|
||||
|
||||
${theme.heading("Non-interactive:")}
|
||||
- Use --yes to accept downgrade prompts
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
createGlobalInstallEnv,
|
||||
cleanupGlobalRenameDirs,
|
||||
globalInstallArgs,
|
||||
isOpenClawSourcePackageInstallSpec,
|
||||
resolveGlobalInstallTarget,
|
||||
resolveGlobalInstallSpec,
|
||||
resolvePnpmGlobalDirFromGlobalRoot,
|
||||
@@ -976,6 +977,14 @@ async function resolvePackageRuntimePreflightError(params: {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function formatUnsupportedOpenClawSourcePackageTargetMessage(target: string): string {
|
||||
return [
|
||||
`Unsupported package update target: ${target}.`,
|
||||
"OpenClaw package updates use published npm artifacts or built tarballs; npm GitHub source installs for openclaw/openclaw do not reliably produce an installable package.",
|
||||
"Use `openclaw update --channel dev` for the moving main checkout, or target `latest`, `beta`, an exact version, or a built `.tgz` package spec.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
async function resolvePackageRuntimeForPreflight(params: {
|
||||
nodeRunner?: string;
|
||||
timeoutMs?: number;
|
||||
@@ -3080,6 +3089,11 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
||||
tag,
|
||||
env: process.env,
|
||||
});
|
||||
if (isOpenClawSourcePackageInstallSpec(packageInstallSpec)) {
|
||||
defaultRuntime.error(formatUnsupportedOpenClawSourcePackageTargetMessage(packageInstallSpec));
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.dryRun) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
globalInstallFallbackArgs,
|
||||
isExplicitPackageInstallSpec,
|
||||
isMainPackageTarget,
|
||||
isOpenClawSourcePackageInstallSpec,
|
||||
OPENCLAW_MAIN_PACKAGE_SPEC,
|
||||
resolveGlobalInstallCommand,
|
||||
resolveGlobalPackageRoot,
|
||||
@@ -249,6 +250,30 @@ describe("update global helpers", () => {
|
||||
expect(canResolveRegistryVersionForPackageTarget("/tmp/openclaw-main.tgz")).toBe(false);
|
||||
});
|
||||
|
||||
it("classifies OpenClaw GitHub source package specs as unsupported package targets", () => {
|
||||
expect(isOpenClawSourcePackageInstallSpec("main")).toBe(true);
|
||||
expect(isOpenClawSourcePackageInstallSpec("github:openclaw/openclaw#main")).toBe(true);
|
||||
expect(isOpenClawSourcePackageInstallSpec("openclaw@github:openclaw/openclaw#main")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(isOpenClawSourcePackageInstallSpec("OpenClaw@github:openclaw/openclaw#main")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
isOpenClawSourcePackageInstallSpec("git+https://github.com/openclaw/openclaw.git#main"),
|
||||
).toBe(true);
|
||||
expect(isOpenClawSourcePackageInstallSpec("https://example.com/openclaw-main.tgz")).toBe(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
isOpenClawSourcePackageInstallSpec(
|
||||
"https://github.com/openclaw/openclaw/releases/download/v2026.5.20/openclaw.tgz",
|
||||
),
|
||||
).toBe(false);
|
||||
expect(isOpenClawSourcePackageInstallSpec("github:other/openclaw#main")).toBe(false);
|
||||
expect(isOpenClawSourcePackageInstallSpec("beta")).toBe(false);
|
||||
});
|
||||
|
||||
it("detects install managers from resolved roots and on-disk presence", async () => {
|
||||
await withTempDir({ prefix: "openclaw-update-global-" }, async (base) => {
|
||||
const npmRoot = path.join(base, "npm-root");
|
||||
|
||||
@@ -89,7 +89,30 @@ export function isExplicitPackageInstallSpec(value: string): boolean {
|
||||
function stripPrimaryPackageAlias(spec: string): string {
|
||||
const normalized = normalizePackageTarget(spec);
|
||||
const prefix = `${PRIMARY_PACKAGE_NAME}@`;
|
||||
return normalized.startsWith(prefix) ? normalized.slice(prefix.length).trim() : normalized;
|
||||
return normalized.toLowerCase().startsWith(prefix)
|
||||
? normalized.slice(prefix.length).trim()
|
||||
: normalized;
|
||||
}
|
||||
|
||||
export function isOpenClawSourcePackageInstallSpec(value: string): boolean {
|
||||
if (isMainPackageTarget(value)) {
|
||||
return true;
|
||||
}
|
||||
const target = stripPrimaryPackageAlias(value);
|
||||
const normalizedTarget = normalizeLowercaseStringOrEmpty(target);
|
||||
if (!normalizedTarget) {
|
||||
return false;
|
||||
}
|
||||
if (/^github:openclaw\/openclaw(?:$|[#/])/u.test(normalizedTarget)) {
|
||||
return true;
|
||||
}
|
||||
const gitUrl = normalizedTarget.replace(/^git\+/u, "");
|
||||
return (
|
||||
/^https?:\/\/github\.com\/openclaw\/openclaw(?:\.git)?(?:$|[?#])/u.test(gitUrl) ||
|
||||
/^ssh:\/\/git@github\.com[:/]openclaw\/openclaw(?:\.git)?(?:$|[?#])/u.test(gitUrl) ||
|
||||
/^git:\/\/github\.com\/openclaw\/openclaw(?:\.git)?(?:$|[?#])/u.test(gitUrl) ||
|
||||
/^git@github\.com:openclaw\/openclaw(?:\.git)?(?:$|[?#])/u.test(gitUrl)
|
||||
);
|
||||
}
|
||||
|
||||
function isPnpmOpenClawSourceInstallSpec(spec: string): boolean {
|
||||
|
||||
@@ -1742,15 +1742,17 @@ describe("runGatewayUpdate", () => {
|
||||
expect(calls).toContain(expectedInstallCommand);
|
||||
});
|
||||
|
||||
it("updates global npm installs from the GitHub main package spec", async () => {
|
||||
it("rejects global npm updates from the GitHub main package spec", async () => {
|
||||
const { calls, result } = await runNpmGlobalUpdateCase({
|
||||
expectedInstallCommand: npmGlobalInstallCommand("github:openclaw/openclaw#main"),
|
||||
tag: "main",
|
||||
});
|
||||
|
||||
expect(result.status).toBe("ok");
|
||||
expect(result.status).toBe("error");
|
||||
expect(result.mode).toBe("npm");
|
||||
expect(calls).toContain(npmGlobalInstallCommand("github:openclaw/openclaw#main"));
|
||||
expect(result.reason).toBe("unsupported-package-target");
|
||||
expect(result.steps[0]?.name).toBe("package target validation");
|
||||
expect(calls).not.toContain(npmGlobalInstallCommand("github:openclaw/openclaw#main"));
|
||||
});
|
||||
|
||||
it("runs doctor after global npm updates before reporting success", async () => {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
cleanupGlobalRenameDirs,
|
||||
createGlobalInstallEnv,
|
||||
detectGlobalInstallManagerForRoot,
|
||||
isOpenClawSourcePackageInstallSpec,
|
||||
resolveGlobalInstallTarget,
|
||||
resolveGlobalInstallSpec,
|
||||
type GlobalInstallManager,
|
||||
@@ -1453,6 +1454,30 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<
|
||||
tag,
|
||||
env: globalInstallEnv,
|
||||
});
|
||||
if (isOpenClawSourcePackageInstallSpec(spec)) {
|
||||
const durationMs = Date.now() - startedAt;
|
||||
return {
|
||||
status: "error",
|
||||
mode: globalManager,
|
||||
root: pkgRoot,
|
||||
reason: "unsupported-package-target",
|
||||
before: { version: beforeVersion },
|
||||
after: { version: beforeVersion },
|
||||
steps: [
|
||||
{
|
||||
name: "package target validation",
|
||||
command: `validate package target ${spec}`,
|
||||
cwd: pkgRoot,
|
||||
durationMs,
|
||||
exitCode: 1,
|
||||
stdoutTail: null,
|
||||
stderrTail:
|
||||
"OpenClaw package updates use published npm artifacts or built tarballs; use the dev channel for GitHub main.",
|
||||
},
|
||||
],
|
||||
durationMs,
|
||||
};
|
||||
}
|
||||
const packageUpdate = await runGlobalPackageUpdateSteps({
|
||||
installTarget,
|
||||
installSpec: spec,
|
||||
|
||||
@@ -96,4 +96,17 @@ describe("install-cli.sh", () => {
|
||||
expect(script).toContain('freshness_flag="--before=$(date -u');
|
||||
expect(script).toContain("env -u NPM_CONFIG_BEFORE -u npm_config_before");
|
||||
});
|
||||
|
||||
it("rejects OpenClaw GitHub source targets for npm installs", () => {
|
||||
const result = runInstallCliShell(`
|
||||
set -euo pipefail
|
||||
source "${SCRIPT_PATH}"
|
||||
OPENCLAW_VERSION=main
|
||||
install_openclaw
|
||||
`);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toContain("npm installs do not support OpenClaw GitHub source targets");
|
||||
expect(result.stdout).toContain("--install-method git --version main");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,6 +104,16 @@ describe("install.ps1 failure handling", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects OpenClaw GitHub source targets for npm installs", () => {
|
||||
const npmInstallBody = extractFunctionBody(source, "Install-OpenClaw");
|
||||
const sourceTargetBody = extractFunctionBody(source, "Test-OpenClawSourcePackageInstallSpec");
|
||||
expect(sourceTargetBody).toContain('$normalizedTag -eq "main"');
|
||||
expect(sourceTargetBody).toContain("^github:openclaw/openclaw");
|
||||
expect(npmInstallBody).toContain("Test-OpenClawSourcePackageInstallSpec -RequestedTag $Tag");
|
||||
expect(npmInstallBody).toContain("npm installs do not support OpenClaw GitHub source targets");
|
||||
expect(npmInstallBody).toContain("-InstallMethod git -Tag main");
|
||||
});
|
||||
|
||||
it("cleans legacy git submodules only from the selected git checkout", () => {
|
||||
const gitInstallBody = extractFunctionBody(source, "Install-OpenClawFromGit");
|
||||
const mainBody = extractFunctionBody(source, "Main");
|
||||
|
||||
@@ -41,6 +41,24 @@ describe("install.sh", () => {
|
||||
expect(script).toContain('cmd+=(--no-fund --no-audit "$freshness_flag" install -g "$spec")');
|
||||
});
|
||||
|
||||
it("rejects OpenClaw GitHub source targets for npm installs", () => {
|
||||
const result = runInstallShell(`
|
||||
set -euo pipefail
|
||||
source "${SCRIPT_PATH}"
|
||||
set +e
|
||||
OPENCLAW_VERSION=main
|
||||
USE_BETA=0
|
||||
install_openclaw
|
||||
status=$?
|
||||
printf 'status=%s\\n' "$status"
|
||||
`);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("status=1");
|
||||
expect(result.stdout).toContain("npm installs do not support OpenClaw GitHub source targets");
|
||||
expect(result.stdout).toContain("--install-method git --version main");
|
||||
});
|
||||
|
||||
it("exports noninteractive apt env during Linux startup", () => {
|
||||
expect(script).toMatch(
|
||||
/detect_os_or_die\s+if \[\[ "\$OS" == "linux" \]\]; then\s+export DEBIAN_FRONTEND="\$\{DEBIAN_FRONTEND:-noninteractive\}"\s+export NEEDRESTART_MODE="\$\{NEEDRESTART_MODE:-a\}"\s+fi/m,
|
||||
|
||||
Reference in New Issue
Block a user