@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
@@ -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,3 @@
1
+ import { Hook } from "@oclif/core";
2
+ declare const hook: Hook<"init">;
3
+ export default hook;
@@ -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,3 @@
1
+ import { Hook } from "@oclif/core";
2
+ declare const hook: Hook<"prerun">;
3
+ 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,9 @@
1
+ declare const _default: {
2
+ PKC_DATA_PATH: string;
3
+ PKC_RPC_URL: import("url").URL;
4
+ KUBO_RPC_URL: import("url").URL;
5
+ IPFS_GATEWAY_URL: import("url").URL;
6
+ HTTP_TRACKERS: string[];
7
+ PKC_LOG_PATH: string;
8
+ };
9
+ export default _default;
@@ -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,2 @@
1
+ import { BsoResolver } from "@bitsocial/bso-resolver";
2
+ export declare function createBsoResolvers(providers?: string[], dataPath?: string): BsoResolver[];
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1 @@
1
+ export {};