mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
feat(release): preflight all extended-stable npm packages (#101757)
* feat(release): preflight all extended-stable npm packages * fix(release): install packed ai runtime in smoke * fix(release): install sibling tarballs before core * fix(release): verify unpublished tarballs locally * fix(release): declare local preflight dependencies * fix(release): pack workspace dependencies for smoke * fix(release): accept pnpm pack artifact path * fix(release): seed ai in sdk smoke * fix(release): preserve prepublish verifier compatibility
This commit is contained in:
parent
9d7a6b5da6
commit
9eeebf7cb1
11 changed files with 452 additions and 107 deletions
65
.github/workflows/openclaw-npm-release.yml
vendored
65
.github/workflows/openclaw-npm-release.yml
vendored
|
|
@ -4,7 +4,7 @@ on:
|
|||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release tag to publish, or a full 40-character workflow-branch commit SHA for validation-only preflight (for example v2026.3.22 or 0123456789abcdef0123456789abcdef01234567)
|
||||
description: Release tag to publish, or any full 40-character commit SHA for validation-only preflight (for example v2026.3.22 or 0123456789abcdef0123456789abcdef01234567)
|
||||
required: true
|
||||
type: string
|
||||
preflight_only:
|
||||
|
|
@ -208,19 +208,16 @@ jobs:
|
|||
RELEASE_SHA=$(git rev-parse HEAD)
|
||||
RELEASE_BRANCH_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
export RELEASE_SHA RELEASE_BRANCH_REF
|
||||
# Fetch the workflow branch so merge-base ancestry checks keep working
|
||||
# for older tagged commits contained in a release branch.
|
||||
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
if [[ "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
BRANCH_SHA="$(git rev-parse "${RELEASE_BRANCH_REF}")"
|
||||
if [[ "${RELEASE_SHA}" != "${BRANCH_SHA}" ]]; then
|
||||
echo "Validation-only SHA mode only supports the current ${WORKFLOW_REF_NAME} HEAD." >&2
|
||||
exit 1
|
||||
fi
|
||||
RELEASE_TAG="v$(node -p "require('./package.json').version")"
|
||||
export RELEASE_TAG
|
||||
RELEASE_BRANCH_REF="${RELEASE_SHA}"
|
||||
export RELEASE_BRANCH_REF
|
||||
echo "Validation-only SHA mode: using synthetic release tag ${RELEASE_TAG} for package metadata checks."
|
||||
else
|
||||
# Tagged release validation still proves the commit is contained in
|
||||
# the workflow branch selected by the operator.
|
||||
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
RELEASE_TAG="${RELEASE_REF}"
|
||||
export RELEASE_TAG
|
||||
fi
|
||||
|
|
@ -232,8 +229,58 @@ jobs:
|
|||
# IF A CHECK CAN TAKE A LONG TIME, NEEDS LIVE CREDENTIALS, OR IS KNOWN TO BE FLAKY,
|
||||
# IT BELONGS IN openclaw-release-checks.yml INSTEAD OF BLOCKING npm PUBLISH.
|
||||
- name: Verify release contents
|
||||
env:
|
||||
OPENCLAW_RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR: ${{ steps.ai_runtime_tarballs.outputs.dir }}
|
||||
run: pnpm release:check
|
||||
|
||||
- name: Exercise all extended-stable plugin npm packages
|
||||
id: plugin_npm_preflight
|
||||
if: ${{ inputs.npm_dist_tag == 'extended-stable' }}
|
||||
env:
|
||||
OPENCLAW_PLUGIN_NPM_PUBLISH_TAG: extended-stable
|
||||
run: |
|
||||
set -euo pipefail
|
||||
plan="$RUNNER_TEMP/plugin-npm-extended-stable-preflight.json"
|
||||
artifact_dir="$RUNNER_TEMP/openclaw-plugin-npm-preflight"
|
||||
rm -rf "$artifact_dir"
|
||||
mkdir -p "$artifact_dir"
|
||||
node --import tsx scripts/plugin-npm-release-plan.ts \
|
||||
--selection-mode all-publishable \
|
||||
--npm-dist-tag extended-stable > "$plan"
|
||||
cp "$plan" "$artifact_dir/plugin-npm-release-plan.json"
|
||||
|
||||
mapfile -t package_dirs < <(jq -r '.all[].packageDir' "$plan")
|
||||
if [[ "${#package_dirs[@]}" == "0" ]]; then
|
||||
echo "Extended-stable plugin preflight resolved no publishable packages." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
runtime_args=()
|
||||
for package_dir in "${package_dirs[@]}"; do
|
||||
runtime_args+=(--package "$package_dir")
|
||||
done
|
||||
node scripts/check-plugin-npm-runtime-builds.mjs "${runtime_args[@]}"
|
||||
|
||||
for package_dir in "${package_dirs[@]}"; do
|
||||
OPENCLAW_PLUGIN_NPM_RUNTIME_BUILD=0 \
|
||||
OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR="$artifact_dir" \
|
||||
bash scripts/plugin-npm-publish.sh --pack "$package_dir" >/dev/null
|
||||
done
|
||||
|
||||
(cd "$artifact_dir" && sha256sum ./*.tgz > SHA256SUMS)
|
||||
package_count="$(jq -r '.all | length' "$plan")"
|
||||
echo "dir=$artifact_dir" >> "$GITHUB_OUTPUT"
|
||||
echo "Extended-stable plugin preflight exercised ${package_count} publishable packages."
|
||||
echo "- Extended-stable plugin packages exercised: ${package_count}" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload extended-stable plugin npm packages
|
||||
if: ${{ inputs.npm_dist_tag == 'extended-stable' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: openclaw-plugin-npm-preflight-${{ inputs.tag }}
|
||||
path: ${{ steps.plugin_npm_preflight.outputs.dir }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Generate dependency release evidence
|
||||
id: dependency_evidence
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -74,12 +74,13 @@ export function validateExtendedStableNpmReleaseRequest(request) {
|
|||
requireExtendedStableBypassTag(request.npmDistTag, bypassExtendedStableGuard);
|
||||
const shaPreflight =
|
||||
request.preflightOnly === true && /^[0-9a-f]{40}$/iu.test(request.releaseTag);
|
||||
if (
|
||||
shaPreflight &&
|
||||
request.checkoutSha &&
|
||||
request.releaseTag.toLowerCase() !== request.checkoutSha.toLowerCase()
|
||||
) {
|
||||
throw new Error("Validation-only SHA must match the checked-out commit exactly.");
|
||||
if (shaPreflight) {
|
||||
if (!/^[0-9a-f]{40}$/iu.test(request.checkoutSha)) {
|
||||
throw new Error("Validation-only SHA preflight requires the full checked-out commit SHA.");
|
||||
}
|
||||
if (request.releaseTag.toLowerCase() !== request.checkoutSha.toLowerCase()) {
|
||||
throw new Error("Validation-only SHA must match the checked-out commit exactly.");
|
||||
}
|
||||
}
|
||||
const effectiveReleaseTag = shaPreflight ? `v${request.packageVersion}` : request.releaseTag;
|
||||
const taggedVersion = effectiveReleaseTag.startsWith("v")
|
||||
|
|
@ -112,24 +113,26 @@ export function validateExtendedStableNpmReleaseRequest(request) {
|
|||
|
||||
const releaseVersion = taggedVersion.version;
|
||||
const extendedStableBranch = `extended-stable/${taggedVersion.year}.${taggedVersion.month}.33`;
|
||||
const expectedWorkflowRef = `refs/heads/${extendedStableBranch}`;
|
||||
if (request.npmWorkflowRef !== expectedWorkflowRef) {
|
||||
throw new Error(
|
||||
`Extended-stable npm workflow ref mismatch: expected ${expectedWorkflowRef}, got ${request.npmWorkflowRef}.`,
|
||||
);
|
||||
}
|
||||
if (request.packageVersion !== releaseVersion) {
|
||||
throw new Error(
|
||||
`Extended-stable npm package version mismatch: expected ${releaseVersion}, got ${request.packageVersion}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const shaValues = [request.checkoutSha, request.tagSha, request.extendedStableBranchSha];
|
||||
if (shaValues.some((sha) => !/^[0-9a-f]{40}$/iu.test(sha))) {
|
||||
throw new Error("Extended-stable npm release identity requires full 40-character Git SHAs.");
|
||||
}
|
||||
if (new Set(shaValues.map((sha) => sha.toLowerCase())).size !== 1) {
|
||||
throw new Error("Extended-stable npm checkout, tag, and branch tip SHAs must match exactly.");
|
||||
if (!shaPreflight) {
|
||||
const expectedWorkflowRef = `refs/heads/${extendedStableBranch}`;
|
||||
if (request.npmWorkflowRef !== expectedWorkflowRef) {
|
||||
throw new Error(
|
||||
`Extended-stable npm workflow ref mismatch: expected ${expectedWorkflowRef}, got ${request.npmWorkflowRef}.`,
|
||||
);
|
||||
}
|
||||
const shaValues = [request.checkoutSha, request.tagSha, request.extendedStableBranchSha];
|
||||
if (shaValues.some((sha) => !/^[0-9a-f]{40}$/iu.test(sha))) {
|
||||
throw new Error("Extended-stable npm release identity requires full 40-character Git SHAs.");
|
||||
}
|
||||
if (new Set(shaValues.map((sha) => sha.toLowerCase())).size !== 1) {
|
||||
throw new Error("Extended-stable npm checkout, tag, and branch tip SHAs must match exactly.");
|
||||
}
|
||||
}
|
||||
|
||||
if (bypassExtendedStableGuard) {
|
||||
|
|
@ -344,17 +347,13 @@ function validateRequestFromRepository() {
|
|||
}
|
||||
const extendedStableBranch = `extended-stable/${parsed.year}.${parsed.month}.33`;
|
||||
if (shaPreflight) {
|
||||
execFileSync(
|
||||
"git",
|
||||
[
|
||||
"fetch",
|
||||
"--no-tags",
|
||||
"origin",
|
||||
`+refs/heads/${extendedStableBranch}:refs/remotes/origin/${extendedStableBranch}`,
|
||||
...(bypassExtendedStableGuard ? [] : ["+refs/heads/main:refs/remotes/origin/main"]),
|
||||
],
|
||||
{ stdio: "inherit" },
|
||||
);
|
||||
if (!bypassExtendedStableGuard) {
|
||||
execFileSync(
|
||||
"git",
|
||||
["fetch", "--no-tags", "origin", "+refs/heads/main:refs/remotes/origin/main"],
|
||||
{ stdio: "inherit" },
|
||||
);
|
||||
}
|
||||
const checkoutSha = git(["rev-parse", "HEAD"]);
|
||||
return validateExtendedStableNpmReleaseRequest({
|
||||
npmDistTag,
|
||||
|
|
@ -364,7 +363,7 @@ function validateRequestFromRepository() {
|
|||
npmWorkflowRef,
|
||||
checkoutSha,
|
||||
tagSha: checkoutSha,
|
||||
extendedStableBranchSha: git(["rev-parse", `refs/remotes/origin/${extendedStableBranch}`]),
|
||||
extendedStableBranchSha: "",
|
||||
packageVersion,
|
||||
mainPackageVersion: bypassExtendedStableGuard
|
||||
? ""
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
#!/usr/bin/env -S node --import tsx
|
||||
// Openclaw Npm Prepublish Verify script supports OpenClaw repository automation.
|
||||
|
||||
import { mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
||||
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { formatErrorMessage } from "../src/infra/errors.ts";
|
||||
import { runNpmVerifyCommand } from "./lib/npm-verify-exec.ts";
|
||||
import { type NpmVerifyCommandInvocation, runNpmVerifyCommand } from "./lib/npm-verify-exec.ts";
|
||||
import { runInstalledWorkspaceBootstrapSmoke } from "./lib/workspace-bootstrap-smoke.mjs";
|
||||
import {
|
||||
collectInstalledPackageErrors,
|
||||
|
|
@ -14,6 +14,7 @@ import {
|
|||
resolveInstalledBinaryCommandInvocation,
|
||||
} from "./openclaw-npm-postpublish-verify.ts";
|
||||
import { resolveNpmCommandInvocation } from "./openclaw-npm-release-check.ts";
|
||||
import { buildCmdExeCommandLine, resolveWindowsCmdExePath } from "./windows-cmd-helpers.mjs";
|
||||
|
||||
type InstalledPackageJson = {
|
||||
version?: string;
|
||||
|
|
@ -69,6 +70,10 @@ export function parseOpenClawNpmPrepublishVerifyArgs(
|
|||
: { dependencyTarballPaths, help: false, tarballPath };
|
||||
}
|
||||
|
||||
export function usesPreparedLocalDependencyInstall(dependencyTarballCount: number): boolean {
|
||||
return dependencyTarballCount === 1;
|
||||
}
|
||||
|
||||
function npmExec(args: string[], cwd: string): string {
|
||||
const invocation = resolveNpmCommandInvocation({
|
||||
npmArgs: args,
|
||||
|
|
@ -90,21 +95,58 @@ function main(argv = process.argv.slice(2)): void {
|
|||
const workingDir = mkdtempSync(join(tmpdir(), "openclaw-prepublish-"));
|
||||
const prefixDir = join(workingDir, "prefix");
|
||||
try {
|
||||
npmExec(
|
||||
[
|
||||
"install",
|
||||
"-g",
|
||||
"--prefix",
|
||||
let binaryInvocation: NpmVerifyCommandInvocation;
|
||||
let packageRoot: string;
|
||||
if (usesPreparedLocalDependencyInstall(args.dependencyTarballPaths.length)) {
|
||||
mkdirSync(prefixDir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(prefixDir, "package.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
private: true,
|
||||
dependencies: {
|
||||
"@openclaw/ai": pathToFileURL(realpathSync(args.dependencyTarballPaths[0])).href,
|
||||
openclaw: pathToFileURL(realpathSync(args.tarballPath)).href,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
);
|
||||
npmExec(["install", "--prefix", prefixDir, "--no-fund", "--no-audit"], workingDir);
|
||||
packageRoot = join(prefixDir, "node_modules", "openclaw");
|
||||
const binaryPath = join(
|
||||
prefixDir,
|
||||
...args.dependencyTarballPaths.map((dependency) => realpathSync(dependency)),
|
||||
realpathSync(args.tarballPath),
|
||||
"--no-fund",
|
||||
"--no-audit",
|
||||
],
|
||||
workingDir,
|
||||
);
|
||||
const globalRoot = npmExec(["root", "-g", "--prefix", prefixDir], workingDir);
|
||||
const packageRoot = join(globalRoot, "openclaw");
|
||||
"node_modules",
|
||||
".bin",
|
||||
process.platform === "win32" ? "openclaw.cmd" : "openclaw",
|
||||
);
|
||||
binaryInvocation =
|
||||
process.platform === "win32"
|
||||
? {
|
||||
command: resolveWindowsCmdExePath(),
|
||||
args: ["/d", "/s", "/c", buildCmdExeCommandLine(binaryPath, ["--version"])],
|
||||
windowsVerbatimArguments: true,
|
||||
}
|
||||
: { command: binaryPath, args: ["--version"] };
|
||||
} else {
|
||||
npmExec(
|
||||
[
|
||||
"install",
|
||||
"-g",
|
||||
"--prefix",
|
||||
prefixDir,
|
||||
...args.dependencyTarballPaths.map((dependency) => realpathSync(dependency)),
|
||||
realpathSync(args.tarballPath),
|
||||
"--no-fund",
|
||||
"--no-audit",
|
||||
],
|
||||
workingDir,
|
||||
);
|
||||
const globalRoot = npmExec(["root", "-g", "--prefix", prefixDir], workingDir);
|
||||
packageRoot = join(globalRoot, "openclaw");
|
||||
binaryInvocation = resolveInstalledBinaryCommandInvocation(prefixDir, ["--version"]);
|
||||
}
|
||||
const pkg = JSON.parse(
|
||||
readFileSync(join(packageRoot, "package.json"), "utf8"),
|
||||
) as InstalledPackageJson;
|
||||
|
|
@ -114,7 +156,6 @@ function main(argv = process.argv.slice(2)): void {
|
|||
installedVersion: pkg.version?.trim() ?? "",
|
||||
packageRoot,
|
||||
});
|
||||
const binaryInvocation = resolveInstalledBinaryCommandInvocation(prefixDir, ["--version"]);
|
||||
const installedBinaryVersion = runNpmVerifyCommand(binaryInvocation, workingDir);
|
||||
if (normalizeInstalledBinaryVersion(installedBinaryVersion) !== resolvedExpectedVersion) {
|
||||
errors.push(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "usage: bash scripts/plugin-npm-publish.sh [--dry-run|--pack-dry-run|--publish] <package-dir>"
|
||||
echo "usage: bash scripts/plugin-npm-publish.sh [--dry-run|--pack|--pack-dry-run|--publish] <package-dir>"
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
||||
|
|
@ -12,7 +12,7 @@ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|||
fi
|
||||
|
||||
mode="${1:-}"
|
||||
if [[ "${mode}" != "--dry-run" && "${mode}" != "--pack-dry-run" && "${mode}" != "--publish" ]]; then
|
||||
if [[ "${mode}" != "--dry-run" && "${mode}" != "--pack" && "${mode}" != "--pack-dry-run" && "${mode}" != "--publish" ]]; then
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
|
|
@ -36,12 +36,16 @@ if [[ "$#" -gt 0 ]]; then
|
|||
echo "unexpected plugin npm publish argument: $1" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ "${mode}" == "--pack" && -z "${OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR:-}" ]]; then
|
||||
echo "--pack requires OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
package_name="$(node -e 'const pkg = require(require("node:path").resolve(process.argv[1], "package.json")); console.log(pkg.name)' "${package_dir}")"
|
||||
package_version="$(node -e 'const pkg = require(require("node:path").resolve(process.argv[1], "package.json")); console.log(pkg.version)' "${package_dir}")"
|
||||
current_beta_version="$(npm view "${package_name}" dist-tags.beta 2>/dev/null || true)"
|
||||
log() {
|
||||
if [[ "${mode}" == "--pack-dry-run" ]]; then
|
||||
if [[ "${mode}" == "--pack" || "${mode}" == "--pack-dry-run" ]]; then
|
||||
printf '%s\n' "$*" >&2
|
||||
else
|
||||
printf '%s\n' "$*"
|
||||
|
|
@ -143,7 +147,7 @@ if [[ "${mirror_auth_requirement}" == "required" && -z "${mirror_auth_token}" ]]
|
|||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${mode}" == "--pack-dry-run" ]]; then
|
||||
if [[ "${mode}" == "--pack" || "${mode}" == "--pack-dry-run" ]]; then
|
||||
{
|
||||
printf 'Publish command:'
|
||||
printf ' %q' "${publish_cmd[@]}"
|
||||
|
|
@ -162,10 +166,18 @@ fi
|
|||
build_package_runtime
|
||||
check_package_shrinkwrap
|
||||
|
||||
if [[ "${mode}" == "--pack-dry-run" ]]; then
|
||||
if [[ "${mode}" == "--pack" || "${mode}" == "--pack-dry-run" ]]; then
|
||||
pack_args=(npm pack --json --ignore-scripts)
|
||||
if [[ "${mode}" == "--pack-dry-run" ]]; then
|
||||
pack_args+=(--dry-run)
|
||||
else
|
||||
mkdir -p "${OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR}"
|
||||
pack_output_dir="$(cd "${OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR}" && pwd)"
|
||||
pack_args+=(--pack-destination "${pack_output_dir}")
|
||||
fi
|
||||
OPENCLAW_PLUGIN_NPM_BUNDLE_DEPENDENCIES=1 \
|
||||
node scripts/lib/plugin-npm-package-manifest.mjs --run "${package_dir}" -- \
|
||||
npm pack --dry-run --json --ignore-scripts
|
||||
"${pack_args[@]}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -50,16 +50,18 @@ import { resolveNpmRunner } from "./npm-runner.mjs";
|
|||
import {
|
||||
collectInstalledPackageErrors,
|
||||
normalizeInstalledBinaryVersion,
|
||||
resolveInstalledBinaryCommandInvocation,
|
||||
resolveInstalledBinaryPath,
|
||||
} from "./openclaw-npm-postpublish-verify.ts";
|
||||
import { resolvePnpmRunner } from "./pnpm-runner.mjs";
|
||||
import { listStaticExtensionAssetOutputs } from "./runtime-postbuild.mjs";
|
||||
import { sparkleBuildFloorsFromShortVersion, type SparkleBuildFloors } from "./sparkle-build.ts";
|
||||
import { buildCmdExeCommandLine } from "./windows-cmd-helpers.mjs";
|
||||
import { buildCmdExeCommandLine, resolveWindowsCmdExePath } from "./windows-cmd-helpers.mjs";
|
||||
|
||||
export { collectBundledExtensionManifestErrors } from "./lib/bundled-extension-manifest.ts";
|
||||
export { packageNameFromSpecifier } from "./lib/plugin-package-dependencies.mjs";
|
||||
|
||||
export const RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV =
|
||||
"OPENCLAW_RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR";
|
||||
|
||||
type PackFile = { path: string };
|
||||
type PackResult = { files?: PackFile[]; filename?: string; unpackedSize?: number };
|
||||
type ReleaseCheckCommandInvocation = {
|
||||
|
|
@ -362,6 +364,19 @@ function execNpm(
|
|||
return runReleaseCheckCommand(invocation, options);
|
||||
}
|
||||
|
||||
function execPnpm(
|
||||
args: string[],
|
||||
options: {
|
||||
cwd?: string;
|
||||
encoding: BufferEncoding;
|
||||
maxBuffer?: number;
|
||||
stdio: "inherit" | ["ignore", "pipe", "pipe"];
|
||||
},
|
||||
): string {
|
||||
const invocation = resolvePnpmRunner({ env: process.env, pnpmArgs: args });
|
||||
return runReleaseCheckCommand(invocation, options);
|
||||
}
|
||||
|
||||
function runPackDry(): PackResult[] {
|
||||
const raw = execNpm(["pack", "--dry-run", "--json", "--ignore-scripts"], {
|
||||
encoding: "utf8",
|
||||
|
|
@ -372,15 +387,16 @@ function runPackDry(): PackResult[] {
|
|||
}
|
||||
|
||||
function runPack(packDestination: string): PackResult[] {
|
||||
const raw = execNpm(
|
||||
["pack", "--json", "--ignore-scripts", "--pack-destination", packDestination],
|
||||
const raw = execPnpm(
|
||||
["--config.ignore-scripts=true", "pack", "--json", "--pack-destination", packDestination],
|
||||
{
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
maxBuffer: 1024 * 1024 * 100,
|
||||
},
|
||||
);
|
||||
return JSON.parse(raw) as PackResult[];
|
||||
const parsed = JSON.parse(raw) as PackResult | PackResult[];
|
||||
return Array.isArray(parsed) ? parsed : [parsed];
|
||||
}
|
||||
|
||||
export function resolvePackedTarballPath(packDestination: string, results: PackResult[]): string {
|
||||
|
|
@ -393,45 +409,120 @@ export function resolvePackedTarballPath(packDestination: string, results: PackR
|
|||
);
|
||||
}
|
||||
const filename = filenames[0];
|
||||
const filenameBasename = basename(filename);
|
||||
const resolvedDestination = resolve(packDestination);
|
||||
const resolvedTarball = resolve(resolvedDestination, filenameBasename);
|
||||
const isLocalFilename = filename === filenameBasename;
|
||||
const isAbsolutePathInDestination = resolve(filename) === resolvedTarball;
|
||||
if (
|
||||
!filename.endsWith(".tgz") ||
|
||||
!filenameBasename.endsWith(".tgz") ||
|
||||
filename.includes("\0") ||
|
||||
filename !== basename(filename) ||
|
||||
filename !== win32.basename(filename)
|
||||
filenameBasename !== win32.basename(filename) ||
|
||||
(!isLocalFilename && !isAbsolutePathInDestination)
|
||||
) {
|
||||
throw new Error(
|
||||
`release-check: npm pack reported unsafe tarball filename ${JSON.stringify(filename)}.`,
|
||||
);
|
||||
}
|
||||
return resolve(packDestination, filename);
|
||||
return resolvedTarball;
|
||||
}
|
||||
|
||||
function installPackedTarball(prefixDir: string, tarballPath: string, cwd: string): void {
|
||||
execNpm(
|
||||
[
|
||||
"install",
|
||||
"-g",
|
||||
"--prefix",
|
||||
prefixDir,
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
tarballPath,
|
||||
],
|
||||
{
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
stdio: "inherit",
|
||||
},
|
||||
export function resolveReleaseCheckLocalPackageTarballs(
|
||||
tarballDir: string | undefined = process.env[RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV],
|
||||
): string[] {
|
||||
if (!tarballDir) {
|
||||
return [];
|
||||
}
|
||||
const resolvedDir = resolve(tarballDir);
|
||||
if (!existsSync(resolvedDir) || !statSync(resolvedDir).isDirectory()) {
|
||||
throw new Error(
|
||||
`release-check: ${RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV} must name a directory.`,
|
||||
);
|
||||
}
|
||||
const tarballs = readdirSync(resolvedDir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isFile() && entry.name.endsWith(".tgz"))
|
||||
.map((entry) => resolve(resolvedDir, entry.name))
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
if (tarballs.length !== 1) {
|
||||
throw new Error(
|
||||
`release-check: ${RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV} contains ${tarballs.length} tarballs; expected exactly one.`,
|
||||
);
|
||||
}
|
||||
return tarballs;
|
||||
}
|
||||
|
||||
export function createPackedTarballInstallArgs(prefixDir: string): string[] {
|
||||
return ["install", "--prefix", prefixDir, "--ignore-scripts", "--no-audit", "--no-fund"];
|
||||
}
|
||||
|
||||
export function writePackedTarballInstallManifest(
|
||||
prefixDir: string,
|
||||
tarballPath: string,
|
||||
localPackageTarballs: string[],
|
||||
): void {
|
||||
if (localPackageTarballs.length > 1) {
|
||||
throw new Error(
|
||||
`release-check: packed install accepts at most one @openclaw/ai tarball; found ${localPackageTarballs.length}.`,
|
||||
);
|
||||
}
|
||||
const dependencies: Record<string, string> = {
|
||||
openclaw: pathToFileURL(tarballPath).href,
|
||||
};
|
||||
if (localPackageTarballs[0]) {
|
||||
dependencies["@openclaw/ai"] = pathToFileURL(localPackageTarballs[0]).href;
|
||||
}
|
||||
mkdirSync(prefixDir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(prefixDir, "package.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
private: true,
|
||||
dependencies,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveGlobalRoot(prefixDir: string, cwd: string): string {
|
||||
return execNpm(["root", "-g", "--prefix", prefixDir], {
|
||||
function installPackedTarball(
|
||||
prefixDir: string,
|
||||
tarballPath: string,
|
||||
cwd: string,
|
||||
localPackageTarballs: string[] = [],
|
||||
): void {
|
||||
writePackedTarballInstallManifest(prefixDir, tarballPath, localPackageTarballs);
|
||||
execNpm(createPackedTarballInstallArgs(prefixDir), {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
}).trim();
|
||||
stdio: "inherit",
|
||||
});
|
||||
}
|
||||
|
||||
export function resolvePackedInstalledBinaryPath(
|
||||
prefixDir: string,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
): string {
|
||||
return join(
|
||||
prefixDir,
|
||||
"node_modules",
|
||||
".bin",
|
||||
platform === "win32" ? "openclaw.cmd" : "openclaw",
|
||||
);
|
||||
}
|
||||
|
||||
function resolvePackedInstalledBinaryCommandInvocation(
|
||||
prefixDir: string,
|
||||
args: string[],
|
||||
): ReleaseCheckCommandInvocation {
|
||||
const binaryPath = resolvePackedInstalledBinaryPath(prefixDir);
|
||||
return process.platform === "win32"
|
||||
? {
|
||||
command: resolveWindowsCmdExePath(),
|
||||
args: ["/d", "/s", "/c", buildCmdExeCommandLine(binaryPath, args)],
|
||||
windowsVerbatimArguments: true,
|
||||
}
|
||||
: { command: binaryPath, args };
|
||||
}
|
||||
|
||||
export function createPackedBundledPluginPostinstallEnv(
|
||||
|
|
@ -556,7 +647,7 @@ function verifyPackedInstalledPackage(params: {
|
|||
prefixDir: string;
|
||||
tmpRoot: string;
|
||||
}): void {
|
||||
const invocation = resolveInstalledBinaryCommandInvocation(params.prefixDir, ["--version"]);
|
||||
const invocation = resolvePackedInstalledBinaryCommandInvocation(params.prefixDir, ["--version"]);
|
||||
const installedBinaryVersion = runReleaseCheckCommand(
|
||||
{
|
||||
command: invocation.command,
|
||||
|
|
@ -584,7 +675,14 @@ function verifyPackedInstalledPackage(params: {
|
|||
export function createPackedPluginSdkTypescriptSmokeProject(params: {
|
||||
consumerDir: string;
|
||||
packageSpec: string;
|
||||
aiPackageSpec?: string;
|
||||
}): void {
|
||||
const dependencies: Record<string, string> = {
|
||||
openclaw: params.packageSpec,
|
||||
};
|
||||
if (params.aiPackageSpec) {
|
||||
dependencies["@openclaw/ai"] = params.aiPackageSpec;
|
||||
}
|
||||
mkdirSync(join(params.consumerDir, "src"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(params.consumerDir, "package.json"),
|
||||
|
|
@ -593,9 +691,7 @@ export function createPackedPluginSdkTypescriptSmokeProject(params: {
|
|||
name: "openclaw-plugin-sdk-type-smoke",
|
||||
private: true,
|
||||
type: "module",
|
||||
dependencies: {
|
||||
openclaw: params.packageSpec,
|
||||
},
|
||||
dependencies,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
|
|
@ -627,11 +723,16 @@ export function createPackedPluginSdkTypescriptSmokeProject(params: {
|
|||
);
|
||||
}
|
||||
|
||||
function runPackedPluginSdkTypescriptSmoke(tarballPath: string, tmpRoot: string): void {
|
||||
function runPackedPluginSdkTypescriptSmoke(
|
||||
tarballPath: string,
|
||||
tmpRoot: string,
|
||||
localPackageTarballs: string[],
|
||||
): void {
|
||||
const consumerDir = join(tmpRoot, "plugin-sdk-type-consumer");
|
||||
createPackedPluginSdkTypescriptSmokeProject({
|
||||
consumerDir,
|
||||
packageSpec: `file:${tarballPath}`,
|
||||
aiPackageSpec: localPackageTarballs[0] ? `file:${localPackageTarballs[0]}` : undefined,
|
||||
});
|
||||
execNpm(["install", "--ignore-scripts", "--no-audit", "--no-fund"], {
|
||||
cwd: consumerDir,
|
||||
|
|
@ -762,7 +863,7 @@ function runPackedCliSmoke(params: {
|
|||
homeDir: string;
|
||||
stateDir: string;
|
||||
}): void {
|
||||
const binaryPath = resolveInstalledBinaryPath(params.prefixDir);
|
||||
const binaryPath = resolvePackedInstalledBinaryPath(params.prefixDir);
|
||||
const env = createPackedCliSmokeEnv(process.env, {
|
||||
HOME: params.homeDir,
|
||||
OPENCLAW_STATE_DIR: params.stateDir,
|
||||
|
|
@ -816,9 +917,10 @@ function runPackedBundledChannelEntrySmoke(): void {
|
|||
const packResults = runPack(packDir);
|
||||
const tarballPath = resolvePackedTarballPath(packDir, packResults);
|
||||
const prefixDir = join(tmpRoot, "prefix");
|
||||
installPackedTarball(prefixDir, tarballPath, tmpRoot);
|
||||
const localPackageTarballs = resolveReleaseCheckLocalPackageTarballs();
|
||||
installPackedTarball(prefixDir, tarballPath, tmpRoot, localPackageTarballs);
|
||||
|
||||
const packageRoot = join(resolveGlobalRoot(prefixDir, tmpRoot), "openclaw");
|
||||
const packageRoot = join(prefixDir, "node_modules", "openclaw");
|
||||
verifyPackedInstalledPackage({
|
||||
expectedVersion,
|
||||
packageRoot,
|
||||
|
|
@ -837,7 +939,7 @@ function runPackedBundledChannelEntrySmoke(): void {
|
|||
runPackedBundledPluginPostinstall(packageRoot);
|
||||
runPackedBundledPluginActivationSmoke(packageRoot, tmpRoot);
|
||||
runPackedTaskRegistryControlRuntimeSmoke(packageRoot);
|
||||
runPackedPluginSdkTypescriptSmoke(tarballPath, tmpRoot);
|
||||
runPackedPluginSdkTypescriptSmoke(tarballPath, tmpRoot, localPackageTarballs);
|
||||
runReleaseCheckCommand(
|
||||
{
|
||||
command: process.execPath,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|||
import {
|
||||
openClawNpmPrepublishVerifyUsage,
|
||||
parseOpenClawNpmPrepublishVerifyArgs,
|
||||
usesPreparedLocalDependencyInstall,
|
||||
} from "../scripts/openclaw-npm-prepublish-verify.ts";
|
||||
|
||||
describe("parseOpenClawNpmPrepublishVerifyArgs", () => {
|
||||
|
|
@ -47,3 +48,11 @@ describe("parseOpenClawNpmPrepublishVerifyArgs", () => {
|
|||
).toThrow("Invalid dependency tarball path: --bad");
|
||||
});
|
||||
});
|
||||
|
||||
describe("usesPreparedLocalDependencyInstall", () => {
|
||||
it("uses the prepared local project only for the single AI tarball release path", () => {
|
||||
expect(usesPreparedLocalDependencyInstall(0)).toBe(false);
|
||||
expect(usesPreparedLocalDependencyInstall(1)).toBe(true);
|
||||
expect(usesPreparedLocalDependencyInstall(2)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -835,6 +835,7 @@ describe("createPackedPluginSdkTypescriptSmokeProject", () => {
|
|||
createPackedPluginSdkTypescriptSmokeProject({
|
||||
consumerDir,
|
||||
packageSpec: `file:${packageRoot}`,
|
||||
aiPackageSpec: "file:/tmp/openclaw-ai.tgz",
|
||||
});
|
||||
|
||||
const packageJson = JSON.parse(readFileSync(join(consumerDir, "package.json"), "utf8")) as {
|
||||
|
|
@ -850,6 +851,7 @@ describe("createPackedPluginSdkTypescriptSmokeProject", () => {
|
|||
);
|
||||
|
||||
expect(packageJson.dependencies?.openclaw).toBe(`file:${packageRoot}`);
|
||||
expect(packageJson.dependencies?.["@openclaw/ai"]).toBe("file:/tmp/openclaw-ai.tgz");
|
||||
expect(tsconfig.compilerOptions?.skipLibCheck).toBe(true);
|
||||
expect(source).toBe(fixtureSource);
|
||||
expect(source).toContain('"openclaw/plugin-sdk"');
|
||||
|
|
@ -898,6 +900,11 @@ describe("resolvePackedTarballPath", () => {
|
|||
expect(
|
||||
resolvePackedTarballPath("/tmp/openclaw-pack", [{ filename: "openclaw-2026.6.17.tgz" }]),
|
||||
).toBe(resolvePath("/tmp/openclaw-pack", "openclaw-2026.6.17.tgz"));
|
||||
expect(
|
||||
resolvePackedTarballPath("/tmp/openclaw-pack", [
|
||||
{ filename: "/tmp/openclaw-pack/openclaw-2026.6.17.tgz" },
|
||||
]),
|
||||
).toBe(resolvePath("/tmp/openclaw-pack", "openclaw-2026.6.17.tgz"));
|
||||
});
|
||||
|
||||
it("rejects path-like npm pack tarball filenames", () => {
|
||||
|
|
|
|||
|
|
@ -209,12 +209,14 @@ describe("extended-stable npm release request", () => {
|
|||
).toEqual({ extendedStable: false });
|
||||
});
|
||||
|
||||
it("accepts a SHA-only extended-stable preflight bound to the branch tip", () => {
|
||||
it("accepts a SHA-only extended-stable preflight from an arbitrary workflow ref", () => {
|
||||
expect(
|
||||
validateExtendedStableNpmReleaseRequest({
|
||||
...valid,
|
||||
preflightOnly: true,
|
||||
releaseTag: sha,
|
||||
npmWorkflowRef: "refs/heads/main",
|
||||
extendedStableBranchSha: "",
|
||||
}),
|
||||
).toEqual({
|
||||
extendedStable: true,
|
||||
|
|
@ -226,8 +228,18 @@ describe("extended-stable npm release request", () => {
|
|||
...valid,
|
||||
preflightOnly: true,
|
||||
releaseTag: "b".repeat(40),
|
||||
npmWorkflowRef: "refs/heads/dev/preflight-candidate",
|
||||
extendedStableBranchSha: "",
|
||||
}),
|
||||
).toThrow(/must match the checked-out commit/u);
|
||||
expect(() =>
|
||||
validateExtendedStableNpmReleaseRequest({
|
||||
...valid,
|
||||
preflightOnly: true,
|
||||
releaseTag: sha,
|
||||
checkoutSha: "",
|
||||
}),
|
||||
).toThrow(/requires the full checked-out commit SHA/u);
|
||||
expect(() => validateExtendedStableNpmReleaseRequest({ ...valid, releaseTag: sha })).toThrow(
|
||||
/exact final vYYYY\.M\.P release tag/u,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -111,6 +111,31 @@ describe("minimal npm extended-stable workflow", () => {
|
|||
expect(summary.run).toContain("Extended-stable guard bypass: ${BYPASS_EXTENDED_STABLE_GUARD}");
|
||||
});
|
||||
|
||||
it("accepts arbitrary SHA preflight targets and exercises every publishable plugin package", () => {
|
||||
const parsed = workflow();
|
||||
const preflight = parsed.jobs?.preflight_openclaw_npm;
|
||||
const metadata = step(preflight, "Validate release metadata");
|
||||
expect(metadata.run).toContain('RELEASE_BRANCH_REF="${RELEASE_SHA}"');
|
||||
expect(metadata.run).not.toContain("Validation-only SHA mode only supports");
|
||||
|
||||
const plugins = step(preflight, "Exercise all extended-stable plugin npm packages");
|
||||
expect(step(preflight, "Verify release contents").env).toMatchObject({
|
||||
OPENCLAW_RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR:
|
||||
"${{ steps.ai_runtime_tarballs.outputs.dir }}",
|
||||
});
|
||||
expect(plugins.if).toBe("${{ inputs.npm_dist_tag == 'extended-stable' }}");
|
||||
expect(plugins.env).toMatchObject({
|
||||
OPENCLAW_PLUGIN_NPM_PUBLISH_TAG: "extended-stable",
|
||||
});
|
||||
expect(plugins.run).toContain("--selection-mode all-publishable");
|
||||
expect(plugins.run).toContain("--npm-dist-tag extended-stable");
|
||||
expect(plugins.run).toContain("scripts/check-plugin-npm-runtime-builds.mjs");
|
||||
expect(plugins.run).toContain("scripts/plugin-npm-publish.sh --pack");
|
||||
expect(plugins.run).toContain("OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR");
|
||||
expect(plugins.run).not.toContain("--publish");
|
||||
expect(step(preflight, "Upload extended-stable plugin npm packages")).toBeDefined();
|
||||
});
|
||||
|
||||
it("authenticates exact extended-stable run and Full Validation identities", () => {
|
||||
const raw = readFileSync(workflowPath, "utf8");
|
||||
expect(raw).toContain("--json workflowName,headBranch,headSha,event,conclusion,url");
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ describe("plugin npm publish wrapper", () => {
|
|||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout.trim()).toBe(
|
||||
"usage: bash scripts/plugin-npm-publish.sh [--dry-run|--pack-dry-run|--publish] <package-dir>",
|
||||
"usage: bash scripts/plugin-npm-publish.sh [--dry-run|--pack|--pack-dry-run|--publish] <package-dir>",
|
||||
);
|
||||
expect(result.stderr).toBe("");
|
||||
});
|
||||
|
|
@ -57,10 +57,18 @@ describe("plugin npm publish wrapper", () => {
|
|||
expect(result.status).toBe(2);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr.trim()).toBe(
|
||||
"usage: bash scripts/plugin-npm-publish.sh [--dry-run|--pack-dry-run|--publish] <package-dir>",
|
||||
"usage: bash scripts/plugin-npm-publish.sh [--dry-run|--pack|--pack-dry-run|--publish] <package-dir>",
|
||||
);
|
||||
});
|
||||
|
||||
it("requires an explicit artifact directory for real pack mode", () => {
|
||||
const result = runPluginPublishWrapper(["--pack", "extensions/telegram"]);
|
||||
|
||||
expect(result.status).toBe(2);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr.trim()).toBe("--pack requires OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR");
|
||||
});
|
||||
|
||||
it("rejects option-like package dirs before package checks", () => {
|
||||
const result = runPluginPublishWrapper(["--dry-run", "--wat"]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
// Release Check tests cover release check script behavior.
|
||||
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
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 { writePackedBundledPluginActivationConfig } from "../../scripts/release-check.ts";
|
||||
import {
|
||||
createPackedTarballInstallArgs,
|
||||
RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV,
|
||||
resolveReleaseCheckLocalPackageTarballs,
|
||||
writePackedTarballInstallManifest,
|
||||
writePackedBundledPluginActivationConfig,
|
||||
} from "../../scripts/release-check.ts";
|
||||
|
||||
function requirePluginEntries(config: { plugins?: { entries?: Record<string, unknown> } }) {
|
||||
if (!config.plugins?.entries) {
|
||||
|
|
@ -13,6 +19,83 @@ function requirePluginEntries(config: { plugins?: { entries?: Record<string, unk
|
|||
}
|
||||
|
||||
describe("release-check", () => {
|
||||
it("installs the packed core and local sibling package tarballs together", () => {
|
||||
expect(createPackedTarballInstallArgs("/tmp/prefix")).toEqual([
|
||||
"install",
|
||||
"--prefix",
|
||||
"/tmp/prefix",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves exactly one prepacked local dependency tarball", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "openclaw-release-check-tarball-test-"));
|
||||
try {
|
||||
writeFileSync(join(root, "openclaw-ai-2026.6.33.tgz"), "fixture");
|
||||
writeFileSync(join(root, "SHA256SUMS"), "fixture");
|
||||
expect(resolveReleaseCheckLocalPackageTarballs(root)).toEqual([
|
||||
join(root, "openclaw-ai-2026.6.33.tgz"),
|
||||
]);
|
||||
expect(resolveReleaseCheckLocalPackageTarballs(undefined)).toEqual([]);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("writes an explicit local project for unpublished core and AI tarballs", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "openclaw-release-check-install-test-"));
|
||||
try {
|
||||
writePackedTarballInstallManifest(root, "/tmp/openclaw.tgz", ["/tmp/openclaw-ai.tgz"]);
|
||||
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
|
||||
dependencies?: Record<string, string>;
|
||||
private?: boolean;
|
||||
};
|
||||
expect(manifest.private).toBe(true);
|
||||
expect(manifest.dependencies).toEqual({
|
||||
"@openclaw/ai": "file:///tmp/openclaw-ai.tgz",
|
||||
openclaw: "file:///tmp/openclaw.tgz",
|
||||
});
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves the no-env local release check path", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "openclaw-release-check-install-test-"));
|
||||
try {
|
||||
writePackedTarballInstallManifest(root, "/tmp/openclaw.tgz", []);
|
||||
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
|
||||
dependencies?: Record<string, string>;
|
||||
private?: boolean;
|
||||
};
|
||||
expect(manifest.private).toBe(true);
|
||||
expect(manifest.dependencies).toEqual({
|
||||
openclaw: "file:///tmp/openclaw.tgz",
|
||||
});
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects missing, empty, or ambiguous local dependency tarball directories", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "openclaw-release-check-tarball-test-"));
|
||||
try {
|
||||
expect(() => resolveReleaseCheckLocalPackageTarballs(join(root, "missing"))).toThrow(
|
||||
RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV,
|
||||
);
|
||||
const empty = join(root, "empty");
|
||||
mkdirSync(empty);
|
||||
expect(() => resolveReleaseCheckLocalPackageTarballs(empty)).toThrow("contains 0 tarballs");
|
||||
writeFileSync(join(empty, "one.tgz"), "fixture");
|
||||
writeFileSync(join(empty, "two.tgz"), "fixture");
|
||||
expect(() => resolveReleaseCheckLocalPackageTarballs(empty)).toThrow("contains 2 tarballs");
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("seeds packaged activation smoke with an included channel plugin", () => {
|
||||
const homeDir = mkdtempSync(join(tmpdir(), "openclaw-release-check-test-"));
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue