chore: Switch from TypeScript to build with tsdown, speeds up pnpm build by 5-10x.

This commit is contained in:
cpojer 2026-01-31 15:25:06 +09:00
parent d2a852b982
commit 67945e8d62
No known key found for this signature in database
GPG key ID: C29F94A3201118AF
25 changed files with 553 additions and 234 deletions

View file

@ -36,4 +36,4 @@ ENV NODE_ENV=production
# This reduces the attack surface by preventing container escape via root privileges # This reduces the attack surface by preventing container escape via root privileges
USER node USER node
CMD ["node", "dist/index.js"] CMD ["node", "dist/index.mjs"]

View file

@ -5,7 +5,7 @@ enum CommandResolver {
private static let helperName = "openclaw" private static let helperName = "openclaw"
static func gatewayEntrypoint(in root: URL) -> String? { static func gatewayEntrypoint(in root: URL) -> String? {
let distEntry = root.appendingPathComponent("dist/index.js").path let distEntry = root.appendingPathComponent("dist/index.mjs").path
if FileManager().isReadableFile(atPath: distEntry) { return distEntry } if FileManager().isReadableFile(atPath: distEntry) { return distEntry }
let openclawEntry = root.appendingPathComponent("openclaw.mjs").path let openclawEntry = root.appendingPathComponent("openclaw.mjs").path
if FileManager().isReadableFile(atPath: openclawEntry) { return openclawEntry } if FileManager().isReadableFile(atPath: openclawEntry) { return openclawEntry }
@ -271,7 +271,7 @@ enum CommandResolver {
} }
let missingEntry = """ let missingEntry = """
openclaw entrypoint missing (looked for dist/index.js or openclaw.mjs); run pnpm build. openclaw entrypoint missing (looked for dist/index.mjs or openclaw.mjs); run pnpm build.
""" """
return self.errorCommand(with: missingEntry) return self.errorCommand(with: missingEntry)
@ -360,10 +360,10 @@ enum CommandResolver {
if command -v openclaw >/dev/null 2>&1; then if command -v openclaw >/dev/null 2>&1; then
CLI="$(command -v openclaw)" CLI="$(command -v openclaw)"
openclaw \(quotedArgs); openclaw \(quotedArgs);
elif [ -n "${PRJ:-}" ] && [ -f "$PRJ/dist/index.js" ]; then elif [ -n "${PRJ:-}" ] && [ -f "$PRJ/dist/index.mjs" ]; then
if command -v node >/dev/null 2>&1; then if command -v node >/dev/null 2>&1; then
CLI="node $PRJ/dist/index.js" CLI="node $PRJ/dist/index.mjs"
node "$PRJ/dist/index.js" \(quotedArgs); node "$PRJ/dist/index.mjs" \(quotedArgs);
else else
echo "Node >=22 required on remote host"; exit 127; echo "Node >=22 required on remote host"; exit 127;
fi fi

View file

@ -45,7 +45,7 @@ import Testing
@Test func gatewayEntrypointPrefersDistOverBin() throws { @Test func gatewayEntrypointPrefersDistOverBin() throws {
let tmp = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) let tmp = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent(UUID().uuidString, isDirectory: true) .appendingPathComponent(UUID().uuidString, isDirectory: true)
let dist = tmp.appendingPathComponent("dist/index.js") let dist = tmp.appendingPathComponent("dist/index.mjs")
let bin = tmp.appendingPathComponent("bin/openclaw.js") let bin = tmp.appendingPathComponent("bin/openclaw.js")
try FileManager().createDirectory(at: dist.deletingLastPathComponent(), withIntermediateDirectories: true) try FileManager().createDirectory(at: dist.deletingLastPathComponent(), withIntermediateDirectories: true)
try FileManager().createDirectory(at: bin.deletingLastPathComponent(), withIntermediateDirectories: true) try FileManager().createDirectory(at: bin.deletingLastPathComponent(), withIntermediateDirectories: true)

View file

@ -19,7 +19,7 @@ services:
command: command:
[ [
"node", "node",
"dist/index.js", "dist/index.mjs",
"gateway", "gateway",
"--bind", "--bind",
"${OPENCLAW_GATEWAY_BIND:-lan}", "${OPENCLAW_GATEWAY_BIND:-lan}",
@ -42,4 +42,4 @@ services:
stdin_open: true stdin_open: true
tty: true tty: true
init: true init: true
entrypoint: ["node", "dist/index.js"] entrypoint: ["node", "dist/index.mjs"]

View file

@ -211,4 +211,4 @@ echo "Token: $OPENCLAW_GATEWAY_TOKEN"
echo "" echo ""
echo "Commands:" echo "Commands:"
echo " ${COMPOSE_HINT} logs -f openclaw-gateway" echo " ${COMPOSE_HINT} logs -f openclaw-gateway"
echo " ${COMPOSE_HINT} exec openclaw-gateway node dist/index.js health --token \"$OPENCLAW_GATEWAY_TOKEN\"" echo " ${COMPOSE_HINT} exec openclaw-gateway node dist/index.mjs health --token \"$OPENCLAW_GATEWAY_TOKEN\""

View file

@ -163,7 +163,7 @@ RUN pnpm ui:build
ENV NODE_ENV=production ENV NODE_ENV=production
CMD ["node","dist/index.js"] CMD ["node","dist/index.mjs"]
``` ```
### Channel setup (optional) ### Channel setup (optional)
@ -190,7 +190,7 @@ Docs: [WhatsApp](/channels/whatsapp), [Telegram](/channels/telegram), [Discord](
### Health check ### Health check
```bash ```bash
docker compose exec openclaw-gateway node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN" docker compose exec openclaw-gateway node dist/index.mjs health --token "$OPENCLAW_GATEWAY_TOKEN"
``` ```
### E2E smoke test (Docker) ### E2E smoke test (Docker)

View file

@ -57,7 +57,7 @@ primary_region = "iad"
NODE_OPTIONS = "--max-old-space-size=1536" NODE_OPTIONS = "--max-old-space-size=1536"
[processes] [processes]
app = "node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan" app = "node dist/index.mjs gateway --allow-unconfigured --port 3000 --bind lan"
[http_service] [http_service]
internal_port = 3000 internal_port = 3000
@ -332,10 +332,10 @@ If you need to change the startup command without a full redeploy:
fly machines list fly machines list
# Update command # Update command
fly machine update <machine-id> --command "node dist/index.js gateway --port 3000 --bind lan" -y fly machine update <machine-id> --command "node dist/index.mjs gateway --port 3000 --bind lan" -y
# Or with memory increase # Or with memory increase
fly machine update <machine-id> --vm-memory 2048 --command "node dist/index.js gateway --port 3000 --bind lan" -y fly machine update <machine-id> --vm-memory 2048 --command "node dist/index.mjs gateway --port 3000 --bind lan" -y
``` ```
**Note:** After `fly deploy`, the machine command may reset to what's in `fly.toml`. If you made manual changes, re-apply them after deploy. **Note:** After `fly deploy`, the machine command may reset to what's in `fly.toml`. If you made manual changes, re-apply them after deploy.

View file

@ -271,7 +271,7 @@ services:
command: command:
[ [
"node", "node",
"dist/index.js", "dist/index.mjs",
"gateway", "gateway",
"--bind", "--bind",
"${OPENCLAW_GATEWAY_BIND}", "${OPENCLAW_GATEWAY_BIND}",
@ -338,7 +338,7 @@ RUN pnpm ui:build
ENV NODE_ENV=production ENV NODE_ENV=production
CMD ["node","dist/index.js"] CMD ["node","dist/index.mjs"]
``` ```
--- ---

View file

@ -183,7 +183,7 @@ services:
command: command:
[ [
"node", "node",
"dist/index.js", "dist/index.mjs",
"gateway", "gateway",
"--bind", "--bind",
"${OPENCLAW_GATEWAY_BIND}", "${OPENCLAW_GATEWAY_BIND}",
@ -250,7 +250,7 @@ RUN pnpm ui:build
ENV NODE_ENV=production ENV NODE_ENV=production
CMD ["node","dist/index.js"] CMD ["node","dist/index.mjs"]
``` ```
--- ---

View file

@ -635,4 +635,4 @@ Plugins run in-process with the Gateway. Treat them as trusted code:
Plugins can (and should) ship tests: Plugins can (and should) ship tests:
- In-repo plugins can keep Vitest tests under `src/**` (example: `src/plugins/voice-call.plugin.test.ts`). - In-repo plugins can keep Vitest tests under `src/**` (example: `src/plugins/voice-call.plugin.test.ts`).
- Separately published plugins should run their own CI (lint/build/test) and validate `openclaw.extensions` points at the built entrypoint (`dist/index.js`). - Separately published plugins should run their own CI (lint/build/test) and validate `openclaw.extensions` points at the built entrypoint (`dist/index.mjs`).

View file

@ -22,7 +22,7 @@ primary_region = "iad" # change to your closest region
NODE_OPTIONS = "--max-old-space-size=1536" NODE_OPTIONS = "--max-old-space-size=1536"
[processes] [processes]
app = "node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan" app = "node dist/index.mjs gateway --allow-unconfigured --port 3000 --bind lan"
# NOTE: No [http_service] block = no public ingress allocated. # NOTE: No [http_service] block = no public ingress allocated.
# The gateway will only be accessible via: # The gateway will only be accessible via:

View file

@ -15,7 +15,7 @@ primary_region = "iad" # change to your closest region
NODE_OPTIONS = "--max-old-space-size=1536" NODE_OPTIONS = "--max-old-space-size=1536"
[processes] [processes]
app = "node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan" app = "node dist/index.mjs gateway --allow-unconfigured --port 3000 --bind lan"
[http_service] [http_service]
internal_port = 3000 internal_port = 3000

View file

@ -3,9 +3,9 @@
"version": "2026.1.29", "version": "2026.1.29",
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent", "description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.mjs",
"exports": { "exports": {
".": "./dist/index.js", ".": "./dist/index.mjs",
"./plugin-sdk": "./dist/plugin-sdk/index.js", "./plugin-sdk": "./dist/plugin-sdk/index.js",
"./plugin-sdk/*": "./dist/plugin-sdk/*", "./plugin-sdk/*": "./dist/plugin-sdk/*",
"./cli-entry": "./openclaw.mjs" "./cli-entry": "./openclaw.mjs"
@ -85,7 +85,7 @@
"docs:bin": "node scripts/build-docs-list.mjs", "docs:bin": "node scripts/build-docs-list.mjs",
"docs:dev": "cd docs && mint dev", "docs:dev": "cd docs && mint dev",
"docs:build": "cd docs && pnpm dlx --reporter append-only mint broken-links", "docs:build": "cd docs && pnpm dlx --reporter append-only mint broken-links",
"build": "pnpm canvas:a2ui:bundle && tsc -p tsconfig.json && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts", "build": "pnpm canvas:a2ui:bundle && tsdown && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts",
"plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts", "plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
"release:check": "node --import tsx scripts/release-check.ts", "release:check": "node --import tsx scripts/release-check.ts",
"ui:install": "node scripts/ui.js install", "ui:install": "node scripts/ui.js install",
@ -208,7 +208,7 @@
"yaml": "^2.8.2", "yaml": "^2.8.2",
"zod": "^4.3.6" "zod": "^4.3.6"
}, },
"optionalDependencies": { "peerDependencies": {
"@napi-rs/canvas": "^0.1.89", "@napi-rs/canvas": "^0.1.89",
"node-llama-cpp": "3.15.1" "node-llama-cpp": "3.15.1"
}, },
@ -236,6 +236,7 @@
"quicktype-core": "^23.2.6", "quicktype-core": "^23.2.6",
"rolldown": "1.0.0-rc.2", "rolldown": "1.0.0-rc.2",
"signal-utils": "^0.21.1", "signal-utils": "^0.21.1",
"tsdown": "^0.20.1",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vitest": "^4.0.18", "vitest": "^4.0.18",

File diff suppressed because it is too large Load diff

View file

@ -12,3 +12,4 @@ onlyBuiltDependencies:
- esbuild - esbuild
- protobufjs - protobufjs
- sharp - sharp
- '@napi-rs/canvas'

View file

@ -31,7 +31,7 @@ echo "Starting gateway container..."
-e "OPENCLAW_SKIP_CRON=1" \ -e "OPENCLAW_SKIP_CRON=1" \
-e "OPENCLAW_SKIP_CANVAS_HOST=1" \ -e "OPENCLAW_SKIP_CANVAS_HOST=1" \
"$IMAGE_NAME" \ "$IMAGE_NAME" \
bash -lc "node dist/index.js gateway --port $PORT --bind lan --allow-unconfigured > /tmp/gateway-net-e2e.log 2>&1" bash -lc "node dist/index.mjs gateway --port $PORT --bind lan --allow-unconfigured > /tmp/gateway-net-e2e.log 2>&1"
echo "Waiting for gateway to come up..." echo "Waiting for gateway to come up..."
for _ in $(seq 1 20); do for _ in $(seq 1 20); do

View file

@ -83,7 +83,7 @@ TRASH
} }
start_gateway() { start_gateway() {
node dist/index.js gateway --port 18789 --bind loopback --allow-unconfigured > /tmp/gateway-e2e.log 2>&1 & node dist/index.mjs gateway --port 18789 --bind loopback --allow-unconfigured > /tmp/gateway-e2e.log 2>&1 &
GATEWAY_PID="$!" GATEWAY_PID="$!"
} }
@ -185,7 +185,7 @@ TRASH
local validate_fn="${4:-}" local validate_fn="${4:-}"
# Default onboarding command wrapper. # Default onboarding command wrapper.
run_wizard_cmd "$case_name" "$home_dir" "node dist/index.js onboard $ONBOARD_FLAGS" "$send_fn" true "$validate_fn" run_wizard_cmd "$case_name" "$home_dir" "node dist/index.mjs onboard $ONBOARD_FLAGS" "$send_fn" true "$validate_fn"
} }
make_home() { make_home() {
@ -268,7 +268,7 @@ TRASH
home_dir="$(make_home local-basic)" home_dir="$(make_home local-basic)"
export HOME="$home_dir" export HOME="$home_dir"
mkdir -p "$HOME" mkdir -p "$HOME"
node dist/index.js onboard \ node dist/index.mjs onboard \
--non-interactive \ --non-interactive \
--accept-risk \ --accept-risk \
--flow quickstart \ --flow quickstart \
@ -345,7 +345,7 @@ NODE
export HOME="$home_dir" export HOME="$home_dir"
mkdir -p "$HOME" mkdir -p "$HOME"
# Smoke test non-interactive remote config write. # Smoke test non-interactive remote config write.
node dist/index.js onboard --non-interactive --accept-risk \ node dist/index.mjs onboard --non-interactive --accept-risk \
--mode remote \ --mode remote \
--remote-url ws://gateway.local:18789 \ --remote-url ws://gateway.local:18789 \
--remote-token remote-token \ --remote-token remote-token \
@ -398,7 +398,7 @@ NODE
} }
JSON JSON
node dist/index.js onboard \ node dist/index.mjs onboard \
--non-interactive \ --non-interactive \
--accept-risk \ --accept-risk \
--flow quickstart \ --flow quickstart \
@ -441,7 +441,7 @@ NODE
local home_dir local home_dir
home_dir="$(make_home channels)" home_dir="$(make_home channels)"
# Channels-only configure flow. # Channels-only configure flow.
run_wizard_cmd channels "$home_dir" "node dist/index.js configure --section channels" send_channels_flow run_wizard_cmd channels "$home_dir" "node dist/index.mjs configure --section channels" send_channels_flow
config_path="$HOME/.openclaw/openclaw.json" config_path="$HOME/.openclaw/openclaw.json"
assert_file "$config_path" assert_file "$config_path"
@ -492,7 +492,7 @@ NODE
} }
JSON JSON
run_wizard_cmd skills "$home_dir" "node dist/index.js configure --section skills" send_skills_flow run_wizard_cmd skills "$home_dir" "node dist/index.mjs configure --section skills" send_skills_flow
config_path="$HOME/.openclaw/openclaw.json" config_path="$HOME/.openclaw/openclaw.json"
assert_file "$config_path" assert_file "$config_path"

View file

@ -29,7 +29,7 @@ module.exports = {
}; };
JS JS
node dist/index.js plugins list --json > /tmp/plugins.json node dist/index.mjs plugins list --json > /tmp/plugins.json
node - <<'"'"'NODE'"'"' node - <<'"'"'NODE'"'"'
const fs = require("node:fs"); const fs = require("node:fs");
@ -81,8 +81,8 @@ module.exports = {
JS JS
tar -czf /tmp/demo-plugin-tgz.tgz -C "$pack_dir" package tar -czf /tmp/demo-plugin-tgz.tgz -C "$pack_dir" package
node dist/index.js plugins install /tmp/demo-plugin-tgz.tgz node dist/index.mjs plugins install /tmp/demo-plugin-tgz.tgz
node dist/index.js plugins list --json > /tmp/plugins2.json node dist/index.mjs plugins list --json > /tmp/plugins2.json
node - <<'"'"'NODE'"'"' node - <<'"'"'NODE'"'"'
const fs = require("node:fs"); const fs = require("node:fs");
@ -118,8 +118,8 @@ module.exports = {
}; };
JS JS
node dist/index.js plugins install "$dir_plugin" node dist/index.mjs plugins install "$dir_plugin"
node dist/index.js plugins list --json > /tmp/plugins3.json node dist/index.mjs plugins list --json > /tmp/plugins3.json
node - <<'"'"'NODE'"'"' node - <<'"'"'NODE'"'"'
const fs = require("node:fs"); const fs = require("node:fs");
@ -156,8 +156,8 @@ module.exports = {
}; };
JS JS
node dist/index.js plugins install "file:$file_pack_dir/package" node dist/index.mjs plugins install "file:$file_pack_dir/package"
node dist/index.js plugins list --json > /tmp/plugins4.json node dist/index.mjs plugins list --json > /tmp/plugins4.json
node - <<'"'"'NODE'"'"' node - <<'"'"'NODE'"'"'
const fs = require("node:fs"); const fs = require("node:fs");

View file

@ -24,7 +24,7 @@ describe("ensureSkillsWatcher", () => {
expect(ignored.some((re) => re.test("/tmp/workspace/skills/node_modules/pkg/index.js"))).toBe( expect(ignored.some((re) => re.test("/tmp/workspace/skills/node_modules/pkg/index.js"))).toBe(
true, true,
); );
expect(ignored.some((re) => re.test("/tmp/workspace/skills/dist/index.js"))).toBe(true); expect(ignored.some((re) => re.test("/tmp/workspace/skills/dist/index.mjs"))).toBe(true);
expect(ignored.some((re) => re.test("/tmp/workspace/skills/.git/config"))).toBe(true); expect(ignored.some((re) => re.test("/tmp/workspace/skills/.git/config"))).toBe(true);
expect(ignored.some((re) => re.test("/tmp/.hidden/skills/index.md"))).toBe(false); expect(ignored.some((re) => re.test("/tmp/.hidden/skills/index.md"))).toBe(false);
}); });

View file

@ -67,7 +67,7 @@ describe("resolveGatewayProgramArguments", () => {
it("falls back to node_modules package dist when .bin path is not resolved", async () => { it("falls back to node_modules package dist when .bin path is not resolved", async () => {
const argv1 = path.resolve("/tmp/.npm/_npx/63c3/node_modules/.bin/openclaw"); const argv1 = path.resolve("/tmp/.npm/_npx/63c3/node_modules/.bin/openclaw");
const indexPath = path.resolve("/tmp/.npm/_npx/63c3/node_modules/openclaw/dist/index.js"); const indexPath = path.resolve("/tmp/.npm/_npx/63c3/node_modules/openclaw/dist/index.mjs");
process.argv = ["node", argv1]; process.argv = ["node", argv1];
fsMocks.realpath.mockRejectedValue(new Error("no realpath")); fsMocks.realpath.mockRejectedValue(new Error("no realpath"));
fsMocks.access.mockImplementation(async (target: string) => { fsMocks.access.mockImplementation(async (target: string) => {

View file

@ -69,7 +69,7 @@ function isGatewayArgv(args: string[]): boolean {
if (!normalized.includes("gateway")) return false; if (!normalized.includes("gateway")) return false;
const entryCandidates = [ const entryCandidates = [
"dist/index.js", "dist/index.mjs",
"dist/index.mjs", "dist/index.mjs",
"dist/entry.js", "dist/entry.js",
"openclaw.mjs", "openclaw.mjs",

View file

@ -6,8 +6,8 @@ describe("isMainModule", () => {
it("returns true when argv[1] matches current file", () => { it("returns true when argv[1] matches current file", () => {
expect( expect(
isMainModule({ isMainModule({
currentFile: "/repo/dist/index.js", currentFile: "/repo/dist/index.mjs",
argv: ["node", "/repo/dist/index.js"], argv: ["node", "/repo/dist/index.mjs"],
cwd: "/repo", cwd: "/repo",
env: {}, env: {},
}), }),
@ -17,10 +17,10 @@ describe("isMainModule", () => {
it("returns true under PM2 when pm_exec_path matches current file", () => { it("returns true under PM2 when pm_exec_path matches current file", () => {
expect( expect(
isMainModule({ isMainModule({
currentFile: "/repo/dist/index.js", currentFile: "/repo/dist/index.mjs",
argv: ["node", "/pm2/lib/ProcessContainerFork.js"], argv: ["node", "/pm2/lib/ProcessContainerFork.js"],
cwd: "/repo", cwd: "/repo",
env: { pm_exec_path: "/repo/dist/index.js", pm_id: "0" }, env: { pm_exec_path: "/repo/dist/index.mjs", pm_id: "0" },
}), }),
).toBe(true); ).toBe(true);
}); });
@ -28,7 +28,7 @@ describe("isMainModule", () => {
it("returns false when running under PM2 but this module is imported", () => { it("returns false when running under PM2 but this module is imported", () => {
expect( expect(
isMainModule({ isMainModule({
currentFile: "/repo/node_modules/openclaw/dist/index.js", currentFile: "/repo/node_modules/openclaw/dist/index.mjs",
argv: ["node", "/repo/app.js"], argv: ["node", "/repo/app.js"],
cwd: "/repo", cwd: "/repo",
env: { pm_exec_path: "/repo/app.js", pm_id: "0" }, env: { pm_exec_path: "/repo/app.js", pm_id: "0" },

View file

@ -98,7 +98,7 @@ describe("installPluginFromArchive", () => {
JSON.stringify({ JSON.stringify({
name: "@openclaw/voice-call", name: "@openclaw/voice-call",
version: "0.0.1", version: "0.0.1",
openclaw: { extensions: ["./dist/index.js"] }, openclaw: { extensions: ["./dist/index.mjs"] },
}), }),
"utf-8", "utf-8",
); );
@ -112,7 +112,10 @@ describe("installPluginFromArchive", () => {
const extensionsDir = path.join(stateDir, "extensions"); const extensionsDir = path.join(stateDir, "extensions");
const { installPluginFromArchive } = await import("./install.js"); const { installPluginFromArchive } = await import("./install.js");
const result = await installPluginFromArchive({ archivePath, extensionsDir }); const result = await installPluginFromArchive({
archivePath,
extensionsDir,
});
expect(result.ok).toBe(true); expect(result.ok).toBe(true);
if (!result.ok) return; if (!result.ok) return;
expect(result.pluginId).toBe("voice-call"); expect(result.pluginId).toBe("voice-call");
@ -131,7 +134,7 @@ describe("installPluginFromArchive", () => {
JSON.stringify({ JSON.stringify({
name: "@openclaw/voice-call", name: "@openclaw/voice-call",
version: "0.0.1", version: "0.0.1",
openclaw: { extensions: ["./dist/index.js"] }, openclaw: { extensions: ["./dist/index.mjs"] },
}), }),
"utf-8", "utf-8",
); );
@ -145,8 +148,14 @@ describe("installPluginFromArchive", () => {
const extensionsDir = path.join(stateDir, "extensions"); const extensionsDir = path.join(stateDir, "extensions");
const { installPluginFromArchive } = await import("./install.js"); const { installPluginFromArchive } = await import("./install.js");
const first = await installPluginFromArchive({ archivePath, extensionsDir }); const first = await installPluginFromArchive({
const second = await installPluginFromArchive({ archivePath, extensionsDir }); archivePath,
extensionsDir,
});
const second = await installPluginFromArchive({
archivePath,
extensionsDir,
});
expect(first.ok).toBe(true); expect(first.ok).toBe(true);
expect(second.ok).toBe(false); expect(second.ok).toBe(false);
@ -165,16 +174,19 @@ describe("installPluginFromArchive", () => {
JSON.stringify({ JSON.stringify({
name: "@openclaw/zipper", name: "@openclaw/zipper",
version: "0.0.1", version: "0.0.1",
openclaw: { extensions: ["./dist/index.js"] }, openclaw: { extensions: ["./dist/index.mjs"] },
}), }),
); );
zip.file("package/dist/index.js", "export {};"); zip.file("package/dist/index.mjs", "export {};");
const buffer = await zip.generateAsync({ type: "nodebuffer" }); const buffer = await zip.generateAsync({ type: "nodebuffer" });
fs.writeFileSync(archivePath, buffer); fs.writeFileSync(archivePath, buffer);
const extensionsDir = path.join(stateDir, "extensions"); const extensionsDir = path.join(stateDir, "extensions");
const { installPluginFromArchive } = await import("./install.js"); const { installPluginFromArchive } = await import("./install.js");
const result = await installPluginFromArchive({ archivePath, extensionsDir }); const result = await installPluginFromArchive({
archivePath,
extensionsDir,
});
expect(result.ok).toBe(true); expect(result.ok).toBe(true);
if (!result.ok) return; if (!result.ok) return;
@ -194,7 +206,7 @@ describe("installPluginFromArchive", () => {
JSON.stringify({ JSON.stringify({
name: "@openclaw/voice-call", name: "@openclaw/voice-call",
version: "0.0.1", version: "0.0.1",
openclaw: { extensions: ["./dist/index.js"] }, openclaw: { extensions: ["./dist/index.mjs"] },
}), }),
"utf-8", "utf-8",
); );
@ -212,7 +224,7 @@ describe("installPluginFromArchive", () => {
JSON.stringify({ JSON.stringify({
name: "@openclaw/voice-call", name: "@openclaw/voice-call",
version: "0.0.2", version: "0.0.2",
openclaw: { extensions: ["./dist/index.js"] }, openclaw: { extensions: ["./dist/index.mjs"] },
}), }),
"utf-8", "utf-8",
); );
@ -263,7 +275,10 @@ describe("installPluginFromArchive", () => {
const extensionsDir = path.join(stateDir, "extensions"); const extensionsDir = path.join(stateDir, "extensions");
const { installPluginFromArchive } = await import("./install.js"); const { installPluginFromArchive } = await import("./install.js");
const result = await installPluginFromArchive({ archivePath, extensionsDir }); const result = await installPluginFromArchive({
archivePath,
extensionsDir,
});
expect(result.ok).toBe(false); expect(result.ok).toBe(false);
if (result.ok) return; if (result.ok) return;
expect(result.error).toContain("openclaw.extensions"); expect(result.error).toContain("openclaw.extensions");

View file

@ -114,7 +114,7 @@ const spawnGatewayInstance = async (name: string): Promise<GatewayInstance> => {
child = spawn( child = spawn(
"node", "node",
[ [
"dist/index.js", "dist/index.mjs",
"gateway", "gateway",
"--port", "--port",
String(port), String(port),
@ -199,7 +199,7 @@ const stopGatewayInstance = async (inst: GatewayInstance) => {
const runCliJson = async (args: string[], env: NodeJS.ProcessEnv): Promise<unknown> => { const runCliJson = async (args: string[], env: NodeJS.ProcessEnv): Promise<unknown> => {
const stdout: string[] = []; const stdout: string[] = [];
const stderr: string[] = []; const stderr: string[] = [];
const child = spawn("node", ["dist/index.js", ...args], { const child = spawn("node", ["dist/index.mjs", ...args], {
cwd: process.cwd(), cwd: process.cwd(),
env: { ...process.env, ...env }, env: { ...process.env, ...env },
stdio: ["ignore", "pipe", "pipe"], stdio: ["ignore", "pipe", "pipe"],

View file

@ -1,17 +1,18 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "allowSyntheticDefaultImports": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"skipLibCheck": true, "module": "NodeNext",
"resolveJsonModule": true, "moduleResolution": "NodeNext",
"noEmit": true,
"noEmitOnError": true, "noEmitOnError": true,
"allowSyntheticDefaultImports": true "outDir": "dist",
"resolveJsonModule": true,
"rootDir": "src",
"skipLibCheck": true,
"strict": true,
"target": "ES2022"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": [ "exclude": [