@bitsocial/bitsocial-cli 0.19.45 → 0.19.46
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 +36 -22
- package/dist/cli/commands/community/create.d.ts +1 -0
- package/dist/cli/commands/community/create.js +38 -2
- package/dist/cli/commands/community/edit.js +12 -16
- package/dist/cli/commands/daemon.js +5 -5
- package/dist/cli/commands/logs.d.ts +5 -0
- package/dist/cli/commands/logs.js +96 -15
- package/dist/cli/commands/update/install.js +11 -7
- package/dist/util.d.ts +6 -1
- package/dist/util.js +44 -4
- package/oclif.manifest.json +37 -4
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -344,7 +344,7 @@ EXAMPLES
|
|
|
344
344
|
$ bitsocial challenge install ./my-local-challenge
|
|
345
345
|
```
|
|
346
346
|
|
|
347
|
-
_See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
347
|
+
_See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/challenge/install.ts)_
|
|
348
348
|
|
|
349
349
|
## `bitsocial challenge list`
|
|
350
350
|
|
|
@@ -367,7 +367,7 @@ EXAMPLES
|
|
|
367
367
|
$ bitsocial challenge list -q
|
|
368
368
|
```
|
|
369
369
|
|
|
370
|
-
_See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
370
|
+
_See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/challenge/list.ts)_
|
|
371
371
|
|
|
372
372
|
## `bitsocial challenge remove NAME`
|
|
373
373
|
|
|
@@ -392,7 +392,7 @@ EXAMPLES
|
|
|
392
392
|
$ bitsocial challenge remove @scope/my-challenge
|
|
393
393
|
```
|
|
394
394
|
|
|
395
|
-
_See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
395
|
+
_See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/challenge/remove.ts)_
|
|
396
396
|
|
|
397
397
|
## `bitsocial community create`
|
|
398
398
|
|
|
@@ -400,12 +400,13 @@ Create a community with specific properties. A newly created community will be s
|
|
|
400
400
|
|
|
401
401
|
```
|
|
402
402
|
USAGE
|
|
403
|
-
$ bitsocial community create --pkcRpcUrl <value> [--privateKeyPath <value>]
|
|
403
|
+
$ bitsocial community create --pkcRpcUrl <value> [--privateKeyPath <value>] [-f <value>]
|
|
404
404
|
|
|
405
405
|
FLAGS
|
|
406
|
-
--
|
|
407
|
-
|
|
408
|
-
|
|
406
|
+
-f, --jsonFile=<value> Path to a JSON/JSONC file containing create options (supports comments)
|
|
407
|
+
--pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
|
|
408
|
+
--privateKeyPath=<value> Private key (PEM) of the community signer that will be used to determine address (if
|
|
409
|
+
address is not a domain). If it's not provided then PKC will generate a private key
|
|
409
410
|
|
|
410
411
|
DESCRIPTION
|
|
411
412
|
Create a community with specific properties. A newly created community will be started after creation and be able to
|
|
@@ -415,9 +416,13 @@ EXAMPLES
|
|
|
415
416
|
Create a community with title 'Hello Plebs' and description 'Welcome'
|
|
416
417
|
|
|
417
418
|
$ bitsocial community create --title 'Hello Plebs' --description 'Welcome'
|
|
419
|
+
|
|
420
|
+
Create a community using options from a JSON/JSONC file
|
|
421
|
+
|
|
422
|
+
$ bitsocial community create --jsonFile ./create-options.json
|
|
418
423
|
```
|
|
419
424
|
|
|
420
|
-
_See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
425
|
+
_See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/community/create.ts)_
|
|
421
426
|
|
|
422
427
|
## `bitsocial community delete ADDRESSES`
|
|
423
428
|
|
|
@@ -442,7 +447,7 @@ EXAMPLES
|
|
|
442
447
|
$ bitsocial community delete 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
|
|
443
448
|
```
|
|
444
449
|
|
|
445
|
-
_See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
450
|
+
_See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/community/delete.ts)_
|
|
446
451
|
|
|
447
452
|
## `bitsocial community edit ADDRESS`
|
|
448
453
|
|
|
@@ -456,7 +461,7 @@ ARGUMENTS
|
|
|
456
461
|
ADDRESS Address of the community to edit. It could be the name domain, or a public key
|
|
457
462
|
|
|
458
463
|
FLAGS
|
|
459
|
-
-f, --jsonFile=<value> Path to a JSON file containing edit options
|
|
464
|
+
-f, --jsonFile=<value> Path to a JSON/JSONC file containing edit options (supports comments)
|
|
460
465
|
--pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
|
|
461
466
|
|
|
462
467
|
DESCRIPTION
|
|
@@ -495,12 +500,12 @@ EXAMPLES
|
|
|
495
500
|
|
|
496
501
|
$ bitsocial community edit bitsocial.bso --settings.fetchThumbnailUrls=false
|
|
497
502
|
|
|
498
|
-
Edit a community using options from a JSON file
|
|
503
|
+
Edit a community using options from a JSON/JSONC file
|
|
499
504
|
|
|
500
505
|
$ bitsocial community edit bitsocial.bso --jsonFile ./edit-options.json
|
|
501
506
|
```
|
|
502
507
|
|
|
503
|
-
_See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
508
|
+
_See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/community/edit.ts)_
|
|
504
509
|
|
|
505
510
|
## `bitsocial community get [ADDRESS]`
|
|
506
511
|
|
|
@@ -531,7 +536,7 @@ EXAMPLES
|
|
|
531
536
|
$ bitsocial community get --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
|
|
532
537
|
```
|
|
533
538
|
|
|
534
|
-
_See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
539
|
+
_See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/community/get.ts)_
|
|
535
540
|
|
|
536
541
|
## `bitsocial community list`
|
|
537
542
|
|
|
@@ -554,7 +559,7 @@ EXAMPLES
|
|
|
554
559
|
$ bitsocial community list
|
|
555
560
|
```
|
|
556
561
|
|
|
557
|
-
_See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
562
|
+
_See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/community/list.ts)_
|
|
558
563
|
|
|
559
564
|
## `bitsocial community start ADDRESSES`
|
|
560
565
|
|
|
@@ -583,7 +588,7 @@ EXAMPLES
|
|
|
583
588
|
$ bitsocial community start $(bitsocial community list -q)
|
|
584
589
|
```
|
|
585
590
|
|
|
586
|
-
_See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
591
|
+
_See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/community/start.ts)_
|
|
587
592
|
|
|
588
593
|
## `bitsocial community stop ADDRESSES`
|
|
589
594
|
|
|
@@ -608,7 +613,7 @@ EXAMPLES
|
|
|
608
613
|
$ bitsocial community stop Qmb99crTbSUfKXamXwZBe829Vf6w5w5TktPkb6WstC9RFW
|
|
609
614
|
```
|
|
610
615
|
|
|
611
|
-
_See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
616
|
+
_See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/community/stop.ts)_
|
|
612
617
|
|
|
613
618
|
## `bitsocial daemon`
|
|
614
619
|
|
|
@@ -649,7 +654,7 @@ EXAMPLES
|
|
|
649
654
|
$ bitsocial daemon --chainProviderUrls https://mainnet.infura.io/v3/YOUR_KEY
|
|
650
655
|
```
|
|
651
656
|
|
|
652
|
-
_See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
657
|
+
_See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/daemon.ts)_
|
|
653
658
|
|
|
654
659
|
## `bitsocial help [COMMAND]`
|
|
655
660
|
|
|
@@ -677,7 +682,8 @@ View the latest BitSocial daemon log file. By default dumps the full log and exi
|
|
|
677
682
|
|
|
678
683
|
```
|
|
679
684
|
USAGE
|
|
680
|
-
$ bitsocial logs [-f] [-n <value>] [--since <value>] [--until <value>] [--logPath <value>]
|
|
685
|
+
$ bitsocial logs [-f] [-n <value>] [--since <value>] [--until <value>] [--logPath <value>] [--stdout |
|
|
686
|
+
--stderr]
|
|
681
687
|
|
|
682
688
|
FLAGS
|
|
683
689
|
-f, --follow Follow log output in real-time (like tail -f)
|
|
@@ -685,6 +691,8 @@ FLAGS
|
|
|
685
691
|
--logPath=<value> Specify the directory containing log files
|
|
686
692
|
--since=<value> Show logs since timestamp (ISO 8601, e.g. 2026-01-02T13:23:37Z) or relative time (e.g. 30s,
|
|
687
693
|
42m, 2h, 1d)
|
|
694
|
+
--stderr Show only stderr log entries (output of pkc-logger library)
|
|
695
|
+
--stdout Show only stdout log entries
|
|
688
696
|
--until=<value> Show logs before timestamp (ISO 8601, e.g. 2026-01-02T13:23:37Z) or relative time (e.g. 30s,
|
|
689
697
|
42m, 2h, 1d)
|
|
690
698
|
|
|
@@ -704,9 +712,15 @@ EXAMPLES
|
|
|
704
712
|
$ bitsocial logs --since 2026-01-02T13:23:37Z --until 2026-01-02T14:00:00Z
|
|
705
713
|
|
|
706
714
|
$ bitsocial logs --since 1h -f
|
|
715
|
+
|
|
716
|
+
$ bitsocial logs --stdout
|
|
717
|
+
|
|
718
|
+
$ bitsocial logs --stderr
|
|
719
|
+
|
|
720
|
+
$ bitsocial logs --stdout -f
|
|
707
721
|
```
|
|
708
722
|
|
|
709
|
-
_See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
723
|
+
_See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/logs.ts)_
|
|
710
724
|
|
|
711
725
|
## `bitsocial update check`
|
|
712
726
|
|
|
@@ -723,7 +737,7 @@ EXAMPLES
|
|
|
723
737
|
$ bitsocial update check
|
|
724
738
|
```
|
|
725
739
|
|
|
726
|
-
_See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
740
|
+
_See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/update/check.ts)_
|
|
727
741
|
|
|
728
742
|
## `bitsocial update install [VERSION]`
|
|
729
743
|
|
|
@@ -755,7 +769,7 @@ EXAMPLES
|
|
|
755
769
|
$ bitsocial update install --no-restart-daemons
|
|
756
770
|
```
|
|
757
771
|
|
|
758
|
-
_See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
772
|
+
_See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/update/install.ts)_
|
|
759
773
|
|
|
760
774
|
## `bitsocial update versions`
|
|
761
775
|
|
|
@@ -777,7 +791,7 @@ EXAMPLES
|
|
|
777
791
|
$ bitsocial update versions --limit 5
|
|
778
792
|
```
|
|
779
793
|
|
|
780
|
-
_See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
794
|
+
_See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.46/src/cli/commands/update/versions.ts)_
|
|
781
795
|
<!-- commandsstop -->
|
|
782
796
|
|
|
783
797
|
## Contribution
|
|
@@ -7,6 +7,7 @@ export default class Create extends BaseCommand {
|
|
|
7
7
|
}[];
|
|
8
8
|
static flags: {
|
|
9
9
|
privateKeyPath: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
jsonFile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
};
|
|
11
12
|
run(): Promise<void>;
|
|
12
13
|
}
|
|
@@ -3,7 +3,7 @@ import { Flags } from "@oclif/core";
|
|
|
3
3
|
import DataObjectParser from "dataobject-parser";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import { BaseCommand } from "../../base-command.js";
|
|
6
|
-
import { PKCLogger } from "../../../util.js";
|
|
6
|
+
import { PKCLogger, mergeDeep, parseJsoncFile } from "../../../util.js";
|
|
7
7
|
import * as remeda from "remeda";
|
|
8
8
|
export default class Create extends BaseCommand {
|
|
9
9
|
static description = "Create a community with specific properties. A newly created community will be started after creation and be able to receive publications. For a list of properties, visit https://github.com/pkcprotocol/pkc-js";
|
|
@@ -11,12 +11,21 @@ export default class Create extends BaseCommand {
|
|
|
11
11
|
{
|
|
12
12
|
description: "Create a community with title 'Hello Plebs' and description 'Welcome'",
|
|
13
13
|
command: "<%= config.bin %> <%= command.id %> --title 'Hello Plebs' --description 'Welcome'"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
description: "Create a community using options from a JSON/JSONC file",
|
|
17
|
+
command: "<%= config.bin %> <%= command.id %> --jsonFile ./create-options.json"
|
|
14
18
|
}
|
|
15
19
|
];
|
|
16
20
|
static flags = {
|
|
17
21
|
privateKeyPath: Flags.file({
|
|
18
22
|
exists: true,
|
|
19
23
|
description: "Private key (PEM) of the community signer that will be used to determine address (if address is not a domain). If it's not provided then PKC will generate a private key"
|
|
24
|
+
}),
|
|
25
|
+
jsonFile: Flags.file({
|
|
26
|
+
char: "f",
|
|
27
|
+
exists: true,
|
|
28
|
+
description: "Path to a JSON/JSONC file containing create options (supports comments)"
|
|
20
29
|
})
|
|
21
30
|
};
|
|
22
31
|
async run() {
|
|
@@ -24,7 +33,34 @@ export default class Create extends BaseCommand {
|
|
|
24
33
|
const log = PKCLogger("bitsocial-cli:commands:community:create");
|
|
25
34
|
log(`flags: `, flags);
|
|
26
35
|
const pkc = await this._connectToPkcRpc(flags.pkcRpcUrl.toString());
|
|
27
|
-
const
|
|
36
|
+
const cliCreateOptions = DataObjectParser.transpose(remeda.omit(flags, ["pkcRpcUrl", "privateKeyPath", "jsonFile"]))["_data"];
|
|
37
|
+
// Parse JSONC file if provided
|
|
38
|
+
let jsonFileOptions = {};
|
|
39
|
+
if (flags.jsonFile) {
|
|
40
|
+
try {
|
|
41
|
+
jsonFileOptions = await parseJsoncFile(flags.jsonFile);
|
|
42
|
+
log("JSONC file options parsed:", jsonFileOptions);
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
if (e instanceof Error) {
|
|
46
|
+
await pkc.destroy();
|
|
47
|
+
this.error(e.message);
|
|
48
|
+
}
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Merge: JSON file options first, then CLI flags override
|
|
53
|
+
let createOptions;
|
|
54
|
+
if (flags.jsonFile && Object.keys(cliCreateOptions).length > 0) {
|
|
55
|
+
createOptions = mergeDeep(jsonFileOptions, cliCreateOptions);
|
|
56
|
+
}
|
|
57
|
+
else if (flags.jsonFile) {
|
|
58
|
+
createOptions = jsonFileOptions;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
createOptions = cliCreateOptions;
|
|
62
|
+
}
|
|
63
|
+
log("Final create options:", createOptions);
|
|
28
64
|
if (flags.privateKeyPath)
|
|
29
65
|
try {
|
|
30
66
|
//@ts-expect-error
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
//@ts-expect-error
|
|
2
2
|
import DataObjectParser from "dataobject-parser";
|
|
3
3
|
import { Args, Flags } from "@oclif/core";
|
|
4
|
-
import fs from "fs";
|
|
5
4
|
import { BaseCommand } from "../../base-command.js";
|
|
6
|
-
import { PKCLogger, mergeDeep } from "../../../util.js";
|
|
5
|
+
import { PKCLogger, mergeDeep, parseJsoncFile, replaceNullWithUndefined } from "../../../util.js";
|
|
7
6
|
import * as remeda from "remeda";
|
|
8
7
|
export default class Edit extends BaseCommand {
|
|
9
8
|
static description = "Edit a community's properties. For a list of properties, visit https://github.com/pkcprotocol/pkc-js";
|
|
@@ -18,7 +17,7 @@ export default class Edit extends BaseCommand {
|
|
|
18
17
|
jsonFile: Flags.file({
|
|
19
18
|
char: "f",
|
|
20
19
|
exists: true,
|
|
21
|
-
description: "Path to a JSON file containing edit options"
|
|
20
|
+
description: "Path to a JSON/JSONC file containing edit options (supports comments)"
|
|
22
21
|
})
|
|
23
22
|
};
|
|
24
23
|
static examples = [
|
|
@@ -54,7 +53,7 @@ export default class Edit extends BaseCommand {
|
|
|
54
53
|
command: "bitsocial community edit bitsocial.bso --settings.fetchThumbnailUrls=false"
|
|
55
54
|
},
|
|
56
55
|
{
|
|
57
|
-
description: "Edit a community using options from a JSON file",
|
|
56
|
+
description: "Edit a community using options from a JSON/JSONC file",
|
|
58
57
|
command: "bitsocial community edit bitsocial.bso --jsonFile ./edit-options.json"
|
|
59
58
|
}
|
|
60
59
|
];
|
|
@@ -65,21 +64,16 @@ export default class Edit extends BaseCommand {
|
|
|
65
64
|
const pkc = await this._connectToPkcRpc(flags.pkcRpcUrl.toString());
|
|
66
65
|
const cliEditOptions = DataObjectParser.transpose(remeda.omit(flags, ["pkcRpcUrl", "jsonFile"]))["_data"];
|
|
67
66
|
log("CLI edit options parsed:", cliEditOptions);
|
|
68
|
-
// Parse
|
|
67
|
+
// Parse JSONC file if provided
|
|
69
68
|
let jsonFileOptions = {};
|
|
70
69
|
if (flags.jsonFile) {
|
|
71
70
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
75
|
-
this.error("JSON file must contain a JSON object (not an array, null, string, or number)");
|
|
76
|
-
}
|
|
77
|
-
jsonFileOptions = parsed;
|
|
78
|
-
log("JSON file options parsed:", jsonFileOptions);
|
|
71
|
+
jsonFileOptions = await parseJsoncFile(flags.jsonFile);
|
|
72
|
+
log("JSONC file options parsed:", jsonFileOptions);
|
|
79
73
|
}
|
|
80
74
|
catch (e) {
|
|
81
|
-
if (e instanceof
|
|
82
|
-
this.error(
|
|
75
|
+
if (e instanceof Error) {
|
|
76
|
+
this.error(e.message);
|
|
83
77
|
}
|
|
84
78
|
throw e;
|
|
85
79
|
}
|
|
@@ -102,9 +96,11 @@ export default class Edit extends BaseCommand {
|
|
|
102
96
|
try {
|
|
103
97
|
const community = await pkc.createCommunity({ address: args.address });
|
|
104
98
|
const mergedState = remeda.pick(community, remeda.keys.strict(editOptions));
|
|
105
|
-
|
|
99
|
+
// JSON file edits use RFC 7396 JSON Merge Patch semantics (arrays replace, objects merge).
|
|
100
|
+
// CLI flag edits use concat semantics (arrays extend with new values).
|
|
101
|
+
const finalMergedState = mergeDeep(mergedState, editOptions, flags.jsonFile ? "replace" : "concat");
|
|
106
102
|
log("Internal community state after merge:", finalMergedState);
|
|
107
|
-
await community.edit(finalMergedState);
|
|
103
|
+
await community.edit(replaceNullWithUndefined(finalMergedState));
|
|
108
104
|
this.log(community.address);
|
|
109
105
|
}
|
|
110
106
|
catch (e) {
|
|
@@ -96,12 +96,12 @@ export default class Daemon extends Command {
|
|
|
96
96
|
const stdoutWrite = process.stdout.write.bind(process.stdout);
|
|
97
97
|
const stderrWrite = process.stderr.write.bind(process.stderr);
|
|
98
98
|
const isLogFileOverLimit = () => logFile.bytesWritten > 20000000; // 20mb
|
|
99
|
-
const writeTimestampedLine = (text) => {
|
|
99
|
+
const writeTimestampedLine = (text, stream) => {
|
|
100
100
|
if (isLogFileOverLimit())
|
|
101
101
|
return;
|
|
102
102
|
if (!text || text.trim().length === 0)
|
|
103
103
|
return;
|
|
104
|
-
const timestamp = `[${new Date().toISOString()}] `;
|
|
104
|
+
const timestamp = `[${new Date().toISOString()}] [${stream}] `;
|
|
105
105
|
const lines = text.split("\n");
|
|
106
106
|
const timestamped = lines.map((line, i) => (i === 0 ? timestamp + line : line)).join("\n");
|
|
107
107
|
logFile.write(timestamped);
|
|
@@ -115,13 +115,13 @@ export default class Daemon extends Command {
|
|
|
115
115
|
debugModule.inspectOpts.colors = true;
|
|
116
116
|
debugModule.inspectOpts.hideDate = true;
|
|
117
117
|
debugModule.log = (...args) => {
|
|
118
|
-
writeTimestampedLine(formatWithOptions({ depth: Logger.inspectOpts?.depth || 10, colors: true }, ...args).trimStart() + EOL);
|
|
118
|
+
writeTimestampedLine(formatWithOptions({ depth: Logger.inspectOpts?.depth || 10, colors: true }, ...args).trimStart() + EOL, "stderr");
|
|
119
119
|
};
|
|
120
120
|
const asString = (data) => (typeof data === "string" ? data : Buffer.from(data).toString());
|
|
121
121
|
process.stdout.write = (...args) => {
|
|
122
122
|
//@ts-expect-error
|
|
123
123
|
const res = stdoutWrite(...args);
|
|
124
|
-
writeTimestampedLine(asString(args[0]));
|
|
124
|
+
writeTimestampedLine(asString(args[0]), "stdout");
|
|
125
125
|
return res;
|
|
126
126
|
};
|
|
127
127
|
process.stderr.write = (...args) => {
|
|
@@ -129,7 +129,7 @@ export default class Daemon extends Command {
|
|
|
129
129
|
// Debug output goes to stderr; we want it in logs only.
|
|
130
130
|
// Real errors are caught by uncaughtException/unhandledRejection handlers
|
|
131
131
|
// which use console.error -> stderr.write -> this override -> log file.
|
|
132
|
-
writeTimestampedLine(asString(args[0]).trimStart());
|
|
132
|
+
writeTimestampedLine(asString(args[0]).trimStart(), "stderr");
|
|
133
133
|
return true;
|
|
134
134
|
};
|
|
135
135
|
const log = Logger("bitsocial-cli:daemon");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from "@oclif/core";
|
|
2
2
|
interface LogEntry {
|
|
3
3
|
timestamp: Date | null;
|
|
4
|
+
stream: "stdout" | "stderr" | null;
|
|
4
5
|
lines: string[];
|
|
5
6
|
}
|
|
6
7
|
export default class Logs extends Command {
|
|
@@ -11,13 +12,17 @@ export default class Logs extends Command {
|
|
|
11
12
|
since: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
until: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
logPath: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
stdout: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
stderr: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
17
|
};
|
|
15
18
|
static examples: string[];
|
|
16
19
|
private _findLatestLogFile;
|
|
17
20
|
_parseTimestamp(value: string): Date;
|
|
18
21
|
_extractTimestamp(line: string): Date | null;
|
|
22
|
+
_extractStream(line: string): "stdout" | "stderr" | null;
|
|
19
23
|
_parseLogEntries(content: string): LogEntry[];
|
|
20
24
|
_filterEntries(entries: LogEntry[], since?: Date, until?: Date): LogEntry[];
|
|
25
|
+
_filterByStream(entries: LogEntry[], stream: "stdout" | "stderr"): LogEntry[];
|
|
21
26
|
_tailEntries(entries: LogEntry[], tailValue: string): LogEntry[];
|
|
22
27
|
run(): Promise<void>;
|
|
23
28
|
}
|
|
@@ -27,6 +27,16 @@ export default class Logs extends Command {
|
|
|
27
27
|
logPath: Flags.directory({
|
|
28
28
|
description: "Specify the directory containing log files",
|
|
29
29
|
required: false
|
|
30
|
+
}),
|
|
31
|
+
stdout: Flags.boolean({
|
|
32
|
+
description: "Show only stdout log entries",
|
|
33
|
+
default: false,
|
|
34
|
+
exclusive: ["stderr"]
|
|
35
|
+
}),
|
|
36
|
+
stderr: Flags.boolean({
|
|
37
|
+
description: "Show only stderr log entries (output of pkc-logger library)",
|
|
38
|
+
default: false,
|
|
39
|
+
exclusive: ["stdout"]
|
|
30
40
|
})
|
|
31
41
|
};
|
|
32
42
|
static examples = [
|
|
@@ -35,7 +45,10 @@ export default class Logs extends Command {
|
|
|
35
45
|
"bitsocial logs -n 50",
|
|
36
46
|
"bitsocial logs --since 5m",
|
|
37
47
|
"bitsocial logs --since 2026-01-02T13:23:37Z --until 2026-01-02T14:00:00Z",
|
|
38
|
-
"bitsocial logs --since 1h -f"
|
|
48
|
+
"bitsocial logs --since 1h -f",
|
|
49
|
+
"bitsocial logs --stdout",
|
|
50
|
+
"bitsocial logs --stderr",
|
|
51
|
+
"bitsocial logs --stdout -f"
|
|
39
52
|
];
|
|
40
53
|
async _findLatestLogFile(logPath) {
|
|
41
54
|
let entries;
|
|
@@ -76,6 +89,12 @@ export default class Logs extends Command {
|
|
|
76
89
|
return null;
|
|
77
90
|
return new Date(match[1]);
|
|
78
91
|
}
|
|
92
|
+
_extractStream(line) {
|
|
93
|
+
const match = line.match(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[(stdout|stderr)\] /);
|
|
94
|
+
if (!match)
|
|
95
|
+
return null;
|
|
96
|
+
return match[1];
|
|
97
|
+
}
|
|
79
98
|
_parseLogEntries(content) {
|
|
80
99
|
const lines = content.split("\n");
|
|
81
100
|
const entries = [];
|
|
@@ -83,7 +102,8 @@ export default class Logs extends Command {
|
|
|
83
102
|
const timestamp = this._extractTimestamp(line);
|
|
84
103
|
if (timestamp !== null) {
|
|
85
104
|
// New timestamped entry
|
|
86
|
-
|
|
105
|
+
const stream = this._extractStream(line);
|
|
106
|
+
entries.push({ timestamp, stream, lines: [line] });
|
|
87
107
|
}
|
|
88
108
|
else if (entries.length > 0) {
|
|
89
109
|
// Continuation line — belongs to the previous entry
|
|
@@ -91,7 +111,7 @@ export default class Logs extends Command {
|
|
|
91
111
|
}
|
|
92
112
|
else {
|
|
93
113
|
// Line before any timestamped entry (legacy/header)
|
|
94
|
-
entries.push({ timestamp: null, lines: [line] });
|
|
114
|
+
entries.push({ timestamp: null, stream: null, lines: [line] });
|
|
95
115
|
}
|
|
96
116
|
}
|
|
97
117
|
return entries;
|
|
@@ -109,6 +129,9 @@ export default class Logs extends Command {
|
|
|
109
129
|
return true;
|
|
110
130
|
});
|
|
111
131
|
}
|
|
132
|
+
_filterByStream(entries, stream) {
|
|
133
|
+
return entries.filter((entry) => entry.stream === stream);
|
|
134
|
+
}
|
|
112
135
|
_tailEntries(entries, tailValue) {
|
|
113
136
|
if (tailValue === "all")
|
|
114
137
|
return entries;
|
|
@@ -126,33 +149,37 @@ export default class Logs extends Command {
|
|
|
126
149
|
const latestLogFile = await this._findLatestLogFile(logPath);
|
|
127
150
|
const since = flags.since ? this._parseTimestamp(flags.since) : undefined;
|
|
128
151
|
const until = flags.until ? this._parseTimestamp(flags.until) : undefined;
|
|
152
|
+
const streamFilter = flags.stdout ? "stdout" : flags.stderr ? "stderr" : undefined;
|
|
129
153
|
if (!flags.follow) {
|
|
130
154
|
const content = await fsPromise.readFile(latestLogFile, "utf-8");
|
|
131
155
|
const entries = this._parseLogEntries(content);
|
|
132
156
|
const filtered = this._filterEntries(entries, since, until);
|
|
133
|
-
const
|
|
157
|
+
const streamFiltered = streamFilter ? this._filterByStream(filtered, streamFilter) : filtered;
|
|
158
|
+
const tailed = this._tailEntries(streamFiltered, flags.tail);
|
|
134
159
|
const output = tailed.map((e) => e.lines.join("\n")).join("\n");
|
|
135
160
|
if (output)
|
|
136
161
|
process.stdout.write(output + "\n");
|
|
137
162
|
return;
|
|
138
163
|
}
|
|
139
164
|
// Follow mode: dump existing content (filtered + tailed) then watch for new data
|
|
140
|
-
|
|
165
|
+
let currentLogFile = latestLogFile;
|
|
166
|
+
const existingContent = await fsPromise.readFile(currentLogFile, "utf-8");
|
|
141
167
|
const entries = this._parseLogEntries(existingContent);
|
|
142
168
|
const filtered = this._filterEntries(entries, since, until);
|
|
143
|
-
const
|
|
169
|
+
const streamFiltered = streamFilter ? this._filterByStream(filtered, streamFilter) : filtered;
|
|
170
|
+
const tailed = this._tailEntries(streamFiltered, flags.tail);
|
|
144
171
|
const initialOutput = tailed.map((e) => e.lines.join("\n")).join("\n");
|
|
145
172
|
if (initialOutput)
|
|
146
173
|
process.stdout.write(initialOutput + "\n");
|
|
147
|
-
const stat = await fsPromise.stat(
|
|
174
|
+
const stat = await fsPromise.stat(currentLogFile);
|
|
148
175
|
let position = stat.size;
|
|
149
176
|
let pendingBuffer = "";
|
|
150
177
|
// Watch for new data using polling (works across filesystems including Docker volumes)
|
|
151
178
|
const readNewData = async () => {
|
|
152
179
|
try {
|
|
153
|
-
const currentStat = await fsPromise.stat(
|
|
180
|
+
const currentStat = await fsPromise.stat(currentLogFile);
|
|
154
181
|
if (currentStat.size > position) {
|
|
155
|
-
const fd = await fsPromise.open(
|
|
182
|
+
const fd = await fsPromise.open(currentLogFile, "r");
|
|
156
183
|
const buf = new Uint8Array(currentStat.size - position);
|
|
157
184
|
const { bytesRead } = await fd.read(buf, 0, buf.length, position);
|
|
158
185
|
await fd.close();
|
|
@@ -166,14 +193,15 @@ export default class Logs extends Command {
|
|
|
166
193
|
}
|
|
167
194
|
pendingBuffer = chunk.slice(lastNewline + 1);
|
|
168
195
|
const completeText = chunk.slice(0, lastNewline + 1);
|
|
169
|
-
if (!since && !until) {
|
|
170
|
-
// No
|
|
196
|
+
if (!since && !until && !streamFilter) {
|
|
197
|
+
// No filtering — pass through directly
|
|
171
198
|
process.stdout.write(completeText);
|
|
172
199
|
}
|
|
173
200
|
else {
|
|
174
201
|
const newEntries = this._parseLogEntries(completeText.replace(/\n$/, ""));
|
|
175
202
|
const filteredNew = this._filterEntries(newEntries, since, until);
|
|
176
|
-
const
|
|
203
|
+
const streamFilteredNew = streamFilter ? this._filterByStream(filteredNew, streamFilter) : filteredNew;
|
|
204
|
+
const output = streamFilteredNew.map((e) => e.lines.join("\n")).join("\n");
|
|
177
205
|
if (output)
|
|
178
206
|
process.stdout.write(output + "\n");
|
|
179
207
|
}
|
|
@@ -183,14 +211,67 @@ export default class Logs extends Command {
|
|
|
183
211
|
// File may have been rotated or deleted
|
|
184
212
|
}
|
|
185
213
|
};
|
|
186
|
-
|
|
214
|
+
// Periodically check if a newer log file has appeared (e.g. after daemon restart)
|
|
215
|
+
const checkForNewLogFile = async () => {
|
|
216
|
+
try {
|
|
217
|
+
const newestFile = await this._findLatestLogFile(logPath);
|
|
218
|
+
if (newestFile === currentLogFile)
|
|
219
|
+
return;
|
|
220
|
+
// Flush any remaining partial line from old file
|
|
221
|
+
if (pendingBuffer) {
|
|
222
|
+
if (!since && !until && !streamFilter) {
|
|
223
|
+
process.stdout.write(pendingBuffer + "\n");
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
const pbEntries = this._parseLogEntries(pendingBuffer);
|
|
227
|
+
const pbFiltered = this._filterEntries(pbEntries, since, until);
|
|
228
|
+
const pbStreamFiltered = streamFilter ? this._filterByStream(pbFiltered, streamFilter) : pbFiltered;
|
|
229
|
+
const pbOutput = pbStreamFiltered.map((e) => e.lines.join("\n")).join("\n");
|
|
230
|
+
if (pbOutput)
|
|
231
|
+
process.stdout.write(pbOutput + "\n");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Switch watchers
|
|
235
|
+
fs.unwatchFile(currentLogFile, readNewData);
|
|
236
|
+
currentLogFile = newestFile;
|
|
237
|
+
pendingBuffer = "";
|
|
238
|
+
process.stderr.write(`\n--- switched to new log file: ${path.basename(newestFile)} ---\n\n`);
|
|
239
|
+
// Read and output entire new file content (with filters, no tail limit)
|
|
240
|
+
const newContent = await fsPromise.readFile(currentLogFile, "utf-8");
|
|
241
|
+
if (newContent) {
|
|
242
|
+
if (!since && !until && !streamFilter) {
|
|
243
|
+
process.stdout.write(newContent);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
const newEntries = this._parseLogEntries(newContent.replace(/\n$/, ""));
|
|
247
|
+
const filteredNew = this._filterEntries(newEntries, since, until);
|
|
248
|
+
const streamFilteredNew = streamFilter
|
|
249
|
+
? this._filterByStream(filteredNew, streamFilter)
|
|
250
|
+
: filteredNew;
|
|
251
|
+
const output = streamFilteredNew.map((e) => e.lines.join("\n")).join("\n");
|
|
252
|
+
if (output)
|
|
253
|
+
process.stdout.write(output + "\n");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const newStat = await fsPromise.stat(currentLogFile);
|
|
257
|
+
position = newStat.size;
|
|
258
|
+
fs.watchFile(currentLogFile, { interval: 300 }, readNewData);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// Directory listing failed or file disappeared — retry next cycle
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
fs.watchFile(currentLogFile, { interval: 300 }, readNewData);
|
|
265
|
+
const newFileCheckInterval = setInterval(checkForNewLogFile, 3000);
|
|
187
266
|
// Keep the process alive and clean up on exit
|
|
188
267
|
process.on("SIGINT", () => {
|
|
189
|
-
|
|
268
|
+
clearInterval(newFileCheckInterval);
|
|
269
|
+
fs.unwatchFile(currentLogFile, readNewData);
|
|
190
270
|
process.exit(0);
|
|
191
271
|
});
|
|
192
272
|
process.on("SIGTERM", () => {
|
|
193
|
-
|
|
273
|
+
clearInterval(newFileCheckInterval);
|
|
274
|
+
fs.unwatchFile(currentLogFile, readNewData);
|
|
194
275
|
process.exit(0);
|
|
195
276
|
});
|
|
196
277
|
// Keep process alive
|
|
@@ -144,15 +144,19 @@ export default class Install extends Command {
|
|
|
144
144
|
pkc.on("error", (err) => {
|
|
145
145
|
errors.push(err);
|
|
146
146
|
});
|
|
147
|
-
await new Promise((resolve
|
|
147
|
+
await new Promise((resolve) => {
|
|
148
148
|
const timeout = setTimeout(() => {
|
|
149
|
-
|
|
150
|
-
reject(lastError ?? new Error(`Timed out waiting for RPC server at ${pkcRpcUrl} to respond`));
|
|
151
|
-
}, 20000);
|
|
152
|
-
pkc.once("communitieschange", () => {
|
|
153
|
-
clearTimeout(timeout);
|
|
149
|
+
pkc.removeListener("communitieschange", handler);
|
|
154
150
|
resolve();
|
|
155
|
-
});
|
|
151
|
+
}, 20000);
|
|
152
|
+
const handler = () => {
|
|
153
|
+
if (pkc.communities.length > 0) {
|
|
154
|
+
pkc.removeListener("communitieschange", handler);
|
|
155
|
+
clearTimeout(timeout);
|
|
156
|
+
resolve();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
pkc.on("communitieschange", handler);
|
|
156
160
|
});
|
|
157
161
|
return pkc;
|
|
158
162
|
}
|
package/dist/util.d.ts
CHANGED
|
@@ -25,8 +25,13 @@ export declare function getLanIpV4Address(): string | undefined;
|
|
|
25
25
|
export declare function loadKuboConfigFile(pkcDataPath: string): Promise<any | undefined>;
|
|
26
26
|
export declare function parseMultiAddrKuboRpcToUrl(kuboMultiAddrString: string): Promise<import("url").URL>;
|
|
27
27
|
export declare function parseMultiAddrIpfsGatewayToUrl(ipfsGatewaymultiAddrString: string): Promise<import("url").URL>;
|
|
28
|
+
/** Recursively replaces all `null` values with `undefined`.
|
|
29
|
+
* Used before calling community.edit() since pkc-js expects `undefined` for removal,
|
|
30
|
+
* but JSON/CLI input produces `null`. */
|
|
31
|
+
export declare function replaceNullWithUndefined(obj: any): any;
|
|
28
32
|
/**
|
|
29
33
|
* Custom merge function that implements CLI-specific merge behavior.
|
|
30
34
|
* This matches the expected behavior from the test suite.
|
|
31
35
|
*/
|
|
32
|
-
export declare function mergeDeep(target: any, source: any): any;
|
|
36
|
+
export declare function mergeDeep(target: any, source: any, arrayStrategy?: "concat" | "replace"): any;
|
|
37
|
+
export declare function parseJsoncFile(filePath: string): Promise<Record<string, unknown>>;
|
package/dist/util.js
CHANGED
|
@@ -2,6 +2,7 @@ import os from "os";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import * as fsPromises from "fs/promises";
|
|
5
|
+
import stripJsonComments from "strip-json-comments";
|
|
5
6
|
import PKCLogger from "@pkcprotocol/pkc-logger";
|
|
6
7
|
export { PKCLogger };
|
|
7
8
|
/**
|
|
@@ -75,11 +76,28 @@ export async function parseMultiAddrIpfsGatewayToUrl(ipfsGatewaymultiAddrString)
|
|
|
75
76
|
throw new Error(`Unable to parse IPFS gateway multiaddr: ${ipfsGatewaymultiAddrString}`);
|
|
76
77
|
return new URL(`http://${parsed.host}:${parsed.port}`);
|
|
77
78
|
}
|
|
79
|
+
/** Recursively replaces all `null` values with `undefined`.
|
|
80
|
+
* Used before calling community.edit() since pkc-js expects `undefined` for removal,
|
|
81
|
+
* but JSON/CLI input produces `null`. */
|
|
82
|
+
export function replaceNullWithUndefined(obj) {
|
|
83
|
+
if (obj === null)
|
|
84
|
+
return undefined;
|
|
85
|
+
if (Array.isArray(obj))
|
|
86
|
+
return obj.map(replaceNullWithUndefined);
|
|
87
|
+
if (typeof obj === "object" && obj.constructor === Object) {
|
|
88
|
+
const result = {};
|
|
89
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
90
|
+
result[key] = replaceNullWithUndefined(value);
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
return obj;
|
|
95
|
+
}
|
|
78
96
|
/**
|
|
79
97
|
* Custom merge function that implements CLI-specific merge behavior.
|
|
80
98
|
* This matches the expected behavior from the test suite.
|
|
81
99
|
*/
|
|
82
|
-
export function mergeDeep(target, source) {
|
|
100
|
+
export function mergeDeep(target, source, arrayStrategy = "concat") {
|
|
83
101
|
function isObject(item) {
|
|
84
102
|
return item && typeof item === "object" && !Array.isArray(item);
|
|
85
103
|
}
|
|
@@ -88,6 +106,10 @@ export function mergeDeep(target, source) {
|
|
|
88
106
|
}
|
|
89
107
|
// Handle arrays with CLI-specific behavior
|
|
90
108
|
if (Array.isArray(target) && Array.isArray(source)) {
|
|
109
|
+
// RFC 7396 JSON Merge Patch: arrays are replaced entirely
|
|
110
|
+
if (arrayStrategy === "replace") {
|
|
111
|
+
return source;
|
|
112
|
+
}
|
|
91
113
|
// Check if source is sparse (has holes/empty items) - indicates indexed assignment like --rules[2]
|
|
92
114
|
const sourceHasHoles = source.length !== Object.keys(source).length;
|
|
93
115
|
if (sourceHasHoles) {
|
|
@@ -97,7 +119,7 @@ export function mergeDeep(target, source) {
|
|
|
97
119
|
for (let i = 0; i < maxLength; i++) {
|
|
98
120
|
if (i in source) {
|
|
99
121
|
if (i in target && isPlainObject(target[i]) && isPlainObject(source[i])) {
|
|
100
|
-
result[i] = mergeDeep(target[i], source[i]);
|
|
122
|
+
result[i] = mergeDeep(target[i], source[i], arrayStrategy);
|
|
101
123
|
}
|
|
102
124
|
else {
|
|
103
125
|
result[i] = source[i];
|
|
@@ -138,10 +160,10 @@ export function mergeDeep(target, source) {
|
|
|
138
160
|
for (const key in source) {
|
|
139
161
|
if (source.hasOwnProperty(key)) {
|
|
140
162
|
if (Array.isArray(target[key]) && Array.isArray(source[key])) {
|
|
141
|
-
result[key] = mergeDeep(target[key], source[key]);
|
|
163
|
+
result[key] = mergeDeep(target[key], source[key], arrayStrategy);
|
|
142
164
|
}
|
|
143
165
|
else if (isPlainObject(target[key]) && isPlainObject(source[key])) {
|
|
144
|
-
result[key] = mergeDeep(target[key], source[key]);
|
|
166
|
+
result[key] = mergeDeep(target[key], source[key], arrayStrategy);
|
|
145
167
|
}
|
|
146
168
|
else {
|
|
147
169
|
result[key] = source[key];
|
|
@@ -153,3 +175,21 @@ export function mergeDeep(target, source) {
|
|
|
153
175
|
// If not both objects/arrays, source takes precedence
|
|
154
176
|
return source;
|
|
155
177
|
}
|
|
178
|
+
export async function parseJsoncFile(filePath) {
|
|
179
|
+
const fileContent = await fsPromises.readFile(filePath, "utf-8");
|
|
180
|
+
const stripped = stripJsonComments(fileContent);
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = JSON.parse(stripped);
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
if (e instanceof SyntaxError) {
|
|
187
|
+
throw new Error(`Invalid JSONC in file ${filePath}: ${e.message}`);
|
|
188
|
+
}
|
|
189
|
+
throw e;
|
|
190
|
+
}
|
|
191
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
192
|
+
throw new Error("JSONC file must contain a JSON object (not an array, null, string, or number)");
|
|
193
|
+
}
|
|
194
|
+
return parsed;
|
|
195
|
+
}
|
package/oclif.manifest.json
CHANGED
|
@@ -72,7 +72,10 @@
|
|
|
72
72
|
"bitsocial logs -n 50",
|
|
73
73
|
"bitsocial logs --since 5m",
|
|
74
74
|
"bitsocial logs --since 2026-01-02T13:23:37Z --until 2026-01-02T14:00:00Z",
|
|
75
|
-
"bitsocial logs --since 1h -f"
|
|
75
|
+
"bitsocial logs --since 1h -f",
|
|
76
|
+
"bitsocial logs --stdout",
|
|
77
|
+
"bitsocial logs --stderr",
|
|
78
|
+
"bitsocial logs --stdout -f"
|
|
76
79
|
],
|
|
77
80
|
"flags": {
|
|
78
81
|
"follow": {
|
|
@@ -114,6 +117,24 @@
|
|
|
114
117
|
"hasDynamicHelp": false,
|
|
115
118
|
"multiple": false,
|
|
116
119
|
"type": "option"
|
|
120
|
+
},
|
|
121
|
+
"stdout": {
|
|
122
|
+
"description": "Show only stdout log entries",
|
|
123
|
+
"exclusive": [
|
|
124
|
+
"stderr"
|
|
125
|
+
],
|
|
126
|
+
"name": "stdout",
|
|
127
|
+
"allowNo": false,
|
|
128
|
+
"type": "boolean"
|
|
129
|
+
},
|
|
130
|
+
"stderr": {
|
|
131
|
+
"description": "Show only stderr log entries (output of pkc-logger library)",
|
|
132
|
+
"exclusive": [
|
|
133
|
+
"stdout"
|
|
134
|
+
],
|
|
135
|
+
"name": "stderr",
|
|
136
|
+
"allowNo": false,
|
|
137
|
+
"type": "boolean"
|
|
117
138
|
}
|
|
118
139
|
},
|
|
119
140
|
"hasDynamicHelp": false,
|
|
@@ -267,6 +288,10 @@
|
|
|
267
288
|
{
|
|
268
289
|
"description": "Create a community with title 'Hello Plebs' and description 'Welcome'",
|
|
269
290
|
"command": "<%= config.bin %> <%= command.id %> --title 'Hello Plebs' --description 'Welcome'"
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"description": "Create a community using options from a JSON/JSONC file",
|
|
294
|
+
"command": "<%= config.bin %> <%= command.id %> --jsonFile ./create-options.json"
|
|
270
295
|
}
|
|
271
296
|
],
|
|
272
297
|
"flags": {
|
|
@@ -285,6 +310,14 @@
|
|
|
285
310
|
"hasDynamicHelp": false,
|
|
286
311
|
"multiple": false,
|
|
287
312
|
"type": "option"
|
|
313
|
+
},
|
|
314
|
+
"jsonFile": {
|
|
315
|
+
"char": "f",
|
|
316
|
+
"description": "Path to a JSON/JSONC file containing create options (supports comments)",
|
|
317
|
+
"name": "jsonFile",
|
|
318
|
+
"hasDynamicHelp": false,
|
|
319
|
+
"multiple": false,
|
|
320
|
+
"type": "option"
|
|
288
321
|
}
|
|
289
322
|
},
|
|
290
323
|
"hasDynamicHelp": false,
|
|
@@ -386,7 +419,7 @@
|
|
|
386
419
|
"command": "bitsocial community edit bitsocial.bso --settings.fetchThumbnailUrls=false"
|
|
387
420
|
},
|
|
388
421
|
{
|
|
389
|
-
"description": "Edit a community using options from a JSON file",
|
|
422
|
+
"description": "Edit a community using options from a JSON/JSONC file",
|
|
390
423
|
"command": "bitsocial community edit bitsocial.bso --jsonFile ./edit-options.json"
|
|
391
424
|
}
|
|
392
425
|
],
|
|
@@ -402,7 +435,7 @@
|
|
|
402
435
|
},
|
|
403
436
|
"jsonFile": {
|
|
404
437
|
"char": "f",
|
|
405
|
-
"description": "Path to a JSON file containing edit options",
|
|
438
|
+
"description": "Path to a JSON/JSONC file containing edit options (supports comments)",
|
|
406
439
|
"name": "jsonFile",
|
|
407
440
|
"hasDynamicHelp": false,
|
|
408
441
|
"multiple": false,
|
|
@@ -725,5 +758,5 @@
|
|
|
725
758
|
]
|
|
726
759
|
}
|
|
727
760
|
},
|
|
728
|
-
"version": "0.19.
|
|
761
|
+
"version": "0.19.46"
|
|
729
762
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitsocial/bitsocial-cli",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.46",
|
|
4
4
|
"description": "Command line interface to Bitsocial API",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"homepage": "https://github.com/bitsocialnet/bitsocial-cli",
|
|
@@ -125,6 +125,7 @@
|
|
|
125
125
|
"exit-hook": "4.0.0",
|
|
126
126
|
"express": "4.19.2",
|
|
127
127
|
"kubo": "0.40.1",
|
|
128
|
+
"strip-json-comments": "5.0.3",
|
|
128
129
|
"tcp-port-used": "1.0.2",
|
|
129
130
|
"tslib": "2.6.2",
|
|
130
131
|
"typescript": "5.9.3"
|