@did-btcr2/cli 0.13.0 → 0.15.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.
Files changed (52) hide show
  1. package/README.md +95 -14
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cjs/index.js +674 -54
  4. package/dist/esm/src/cli.js +25 -3
  5. package/dist/esm/src/cli.js.map +1 -1
  6. package/dist/esm/src/commands/config.js +124 -8
  7. package/dist/esm/src/commands/config.js.map +1 -1
  8. package/dist/esm/src/commands/create.js +10 -1
  9. package/dist/esm/src/commands/create.js.map +1 -1
  10. package/dist/esm/src/commands/deactivate.js +33 -6
  11. package/dist/esm/src/commands/deactivate.js.map +1 -1
  12. package/dist/esm/src/commands/profile.js +5 -3
  13. package/dist/esm/src/commands/profile.js.map +1 -1
  14. package/dist/esm/src/commands/update.js +33 -6
  15. package/dist/esm/src/commands/update.js.map +1 -1
  16. package/dist/esm/src/config-schema.js +149 -0
  17. package/dist/esm/src/config-schema.js.map +1 -0
  18. package/dist/esm/src/config.js +515 -35
  19. package/dist/esm/src/config.js.map +1 -1
  20. package/dist/esm/src/keystore/paths.js +3 -2
  21. package/dist/esm/src/keystore/paths.js.map +1 -1
  22. package/dist/esm/src/output.js +49 -0
  23. package/dist/esm/src/output.js.map +1 -1
  24. package/dist/esm/src/types.js +11 -0
  25. package/dist/esm/src/types.js.map +1 -1
  26. package/dist/types/src/cli.d.ts.map +1 -1
  27. package/dist/types/src/commands/config.d.ts.map +1 -1
  28. package/dist/types/src/commands/create.d.ts.map +1 -1
  29. package/dist/types/src/commands/deactivate.d.ts.map +1 -1
  30. package/dist/types/src/commands/profile.d.ts.map +1 -1
  31. package/dist/types/src/commands/update.d.ts.map +1 -1
  32. package/dist/types/src/config-schema.d.ts +24 -0
  33. package/dist/types/src/config-schema.d.ts.map +1 -0
  34. package/dist/types/src/config.d.ts +228 -6
  35. package/dist/types/src/config.d.ts.map +1 -1
  36. package/dist/types/src/keystore/paths.d.ts.map +1 -1
  37. package/dist/types/src/output.d.ts +22 -0
  38. package/dist/types/src/output.d.ts.map +1 -1
  39. package/dist/types/src/types.d.ts +33 -0
  40. package/dist/types/src/types.d.ts.map +1 -1
  41. package/package.json +4 -4
  42. package/src/cli.ts +27 -3
  43. package/src/commands/config.ts +135 -8
  44. package/src/commands/create.ts +13 -1
  45. package/src/commands/deactivate.ts +53 -6
  46. package/src/commands/profile.ts +5 -3
  47. package/src/commands/update.ts +53 -6
  48. package/src/config-schema.ts +178 -0
  49. package/src/config.ts +693 -43
  50. package/src/keystore/paths.ts +3 -2
  51. package/src/output.ts +53 -0
  52. package/src/types.ts +23 -0
@@ -0,0 +1,178 @@
1
+ import { CLIError } from './error.js';
2
+ import { SUPPORTED_NETWORKS } from './types.js';
3
+
4
+ /**
5
+ * Declarative schema of the known config-file paths, used by both the write-time
6
+ * validation in `config set` and the strict `config validate` check so the two
7
+ * cannot disagree. Leaves name a value kind: `'string'`, `'number'`, `'object'`
8
+ * (a free-form map, e.g. headers), or `'enum:<name>'`. The `'*'` key under
9
+ * `profiles` matches any profile name.
10
+ */
11
+ const CONFIG_SCHEMA: SchemaNode = {
12
+ schemaVersion : 'number',
13
+ defaults : {
14
+ profile : 'string',
15
+ network : 'enum:network',
16
+ output : 'enum:output',
17
+ },
18
+ profiles : {
19
+ '*' : {
20
+ network : 'enum:network',
21
+ btc : {
22
+ rest : 'string',
23
+ rpcUrl : 'string',
24
+ rpcUser : 'string',
25
+ rpcPass : 'string',
26
+ feeRate : 'number',
27
+ changeAddress : 'string',
28
+ timeoutMs : 'number',
29
+ headers : 'object',
30
+ wallet : 'string',
31
+ rpcHeaders : 'object',
32
+ },
33
+ cas : {
34
+ gateway : 'string',
35
+ rpcUrl : 'string',
36
+ timeoutMs : 'number',
37
+ },
38
+ identity : {
39
+ keystore : 'string',
40
+ default : 'string',
41
+ },
42
+ },
43
+ },
44
+ };
45
+
46
+ type SchemaLeaf = 'string' | 'number' | 'object' | `enum:${string}`;
47
+ type SchemaNode = { [key: string]: SchemaLeaf | SchemaNode };
48
+
49
+ /** One problem found in a config file: the dotted path and a human-readable reason. */
50
+ export interface ConfigIssue {
51
+ path : string;
52
+ issue : string;
53
+ }
54
+
55
+ /**
56
+ * Resolves a dotted config path to its schema node: a leaf kind string, a nested
57
+ * {@link SchemaNode}, or `undefined` when the path is not part of the known
58
+ * schema. A segment under `profiles` matches the `'*'` template.
59
+ */
60
+ function lookupSchemaNode(dotted: string): SchemaLeaf | SchemaNode | undefined {
61
+ let node: SchemaLeaf | SchemaNode | undefined = CONFIG_SCHEMA;
62
+ for (const segment of dotted.split('.')) {
63
+ if (typeof node !== 'object') return undefined;
64
+ // Own-property checks only: `in` would match inherited Object.prototype names
65
+ // (`toString`, `constructor`, `__proto__`), treating a builtin as a known key.
66
+ if (Object.hasOwn(node, segment)) {
67
+ node = node[segment];
68
+ } else if (Object.hasOwn(node, '*')) {
69
+ node = node['*'];
70
+ } else {
71
+ return undefined;
72
+ }
73
+ }
74
+ return node;
75
+ }
76
+
77
+ /** Whether a dotted path is part of the known config schema (leaf or intermediate). */
78
+ export function isKnownConfigPath(dotted: string): boolean {
79
+ return lookupSchemaNode(dotted) !== undefined;
80
+ }
81
+
82
+ /**
83
+ * Validates a value against a leaf kind, throwing a {@link CLIError} for a value
84
+ * that does not match: an out-of-range enum, a non-number for a `number` leaf, or
85
+ * a non-object for an `object` leaf (a map such as `headers`). A no-op for
86
+ * `string` leaves and for intermediate nodes.
87
+ */
88
+ function assertLeafValue(kind: SchemaLeaf, dotted: string, value: unknown): void {
89
+ if (kind === 'enum:network' && !SUPPORTED_NETWORKS.includes(value as never)) {
90
+ throw new CLIError(
91
+ `Invalid value for ${dotted}: "${String(value)}". Expected one of ${SUPPORTED_NETWORKS.join(', ')}.`,
92
+ 'INVALID_ARGUMENT_ERROR',
93
+ { path: dotted, value },
94
+ );
95
+ }
96
+ if (kind === 'enum:output' && value !== 'json' && value !== 'text') {
97
+ throw new CLIError(
98
+ `Invalid value for ${dotted}: "${String(value)}". Expected "json" or "text".`,
99
+ 'INVALID_ARGUMENT_ERROR',
100
+ { path: dotted, value },
101
+ );
102
+ }
103
+ if (kind === 'number' && typeof value !== 'number') {
104
+ throw new CLIError(
105
+ `Invalid value for ${dotted}: expected a number, got ${typeof value}. `
106
+ + 'Pass a bare number (e.g. `config set ' + dotted + ' 5`).',
107
+ 'INVALID_ARGUMENT_ERROR',
108
+ { path: dotted, value },
109
+ );
110
+ }
111
+ if (kind === 'object' && (typeof value !== 'object' || value === null || Array.isArray(value))) {
112
+ throw new CLIError(
113
+ `Invalid value for ${dotted}: expected a JSON object (e.g. \`config set ${dotted} '{"Key":"Value"}'\`).`,
114
+ 'INVALID_ARGUMENT_ERROR',
115
+ { path: dotted, value },
116
+ );
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Write-time validation for `config set`. An enum, number, or object leaf whose
122
+ * value has the wrong kind is a hard rejection (throws). An unknown path is
123
+ * permitted but reported, so `config set` can warn and still write (keeping
124
+ * forward-compatible and third-party keys usable). Returns `{ unknownPath }`.
125
+ */
126
+ export function validateConfigSet(dotted: string, value: unknown): { unknownPath: boolean } {
127
+ const node = lookupSchemaNode(dotted);
128
+ if (node === undefined) return { unknownPath: true };
129
+ if (typeof node === 'string') {
130
+ assertLeafValue(node, dotted, value);
131
+ }
132
+ return { unknownPath: false };
133
+ }
134
+
135
+ /**
136
+ * Strict validation for `config validate`: walks a parsed config and collects
137
+ * every unknown key and out-of-enum value, plus a `schemaVersion` that is newer
138
+ * than this CLI supports. Never throws; returns the full list so the caller can
139
+ * report all problems at once.
140
+ */
141
+ export function findConfigIssues(config: Record<string, unknown>, supportedSchemaVersion: number): ConfigIssue[] {
142
+ const issues: ConfigIssue[] = [];
143
+
144
+ const version = config.schemaVersion;
145
+ if (typeof version === 'number' && version > supportedSchemaVersion) {
146
+ issues.push({
147
+ path : 'schemaVersion',
148
+ issue : `newer than supported (${version} > ${supportedSchemaVersion}); upgrade the CLI to use this file`,
149
+ });
150
+ }
151
+
152
+ walk(config, [], issues);
153
+ return issues;
154
+ }
155
+
156
+ /** Recursively collects unknown-key and bad-enum issues under `prefix`. */
157
+ function walk(obj: Record<string, unknown>, prefix: string[], issues: ConfigIssue[]): void {
158
+ for (const [ key, value ] of Object.entries(obj)) {
159
+ const path = [ ...prefix, key ];
160
+ const dotted = path.join('.');
161
+ const node = lookupSchemaNode(dotted);
162
+
163
+ if (node === undefined) {
164
+ issues.push({ path: dotted, issue: 'unknown key' });
165
+ continue; // Do not descend into an unknown subtree.
166
+ }
167
+
168
+ if (typeof node === 'string') {
169
+ try {
170
+ assertLeafValue(node, dotted, value);
171
+ } catch (error) {
172
+ issues.push({ path: dotted, issue: (error as Error).message });
173
+ }
174
+ } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
175
+ walk(value as Record<string, unknown>, path, issues);
176
+ }
177
+ }
178
+ }