@bitsocial/bitsocial-cli 0.19.66 → 0.19.67
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/README.md +16 -16
- package/dist/cli/commands/daemon.js +12 -2
- package/dist/cli/commands/logs.js +8 -3
- package/dist/cli/commands/update/install.d.ts +6 -0
- package/dist/cli/commands/update/install.js +38 -9
- package/dist/common-utils/daemon-state.d.ts +7 -0
- package/dist/common-utils/daemon-state.js +7 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -421,7 +421,7 @@ EXAMPLES
|
|
|
421
421
|
$ bitsocial challenge install ./my-local-challenge
|
|
422
422
|
```
|
|
423
423
|
|
|
424
|
-
_See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
424
|
+
_See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/challenge/install.ts)_
|
|
425
425
|
|
|
426
426
|
## `bitsocial challenge list`
|
|
427
427
|
|
|
@@ -447,7 +447,7 @@ EXAMPLES
|
|
|
447
447
|
$ bitsocial challenge list -q
|
|
448
448
|
```
|
|
449
449
|
|
|
450
|
-
_See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
450
|
+
_See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/challenge/list.ts)_
|
|
451
451
|
|
|
452
452
|
## `bitsocial challenge ls`
|
|
453
453
|
|
|
@@ -501,7 +501,7 @@ EXAMPLES
|
|
|
501
501
|
$ bitsocial challenge remove @scope/my-challenge
|
|
502
502
|
```
|
|
503
503
|
|
|
504
|
-
_See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
504
|
+
_See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/challenge/remove.ts)_
|
|
505
505
|
|
|
506
506
|
## `bitsocial challenge rm NAME`
|
|
507
507
|
|
|
@@ -615,7 +615,7 @@ EXAMPLES
|
|
|
615
615
|
$ bitsocial community create --jsonFile ./create-options.json
|
|
616
616
|
```
|
|
617
617
|
|
|
618
|
-
_See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
618
|
+
_See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/create.ts)_
|
|
619
619
|
|
|
620
620
|
## `bitsocial community delete ADDRESSES`
|
|
621
621
|
|
|
@@ -640,7 +640,7 @@ EXAMPLES
|
|
|
640
640
|
$ bitsocial community delete 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
|
|
641
641
|
```
|
|
642
642
|
|
|
643
|
-
_See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
643
|
+
_See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/delete.ts)_
|
|
644
644
|
|
|
645
645
|
## `bitsocial community edit ADDRESS`
|
|
646
646
|
|
|
@@ -710,7 +710,7 @@ EXAMPLES
|
|
|
710
710
|
$ bitsocial community edit bitsocial.bso --jsonFile ./edit-options.json
|
|
711
711
|
```
|
|
712
712
|
|
|
713
|
-
_See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
713
|
+
_See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/edit.ts)_
|
|
714
714
|
|
|
715
715
|
## `bitsocial community export [ADDRESS]`
|
|
716
716
|
|
|
@@ -751,7 +751,7 @@ EXAMPLES
|
|
|
751
751
|
$ bitsocial community export --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
|
|
752
752
|
```
|
|
753
753
|
|
|
754
|
-
_See code: [src/cli/commands/community/export.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
754
|
+
_See code: [src/cli/commands/community/export.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/export.ts)_
|
|
755
755
|
|
|
756
756
|
## `bitsocial community get [ADDRESS]`
|
|
757
757
|
|
|
@@ -782,7 +782,7 @@ EXAMPLES
|
|
|
782
782
|
$ bitsocial community get --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
|
|
783
783
|
```
|
|
784
784
|
|
|
785
|
-
_See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
785
|
+
_See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/get.ts)_
|
|
786
786
|
|
|
787
787
|
## `bitsocial community list`
|
|
788
788
|
|
|
@@ -805,7 +805,7 @@ EXAMPLES
|
|
|
805
805
|
$ bitsocial community list
|
|
806
806
|
```
|
|
807
807
|
|
|
808
|
-
_See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
808
|
+
_See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/list.ts)_
|
|
809
809
|
|
|
810
810
|
## `bitsocial community start ADDRESSES`
|
|
811
811
|
|
|
@@ -839,7 +839,7 @@ EXAMPLES
|
|
|
839
839
|
$ bitsocial community start $(bitsocial community list -q) --concurrency 1
|
|
840
840
|
```
|
|
841
841
|
|
|
842
|
-
_See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
842
|
+
_See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/start.ts)_
|
|
843
843
|
|
|
844
844
|
## `bitsocial community stop ADDRESSES`
|
|
845
845
|
|
|
@@ -864,7 +864,7 @@ EXAMPLES
|
|
|
864
864
|
$ bitsocial community stop Qmb99crTbSUfKXamXwZBe829Vf6w5w5TktPkb6WstC9RFW
|
|
865
865
|
```
|
|
866
866
|
|
|
867
|
-
_See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
867
|
+
_See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/stop.ts)_
|
|
868
868
|
|
|
869
869
|
## `bitsocial daemon`
|
|
870
870
|
|
|
@@ -911,7 +911,7 @@ EXAMPLES
|
|
|
911
911
|
$ bitsocial daemon --no-allowPrivateKeyExport
|
|
912
912
|
```
|
|
913
913
|
|
|
914
|
-
_See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
914
|
+
_See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/daemon.ts)_
|
|
915
915
|
|
|
916
916
|
## `bitsocial help [COMMAND]`
|
|
917
917
|
|
|
@@ -977,7 +977,7 @@ EXAMPLES
|
|
|
977
977
|
$ bitsocial logs --stdout -f
|
|
978
978
|
```
|
|
979
979
|
|
|
980
|
-
_See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
980
|
+
_See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/logs.ts)_
|
|
981
981
|
|
|
982
982
|
## `bitsocial update check`
|
|
983
983
|
|
|
@@ -994,7 +994,7 @@ EXAMPLES
|
|
|
994
994
|
$ bitsocial update check
|
|
995
995
|
```
|
|
996
996
|
|
|
997
|
-
_See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
997
|
+
_See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/update/check.ts)_
|
|
998
998
|
|
|
999
999
|
## `bitsocial update install [VERSION]`
|
|
1000
1000
|
|
|
@@ -1026,7 +1026,7 @@ EXAMPLES
|
|
|
1026
1026
|
$ bitsocial update install --no-restart-daemons
|
|
1027
1027
|
```
|
|
1028
1028
|
|
|
1029
|
-
_See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
1029
|
+
_See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/update/install.ts)_
|
|
1030
1030
|
|
|
1031
1031
|
## `bitsocial update versions`
|
|
1032
1032
|
|
|
@@ -1048,7 +1048,7 @@ EXAMPLES
|
|
|
1048
1048
|
$ bitsocial update versions --limit 5
|
|
1049
1049
|
```
|
|
1050
1050
|
|
|
1051
|
-
_See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
1051
|
+
_See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/update/versions.ts)_
|
|
1052
1052
|
<!-- commandsstop -->
|
|
1053
1053
|
|
|
1054
1054
|
## Contribution
|
|
@@ -9,7 +9,7 @@ import { printBanner } from "../ascii-banner.js";
|
|
|
9
9
|
import { loadChallengesIntoPKC, formatChallengeNameVersion } from "../../challenge-packages/challenge-utils.js";
|
|
10
10
|
import { migrateDataDirectory } from "../../common-utils/data-migration.js";
|
|
11
11
|
import { createBsoResolvers, DEFAULT_PROVIDERS } from "../../common-utils/resolvers.js";
|
|
12
|
-
import { pruneStaleStates, writeDaemonState, deleteDaemonState } from "../../common-utils/daemon-state.js";
|
|
12
|
+
import { pruneStaleStates, writeDaemonState, deleteDaemonState, DAEMON_SHUTDOWN_TIMEOUT_MS } from "../../common-utils/daemon-state.js";
|
|
13
13
|
import { createDaemonFileLogger } from "../../common-utils/daemon-file-logger.js";
|
|
14
14
|
import fs from "fs";
|
|
15
15
|
import fsPromise from "fs/promises";
|
|
@@ -468,6 +468,16 @@ export default class Daemon extends Command {
|
|
|
468
468
|
}
|
|
469
469
|
};
|
|
470
470
|
const killKuboProcess = async () => {
|
|
471
|
+
// Test hook (issue #70): hold off the kubo teardown for a fixed delay so a test can
|
|
472
|
+
// deterministically reproduce the window where the daemon's RPC port is already free
|
|
473
|
+
// (daemonServer.destroy() runs in parallel) but kubo is still alive and bound. This is
|
|
474
|
+
// exactly the window `update install` must not restart into — see
|
|
475
|
+
// test/cli/update-install-restart-race.test.ts. The daemon process stays alive for the
|
|
476
|
+
// duration because the exit hook awaits killKuboProcess() before exiting.
|
|
477
|
+
const kuboShutdownDelayRaw = process.env["PKC_CLI_TEST_KUBO_SHUTDOWN_DELAY_MS"];
|
|
478
|
+
const kuboShutdownDelay = kuboShutdownDelayRaw ? Number(kuboShutdownDelayRaw) : 0;
|
|
479
|
+
if (Number.isFinite(kuboShutdownDelay) && kuboShutdownDelay > 0)
|
|
480
|
+
await new Promise((resolve) => setTimeout(resolve, kuboShutdownDelay));
|
|
471
481
|
// Wait (bounded) for any in-flight start attempt so we kill the kubo it may still
|
|
472
482
|
// spawn. Both promises settle on all failure paths (issue #70), but a spawned kubo
|
|
473
483
|
// that wedges before "Daemon is ready" without exiting keeps them pending — the
|
|
@@ -538,7 +548,7 @@ export default class Daemon extends Command {
|
|
|
538
548
|
log.error("Error shutting down daemon server", e);
|
|
539
549
|
}
|
|
540
550
|
await kuboKillPromise;
|
|
541
|
-
}, { wait:
|
|
551
|
+
}, { wait: DAEMON_SHUTDOWN_TIMEOUT_MS } // could take two minutes to shut down
|
|
542
552
|
);
|
|
543
553
|
// Emergency cleanup: if the process force-exits (e.g. double Ctrl+C),
|
|
544
554
|
// synchronously SIGKILL every live kubo's process group. This is a no-op if
|
|
@@ -163,6 +163,14 @@ export default class Logs extends Command {
|
|
|
163
163
|
// Follow mode: dump existing content (filtered + tailed) then watch for new data
|
|
164
164
|
let currentLogFile = latestLogFile;
|
|
165
165
|
const existingContent = await fsPromise.readFile(currentLogFile, "utf-8");
|
|
166
|
+
// Anchor the follow offset to exactly the bytes we just read, NOT a separate
|
|
167
|
+
// fsPromise.stat() taken afterwards. A later stat is racy: any append landing between
|
|
168
|
+
// this read and the stat is skipped (position jumps past it) yet was never in the dump
|
|
169
|
+
// above, so follow mode silently drops those lines. Under load that window widens — this
|
|
170
|
+
// was the cause of the intermittent CI failure where an appended line was never surfaced
|
|
171
|
+
// (issue #77). Byte length (not string length) because position indexes bytes in the file.
|
|
172
|
+
let position = Buffer.byteLength(existingContent, "utf-8");
|
|
173
|
+
let pendingBuffer = "";
|
|
166
174
|
const entries = this._parseLogEntries(existingContent);
|
|
167
175
|
const filtered = this._filterEntries(entries, since, until);
|
|
168
176
|
const streamFiltered = streamFilter ? this._filterByStream(filtered, streamFilter) : filtered;
|
|
@@ -170,9 +178,6 @@ export default class Logs extends Command {
|
|
|
170
178
|
const initialOutput = tailed.map((e) => e.lines.join("\n")).join("\n");
|
|
171
179
|
if (initialOutput)
|
|
172
180
|
process.stdout.write(initialOutput + "\n");
|
|
173
|
-
const stat = await fsPromise.stat(currentLogFile);
|
|
174
|
-
let position = stat.size;
|
|
175
|
-
let pendingBuffer = "";
|
|
176
181
|
// Watch for new data by reading directly from `position`. We intentionally do
|
|
177
182
|
// NOT gate on fsPromise.stat().size — on Windows + NTFS, stat() returns a stale
|
|
178
183
|
// size for a short window after another process appends, which causes the gate
|
|
@@ -10,5 +10,11 @@ export default class Install extends Command {
|
|
|
10
10
|
};
|
|
11
11
|
static examples: string[];
|
|
12
12
|
run(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Poll until the given PID no longer exists (signal 0 throws ESRCH), or the timeout elapses.
|
|
15
|
+
* Returns true if the process exited, false on timeout. EPERM means the process is still alive
|
|
16
|
+
* but owned by another user, so we keep waiting.
|
|
17
|
+
*/
|
|
18
|
+
private _waitForProcessExit;
|
|
13
19
|
private _restartDaemons;
|
|
14
20
|
}
|
|
@@ -4,7 +4,7 @@ import tcpPortUsed from "tcp-port-used";
|
|
|
4
4
|
import { fetchLatestVersion, installGlobal } from "../../../update/npm-registry.js";
|
|
5
5
|
import { fastInstallGlobal } from "../../../update/fast-update.js";
|
|
6
6
|
import { compareVersions } from "../../../update/semver.js";
|
|
7
|
-
import { getAliveDaemonStates } from "../../../common-utils/daemon-state.js";
|
|
7
|
+
import { getAliveDaemonStates, DAEMON_SHUTDOWN_TIMEOUT_MS } from "../../../common-utils/daemon-state.js";
|
|
8
8
|
export default class Install extends Command {
|
|
9
9
|
static description = "Install a specific version of bitsocial from npm";
|
|
10
10
|
static args = {
|
|
@@ -54,15 +54,17 @@ export default class Install extends Command {
|
|
|
54
54
|
throw e;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
// Wait for
|
|
57
|
+
// Wait for each daemon process to fully exit — NOT just for its RPC port to free.
|
|
58
|
+
// The daemon releases its RPC port (daemonServer.destroy()) before it finishes killing
|
|
59
|
+
// its kubo child, so a port-only wait lets us restart while the old kubo still holds the
|
|
60
|
+
// IPFS API port; the new daemon then dies on startup with "IPFS API port already in use"
|
|
61
|
+
// (issue #70). The daemon's exit hook kills kubo before the process exits, so waiting for
|
|
62
|
+
// the PID to disappear guarantees the kubo port is free before we restart.
|
|
58
63
|
for (const d of aliveDaemons) {
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const freed = await tcpPortUsed.waitUntilFree(port, 500, 30000).then(() => true).catch(() => false);
|
|
64
|
-
if (!freed) {
|
|
65
|
-
this.error(`Daemon (PID ${d.pid}) did not shut down within 30 seconds on port ${port}.`, { exit: 1 });
|
|
64
|
+
this.log(`Waiting for daemon (PID ${d.pid}) to exit...`);
|
|
65
|
+
const exited = await this._waitForProcessExit(d.pid, DAEMON_SHUTDOWN_TIMEOUT_MS);
|
|
66
|
+
if (!exited) {
|
|
67
|
+
this.error(`Daemon (PID ${d.pid}) did not shut down within ${DAEMON_SHUTDOWN_TIMEOUT_MS / 1000} seconds.`, { exit: 1 });
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
70
|
this.log("All daemons stopped.");
|
|
@@ -118,6 +120,33 @@ export default class Install extends Command {
|
|
|
118
120
|
this.log("To see the daemon logs run `bitsocial logs --stdout`");
|
|
119
121
|
}
|
|
120
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Poll until the given PID no longer exists (signal 0 throws ESRCH), or the timeout elapses.
|
|
125
|
+
* Returns true if the process exited, false on timeout. EPERM means the process is still alive
|
|
126
|
+
* but owned by another user, so we keep waiting.
|
|
127
|
+
*/
|
|
128
|
+
async _waitForProcessExit(pid, timeoutMs) {
|
|
129
|
+
const deadline = Date.now() + timeoutMs;
|
|
130
|
+
while (Date.now() < deadline) {
|
|
131
|
+
try {
|
|
132
|
+
process.kill(pid, 0);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
if (e.code === "ESRCH")
|
|
136
|
+
return true; // no such process — it exited
|
|
137
|
+
}
|
|
138
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
139
|
+
}
|
|
140
|
+
// Final check so a process that exits in the last interval isn't reported as a timeout
|
|
141
|
+
try {
|
|
142
|
+
process.kill(pid, 0);
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
if (e.code === "ESRCH")
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
121
150
|
async _restartDaemons(daemons) {
|
|
122
151
|
this.log(`Restarting ${daemons.length} daemon(s)...`);
|
|
123
152
|
for (const d of daemons) {
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maximum time a daemon is allowed to shut down its kubo + RPC server during its
|
|
3
|
+
* async exit hook. The `update install --restart-daemons` orchestrator must wait at
|
|
4
|
+
* least this long for a stopped daemon's PID to disappear before giving up — otherwise
|
|
5
|
+
* a slow-but-valid shutdown (within the daemon's own contract) aborts the update midway.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DAEMON_SHUTDOWN_TIMEOUT_MS = 120000;
|
|
1
8
|
export interface DaemonState {
|
|
2
9
|
pid: number;
|
|
3
10
|
startedAt: string;
|
|
@@ -5,6 +5,13 @@ import { execFile } from "child_process";
|
|
|
5
5
|
import { promisify } from "util";
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
7
|
const DAEMON_STATES_DIR = path.join(defaults.PKC_DATA_PATH, ".daemon_states");
|
|
8
|
+
/**
|
|
9
|
+
* Maximum time a daemon is allowed to shut down its kubo + RPC server during its
|
|
10
|
+
* async exit hook. The `update install --restart-daemons` orchestrator must wait at
|
|
11
|
+
* least this long for a stopped daemon's PID to disappear before giving up — otherwise
|
|
12
|
+
* a slow-but-valid shutdown (within the daemon's own contract) aborts the update midway.
|
|
13
|
+
*/
|
|
14
|
+
export const DAEMON_SHUTDOWN_TIMEOUT_MS = 120000;
|
|
8
15
|
function stateFilePath(pid) {
|
|
9
16
|
return path.join(DAEMON_STATES_DIR, `${pid}-daemon.state`);
|
|
10
17
|
}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED