test-native-plugin.sh 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. #!/usr/bin/env bash
  2. # Test: OpenClaw native plugin package
  3. # Verifies the Superpowers repo exposes a native OpenClaw plugin contract.
  4. set -euo pipefail
  5. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  6. REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
  7. cd "$REPO_ROOT"
  8. echo "=== Test: OpenClaw native plugin package ==="
  9. echo "Test 1: Checking OpenClaw manifest..."
  10. node <<'NODE'
  11. import fs from "node:fs";
  12. const manifestPath = "openclaw.plugin.json";
  13. if (!fs.existsSync(manifestPath)) {
  14. throw new Error(`${manifestPath} is missing`);
  15. }
  16. const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
  17. const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
  18. if (manifest.id !== "superpowers-openclaw") {
  19. throw new Error(`unexpected manifest id: ${manifest.id}`);
  20. }
  21. if (manifest.version !== pkg.version) {
  22. throw new Error(`manifest version ${manifest.version} must match package version ${pkg.version}`);
  23. }
  24. if (!Array.isArray(manifest.skills) || manifest.skills.length !== 1 || manifest.skills[0] !== "./skills") {
  25. throw new Error(`unexpected skills declaration: ${JSON.stringify(manifest.skills)}`);
  26. }
  27. if (
  28. !manifest.configSchema ||
  29. manifest.configSchema.type !== "object" ||
  30. manifest.configSchema.additionalProperties !== false ||
  31. manifest.configSchema.properties !== undefined
  32. ) {
  33. throw new Error(`manifest must declare an empty object configSchema: ${JSON.stringify(manifest.configSchema)}`);
  34. }
  35. if ("entrypoint" in manifest) {
  36. throw new Error("OpenClaw entrypoints belong in package.json openclaw.extensions, not openclaw.plugin.json");
  37. }
  38. if ("hooks" in manifest) {
  39. throw new Error("OpenClaw hook registration belongs in the runtime entrypoint, not openclaw.plugin.json");
  40. }
  41. NODE
  42. echo " [PASS] Manifest declares plugin skills correctly"
  43. echo "Test 2: Checking package OpenClaw extension metadata..."
  44. node <<'NODE'
  45. import fs from "node:fs";
  46. const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
  47. if (pkg.name !== "superpowers") {
  48. throw new Error(`package name changed unexpectedly: ${pkg.name}`);
  49. }
  50. if (pkg.type !== "module") {
  51. throw new Error("OpenClaw runtime should preserve the repo's ESM package mode");
  52. }
  53. const extensions = pkg.openclaw?.extensions;
  54. if (!Array.isArray(extensions) || extensions.length !== 1 || extensions[0] !== "./.openclaw/index.js") {
  55. throw new Error(`unexpected openclaw.extensions: ${JSON.stringify(extensions)}`);
  56. }
  57. NODE
  58. echo " [PASS] package.json points OpenClaw at the runtime entrypoint"
  59. echo "Test 3: Checking runtime entrypoint syntax and hook behavior..."
  60. node --check .openclaw/index.js
  61. node <<'NODE'
  62. const module = await import(new URL("./.openclaw/index.js", import.meta.url));
  63. const plugin = module.default;
  64. if (!plugin || plugin.id !== "superpowers-openclaw" || typeof plugin.register !== "function") {
  65. throw new Error("OpenClaw runtime must default-export a plugin with register(api)");
  66. }
  67. const hooks = [];
  68. const api = {
  69. pluginConfig: {},
  70. rootDir: process.cwd(),
  71. logger: { warn(message) { throw new Error(`unexpected warning: ${message}`); } },
  72. on(name, handler) {
  73. hooks.push({ name, handler });
  74. },
  75. };
  76. plugin.register(api);
  77. if (hooks.length !== 1 || hooks[0].name !== "before_prompt_build") {
  78. throw new Error(`unexpected hooks: ${JSON.stringify(hooks.map((hook) => hook.name))}`);
  79. }
  80. const result = await hooks[0].handler();
  81. if (!result?.prependSystemContext?.includes("Superpowers")) {
  82. throw new Error("before_prompt_build hook must inject Superpowers guidance");
  83. }
  84. if (result.prependSystemContext.includes("~/.openclaw/skills")) {
  85. throw new Error("native plugin guidance must not advertise managed skill symlinks");
  86. }
  87. NODE
  88. echo " [PASS] Runtime entrypoint registers the prompt hook"
  89. echo "Test 4: Checking README install flow..."
  90. if ! grep -q 'openclaw plugins install --link ~/.openclaw/vendor/superpowers' README.md; then
  91. echo " [FAIL] README must document linked OpenClaw plugin install"
  92. exit 1
  93. fi
  94. if ! grep -q -- '--dangerously-force-unsafe-install' README.md; then
  95. echo " [FAIL] README must document OpenClaw scanner override required by existing helper scripts"
  96. exit 1
  97. fi
  98. if ! grep -q 'skills/writing-skills/render-graphs.js' README.md; then
  99. echo " [FAIL] README must explain the exact Superpowers helper that triggers OpenClaw's scanner"
  100. exit 1
  101. fi
  102. if ! grep -q 'not part of the OpenClaw runtime hook' README.md; then
  103. echo " [FAIL] README must explain that the flagged helper is not part of the OpenClaw runtime hook"
  104. exit 1
  105. fi
  106. if ! grep -q 'openclaw plugins enable superpowers-openclaw' README.md; then
  107. echo " [FAIL] README must document enabling the OpenClaw plugin"
  108. exit 1
  109. fi
  110. if ! grep -q 'openclaw gateway restart' README.md; then
  111. echo " [FAIL] README must document restarting the gateway"
  112. exit 1
  113. fi
  114. if grep -q 'OPENCLAW_SKILLS_DIR\|ln -s\|AGENTS-snippet\|.openclaw/INSTALL.md' README.md; then
  115. echo " [FAIL] README must not use the legacy symlink/snippet wrapper or extra install doc"
  116. exit 1
  117. fi
  118. echo " [PASS] README documents native plugin commands"
  119. echo ""
  120. echo "=== OpenClaw native plugin tests passed ==="