@learncard/cli 3.3.176 → 3.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,85 @@
1
1
  # @learncard/cli
2
2
 
3
+ ## 3.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#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
8
+
9
+ 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.
10
+
11
+ Updates `@learncard/cli` to consume the new package and expose REPL helpers for export, import, and restore.
12
+
13
+ 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.
14
+
15
+ Adds bounded status-list fetching, optional verify-before-import support, bundle size guards, and capped holder metadata pagination.
16
+
17
+ ### Patch Changes
18
+
19
+ - 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)]:
20
+ - @learncard/types@5.17.0
21
+ - @learncard/holder-continuity@0.2.0
22
+ - @learncard/didkit-plugin@1.9.0
23
+ - @learncard/core@9.4.20
24
+ - @learncard/init@2.3.20
25
+ - @learncard/render-method-plugin@3.0.0
26
+ - @learncard/learn-cloud-plugin@2.3.25
27
+ - @learncard/ler-rs-plugin@0.1.11
28
+ - @learncard/linked-claims-plugin@0.2.20
29
+ - @learncard/open-badge-v2-plugin@1.1.21
30
+ - @learncard/simple-signing-plugin@1.1.24
31
+
32
+ ## 3.3.177
33
+
34
+ ### Patch Changes
35
+
36
+ - [#1240](https://github.com/learningeconomy/LearnCard/pull/1240) [`3a05603c72d76020b43ec6bbd5e31b2b31c0fd2b`](https://github.com/learningeconomy/LearnCard/commit/3a05603c72d76020b43ec6bbd5e31b2b31c0fd2b) Thanks [@gerardopar](https://github.com/gerardopar)! - feat: [LC-1812][LC-1855] - Add Render Method Plugin + Render Method Toggle
37
+
38
+ Adds `@learncard/render-method-plugin` for attaching and reading W3C `renderMethod` entries on Verifiable Credentials. The plugin is registered as part of the default LearnCard wallet stack.
39
+
40
+ **Write side** (`wallet.invoke`): `attachRenderMethod`, `buildTemplateRenderMethod`.
41
+
42
+ **Read side** (`wallet.invoke` and direct imports):
43
+
44
+ - String-based sugar (most common case): `findTemplateRenderMethod(vc, suite | suites[])`, `findTemplateRenderMethods(vc, suite | suites[])`.
45
+ - Backward-compatible alias: `getSvgMustacheRenderMethod`.
46
+ - Predicate-based escape hatch: `findRenderMethod`, `findRenderMethods`.
47
+ - Raw access: `getRenderMethods` (unwraps `CertifiedBoostCredential`, normalizes object↔array).
48
+ - Data shaping: `buildRenderData(vc, renderProperty?)` (Mustache context with `vc` / `credential` / `credentialSubjects` aliases, RFC 6901 overlay).
49
+ - Exported type guards: `isTemplateRenderMethod`, `isSvgMustacheRenderMethod`.
50
+
51
+ The pure data-shaping helpers (Mustache context, JSON Pointer overlay, boost unwrapping) moved out of `apps/learn-card-app/src/helpers/renderMethod.helpers.ts` into the plugin; the app now only owns the SVG-specific render pipeline (Mustache hydration + DOMPurify sanitization + React display).
52
+
53
+ **`@learncard/types`**: `TemplateRenderMethodValidator` widened — `renderSuite` and `outputPreference.mediaType` are now `z.string()` (were `z.literal('svg-mustache')` and `z.literal('image/svg+xml')`). The literals couldn't express the W3C spec's openness to other suites/types and made the plugin's "future-extensible" read API contradictory. Backward-compatible: all existing literal values still validate.
54
+
55
+ Behavior:
56
+
57
+ - `attachRenderMethod` is **opt-in**: calling it without a config returns the VC unchanged. Pass `{ templateId: DEFAULT_TEMPLATE_ID }` or a custom `{ templateId | templateValue }` to actually attach a render method. This avoids polluting every issued credential's `@context` with the render-method JSON-LD context URL.
58
+ - `templateId` must be an `http://` or `https://` URL. Empty values, `javascript:`, `file:`, `data:` and other schemes are rejected at the plugin boundary.
59
+ - `templateValue` is URL-encoded into a `data:image/svg+xml,` URI. Empty or whitespace-only values are rejected.
60
+ - The `useRenderMethodEnabled` LaunchDarkly flag gates display only (the `RenderMethodDisplay` component, the `BoostDisplayStyleSelector`, the `getSvgMustacheRenderMethod` lookup). It does not gate the write path.
61
+
62
+ Known risk:
63
+
64
+ - The JSON-LD context URL (`https://digitalbazaar.github.io/vc-render-method-context/contexts/v2rc2.jsonld`) is a community-group draft, not a finalized W3C TR. VCs issued with this context depend on the URL remaining stable. Follow-up work will bundle the context locally and migrate to the W3C TR URL when published.
65
+
66
+ Types:
67
+
68
+ - `@learncard/types` adds `TemplateRenderMethod`, `RenderMethod`, and a top-level `renderMethod` field on `UnsignedVC`.
69
+ - `learn-card-base` extends `BespokeLearnCard` to include `RenderMethodPlugin`, making `wallet.invoke.attachRenderMethod` and `wallet.invoke.buildTemplateRenderMethod` type-safe across the codebase.
70
+
71
+ - Updated dependencies [[`3a05603c72d76020b43ec6bbd5e31b2b31c0fd2b`](https://github.com/learningeconomy/LearnCard/commit/3a05603c72d76020b43ec6bbd5e31b2b31c0fd2b), [`37439411ac68618fc27898ac4c0f48dbef4e424b`](https://github.com/learningeconomy/LearnCard/commit/37439411ac68618fc27898ac4c0f48dbef4e424b), [`37439411ac68618fc27898ac4c0f48dbef4e424b`](https://github.com/learningeconomy/LearnCard/commit/37439411ac68618fc27898ac4c0f48dbef4e424b)]:
72
+ - @learncard/types@5.16.0
73
+ - @learncard/render-method-plugin@2.0.0
74
+ - @learncard/init@2.3.19
75
+ - @learncard/core@9.4.19
76
+ - @learncard/didkit-plugin@1.8.10
77
+ - @learncard/learn-cloud-plugin@2.3.24
78
+ - @learncard/ler-rs-plugin@0.1.10
79
+ - @learncard/linked-claims-plugin@0.2.19
80
+ - @learncard/open-badge-v2-plugin@1.1.20
81
+ - @learncard/simple-signing-plugin@1.1.23
82
+
3
83
  ## 3.3.176
4
84
 
5
85
  ### 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');
@@ -15,7 +17,9 @@ var figlet = require('figlet');
15
17
  var commander = require('commander');
16
18
  var clipboard = require('clipboardy');
17
19
  var lerRsPlugin = require('@learncard/ler-rs-plugin');
20
+ var renderMethodPlugin = require('@learncard/render-method-plugin');
18
21
  var crypto = require('crypto');
22
+ var holderContinuity = require('@learncard/holder-continuity');
19
23
 
20
24
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
21
25
 
@@ -30,14 +34,49 @@ var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
30
34
 
31
35
  const generateRandomSeed = () => crypto__default["default"].randomBytes(32).toString("hex");
32
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
+
33
71
  var name = "@learncard/cli";
34
- var version = "3.3.176";
72
+ var version = "3.4.0";
35
73
  var description = "Command Line Interface for LearnCard";
36
74
  var main = "dist/index.js";
37
75
  var bin = "dist/index.js";
38
76
  var scripts = {
39
77
  start: "rollup -c && node --enable-source-maps dist/index.js",
40
- build: "rollup -c"
78
+ build: "rollup -c",
79
+ test: "vitest run"
41
80
  };
42
81
  var author = "Learning Economy Foundation (www.learningeconomy.io)";
43
82
  var license = "MIT";
@@ -49,7 +88,9 @@ var dependencies = {
49
88
  "@learncard/ler-rs-plugin": "workspace:*",
50
89
  "@learncard/linked-claims-plugin": "workspace:*",
51
90
  "@learncard/open-badge-v2-plugin": "workspace:*",
91
+ "@learncard/render-method-plugin": "workspace:*",
52
92
  "@learncard/simple-signing-plugin": "workspace:*",
93
+ "@learncard/holder-continuity": "workspace:*",
53
94
  "@learncard/types": "workspace:*",
54
95
  "@rollup/plugin-json": "^4.1.0",
55
96
  clipboardy: "^4.0.0",
@@ -65,7 +106,8 @@ var devDependencies = {
65
106
  "@types/figlet": "^1.5.4",
66
107
  "@types/node": "^18.0.0",
67
108
  nodemon: "^2.0.16",
68
- "ts-node": "^10.8.1"
109
+ "ts-node": "^10.8.1",
110
+ vitest: "^1.6.0"
69
111
  };
70
112
  var repository = {
71
113
  type: "git",
@@ -100,7 +142,13 @@ const g = {
100
142
  seed: gradient__default["default"](["cyan", "green"])("seed"),
101
143
  generateRandomSeed: gradient__default["default"](["cyan", "green"])("generateRandomSeed"),
102
144
  types: gradient__default["default"](["cyan", "green"])("types"),
103
- 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")
104
152
  };
105
153
  const copyFunction = (text) => {
106
154
  if (typeof text === "object") {
@@ -113,7 +161,25 @@ const copyFunction = (text) => {
113
161
  clipboard__default["default"].writeSync(text);
114
162
  console.log("Copied to clipboard!");
115
163
  } catch (error) {
116
- 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");
117
183
  }
118
184
  };
119
185
  commander.program.version(packageJson.version).argument("[seed]").action(async (_seed = generateRandomSeed()) => {
@@ -130,13 +196,14 @@ commander.program.version(packageJson.version).argument("[seed]").action(async (
130
196
  globalThis.emptyLearnCard = init.emptyLearnCard;
131
197
  globalThis.learnCardFromSeed = init.learnCardFromSeed;
132
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
+ );
133
202
  const _learnCard = await init.initLearnCard({
134
203
  seed,
135
204
  network: true,
136
205
  allowRemoteContexts: true,
137
- didkit: fs__default["default"].readFile(
138
- require.resolve("@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm")
139
- )
206
+ didkit
140
207
  });
141
208
  const simpleSigningLc = await _learnCard.addPlugin(
142
209
  await simpleSigningPlugin.getSimpleSigningPlugin(_learnCard, "https://api.learncard.app/trpc")
@@ -148,56 +215,96 @@ commander.program.version(packageJson.version).argument("[seed]").action(async (
148
215
  globalThis.learnCard = await globalThis.learnCard.addPlugin(
149
216
  openBadgeV2Plugin.openBadgeV2Plugin(globalThis.learnCard)
150
217
  );
218
+ globalThis.learnCard = await globalThis.learnCard.addPlugin(
219
+ renderMethodPlugin.getRenderMethodPlugin(globalThis.learnCard)
220
+ );
151
221
  globalThis.types = types__default["default"];
152
222
  globalThis.getTestCache = core.getTestCache;
153
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;
154
240
  (_b = (_a = process.stdout).moveCursor) == null ? void 0 : _b.call(_a, 0, -1);
155
241
  (_d = (_c = process.stdout).clearLine) == null ? void 0 : _d.call(_c, 1);
156
242
  console.log("Wallet created!\n");
157
- 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");
158
- console.log("\u2502 Variables Available \u2502");
159
- 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");
160
- console.log("\u2502 Variable \u2502 Description \u2502");
161
- 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");
162
- console.log(`\u2502 ${g.learnCard} \u2502 Learn Card Wallet \u2502`);
163
- console.log(`\u2502 ${g.initLearnCard} \u2502 Wallet Instantiation Function \u2502`);
164
- console.log(`\u2502 ${g.seed} \u2502 Seed used to generate wallet \u2502`);
165
- console.log(`\u2502 ${g.generateRandomSeed} \u2502 Generates a random seed \u2502`);
166
- console.log(`\u2502 ${g.types} \u2502 Helpful zod validators \u2502`);
167
- console.log(`\u2502 ${g.copy} \u2502 Copy text to clipboard \u2502`);
168
- 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");
169
259
  console.log("");
170
260
  console.log(
171
261
  "For help/documentation regarding your wallet, please read the documentation at\n"
172
262
  );
173
263
  console.log("https://docs.learncard.com/sdks/learncard-core/construction\n");
174
264
  console.log("To get a feel for what's possible, try some of the following commands\n");
175
- 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");
176
- console.log("\u2502 Description \u2502 Command \u2502");
177
- 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");
178
265
  console.log(
179
- `\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`
180
288
  );
181
289
  console.log(
182
- `\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`
183
291
  );
184
292
  console.log(
185
- `\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`
186
294
  );
187
295
  console.log(
188
- `\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`
189
297
  );
190
298
  console.log(
191
- `\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`
192
300
  );
193
301
  console.log(
194
- `\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"
195
303
  );
196
- 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");
197
304
  console.log("");
198
305
  repl__default["default"].start({
199
306
  colorize: (input) => {
200
- 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);
201
308
  }
202
309
  });
203
310
  }).parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@learncard/cli",
3
- "version": "3.3.176",
3
+ "version": "3.4.0",
4
4
  "description": "Command Line Interface for LearnCard",
5
5
  "main": "dist/index.js",
6
6
  "bin": "dist/index.js",
@@ -15,22 +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/didkit-plugin": "^1.8.9",
19
- "@learncard/init": "^2.3.18",
20
- "@learncard/learn-cloud-plugin": "2.3.23",
21
- "@learncard/ler-rs-plugin": "0.1.9",
22
- "@learncard/linked-claims-plugin": "0.2.18",
23
- "@learncard/open-badge-v2-plugin": "1.1.19",
24
- "@learncard/simple-signing-plugin": "1.1.22",
25
- "@learncard/types": "5.15.0",
26
- "@learncard/core": "9.4.18"
18
+ "@learncard/core": "9.4.20",
19
+ "@learncard/didkit-plugin": "^1.9.0",
20
+ "@learncard/init": "^2.3.20",
21
+ "@learncard/learn-cloud-plugin": "2.3.25",
22
+ "@learncard/ler-rs-plugin": "0.1.11",
23
+ "@learncard/linked-claims-plugin": "0.2.20",
24
+ "@learncard/open-badge-v2-plugin": "1.1.21",
25
+ "@learncard/render-method-plugin": "3.0.0",
26
+ "@learncard/holder-continuity": "0.2.0",
27
+ "@learncard/types": "5.17.0",
28
+ "@learncard/simple-signing-plugin": "1.1.24"
27
29
  },
28
30
  "devDependencies": {
29
31
  "@types/cors": "^2.8.12",
30
32
  "@types/figlet": "^1.5.4",
31
33
  "@types/node": "^18.0.0",
32
34
  "nodemon": "^2.0.16",
33
- "ts-node": "^10.8.1"
35
+ "ts-node": "^10.8.1",
36
+ "vitest": "^1.6.0"
34
37
  },
35
38
  "repository": {
36
39
  "type": "git",
@@ -42,6 +45,7 @@
42
45
  },
43
46
  "scripts": {
44
47
  "start": "rollup -c && node --enable-source-maps dist/index.js",
45
- "build": "rollup -c"
48
+ "build": "rollup -c",
49
+ "test": "vitest run"
46
50
  }
47
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';
@@ -14,8 +16,20 @@ import { program } from 'commander';
14
16
  import clipboard from 'clipboardy';
15
17
 
16
18
  import { getLerRsPlugin } from '@learncard/ler-rs-plugin';
19
+ import { getRenderMethodPlugin } from '@learncard/render-method-plugin';
17
20
 
18
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';
19
33
 
20
34
  import packageJson from '../package.json';
21
35
 
@@ -29,7 +43,13 @@ const g = {
29
43
  seed: gradient(['cyan', 'green'])('seed'),
30
44
  generateRandomSeed: gradient(['cyan', 'green'])('generateRandomSeed'),
31
45
  types: gradient(['cyan', 'green'])('types'),
46
+ getLearnCardBundlePassword: gradient(['cyan', 'green'])('getLearnCardBundlePassword'),
32
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'),
33
53
  };
34
54
 
35
55
  const copyFunction = (text: string | object | number) => {
@@ -43,7 +63,28 @@ const copyFunction = (text: string | object | number) => {
43
63
  clipboard.writeSync(text);
44
64
  console.log('Copied to clipboard!');
45
65
  } catch (error) {
46
- 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');
47
88
  }
48
89
  };
49
90
 
@@ -68,13 +109,15 @@ program
68
109
  globalThis.learnCardFromSeed = learnCardFromSeed;
69
110
  globalThis.initLearnCard = initLearnCard;
70
111
 
112
+ const didkit = fs.readFile(
113
+ require.resolve('@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm')
114
+ );
115
+
71
116
  const _learnCard = await initLearnCard({
72
117
  seed,
73
118
  network: true,
74
119
  allowRemoteContexts: true,
75
- didkit: fs.readFile(
76
- require.resolve('@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm')
77
- ),
120
+ didkit,
78
121
  });
79
122
 
80
123
  const simpleSigningLc = await _learnCard.addPlugin(
@@ -92,10 +135,32 @@ program
92
135
  openBadgeV2Plugin(globalThis.learnCard)
93
136
  );
94
137
 
138
+ // Add Render Method plugin for attaching W3C renderMethod to VCs
139
+ globalThis.learnCard = await globalThis.learnCard.addPlugin(
140
+ getRenderMethodPlugin(globalThis.learnCard)
141
+ );
142
+
95
143
  globalThis.types = types;
96
144
  globalThis.getTestCache = getTestCache;
97
145
 
98
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;
99
164
 
100
165
  // delete 'Creating wallet...' message
101
166
  process.stdout.moveCursor?.(0, -1);
@@ -103,18 +168,22 @@ program
103
168
 
104
169
  console.log('Wallet created!\n');
105
170
 
106
- console.log('┌────────────────────────────────────────────────────┐');
107
- console.log('│ Variables Available │');
108
- console.log('├────────────────────┬───────────────────────────────┤');
109
- console.log('│ Variable │ Description │');
110
- console.log('├────────────────────┼───────────────────────────────┤');
111
- console.log(`│ ${g.learnCard} │ Learn Card Wallet │`);
112
- console.log(`│ ${g.initLearnCard} │ Wallet Instantiation Function │`);
113
- console.log(`│ ${g.seed} │ Seed used to generate wallet │`);
114
- console.log(`│ ${g.generateRandomSeed} │ Generates a random seed │`);
115
- console.log(`│ ${g.types} │ Helpful zod validators │`);
116
- console.log(`│ ${g.copy} │ Copy text to clipboard │`);
117
- 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('└────────────────────────────┴──────────────────────────────────┘');
118
187
 
119
188
  console.log('');
120
189
 
@@ -126,28 +195,45 @@ program
126
195
 
127
196
  console.log("To get a feel for what's possible, try some of the following commands\n");
128
197
 
129
- console.log('┌─────────────────────────┬────────────────────────────────────────────────┐');
130
- console.log('│ Description │ Command │');
131
- console.log('├─────────────────────────┼────────────────────────────────────────────────┤');
132
198
  console.log(
133
- `│ 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); │`
134
221
  );
135
222
  console.log(
136
- `│ Generate an unsigned VC │ ${g.learnCard}.invoke.getTestVc(); │`
223
+ `│ Verify a signed VP │ await ${g.learnCard}.invoke.verifyPresentation(vp); │`
137
224
  );
138
225
  console.log(
139
- `│ Issue a signed VC │ await ${g.learnCard}.invoke.issueCredential(uvc); │`
226
+ `│ Prompt bundle password │ const password = await ${g.getLearnCardBundlePassword}(); │`
140
227
  );
141
228
  console.log(
142
- `│ Verify a signed VC │ await ${g.learnCard}.invoke.verifyCredential(vc); │`
229
+ `│ Export wallet ZIP │ await ${g.exportLearnCardBundle}(${g.learnCard}, { out: './export.zip', password }); │`
143
230
  );
144
231
  console.log(
145
- `│ Issue a signed VP │ await ${g.learnCard}.invoke.issuePresentation(vc); │`
232
+ `│ Restore original wallet │ await ${g.restoreLearnCardFromBundle}('./export.zip', { password }); │`
146
233
  );
147
234
  console.log(
148
- `│ Verify a signed VP │ await ${g.learnCard}.invoke.verifyPresentation(vp); │`
235
+ '└─────────────────────────┴───────────────────────────────────────────────────────────────────────────────────┘'
149
236
  );
150
- console.log('└─────────────────────────┴────────────────────────────────────────────────┘');
151
237
 
152
238
  console.log('');
153
239
 
@@ -160,7 +246,13 @@ program
160
246
  .replace('learnCard', g.learnCard)
161
247
  .replace('seed', g.seed)
162
248
  .replace('generateRandomSeed', g.generateRandomSeed)
163
- .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);
164
256
  },
165
257
  });
166
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
+ });