@bitsocial/bitsocial-cli 0.19.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +706 -0
  3. package/bin/dev +20 -0
  4. package/bin/dev.cmd +3 -0
  5. package/bin/postinstall.js +125 -0
  6. package/bin/run +13 -0
  7. package/bin/run.cmd +3 -0
  8. package/dist/challenge-packages/challenge-utils.d.ts +24 -0
  9. package/dist/challenge-packages/challenge-utils.js +304 -0
  10. package/dist/cli/base-command.d.ts +11 -0
  11. package/dist/cli/base-command.js +45 -0
  12. package/dist/cli/commands/challenge/install.d.ts +12 -0
  13. package/dist/cli/commands/challenge/install.js +131 -0
  14. package/dist/cli/commands/challenge/list.d.ts +10 -0
  15. package/dist/cli/commands/challenge/list.js +37 -0
  16. package/dist/cli/commands/challenge/remove.d.ts +12 -0
  17. package/dist/cli/commands/challenge/remove.js +60 -0
  18. package/dist/cli/commands/community/create.d.ts +12 -0
  19. package/dist/cli/commands/community/create.js +54 -0
  20. package/dist/cli/commands/community/delete.d.ts +10 -0
  21. package/dist/cli/commands/community/delete.js +44 -0
  22. package/dist/cli/commands/community/edit.d.ts +12 -0
  23. package/dist/cli/commands/community/edit.js +74 -0
  24. package/dist/cli/commands/community/get.d.ts +9 -0
  25. package/dist/cli/commands/community/get.js +32 -0
  26. package/dist/cli/commands/community/list.d.ts +9 -0
  27. package/dist/cli/commands/community/list.js +30 -0
  28. package/dist/cli/commands/community/start.d.ts +13 -0
  29. package/dist/cli/commands/community/start.js +46 -0
  30. package/dist/cli/commands/community/stop.d.ts +10 -0
  31. package/dist/cli/commands/community/stop.js +44 -0
  32. package/dist/cli/commands/daemon.d.ts +14 -0
  33. package/dist/cli/commands/daemon.js +484 -0
  34. package/dist/cli/commands/logs.d.ts +24 -0
  35. package/dist/cli/commands/logs.js +199 -0
  36. package/dist/cli/commands/subplebbit/create.d.ts +12 -0
  37. package/dist/cli/commands/subplebbit/create.js +54 -0
  38. package/dist/cli/commands/subplebbit/edit.d.ts +12 -0
  39. package/dist/cli/commands/subplebbit/edit.js +73 -0
  40. package/dist/cli/commands/subplebbit/get.d.ts +9 -0
  41. package/dist/cli/commands/subplebbit/get.js +32 -0
  42. package/dist/cli/commands/subplebbit/list.d.ts +9 -0
  43. package/dist/cli/commands/subplebbit/list.js +30 -0
  44. package/dist/cli/commands/subplebbit/start.d.ts +10 -0
  45. package/dist/cli/commands/subplebbit/start.js +41 -0
  46. package/dist/cli/commands/subplebbit/stop.d.ts +10 -0
  47. package/dist/cli/commands/subplebbit/stop.js +43 -0
  48. package/dist/cli/commands/update/check.d.ts +6 -0
  49. package/dist/cli/commands/update/check.js +28 -0
  50. package/dist/cli/commands/update/install.d.ts +12 -0
  51. package/dist/cli/commands/update/install.js +63 -0
  52. package/dist/cli/commands/update/versions.d.ts +9 -0
  53. package/dist/cli/commands/update/versions.js +29 -0
  54. package/dist/cli/hooks/init/version-hook.d.ts +3 -0
  55. package/dist/cli/hooks/init/version-hook.js +43 -0
  56. package/dist/cli/hooks/prerun/parse-dynamic-flags-hook.d.ts +3 -0
  57. package/dist/cli/hooks/prerun/parse-dynamic-flags-hook.js +94 -0
  58. package/dist/cli/types.d.ts +4 -0
  59. package/dist/cli/types.js +1 -0
  60. package/dist/common-utils/data-migration.d.ts +1 -0
  61. package/dist/common-utils/data-migration.js +27 -0
  62. package/dist/common-utils/defaults.d.ts +9 -0
  63. package/dist/common-utils/defaults.js +10 -0
  64. package/dist/common-utils/resolvers.d.ts +2 -0
  65. package/dist/common-utils/resolvers.js +6 -0
  66. package/dist/index.d.ts +1 -0
  67. package/dist/index.js +1 -0
  68. package/dist/ipfs/startIpfs.d.ts +3 -0
  69. package/dist/ipfs/startIpfs.js +304 -0
  70. package/dist/seeder.d.ts +1 -0
  71. package/dist/seeder.js +83 -0
  72. package/dist/update/npm-registry.d.ts +6 -0
  73. package/dist/update/npm-registry.js +66 -0
  74. package/dist/update/semver.d.ts +5 -0
  75. package/dist/update/semver.js +29 -0
  76. package/dist/util.d.ts +31 -0
  77. package/dist/util.js +157 -0
  78. package/dist/webui/daemon-server.d.ts +10 -0
  79. package/dist/webui/daemon-server.js +140 -0
  80. package/package.json +143 -0
package/bin/dev ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ import oclif from "@oclif/core";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { register } from "ts-node";
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const project = path.join(__dirname, "..", "tsconfig.json");
10
+
11
+ // In dev mode -> use ts-node and dev plugins
12
+ process.env.NODE_ENV = "development";
13
+
14
+ register({ project });
15
+
16
+ // In dev mode, always show stack traces
17
+ oclif.settings.debug = true;
18
+
19
+ // Start the CLI
20
+ oclif.run(undefined, import.meta.url).then(oclif.flush).catch(oclif.Errors.handle);
package/bin/dev.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\dev" %*
@@ -0,0 +1,125 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createWriteStream } from "node:fs";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { Readable } from "node:stream";
6
+ import { finished as streamFinished } from "node:stream/promises";
7
+ import { fileURLToPath } from "node:url";
8
+ import decompress from "decompress";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const packageRoot = path.join(__dirname, "..");
13
+
14
+ async function main() {
15
+ const distDir = path.join(packageRoot, "dist");
16
+ try {
17
+ await fs.access(distDir);
18
+ } catch {
19
+ // dist/ doesn't exist — dev install before build, skip silently
20
+ return;
21
+ }
22
+
23
+ const dstOfWebui = path.join(distDir, "webuis");
24
+ try {
25
+ await fs.mkdir(dstOfWebui);
26
+ } catch (e) {
27
+ if (e.code === "EEXIST") {
28
+ console.log("Web UIs directory already exists, skipping download");
29
+ return;
30
+ }
31
+ throw e;
32
+ }
33
+
34
+ const pkg = JSON.parse(await fs.readFile(path.join(packageRoot, "package.json"), "utf-8"));
35
+ const webuis = pkg.webuis;
36
+ if (!webuis || webuis.length === 0) {
37
+ console.warn("Warning: No webuis configured in package.json");
38
+ return;
39
+ }
40
+
41
+ const githubToken = process.env["GITHUB_TOKEN"];
42
+ if (githubToken) console.log("Using GITHUB_TOKEN for API requests");
43
+
44
+ for (const entry of webuis) {
45
+ const { url, sha256OfHtmlZip } = entry;
46
+ try {
47
+ // Parse "https://github.com/{owner}/{repo}/releases/tag/{tag}"
48
+ const match = url.match(/github\.com\/([^/]+\/[^/]+)\/releases\/tag\/(.+)$/);
49
+ if (!match) {
50
+ console.warn(`Warning: Could not parse GitHub release URL: ${url}. Skipping.`);
51
+ continue;
52
+ }
53
+ const [, ownerRepo, tag] = match;
54
+
55
+ const headers = githubToken ? { authorization: `Bearer ${githubToken}` } : undefined;
56
+ const releaseReq = await fetch(`https://api.github.com/repos/${ownerRepo}/releases/tags/${tag}`, { headers });
57
+ if (!releaseReq.ok) {
58
+ console.warn(`Warning: Failed to fetch release for ${ownerRepo}@${tag}, status ${releaseReq.status}. Skipping.`);
59
+ continue;
60
+ }
61
+
62
+ const release = await releaseReq.json();
63
+ const htmlZipAsset = release.assets.find((asset) => asset.name.includes("html"));
64
+ if (!htmlZipAsset) {
65
+ console.warn(`Warning: No HTML zip asset found in ${ownerRepo}@${tag}. Skipping.`);
66
+ continue;
67
+ }
68
+
69
+ const zipfilePath = path.join(dstOfWebui, htmlZipAsset.name);
70
+ const downloadReq = await fetch(htmlZipAsset["browser_download_url"], { headers });
71
+ if (!downloadReq.ok || !downloadReq.body) {
72
+ console.warn(`Warning: Failed to download ${htmlZipAsset.name}, status ${downloadReq.status}. Skipping.`);
73
+ continue;
74
+ }
75
+
76
+ const writer = createWriteStream(zipfilePath);
77
+ await streamFinished(Readable.fromWeb(downloadReq.body).pipe(writer));
78
+ writer.close();
79
+ console.log(`Downloaded ${htmlZipAsset.name}`);
80
+
81
+ // Verify SHA-256 checksum
82
+ const fileBuffer = await fs.readFile(zipfilePath);
83
+ const actualHash = createHash("sha256").update(fileBuffer).digest("hex");
84
+ if (actualHash !== sha256OfHtmlZip) {
85
+ await fs.rm(zipfilePath);
86
+ console.error(
87
+ `ERROR: SHA-256 mismatch for ${htmlZipAsset.name}!\n` +
88
+ ` Expected: ${sha256OfHtmlZip}\n` +
89
+ ` Actual: ${actualHash}\n` +
90
+ `This could indicate a supply chain attack. Aborting.`
91
+ );
92
+ process.exit(1);
93
+ }
94
+ console.log(`Verified SHA-256 checksum for ${htmlZipAsset.name}`);
95
+
96
+ await decompress(zipfilePath, dstOfWebui);
97
+ console.log(`Extracted ${htmlZipAsset.name}`);
98
+ await fs.rm(zipfilePath);
99
+
100
+ // Rename index.html to prevent access to unconfigured version
101
+ const extractedDirName = htmlZipAsset.name.replace(".zip", "");
102
+ const indexPath = path.join(dstOfWebui, extractedDirName, "index.html");
103
+ const backupPath = path.join(dstOfWebui, extractedDirName, "index_backup_no_rpc.html");
104
+ await fs.rename(indexPath, backupPath);
105
+ console.log(`Downloaded ${ownerRepo}@${tag} successfully`);
106
+ } catch (e) {
107
+ console.warn(`Warning: Failed to process ${url}: ${e}. Skipping.`);
108
+ continue;
109
+ }
110
+ }
111
+
112
+ // Verify at least one web UI was downloaded
113
+ const downloadedEntries = await fs.readdir(dstOfWebui, { withFileTypes: true });
114
+ const webuiDirs = downloadedEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
115
+ if (webuiDirs.length === 0) {
116
+ console.error("ERROR: No web UIs were downloaded. At least one web UI is required.");
117
+ process.exit(1);
118
+ }
119
+ console.log(`Successfully downloaded ${webuiDirs.length} web UI(s):`, webuiDirs);
120
+ }
121
+
122
+ main().catch((err) => {
123
+ console.warn(`Warning: postinstall webui download failed: ${err}`);
124
+ // Don't fail the install for non-checksum errors
125
+ });
package/bin/run ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Save and strip DEBUG before any imports so the debug module
4
+ // doesn't auto-enable stderr output. The daemon command will
5
+ // re-enable namespaces programmatically and redirect to the log file.
6
+ if (process.env.DEBUG) {
7
+ process.env._PLEBBIT_DEBUG = process.env.DEBUG;
8
+ delete process.env.DEBUG;
9
+ }
10
+
11
+ // Dynamic import so the stripping above runs before oclif loads
12
+ const oclif = await import("@oclif/core");
13
+ oclif.default.run(undefined, import.meta.url).then(oclif.default.flush).catch(oclif.default.Errors.handle);
package/bin/run.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\run" %*
@@ -0,0 +1,24 @@
1
+ export interface InstalledChallenge {
2
+ name: string;
3
+ version: string;
4
+ description: string;
5
+ path: string;
6
+ }
7
+ export declare function getChallengesDir(dataPath?: string): string;
8
+ export declare function ensureChallengesDir(dataPath?: string): Promise<string>;
9
+ export declare function challengeNameToDir(challengesDir: string, name: string): string;
10
+ export declare function readChallengePackageJson(challengeDir: string): Promise<{
11
+ name: string;
12
+ version?: string;
13
+ description?: string;
14
+ main?: string;
15
+ exports?: any;
16
+ }>;
17
+ export declare function listInstalledChallenges(dataPath?: string): Promise<InstalledChallenge[]>;
18
+ export declare function getNpmCliPath(): Promise<string>;
19
+ export declare function getNpmEnv(): NodeJS.ProcessEnv;
20
+ export declare function ensureNpmAvailable(): Promise<void>;
21
+ export declare function runNpmPack(packageSpec: string, destDir: string): Promise<string>;
22
+ export declare function runNpmInstall(challengeDir: string): Promise<void>;
23
+ export declare function verifyNativeModuleAbi(challengeDir: string): Promise<void>;
24
+ export declare function loadChallengesIntoPKC(dataPath?: string): Promise<string[]>;
@@ -0,0 +1,304 @@
1
+ import path from "path";
2
+ import { pathToFileURL } from "node:url";
3
+ import fs from "fs/promises";
4
+ import { execFileSync, spawn } from "child_process";
5
+ import defaults from "../common-utils/defaults.js";
6
+ export function getChallengesDir(dataPath) {
7
+ return path.join(dataPath || defaults.PKC_DATA_PATH, "challenges");
8
+ }
9
+ export async function ensureChallengesDir(dataPath) {
10
+ const dir = getChallengesDir(dataPath);
11
+ await fs.mkdir(dir, { recursive: true });
12
+ return dir;
13
+ }
14
+ export function challengeNameToDir(challengesDir, name) {
15
+ // Handles both scoped (@org/pkg) and unscoped (pkg) names
16
+ return path.join(challengesDir, ...name.split("/"));
17
+ }
18
+ export async function readChallengePackageJson(challengeDir) {
19
+ const pkgPath = path.join(challengeDir, "package.json");
20
+ const content = await fs.readFile(pkgPath, "utf-8");
21
+ const pkg = JSON.parse(content);
22
+ if (!pkg.name || typeof pkg.name !== "string") {
23
+ throw new Error(`Invalid package.json in ${challengeDir}: missing "name" field`);
24
+ }
25
+ return pkg;
26
+ }
27
+ export async function listInstalledChallenges(dataPath) {
28
+ const challengesDir = getChallengesDir(dataPath);
29
+ const results = [];
30
+ let entries;
31
+ try {
32
+ entries = await fs.readdir(challengesDir, { withFileTypes: true });
33
+ }
34
+ catch {
35
+ return results; // dir doesn't exist = no challenges
36
+ }
37
+ for (const entry of entries) {
38
+ if (!entry.isDirectory())
39
+ continue;
40
+ if (entry.name.startsWith("@")) {
41
+ // Scoped package: read @scope/*/package.json
42
+ const scopeDir = path.join(challengesDir, entry.name);
43
+ let scopeEntries;
44
+ try {
45
+ scopeEntries = await fs.readdir(scopeDir, { withFileTypes: true });
46
+ }
47
+ catch {
48
+ continue;
49
+ }
50
+ for (const scopeEntry of scopeEntries) {
51
+ if (!scopeEntry.isDirectory())
52
+ continue;
53
+ const pkgDir = path.join(scopeDir, scopeEntry.name);
54
+ try {
55
+ const pkg = await readChallengePackageJson(pkgDir);
56
+ results.push({
57
+ name: pkg.name,
58
+ version: pkg.version || "unknown",
59
+ description: pkg.description || "",
60
+ path: pkgDir
61
+ });
62
+ }
63
+ catch {
64
+ // skip invalid entries
65
+ }
66
+ }
67
+ }
68
+ else {
69
+ // Unscoped package
70
+ const pkgDir = path.join(challengesDir, entry.name);
71
+ try {
72
+ const pkg = await readChallengePackageJson(pkgDir);
73
+ results.push({
74
+ name: pkg.name,
75
+ version: pkg.version || "unknown",
76
+ description: pkg.description || "",
77
+ path: pkgDir
78
+ });
79
+ }
80
+ catch {
81
+ // skip invalid entries
82
+ }
83
+ }
84
+ }
85
+ return results;
86
+ }
87
+ function getNpmCliPathRelative(nodeExecPath) {
88
+ // npm-cli.js lives at a standard location relative to a Node binary.
89
+ const nodeDir = path.dirname(nodeExecPath);
90
+ if (process.platform === "win32") {
91
+ // Windows: <node-dir>/node_modules/npm/bin/npm-cli.js
92
+ return path.join(nodeDir, "node_modules", "npm", "bin", "npm-cli.js");
93
+ }
94
+ // Unix (nvm, official installers, distro packages):
95
+ // <node-dir>/../lib/node_modules/npm/bin/npm-cli.js
96
+ return path.join(nodeDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
97
+ }
98
+ export async function getNpmCliPath() {
99
+ // 1. Try relative to our own Node binary (works for nvm / system Node)
100
+ const relativePath = getNpmCliPathRelative(process.execPath);
101
+ try {
102
+ await fs.access(relativePath);
103
+ return relativePath;
104
+ }
105
+ catch {
106
+ // Not found relative to process.execPath (e.g. oclif-bundled Node without npm)
107
+ }
108
+ // 2. Fall back to the system npm found on PATH
109
+ try {
110
+ const cmd = process.platform === "win32" ? "where.exe" : "which";
111
+ const npmBin = execFileSync(cmd, ["npm"], { encoding: "utf8" }).trim().split("\n")[0].trim();
112
+ // npmBin is a symlink or script; resolve to the real path, then derive npm-cli.js
113
+ // For most installs: npm -> <prefix>/lib/node_modules/npm/bin/npm-cli.js
114
+ const realNpmBin = await fs.realpath(npmBin);
115
+ // If realpath leads directly to npm-cli.js, use it
116
+ if (realNpmBin.endsWith("npm-cli.js")) {
117
+ return realNpmBin;
118
+ }
119
+ // Otherwise, the system npm binary lives beside a Node that has npm installed —
120
+ // derive npm-cli.js relative to that Node
121
+ const systemNodeDir = path.dirname(realNpmBin);
122
+ const systemNpmCli = process.platform === "win32"
123
+ ? path.join(systemNodeDir, "node_modules", "npm", "bin", "npm-cli.js")
124
+ : path.join(systemNodeDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
125
+ await fs.access(systemNpmCli);
126
+ return systemNpmCli;
127
+ }
128
+ catch {
129
+ // Could not locate npm on PATH either
130
+ }
131
+ // Return the original relative path so callers get the familiar error message
132
+ return relativePath;
133
+ }
134
+ export function getNpmEnv() {
135
+ // Prepend our Node's directory to PATH so that npm subprocesses
136
+ // (node-gyp, lifecycle scripts) also use the same Node binary
137
+ const nodeDir = path.dirname(process.execPath);
138
+ const pathSep = process.platform === "win32" ? ";" : ":";
139
+ return {
140
+ ...process.env,
141
+ PATH: nodeDir + pathSep + (process.env["PATH"] || "")
142
+ };
143
+ }
144
+ const npmErrorMessage = `npm is required to install challenge packages but was not found at the expected location ` +
145
+ `relative to Node ${process.version} (${process.execPath}).\n` +
146
+ `Install Node.js ${process.version} from https://nodejs.org/ (npm is included with Node.js) and retry.`;
147
+ export async function ensureNpmAvailable() {
148
+ const npmCliPath = await getNpmCliPath();
149
+ try {
150
+ await fs.access(npmCliPath);
151
+ }
152
+ catch {
153
+ throw new Error(npmErrorMessage);
154
+ }
155
+ return new Promise((resolve, reject) => {
156
+ // Run npm through our own Node binary so process.execPath is correct
157
+ const proc = spawn(process.execPath, [npmCliPath, "--version"], { stdio: "ignore", env: getNpmEnv() });
158
+ proc.on("error", () => {
159
+ reject(new Error(npmErrorMessage));
160
+ });
161
+ proc.on("close", (code) => {
162
+ if (code === 0)
163
+ resolve();
164
+ else
165
+ reject(new Error(npmErrorMessage));
166
+ });
167
+ });
168
+ }
169
+ export async function runNpmPack(packageSpec, destDir) {
170
+ const npmCliPath = await getNpmCliPath();
171
+ return new Promise((resolve, reject) => {
172
+ const proc = spawn(process.execPath, [npmCliPath, "pack", packageSpec, "--pack-destination", destDir], {
173
+ stdio: ["ignore", "pipe", "inherit"],
174
+ env: getNpmEnv()
175
+ });
176
+ let stdout = "";
177
+ proc.stdout.on("data", (data) => {
178
+ stdout += data.toString();
179
+ });
180
+ proc.on("error", (err) => {
181
+ reject(new Error(`Failed to run npm pack: ${err.message}`));
182
+ });
183
+ proc.on("close", (code) => {
184
+ if (code === 0) {
185
+ // npm pack prints the tarball filename on the last non-empty line of stdout
186
+ const filename = stdout.trim().split("\n").pop()?.trim();
187
+ if (!filename) {
188
+ reject(new Error("npm pack succeeded but produced no output"));
189
+ return;
190
+ }
191
+ resolve(path.join(destDir, filename));
192
+ }
193
+ else {
194
+ reject(new Error(`npm pack exited with code ${code}`));
195
+ }
196
+ });
197
+ });
198
+ }
199
+ export async function runNpmInstall(challengeDir) {
200
+ const npmCliPath = await getNpmCliPath();
201
+ // Strip devDependencies from the manifest before running npm install.
202
+ // npm's Arborist resolves ALL declared deps (including dev) during tree
203
+ // building even with --omit=dev — unresolvable devDep versions cause
204
+ // ETARGET failures before the omit filter applies. Removing them from
205
+ // the manifest prevents Arborist from creating those edges at all.
206
+ // The original package.json is restored after install (byte-identical).
207
+ const pkgJsonPath = path.join(challengeDir, "package.json");
208
+ const originalContent = await fs.readFile(pkgJsonPath, "utf-8");
209
+ const pkg = JSON.parse(originalContent);
210
+ const hadDevDeps = pkg.devDependencies !== undefined;
211
+ if (hadDevDeps) {
212
+ const stripped = { ...pkg };
213
+ delete stripped.devDependencies;
214
+ await fs.writeFile(pkgJsonPath, JSON.stringify(stripped, null, 2) + "\n");
215
+ }
216
+ try {
217
+ await new Promise((resolve, reject) => {
218
+ // Run npm through our own Node binary to guarantee ABI-compatible
219
+ // native modules — npm's process.execPath and lifecycle scripts
220
+ // will all use the same Node that's running bitsocial-cli.
221
+ // Use piped stdio and forward explicitly so output is visible even
222
+ // when the parent process has piped stdio (e.g. spawned by tests).
223
+ const args = [npmCliPath, "install", "--omit=dev", "--no-audit", "--no-fund"];
224
+ if (process.platform === "win32") {
225
+ args.push("--legacy-peer-deps");
226
+ }
227
+ const proc = spawn(process.execPath, args, {
228
+ cwd: challengeDir,
229
+ stdio: ["ignore", "pipe", "pipe"],
230
+ env: getNpmEnv()
231
+ });
232
+ proc.stdout?.pipe(process.stdout);
233
+ proc.stderr?.pipe(process.stderr);
234
+ proc.on("error", (err) => {
235
+ reject(new Error(`Failed to run npm install: ${err.message}`));
236
+ });
237
+ proc.on("close", (code) => {
238
+ if (code === 0)
239
+ resolve();
240
+ else
241
+ reject(new Error(`npm install exited with code ${code}`));
242
+ });
243
+ });
244
+ }
245
+ finally {
246
+ if (hadDevDeps) {
247
+ await fs.writeFile(pkgJsonPath, originalContent);
248
+ }
249
+ }
250
+ }
251
+ export async function verifyNativeModuleAbi(challengeDir) {
252
+ // Scan for .node files (native addons) and try to dlopen each one.
253
+ // Node checks NODE_MODULE_VERSION before calling any init code,
254
+ // so this safely catches ABI mismatches without side effects.
255
+ const entries = await fs.readdir(challengeDir, { recursive: true });
256
+ const nodeFiles = entries.filter((entry) => typeof entry === "string" && entry.endsWith(".node"));
257
+ if (nodeFiles.length === 0)
258
+ return;
259
+ const mismatched = [];
260
+ for (const file of nodeFiles) {
261
+ const filePath = path.join(challengeDir, file);
262
+ const mod = { exports: {} };
263
+ try {
264
+ process.dlopen(mod, filePath);
265
+ }
266
+ catch (err) {
267
+ const msg = err instanceof Error ? err.message : String(err);
268
+ if (msg.includes("NODE_MODULE_VERSION") || msg.includes("was compiled against a different")) {
269
+ mismatched.push(file);
270
+ }
271
+ // Other errors (missing dependencies, etc.) are fine at this stage —
272
+ // the module might still work once the full package is loaded
273
+ }
274
+ }
275
+ if (mismatched.length > 0) {
276
+ throw new Error(`ABI mismatch: the following native modules were compiled for a different Node.js version:\n` +
277
+ mismatched.map((f) => ` - ${f}`).join("\n") +
278
+ `\nThe running Node.js is ${process.version} (modules ABI ${process.versions.modules}).\n` +
279
+ `Ensure the challenge package was built for this Node.js version.`);
280
+ }
281
+ }
282
+ export async function loadChallengesIntoPKC(dataPath) {
283
+ const challenges = await listInstalledChallenges(dataPath);
284
+ if (challenges.length === 0)
285
+ return [];
286
+ const PKC = await import("@pkcprotocol/pkc-js");
287
+ const loadedNames = [];
288
+ for (const challenge of challenges) {
289
+ try {
290
+ const pkg = await readChallengePackageJson(challenge.path);
291
+ // Resolve the entry point
292
+ const entryPoint = pkg.main || "index.js";
293
+ const entryPath = path.resolve(challenge.path, entryPoint);
294
+ const imported = await import(pathToFileURL(entryPath).href);
295
+ const factory = imported.default || imported;
296
+ PKC.default.challenges[challenge.name] = factory;
297
+ loadedNames.push(challenge.name);
298
+ }
299
+ catch (err) {
300
+ console.error(`Failed to load challenge "${challenge.name}":`, err);
301
+ }
302
+ }
303
+ return loadedNames;
304
+ }
@@ -0,0 +1,11 @@
1
+ import { Command } from "@oclif/core";
2
+ import PKC from "@pkcprotocol/pkc-js";
3
+ type PKCInstance = Awaited<ReturnType<typeof PKC>>;
4
+ export declare abstract class BaseCommand extends Command {
5
+ static baseFlags: {
6
+ pkcRpcUrl: import("@oclif/core/interfaces").OptionFlag<import("url").URL, import("@oclif/core/interfaces").CustomOptions>;
7
+ };
8
+ init(): Promise<void>;
9
+ protected _connectToPkcRpc(pkcRpcUrl: string): Promise<PKCInstance>;
10
+ }
11
+ export {};
@@ -0,0 +1,45 @@
1
+ import { Command, Flags } from "@oclif/core";
2
+ import defaults from "../common-utils/defaults.js";
3
+ import PKC from "@pkcprotocol/pkc-js";
4
+ import { getPKCLogger, setupDebugLogger } from "../util.js";
5
+ const getPKCConnectOverride = () => {
6
+ const globalWithOverride = globalThis;
7
+ return globalWithOverride.__PKC_RPC_CONNECT_OVERRIDE;
8
+ };
9
+ export class BaseCommand extends Command {
10
+ static baseFlags = {
11
+ pkcRpcUrl: Flags.url({
12
+ summary: "URL to PKC RPC",
13
+ required: true,
14
+ default: defaults.PKC_RPC_URL
15
+ })
16
+ };
17
+ async init() {
18
+ await super.init();
19
+ const Logger = await getPKCLogger();
20
+ setupDebugLogger(Logger, { enableDefaultNamespace: false });
21
+ }
22
+ async _connectToPkcRpc(pkcRpcUrl) {
23
+ const connectOverride = getPKCConnectOverride();
24
+ if (connectOverride) {
25
+ return connectOverride(pkcRpcUrl);
26
+ }
27
+ const pkc = await PKC({ pkcRpcClientsOptions: [pkcRpcUrl] });
28
+ const errors = [];
29
+ pkc.on("error", (err) => {
30
+ errors.push(err);
31
+ console.error("Error from pkc instance", err);
32
+ });
33
+ await new Promise((resolve, reject) => {
34
+ const timeout = setTimeout(() => {
35
+ const lastError = errors[errors.length - 1];
36
+ reject(lastError ?? new Error(`Timed out waiting for RPC server at ${pkcRpcUrl} to respond`));
37
+ }, 20000);
38
+ pkc.once("communitieschange", () => {
39
+ clearTimeout(timeout);
40
+ resolve();
41
+ });
42
+ });
43
+ return pkc;
44
+ }
45
+ }
@@ -0,0 +1,12 @@
1
+ import { Command } from "@oclif/core";
2
+ export default class Install extends Command {
3
+ static description: string;
4
+ static args: {
5
+ package: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static flags: {
8
+ "pkcOptions.dataPath": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ static examples: string[];
11
+ run(): Promise<void>;
12
+ }