diff --git a/packages/learn-card-cli/package.json b/packages/learn-card-cli/package.json index 09f9d4ea3e..2cc59fe30b 100644 --- a/packages/learn-card-cli/package.json +++ b/packages/learn-card-cli/package.json @@ -15,6 +15,7 @@ "@learncard/didkit-plugin": "workspace:^", "@learncard/init": "workspace:^", "@learncard/learn-cloud-plugin": "workspace:*", + "@learncard/ler-rs-plugin": "workspace:*", "@learncard/simple-signing-plugin": "workspace:*", "@learncard/types": "workspace:*", "@rollup/plugin-json": "^4.1.0", diff --git a/packages/learn-card-cli/src/index.tsx b/packages/learn-card-cli/src/index.tsx index 0e3438b15a..625679bdaf 100644 --- a/packages/learn-card-cli/src/index.tsx +++ b/packages/learn-card-cli/src/index.tsx @@ -11,6 +11,7 @@ import figlet from 'figlet'; import { program } from 'commander'; import clipboard from 'clipboardy'; +import { getLerRsPlugin } from '@learncard/ler-rs-plugin'; import { generateRandomSeed } from './random'; @@ -74,10 +75,12 @@ program ), }); - globalThis.learnCard = await _learnCard.addPlugin( + const simpleSigningLc = await _learnCard.addPlugin( await getSimpleSigningPlugin(_learnCard, 'https://api.learncard.app/trpc') ); + globalThis.learnCard = await simpleSigningLc.addPlugin(getLerRsPlugin(simpleSigningLc)); + globalThis.types = types; globalThis.getTestCache = getTestCache; diff --git a/packages/plugins/ler-rs/.gitignore b/packages/plugins/ler-rs/.gitignore new file mode 100644 index 0000000000..bf015247b5 --- /dev/null +++ b/packages/plugins/ler-rs/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +tsconfig.tsbuildinfo diff --git a/packages/plugins/ler-rs/README.md b/packages/plugins/ler-rs/README.md new file mode 100644 index 0000000000..6299a7c0a0 --- /dev/null +++ b/packages/plugins/ler-rs/README.md @@ -0,0 +1,210 @@ +# @learncard/ler-rs-plugin + +Create, package, and verify Learning & Employment Record Resume (LER-RS) credentials. + +## Install + +This plugin is part of the LearnCard monorepo and is built with the workspace. + +## API + +- `createLerRecord(params: CreateLerRecordParams): Promise` +- `createLerPresentation(params: CreateLerPresentationParams): Promise` +- `verifyLerPresentation(params: VerifyLerPresentationParams): Promise` + +See `src/types.ts` for types and `src/ler-rs.ts` for implementation details. + +## Notes + +- Follows the Container + Verifiable Proof pattern by wrapping self-asserted containers and embedding VCs in `verifications` arrays. +- Ensures credential and presentation `type` fields are non-empty arrays. + +--- + +## Requirements + +- Your `LearnCard` must already have the VC plugin installed (provided by standard `@learncard/init` initializers). +- Add this plugin by passing the base LearnCard to the factory and then calling `addPlugin`: + +```ts +import { getLerRsPlugin } from '@learncard/ler-rs-plugin'; + +// baseLc should already include the VC plugin (e.g., via @learncard/init) +const lc = await baseLc.addPlugin(getLerRsPlugin(baseLc)); +``` + +The plugin captures `baseLc` internally to issue and verify VCs/VPs. + +## Quick start + +```ts +import type { + PersonProfile, + WorkHistoryItem, + EducationHistoryItem, + CertificationItem, +} from '@learncard/ler-rs-plugin'; +import { getLerRsPlugin } from '@learncard/ler-rs-plugin'; + +// 1) Add plugin +const lc = await baseLc.addPlugin(getLerRsPlugin(baseLc)); + +// 2) Build a LER-RS credential (self-asserted + optional embedded VCs) +const person: PersonProfile = { + id: 'did:example:alice', + givenName: 'Alice', + familyName: 'Anderson', + email: 'alice@example.com', +}; + +const workHistory: WorkHistoryItem[] = [ + { + position: 'Marketing Professional', + employer: 'ABC Company', + start: '2022-01-01', + end: '2024-06-01', + narrative: 'Led a multi-channel campaign with 200% ROI.', + }, +]; + +const educationHistory: EducationHistoryItem[] = [ + { + institution: 'State University', + degree: 'B.S. Business', + specializations: ['Marketing Analytics'], + start: '2018-09-01', + end: '2022-05-15', + }, +]; + +const certifications: CertificationItem[] = [ + { + name: 'Google Analytics Certification', + issuingAuthority: 'Google', + status: 'active', + narrative: 'Validated proficiency in GA4 and attribution modeling.', + }, +]; + +const skills = ['SEO/SEM', 'Content Strategy', 'Team Leadership']; + +const lerVc = await lc.invoke.createLerRecord({ + person, + workHistory, + educationHistory, + certifications, + skills, +}); + +// 3) Package into a VP (must include at least one LER-RS VC) +const vp = await lc.invoke.createLerPresentation({ + credentials: [lerVc], + domain: 'apply.acme.com', + challenge: 'a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8', +}); + +// 4) Verify the presentation +const verification = await lc.invoke.verifyLerPresentation({ + presentation: vp, + domain: 'apply.acme.com', + challenge: 'a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8', +}); + +console.log(verification.verified); +for (const r of verification.credentialResults) { + console.log(r.credential.id, r.verified, r.isSelfIssued, r.errors); +} +``` + +## Wrapping existing VCs inside containers + +Each container item (`workHistory`, `educationHistory`, `certifications`) can embed externally issued VCs as verifications. Provide `verifiableCredential` in the item to include it under `verifications`: + +```ts +// Assume employmentVc is a third-party or previously issued VC +const employmentVc = await lc.read.get('urn:uuid:employment-123'); + +const lerWithProof = await lc.invoke.createLerRecord({ + person, + workHistory: [ + { + narrative: 'My key responsibilities and outcomes.', + verifiableCredential: employmentVc, + }, + ], +}); +``` + +## Schema mapping guidance + +This plugin follows a "Container + Verifiable Proof" model. For `credentialSubject.ler`, we map as follows (see `src/ler-rs.ts`): + +- __person__ + - From `params.person` + - Maps to `ler.person` with `name.givenName`, `name.familyName`, and `name.formattedName` + +- __communication__ + - If `person.email` is provided, becomes `ler.communication.emails = [{ address: email }]` + +- __skills__ + - `params.skills: string[]` → `ler.skills = [{ name: string }]` + +- __employmentHistories__ (from `params.workHistory`) + - `employer` → `container.organization.tradeName` + - `position`, `start`, `end` → `container.positionHistories = [{ title, start, end }]` + - `narrative` → `container.narrative` + - `verifiableCredential` → `container.verifications = [VC]` + - Any other keys on the item are merged into the container as-is + +- __educationAndLearnings__ (from `params.educationHistory`) + - `institution`, `start`, `end` → same key names on container + - `degree`, `specializations` → `container.educationDegrees = [{ name: degree, specializations }]` + - `narrative` → `container.narrative` + - `verifiableCredential` → `container.verifications = [VC]` + +- __certifications__ (from `params.certifications`) + - All provided keys are copied into the container + - `narrative` → `container.narrative` + - `verifiableCredential` → `container.verifications = [VC]` + +Resulting VC shape (high-level): + +```ts +{ + '@context': ['https://www.w3.org/ns/credentials/v2'], + type: ['VerifiableCredential', 'LERRS'], + issuer: 'did:...issuer', + credentialSubject: { + id: 'did:...subject', + ler: { + person: { id, name: { givenName, familyName, formattedName } }, + communication?: { emails?: [{ address }] }, + skills?: [{ name }], + employmentHistories?: [{ ...container, verifications?: [VC] }], + educationAndLearnings?: [{ ...container, verifications?: [VC] }], + certifications?: [{ ...container, verifications?: [VC] }], + narratives?: string[], + } + } +} +``` + +## Verification behavior + +- `verifyLerPresentation` verifies the VP and each embedded VC. +- `VerificationResult.verified` is true when: + - The presentation verifies, and + - Every credential either verifies OR is considered self-issued. +- A credential is considered __self-issued__ when: + - It has type `LERRS`, or + - Its `issuer` DID equals the VP `holder` DID. + +## Troubleshooting + +- __Non-empty type arrays__: VC/VP `type` are always emitted as non-empty arrays to satisfy validators (e.g., Zod schemas that require `[string, ...string[]]`). +- __Missing VC plugin__: Ensure your base LearnCard already includes `@learncard/vc-plugin` (standard in `@learncard/init` initializers). + +## Contributing + +- Types live in `src/types.ts`. Implementation is in `src/ler-rs.ts`. +- Please add tests for new behaviors and keep examples in this README up-to-date with the code. diff --git a/packages/plugins/ler-rs/package.json b/packages/plugins/ler-rs/package.json new file mode 100644 index 0000000000..9a6427b4b3 --- /dev/null +++ b/packages/plugins/ler-rs/package.json @@ -0,0 +1,44 @@ +{ + "name": "@learncard/ler-rs-plugin", + "version": "0.1.0", + "description": "LER-RS plugin for LearnCard: create, package, and verify Learning & Employment Record Resumes", + "main": "./dist/index.js", + "module": "./dist/ler-rs-plugin.esm.js", + "files": [ + "dist" + ], + "scripts": { + "build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.js && tsc --p tsconfig.json", + "test": "jest --passWithNoTests", + "test:watch": "jest --watch", + "test:coverage": "jest --silent --ci --coverage --coverageReporters=\"text\" --coverageReporters=\"text-summary\"" + }, + "author": "Learning Economy Foundation (www.learningeconomy.io)", + "license": "MIT", + "homepage": "https://github.com/WeLibraryOS/LearnCard#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/WeLibraryOS/LearnCard.git" + }, + "bugs": { + "url": "https://github.com/WeLibraryOS/LearnCard/issues" + }, + "devDependencies": { + "@types/jest": "^29.2.2", + "@types/node": "^17.0.31", + "aqu": "0.4.3", + "esbuild": "^0.14.38", + "esbuild-jest": "^0.5.0", + "esbuild-plugin-copy": "^1.3.0", + "jest": "^29.3.0", + "rimraf": "^3.0.2", + "shx": "^0.3.4", + "ts-jest": "^29.0.3" + }, + "types": "./dist/index.d.ts", + "dependencies": { + "@learncard/core": "workspace:*", + "@learncard/types": "workspace:*", + "@learncard/vc-plugin": "workspace:*" + } +} diff --git a/packages/plugins/ler-rs/project.json b/packages/plugins/ler-rs/project.json new file mode 100644 index 0000000000..ca3fdc5acb --- /dev/null +++ b/packages/plugins/ler-rs/project.json @@ -0,0 +1,21 @@ +{ + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "name": "ler-rs-plugin", + "sourceRoot": "packages/plugins/ler-rs/src", + "projectType": "library", + "root": "packages/plugins/ler-rs", + "tags": [], + "implicitDependencies": ["types", "core", "vc-plugin"], + "namedInputs": { + "scripts": ["{projectRoot}/scripts/**/*"], + "source": ["{projectRoot}/src/**/*"], + "dist": ["{projectRoot}/dist/**/*"] + }, + "targets": { + "build": { + "executor": "nx:run-script", + "inputs": ["source", "scripts"], + "options": { "script": "build" } + } + } +} diff --git a/packages/plugins/ler-rs/scripts/build.mjs b/packages/plugins/ler-rs/scripts/build.mjs new file mode 100644 index 0000000000..606e1790c8 --- /dev/null +++ b/packages/plugins/ler-rs/scripts/build.mjs @@ -0,0 +1,80 @@ +import path from 'path'; + +import esbuild from 'esbuild'; +import rimraf from 'rimraf'; + +const buildOptions = { + // target: 'es6', + target: 'es2020', + sourcemap: true, + external: ['isomorphic-fetch', 'isomorphic-webcrypto'], +}; + +const configurations = [ + { + keepNames: true, + bundle: true, + sourcemap: 'external', + incremental: true, + tsconfig: 'tsconfig.json', + plugins: [], + entryPoints: ['src/index.ts'], + format: 'cjs', + outfile: 'dist/ler-rs-plugin.cjs.development.js', + ...buildOptions, + }, + { + keepNames: true, + bundle: true, + sourcemap: 'external', + incremental: true, + tsconfig: 'tsconfig.json', + plugins: [], + entryPoints: ['src/index.ts'], + minify: true, + format: 'cjs', + outfile: 'dist/ler-rs-plugin.cjs.production.min.js', + ...buildOptions, + }, + { + keepNames: true, + bundle: true, + sourcemap: 'external', + incremental: true, + tsconfig: 'tsconfig.json', + plugins: [], + entryPoints: ['src/index.ts'], + format: 'esm', + outfile: 'dist/ler-rs-plugin.esm.js', + ...buildOptions, + }, +]; + +function asyncRimraf(path) { + return new Promise((resolve, reject) => { + rimraf(path, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + +await Promise.all( + configurations.map(async config => { + var dir = config.outdir || path.dirname(config.outfile); + await asyncRimraf(dir).catch(() => { + console.log('Unable to delete directory', dir); + }); + }) +); + +await Promise.all(configurations.map(config => esbuild.build(config))).catch(err => { + console.error('❌ Build failed'); + process.exit(1); +}); + +console.log('✔ Build successful'); +process.exit(0); diff --git a/packages/plugins/ler-rs/scripts/mixedEntypoint.js b/packages/plugins/ler-rs/scripts/mixedEntypoint.js new file mode 100644 index 0000000000..3d07537211 --- /dev/null +++ b/packages/plugins/ler-rs/scripts/mixedEntypoint.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./ler-rs-plugin.cjs.production.min.js'); +} else { + module.exports = require('./ler-rs-plugin.cjs.development.js'); +} diff --git a/packages/plugins/ler-rs/src/index.ts b/packages/plugins/ler-rs/src/index.ts new file mode 100644 index 0000000000..e0f90b81b2 --- /dev/null +++ b/packages/plugins/ler-rs/src/index.ts @@ -0,0 +1,2 @@ +export { getLerRsPlugin } from './ler-rs'; +export * from './types'; diff --git a/packages/plugins/ler-rs/src/ler-rs.ts b/packages/plugins/ler-rs/src/ler-rs.ts new file mode 100644 index 0000000000..8ee880654e --- /dev/null +++ b/packages/plugins/ler-rs/src/ler-rs.ts @@ -0,0 +1,177 @@ +import { VC as VerifiableCredential, UnsignedVC, VP as VerifiablePresentation, UnsignedVP } from '@learncard/types'; +import { LERRSDependentLearnCard, LERRSPlugin, CreateLerRecordParams, CreateLerPresentationParams, VerifyLerPresentationParams, VerificationResult, LerRsRecord } from './types'; + +const VC_CONTEXT = 'https://www.w3.org/ns/credentials/v2'; +const LERRS_TYPE = 'LERRS'; + +const toArray = (maybe: T | T[] | undefined): T[] => (maybe == null ? [] : Array.isArray(maybe) ? maybe : [maybe]); + +const buildEmploymentHistories = (items: NonNullable): LerRsRecord['employmentHistories'] => { + return items.map(item => { + const { narrative, verifiableCredential, position, employer, start, end, ...rest } = item; + + const container: Record = { ...rest }; + + if (employer) container.organization = { tradeName: employer }; + if (position || start || end) { + const ph: Record = {}; + if (position) ph.title = position; + if (start) ph.start = start; + if (end) ph.end = end; + container.positionHistories = [ph]; + } + if (narrative) container.narrative = narrative; + + const verifications = verifiableCredential ? [verifiableCredential] : []; + return { ...container, ...(verifications.length ? { verifications } : {}) }; + }); +}; + +const buildEducationAndLearnings = (items: NonNullable): LerRsRecord['educationAndLearnings'] => { + return items.map(item => { + const { narrative, verifiableCredential, institution, start, end, degree, specializations, ...rest } = item; + + const container: Record = { ...rest }; + if (institution) container.institution = institution; + if (start) container.start = start; + if (end) container.end = end; + if (degree || specializations) { + container.educationDegrees = [{ ...(degree ? { name: degree } : {}), ...(specializations ? { specializations } : {}) }]; + } + if (narrative) container.narrative = narrative; + + const verifications = verifiableCredential ? [verifiableCredential] : []; + return { ...container, ...(verifications.length ? { verifications } : {}) }; + }); +}; + +const buildCertifications = (items: NonNullable): LerRsRecord['certifications'] => { + return items.map(item => { + const { narrative, verifiableCredential, ...rest } = item; + + const container: Record = { ...rest }; + if (narrative) container.narrative = narrative; + + const verifications = verifiableCredential ? [verifiableCredential] : []; + return { ...container, ...(verifications.length ? { verifications } : {}) }; + }); +}; + +export const getLerRsPlugin = (initLearnCard: LERRSDependentLearnCard): LERRSPlugin => { + return { + name: 'LER-RS', + displayName: 'LER-RS', + description: 'Create, package, and verify Learning & Employment Record Resume (LER-RS) credentials', + methods: { + createLerRecord: async (_learnCard, params): Promise => { + const signer = params.learnCard ?? _learnCard; + const did = signer.id.did(); + + const personSection: LerRsRecord['person'] = { + id: params.person.id, + name: { + givenName: params.person.givenName, + familyName: params.person.familyName, + formattedName: `${params.person.givenName} ${params.person.familyName}`, + }, + }; + + const communication: LerRsRecord['communication'] | undefined = params.person.email + ? { emails: [{ address: params.person.email }] } + : undefined; + + const lerRecord: LerRsRecord = { + person: personSection, + ...(communication ? { communication } : {}), + skills: (params.skills || []).map(s => ({ name: s })), + employmentHistories: params.workHistory ? buildEmploymentHistories(params.workHistory) : undefined, + educationAndLearnings: params.educationHistory ? buildEducationAndLearnings(params.educationHistory) : undefined, + certifications: params.certifications ? buildCertifications(params.certifications) : undefined, + narratives: [], + }; + + const unsignedVC: UnsignedVC = { + '@context': [VC_CONTEXT], + id: `urn:uuid:${crypto.randomUUID()}`, + type: ['VerifiableCredential', LERRS_TYPE], + issuer: did, + validFrom: new Date().toISOString(), + credentialSubject: { + id: params.person.id, + ler: lerRecord, + }, + }; + + return initLearnCard.invoke.issueCredential(unsignedVC, { proofPurpose: 'assertionMethod' }); + }, + + createLerPresentation: async (_learnCard, params): Promise => { + const signer = params.learnCard ?? _learnCard; + const did = signer.id.did(); + + if (!params.credentials.length) throw new Error('createLerPresentation: credentials array must contain at least one credential'); + const containsLer = params.credentials.some(vc => Array.isArray(vc.type) && vc.type.includes(LERRS_TYPE)); + if (!containsLer) throw new Error('createLerPresentation: credentials must include at least one LER-RS credential'); + + const vp: UnsignedVP = { + '@context': [VC_CONTEXT], + type: ['VerifiablePresentation'], + holder: did, + verifiableCredential: params.credentials.length === 1 ? params.credentials[0] : params.credentials, + }; + + return initLearnCard.invoke.issuePresentation(vp, { + ...(params.domain ? { domain: params.domain } : {}), + ...(params.challenge ? { challenge: params.challenge } : {}), + }); + }, + + verifyLerPresentation: async (_learnCard, { presentation, domain, challenge }: VerifyLerPresentationParams): Promise => { + const presCheck = await initLearnCard.invoke.verifyPresentation(presentation, { + ...(domain ? { domain } : {}), + ...(challenge ? { challenge } : {}), + }); + + const presentationResult = { + verified: presCheck.errors.length === 0, + errors: presCheck.errors.length ? presCheck.errors : undefined, + }; + + const credentialResults: VerificationResult['credentialResults'] = []; + + if (typeof presentation !== 'string') { + const holder = presentation.holder; + const vcs = toArray(presentation.verifiableCredential as any); + + for (const credential of vcs) { + try { + const credCheck = await initLearnCard.invoke.verifyCredential(credential); + const issuerDid = typeof credential.issuer === 'string' ? credential.issuer : credential.issuer?.id; + const isSelfIssued = (Array.isArray(credential.type) && credential.type.includes(LERRS_TYPE)) || (!!holder && !!issuerDid && issuerDid === holder); + + credentialResults.push({ + credential, + verified: credCheck.errors.length === 0, + isSelfIssued, + errors: credCheck.errors.length ? credCheck.errors : undefined, + }); + } catch (err) { + credentialResults.push({ + credential, + verified: false, + isSelfIssued: false, + errors: [err instanceof Error ? err.message : 'Unknown error verifying credential'], + }); + } + } + } + + return { + verified: presentationResult.verified && credentialResults.every(r => r.verified || r.isSelfIssued), + presentationResult, + credentialResults, + }; + }, + }, + }; +}; diff --git a/packages/plugins/ler-rs/src/types.ts b/packages/plugins/ler-rs/src/types.ts new file mode 100644 index 0000000000..e0f36f293e --- /dev/null +++ b/packages/plugins/ler-rs/src/types.ts @@ -0,0 +1,124 @@ +import { Plugin, LearnCard } from '@learncard/core'; +import { VC as VerifiableCredential, VP as VerifiablePresentation, UnsignedVC, UnsignedVP, VerificationCheck } from '@learncard/types'; +import type { VCPluginMethods, VCPluginDependentMethods } from '@learncard/vc-plugin'; + +export interface PersonProfile { + id: string; // User's DID + givenName: string; + familyName: string; + email?: string; +} + +// Simple skills structure with optional endorsements +export interface LerSkill { + name: string; + comments?: string[]; + yearsOfExperience?: number; + endorsers?: Array<{ person?: { name?: { formattedName?: string } }; organization?: { name?: string } }>; +} + +// Container item types allow either self-asserted fields and/or embedded VCs via `verifiableCredential` +export interface WorkHistoryItem { + narrative?: string; + verifiableCredential?: VerifiableCredential; + // Common self-asserted fields + position?: string; + employer?: string; + start?: string; + end?: string; + [key: string]: unknown; +} + +export interface EducationHistoryItem { + narrative?: string; + verifiableCredential?: VerifiableCredential; + institution?: string; + start?: string; + end?: string; + degree?: string; + specializations?: string[]; + [key: string]: unknown; +} + +export interface CertificationItem { + narrative?: string; + verifiableCredential?: VerifiableCredential; + name?: string; + issuingAuthority?: string; + status?: string; + effectiveTimePeriod?: { start?: string; end?: string }; + [key: string]: unknown; +} + +// High-level LER-RS record assembled into the VC's credentialSubject +export interface LerRsRecord { + person: { + id: string; + name: { + givenName: string; + familyName: string; + formattedName?: string; + }; + }; + communication?: { + emails?: { address: string }[]; + web?: string[]; + phone?: string[]; + address?: unknown; + social?: string[]; + }; + skills?: LerSkill[]; + narratives?: string[]; + educationAndLearnings?: Array<({ verifications?: VerifiableCredential[] } & Record)>; + employmentHistories?: Array<({ verifications?: VerifiableCredential[] } & Record)>; + licenses?: Array<({ verifications?: VerifiableCredential[] } & Record)>; + certifications?: Array<({ verifications?: VerifiableCredential[] } & Record)>; + positionPreferences?: Record; + attachments?: Array>; +} + +export interface CreateLerRecordParams { + learnCard: LearnCard; + person: PersonProfile; + workHistory?: WorkHistoryItem[]; + educationHistory?: EducationHistoryItem[]; + certifications?: CertificationItem[]; + skills?: string[]; +} + +export interface CreateLerPresentationParams { + learnCard: LearnCard; + credentials: VerifiableCredential[]; + domain?: string; + challenge?: string; +} + +export interface VerifyLerPresentationParams { + presentation: VerifiablePresentation | string; + domain?: string; + challenge?: string; +} + +export interface VerificationResult { + verified: boolean; // Overall status + presentationResult: { + verified: boolean; + errors?: string[]; + }; + credentialResults: { + credential: VerifiableCredential; + verified: boolean; + isSelfIssued: boolean; + errors?: string[]; + }[]; +} + +export type LERRSDependentLearnCard = LearnCard; + +export type LERRSPluginMethods = { + createLerRecord: (params: CreateLerRecordParams) => Promise; + createLerPresentation: (params: CreateLerPresentationParams) => Promise; + verifyLerPresentation: (params: VerifyLerPresentationParams) => Promise; +}; + +export type LERRSPlugin = Plugin<'LER-RS', never, LERRSPluginMethods, 'id', VCPluginMethods & VCPluginDependentMethods>; diff --git a/packages/plugins/ler-rs/tsconfig.json b/packages/plugins/ler-rs/tsconfig.json new file mode 100644 index 0000000000..1e45cfffe5 --- /dev/null +++ b/packages/plugins/ler-rs/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "lib": ["dom", "es2021"], + "noLib": false, + "rootDir": "src", + "skipLibCheck": true, + "paths": {}, + "allowSyntheticDefaultImports": true, + "moduleResolution": "bundler" + }, + "include": ["src", "src/test"], + "exclude": ["node_modules", "example"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b9a88cdb6..9303131e8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,7 +52,7 @@ importers: version: 2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: specifier: ^2.27.5 - version: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + version: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1) eslint-plugin-prettier: specifier: ^4.2.1 version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8) @@ -433,6 +433,9 @@ importers: '@learncard/learn-cloud-plugin': specifier: workspace:* version: link:../plugins/learn-cloud + '@learncard/ler-rs-plugin': + specifier: workspace:* + version: link:../plugins/ler-rs '@learncard/simple-signing-plugin': specifier: workspace:* version: link:../plugins/simple-signing-plugin @@ -1452,6 +1455,49 @@ importers: specifier: ^29.0.3 version: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.14.54)(jest@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@17.0.45)(typescript@5.6.2)))(typescript@5.6.2) + packages/plugins/ler-rs: + dependencies: + '@learncard/core': + specifier: workspace:* + version: link:../../learn-card-core + '@learncard/types': + specifier: workspace:* + version: link:../../learn-card-types + '@learncard/vc-plugin': + specifier: workspace:* + version: link:../vc + devDependencies: + '@types/jest': + specifier: ^29.2.2 + version: 29.5.14 + '@types/node': + specifier: ^17.0.31 + version: 17.0.45 + aqu: + specifier: 0.4.3 + version: 0.4.3(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(@types/node@17.0.45)(babel-jest@29.7.0(@babel/core@7.26.10))(ts-node@10.9.2(@types/node@17.0.45)(typescript@5.6.2)) + esbuild: + specifier: ^0.14.38 + version: 0.14.54 + esbuild-jest: + specifier: ^0.5.0 + version: 0.5.0(esbuild@0.14.54) + esbuild-plugin-copy: + specifier: ^1.3.0 + version: 1.6.0(esbuild@0.14.54) + jest: + specifier: ^29.3.0 + version: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@17.0.45)(typescript@5.6.2)) + rimraf: + specifier: ^3.0.2 + version: 3.0.2 + shx: + specifier: ^0.3.4 + version: 0.3.4 + ts-jest: + specifier: ^29.0.3 + version: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.14.54)(jest@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@17.0.45)(typescript@5.6.2)))(typescript@5.6.2) + packages/plugins/simple-signing-plugin: dependencies: '@learncard/simple-signing-client': @@ -1871,7 +1917,7 @@ importers: version: 2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: specifier: ^2.26.0 - version: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + version: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1) eslint-plugin-prettier: specifier: ^4.2.1 version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8) @@ -2463,13 +2509,13 @@ importers: version: 2.6.1 '@metamask/eslint-config': specifier: ^10.0.0 - version: 10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8) + version: 10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8) '@metamask/eslint-config-jest': specifier: ^10.0.0 - version: 10.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2))(eslint@8.57.1) + version: 10.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2))(eslint@8.57.1) '@metamask/eslint-config-nodejs': specifier: ^8.0.0 - version: 8.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-node@11.1.0(eslint@8.57.1))(eslint@8.57.1) + version: 8.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-node@11.1.0(eslint@8.57.1))(eslint@8.57.1) '@metamask/providers': specifier: ^9.1.0 version: 9.1.0 @@ -26501,23 +26547,23 @@ snapshots: '@metamask/detect-provider@1.2.0': {} - '@metamask/eslint-config-jest@10.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2))(eslint@8.57.1)': + '@metamask/eslint-config-jest@10.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2))(eslint@8.57.1)': dependencies: - '@metamask/eslint-config': 10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8) + '@metamask/eslint-config': 10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8) eslint: 8.57.1 eslint-plugin-jest: 26.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.6.2)))(typescript@5.6.2) - '@metamask/eslint-config-nodejs@8.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-node@11.1.0(eslint@8.57.1))(eslint@8.57.1)': + '@metamask/eslint-config-nodejs@8.0.0(@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8))(eslint-plugin-node@11.1.0(eslint@8.57.1))(eslint@8.57.1)': dependencies: - '@metamask/eslint-config': 10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8) + '@metamask/eslint-config': 10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8) eslint: 8.57.1 eslint-plugin-node: 11.1.0(eslint@8.57.1) - '@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8)': + '@metamask/eslint-config@10.0.0(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1))(eslint-plugin-jsdoc@39.9.1(eslint@8.57.1))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8))(eslint@8.57.1)(prettier@2.8.8)': dependencies: eslint: 8.57.1 eslint-config-prettier: 8.10.0(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1) eslint-plugin-jsdoc: 39.9.1(eslint@8.57.1) eslint-plugin-prettier: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8) prettier: 2.8.8 @@ -34229,7 +34275,7 @@ snapshots: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.1 - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1) object.assign: 4.1.7 object.entries: 1.1.9 semver: 6.3.1 @@ -34240,7 +34286,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.6.2) eslint: 8.57.1 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1) eslint-config-prettier@8.10.0(eslint@8.57.1): dependencies: @@ -34258,7 +34304,7 @@ snapshots: dependencies: debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.1 - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1) glob: 7.2.3 is-glob: 4.0.3 resolve: 1.22.10 @@ -34266,7 +34312,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@5.5.0) optionalDependencies: @@ -34283,7 +34329,7 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -34294,7 +34340,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3