@did-btcr2/cli 0.5.3 → 0.8.0
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 +137 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +163 -30
- package/dist/esm/src/cli.js +22 -11
- package/dist/esm/src/cli.js.map +1 -1
- package/dist/esm/src/commands/create.js +2 -1
- package/dist/esm/src/commands/create.js.map +1 -1
- package/dist/esm/src/commands/deactivate.js +14 -5
- package/dist/esm/src/commands/deactivate.js.map +1 -1
- package/dist/esm/src/commands/resolve.js +4 -3
- package/dist/esm/src/commands/resolve.js.map +1 -1
- package/dist/esm/src/commands/update.js +15 -5
- package/dist/esm/src/commands/update.js.map +1 -1
- package/dist/esm/src/config.js +142 -0
- package/dist/esm/src/config.js.map +1 -0
- package/dist/esm/src/index.js +1 -0
- package/dist/esm/src/index.js.map +1 -1
- package/dist/types/src/cli.d.ts +10 -5
- package/dist/types/src/cli.d.ts.map +1 -1
- package/dist/types/src/commands/create.d.ts +2 -2
- package/dist/types/src/commands/create.d.ts.map +1 -1
- package/dist/types/src/commands/deactivate.d.ts +2 -2
- package/dist/types/src/commands/deactivate.d.ts.map +1 -1
- package/dist/types/src/commands/resolve.d.ts +2 -2
- package/dist/types/src/commands/resolve.d.ts.map +1 -1
- package/dist/types/src/commands/update.d.ts +2 -2
- package/dist/types/src/commands/update.d.ts.map +1 -1
- package/dist/types/src/config.d.ts +131 -0
- package/dist/types/src/config.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +7 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/cli.ts +22 -12
- package/src/commands/create.ts +3 -2
- package/src/commands/deactivate.ts +22 -6
- package/src/commands/resolve.ts +4 -4
- package/src/commands/update.ts +23 -6
- package/src/config.ts +226 -0
- package/src/index.ts +1 -0
- package/src/types.ts +10 -3
package/src/commands/update.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type { DidBtcr2Api } from '@did-btcr2/api';
|
|
2
1
|
import type { Command } from 'commander';
|
|
2
|
+
import { deriveNetwork, type ApiFactory } from '../config.js';
|
|
3
3
|
import { CLIError } from '../error.js';
|
|
4
|
-
import { formatResult } from '../output.js';
|
|
5
4
|
import type { GlobalOptions, UpdateCommandOptions } from '../types.js';
|
|
6
5
|
|
|
7
6
|
export function registerUpdateCommand(
|
|
8
7
|
program : Command,
|
|
9
|
-
|
|
8
|
+
factory : ApiFactory,
|
|
10
9
|
globals : () => GlobalOptions,
|
|
11
10
|
): void {
|
|
12
11
|
program
|
|
@@ -49,9 +48,27 @@ export function registerUpdateCommand(
|
|
|
49
48
|
verificationMethodId : options.verificationMethodId,
|
|
50
49
|
beaconId : options.beaconId as UpdateCommandOptions['beaconId'],
|
|
51
50
|
};
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
const did = parsed.sourceDocument?.id;
|
|
52
|
+
if (!did) {
|
|
53
|
+
throw new CLIError(
|
|
54
|
+
'Source document must contain an "id" field.',
|
|
55
|
+
'INVALID_ARGUMENT_ERROR',
|
|
56
|
+
options
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
// The CLI does not yet have a way to load signing material (keystore,
|
|
60
|
+
// KMS config, hardware wallet, etc.), so signed updates from the CLI are
|
|
61
|
+
// not yet wired up. Drive the SDK directly with a `Signer` for now.
|
|
62
|
+
// Variables above are kept so command parsing + validation still works.
|
|
63
|
+
void deriveNetwork(did);
|
|
64
|
+
void factory;
|
|
65
|
+
void globals;
|
|
66
|
+
void parsed;
|
|
67
|
+
throw new CLIError(
|
|
68
|
+
'CLI signing is not yet implemented. Use @did-btcr2/api with a Signer directly.',
|
|
69
|
+
'NOT_IMPLEMENTED_ERROR',
|
|
70
|
+
{ command: 'update' }
|
|
71
|
+
);
|
|
55
72
|
});
|
|
56
73
|
}
|
|
57
74
|
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { createApi, Identifier, type BitcoinApiConfig, type DidBtcr2Api } from '@did-btcr2/api';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { CLIError } from './error.js';
|
|
6
|
+
import { SUPPORTED_NETWORKS, type NetworkOption } from './types.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Endpoint overrides provided via CLI flags, env vars, or config file.
|
|
10
|
+
* These override the per-network defaults from
|
|
11
|
+
* `DEFAULT_BITCOIN_NETWORK_CONFIG`.
|
|
12
|
+
*
|
|
13
|
+
* `config` and `profile` control config-file resolution and are only
|
|
14
|
+
* meaningful when passed through the full merge chain.
|
|
15
|
+
*/
|
|
16
|
+
export type ConnectionOverrides = {
|
|
17
|
+
btcRest? : string;
|
|
18
|
+
btcRpcUrl? : string;
|
|
19
|
+
btcRpcUser?: string;
|
|
20
|
+
btcRpcPass?: string;
|
|
21
|
+
casGateway?: string;
|
|
22
|
+
config? : string;
|
|
23
|
+
profile? : string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* On-disk config file schema.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```json
|
|
31
|
+
* {
|
|
32
|
+
* "profiles": {
|
|
33
|
+
* "regtest": {
|
|
34
|
+
* "btc": {
|
|
35
|
+
* "rest": "http://localhost:3000",
|
|
36
|
+
* "rpcUrl": "http://localhost:18443",
|
|
37
|
+
* "rpcUser": "polaruser",
|
|
38
|
+
* "rpcPass": "polarpass"
|
|
39
|
+
* }
|
|
40
|
+
* },
|
|
41
|
+
* "bitcoin": {
|
|
42
|
+
* "btc": { "rest": "https://my-mempool/api" },
|
|
43
|
+
* "cas": { "gateway": "https://ipfs.io" }
|
|
44
|
+
* }
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export type ConfigFile = {
|
|
50
|
+
profiles?: Record<string, {
|
|
51
|
+
btc?: {
|
|
52
|
+
rest? : string;
|
|
53
|
+
rpcUrl? : string;
|
|
54
|
+
rpcUser? : string;
|
|
55
|
+
rpcPass? : string;
|
|
56
|
+
};
|
|
57
|
+
cas?: {
|
|
58
|
+
gateway?: string;
|
|
59
|
+
};
|
|
60
|
+
}>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Factory function that creates a configured {@link DidBtcr2Api} instance.
|
|
65
|
+
*
|
|
66
|
+
* When `network` is provided, the returned API is wired to that network's
|
|
67
|
+
* default Bitcoin endpoints (mempool.space for public networks, localhost
|
|
68
|
+
* Polar for regtest). Optional `overrides` let callers replace individual
|
|
69
|
+
* endpoints on top of the defaults. When `network` is omitted, no Bitcoin
|
|
70
|
+
* or CAS is configured — suitable for offline operations like `create`.
|
|
71
|
+
*/
|
|
72
|
+
export type ApiFactory = (network?: NetworkOption, overrides?: ConnectionOverrides) => DidBtcr2Api;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Environment variable names consulted by {@link defaultApiFactory}.
|
|
76
|
+
*
|
|
77
|
+
* | Variable | Equivalent flag |
|
|
78
|
+
* |-----------------------|--------------------|
|
|
79
|
+
* | `BTCR2_BTC_REST` | `--btc-rest` |
|
|
80
|
+
* | `BTCR2_BTC_RPC_URL` | `--btc-rpc-url` |
|
|
81
|
+
* | `BTCR2_BTC_RPC_USER` | `--btc-rpc-user` |
|
|
82
|
+
* | `BTCR2_BTC_RPC_PASS` | `--btc-rpc-pass` |
|
|
83
|
+
* | `BTCR2_CAS_GATEWAY` | `--cas-gateway` |
|
|
84
|
+
*/
|
|
85
|
+
export const ENV_VARS = {
|
|
86
|
+
BTC_REST : 'BTCR2_BTC_REST',
|
|
87
|
+
BTC_RPC_URL : 'BTCR2_BTC_RPC_URL',
|
|
88
|
+
BTC_RPC_USER : 'BTCR2_BTC_RPC_USER',
|
|
89
|
+
BTC_RPC_PASS : 'BTCR2_BTC_RPC_PASS',
|
|
90
|
+
CAS_GATEWAY : 'BTCR2_CAS_GATEWAY',
|
|
91
|
+
} as const;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Reads {@link ConnectionOverrides} from environment variables.
|
|
95
|
+
* Only defined (non-empty) values are included.
|
|
96
|
+
*/
|
|
97
|
+
export function readEnvOverrides(): ConnectionOverrides {
|
|
98
|
+
const env = (key: string): string | undefined => process.env[key] || undefined;
|
|
99
|
+
return {
|
|
100
|
+
btcRest : env(ENV_VARS.BTC_REST),
|
|
101
|
+
btcRpcUrl : env(ENV_VARS.BTC_RPC_URL),
|
|
102
|
+
btcRpcUser : env(ENV_VARS.BTC_RPC_USER),
|
|
103
|
+
btcRpcPass : env(ENV_VARS.BTC_RPC_PASS),
|
|
104
|
+
casGateway : env(ENV_VARS.CAS_GATEWAY),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Default config file path following the XDG Base Directory Specification.
|
|
110
|
+
*
|
|
111
|
+
* Resolution order:
|
|
112
|
+
* 1. `$XDG_CONFIG_HOME/btcr2/config.json`
|
|
113
|
+
* 2. `%APPDATA%/btcr2/config.json` (Windows)
|
|
114
|
+
* 3. `~/.config/btcr2/config.json` (fallback)
|
|
115
|
+
*/
|
|
116
|
+
export function defaultConfigPath(): string {
|
|
117
|
+
const base = process.env.XDG_CONFIG_HOME
|
|
118
|
+
?? process.env.APPDATA
|
|
119
|
+
?? join(homedir(), '.config');
|
|
120
|
+
return join(base, 'btcr2', 'config.json');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Reads and parses a config file. Returns `undefined` if the file does
|
|
125
|
+
* not exist or cannot be parsed.
|
|
126
|
+
*/
|
|
127
|
+
export function readConfigFile(path: string): ConfigFile | undefined {
|
|
128
|
+
try {
|
|
129
|
+
const content = readFileSync(path, 'utf-8');
|
|
130
|
+
return JSON.parse(content) as ConfigFile;
|
|
131
|
+
} catch {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Extracts {@link ConnectionOverrides} from a named profile in a
|
|
138
|
+
* {@link ConfigFile}. Returns an empty object if the profile does not exist.
|
|
139
|
+
*/
|
|
140
|
+
export function profileToOverrides(
|
|
141
|
+
config : ConfigFile,
|
|
142
|
+
profileName : string,
|
|
143
|
+
): ConnectionOverrides {
|
|
144
|
+
const profile = config.profiles?.[profileName];
|
|
145
|
+
if (!profile) return {};
|
|
146
|
+
return {
|
|
147
|
+
btcRest : profile.btc?.rest,
|
|
148
|
+
btcRpcUrl : profile.btc?.rpcUrl,
|
|
149
|
+
btcRpcUser : profile.btc?.rpcUser,
|
|
150
|
+
btcRpcPass : profile.btc?.rpcPass,
|
|
151
|
+
casGateway : profile.cas?.gateway,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Default {@link ApiFactory} backed by network defaults from
|
|
157
|
+
* `@did-btcr2/bitcoin` (mempool.space for public networks, localhost for
|
|
158
|
+
* regtest).
|
|
159
|
+
*
|
|
160
|
+
* Override precedence (highest wins):
|
|
161
|
+
* CLI flags → env vars → config file profile → network defaults.
|
|
162
|
+
*
|
|
163
|
+
* When no `--profile` is given, the network name is used as the profile
|
|
164
|
+
* key (e.g. a regtest DID auto-selects the `"regtest"` profile).
|
|
165
|
+
*/
|
|
166
|
+
export function defaultApiFactory(network?: NetworkOption, overrides?: ConnectionOverrides): DidBtcr2Api {
|
|
167
|
+
if (!network) return createApi();
|
|
168
|
+
|
|
169
|
+
// Layer 1: Config file profile (lowest precedence of the three override layers)
|
|
170
|
+
const configPath = overrides?.config ?? defaultConfigPath();
|
|
171
|
+
const profileName = overrides?.profile ?? network;
|
|
172
|
+
const file = readConfigFile(configPath);
|
|
173
|
+
const fileOverrides = file ? profileToOverrides(file, profileName) : {};
|
|
174
|
+
|
|
175
|
+
// Layer 2: Environment variables
|
|
176
|
+
const env = readEnvOverrides();
|
|
177
|
+
|
|
178
|
+
// Merge: CLI flags → env vars → config file → (network defaults handled by BitcoinConnection)
|
|
179
|
+
const merged: ConnectionOverrides = {
|
|
180
|
+
btcRest : overrides?.btcRest ?? env.btcRest ?? fileOverrides.btcRest,
|
|
181
|
+
btcRpcUrl : overrides?.btcRpcUrl ?? env.btcRpcUrl ?? fileOverrides.btcRpcUrl,
|
|
182
|
+
btcRpcUser : overrides?.btcRpcUser ?? env.btcRpcUser ?? fileOverrides.btcRpcUser,
|
|
183
|
+
btcRpcPass : overrides?.btcRpcPass ?? env.btcRpcPass ?? fileOverrides.btcRpcPass,
|
|
184
|
+
casGateway : overrides?.casGateway ?? env.casGateway ?? fileOverrides.casGateway,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const btc: BitcoinApiConfig = { network };
|
|
188
|
+
|
|
189
|
+
if (merged.btcRest) {
|
|
190
|
+
btc.rest = { host: merged.btcRest };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (merged.btcRpcUrl) {
|
|
194
|
+
btc.rpc = {
|
|
195
|
+
host : merged.btcRpcUrl,
|
|
196
|
+
username : merged.btcRpcUser,
|
|
197
|
+
password : merged.btcRpcPass,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const cas = merged.casGateway ? { gateway: merged.casGateway } : undefined;
|
|
202
|
+
|
|
203
|
+
return createApi({ btc, ...(cas && { cas }) });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Extracts and validates the Bitcoin network from a DID string.
|
|
208
|
+
*
|
|
209
|
+
* Decodes the DID via {@link Identifier.decode}, then checks that the
|
|
210
|
+
* embedded network is one of the supported values.
|
|
211
|
+
*
|
|
212
|
+
* @param did A `did:btcr2:...` identifier string.
|
|
213
|
+
* @returns The validated {@link NetworkOption}.
|
|
214
|
+
* @throws {CLIError} If the network is unsupported.
|
|
215
|
+
*/
|
|
216
|
+
export function deriveNetwork(did: string): NetworkOption {
|
|
217
|
+
const { network } = Identifier.decode(did);
|
|
218
|
+
if (!SUPPORTED_NETWORKS.includes(network as NetworkOption)) {
|
|
219
|
+
throw new CLIError(
|
|
220
|
+
`Unsupported network "${network}" in DID.`,
|
|
221
|
+
'INVALID_ARGUMENT_ERROR',
|
|
222
|
+
{ did, network }
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return network as NetworkOption;
|
|
226
|
+
}
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -36,7 +36,14 @@ export type CommandResult =
|
|
|
36
36
|
| { action: 'deactivate'; data: SignedBTCR2Update };
|
|
37
37
|
|
|
38
38
|
export interface GlobalOptions {
|
|
39
|
-
output
|
|
40
|
-
verbose
|
|
41
|
-
quiet
|
|
39
|
+
output : OutputFormat;
|
|
40
|
+
verbose : boolean;
|
|
41
|
+
quiet : boolean;
|
|
42
|
+
config? : string;
|
|
43
|
+
profile? : string;
|
|
44
|
+
btcRest? : string;
|
|
45
|
+
btcRpcUrl? : string;
|
|
46
|
+
btcRpcUser?: string;
|
|
47
|
+
btcRpcPass?: string;
|
|
48
|
+
casGateway?: string;
|
|
42
49
|
}
|