@learncard/cli 3.3.177 → 3.4.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # @learncard/cli
2
2
 
3
+ ## 3.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`1706490abb9a8c1b099882c84d144ccabf92ffe2`](https://github.com/learningeconomy/LearnCard/commit/1706490abb9a8c1b099882c84d144ccabf92ffe2), [`1706490abb9a8c1b099882c84d144ccabf92ffe2`](https://github.com/learningeconomy/LearnCard/commit/1706490abb9a8c1b099882c84d144ccabf92ffe2)]:
8
+ - @learncard/init@2.3.21
9
+ - @learncard/didkit-plugin@1.9.1
10
+ - @learncard/learn-cloud-plugin@2.3.26
11
+ - @learncard/ler-rs-plugin@0.1.12
12
+ - @learncard/linked-claims-plugin@0.2.21
13
+ - @learncard/open-badge-v2-plugin@1.1.22
14
+ - @learncard/types@5.17.1
15
+ - @learncard/holder-continuity@0.2.1
16
+ - @learncard/core@9.4.21
17
+ - @learncard/render-method-plugin@3.0.1
18
+ - @learncard/simple-signing-plugin@1.1.25
19
+
20
+ ## 3.4.0
21
+
22
+ ### Minor Changes
23
+
24
+ - [#1269](https://github.com/learningeconomy/LearnCard/pull/1269) [`406f5f64ff49aaecbf8cb499a7f6b294c7105cc3`](https://github.com/learningeconomy/LearnCard/commit/406f5f64ff49aaecbf8cb499a7f6b294c7105cc3) Thanks [@TaylorBeeston](https://github.com/TaylorBeeston)! - feat: [LC-1798] Holder continuity export, restore, and metadata
25
+
26
+ Adds a new `@learncard/holder-continuity` package for creating encrypted holder continuity bundles, reading them back, importing credentials into a fresh wallet, and restoring the original wallet directly from the exported private-key seed.
27
+
28
+ Updates `@learncard/cli` to consume the new package and expose REPL helpers for export, import, and restore.
29
+
30
+ Adds holder export metadata types, an authenticated brain-service route, and a network plugin method for exporting consent records and transaction history without exposing credential payloads or key material from the service.
31
+
32
+ Adds bounded status-list fetching, optional verify-before-import support, bundle size guards, and capped holder metadata pagination.
33
+
34
+ ### Patch Changes
35
+
36
+ - Updated dependencies [[`7e90089f517908562becf72eb3831e9208232278`](https://github.com/learningeconomy/LearnCard/commit/7e90089f517908562becf72eb3831e9208232278), [`406f5f64ff49aaecbf8cb499a7f6b294c7105cc3`](https://github.com/learningeconomy/LearnCard/commit/406f5f64ff49aaecbf8cb499a7f6b294c7105cc3), [`7c5fea147f7c9876dd8d7cbe2ece082eb0e5a42b`](https://github.com/learningeconomy/LearnCard/commit/7c5fea147f7c9876dd8d7cbe2ece082eb0e5a42b)]:
37
+ - @learncard/types@5.17.0
38
+ - @learncard/holder-continuity@0.2.0
39
+ - @learncard/didkit-plugin@1.9.0
40
+ - @learncard/core@9.4.20
41
+ - @learncard/init@2.3.20
42
+ - @learncard/render-method-plugin@3.0.0
43
+ - @learncard/learn-cloud-plugin@2.3.25
44
+ - @learncard/ler-rs-plugin@0.1.11
45
+ - @learncard/linked-claims-plugin@0.2.20
46
+ - @learncard/open-badge-v2-plugin@1.1.21
47
+ - @learncard/simple-signing-plugin@1.1.24
48
+
3
49
  ## 3.3.177
4
50
 
5
51
  ### Patch Changes
package/README.md CHANGED
@@ -12,6 +12,7 @@ you all the tools you need to easily play around with the Learn Card SDK!
12
12
  ![LearnCard CLI](https://user-images.githubusercontent.com/2185016/201382605-13eb7bb2-f6b6-4099-97a1-1a623bf58486.gif)
13
13
 
14
14
  ## Documentation
15
+
15
16
  All LearnCard documentation can be found at:
16
17
  https://docs.learncard.com
17
18
 
@@ -24,7 +25,45 @@ npx @learncard/cli
24
25
  npx @learncard/cli 1b498556081a298261313657c32d5d0a9ce8285dc4d659e6787392207e4a7ac2
25
26
  ```
26
27
 
28
+ ## Holder continuity export
29
+
30
+ The CLI exposes REPL helpers from `@learncard/holder-continuity` for holder-controlled export, restore, and self-import:
31
+
32
+ ```js
33
+ const password = await getLearnCardBundlePassword();
34
+
35
+ await exportLearnCardBundle(learnCard, {
36
+ out: './learncard-export.zip',
37
+ password,
38
+ });
39
+
40
+ const freshWallet = await initLearnCard({ seed: '0'.repeat(64), network: true });
41
+ await importLearnCardBundle('./learncard-export.zip', {
42
+ password,
43
+ wallet: freshWallet,
44
+ verifyBeforeImport: true,
45
+ });
46
+ ```
47
+
48
+ ```js
49
+ const restoredWallet = await restoreLearnCardFromBundle('./learncard-export.zip', { password });
50
+ ```
51
+
52
+ `getLearnCardBundlePassword()` prompts without echoing the password into the REPL, which avoids saving it in REPL history. You can still pass a password string directly for local scripts.
53
+
54
+ `restoreLearnCardFromBundle(...)` decrypts the exported seed and returns a wallet with the original DID. It does not upload bundle payloads or recreate index records; use `importLearnCardBundle(...)` when copying credentials into another wallet.
55
+
56
+ If you omit the first argument, the CLI exports the default `learnCard` wallet it created at startup:
57
+
58
+ ```js
59
+ const password = await getLearnCardBundlePassword();
60
+ await exportLearnCardBundle({ out: './learncard-export.zip', password });
61
+ ```
62
+
63
+ See `@learncard/holder-continuity` `BUNDLE_SPEC.md` for the ZIP layout and manifest hashing rules.
64
+
27
65
  ## Contributing
66
+
28
67
  Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
29
68
 
30
69
  Please make sure to update tests as appropriate.
@@ -33,7 +72,6 @@ Please make sure to update tests as appropriate.
33
72
 
34
73
  **[Learning Economy Foundation (LEF)](https://www.learningeconomy.io)** is a 501(c)(3) non-profit organization leveraging global standards and web3 protocols to bring quality skills and equal opportunity to every human on earth, and address the persistent inequities that exist around the globe in education and employment. We help you build the future of education and work with:
35
74
 
36
-
37
75
  ## License
38
76
 
39
77
  MIT © [Learning Economy Foundation](https://github.com/Learning-Economy-Foundation)
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@
3
3
 
4
4
  var fs = require('fs/promises');
5
5
  var dns = require('node:dns');
6
+ var promises = require('node:readline/promises');
7
+ var node_stream = require('node:stream');
6
8
  var repl = require('pretty-repl');
7
9
  var core = require('@learncard/core');
8
10
  var init = require('@learncard/init');
@@ -17,6 +19,7 @@ var clipboard = require('clipboardy');
17
19
  var lerRsPlugin = require('@learncard/ler-rs-plugin');
18
20
  var renderMethodPlugin = require('@learncard/render-method-plugin');
19
21
  var crypto = require('crypto');
22
+ var holderContinuity = require('@learncard/holder-continuity');
20
23
 
21
24
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
22
25
 
@@ -31,14 +34,49 @@ var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
31
34
 
32
35
  const generateRandomSeed = () => crypto__default["default"].randomBytes(32).toString("hex");
33
36
 
37
+ var __defProp = Object.defineProperty;
38
+ var __defProps = Object.defineProperties;
39
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
40
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
41
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
42
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
43
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
44
+ var __spreadValues = (a, b) => {
45
+ for (var prop in b || (b = {}))
46
+ if (__hasOwnProp.call(b, prop))
47
+ __defNormalProp(a, prop, b[prop]);
48
+ if (__getOwnPropSymbols)
49
+ for (var prop of __getOwnPropSymbols(b)) {
50
+ if (__propIsEnum.call(b, prop))
51
+ __defNormalProp(a, prop, b[prop]);
52
+ }
53
+ return a;
54
+ };
55
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
56
+ const createExportLearnCardBundleHelper = (exportBundle, defaultWallet) => {
57
+ return (walletOrOptions, maybeOptions) => {
58
+ if (maybeOptions) return exportBundle(walletOrOptions, maybeOptions);
59
+ return exportBundle(defaultWallet, walletOrOptions);
60
+ };
61
+ };
62
+ const createRestoreLearnCardFromBundleHelper = (restoreBundle, defaultInit) => {
63
+ return (path, options = {}) => {
64
+ var _a;
65
+ return restoreBundle(path, __spreadProps(__spreadValues({}, options), {
66
+ init: __spreadValues(__spreadValues({}, defaultInit), (_a = options.init) != null ? _a : {})
67
+ }));
68
+ };
69
+ };
70
+
34
71
  var name = "@learncard/cli";
35
- var version = "3.3.177";
72
+ var version = "3.4.1";
36
73
  var description = "Command Line Interface for LearnCard";
37
74
  var main = "dist/index.js";
38
75
  var bin = "dist/index.js";
39
76
  var scripts = {
40
77
  start: "rollup -c && node --enable-source-maps dist/index.js",
41
- build: "rollup -c"
78
+ build: "rollup -c",
79
+ test: "vitest run"
42
80
  };
43
81
  var author = "Learning Economy Foundation (www.learningeconomy.io)";
44
82
  var license = "MIT";
@@ -49,9 +87,10 @@ var dependencies = {
49
87
  "@learncard/learn-cloud-plugin": "workspace:*",
50
88
  "@learncard/ler-rs-plugin": "workspace:*",
51
89
  "@learncard/linked-claims-plugin": "workspace:*",
52
- "@learncard/render-method-plugin": "workspace:*",
53
90
  "@learncard/open-badge-v2-plugin": "workspace:*",
91
+ "@learncard/render-method-plugin": "workspace:*",
54
92
  "@learncard/simple-signing-plugin": "workspace:*",
93
+ "@learncard/holder-continuity": "workspace:*",
55
94
  "@learncard/types": "workspace:*",
56
95
  "@rollup/plugin-json": "^4.1.0",
57
96
  clipboardy: "^4.0.0",
@@ -67,7 +106,8 @@ var devDependencies = {
67
106
  "@types/figlet": "^1.5.4",
68
107
  "@types/node": "^18.0.0",
69
108
  nodemon: "^2.0.16",
70
- "ts-node": "^10.8.1"
109
+ "ts-node": "^10.8.1",
110
+ vitest: "^1.6.0"
71
111
  };
72
112
  var repository = {
73
113
  type: "git",
@@ -102,7 +142,13 @@ const g = {
102
142
  seed: gradient__default["default"](["cyan", "green"])("seed"),
103
143
  generateRandomSeed: gradient__default["default"](["cyan", "green"])("generateRandomSeed"),
104
144
  types: gradient__default["default"](["cyan", "green"])("types"),
105
- copy: gradient__default["default"](["cyan", "green"])("copy")
145
+ getLearnCardBundlePassword: gradient__default["default"](["cyan", "green"])("getLearnCardBundlePassword"),
146
+ copy: gradient__default["default"](["cyan", "green"])("copy"),
147
+ exportLearnCardBundle: gradient__default["default"](["cyan", "green"])("exportLearnCardBundle"),
148
+ importLearnCardBundle: gradient__default["default"](["cyan", "green"])("importLearnCardBundle"),
149
+ createLearnCardBundle: gradient__default["default"](["cyan", "green"])("createLearnCardBundle"),
150
+ readLearnCardBundle: gradient__default["default"](["cyan", "green"])("readLearnCardBundle"),
151
+ restoreLearnCardFromBundle: gradient__default["default"](["cyan", "green"])("restoreLearnCardFromBundle")
106
152
  };
107
153
  const copyFunction = (text) => {
108
154
  if (typeof text === "object") {
@@ -115,7 +161,25 @@ const copyFunction = (text) => {
115
161
  clipboard__default["default"].writeSync(text);
116
162
  console.log("Copied to clipboard!");
117
163
  } catch (error) {
118
- console.error("Failed to copy to clipboard:", error instanceof Error ? error.message : "Unknown error");
164
+ console.error(
165
+ "Failed to copy to clipboard:",
166
+ error instanceof Error ? error.message : "Unknown error"
167
+ );
168
+ }
169
+ };
170
+ const getLearnCardBundlePassword = async (prompt = "Bundle password: ") => {
171
+ process.stdout.write(prompt);
172
+ const mutedOutput = new node_stream.Writable({
173
+ write(_chunk, _encoding, callback) {
174
+ callback();
175
+ }
176
+ });
177
+ const rl = promises.createInterface({ input: process.stdin, output: mutedOutput, terminal: true });
178
+ try {
179
+ return await rl.question("");
180
+ } finally {
181
+ rl.close();
182
+ process.stdout.write("\n");
119
183
  }
120
184
  };
121
185
  commander.program.version(packageJson.version).argument("[seed]").action(async (_seed = generateRandomSeed()) => {
@@ -132,13 +196,14 @@ commander.program.version(packageJson.version).argument("[seed]").action(async (
132
196
  globalThis.emptyLearnCard = init.emptyLearnCard;
133
197
  globalThis.learnCardFromSeed = init.learnCardFromSeed;
134
198
  globalThis.initLearnCard = init.initLearnCard;
199
+ const didkit = fs__default["default"].readFile(
200
+ require.resolve("@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm")
201
+ );
135
202
  const _learnCard = await init.initLearnCard({
136
203
  seed,
137
204
  network: true,
138
205
  allowRemoteContexts: true,
139
- didkit: fs__default["default"].readFile(
140
- require.resolve("@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm")
141
- )
206
+ didkit
142
207
  });
143
208
  const simpleSigningLc = await _learnCard.addPlugin(
144
209
  await simpleSigningPlugin.getSimpleSigningPlugin(_learnCard, "https://api.learncard.app/trpc")
@@ -156,53 +221,90 @@ commander.program.version(packageJson.version).argument("[seed]").action(async (
156
221
  globalThis.types = types__default["default"];
157
222
  globalThis.getTestCache = core.getTestCache;
158
223
  globalThis.copy = copyFunction;
224
+ globalThis.getLearnCardBundlePassword = getLearnCardBundlePassword;
225
+ globalThis.exportLearnCardBundle = createExportLearnCardBundleHelper(
226
+ holderContinuity.exportLearnCardBundle,
227
+ globalThis.learnCard
228
+ );
229
+ globalThis.restoreLearnCardFromBundle = createRestoreLearnCardFromBundleHelper(
230
+ holderContinuity.restoreLearnCardFromBundle,
231
+ {
232
+ network: true,
233
+ allowRemoteContexts: true,
234
+ didkit
235
+ }
236
+ );
237
+ globalThis.importLearnCardBundle = holderContinuity.importLearnCardBundle;
238
+ globalThis.createLearnCardBundle = holderContinuity.createLearnCardBundle;
239
+ globalThis.readLearnCardBundle = holderContinuity.readLearnCardBundle;
159
240
  (_b = (_a = process.stdout).moveCursor) == null ? void 0 : _b.call(_a, 0, -1);
160
241
  (_d = (_c = process.stdout).clearLine) == null ? void 0 : _d.call(_c, 1);
161
242
  console.log("Wallet created!\n");
162
- console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
163
- console.log("\u2502 Variables Available \u2502");
164
- console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
165
- console.log("\u2502 Variable \u2502 Description \u2502");
166
- console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
167
- console.log(`\u2502 ${g.learnCard} \u2502 Learn Card Wallet \u2502`);
168
- console.log(`\u2502 ${g.initLearnCard} \u2502 Wallet Instantiation Function \u2502`);
169
- console.log(`\u2502 ${g.seed} \u2502 Seed used to generate wallet \u2502`);
170
- console.log(`\u2502 ${g.generateRandomSeed} \u2502 Generates a random seed \u2502`);
171
- console.log(`\u2502 ${g.types} \u2502 Helpful zod validators \u2502`);
172
- console.log(`\u2502 ${g.copy} \u2502 Copy text to clipboard \u2502`);
173
- console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
243
+ console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
244
+ console.log("\u2502 Variables Available \u2502");
245
+ console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
246
+ console.log("\u2502 Variable \u2502 Description \u2502");
247
+ console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
248
+ console.log(`\u2502 ${g.learnCard} \u2502 Learn Card Wallet \u2502`);
249
+ console.log(`\u2502 ${g.initLearnCard} \u2502 Wallet Instantiation Function \u2502`);
250
+ console.log(`\u2502 ${g.seed} \u2502 Seed used to generate wallet \u2502`);
251
+ console.log(`\u2502 ${g.generateRandomSeed} \u2502 Generates a random seed \u2502`);
252
+ console.log(`\u2502 ${g.types} \u2502 Helpful zod validators \u2502`);
253
+ console.log(`\u2502 ${g.copy} \u2502 Copy text to clipboard \u2502`);
254
+ console.log(`\u2502 ${g.getLearnCardBundlePassword} \u2502 Prompt for bundle password \u2502`);
255
+ console.log(`\u2502 ${g.exportLearnCardBundle} \u2502 Export wallet continuity ZIP \u2502`);
256
+ console.log(`\u2502 ${g.importLearnCardBundle} \u2502 Import continuity ZIP \u2502`);
257
+ console.log(`\u2502 ${g.restoreLearnCardFromBundle} \u2502 Restore original wallet from ZIP \u2502`);
258
+ console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
174
259
  console.log("");
175
260
  console.log(
176
261
  "For help/documentation regarding your wallet, please read the documentation at\n"
177
262
  );
178
263
  console.log("https://docs.learncard.com/sdks/learncard-core/construction\n");
179
264
  console.log("To get a feel for what's possible, try some of the following commands\n");
180
- console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
181
- console.log("\u2502 Description \u2502 Command \u2502");
182
- console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
183
265
  console.log(
184
- `\u2502 View your did \u2502 ${g.learnCard}.id.did(); \u2502`
266
+ "\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"
267
+ );
268
+ console.log(
269
+ "\u2502 Description \u2502 Command \u2502"
270
+ );
271
+ console.log(
272
+ "\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"
273
+ );
274
+ console.log(
275
+ `\u2502 View your did \u2502 ${g.learnCard}.id.did(); \u2502`
276
+ );
277
+ console.log(
278
+ `\u2502 Generate an unsigned VC \u2502 ${g.learnCard}.invoke.getTestVc(); \u2502`
279
+ );
280
+ console.log(
281
+ `\u2502 Issue a signed VC \u2502 await ${g.learnCard}.invoke.issueCredential(uvc); \u2502`
282
+ );
283
+ console.log(
284
+ `\u2502 Verify a signed VC \u2502 await ${g.learnCard}.invoke.verifyCredential(vc); \u2502`
285
+ );
286
+ console.log(
287
+ `\u2502 Issue a signed VP \u2502 await ${g.learnCard}.invoke.issuePresentation(vc); \u2502`
185
288
  );
186
289
  console.log(
187
- `\u2502 Generate an unsigned VC \u2502 ${g.learnCard}.invoke.getTestVc(); \u2502`
290
+ `\u2502 Verify a signed VP \u2502 await ${g.learnCard}.invoke.verifyPresentation(vp); \u2502`
188
291
  );
189
292
  console.log(
190
- `\u2502 Issue a signed VC \u2502 await ${g.learnCard}.invoke.issueCredential(uvc); \u2502`
293
+ `\u2502 Prompt bundle password \u2502 const password = await ${g.getLearnCardBundlePassword}(); \u2502`
191
294
  );
192
295
  console.log(
193
- `\u2502 Verify a signed VC \u2502 await ${g.learnCard}.invoke.verifyCredential(vc); \u2502`
296
+ `\u2502 Export wallet ZIP \u2502 await ${g.exportLearnCardBundle}(${g.learnCard}, { out: './export.zip', password }); \u2502`
194
297
  );
195
298
  console.log(
196
- `\u2502 Issue a signed VP \u2502 await ${g.learnCard}.invoke.issuePresentation(vc); \u2502`
299
+ `\u2502 Restore original wallet \u2502 await ${g.restoreLearnCardFromBundle}('./export.zip', { password }); \u2502`
197
300
  );
198
301
  console.log(
199
- `\u2502 Verify a signed VP \u2502 await ${g.learnCard}.invoke.verifyPresentation(vp); \u2502`
302
+ "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"
200
303
  );
201
- console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
202
304
  console.log("");
203
305
  repl__default["default"].start({
204
306
  colorize: (input) => {
205
- return input.replace("emptyLearnCard", g.emptyLearnCard).replace("learnCardFromKey", g.learnCardFromKey).replace("initLearnCard", g.initLearnCard).replace("learnCard", g.learnCard).replace("seed", g.seed).replace("generateRandomSeed", g.generateRandomSeed).replace("copy", g.copy);
307
+ return input.replace("emptyLearnCard", g.emptyLearnCard).replace("learnCardFromKey", g.learnCardFromKey).replace("initLearnCard", g.initLearnCard).replace("learnCard", g.learnCard).replace("seed", g.seed).replace("generateRandomSeed", g.generateRandomSeed).replace("copy", g.copy).replace("getLearnCardBundlePassword", g.getLearnCardBundlePassword).replace("exportLearnCardBundle", g.exportLearnCardBundle).replace("importLearnCardBundle", g.importLearnCardBundle).replace("createLearnCardBundle", g.createLearnCardBundle).replace("readLearnCardBundle", g.readLearnCardBundle).replace("restoreLearnCardFromBundle", g.restoreLearnCardFromBundle);
206
308
  }
207
309
  });
208
310
  }).parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@learncard/cli",
3
- "version": "3.3.177",
3
+ "version": "3.4.1",
4
4
  "description": "Command Line Interface for LearnCard",
5
5
  "main": "dist/index.js",
6
6
  "bin": "dist/index.js",
@@ -15,23 +15,25 @@
15
15
  "pretty-repl": "^3.1.1",
16
16
  "rollup": "^2.80.0",
17
17
  "rollup-plugin-esbuild": "^4.9.1",
18
- "@learncard/core": "9.4.19",
19
- "@learncard/didkit-plugin": "^1.8.10",
20
- "@learncard/init": "^2.3.19",
21
- "@learncard/learn-cloud-plugin": "2.3.24",
22
- "@learncard/linked-claims-plugin": "0.2.19",
23
- "@learncard/ler-rs-plugin": "0.1.10",
24
- "@learncard/render-method-plugin": "2.0.0",
25
- "@learncard/open-badge-v2-plugin": "1.1.20",
26
- "@learncard/simple-signing-plugin": "1.1.23",
27
- "@learncard/types": "5.16.0"
18
+ "@learncard/core": "9.4.21",
19
+ "@learncard/learn-cloud-plugin": "2.3.26",
20
+ "@learncard/didkit-plugin": "^1.9.1",
21
+ "@learncard/linked-claims-plugin": "0.2.21",
22
+ "@learncard/open-badge-v2-plugin": "1.1.22",
23
+ "@learncard/ler-rs-plugin": "0.1.12",
24
+ "@learncard/init": "^2.3.21",
25
+ "@learncard/render-method-plugin": "3.0.1",
26
+ "@learncard/simple-signing-plugin": "1.1.25",
27
+ "@learncard/types": "5.17.1",
28
+ "@learncard/holder-continuity": "0.2.1"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/cors": "^2.8.12",
31
32
  "@types/figlet": "^1.5.4",
32
33
  "@types/node": "^18.0.0",
33
34
  "nodemon": "^2.0.16",
34
- "ts-node": "^10.8.1"
35
+ "ts-node": "^10.8.1",
36
+ "vitest": "^1.6.0"
35
37
  },
36
38
  "repository": {
37
39
  "type": "git",
@@ -43,6 +45,7 @@
43
45
  },
44
46
  "scripts": {
45
47
  "start": "rollup -c && node --enable-source-maps dist/index.js",
46
- "build": "rollup -c"
48
+ "build": "rollup -c",
49
+ "test": "vitest run"
47
50
  }
48
51
  }
package/project.json CHANGED
@@ -9,7 +9,8 @@
9
9
  "nx-run-command",
10
10
  "types",
11
11
  "init",
12
- "open-badge-v2-plugin"
12
+ "open-badge-v2-plugin",
13
+ "holder-continuity"
13
14
  ],
14
15
  "namedInputs": {
15
16
  "source": ["{projectRoot}/src/**/*"],
@@ -23,6 +24,13 @@
23
24
  "script": "build"
24
25
  }
25
26
  },
27
+ "test": {
28
+ "executor": "nx:run-script",
29
+ "inputs": ["source"],
30
+ "options": {
31
+ "script": "test"
32
+ }
33
+ },
26
34
  "start": {
27
35
  "executor": "./tools/executors/workspace:run-command",
28
36
  "dependsOn": ["^build"],
package/src/index.tsx CHANGED
@@ -1,5 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
  import dns from 'node:dns';
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { Writable } from 'node:stream';
3
5
 
4
6
  import repl from 'pretty-repl';
5
7
  import { getTestCache } from '@learncard/core';
@@ -17,6 +19,17 @@ import { getLerRsPlugin } from '@learncard/ler-rs-plugin';
17
19
  import { getRenderMethodPlugin } from '@learncard/render-method-plugin';
18
20
 
19
21
  import { generateRandomSeed } from './random';
22
+ import {
23
+ createLearnCardBundle,
24
+ exportLearnCardBundle as writeLearnCardBundle,
25
+ importLearnCardBundle,
26
+ readLearnCardBundle,
27
+ restoreLearnCardFromBundle as restoreBundle,
28
+ } from '@learncard/holder-continuity';
29
+ import {
30
+ createExportLearnCardBundleHelper,
31
+ createRestoreLearnCardFromBundleHelper,
32
+ } from './replHelpers';
20
33
 
21
34
  import packageJson from '../package.json';
22
35
 
@@ -30,7 +43,13 @@ const g = {
30
43
  seed: gradient(['cyan', 'green'])('seed'),
31
44
  generateRandomSeed: gradient(['cyan', 'green'])('generateRandomSeed'),
32
45
  types: gradient(['cyan', 'green'])('types'),
46
+ getLearnCardBundlePassword: gradient(['cyan', 'green'])('getLearnCardBundlePassword'),
33
47
  copy: gradient(['cyan', 'green'])('copy'),
48
+ exportLearnCardBundle: gradient(['cyan', 'green'])('exportLearnCardBundle'),
49
+ importLearnCardBundle: gradient(['cyan', 'green'])('importLearnCardBundle'),
50
+ createLearnCardBundle: gradient(['cyan', 'green'])('createLearnCardBundle'),
51
+ readLearnCardBundle: gradient(['cyan', 'green'])('readLearnCardBundle'),
52
+ restoreLearnCardFromBundle: gradient(['cyan', 'green'])('restoreLearnCardFromBundle'),
34
53
  };
35
54
 
36
55
  const copyFunction = (text: string | object | number) => {
@@ -44,7 +63,28 @@ const copyFunction = (text: string | object | number) => {
44
63
  clipboard.writeSync(text);
45
64
  console.log('Copied to clipboard!');
46
65
  } catch (error) {
47
- console.error('Failed to copy to clipboard:', error instanceof Error ? error.message : 'Unknown error');
66
+ console.error(
67
+ 'Failed to copy to clipboard:',
68
+ error instanceof Error ? error.message : 'Unknown error'
69
+ );
70
+ }
71
+ };
72
+
73
+ const getLearnCardBundlePassword = async (prompt = 'Bundle password: '): Promise<string> => {
74
+ process.stdout.write(prompt);
75
+
76
+ const mutedOutput = new Writable({
77
+ write(_chunk, _encoding, callback) {
78
+ callback();
79
+ },
80
+ });
81
+ const rl = createInterface({ input: process.stdin, output: mutedOutput, terminal: true });
82
+
83
+ try {
84
+ return await rl.question('');
85
+ } finally {
86
+ rl.close();
87
+ process.stdout.write('\n');
48
88
  }
49
89
  };
50
90
 
@@ -69,13 +109,15 @@ program
69
109
  globalThis.learnCardFromSeed = learnCardFromSeed;
70
110
  globalThis.initLearnCard = initLearnCard;
71
111
 
112
+ const didkit = fs.readFile(
113
+ require.resolve('@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm')
114
+ );
115
+
72
116
  const _learnCard = await initLearnCard({
73
117
  seed,
74
118
  network: true,
75
119
  allowRemoteContexts: true,
76
- didkit: fs.readFile(
77
- require.resolve('@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm')
78
- ),
120
+ didkit,
79
121
  });
80
122
 
81
123
  const simpleSigningLc = await _learnCard.addPlugin(
@@ -102,6 +144,23 @@ program
102
144
  globalThis.getTestCache = getTestCache;
103
145
 
104
146
  globalThis.copy = copyFunction;
147
+ globalThis.getLearnCardBundlePassword = getLearnCardBundlePassword;
148
+ globalThis.exportLearnCardBundle = createExportLearnCardBundleHelper(
149
+ writeLearnCardBundle,
150
+ globalThis.learnCard
151
+ );
152
+
153
+ globalThis.restoreLearnCardFromBundle = createRestoreLearnCardFromBundleHelper(
154
+ restoreBundle,
155
+ {
156
+ network: true,
157
+ allowRemoteContexts: true,
158
+ didkit,
159
+ }
160
+ );
161
+ globalThis.importLearnCardBundle = importLearnCardBundle;
162
+ globalThis.createLearnCardBundle = createLearnCardBundle;
163
+ globalThis.readLearnCardBundle = readLearnCardBundle;
105
164
 
106
165
  // delete 'Creating wallet...' message
107
166
  process.stdout.moveCursor?.(0, -1);
@@ -109,18 +168,22 @@ program
109
168
 
110
169
  console.log('Wallet created!\n');
111
170
 
112
- console.log('┌────────────────────────────────────────────────────┐');
113
- console.log('│ Variables Available │');
114
- console.log('├────────────────────┬───────────────────────────────┤');
115
- console.log('│ Variable Description │');
116
- console.log('├────────────────────┼───────────────────────────────┤');
117
- console.log(`│ ${g.learnCard} │ Learn Card Wallet │`);
118
- console.log(`│ ${g.initLearnCard} │ Wallet Instantiation Function │`);
119
- console.log(`│ ${g.seed} │ Seed used to generate wallet │`);
120
- console.log(`│ ${g.generateRandomSeed} │ Generates a random seed │`);
121
- console.log(`│ ${g.types} │ Helpful zod validators │`);
122
- console.log(`│ ${g.copy} │ Copy text to clipboard │`);
123
- console.log('└────────────────────┴───────────────────────────────┘');
171
+ console.log('┌───────────────────────────────────────────────────────────────┐');
172
+ console.log('│ Variables Available │');
173
+ console.log('├────────────────────────────┬──────────────────────────────────┤');
174
+ console.log('│ Variable Description │');
175
+ console.log('├────────────────────────────┼──────────────────────────────────┤');
176
+ console.log(`│ ${g.learnCard} │ Learn Card Wallet │`);
177
+ console.log(`│ ${g.initLearnCard} │ Wallet Instantiation Function │`);
178
+ console.log(`│ ${g.seed} │ Seed used to generate wallet │`);
179
+ console.log(`│ ${g.generateRandomSeed} │ Generates a random seed │`);
180
+ console.log(`│ ${g.types} │ Helpful zod validators │`);
181
+ console.log(`│ ${g.copy} │ Copy text to clipboard │`);
182
+ console.log(`│ ${g.getLearnCardBundlePassword} │ Prompt for bundle password │`);
183
+ console.log(`│ ${g.exportLearnCardBundle} │ Export wallet continuity ZIP │`);
184
+ console.log(`│ ${g.importLearnCardBundle} │ Import continuity ZIP │`);
185
+ console.log(`│ ${g.restoreLearnCardFromBundle} │ Restore original wallet from ZIP │`);
186
+ console.log('└────────────────────────────┴──────────────────────────────────┘');
124
187
 
125
188
  console.log('');
126
189
 
@@ -132,28 +195,45 @@ program
132
195
 
133
196
  console.log("To get a feel for what's possible, try some of the following commands\n");
134
197
 
135
- console.log('┌─────────────────────────┬────────────────────────────────────────────────┐');
136
- console.log('│ Description │ Command │');
137
- console.log('├─────────────────────────┼────────────────────────────────────────────────┤');
138
198
  console.log(
139
- `│ View your did │ ${g.learnCard}.id.did(); │`
199
+ '┌─────────────────────────┬───────────────────────────────────────────────────────────────────────────────────┐'
200
+ );
201
+ console.log(
202
+ '│ Description │ Command │'
203
+ );
204
+ console.log(
205
+ '├─────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┤'
206
+ );
207
+ console.log(
208
+ `│ View your did │ ${g.learnCard}.id.did(); │`
209
+ );
210
+ console.log(
211
+ `│ Generate an unsigned VC │ ${g.learnCard}.invoke.getTestVc(); │`
212
+ );
213
+ console.log(
214
+ `│ Issue a signed VC │ await ${g.learnCard}.invoke.issueCredential(uvc); │`
215
+ );
216
+ console.log(
217
+ `│ Verify a signed VC │ await ${g.learnCard}.invoke.verifyCredential(vc); │`
218
+ );
219
+ console.log(
220
+ `│ Issue a signed VP │ await ${g.learnCard}.invoke.issuePresentation(vc); │`
140
221
  );
141
222
  console.log(
142
- `│ Generate an unsigned VC │ ${g.learnCard}.invoke.getTestVc(); │`
223
+ `│ Verify a signed VPawait ${g.learnCard}.invoke.verifyPresentation(vp); │`
143
224
  );
144
225
  console.log(
145
- `│ Issue a signed VC │ await ${g.learnCard}.invoke.issueCredential(uvc); │`
226
+ `│ Prompt bundle passwordconst password = await ${g.getLearnCardBundlePassword}(); │`
146
227
  );
147
228
  console.log(
148
- `│ Verify a signed VC │ await ${g.learnCard}.invoke.verifyCredential(vc); │`
229
+ `│ Export wallet ZIP │ await ${g.exportLearnCardBundle}(${g.learnCard}, { out: './export.zip', password }); │`
149
230
  );
150
231
  console.log(
151
- `│ Issue a signed VP │ await ${g.learnCard}.invoke.issuePresentation(vc); │`
232
+ `│ Restore original wallet │ await ${g.restoreLearnCardFromBundle}('./export.zip', { password }); │`
152
233
  );
153
234
  console.log(
154
- `│ Verify a signed VP │ await ${g.learnCard}.invoke.verifyPresentation(vp); │`
235
+ '└─────────────────────────┴───────────────────────────────────────────────────────────────────────────────────┘'
155
236
  );
156
- console.log('└─────────────────────────┴────────────────────────────────────────────────┘');
157
237
 
158
238
  console.log('');
159
239
 
@@ -166,7 +246,13 @@ program
166
246
  .replace('learnCard', g.learnCard)
167
247
  .replace('seed', g.seed)
168
248
  .replace('generateRandomSeed', g.generateRandomSeed)
169
- .replace('copy', g.copy);
249
+ .replace('copy', g.copy)
250
+ .replace('getLearnCardBundlePassword', g.getLearnCardBundlePassword)
251
+ .replace('exportLearnCardBundle', g.exportLearnCardBundle)
252
+ .replace('importLearnCardBundle', g.importLearnCardBundle)
253
+ .replace('createLearnCardBundle', g.createLearnCardBundle)
254
+ .replace('readLearnCardBundle', g.readLearnCardBundle)
255
+ .replace('restoreLearnCardFromBundle', g.restoreLearnCardFromBundle);
170
256
  },
171
257
  });
172
258
  })
@@ -0,0 +1,80 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import type {
4
+ ExportLearnCardBundleOptions,
5
+ LearnCardBundleWallet,
6
+ RestoreLearnCardFromBundleOptions,
7
+ } from '@learncard/holder-continuity';
8
+ import {
9
+ createExportLearnCardBundleHelper,
10
+ createRestoreLearnCardFromBundleHelper,
11
+ } from './replHelpers';
12
+
13
+ const createWallet = (did: string): LearnCardBundleWallet => ({
14
+ id: { did: () => did },
15
+ invoke: {},
16
+ index: { LearnCloud: { get: async () => [], add: async () => true } },
17
+ read: { get: async () => undefined },
18
+ store: { LearnCloud: {} },
19
+ });
20
+
21
+ describe('createExportLearnCardBundleHelper', () => {
22
+ it('uses the default wallet when only options are provided', async () => {
23
+ const defaultWallet = createWallet('did:key:default');
24
+ const exportBundle = vi.fn(async () => ({
25
+ data: Buffer.from(''),
26
+ manifest: {} as never,
27
+ warnings: [],
28
+ }));
29
+ const helper = createExportLearnCardBundleHelper(exportBundle, defaultWallet);
30
+ const options: ExportLearnCardBundleOptions = { out: './bundle.zip', password: 'secret' };
31
+
32
+ await helper(options);
33
+
34
+ expect(exportBundle).toHaveBeenCalledWith(defaultWallet, options);
35
+ });
36
+
37
+ it('uses the provided wallet when wallet and options are both provided', async () => {
38
+ const defaultWallet = createWallet('did:key:default');
39
+ const otherWallet = createWallet('did:key:other');
40
+ const exportBundle = vi.fn(async () => ({
41
+ data: Buffer.from(''),
42
+ manifest: {} as never,
43
+ warnings: [],
44
+ }));
45
+ const helper = createExportLearnCardBundleHelper(exportBundle, defaultWallet);
46
+ const options: ExportLearnCardBundleOptions = { out: './bundle.zip', password: 'secret' };
47
+
48
+ await helper(otherWallet, options);
49
+
50
+ expect(exportBundle).toHaveBeenCalledWith(otherWallet, options);
51
+ });
52
+ });
53
+
54
+ describe('createRestoreLearnCardFromBundleHelper', () => {
55
+ it('merges default init config with per-call overrides', async () => {
56
+ const restoreBundle = vi.fn(async () => ({ restored: true }));
57
+ const helper = createRestoreLearnCardFromBundleHelper(restoreBundle, {
58
+ network: true,
59
+ allowRemoteContexts: true,
60
+ didkit: 'node',
61
+ });
62
+ const options: Omit<RestoreLearnCardFromBundleOptions, 'init'> & {
63
+ init?: Partial<RestoreLearnCardFromBundleOptions['init']>;
64
+ } = {
65
+ password: 'secret',
66
+ init: { network: 'http://localhost:4000/trpc' },
67
+ };
68
+
69
+ await helper('./bundle.zip', options);
70
+
71
+ expect(restoreBundle).toHaveBeenCalledWith('./bundle.zip', {
72
+ password: 'secret',
73
+ init: {
74
+ network: 'http://localhost:4000/trpc',
75
+ allowRemoteContexts: true,
76
+ didkit: 'node',
77
+ },
78
+ });
79
+ });
80
+ });
@@ -0,0 +1,43 @@
1
+ import type {
2
+ ExportLearnCardBundleOptions,
3
+ LearnCardBundleWallet,
4
+ RestoreLearnCardFromBundleOptions,
5
+ RestoreLearnCardInit,
6
+ } from '@learncard/holder-continuity';
7
+
8
+ type ExportLearnCardBundleFn = typeof import('@learncard/holder-continuity').exportLearnCardBundle;
9
+ type RestoreLearnCardFromBundleFn = typeof import('@learncard/holder-continuity').restoreLearnCardFromBundle;
10
+
11
+ export const createExportLearnCardBundleHelper = (
12
+ exportBundle: ExportLearnCardBundleFn,
13
+ defaultWallet: LearnCardBundleWallet
14
+ ) => {
15
+ return (
16
+ walletOrOptions: LearnCardBundleWallet | ExportLearnCardBundleOptions,
17
+ maybeOptions?: ExportLearnCardBundleOptions
18
+ ) => {
19
+ if (maybeOptions) return exportBundle(walletOrOptions as LearnCardBundleWallet, maybeOptions);
20
+
21
+ return exportBundle(defaultWallet, walletOrOptions as ExportLearnCardBundleOptions);
22
+ };
23
+ };
24
+
25
+ export const createRestoreLearnCardFromBundleHelper = (
26
+ restoreBundle: RestoreLearnCardFromBundleFn,
27
+ defaultInit: RestoreLearnCardInit
28
+ ) => {
29
+ return (
30
+ path: string,
31
+ options: (Omit<RestoreLearnCardFromBundleOptions, 'init'> & {
32
+ init?: Partial<RestoreLearnCardInit>;
33
+ }) = {}
34
+ ) => {
35
+ return restoreBundle(path, {
36
+ ...options,
37
+ init: {
38
+ ...defaultInit,
39
+ ...(options.init ?? {}),
40
+ } as RestoreLearnCardInit,
41
+ });
42
+ };
43
+ };
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node',
6
+ globals: true,
7
+ include: ['src/**/*.test.ts'],
8
+ exclude: ['src/bundle/**'],
9
+ },
10
+ });