@learncard/cli 3.4.5 → 3.4.6

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,24 @@
1
1
  # @learncard/cli
2
2
 
3
+ ## 3.4.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1303](https://github.com/learningeconomy/LearnCard/pull/1303) [`59d79e9c2aed145284d6cc3de4c53ef0d3415299`](https://github.com/learningeconomy/LearnCard/commit/59d79e9c2aed145284d6cc3de4c53ef0d3415299) Thanks [@TaylorBeeston](https://github.com/TaylorBeeston)! - Switch workspace development to Bun source-mode resolution while preserving package build outputs for npm publishing.
8
+
9
+ - Updated dependencies [[`59d79e9c2aed145284d6cc3de4c53ef0d3415299`](https://github.com/learningeconomy/LearnCard/commit/59d79e9c2aed145284d6cc3de4c53ef0d3415299), [`8bcccce23f919e9bcd0d22d87e7d33242b557930`](https://github.com/learningeconomy/LearnCard/commit/8bcccce23f919e9bcd0d22d87e7d33242b557930), [`8bcccce23f919e9bcd0d22d87e7d33242b557930`](https://github.com/learningeconomy/LearnCard/commit/8bcccce23f919e9bcd0d22d87e7d33242b557930), [`8bcccce23f919e9bcd0d22d87e7d33242b557930`](https://github.com/learningeconomy/LearnCard/commit/8bcccce23f919e9bcd0d22d87e7d33242b557930)]:
10
+ - @learncard/holder-continuity@0.2.6
11
+ - @learncard/core@9.4.25
12
+ - @learncard/init@2.4.4
13
+ - @learncard/types@5.17.5
14
+ - @learncard/didkit-plugin@1.9.5
15
+ - @learncard/learn-cloud-plugin@2.3.30
16
+ - @learncard/ler-rs-plugin@0.1.16
17
+ - @learncard/linked-claims-plugin@0.2.25
18
+ - @learncard/open-badge-v2-plugin@1.1.26
19
+ - @learncard/render-method-plugin@3.0.5
20
+ - @learncard/simple-signing-plugin@1.1.29
21
+
3
22
  ## 3.4.5
4
23
 
5
24
  ### Patch Changes
package/dist/index.js CHANGED
@@ -3,9 +3,9 @@
3
3
 
4
4
  var fs = require('fs/promises');
5
5
  var dns = require('node:dns');
6
+ var node_readline = require('node:readline');
6
7
  var promises = require('node:readline/promises');
7
8
  var node_stream = require('node:stream');
8
- var repl = require('pretty-repl');
9
9
  var core = require('@learncard/core');
10
10
  var init = require('@learncard/init');
11
11
  var simpleSigningPlugin = require('@learncard/simple-signing-plugin');
@@ -23,10 +23,27 @@ var holderContinuity = require('@learncard/holder-continuity');
23
23
 
24
24
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
25
25
 
26
+ function _interopNamespace(e) {
27
+ if (e && e.__esModule) return e;
28
+ var n = Object.create(null);
29
+ if (e) {
30
+ Object.keys(e).forEach(function (k) {
31
+ if (k !== 'default') {
32
+ var d = Object.getOwnPropertyDescriptor(e, k);
33
+ Object.defineProperty(n, k, d.get ? d : {
34
+ enumerable: true,
35
+ get: function () { return e[k]; }
36
+ });
37
+ }
38
+ });
39
+ }
40
+ n["default"] = e;
41
+ return Object.freeze(n);
42
+ }
43
+
26
44
  var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
27
45
  var dns__default = /*#__PURE__*/_interopDefaultLegacy(dns);
28
- var repl__default = /*#__PURE__*/_interopDefaultLegacy(repl);
29
- var types__default = /*#__PURE__*/_interopDefaultLegacy(types);
46
+ var types__namespace = /*#__PURE__*/_interopNamespace(types);
30
47
  var gradient__default = /*#__PURE__*/_interopDefaultLegacy(gradient);
31
48
  var figlet__default = /*#__PURE__*/_interopDefaultLegacy(figlet);
32
49
  var clipboard__default = /*#__PURE__*/_interopDefaultLegacy(clipboard);
@@ -70,12 +87,14 @@ const createRestoreLearnCardFromBundleHelper = (restoreBundle, defaultInit) => {
70
87
  };
71
88
 
72
89
  var name = "@learncard/cli";
73
- var version = "3.4.5";
90
+ var version = "3.4.6";
74
91
  var description = "Command Line Interface for LearnCard";
75
92
  var main = "dist/index.js";
76
93
  var bin = "dist/index.js";
77
94
  var scripts = {
78
- start: "rollup -c && node --enable-source-maps dist/index.js",
95
+ start: "bun --conditions=development src/index.tsx",
96
+ "start:built": "rollup -c && node --enable-source-maps dist/index.js",
97
+ dev: "bun --conditions=development src/index.tsx",
79
98
  build: "rollup -c",
80
99
  test: "vitest run"
81
100
  };
@@ -134,22 +153,56 @@ var packageJson = {
134
153
  bugs: bugs
135
154
  };
136
155
 
156
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
157
+ var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it);
137
158
  dns__default["default"].setDefaultResultOrder("ipv4first");
159
+ const replGlobals = [
160
+ "learnCard",
161
+ "emptyLearnCard",
162
+ "learnCardFromSeed",
163
+ "initLearnCard",
164
+ "seed",
165
+ "generateRandomSeed",
166
+ "types",
167
+ "getTestCache",
168
+ "copy",
169
+ "getLearnCardBundlePassword",
170
+ "exportLearnCardBundle",
171
+ "importLearnCardBundle",
172
+ "createLearnCardBundle",
173
+ "readLearnCardBundle",
174
+ "restoreLearnCardFromBundle"
175
+ ];
176
+ const replCommands = [".exit"];
177
+ const identifierPattern = /^[A-Za-z_$][\w$]*$/;
178
+ const colorizeName = (name) => {
179
+ const colorizedName = gradient__default["default"](["cyan", "green"])(name);
180
+ if (colorizedName !== name || !process.stdout.isTTY) return colorizedName;
181
+ return `\x1B[36m${name}\x1B[39m`;
182
+ };
138
183
  const g = {
139
- learnCard: gradient__default["default"](["cyan", "green"])("learnCard"),
140
- emptyLearnCard: gradient__default["default"](["cyan", "green"])("emptyLearnCard"),
141
- learnCardFromKey: gradient__default["default"](["cyan", "green"])("learnCardFromKey"),
142
- initLearnCard: gradient__default["default"](["cyan", "green"])("initLearnCard"),
143
- seed: gradient__default["default"](["cyan", "green"])("seed"),
144
- generateRandomSeed: gradient__default["default"](["cyan", "green"])("generateRandomSeed"),
145
- types: gradient__default["default"](["cyan", "green"])("types"),
146
- getLearnCardBundlePassword: gradient__default["default"](["cyan", "green"])("getLearnCardBundlePassword"),
147
- copy: gradient__default["default"](["cyan", "green"])("copy"),
148
- exportLearnCardBundle: gradient__default["default"](["cyan", "green"])("exportLearnCardBundle"),
149
- importLearnCardBundle: gradient__default["default"](["cyan", "green"])("importLearnCardBundle"),
150
- createLearnCardBundle: gradient__default["default"](["cyan", "green"])("createLearnCardBundle"),
151
- readLearnCardBundle: gradient__default["default"](["cyan", "green"])("readLearnCardBundle"),
152
- restoreLearnCardFromBundle: gradient__default["default"](["cyan", "green"])("restoreLearnCardFromBundle")
184
+ learnCard: colorizeName("learnCard"),
185
+ emptyLearnCard: colorizeName("emptyLearnCard"),
186
+ learnCardFromSeed: colorizeName("learnCardFromSeed"),
187
+ initLearnCard: colorizeName("initLearnCard"),
188
+ seed: colorizeName("seed"),
189
+ generateRandomSeed: colorizeName("generateRandomSeed"),
190
+ types: colorizeName("types"),
191
+ getTestCache: colorizeName("getTestCache"),
192
+ getLearnCardBundlePassword: colorizeName("getLearnCardBundlePassword"),
193
+ copy: colorizeName("copy"),
194
+ exportLearnCardBundle: colorizeName("exportLearnCardBundle"),
195
+ importLearnCardBundle: colorizeName("importLearnCardBundle"),
196
+ createLearnCardBundle: colorizeName("createLearnCardBundle"),
197
+ readLearnCardBundle: colorizeName("readLearnCardBundle"),
198
+ restoreLearnCardFromBundle: colorizeName("restoreLearnCardFromBundle")
199
+ };
200
+ const colorizeReplInput = (input) => {
201
+ return [...replGlobals].sort((a, b) => b.length - a.length).reduce((output, name) => {
202
+ const colorizedName = g[name];
203
+ if (!colorizedName) return output;
204
+ return output.replace(new RegExp(`\\b${name}\\b`, "g"), colorizedName);
205
+ }, input);
153
206
  };
154
207
  const copyFunction = (text) => {
155
208
  if (typeof text === "object") {
@@ -183,6 +236,277 @@ const getLearnCardBundlePassword = async (prompt = "Bundle password: ") => {
183
236
  process.stdout.write("\n");
184
237
  }
185
238
  };
239
+ const globalAssignmentPattern = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([\s\S]*?);?\s*$/;
240
+ const parseGlobalAssignment = (source) => {
241
+ const match = source.match(globalAssignmentPattern);
242
+ if (!match) return;
243
+ const [, name, initializer] = match;
244
+ return { name, initializer };
245
+ };
246
+ const getCurrentToken = (line) => {
247
+ var _a, _b;
248
+ return (_b = (_a = line.match(/(?:[A-Za-z_$][\w$]*\.)*[A-Za-z_$][\w$]*\.?$/)) == null ? void 0 : _a[0]) != null ? _b : "";
249
+ };
250
+ const getCompletionBase = (token) => {
251
+ const lastDotIndex = token.lastIndexOf(".");
252
+ if (lastDotIndex === -1) return;
253
+ return token.slice(0, lastDotIndex);
254
+ };
255
+ const getCompletionPrefix = (token) => {
256
+ const lastDotIndex = token.lastIndexOf(".");
257
+ if (lastDotIndex === -1) return token;
258
+ return token.slice(lastDotIndex + 1);
259
+ };
260
+ const getOwnPropertyNames = (value) => {
261
+ if (value === null || value === void 0) return [];
262
+ const names = /* @__PURE__ */ new Set();
263
+ let current = Object(value);
264
+ while (current && current !== Object.prototype) {
265
+ for (const name of Object.getOwnPropertyNames(current)) names.add(name);
266
+ current = Object.getPrototypeOf(current);
267
+ }
268
+ return [...names];
269
+ };
270
+ const completeBunReplInput = (line) => {
271
+ const token = getCurrentToken(line);
272
+ if (!token) return [replCommands, ""];
273
+ const base = getCompletionBase(token);
274
+ const prefix = getCompletionPrefix(token);
275
+ if (!base) {
276
+ const completions = [...replCommands, ...replGlobals].filter(
277
+ (name) => name.startsWith(prefix)
278
+ );
279
+ return [completions.length ? completions : replGlobals, token];
280
+ }
281
+ try {
282
+ const target = globalThis.eval(base);
283
+ const completions = getOwnPropertyNames(target).filter((name) => identifierPattern.test(name)).filter((name) => name.startsWith(prefix)).map((name) => `${base}.${name}`);
284
+ return [completions, token];
285
+ } catch (e) {
286
+ return [[], token];
287
+ }
288
+ };
289
+ const evaluateBunReplInput = async (source) => {
290
+ try {
291
+ return await globalThis.eval(`(async () => (${source}))()`);
292
+ } catch (e) {
293
+ const globalAssignment = parseGlobalAssignment(source);
294
+ if (globalAssignment) {
295
+ const value = await globalThis.eval(
296
+ `(async () => (${globalAssignment.initializer}))()`
297
+ );
298
+ globalThis[globalAssignment.name] = value;
299
+ return value;
300
+ }
301
+ return await globalThis.eval(`(async () => { ${source} })()`);
302
+ }
303
+ };
304
+ const getSharedCompletion = (matches) => {
305
+ if (!matches.length) return;
306
+ return matches.reduce((prefix, match) => {
307
+ let index = 0;
308
+ while (index < prefix.length && prefix[index] === match[index]) index += 1;
309
+ return prefix.slice(0, index);
310
+ });
311
+ };
312
+ const renderBunReplLine = (prompt, line, cursor, colorize) => {
313
+ process.stdout.clearLine(0);
314
+ process.stdout.cursorTo(0);
315
+ process.stdout.write(prompt + colorize(line));
316
+ const trailingLength = line.length - cursor;
317
+ if (trailingLength > 0) process.stdout.write(`\x1B[${trailingLength}D`);
318
+ };
319
+ const startReadlineRepl = async (colorize) => {
320
+ const rl = promises.createInterface({
321
+ input: process.stdin,
322
+ output: process.stdout,
323
+ prompt: "> ",
324
+ completer: completeBunReplInput
325
+ });
326
+ rl.prompt();
327
+ try {
328
+ for (var iter = __forAwait(rl), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
329
+ const line = temp.value;
330
+ const source = line.trim();
331
+ if (source === ".exit") break;
332
+ if (!source) {
333
+ rl.prompt();
334
+ continue;
335
+ }
336
+ try {
337
+ const result = await evaluateBunReplInput(source);
338
+ if (result !== void 0) console.dir(result, { depth: 6 });
339
+ } catch (error2) {
340
+ console.error(error2);
341
+ }
342
+ if (colorize) process.stdout.write(colorize(""));
343
+ rl.prompt();
344
+ }
345
+ } catch (temp) {
346
+ error = [temp];
347
+ } finally {
348
+ try {
349
+ more && (temp = iter.return) && await temp.call(iter);
350
+ } finally {
351
+ if (error)
352
+ throw error[0];
353
+ }
354
+ }
355
+ rl.close();
356
+ };
357
+ const startBunRepl = async (colorize) => {
358
+ if (!process.stdin.isTTY || !process.stdin.setRawMode) {
359
+ await startReadlineRepl(colorize);
360
+ return;
361
+ }
362
+ const prompt = "> ";
363
+ const history = [];
364
+ let line = "";
365
+ let cursor = 0;
366
+ let historyIndex = 0;
367
+ let draft = "";
368
+ let pending = Promise.resolve();
369
+ const resetHistory = () => {
370
+ historyIndex = history.length;
371
+ draft = "";
372
+ };
373
+ const setLine = (nextLine) => {
374
+ line = nextLine;
375
+ cursor = line.length;
376
+ renderBunReplLine(prompt, line, cursor, colorize);
377
+ };
378
+ const insertText = (text) => {
379
+ line = line.slice(0, cursor) + text + line.slice(cursor);
380
+ cursor += text.length;
381
+ renderBunReplLine(prompt, line, cursor, colorize);
382
+ };
383
+ const applyCompletion = () => {
384
+ const linePrefix = line.slice(0, cursor);
385
+ const [matches, token] = completeBunReplInput(linePrefix);
386
+ const completion = matches.length === 1 ? matches[0] : getSharedCompletion(matches);
387
+ if (completion && completion !== token) {
388
+ line = line.slice(0, cursor - token.length) + completion + line.slice(cursor);
389
+ cursor += completion.length - token.length;
390
+ renderBunReplLine(prompt, line, cursor, colorize);
391
+ return;
392
+ }
393
+ if (matches.length > 1) {
394
+ process.stdout.write(`
395
+ ${matches.join(" ")}
396
+ `);
397
+ renderBunReplLine(prompt, line, cursor, colorize);
398
+ }
399
+ };
400
+ const evaluateLine = async () => {
401
+ process.stdout.write("\n");
402
+ const source = line.trim();
403
+ if (source === ".exit") return false;
404
+ if (source) {
405
+ history.push(source);
406
+ try {
407
+ const result = await evaluateBunReplInput(source);
408
+ if (result !== void 0) console.dir(result, { depth: 6 });
409
+ } catch (error) {
410
+ console.error(error);
411
+ }
412
+ }
413
+ line = "";
414
+ cursor = 0;
415
+ resetHistory();
416
+ renderBunReplLine(prompt, line, cursor, colorize);
417
+ return true;
418
+ };
419
+ const handleKey = async (input, key) => {
420
+ var _a, _b;
421
+ if (key.ctrl && key.name === "c") return false;
422
+ if (key.name === "return" || key.name === "enter") return await evaluateLine();
423
+ if (key.ctrl && key.name === "d" && !line) return false;
424
+ if (key.name === "tab") {
425
+ applyCompletion();
426
+ return true;
427
+ }
428
+ if (key.name === "backspace") {
429
+ if (cursor === 0) return true;
430
+ line = line.slice(0, cursor - 1) + line.slice(cursor);
431
+ cursor -= 1;
432
+ renderBunReplLine(prompt, line, cursor, colorize);
433
+ return true;
434
+ }
435
+ if (key.name === "delete") {
436
+ if (cursor >= line.length) return true;
437
+ line = line.slice(0, cursor) + line.slice(cursor + 1);
438
+ renderBunReplLine(prompt, line, cursor, colorize);
439
+ return true;
440
+ }
441
+ if (key.name === "left") {
442
+ if (cursor > 0) cursor -= 1;
443
+ renderBunReplLine(prompt, line, cursor, colorize);
444
+ return true;
445
+ }
446
+ if (key.name === "right") {
447
+ if (cursor < line.length) cursor += 1;
448
+ renderBunReplLine(prompt, line, cursor, colorize);
449
+ return true;
450
+ }
451
+ if (key.name === "home") {
452
+ cursor = 0;
453
+ renderBunReplLine(prompt, line, cursor, colorize);
454
+ return true;
455
+ }
456
+ if (key.name === "end") {
457
+ cursor = line.length;
458
+ renderBunReplLine(prompt, line, cursor, colorize);
459
+ return true;
460
+ }
461
+ if (key.name === "up") {
462
+ if (historyIndex === history.length) draft = line;
463
+ if (historyIndex > 0) historyIndex -= 1;
464
+ setLine((_a = history[historyIndex]) != null ? _a : draft);
465
+ return true;
466
+ }
467
+ if (key.name === "down") {
468
+ if (historyIndex < history.length) historyIndex += 1;
469
+ setLine(historyIndex === history.length ? draft : (_b = history[historyIndex]) != null ? _b : "");
470
+ return true;
471
+ }
472
+ if (input && !key.ctrl && input >= " ") {
473
+ insertText(input);
474
+ resetHistory();
475
+ }
476
+ return true;
477
+ };
478
+ await new Promise((resolve) => {
479
+ var _a, _b;
480
+ const onKeypress = (input, key) => {
481
+ pending = pending.then(async () => {
482
+ const keepRunning = await handleKey(input, key);
483
+ if (!keepRunning) cleanup();
484
+ });
485
+ };
486
+ const cleanup = () => {
487
+ var _a2, _b2;
488
+ process.stdin.off("keypress", onKeypress);
489
+ (_b2 = (_a2 = process.stdin).setRawMode) == null ? void 0 : _b2.call(_a2, false);
490
+ process.stdin.pause();
491
+ process.stdout.write("\n");
492
+ resolve();
493
+ };
494
+ node_readline.emitKeypressEvents(process.stdin);
495
+ (_b = (_a = process.stdin).setRawMode) == null ? void 0 : _b.call(_a, true);
496
+ process.stdin.resume();
497
+ console.log("Bun dev REPL ready. Tab completes globals/properties. Type .exit to quit.");
498
+ renderBunReplLine(prompt, line, cursor, colorize);
499
+ process.stdin.on("keypress", onKeypress);
500
+ });
501
+ };
502
+ const startCliRepl = async (colorize) => {
503
+ if (!("Bun" in globalThis)) {
504
+ const repl = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('pretty-repl')); });
505
+ repl.default.start({ colorize });
506
+ return;
507
+ }
508
+ await startBunRepl(colorize);
509
+ };
186
510
  commander.program.version(packageJson.version).argument("[seed]").action(async (_seed = generateRandomSeed()) => {
187
511
  var _a, _b, _c, _d;
188
512
  console.clear();
@@ -219,7 +543,7 @@ commander.program.version(packageJson.version).argument("[seed]").action(async (
219
543
  globalThis.learnCard = await globalThis.learnCard.addPlugin(
220
544
  renderMethodPlugin.getRenderMethodPlugin(globalThis.learnCard)
221
545
  );
222
- globalThis.types = types__default["default"];
546
+ globalThis.types = types__namespace;
223
547
  globalThis.getTestCache = core.getTestCache;
224
548
  globalThis.copy = copyFunction;
225
549
  globalThis.getLearnCardBundlePassword = getLearnCardBundlePassword;
@@ -303,9 +627,5 @@ commander.program.version(packageJson.version).argument("[seed]").action(async (
303
627
  "\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"
304
628
  );
305
629
  console.log("");
306
- repl__default["default"].start({
307
- colorize: (input) => {
308
- 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);
309
- }
310
- });
630
+ await startCliRepl(colorizeReplInput);
311
631
  }).parse(process.argv);
package/package.json CHANGED
@@ -1,51 +1,53 @@
1
1
  {
2
- "name": "@learncard/cli",
3
- "version": "3.4.5",
4
- "description": "Command Line Interface for LearnCard",
5
- "main": "dist/index.js",
6
- "bin": "dist/index.js",
7
- "author": "Learning Economy Foundation (www.learningeconomy.io)",
8
- "license": "MIT",
9
- "dependencies": {
10
- "@rollup/plugin-json": "^4.1.0",
11
- "clipboardy": "^4.0.0",
12
- "commander": "^9.3.0",
13
- "figlet": "^1.5.2",
14
- "gradient-string": "^2.0.1",
15
- "pretty-repl": "^3.1.1",
16
- "rollup": "^2.80.0",
17
- "rollup-plugin-esbuild": "^4.9.1",
18
- "@learncard/core": "9.4.24",
19
- "@learncard/didkit-plugin": "^1.9.4",
20
- "@learncard/learn-cloud-plugin": "2.3.29",
21
- "@learncard/linked-claims-plugin": "0.2.24",
22
- "@learncard/ler-rs-plugin": "0.1.15",
23
- "@learncard/open-badge-v2-plugin": "1.1.25",
24
- "@learncard/render-method-plugin": "3.0.4",
25
- "@learncard/simple-signing-plugin": "1.1.28",
26
- "@learncard/holder-continuity": "0.2.5",
27
- "@learncard/types": "5.17.4",
28
- "@learncard/init": "^2.4.3"
29
- },
30
- "devDependencies": {
31
- "@types/cors": "^2.8.12",
32
- "@types/figlet": "^1.5.4",
33
- "@types/node": "^18.0.0",
34
- "nodemon": "^2.0.16",
35
- "ts-node": "^10.8.1",
36
- "vitest": "^1.6.0"
37
- },
38
- "repository": {
39
- "type": "git",
40
- "url": "https://github.com/learningeconomy/LearnCard"
41
- },
42
- "homepage": "https://github.com/learningeconomy/LearnCard/tree/main/packages/learn-card-cli/README.md",
43
- "bugs": {
44
- "url": "https://github.com/learningeconomy/LearnCard/issues"
45
- },
46
- "scripts": {
47
- "start": "rollup -c && node --enable-source-maps dist/index.js",
48
- "build": "rollup -c",
49
- "test": "vitest run"
50
- }
51
- }
2
+ "name": "@learncard/cli",
3
+ "version": "3.4.6",
4
+ "description": "Command Line Interface for LearnCard",
5
+ "main": "dist/index.js",
6
+ "bin": "dist/index.js",
7
+ "scripts": {
8
+ "start": "bun --conditions=development src/index.tsx",
9
+ "start:built": "rollup -c && node --enable-source-maps dist/index.js",
10
+ "dev": "bun --conditions=development src/index.tsx",
11
+ "build": "rollup -c",
12
+ "test": "vitest run"
13
+ },
14
+ "author": "Learning Economy Foundation (www.learningeconomy.io)",
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "@learncard/core": "workspace:*",
18
+ "@learncard/didkit-plugin": "workspace:^",
19
+ "@learncard/init": "workspace:^",
20
+ "@learncard/learn-cloud-plugin": "workspace:*",
21
+ "@learncard/ler-rs-plugin": "workspace:*",
22
+ "@learncard/linked-claims-plugin": "workspace:*",
23
+ "@learncard/open-badge-v2-plugin": "workspace:*",
24
+ "@learncard/render-method-plugin": "workspace:*",
25
+ "@learncard/simple-signing-plugin": "workspace:*",
26
+ "@learncard/holder-continuity": "workspace:*",
27
+ "@learncard/types": "workspace:*",
28
+ "@rollup/plugin-json": "^4.1.0",
29
+ "clipboardy": "^4.0.0",
30
+ "commander": "^9.3.0",
31
+ "figlet": "^1.5.2",
32
+ "gradient-string": "^2.0.1",
33
+ "pretty-repl": "^3.1.1",
34
+ "rollup": "^2.80.0",
35
+ "rollup-plugin-esbuild": "^4.9.1"
36
+ },
37
+ "devDependencies": {
38
+ "@types/cors": "^2.8.12",
39
+ "@types/figlet": "^1.5.4",
40
+ "@types/node": "^18.0.0",
41
+ "nodemon": "^2.0.16",
42
+ "ts-node": "^10.8.1",
43
+ "vitest": "^1.6.0"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/learningeconomy/LearnCard"
48
+ },
49
+ "homepage": "https://github.com/learningeconomy/LearnCard/tree/main/packages/learn-card-cli/README.md",
50
+ "bugs": {
51
+ "url": "https://github.com/learningeconomy/LearnCard/issues"
52
+ }
53
+ }
package/project.json CHANGED
@@ -35,7 +35,7 @@
35
35
  "executor": "./tools/executors/workspace:run-command",
36
36
  "dependsOn": ["^build"],
37
37
  "options": {
38
- "command": "pnpm start",
38
+ "command": "bun run start",
39
39
  "cwd": "{root}/packages/learn-card-cli"
40
40
  }
41
41
  }
package/src/index.tsx CHANGED
@@ -1,14 +1,14 @@
1
1
  import fs from 'fs/promises';
2
2
  import dns from 'node:dns';
3
+ import { emitKeypressEvents } from 'node:readline';
3
4
  import { createInterface } from 'node:readline/promises';
4
5
  import { Writable } from 'node:stream';
5
6
 
6
- import repl from 'pretty-repl';
7
7
  import { getTestCache } from '@learncard/core';
8
8
  import { initLearnCard, emptyLearnCard, learnCardFromSeed } from '@learncard/init';
9
9
  import { getSimpleSigningPlugin } from '@learncard/simple-signing-plugin';
10
10
  import { openBadgeV2Plugin } from '@learncard/open-badge-v2-plugin';
11
- import types from '@learncard/types';
11
+ import * as types from '@learncard/types';
12
12
  import { getLinkedClaimsPlugin } from '@learncard/linked-claims-plugin';
13
13
  import gradient from 'gradient-string';
14
14
  import figlet from 'figlet';
@@ -35,21 +35,71 @@ import packageJson from '../package.json';
35
35
 
36
36
  dns.setDefaultResultOrder('ipv4first');
37
37
 
38
+ type ReplCompletion = [string[], string];
39
+ type ReplKey = {
40
+ ctrl?: boolean;
41
+ name?: string;
42
+ sequence?: string;
43
+ };
44
+
45
+ const replGlobals = [
46
+ 'learnCard',
47
+ 'emptyLearnCard',
48
+ 'learnCardFromSeed',
49
+ 'initLearnCard',
50
+ 'seed',
51
+ 'generateRandomSeed',
52
+ 'types',
53
+ 'getTestCache',
54
+ 'copy',
55
+ 'getLearnCardBundlePassword',
56
+ 'exportLearnCardBundle',
57
+ 'importLearnCardBundle',
58
+ 'createLearnCardBundle',
59
+ 'readLearnCardBundle',
60
+ 'restoreLearnCardFromBundle',
61
+ ];
62
+
63
+ const replCommands = ['.exit'];
64
+
65
+ const identifierPattern = /^[A-Za-z_$][\w$]*$/;
66
+
67
+ const colorizeName = (name: string): string => {
68
+ const colorizedName = gradient(['cyan', 'green'])(name);
69
+
70
+ if (colorizedName !== name || !process.stdout.isTTY) return colorizedName;
71
+
72
+ return `\x1b[36m${name}\x1b[39m`;
73
+ };
74
+
38
75
  const g = {
39
- learnCard: gradient(['cyan', 'green'])('learnCard'),
40
- emptyLearnCard: gradient(['cyan', 'green'])('emptyLearnCard'),
41
- learnCardFromKey: gradient(['cyan', 'green'])('learnCardFromKey'),
42
- initLearnCard: gradient(['cyan', 'green'])('initLearnCard'),
43
- seed: gradient(['cyan', 'green'])('seed'),
44
- generateRandomSeed: gradient(['cyan', 'green'])('generateRandomSeed'),
45
- types: gradient(['cyan', 'green'])('types'),
46
- getLearnCardBundlePassword: gradient(['cyan', 'green'])('getLearnCardBundlePassword'),
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'),
76
+ learnCard: colorizeName('learnCard'),
77
+ emptyLearnCard: colorizeName('emptyLearnCard'),
78
+ learnCardFromSeed: colorizeName('learnCardFromSeed'),
79
+ initLearnCard: colorizeName('initLearnCard'),
80
+ seed: colorizeName('seed'),
81
+ generateRandomSeed: colorizeName('generateRandomSeed'),
82
+ types: colorizeName('types'),
83
+ getTestCache: colorizeName('getTestCache'),
84
+ getLearnCardBundlePassword: colorizeName('getLearnCardBundlePassword'),
85
+ copy: colorizeName('copy'),
86
+ exportLearnCardBundle: colorizeName('exportLearnCardBundle'),
87
+ importLearnCardBundle: colorizeName('importLearnCardBundle'),
88
+ createLearnCardBundle: colorizeName('createLearnCardBundle'),
89
+ readLearnCardBundle: colorizeName('readLearnCardBundle'),
90
+ restoreLearnCardFromBundle: colorizeName('restoreLearnCardFromBundle'),
91
+ };
92
+
93
+ const colorizeReplInput = (input: string): string => {
94
+ return [...replGlobals]
95
+ .sort((a, b) => b.length - a.length)
96
+ .reduce((output, name) => {
97
+ const colorizedName = g[name as keyof typeof g];
98
+
99
+ if (!colorizedName) return output;
100
+
101
+ return output.replace(new RegExp(`\\b${name}\\b`, 'g'), colorizedName);
102
+ }, input);
53
103
  };
54
104
 
55
105
  const copyFunction = (text: string | object | number) => {
@@ -88,6 +138,372 @@ const getLearnCardBundlePassword = async (prompt = 'Bundle password: '): Promise
88
138
  }
89
139
  };
90
140
 
141
+ const globalAssignmentPattern = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([\s\S]*?);?\s*$/;
142
+
143
+ const parseGlobalAssignment = (
144
+ source: string
145
+ ): { name: string; initializer: string } | undefined => {
146
+ const match = source.match(globalAssignmentPattern);
147
+
148
+ if (!match) return;
149
+
150
+ const [, name, initializer] = match;
151
+
152
+ return { name, initializer };
153
+ };
154
+
155
+ const getCurrentToken = (line: string): string => {
156
+ return line.match(/(?:[A-Za-z_$][\w$]*\.)*[A-Za-z_$][\w$]*\.?$/)?.[0] ?? '';
157
+ };
158
+
159
+ const getCompletionBase = (token: string): string | undefined => {
160
+ const lastDotIndex = token.lastIndexOf('.');
161
+
162
+ if (lastDotIndex === -1) return;
163
+
164
+ return token.slice(0, lastDotIndex);
165
+ };
166
+
167
+ const getCompletionPrefix = (token: string): string => {
168
+ const lastDotIndex = token.lastIndexOf('.');
169
+
170
+ if (lastDotIndex === -1) return token;
171
+
172
+ return token.slice(lastDotIndex + 1);
173
+ };
174
+
175
+ const getOwnPropertyNames = (value: unknown): string[] => {
176
+ if (value === null || value === undefined) return [];
177
+
178
+ const names = new Set<string>();
179
+
180
+ let current: unknown = Object(value);
181
+
182
+ while (current && current !== Object.prototype) {
183
+ for (const name of Object.getOwnPropertyNames(current)) names.add(name);
184
+
185
+ current = Object.getPrototypeOf(current);
186
+ }
187
+
188
+ return [...names];
189
+ };
190
+
191
+ const completeBunReplInput = (line: string): ReplCompletion => {
192
+ const token = getCurrentToken(line);
193
+
194
+ if (!token) return [replCommands, ''];
195
+
196
+ const base = getCompletionBase(token);
197
+ const prefix = getCompletionPrefix(token);
198
+
199
+ if (!base) {
200
+ const completions = [...replCommands, ...replGlobals].filter(name =>
201
+ name.startsWith(prefix)
202
+ );
203
+
204
+ return [completions.length ? completions : replGlobals, token];
205
+ }
206
+
207
+ try {
208
+ const target = globalThis.eval(base) as unknown;
209
+ const completions = getOwnPropertyNames(target)
210
+ .filter(name => identifierPattern.test(name))
211
+ .filter(name => name.startsWith(prefix))
212
+ .map(name => `${base}.${name}`);
213
+
214
+ return [completions, token];
215
+ } catch {
216
+ return [[], token];
217
+ }
218
+ };
219
+
220
+ const evaluateBunReplInput = async (source: string): Promise<unknown> => {
221
+ try {
222
+ return await globalThis.eval(`(async () => (${source}))()`);
223
+ } catch {
224
+ const globalAssignment = parseGlobalAssignment(source);
225
+
226
+ if (globalAssignment) {
227
+ const value = await globalThis.eval(
228
+ `(async () => (${globalAssignment.initializer}))()`
229
+ );
230
+ (globalThis as Record<string, unknown>)[globalAssignment.name] = value;
231
+ return value;
232
+ }
233
+
234
+ return await globalThis.eval(`(async () => { ${source} })()`);
235
+ }
236
+ };
237
+
238
+ const getSharedCompletion = (matches: string[]): string | undefined => {
239
+ if (!matches.length) return;
240
+
241
+ return matches.reduce((prefix, match) => {
242
+ let index = 0;
243
+
244
+ while (index < prefix.length && prefix[index] === match[index]) index += 1;
245
+
246
+ return prefix.slice(0, index);
247
+ });
248
+ };
249
+
250
+ const renderBunReplLine = (
251
+ prompt: string,
252
+ line: string,
253
+ cursor: number,
254
+ colorize: (input: string) => string
255
+ ): void => {
256
+ process.stdout.clearLine(0);
257
+ process.stdout.cursorTo(0);
258
+ process.stdout.write(prompt + colorize(line));
259
+
260
+ const trailingLength = line.length - cursor;
261
+
262
+ if (trailingLength > 0) process.stdout.write(`\x1b[${trailingLength}D`);
263
+ };
264
+
265
+ const startReadlineRepl = async (colorize?: (input: string) => string): Promise<void> => {
266
+ const rl = createInterface({
267
+ input: process.stdin,
268
+ output: process.stdout,
269
+ prompt: '> ',
270
+ completer: completeBunReplInput,
271
+ });
272
+
273
+ rl.prompt();
274
+
275
+ for await (const line of rl) {
276
+ const source = line.trim();
277
+
278
+ if (source === '.exit') break;
279
+
280
+ if (!source) {
281
+ rl.prompt();
282
+
283
+ continue;
284
+ }
285
+
286
+ try {
287
+ const result = await evaluateBunReplInput(source);
288
+
289
+ if (result !== undefined) console.dir(result, { depth: 6 });
290
+ } catch (error) {
291
+ console.error(error);
292
+ }
293
+
294
+ if (colorize) process.stdout.write(colorize(''));
295
+
296
+ rl.prompt();
297
+ }
298
+
299
+ rl.close();
300
+ };
301
+
302
+ const startBunRepl = async (colorize: (input: string) => string): Promise<void> => {
303
+ if (!process.stdin.isTTY || !process.stdin.setRawMode) {
304
+ await startReadlineRepl(colorize);
305
+
306
+ return;
307
+ }
308
+
309
+ const prompt = '> ';
310
+ const history: string[] = [];
311
+
312
+ let line = '';
313
+ let cursor = 0;
314
+ let historyIndex = 0;
315
+ let draft = '';
316
+ let pending = Promise.resolve();
317
+
318
+ const resetHistory = (): void => {
319
+ historyIndex = history.length;
320
+ draft = '';
321
+ };
322
+
323
+ const setLine = (nextLine: string): void => {
324
+ line = nextLine;
325
+ cursor = line.length;
326
+ renderBunReplLine(prompt, line, cursor, colorize);
327
+ };
328
+
329
+ const insertText = (text: string): void => {
330
+ line = line.slice(0, cursor) + text + line.slice(cursor);
331
+ cursor += text.length;
332
+ renderBunReplLine(prompt, line, cursor, colorize);
333
+ };
334
+
335
+ const applyCompletion = (): void => {
336
+ const linePrefix = line.slice(0, cursor);
337
+ const [matches, token] = completeBunReplInput(linePrefix);
338
+ const completion = matches.length === 1 ? matches[0] : getSharedCompletion(matches);
339
+
340
+ if (completion && completion !== token) {
341
+ line = line.slice(0, cursor - token.length) + completion + line.slice(cursor);
342
+ cursor += completion.length - token.length;
343
+ renderBunReplLine(prompt, line, cursor, colorize);
344
+
345
+ return;
346
+ }
347
+
348
+ if (matches.length > 1) {
349
+ process.stdout.write(`\n${matches.join(' ')}\n`);
350
+ renderBunReplLine(prompt, line, cursor, colorize);
351
+ }
352
+ };
353
+
354
+ const evaluateLine = async (): Promise<boolean> => {
355
+ process.stdout.write('\n');
356
+
357
+ const source = line.trim();
358
+
359
+ if (source === '.exit') return false;
360
+
361
+ if (source) {
362
+ history.push(source);
363
+
364
+ try {
365
+ const result = await evaluateBunReplInput(source);
366
+
367
+ if (result !== undefined) console.dir(result, { depth: 6 });
368
+ } catch (error) {
369
+ console.error(error);
370
+ }
371
+ }
372
+
373
+ line = '';
374
+ cursor = 0;
375
+ resetHistory();
376
+ renderBunReplLine(prompt, line, cursor, colorize);
377
+
378
+ return true;
379
+ };
380
+
381
+ const handleKey = async (input: string, key: ReplKey): Promise<boolean> => {
382
+ if (key.ctrl && key.name === 'c') return false;
383
+
384
+ if (key.name === 'return' || key.name === 'enter') return await evaluateLine();
385
+
386
+ if (key.ctrl && key.name === 'd' && !line) return false;
387
+
388
+ if (key.name === 'tab') {
389
+ applyCompletion();
390
+
391
+ return true;
392
+ }
393
+
394
+ if (key.name === 'backspace') {
395
+ if (cursor === 0) return true;
396
+
397
+ line = line.slice(0, cursor - 1) + line.slice(cursor);
398
+ cursor -= 1;
399
+ renderBunReplLine(prompt, line, cursor, colorize);
400
+
401
+ return true;
402
+ }
403
+
404
+ if (key.name === 'delete') {
405
+ if (cursor >= line.length) return true;
406
+
407
+ line = line.slice(0, cursor) + line.slice(cursor + 1);
408
+ renderBunReplLine(prompt, line, cursor, colorize);
409
+
410
+ return true;
411
+ }
412
+
413
+ if (key.name === 'left') {
414
+ if (cursor > 0) cursor -= 1;
415
+
416
+ renderBunReplLine(prompt, line, cursor, colorize);
417
+
418
+ return true;
419
+ }
420
+
421
+ if (key.name === 'right') {
422
+ if (cursor < line.length) cursor += 1;
423
+
424
+ renderBunReplLine(prompt, line, cursor, colorize);
425
+
426
+ return true;
427
+ }
428
+
429
+ if (key.name === 'home') {
430
+ cursor = 0;
431
+ renderBunReplLine(prompt, line, cursor, colorize);
432
+
433
+ return true;
434
+ }
435
+
436
+ if (key.name === 'end') {
437
+ cursor = line.length;
438
+ renderBunReplLine(prompt, line, cursor, colorize);
439
+
440
+ return true;
441
+ }
442
+
443
+ if (key.name === 'up') {
444
+ if (historyIndex === history.length) draft = line;
445
+ if (historyIndex > 0) historyIndex -= 1;
446
+
447
+ setLine(history[historyIndex] ?? draft);
448
+
449
+ return true;
450
+ }
451
+
452
+ if (key.name === 'down') {
453
+ if (historyIndex < history.length) historyIndex += 1;
454
+
455
+ setLine(historyIndex === history.length ? draft : history[historyIndex] ?? '');
456
+
457
+ return true;
458
+ }
459
+
460
+ if (input && !key.ctrl && input >= ' ') {
461
+ insertText(input);
462
+ resetHistory();
463
+ }
464
+
465
+ return true;
466
+ };
467
+
468
+ await new Promise<void>(resolve => {
469
+ const onKeypress = (input: string, key: ReplKey): void => {
470
+ pending = pending.then(async () => {
471
+ const keepRunning = await handleKey(input, key);
472
+
473
+ if (!keepRunning) cleanup();
474
+ });
475
+ };
476
+
477
+ const cleanup = (): void => {
478
+ process.stdin.off('keypress', onKeypress);
479
+ process.stdin.setRawMode?.(false);
480
+ process.stdin.pause();
481
+ process.stdout.write('\n');
482
+ resolve();
483
+ };
484
+
485
+ emitKeypressEvents(process.stdin);
486
+ process.stdin.setRawMode?.(true);
487
+ process.stdin.resume();
488
+
489
+ console.log('Bun dev REPL ready. Tab completes globals/properties. Type .exit to quit.');
490
+ renderBunReplLine(prompt, line, cursor, colorize);
491
+ process.stdin.on('keypress', onKeypress);
492
+ });
493
+ };
494
+
495
+ const startCliRepl = async (colorize: (input: string) => string): Promise<void> => {
496
+ if (!('Bun' in globalThis)) {
497
+ const repl = await import('pretty-repl');
498
+
499
+ repl.default.start({ colorize });
500
+
501
+ return;
502
+ }
503
+
504
+ await startBunRepl(colorize);
505
+ };
506
+
91
507
  program
92
508
  .version(packageJson.version)
93
509
  .argument('[seed]')
@@ -237,23 +653,6 @@ program
237
653
 
238
654
  console.log('');
239
655
 
240
- repl.start({
241
- colorize: (input: string) => {
242
- return input
243
- .replace('emptyLearnCard', g.emptyLearnCard)
244
- .replace('learnCardFromKey', g.learnCardFromKey)
245
- .replace('initLearnCard', g.initLearnCard)
246
- .replace('learnCard', g.learnCard)
247
- .replace('seed', g.seed)
248
- .replace('generateRandomSeed', g.generateRandomSeed)
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);
256
- },
257
- });
656
+ await startCliRepl(colorizeReplInput);
258
657
  })
259
658
  .parse(process.argv);
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Learning Economy Foundation <sdk@learningeconomy.io>
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.