@thirtified/eudi-dev 0.0.0-stage → 0.1.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.
Files changed (47) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +223 -2
  3. package/dist/binary.d.ts +42 -0
  4. package/dist/binary.js +76 -0
  5. package/dist/binary.js.map +1 -0
  6. package/dist/commands/credentials.d.ts +151 -0
  7. package/dist/commands/credentials.js +234 -0
  8. package/dist/commands/credentials.js.map +1 -0
  9. package/dist/commands/registrar.d.ts +116 -0
  10. package/dist/commands/registrar.js +150 -0
  11. package/dist/commands/registrar.js.map +1 -0
  12. package/dist/commands/trust.d.ts +58 -0
  13. package/dist/commands/trust.js +90 -0
  14. package/dist/commands/trust.js.map +1 -0
  15. package/dist/errors.d.ts +47 -0
  16. package/dist/errors.js +41 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/eudi.d.ts +66 -0
  19. package/dist/eudi.js +113 -0
  20. package/dist/eudi.js.map +1 -0
  21. package/dist/index.d.ts +10 -0
  22. package/dist/index.js +8 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/input.d.ts +32 -0
  25. package/dist/input.js +7 -0
  26. package/dist/input.js.map +1 -0
  27. package/dist/runner.d.ts +86 -0
  28. package/dist/runner.js +246 -0
  29. package/dist/runner.js.map +1 -0
  30. package/dist/types.d.ts +345 -0
  31. package/dist/types.js +11 -0
  32. package/dist/types.js.map +1 -0
  33. package/dist/version.d.ts +24 -0
  34. package/dist/version.js +35 -0
  35. package/dist/version.js.map +1 -0
  36. package/package.json +71 -4
  37. package/src/binary.ts +114 -0
  38. package/src/commands/credentials.ts +384 -0
  39. package/src/commands/registrar.ts +243 -0
  40. package/src/commands/trust.ts +123 -0
  41. package/src/errors.ts +66 -0
  42. package/src/eudi.ts +159 -0
  43. package/src/index.ts +44 -0
  44. package/src/input.ts +27 -0
  45. package/src/runner.ts +310 -0
  46. package/src/types.ts +396 -0
  47. package/src/version.ts +36 -0
package/package.json CHANGED
@@ -1,6 +1,73 @@
1
1
  {
2
2
  "name": "@thirtified/eudi-dev",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Typed Node.js wrapper for the eudi-dev CLI (decode, validate, dcql, issue, trust, registrar)",
5
+ "keywords": [
6
+ "eudi",
7
+ "eudi-wallet",
8
+ "sd-jwt",
9
+ "mdoc",
10
+ "openid4vp",
11
+ "openid4vci",
12
+ "testing"
13
+ ],
14
+ "license": "Apache-2.0",
15
+ "author": "Julian Dreissig",
16
+ "homepage": "https://github.com/thirtified/eudi-dev-node#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/thirtified/eudi-dev-node/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/thirtified/eudi-dev-node.git"
23
+ },
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "src",
38
+ "README.md"
39
+ ],
40
+ "engines": {
41
+ "node": ">=22"
42
+ },
43
+ "packageManager": "pnpm@12.18.0",
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "provenance": true
47
+ },
48
+ "scripts": {
49
+ "build": "tsc -p tsconfig.build.json",
50
+ "typecheck": "tsc -p tsconfig.json --noEmit",
51
+ "test": "node --import tsx --test \"test/unit/**/*.test.ts\"",
52
+ "test:integration": "node --import tsx --test --test-concurrency=1 \"test/integration/**/*.test.ts\"",
53
+ "prepack": "pnpm run build",
54
+ "build:platform": "node scripts/build-platform-packages.mjs",
55
+ "lint": "biome check .",
56
+ "lint:fix": "biome check --write .",
57
+ "lint:package": "pnpm run build && publint && attw --pack . --profile esm-only"
58
+ },
59
+ "optionalDependencies": {
60
+ "@thirtified/eudi-dev-darwin-arm64": "3.10.1",
61
+ "@thirtified/eudi-dev-darwin-x64": "3.10.1",
62
+ "@thirtified/eudi-dev-linux-arm64": "3.10.1",
63
+ "@thirtified/eudi-dev-linux-x64": "3.10.1"
64
+ },
65
+ "devDependencies": {
66
+ "@arethetypeswrong/cli": "0.18.5",
67
+ "@biomejs/biome": "2.6.7",
68
+ "@types/node": "^22.0.0",
69
+ "publint": "0.3.25",
70
+ "tsx": "^4.19.0",
71
+ "typescript": "^7.0.2"
72
+ }
73
+ }
package/src/binary.ts ADDED
@@ -0,0 +1,114 @@
1
+ import { access, constants, stat } from 'node:fs/promises';
2
+ import { createRequire } from 'node:module';
3
+
4
+ import { EudiDevBinaryError } from './errors.js';
5
+
6
+ /**
7
+ * One per-platform npm package that ships the upstream release binary.
8
+ * `scripts/build-platform-packages.mjs` builds them from the same table.
9
+ */
10
+ export interface PlatformPackage {
11
+ /** npm package name, e.g. `@thirtified/eudi-dev-linux-x64`. */
12
+ name: string;
13
+ /** Node `process.platform` value (`os` in package.json). */
14
+ os: 'darwin' | 'linux';
15
+ /** Node `process.arch` value (`cpu` in package.json). */
16
+ cpu: 'arm64' | 'x64';
17
+ /** Path of the binary inside the package. */
18
+ binPath: 'bin/eudi';
19
+ }
20
+
21
+ /** The platform packages listed in `optionalDependencies`. */
22
+ export const PLATFORM_PACKAGES: readonly PlatformPackage[] = [
23
+ { name: '@thirtified/eudi-dev-darwin-arm64', os: 'darwin', cpu: 'arm64', binPath: 'bin/eudi' },
24
+ { name: '@thirtified/eudi-dev-darwin-x64', os: 'darwin', cpu: 'x64', binPath: 'bin/eudi' },
25
+ { name: '@thirtified/eudi-dev-linux-x64', os: 'linux', cpu: 'x64', binPath: 'bin/eudi' },
26
+ { name: '@thirtified/eudi-dev-linux-arm64', os: 'linux', cpu: 'arm64', binPath: 'bin/eudi' },
27
+ ];
28
+
29
+ export interface ResolveBinaryOptions {
30
+ /** Use this binary and skip the lookup. Takes precedence over `EUDI_DEV_BIN`. */
31
+ bin?: string | undefined;
32
+ /** Override the detected platform (mainly for tests). Default `process.platform`. */
33
+ platform?: NodeJS.Platform | undefined;
34
+ /** Override the detected architecture (mainly for tests). Default `process.arch`. */
35
+ arch?: string | undefined;
36
+ /**
37
+ * Resolves a module specifier to a file path (mainly for tests). Default
38
+ * `createRequire(import.meta.url).resolve`.
39
+ */
40
+ resolve?: ((specifier: string) => string) | undefined;
41
+ }
42
+
43
+ /**
44
+ * The platform package for a Node platform and architecture. There is none
45
+ * for Windows yet; `EUDI_DEV_BIN` can point to upstream's `eudi.exe`, untested.
46
+ */
47
+ export function platformPackage(platform: NodeJS.Platform = process.platform, arch: string = process.arch): PlatformPackage {
48
+ const pkg = PLATFORM_PACKAGES.find((p) => p.os === platform && p.cpu === arch);
49
+ if (!pkg) {
50
+ throw new EudiDevBinaryError(
51
+ `eudi-dev publishes no binary for ${platform}/${arch}. Set EUDI_DEV_BIN to an eudi binary you built with "go build".`,
52
+ );
53
+ }
54
+ return pkg;
55
+ }
56
+
57
+ async function isExecutableFile(path: string): Promise<boolean> {
58
+ try {
59
+ const s = await stat(path);
60
+ if (!s.isFile()) return false;
61
+ if (process.platform !== 'win32') await access(path, constants.X_OK);
62
+ return true;
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Finds the eudi-dev binary.
70
+ *
71
+ * Order: `options.bin`, `EUDI_DEV_BIN`, then the binary in the platform
72
+ * package (for example `@thirtified/eudi-dev-linux-x64`) that npm installed
73
+ * as an optional dependency.
74
+ */
75
+ export async function resolveBinary(options: ResolveBinaryOptions = {}): Promise<string> {
76
+ const explicit = options.bin ?? process.env.EUDI_DEV_BIN;
77
+ if (explicit) {
78
+ if (!(await isExecutableFile(explicit))) {
79
+ const source = options.bin ? 'bin option' : 'EUDI_DEV_BIN';
80
+ throw new EudiDevBinaryError(`${source} points to ${explicit}, which is not an executable file`);
81
+ }
82
+ return explicit;
83
+ }
84
+
85
+ const platform = options.platform ?? process.platform;
86
+ const arch = options.arch ?? process.arch;
87
+ const pkg = platformPackage(platform, arch);
88
+ const specifier = `${pkg.name}/${pkg.binPath}`;
89
+ const resolve = options.resolve ?? createRequire(import.meta.url).resolve;
90
+
91
+ let path: string;
92
+ try {
93
+ path = resolve(specifier);
94
+ } catch (err) {
95
+ throw new EudiDevBinaryError(
96
+ [
97
+ `The eudi-dev binary package ${pkg.name} is not installed.`,
98
+ `It is an optional dependency of @thirtified/eudi-dev, and npm skips it when:`,
99
+ ` - optional dependencies are left out (--omit=optional, --no-optional, or omit=optional in .npmrc or NPM_CONFIG_OMIT)`,
100
+ ` - package-lock.json was created on another OS or CPU and has no entry for it (npm/cli#4828).`,
101
+ ` Delete node_modules and package-lock.json, then run "npm install" again.`,
102
+ ` - this platform (${platform}/${arch}) is not supported`,
103
+ `Set EUDI_DEV_BIN to an eudi binary to skip the lookup.`,
104
+ ].join('\n'),
105
+ { cause: err },
106
+ );
107
+ }
108
+ if (!(await isExecutableFile(path))) {
109
+ throw new EudiDevBinaryError(
110
+ `${path} from ${pkg.name} is not an executable file. Reinstall the package, or set EUDI_DEV_BIN to an eudi binary.`,
111
+ );
112
+ }
113
+ return path;
114
+ }
@@ -0,0 +1,384 @@
1
+ import { EudiDevError } from '../errors.js';
2
+ import { type FileInput, type Input, isPathInput } from '../input.js';
3
+ import { ArgList, positionalInput, type Runner, type RunOptions, Scratch } from '../runner.js';
4
+ import type { DcqlQuery, DecodeCredentialResult, DecodeResult, IssueDocument, JsonObject, ValidateReport, VersionInfo } from '../types.js';
5
+
6
+ /** Per-call options shared by every wrapper. */
7
+ export type CallOptions = Pick<RunOptions, 'timeoutMs' | 'signal'> & {
8
+ /** Appended to the generated arguments, for flags this package doesn't wrap. */
9
+ extraArgs?: string[] | undefined;
10
+ };
11
+
12
+ function runOpts(opts: CallOptions, stdin?: string | Uint8Array): RunOptions {
13
+ const r: RunOptions = {};
14
+ if (opts.timeoutMs !== undefined) r.timeoutMs = opts.timeoutMs;
15
+ if (opts.signal !== undefined) r.signal = opts.signal;
16
+ if (stdin !== undefined) r.stdin = stdin;
17
+ return r;
18
+ }
19
+
20
+ // ── version ───────────────────────────────────────────────────────────────
21
+
22
+ export async function version(runner: Runner, opts: CallOptions = {}): Promise<VersionInfo> {
23
+ const { document } = await runner.run<VersionInfo>(['version', ...(opts.extraArgs ?? [])], runOpts(opts));
24
+ return document;
25
+ }
26
+
27
+ // ── decode ────────────────────────────────────────────────────────────────
28
+
29
+ export type DecodeFormat = 'sdjwt' | 'jwt' | 'mdoc' | 'vci' | 'vp' | 'trusted-list' | 'x509';
30
+
31
+ export interface DecodeOptions extends CallOptions {
32
+ /** Skip auto-detection (`--format`). */
33
+ format?: DecodeFormat | undefined;
34
+ }
35
+
36
+ /**
37
+ * `eudi decode`: inspects a credential, certificate, OpenID4VCI offer,
38
+ * OpenID4VP request or trusted list. For credentials and certificates the
39
+ * document also holds the decoder's checks under `validation.checks`.
40
+ */
41
+ export async function decode(runner: Runner, input: Input, opts: DecodeOptions = {}): Promise<DecodeResult> {
42
+ const { arg, stdin } = await positionalInput(input);
43
+ const args = new ArgList('decode').value('format', opts.format).push(...(opts.extraArgs ?? []), arg);
44
+ const { document } = await runner.run<DecodeResult>(args.args, runOpts(opts, stdin));
45
+ return document;
46
+ }
47
+
48
+ /** True when a decode result is a credential or certificate (it carries `validation`). */
49
+ export function isDecodedCredential(doc: DecodeResult): doc is DecodeCredentialResult {
50
+ const format = (doc as { format?: unknown }).format;
51
+ return (
52
+ typeof doc === 'object' &&
53
+ doc !== null &&
54
+ typeof (doc as { validation?: unknown }).validation === 'object' &&
55
+ (format === 'dc+sd-jwt' || format === 'jwt' || format === 'mso_mdoc' || format === 'x509')
56
+ );
57
+ }
58
+
59
+ // ── validate ──────────────────────────────────────────────────────────────
60
+
61
+ export interface ValidateOptions extends CallOptions {
62
+ /** Issuer public key, PEM or JWK (`--key`). A JWK object is serialised. */
63
+ key?: FileInput | JsonObject | undefined;
64
+ /** ETSI trusted list JWT: content, `{ path }` or `{ url }` (`--trusted-list`). */
65
+ trustedList?: FileInput | undefined;
66
+ /** Check the status list when the credential references one. Default `true` upstream. */
67
+ statusList?: boolean | undefined;
68
+ /** Don't fail on a credential outside its validity period. */
69
+ allowExpired?: boolean | undefined;
70
+ /** Also report HAIP 1.0 findings (informational, they don't fail validation). */
71
+ haip?: boolean | undefined;
72
+ }
73
+
74
+ export interface ValidateResult {
75
+ /** False when a type, expiry, integrity, signature, certificate revocation or status check failed. */
76
+ valid: boolean;
77
+ /** The first failing check as `"<name>: <detail>"`, as eudi-dev reports it. */
78
+ error?: string;
79
+ report: ValidateReport;
80
+ stderr: string;
81
+ }
82
+
83
+ /**
84
+ * `eudi validate`. A failing check does not throw: the result has
85
+ * `valid: false` and the full report. It throws {@link EudiDevError} when
86
+ * there is no report, for example for input that doesn't parse.
87
+ */
88
+ export async function validate(runner: Runner, input: Input, opts: ValidateOptions = {}): Promise<ValidateResult> {
89
+ const scratch = new Scratch();
90
+ try {
91
+ const { arg, stdin } = await positionalInput(input);
92
+ const args = new ArgList('validate');
93
+ if (opts.key !== undefined) {
94
+ const key = isJsonObject(opts.key) ? JSON.stringify(opts.key) : opts.key;
95
+ args.value('key', await scratch.file(key, 'key'));
96
+ }
97
+ if (opts.trustedList !== undefined) {
98
+ args.value('trusted-list', await scratch.file(opts.trustedList, 'trusted-list.jwt', { allowUrl: true }));
99
+ }
100
+ args
101
+ .bool('status-list', opts.statusList)
102
+ .on('allow-expired', opts.allowExpired)
103
+ .on('haip', opts.haip)
104
+ .push(...(opts.extraArgs ?? []), arg);
105
+
106
+ try {
107
+ const { document, stderr } = await runner.run<ValidateReport>(args.args, runOpts(opts, stdin));
108
+ return { valid: true, report: document, stderr };
109
+ } catch (err) {
110
+ if (err instanceof EudiDevError && isReport(err.document)) {
111
+ return { valid: false, error: err.message, report: err.document, stderr: err.stderr };
112
+ }
113
+ throw err;
114
+ }
115
+ } finally {
116
+ await scratch.cleanup();
117
+ }
118
+ }
119
+
120
+ function isReport(doc: unknown): doc is ValidateReport {
121
+ return typeof doc === 'object' && doc !== null && Array.isArray((doc as { checks?: unknown }).checks);
122
+ }
123
+
124
+ function isJsonObject(v: unknown): v is JsonObject {
125
+ return (
126
+ typeof v === 'object' &&
127
+ v !== null &&
128
+ !(v instanceof Uint8Array) &&
129
+ !('path' in v && typeof (v as { path: unknown }).path === 'string') &&
130
+ !('url' in v && typeof (v as { url: unknown }).url === 'string')
131
+ );
132
+ }
133
+
134
+ // ── dcql ──────────────────────────────────────────────────────────────────
135
+
136
+ /** `eudi dcql`: a DCQL query asking for every claim of the credential. */
137
+ export async function dcql(runner: Runner, input: Input, opts: CallOptions = {}): Promise<DcqlQuery> {
138
+ const { arg, stdin } = await positionalInput(input);
139
+ const { document } = await runner.run<DcqlQuery>(['dcql', ...(opts.extraArgs ?? []), arg], runOpts(opts, stdin));
140
+ return document;
141
+ }
142
+
143
+ // ── issue ─────────────────────────────────────────────────────────────────
144
+
145
+ export type CredentialCategory = 'pid' | 'qeaa' | 'pub-eaa' | 'eaa' | 'unlisted';
146
+
147
+ /** Card display flags, used with `wallet: true`. */
148
+ export interface IssueDisplayOptions {
149
+ name?: string | undefined;
150
+ description?: string | undefined;
151
+ backgroundColor?: string | undefined;
152
+ textColor?: string | undefined;
153
+ /** File path, data URI or http(s) URL. */
154
+ logo?: string | undefined;
155
+ logoAlt?: string | undefined;
156
+ /** File path, data URI or http(s) URL. */
157
+ backgroundImage?: string | undefined;
158
+ /** Take unset display values from this template. */
159
+ template?: string | undefined;
160
+ }
161
+
162
+ /** Trust and issuer metadata stored for the credential type with `wallet: true`. */
163
+ export interface IssueRegistrationOptions {
164
+ category?: CredentialCategory | undefined;
165
+ entitlements?: string[] | undefined;
166
+ trustedListType?: string | undefined;
167
+ statusDeterminationApproach?: string | undefined;
168
+ schemeCommunityRule?: string | undefined;
169
+ schemeTerritory?: string | undefined;
170
+ trustedEntityName?: string | undefined;
171
+ issuanceServiceType?: string | undefined;
172
+ revocationServiceType?: string | undefined;
173
+ issuanceServiceName?: string | undefined;
174
+ revocationServiceName?: string | undefined;
175
+ }
176
+
177
+ export interface IssueCommonOptions extends CallOptions {
178
+ /** Claims as an object (written to a temp file) or `{ path }` to a JSON file. Overrides top-level template claims. */
179
+ claims?: JsonObject | { path: string } | undefined;
180
+ /** Private signing key, PEM or JWK. Ephemeral P-256 if omitted. */
181
+ key?: FileInput | JsonObject | undefined;
182
+ /** Certificate chain PEM, leaf first, embedded as x5c / x5chain. Requires `key`. */
183
+ cert?: FileInput | undefined;
184
+ /** Expiration as a Go duration, default `720h`. */
185
+ exp?: string | undefined;
186
+ /** Not-before: RFC 3339 time or a duration such as `-1h`. */
187
+ nbf?: string | undefined;
188
+ /** Use the full EUDI PID Rulebook claim set. */
189
+ pid?: boolean | undefined;
190
+ /** Claim names to leave out. */
191
+ omit?: string[] | undefined;
192
+ /** Template name, or `{ path }` to a template file. */
193
+ template?: string | { path: string } | undefined;
194
+ /** Save the issued claims and settings as a template with this name. */
195
+ saveTemplate?: string | undefined;
196
+ statusListUri?: string | undefined;
197
+ statusListIdx?: number | undefined;
198
+ /** Import into the wallet under the instance's EUDI_DEV_HOME. */
199
+ wallet?: boolean | undefined;
200
+ walletDir?: string | undefined;
201
+ templatesDir?: string | undefined;
202
+ /** With `wallet`: `file`, `memory`, `auto` or a `postgres://` URL. */
203
+ storage?: string | undefined;
204
+ /** With `wallet`: derive generated keys from this string. */
205
+ seed?: string | undefined;
206
+ display?: IssueDisplayOptions | undefined;
207
+ registration?: IssueRegistrationOptions | undefined;
208
+ /** With `saveTemplate`: add the template to the attestation catalogue with this entry. */
209
+ catalog?: JsonObject | undefined;
210
+ }
211
+
212
+ export interface IssueSdJwtOptions extends IssueCommonOptions {
213
+ iss?: string | undefined;
214
+ vct?: string | undefined;
215
+ /** Claims issued plainly instead of selectively disclosable (dotted paths for nested claims). */
216
+ alwaysDisclosed?: string[] | undefined;
217
+ /** With `wallet`: issue this many copies with separate holder keys (max 8). */
218
+ batch?: number | undefined;
219
+ /** With `wallet`: issue without a holder key. */
220
+ unbound?: boolean | undefined;
221
+ }
222
+
223
+ export interface IssueJwtOptions extends IssueCommonOptions {
224
+ iss?: string | undefined;
225
+ vct?: string | undefined;
226
+ }
227
+
228
+ export interface IssueMdocOptions extends IssueCommonOptions {
229
+ docType?: string | undefined;
230
+ namespace?: string | undefined;
231
+ batch?: number | undefined;
232
+ unbound?: boolean | undefined;
233
+ }
234
+
235
+ export interface IssueResult {
236
+ /** The credential: SD-JWT / JWT compact form, or base64url / hex mdoc. */
237
+ credential: string;
238
+ /** Wallet credential id, set when `wallet: true` stored it. */
239
+ id?: string;
240
+ /** Public JWK of the ephemeral signing key, when no `key` was given. */
241
+ publicJwk?: JsonObject;
242
+ stderr: string;
243
+ }
244
+
245
+ const EPHEMERAL_MARKER = 'Ephemeral signing key (public JWK):';
246
+
247
+ /** Extracts the ephemeral public JWK that `issue` prints to stderr. Exported for tests. */
248
+ export function parseEphemeralJwk(stderr: string): JsonObject | undefined {
249
+ const lines = stderr.split(/\r?\n/);
250
+ const start = lines.findIndex((l) => l.trim() === EPHEMERAL_MARKER);
251
+ if (start < 0) return undefined;
252
+ // Upstream prints the JWK with json.MarshalIndent, so it spans several
253
+ // lines. Grow the candidate line by line until it parses.
254
+ let candidate = '';
255
+ for (const line of lines.slice(start + 1, start + 1 + 64)) {
256
+ candidate += `${line}\n`;
257
+ try {
258
+ const jwk: unknown = JSON.parse(candidate);
259
+ return typeof jwk === 'object' && jwk !== null && !Array.isArray(jwk) ? (jwk as JsonObject) : undefined;
260
+ } catch {
261
+ // incomplete, keep reading
262
+ }
263
+ }
264
+ return undefined;
265
+ }
266
+
267
+ async function commonIssueArgs(args: ArgList, opts: IssueCommonOptions, scratch: Scratch): Promise<void> {
268
+ if (opts.claims !== undefined) {
269
+ const path = isPathInput(opts.claims)
270
+ ? await scratch.file(opts.claims, 'claims.json')
271
+ : await scratch.write('claims.json', JSON.stringify(opts.claims));
272
+ args.value('claims', `@${path}`);
273
+ }
274
+ if (opts.key !== undefined) {
275
+ const key = isJsonObject(opts.key) ? JSON.stringify(opts.key) : opts.key;
276
+ args.value('key', await scratch.file(key, 'key'));
277
+ }
278
+ if (opts.cert !== undefined) args.value('cert', await scratch.file(opts.cert, 'cert.pem'));
279
+ if (opts.template !== undefined) {
280
+ args.value('template', typeof opts.template === 'string' ? opts.template : await scratch.file(opts.template, 'template.json'));
281
+ }
282
+ args
283
+ .value('exp', opts.exp)
284
+ .value('nbf', opts.nbf)
285
+ .on('pid', opts.pid)
286
+ .csv('omit', opts.omit)
287
+ .value('save-template', opts.saveTemplate)
288
+ .value('status-list-uri', opts.statusListUri)
289
+ .value('status-list-idx', opts.statusListIdx)
290
+ .on('wallet', opts.wallet)
291
+ .value('wallet-dir', opts.walletDir)
292
+ .value('templates-dir', opts.templatesDir)
293
+ .value('storage', opts.storage)
294
+ .value('seed', opts.seed);
295
+
296
+ const d = opts.display;
297
+ if (d) {
298
+ args
299
+ .value('display-name', d.name)
300
+ .value('display-description', d.description)
301
+ .value('background-color', d.backgroundColor)
302
+ .value('text-color', d.textColor)
303
+ .value('logo', d.logo)
304
+ .value('logo-alt', d.logoAlt)
305
+ .value('background-image', d.backgroundImage)
306
+ .value('display-template', d.template);
307
+ }
308
+ const r = opts.registration;
309
+ if (r) {
310
+ args
311
+ .value('category', r.category)
312
+ .repeat('entitlement', r.entitlements)
313
+ .value('trusted-list-type', r.trustedListType)
314
+ .value('status-determination-approach', r.statusDeterminationApproach)
315
+ .value('scheme-community-rule', r.schemeCommunityRule)
316
+ .value('scheme-territory', r.schemeTerritory)
317
+ .value('trusted-entity-name', r.trustedEntityName)
318
+ .value('issuance-service-type', r.issuanceServiceType)
319
+ .value('revocation-service-type', r.revocationServiceType)
320
+ .value('issuance-service-name', r.issuanceServiceName)
321
+ .value('revocation-service-name', r.revocationServiceName);
322
+ }
323
+ if (opts.catalog !== undefined) {
324
+ args.value('catalog', await scratch.write('catalog.json', JSON.stringify(opts.catalog)));
325
+ }
326
+ }
327
+
328
+ async function runIssue(runner: Runner, args: ArgList, opts: IssueCommonOptions, scratch: Scratch): Promise<IssueResult> {
329
+ try {
330
+ args.push(...(opts.extraArgs ?? []));
331
+ const { document, stderr } = await runner.run<IssueDocument>(args.args, runOpts(opts));
332
+ const result: IssueResult = { credential: document.credential, stderr };
333
+ if (document.id !== undefined) result.id = document.id;
334
+ const jwk = parseEphemeralJwk(stderr);
335
+ if (jwk) result.publicJwk = jwk;
336
+ return result;
337
+ } finally {
338
+ await scratch.cleanup();
339
+ }
340
+ }
341
+
342
+ export async function issueSdJwt(runner: Runner, opts: IssueSdJwtOptions = {}): Promise<IssueResult> {
343
+ const scratch = new Scratch();
344
+ const args = new ArgList('issue', 'sdjwt');
345
+ try {
346
+ await commonIssueArgs(args, opts, scratch);
347
+ } catch (err) {
348
+ await scratch.cleanup();
349
+ throw err;
350
+ }
351
+ args
352
+ .value('iss', opts.iss)
353
+ .value('vct', opts.vct)
354
+ .csv('always-disclosed', opts.alwaysDisclosed)
355
+ .value('batch', opts.batch)
356
+ .on('unbound', opts.unbound);
357
+ return runIssue(runner, args, opts, scratch);
358
+ }
359
+
360
+ export async function issueJwt(runner: Runner, opts: IssueJwtOptions = {}): Promise<IssueResult> {
361
+ const scratch = new Scratch();
362
+ const args = new ArgList('issue', 'jwt');
363
+ try {
364
+ await commonIssueArgs(args, opts, scratch);
365
+ } catch (err) {
366
+ await scratch.cleanup();
367
+ throw err;
368
+ }
369
+ args.value('iss', opts.iss).value('vct', opts.vct);
370
+ return runIssue(runner, args, opts, scratch);
371
+ }
372
+
373
+ export async function issueMdoc(runner: Runner, opts: IssueMdocOptions = {}): Promise<IssueResult> {
374
+ const scratch = new Scratch();
375
+ const args = new ArgList('issue', 'mdoc');
376
+ try {
377
+ await commonIssueArgs(args, opts, scratch);
378
+ } catch (err) {
379
+ await scratch.cleanup();
380
+ throw err;
381
+ }
382
+ args.value('doc-type', opts.docType).value('namespace', opts.namespace).value('batch', opts.batch).on('unbound', opts.unbound);
383
+ return runIssue(runner, args, opts, scratch);
384
+ }