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