@learncard/cli 3.6.8 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -0
- package/README.md +36 -12
- package/dist/index.js +6673 -2038
- package/package.json +27 -15
- package/project.json +0 -42
- package/rollup.config.js +0 -34
- package/scripts/embed-snippets.ts +0 -47
- package/src/auth-grant.test.ts +0 -54
- package/src/auth-grant.ts +0 -34
- package/src/clr/validate.test.ts +0 -65
- package/src/clr/validate.ts +0 -242
- package/src/clr.ts +0 -119
- package/src/consent-contract.ts +0 -116
- package/src/demo-inbox-refresh.test.ts +0 -737
- package/src/demo-inbox-refresh.ts +0 -804
- package/src/demo-refresh-command.test.ts +0 -57
- package/src/demo-refresh-command.ts +0 -22
- package/src/demo-refresh-ui.test.ts +0 -66
- package/src/demo-refresh-ui.ts +0 -65
- package/src/demo-refresh.test.ts +0 -140
- package/src/demo-refresh.ts +0 -309
- package/src/doctor/checks.test.ts +0 -448
- package/src/doctor/checks.ts +0 -497
- package/src/doctor.test.ts +0 -67
- package/src/doctor.ts +0 -118
- package/src/embed.ts +0 -102
- package/src/inbox.test.ts +0 -257
- package/src/inbox.ts +0 -221
- package/src/index.tsx +0 -876
- package/src/init.ts +0 -26
- package/src/open.test.ts +0 -40
- package/src/open.ts +0 -186
- package/src/org/apply.test.ts +0 -1108
- package/src/org/apply.ts +0 -924
- package/src/org/branding.test.ts +0 -60
- package/src/org/diff.ts +0 -14
- package/src/org/load.ts +0 -50
- package/src/org/schema.test.ts +0 -256
- package/src/org/schema.ts +0 -216
- package/src/org.ts +0 -124
- package/src/out.test.ts +0 -40
- package/src/out.ts +0 -16
- package/src/phase-two.test.ts +0 -154
- package/src/project.test.ts +0 -232
- package/src/project.ts +0 -489
- package/src/promote.test.ts +0 -142
- package/src/promote.ts +0 -202
- package/src/random.ts +0 -3
- package/src/refresh.test.ts +0 -86
- package/src/refresh.ts +0 -93
- package/src/replHelpers.test.ts +0 -80
- package/src/replHelpers.ts +0 -45
- package/src/revoke.test.ts +0 -29
- package/src/revoke.ts +0 -65
- package/src/send-template.test.ts +0 -20
- package/src/send.test.ts +0 -321
- package/src/send.ts +0 -373
- package/src/setup-signing.test.ts +0 -62
- package/src/setup-signing.ts +0 -185
- package/src/snippet-files.ts +0 -16
- package/src/status.test.ts +0 -33
- package/src/status.ts +0 -94
- package/src/token.test.ts +0 -28
- package/src/token.ts +0 -139
- package/src/types.d.ts +0 -17
- package/src/verify.test.ts +0 -35
- package/src/verify.ts +0 -66
- package/src/webhook.ts +0 -247
- package/src/whoami.test.ts +0 -67
- package/src/whoami.ts +0 -129
- package/tsconfig.json +0 -38
- package/vitest.config.ts +0 -8
package/src/clr.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
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
|
-
};
|
package/src/consent-contract.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
connect,
|
|
3
|
-
createPrompts,
|
|
4
|
-
ensureIdentity,
|
|
5
|
-
ensureProfile,
|
|
6
|
-
KEYS,
|
|
7
|
-
loadProject,
|
|
8
|
-
localizeSnippet,
|
|
9
|
-
resolveServices,
|
|
10
|
-
saveProject,
|
|
11
|
-
STAGING_NETWORK,
|
|
12
|
-
PRODUCTION_NETWORK,
|
|
13
|
-
type ProjectOptions,
|
|
14
|
-
appUrlFor,
|
|
15
|
-
} from './project';
|
|
16
|
-
import {
|
|
17
|
-
CREATE_CONTRACT_MJS,
|
|
18
|
-
CONSENT_CALLBACK_MJS,
|
|
19
|
-
READ_USER_DATA_MJS,
|
|
20
|
-
ISSUE_THROUGH_CONTRACT_MJS,
|
|
21
|
-
} from './generated/snippets';
|
|
22
|
-
import { writeSnippet } from './snippet-files';
|
|
23
|
-
import { out } from './out';
|
|
24
|
-
|
|
25
|
-
export const consentUrl = (uri: string, returnTo: string, network: string): string => {
|
|
26
|
-
const url = new URL('/consent-flow', appUrlFor(network));
|
|
27
|
-
url.searchParams.set('uri', uri);
|
|
28
|
-
url.searchParams.set('returnTo', returnTo);
|
|
29
|
-
return url.toString();
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const runConsentContract = async (
|
|
33
|
-
options: ProjectOptions & {
|
|
34
|
-
redirectUrl?: string;
|
|
35
|
-
description?: string;
|
|
36
|
-
needsGuardianConsent?: boolean;
|
|
37
|
-
}
|
|
38
|
-
): Promise<void> => {
|
|
39
|
-
const project = await loadProject(process.cwd());
|
|
40
|
-
const returnTo =
|
|
41
|
-
options.redirectUrl || project.env.RETURN_TO || 'http://localhost:3000/consent-callback';
|
|
42
|
-
if (!['http:', 'https:'].includes(new URL(returnTo).protocol)) {
|
|
43
|
-
throw new Error('Redirect URL must use HTTP or HTTPS.');
|
|
44
|
-
}
|
|
45
|
-
const identity = await ensureIdentity(project, options);
|
|
46
|
-
const learnCard = await connect(project, options);
|
|
47
|
-
await ensureProfile(learnCard, identity, project);
|
|
48
|
-
let uri = project.env[KEYS.CONTRACT_URI];
|
|
49
|
-
if (!uri) {
|
|
50
|
-
const prompts = createPrompts(options.yes);
|
|
51
|
-
let name: string;
|
|
52
|
-
try {
|
|
53
|
-
name = options.name ?? (await prompts.ask('Contract name', identity.displayName));
|
|
54
|
-
} finally {
|
|
55
|
-
prompts.close();
|
|
56
|
-
}
|
|
57
|
-
const contract = {
|
|
58
|
-
name,
|
|
59
|
-
description:
|
|
60
|
-
options.description ??
|
|
61
|
-
'Allow us to read your name and achievements and send achievements.',
|
|
62
|
-
...(options.needsGuardianConsent ? { needsGuardianConsent: true } : {}),
|
|
63
|
-
redirectUrl: returnTo,
|
|
64
|
-
contract: {
|
|
65
|
-
read: {
|
|
66
|
-
personal: { name: { required: false } },
|
|
67
|
-
credentials: { categories: { Achievement: { required: false } } },
|
|
68
|
-
},
|
|
69
|
-
write: {
|
|
70
|
-
personal: {},
|
|
71
|
-
credentials: { categories: { Achievement: { required: false } } },
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
uri = await learnCard.invoke.createContract(contract);
|
|
76
|
-
out.log('Created your consent contract.');
|
|
77
|
-
} else {
|
|
78
|
-
out.log('Reusing your saved consent contract.');
|
|
79
|
-
}
|
|
80
|
-
await saveProject(project, { [KEYS.CONTRACT_URI]: uri, RETURN_TO: returnTo });
|
|
81
|
-
const services = resolveServices(project.env, options.network);
|
|
82
|
-
const files: string[] = [];
|
|
83
|
-
for (const [file, source] of Object.entries({
|
|
84
|
-
'create-contract.mjs': CREATE_CONTRACT_MJS,
|
|
85
|
-
'consent-callback.mjs': CONSENT_CALLBACK_MJS,
|
|
86
|
-
'read-user-data.mjs': READ_USER_DATA_MJS,
|
|
87
|
-
'issue-through-contract.mjs': ISSUE_THROUGH_CONTRACT_MJS,
|
|
88
|
-
})) {
|
|
89
|
-
// The canonical tutorial uses a destructured SECURE_SEED; normalize only for localization.
|
|
90
|
-
const localized =
|
|
91
|
-
services.network === PRODUCTION_NETWORK
|
|
92
|
-
? source
|
|
93
|
-
: localizeSnippet(
|
|
94
|
-
source.replaceAll(
|
|
95
|
-
'seed: SECURE_SEED, network: true',
|
|
96
|
-
'seed: process.env.SECURE_SEED, network: true'
|
|
97
|
-
),
|
|
98
|
-
services
|
|
99
|
-
);
|
|
100
|
-
if (await writeSnippet(file, localized)) files.push(`./${file}`);
|
|
101
|
-
}
|
|
102
|
-
const contractConsentUrl = consentUrl(uri, returnTo, services.network);
|
|
103
|
-
out.log(contractConsentUrl);
|
|
104
|
-
if (![PRODUCTION_NETWORK, STAGING_NETWORK].includes(services.network)) {
|
|
105
|
-
out.log(
|
|
106
|
-
'Note: this is a production app URL; use an app connected to your local network to consent.'
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
out.log(
|
|
110
|
-
'The tutorial create-contract.mjs creates a new contract and requires HTTPS; reuse CONTRACT_URI for this dev setup.'
|
|
111
|
-
);
|
|
112
|
-
out.log('Read scripts require a verified CONSENT_VP, not a bare DID.');
|
|
113
|
-
out.log('Next: node --env-file=.env consent-callback.mjs');
|
|
114
|
-
out.log('See the contract in the app: npx @learncard/cli open contract');
|
|
115
|
-
out.set({ contractUri: uri, consentUrl: contractConsentUrl, returnTo, files });
|
|
116
|
-
};
|