@did-btcr2/cli 0.3.1 → 0.5.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/LICENSE +373 -1
- package/dist/cjs/cli.js +50 -264
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/commands/create.js +48 -0
- package/dist/cjs/commands/create.js.map +1 -0
- package/dist/cjs/commands/deactivate.js +41 -0
- package/dist/cjs/commands/deactivate.js.map +1 -0
- package/dist/cjs/commands/index.js +5 -0
- package/dist/cjs/commands/index.js.map +1 -0
- package/dist/cjs/commands/resolve.js +43 -0
- package/dist/cjs/commands/resolve.js.map +1 -0
- package/dist/cjs/commands/update.js +39 -0
- package/dist/cjs/commands/update.js.map +1 -0
- package/dist/cjs/error.js +1 -1
- package/dist/cjs/error.js.map +1 -1
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output.js +16 -0
- package/dist/cjs/output.js.map +1 -0
- package/dist/cjs/types.js +4 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/version.js +22 -0
- package/dist/cjs/version.js.map +1 -0
- package/dist/esm/src/cli.js +50 -264
- package/dist/esm/src/cli.js.map +1 -1
- package/dist/esm/src/commands/create.js +48 -0
- package/dist/esm/src/commands/create.js.map +1 -0
- package/dist/esm/src/commands/deactivate.js +41 -0
- package/dist/esm/src/commands/deactivate.js.map +1 -0
- package/dist/esm/src/commands/index.js +5 -0
- package/dist/esm/src/commands/index.js.map +1 -0
- package/dist/esm/src/commands/resolve.js +43 -0
- package/dist/esm/src/commands/resolve.js.map +1 -0
- package/dist/esm/src/commands/update.js +39 -0
- package/dist/esm/src/commands/update.js.map +1 -0
- package/dist/esm/src/error.js +1 -1
- package/dist/esm/src/error.js.map +1 -1
- package/dist/esm/src/index.js +4 -1
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/output.js +16 -0
- package/dist/esm/src/output.js.map +1 -0
- package/dist/esm/src/types.js +4 -0
- package/dist/esm/src/types.js.map +1 -0
- package/dist/esm/src/version.js +22 -0
- package/dist/esm/src/version.js.map +1 -0
- package/dist/types/src/cli.d.ts +7 -98
- package/dist/types/src/cli.d.ts.map +1 -1
- package/dist/types/src/commands/create.d.ts +5 -0
- package/dist/types/src/commands/create.d.ts.map +1 -0
- package/dist/types/src/commands/deactivate.d.ts +5 -0
- package/dist/types/src/commands/deactivate.d.ts.map +1 -0
- package/dist/types/src/commands/index.d.ts +5 -0
- package/dist/types/src/commands/index.d.ts.map +1 -0
- package/dist/types/src/commands/resolve.d.ts +5 -0
- package/dist/types/src/commands/resolve.d.ts.map +1 -0
- package/dist/types/src/commands/update.d.ts +5 -0
- package/dist/types/src/commands/update.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/output.d.ts +11 -0
- package/dist/types/src/output.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +42 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/src/version.d.ts +2 -0
- package/dist/types/src/version.d.ts.map +1 -0
- package/package.json +7 -7
- package/src/cli.ts +61 -348
- package/src/commands/create.ts +87 -0
- package/src/commands/deactivate.ts +72 -0
- package/src/commands/index.ts +4 -0
- package/src/commands/resolve.ts +65 -0
- package/src/commands/update.ts +74 -0
- package/src/error.ts +2 -2
- package/src/index.ts +4 -1
- package/src/output.ts +17 -0
- package/src/types.ts +42 -0
- package/src/version.ts +21 -0
- package/dist/cjs/command.js +0 -35
- package/dist/cjs/command.js.map +0 -1
- package/dist/esm/src/command.js +0 -35
- package/dist/esm/src/command.js.map +0 -1
- package/dist/types/cli.d.ts +0 -111
- package/dist/types/command.d.ts +0 -55
- package/dist/types/error.d.ts +0 -7
- package/dist/types/index.d.ts +0 -3
- package/dist/types/src/command.d.ts +0 -56
- package/dist/types/src/command.d.ts.map +0 -1
- package/src/command.ts +0 -77
package/src/cli.ts
CHANGED
|
@@ -1,374 +1,87 @@
|
|
|
1
|
-
import { Identifier } from '@did-btcr2/method';
|
|
2
1
|
import { Command, CommanderError } from 'commander';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
UpdateCommandOptions,
|
|
11
|
-
} from './command.js';
|
|
2
|
+
import { DidBtcr2Api, createApi } from '@did-btcr2/api';
|
|
3
|
+
import {
|
|
4
|
+
registerCreateCommand,
|
|
5
|
+
registerDeactivateCommand,
|
|
6
|
+
registerResolveCommand,
|
|
7
|
+
registerUpdateCommand,
|
|
8
|
+
} from './commands/index.js';
|
|
12
9
|
import { CLIError } from './error.js';
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
import { GlobalOptions } from './types.js';
|
|
11
|
+
import { VERSION } from './version.js';
|
|
15
12
|
|
|
16
13
|
/**
|
|
17
14
|
* CLI tool for the did:btcr2 method.
|
|
18
|
-
* @type {DidBtcr2Cli}
|
|
19
|
-
* @class DidBtcr2Cli
|
|
20
15
|
*/
|
|
21
16
|
export class DidBtcr2Cli {
|
|
22
17
|
public readonly program: Command;
|
|
23
|
-
|
|
24
|
-
constructor() {
|
|
25
|
-
this.program = new Command('btcr2')
|
|
26
|
-
.version('btcr2 0.1.0', '-v, --version', 'Output the current version')
|
|
27
|
-
.description('CLI tool for the did:btcr2 method');
|
|
28
|
-
|
|
29
|
-
this.configureCommands();
|
|
30
|
-
}
|
|
18
|
+
private readonly api: DidBtcr2Api;
|
|
31
19
|
|
|
32
20
|
/**
|
|
33
|
-
*
|
|
34
|
-
* @
|
|
21
|
+
* Initializes the CLI with an optional pre-configured API instance.
|
|
22
|
+
* @param {DidBtcr2Api} api - Optional API instance. Defaults to an unconfigured instance.
|
|
35
23
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.program
|
|
39
|
-
.
|
|
40
|
-
.description('
|
|
41
|
-
.
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
52
|
-
.action(async (options: { type: string; network: string; bytes: string }) => {
|
|
53
|
-
const parsedOptions = this.parseCreateOptions(options);
|
|
54
|
-
const result = await this.invokeCommand({ options: parsedOptions, action: 'create', command: new Btcr2Command() });
|
|
55
|
-
this.printResult(result);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Resolve command
|
|
59
|
-
this.program
|
|
60
|
-
.command('resolve')
|
|
61
|
-
.alias('read')
|
|
62
|
-
.description('Resolve the DID document of the identifier.')
|
|
63
|
-
.requiredOption('-i, --identifier <identifier>', 'did:btcr2 identifier')
|
|
64
|
-
.option('-r, --resolutionOptions <resolutionOptions>', 'JSON string containing resolution options')
|
|
65
|
-
.option('-p, --resolutionOptionsPath <resolutionOptionsPath>', 'Path to a JSON file containing resolution options')
|
|
66
|
-
.action(async (options: { identifier: string; resolutionOptions?: string; resolutionOptionsPath?: string }) => {
|
|
67
|
-
const parsedOptions = await this.parseResolveOptions(options);
|
|
68
|
-
const result = await this.invokeCommand({ options: parsedOptions, action: 'resolve', command: new Btcr2Command() });
|
|
69
|
-
this.printResult(result);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Update command
|
|
73
|
-
this.program
|
|
74
|
-
.command('update')
|
|
75
|
-
.description('Update a did:btcr2 document.')
|
|
76
|
-
.requiredOption('-i, --identifier <identifier>', 'did:btcr2 identifier')
|
|
77
|
-
.requiredOption('-s, --sourceDocument <sourceDocument>', 'Source DID document as JSON string')
|
|
78
|
-
.requiredOption('-v, --sourceVersionId <sourceVersionId>', 'Source version ID as a number')
|
|
79
|
-
.requiredOption('-p, --patch <patch>', 'JSON Patch operations as a JSON string array')
|
|
80
|
-
.requiredOption('-m, --verificationMethodId <verificationMethodId>', 'Did document verification method ID as a string')
|
|
81
|
-
.requiredOption('-b, --beaconIds <beaconIds>', 'Beacon IDs as a JSON string array')
|
|
82
|
-
.action(async (options: {
|
|
83
|
-
sourceDocument: string;
|
|
84
|
-
sourceVersionId: number;
|
|
85
|
-
patches: string;
|
|
86
|
-
verificationMethodId: string;
|
|
87
|
-
beaconId: string;
|
|
88
|
-
}) => {
|
|
89
|
-
const parsedOptions = this.parseUpdateOptions(options);
|
|
90
|
-
const result = await this.invokeCommand({ options: parsedOptions, action: 'update', command: new Btcr2Command() });
|
|
91
|
-
this.printResult(result);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Deactivate command
|
|
95
|
-
this.program
|
|
96
|
-
.command('deactivate')
|
|
97
|
-
.alias('delete')
|
|
98
|
-
.description('Deactivate the did:btcr2 identifier permanently.')
|
|
99
|
-
.action(async () => {
|
|
100
|
-
const result = await this.invokeCommand({ options: {}, action: 'deactivate', command: new Btcr2Command() });
|
|
101
|
-
this.printResult(result);
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Invokes a command with the provided request.
|
|
107
|
-
* @param {object} request The command request
|
|
108
|
-
* @param {any} request.options The command options
|
|
109
|
-
* @param {CommandRequest['action']} request.action The command action
|
|
110
|
-
* @param {Btcr2Command} request.command The command instance
|
|
111
|
-
* @returns {Promise<CommandResult>} The command result
|
|
112
|
-
*/
|
|
113
|
-
private async invokeCommand(request: {
|
|
114
|
-
options: any;
|
|
115
|
-
action: CommandRequest['action'];
|
|
116
|
-
command: Btcr2Command;
|
|
117
|
-
}): Promise<CommandResult> {
|
|
118
|
-
return await request.command.execute({ action: request.action, options: request.options } as CommandRequest);
|
|
24
|
+
constructor(api: DidBtcr2Api = createApi()) {
|
|
25
|
+
this.api = api;
|
|
26
|
+
this.program = new Command('btcr2')
|
|
27
|
+
.version(`btcr2 ${VERSION}`, '-v, --version', 'Output the current version')
|
|
28
|
+
.description('CLI tool for the did:btcr2 method')
|
|
29
|
+
.option('-o, --output <format>', 'Output format <json|text>', 'text')
|
|
30
|
+
.option('--verbose', 'Verbose output', false)
|
|
31
|
+
.option('--quiet', 'Suppress non-essential output', false);
|
|
32
|
+
|
|
33
|
+
const globals = (): GlobalOptions => this.program.opts() as GlobalOptions;
|
|
34
|
+
|
|
35
|
+
registerCreateCommand(this.program, this.api, globals);
|
|
36
|
+
registerResolveCommand(this.program, this.api, globals);
|
|
37
|
+
registerUpdateCommand(this.program, this.api, globals);
|
|
38
|
+
registerDeactivateCommand(this.program, this.api, globals);
|
|
119
39
|
}
|
|
120
40
|
|
|
121
41
|
/**
|
|
122
42
|
* Runs the CLI with the provided argv or process.argv.
|
|
123
|
-
* @param {string[]} [argv]
|
|
43
|
+
* @param {string[]} [argv] - Optional array of command-line arguments.
|
|
44
|
+
* @returns {Promise<void>} - Resolves when execution is complete.
|
|
124
45
|
*/
|
|
125
|
-
public async run(argv?: string[]) {
|
|
46
|
+
public async run(argv?: string[]): Promise<void> {
|
|
126
47
|
try {
|
|
127
|
-
const normalized =
|
|
48
|
+
const normalized = normalizeArgv(argv ?? process.argv);
|
|
128
49
|
await this.program.parseAsync(normalized, { from: 'node' });
|
|
129
50
|
if (!this.program.args.length) this.program.outputHelp();
|
|
130
|
-
} catch (error:
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Normalizes argv to ensure it has at least two elements.
|
|
137
|
-
* @param {string[]} argv The argv array to normalize
|
|
138
|
-
* @returns {string[]} Normalized argv array
|
|
139
|
-
*/
|
|
140
|
-
private normalizeArgv(argv: string[]): string[] {
|
|
141
|
-
if (argv.length >= 2) return argv;
|
|
142
|
-
if (argv.length === 1) return ['node', argv[0]];
|
|
143
|
-
return ['node', 'btcr2'];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Handles errors thrown during CLI execution.
|
|
148
|
-
* @param {unknown} error The error to handle
|
|
149
|
-
* @returns {void}
|
|
150
|
-
*/
|
|
151
|
-
private handleError(error: unknown): void {
|
|
152
|
-
if (error instanceof CommanderError && (error.code === 'commander.helpDisplayed' || error.code === 'commander.help')) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (error instanceof CLIError) {
|
|
156
|
-
console.error(error.message);
|
|
157
|
-
process.exitCode ??= 1;
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
console.error(error);
|
|
161
|
-
process.exitCode ??= 1;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Parses create command options and throws CLIError on invalid input.
|
|
166
|
-
* @param {object} options The create command options
|
|
167
|
-
* @param {string} options.type The identifier type
|
|
168
|
-
* @param {string} options.network The bitcoin network
|
|
169
|
-
* @param {string} options.bytes The genesis bytes as a hex string
|
|
170
|
-
* @returns {CreateCommandOptions} The parsed create command options
|
|
171
|
-
*/
|
|
172
|
-
private parseCreateOptions(options: { type: string; network: string; bytes: string; }): CreateCommandOptions {
|
|
173
|
-
if (!['k', 'x'].includes(options.type)) {
|
|
174
|
-
throw new CLIError(
|
|
175
|
-
'Invalid type. Must be "k" or "x".',
|
|
176
|
-
'INVALID_ARGUMENT_ERROR',
|
|
177
|
-
options
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
if (!this.isNetworkValid(options.network)) {
|
|
181
|
-
throw new CLIError(
|
|
182
|
-
'Invalid network. Must be one of "bitcoin", "testnet3", "testnet4", "signet", "mutinynet", or "regtest".',
|
|
183
|
-
'INVALID_ARGUMENT_ERROR',
|
|
184
|
-
options
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
if (Buffer.from(options.bytes, 'hex').length === 0) {
|
|
188
|
-
throw new CLIError(
|
|
189
|
-
'Invalid bytes. Must be a non-empty hex string.',
|
|
190
|
-
'INVALID_ARGUMENT_ERROR',
|
|
191
|
-
options
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
return {
|
|
195
|
-
type : options.type as CreateCommandOptions['type'],
|
|
196
|
-
network : options.network as NetworkOption,
|
|
197
|
-
bytes : options.bytes,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Parses resolve command options and throws CLIError on invalid input.
|
|
203
|
-
* @param {object} options The resolve command options
|
|
204
|
-
* @param {string} options.identifier The did:btcr2 identifier
|
|
205
|
-
* @param {string} [options.resolutionOptions] JSON string of resolution options
|
|
206
|
-
* @param {string} [options.resolutionOptionsPath] Path to a JSON file of resolution options
|
|
207
|
-
* @returns {Promise<ResolveCommandOptions>} The parsed resolve command options
|
|
208
|
-
*/
|
|
209
|
-
private async parseResolveOptions(options: {
|
|
210
|
-
identifier: string;
|
|
211
|
-
resolutionOptions?: string;
|
|
212
|
-
resolutionOptionsPath?: string;
|
|
213
|
-
}): Promise<ResolveCommandOptions> {
|
|
214
|
-
this.validateIdentifier(options.identifier, options);
|
|
215
|
-
const resolutionOptions = await this.parseResolutionOptions(options);
|
|
216
|
-
return {
|
|
217
|
-
identifier : options.identifier,
|
|
218
|
-
...(resolutionOptions && { options: resolutionOptions }),
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Parses update command options and throws CLIError on invalid input.
|
|
224
|
-
* @param {object} options The update command options
|
|
225
|
-
* @param {string} options.sourceDocument The source DID document as a JSON string
|
|
226
|
-
* @param {number} options.sourceVersionId The source version ID as a number
|
|
227
|
-
* @param {string} options.patches The JSON Patch operations as a JSON string array
|
|
228
|
-
* @param {string} options.verificationMethodId The DID document verification method ID as a string
|
|
229
|
-
* @param {string} options.beaconId The beacon IDs as a JSON string array
|
|
230
|
-
* @returns {UpdateCommandOptions} The parsed update command options
|
|
231
|
-
*/
|
|
232
|
-
private parseUpdateOptions(options: {
|
|
233
|
-
sourceDocument: string;
|
|
234
|
-
sourceVersionId: number;
|
|
235
|
-
patches: string;
|
|
236
|
-
verificationMethodId: string;
|
|
237
|
-
beaconId: string;
|
|
238
|
-
}): UpdateCommandOptions {
|
|
239
|
-
const sourceDocument = this.parseJsonOption<UpdateCommandOptions['sourceDocument']>(
|
|
240
|
-
options.sourceDocument,
|
|
241
|
-
'Invalid options. Must be a valid JSON string.',
|
|
242
|
-
options
|
|
243
|
-
);
|
|
244
|
-
const patches = this.parseJsonOption<UpdateCommandOptions['patches']>(
|
|
245
|
-
options.patches,
|
|
246
|
-
'Invalid options. Must be a valid JSON string.',
|
|
247
|
-
options
|
|
248
|
-
);
|
|
249
|
-
const beaconId = this.parseJsonOption<UpdateCommandOptions['beaconId']>(
|
|
250
|
-
options.beaconId,
|
|
251
|
-
'Invalid options. Must be a valid JSON string.',
|
|
252
|
-
options
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
return {
|
|
256
|
-
sourceDocument,
|
|
257
|
-
sourceVersionId : Number(options.sourceVersionId),
|
|
258
|
-
patches,
|
|
259
|
-
verificationMethodId : options.verificationMethodId,
|
|
260
|
-
beaconId,
|
|
261
|
-
} as UpdateCommandOptions;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Parses a JSON option and throws a CLIError on failure.
|
|
266
|
-
* @param {string} value JSON string to parse
|
|
267
|
-
* @param {string} errorMessage Error message to use on failure
|
|
268
|
-
* @param {Record<string, any>} data Additional data to include in the error
|
|
269
|
-
* @returns {T} Parsed JSON object
|
|
270
|
-
*/
|
|
271
|
-
private parseJsonOption<T>(value: string, errorMessage: string, data: Record<string, any>): T {
|
|
272
|
-
try {
|
|
273
|
-
return JSON.parse(value) as T;
|
|
274
|
-
} catch {
|
|
275
|
-
throw new CLIError(
|
|
276
|
-
errorMessage,
|
|
277
|
-
'INVALID_ARGUMENT_ERROR',
|
|
278
|
-
data
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Parses resolution options from JSON string or file path.
|
|
285
|
-
* @param {object} options The options containing resolution options
|
|
286
|
-
* @param {string} [options.resolutionOptions] JSON string of resolution options
|
|
287
|
-
* @param {string} [options.resolutionOptionsPath] Path to a JSON file of resolution options
|
|
288
|
-
* @returns {Promise<any>} The parsed resolution options
|
|
289
|
-
*/
|
|
290
|
-
private async parseResolutionOptions(options: { resolutionOptions?: string; resolutionOptionsPath?: string; }): Promise<any> {
|
|
291
|
-
if (options.resolutionOptions) {
|
|
292
|
-
return this.parseJsonOption(options.resolutionOptions, 'Invalid options. Must be a valid JSON string.', options);
|
|
293
|
-
}
|
|
294
|
-
if (options.resolutionOptionsPath) {
|
|
295
|
-
try {
|
|
296
|
-
const data = await readFile(options.resolutionOptionsPath, 'utf-8');
|
|
297
|
-
return JSON.parse(data);
|
|
298
|
-
} catch {
|
|
299
|
-
throw new CLIError(
|
|
300
|
-
'Invalid options path. Must be a valid path to a JSON file.',
|
|
301
|
-
'INVALID_ARGUMENT_ERROR',
|
|
302
|
-
options
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return undefined;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Validates the did:btcr2 identifier format.
|
|
311
|
-
* @param {string} identifier The identifier to validate
|
|
312
|
-
* @param {Record<string, any>} data Additional data to include in the error
|
|
313
|
-
* @returns {void}
|
|
314
|
-
*/
|
|
315
|
-
private validateIdentifier(identifier: string, data: Record<string, any>): void {
|
|
316
|
-
try {
|
|
317
|
-
Identifier.decode(identifier);
|
|
318
|
-
} catch {
|
|
319
|
-
throw new CLIError(
|
|
320
|
-
'Invalid identifier. Must be a valid did:btcr2 identifier.',
|
|
321
|
-
'INVALID_ARGUMENT_ERROR',
|
|
322
|
-
data
|
|
323
|
-
);
|
|
51
|
+
} catch (error: unknown) {
|
|
52
|
+
handleError(error);
|
|
324
53
|
}
|
|
325
54
|
}
|
|
55
|
+
}
|
|
326
56
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Normalizes argv to ensure it has at least two elements.
|
|
59
|
+
* @param {string[]} argv - The original argv array.
|
|
60
|
+
* @returns {string[]} - The normalized argv array.
|
|
61
|
+
*/
|
|
62
|
+
function normalizeArgv(argv: string[]): string[] {
|
|
63
|
+
if (argv.length >= 2) return argv;
|
|
64
|
+
if (argv.length === 1) return ['node', argv[0]];
|
|
65
|
+
return ['node', 'btcr2'];
|
|
66
|
+
}
|
|
335
67
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
case 'read':
|
|
348
|
-
this.log(result.resolution);
|
|
349
|
-
break;
|
|
350
|
-
case 'update':
|
|
351
|
-
this.log(result.signed);
|
|
352
|
-
break;
|
|
353
|
-
case 'deactivate':
|
|
354
|
-
case 'delete':
|
|
355
|
-
this.log(result.message);
|
|
356
|
-
break;
|
|
357
|
-
default:
|
|
358
|
-
this.log(result);
|
|
359
|
-
}
|
|
68
|
+
/**
|
|
69
|
+
* Handles errors thrown during CLI execution.
|
|
70
|
+
* @param {unknown} error - The error to handle.
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
function handleError(error: unknown): void {
|
|
74
|
+
if (
|
|
75
|
+
error instanceof CommanderError &&
|
|
76
|
+
(error.code === 'commander.helpDisplayed' || error.code === 'commander.help')
|
|
77
|
+
) {
|
|
78
|
+
return;
|
|
360
79
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
* @returns {void}
|
|
366
|
-
*/
|
|
367
|
-
private log(payload: unknown): void {
|
|
368
|
-
if (typeof payload === 'string') {
|
|
369
|
-
console.log(payload);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
80
|
+
if (error instanceof CLIError) {
|
|
81
|
+
console.error(error.message);
|
|
82
|
+
process.exitCode ??= 1;
|
|
83
|
+
return;
|
|
373
84
|
}
|
|
85
|
+
console.error(error);
|
|
86
|
+
process.exitCode ??= 1;
|
|
374
87
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { DidBtcr2Api } from '@did-btcr2/api';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { CLIError } from '../error.js';
|
|
4
|
+
import { formatResult } from '../output.js';
|
|
5
|
+
import {
|
|
6
|
+
CreateCommandOptions,
|
|
7
|
+
GlobalOptions,
|
|
8
|
+
NetworkOption,
|
|
9
|
+
SUPPORTED_NETWORKS,
|
|
10
|
+
} from '../types.js';
|
|
11
|
+
|
|
12
|
+
/** Expected byte length per identifier type: compressed secp256k1 = 33, SHA-256 hash = 32. */
|
|
13
|
+
const EXPECTED_BYTES: Record<'k' | 'x', { length: number; label: string }> = {
|
|
14
|
+
k : { length: 33, label: 'secp256k1 compressed public key (33 bytes)' },
|
|
15
|
+
x : { length: 32, label: 'SHA-256 hash (32 bytes)' },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function registerCreateCommand(
|
|
19
|
+
program : Command,
|
|
20
|
+
api : DidBtcr2Api,
|
|
21
|
+
globals : () => GlobalOptions,
|
|
22
|
+
): void {
|
|
23
|
+
program
|
|
24
|
+
.command('create')
|
|
25
|
+
.description('Create an identifier and initial DID document')
|
|
26
|
+
.requiredOption('-t, --type <type>', 'Identifier type <k|x>', 'k')
|
|
27
|
+
.requiredOption(
|
|
28
|
+
'-n, --network <network>',
|
|
29
|
+
'Identifier bitcoin network <bitcoin|testnet3|testnet4|signet|mutinynet|regtest>'
|
|
30
|
+
)
|
|
31
|
+
.requiredOption(
|
|
32
|
+
'-b, --bytes <bytes>',
|
|
33
|
+
'Genesis bytes as a hex string. ' +
|
|
34
|
+
'If type=k, MUST be secp256k1 public key. ' +
|
|
35
|
+
'If type=x, MUST be SHA-256 hash of a genesis document'
|
|
36
|
+
)
|
|
37
|
+
.action(async (options: { type: string; network: string; bytes: string }) => {
|
|
38
|
+
const parsed = validateCreateOptions(options);
|
|
39
|
+
const type = parsed.type === 'k' ? 'deterministic' : 'external';
|
|
40
|
+
const genesisBytes = Buffer.from(parsed.bytes, 'hex');
|
|
41
|
+
const data = api.createDid(type, genesisBytes, { network: parsed.network });
|
|
42
|
+
const result = { action: 'create' as const, data };
|
|
43
|
+
console.log(formatResult(result, globals()));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validateCreateOptions(
|
|
48
|
+
options: { type: string; network: string; bytes: string }
|
|
49
|
+
): CreateCommandOptions {
|
|
50
|
+
if (!['k', 'x'].includes(options.type)) {
|
|
51
|
+
throw new CLIError(
|
|
52
|
+
'Invalid type. Must be "k" or "x".',
|
|
53
|
+
'INVALID_ARGUMENT_ERROR',
|
|
54
|
+
options
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (!SUPPORTED_NETWORKS.includes(options.network as NetworkOption)) {
|
|
58
|
+
throw new CLIError(
|
|
59
|
+
'Invalid network. Must be one of "bitcoin", "testnet3", "testnet4", "signet", "mutinynet", or "regtest".',
|
|
60
|
+
'INVALID_ARGUMENT_ERROR',
|
|
61
|
+
options
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const buf = Buffer.from(options.bytes, 'hex');
|
|
66
|
+
if (buf.length === 0) {
|
|
67
|
+
throw new CLIError(
|
|
68
|
+
'Invalid bytes. Must be a non-empty hex string.',
|
|
69
|
+
'INVALID_ARGUMENT_ERROR',
|
|
70
|
+
options
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const expected = EXPECTED_BYTES[options.type as 'k' | 'x'];
|
|
74
|
+
if (buf.length !== expected.length) {
|
|
75
|
+
throw new CLIError(
|
|
76
|
+
`Invalid bytes length for type="${options.type}": expected ${expected.label}, got ${buf.length} bytes.`,
|
|
77
|
+
'INVALID_ARGUMENT_ERROR',
|
|
78
|
+
options
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
type : options.type as 'k' | 'x',
|
|
84
|
+
network : options.network as NetworkOption,
|
|
85
|
+
bytes : options.bytes,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { DidBtcr2Api } from '@did-btcr2/api';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { CLIError } from '../error.js';
|
|
4
|
+
import { formatResult } from '../output.js';
|
|
5
|
+
import { GlobalOptions, UpdateCommandOptions } from '../types.js';
|
|
6
|
+
|
|
7
|
+
/** The JSON Patch that marks a DID document as permanently deactivated. */
|
|
8
|
+
const DEACTIVATION_PATCH = [{ op: 'add' as const, path: '/deactivated', value: true }];
|
|
9
|
+
|
|
10
|
+
export function registerDeactivateCommand(
|
|
11
|
+
program : Command,
|
|
12
|
+
api : DidBtcr2Api,
|
|
13
|
+
globals : () => GlobalOptions,
|
|
14
|
+
): void {
|
|
15
|
+
program
|
|
16
|
+
.command('deactivate')
|
|
17
|
+
.alias('delete')
|
|
18
|
+
.description('Deactivate the did:btcr2 identifier permanently. This is irreversible.')
|
|
19
|
+
.requiredOption(
|
|
20
|
+
'-s, --source-document <json>',
|
|
21
|
+
'Current DID document as JSON string',
|
|
22
|
+
parseJsonArg('--source-document'),
|
|
23
|
+
)
|
|
24
|
+
.requiredOption(
|
|
25
|
+
'--source-version-id <number>',
|
|
26
|
+
'Current version ID of the DID document'
|
|
27
|
+
)
|
|
28
|
+
.requiredOption(
|
|
29
|
+
'-m, --verification-method-id <id>',
|
|
30
|
+
'DID document verification method ID used to sign the deactivation'
|
|
31
|
+
)
|
|
32
|
+
.requiredOption(
|
|
33
|
+
'-b, --beacon-id <json>',
|
|
34
|
+
'Beacon ID as a JSON string',
|
|
35
|
+
parseJsonArg('--beacon-id'),
|
|
36
|
+
)
|
|
37
|
+
.action(async (options: {
|
|
38
|
+
sourceDocument : unknown;
|
|
39
|
+
sourceVersionId : string;
|
|
40
|
+
verificationMethodId : string;
|
|
41
|
+
beaconId : unknown;
|
|
42
|
+
}) => {
|
|
43
|
+
const parsed: UpdateCommandOptions = {
|
|
44
|
+
sourceDocument : options.sourceDocument as UpdateCommandOptions['sourceDocument'],
|
|
45
|
+
patches : DEACTIVATION_PATCH,
|
|
46
|
+
sourceVersionId : Number(options.sourceVersionId),
|
|
47
|
+
verificationMethodId : options.verificationMethodId,
|
|
48
|
+
beaconId : options.beaconId as UpdateCommandOptions['beaconId'],
|
|
49
|
+
};
|
|
50
|
+
const data = await api.btcr2.update(parsed);
|
|
51
|
+
const result = { action: 'deactivate' as const, data };
|
|
52
|
+
console.log(formatResult(result, globals()));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns a commander argParser that validates JSON.
|
|
58
|
+
* Errors at parse time with a clear flag reference.
|
|
59
|
+
*/
|
|
60
|
+
function parseJsonArg(flagName: string): (value: string) => unknown {
|
|
61
|
+
return (value: string): unknown => {
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(value);
|
|
64
|
+
} catch {
|
|
65
|
+
throw new CLIError(
|
|
66
|
+
`Invalid JSON for ${flagName}. Must be a valid JSON string.`,
|
|
67
|
+
'INVALID_ARGUMENT_ERROR',
|
|
68
|
+
{ flagName, value }
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { DidBtcr2Api } from '@did-btcr2/api';
|
|
2
|
+
import { Identifier } from '@did-btcr2/api';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { CLIError } from '../error.js';
|
|
6
|
+
import { formatResult } from '../output.js';
|
|
7
|
+
import { GlobalOptions, ResolveCommandOptions } from '../types.js';
|
|
8
|
+
|
|
9
|
+
export function registerResolveCommand(
|
|
10
|
+
program : Command,
|
|
11
|
+
api : DidBtcr2Api,
|
|
12
|
+
globals : () => GlobalOptions,
|
|
13
|
+
): void {
|
|
14
|
+
program
|
|
15
|
+
.command('resolve')
|
|
16
|
+
.alias('read')
|
|
17
|
+
.description('Resolve the DID document of the identifier.')
|
|
18
|
+
.requiredOption('-i, --identifier <identifier>', 'did:btcr2 identifier')
|
|
19
|
+
.option('-r, --resolution-options <json>', 'JSON string containing resolution options')
|
|
20
|
+
.option('-p, --resolution-options-path <path>', 'Path to a JSON file containing resolution options')
|
|
21
|
+
.action(async (options: {
|
|
22
|
+
identifier: string;
|
|
23
|
+
resolutionOptions?: string;
|
|
24
|
+
resolutionOptionsPath?: string;
|
|
25
|
+
}) => {
|
|
26
|
+
const parsed = await validateResolveOptions(options);
|
|
27
|
+
const data = await api.resolveDid(parsed.identifier, parsed.options);
|
|
28
|
+
const result = { action: 'resolve' as const, data };
|
|
29
|
+
console.log(formatResult(result, globals()));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function validateResolveOptions(options: {
|
|
34
|
+
identifier: string;
|
|
35
|
+
resolutionOptions?: string;
|
|
36
|
+
resolutionOptionsPath?: string;
|
|
37
|
+
}): Promise<ResolveCommandOptions> {
|
|
38
|
+
// Validate identifier format early
|
|
39
|
+
Identifier.decode(options.identifier);
|
|
40
|
+
|
|
41
|
+
let resolutionOptions = undefined;
|
|
42
|
+
if (options.resolutionOptions) {
|
|
43
|
+
try {
|
|
44
|
+
resolutionOptions = JSON.parse(options.resolutionOptions);
|
|
45
|
+
} catch {
|
|
46
|
+
throw new CLIError(
|
|
47
|
+
'Invalid resolution options. Must be a valid JSON string.',
|
|
48
|
+
'INVALID_ARGUMENT_ERROR',
|
|
49
|
+
options
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
} else if (options.resolutionOptionsPath) {
|
|
53
|
+
try {
|
|
54
|
+
const content = await readFile(options.resolutionOptionsPath, 'utf-8');
|
|
55
|
+
resolutionOptions = JSON.parse(content);
|
|
56
|
+
} catch {
|
|
57
|
+
throw new CLIError(
|
|
58
|
+
'Invalid resolution options path. Must be a valid path to a JSON file.',
|
|
59
|
+
'INVALID_ARGUMENT_ERROR',
|
|
60
|
+
options
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { identifier: options.identifier, options: resolutionOptions };
|
|
65
|
+
}
|