diff --git a/bin/heroku-repl.js b/bin/heroku-repl.js index 93eee08493..eb23df612f 100644 --- a/bin/heroku-repl.js +++ b/bin/heroku-repl.js @@ -1,15 +1,16 @@ +import {run, ux} from '@oclif/core' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' // do not use the older node:readline module // else things will break -const readline = require('node:readline/promises') -const yargs = require('yargs-parser') -const util = require('util') -const path = require('node:path') -const fs = require('node:fs') -const {ux, run} = require('@oclif/core') -const os = require('node:os') +import readline from 'node:readline/promises' +import util from 'node:util' +import shellQuote from 'shell-quote' +import yargs from 'yargs-parser' + const historyFile = path.join(process.env.HOME || process.env.USERPROFILE || os.homedir(), '.heroku_repl_history') const stateFile = path.join(process.env.HOME || process.env.USERPROFILE || os.homedir(), '.heroku_repl_state') -const shellQuote = require('shell-quote') const maxHistory = 1000 const mcpMode = process.env.HEROKU_MCP_MODE === 'true' @@ -25,16 +26,16 @@ const mcpMode = process.env.HEROKU_MCP_MODE === 'true' * heroku > spaces:create --team */ const completionCommandByName = new Map([ - ['app', ['apps', ['--all', '--json']]], - ['org', ['orgs', ['--json']]], - ['team', ['teams', ['--json']]], - ['space', ['spaces', ['--json']]], - ['pipeline', ['pipelines', ['--json']]], ['addon', ['addons', ['--json']]], + ['app', ['apps', ['--all', '--json']]], ['domain', ['domains', ['--json']]], ['dyno', ['ps', ['--json']]], + ['org', ['orgs', ['--json']]], + ['pipeline', ['pipelines', ['--json']]], ['release', ['releases', ['--json']]], + ['space', ['spaces', ['--json']]], ['stack', ['apps:stacks', ['--json']]], + ['team', ['teams', ['--json']]], ]) /** @@ -56,12 +57,6 @@ class HerokuRepl { */ #config - /** - * A map of key/value pairs used for - * the 'set' and 'unset' command - */ - #setValues = new Map() - /** * The history of the REPL commands used */ @@ -72,109 +67,6 @@ class HerokuRepl { */ #historyStream - /** - * The readline interface used for the REPL - */ - #rl - - /** - * Constructs a new instance of the HerokuRepl class. - * - * @param {Config} config The oclif core config object - */ - constructor(config) { - this.#createInterface() - this.#config = config - } - - /** - * Prepares the REPL history by loading - * the previous history from the history file - * and opening a write stream for new entries. - * - * @returns {Promise} a promise that resolves when the history has been loaded - */ - #prepareHistory() { - this.#historyStream = fs.createWriteStream(historyFile, { - flags: 'a', - encoding: 'utf8', - }) - - // Load existing history first - if (fs.existsSync(historyFile)) { - this.#history = fs.readFileSync(historyFile, 'utf8') - .split('\n') - .filter(line => line.trim()) - .reverse() - .splice(0, maxHistory) - } - } - - /** - * Loads the previous session state from the state file. - * @returns {void} - */ - #loadState() { - if (fs.existsSync(stateFile)) { - try { - const state = JSON.parse(fs.readFileSync(stateFile, 'utf8')) - for (const entry of Object.entries(state)) { - this.#updateFlagsByName('set', entry, true) - } - - process.stdout.write('session restored') - } catch { - // noop - } - } - } - - /** - * Creates a new readline interface. - * - * @returns {readline.Interface} the readline interface - */ - #createInterface() { - this.#rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - prompt: 'heroku > ', - removeHistoryDuplicates: true, - historySize: maxHistory, - completer: async line => { - if (mcpMode) { - return [[], line] - } - - // Use shell-quote to tokenize the line for robust parsing - const tokens = shellQuote.parse(line) - const stringTokens = tokens.filter(t => typeof t === 'string') - const [command = '', ...parts] = stringTokens - if (command === 'set') { - return this.#buildSetCompletions(parts) - } - - const commandMeta = this.#config.findCommand(command) - if (!commandMeta) { - const matches = this.#config.commands.filter(({id}) => id.startsWith(command)) - return [matches.map(({id}) => id).sort(), line] - } - - return this.#buildCompletions(commandMeta, parts, line) - }, - }) - this.#prepareHistory() - this.#loadState() - - this.#rl.history.push(...this.#history) - this.#rl.on('line', this.#processLine) - this.#rl.once('close', () => { - this.#historyStream?.close() - fs.writeFileSync(stateFile, JSON.stringify(Object.fromEntries(this.#setValues)), 'utf8') - }) - this.#rl.prompt() - } - /** * Processes the line received from the terminal stdin * @@ -195,8 +87,8 @@ class HerokuRepl { // flag/arg extraction const {_: [command, ...positionalArgs], ...flags} = yargs(stringTokens, { configuration: { - 'camel-case-expansion': false, 'boolean-negation': false, + 'camel-case-expansion': false, }, }) const args = Object.entries(flags).flatMap(([key, value]) => { @@ -208,6 +100,7 @@ class HerokuRepl { }).concat(positionalArgs) if (command === 'exit') { + // eslint-disable-next-line n/no-process-exit process.exit(0) } @@ -279,55 +172,33 @@ class HerokuRepl { } /** - * Updates the session state based on the command and args. - * - * @param {'set'|'unset'} command either 'set' or 'unset' - * @param {string[]} args an array of arg names - * @param {boolean} omitConfirmation when false. no confirmation is printed to stdout - * @returns {void} + * The readline interface used for the REPL */ - #updateFlagsByName(command, args, omitConfirmation) { - if (command === 'set') { - const [key, value] = args - if (key && value) { - this.#setValues.set(key, value) - - if (!omitConfirmation) { - process.stdout.write(`setting --${key} to ${value}\n`) - } - - if (key === 'app') { - this.#rl.setPrompt(`${value} > `) - } - } else { - const values = [] - for (const [flag, value] of this.#setValues) { - values.push({flag, value}) - } - - if (values.length === 0) { - return console.info('no flags set') - } - - ux.table(values, { - flag: {header: 'Flag'}, - value: {header: 'Value'}, - }) - } - } + #rl - if (command === 'unset') { - const [key] = args + /** + * A map of key/value pairs used for + * the 'set' and 'unset' command + */ + #setValues = new Map() - if (!omitConfirmation) { - process.stdout.write(`unsetting --${key}\n`) - } + /** + * Constructs a new instance of the HerokuRepl class. + * + * @param {Config} config The oclif core config object + */ + constructor(config) { + this.#createInterface() + this.#config = config + } - this.#setValues.delete(key) - if (key === 'app') { - this.#rl.setPrompt('heroku > ') - } - } + /** + * Starts the REPL by showing the prompt. + * + * @returns {void} + */ + start() { + this.#rl.prompt() } /** @@ -343,15 +214,16 @@ class HerokuRepl { */ async #buildCompletions(commandMeta, flagsOrArgs = [], line = '') { const {args, flags} = commandMeta - const {requiredInputs: requiredFlags, optionalInputs: optionalFlags} = this.#collectInputsFromManifest(flags) - const {requiredInputs: requiredArgs, optionalInputs: optionalArgs} = this.#collectInputsFromManifest(args) + const {optionalInputs: optionalFlags, requiredInputs: requiredFlags} = this.#collectInputsFromManifest(flags) + const {optionalInputs: optionalArgs, requiredInputs: requiredArgs} = this.#collectInputsFromManifest(args) const {_: userArgs, ...userFlags} = yargs(flagsOrArgs, { configuration: { - 'camel-case-expansion': false, 'boolean-negation': false, + 'camel-case-expansion': false, }, }) + // eslint-disable-next-line unicorn/prefer-at const current = flagsOrArgs[flagsOrArgs.length - 1] ?? '' // Order of precedence: @@ -362,11 +234,11 @@ class HerokuRepl { // 5. End of line // Flags *must* occur first since they may influence // the completions for args. - return await this.#getCompletionsForFlag(line, current, requiredFlags, userFlags, commandMeta) || - await this.#getCompletionsForArg(current, requiredArgs, userArgs) || - await this.#getCompletionsForFlag(line, current, optionalFlags, userFlags, commandMeta) || - await this.#getCompletionsForArg(current, optionalArgs, userArgs) || - this.#getCompletionsForEndOfLine(line, flags, userFlags) + return await this.#getCompletionsForFlag(line, current, requiredFlags, userFlags, commandMeta) + || await this.#getCompletionsForArg(current, requiredArgs, userArgs) + || await this.#getCompletionsForFlag(line, current, optionalFlags, userFlags, commandMeta) + || await this.#getCompletionsForArg(current, optionalArgs, userArgs) + || this.#getCompletionsForEndOfLine(line, flags, userFlags) } /** @@ -394,86 +266,144 @@ class HerokuRepl { } /** - * Get completions for the end of the line. + * Capture stdout by deflecting it to a + * trap function and returning the output. * - * @param {string} line the current line - * @param {Record} flags the flags for the command - * @param {Record} userFlags the flags that have already been used - * @returns {[string[], string]} the completions and the current input + * This is useful for silently capturing the output + * of a command that normally prints to stdout. + * + * @param {CallableFunction} fn the function to capture stdout for + * @returns {Promise} the output from stdout */ - #getCompletionsForEndOfLine(line, flags, userFlags) { - const flagKeys = Object.keys(userFlags) - // If there are no more flags to complete, - // return an empty array. - return flagKeys.length < Object.keys(flags).length ? [[line.endsWith(' ') ? '--' : ' --'], ''] : [[], ''] + async #captureStdout(fn) { + const output = [] + const originalWrite = process.stdout.write + // Replace stdout.write temporarily + process.stdout.write = chunk => { + output.push(typeof chunk === 'string' ? chunk : chunk.toString()) + return true + } + + try { + await fn() + return output.join('') + } finally { + // Restore original stdout + process.stdout.write = originalWrite + } } /** - * Get completions for a flag or flag value. + * Collect inputs from the command manifest and sorts + * them by type and then by required status. * - * @param {string} line the current line - * @param {string} current the current input - * @param {string[]} flags the flags for the command - * @param {string[]} userFlags the flags that have already been used - * @param {Record} commandMeta the metadata for the command - * @return {Promise<[string[], string]>} the completions and the current input + * @param {Record} commandMeta the metadata from the command manifest + * @returns {{requiredInputs: {long: string, short: string}[], optionalInputs: {long: string, short: string}[]}} the inputs from the command manifest */ - async #getCompletionsForFlag(line, current, flags, userFlags, commandMeta) { - const commandMetaWithCharKeys = {...commandMeta} - // make sure the commandMeta also contains keys for char fields - Object.keys(commandMeta.flags).forEach(key => { - const flag = commandMeta.flags[key] - if (flag.char) { - commandMetaWithCharKeys.flags[flag.char] = flag + #collectInputsFromManifest(commandMeta) { + const requiredInputs = [] + const optionalInputs = [] + + // Prioritize options over booleans + const keysByType = Object.keys(commandMeta).sort((a, b) => { + const {type: aType} = commandMeta[a] + const {type: bType} = commandMeta[b] + if (aType === bType) { + return 0 } - }) - // flag completion for long and short flags. - // flags that have already been used are - // not included in the completions. - const isFlag = current.startsWith('-') - const isLongFlag = current.startsWith('--') - if (isFlag) { - const rawFlag = isLongFlag ? current.slice(2) : current.slice(1) - const prop = isLongFlag ? 'long' : 'short' - const matched = flags - .filter(flag => { - return !Reflect.has(userFlags, flag.short) && !Reflect.has(userFlags, flag.long) && - (!rawFlag || flag[prop]?.startsWith(rawFlag)) - }) - .map(f => isLongFlag ? f.long : f.short) - .filter(Boolean) - if (matched?.length > 0) { - return [matched, rawFlag] + if (aType === 'option') { + return -1 } - } - // Does the flag have a value or is it - // expected to have a value? - const flagKeys = Object.keys(userFlags) - const flag = flagKeys[flagKeys.length - 1] - const isBooleanFlag = commandMetaWithCharKeys.flags[flag]?.type === 'boolean' - if (this.#isFlagValueComplete(line) || isBooleanFlag || current === '--' || current === '-') { - return null - } + if (bType === 'option') { + return 1 + } - const {options, type, name} = commandMetaWithCharKeys.flags[flag] ?? {} - // Options are defined in the metadata - // for the command. If the flag has options - // defined, we will attempt to complete - // based on the options. - if (type === 'option') { - if (options?.length > 0) { - const optionComplete = options.includes(current) - const matched = options.filter(o => o.startsWith(current)) + return 0 + }) + const includedFlags = new Set() + for (const key of keysByType) { + const {char: short, name: long, required: isRequired} = commandMeta[key] + if (includedFlags.has(long)) { + continue + } - if (!optionComplete) { - return matched.length > 0 ? [matched, current] : [options, current] - } + includedFlags.add(long) + if (isRequired) { + requiredInputs.push({long, short}) + continue } - return [await this.#getCompletion(name, isFlag ? '' : current), current] + optionalInputs.push({long, short}) } + + // Prioritize required inputs + // over optional inputs + // required inputs are sorted + // alphabetically. optional + // inputs are sorted alphabetically + // and then pushed to the end of + // the list. + requiredInputs.sort((a, b) => { + if (a.long < b.long) { + return -1 + } + + if (a.long > b.long) { + return 1 + } + + return 0 + }) + + return {optionalInputs, requiredInputs} + } + + /** + * Creates a new readline interface. + * + * @returns {readline.Interface} the readline interface + */ + #createInterface() { + this.#rl = readline.createInterface({ + completer: async line => { + if (mcpMode) { + return [[], line] + } + + // Use shell-quote to tokenize the line for robust parsing + const tokens = shellQuote.parse(line) + const stringTokens = tokens.filter(t => typeof t === 'string') + const [command = '', ...parts] = stringTokens + if (command === 'set') { + return this.#buildSetCompletions(parts) + } + + const commandMeta = this.#config.findCommand(command) + if (!commandMeta) { + const matches = this.#config.commands.filter(({id}) => id.startsWith(command)) + return [matches.map(({id}) => id).sort(), line] + } + + return this.#buildCompletions(commandMeta, parts, line) + }, + historySize: maxHistory, + input: process.stdin, + output: process.stdout, + prompt: 'heroku > ', + removeHistoryDuplicates: true, + }) + this.#prepareHistory() + this.#loadState() + + this.#rl.history.push(...this.#history) + this.#rl.on('line', this.#processLine) + this.#rl.once('close', () => { + this.#historyStream?.close() + fs.writeFileSync(stateFile, JSON.stringify(Object.fromEntries(this.#setValues)), 'utf8') + }) + this.#rl.prompt() } /** @@ -509,6 +439,115 @@ class HerokuRepl { } } + /** + * Get completions for an arg. + * + * @param {string} current the current input + * @param {({long: string}[])} args the args for the command + * @param {string[]} userArgs the args that have already been used + * @returns {Promise<[string[], string] | null>} the completions and the current input + */ + async #getCompletionsForArg(current, args = [], userArgs = []) { + if (userArgs.length <= args.length) { + const arg = args[userArgs.length] + if (arg) { + const {long} = arg + if (completionCommandByName.has(long)) { + const completions = await this.#getCompletion(long, current) + if (completions.length > 0) { + return [completions, current] + } + } + + return [[`<${long}>`], current] + } + } + + return null + } + + /** + * Get completions for the end of the line. + * + * @param {string} line the current line + * @param {Record} flags the flags for the command + * @param {Record} userFlags the flags that have already been used + * @returns {[string[], string]} the completions and the current input + */ + #getCompletionsForEndOfLine(line, flags, userFlags) { + const flagKeys = Object.keys(userFlags) + // If there are no more flags to complete, + // return an empty array. + return flagKeys.length < Object.keys(flags).length ? [[line.endsWith(' ') ? '--' : ' --'], ''] : [[], ''] + } + + /** + * Get completions for a flag or flag value. + * + * @param {string} line the current line + * @param {string} current the current input + * @param {string[]} flags the flags for the command + * @param {string[]} userFlags the flags that have already been used + * @param {Record} commandMeta the metadata for the command + * @return {Promise<[string[], string]>} the completions and the current input + */ + async #getCompletionsForFlag(line, current, flags, userFlags, commandMeta) { + const commandMetaWithCharKeys = {...commandMeta} + // make sure the commandMeta also contains keys for char fields + Object.keys(commandMeta.flags).forEach(key => { + const flag = commandMeta.flags[key] + if (flag.char) { + commandMetaWithCharKeys.flags[flag.char] = flag + } + }) + // flag completion for long and short flags. + // flags that have already been used are + // not included in the completions. + const isFlag = current.startsWith('-') + const isLongFlag = current.startsWith('--') + if (isFlag) { + const rawFlag = isLongFlag ? current.slice(2) : current.slice(1) + const prop = isLongFlag ? 'long' : 'short' + const matched = flags + .filter(flag => !Reflect.has(userFlags, flag.short) && !Reflect.has(userFlags, flag.long) + && (!rawFlag || flag[prop]?.startsWith(rawFlag))) + .map(f => isLongFlag ? f.long : f.short) + .filter(Boolean) + + if (matched?.length > 0) { + return [matched, rawFlag] + } + } + + // Does the flag have a value or is it + // expected to have a value? + const flagKeys = Object.keys(userFlags) + // eslint-disable-next-line unicorn/prefer-at + const flag = flagKeys[flagKeys.length - 1] + const isBooleanFlag = commandMetaWithCharKeys.flags[flag]?.type === 'boolean' + if (this.#isFlagValueComplete(line) || isBooleanFlag || current === '--' || current === '-') { + return null + } + + const {name, options, type} = commandMetaWithCharKeys.flags[flag] ?? {} + // Options are defined in the metadata + // for the command. If the flag has options + // defined, we will attempt to complete + // based on the options. + if (type === 'option') { + if (options?.length > 0) { + const optionComplete = options.includes(current) + const matched = options.filter(o => o.startsWith(current)) + + if (!optionComplete) { + return matched.length > 0 ? [matched, current] : [options, current] + } + } + + return [await this.#getCompletion(name, isFlag ? '' : current), current] + } + } + #isFlagValueComplete(input) { const tokens = shellQuote.parse(input.trim()) const len = tokens.length @@ -556,127 +595,100 @@ class HerokuRepl { } /** - * Capture stdout by deflecting it to a - * trap function and returning the output. - * - * This is useful for silently capturing the output - * of a command that normally prints to stdout. - * - * @param {CallableFunction} fn the function to capture stdout for - * @returns {Promise} the output from stdout + * Loads the previous session state from the state file. + * @returns {void} */ - async #captureStdout(fn) { - const output = [] - const originalWrite = process.stdout.write - // Replace stdout.write temporarily - process.stdout.write = chunk => { - output.push(typeof chunk === 'string' ? chunk : chunk.toString()) - return true - } + #loadState() { + if (fs.existsSync(stateFile)) { + try { + const state = JSON.parse(fs.readFileSync(stateFile, 'utf8')) + for (const entry of Object.entries(state)) { + this.#updateFlagsByName('set', entry, true) + } - try { - await fn() - return output.join('') - } finally { - // Restore original stdout - process.stdout.write = originalWrite + process.stdout.write('session restored') + } catch { + // noop + } } } /** - * Get completions for an arg. + * Prepares the REPL history by loading + * the previous history from the history file + * and opening a write stream for new entries. * - * @param {string} current the current input - * @param {({long: string}[])} args the args for the command - * @param {string[]} userArgs the args that have already been used - * @returns {Promise<[string[], string] | null>} the completions and the current input + * @returns {Promise} a promise that resolves when the history has been loaded */ - async #getCompletionsForArg(current, args = [], userArgs = []) { - if (userArgs.length <= args.length) { - const arg = args[userArgs.length] - if (arg) { - const {long} = arg - if (completionCommandByName.has(long)) { - const completions = await this.#getCompletion(long, current) - if (completions.length > 0) { - return [completions, current] - } - } + #prepareHistory() { + this.#historyStream = fs.createWriteStream(historyFile, { + encoding: 'utf8', + flags: 'a', + }) - return [[`<${long}>`], current] - } + // Load existing history first + if (fs.existsSync(historyFile)) { + this.#history = fs.readFileSync(historyFile, 'utf8') + .split('\n') + .filter(line => line.trim()) + .reverse() + .splice(0, maxHistory) } - - return null } /** - * Collect inputs from the command manifest and sorts - * them by type and then by required status. + * Updates the session state based on the command and args. * - * @param {Record} commandMeta the metadata from the command manifest - * @returns {{requiredInputs: {long: string, short: string}[], optionalInputs: {long: string, short: string}[]}} the inputs from the command manifest + * @param {'set'|'unset'} command either 'set' or 'unset' + * @param {string[]} args an array of arg names + * @param {boolean} omitConfirmation when false. no confirmation is printed to stdout + * @returns {void} */ - #collectInputsFromManifest(commandMeta) { - const requiredInputs = [] - const optionalInputs = [] - - // Prioritize options over booleans - const keysByType = Object.keys(commandMeta).sort((a, b) => { - const {type: aType} = commandMeta[a] - const {type: bType} = commandMeta[b] - if (aType === bType) { - return 0 - } + #updateFlagsByName(command, args, omitConfirmation) { + if (command === 'set') { + const [key, value] = args + if (key && value) { + this.#setValues.set(key, value) - if (aType === 'option') { - return -1 - } + if (!omitConfirmation) { + process.stdout.write(`setting --${key} to ${value}\n`) + } - if (bType === 'option') { - return 1 - } + if (key === 'app') { + this.#rl.setPrompt(`${value} > `) + } + } else { + const values = [] + for (const [flag, value] of this.#setValues) { + values.push({flag, value}) + } - return 0 - }) - const includedFlags = new Set() - for (const key of keysByType) { - const {required: isRequired, char: short, name: long} = commandMeta[key] - if (includedFlags.has(long)) { - continue - } + if (values.length === 0) { + return console.info('no flags set') + } - includedFlags.add(long) - if (isRequired) { - requiredInputs.push({long, short}) - continue + ux.table(values, { + flag: {header: 'Flag'}, + value: {header: 'Value'}, + }) } - - optionalInputs.push({long, short}) } - // Prioritize required inputs - // over optional inputs - // required inputs are sorted - // alphabetically. optional - // inputs are sorted alphabetically - // and then pushed to the end of - // the list. - requiredInputs.sort((a, b) => { - if (a.long < b.long) { - return -1 - } + if (command === 'unset') { + const [key] = args - if (a.long > b.long) { - return 1 + if (!omitConfirmation) { + process.stdout.write(`unsetting --${key}\n`) } - return 0 - }) - - return {requiredInputs, optionalInputs} + this.#setValues.delete(key) + if (key === 'app') { + this.#rl.setPrompt('heroku > ') + } + } } } -module.exports.herokuRepl = async function (config) { + +export async function herokuRepl(config) { return new HerokuRepl(config) } diff --git a/bin/run b/bin/run index 5a224a03ec..3807c5e3b6 100755 --- a/bin/run +++ b/bin/run @@ -1,8 +1,14 @@ #!/usr/bin/env node +/* eslint-disable n/no-process-exit */ /* eslint-disable n/no-unpublished-bin */ -import {execute} from '@oclif/core' +import {Config, execute} from '@oclif/core' +import {dirname} from 'node:path' +import {fileURLToPath} from 'node:url' +import yargs from 'yargs-parser' + import * as globalTelemetry from '../dist/global_telemetry.js' +import {herokuRepl} from './heroku-repl.js' process.env.HEROKU_UPDATE_INSTRUCTIONS = process.env.HEROKU_UPDATE_INSTRUCTIONS || 'update with: "npm update -g heroku"' @@ -42,4 +48,14 @@ process.on('SIGTERM', async () => { globalTelemetry.initializeInstrumentation() -await execute({dir: import.meta.url}) +// Check for --repl flag before executing +const parsedArgs = yargs(process.argv.slice(2)) +const {_: [commandName, ...args], ...flags} = parsedArgs +if (flags.repl && args.length === 0 && Object.keys(flags).length === 1) { + const __filename = fileURLToPath(import.meta.url) + const __dirname = dirname(__filename) + const config = await Config.load({root: dirname(__dirname)}) + await herokuRepl(config) +} else { + await execute({dir: import.meta.url}) +} diff --git a/package-lock.json b/package-lock.json index 7ea06f1b7d..ef162236ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2483,9 +2483,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -2518,9 +2518,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -3532,9 +3532,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -10006,9 +10006,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -12839,9 +12839,9 @@ } }, "node_modules/dotgitignore/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -13433,9 +13433,9 @@ } }, "node_modules/eslint-config-oclif-typescript/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -13483,9 +13483,9 @@ } }, "node_modules/eslint-config-oclif/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -13780,9 +13780,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -14145,9 +14145,9 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -14180,9 +14180,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -14629,9 +14629,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", + "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -14785,9 +14785,9 @@ } }, "node_modules/flat-cache/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -15470,15 +15470,15 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", - "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -15852,9 +15852,9 @@ } }, "node_modules/hono": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", - "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.2.tgz", + "integrity": "sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -17460,9 +17460,9 @@ } }, "node_modules/istanbul-lib-processinfo/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -18371,12 +18371,12 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -18385,6 +18385,27 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -18506,9 +18527,9 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", + "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", "dev": true, "license": "ISC", "dependencies": { @@ -21693,9 +21714,9 @@ } }, "node_modules/nyc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -23361,9 +23382,9 @@ } }, "node_modules/qqjs/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -25240,9 +25261,9 @@ "license": "ISC" }, "node_modules/spawn-wrap/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -25842,9 +25863,9 @@ } }, "node_modules/temp/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -25946,9 +25967,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": {