mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
feat: bundle plugin npm dependencies
This commit is contained in:
@@ -28,7 +28,7 @@ Docs: https://docs.openclaw.ai
|
||||
- QA-Lab: include an opt-in `update.run` package self-upgrade sentinel for destructive latest-package recovery checks.
|
||||
- QA-Lab: add Codex plugin lifecycle and auth-profile fixture coverage for missing installs, pinned-version drift, first-turn install ordering, and doctor migration safety. (#80323, refs #80174) Thanks @100yenadmin.
|
||||
- Models/perf: pre-warm the provider auth-state map at gateway startup so `/models` and every model-listing call short-circuits the per-provider plugin / external-CLI discovery on the hot path. Per-call cost drops from ~20 s to ~5 ms (~4,100×); the one-time startup warm resets and re-warms after hot reloads. (#84816) Thanks @sjf.
|
||||
- Release/security: ship the root npm package and OpenClaw-owned npm plugins with generated shrinkwrap and require review for lockfile/shrinkwrap changes so published installs use locked dependency graphs.
|
||||
- Release/security: ship the root npm package and OpenClaw-owned npm plugins with generated shrinkwrap, support bundled plugin runtime dependencies for suitable plugin tarballs, and require review for lockfile/shrinkwrap changes so published installs use locked dependency graphs.
|
||||
- Tests/perf: isolate doctor core health check unit coverage from real skills/workspace discovery so `doctor-core-checks` no longer dominates unit perf while keeping one real skills-readiness smoke. (#84493) Thanks @frankekn.
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -59,7 +59,9 @@ OpenClaw source checkouts use `pnpm-lock.yaml`. The published `openclaw` npm
|
||||
package and OpenClaw-owned npm plugin packages include `npm-shrinkwrap.json`,
|
||||
npm's publishable dependency lockfile, so package installs use the reviewed
|
||||
transitive dependency graph from the release instead of resolving a fresh graph
|
||||
at install time.
|
||||
at install time. OpenClaw-owned npm plugin packages also publish with
|
||||
`bundleDependencies`, so their runtime dependency files are carried in the
|
||||
plugin tarball instead of depending only on install-time resolution.
|
||||
|
||||
This is a supply-chain hardening measure:
|
||||
|
||||
@@ -67,6 +69,7 @@ This is a supply-chain hardening measure:
|
||||
- transitive dependency updates become visible review surfaces;
|
||||
- the package tarball contains the dependency graph that release validators
|
||||
checked;
|
||||
- OpenClaw-owned plugin tarballs contain the dependency files from that graph;
|
||||
- `package-lock.json` stays out of the published package, because npm does not
|
||||
treat it as the publishable lock contract.
|
||||
|
||||
@@ -87,10 +90,11 @@ Use `pnpm deps:shrinkwrap:root:generate` and
|
||||
`pnpm deps:shrinkwrap:root:check` only when you intentionally want to refresh
|
||||
the root `openclaw` package without touching plugin packages.
|
||||
|
||||
Review `pnpm-lock.yaml`, `npm-shrinkwrap.json`, and any `package-lock.json`
|
||||
diff as security-sensitive. The package validators require shrinkwrap in new
|
||||
root package tarballs and the plugin npm publish path checks plugin-local
|
||||
shrinkwrap before packing or publishing. Package validators reject
|
||||
Review `pnpm-lock.yaml`, `npm-shrinkwrap.json`, bundled plugin dependency
|
||||
payloads, and any `package-lock.json` diff as security-sensitive. The package
|
||||
validators require shrinkwrap in new root package tarballs and the plugin npm
|
||||
publish path checks plugin-local shrinkwrap, installs package-local bundled
|
||||
dependencies, and then packs or publishes. Package validators reject
|
||||
`package-lock.json`.
|
||||
|
||||
To inspect a published package:
|
||||
@@ -106,6 +110,7 @@ the same tar entry:
|
||||
```bash
|
||||
npm pack @openclaw/discord@<version> --json --pack-destination /tmp/openclaw-plugin-pack
|
||||
tar -tf /tmp/openclaw-plugin-pack/openclaw-discord-<version>.tgz | grep '^package/npm-shrinkwrap.json$'
|
||||
tar -tf /tmp/openclaw-plugin-pack/openclaw-discord-<version>.tgz | grep '^package/node_modules/'
|
||||
```
|
||||
|
||||
Background: [npm-shrinkwrap.json](https://docs.npmjs.com/cli/v11/configuring-npm/npm-shrinkwrap-json).
|
||||
|
||||
@@ -74,6 +74,13 @@ policy, and writes `extensions/<id>/npm-shrinkwrap.json` for each
|
||||
OpenClaw does not require it for community packages, but npm will respect it
|
||||
when present.
|
||||
|
||||
OpenClaw-owned npm plugin packages also publish with `bundleDependencies`. The
|
||||
npm publish path overlays `bundleDependencies: true`, removes dev-only
|
||||
workspace metadata from the published package manifest, runs a script-free npm
|
||||
install for package-local runtime dependencies, then packs or publishes the
|
||||
plugin tarball with those dependency files included. The root `openclaw`
|
||||
package does not bundle its full dependency tree.
|
||||
|
||||
Plugins that import `openclaw/plugin-sdk/*` declare `openclaw` as a peer
|
||||
dependency. OpenClaw does not let npm install a separate registry copy of the
|
||||
host package into the managed root, because stale host packages can affect npm
|
||||
|
||||
6
extensions/msteams/npm-shrinkwrap.json
generated
6
extensions/msteams/npm-shrinkwrap.json
generated
@@ -1521,9 +1521,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
|
||||
6
extensions/slack/npm-shrinkwrap.json
generated
6
extensions/slack/npm-shrinkwrap.json
generated
@@ -1258,9 +1258,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
|
||||
6
extensions/whatsapp/npm-shrinkwrap.json
generated
6
extensions/whatsapp/npm-shrinkwrap.json
generated
@@ -1830,9 +1830,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
|
||||
6
extensions/zalouser/npm-shrinkwrap.json
generated
6
extensions/zalouser/npm-shrinkwrap.json
generated
@@ -356,9 +356,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
|
||||
6
npm-shrinkwrap.json
generated
6
npm-shrinkwrap.json
generated
@@ -4470,9 +4470,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
|
||||
@@ -108,6 +108,70 @@ function assertPluginNpmRuntimeBuildExists(plan) {
|
||||
assertPackageFilesDoNotExcludeRequiredRuntimeArtifacts(plan);
|
||||
}
|
||||
|
||||
function hasPackageRuntimeDependencies(packageJson) {
|
||||
return (
|
||||
Object.keys(packageJson.dependencies ?? {}).length > 0 ||
|
||||
Object.keys(packageJson.optionalDependencies ?? {}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function shouldBundleDependencies(value) {
|
||||
return value === true || value === "1" || value === "true";
|
||||
}
|
||||
|
||||
function installPackageLocalBundledDependencies(params) {
|
||||
const packageJson = params.packageJson;
|
||||
if (packageJson.bundleDependencies !== true || !hasPackageRuntimeDependencies(packageJson)) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const shrinkwrapPath = path.join(params.packageDir, "npm-shrinkwrap.json");
|
||||
if (!fs.existsSync(shrinkwrapPath)) {
|
||||
throw new Error(
|
||||
`package-local bundled dependency install requires npm-shrinkwrap.json for ${params.pluginDir}`,
|
||||
);
|
||||
}
|
||||
|
||||
const nodeModulesPath = path.join(params.packageDir, "node_modules");
|
||||
if (fs.existsSync(nodeModulesPath)) {
|
||||
throw new Error(
|
||||
`package-local bundled dependency install refuses to replace existing node_modules for ${params.pluginDir}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.error(`[plugin-npm-publish] installing bundled dependencies for ${params.pluginDir}`);
|
||||
const result = spawnSync(
|
||||
"npm",
|
||||
[
|
||||
"install",
|
||||
"--omit=dev",
|
||||
"--omit=peer",
|
||||
"--legacy-peer-deps",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
"--package-lock=false",
|
||||
"--loglevel=error",
|
||||
],
|
||||
{
|
||||
cwd: params.packageDir,
|
||||
env: process.env,
|
||||
stdio: ["ignore", "ignore", "inherit"],
|
||||
},
|
||||
);
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
if ((result.status ?? 1) !== 0) {
|
||||
throw new Error(
|
||||
`package-local bundled dependency install failed for ${params.pluginDir} with exit ${result.status ?? 1}`,
|
||||
);
|
||||
}
|
||||
return () => {
|
||||
fs.rmSync(nodeModulesPath, { recursive: true, force: true });
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveAugmentedPluginNpmPackageJson(params) {
|
||||
const repoRoot = path.resolve(params.repoRoot ?? ".");
|
||||
const packageDir = resolvePackageDir(repoRoot, params.packageDir);
|
||||
@@ -147,6 +211,11 @@ export function resolveAugmentedPluginNpmPackageJson(params) {
|
||||
...(plan.runtimeSetupEntry ? { runtimeSetupEntry: plan.runtimeSetupEntry } : {}),
|
||||
},
|
||||
};
|
||||
if (shouldBundleDependencies(params.bundleDependencies)) {
|
||||
packageJson.bundleDependencies = true;
|
||||
delete packageJson.bundledDependencies;
|
||||
delete packageJson.devDependencies;
|
||||
}
|
||||
const changed = JSON.stringify(packageJson) !== JSON.stringify(plan.packageJson);
|
||||
return {
|
||||
packageJsonPath,
|
||||
@@ -155,6 +224,7 @@ export function resolveAugmentedPluginNpmPackageJson(params) {
|
||||
changed,
|
||||
packageJson,
|
||||
pluginDir: plan.pluginDir,
|
||||
bundleDependencies: shouldBundleDependencies(params.bundleDependencies),
|
||||
reason: changed ? "package-local-runtime" : "unchanged",
|
||||
};
|
||||
}
|
||||
@@ -290,6 +360,7 @@ export function resolveAugmentedPluginNpmManifest(params) {
|
||||
export function withAugmentedPluginNpmManifestForPackage(params, callback) {
|
||||
const repoRoot = path.resolve(params.repoRoot ?? ".");
|
||||
const packageDir = resolvePackageDir(repoRoot, params.packageDir);
|
||||
const bundleDependencies = shouldBundleDependencies(params.bundleDependencies);
|
||||
const resolvedManifest = resolveAugmentedPluginNpmManifest({
|
||||
repoRoot,
|
||||
packageDir,
|
||||
@@ -297,6 +368,7 @@ export function withAugmentedPluginNpmManifestForPackage(params, callback) {
|
||||
const resolvedPackageJson = resolveAugmentedPluginNpmPackageJson({
|
||||
repoRoot,
|
||||
packageDir,
|
||||
bundleDependencies,
|
||||
});
|
||||
|
||||
if (
|
||||
@@ -332,7 +404,15 @@ export function withAugmentedPluginNpmManifestForPackage(params, callback) {
|
||||
);
|
||||
writeJsonFile(resolvedPackageJson.packageJsonPath, resolvedPackageJson.packageJson);
|
||||
}
|
||||
let cleanupBundledDependencies = () => {};
|
||||
try {
|
||||
if (bundleDependencies && resolvedPackageJson.packageJson) {
|
||||
cleanupBundledDependencies = installPackageLocalBundledDependencies({
|
||||
packageDir,
|
||||
packageJson: resolvedPackageJson.packageJson,
|
||||
pluginDir: resolvedPackageJson.pluginDir ?? path.basename(packageDir),
|
||||
});
|
||||
}
|
||||
return callback({
|
||||
...resolvedManifest,
|
||||
packageDir,
|
||||
@@ -341,6 +421,7 @@ export function withAugmentedPluginNpmManifestForPackage(params, callback) {
|
||||
packageJsonApplied: resolvedPackageJson.changed && Boolean(resolvedPackageJson.packageJson),
|
||||
});
|
||||
} finally {
|
||||
cleanupBundledDependencies();
|
||||
if (originalManifest !== undefined) {
|
||||
fs.writeFileSync(resolvedManifest.manifestPath, originalManifest, "utf8");
|
||||
}
|
||||
@@ -372,17 +453,23 @@ function parseRunArgs(argv) {
|
||||
|
||||
function main(argv = process.argv.slice(2)) {
|
||||
const { packageDir, command, args } = parseRunArgs(argv);
|
||||
return withAugmentedPluginNpmManifestForPackage({ packageDir }, ({ packageDir: cwd }) => {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd,
|
||||
env: process.env,
|
||||
stdio: "inherit",
|
||||
});
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
return result.status ?? 1;
|
||||
});
|
||||
return withAugmentedPluginNpmManifestForPackage(
|
||||
{
|
||||
packageDir,
|
||||
bundleDependencies: process.env.OPENCLAW_PLUGIN_NPM_BUNDLE_DEPENDENCIES,
|
||||
},
|
||||
({ packageDir: cwd }) => {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd,
|
||||
env: process.env,
|
||||
stdio: "inherit",
|
||||
});
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
return result.status ?? 1;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
|
||||
@@ -140,7 +140,8 @@ build_package_runtime
|
||||
check_package_shrinkwrap
|
||||
|
||||
if [[ "${mode}" == "--pack-dry-run" ]]; then
|
||||
node scripts/lib/plugin-npm-package-manifest.mjs --run "${package_dir}" -- \
|
||||
OPENCLAW_PLUGIN_NPM_BUNDLE_DEPENDENCIES=1 \
|
||||
node scripts/lib/plugin-npm-package-manifest.mjs --run "${package_dir}" -- \
|
||||
npm pack --dry-run --json --ignore-scripts
|
||||
exit 0
|
||||
fi
|
||||
@@ -149,7 +150,8 @@ fi
|
||||
cleanup_files=()
|
||||
trap 'rm -f "${cleanup_files[@]}"' EXIT
|
||||
run_with_manifest_overlay() {
|
||||
node scripts/lib/plugin-npm-package-manifest.mjs --run "${package_dir}" -- "$@"
|
||||
OPENCLAW_PLUGIN_NPM_BUNDLE_DEPENDENCIES=1 \
|
||||
node scripts/lib/plugin-npm-package-manifest.mjs --run "${package_dir}" -- "$@"
|
||||
}
|
||||
publish_userconfig=""
|
||||
if [[ -n "${publish_auth_token}" ]]; then
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
@@ -97,6 +97,17 @@ function writePublishablePluginPackage(repoDir: string): string {
|
||||
return packageDir;
|
||||
}
|
||||
|
||||
function writeLocalDependencyPackage(packageDir: string): void {
|
||||
const dependencyDir = join(packageDir, "deps", "local-runtime-dep");
|
||||
mkdirSync(dependencyDir, { recursive: true });
|
||||
writeJsonFile(join(dependencyDir, "package.json"), {
|
||||
name: "local-runtime-dep",
|
||||
version: "1.0.0",
|
||||
main: "index.js",
|
||||
});
|
||||
writeFileText(join(dependencyDir, "index.js"), "module.exports = 1;\n");
|
||||
}
|
||||
|
||||
describe("plugin npm package manifest staging", () => {
|
||||
it("overlays generated channel configs while packing and restores source manifest", () => {
|
||||
const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-package-manifest-");
|
||||
@@ -172,12 +183,14 @@ describe("plugin npm package manifest staging", () => {
|
||||
const resolved = resolveAugmentedPluginNpmPackageJson({
|
||||
repoRoot: repoDir,
|
||||
packageDir,
|
||||
bundleDependencies: true,
|
||||
});
|
||||
expect(resolved.changed).toBe(true);
|
||||
expect(resolved.packageJson).toEqual({
|
||||
name: "@openclaw/diffs",
|
||||
version: "2026.5.3",
|
||||
type: "module",
|
||||
bundleDependencies: true,
|
||||
files: [
|
||||
"dist/**",
|
||||
"openclaw.plugin.json",
|
||||
@@ -209,17 +222,94 @@ describe("plugin npm package manifest staging", () => {
|
||||
});
|
||||
|
||||
const originalText = readFileSync(join(packageDir, "package.json"), "utf8");
|
||||
withAugmentedPluginNpmManifestForPackage({ repoRoot: repoDir, packageDir }, () => {
|
||||
const stagedPackageJson = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
|
||||
expect(stagedPackageJson.openclaw.extensions).toEqual(["./index.ts"]);
|
||||
expect(stagedPackageJson.openclaw.runtimeExtensions).toEqual(["./dist/index.js"]);
|
||||
expect(stagedPackageJson.openclaw.runtimeSetupEntry).toBe("./dist/setup-entry.js");
|
||||
expect(stagedPackageJson.files).toContain("dist/**");
|
||||
expect(stagedPackageJson.files).toContain("npm-shrinkwrap.json");
|
||||
expect(stagedPackageJson.files).toContain("skills/**");
|
||||
expect(stagedPackageJson.peerDependencies.openclaw).toBe(">=2026.4.30");
|
||||
expect(stagedPackageJson.peerDependenciesMeta.openclaw.optional).toBe(true);
|
||||
withAugmentedPluginNpmManifestForPackage(
|
||||
{ repoRoot: repoDir, packageDir, bundleDependencies: true },
|
||||
() => {
|
||||
const stagedPackageJson = JSON.parse(
|
||||
readFileSync(join(packageDir, "package.json"), "utf8"),
|
||||
);
|
||||
expect(stagedPackageJson.openclaw.extensions).toEqual(["./index.ts"]);
|
||||
expect(stagedPackageJson.openclaw.runtimeExtensions).toEqual(["./dist/index.js"]);
|
||||
expect(stagedPackageJson.openclaw.runtimeSetupEntry).toBe("./dist/setup-entry.js");
|
||||
expect(stagedPackageJson.bundleDependencies).toBe(true);
|
||||
expect(stagedPackageJson.files).toContain("dist/**");
|
||||
expect(stagedPackageJson.files).toContain("npm-shrinkwrap.json");
|
||||
expect(stagedPackageJson.files).toContain("skills/**");
|
||||
expect(stagedPackageJson.peerDependencies.openclaw).toBe(">=2026.4.30");
|
||||
expect(stagedPackageJson.peerDependenciesMeta.openclaw.optional).toBe(true);
|
||||
},
|
||||
);
|
||||
expect(readFileSync(join(packageDir, "package.json"), "utf8")).toBe(originalText);
|
||||
});
|
||||
|
||||
it("installs and cleans package-local bundled dependencies while packing", () => {
|
||||
const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-package-bundled-deps-");
|
||||
const packageDir = writePublishablePluginPackage(repoDir);
|
||||
writeFileText(join(packageDir, "dist", "index.js"), "export {};\n");
|
||||
writeFileText(join(packageDir, "dist", "setup-entry.js"), "export {};\n");
|
||||
writeLocalDependencyPackage(packageDir);
|
||||
writeJsonFile(join(packageDir, "package.json"), {
|
||||
name: "@openclaw/diffs",
|
||||
version: "2026.5.3",
|
||||
type: "module",
|
||||
dependencies: {
|
||||
"local-runtime-dep": "file:./deps/local-runtime-dep",
|
||||
},
|
||||
devDependencies: {
|
||||
"@openclaw/plugin-sdk": "workspace:*",
|
||||
},
|
||||
openclaw: {
|
||||
extensions: ["./index.ts"],
|
||||
setupEntry: "./setup-entry.ts",
|
||||
compat: {
|
||||
pluginApi: ">=2026.4.30",
|
||||
},
|
||||
release: {
|
||||
publishToNpm: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJsonFile(join(packageDir, "npm-shrinkwrap.json"), {
|
||||
name: "@openclaw/diffs",
|
||||
version: "2026.5.3",
|
||||
lockfileVersion: 3,
|
||||
requires: true,
|
||||
packages: {
|
||||
"": {
|
||||
name: "@openclaw/diffs",
|
||||
version: "2026.5.3",
|
||||
dependencies: {
|
||||
"local-runtime-dep": "file:./deps/local-runtime-dep",
|
||||
},
|
||||
},
|
||||
"deps/local-runtime-dep": {
|
||||
name: "local-runtime-dep",
|
||||
version: "1.0.0",
|
||||
},
|
||||
"node_modules/local-runtime-dep": {
|
||||
resolved: "deps/local-runtime-dep",
|
||||
link: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const originalText = readFileSync(join(packageDir, "package.json"), "utf8");
|
||||
const nodeModulesPath = join(packageDir, "node_modules");
|
||||
expect(existsSync(nodeModulesPath)).toBe(false);
|
||||
|
||||
withAugmentedPluginNpmManifestForPackage(
|
||||
{ repoRoot: repoDir, packageDir, bundleDependencies: true },
|
||||
() => {
|
||||
const stagedPackageJson = JSON.parse(
|
||||
readFileSync(join(packageDir, "package.json"), "utf8"),
|
||||
);
|
||||
expect(stagedPackageJson.bundleDependencies).toBe(true);
|
||||
expect(stagedPackageJson.devDependencies).toBeUndefined();
|
||||
expect(existsSync(join(nodeModulesPath, "local-runtime-dep", "package.json"))).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
expect(existsSync(nodeModulesPath)).toBe(false);
|
||||
expect(readFileSync(join(packageDir, "package.json"), "utf8")).toBe(originalText);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user