openclaw/scripts/openclaw-postpack.mjs
Ayaan Zaidi ae3aff5c28
fix(update): complete package lifecycle outside dist inventory
## Problem

A pnpm 11 global update from OpenClaw 2026.7.1-2 to 2026.8.1 fails verification and rolls back. The 2026.8.1 package puts `openclaw-install-guard` inside `dist/`, but the old updater treats every uninventoried `dist/` file as invalid.

## Root cause

Package lifecycle state was stored inside the closed `dist/` inventory. That made a valid pending lifecycle marker look like package corruption to an older updater.

## Fix

- Store pending lifecycle state at package root as `.openclaw-lifecycle-pending`.
- Let postinstall remove the marker only after all lifecycle work succeeds.
- Use one lifecycle completion owner from the updater, `dist/index.js`, and `openclaw.mjs`.
- Keep the lifecycle lock valid beyond the full preinstall and postinstall timeout budget.
- Keep temporary recovery support for the 2026.8.1 `dist/openclaw-install-guard` path.
- Keep source package preparation and worker package generation aligned with the new marker contract.

## Product proof

- Red: a published 2026.7.1-2 pnpm 11 install rejected the published 2026.8.1 package with `unexpected packaged dist file dist/openclaw-install-guard`, exited nonzero, and remained on 2026.7.1-2.
- Green: the built candidate passes the old-updater upgrade path, the pnpm 11 lifecycle-repair path, a forced postinstall failure and retry, and native npm controls.
- Anti-cheat: the proof checks the installed CLI version before and after the update.

## Validation

- `node scripts/run-vitest.mjs src/infra/package-lifecycle.test.ts src/infra/package-update-steps.pnpm11-guard.test.ts src/index.entrypoint.test.ts`
- Focused lifecycle, updater, tarball, postinstall, inventory, and entrypoint suites: 176 tests passed.
- Exact-head lifecycle lock suite: 4 tests passed, including the old 20-minute expiry boundary.
- Remote core and scripts checks passed.
- `git diff --check`
- GitHub CI is the full release and platform gate.

## Scope

- Production and release-tooling delta: +370/-162, net +208.
- Test and CI support delta: +370/-84, net +286.
- The production growth adds the shared lifecycle owner, crash-safe retry marker, and concurrent-launch lock. It removes the updater-only lifecycle sequence and keeps the closed `dist/` verifier unchanged.
- Sibling coverage: updater, package launcher, legacy package entrypoint, installers, tarball validator, worker bootstrap package, and Docker package preparation.

## ClawSweeper

- No actionable code findings.
- Rank-up skip: the package-upgrade trace came from an internal isolated runner and is not suitable for a public log attachment. Exact-head GitHub CI and the focused regression commands above remain the public proof.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-31 15:12:42 +05:30

34 lines
1.2 KiB
JavaScript
Executable file

#!/usr/bin/env node
import { rm } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
LEGACY_PACKAGE_INSTALL_GUARD_RELATIVE_PATH,
PACKAGE_LIFECYCLE_PENDING_RELATIVE_PATH,
} from "./lib/package-lifecycle-marker.mjs";
// Restores every source artifact temporarily rewritten for npm packaging.
import { restorePackageChangelog } from "./package-changelog.mjs";
import { restorePackageDocsMap } from "./package-docs-map.mjs";
import { restorePackageManifest } from "./package-manifest.mjs";
export async function restorePrepackArtifacts(cwd = process.cwd()) {
await restorePackageChangelog(cwd);
await restorePackageManifest(cwd);
// Release the lifecycle receipt only after every other source mutation settles.
await restorePackageDocsMap(cwd);
await Promise.all(
[PACKAGE_LIFECYCLE_PENDING_RELATIVE_PATH, LEGACY_PACKAGE_INSTALL_GUARD_RELATIVE_PATH].map(
(relativePath) => rm(path.join(cwd, relativePath), { force: true }),
),
);
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
try {
await restorePrepackArtifacts();
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
}
}