diff --git a/pkg/core/src/index.ts b/pkg/core/src/index.ts index 77e42706..075db3b1 100644 --- a/pkg/core/src/index.ts +++ b/pkg/core/src/index.ts @@ -1,4 +1,4 @@ -export * from '@slangroom/core/lexicon'; +export * from '@slangroom/core/lexer'; export * from '@slangroom/core/parser'; export * from '@slangroom/core/lexer'; export * from '@slangroom/core/visitor'; diff --git a/pkg/core/src/lexer.ts b/pkg/core/src/lexer.ts index d6a0c9ea..c0118b8a 100644 --- a/pkg/core/src/lexer.ts +++ b/pkg/core/src/lexer.ts @@ -1,10 +1,49 @@ -import { Lexicon } from '@slangroom/core'; -import { Lexer } from '@slangroom/deps/chevrotain'; - -/** - * Lexes the given line. - */ -export const lex = (lexicon: Lexicon, line: string) => { - const lexer = new Lexer(lexicon.tokens); - return lexer.tokenize(line); +export class Token { + readonly name: string; + readonly isIdent: boolean; + + constructor( + readonly raw: string, + readonly start: number, + readonly end: number, + ) { + this.name = this.raw.toLowerCase(); + this.isIdent = this.raw.charAt(0) === "'"; + } +} + +export class LexError extends Error { + constructor(t: Token) { + super(); + this.name = 'LexError'; + this.message = `unclosed single-quote at ${t.start},${t.end}: ${t.raw}`; + } +} + +export const lex = (line: string): Token[] => { + const tokens: Token[] = []; + const c = [...line]; + let raw = ''; + let i = 0; + + while (i < c.length) { + while (c[i] === ' ' || c[i] === '\t') ++i; + + if (c[i] === "'") { + const start = i; + raw += c[i++]; + while (i < c.length && c[i] !== "'") raw += c[i++]; + if (i >= c.length) throw new LexError(new Token(raw, start, c.length - 1)); + raw += c[i++]; + tokens.push(new Token(raw, start, i - 1)); + raw = ''; + } else { + const start = i; + while (i < c.length && c[i] !== ' ' && c[i] !== '\t' && c[i] !== "'") raw += c[i++]; + if (raw.length) tokens.push(new Token(raw, start, i - 1)); + raw = ''; + } + } + + return tokens; }; diff --git a/pkg/core/src/lexicon.ts b/pkg/core/src/lexicon.ts index f7cb412a..247b9c7b 100644 --- a/pkg/core/src/lexicon.ts +++ b/pkg/core/src/lexicon.ts @@ -16,7 +16,7 @@ export class Lexicon { name: 'Whitespace', pattern: /\s+/, group: Lexer.SKIPPED, - }) + }), ); this.#store.set( @@ -25,7 +25,7 @@ export class Lexicon { name: 'Comment', pattern: /#[^\n\r]*/, group: 'comments', - }) + }), ); this.#store.set( @@ -33,7 +33,7 @@ export class Lexicon { createToken({ name: 'Identifier', pattern: /'(?:[^\\']|\\(?:[bfnrtv'\\/]|u[0-9a-fA-F]{4}))*'/, - }) + }), ); } diff --git a/pkg/core/src/parser.ts b/pkg/core/src/parser.ts index ac0de01f..45bd1a25 100644 --- a/pkg/core/src/parser.ts +++ b/pkg/core/src/parser.ts @@ -1,119 +1,154 @@ -import { Lexicon } from '@slangroom/core'; -import { - CstParser, - type IToken, - type CstNode, - type IOrAlt, - type ConsumeMethodOpts, -} from '@slangroom/deps/chevrotain'; - -export type StatementCst = CstNode & { - children: { [K in string]: [PhraseCst] }; -}; +import { PluginMap, Token, type PluginMapKey } from '@slangroom/core'; + +export class ParseError extends Error { + static wrong(have: Token, wantFirst: string, ...wantRest: string[]) { + const wants = [wantFirst, ...wantRest]; + return new ParseError( + `"${have.raw}" between (${have.start}, ${have.end}) must be one of: ${wants.join( + ', ', + )}`, + ); + } -export type PhraseCst = CstNode & { - children: { - connect?: [IToken]; - } & { open?: [IToken] } & { into?: [IToken] } & { - [K in string]: [IToken | PhraseCst]; - }; -}; + static missing(wantFirst: string, ...wantRest: string[]) { + const wants = [wantFirst, ...wantRest]; + return new ParseError(`missing token(s): ${wants.join(', ')}`); + } -export class Parser extends CstParser { - #phrases: IOrAlt[]; - #lexicon: Lexicon; - - constructor(lexicon: Lexicon, parsers: ((this: Parser) => void)[]) { - super(lexicon.tokens, { maxLookahead: 1024 }); - this.#lexicon = lexicon; - parsers = [...new Set(parsers)]; - parsers.forEach((p) => p.apply(this)); - this.#phrases = Object.entries(this).reduce((acc, [k, v]) => { - if (k.endsWith('Phrase') && typeof v === 'function') - acc.push({ ALT: () => this.SUBRULE(v) }); - return acc; - }, [] as IOrAlt[]); - this.performSelfAnalysis(); + static extra(token: Token) { + return new ParseError(`extra token (${token.start}, ${token.end}): ${token.raw}`); } /** - * {@inheritDoc Lexicon.token} + * @internal */ - #token(name: string) { - return this.#lexicon.token(name); + constructor(message: string) { + super(message); + this.name = 'ParseError'; } +} - tokenn(idx: number, name: string, opts?: ConsumeMethodOpts) { - this.consume(idx, this.#token(name), opts); - } +export type Cst = { + givenThen?: 'given' | 'then'; + errors: ParseError[]; + matches: Match[]; +}; - token(name: string, opts?: ConsumeMethodOpts) { - this.tokenn(0, name, opts); - } +export type Match = { + bindings: Map; + key: PluginMapKey; + err: ParseError[]; + into?: string; +} & ( + | { + open?: string; + connect?: never; + } + | { + open?: never; + connect?: string; + } + ); + +export const parse = (p: PluginMap, t: Token[]): Cst => { + const cst: Cst = { + matches: [], + errors: [], + }; + let givenThen: 'given' | 'then' | undefined; - token1(name: string, opts?: ConsumeMethodOpts) { - this.tokenn(1, name, opts); - } + // Given or Then + if (t[0]?.name === 'given') givenThen = 'given'; + else if (t[0]?.name === 'then') givenThen = 'then'; + else if (t[0]) cst.errors.push(ParseError.wrong(t[0], 'given', 'then')); + else cst.errors.push(ParseError.missing('given', 'then')); - token2(name: string, opts?: ConsumeMethodOpts) { - this.tokenn(2, name, opts); - } + // TODO: should we allow "that" here ("Given that I") - token3(name: string, opts?: ConsumeMethodOpts) { - this.tokenn(3, name, opts); + // I + if (t[1]) { + if (t[1]?.raw !== 'I') cst.errors.push(ParseError.wrong(t[1], 'I')); + } else { + cst.errors.push(ParseError.missing('I')); } - - statement = this.RULE('statement', () => { - this.OR(this.#phrases); + p.forEach(([k]) => { + let i = 1; + const m: Match = { key: k, bindings: new Map(), err: [] }; + const curErrLen = cst.matches[0]?.err.length; + const lemmeout = {}; + const newErr = (have: undefined | Token, wantsFirst: string, ...wantsRest: string[]) => { + if (have) m.err.push(ParseError.wrong(have, wantsFirst, ...wantsRest)); + else m.err.push(ParseError.missing(wantsFirst, ...wantsRest)); + if (curErrLen !== undefined && m.err.length > curErrLen) throw lemmeout; + }; + try { + // Open 'ident' and|Connect to 'ident' and + if (k.openconnect === 'open') { + if (t[++i]?.name !== 'open') newErr(t[i], 'open'); + const ident = t[++i]; + if (ident?.isIdent) m.open = ident.raw.slice(1, -1); + else newErr(ident, ''); + if (t[++i]?.name !== 'and') newErr(t[i], 'and'); + } else if (k.openconnect === 'connect') { + if (t[++i]?.name !== 'connect') newErr(t[i], 'connect'); + if (t[++i]?.name !== 'to') newErr(t[i], 'connect'); + const ident = t[++i]; + if (ident?.isIdent) m.connect = ident.raw.slice(1, -1); + else newErr(ident, ''); + if (t[++i]?.name !== 'and') newErr(t[i], 'and'); + } + + // Send $buzzword 'ident' And + // TODO: allow spaces in between params + const params = new Set(k.params); + k.params?.forEach(() => { + if (t[++i]?.name !== 'send') newErr(t[i], 'send'); + + const tokName = t[++i]; + if (tokName && params.has(tokName.name)) { + params.delete(tokName.name); + } else { + const [first, ...rest] = [...params.values()] as [string, ...string[]]; + newErr(t[i], first, ...rest); + } + + const ident = t[++i]; + if (ident?.isIdent) { + if (tokName) m.bindings.set(tokName.name, ident.raw.slice(1, -1)); + } else { + newErr(ident, ''); + } + if (t[++i]?.name !== 'and') newErr(t[i], 'and'); + }); + + // $buzzwords + k.phrase.split(' ').forEach((name) => t[++i]?.name !== name && newErr(t[i], name)); + + // Save Output Into 'ident' + const ident = t[t.length - 1]; + if (t.length - i >= 5 && ident?.isIdent) { + for (++i; i < t.length - 4; ++i) m.err.push(ParseError.extra(t[i] as Token)); + if (t[t.length - 4]?.name !== 'and') newErr(t[t.length - 4], 'and'); + if (t[t.length - 3]?.name !== 'output') newErr(t[t.length - 3], 'output'); + if (t[t.length - 2]?.name !== 'into') newErr(t[t.length - 2], 'into'); + if ( + t[t.length - 4]?.name === 'and' && + t[t.length - 3]?.name === 'output' && + t[t.length - 2]?.name === 'into' + ) + m.into = ident.raw.slice(1, -1); + } else { + for (++i; i < t.length; ++i) m.err.push(ParseError.extra(t[i] as Token)); + } + + if (curErrLen !== undefined && m.err.length > curErrLen) throw lemmeout; + if (curErrLen !== undefined && m.err.length < curErrLen) cst.matches.length = 0; + cst.matches.push(m); + } catch (e) { + if (e !== lemmeout) throw e; + } }); - connect() { - this.tokenn(255, 'connect'); - this.tokenn(255, 'to'); - this.tokenn(255, 'identifier', { LABEL: 'connect' }); - this.tokenn(255, 'and'); - } - - open() { - this.tokenn(255, 'open'); - this.tokenn(255, 'identifier', { LABEL: 'open' }); - this.tokenn(255, 'and'); - } - - into() { - this.tokenn(254, 'and'); - this.tokenn(254, 'output'); - this.tokenn(254, 'into'); - this.tokenn(254, 'identifier', { LABEL: 'into' }); - } - - sendpassn(idx: number, parameter: string) { - this.or(idx, [ - { ALT: () => this.tokenn(idx, 'send', { LABEL: `sendpass${idx}` }) }, - { ALT: () => this.tokenn(idx, 'pass', { LABEL: `sendpass${idx}` }) }, - ]); - this.tokenn(idx, parameter, { LABEL: `sendpass${idx}.parameter` }); - this.tokenn(idx, 'identifier', { LABEL: `sendpass${idx}.identifier` }); - this.tokenn(idx, 'and', { LABEL: `sendpass${idx}.and` }); - } - - sendpass(parameter: string) { - this.sendpassn(0, parameter); - } - - sendpass1(parameter: string) { - this.sendpassn(1, parameter); - } - - sendpass2(parameter: string) { - this.sendpassn(2, parameter); - } -} - -export const parse = (parser: Parser, tokens: IToken[]) => { - parser.input = tokens; - return { - cst: parser.statement(), - errors: parser.errors, - }; + if (givenThen) cst.givenThen = givenThen; + return cst; }; diff --git a/pkg/core/src/plugin.ts b/pkg/core/src/plugin.ts index d52a214a..fc0f11fe 100644 --- a/pkg/core/src/plugin.ts +++ b/pkg/core/src/plugin.ts @@ -1,35 +1,154 @@ -import type { Jsonable, ZenParams } from '@slangroom/shared'; -import { Parser, type Statement } from '@slangroom/core'; +import type { Jsonable } from '@slangroom/shared'; +import type { Ast } from '@slangroom/core'; -/** - * A Plugin definition. - */ -export type Plugin = { - parser: (this: Parser) => void; - executor: PluginExecutor; +export type PluginMapKey = { + phrase: string; + params?: string[]; + openconnect?: 'open' | 'connect'; }; -/** - * @example - * ```ts - * const myPlugin = async (ctx: PluginContext) => Promise { - * if (ctx.phrase !== "doesn't match with my definition") - * return ctx.fail("syntax error"); - * - * const filePath = ctx.fetch("path"); - * if (typeof filePath !== "string") - * return ctx.fail("file must be string")$ - * - * const [err, result] = writeFile(filePath, "hello, world!"); - * if (err) - * return ctx.fail(err); - * - * return ctx.pass(result); - * } - * ``` - */ +export class DuplicatePluginError extends Error { + constructor({ openconnect, params, phrase }: PluginMapKey) { + super( + `duplicated plugin with key: openconnect=${openconnect ?? ''} params=${[ + ...(params ?? []), + ].join(', ')} phrase="${phrase}"`, + ); + this.name = 'DubplicatePluginError'; + } +} + +export class PluginMap { + #store: [PluginMapKey, PluginExecutor][] = []; + + #index(their: PluginMapKey) { + return this.#store.findIndex(([our]) => { + const openconn = their.openconnect === our.openconnect; + const params = + their.params?.length === our.params?.length && + (their.params ?? []).every((v, i) => v === our.params?.[i]); + const phrase = their.phrase === our.phrase; + return openconn && params && phrase; + }); + } + + forEach(cb: (value: [PluginMapKey, PluginExecutor]) => void) { + this.#store.forEach(cb); + } + + get(k: PluginMapKey) { + return this.#store[this.#index(k)]?.[1]; + } + + has(k: PluginMapKey) { + return this.#index(k) !== -1; + } + + set(k: PluginMapKey, v: PluginExecutor) { + if (this.has(k)) throw new DuplicatePluginError(k); + this.#store.push([k, v]); + } +} + export type PluginExecutor = (ctx: PluginContext) => PluginResult | Promise; +export class Plugin { + store = new PluginMap(); + + new(phrase: string, executor: PluginExecutor): PluginExecutor; + new(params: string[], phrase: string, executor: PluginExecutor): PluginExecutor; + new(openconnect: 'open' | 'connect', phrase: string, executor: PluginExecutor): PluginExecutor; + new( + openconnect: 'open' | 'connect', + params: string[], + phrase: string, + executor: PluginExecutor, + ): PluginExecutor; + new( + phraseOrParamsOrOpenconnect: string | string[] | 'open' | 'connect', + executorOrPhraseOrParams: PluginExecutor | string | string[], + executorOrPhrase?: PluginExecutor | string, + executor?: PluginExecutor, + ): PluginExecutor { + let openconnect: PluginMapKey['openconnect']; + let params: undefined | string[]; + let phrase: string; + + if ( + // The 4th clause: + typeof phraseOrParamsOrOpenconnect === 'string' && + (phraseOrParamsOrOpenconnect === 'open' || phraseOrParamsOrOpenconnect === 'connect') && + Array.isArray(executorOrPhraseOrParams) && + typeof executorOrPhrase === 'string' && + executor + ) { + openconnect = phraseOrParamsOrOpenconnect; + params = executorOrPhraseOrParams; + phrase = executorOrPhrase; + } else if ( + // The 3rd clause: + typeof phraseOrParamsOrOpenconnect === 'string' && + (phraseOrParamsOrOpenconnect === 'open' || phraseOrParamsOrOpenconnect === 'connect') && + typeof executorOrPhraseOrParams === 'string' && + typeof executorOrPhrase === 'function' + ) { + openconnect = phraseOrParamsOrOpenconnect; + phrase = executorOrPhraseOrParams; + executor = executorOrPhrase; + } else if ( + // The 2nd clause: + Array.isArray(phraseOrParamsOrOpenconnect) && + typeof executorOrPhraseOrParams === 'string' && + typeof executorOrPhrase === 'function' + ) { + params = phraseOrParamsOrOpenconnect; + phrase = executorOrPhraseOrParams; + executor = executorOrPhrase; + } else if ( + // The 1st clause: + typeof phraseOrParamsOrOpenconnect === 'string' && + typeof executorOrPhraseOrParams === 'function' + ) { + phrase = phraseOrParamsOrOpenconnect; + executor = executorOrPhraseOrParams; + } else { + throw new Error('unreachable'); + } + + // TODO: allow dashes and underscores only in between words + if (phrase.split(' ').some((x) => !x.match(/^[0-9a-z_-]+$/))) + throw new Error( + 'phrase must composed of alpha-numerical, underscore, and dash values split by a single space', + ); + + const key: PluginMapKey = { phrase: phrase }; + if (openconnect) key.openconnect = openconnect; + if (params) { + // TODO: allow dashes and underscores and only in between words + if (params.some((x) => !x.match(/^[0-9a-z_-]+$/))) + throw new Error( + 'each params must composed of alpha-numerical values, optionally split by dashes or underscores', + ); + const duplicates = [ + ...params.reduce((acc, cur) => { + const found = acc.get(cur); + if (found) acc.set(cur, found + 1); + else acc.set(cur, 1); + return acc; + }, new Map()), + ].reduce((acc, cur) => { + if (cur[1] > 1) acc.push(cur[0]); + return acc; + }, [] as string[]); + if (duplicates.length) + throw new Error(`params must not have duplicate values: ${duplicates.join(', ')}`); + key.params = params; + } + this.store.set(key, executor); + return executor; + } +} + // Todo: Maybe we should adapt some sort of monad library. /** @@ -43,11 +162,6 @@ export type ResultErr = { ok: false; error: any }; * The Plugin Context. It has every info a Plugin needs, plus some utilities. */ export type PluginContext = { - /** - * The Phrase part of a Statement. - */ - phrase: string; - /** * Gets the value of the Open or Connect part of a Statement, if available. * @@ -118,45 +232,10 @@ export type PluginContext = { * `'myProxy'`. */ export class PluginContextImpl implements PluginContext { - /** - * {@inheritDoc PluginContext.phrase} - */ - readonly phrase: string; - - /** - * The name of the identifier used to reference the Open or Connect - * parameters (via {@link #zparams}). It is an rhs value. - */ - #openconnect: string | undefined = undefined; - - /** - * A map between parameters that should be provided to a statetment and - * identifiers. - * - * @remarks - * Such as `address => 'myAddress'` and `proxy => 'foobar'`. The ones on - * the lhs are what the statement needs, and the ones on the rhs are the - * identifiers provided to the contract (via [[#data]] or [[#keys]]). - */ - #bindings = new Map(); + #ast: Ast; - /** - * The ZenParams. - */ - #zparams: ZenParams = { data: {}, keys: {} }; - - constructor(stmt: Statement, zparams: ZenParams) { - this.phrase = stmt.phrase; - this.#openconnect = stmt.openconnect; - this.#bindings = stmt.bindings; - this.#zparams = zparams; - } - - /** - * Gets the value referenced by {@link name} from {@link #zparams}. - */ - #getDataKeys(rhs: string): undefined | Jsonable { - return this.#zparams.data[rhs] ? this.#zparams.data[rhs] : this.#zparams.keys[rhs]; + constructor(ast: Ast) { + this.#ast = ast; } /** @@ -177,17 +256,7 @@ export class PluginContextImpl implements PluginContext { * {@inheritDoc PluginContext.getConnect} */ getConnect(): string[] { - if (!this.#openconnect) return []; - const val = this.#getDataKeys(this.#openconnect); - if (typeof val === 'string') return [val]; - if (Array.isArray(val)) { - if (val.every((x) => typeof x === 'string')) return val as string[]; - else - throw new Error( - `the array referenced by ${this.#openconnect} must solely composed of strings` - ); - } - return []; + return this.#ast.connect || []; } /** @@ -203,23 +272,23 @@ export class PluginContextImpl implements PluginContext { * {@inheritDoc PluginContext.getOpen} */ getOpen(): string[] { - return this.getConnect(); + return this.#ast.open || []; } /** * {@inheritDoc PluginContext.fetchOpen} */ fetchOpen(): [string, ...string[]] { - return this.fetchConnect(); + const val = this.getOpen(); + if (val.length === 0) throw new Error('a connect is required'); + return val as [string, ...string[]]; } /** * {@inheritDoc PluginContext.get} */ get(lhs: string): undefined | Jsonable { - const rhs = this.#bindings.get(lhs); - if (!rhs) return undefined; - return this.#getDataKeys(rhs); + return this.#ast.params.get(lhs); } /** diff --git a/pkg/core/src/slangroom.ts b/pkg/core/src/slangroom.ts index 4a817814..f5d2c218 100644 --- a/pkg/core/src/slangroom.ts +++ b/pkg/core/src/slangroom.ts @@ -1,99 +1,59 @@ import { getIgnoredStatements } from '@slangroom/ignored'; import { type ZenOutput, ZenParams, zencodeExec } from '@slangroom/shared'; -import { - lex, - parse, - visit, - PluginContextImpl, - Parser, - Lexicon, - type Plugin, - type StatementCst, - type Statement, - type PluginExecutor, -} from '@slangroom/core'; +import { lex, parse, visit, Plugin, PluginMap, PluginContextImpl } from '@slangroom/core'; -type Plugins = Plugin | Array; +export type Plugins = Plugin | Plugin[]; export class Slangroom { - #lexicon = new Lexicon(); - #parser: Parser; - #executors: PluginExecutor[] = []; + #plugins = new PluginMap(); constructor(first: Plugins, ...rest: Plugins[]) { - const parsers: ((this: Parser) => void)[] = []; - const recurse = (x: Plugins) => { - if (Array.isArray(x)) { - x.forEach(recurse); - } else { - parsers.push(x.parser); - this.#executors.push(x.executor); - } - }; - [first, ...rest].forEach(recurse); - this.#parser = new Parser(this.#lexicon, parsers); - } - - #astify(line: string) { - const lexed = lex(this.#lexicon, line); - if (lexed.errors.length) return { errors: lexed.errors }; - const parsed = parse(this.#parser, lexed.tokens); - if (parsed.errors.length) return { errors: parsed.errors }; - return { ast: visit(this.#parser, parsed.cst as StatementCst) }; + const p = this.#plugins; + [first, ...rest].forEach(function recurse(x) { + if (Array.isArray(x)) x.forEach(recurse); + else x.store.forEach(([k, v]) => p.set(k, v)); + }); } async execute(contract: string, optParams?: Partial): Promise { - const zparams = requirifyZenParams(optParams); - const givens: Statement[] = []; - const thens: Statement[] = []; - const ignoreds = await getIgnoredStatements(contract, zparams); - ignoreds.forEach((x: string) => { - const given = x.split(/^\s*given\s+i\s+/i); - if (given[1]) { - const { ast, errors } = this.#astify(given[1]); - if (!ast) throw errors; - givens.push(ast); - return; - } - - const then = x.split(/^\s*then\s+i\s+/i); - if (then[1]) { - const { ast, errors } = this.#astify(then[1]); - if (!ast) throw errors; - thens.push(ast); - } + const paramsGiven = requirifyZenParams(optParams); + const ignoredLines = await getIgnoredStatements(contract, paramsGiven); + const lexedLines = ignoredLines.map(lex); + const parsedLines = lexedLines.map((x) => parse(this.#plugins, x)); + parsedLines.forEach((x) => { + if (x.errors.length) throw new Error(`general errors: ${x.errors.join('\n')}`); + if (x.matches[0]?.err.length) + throw new Error(`${x.matches.map((y) => y.err).join('\n')}`); }); - for (const g of givens) await this.#execute(g, zparams); - - const zout = await zencodeExec(contract, zparams); - - const params: ZenParams = { data: zout.result, keys: zparams.keys }; - for (const t of thens) await this.#execute(t, params); + const cstGivens = parsedLines.filter((x) => x.givenThen === 'given'); + for (const cst of cstGivens) { + const ast = visit(cst, paramsGiven); + const exec = this.#plugins.get(ast.key); + if (!exec) throw new Error('no statements matched'); + const res = await exec(new PluginContextImpl(ast)); + if (res.ok && ast.into) paramsGiven.data[ast.into] = res.value; + } - return { result: params.data, logs: zout.logs }; - } + const zout = await zencodeExec(contract, paramsGiven); + const paramsThen: ZenParams = { data: zout.result, keys: paramsGiven.keys }; - async #execute(stmt: Statement, zparams: ZenParams) { - const ctx = new PluginContextImpl(stmt, zparams); - for (const p of this.#executors) { - const result = await p(ctx); - if (result.ok) { - if (stmt.into) zparams.data[stmt.into] = result.value; - return; - } + const cstThens = parsedLines.filter((x) => x.givenThen === 'then'); + for (const cst of cstThens) { + const ast = visit(cst, paramsThen); + const exec = this.#plugins.get(ast.key); + if (!exec) throw new Error('no statements matched'); + const res = await exec(new PluginContextImpl(ast)); + if (res.ok && ast.into) paramsThen.data[ast.into] = res.value; } - throw new Error('no statements matched'); - } - getParser() { - return this.#parser; + return { result: paramsThen.data, logs: zout.logs }; } } const requirifyZenParams = (params?: Partial): Required => { - if (!params) return { data: {}, keys: {}, conf: "", extra: {} }; + if (!params) return { data: {}, keys: {}, conf: '', extra: {} }; if (!params.data) params.data = {}; if (!params.keys) params.keys = {}; - return {extra: {}, conf: "", ...params} as Required; + return { extra: {}, conf: '', ...params } as Required; }; diff --git a/pkg/core/src/visitor.ts b/pkg/core/src/visitor.ts index 20361efa..f2b1ddf1 100644 --- a/pkg/core/src/visitor.ts +++ b/pkg/core/src/visitor.ts @@ -1,92 +1,71 @@ -import { Parser, type StatementCst } from '@slangroom/core'; +import type { Cst, PluginMapKey } from '@slangroom/core'; +import type { Jsonable, ZenParams } from '@slangroom/shared'; -export type Statement = { - openconnect?: string; - bindings: Map; - phrase: string; +export type Ast = { + key: PluginMapKey; + params: Map; into?: string; -}; - -export class ErrorKeyExists extends Error { - constructor(key: string) { - super(`key already exists: ${key}`); - this.name = 'ErrorKeyExists'; - } -} +} & ( + | { + open?: [string, ...string[]]; + connect?: never; + } + | { + open?: never; + connect?: [string, ...string[]]; + } +); -export const visit = (parser: Parser, cst: StatementCst): Statement => { - const BaseCstVisitor = parser.getBaseCstVisitorConstructor(); - - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions - interface Visitor { - visit(cst: StatementCst): ReturnType; - // visit(cst: PhraseCst): ReturnType; - } +export const visit = (cst: Cst, params: ZenParams): Ast => { + if (cst.errors.length) throw new Error('cst must not have any general errors'); + if (!cst.givenThen) throw new Error('cst must have a given or then'); + if (cst.matches.some((x) => x.err.length > 0)) + throw new Error('cst must not have any match errors'); - class Visitor extends BaseCstVisitor { - constructor() { - super(); - // we don't want validation since we can't provide one for phrases - // this.validateVisitor(); - } + const m = cst.matches[0]; + if (!m) throw new Error('cst must have a valid match'); - statement(ctx: StatementCst['children']): Statement { - const phrase = Object.entries(ctx).find(([k]) => k.endsWith('Phrase'))?.[1][0].children; - if (!phrase) throw 'no way'; + const ast: Ast = { + key: m.key, + params: new Map(), + }; - delete phrase['And']; + if (m.into) ast.into = m.into; + if (m.open) ast.open = fetchOpen(params, m.open); + if (m.connect) ast.connect = fetchConnect(params, m.connect); - const openconnect = - phrase['open']?.[0].image.slice(1, -1) ?? phrase['connect']?.[0].image.slice(1, -1); - delete phrase['Open']; - delete phrase['open']; - delete phrase['Connect']; - delete phrase['To']; - delete phrase['connect']; + m.bindings?.forEach((ident, name) => { + const val = fetchDatakeys(params, ident); + ast.params.set(name, val); + }); - const into = phrase['into']?.[0].image.slice(1, -1); - delete phrase['Output']; - delete phrase['Into']; - delete phrase['into']; + return ast; +}; - const bindings = new Map(); - const phraseAcc: string[] = []; - Object.entries(phrase).forEach((x) => - (function recurse(root, [k, [v]]) { - if ('children' in v) { - Object.entries(v.children).forEach((x) => recurse(v.children, x)); - } else if (/sendpass\d+/.test(k)) { - if (!/sendpass\d+$/.test(k)) return; - const param = root[`${k}.parameter`]; - const ident = root[`${k}.identifier`]; - if (param && 'image' in param[0] && ident && 'image' in ident[0]) { - const key = param[0].image; - const value = ident[0].image.slice(1, -1); - // would not happen normally - if (bindings.has(key)) throw new ErrorKeyExists(key); - bindings.set(key, value); - } - delete root[k]; - delete root[`${k}.parameter`]; - delete root[`${k}.identifier`]; - delete root[`${k}.and`]; - } else { - phraseAcc.push(v.image.toLowerCase()); - } - })(phrase, x) - ); +const getDatakeys = (params: ZenParams, rhs: string): undefined | Jsonable => + params.data[rhs] ? params.data[rhs] : params.keys[rhs]; - const stmt: Statement = { - phrase: phraseAcc.join(' '), - bindings: bindings, - }; +const fetchDatakeys = (params: ZenParams, rhs: string): Jsonable => { + const val = getDatakeys(params, rhs); + if (!val) throw new Error('cannot be undefined'); + return val; +}; - if (openconnect) stmt.openconnect = openconnect; - if (into) stmt.into = into; - return stmt; - } +const getOpen = (params: ZenParams, rhs: string): string[] => { + const val = getDatakeys(params, rhs); + if (typeof val === 'string') return [val]; + if (Array.isArray(val)) { + if (val.every((x) => typeof x === 'string')) return val as string[]; + else throw new Error(`the array referenced by ${rhs} must solely composed of strings`); } + return []; +}; - const visitor = new Visitor(); - return visitor.visit(cst); +const fetchOpen = (params: ZenParams, rhs: string): [string, ...string[]] => { + const val = getOpen(params, rhs); + if (val.length === 0) throw new Error('a connect is required'); + return val as [string, ...string[]]; }; + +const fetchConnect = (params: ZenParams, rhs: string): [string, ...string[]] => + fetchOpen(params, rhs); diff --git a/pkg/core/test/lexer.ts b/pkg/core/test/lexer.ts new file mode 100644 index 00000000..6aadf778 --- /dev/null +++ b/pkg/core/test/lexer.ts @@ -0,0 +1,68 @@ +import test from 'ava'; +import { lex, Token, LexError } from '@slangroom/core'; + +test('lexer works', (t) => { + Object.entries({ + '': [], + A: [new Token('A', 0, 0)], + ' b': [new Token('b', 1, 1)], + ' C': [new Token('C', 1, 1)], + ' d': [new Token('d', 2, 2)], + ' E': [new Token('E', 2, 2)], + "Given that I have a 'string' named 'password'": [ + new Token('Given', 0, 4), + new Token('that', 6, 9), + new Token('I', 11, 11), + new Token('have', 13, 16), + new Token('a', 18, 18), + new Token("'string'", 20, 27), + new Token('named', 29, 33), + new Token("'password'", 35, 44), + ], + "Given that I have a 'string' named 'header'": [ + new Token('Given', 0, 4), + new Token('that', 6, 9), + new Token('I', 11, 11), + new Token('have', 13, 16), + new Token('a', 18, 18), + new Token("'string'", 20, 27), + new Token('named', 29, 33), + new Token("'header'", 35, 42), + ], + "Given that I have a 'string' named 'message'": [ + new Token('Given', 0, 4), + new Token('that', 6, 9), + new Token('I', 11, 11), + new Token('have', 13, 16), + new Token('a', 18, 18), + new Token("'string'", 20, 27), + new Token('named', 29, 33), + new Token("'message'", 35, 43), + ], + "When I encrypt the secret message 'message' with 'password'": [ + new Token('When', 0, 3), + new Token('I', 5, 5), + new Token('encrypt', 7, 13), + new Token('the', 15, 17), + new Token('secret', 19, 24), + new Token('message', 26, 32), + new Token("'message'", 34, 42), + new Token('with', 44, 47), + new Token("'password'", 49, 58), + ], + "Then print the 'secret message'": [ + new Token('Then', 0, 3), + new Token('print', 5, 9), + new Token('the', 11, 13), + new Token("'secret message'", 15, 30), + ], + }).forEach(([give, want]) => { + const have = lex(give); + t.deepEqual(have, want); + }); + + const err = t.throws(() => lex("When I encrypt the secret message 'message"), { + instanceOf: LexError, + }) as LexError; + t.is(err.message, `unclosed single-quote at 34,41: 'message`); +}); diff --git a/pkg/core/test/parser.ts b/pkg/core/test/parser.ts new file mode 100644 index 00000000..e17129f7 --- /dev/null +++ b/pkg/core/test/parser.ts @@ -0,0 +1,512 @@ +import test from 'ava'; +import { lex, parse, ParseError, Plugin, Token } from '@slangroom/core'; + +test('parser works', (t) => { + const p = new Plugin(); + + p.new('love asche', (ctx) => ctx.pass(null)); + p.new('a b c', (ctx) => ctx.pass(null)); + p.new('domates biber patlican', (ctx) => ctx.pass(null)); + p.new('open', 'write file', (ctx) => ctx.pass(null)); + p.new('connect', ['object'], 'send http request', (ctx) => ctx.pass(null)); + p.new(['things'], 'to do', (ctx) => ctx.pass(null)); + p.new(['foo', 'bar'], 'testing params order', (ctx) => ctx.pass(null)); + + Object.entries>({ + '': { + errors: [ParseError.missing('given', 'then'), ParseError.missing('I')], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ParseError.missing('love'), ParseError.missing('asche')], + }, + ], + }, + + G: { + errors: [ + ParseError.wrong(new Token('G', 0, 0), 'given', 'then'), + ParseError.missing('I'), + ], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ParseError.missing('love'), ParseError.missing('asche')], + }, + ], + }, + t: { + errors: [ + ParseError.wrong(new Token('t', 0, 0), 'given', 'then'), + ParseError.missing('I'), + ], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ParseError.missing('love'), ParseError.missing('asche')], + }, + ], + }, + + 'given Me': { + givenThen: 'given', + errors: [ParseError.wrong(new Token('Me', 6, 7), 'I')], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ParseError.missing('love'), ParseError.missing('asche')], + }, + ], + }, + 'Then myself': { + givenThen: 'then', + errors: [ParseError.wrong(new Token('myself', 5, 10), 'I')], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ParseError.missing('love'), ParseError.missing('asche')], + }, + ], + }, + + 'Given I love notAsche': { + givenThen: 'given', + errors: [], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ParseError.wrong(new Token('notAsche', 13, 20), 'asche')], + }, + ], + }, + 'Then I a b foobar': { + givenThen: 'then', + errors: [], + matches: [ + { + key: { phrase: 'a b c' }, + bindings: new Map(), + err: [ParseError.wrong(new Token('foobar', 11, 16), 'c')], + }, + ], + }, + 'giVen I domates patates biber': { + givenThen: 'given', + errors: [], + matches: [ + { + key: { + phrase: 'domates biber patlican', + }, + bindings: new Map(), + err: [ + ParseError.wrong(new Token('patates', 16, 22), 'biber'), + ParseError.wrong(new Token('biber', 24, 28), 'patlican'), + ], + }, + ], + }, + "Given I open 'xfiles' and write files": { + givenThen: 'given', + errors: [], + matches: [ + { + key: { + openconnect: 'open', + phrase: 'write file', + }, + open: 'xfiles', + bindings: new Map([]), + err: [ParseError.wrong(new Token('files', 32, 36), 'file')], + }, + ], + }, + + 'Then I Like Asche': { + givenThen: 'then', + errors: [], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ParseError.wrong(new Token('Like', 7, 10), 'love')], + }, + ], + }, + 'Given I a biber c': { + givenThen: 'given', + errors: [], + matches: [ + { + key: { phrase: 'a b c' }, + bindings: new Map(), + err: [ParseError.wrong(new Token('biber', 10, 14), 'b')], + }, + ], + }, + 'then I domates b patates': { + givenThen: 'then', + errors: [], + matches: [ + { + key: { phrase: 'a b c' }, + bindings: new Map(), + err: [ + ParseError.wrong(new Token('domates', 7, 13), 'a'), + ParseError.wrong(new Token('patates', 17, 23), 'c'), + ], + }, + { + key: { phrase: 'domates biber patlican' }, + bindings: new Map(), + err: [ + ParseError.wrong(new Token('b', 15, 15), 'biber'), + ParseError.wrong(new Token('patates', 17, 23), 'patlican'), + ], + }, + ], + }, + + 'given i love Asche and so': { + givenThen: 'given', + errors: [ParseError.wrong(new Token('i', 6, 6), 'I')], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ + ParseError.extra(new Token('and', 19, 21)), + ParseError.extra(new Token('so', 23, 24)), + ], + }, + ], + }, + 'Then i a b c and d': { + givenThen: 'then', + errors: [ParseError.wrong(new Token('i', 5, 5), 'I')], + matches: [ + { + key: { phrase: 'a b c' }, + bindings: new Map(), + err: [ + ParseError.extra(new Token('and', 13, 15)), + ParseError.extra(new Token('d', 17, 17)), + ], + }, + ], + }, + 'Then I domates biber patlican patates': { + givenThen: 'then', + errors: [], + matches: [ + { + key: { phrase: 'domates biber patlican' }, + bindings: new Map(), + err: [ParseError.extra(new Token('patates', 30, 36))], + }, + ], + }, + "Given I open 'xfiles' and write file and stuff": { + givenThen: 'given', + errors: [], + matches: [ + { + key: { + openconnect: 'open', + phrase: 'write file', + }, + open: 'xfiles', + bindings: new Map([]), + err: [ + ParseError.extra(new Token('and', 37, 39)), + ParseError.extra(new Token('stuff', 41, 45)), + ], + }, + ], + }, + + "given i love Asche and so and output into 'foo'": { + givenThen: 'given', + errors: [ParseError.wrong(new Token('i', 6, 6), 'I')], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ + ParseError.extra(new Token('and', 19, 21)), + ParseError.extra(new Token('so', 23, 24)), + ], + into: 'foo', + }, + ], + }, + "Then i a b c and d and output into 'bar'": { + givenThen: 'then', + errors: [ParseError.wrong(new Token('i', 5, 5), 'I')], + matches: [ + { + key: { phrase: 'a b c' }, + bindings: new Map(), + err: [ + ParseError.extra(new Token('and', 13, 15)), + ParseError.extra(new Token('d', 17, 17)), + ], + into: 'bar', + }, + ], + }, + "Then I domates biber patlican patates and output into 'baz'": { + givenThen: 'then', + errors: [], + matches: [ + { + key: { phrase: 'domates biber patlican' }, + bindings: new Map(), + err: [ParseError.extra(new Token('patates', 30, 36))], + into: 'baz', + }, + ], + }, + "Given I open 'xfiles' and write file and stuff and output into 'quz'": { + givenThen: 'given', + errors: [], + matches: [ + { + key: { + openconnect: 'open', + phrase: 'write file', + }, + open: 'xfiles', + bindings: new Map([]), + err: [ + ParseError.extra(new Token('and', 37, 39)), + ParseError.extra(new Token('stuff', 41, 45)), + ], + into: 'quz', + }, + ], + }, + + "given i love Asche and so output into 'foo'": { + givenThen: 'given', + errors: [ParseError.wrong(new Token('i', 6, 6), 'I')], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ + ParseError.extra(new Token('and', 19, 21)), + ParseError.wrong(new Token('so', 23, 24), 'and'), + ], + }, + ], + }, + "Then i a b c and d into 'bar'": { + givenThen: 'then', + errors: [ParseError.wrong(new Token('i', 5, 5), 'I')], + matches: [ + { + key: { phrase: 'a b c' }, + bindings: new Map(), + err: [ParseError.wrong(new Token('d', 17, 17), 'output')], + }, + ], + }, + "Then I domates biber patlican patates 'baz'": { + givenThen: 'then', + errors: [], + matches: [ + { + key: { phrase: 'domates biber patlican' }, + bindings: new Map(), + err: [ + ParseError.extra(new Token('patates', 30, 36)), + ParseError.extra(new Token("'baz'", 38, 42)), + ], + }, + ], + }, + "Given I open 'xfiles' and write file and stuff and output 'quz'": { + givenThen: 'given', + errors: [], + matches: [ + { + key: { + openconnect: 'open', + phrase: 'write file', + }, + open: 'xfiles', + bindings: new Map([]), + err: [ + ParseError.extra(new Token('and', 37, 39)), + ParseError.wrong(new Token('stuff', 41, 45), 'and'), + ParseError.wrong(new Token('and', 47, 49), 'output'), + ParseError.wrong(new Token('output', 51, 56), 'into'), + ], + }, + ], + }, + + "given i love Asche save output into 'foo'": { + givenThen: 'given', + errors: [ParseError.wrong(new Token('i', 6, 6), 'I')], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [ParseError.wrong(new Token('save', 19, 22), 'and')], + }, + ], + }, + + 'Given I love Asche': { + givenThen: 'given', + errors: [], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [], + }, + ], + }, + 'Then I a b c': { + givenThen: 'then', + errors: [], + matches: [{ key: { phrase: 'a b c' }, bindings: new Map(), err: [] }], + }, + 'Given I domates Biber patlIcan': { + givenThen: 'given', + errors: [], + matches: [{ key: { phrase: 'domates biber patlican' }, bindings: new Map(), err: [] }], + }, + "Given I open 'xfiles' and write file": { + givenThen: 'given', + errors: [], + matches: [ + { + key: { + openconnect: 'open', + phrase: 'write file', + }, + open: 'xfiles', + bindings: new Map([]), + err: [], + }, + ], + }, + "Then I connect to 'url' and send object 'myObj' and send http request": { + givenThen: 'then', + errors: [], + matches: [ + { + key: { + openconnect: 'connect', + phrase: 'send http request', + params: ['object'], + }, + bindings: new Map([['object', 'myObj']]), + connect: 'url', + err: [], + }, + ], + }, + + "Given I love Asche and output into 'foo'": { + givenThen: 'given', + errors: [], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [], + into: 'foo', + }, + ], + }, + "Then I a b c and OUTPUT iNto 'bar'": { + givenThen: 'then', + errors: [], + matches: [ + { + key: { phrase: 'a b c' }, + bindings: new Map(), + err: [], + into: 'bar', + }, + ], + }, + "Given I domates Biber patlIcan AND OutPut inTo 'baz'": { + givenThen: 'given', + errors: [], + matches: [ + { + key: { phrase: 'domates biber patlican' }, + bindings: new Map(), + err: [], + into: 'baz', + }, + ], + }, + "Given I open 'xfiles' and write file and Output Into 'Quz'": { + givenThen: 'given', + errors: [], + matches: [ + { + key: { + openconnect: 'open', + phrase: 'write file', + }, + open: 'xfiles', + bindings: new Map([]), + err: [], + into: 'Quz', + }, + ], + }, + "Then I connect to 'url' and send object 'myObj' and send http request and output into 'result'": + { + givenThen: 'then', + errors: [], + matches: [ + { + key: { + openconnect: 'connect', + phrase: 'send http request', + params: ['object'], + }, + bindings: new Map([['object', 'myObj']]), + connect: 'url', + err: [], + into: 'result', + }, + ], + }, + "Given I send bar 'bar_ident' and send foo 'foo_ident' and testing params order": { + givenThen: 'given', + errors: [], + matches: [ + { + key: { + params: ['foo', 'bar'], + phrase: 'testing params order', + }, + bindings: new Map([ + ['bar', 'bar_ident'], + ['foo', 'foo_ident'], + ]), + err: [], + }, + ], + }, + }).forEach(([give, want], index) => { + const have = parse(p.store, lex(give)); + t.deepEqual(have, want, `${index}: ${give}`); + }); +}); diff --git a/pkg/core/test/plugin.ts b/pkg/core/test/plugin.ts new file mode 100644 index 00000000..b741d947 --- /dev/null +++ b/pkg/core/test/plugin.ts @@ -0,0 +1,159 @@ +import test from 'ava'; +import { DuplicatePluginError, Plugin, type PluginExecutor } from '@slangroom/core'; + +test('Plugin.new() phrase alpha-numerical checks work', (t) => { + [ + '', + ' ', + ' ', + '\n', + 'NoN Lower cAse', + 'double spacing', + 'tabs in between', + 'new\nlines', + ' leading spaces', + 'trailing spaces ', + ' leading and trailing spaces ', + ' leading tabs', + 'trailing tabs ', + ' leading and trailing tabs ', + "some 'identifiers' in between", + ].forEach((x) => { + const err = t.throws(() => new Plugin().new(x, (ctx) => ctx.pass(null)), { + instanceOf: Error, + }) as Error; + t.is( + err.message, + 'phrase must composed of alpha-numerical, underscore, and dash values split by a single space', + ); + }); + + ['foo', 'foo bar', 'foo_bar', 'foo-bar', 'foo bar baz', 'foo_bar_baz', 'foo-bar-baz'].forEach( + (x) => { + t.notThrows(() => new Plugin().new(x, (ctx) => ctx.pass(null))); + }, + ); +}); + +test('Plugin.new() params alpha-numerical checks work', (t) => { + [ + '', + ' ', + ' ', + '\n', + 'NoN Lower cAse', + 'double spacing', + 'tabs in between', + 'new\nlines', + ' leading spaces', + 'trailing spaces ', + ' leading and trailing spaces ', + ' leading tabs', + 'trailing tabs ', + ' leading and trailing tabs ', + "some 'identifiers' in between", + 'punctuation!', + ].forEach((x) => { + const err = t.throws( + () => new Plugin().new([x], 'doesnt matter', (ctx) => ctx.pass(null)), + { instanceOf: Error }, + ) as Error; + t.is( + err.message, + 'each params must composed of alpha-numerical values, optionally split by dashes or underscores', + ); + }); + + ['foo', 'foo_bar', 'foo-bar', 'foo_bar_baz', 'foo-bar-baz'].forEach((x) => { + t.notThrows(() => new Plugin().new(x, (ctx) => ctx.pass(null))); + }); +}); + +test('Plugin.new() duplicates detected', (t) => { + const f: PluginExecutor = (ctx) => ctx.pass(null); + t.is( + ( + t.throws( + () => { + const p = new Plugin(); + p.new('a', f); + p.new('a', f); + }, + { instanceOf: DuplicatePluginError }, + ) as DuplicatePluginError + ).message, + `duplicated plugin with key: openconnect= params= phrase="a"`, + ); + + t.is( + ( + t.throws( + () => { + const p = new Plugin(); + p.new('a', f); + p.new('open', 'a', f); + p.new('open', 'a', f); + }, + { instanceOf: DuplicatePluginError }, + ) as DuplicatePluginError + ).message, + `duplicated plugin with key: openconnect=open params= phrase="a"`, + ); + + t.is( + ( + t.throws( + () => { + const p = new Plugin(); + p.new('a', f); + p.new('open', 'a', f); + p.new('connect', 'a', f); + p.new('open', ['foo'], 'a', f); + p.new('connect', ['foo'], 'a', f); + p.new('connect', ['foo'], 'a', f); + }, + { instanceOf: DuplicatePluginError }, + ) as DuplicatePluginError + ).message, + `duplicated plugin with key: openconnect=connect params=foo phrase="a"`, + ); +}); + +test('Plugin.new() clauses work', (t) => { + const f: PluginExecutor = (ctx) => ctx.pass(null); + { + const p = new Plugin(); + p.new('love asche', f); + t.true(p.store.has({ phrase: 'love asche' })); + } + + { + const p = new Plugin(); + p.new('open', 'love asche', f); + t.true(p.store.has({ openconnect: 'open', phrase: 'love asche' })); + } + + { + const p = new Plugin(); + p.new('connect', 'love asche', f); + t.true(p.store.has({ openconnect: 'connect', phrase: 'love asche' })); + } + + { + const p = new Plugin(); + p.new(['howmuch'], 'love asche', f); + t.true(p.store.has({ params: ['howmuch'], phrase: 'love asche' })); + } + + { + const p = new Plugin(); + p.new('open', ['howmuch'], 'love asche', f); + t.true(p.store.has({ openconnect: 'open', params: ['howmuch'], phrase: 'love asche' })); + } + + { + const p = new Plugin(); + p.new('connect', ['howmuch'], 'love asche', f); + t.true(p.store.has({ openconnect: 'connect', params: ['howmuch'], phrase: 'love asche' })); + } +}); diff --git a/pkg/core/test/slangroom.ts b/pkg/core/test/slangroom.ts index 3a4aea2d..57676cf3 100644 --- a/pkg/core/test/slangroom.ts +++ b/pkg/core/test/slangroom.ts @@ -1,11 +1,20 @@ import test from 'ava'; -import { - Slangroom, - Parser, - type PluginContext, - type PluginResult, - type Plugin, -} from '@slangroom/core'; +import { DuplicatePluginError, Plugin, Slangroom } from '@slangroom/core'; + +test("doesn't allow duplicated plugins", (t) => { + const p0 = new Plugin(); + p0.new('love asche', (ctx) => ctx.pass(null)); + const p1 = new Plugin(); + p1.new('love asche', (ctx) => ctx.pass(null)); + t.is( + ( + t.throws(() => new Slangroom(p0, p1), { + instanceOf: DuplicatePluginError, + }) as DuplicatePluginError + ).message, + `duplicated plugin with key: openconnect= params= phrase="love asche"`, + ); +}); test('runs all unknown statements', async (t) => { let usedP0 = false; @@ -14,94 +23,39 @@ test('runs all unknown statements', async (t) => { let usedP3 = false; let usedP4 = false; - const p0: Plugin = { - parser: function (this: Parser) { - this.RULE('p0Phrase', () => { - this.token('a'); - this.into(); - }); - }, - executor: (ctx: PluginContext): PluginResult => { - if (ctx.phrase === 'a') { - usedP0 = true; - return ctx.pass('foo'); - } - return ctx.fail('Unkown phrase'); - }, - }; + const p0 = new Plugin(); + p0.new('a', (ctx) => { + usedP0 = true; + return ctx.pass('foo'); + }); - const p1: Plugin = { - parser: function (this: Parser) { - this.RULE('p1Phrase', () => { - this.sendpass('a'); - this.token('b'); - this.into(); - }); - }, - executor: (ctx: PluginContext): PluginResult => { - if (ctx.phrase === 'b') { - usedP1 = true; - t.is(ctx.fetch('a'), 'foo'); - return ctx.pass('bar'); - } - return ctx.fail('Unknown phrase'); - }, - }; + const p1 = new Plugin(); + p1.new(['a'], 'b', (ctx) => { + usedP1 = true; + t.is(ctx.fetch('a'), 'foo'); + return ctx.pass('bar'); + }); - const p2: Plugin = { - parser: function (this: Parser) { - this.RULE('p2Phrase', () => { - this.sendpass('a'); - this.token('c'); - this.token('d'); - this.into(); - }); - }, - executor: (ctx: PluginContext): PluginResult => { - if (ctx.phrase === 'c d') { - usedP2 = true; - t.is(ctx.fetch('a'), 'bar'); - return ctx.pass('foobar'); - } - return ctx.fail('Unkown phrase'); - }, - }; + const p2 = new Plugin(); + p2.new(['a'], 'c d', (ctx) => { + usedP2 = true; + t.is(ctx.fetch('a'), 'bar'); + return ctx.pass('foobar'); + }); - const p3: Plugin = { - parser: function (this: Parser) { - this.RULE('p3Phrase', () => { - this.open(); - this.token('e'); - this.token('f'); - }); - }, - executor: (ctx) => { - if (ctx.phrase === 'e f') { - usedP3 = true; - t.is(ctx.fetchOpen()[0], 'bar'); - return ctx.pass(null); - } - return ctx.fail('Unkown phrase'); - }, - }; + const p3 = new Plugin(); + p3.new('open', 'e f', (ctx) => { + usedP3 = true; + t.is(ctx.fetchOpen()[0], 'bar'); + return ctx.pass(null); + }); - const p4: Plugin = { - parser: function (this: Parser) { - this.RULE('p4Phrase', () => { - this.connect(); - this.token('f'); - this.token('g'); - }); - }, - executor: (ctx) => { - if (ctx.phrase === 'f g') { - usedP4 = true; - t.is(ctx.fetchConnect()[0], 'foo'); - return ctx.pass(null); - } - return ctx.fail('Unkown phrase'); - }, - }; + const p4 = new Plugin(); + p4.new('connect', 'f g', (ctx) => { + usedP4 = true; + t.is(ctx.fetchConnect()[0], 'foo'); + return ctx.pass(null); + }); const script = ` Rule unknown ignore diff --git a/pkg/core/test/visitor.ts b/pkg/core/test/visitor.ts index bf8065cc..41a5b1f0 100644 --- a/pkg/core/test/visitor.ts +++ b/pkg/core/test/visitor.ts @@ -1,137 +1,115 @@ import test from 'ava'; -import { Parser, Lexicon, parse, lex, visit } from '@slangroom/core'; +import { visit, type Cst, type Ast } from '@slangroom/core'; +import { ZenParams } from '@slangroom/shared'; +import { inspect } from 'util'; -const astify = (line: string, parser: (this: Parser) => void) => { - const lxcon = new Lexicon(); - const p = new Parser(lxcon, [parser]); - const lexed = lex(lxcon, line); - if (lexed.errors.length) throw lexed.errors; - const parsed = parse(p, lexed.tokens); - if (parsed.errors.length) throw parsed.errors; - return visit(p, parsed.cst as Parameters[1]); -}; - -test('ast is okay: only phrase is given', (t) => { - const ast = astify('read the ethereum balance', function (this: Parser) { - this.RULE('testPhrase', () => { - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token('balance'); - }); - }); - t.deepEqual(ast, { - phrase: 'read the ethereum balance', - bindings: new Map(), - }); -}); - -test('ast is okay: phrase and connect are given', (t) => { - const ast = astify( - "connect to 'foo' and read the ethereum balance", - function (this: Parser) { - this.RULE('testPhrase', () => { - this.connect(); - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token('balance'); - }); - } - ); - t.deepEqual(ast, { - openconnect: 'foo', - phrase: 'read the ethereum balance', - bindings: new Map(), - }); -}); - -test('ast is okay: phrase and open are given', (t) => { - const ast = astify("open 'foo' and read the ethereum balance", function (this: Parser) { - this.RULE('testPhrase', () => { - this.open(); - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token('balance'); - }); - }); - t.deepEqual(ast, { - openconnect: 'foo', - phrase: 'read the ethereum balance', - bindings: new Map(), - }); -}); - -test('ast is okay: phrase and bindings are given', (t) => { - const ast = astify( - "send address 'addr' and pass contract 'contract' and read the ethereum balance", - function (this: Parser) { - this.RULE('testPhrase', () => { - this.sendpass('address'); - this.sendpass1('contract'); - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token('balance'); - }); - } - ); - t.deepEqual(ast, { - phrase: 'read the ethereum balance', - bindings: new Map([ - ['address', 'addr'], - ['contract', 'contract'], - ]), - }); -}); - -test('ast is okay: phrase and connect and bindings are all given', (t) => { - const ast = astify( - "connect to 'foo' and pass address 'addr' and send contract 'contract' and read the ethereum balance", - function (this: Parser) { - this.RULE('testPhrase', () => { - this.connect(); - this.sendpass('address'); - this.sendpass1('contract'); - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token('balance'); - }); - } - ); - t.deepEqual(ast, { - openconnect: 'foo', - phrase: 'read the ethereum balance', - bindings: new Map([ - ['address', 'addr'], - ['contract', 'contract'], - ]), - }); -}); - -test('ast is okay: phrase and open and bindings are all given', (t) => { - const ast = astify( - "open 'foo' and pass address 'addr' and send contract 'contract' and read the ethereum balance", - function (this: Parser) { - this.RULE('testPhrase', () => { - this.open(); - this.sendpass('address'); - this.sendpass1('contract'); - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token('balance'); - }); - } - ); - t.deepEqual(ast, { - openconnect: 'foo', - phrase: 'read the ethereum balance', - bindings: new Map([ - ['address', 'addr'], - ['contract', 'contract'], - ]), +test('visitor works', (t) => { + ( + [ + [ + { data: {}, keys: {} }, + { + givenThen: 'given', + errors: [], + matches: [ + { + key: { phrase: 'love asche' }, + bindings: new Map(), + err: [], + }, + ], + }, + { + key: { phrase: 'love asche' }, + params: new Map(), + }, + ], + [ + { data: { value: 'so much' }, keys: {} }, + { + givenThen: 'then', + errors: [], + matches: [ + { + key: { + params: new Set(['howmuch']), + phrase: 'love asche', + }, + bindings: new Map([['howmuch', 'value']]), + err: [], + }, + ], + }, + { + key: { + params: new Set(['howmuch']), + phrase: 'love asche', + }, + params: new Map([['howmuch', 'so much']]), + }, + ], + [ + { + data: { myObj: 'some data', myUrl: 'https://example.com/pathway/to/hell' }, + keys: {}, + }, + { + givenThen: 'given', + errors: [], + matches: [ + { + key: { + openconnect: 'connect', + params: new Set(['object']), + phrase: 'http post request', + }, + bindings: new Map([['object', 'myObj']]), + connect: 'myUrl', + err: [], + }, + ], + }, + { + key: { + openconnect: 'connect', + params: new Set(['object']), + phrase: 'http post request', + }, + params: new Map([['object', 'some data']]), + connect: ['https://example.com/pathway/to/hell'], + }, + ], + [ + { data: { myFile: 'pathway/to/hell' }, keys: {} }, + { + givenThen: 'given', + errors: [], + matches: [ + { + key: { + openconnect: 'open', + params: new Set(), + phrase: 'read file', + }, + bindings: new Map(), + open: 'myFile', + err: [], + }, + ], + }, + { + key: { + openconnect: 'open', + params: new Set(), + phrase: 'read file', + }, + params: new Map(), + open: ['pathway/to/hell'], + }, + ], + ] as [ZenParams, Cst, Ast][] + ).forEach(([params, cst, want]) => { + const have = visit(cst, params); + t.deepEqual(have, want, inspect(cst, false, null)); }); }); diff --git a/pkg/ethereum/src/index.ts b/pkg/ethereum/src/index.ts index 7de6b4da..6b3f2170 100644 --- a/pkg/ethereum/src/index.ts +++ b/pkg/ethereum/src/index.ts @@ -1,3 +1,2 @@ -export * from '@slangroom/ethereum/parser'; export * from '@slangroom/ethereum/plugin'; export * from '@slangroom/ethereum/erc20abi'; diff --git a/pkg/ethereum/src/parser.ts b/pkg/ethereum/src/parser.ts deleted file mode 100644 index 1f7cba54..00000000 --- a/pkg/ethereum/src/parser.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Parser } from '@slangroom/core'; - -export function parser(this: Parser) { - this.RULE('ethereumPhrase', () => { - this.connect(); - this.OR1([ - { ALT: () => this.SUBRULE(ethNonce) }, - { ALT: () => this.SUBRULE(ethBytes) }, - { ALT: () => this.SUBRULE(ethBalance) }, - { ALT: () => this.SUBRULE(ethBroadcast) }, - { ALT: () => this.SUBRULE(ethGasPrice) }, - { ALT: () => this.SUBRULE(erc20) }, - { ALT: () => this.SUBRULE(erc721) }, - ]); - this.into(); - }); - - const ethNonce = this.RULE('ethereumEthereumNonce', () => { - this.sendpass('address'); - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token('nonce'); - }); - - const ethBytes = this.RULE('ethereumEthereumBytes', () => { - this.sendpass('transaction_id'); - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token('bytes'); - }); - - const ethBalance = this.RULE('ethereumEthereumBalance', () => { - this.sendpass('address'); - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token('balance'); - }); - - const ethBroadcast = this.RULE('ethereumEthereumBroadcast', () => { - this.sendpass('transaction'); - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token1('transaction'); - this.token('id'); - this.token('after'); - this.token('broadcast'); - }); - - const ethGasPrice = this.RULE('ethereumEthereumGasPrice', () => { - this.token('read'); - this.token('the'); - this.token('ethereum'); - this.token('suggested'); - this.token('gas'); - this.token('price'); - }); - - const erc20 = this.RULE('ethereumErc20', () => { - this.sendpass('sc'); - this.token('read'); - this.token('the'); - this.token('erc20'); - this.OR2([ - { ALT: () => this.token('decimals') }, - { ALT: () => this.token('name') }, - { ALT: () => this.token('symbol') }, - { ALT: () => this.token('balance') }, - { - ALT: () => { - // TODO: how to handle to vs total? - this.token('ttal'); - this.token('supply'); - }, - }, - ]); - }); - - const erc721 = this.RULE('ethereumErc721', () => { - this.token('read'); - this.token('the'); - this.token('erc721'); - this.OR([ - { - ALT: () => { - this.token('id'); - this.token('in'); - this.token('transaction'); - }, - }, - { ALT: () => this.token('owner') }, - { ALT: () => this.token('asset') }, - ]); - }); -} diff --git a/pkg/ethereum/src/plugin.ts b/pkg/ethereum/src/plugin.ts index 3476b79d..234aa6ed 100644 --- a/pkg/ethereum/src/plugin.ts +++ b/pkg/ethereum/src/plugin.ts @@ -1,59 +1,34 @@ -import type { Plugin, PluginContext, PluginResult } from '@slangroom/core'; -import type { JsonableArray } from '@slangroom/shared'; -import { parser, erc20abi } from '@slangroom/ethereum'; +import { Plugin, type PluginExecutor } from '@slangroom/core'; +import { erc20abi } from '@slangroom/ethereum'; import { Web3 } from 'web3'; import { isAddress } from 'web3-validator'; -/** +const p = new Plugin(); + +/* * @internal */ -export const execute = async ( - ctx: PluginContext, - kind: - | 'ethNonce' - | 'ethGasPrice' - | 'ethBalance' - | 'ethBytes' - | 'ethBroadcast' - | 'erc721id' - | 'erc721owner' - | 'erc721asset' - | 'erc20balance' - | 'erc20symbol' - | 'erc20decimals' - | 'erc20name' - | 'erc20totalSupply' -): Promise => { +export const ethNonce = p.new('connect', ['address'], 'read the ethereum nonce', async (ctx) => { const web3 = new Web3(ctx.fetchConnect()[0]); + const addr = ctx.fetch('address'); + if (typeof addr !== 'string') return ctx.fail('address must be string'); + const nonce = await web3.eth.getTransactionCount(addr); + return ctx.pass(nonce.toString()); +}); - if (kind === 'ethNonce') { - const address = ctx.fetch('address') as string; - const nonce = await web3.eth.getTransactionCount(address); - return ctx.pass(nonce.toString()); - } - - if (kind === 'ethGasPrice') { - const gasPrice = await web3.eth.getGasPrice(); - return ctx.pass(gasPrice.toString()); - } - - if (kind === 'ethBalance') { - // TODO: different statement for string and array - const address = ctx.get('address'); - if (address) return ctx.pass((await web3.eth.getBalance(address as string)).toString()); - const addresses = ctx.fetch('addresses'); - if (Array.isArray(addresses)) { - const balances = await Promise.all( - addresses.map((addr) => web3.eth.getBalance(addr as string)) - ); - return ctx.pass(balances.map((b) => b.toString()) as JsonableArray); - } - } - - if (kind === 'ethBytes') { - const tag = ctx.fetch('transaction_id') as string; +/** + * @internal + */ +export const ethBytes = p.new( + 'connect', + ['transaction_id'], + 'read the ethereum bytes', + async (ctx) => { + const web3 = new Web3(ctx.fetchConnect()[0]); + const tag = ctx.fetch('transaction_id'); + if (typeof tag !== 'string') return ctx.fail('tag must be string'); const receipt = await web3.eth.getTransactionReceipt( - tag.startsWith('0x') ? tag : '0x' + tag + tag.startsWith('0x') ? tag : '0x' + tag, ); if (!receipt) return ctx.fail("Transaction id doesn't exist"); if (!receipt.status) return ctx.fail('Failed transaction'); @@ -63,95 +38,168 @@ export const execute = async ( } catch (e) { return ctx.fail('Empty transaction'); } - } + }, +); + +/** + * @internal + */ +export const ethBalanceAddr = p.new( + 'connect', + ['address'], + 'read the ethereum balance', + async (ctx) => { + const web3 = new Web3(ctx.fetchConnect()[0]); + const addr = ctx.get('address'); + if (typeof addr !== 'string') return ctx.fail('address must be string'); + return ctx.pass((await web3.eth.getBalance(addr)).toString()); + }, +); + +/** + * @internal + */ +export const ethBalanceAddrs = p.new( + 'connect', + ['addresses'], + 'read the ethereum balance', + async (ctx) => { + const web3 = new Web3(ctx.fetchConnect()[0]); + const addrs = ctx.fetch('addresses') as string[]; // next line will ensure type + if (!Array.isArray(addrs) || !addrs.every((x) => typeof x === 'string')) + return ctx.fail('addresses must be string array'); + const balances = await Promise.all(addrs.map((addr) => web3.eth.getBalance(addr))); + return ctx.pass(balances.map((b) => b.toString())); + }, +); + +/** + * @internal + */ +export const ethGasPrice = p.new( + 'connect', + 'read the ethereum suggested gas price', + async (ctx) => { + const web3 = new Web3(ctx.fetchConnect()[0]); + const gasPrice = await web3.eth.getGasPrice(); + return ctx.pass(gasPrice.toString()); + }, +); - if (kind === 'ethBroadcast') { - const rawtx = ctx.fetch('transaction') as string; +/** + * @internal + */ +export const ethBrodcast = p.new( + 'connect', + ['transaction'], + 'read the ethereum transaction id after broadcast', + async (ctx) => { + const web3 = new Web3(ctx.fetchConnect()[0]); + const rawtx = ctx.fetch('transaction'); + if (typeof rawtx !== 'string') return ctx.fail('transaction must be string'); const receipt = await web3.eth.sendSignedTransaction( - rawtx.startsWith('0x') ? rawtx : '0x' + rawtx + rawtx.startsWith('0x') ? rawtx : '0x' + rawtx, ); - if (receipt.status) { - return ctx.pass(receipt.transactionHash.toString().substring(2)); // Remove 0x - } else { - throw new Error('Transaction failed'); - } - } + if (!receipt.status) ctx.fail('transaction failed'); + return ctx.pass(receipt.transactionHash.toString().substring(2)); // remove 0x + }, +); + +const erc20helper = ( + name: 'symbol()' | 'decimals()' | 'name()' | 'totalSupply()' | 'balanceOf()', +): PluginExecutor => { + return async (ctx) => { + const web3 = new Web3(ctx.fetchConnect()[0]); + const sc = ctx.fetch('sc'); + if (typeof sc !== 'string') return ctx.fail('sc must be string'); + if (!isAddress(sc)) return ctx.fail(`sc must be a valid ethereum address: ${sc}`); + const erc20 = new web3.eth.Contract(erc20abi, sc); + const res = (await erc20.methods[name]?.().call())?.toString(); + if (!res) return ctx.fail(`${name} call failed`); + return ctx.pass(res); + }; +}; + +/** + * @internal + */ +export const erc20decimals = p.new( + 'connect', + ['sc'], + 'read the erc20 decimals', + erc20helper('decimals()'), +); + +/** + * @internal + */ +export const erc20name = p.new('connect', ['sc'], 'read the erc20 name', erc20helper('name()')); - /*if (kind === 'erc20balance') { - const sc = ctx.fetch("sc") as string; - //const address = ctx.fetch("address") as string; - if (!isAddress(sc)) - throw new Error(`Not an ethereum address ${sc}`); - const erc20 = new web3.eth.Contract(erc20Abi, sc); - console.log(erc20.methods['balanceOf']?.('ciccio')) +/** + * @internal + */ +export const erc20symbol = p.new( + 'connect', + ['sc'], + 'read the erc20 symbol', + erc20helper('symbol()'), +); - return ctx.pass(await erc20.methods?.["balanceOf"]?.()?.call() || ""); - } +/** + * @internal + */ +export const erc20balance = p.new( + 'connect', + ['sc', 'address'], + 'read the erc20 balance', + erc20helper('balanceOf()'), +); + +/** + * @internal + */ +export const erc20totalSupply = p.new( + 'connect', + ['sc'], + 'read the erc20 total supply', + erc20helper('totalSupply()'), +); - if (kind === 'erc721id') { +/** + * @internal + */ +export const erc721id = p.new( + 'connect', + ['transaction_id'], + 'read the erc721 id in transaction', + async (ctx) => { + const ERC721_TRANSFER_EVENT = + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; + const web3 = new Web3(ctx.fetchConnect()[0]); const tag = ctx.fetch('transaction_id') as string; const receipt = await web3.eth.getTransactionReceipt( - tag.startsWith('0x') ? tag : '0x' + tag + tag.startsWith('0x') ? tag : '0x' + tag, ); const log = receipt.logs.find( - (v) => v && v.topics && v.topics.length > 0 && v.topics[0] === ERC721_TRANSFER_EVENT + (v) => v && v.topics && v.topics.length > 0 && v.topics[0] === ERC721_TRANSFER_EVENT, ); - if (!log || !log.topics) - throw new Error('Token Id not found'); + if (!log || !log.topics) return ctx.fail('Token Id not found'); return ctx.pass(parseInt(log.topics[3]?.toString() || '', 16)); - } */ - - const erc20_0 = new Map([ - ['erc20symbol', 'symbol()'], - ['erc20decimals', 'decimals()'], - ['erc20name', 'name()'], - ['erc20totalSupply', 'totalSupply()'], - ]); - - if (erc20_0.has(kind)) { - const sc = ctx.fetch('sc') as string; - if (!isAddress(sc)) throw new Error(`Not an ethereum address ${sc}`); - const erc20 = new web3.eth.Contract(erc20abi, sc); - return ctx.pass( - (await erc20.methods[erc20_0.get(kind) || '']?.().call())?.toString() || '' - ); - } + }, +); - return ctx.fail('Should not be here'); -}; +/** + * @internal + */ +export const erc712owner = p.new('connect', 'read the erc721 owner', async (ctx) => { + return ctx.fail('not implemented'); +}); -export const ethereum: Plugin = { - parser: parser, - executor: async (ctx) => { - switch (ctx.phrase) { - case 'read the ethereum nonce': - return await execute(ctx, 'ethNonce'); - case 'read the ethereum bytes': - return await execute(ctx, 'ethBytes'); - case 'read the ethereum balance': - return await execute(ctx, 'ethBalance'); - case 'read the ethereum suggested gas price': - return await execute(ctx, 'ethGasPrice'); - case 'read the ethereum transaction id after broadcast': - return await execute(ctx, 'ethBroadcast'); - case 'read the erc20 decimals': - return await execute(ctx, 'erc20decimals'); - case 'read the erc20 name': - return await execute(ctx, 'erc20name'); - case 'read the erc20 symbol': - return await execute(ctx, 'erc20symbol'); - case 'read the erc20 balance': - return await execute(ctx, 'erc20balance'); - case 'read the erc20 total supply': - return await execute(ctx, 'erc20totalSupply'); - case 'read the erc721 id in transaction': - return await execute(ctx, 'erc721id'); - case 'read the erc721 owner': - return await execute(ctx, 'erc721owner'); - case 'read the erc721 asset': - return await execute(ctx, 'erc721asset'); - default: - return ctx.fail('no match'); - } - }, -}; +/** + * @internal + */ +export const erc721asset = p.new('connect', 'read the erc721 asset', async (ctx) => { + return ctx.fail('not implemented'); +}); + +export const ethereum = p; diff --git a/pkg/ethereum/test/e2e.ts b/pkg/ethereum/test/e2e.ts index 6b6fff38..282b34dc 100644 --- a/pkg/ethereum/test/e2e.ts +++ b/pkg/ethereum/test/e2e.ts @@ -3,7 +3,7 @@ import { Slangroom } from '@slangroom/core'; import { ethereum } from '@slangroom/ethereum'; test('Retrieve a zenroom object', async (t) => { - const script = ` + const contract = ` Rule unknown ignore Scenario ethereum Given I connect to 'fabchain' and send transaction_id 'my_tag' and read the ethereum bytes and output into 'poem_bytes' @@ -13,8 +13,8 @@ When I rename the 'string' to 'poem' Then print data `; - const slangroom = new Slangroom(ethereum); - const res = await slangroom.execute(script, { + const sl = new Slangroom(ethereum); + const res = await sl.execute(contract, { data: { fabchain: 'http://78.47.38.223:9485', my_tag: '0467636a2557a1ccdaf10ce17ee74340096c510acfa9181c85756d43a8bed522', @@ -27,12 +27,12 @@ Then print data poem_bytes: '000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000674e656c206d657a7a6f2064656c2063616d6d696e206469206e6f7374726120766974610a6d6920726974726f7661692070657220756e612073656c7661206f73637572612c0a6368c3a9206c612064697269747461207669612065726120736d6172726974612e00000000000000000000000000000000000000000000000000', }, - res.logs + res.logs, ); }); test('Store an object on eth', async (t) => { - const script = ` + const contract = ` Rule unknown ignore Scenario ethereum Given I connect to 'fabchain' and send address 'my_address' and read the ethereum nonce and output into 'ethereum_nonce' @@ -52,8 +52,8 @@ Then print the 'signed ethereum transaction' Then print data Then I connect to 'fabchain' and send transaction 'signed_ethereum_transaction' and read the ethereum transaction id after broadcast and output into 'transaction_id' `; - const slangroom = new Slangroom(ethereum); - const res = await slangroom.execute(script, { + const sl = new Slangroom(ethereum); + const res = await sl.execute(contract, { data: { keyring: { ethereum: '52268bd5befb5e375f231c16a0614ff97ce81b105190c293a482efe9745c95ae', @@ -68,29 +68,25 @@ Then I connect to 'fabchain' and send transaction 'signed_ethereum_transaction' }, }); - t.truthy(typeof res.result['transaction_id'] == 'string', res.logs); + t.truthy(typeof res.result['transaction_id'] === 'string', res.logs); }); test('Make slangroom fail', async (t) => { - const script = ` + const contract = ` Rule unknown ignore Scenario ethereum Given I connect to 'fabchain' and send address 'my_address' and read the ethereum and output into 'ethereum_nonce' Given nothing Then print data `; - const slangroom = new Slangroom(ethereum); - try { - await slangroom.execute(script, { - data: { - my_address: '0x7d6df85bDBCe99151c813fd1DDE6BC007c523C27', - fabchain: 'http://78.47.38.223:9485', - }, - }); - } catch { - t.truthy(true); - return; - } - t.falsy(false); - return; + const sl = new Slangroom(ethereum); + await t.throwsAsync( + async () => + await sl.execute(contract, { + data: { + my_address: '0x7d6df85bDBCe99151c813fd1DDE6BC007c523C27', + fabchain: 'http://78.47.38.223:9485', + }, + }), + ); }); diff --git a/pkg/ethereum/test/plugin.ts b/pkg/ethereum/test/plugin.ts index f613ce0e..93d3b18b 100644 --- a/pkg/ethereum/test/plugin.ts +++ b/pkg/ethereum/test/plugin.ts @@ -1,7 +1,17 @@ import ava, { TestFn } from 'ava'; import { Web3 } from 'web3'; import { PluginContextTest } from '@slangroom/core'; -import { execute } from '@slangroom/ethereum'; +import { + erc20decimals, + erc20name, + erc20symbol, + erc20totalSupply, + ethBalanceAddr, + ethBalanceAddrs, + ethBytes, + ethGasPrice, + ethNonce, +} from '@slangroom/ethereum'; const test = ava as TestFn<{ web3: Web3 }>; @@ -9,7 +19,7 @@ test('read the ethereum nonce', async (t) => { const ctx = new PluginContextTest('http://78.47.38.223:9485', { address: '0x2D010920b43aFb54f8d5fB51c9354FbC674b28Fc', }); - const res = await execute(ctx, 'ethNonce'); + const res = await ethNonce(ctx); t.deepEqual(res, { ok: true, value: '0', @@ -18,7 +28,7 @@ test('read the ethereum nonce', async (t) => { test('Ethereum gas price', async (t) => { const ctx = PluginContextTest.connect('http://78.47.38.223:9485'); - const res = await execute(ctx, 'ethGasPrice'); + const res = await ethGasPrice(ctx); t.truthy(res.ok); if (res.ok) t.is(typeof res.value, 'string'); }); @@ -29,7 +39,7 @@ test('Retrieve a zenroom object', async (t) => { const ctx = new PluginContextTest('http://78.47.38.223:9485', { transaction_id: '0x0467636a2557a1ccdaf10ce17ee74340096c510acfa9181c85756d43a8bed522', }); - const res = await execute(ctx, 'ethBytes'); + const res = await ethBytes(ctx); t.deepEqual(res, { ok: true, value: poem, @@ -40,7 +50,7 @@ test('Ethereum balance', async (t) => { const ctx = new PluginContextTest('http://78.47.38.223:9485', { address: '0x2D010920b43aFb54f8d5fB51c9354FbC674b28Fc', }); - const res = await execute(ctx, 'ethBalance'); + const res = await ethBalanceAddr(ctx); t.deepEqual(res, { ok: true, value: '1000000000000000000000', @@ -55,52 +65,64 @@ test('Read the balance of an array of addresses', async (t) => { '0x4743879F5e9dc3fcE41E30380365441E8D14CCEc', ], }); - const res = await execute(ctx, 'ethBalance'); + const res = await ethBalanceAddrs(ctx); t.truthy(res.ok); if (res.ok) for (const v of res.value as string[]) t.is(typeof v, 'string'); }); -/* -test("Ethereum transaction id after broadcast", async (t) => { - const ast = line2Ast("Ethereum transaction id after broadcast of 'signed tx'"); - t.deepEqual(ast.value, { kind: EthereumRequestKind.EthereumBroadcast, rawTransaction: 'signed tx'}) -}) -*/ +test('erc20 symbol()', async (t) => { + const ctx = new PluginContextTest('http://78.47.38.223:9485', { + sc: '0x720F72765775bb85EAAa08BB74442F106d3ffA03', + }); + const res = await erc20symbol(ctx); + t.deepEqual(res, { + ok: true, + value: 'NMT', + }); +}); -test('Erc20 method without arg', async (t) => { - const tokenResult: Partial<{ - [K in Parameters[1]]: string; - }> = { - erc20symbol: 'NMT', - erc20name: 'Non movable token', - erc20totalSupply: '1000', - erc20decimals: '18', - }; +test('erc20 name()', async (t) => { + const ctx = new PluginContextTest('http://78.47.38.223:9485', { + sc: '0x720F72765775bb85EAAa08BB74442F106d3ffA03', + }); + const res = await erc20name(ctx); + t.deepEqual(res, { + ok: true, + value: 'Non movable token', + }); +}); - for (const [k, v] of Object.entries(tokenResult)) { - const ctx = new PluginContextTest('http://78.47.38.223:9485', { - sc: '0x720F72765775bb85EAAa08BB74442F106d3ffA03', - }); - const res = await execute(ctx, k as keyof typeof tokenResult); - t.deepEqual(res, { - ok: true, - value: v, - }); - } +test('erc20 totalSupply()', async (t) => { + const ctx = new PluginContextTest('http://78.47.38.223:9485', { + sc: '0x720F72765775bb85EAAa08BB74442F106d3ffA03', + }); + const res = await erc20totalSupply(ctx); + t.deepEqual(res, { + ok: true, + value: '1000', + }); }); -test('erc20 with invalid address', async (t) => { +test('erc20 decimals()', async (t) => { const ctx = new PluginContextTest('http://78.47.38.223:9485', { - sc: '0x720765775bb85EAAa08BB74442F106d3ffA03', + sc: '0x720F72765775bb85EAAa08BB74442F106d3ffA03', + }); + const res = await erc20decimals(ctx); + t.deepEqual(res, { + ok: true, + value: '18', }); +}); - try { - await execute(ctx, 'erc20symbol'); - } catch (e) { - t.truthy(true); - return; - } - t.falsy(true); +test('erc20 with invalid address', async (t) => { + const sc = '0x720765775bb85EAAa08BB74442F106d3ffA03'; + const ctx = new PluginContextTest('http://78.47.38.223:9485', { sc: sc }); + + const res = await erc20symbol(ctx); + t.deepEqual(res, { + ok: false, + error: `sc must be a valid ethereum address: ${sc}`, + }); }); /*test("Erc20 method with arg", async (t) => { diff --git a/pkg/fs/.npmignore b/pkg/fs/.npmignore new file mode 120000 index 00000000..b4359f69 --- /dev/null +++ b/pkg/fs/.npmignore @@ -0,0 +1 @@ +../../.npmignore \ No newline at end of file diff --git a/pkg/fs/package.json b/pkg/fs/package.json new file mode 100644 index 00000000..342368b3 --- /dev/null +++ b/pkg/fs/package.json @@ -0,0 +1,39 @@ +{ + "name": "@slangroom/fs", + "version": "1.0.0", + "dependencies": { + "@slangroom/core": "workspace:*", + "axios": "^1.5.1", + "extract-zip": "^2.0.1" + }, + "repository": "https://github.com/dyne/slangroom", + "license": "AGPL-3.0-only", + "type": "module", + "main": "./build/cjs/src/index.js", + "types": "./build/cjs/src/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./build/esm/src/index.d.ts", + "default": "./build/esm/src/index.js" + }, + "require": { + "types": "./build/cjs/src/index.d.ts", + "default": "./build/cjs/src/index.js" + } + }, + "./*": { + "import": { + "types": "./build/esm/src/*.d.ts", + "default": "./build/esm/src/*.js" + }, + "require": { + "types": "./build/cjs/src/*.d.ts", + "default": "./build/cjs/src/*.js" + } + } + }, + "publishConfig": { + "access": "public" + } +} diff --git a/pkg/fs/src/index.ts b/pkg/fs/src/index.ts new file mode 100644 index 00000000..93eb2367 --- /dev/null +++ b/pkg/fs/src/index.ts @@ -0,0 +1 @@ +export * from '@slangroom/fs/plugin'; diff --git a/pkg/fs/src/plugin.ts b/pkg/fs/src/plugin.ts new file mode 100644 index 00000000..e95b049f --- /dev/null +++ b/pkg/fs/src/plugin.ts @@ -0,0 +1,140 @@ +import { Plugin } from '@slangroom/core'; +import * as path from 'node:path'; +import * as fspkg from 'node:fs/promises'; +import * as os from 'node:os'; +import axios from 'axios'; +import extractZip from 'extract-zip'; + +/** + * @internal + */ +export const sandboxDir = () => { + // TODO: sanitize sandboxDir + const ret = process.env['FILES_DIR']; + if (!ret) throw new Error('$FILES_DIR must be provided'); + return ret; +}; + +const resolveDirPath = (unsafe: string) => { + const normalized = path.normalize(unsafe); + // `/` and `..` prevent directory traversal + const doesDirectoryTraversal = normalized.startsWith('/') || normalized.startsWith('..'); + // Unlike `resolveFilepath`, we allow `.` to be used here, obviously. + if (doesDirectoryTraversal) return { error: `dirpath is unsafe: ${unsafe}` }; + return { dirpath: path.join(sandboxDir(), normalized) }; +}; + +const resolveFilepath = (unsafe: string) => { + const normalized = path.normalize(unsafe); + // `/` and `..` prevent directory traversal + const doesDirectoryTraversal = normalized.startsWith('/') || normalized.startsWith('..'); + // `.` ensures that `foo/bar` or `./foo` is valid, while `.` isn't + // (that is, no "real" filepath is provided) + const DoesntProvideFile = normalized.startsWith('.'); + if (doesDirectoryTraversal || DoesntProvideFile) + return { error: `filepath is unsafe: ${unsafe}` }; + return { filepath: path.join(sandboxDir(), normalized) }; +}; + +const readFile = async (safePath: string) => { + const str = await fspkg.readFile(safePath, 'utf8'); + return JSON.parse(str); +}; + +const p = new Plugin(); + +/** + * @internal + */ +export const downloadAndExtract = p.new( + 'connect', + ['path'], + 'download and extract', + async (ctx) => { + const zipUrl = ctx.fetchConnect()[0]; + const unsafe = ctx.fetch('path'); + if (typeof unsafe !== 'string') return ctx.fail('path must be string'); + + const { dirpath: dirPath, error } = resolveDirPath(unsafe); + if (!dirPath) return ctx.fail(error); + await fspkg.mkdir(dirPath, { recursive: true }); + + try { + const resp = await axios.get(zipUrl, { responseType: 'arraybuffer' }); + const tempdir = await fspkg.mkdtemp(path.join(os.tmpdir(), 'slangroom-')); + const tempfile = path.join(tempdir, 'downloaded'); + await fspkg.writeFile(tempfile, resp.data); + await extractZip(tempfile, { dir: dirPath }); + await fspkg.rm(tempdir, { recursive: true }); + return ctx.pass(null); + } catch (e) { + if (e instanceof Error) return ctx.fail(e.message); + return ctx.fail(`unknown error: ${e}`); + } + }, +); + +/** + * @internal + */ +export const readFileContent = p.new(['path'], 'read file content', async (ctx) => { + const unsafe = ctx.fetch('path'); + if (typeof unsafe !== 'string') return ctx.fail('path must be string'); + + const { filepath, error } = resolveFilepath(unsafe); + if (!filepath) return ctx.fail(error); + + return ctx.pass(await readFile(filepath)); +}); + +/** + * @internal + */ +export const storeInFile = p.new(['content', 'path'], 'store in file', async (ctx) => { + // TODO: should `ctx.fetch('content')` return a JsonableObject? + const content = JSON.stringify(ctx.fetch('content')); + const unsafe = ctx.fetch('path'); + if (typeof unsafe !== 'string') return ctx.fail('path must be string'); + + const { filepath, error } = resolveFilepath(unsafe); + if (!filepath) return ctx.fail(error); + + await fspkg.mkdir(path.dirname(filepath), { recursive: true }); + await fspkg.writeFile(filepath, content); + return ctx.pass(null); +}); + +/** + * @internal + */ +export const listDirectoryContent = p.new(['path'], 'list directory content', async (ctx) => { + const unsafe = ctx.fetch('path'); + if (typeof unsafe !== 'string') return ctx.fail('path must be string'); + + const { dirpath, error } = resolveDirPath(unsafe); + if (!dirpath) return ctx.fail(error); + + const filepaths = (await fspkg.readdir(dirpath)).map((f) => path.join(dirpath, f)); + const stats = await Promise.all(filepaths.map((f) => fspkg.stat(f))); + const result = stats.map((stat, i) => { + const filepath = filepaths[i] as string; + return { + name: filepath, + mode: stat.mode.toString(8), + dev: stat.dev, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + size: stat.size, + blksize: stat.blksize, + blocks: stat.blocks, + atime: stat.atime.toISOString(), + mtime: stat.mtime.toISOString(), + ctime: stat.ctime.toISOString(), + birthtime: stat.birthtime.toISOString(), + }; + }); + return ctx.pass(result); +}); + +export const fs = p; diff --git a/pkg/fs/test/plugin.ts b/pkg/fs/test/plugin.ts new file mode 100644 index 00000000..e1880420 --- /dev/null +++ b/pkg/fs/test/plugin.ts @@ -0,0 +1,96 @@ +import ava, { type TestFn } from 'ava'; +import { PluginContextTest } from '@slangroom/core'; +import { + downloadAndExtract, + listDirectoryContent, + readFileContent, + storeInFile, +} from '@slangroom/fs'; +import * as fs from 'node:fs/promises'; +import { join } from 'node:path'; +import * as os from 'node:os'; +import nock from 'nock'; + +nock('http://localhost').get('/').reply( + 200, + // Zip of a file named 'foo.txt' that has the content of 'bar'. + Buffer.from( + 'UEsDBAoAAAAAADRzTFfps6IEBAAAAAQAAAAHABwAZm9vLnR4dFVUCQADs9cnZWELKWV1eAsAAQToAwAABOgDAABiYXIKUEsBAh4DCgAAAAAANHNMV+mzogQEAAAABAAAAAcAGAAAAAAAAQAAAICBAAAAAGZvby50eHRVVAUAA7PXJ2V1eAsAAQToAwAABOgDAABQSwUGAAAAAAEAAQBNAAAARQAAAAAA', + 'base64', + ), +); + +// Tests in this file must be run serially, since we're modifying `process.env` + +const jsonify = JSON.stringify; +const test = ava as TestFn; + +test.beforeEach(async (t) => { + const tmpdir = await fs.mkdtemp(join(os.tmpdir(), 'slangroom-test-')); + process.env['FILES_DIR'] = tmpdir; + t.context = tmpdir; +}); + +test.afterEach(async (t) => await fs.rm(t.context, { recursive: true })); + +// Read the comments of the nock instance above in order to understand how this +// test works. +test.serial('downloadAndExtract works', async (t) => { + const path = join('foo', 'bar'); + const dir = join(t.context, path); + const ctx = new PluginContextTest('http://localhost/', { path: path }); + const res = await downloadAndExtract(ctx); + t.deepEqual(res, { ok: true, value: null }); + const content = await fs.readFile(join(dir, 'foo.txt')); + t.is(content.toString(), 'bar\n'); +}); + +test.serial('readFileContent works', async (t) => { + const path = 'foo'; + const content = { a: 1, b: 2 }; + await fs.writeFile(join(t.context, path), jsonify(content)); + const ctx = PluginContextTest.params({ path: path }); + const res = await readFileContent(ctx); + t.deepEqual(res, { ok: true, value: content }); +}); + +test.serial('storeInFile works', async (t) => { + const path = 'foo'; + const content = { a: 1, b: 2 }; + const ctx = PluginContextTest.params({ path: path, content: content }); + const res = await storeInFile(ctx); + t.deepEqual(res, { ok: true, value: null }); + const buf = await fs.readFile(join(t.context, path)); + t.is(buf.toString(), jsonify(content)); +}); + +test.serial('listDirectoryContent works', async (t) => { + const path = join('foo', 'bar'); + const dir = join(t.context, path); + await fs.mkdir(dir, { recursive: true }); + const files = [join(dir, 'file0'), join(dir, 'file1')]; + files.forEach(async (f) => await fs.appendFile(f, '')); + const stats = await Promise.all(files.map((f) => fs.stat(f))); + const ctx = PluginContextTest.params({ path: path }); + const value = stats.map((stat, i) => { + const filepath = files[i] as string; + return { + name: filepath, + mode: stat.mode.toString(8), + dev: stat.dev, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + size: stat.size, + blksize: stat.blksize, + blocks: stat.blocks, + atime: stat.atime.toISOString(), + mtime: stat.mtime.toISOString(), + ctime: stat.ctime.toISOString(), + birthtime: stat.birthtime.toISOString(), + }; + }); + const want = { ok: true, value: value }; + const have = await listDirectoryContent(ctx); + t.deepEqual(have, want); +}); diff --git a/pkg/fs/tsconfig.json b/pkg/fs/tsconfig.json new file mode 120000 index 00000000..fd0e4743 --- /dev/null +++ b/pkg/fs/tsconfig.json @@ -0,0 +1 @@ +../../tsconfig.json \ No newline at end of file diff --git a/pkg/git/.npmignore b/pkg/git/.npmignore new file mode 120000 index 00000000..b4359f69 --- /dev/null +++ b/pkg/git/.npmignore @@ -0,0 +1 @@ +../../.npmignore \ No newline at end of file diff --git a/pkg/git/package.json b/pkg/git/package.json new file mode 100644 index 00000000..7e521447 --- /dev/null +++ b/pkg/git/package.json @@ -0,0 +1,38 @@ +{ + "name": "@slangroom/git", + "version": "1.0.0", + "dependencies": { + "@slangroom/core": "workspace:*", + "isomorphic-git": "^1.24.5" + }, + "repository": "https://github.com/dyne/slangroom", + "license": "AGPL-3.0-only", + "type": "module", + "main": "./build/cjs/src/index.js", + "types": "./build/cjs/src/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./build/esm/src/index.d.ts", + "default": "./build/esm/src/index.js" + }, + "require": { + "types": "./build/cjs/src/index.d.ts", + "default": "./build/cjs/src/index.js" + } + }, + "./*": { + "import": { + "types": "./build/esm/src/*.d.ts", + "default": "./build/esm/src/*.js" + }, + "require": { + "types": "./build/cjs/src/*.d.ts", + "default": "./build/cjs/src/*.js" + } + } + }, + "publishConfig": { + "access": "public" + } +} diff --git a/pkg/git/src/index.ts b/pkg/git/src/index.ts new file mode 100644 index 00000000..491ef5f5 --- /dev/null +++ b/pkg/git/src/index.ts @@ -0,0 +1 @@ +export * from '@slangroom/git/plugin'; diff --git a/pkg/git/src/plugin.ts b/pkg/git/src/plugin.ts new file mode 100644 index 00000000..a9eb7826 --- /dev/null +++ b/pkg/git/src/plugin.ts @@ -0,0 +1,120 @@ +import { Plugin } from '@slangroom/core'; +import gitpkg from 'isomorphic-git'; +// TODO: why does this require index.js? +import http from 'isomorphic-git/http/node/index.js'; +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; + +/** + * @internal + */ +export const sandboxDir = () => { + // TODO: sanitize sandboxDir + const ret = process.env['FILES_DIR']; + if (!ret) throw new Error('$FILES_DIR must be provided'); + return ret; +}; + +const sandboxizeDir = (unsafe: string) => { + const normalized = path.normalize(unsafe); + // `/` and `..` prevent directory traversal + const doesDirectoryTraversal = normalized.startsWith('/') || normalized.startsWith('..'); + // Unlike `resolveFilepath`, we allow `.` to be used here, obviously. + if (doesDirectoryTraversal) return { error: `dirpath is unsafe: ${unsafe}` }; + return { dirpath: path.join(sandboxDir(), normalized) }; +}; + +const sandboxizeFile = (sandboxdir: string, unsafe: string) => { + const normalized = path.normalize(unsafe); + // `/` and `..` prevent directory traversal + const doesDirectoryTraversal = normalized.startsWith('/') || normalized.startsWith('..'); + // `.` ensures that `foo/bar` or `./foo` is valid, while `.` isn't + // (that is, no "real" filepath is provided) + const DoesntProvideFile = normalized.startsWith('.'); + if (doesDirectoryTraversal || DoesntProvideFile) + return { error: `filepath is unsafe: ${unsafe}` }; + return { filepath: path.join(sandboxdir, normalized) }; +}; + +const p = new Plugin(); + +/** + * @internal + */ +export const verifyGitRepository = p.new('open', 'verify git repository', async (ctx) => { + const unsafe = ctx.fetchOpen()[0]; + const { dirpath, error } = sandboxizeDir(unsafe); + if (!dirpath) return ctx.fail(error); + + try { + await gitpkg.findRoot({ fs: fs, filepath: dirpath }); + return ctx.pass(null); + } catch (e) { + return ctx.fail(e); + } +}); + +/* + * @internal + */ +export const cloneRepository = p.new('connect', ['path'], 'clone repository', async (ctx) => { + const repoUrl = ctx.fetchConnect()[0]; + const unsafe = ctx.fetch('path'); + if (typeof unsafe !== 'string') return ctx.fail('path must be string'); + + const { dirpath, error } = sandboxizeDir(unsafe); + if (!dirpath) return ctx.fail(error); + + await gitpkg.clone({ fs: fs, http: http, dir: dirpath, url: repoUrl }); + return ctx.pass(null); +}); + +/* + * @internal + */ +export const createNewGitCommit = p.new('open', 'create new git commit', async (ctx) => { + const unsafe = ctx.fetchOpen()[0]; + const { dirpath, error } = sandboxizeDir(unsafe); + if (!dirpath) return ctx.fail(error); + + const commit = ctx.fetch('commit') as { + message: string; + author: string; + email: string; + files: string[]; + }; + + try { + commit.files.map((unsafe) => { + const { filepath, error: ferror } = sandboxizeFile(dirpath, unsafe); + if (!filepath) throw ferror; + return filepath; + }); + } catch (e) { + return ctx.fail(e); + } + + await Promise.all( + commit.files.map((safe) => { + return gitpkg.add({ + fs: fs, + dir: dirpath, + filepath: safe, + }); + }), + ); + + const hash = await gitpkg.commit({ + fs: fs, + dir: dirpath, + message: commit.message, + author: { + name: commit.author, + email: commit.email, + }, + }); + + return ctx.pass(hash); +}); + +export const git = p; diff --git a/pkg/git/test/plugin.ts b/pkg/git/test/plugin.ts new file mode 100644 index 00000000..5971a941 --- /dev/null +++ b/pkg/git/test/plugin.ts @@ -0,0 +1,62 @@ +import ava, { type TestFn } from 'ava'; +import * as fs from 'node:fs/promises'; +import { join } from 'node:path'; +import * as os from 'node:os'; +import git from 'isomorphic-git'; +import { PluginContextTest } from '@slangroom/core'; +import { cloneRepository, createNewGitCommit, verifyGitRepository } from '@slangroom/git'; + +const test = ava as TestFn; + +test.beforeEach(async (t) => { + const tmpdir = await fs.mkdtemp(join(os.tmpdir(), 'slangroom-test-')); + process.env['FILES_DIR'] = tmpdir; + t.context = tmpdir; +}); + +test.afterEach(async (t) => await fs.rm(t.context, { recursive: true })); + +test.serial('verifyGitRepository works', async (t) => { + const path = join('foo', 'bar'); + const dir = join(t.context, path); + await git.init({ fs: fs, dir: dir }); + const ctx = PluginContextTest.open(path); + const res = await verifyGitRepository(ctx); + t.deepEqual(res, { ok: true, value: null }); +}); + +// TODO: somehow make this work with nock using dumb http +test.serial('cloneRepository works', async (t) => { + const path = join('foo', 'bar'); + const dir = join(t.context, path); + const ctx = new PluginContextTest('https://github.com/srfsh/dumb', { + path: path, + }); + const res = await cloneRepository(ctx); + t.deepEqual(res, { ok: true, value: null }); + const content = await fs.readFile(join(dir, 'README.md')); + t.is(content.toString(), '# dumb\nA repo only for testing. It shall never change.\n'); +}); + +test.serial('createNewCommit works', async (t) => { + const path = join('foo', 'bar'); + const dir = join(t.context, path); + await git.init({ fs: fs, dir: dir }); + const files = ['file0.txt', 'file1.txt']; + files.forEach(async (f) => await fs.appendFile(join(dir, f), `test data ${f}`)); + const commitParams = { + message: 'my message', + author: 'my author', + email: 'email@example.com', + files: files, + }; + const ctx = new PluginContextTest(path, { commit: commitParams }); + const res = await createNewGitCommit(ctx); + const hash = await git.resolveRef({ fs: fs, dir: dir, ref: 'HEAD' }); + t.deepEqual(res, { ok: true, value: hash }); + const { commit } = await git.readCommit({ fs: fs, dir: dir, oid: hash }); + t.is(commit.author.name, commitParams.author); + t.is(commit.author.email, commitParams.email); + // newline is required + t.is(commit.message, `${commitParams.message}\n`); +}); diff --git a/pkg/git/tsconfig.json b/pkg/git/tsconfig.json new file mode 120000 index 00000000..fd0e4743 --- /dev/null +++ b/pkg/git/tsconfig.json @@ -0,0 +1 @@ +../../tsconfig.json \ No newline at end of file diff --git a/pkg/http/src/index.ts b/pkg/http/src/index.ts index 173aaf32..2fd9a634 100644 --- a/pkg/http/src/index.ts +++ b/pkg/http/src/index.ts @@ -1,2 +1 @@ -export * from '@slangroom/http/parser'; export * from '@slangroom/http/plugin'; diff --git a/pkg/http/src/parser.ts b/pkg/http/src/parser.ts deleted file mode 100644 index f18f4934..00000000 --- a/pkg/http/src/parser.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Parser } from '@slangroom/core'; - -export function parser(this: Parser) { - this.RULE('httpPhrase', () => { - this.connect(); - this.OPTION(() => this.sendpass('object')); - this.OPTION1(() => this.sendpass1('headers')); - this.token('do'); - this.OPTION2(() => this.SUBRULE(kind)); - this.SUBRULE(method); - this.into(); - }); - - const method = this.RULE('httpMethod', () => { - this.OR([ - { ALT: () => this.token('get') }, - { ALT: () => this.token('post') }, - { ALT: () => this.token('patch') }, - { ALT: () => this.token('put') }, - { ALT: () => this.token('delete') }, - ]); - }); - - const kind = this.RULE('httpKind', () => { - this.OR([ - { ALT: () => this.token('sequential') }, - { ALT: () => this.token('parallel') }, - { ALT: () => this.token('same') }, - ]); - }); -} diff --git a/pkg/http/src/plugin.ts b/pkg/http/src/plugin.ts index c47af93d..d04751d6 100644 --- a/pkg/http/src/plugin.ts +++ b/pkg/http/src/plugin.ts @@ -1,8 +1,11 @@ -import { JsonableArray } from '@slangroom/shared'; -import type { Plugin, PluginContext, PluginResult } from '@slangroom/core'; -import { parser } from '@slangroom/http'; +import type { JsonableArray } from '@slangroom/shared'; +import { Plugin, type PluginExecutor } from '@slangroom/core'; import axios, { type AxiosRequestConfig } from 'axios'; +export type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'; + +const p = new Plugin(); + /** * The default timeout of an HTTP request in milliseconds. */ @@ -14,64 +17,39 @@ const { request } = axios.create({ timeout: DefaultTimeoutMs, }); -/** - * @internal - */ -export const execute = async ( - ctx: PluginContext, - kind: 'default' | 'sequential' | 'parallel' | 'same', - method: 'get' | 'post' | 'put' | 'patch' | 'delete' -): Promise => { - const url = ctx.fetchConnect()[0]; - const headers = ctx.get('headers'); - if (kind === 'default') { +const defaultRequest = (m: HttpMethod): PluginExecutor => { + return async (ctx) => { + const url = ctx.fetchConnect()[0]; + // TODO: typecheck headers + const headers = ctx.get('headers') as any; + const object = ctx.get('object'); let error: any = null; - const requestData: AxiosRequestConfig = { - url: url, - method: method, - data: ctx.get('object') as any, - }; - if (headers) requestData['headers'] = headers as any; - const req = await request(requestData).catch((e) => (error = e)); + const conf: AxiosRequestConfig = { url: url, method: m }; + if (object) conf.data = object; + if (headers) conf.headers = headers; + const req = await request(conf).catch((e) => (error = e)); const zenResult = error ? { status: error.code, error: '' } : { status: req.status, result: req.data || '' }; return ctx.pass(zenResult); - } else if (kind === 'sequential') { - throw new Error('Not yet implemented'); - } else { + }; +}; + +const parallelRequest = (m: HttpMethod): PluginExecutor => { + return async (ctx) => { const reqs = []; const urls = ctx.fetchConnect(); - if (kind === 'parallel') { - // TODO: check type of body (needs to be JsonableArray of - // JsonableObject) - const objects = ctx.fetch('object') as JsonableArray; - for (const [i, u] of urls.entries()) { - const requestData: AxiosRequestConfig = { - url: u, - method: method, - data: objects[i], - }; - if (headers) { - requestData['headers'] = headers as any; - } - reqs.push(request(requestData)); - } - } else { - // TODO: check type of body (needs to be JsonableObject) - const object = ctx.fetch('object') as JsonableArray; - for (const u of urls) { - const requestData: AxiosRequestConfig = { - url: u, - method: method, - data: object, - }; - if (headers) { - requestData['headers'] = headers as any; - } - reqs.push(request(requestData)); - } + // TODO: typecheck object (JsonableArray of JsonableObject) + const objects = ctx.get('object') as undefined | JsonableArray; + // TODO: typecheck headers + const headers = ctx.get('headers') as any; + for (const [i, u] of urls.entries()) { + const conf: AxiosRequestConfig = { url: u, method: m }; + if (objects) conf.data = objects[i]; + if (headers) conf.headers = headers; + reqs.push(request(conf)); } + const results: JsonableArray = new Array(reqs.length); const errors: { [key: number]: any } = {}; const parallelWithCatch = reqs.map((v, i) => v.catch((e) => (errors[i] = e))); @@ -83,31 +61,88 @@ export const execute = async ( results[i] = zenResult; }); return ctx.pass(results); - } + }; }; -export const http: Plugin = { - parser: parser, - executor: async (ctx) => { - switch (ctx.phrase) { - case 'do get': - return await execute(ctx, 'default', 'get'); - case 'do sequential get': - return await execute(ctx, 'sequential', 'get'); - case 'do parallel get': - return await execute(ctx, 'parallel', 'get'); - case 'do same get': - return await execute(ctx, 'same', 'get'); - case 'do post': - return await execute(ctx, 'default', 'post'); - case 'do sequential post': - return await execute(ctx, 'sequential', 'post'); - case 'do parallel post': - return await execute(ctx, 'parallel', 'post'); - case 'do same post': - return await execute(ctx, 'same', 'post'); - default: - return ctx.fail('no match'); +const sameRequest = (m: HttpMethod): PluginExecutor => { + return async (ctx) => { + const reqs = []; + const urls = ctx.fetchConnect(); + // TODO: typecheck object (JsonableArray of JsonableObject) + const object = ctx.get('object') as undefined | JsonableArray; + // TODO: typecheck headers + const headers = ctx.get('headers') as any; + for (const u of urls) { + const conf: AxiosRequestConfig = { url: u, method: m }; + if (object) conf.data = object; + if (headers) conf.headers = headers; + reqs.push(request(conf)); } - }, + + const results: JsonableArray = new Array(reqs.length); + const errors: { [key: number]: any } = {}; + const parallelWithCatch = reqs.map((v, i) => v.catch((e) => (errors[i] = e))); + const parallelResults = await axios.all(parallelWithCatch); + parallelResults.map((r, i) => { + const zenResult = errors[i] + ? { status: errors[i].code, result: '' } + : { status: r.status, result: r.data || '' }; + results[i] = zenResult; + }); + return ctx.pass(results); + }; }; + +/** + * @internal + */ +export const defaults = {} as { + [K in + | HttpMethod + | `${HttpMethod}Object` + | `${HttpMethod}Headers` + | `${HttpMethod}ObjectHeaders`]: PluginExecutor; +}; + +/* + * @internal + */ +export const sequentials = {} as typeof defaults; + +/** + * @internal + */ +export const parallels = {} as typeof defaults; + +/** + * @internal + */ +export const sames = {} as typeof defaults; + +(['get', 'post', 'put', 'patch', 'delete'] as HttpMethod[]).forEach((m) => { + [defaults, sequentials, parallels, sames].forEach((x) => { + let phrase: string, cb: PluginExecutor; + if (x === defaults) { + phrase = `do ${m}`; + cb = defaultRequest(m); + } else if (x === sequentials) { + phrase = `do sequential ${m}`; + cb = (ctx) => ctx.fail('not implemented'); + } else if (x === parallels) { + phrase = `do parallel ${m}`; + cb = parallelRequest(m); + } else if (x === sames) { + phrase = `do same ${m}`; + cb = sameRequest(m); + } else { + throw new Error('unreachable'); + } + + x[m] = p.new('connect', phrase, cb); + x[`${m}Object`] = p.new('connect', ['object'], phrase, cb); + x[`${m}Headers`] = p.new('connect', ['headers'], phrase, cb); + x[`${m}ObjectHeaders`] = p.new('connect', ['object', 'headers'], phrase, cb); + }); +}); + +export const http = p; diff --git a/pkg/http/test/e2e.ts b/pkg/http/test/e2e.ts index 06b4152c..bcee7304 100644 --- a/pkg/http/test/e2e.ts +++ b/pkg/http/test/e2e.ts @@ -56,7 +56,7 @@ Then I connect to 'final_endpoints' and send object 'string_array' and do parall { status: 200, result: 'received result' }, ], }, - res.logs + res.logs, ); }); @@ -86,6 +86,6 @@ Then print data status: 200, }, }, - res.logs + res.logs, ); }); diff --git a/pkg/http/test/plugin.ts b/pkg/http/test/plugin.ts index e5870d36..6100850b 100644 --- a/pkg/http/test/plugin.ts +++ b/pkg/http/test/plugin.ts @@ -1,6 +1,6 @@ import test from 'ava'; import { PluginContextTest } from '@slangroom/core'; -import { execute } from '@slangroom/http'; +import { defaults, sames, parallels } from '@slangroom/http'; import nock from 'nock'; nock('http://localhost') @@ -40,7 +40,7 @@ nock('http://localhost') test('Simple GET', async (t) => { const ctx = PluginContextTest.connect('http://localhost/normaljson'); - const res = await execute(ctx, 'default', 'get'); + const res = await defaults.get(ctx); t.deepEqual(res, { ok: true, value: { @@ -62,7 +62,7 @@ test('single put with data', async (t) => { const ctx = new PluginContextTest('http://localhost/sendresultwithput', { object: { myData: 'foobar' }, }); - const res = await execute(ctx, 'default', 'put'); + const res = await defaults.putObject(ctx); t.deepEqual(res, { ok: true, value: { status: 200, result: 'received result' }, @@ -73,7 +73,7 @@ test('single post with data', async (t) => { const ctx = new PluginContextTest('http://localhost/sendresult', { object: { myData: 'foobar' }, }); - const res = await execute(ctx, 'default', 'post'); + const res = await defaults.postObject(ctx); t.deepEqual(res, { ok: true, value: { status: 200, result: 'received result' }, @@ -83,9 +83,9 @@ test('single post with data', async (t) => { test('multiple post with data', async (t) => { const ctx = new PluginContextTest( ['http://localhost/sendresult', 'http://localhost/normaljson'], - { object: { myData: 'foobar' } } + { object: { myData: 'foobar' } }, ); - const res = await execute(ctx, 'same', 'post'); + const res = await sames.postObject(ctx); t.deepEqual(res, { ok: true, value: [ @@ -102,9 +102,9 @@ test('POSTs with custom different', async (t) => { 'http://localhost/normaljson', 'http://localhost/sendresult', ], - { object: [{ myData: 'foobar' }, { myData: 'foobar' }, { mData: 'foobar' }] } + { object: [{ myData: 'foobar' }, { myData: 'foobar' }, { mData: 'foobar' }] }, ); - const res = await execute(ctx, 'parallel', 'post'); + const res = await parallels.postObject(ctx); t.deepEqual(res, { ok: true, value: [ diff --git a/pkg/ignored/src/index.ts b/pkg/ignored/src/index.ts index 03fb21b3..de1c29cf 100644 --- a/pkg/ignored/src/index.ts +++ b/pkg/ignored/src/index.ts @@ -12,7 +12,7 @@ import { zencodeExec, type ZenParams } from '@slangroom/shared'; */ export const getIgnoredStatements = async ( contract: string, - params: ZenParams + params: ZenParams, ): Promise => { // Since we want to get the list of ignored statements, we don't want to // throw if Zenroom execution fails (but we do fail if something other than @@ -32,7 +32,6 @@ export const getIgnoredStatements = async ( // throw e; logs = JSON.parse(e.message); } - const regexIgnored = /(?<=\[W\] Zencode line [0-9]+ pattern ignored: ).*/ - return logs.flatMap(log => log.match(regexIgnored) || []) + const regexIgnored = /(?<=\[W\] Zencode line [0-9]+ pattern ignored: ).*/; + return logs.flatMap((log) => log.match(regexIgnored) || []); }; - diff --git a/pkg/shared/src/zenroom.ts b/pkg/shared/src/zenroom.ts index e6611ac0..07e37783 100644 --- a/pkg/shared/src/zenroom.ts +++ b/pkg/shared/src/zenroom.ts @@ -37,7 +37,7 @@ const stringify = (params: ZenParams) => { data: JSON.stringify(params.data), keys: JSON.stringify(params.keys), extra: JSON.stringify(params.extra || {}), - conf: params.conf || "", + conf: params.conf || '', }; }; diff --git a/pkg/shared/test/zenroom.ts b/pkg/shared/test/zenroom.ts index f5578103..3db91719 100644 --- a/pkg/shared/test/zenroom.ts +++ b/pkg/shared/test/zenroom.ts @@ -22,8 +22,13 @@ Given nothing When I create the ecdh key Then print the 'keyring' `; - const res = await zencodeExec(contract, { data: {}, extra: {}, keys: {}, conf: "debug=3, rngseed=hex:74eeeab870a394175fae808dd5dd3b047f3ee2d6a8d01e14bff94271565625e98a63babe8dd6cbea6fedf3e19de4bc80314b861599522e44409fdd20f7cd6cfc" }); + const res = await zencodeExec(contract, { + data: {}, + extra: {}, + keys: {}, + conf: 'debug=3, rngseed=hex:74eeeab870a394175fae808dd5dd3b047f3ee2d6a8d01e14bff94271565625e98a63babe8dd6cbea6fedf3e19de4bc80314b861599522e44409fdd20f7cd6cfc', + }); t.deepEqual(res.result, { - keyring: { ecdh: "Aku7vkJ7K01gQehKELav3qaQfTeTMZKgK+5VhaR3Ui0=" } - }) + keyring: { ecdh: 'Aku7vkJ7K01gQehKELav3qaQfTeTMZKgK+5VhaR3Ui0=' }, + }); }); diff --git a/pkg/wallet/src/index.ts b/pkg/wallet/src/index.ts index 877820bb..e29aa443 100644 --- a/pkg/wallet/src/index.ts +++ b/pkg/wallet/src/index.ts @@ -1,2 +1 @@ -export * from '@slangroom/wallet/parser'; export * from '@slangroom/wallet/plugin'; diff --git a/pkg/wallet/src/parser.ts b/pkg/wallet/src/parser.ts deleted file mode 100644 index fd41ca93..00000000 --- a/pkg/wallet/src/parser.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Parser } from '@slangroom/core'; - -export function parser(this: Parser) { - this.RULE('walletPhrase', () => { - this.OR([ - { ALT: () => this.SUBRULE(createVcSdJwt), }, - { ALT: () => this.SUBRULE(presentVcSdJwt), }, - { ALT: () => this.SUBRULE(verifyVcSdJwt), }, - { ALT: () => this.SUBRULE(keyGen), }, - { ALT: () => this.SUBRULE(pkGen), }, - { ALT: () => this.SUBRULE(prettyPrintSdJwt), }, - ]) - this.into(); - }); - - const createVcSdJwt = this.RULE('createVcSdJwt', () => { - this.sendpass('jwk'); - this.sendpass1('holder'); - this.sendpass2('object'); - this.sendpassn(3, 'fields'); - this.token('create'); - this.token('vc'); - this.token('sd'); - this.token('jwt'); - }) - const keyGen = this.RULE('keyGen', () => { - this.token('create'); - this.token('p-256'); - this.token('key'); - }) - const pkGen = this.RULE('pkGen', () => { - this.sendpass('sk'); - this.token('create'); - this.token('p-256'); - this.token('public'); - this.token('key'); - }) - const presentVcSdJwt = this.RULE('presentVcSdJwt', () => { - this.sendpass('verifier url'); - this.sendpass1('issued vc'); - this.sendpass2('disclosed'); - this.sendpassn(3, 'nonce'); - this.sendpassn(4, 'holder'); - this.token('present'); - this.token('vc'); - this.token('sd'); - this.token('jwt'); - }) - const verifyVcSdJwt = this.RULE('verifyVcSdJwt', () => { - this.sendpass('verifier url'); - this.sendpass1('issued vc'); - this.sendpass2('nonce'); - this.sendpassn(3, 'issuer'); - this.token('verify'); - this.token('vc'); - this.token('sd'); - this.token('jwt'); - }) - const prettyPrintSdJwt = this.RULE('prettyPrintSdJwt', () => { - this.sendpass('token'); - this.token('pretty'); - this.token('print'); - this.token('sd'); - this.token('jwt'); - }) -} diff --git a/pkg/wallet/src/plugin.ts b/pkg/wallet/src/plugin.ts index 0f2071c4..b10833bf 100644 --- a/pkg/wallet/src/plugin.ts +++ b/pkg/wallet/src/plugin.ts @@ -1,40 +1,60 @@ -import type { Plugin, PluginContext, PluginResult } from '@slangroom/core'; -import { parser } from '@slangroom/wallet'; - -import { DisclosureFrame, Hasher, KeyBindingVerifier, Signer, base64encode, decodeJWT } from '@meeco/sd-jwt'; +import { Plugin } from '@slangroom/core'; +import { + base64encode, + decodeJWT, + type DisclosureFrame, + type Hasher, + type KeyBindingVerifier, + type Signer, +} from '@meeco/sd-jwt'; import { createHash } from 'crypto'; -import { JWTHeaderParameters, JWTPayload, KeyLike, SignJWT, exportJWK, importJWK, generateKeyPair, JWK, jwtVerify } from 'jose'; import { - CreateSDJWTPayload, - HasherConfig, + SignJWT, + exportJWK, + importJWK, + generateKeyPair, + jwtVerify, + type JWTHeaderParameters, + type JWTPayload, + type KeyLike, + type JWK, +} from 'jose'; +import { Holder, Issuer, - SignerConfig, - VCClaims, Verifier, defaultHashAlgorithm, supportedAlgorithm, + type SignerConfig, + type CreateSDJWTPayload, + type HasherConfig, + type VCClaims, } from '@meeco/sd-jwt-vc'; -import { JsonableArray, JsonableObject } from '@slangroom/shared'; +import type { JsonableArray, JsonableObject } from '@slangroom/shared'; -const hasherCallbackFn = function (alg: string = defaultHashAlgorithm): Hasher { +const hasherCallbackFn = (alg: string = defaultHashAlgorithm): Hasher => { return (data: string): string => { const digest = createHash(alg).update(data).digest(); return base64encode(digest); }; }; -const signerCallbackFn = function (privateKey: Uint8Array | KeyLike): Signer { +const signerCallbackFn = (privateKey: Uint8Array | KeyLike): Signer => { return async (protectedHeader: JWTHeaderParameters, payload: JWTPayload): Promise => { - return (await new SignJWT(payload).setProtectedHeader(protectedHeader).sign(privateKey)).split('.').pop() || ""; + return ( + (await new SignJWT(payload).setProtectedHeader(protectedHeader).sign(privateKey)) + .split('.') + .pop() || '' + ); }; }; + const hasher: HasherConfig = { - alg: 'sha256', - callback: hasherCallbackFn('sha256'), + alg: 'sha256', + callback: hasherCallbackFn('sha256'), }; -const keyBindingVerifierCallbackFn = function (): KeyBindingVerifier { +const keyBindingVerifierCallbackFn = (): KeyBindingVerifier => { return async (kbjwt: string, holderJWK: JWK) => { const { header } = decodeJWT(kbjwt); @@ -48,14 +68,14 @@ const keyBindingVerifierCallbackFn = function (): KeyBindingVerifier { }; }; -function verifierCallbackFn(publicKey: Uint8Array | KeyLike) { +const verifierCallbackFn = (publicKey: Uint8Array | KeyLike) => { return async (jwt: string): Promise => { const verifiedKbJWT = await jwtVerify(jwt, publicKey); return !!verifiedKbJWT; }; -} +}; -function kbVeriferCallbackFn(expectedAud: string, expectedNonce: string): KeyBindingVerifier { +const kbVerifierCallbackFn = (expectedAud: string, expectedNonce: string): KeyBindingVerifier => { return async (kbjwt: string, holderJWK: JWK) => { const { header, payload } = decodeJWT(kbjwt); @@ -76,174 +96,184 @@ function kbVeriferCallbackFn(expectedAud: string, expectedNonce: string): KeyBin const verifiedKbJWT = await jwtVerify(kbjwt, holderKey); return !!verifiedKbJWT; }; -} - -const createVCSDJWT = async (ctx: PluginContext): Promise => { - const sk = ctx.fetch('jwk') as JsonableObject - const object = ctx.fetch('object') as JsonableObject - const holder = ctx.fetch('holder') as JsonableObject - const fields = ctx.fetch('fields') as JsonableArray - // TODO: generate in another statement - const signer: SignerConfig = { - alg: supportedAlgorithm.ES256, - callback: signerCallbackFn(await importJWK(sk)), - }; - const issuer = new Issuer(signer, hasher); - - - const payload: CreateSDJWTPayload = { - iat: Date.now(), - cnf: { - jwk: holder, - }, - iss: 'https://valid.issuer.url', - }; - const vcClaims: VCClaims = { - type: 'VerifiableCredential', - status: { - idx: 'statusIndex', - uri: 'https://valid.status.url', - }, - object - }; - - const sdVCClaimsDisclosureFrame: DisclosureFrame = { object: { _sd: fields } }; - - const result = await issuer.createVCSDJWT(vcClaims, payload, sdVCClaimsDisclosureFrame); - - return ctx.pass(result); -} - - -const presentVCSDJWT = async (ctx: PluginContext): Promise => { - const verifierUrl = ctx.fetch('verifier url') as string - const issuedVc = ctx.fetch('issued vc') as string - const disclosed = ctx.fetch('disclosed') as JsonableObject[] - const nonce = ctx.fetch('nonce') as string - const holderSk = ctx.fetch('holder') as JsonableObject - - const pk = await importJWK(holderSk); - const disclosureList = [] - - const tildeTokens = issuedVc.split("~") +}; - for(let i = 1; i < tildeTokens.length; i++) { - const encoded = tildeTokens[i] || ""; - if(!encoded) { - continue - } - const disclose = JSON.parse(atob(encoded)) - if(!disclosed.includes(disclose[1])) { - continue +const p = new Plugin(); + +/** + * @internal + */ +export const createVcSdJwt = p.new( + ['jwk', 'object', 'holder', 'fields'], + 'create vc sd jwt', + async (ctx) => { + // TODO: typecheck jwt + const sk = ctx.fetch('jwk') as JsonableObject; + // TODO: typecheck object + const object = ctx.fetch('object') as JsonableObject; + // TODO: typecheck holder + const holder = ctx.fetch('holder') as JsonableObject; + // TODO: typecheck fields + const fields = ctx.fetch('fields') as JsonableArray; + // TODO: generate in another statement + const signer: SignerConfig = { + alg: supportedAlgorithm.ES256, + callback: signerCallbackFn(await importJWK(sk)), + }; + + const issuer = new Issuer(signer, hasher); + + const payload: CreateSDJWTPayload = { + iat: Date.now(), + cnf: { jwk: holder }, + iss: 'https://valid.issuer.url', + }; + const vcClaims: VCClaims = { + type: 'VerifiableCredential', + status: { idx: 'statusIndex', uri: 'https://valid.status.url' }, + object: object, + }; + + const sdVCClaimsDisclosureFrame: DisclosureFrame = { object: { _sd: fields } }; + + const result = await issuer.createVCSDJWT(vcClaims, payload, sdVCClaimsDisclosureFrame); + + return ctx.pass(result); + }, +); + +/** + * @internal + */ +export const presentVcSdJwt = p.new( + ['verifier_url', 'issued_vc', 'disclosed', 'nonce', 'holder'], + 'present vc sd jwt', + async (ctx) => { + const verifierUrl = ctx.fetch('verifier_url'); + if (typeof verifierUrl !== 'string') return ctx.fail('verifier_url must be string'); + const issuedVc = ctx.fetch('issued_vc'); + if (typeof issuedVc !== 'string') return ctx.fail('issued_vc must be string'); + const nonce = ctx.fetch('nonce') as string; + if (typeof nonce !== 'string') return ctx.fail('nonce must be string'); + // TODO: typecheck disclosed + const disclosed = ctx.fetch('disclosed') as JsonableObject[]; + // TODO: typecheck holderSk + const holderSk = ctx.fetch('holder') as JsonableObject; + + const pk = await importJWK(holderSk); + const disclosureList = []; + + const tildeTokens = issuedVc.split('~'); + + for (let i = 1; i < tildeTokens.length; ++i) { + const encoded = tildeTokens[i]; + if (!encoded) continue; + const disclose = JSON.parse(atob(encoded)); + if (!disclosed.includes(disclose[1])) continue; + disclosureList.push({ + key: disclose[1], + value: disclose[2], + disclosure: encoded, + }); } - disclosureList.push({ - key: disclose[1], - value: disclose[2], - disclosure: encoded, - }) - } - const signer: SignerConfig = { - alg: supportedAlgorithm.ES256, - callback: signerCallbackFn(pk), - }; - const holder = new Holder(signer); + const signer: SignerConfig = { + alg: supportedAlgorithm.ES256, + callback: signerCallbackFn(pk), + }; + const holder = new Holder(signer); + + const { vcSDJWTWithkeyBindingJWT } = await holder.presentVCSDJWT(issuedVc, disclosureList, { + nonce: nonce, + audience: verifierUrl, + keyBindingVerifyCallbackFn: keyBindingVerifierCallbackFn(), + }); + return ctx.pass(vcSDJWTWithkeyBindingJWT); + }, +); + +/** + * @internal + */ +export const verifyVcSdJwt = p.new( + ['verifier_url', 'issued_vc', 'nonce', 'issuer'], + 'verify vc sd jwt', + async (ctx) => { + const verifierUrl = ctx.fetch('verifier_url'); + if (typeof verifierUrl !== 'string') return ctx.fail('verifier_url must be string'); + const issuedVc = ctx.fetch('issued_vc'); + if (typeof issuedVc !== 'string') return ctx.fail('issued_vc must be string'); + const nonce = ctx.fetch('nonce'); + if (typeof nonce !== 'string') return ctx.fail('nonce must be string'); + // TODO: typecheck issuer + const issuer = ctx.fetch('issuer') as JsonableObject; + const issuerPubKey = await importJWK(issuer); + + const verifier = new Verifier(); + + const result = await verifier.verifyVCSDJWT( + issuedVc, + verifierCallbackFn(issuerPubKey), + hasherCallbackFn(defaultHashAlgorithm), + kbVerifierCallbackFn(verifierUrl, nonce), + ); + return ctx.pass(result as JsonableObject); + }, +); - const { vcSDJWTWithkeyBindingJWT } = await holder.presentVCSDJWT(issuedVc, disclosureList, { - nonce: nonce, - audience: verifierUrl, - keyBindingVerifyCallbackFn: keyBindingVerifierCallbackFn(), - }); - return ctx.pass(vcSDJWTWithkeyBindingJWT); -} - -const verifyVCSDJWT = async (ctx: PluginContext): Promise => { - const verifierUrl = ctx.fetch('verifier url') as string - const issuedVc = ctx.fetch('issued vc') as string - const nonce = ctx.fetch('nonce') as string - const issuer = ctx.fetch('issuer') as JsonableObject - const issuerPubKey = await importJWK(issuer) - - const verifier = new Verifier(); - - const result = await verifier.verifyVCSDJWT( - issuedVc, - verifierCallbackFn(issuerPubKey), - hasherCallbackFn(defaultHashAlgorithm), - kbVeriferCallbackFn(verifierUrl, nonce), - ); - return ctx.pass(result as JsonableObject) -} - -const keyGen = async (ctx: PluginContext) : Promise => { +/** + * @internal + */ +export const keyGen = p.new('create p-256 key', async (ctx) => { // Elliptic Curve Digital Signature Algorithm with the P-256 curve and the SHA-256 hash function const keyPair = await generateKeyPair(supportedAlgorithm.ES256); - const sk = await exportJWK(keyPair.privateKey) + const sk = await exportJWK(keyPair.privateKey); return ctx.pass({ - kty: sk.kty || "EC", - crv: sk.crv || "P-256", - x: sk.x || "", - y: sk.y || "", - d: sk.d || "" - }) -} + kty: sk.kty || 'EC', + crv: sk.crv || 'P-256', + x: sk.x || '', + y: sk.y || '', + d: sk.d || '', + }); +}); -const pubGen = async (ctx: PluginContext) : Promise => { - const sk = ctx.fetch('sk') as JsonableObject +/** + * @internal + */ +export const pubGen = p.new(['sk'], 'create p-256 public key', async (ctx) => { + // TODO: typecheck sk + const sk = ctx.fetch('sk') as JsonableObject; return ctx.pass({ - kty: (sk['kty'] as string) || "", - x: (sk['x'] as string) || "", - y: (sk['y'] as string) || "", - crv: (sk['crv'] as string) || "", - }) -} - -const prettyPrintSdJwt = async (ctx: PluginContext) : Promise => { - const jwt = ctx.fetch('token') as string; - const tokens = jwt.split("~"); - const result = [] + kty: (sk['kty'] as string) || '', + x: (sk['x'] as string) || '', + y: (sk['y'] as string) || '', + crv: (sk['crv'] as string) || '', + }); +}); + +export const prettyPrintSdJwt = p.new(['token'], 'pretty print sd jwt', async (ctx) => { + const jwt = ctx.fetch('token'); + if (typeof jwt !== 'string') return ctx.fail('token must be string'); + const tokens = jwt.split('~'); + const res = []; const tryDecode = (s: string) => { try { return JSON.parse(atob(s)); } catch (e) { return s; } - } - for(let i=0; i { - switch (ctx.phrase) { - case 'create vc sd jwt': - return await createVCSDJWT(ctx); - case 'present vc sd jwt': - return await presentVCSDJWT(ctx); - case 'verify vc sd jwt': - return await verifyVCSDJWT(ctx); - case 'create p-256 key': - return await keyGen(ctx); - case 'create p-256 public key': - return await pubGen(ctx); - case 'pretty print sd jwt': - return await prettyPrintSdJwt(ctx); - default: - return ctx.fail('no match'); - } - }, -}; + return ctx.pass(res); +}); + +export const wallet = p; diff --git a/pkg/wallet/test/e2e.ts b/pkg/wallet/test/e2e.ts index d61aa2f9..3d234869 100644 --- a/pkg/wallet/test/e2e.ts +++ b/pkg/wallet/test/e2e.ts @@ -21,7 +21,7 @@ Given I have a 'string' named 'vcsdjwt' Given I have a 'string dictionary' named 'pretty_jwt' Then print data `; - const nonce = 'nIdBbNgRqCXBl8YOkfVdg==' + const nonce = 'nIdBbNgRqCXBl8YOkfVdg=='; const slangroom = new Slangroom(wallet); const res = await slangroom.execute(scriptCreate, { keys: { @@ -29,35 +29,34 @@ Then print data name: 'test person', age: 25, }, - fields: ['name', 'age'] - } + fields: ['name', 'age'], + }, }); t.truthy(res.result['vcsdjwt']); t.is((res.result['pretty_jwt'] as JsonableArray).length, 3); - const scriptPrepare = ` Rule unknown ignore -Given I send verifier url 'verifier_url' and send issued vc 'issued_vc' and send disclosed 'disclosed' and send nonce 'nonce' and send holder 'holder_jwk' and present vc sd jwt and output into 'presentation' +Given I send verifier_url 'verifier_url' and send issued_vc 'issued_vc' and send disclosed 'disclosed' and send nonce 'nonce' and send holder 'holder_jwk' and present vc sd jwt and output into 'presentation' Given I have a 'string' named 'presentation' Then print data `; const res2 = await slangroom.execute(scriptPrepare, { keys: { - disclosed: ["name"], + disclosed: ['name'], nonce, verifier_url: 'https://valid.verifier.url', holder_jwk: res.result['holder_jwk'] || {}, - issued_vc: res.result['vcsdjwt'] || "", - } + issued_vc: res.result['vcsdjwt'] || '', + }, }); t.truthy(res2.result['presentation']); const scriptVerify = ` Rule unknown ignore -Given I send verifier url 'verifier_url' and send issued vc 'presentation' and send nonce 'nonce' and send issuer 'issuer_jwk' and verify vc sd jwt and output into 'verification' +Given I send verifier_url 'verifier_url' and send issued_vc 'presentation' and send nonce 'nonce' and send issuer 'issuer_jwk' and verify vc sd jwt and output into 'verification' Given I have a 'string dictionary' named 'verification' Then print data `; @@ -66,9 +65,8 @@ Then print data nonce, verifier_url: 'https://valid.verifier.url', issuer_jwk: res.result['issuer_public_jwk'] || {}, - presentation: res2.result['presentation'] || "", - } + presentation: res2.result['presentation'] || '', + }, }); t.truthy(res3.result['verification']); - }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da666f95..ce3fa169 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -16,10 +16,10 @@ importers: version: 20.9.0 '@typescript-eslint/eslint-plugin': specifier: ^6.10.0 - version: 6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2) + version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/parser': specifier: ^6.10.0 - version: 6.10.0(eslint@8.53.0)(typescript@5.2.2) + version: 6.11.0(eslint@8.53.0)(typescript@5.2.2) ava: specifier: ^5.3.1 version: 5.3.1 @@ -40,7 +40,7 @@ importers: version: 13.3.8 prettier: specifier: ^3.0.3 - version: 3.0.3 + version: 3.1.0 ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@20.9.0)(typescript@5.2.2) @@ -92,7 +92,7 @@ importers: version: 10.5.0 zenroom: specifier: ^4.0.0 - version: 4.0.0 + version: 4.2.1 pkg/ethereum: dependencies: @@ -112,6 +112,27 @@ importers: specifier: ^2.0.3 version: 2.0.3 + pkg/fs: + dependencies: + '@slangroom/core': + specifier: workspace:* + version: link:../core + axios: + specifier: ^1.5.1 + version: 1.5.1 + extract-zip: + specifier: ^2.0.1 + version: 2.0.1 + + pkg/git: + dependencies: + '@slangroom/core': + specifier: workspace:* + version: link:../core + isomorphic-git: + specifier: ^1.24.5 + version: 1.25.0 + pkg/http: dependencies: '@slangroom/core': @@ -158,7 +179,7 @@ importers: version: 5.0.0 jose: specifier: ^5.1.0 - version: 5.1.0 + version: 5.1.1 packages: @@ -638,6 +659,11 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true + /@eslint-community/regexpp@4.5.1: + resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + /@eslint/eslintrc@2.1.3: resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -645,7 +671,7 @@ packages: ajv: 6.12.6 debug: 4.3.4 espree: 9.6.1 - globals: 13.23.0 + globals: 13.20.0 ignore: 5.2.4 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -712,20 +738,29 @@ packages: engines: {node: '>=8'} dev: true + /@jridgewell/resolve-uri@3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + dev: true + /@jridgewell/resolve-uri@3.1.1: resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} dev: true + /@jridgewell/sourcemap-codec@1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + dev: true + /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} dev: true - /@jridgewell/trace-mapping@0.3.20: - resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} + /@jridgewell/trace-mapping@0.3.18: + resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} dependencies: - '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 dev: true /@jridgewell/trace-mapping@0.3.9: @@ -780,7 +815,7 @@ packages: resolution: {integrity: sha512-WFyDgjuHHj5dC8WYA4w1hoMyUL8pnb7eFNMVYgPYzXi1L8yoQPpvJr02wOWl464XJbhhfr8Q8tMdMHI7YUSbMQ==} engines: {node: '>=16.15.0', npm: '>=8.5.0'} dependencies: - '@npmcli/run-script': 7.0.2 + '@npmcli/run-script': 7.0.1 chalk: 5.3.0 clone-deep: 4.0.1 config-chain: 1.1.13 @@ -790,7 +825,7 @@ packages: fs-extra: 11.1.1 glob-parent: 6.0.2 globby: 13.2.2 - inquirer: 9.2.12 + inquirer: 9.2.11 is-ci: 3.0.1 json5: 2.2.3 load-json-file: 7.0.1 @@ -805,7 +840,7 @@ packages: strong-log-transformer: 2.1.0 write-file-atomic: 5.0.1 write-json-file: 5.0.0 - write-pkg: 6.0.1 + write-pkg: 6.0.0 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -833,7 +868,7 @@ packages: '@lerna-lite/cli': 2.6.0(@lerna-lite/publish@2.6.0)(@lerna-lite/version@2.6.0)(typescript@5.2.2) '@lerna-lite/core': 2.6.0(typescript@5.2.2) '@lerna-lite/version': 2.6.0(@lerna-lite/publish@2.6.0)(typescript@5.2.2) - '@npmcli/arborist': 7.2.1 + '@npmcli/arborist': 7.2.0 byte-size: 8.1.1 chalk: 5.3.0 columnify: 1.6.0 @@ -885,7 +920,7 @@ packages: dedent: 1.5.1 fs-extra: 11.1.1 get-stream: 8.0.1 - git-url-parse: 13.1.1 + git-url-parse: 13.1.0 graceful-fs: 4.2.11 is-stream: 3.0.0 load-json-file: 7.0.1 @@ -916,11 +951,9 @@ packages: - typescript dev: true - /@ljharb/through@2.3.11: - resolution: {integrity: sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w==} + /@ljharb/through@2.3.9: + resolution: {integrity: sha512-yN599ZBuMPPK4tdoToLlvgJB4CLK8fGl7ntfy0Wn7U6ttNvHYurd81bfUiK/6sMkiIwm65R6ck4L6+Y3DfVbNQ==} engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 dev: true /@meeco/sd-jwt-vc@0.0.4: @@ -974,14 +1007,14 @@ packages: agent-base: 7.1.0 http-proxy-agent: 7.0.0 https-proxy-agent: 7.0.2 - lru-cache: 10.0.2 + lru-cache: 10.0.1 socks-proxy-agent: 8.0.2 transitivePeerDependencies: - supports-color dev: true - /@npmcli/arborist@7.2.1: - resolution: {integrity: sha512-o1QIAX56FC8HEPF+Hf4V4/hck9j0a3UiLnMX4aDHPbtU4Po1tUOUSmc2GAx947VWT+acrdMYTDkqUt2CaSXt7A==} + /@npmcli/arborist@7.2.0: + resolution: {integrity: sha512-J6XCan+5nV6F94E0+9z//OnZADcqHw6HGDO0ynX+Ayd6GEopK0odq99V+UQjb8P1zexTmCWGvxp4jU5OM6NTtg==} engines: {node: ^16.14.0 || >=18.0.0} hasBin: true dependencies: @@ -994,8 +1027,8 @@ packages: '@npmcli/node-gyp': 3.0.0 '@npmcli/package-json': 5.0.0 '@npmcli/query': 3.0.1 - '@npmcli/run-script': 7.0.2 - bin-links: 4.0.3 + '@npmcli/run-script': 7.0.1 + bin-links: 4.0.2 cacache: 18.0.0 common-ancestor-path: 1.0.1 hosted-git-info: 7.0.1 @@ -1035,7 +1068,7 @@ packages: engines: {node: ^16.14.0 || >=18.0.0} dependencies: '@npmcli/promise-spawn': 7.0.0 - lru-cache: 10.0.2 + lru-cache: 10.0.1 npm-pick-manifest: 9.0.0 proc-log: 3.0.0 promise-inflight: 1.0.1 @@ -1117,13 +1150,13 @@ packages: postcss-selector-parser: 6.0.13 dev: true - /@npmcli/run-script@7.0.2: - resolution: {integrity: sha512-Omu0rpA8WXvcGeY6DDzyRoY1i5DkCBkzyJ+m2u7PD6quzb0TvSqdIPOkTn8ZBOj7LbbcbMfZ3c5skwSu6m8y2w==} + /@npmcli/run-script@7.0.1: + resolution: {integrity: sha512-Od/JMrgkjZ8alyBE0IzeqZDiF1jgMez9Gkc/OYrCkHHiXNwM0wc6s7+h+xM7kYDZkS0tAoOLr9VvygyE5+2F7g==} engines: {node: ^16.14.0 || >=18.0.0} dependencies: '@npmcli/node-gyp': 3.0.0 '@npmcli/promise-spawn': 7.0.0 - node-gyp: 10.0.1 + node-gyp: 9.4.0 read-package-json-fast: 3.0.2 which: 4.0.0 transitivePeerDependencies: @@ -1145,7 +1178,7 @@ packages: '@octokit/request-error': 3.0.3 '@octokit/types': 9.3.2 before-after-hook: 2.2.3 - universal-user-agent: 6.0.1 + universal-user-agent: 6.0.0 transitivePeerDependencies: - encoding dev: true @@ -1156,7 +1189,7 @@ packages: dependencies: '@octokit/types': 9.3.2 is-plain-object: 5.0.0 - universal-user-agent: 6.0.1 + universal-user-agent: 6.0.0 dev: true /@octokit/graphql@5.0.6: @@ -1165,7 +1198,7 @@ packages: dependencies: '@octokit/request': 6.2.8 '@octokit/types': 9.3.2 - universal-user-agent: 6.0.1 + universal-user-agent: 6.0.0 transitivePeerDependencies: - encoding dev: true @@ -1225,7 +1258,7 @@ packages: '@octokit/types': 9.3.2 is-plain-object: 5.0.0 node-fetch: 2.7.0 - universal-user-agent: 6.0.1 + universal-user-agent: 6.0.0 transitivePeerDependencies: - encoding dev: true @@ -1304,8 +1337,8 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true - /@sigstore/sign@2.2.0: - resolution: {integrity: sha512-AAbmnEHDQv6CSfrWA5wXslGtzLPtAtHZleKOgxdQYvx/s76Fk6T6ZVt7w2IGV9j1UrFeBocTTQxaXG2oRrDhYA==} + /@sigstore/sign@2.1.0: + resolution: {integrity: sha512-4VRpfJxs+8eLqzLVrZngVNExVA/zAhVbi4UT4zmtLi4xRd7vz5qie834OgkrGsLlLB1B2nz/3wUxT1XAUBe8gw==} engines: {node: ^16.14.0 || >=18.0.0} dependencies: '@sigstore/bundle': 2.1.0 @@ -1325,6 +1358,11 @@ packages: - supports-color dev: true + /@tootallnate/once@2.0.0: + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + dev: true + /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} dev: true @@ -1354,16 +1392,16 @@ packages: minimatch: 9.0.3 dev: true - /@types/istanbul-lib-coverage@2.0.5: - resolution: {integrity: sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==} + /@types/istanbul-lib-coverage@2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} dev: true - /@types/json-schema@7.0.15: - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + /@types/json-schema@7.0.12: + resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} dev: true - /@types/minimist@1.2.5: - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + /@types/minimist@1.2.3: + resolution: {integrity: sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==} dev: true /@types/node@20.3.1: @@ -1375,12 +1413,12 @@ packages: dependencies: undici-types: 5.26.5 - /@types/normalize-package-data@2.4.4: - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + /@types/normalize-package-data@2.4.2: + resolution: {integrity: sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==} dev: true - /@types/semver@7.5.5: - resolution: {integrity: sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==} + /@types/semver@7.5.0: + resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} dev: true /@types/ws@8.5.3: @@ -1389,8 +1427,16 @@ packages: '@types/node': 20.9.0 dev: false - /@typescript-eslint/eslint-plugin@6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2): - resolution: {integrity: sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==} + /@types/yauzl@2.10.3: + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + requiresBuild: true + dependencies: + '@types/node': 20.9.0 + dev: false + optional: true + + /@typescript-eslint/eslint-plugin@6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha @@ -1400,12 +1446,12 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 6.10.0(eslint@8.53.0)(typescript@5.2.2) - '@typescript-eslint/scope-manager': 6.10.0 - '@typescript-eslint/type-utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) - '@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.10.0 + '@eslint-community/regexpp': 4.5.1 + '@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 6.11.0 + '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.11.0 debug: 4.3.4 eslint: 8.53.0 graphemer: 1.4.0 @@ -1418,8 +1464,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.10.0(eslint@8.53.0)(typescript@5.2.2): - resolution: {integrity: sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==} + /@typescript-eslint/parser@6.11.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -1428,10 +1474,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.10.0 - '@typescript-eslint/types': 6.10.0 - '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.10.0 + '@typescript-eslint/scope-manager': 6.11.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.11.0 debug: 4.3.4 eslint: 8.53.0 typescript: 5.2.2 @@ -1439,16 +1485,16 @@ packages: - supports-color dev: true - /@typescript-eslint/scope-manager@6.10.0: - resolution: {integrity: sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==} + /@typescript-eslint/scope-manager@6.11.0: + resolution: {integrity: sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.10.0 - '@typescript-eslint/visitor-keys': 6.10.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/visitor-keys': 6.11.0 dev: true - /@typescript-eslint/type-utils@6.10.0(eslint@8.53.0)(typescript@5.2.2): - resolution: {integrity: sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==} + /@typescript-eslint/type-utils@6.11.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -1457,8 +1503,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) - '@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2) + '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2) debug: 4.3.4 eslint: 8.53.0 ts-api-utils: 1.0.3(typescript@5.2.2) @@ -1467,13 +1513,13 @@ packages: - supports-color dev: true - /@typescript-eslint/types@6.10.0: - resolution: {integrity: sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==} + /@typescript-eslint/types@6.11.0: + resolution: {integrity: sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==} engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@6.10.0(typescript@5.2.2): - resolution: {integrity: sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==} + /@typescript-eslint/typescript-estree@6.11.0(typescript@5.2.2): + resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -1481,8 +1527,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.10.0 - '@typescript-eslint/visitor-keys': 6.10.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/visitor-keys': 6.11.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -1493,18 +1539,18 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@6.10.0(eslint@8.53.0)(typescript@5.2.2): - resolution: {integrity: sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==} + /@typescript-eslint/utils@6.11.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.5 - '@typescript-eslint/scope-manager': 6.10.0 - '@typescript-eslint/types': 6.10.0 - '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 6.11.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2) eslint: 8.53.0 semver: 7.5.4 transitivePeerDependencies: @@ -1512,12 +1558,12 @@ packages: - typescript dev: true - /@typescript-eslint/visitor-keys@6.10.0: - resolution: {integrity: sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==} + /@typescript-eslint/visitor-keys@6.11.0: + resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.10.0 - eslint-visitor-keys: 3.4.3 + '@typescript-eslint/types': 6.11.0 + eslint-visitor-keys: 3.4.1 dev: true /@ungap/structured-clone@1.2.0: @@ -1532,6 +1578,10 @@ packages: through: 2.3.8 dev: true + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: true + /abbrev@2.0.0: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1556,21 +1606,21 @@ packages: event-target-shim: 5.0.1 dev: true - /acorn-jsx@5.3.2(acorn@8.11.2): + /acorn-jsx@5.3.2(acorn@8.9.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.11.2 + acorn: 8.9.0 dev: true - /acorn-walk@8.3.0: - resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} dev: true - /acorn@8.11.2: - resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} + /acorn@8.9.0: + resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -1579,6 +1629,15 @@ packages: resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} dev: true + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /agent-base@7.1.0: resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} engines: {node: '>= 14'} @@ -1588,6 +1647,13 @@ packages: - supports-color dev: true + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: true + /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -1630,8 +1696,8 @@ packages: engines: {node: '>=12'} dev: true - /ansi-sequence-parser@1.1.1: - resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==} + /ansi-sequence-parser@1.1.0: + resolution: {integrity: sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==} dev: true /ansi-styles@3.2.1: @@ -1665,6 +1731,14 @@ packages: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} dev: true + /are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + dev: true + /are-we-there-yet@4.0.1: resolution: {integrity: sha512-2zuA+jpOYBRgoBCfa+fB87Rk0oGJjDX6pxGzqH6f33NzUhG25Xur6R0u0Z9VVAq8Z5JvQpQI6j6rtonuivC8QA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1716,6 +1790,10 @@ packages: engines: {node: '>=12'} dev: true + /async-lock@1.4.0: + resolution: {integrity: sha512-coglx5yIWuetakm3/1dsX9hxCNox22h7+V80RQOu2XUUMidtArxKoZoOtHUPuR84SycKTXzgGzAUR5hJxujyJQ==} + dev: false + /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: false @@ -1730,17 +1808,17 @@ packages: '@ava/typescript': optional: true dependencies: - acorn: 8.11.2 - acorn-walk: 8.3.0 + acorn: 8.9.0 + acorn-walk: 8.2.0 ansi-styles: 6.2.1 arrgv: 1.0.2 arrify: 3.0.0 - callsites: 4.1.0 + callsites: 4.0.0 cbor: 8.1.0 chalk: 5.3.0 chokidar: 3.5.3 chunkd: 2.0.1 - ci-info: 3.9.0 + ci-info: 3.8.0 ci-parallel-vars: 1.0.1 clean-yaml-object: 0.1.0 cli-truncate: 3.1.0 @@ -1808,11 +1886,11 @@ packages: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} dev: true - /bin-links@4.0.3: - resolution: {integrity: sha512-obsRaULtJurnfox/MDwgq6Yo9kzbv1CPTk/1/s7Z/61Lezc8IKkFCOXNeVLXz0456WRzBQmSsDWlai2tIhBsfA==} + /bin-links@4.0.2: + resolution: {integrity: sha512-jxJ0PbXR8eQyPlExCvCs3JFnikvs1Yp4gUJt6nmgathdOwvur+q22KWC3h20gvWl4T/14DXKj2IlkJwwZkZPOw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - cmd-shim: 6.0.2 + cmd-shim: 6.0.1 npm-normalize-package-bin: 3.0.1 read-cmd-shim: 4.0.0 write-file-atomic: 5.0.1 @@ -1861,6 +1939,10 @@ packages: base-x: 4.0.0 dev: false + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: false + /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: true @@ -1899,16 +1981,34 @@ packages: '@istanbuljs/schema': 0.1.3 find-up: 5.0.0 foreground-child: 2.0.0 - istanbul-lib-coverage: 3.2.1 + istanbul-lib-coverage: 3.2.0 istanbul-lib-report: 3.0.1 istanbul-reports: 3.1.6 rimraf: 3.0.2 test-exclude: 6.0.0 - v8-to-istanbul: 9.1.3 + v8-to-istanbul: 9.1.0 yargs: 17.7.2 yargs-parser: 21.1.1 dev: true + /cacache@17.1.4: + resolution: {integrity: sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + '@npmcli/fs': 3.1.0 + fs-minipass: 3.0.3 + glob: 10.3.10 + lru-cache: 7.18.3 + minipass: 7.0.4 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + p-map: 4.0.0 + ssri: 10.0.5 + tar: 6.2.0 + unique-filename: 3.0.0 + dev: true + /cacache@18.0.0: resolution: {integrity: sha512-I7mVOPl3PUCeRub1U8YoGz2Lqv9WOBpobZ8RyWFXmReuILz+3OAyTa5oH3QPdtKZD7N0Yk00aLfzn0qvp8dZ1w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -1916,7 +2016,7 @@ packages: '@npmcli/fs': 3.1.0 fs-minipass: 3.0.3 glob: 10.3.10 - lru-cache: 10.0.2 + lru-cache: 10.0.1 minipass: 7.0.4 minipass-collect: 1.0.2 minipass-flush: 1.0.5 @@ -1933,14 +2033,15 @@ packages: function-bind: 1.1.2 get-intrinsic: 1.2.2 set-function-length: 1.1.1 + dev: false /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} dev: true - /callsites@4.1.0: - resolution: {integrity: sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw==} + /callsites@4.0.0: + resolution: {integrity: sha512-y3jRROutgpKdz5vzEhWM34TidDU8vkJppF8dszITeb1PQmSqV3DTxyV8G/lyO/DNvtE1YTedehmw9MPZsCBHxQ==} engines: {node: '>=12.20'} dev: true @@ -2014,7 +2115,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.3 + fsevents: 2.3.2 dev: true /chownr@2.0.0: @@ -2026,8 +2127,8 @@ packages: resolution: {integrity: sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==} dev: true - /ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + /ci-info@3.8.0: + resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} dev: true @@ -2035,6 +2136,10 @@ packages: resolution: {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==} dev: true + /clean-git-ref@2.0.1: + resolution: {integrity: sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==} + dev: false + /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -2108,8 +2213,8 @@ packages: engines: {node: '>=0.8'} dev: true - /cmd-shim@6.0.2: - resolution: {integrity: sha512-+FFYbB0YLaAkhkcrjkyNLYDiOsFSfRjwjY19LXk/psmMx1z00xlCv7hhQoTGXXIKi+YXHL/iiFo8NqMVQX9nOw==} + /cmd-shim@6.0.1: + resolution: {integrity: sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true @@ -2200,7 +2305,7 @@ packages: js-string-escape: 1.0.1 lodash: 4.17.21 md5-hex: 3.0.1 - semver: 7.5.4 + semver: 7.5.2 well-known-symbols: 2.0.0 dev: true @@ -2302,8 +2407,8 @@ packages: meow: 8.1.2 dev: true - /convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: true /convert-to-spaces@2.0.1: @@ -2402,7 +2507,6 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: true /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} @@ -2417,6 +2521,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + dev: false + /dedent@1.5.1: resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} peerDependencies: @@ -2448,6 +2559,7 @@ packages: get-intrinsic: 1.2.2 gopd: 1.0.1 has-property-descriptors: 1.0.1 + dev: false /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -2467,6 +2579,10 @@ packages: engines: {node: '>=12.20'} dev: true + /diff3@0.0.3: + resolution: {integrity: sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==} + dev: false + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -2527,6 +2643,12 @@ packages: dev: true optional: true + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: false + /env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -2644,6 +2766,11 @@ packages: estraverse: 5.3.0 dev: true + /eslint-visitor-keys@3.4.1: + resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2677,7 +2804,7 @@ packages: file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.23.0 + globals: 13.20.0 graphemer: 1.4.0 ignore: 5.2.4 imurmurhash: 0.1.4 @@ -2700,8 +2827,8 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.11.2 - acorn-jsx: 5.3.2(acorn@8.11.2) + acorn: 8.9.0 + acorn-jsx: 5.3.2(acorn@8.9.0) eslint-visitor-keys: 3.4.3 dev: true @@ -2786,6 +2913,20 @@ packages: tmp: 0.0.33 dev: true + /extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + dependencies: + debug: 4.3.4 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + dev: false + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -2794,8 +2935,8 @@ packages: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} dev: true - /fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2819,6 +2960,12 @@ packages: reusify: 1.0.4 dev: true + /fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + dependencies: + pend: 1.2.0 + dev: false + /fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -2839,7 +2986,7 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flat-cache: 3.2.0 + flat-cache: 3.0.4 dev: true /fill-range@7.0.1: @@ -2880,17 +3027,16 @@ packages: path-exists: 5.0.0 dev: true - /flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + /flat-cache@3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flatted: 3.2.9 - keyv: 4.5.4 + flatted: 3.2.7 rimraf: 3.0.2 dev: true - /flatted@3.2.9: - resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + /flatted@3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true /follow-redirects@1.15.3: @@ -2947,7 +3093,7 @@ packages: dependencies: graceful-fs: 4.2.11 jsonfile: 6.1.0 - universalify: 2.0.1 + universalify: 2.0.0 dev: true /fs-minipass@2.1.0: @@ -2976,17 +3122,23 @@ packages: dev: true optional: true - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + /gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + dev: true + /gauge@5.0.1: resolution: {integrity: sha512-CmykPMJGuNan/3S4kZOpvvPYSNqSHANiWnh9XcMU2pSjtBfF0XzZ2p1bFAxTbnFxyBuPxQYHhzwaoOmUdqzvxQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -3013,6 +3165,7 @@ packages: has-proto: 1.0.1 has-symbols: 1.0.3 hasown: 2.0.0 + dev: false /get-pkg-repo@4.2.1: resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} @@ -3025,6 +3178,13 @@ packages: yargs: 16.2.0 dev: true + /get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + dependencies: + pump: 3.0.0 + dev: false + /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -3064,8 +3224,8 @@ packages: parse-url: 8.1.0 dev: true - /git-url-parse@13.1.1: - resolution: {integrity: sha512-PCFJyeSSdtnbfhSNRw9Wk96dDCNx+sogTe4YNXeXSJxt7xz5hvXekuRn9JX7m+Mf4OscCu8h+mtAl3+h5Fo8lQ==} + /git-url-parse@13.1.0: + resolution: {integrity: sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==} dependencies: git-up: 7.0.0 dev: true @@ -3113,8 +3273,8 @@ packages: path-is-absolute: 1.0.1 dev: true - /globals@13.23.0: - resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} + /globals@13.20.0: + resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 @@ -3126,7 +3286,7 @@ packages: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.2 + fast-glob: 3.3.1 ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 @@ -3137,7 +3297,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: dir-glob: 3.0.1 - fast-glob: 3.3.2 + fast-glob: 3.3.1 ignore: 5.2.4 merge2: 1.4.1 slash: 4.0.0 @@ -3147,6 +3307,7 @@ packages: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.2 + dev: false /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -3188,14 +3349,17 @@ packages: resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} dependencies: get-intrinsic: 1.2.2 + dev: false /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} + dev: false /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + dev: false /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} @@ -3208,11 +3372,19 @@ packages: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} dev: true + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.2 + dev: true + /hasown@2.0.0: resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 + dev: false /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -3229,7 +3401,7 @@ packages: resolution: {integrity: sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==} engines: {node: ^16.14.0 || >=18.0.0} dependencies: - lru-cache: 10.0.2 + lru-cache: 10.0.1 dev: true /html-escaper@2.0.2: @@ -3240,6 +3412,17 @@ packages: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} dev: true + /http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /http-proxy-agent@7.0.0: resolution: {integrity: sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==} engines: {node: '>= 14'} @@ -3250,6 +3433,16 @@ packages: - supports-color dev: true + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /https-proxy-agent@7.0.2: resolution: {integrity: sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==} engines: {node: '>= 14'} @@ -3265,6 +3458,12 @@ packages: engines: {node: '>=16.17.0'} dev: true + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: true + /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -3275,6 +3474,7 @@ packages: /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + requiresBuild: true dependencies: safer-buffer: 2.1.2 dev: true @@ -3299,7 +3499,6 @@ packages: /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} - dev: true /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -3347,11 +3546,11 @@ packages: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: true - /inquirer@9.2.12: - resolution: {integrity: sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==} + /inquirer@9.2.11: + resolution: {integrity: sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==} engines: {node: '>=14.18.0'} dependencies: - '@ljharb/through': 2.3.11 + '@ljharb/through': 2.3.9 ansi-escapes: 4.3.2 chalk: 5.3.0 cli-cursor: 3.1.0 @@ -3405,13 +3604,13 @@ packages: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true dependencies: - ci-info: 3.9.0 + ci-info: 3.8.0 dev: true - /is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: - hasown: 2.0.0 + has: 1.0.3 dev: true /is-error@2.2.2: @@ -3561,6 +3760,24 @@ packages: engines: {node: '>=0.10.0'} dev: true + /isomorphic-git@1.25.0: + resolution: {integrity: sha512-F8X7z74gL+jN4bd6qB6a3Z0QQzonWPkiQ3nK/oFWlrc2pIwVM9Uksl3YMFh99ltswsqoCoOthgasybX08/fiGg==} + engines: {node: '>=12'} + hasBin: true + dependencies: + async-lock: 1.4.0 + clean-git-ref: 2.0.1 + crc-32: 1.2.2 + diff3: 0.0.3 + ignore: 5.2.4 + minimisted: 2.0.1 + pako: 1.0.11 + pify: 4.0.1 + readable-stream: 3.6.2 + sha.js: 2.4.11 + simple-get: 4.0.1 + dev: false + /isomorphic-ws@5.0.0(ws@8.14.2): resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} peerDependencies: @@ -3569,8 +3786,8 @@ packages: ws: 8.14.2 dev: false - /istanbul-lib-coverage@3.2.1: - resolution: {integrity: sha512-opCrKqbthmq3SKZ10mFMQG9dk3fTa3quaOLD35kJa5ejwZHd9xAr+kLuziiZz2cG32s4lMZxNdmdcEQnTDP4+g==} + /istanbul-lib-coverage@3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} engines: {node: '>=8'} dev: true @@ -3578,7 +3795,7 @@ packages: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} dependencies: - istanbul-lib-coverage: 3.2.1 + istanbul-lib-coverage: 3.2.0 make-dir: 4.0.0 supports-color: 7.2.0 dev: true @@ -3600,8 +3817,8 @@ packages: '@pkgjs/parseargs': 0.11.0 dev: true - /jose@5.1.0: - resolution: {integrity: sha512-H+RVqxA6apaJ0rcQYupKYhos7uosAiF42gUcWZiwhICWMphDULFj/CRr1R0tV/JCv9DEeJaSyYYpc9luHHNT4g==} + /jose@5.1.1: + resolution: {integrity: sha512-bfB+lNxowY49LfrBO0ITUn93JbUhxUN8I11K6oI5hJu/G6PO6fEUddVLjqdD0cQ9SXIHWXuWh7eJYwZF7Z0N/g==} dev: false /js-string-escape@1.0.1: @@ -3628,10 +3845,6 @@ packages: argparse: 2.0.1 dev: true - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true - /json-parse-better-errors@1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} dev: true @@ -3674,7 +3887,7 @@ packages: /jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: - universalify: 2.0.1 + universalify: 2.0.0 optionalDependencies: graceful-fs: 4.2.11 dev: true @@ -3692,12 +3905,6 @@ packages: resolution: {integrity: sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==} dev: true - /keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - dependencies: - json-buffer: 3.0.1 - dev: true - /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -3725,7 +3932,7 @@ packages: resolution: {integrity: sha512-w5Ev46SnPaEpjfa0a5+p2vYSB19nONF/mRX8RcIRp2gpPxMWldFVZy/fXei/uflMLQq33mjKMqiVoNcz6ZJCYg==} engines: {node: ^16.14.0 || >=18.0.0} dependencies: - ci-info: 3.9.0 + ci-info: 3.8.0 normalize-package-data: 6.0.0 npm-package-arg: 11.0.1 npm-registry-fetch: 16.1.0 @@ -3741,8 +3948,8 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /lines-and-columns@2.0.4: - resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} + /lines-and-columns@2.0.3: + resolution: {integrity: sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true @@ -3809,11 +4016,9 @@ packages: is-unicode-supported: 0.1.0 dev: true - /lru-cache@10.0.2: - resolution: {integrity: sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==} + /lru-cache@10.0.1: + resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} engines: {node: 14 || >=16.14} - dependencies: - semver: 7.5.4 dev: true /lru-cache@6.0.0: @@ -3823,6 +4028,11 @@ packages: yallist: 4.0.0 dev: true + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: true + /lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} dev: true @@ -3838,6 +4048,29 @@ packages: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true + /make-fetch-happen@11.1.1: + resolution: {integrity: sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + agentkeepalive: 4.5.0 + cacache: 17.1.4 + http-cache-semantics: 4.1.1 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 5.0.0 + minipass-fetch: 3.0.4 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.3 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 10.0.5 + transitivePeerDependencies: + - supports-color + dev: true + /make-fetch-happen@13.0.0: resolution: {integrity: sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==} engines: {node: ^16.14.0 || >=18.0.0} @@ -3911,7 +4144,7 @@ packages: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} dependencies: - '@types/minimist': 1.2.5 + '@types/minimist': 1.2.3 camelcase-keys: 6.2.2 decamelize-keys: 1.1.1 hard-rejection: 2.1.0 @@ -3963,6 +4196,11 @@ packages: engines: {node: '>=12'} dev: true + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + dev: false + /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -3992,7 +4230,12 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true + + /minimisted@2.0.1: + resolution: {integrity: sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==} + dependencies: + minimist: 1.2.8 + dev: false /minipass-collect@1.0.2: resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} @@ -4078,7 +4321,6 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -4145,21 +4387,22 @@ packages: formdata-polyfill: 4.0.10 dev: true - /node-gyp@10.0.1: - resolution: {integrity: sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==} - engines: {node: ^16.14.0 || >=18.0.0} + /node-gyp@9.4.0: + resolution: {integrity: sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg==} + engines: {node: ^12.13 || ^14.13 || >=16} hasBin: true dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.1 - glob: 10.3.10 + glob: 7.2.3 graceful-fs: 4.2.11 - make-fetch-happen: 13.0.0 - nopt: 7.2.0 - proc-log: 3.0.0 + make-fetch-happen: 11.1.1 + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 semver: 7.5.4 tar: 6.2.0 - which: 4.0.0 + which: 2.0.2 transitivePeerDependencies: - supports-color dev: true @@ -4169,6 +4412,14 @@ packages: engines: {node: '>=12.19'} dev: true + /nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: true + /nopt@7.2.0: resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -4181,7 +4432,7 @@ packages: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.8 + resolve: 1.22.6 semver: 5.7.2 validate-npm-package-license: 3.0.4 dev: true @@ -4191,7 +4442,7 @@ packages: engines: {node: '>=10'} dependencies: hosted-git-info: 4.1.0 - is-core-module: 2.13.1 + is-core-module: 2.13.0 semver: 7.5.4 validate-npm-package-license: 3.0.4 dev: true @@ -4201,7 +4452,7 @@ packages: engines: {node: ^16.14.0 || >=18.0.0} dependencies: hosted-git-info: 7.0.1 - is-core-module: 2.13.1 + is-core-module: 2.13.0 semver: 7.5.4 validate-npm-package-license: 3.0.4 dev: true @@ -4279,6 +4530,16 @@ packages: path-key: 4.0.0 dev: true + /npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + dev: true + /npmlog@7.0.1: resolution: {integrity: sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -4293,7 +4554,6 @@ packages: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 - dev: true /onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} @@ -4469,7 +4729,7 @@ packages: '@npmcli/git': 5.0.3 '@npmcli/installed-package-contents': 2.0.2 '@npmcli/promise-spawn': 7.0.0 - '@npmcli/run-script': 7.0.2 + '@npmcli/run-script': 7.0.1 cacache: 18.0.0 fs-minipass: 3.0.3 minipass: 7.0.4 @@ -4489,6 +4749,10 @@ packages: - supports-color dev: true + /pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: false + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -4523,14 +4787,14 @@ packages: lines-and-columns: 1.2.4 dev: true - /parse-json@7.1.1: - resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} + /parse-json@7.1.0: + resolution: {integrity: sha512-ihtdrgbqdONYD156Ap6qTcaGcGdkdAxodO1wLqQ/j7HP1u2sFYppINiq4jyC8F+Nm+4fVufylCV00QmkTHkSUg==} engines: {node: '>=16'} dependencies: '@babel/code-frame': 7.22.13 error-ex: 1.3.2 json-parse-even-better-errors: 3.0.0 - lines-and-columns: 2.0.4 + lines-and-columns: 2.0.3 type-fest: 3.13.1 dev: true @@ -4589,7 +4853,7 @@ packages: resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} engines: {node: '>=16 || 14 >=14.17'} dependencies: - lru-cache: 10.0.2 + lru-cache: 10.0.1 minipass: 7.0.4 dev: true @@ -4605,6 +4869,10 @@ packages: engines: {node: '>=8'} dev: true + /pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + dev: false + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -4620,6 +4888,11 @@ packages: engines: {node: '>=4'} dev: true + /pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + dev: false + /pify@6.1.0: resolution: {integrity: sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==} engines: {node: '>=14.16'} @@ -4676,8 +4949,8 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier@3.0.3: - resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} + /prettier@3.1.0: + resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==} engines: {node: '>=14'} hasBin: true dev: true @@ -4745,8 +5018,15 @@ packages: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + /pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: false + + /punycode@2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} dev: true @@ -4812,7 +5092,7 @@ packages: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} dependencies: - '@types/normalize-package-data': 2.4.4 + '@types/normalize-package-data': 2.4.2 normalize-package-data: 2.5.0 parse-json: 5.2.0 type-fest: 0.6.0 @@ -4822,10 +5102,10 @@ packages: resolution: {integrity: sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==} engines: {node: '>=16'} dependencies: - '@types/normalize-package-data': 2.4.4 + '@types/normalize-package-data': 2.4.2 normalize-package-data: 6.0.0 - parse-json: 7.1.1 - type-fest: 4.7.1 + parse-json: 7.1.0 + type-fest: 4.4.0 dev: true /readable-stream@2.3.8: @@ -4847,7 +5127,6 @@ packages: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - dev: true /readable-stream@4.4.2: resolution: {integrity: sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==} @@ -4901,11 +5180,11 @@ packages: engines: {node: '>=8'} dev: true - /resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + /resolve@1.22.6: + resolution: {integrity: sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==} hasBin: true dependencies: - is-core-module: 2.13.1 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true @@ -4958,7 +5237,6 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: true /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -4969,6 +5247,14 @@ packages: hasBin: true dev: true + /semver@7.5.2: + resolution: {integrity: sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + /semver@7.5.4: resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} @@ -4996,11 +5282,20 @@ packages: get-intrinsic: 1.2.2 gopd: 1.0.1 has-property-descriptors: 1.0.1 + dev: false /setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} dev: false + /sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: false + /shallow-clone@3.0.1: resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} engines: {node: '>=8'} @@ -5020,10 +5315,10 @@ packages: engines: {node: '>=8'} dev: true - /shiki@0.14.5: - resolution: {integrity: sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==} + /shiki@0.14.2: + resolution: {integrity: sha512-ltSZlSLOuSY0M0Y75KA+ieRaZ0Trf5Wl3gutE7jzLuIcWxLp5i/uEnLoQWNvgKXQ5OMpGkJnVMRLAuzjc0LJ2A==} dependencies: - ansi-sequence-parser: 1.1.1 + ansi-sequence-parser: 1.1.0 jsonc-parser: 3.2.0 vscode-oniguruma: 1.7.0 vscode-textmate: 8.0.0 @@ -5044,12 +5339,24 @@ packages: dependencies: '@sigstore/bundle': 2.1.0 '@sigstore/protobuf-specs': 0.2.1 - '@sigstore/sign': 2.2.0 + '@sigstore/sign': 2.1.0 '@sigstore/tuf': 2.2.0 transitivePeerDependencies: - supports-color dev: true + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: false + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: false + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -5078,6 +5385,17 @@ packages: engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} dev: true + /socks-proxy-agent@7.0.0: + resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} + engines: {node: '>= 10'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + socks: 2.7.1 + transitivePeerDependencies: + - supports-color + dev: true + /socks-proxy-agent@8.0.2: resolution: {integrity: sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==} engines: {node: '>= 14'} @@ -5194,7 +5512,6 @@ packages: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 - dev: true /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} @@ -5383,8 +5700,8 @@ packages: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 20.9.0 - acorn: 8.11.2 - acorn-walk: 8.3.0 + acorn: 8.9.0 + acorn-walk: 8.2.0 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 @@ -5456,8 +5773,8 @@ packages: engines: {node: '>=14.16'} dev: true - /type-fest@4.7.1: - resolution: {integrity: sha512-iWr8RUmzAJRfhZugX9O7nZE6pCxDU8CZ3QxsLuTnGcBLJpCaP2ll3s4eMTBoFnU/CeXY/5rfQSuAEsTGJO4y8A==} + /type-fest@4.4.0: + resolution: {integrity: sha512-HT3RRs7sTfY22KuPQJkD/XjbTbxgP2Je5HPt6H6JEGvcjHd5Lqru75EbrP3tb4FYjNJ+DjLp+MNQTFQU0mhXNw==} engines: {node: '>=16'} dev: true @@ -5481,7 +5798,7 @@ packages: lunr: 2.3.9 marked: 4.3.0 minimatch: 9.0.3 - shiki: 0.14.5 + shiki: 0.14.2 typescript: 5.2.2 dev: true @@ -5515,24 +5832,23 @@ packages: imurmurhash: 0.1.4 dev: true - /universal-user-agent@6.0.1: - resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + /universal-user-agent@6.0.0: + resolution: {integrity: sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==} dev: true - /universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + /universalify@2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} dev: true /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.3.1 + punycode: 2.3.0 dev: true /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true /util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} @@ -5553,13 +5869,13 @@ packages: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true - /v8-to-istanbul@9.1.3: - resolution: {integrity: sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==} + /v8-to-istanbul@9.1.0: + resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.20 - '@types/istanbul-lib-coverage': 2.0.5 - convert-source-map: 2.0.0 + '@jridgewell/trace-mapping': 0.3.18 + '@types/istanbul-lib-coverage': 2.0.4 + convert-source-map: 1.9.0 dev: true /validate-npm-package-license@3.0.4: @@ -5599,15 +5915,15 @@ packages: engines: {node: '>= 8'} dev: true - /web3-core@4.3.1: - resolution: {integrity: sha512-xa3w5n/ESxp5HIbrwsYBhpAPx2KI5LprjRFEtRwP0GpqqhTcCSMMYoyItRqQQ+k9YnB0PoFpWJfJI6Qn5x8YUQ==} + /web3-core@4.3.0: + resolution: {integrity: sha512-//cy1W780nkMXd/9g2+GIa7KlHMuE+PJPhPD4NdwpUxvtQni6GkXSxtFnImZufyGVP+BpO5g7QneiSeu5ce+IQ==} engines: {node: '>=14', npm: '>=6.12.0'} dependencies: - web3-errors: 1.1.4 + web3-errors: 1.1.3 web3-eth-iban: 4.0.7 web3-providers-http: 4.1.0 web3-providers-ws: 4.0.7 - web3-types: 1.3.1 + web3-types: 1.3.0 web3-utils: 4.0.7 web3-validator: 2.0.3 optionalDependencies: @@ -5618,11 +5934,11 @@ packages: - utf-8-validate dev: false - /web3-errors@1.1.4: - resolution: {integrity: sha512-WahtszSqILez+83AxGecVroyZsMuuRT+KmQp4Si5P4Rnqbczno1k748PCrZTS1J4UCPmXMG2/Vt+0Bz2zwXkwQ==} + /web3-errors@1.1.3: + resolution: {integrity: sha512-3GA4leG6XsKLmFWe62mIjVgW4GhkJmvd4IaRLgnKtNnqNmx6L9YWysYwgQ09BaD/NmhKN+AtalSVRds8gU+N0w==} engines: {node: '>=14', npm: '>=6.12.0'} dependencies: - web3-types: 1.3.1 + web3-types: 1.3.0 dev: false /web3-eth-abi@4.1.4(typescript@5.2.2): @@ -5630,8 +5946,8 @@ packages: engines: {node: '>=14', npm: '>=6.12.0'} dependencies: abitype: 0.7.1(typescript@5.2.2) - web3-errors: 1.1.4 - web3-types: 1.3.1 + web3-errors: 1.1.3 + web3-types: 1.3.0 web3-utils: 4.0.7 web3-validator: 2.0.3 transitivePeerDependencies: @@ -5646,21 +5962,21 @@ packages: '@ethereumjs/rlp': 4.0.1 crc-32: 1.2.2 ethereum-cryptography: 2.1.2 - web3-errors: 1.1.4 - web3-types: 1.3.1 + web3-errors: 1.1.3 + web3-types: 1.3.0 web3-utils: 4.0.7 web3-validator: 2.0.3 dev: false - /web3-eth-contract@4.1.3(typescript@5.2.2): - resolution: {integrity: sha512-F6e3eyetUDiNOb78EDVJtNOb0H1GPz3xAZH8edSfYdhaxI9tTutP2V3p++kh2ZJ/RrdE2+xil7H/nPLgHymBvg==} + /web3-eth-contract@4.1.2(typescript@5.2.2): + resolution: {integrity: sha512-kVcMIurY4buNOfVhuE1Gg8NmSo5EmAOgBwECwU6lE8xUkbWAC6nhJGX1fgZjoC075HnSr89G1zgwlePyEdC0wQ==} engines: {node: '>=14', npm: '>=6.12.0'} dependencies: - web3-core: 4.3.1 - web3-errors: 1.1.4 + web3-core: 4.3.0 + web3-errors: 1.1.3 web3-eth: 4.3.1(typescript@5.2.2) web3-eth-abi: 4.1.4(typescript@5.2.2) - web3-types: 1.3.1 + web3-types: 1.3.0 web3-utils: 4.0.7 web3-validator: 2.0.3 transitivePeerDependencies: @@ -5676,12 +5992,12 @@ packages: engines: {node: '>=14', npm: '>=6.12.0'} dependencies: '@adraffy/ens-normalize': 1.10.0 - web3-core: 4.3.1 - web3-errors: 1.1.4 + web3-core: 4.3.0 + web3-errors: 1.1.3 web3-eth: 4.3.1(typescript@5.2.2) - web3-eth-contract: 4.1.3(typescript@5.2.2) + web3-eth-contract: 4.1.2(typescript@5.2.2) web3-net: 4.0.7 - web3-types: 1.3.1 + web3-types: 1.3.0 web3-utils: 4.0.7 web3-validator: 2.0.3 transitivePeerDependencies: @@ -5696,8 +6012,8 @@ packages: resolution: {integrity: sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ==} engines: {node: '>=14', npm: '>=6.12.0'} dependencies: - web3-errors: 1.1.4 - web3-types: 1.3.1 + web3-errors: 1.1.3 + web3-types: 1.3.0 web3-utils: 4.0.7 web3-validator: 2.0.3 dev: false @@ -5706,10 +6022,10 @@ packages: resolution: {integrity: sha512-sXeyLKJ7ddQdMxz1BZkAwImjqh7OmKxhXoBNF3isDmD4QDpMIwv/t237S3q4Z0sZQamPa/pHebJRWVuvP8jZdw==} engines: {node: '>=14', npm: '>=6.12.0'} dependencies: - web3-core: 4.3.1 + web3-core: 4.3.0 web3-eth: 4.3.1(typescript@5.2.2) web3-rpc-methods: 1.1.3 - web3-types: 1.3.1 + web3-types: 1.3.0 web3-utils: 4.0.7 web3-validator: 2.0.3 transitivePeerDependencies: @@ -5725,14 +6041,14 @@ packages: engines: {node: '>=14', npm: '>=6.12.0'} dependencies: setimmediate: 1.0.5 - web3-core: 4.3.1 - web3-errors: 1.1.4 + web3-core: 4.3.0 + web3-errors: 1.1.3 web3-eth-abi: 4.1.4(typescript@5.2.2) web3-eth-accounts: 4.1.0 web3-net: 4.0.7 web3-providers-ws: 4.0.7 web3-rpc-methods: 1.1.3 - web3-types: 1.3.1 + web3-types: 1.3.0 web3-utils: 4.0.7 web3-validator: 2.0.3 transitivePeerDependencies: @@ -5747,9 +6063,9 @@ packages: resolution: {integrity: sha512-SzEaXFrBjY25iQGk5myaOfO9ZyfTwQEa4l4Ps4HDNVMibgZji3WPzpjq8zomVHMwi8bRp6VV7YS71eEsX7zLow==} engines: {node: '>=14', npm: '>=6.12.0'} dependencies: - web3-core: 4.3.1 + web3-core: 4.3.0 web3-rpc-methods: 1.1.3 - web3-types: 1.3.1 + web3-types: 1.3.0 web3-utils: 4.0.7 transitivePeerDependencies: - bufferutil @@ -5762,8 +6078,8 @@ packages: engines: {node: '>=14', npm: '>=6.12.0'} dependencies: cross-fetch: 4.0.0 - web3-errors: 1.1.4 - web3-types: 1.3.1 + web3-errors: 1.1.3 + web3-types: 1.3.0 web3-utils: 4.0.7 transitivePeerDependencies: - encoding @@ -5774,8 +6090,8 @@ packages: engines: {node: '>=14', npm: '>=6.12.0'} requiresBuild: true dependencies: - web3-errors: 1.1.4 - web3-types: 1.3.1 + web3-errors: 1.1.3 + web3-types: 1.3.0 web3-utils: 4.0.7 dev: false optional: true @@ -5786,8 +6102,8 @@ packages: dependencies: '@types/ws': 8.5.3 isomorphic-ws: 5.0.0(ws@8.14.2) - web3-errors: 1.1.4 - web3-types: 1.3.1 + web3-errors: 1.1.3 + web3-types: 1.3.0 web3-utils: 4.0.7 ws: 8.14.2 transitivePeerDependencies: @@ -5799,8 +6115,8 @@ packages: resolution: {integrity: sha512-XB6SsCZZPdZUMPIRqDxJkZFKMu0/Y+yaExAt+Z7RqmuM7xF55fJ/Qb84LQho8zarvUoYziy4jnIfs+SXImxQUw==} engines: {node: '>=14', npm: '>=6.12.0'} dependencies: - web3-core: 4.3.1 - web3-types: 1.3.1 + web3-core: 4.3.0 + web3-types: 1.3.0 web3-validator: 2.0.3 transitivePeerDependencies: - bufferutil @@ -5808,8 +6124,8 @@ packages: - utf-8-validate dev: false - /web3-types@1.3.1: - resolution: {integrity: sha512-8fXi7h/t95VKRtgU4sxprLPZpsTh3jYDfSghshIDBgUD/OoGe5S+syP24SUzBZYllZ/L+hMr2gdp/0bGJa8pYQ==} + /web3-types@1.3.0: + resolution: {integrity: sha512-ReRq6D0w6Mr6PkC8mtn6JwBgbVAobPFYNWFO994C7LzTNweYQegb0peri5KMpEHQm2iG2tQbiIyAAeseIohc2Q==} engines: {node: '>=14', npm: '>=6.12.0'} dev: false @@ -5818,8 +6134,8 @@ packages: engines: {node: '>=14', npm: '>=6.12.0'} dependencies: ethereum-cryptography: 2.1.2 - web3-errors: 1.1.4 - web3-types: 1.3.1 + web3-errors: 1.1.3 + web3-types: 1.3.0 web3-validator: 2.0.3 dev: false @@ -5829,8 +6145,8 @@ packages: dependencies: ethereum-cryptography: 2.1.2 util: 0.12.5 - web3-errors: 1.1.4 - web3-types: 1.3.1 + web3-errors: 1.1.3 + web3-types: 1.3.0 zod: 3.22.4 dev: false @@ -5838,12 +6154,12 @@ packages: resolution: {integrity: sha512-zSB+Ds1lSMu/IhAX0xKhiFI7ZA1BQ6y2WOqFE9ikqPjaMkpOEBBkl61nzWfLJRoerTB1ohEXAP20jLDXcFd4hQ==} engines: {node: '>=14.0.0', npm: '>=6.12.0'} dependencies: - web3-core: 4.3.1 - web3-errors: 1.1.4 + web3-core: 4.3.0 + web3-errors: 1.1.3 web3-eth: 4.3.1(typescript@5.2.2) web3-eth-abi: 4.1.4(typescript@5.2.2) web3-eth-accounts: 4.1.0 - web3-eth-contract: 4.1.3(typescript@5.2.2) + web3-eth-contract: 4.1.2(typescript@5.2.2) web3-eth-ens: 4.0.8(typescript@5.2.2) web3-eth-iban: 4.0.7 web3-eth-personal: 4.0.8(typescript@5.2.2) @@ -5851,7 +6167,7 @@ packages: web3-providers-http: 4.1.0 web3-providers-ws: 4.0.7 web3-rpc-methods: 1.1.3 - web3-types: 1.3.1 + web3-types: 1.3.0 web3-utils: 4.0.7 web3-validator: 2.0.3 transitivePeerDependencies: @@ -5942,7 +6258,6 @@ packages: /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true /write-file-atomic@3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} @@ -5971,14 +6286,14 @@ packages: write-file-atomic: 3.0.3 dev: true - /write-pkg@6.0.1: - resolution: {integrity: sha512-ZwKp0+CQCNrJbhHStRy6IVDnVjvD4gYy6MhQLKgBnl85oaiTNXhvtuox7AqvOSf1wta0YW4U5JidjpJnd1i8TA==} + /write-pkg@6.0.0: + resolution: {integrity: sha512-lGAH18qfqlukADIiFz1khQQO+AfPcNKf+oJIktIADWISarSSG9MPoWmveT+GhTGh9nQLpw0iPZyucgbqDngHeQ==} engines: {node: '>=16'} dependencies: deepmerge-ts: 5.1.0 read-pkg: 8.1.0 sort-keys: 5.0.0 - type-fest: 4.7.1 + type-fest: 3.13.1 write-json-file: 5.0.0 dev: true @@ -6052,6 +6367,13 @@ packages: requiresBuild: true dev: false + /yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + dev: false + /yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -6067,8 +6389,8 @@ packages: engines: {node: '>=12.20'} dev: true - /zenroom@4.0.0: - resolution: {integrity: sha512-RRRwdJ7k4QKTB7XCLpmHpU9fPBwxjIX69SUiJVvwq1AX3xSUCFQLHOArMZu8PN7lYM5B4dwiihkiE9ZCR3YfNA==} + /zenroom@4.2.1: + resolution: {integrity: sha512-P7ls3m1o/PWE3l2Ug4lLANfMchsqn+byyiG404SdmzXe7yANVs6PRup6OF8pn/EVq/9NSufvw1ZkOmdqCMfE6w==} dependencies: yarn: 1.22.19 dev: false