@bitsocial/bitsocial-cli 0.19.88 → 0.19.90
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 +30 -23
- package/dist/challenge-packages/challenge-utils.d.ts +20 -0
- package/dist/challenge-packages/challenge-utils.js +124 -3
- package/dist/cli/commands/challenge/install.d.ts +2 -2
- package/dist/cli/commands/challenge/install.js +8 -9
- package/dist/cli/commands/challenge/remove.d.ts +2 -2
- package/dist/cli/commands/challenge/remove.js +7 -10
- package/oclif.manifest.json +19 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -328,13 +328,14 @@ Install a challenge package (npm package name, git URL, tarball URL, or local pa
|
|
|
328
328
|
|
|
329
329
|
```
|
|
330
330
|
USAGE
|
|
331
|
-
$ bitsocial challenge add PACKAGE [--pkcOptions.dataPath <value>]
|
|
331
|
+
$ bitsocial challenge add PACKAGE --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
|
|
332
332
|
|
|
333
333
|
ARGUMENTS
|
|
334
334
|
PACKAGE Package specifier — anything npm can install (name, name@version, git URL, tarball URL, local path)
|
|
335
335
|
|
|
336
336
|
FLAGS
|
|
337
337
|
--pkcOptions.dataPath=<value> Data path to install the challenge into
|
|
338
|
+
--pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
|
|
338
339
|
|
|
339
340
|
DESCRIPTION
|
|
340
341
|
Install a challenge package (npm package name, git URL, tarball URL, or local path)
|
|
@@ -361,13 +362,14 @@ Install a challenge package (npm package name, git URL, tarball URL, or local pa
|
|
|
361
362
|
|
|
362
363
|
```
|
|
363
364
|
USAGE
|
|
364
|
-
$ bitsocial challenge i PACKAGE [--pkcOptions.dataPath <value>]
|
|
365
|
+
$ bitsocial challenge i PACKAGE --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
|
|
365
366
|
|
|
366
367
|
ARGUMENTS
|
|
367
368
|
PACKAGE Package specifier — anything npm can install (name, name@version, git URL, tarball URL, local path)
|
|
368
369
|
|
|
369
370
|
FLAGS
|
|
370
371
|
--pkcOptions.dataPath=<value> Data path to install the challenge into
|
|
372
|
+
--pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
|
|
371
373
|
|
|
372
374
|
DESCRIPTION
|
|
373
375
|
Install a challenge package (npm package name, git URL, tarball URL, or local path)
|
|
@@ -394,13 +396,14 @@ Install a challenge package (npm package name, git URL, tarball URL, or local pa
|
|
|
394
396
|
|
|
395
397
|
```
|
|
396
398
|
USAGE
|
|
397
|
-
$ bitsocial challenge install PACKAGE [--pkcOptions.dataPath <value>]
|
|
399
|
+
$ bitsocial challenge install PACKAGE --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
|
|
398
400
|
|
|
399
401
|
ARGUMENTS
|
|
400
402
|
PACKAGE Package specifier — anything npm can install (name, name@version, git URL, tarball URL, local path)
|
|
401
403
|
|
|
402
404
|
FLAGS
|
|
403
405
|
--pkcOptions.dataPath=<value> Data path to install the challenge into
|
|
406
|
+
--pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
|
|
404
407
|
|
|
405
408
|
DESCRIPTION
|
|
406
409
|
Install a challenge package (npm package name, git URL, tarball URL, or local path)
|
|
@@ -421,7 +424,7 @@ EXAMPLES
|
|
|
421
424
|
$ bitsocial challenge install ./my-local-challenge
|
|
422
425
|
```
|
|
423
426
|
|
|
424
|
-
_See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
427
|
+
_See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/challenge/install.ts)_
|
|
425
428
|
|
|
426
429
|
## `bitsocial challenge list`
|
|
427
430
|
|
|
@@ -447,7 +450,7 @@ EXAMPLES
|
|
|
447
450
|
$ bitsocial challenge list -q
|
|
448
451
|
```
|
|
449
452
|
|
|
450
|
-
_See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
453
|
+
_See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/challenge/list.ts)_
|
|
451
454
|
|
|
452
455
|
## `bitsocial challenge ls`
|
|
453
456
|
|
|
@@ -479,13 +482,14 @@ Remove an installed challenge package
|
|
|
479
482
|
|
|
480
483
|
```
|
|
481
484
|
USAGE
|
|
482
|
-
$ bitsocial challenge remove NAME [--pkcOptions.dataPath <value>]
|
|
485
|
+
$ bitsocial challenge remove NAME --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
|
|
483
486
|
|
|
484
487
|
ARGUMENTS
|
|
485
488
|
NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
|
|
486
489
|
|
|
487
490
|
FLAGS
|
|
488
491
|
--pkcOptions.dataPath=<value> Data path where challenges are installed
|
|
492
|
+
--pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
|
|
489
493
|
|
|
490
494
|
DESCRIPTION
|
|
491
495
|
Remove an installed challenge package
|
|
@@ -501,7 +505,7 @@ EXAMPLES
|
|
|
501
505
|
$ bitsocial challenge remove @scope/my-challenge
|
|
502
506
|
```
|
|
503
507
|
|
|
504
|
-
_See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
508
|
+
_See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/challenge/remove.ts)_
|
|
505
509
|
|
|
506
510
|
## `bitsocial challenge rm NAME`
|
|
507
511
|
|
|
@@ -509,13 +513,14 @@ Remove an installed challenge package
|
|
|
509
513
|
|
|
510
514
|
```
|
|
511
515
|
USAGE
|
|
512
|
-
$ bitsocial challenge rm NAME [--pkcOptions.dataPath <value>]
|
|
516
|
+
$ bitsocial challenge rm NAME --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
|
|
513
517
|
|
|
514
518
|
ARGUMENTS
|
|
515
519
|
NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
|
|
516
520
|
|
|
517
521
|
FLAGS
|
|
518
522
|
--pkcOptions.dataPath=<value> Data path where challenges are installed
|
|
523
|
+
--pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
|
|
519
524
|
|
|
520
525
|
DESCRIPTION
|
|
521
526
|
Remove an installed challenge package
|
|
@@ -537,13 +542,14 @@ Remove an installed challenge package
|
|
|
537
542
|
|
|
538
543
|
```
|
|
539
544
|
USAGE
|
|
540
|
-
$ bitsocial challenge un NAME [--pkcOptions.dataPath <value>]
|
|
545
|
+
$ bitsocial challenge un NAME --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
|
|
541
546
|
|
|
542
547
|
ARGUMENTS
|
|
543
548
|
NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
|
|
544
549
|
|
|
545
550
|
FLAGS
|
|
546
551
|
--pkcOptions.dataPath=<value> Data path where challenges are installed
|
|
552
|
+
--pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
|
|
547
553
|
|
|
548
554
|
DESCRIPTION
|
|
549
555
|
Remove an installed challenge package
|
|
@@ -565,13 +571,14 @@ Remove an installed challenge package
|
|
|
565
571
|
|
|
566
572
|
```
|
|
567
573
|
USAGE
|
|
568
|
-
$ bitsocial challenge uninstall NAME [--pkcOptions.dataPath <value>]
|
|
574
|
+
$ bitsocial challenge uninstall NAME --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
|
|
569
575
|
|
|
570
576
|
ARGUMENTS
|
|
571
577
|
NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
|
|
572
578
|
|
|
573
579
|
FLAGS
|
|
574
580
|
--pkcOptions.dataPath=<value> Data path where challenges are installed
|
|
581
|
+
--pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
|
|
575
582
|
|
|
576
583
|
DESCRIPTION
|
|
577
584
|
Remove an installed challenge package
|
|
@@ -615,7 +622,7 @@ EXAMPLES
|
|
|
615
622
|
$ bitsocial community create --jsonFile ./create-options.json
|
|
616
623
|
```
|
|
617
624
|
|
|
618
|
-
_See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
625
|
+
_See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/create.ts)_
|
|
619
626
|
|
|
620
627
|
## `bitsocial community delete ADDRESSES`
|
|
621
628
|
|
|
@@ -640,7 +647,7 @@ EXAMPLES
|
|
|
640
647
|
$ bitsocial community delete 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
|
|
641
648
|
```
|
|
642
649
|
|
|
643
|
-
_See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
650
|
+
_See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/delete.ts)_
|
|
644
651
|
|
|
645
652
|
## `bitsocial community edit ADDRESS`
|
|
646
653
|
|
|
@@ -710,7 +717,7 @@ EXAMPLES
|
|
|
710
717
|
$ bitsocial community edit bitsocial.bso --jsonFile ./edit-options.json
|
|
711
718
|
```
|
|
712
719
|
|
|
713
|
-
_See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
720
|
+
_See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/edit.ts)_
|
|
714
721
|
|
|
715
722
|
## `bitsocial community export [ADDRESS]`
|
|
716
723
|
|
|
@@ -751,7 +758,7 @@ EXAMPLES
|
|
|
751
758
|
$ bitsocial community export --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
|
|
752
759
|
```
|
|
753
760
|
|
|
754
|
-
_See code: [src/cli/commands/community/export.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
761
|
+
_See code: [src/cli/commands/community/export.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/export.ts)_
|
|
755
762
|
|
|
756
763
|
## `bitsocial community get [ADDRESS]`
|
|
757
764
|
|
|
@@ -782,7 +789,7 @@ EXAMPLES
|
|
|
782
789
|
$ bitsocial community get --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
|
|
783
790
|
```
|
|
784
791
|
|
|
785
|
-
_See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
792
|
+
_See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/get.ts)_
|
|
786
793
|
|
|
787
794
|
## `bitsocial community list`
|
|
788
795
|
|
|
@@ -805,7 +812,7 @@ EXAMPLES
|
|
|
805
812
|
$ bitsocial community list
|
|
806
813
|
```
|
|
807
814
|
|
|
808
|
-
_See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
815
|
+
_See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/list.ts)_
|
|
809
816
|
|
|
810
817
|
## `bitsocial community start ADDRESSES`
|
|
811
818
|
|
|
@@ -839,7 +846,7 @@ EXAMPLES
|
|
|
839
846
|
$ bitsocial community start $(bitsocial community list -q) --concurrency 1
|
|
840
847
|
```
|
|
841
848
|
|
|
842
|
-
_See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
849
|
+
_See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/start.ts)_
|
|
843
850
|
|
|
844
851
|
## `bitsocial community stop ADDRESSES`
|
|
845
852
|
|
|
@@ -864,7 +871,7 @@ EXAMPLES
|
|
|
864
871
|
$ bitsocial community stop Qmb99crTbSUfKXamXwZBe829Vf6w5w5TktPkb6WstC9RFW
|
|
865
872
|
```
|
|
866
873
|
|
|
867
|
-
_See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
874
|
+
_See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/stop.ts)_
|
|
868
875
|
|
|
869
876
|
## `bitsocial daemon`
|
|
870
877
|
|
|
@@ -915,7 +922,7 @@ EXAMPLES
|
|
|
915
922
|
$ bitsocial daemon --no-allowPrivateKeyExport
|
|
916
923
|
```
|
|
917
924
|
|
|
918
|
-
_See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
925
|
+
_See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/daemon.ts)_
|
|
919
926
|
|
|
920
927
|
## `bitsocial help [COMMAND]`
|
|
921
928
|
|
|
@@ -981,7 +988,7 @@ EXAMPLES
|
|
|
981
988
|
$ bitsocial logs --stdout -f
|
|
982
989
|
```
|
|
983
990
|
|
|
984
|
-
_See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
991
|
+
_See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/logs.ts)_
|
|
985
992
|
|
|
986
993
|
## `bitsocial update check`
|
|
987
994
|
|
|
@@ -998,7 +1005,7 @@ EXAMPLES
|
|
|
998
1005
|
$ bitsocial update check
|
|
999
1006
|
```
|
|
1000
1007
|
|
|
1001
|
-
_See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
1008
|
+
_See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/update/check.ts)_
|
|
1002
1009
|
|
|
1003
1010
|
## `bitsocial update install [VERSION]`
|
|
1004
1011
|
|
|
@@ -1030,7 +1037,7 @@ EXAMPLES
|
|
|
1030
1037
|
$ bitsocial update install --no-restart-daemons
|
|
1031
1038
|
```
|
|
1032
1039
|
|
|
1033
|
-
_See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
1040
|
+
_See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/update/install.ts)_
|
|
1034
1041
|
|
|
1035
1042
|
## `bitsocial update versions`
|
|
1036
1043
|
|
|
@@ -1052,7 +1059,7 @@ EXAMPLES
|
|
|
1052
1059
|
$ bitsocial update versions --limit 5
|
|
1053
1060
|
```
|
|
1054
1061
|
|
|
1055
|
-
_See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.
|
|
1062
|
+
_See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/update/versions.ts)_
|
|
1056
1063
|
<!-- commandsstop -->
|
|
1057
1064
|
|
|
1058
1065
|
## Contribution
|
|
@@ -22,4 +22,24 @@ export declare function runNpmPack(packageSpec: string, destDir: string): Promis
|
|
|
22
22
|
export declare function runNpmInstall(challengeDir: string): Promise<void>;
|
|
23
23
|
export declare function verifyNativeModuleAbi(challengeDir: string): Promise<void>;
|
|
24
24
|
export declare function formatChallengeNameVersion(challenge: Pick<InstalledChallenge, "name" | "version">): string;
|
|
25
|
+
/**
|
|
26
|
+
* HTTP url of a daemon's challenge-reload endpoint, derived from its --pkcRpcUrl. The endpoint
|
|
27
|
+
* is served by the same http server as the RPC socket, and its local-only variant requires the
|
|
28
|
+
* request to come from the loopback interface — so a wildcard bind is dialed as loopback rather
|
|
29
|
+
* than as 0.0.0.0.
|
|
30
|
+
*/
|
|
31
|
+
export declare function challengeReloadUrlFromPkcRpcUrl(pkcRpcUrl: string): string | undefined;
|
|
32
|
+
/** How long install/remove wait for a daemon to finish reloading before giving up. */
|
|
33
|
+
export declare const CHALLENGE_RELOAD_TIMEOUT_MS = 30000;
|
|
34
|
+
/**
|
|
35
|
+
* Ask the daemon at `pkcRpcUrl` to reload its challenge packages, so an install/remove takes
|
|
36
|
+
* effect without a restart. Returns whether a daemon actually reloaded — best-effort, since
|
|
37
|
+
* no daemon running is not an error.
|
|
38
|
+
*
|
|
39
|
+
* The request is bounded: a daemon that accepts the connection but never answers (busy, wedged,
|
|
40
|
+
* or something else listening on the port) would otherwise hold the CLI for undici's 300s
|
|
41
|
+
* default. Aborting only ends our wait — the daemon may still finish the reload.
|
|
42
|
+
*/
|
|
43
|
+
export declare function reloadChallengesInDaemon(pkcRpcUrl: string | URL, timeoutMs?: number): Promise<boolean>;
|
|
44
|
+
export declare function hashChallengePackageContents(challengeDir: string): Promise<string>;
|
|
25
45
|
export declare function loadChallengesIntoPKC(dataPath?: string): Promise<InstalledChallenge[]>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { pathToFileURL } from "node:url";
|
|
3
4
|
import fs from "fs/promises";
|
|
4
5
|
import { execFileSync, spawn } from "child_process";
|
|
@@ -288,11 +289,114 @@ export async function verifyNativeModuleAbi(challengeDir) {
|
|
|
288
289
|
export function formatChallengeNameVersion(challenge) {
|
|
289
290
|
return challenge.version && challenge.version !== "unknown" ? `${challenge.name}@${challenge.version}` : challenge.name;
|
|
290
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* HTTP url of a daemon's challenge-reload endpoint, derived from its --pkcRpcUrl. The endpoint
|
|
294
|
+
* is served by the same http server as the RPC socket, and its local-only variant requires the
|
|
295
|
+
* request to come from the loopback interface — so a wildcard bind is dialed as loopback rather
|
|
296
|
+
* than as 0.0.0.0.
|
|
297
|
+
*/
|
|
298
|
+
export function challengeReloadUrlFromPkcRpcUrl(pkcRpcUrl) {
|
|
299
|
+
let url;
|
|
300
|
+
try {
|
|
301
|
+
url = new URL(pkcRpcUrl);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
if (!url.port)
|
|
307
|
+
return undefined;
|
|
308
|
+
// URL.hostname keeps the brackets around an IPv6 literal — strip them so the host can be
|
|
309
|
+
// compared and re-bracketed exactly once
|
|
310
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
311
|
+
const host = hostname === "0.0.0.0" || hostname === "::" ? "127.0.0.1" : hostname;
|
|
312
|
+
return `http://${host.includes(":") ? `[${host}]` : host}:${url.port}/api/challenges/reload`;
|
|
313
|
+
}
|
|
314
|
+
/** How long install/remove wait for a daemon to finish reloading before giving up. */
|
|
315
|
+
export const CHALLENGE_RELOAD_TIMEOUT_MS = 30000;
|
|
316
|
+
/**
|
|
317
|
+
* Ask the daemon at `pkcRpcUrl` to reload its challenge packages, so an install/remove takes
|
|
318
|
+
* effect without a restart. Returns whether a daemon actually reloaded — best-effort, since
|
|
319
|
+
* no daemon running is not an error.
|
|
320
|
+
*
|
|
321
|
+
* The request is bounded: a daemon that accepts the connection but never answers (busy, wedged,
|
|
322
|
+
* or something else listening on the port) would otherwise hold the CLI for undici's 300s
|
|
323
|
+
* default. Aborting only ends our wait — the daemon may still finish the reload.
|
|
324
|
+
*/
|
|
325
|
+
export async function reloadChallengesInDaemon(pkcRpcUrl, timeoutMs = CHALLENGE_RELOAD_TIMEOUT_MS) {
|
|
326
|
+
const reloadUrl = challengeReloadUrlFromPkcRpcUrl(pkcRpcUrl.toString());
|
|
327
|
+
if (!reloadUrl)
|
|
328
|
+
return false;
|
|
329
|
+
try {
|
|
330
|
+
const res = await fetch(reloadUrl, { method: "POST", signal: AbortSignal.timeout(timeoutMs) });
|
|
331
|
+
return res.ok;
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return false; // daemon not running or not answering, that's fine
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Hash of a challenge package's own source, used as the import cache key (see
|
|
338
|
+
// loadChallengesIntoPKC). Only node_modules is skipped: dependencies are pinned by the
|
|
339
|
+
// install that produced this package dir, and hashing them would make every reload walk
|
|
340
|
+
// the entire dependency tree. Dot-prefixed files and directories are hashed — pkg.main
|
|
341
|
+
// may point into one (".dist/index.js"), and skipping them left that entry out of the key,
|
|
342
|
+
// so a same-version replacement kept the old import URL and served the stale module.
|
|
343
|
+
export async function hashChallengePackageContents(challengeDir) {
|
|
344
|
+
const hash = createHash("sha256");
|
|
345
|
+
const walk = async (dir, relativeDir) => {
|
|
346
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
347
|
+
// Sort so the digest does not depend on readdir order
|
|
348
|
+
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
349
|
+
for (const entry of entries) {
|
|
350
|
+
if (entry.name === "node_modules")
|
|
351
|
+
continue;
|
|
352
|
+
const absolutePath = path.join(dir, entry.name);
|
|
353
|
+
const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
354
|
+
if (entry.isDirectory()) {
|
|
355
|
+
await walk(absolutePath, relativePath);
|
|
356
|
+
}
|
|
357
|
+
else if (entry.isFile()) {
|
|
358
|
+
// Frame each record: feeding the path and the raw bytes back to back makes the
|
|
359
|
+
// stream ambiguous, so {index.js:"A", j:"Z"} and {index.js:"AjZ"} would hash
|
|
360
|
+
// identically and a changed entry could reuse the previous cache key
|
|
361
|
+
const contents = await fs.readFile(absolutePath);
|
|
362
|
+
const fileDigest = createHash("sha256").update(contents).digest("hex");
|
|
363
|
+
hash.update(`${relativePath}\0${fileDigest}\0`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
await walk(challengeDir, "");
|
|
368
|
+
return hash.digest("hex").slice(0, 16);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* What each challenge name held in PKC.challenges before this process first registered a package
|
|
372
|
+
* under it. PKC.challenges *is* pkc-js's own registry, so a package named after a built-in
|
|
373
|
+
* ("question") shadows it — unregistering has to hand the built-in back rather than delete the
|
|
374
|
+
* key. `hadPrevious: false` means the name was ours alone and can be deleted.
|
|
375
|
+
*
|
|
376
|
+
* A process serves one data path (the daemon passes its own), so this is not keyed by data path.
|
|
377
|
+
*/
|
|
378
|
+
const shadowedChallenges = new Map();
|
|
291
379
|
export async function loadChallengesIntoPKC(dataPath) {
|
|
292
380
|
const challenges = await listInstalledChallenges(dataPath);
|
|
381
|
+
const PKC = await import("@pkcprotocol/pkc-js");
|
|
382
|
+
const registry = PKC.default.challenges;
|
|
383
|
+
// Hand back any name whose package is no longer installed, so `challenge remove` takes effect
|
|
384
|
+
// without a restart. A package that is still installed but fails to import keeps its
|
|
385
|
+
// previously loaded factory: it is excluded from the returned list either way, and dropping a
|
|
386
|
+
// working challenge because its replacement is broken would take the community's publication
|
|
387
|
+
// flow down instead of leaving it on known-good code.
|
|
388
|
+
const installedNames = new Set(challenges.map((challenge) => challenge.name));
|
|
389
|
+
for (const [name, original] of shadowedChallenges) {
|
|
390
|
+
if (installedNames.has(name))
|
|
391
|
+
continue;
|
|
392
|
+
if (original.hadPrevious)
|
|
393
|
+
registry[name] = original.previous;
|
|
394
|
+
else
|
|
395
|
+
delete registry[name];
|
|
396
|
+
shadowedChallenges.delete(name);
|
|
397
|
+
}
|
|
293
398
|
if (challenges.length === 0)
|
|
294
399
|
return [];
|
|
295
|
-
const PKC = await import("@pkcprotocol/pkc-js");
|
|
296
400
|
const loaded = [];
|
|
297
401
|
for (const challenge of challenges) {
|
|
298
402
|
try {
|
|
@@ -300,9 +404,26 @@ export async function loadChallengesIntoPKC(dataPath) {
|
|
|
300
404
|
// Resolve the entry point
|
|
301
405
|
const entryPoint = pkg.main || "index.js";
|
|
302
406
|
const entryPath = path.resolve(challenge.path, entryPoint);
|
|
303
|
-
|
|
407
|
+
// Node caches ESM modules by URL, and `challenge install` swaps a new build onto
|
|
408
|
+
// the same destination path. Importing the bare path would therefore hand back
|
|
409
|
+
// the module evaluated before the upgrade, so the registry would keep serving the
|
|
410
|
+
// old factory while metadata reports the new version (issue #124). Keying the URL
|
|
411
|
+
// on the package contents re-evaluates the entry whenever the package changes and
|
|
412
|
+
// stays byte-identical (so cached, so factory-identical) when it does not.
|
|
413
|
+
//
|
|
414
|
+
// Only the entry module is re-evaluated: relative imports inside the package do not
|
|
415
|
+
// inherit the query, so a multi-file package graph would keep its stale submodules.
|
|
416
|
+
// Challenge packages are expected to ship a bundled entry point.
|
|
417
|
+
const entryUrl = pathToFileURL(entryPath);
|
|
418
|
+
entryUrl.searchParams.set("bitsocialChallengeContent", await hashChallengePackageContents(challenge.path));
|
|
419
|
+
const imported = await import(entryUrl.href);
|
|
304
420
|
const factory = imported.default || imported;
|
|
305
|
-
|
|
421
|
+
// Remember what this name held before we first took it over, so `challenge remove`
|
|
422
|
+
// can hand it back instead of leaving a dead key (or deleting a pkc-js built-in)
|
|
423
|
+
if (!shadowedChallenges.has(challenge.name)) {
|
|
424
|
+
shadowedChallenges.set(challenge.name, { hadPrevious: challenge.name in registry, previous: registry[challenge.name] });
|
|
425
|
+
}
|
|
426
|
+
registry[challenge.name] = factory;
|
|
306
427
|
loaded.push(challenge);
|
|
307
428
|
}
|
|
308
429
|
catch (err) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class Install extends
|
|
1
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
|
+
export default class Install extends BaseCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static aliases: string[];
|
|
5
5
|
static args: {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Args, Flags
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
3
|
import path from "path";
|
|
3
4
|
import fs from "fs/promises";
|
|
4
5
|
import decompress from "decompress";
|
|
5
6
|
import defaults from "../../../common-utils/defaults.js";
|
|
6
|
-
import { ensureNpmAvailable, ensureChallengesDir, challengeNameToDir, readChallengePackageJson, runNpmPack, runNpmInstall, verifyNativeModuleAbi } from "../../../challenge-packages/challenge-utils.js";
|
|
7
|
-
export default class Install extends
|
|
7
|
+
import { ensureNpmAvailable, ensureChallengesDir, challengeNameToDir, readChallengePackageJson, runNpmPack, runNpmInstall, verifyNativeModuleAbi, reloadChallengesInDaemon } from "../../../challenge-packages/challenge-utils.js";
|
|
8
|
+
export default class Install extends BaseCommand {
|
|
8
9
|
static description = "Install a challenge package (npm package name, git URL, tarball URL, or local path)";
|
|
9
10
|
static aliases = ["challenge:i", "challenge:add"];
|
|
10
11
|
static args = {
|
|
@@ -111,12 +112,10 @@ export default class Install extends Command {
|
|
|
111
112
|
const version = pkg.version ? `@${pkg.version}` : "";
|
|
112
113
|
const elapsedSeconds = Math.max(1, Math.round((Date.now() - startTime) / 1000));
|
|
113
114
|
this.log(`${alreadyExists ? "changed" : "added"} ${pkg.name}${version} in ${elapsedSeconds}s`);
|
|
114
|
-
// 10. Best-effort reload
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
catch {
|
|
119
|
-
// daemon not running, that's fine
|
|
115
|
+
// 10. Best-effort reload in the daemon at --pkcRpcUrl, so the install takes effect
|
|
116
|
+
// without a restart
|
|
117
|
+
if (await reloadChallengesInDaemon(flags.pkcRpcUrl)) {
|
|
118
|
+
this.log(`reloaded ${pkg.name}${version} in the daemon at ${flags.pkcRpcUrl}`);
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
121
|
finally {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class Remove extends
|
|
1
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
|
+
export default class Remove extends BaseCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static aliases: string[];
|
|
5
5
|
static args: {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Args, Flags
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
3
|
import fs from "fs/promises";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import defaults from "../../../common-utils/defaults.js";
|
|
5
|
-
import { getChallengesDir, challengeNameToDir, readChallengePackageJson } from "../../../challenge-packages/challenge-utils.js";
|
|
6
|
-
export default class Remove extends
|
|
6
|
+
import { getChallengesDir, challengeNameToDir, readChallengePackageJson, reloadChallengesInDaemon } from "../../../challenge-packages/challenge-utils.js";
|
|
7
|
+
export default class Remove extends BaseCommand {
|
|
7
8
|
static description = "Remove an installed challenge package";
|
|
8
9
|
static aliases = ["challenge:uninstall", "challenge:rm", "challenge:un"];
|
|
9
10
|
static args = {
|
|
@@ -60,12 +61,8 @@ export default class Remove extends Command {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
this.log(`removed ${args.name}${version}`);
|
|
63
|
-
// Best-effort reload
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
// daemon not running, that's fine
|
|
69
|
-
}
|
|
64
|
+
// Best-effort reload in the daemon at --pkcRpcUrl, so the removal takes effect
|
|
65
|
+
// without a restart
|
|
66
|
+
await reloadChallengesInDaemon(flags.pkcRpcUrl);
|
|
70
67
|
}
|
|
71
68
|
}
|
package/oclif.manifest.json
CHANGED
|
@@ -195,6 +195,15 @@
|
|
|
195
195
|
"bitsocial challenge install ./my-local-challenge"
|
|
196
196
|
],
|
|
197
197
|
"flags": {
|
|
198
|
+
"pkcRpcUrl": {
|
|
199
|
+
"name": "pkcRpcUrl",
|
|
200
|
+
"required": true,
|
|
201
|
+
"summary": "URL to PKC RPC",
|
|
202
|
+
"default": "ws://localhost:9138/",
|
|
203
|
+
"hasDynamicHelp": false,
|
|
204
|
+
"multiple": false,
|
|
205
|
+
"type": "option"
|
|
206
|
+
},
|
|
198
207
|
"pkcOptions.dataPath": {
|
|
199
208
|
"description": "Data path to install the challenge into",
|
|
200
209
|
"name": "pkcOptions.dataPath",
|
|
@@ -284,6 +293,15 @@
|
|
|
284
293
|
"bitsocial challenge remove @scope/my-challenge"
|
|
285
294
|
],
|
|
286
295
|
"flags": {
|
|
296
|
+
"pkcRpcUrl": {
|
|
297
|
+
"name": "pkcRpcUrl",
|
|
298
|
+
"required": true,
|
|
299
|
+
"summary": "URL to PKC RPC",
|
|
300
|
+
"default": "ws://localhost:9138/",
|
|
301
|
+
"hasDynamicHelp": false,
|
|
302
|
+
"multiple": false,
|
|
303
|
+
"type": "option"
|
|
304
|
+
},
|
|
287
305
|
"pkcOptions.dataPath": {
|
|
288
306
|
"description": "Data path where challenges are installed",
|
|
289
307
|
"name": "pkcOptions.dataPath",
|
|
@@ -885,5 +903,5 @@
|
|
|
885
903
|
]
|
|
886
904
|
}
|
|
887
905
|
},
|
|
888
|
-
"version": "0.19.
|
|
906
|
+
"version": "0.19.90"
|
|
889
907
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitsocial/bitsocial-cli",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.90",
|
|
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",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"@oclif/plugin-help": "6.2.36",
|
|
120
120
|
"@oclif/plugin-not-found": "3.2.73",
|
|
121
121
|
"@oclif/table": "0.5.1",
|
|
122
|
-
"@pkcprotocol/pkc-js": "0.0.
|
|
122
|
+
"@pkcprotocol/pkc-js": "0.0.82",
|
|
123
123
|
"dataobject-parser": "1.2.22",
|
|
124
124
|
"decompress": "4.2.1",
|
|
125
125
|
"env-paths": "2.2.1",
|