@learncard/cli 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +230 -2
  3. package/dist/index.js +4337 -986
  4. package/examples/branded.network.yaml +20 -0
  5. package/examples/delegated-service-account.network.yaml +23 -0
  6. package/examples/minimal.network.yaml +7 -0
  7. package/examples/self-hosted-signing.network.yaml +10 -0
  8. package/examples/service-account.network.yaml +13 -0
  9. package/examples/state-districts.network.yaml +36 -0
  10. package/package.json +21 -17
  11. package/src/auth-grant.test.ts +54 -0
  12. package/src/auth-grant.ts +34 -0
  13. package/src/clr/validate.test.ts +65 -0
  14. package/src/clr/validate.ts +242 -0
  15. package/src/clr.ts +119 -0
  16. package/src/demo-inbox-refresh.test.ts +737 -0
  17. package/src/demo-inbox-refresh.ts +804 -0
  18. package/src/demo-refresh-command.test.ts +57 -0
  19. package/src/demo-refresh-command.ts +22 -0
  20. package/src/demo-refresh-ui.test.ts +66 -0
  21. package/src/demo-refresh-ui.ts +65 -0
  22. package/src/demo-refresh.test.ts +140 -0
  23. package/src/demo-refresh.ts +309 -0
  24. package/src/doctor/checks.test.ts +448 -0
  25. package/src/doctor/checks.ts +497 -0
  26. package/src/doctor.test.ts +67 -0
  27. package/src/doctor.ts +118 -0
  28. package/src/inbox.test.ts +257 -0
  29. package/src/inbox.ts +221 -0
  30. package/src/index.tsx +70 -8
  31. package/src/init.ts +1 -1
  32. package/src/open.ts +1 -1
  33. package/src/org/apply.test.ts +1108 -0
  34. package/src/org/apply.ts +924 -0
  35. package/src/org/branding.test.ts +60 -0
  36. package/src/org/diff.ts +14 -0
  37. package/src/org/load.ts +50 -0
  38. package/src/org/schema.test.ts +256 -0
  39. package/src/org/schema.ts +216 -0
  40. package/src/org.ts +124 -0
  41. package/src/project.test.ts +26 -1
  42. package/src/project.ts +105 -10
  43. package/src/promote.test.ts +142 -0
  44. package/src/promote.ts +202 -0
  45. package/src/refresh.test.ts +86 -0
  46. package/src/refresh.ts +93 -0
  47. package/src/send.test.ts +278 -2
  48. package/src/send.ts +152 -24
  49. package/src/setup-signing.ts +1 -1
  50. package/src/status.ts +2 -4
  51. package/src/whoami.test.ts +67 -0
  52. package/src/whoami.ts +129 -0
  53. package/tsconfig.json +1 -1
package/src/clr.ts ADDED
@@ -0,0 +1,119 @@
1
+ import fs from 'node:fs/promises';
2
+ import type { Command } from 'commander';
3
+
4
+ import {
5
+ connect,
6
+ loadProject,
7
+ resolveServices,
8
+ PRODUCTION_NETWORK,
9
+ type ProjectOptions,
10
+ } from './project';
11
+ import { out } from './out';
12
+ import { validateClr, type ClrValidationProfile } from './clr/validate';
13
+
14
+ export type ClrValidateOptions = ProjectOptions & {
15
+ profile?: string;
16
+ dryRunSign?: boolean;
17
+ allowProduction?: boolean;
18
+ };
19
+
20
+ const PROFILES: readonly ClrValidationProfile[] = ['provisional', 'official'];
21
+
22
+ const parseProfile = (value?: string): ClrValidationProfile | undefined => {
23
+ if (value === undefined) return undefined;
24
+ if ((PROFILES as readonly string[]).includes(value)) return value as ClrValidationProfile;
25
+ throw new Error(`Unknown --profile "${value}". Use provisional or official.`);
26
+ };
27
+
28
+ /**
29
+ * The signing key is derived from `credential.issuer`, so a transcript that
30
+ * still carries a placeholder issuer would fail to sign for a reason unrelated
31
+ * to its content. The dry run signs as this project's wallet instead.
32
+ */
33
+ export const withIssuer = (
34
+ credential: Record<string, unknown>,
35
+ did: string
36
+ ): Record<string, unknown> => {
37
+ const issuer = credential.issuer;
38
+ if (issuer && typeof issuer === 'object' && !Array.isArray(issuer))
39
+ return { ...credential, issuer: { ...(issuer as Record<string, unknown>), id: did } };
40
+ return { ...credential, issuer: did };
41
+ };
42
+
43
+ export const runClrValidate = async (file: string, options: ClrValidateOptions): Promise<void> => {
44
+ const text = await fs.readFile(file, 'utf8');
45
+ let json: unknown;
46
+ try {
47
+ json = JSON.parse(text);
48
+ } catch (error) {
49
+ const message = error instanceof Error ? error.message : String(error);
50
+ throw new Error(`Invalid JSON in ${file}: ${message}`, { cause: error });
51
+ }
52
+
53
+ const profile = parseProfile(options.profile);
54
+ const { errors, warnings, summary } = validateClr(json, { profile });
55
+
56
+ for (const error of errors) out.log(`✖ ${error}`);
57
+ for (const warning of warnings) out.log(`⚠ ${warning}`);
58
+
59
+ const ok = errors.length === 0;
60
+ if (!ok) process.exitCode = 1;
61
+ let signOk = true;
62
+
63
+ if (options.dryRunSign) {
64
+ if (!ok) {
65
+ out.log('Skipping --dry-run-sign: fix validation errors first.');
66
+ } else {
67
+ const project = await loadProject(process.cwd());
68
+ const services = resolveServices(project.env, options.network);
69
+ if (services.network === PRODUCTION_NETWORK && !options.allowProduction) {
70
+ throw new Error(
71
+ 'Refusing --dry-run-sign against production. Pass --network staging or --allow-production.'
72
+ );
73
+ }
74
+ const learnCard = await connect(project, options);
75
+ const signed = await learnCard.invoke.issueCredential(
76
+ withIssuer(json as Record<string, unknown>, learnCard.id.did()) as never
77
+ );
78
+ const verification = await learnCard.invoke.verifyCredential(signed);
79
+ out.log('Signed: true (dry run only — nothing was sent or stored)');
80
+ for (const check of verification.checks) out.log(`✓ ${check}`);
81
+ for (const warning of verification.warnings) out.log(`! ${warning}`);
82
+ for (const error of verification.errors) out.log(`✗ ${error}`);
83
+ if (verification.errors.length) {
84
+ signOk = false;
85
+ process.exitCode = 1;
86
+ }
87
+ out.set({ signed: true, verification });
88
+ }
89
+ }
90
+
91
+ out.set({ ok: ok && signOk, errors, warnings, summary });
92
+ };
93
+
94
+ export const registerClrCommand = (
95
+ program: Command,
96
+ run: (
97
+ command: string,
98
+ options: { json?: boolean },
99
+ action: (didkit: Promise<Buffer>) => Promise<void>,
100
+ wrap?: boolean
101
+ ) => Promise<void>
102
+ ): void => {
103
+ const clr = program.command('clr').description('CLR 2.0 transcript tools.');
104
+ clr.command('validate <file>')
105
+ .description('Validate a CLR 2.0 transcript file against schema and issuer-profile rules.')
106
+ .option('--profile <name>', 'provisional or official profile checks')
107
+ .option(
108
+ '--dry-run-sign',
109
+ 'sign + verify in memory to catch signing failures; never sends or stores'
110
+ )
111
+ .option('--allow-production', 'allow --dry-run-sign against the production network')
112
+ .option('--network <url>', 'network tRPC URL or staging (default: production)')
113
+ .option('--json', 'print a single JSON result on stdout')
114
+ .action((file, options) =>
115
+ run('clr validate', options, async didkit => {
116
+ await runClrValidate(file, { ...options, didkit });
117
+ })
118
+ );
119
+ };