@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.
- package/LICENSE +674 -0
- package/README.md +706 -0
- package/bin/dev +20 -0
- package/bin/dev.cmd +3 -0
- package/bin/postinstall.js +125 -0
- package/bin/run +13 -0
- package/bin/run.cmd +3 -0
- package/dist/challenge-packages/challenge-utils.d.ts +24 -0
- package/dist/challenge-packages/challenge-utils.js +304 -0
- package/dist/cli/base-command.d.ts +11 -0
- package/dist/cli/base-command.js +45 -0
- package/dist/cli/commands/challenge/install.d.ts +12 -0
- package/dist/cli/commands/challenge/install.js +131 -0
- package/dist/cli/commands/challenge/list.d.ts +10 -0
- package/dist/cli/commands/challenge/list.js +37 -0
- package/dist/cli/commands/challenge/remove.d.ts +12 -0
- package/dist/cli/commands/challenge/remove.js +60 -0
- package/dist/cli/commands/community/create.d.ts +12 -0
- package/dist/cli/commands/community/create.js +54 -0
- package/dist/cli/commands/community/delete.d.ts +10 -0
- package/dist/cli/commands/community/delete.js +44 -0
- package/dist/cli/commands/community/edit.d.ts +12 -0
- package/dist/cli/commands/community/edit.js +74 -0
- package/dist/cli/commands/community/get.d.ts +9 -0
- package/dist/cli/commands/community/get.js +32 -0
- package/dist/cli/commands/community/list.d.ts +9 -0
- package/dist/cli/commands/community/list.js +30 -0
- package/dist/cli/commands/community/start.d.ts +13 -0
- package/dist/cli/commands/community/start.js +46 -0
- package/dist/cli/commands/community/stop.d.ts +10 -0
- package/dist/cli/commands/community/stop.js +44 -0
- package/dist/cli/commands/daemon.d.ts +14 -0
- package/dist/cli/commands/daemon.js +484 -0
- package/dist/cli/commands/logs.d.ts +24 -0
- package/dist/cli/commands/logs.js +199 -0
- package/dist/cli/commands/subplebbit/create.d.ts +12 -0
- package/dist/cli/commands/subplebbit/create.js +54 -0
- package/dist/cli/commands/subplebbit/edit.d.ts +12 -0
- package/dist/cli/commands/subplebbit/edit.js +73 -0
- package/dist/cli/commands/subplebbit/get.d.ts +9 -0
- package/dist/cli/commands/subplebbit/get.js +32 -0
- package/dist/cli/commands/subplebbit/list.d.ts +9 -0
- package/dist/cli/commands/subplebbit/list.js +30 -0
- package/dist/cli/commands/subplebbit/start.d.ts +10 -0
- package/dist/cli/commands/subplebbit/start.js +41 -0
- package/dist/cli/commands/subplebbit/stop.d.ts +10 -0
- package/dist/cli/commands/subplebbit/stop.js +43 -0
- package/dist/cli/commands/update/check.d.ts +6 -0
- package/dist/cli/commands/update/check.js +28 -0
- package/dist/cli/commands/update/install.d.ts +12 -0
- package/dist/cli/commands/update/install.js +63 -0
- package/dist/cli/commands/update/versions.d.ts +9 -0
- package/dist/cli/commands/update/versions.js +29 -0
- package/dist/cli/hooks/init/version-hook.d.ts +3 -0
- package/dist/cli/hooks/init/version-hook.js +43 -0
- package/dist/cli/hooks/prerun/parse-dynamic-flags-hook.d.ts +3 -0
- package/dist/cli/hooks/prerun/parse-dynamic-flags-hook.js +94 -0
- package/dist/cli/types.d.ts +4 -0
- package/dist/cli/types.js +1 -0
- package/dist/common-utils/data-migration.d.ts +1 -0
- package/dist/common-utils/data-migration.js +27 -0
- package/dist/common-utils/defaults.d.ts +9 -0
- package/dist/common-utils/defaults.js +10 -0
- package/dist/common-utils/resolvers.d.ts +2 -0
- package/dist/common-utils/resolvers.js +6 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ipfs/startIpfs.d.ts +3 -0
- package/dist/ipfs/startIpfs.js +304 -0
- package/dist/seeder.d.ts +1 -0
- package/dist/seeder.js +83 -0
- package/dist/update/npm-registry.d.ts +6 -0
- package/dist/update/npm-registry.js +66 -0
- package/dist/update/semver.d.ts +5 -0
- package/dist/update/semver.js +29 -0
- package/dist/util.d.ts +31 -0
- package/dist/util.js +157 -0
- package/dist/webui/daemon-server.d.ts +10 -0
- package/dist/webui/daemon-server.js +140 -0
- package/package.json +143 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
export default class Versions extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
};
|
|
7
|
+
static examples: string[];
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Command, Flags } from "@oclif/core";
|
|
2
|
+
import { fetchAllVersions } from "../../../update/npm-registry.js";
|
|
3
|
+
export default class Versions extends Command {
|
|
4
|
+
static description = "List available bitsocial versions on npm";
|
|
5
|
+
static flags = {
|
|
6
|
+
limit: Flags.integer({
|
|
7
|
+
description: "Maximum number of versions to display",
|
|
8
|
+
default: 20
|
|
9
|
+
})
|
|
10
|
+
};
|
|
11
|
+
static examples = ["bitsocial update versions", "bitsocial update versions --limit 5"];
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(Versions);
|
|
14
|
+
let versions;
|
|
15
|
+
try {
|
|
16
|
+
versions = await fetchAllVersions();
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
this.error(`Failed to fetch versions: ${err.message}`, { exit: 1 });
|
|
20
|
+
}
|
|
21
|
+
const current = this.config.version;
|
|
22
|
+
// Show newest first, limited to --limit
|
|
23
|
+
const display = versions.slice(-flags.limit).reverse();
|
|
24
|
+
for (const v of display) {
|
|
25
|
+
const marker = v === current ? "* " : " ";
|
|
26
|
+
this.log(`${marker}${v}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
// Get pkc-js version from its package.json
|
|
5
|
+
const getPkcJsVersion = () => {
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
// Get path to pkc-js module
|
|
8
|
+
const pkcJsPath = require.resolve("@pkcprotocol/pkc-js");
|
|
9
|
+
// Navigate to package root (pkc-js main export is dist/node/index.js)
|
|
10
|
+
const pkcJsRoot = dirname(dirname(dirname(pkcJsPath)));
|
|
11
|
+
const pkcPkgPath = join(pkcJsRoot, "package.json");
|
|
12
|
+
const pkcPkg = JSON.parse(readFileSync(pkcPkgPath, "utf-8"));
|
|
13
|
+
return pkcPkg.version;
|
|
14
|
+
};
|
|
15
|
+
// Get commit hash from CLI's package.json dependency URL (if installed from git)
|
|
16
|
+
const getPkcJsCommit = (cliRoot) => {
|
|
17
|
+
try {
|
|
18
|
+
const cliPkgPath = join(cliRoot, "package.json");
|
|
19
|
+
const cliPkg = JSON.parse(readFileSync(cliPkgPath, "utf-8"));
|
|
20
|
+
const pkcJsDep = cliPkg.dependencies["@pkcprotocol/pkc-js"];
|
|
21
|
+
// Extract commit hash from URL like "https://github.com/pkcprotocol/pkc-js#542952a1..."
|
|
22
|
+
const match = pkcJsDep?.match(/#([a-f0-9]+)$/);
|
|
23
|
+
return match ? match[1].substring(0, 7) : undefined;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const hook = async function (opts) {
|
|
30
|
+
// Check process.argv because oclif normalizes argv and --version becomes the id, not part of argv
|
|
31
|
+
if (process.argv.includes("--version")) {
|
|
32
|
+
const { config } = opts;
|
|
33
|
+
const pkcJsVersion = getPkcJsVersion();
|
|
34
|
+
const commit = getPkcJsCommit(config.root);
|
|
35
|
+
const commitStr = commit ? ` (${commit})` : "";
|
|
36
|
+
// Output CLI version on first line, pkc-js version + commit on second line
|
|
37
|
+
this.log(`${config.name}/${config.version} ${config.platform}-${config.arch} node-${process.version}`);
|
|
38
|
+
this.log(`pkc-js/${pkcJsVersion}${commitStr}`);
|
|
39
|
+
// Use process.exit to actually stop - this.exit(0) throws an error that gets caught by oclif
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export default hook;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Flags } from "@oclif/core";
|
|
2
|
+
import * as remeda from "remeda";
|
|
3
|
+
const parseCommandInputToProperType = (input) => {
|
|
4
|
+
if (input === "true")
|
|
5
|
+
return true;
|
|
6
|
+
else if (input === "false")
|
|
7
|
+
return false;
|
|
8
|
+
else if (input === "null")
|
|
9
|
+
return null;
|
|
10
|
+
else if (input.trim() !== "" && !Number.isNaN(Number(input)))
|
|
11
|
+
return Number(input);
|
|
12
|
+
else
|
|
13
|
+
return input;
|
|
14
|
+
};
|
|
15
|
+
const parseFlags = (argv) => {
|
|
16
|
+
const args = argv;
|
|
17
|
+
return args.reduce((accumulator, currentArg, index) => {
|
|
18
|
+
// Skip if not a flag
|
|
19
|
+
if (!currentArg.startsWith("--")) {
|
|
20
|
+
return accumulator;
|
|
21
|
+
}
|
|
22
|
+
const flag = currentArg.slice(2); // Remove '--'
|
|
23
|
+
// Handle --flag=value syntax
|
|
24
|
+
if (flag.includes("=")) {
|
|
25
|
+
const [flagPath, value] = flag.split("=");
|
|
26
|
+
return setNestedValue(accumulator, flagPath, value);
|
|
27
|
+
}
|
|
28
|
+
const nextArg = args[index + 1];
|
|
29
|
+
// Handle boolean flags (no value or next arg is a flag)
|
|
30
|
+
if (!nextArg || nextArg.startsWith("--")) {
|
|
31
|
+
return setNestedValue(accumulator, flag, true);
|
|
32
|
+
}
|
|
33
|
+
// Handle regular --flag value syntax
|
|
34
|
+
return setNestedValue(accumulator, flag, nextArg);
|
|
35
|
+
}, {});
|
|
36
|
+
};
|
|
37
|
+
// Helper function to set nested values
|
|
38
|
+
const setNestedValue = (obj, path, value) => {
|
|
39
|
+
const keys = path.split(".");
|
|
40
|
+
const lastKey = keys.pop();
|
|
41
|
+
// Traverse/create nested objects
|
|
42
|
+
let current = obj;
|
|
43
|
+
keys.forEach((key) => {
|
|
44
|
+
current[key] = current[key] || {};
|
|
45
|
+
current = current[key];
|
|
46
|
+
});
|
|
47
|
+
// Set or append value at final position
|
|
48
|
+
if (typeof value === "boolean") {
|
|
49
|
+
current[lastKey] = value;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
current[lastKey] = [...(current[lastKey] || []), value];
|
|
53
|
+
}
|
|
54
|
+
return obj;
|
|
55
|
+
};
|
|
56
|
+
const traverseObjectToSetAsFlagInOclif = (opts, flagsGrouped, path = "") => {
|
|
57
|
+
for (const flagName of Object.keys(flagsGrouped)) {
|
|
58
|
+
if (remeda.isPlainObject(flagsGrouped[flagName])) {
|
|
59
|
+
traverseObjectToSetAsFlagInOclif(opts, flagsGrouped[flagName], path + flagName + ".");
|
|
60
|
+
if (Object.keys(flagsGrouped[flagName]).length !== 0)
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const flagsIndices = Object.assign({}, ...Object.keys(flagsGrouped).map((flag) => ({ [flag]: 0 })));
|
|
64
|
+
const multipleValues = Array.isArray(flagsGrouped[flagName]) && flagsGrouped[flagName].length > 1;
|
|
65
|
+
const parsedValue = flagsGrouped[flagName];
|
|
66
|
+
if (typeof parsedValue === "boolean")
|
|
67
|
+
opts.Command.flags[path + flagName] = Flags.boolean({ default: true });
|
|
68
|
+
else
|
|
69
|
+
opts.Command.flags[path + flagName] = Flags.string({
|
|
70
|
+
//@ts-expect-error
|
|
71
|
+
multiple: multipleValues,
|
|
72
|
+
parse: async (input) => {
|
|
73
|
+
const parsed = parseCommandInputToProperType(parsedValue[flagsIndices[flagName]++]);
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const hook = async function (opts) {
|
|
80
|
+
// Need to parse flag here and add it to opts.Command._flags
|
|
81
|
+
if (opts.Command.id === "community:edit" || opts.Command.id === "community:create" || opts.Command.id === "daemon") {
|
|
82
|
+
// Parse the dynamic flags and add them to opts.Command.flags so that it wouldn't throw
|
|
83
|
+
if (opts.argv.length <= 1)
|
|
84
|
+
return; // if no flags are provided, then we don't need to do anything
|
|
85
|
+
for (let i = 0; i < opts.argv.length; i++)
|
|
86
|
+
if (typeof opts.argv[i] !== "string")
|
|
87
|
+
opts.argv[i] = String(opts.argv[i]);
|
|
88
|
+
const flagsGrouped = parseFlags(opts.Command.id === "daemon" ? ["", ...opts.argv] : opts.argv);
|
|
89
|
+
if (Object.keys(flagsGrouped).length > 0 && !opts.Command.flags)
|
|
90
|
+
opts.Command.flags = {};
|
|
91
|
+
traverseObjectToSetAsFlagInOclif(opts, flagsGrouped);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
export default hook;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CreateNewLocalCommunityUserOptions as PKCCreateCommunityOptions } from "@pkcprotocol/pkc-js/dist/node/community/types.js";
|
|
2
|
+
export interface CliCreateCommunityOptions extends Pick<PKCCreateCommunityOptions, "title" | "description" | "suggested" | "settings" | "features" | "roles" | "rules" | "flairs"> {
|
|
3
|
+
privateKeyPath?: string;
|
|
4
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function migrateDataDirectory(dataPath: string): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import envPaths from "env-paths";
|
|
4
|
+
export function migrateDataDirectory(dataPath) {
|
|
5
|
+
const defaultNewPath = envPaths("bitsocial", { suffix: "" }).data;
|
|
6
|
+
const defaultOldPath = envPaths("plebbit", { suffix: "" }).data;
|
|
7
|
+
// Step 1: Migrate old default dir (envPaths("plebbit")) → new default (envPaths("bitsocial"))
|
|
8
|
+
if (dataPath === defaultNewPath && fs.existsSync(defaultOldPath)) {
|
|
9
|
+
if (!fs.existsSync(defaultNewPath)) {
|
|
10
|
+
fs.renameSync(defaultOldPath, defaultNewPath);
|
|
11
|
+
console.log(`Migrated data directory: ${defaultOldPath} → ${defaultNewPath}`);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
console.warn(`Both ${defaultOldPath} and ${defaultNewPath} exist. Using ${defaultNewPath}.`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Step 2: Migrate subplebbits/ → communities/ inside data directory
|
|
18
|
+
const oldSubDir = path.join(dataPath, "subplebbits");
|
|
19
|
+
const newSubDir = path.join(dataPath, "communities");
|
|
20
|
+
if (fs.existsSync(oldSubDir) && !fs.existsSync(newSubDir)) {
|
|
21
|
+
fs.renameSync(oldSubDir, newSubDir);
|
|
22
|
+
console.log(`Migrated ${oldSubDir} → ${newSubDir}`);
|
|
23
|
+
}
|
|
24
|
+
else if (fs.existsSync(oldSubDir) && fs.existsSync(newSubDir)) {
|
|
25
|
+
console.warn(`Both ${oldSubDir} and ${newSubDir} exist. Using ${newSubDir}.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Not sure 'defaults' is the best name here
|
|
2
|
+
import envPaths from "env-paths";
|
|
3
|
+
export default {
|
|
4
|
+
PKC_DATA_PATH: envPaths("bitsocial", { suffix: "" }).data,
|
|
5
|
+
PKC_RPC_URL: new URL("ws://localhost:9138"),
|
|
6
|
+
KUBO_RPC_URL: new URL(process.env["KUBO_RPC_URL"] || "http://127.0.0.1:50019/api/v0"),
|
|
7
|
+
IPFS_GATEWAY_URL: new URL(process.env["IPFS_GATEWAY_URL"] || "http://127.0.0.1:6473"),
|
|
8
|
+
HTTP_TRACKERS: ["https://peers.pleb.bot", "https://routing.lol", "https://peers.forumindex.com", "https://peers.plebpubsub.xyz"],
|
|
9
|
+
PKC_LOG_PATH: envPaths("bitsocial", { suffix: "" }).log
|
|
10
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { BsoResolver } from "@bitsocial/bso-resolver";
|
|
2
|
+
const DEFAULT_PROVIDERS = ["viem", "https://ethrpc.xyz"];
|
|
3
|
+
export function createBsoResolvers(providers, dataPath) {
|
|
4
|
+
const resolverProviders = providers && providers.length > 0 ? providers : DEFAULT_PROVIDERS;
|
|
5
|
+
return resolverProviders.map((provider) => new BsoResolver({ key: `bso-${provider}`, provider, dataPath }));
|
|
6
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from "@oclif/core";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from "@oclif/core";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ChildProcessWithoutNullStreams } from "child_process";
|
|
2
|
+
export declare function mergeCliDefaultsIntoIpfsConfig(log: any, ipfsConfigPath: string, apiUrl: URL, gatewayUrl: URL): Promise<void>;
|
|
3
|
+
export declare function startKuboNode(apiUrl: URL, gatewayUrl: URL, dataPath: string, onSpawn?: (process: ChildProcessWithoutNullStreams) => void): Promise<ChildProcessWithoutNullStreams>;
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import * as fsPromises from "fs/promises";
|
|
5
|
+
import assert from "assert";
|
|
6
|
+
import tcpPortUsed from "tcp-port-used";
|
|
7
|
+
import { path as ipfsExePathFunc } from "kubo";
|
|
8
|
+
import { getPKCLogger } from "../util.js";
|
|
9
|
+
async function getKuboExePath() {
|
|
10
|
+
return ipfsExePathFunc();
|
|
11
|
+
}
|
|
12
|
+
async function getKuboVersion() {
|
|
13
|
+
const kuboPackageJson = await import("kubo/package.json", { with: { type: "json" } });
|
|
14
|
+
return kuboPackageJson.default.version;
|
|
15
|
+
}
|
|
16
|
+
export async function mergeCliDefaultsIntoIpfsConfig(log, ipfsConfigPath, apiUrl, gatewayUrl) {
|
|
17
|
+
const currentIpfsConfigFile = JSON.parse((await fsPromises.readFile(ipfsConfigPath)).toString());
|
|
18
|
+
const existingGatewayConfig = currentIpfsConfigFile["Gateway"] ?? {};
|
|
19
|
+
const existingPublicGatewaysConfig = existingGatewayConfig["PublicGateways"] ?? {};
|
|
20
|
+
const gatewayPublicGateways = {};
|
|
21
|
+
for (const [hostname, gatewayConfig] of Object.entries(existingPublicGatewaysConfig)) {
|
|
22
|
+
if (typeof gatewayConfig === "object" && gatewayConfig !== null) {
|
|
23
|
+
gatewayPublicGateways[hostname] = { ...gatewayConfig, UseSubdomains: false };
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
gatewayPublicGateways[hostname] = { UseSubdomains: false };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const canonicalGatewayHostname = gatewayUrl.hostname.includes(":") ? `[${gatewayUrl.hostname}]` : gatewayUrl.hostname;
|
|
30
|
+
const hostnamesForDefaults = new Set([canonicalGatewayHostname, "localhost", "127.0.0.1", "[::1]"]);
|
|
31
|
+
for (const hostname of hostnamesForDefaults) {
|
|
32
|
+
if (!hostname)
|
|
33
|
+
continue;
|
|
34
|
+
const existingConfig = gatewayPublicGateways[hostname];
|
|
35
|
+
const normalizedExistingConfig = typeof existingConfig === "object" && existingConfig !== null ? { ...existingConfig } : {};
|
|
36
|
+
const paths = Array.isArray(normalizedExistingConfig.Paths) && normalizedExistingConfig.Paths.length > 0
|
|
37
|
+
? normalizedExistingConfig.Paths
|
|
38
|
+
: ["/ipfs/", "/ipns/"];
|
|
39
|
+
gatewayPublicGateways[hostname] = {
|
|
40
|
+
...normalizedExistingConfig,
|
|
41
|
+
InlineDNSLink: normalizedExistingConfig.InlineDNSLink !== undefined ? normalizedExistingConfig.InlineDNSLink : false,
|
|
42
|
+
UseSubdomains: false,
|
|
43
|
+
Paths: paths
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const mergedIpfsConfig = {
|
|
47
|
+
...currentIpfsConfigFile,
|
|
48
|
+
Addresses: {
|
|
49
|
+
...(currentIpfsConfigFile["Addresses"] ?? {}),
|
|
50
|
+
Gateway: `/ip4/${gatewayUrl.hostname}/tcp/${gatewayUrl.port}`,
|
|
51
|
+
API: `/ip4/${apiUrl.hostname}/tcp/${apiUrl.port}`,
|
|
52
|
+
Swarm: [
|
|
53
|
+
"/ip4/0.0.0.0/tcp/0",
|
|
54
|
+
"/ip6/::/tcp/0",
|
|
55
|
+
"/ip4/0.0.0.0/udp/0/quic-v1",
|
|
56
|
+
"/ip4/0.0.0.0/udp/0/quic-v1/webtransport",
|
|
57
|
+
"/ip6/::/udp/0/quic-v1",
|
|
58
|
+
"/ip6/::/udp/0/quic-v1/webtransport"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
AutoTLS: {
|
|
62
|
+
...(currentIpfsConfigFile["AutoTLS"] ?? {}),
|
|
63
|
+
Enabled: true
|
|
64
|
+
},
|
|
65
|
+
Gateway: {
|
|
66
|
+
...existingGatewayConfig,
|
|
67
|
+
PublicGateways: gatewayPublicGateways
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
await fsPromises.writeFile(ipfsConfigPath, JSON.stringify(mergedIpfsConfig, null, 4));
|
|
71
|
+
log("Applied bitsocial CLI defaults to freshly initialized IPFS config.", ipfsConfigPath);
|
|
72
|
+
}
|
|
73
|
+
// use this custom function instead of spawnSync for better logging
|
|
74
|
+
// also spawnSync might have been causing crash on start on windows
|
|
75
|
+
function _spawnAsync(log, ...args) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
//@ts-ignore
|
|
78
|
+
const spawedProcess = spawn(...args);
|
|
79
|
+
let errorMessage = "";
|
|
80
|
+
spawedProcess.on("exit", (exitCode, signal) => {
|
|
81
|
+
if (exitCode === 0)
|
|
82
|
+
resolve(null);
|
|
83
|
+
else {
|
|
84
|
+
const error = new Error(errorMessage);
|
|
85
|
+
Object.assign(error, { exitCode, pid: spawedProcess.pid, signal });
|
|
86
|
+
reject(error);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
spawedProcess.stderr.on("data", (data) => {
|
|
90
|
+
log.trace(data.toString());
|
|
91
|
+
errorMessage += data.toString();
|
|
92
|
+
});
|
|
93
|
+
spawedProcess.stdin.on("data", (data) => log.trace(data.toString()));
|
|
94
|
+
spawedProcess.stdout.on("data", (data) => log.trace(data.toString()));
|
|
95
|
+
spawedProcess.on("error", (data) => {
|
|
96
|
+
errorMessage += data.toString();
|
|
97
|
+
log.error(data.toString());
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
let cachedMultiaddrFactory;
|
|
102
|
+
async function getMultiaddrFactory() {
|
|
103
|
+
if (!cachedMultiaddrFactory) {
|
|
104
|
+
const module = (await import("@multiformats/multiaddr"));
|
|
105
|
+
cachedMultiaddrFactory = module.multiaddr;
|
|
106
|
+
}
|
|
107
|
+
return cachedMultiaddrFactory;
|
|
108
|
+
}
|
|
109
|
+
async function parseTcpMultiaddr(multiAddrString) {
|
|
110
|
+
if (!multiAddrString)
|
|
111
|
+
return undefined;
|
|
112
|
+
try {
|
|
113
|
+
const factory = await getMultiaddrFactory();
|
|
114
|
+
const address = factory(multiAddrString);
|
|
115
|
+
const components = address.getComponents();
|
|
116
|
+
const tcpComponent = components.find((component) => component.name === "tcp");
|
|
117
|
+
const hostComponent = components.find((component) => ["ip4", "ip6", "dns", "dns4", "dns6", "dnsaddr"].includes(component.name));
|
|
118
|
+
const host = hostComponent?.value;
|
|
119
|
+
const portValue = tcpComponent?.value ? Number(tcpComponent.value) : undefined;
|
|
120
|
+
if (!host || !portValue || !Number.isFinite(portValue) || portValue <= 0)
|
|
121
|
+
return undefined;
|
|
122
|
+
return { host, port: portValue };
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function ensureIpfsPortsAreAvailable(log, configPath, apiUrl, gatewayUrl) {
|
|
129
|
+
let configRaw;
|
|
130
|
+
try {
|
|
131
|
+
configRaw = await fsPromises.readFile(configPath, "utf-8");
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
135
|
+
throw new Error(`Unable to read IPFS config at ${configPath} to validate ports: ${message}`);
|
|
136
|
+
}
|
|
137
|
+
let config;
|
|
138
|
+
try {
|
|
139
|
+
config = JSON.parse(configRaw);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
+
throw new Error(`Unable to parse IPFS config at ${configPath} to validate ports: ${message}`);
|
|
144
|
+
}
|
|
145
|
+
const addresses = config?.Addresses ?? {};
|
|
146
|
+
const checks = new Map();
|
|
147
|
+
const addCheck = (label, host, port, source) => {
|
|
148
|
+
if (!host || typeof host !== "string")
|
|
149
|
+
return;
|
|
150
|
+
if (!Number.isFinite(port) || port <= 0)
|
|
151
|
+
return;
|
|
152
|
+
const key = `${label}|${host}:${port}`;
|
|
153
|
+
if (!checks.has(key))
|
|
154
|
+
checks.set(key, { label, host, port, source });
|
|
155
|
+
};
|
|
156
|
+
const apiMultiAddr = typeof addresses.API === "string" ? await parseTcpMultiaddr(addresses.API) : undefined;
|
|
157
|
+
if (apiMultiAddr)
|
|
158
|
+
addCheck("IPFS API", apiMultiAddr.host, apiMultiAddr.port, addresses.API);
|
|
159
|
+
else if (apiUrl?.hostname) {
|
|
160
|
+
const fallbackPort = Number(apiUrl.port || (apiUrl.protocol === "https:" ? 443 : 80));
|
|
161
|
+
if (Number.isFinite(fallbackPort) && fallbackPort > 0)
|
|
162
|
+
addCheck("IPFS API", apiUrl.hostname, fallbackPort, apiUrl.toString());
|
|
163
|
+
}
|
|
164
|
+
const gatewayMultiAddr = typeof addresses.Gateway === "string" ? await parseTcpMultiaddr(addresses.Gateway) : undefined;
|
|
165
|
+
if (gatewayMultiAddr)
|
|
166
|
+
addCheck("IPFS Gateway", gatewayMultiAddr.host, gatewayMultiAddr.port, addresses.Gateway);
|
|
167
|
+
else if (gatewayUrl?.hostname) {
|
|
168
|
+
const fallbackPort = Number(gatewayUrl.port || (gatewayUrl.protocol === "https:" ? 443 : 80));
|
|
169
|
+
if (Number.isFinite(fallbackPort) && fallbackPort > 0)
|
|
170
|
+
addCheck("IPFS Gateway", gatewayUrl.hostname, fallbackPort, gatewayUrl.toString());
|
|
171
|
+
}
|
|
172
|
+
const swarmAddresses = Array.isArray(addresses.Swarm)
|
|
173
|
+
? addresses.Swarm.filter((addr) => typeof addr === "string")
|
|
174
|
+
: typeof addresses.Swarm === "string"
|
|
175
|
+
? [addresses.Swarm]
|
|
176
|
+
: [];
|
|
177
|
+
for (const swarmAddr of swarmAddresses) {
|
|
178
|
+
const parsed = await parseTcpMultiaddr(swarmAddr);
|
|
179
|
+
if (parsed)
|
|
180
|
+
addCheck("IPFS Swarm", parsed.host, parsed.port, swarmAddr);
|
|
181
|
+
}
|
|
182
|
+
for (const check of checks.values()) {
|
|
183
|
+
const inUse = await tcpPortUsed.check(check.port, check.host);
|
|
184
|
+
log.trace?.(`Validating ${check.label} port ${check.host}:${check.port} (source: ${check.source}) - in use: ${inUse}`);
|
|
185
|
+
if (inUse) {
|
|
186
|
+
throw new Error(`Cannot start IPFS daemon because the ${check.label} port ${check.host}:${check.port} (configured as ${check.source}) is already in use.`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export async function startKuboNode(apiUrl, gatewayUrl, dataPath, onSpawn) {
|
|
191
|
+
return new Promise(async (resolve, reject) => {
|
|
192
|
+
const log = (await getPKCLogger())("bitsocial-cli:ipfs:startKuboNode");
|
|
193
|
+
const ipfsDataPath = process.env["IPFS_PATH"] || path.join(dataPath, ".bitsocial-cli.ipfs");
|
|
194
|
+
await fs.promises.mkdir(ipfsDataPath, { recursive: true });
|
|
195
|
+
const ipfsConfigPath = path.join(ipfsDataPath, "config");
|
|
196
|
+
const kuboExePath = await getKuboExePath();
|
|
197
|
+
const kuboVersion = await getKuboVersion();
|
|
198
|
+
log(`Using Kubo version: ${kuboVersion}`);
|
|
199
|
+
log(`IpfsDataPath (${ipfsDataPath}), kuboExePath (${kuboExePath})`, "kubo ipfs config file", path.join(ipfsDataPath, "config"));
|
|
200
|
+
log("If you would like to change kubo config, please edit the config file at", path.join(ipfsDataPath, "config"));
|
|
201
|
+
const env = { IPFS_PATH: ipfsDataPath, DEBUG_COLORS: "1" };
|
|
202
|
+
let configJustInitialized = false;
|
|
203
|
+
try {
|
|
204
|
+
await _spawnAsync(log, kuboExePath, ["init"], { env, hideWindows: true });
|
|
205
|
+
configJustInitialized = true;
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
const error = e;
|
|
209
|
+
if (!error?.message?.includes("ipfs configuration file already exists!"))
|
|
210
|
+
throw new Error("Failed to call ipfs init" + error);
|
|
211
|
+
}
|
|
212
|
+
if (configJustInitialized) {
|
|
213
|
+
await _spawnAsync(log, kuboExePath, ["config", "profile", "apply", `server`], {
|
|
214
|
+
env,
|
|
215
|
+
hideWindows: true
|
|
216
|
+
});
|
|
217
|
+
log("Called 'ipfs config profile apply server' successfully");
|
|
218
|
+
await mergeCliDefaultsIntoIpfsConfig(log, ipfsConfigPath, apiUrl, gatewayUrl);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
log("IPFS config already exists; skipping config overrides to preserve user changes.");
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
await _spawnAsync(log, kuboExePath, ["repo", "migrate"], { env, hideWindows: true });
|
|
225
|
+
log("Ensured IPFS repository is migrated to the latest supported version.");
|
|
226
|
+
}
|
|
227
|
+
catch (migrationError) {
|
|
228
|
+
log.error("Failed to run IPFS repo migrations automatically", migrationError);
|
|
229
|
+
throw migrationError;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
await ensureIpfsPortsAreAvailable(log, ipfsConfigPath, apiUrl, gatewayUrl);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const daemonArgs = ["--enable-namesys-pubsub", "--migrate"];
|
|
239
|
+
const kuboProcess = spawn(kuboExePath, ["daemon", ...daemonArgs], {
|
|
240
|
+
env,
|
|
241
|
+
cwd: process.cwd(),
|
|
242
|
+
detached: true
|
|
243
|
+
});
|
|
244
|
+
onSpawn?.(kuboProcess);
|
|
245
|
+
log.trace(`Kubo ipfs daemon process started with pid ${kuboProcess.pid} and args`, daemonArgs);
|
|
246
|
+
let lastError = "Kubo process exited before Daemon was ready."; // Default error for premature exit
|
|
247
|
+
let daemonReady = false;
|
|
248
|
+
// Define handlers upfront to allow removal
|
|
249
|
+
const onProcessExit = () => {
|
|
250
|
+
if (!daemonReady) {
|
|
251
|
+
// Only reject if daemon wasn't ready (i.e., startup failed)
|
|
252
|
+
log.error(`Kubo ipfs process with pid ${kuboProcess.pid} exited prematurely. Last error: ${lastError}`);
|
|
253
|
+
// Clean up all listeners associated with this promise to prevent multiple rejections or logs from this context
|
|
254
|
+
kuboProcess.removeAllListeners();
|
|
255
|
+
reject(new Error(lastError));
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// If daemon was already ready, this exit is handled by listeners in daemon.ts (e.g., keepKuboUp or asyncExitHook)
|
|
259
|
+
log.trace(`Kubo ipfs process with pid ${kuboProcess.pid} exited after daemon was ready.`);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
const onProcessError = (err) => {
|
|
263
|
+
lastError = err.message || "Kubo process encountered an error during startup.";
|
|
264
|
+
log.error(`Kubo process error: ${lastError}`);
|
|
265
|
+
if (!daemonReady) {
|
|
266
|
+
// Only reject if daemon wasn't ready
|
|
267
|
+
kuboProcess.removeAllListeners();
|
|
268
|
+
reject(err);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const onDaemonReadyOutput = (data) => {
|
|
272
|
+
const output = data.toString();
|
|
273
|
+
log.trace(output);
|
|
274
|
+
if (output.match("Daemon is ready")) {
|
|
275
|
+
daemonReady = true;
|
|
276
|
+
assert(typeof kuboProcess.pid === "number", `kuboProcess.pid (${kuboProcess.pid}) is not a valid pid`);
|
|
277
|
+
const delayRaw = process.env["PKC_CLI_TEST_IPFS_READY_DELAY_MS"];
|
|
278
|
+
const readyDelay = delayRaw ? Number(delayRaw) : 0;
|
|
279
|
+
const completeResolve = () => {
|
|
280
|
+
// IMPORTANT: Remove promise-specific handlers once startup is successful
|
|
281
|
+
kuboProcess.removeListener("exit", onProcessExit);
|
|
282
|
+
kuboProcess.removeListener("error", onProcessError);
|
|
283
|
+
// Stderr listener can remain for ongoing logging if desired, or be removed too.
|
|
284
|
+
// kuboProcess.stderr.removeListener("data", onStderrData); // If you want to stop this specific stderr logging
|
|
285
|
+
resolve(kuboProcess);
|
|
286
|
+
};
|
|
287
|
+
if (Number.isFinite(readyDelay) && readyDelay > 0)
|
|
288
|
+
setTimeout(completeResolve, readyDelay);
|
|
289
|
+
else
|
|
290
|
+
completeResolve();
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const onStderrData = (data) => {
|
|
294
|
+
const errorOutput = data.toString();
|
|
295
|
+
lastError = errorOutput; // Keep track of the last thing seen on stderr for error reporting
|
|
296
|
+
log.error(errorOutput); // Log all stderr output
|
|
297
|
+
};
|
|
298
|
+
kuboProcess.stderr.on("data", onStderrData);
|
|
299
|
+
// kuboProcess.stdin.on("data", (data) => log.trace(data.toString())); // Listening on child's stdin is unusual, usually for writing.
|
|
300
|
+
kuboProcess.stdout.on("data", onDaemonReadyOutput);
|
|
301
|
+
kuboProcess.on("error", onProcessError); // For spawn errors or other direct errors from the process object itself
|
|
302
|
+
kuboProcess.on("exit", onProcessExit); // For when the process terminates
|
|
303
|
+
});
|
|
304
|
+
}
|
package/dist/seeder.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|