diff --git a/assistant/package.json b/assistant/package.json index 4ef92ad..b1d5c80 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -67,6 +67,7 @@ "nanoid": "^5.1.6", "send": "^1.2.1", "supports-color": "^10.2.2", + "table": "^6.9.0", "ws": "npm:@kevisual/ws" }, "engines": { diff --git a/assistant/src/command/app-manager/index.ts b/assistant/src/command/app-manager/index.ts index 0190b2a..d7a7585 100644 --- a/assistant/src/command/app-manager/index.ts +++ b/assistant/src/command/app-manager/index.ts @@ -1,6 +1,7 @@ import { AssistantApp } from '@/module/assistant/index.ts'; import { program, Command, assistantConfig } from '@/program.ts'; import { AppDownload } from '@/services/app/index.ts'; +import { table } from 'table'; const appManagerCommand = new Command('app-manager').alias('am').description('Manage Assistant Apps 管理本地的应用模块'); program.addCommand(appManagerCommand); @@ -8,11 +9,43 @@ program.addCommand(appManagerCommand); appManagerCommand .command('list') .description('List all installed apps') - .action(async () => { + .option('-s, --status ', '列出状态信息, 可选值: running, stopped, inactive') + .option('-w, --wide', '显示更多信息') + .action(async (opts) => { const manager = new AssistantApp(assistantConfig); await manager.loadConfig(); - const showInfos = manager.getAllAppShowInfo(); - console.log('Installed Apps:', showInfos); + let showInfos = manager.getAllAppShowInfo(); + const isWide = opts.wide ?? false; + let header = []; + if (!isWide) { + showInfos = showInfos.map((item) => { + return { key: item.key, status: item.status }; + }); + header = ['Key', 'Status']; + } + if (opts.status) { + const showList = showInfos.filter(info => info.status === opts.status); + if (showList.length === 0) { + console.log(`No apps with status: ${opts.status}`); + return; + } + const teables = showList.map(item => Object.values(item)); + teables.unshift(header); + console.log('App Start Info:\n') + console.log(table(teables)); + return + } + if (showInfos.length === 0) { + console.log('No installed apps found.'); + return; + } + header = Object.keys(showInfos[0]);; + const teables = showInfos.map(item => Object.values(item)); + teables.unshift(header); + + console.log('Installed Apps:\n') + console.log(table(teables)); + }); appManagerCommand diff --git a/assistant/src/command/run-scripts/index.ts b/assistant/src/command/run-scripts/index.ts index e51b9d6..ff994d0 100644 --- a/assistant/src/command/run-scripts/index.ts +++ b/assistant/src/command/run-scripts/index.ts @@ -1,15 +1,69 @@ import { program, Command, assistantConfig } from '@/program.ts'; import { spawnSync } from 'node:child_process'; +import path from 'node:path'; +import fs from 'node:fs'; +import { AssistantApp, checkFileExists } from '@/lib.ts'; +import { logger } from '@/module/logger.ts'; +import { LoadApp, StopApp } from '@/module/local-apps/src/modules/manager.ts'; + const runScriptsCommand = new Command('run-scripts') .alias('run') .arguments(' [env]') + .option('-l --local', '使用当前文件夹的package.json中的scripts', false) .description('运行脚本,在assistant.config.json中配置的脚本') - .action(async (cmd, env) => { + .action(async (cmd, env, opts) => { + const useLocal = opts.local; + const showScripts = cmd === 'show'; + const showScriptFunc = (scripts: any) => { + console.log('可用的本地脚本:'); + let has = false; + Object.keys(scripts).forEach((key) => { + console.log(`- ${key}: ${scripts[key]}`); + has = true; + }); + if (!has) { + console.log('当前未定义任何脚本。'); + } + } + if (useLocal) { + const pkgPath = path.join(process.cwd(), 'package.json'); + if (checkFileExists(pkgPath) === false) { + console.error('当前目录下未找到 package.json 文件。'); + return; + } + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + const scripts = pkg.scripts || {}; + if (showScripts) { + showScriptFunc(scripts); + return; + } + const script = scripts[cmd]; + if (!script) { + console.error(`Script "${cmd}" not found in local package.json.`); + return; + } + const command = [script, ...(env ? [env] : [])].join(' '); + const res = spawnSync(command, { shell: true, stdio: 'inherit', cwd: assistantConfig.configDir }); + console.log(`执行 "[${command}]"...`); + if (res.error) { + console.error(`执行失败 "${cmd}":`, res.error); + return; + } + if (res.status !== 0) { + console.error(`本地脚本 "${cmd}" 以代码 ${res.status} 退出`); + return; + } + return; + } assistantConfig.checkMounted(); const configs = assistantConfig.getCacheAssistantConfig(); const scripts = configs?.scripts || {}; try { const script = scripts[cmd]; + if (showScripts) { + showScriptFunc(scripts); + return; + } if (!script) { console.error(`Script "${cmd}" not found.`); return; @@ -32,3 +86,93 @@ const runScriptsCommand = new Command('run-scripts') } }); program.addCommand(runScriptsCommand); + +const createRandomApp = (opts: { app: any, package: any, pwd: string, status?: string }) => { + const { app, package: packageJson, pwd } = opts; + if (!app.status) { + app.status = opts.status || 'running' + } + if (!app.key) { + const randomSuffix = Math.random().toString(36).substring(2, 8); + app.key = packageJson.basename || `${'unknown-app'}-${randomSuffix}`; + } + app.path = pwd; + if (app.type === 'pm2-system-app' && !app.pm2Options) { + app.pm2Options = { + cwd: pwd, + } + } + return app; +} +const start = new Command('start') + .description('获取package.json中app参数并启动对应的app') + .option('-s --save', '保存应用信息到assistant配置中', false) + .action(async (opts) => { + // assistantConfig.checkMounted(); + const pwd = process.cwd(); + const packageJsonPath = path.join(pwd, 'package.json'); + if (checkFileExists(packageJsonPath) === false) { + logger.error('package.json 在当前目录未找到,请在包含 package.json 的目录下运行此命令。'); + return + } + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + const appKey = packageJson.app; + if (!appKey) { + logger.error('package.json 中未找到 app 字段,请确保在 package.json 中正确配置 app 字段。'); + return + } + const app = createRandomApp({ app: packageJson.app, package: packageJson, pwd }); + if (app.type !== 'system-app') { + const load = await LoadApp(app, {}); + if (!load) { + logger.error(`未能加载应用, 请确保应用名称正确且已安装。`, app.type); + return + } + } else { + LoadApp(app, {}).then(() => { + logger.info(`系统应用已启动: ${app.key}`); + }).catch((err) => { + logger.error(`启动系统应用失败: ${app.key}`, err); + }); + } + + if (opts.save) { + assistantConfig.checkMounted(); + const manager = new AssistantApp(assistantConfig, app); + await manager.loadConfig(); + await manager.saveAppInfo(app); + } + }); + +program.addCommand(start); + +const stop = new Command('stop') + .description('获取package.json中app参数并停止对应的app') + .option('-t --todo ', '停止应用,在pm2中如果为stop则停止,如果为remove则删除,默认为stop', 'stop') + .option('-s --save', '保存应用信息到assistant配置中', false) + .action(async (opts) => { + // assistantConfig.checkMounted(); + const pwd = process.cwd(); + const packageJsonPath = path.join(pwd, 'package.json'); + if (checkFileExists(packageJsonPath) === false) { + logger.error('package.json 在当前目录未找到,请在包含 package.json 的目录下运行此命令。'); + return + } + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + const appKey = packageJson.app; + if (!appKey) { + logger.error('package.json 中未找到 app 字段,请确保在 package.json 中正确配置 app 字段。'); + return + } + const app = createRandomApp({ app: packageJson.app, package: packageJson, pwd, status: 'stopped' }); + await StopApp(app, { todo: opts.todo }); + if (opts.save) { + assistantConfig.checkMounted(); + const manager = new AssistantApp(assistantConfig, app); + await manager.loadConfig(); + await manager.removeApp(app.key, { deleteFile: false }); + } + }); + +program.addCommand(stop); + diff --git a/assistant/src/module/local-apps/src/index.ts b/assistant/src/module/local-apps/src/index.ts index 0828db7..0d0b8ce 100644 --- a/assistant/src/module/local-apps/src/index.ts +++ b/assistant/src/module/local-apps/src/index.ts @@ -40,7 +40,3 @@ if (DEV_SERVER) { }); loadManager({ configFilename: 'b.json' }); } - -export const loadApp = (mainApp: App, appInfo?: any) => { - // -}; diff --git a/assistant/src/module/local-apps/src/modules/manager.ts b/assistant/src/module/local-apps/src/modules/manager.ts index 2396224..2de18e9 100644 --- a/assistant/src/module/local-apps/src/modules/manager.ts +++ b/assistant/src/module/local-apps/src/modules/manager.ts @@ -53,6 +53,7 @@ export type AppInfo = { path?: string; // 文件路径 env?: Record; // 环境变量 engine?: string; // runtime, python node deno bun etc + init?: boolean; // 是否需要初始化安装npm等依赖 /** * pm2 选项, 仅仅当是AppType.Pm2SystemApp的时候生效 * pm2 选项可以参考 https://pm2.keymetrics.io/docs/usage/application-declaration/ @@ -125,71 +126,7 @@ export class Manager { async loadApp(app: T) { const mainApp = this.mainApp; this.apps.set(app.key, app); - if (app.status !== 'running') { - return; - } - if (!fileIsExist(app.path)) { - console.error('app is not found'); - return; - } - const pathEntry = path.join(app.path, app.entry); - if (!fileIsExist(pathEntry)) { - console.error('file entry not found'); - return; - } - const entry = app.entry + `?timestamp=${app?.timestamp}`; - // 注册路由 - if (app.type === AppType.MicroApp) { - const childProcess = fork(app.entry, [], { - stdio: 'inherit', // 共享主进程的标准输入输出 - cwd: app.path, - env: { - ...process.env, - ...app.env, - APP_KEY: app.key, - APP_PATH: app.path, - APP_ENTRY: entry - } - }); - app.process = childProcess; - } else if (app.type === AppType.SystemApp) { - const pathEntryAndTimestamp = path.join(app.path, entry); - // Windows下需要使用file://协议,并将反斜杠转换为正斜杠 - const importPath = process.platform === 'win32' - ? 'file:///' + pathEntryAndTimestamp.replace(/\\/g, '/') - : pathEntryAndTimestamp; - const module = await import(importPath); - if (module.loadApp && mainApp) { - await module.loadApp?.(mainApp, app); - } - } else if (app.type === AppType.GatewayApp) { - console.log('gateway app not support'); - } else if (app.type === AppType.Pm2SystemApp) { - const pathEntry = path.join(app.path, app.entry); - const pm2Manager = new Pm2Manager({ - appName: app.key, - script: pathEntry, - pm2Connect: this.#pm2Connect - }); - - // const isInstall = await checkInstall(app); - // if (!isInstall) { - // console.log('install failed'); - // return; - // } - const pm2Options: StartOptions = app.pm2Options || {}; - if (app?.engine) { - pm2Options.interpreter = pm2Options.interpreter || app?.engine; - } - if (!pm2Options.cwd) { - pm2Options.cwd = path.join(app.path, '../..'); - } - await pm2Manager.start(pm2Options); - } else { - console.error('app type not support', app.type); - } - console.log(`load ${app.type} success`, app.key); - return true; + return await LoadApp(app, { mainApp, pm2Connect: this.#pm2Connect }); } /** * create new app info @@ -308,24 +245,7 @@ export class Manager { if (!app) { return; } - if (app.status === 'stop' && app.type === AppType.SystemApp) { - console.log(`app ${key} is stopped`); - return; - } - app.status = 'stop'; - if (app.type === AppType.MicroApp) { - if (app.process) { - app.process.kill(); - } - } - if (app.type === AppType.Pm2SystemApp) { - const pm2Manager = new Pm2Manager({ - appName: app.key, - script: app.entry, - pm2Connect: this.#pm2Connect - }); - await pm2Manager.stop(); - } + await StopApp(app, { pm2Connect: this.#pm2Connect, todo: 'stop' }); await this.saveAppInfo(app); } async restart(key: string) { @@ -393,8 +313,9 @@ export class Manager { * @param key * @returns */ - async removeApp(key: string) { + async removeApp(key: string, opts?: { deleteFile?: boolean }) { const app = this.apps.get(key); + const deleteFile = opts?.deleteFile ?? true; if (!app) { return false; } @@ -414,6 +335,9 @@ export class Manager { } catch (e) { console.log('delete pm2 process error', e); } + if (!deleteFile) { + return true; + } try { deleteFileAppInfo(key, this.appsPath); } catch (e) { @@ -485,7 +409,105 @@ export class Manager { } } } +export const LoadApp = async (app: AppInfo, opts?: { mainApp?: any, pm2Connect?: any }) => { + const mainApp = opts?.mainApp; + const pm2Connect = opts?.pm2Connect; + if (app.status !== 'running') { + return false; + } + if (!fileIsExist(app.path)) { + console.error('app is not found'); + return false; + } + const pathEntry = path.join(app.path, app.entry); + if (!fileIsExist(pathEntry)) { + console.error('file entry not found'); + return false; + } + const entry = app.entry + `?timestamp=${app?.timestamp}`; + // 注册路由 + if (app.type === AppType.MicroApp) { + const childProcess = fork(app.entry, [], { + stdio: 'inherit', // 共享主进程的标准输入输出 + cwd: app.path, + env: { + ...process.env, + ...app.env, + APP_KEY: app.key, + APP_PATH: app.path, + APP_ENTRY: entry + } + }); + app.process = childProcess; + } else if (app.type === AppType.SystemApp) { + const pathEntryAndTimestamp = path.join(app.path, entry); + // Windows下需要使用file://协议,并将反斜杠转换为正斜杠 + const importPath = process.platform === 'win32' + ? 'file:///' + pathEntryAndTimestamp.replace(/\\/g, '/') + : pathEntryAndTimestamp; + const module = await import(importPath); + if (module.loadApp && mainApp) { + await module.loadApp?.(mainApp, app); + } + } else if (app.type === AppType.GatewayApp) { + console.log('gateway app not support'); + } else if (app.type === AppType.Pm2SystemApp) { + const pathEntry = path.join(app.path, app.entry); + console.log('pm2 system app start', pathEntry); + const pm2Manager = new Pm2Manager({ + appName: app.key, + script: pathEntry, + pm2Connect: pm2Connect + }); + if (app?.init) { + const isInstall = await checkInstall(app); + if (!isInstall) { + console.log('install failed'); + return false; + } + } + const pm2Options: StartOptions = app.pm2Options || {}; + if (app?.engine) { + pm2Options.interpreter = pm2Options.interpreter || app?.engine; + } + if (!pm2Options.cwd) { + pm2Options.cwd = path.join(app.path, '../..'); + } + console.log('pm2 start options', pm2Options); + await pm2Manager.start(pm2Options); + } else if (app.type === AppType.ScriptApp) { + // console.log('script app 直接运行,不需要启动'); + return true; + } else { + console.error('app type not support', app.type); + } + console.log(`load ${app.type} success`, app.key); + return true; +} +export const StopApp = async (app: AppInfo, opts?: { pm2Connect?: Pm2Connect, todo?: 'stop' | 'remove' | 'restart' }) => { + const key = app.key; + const pm2Connect = opts?.pm2Connect; + const todo = opts?.todo || 'stop'; + if (app.status === 'stop' && app.type === AppType.SystemApp) { + console.log(`app ${key} is stopped`); + return; + } + app.status = 'stop'; + if (app.type === AppType.MicroApp) { + if (app.process) { + app.process.kill(); + } + } + if (app.type === AppType.Pm2SystemApp) { + const pm2Manager = new Pm2Manager({ + appName: app.key, + script: app.entry, + pm2Connect: pm2Connect + }); + await pm2Manager[todo]?.(); + } +} /** * 安装app通过key * @param key diff --git a/assistant/src/module/local-apps/src/modules/pm2.ts b/assistant/src/module/local-apps/src/modules/pm2.ts index ef2fd1c..2561a66 100644 --- a/assistant/src/module/local-apps/src/modules/pm2.ts +++ b/assistant/src/module/local-apps/src/modules/pm2.ts @@ -108,6 +108,9 @@ export class Pm2Connect { } } type RunOptions = { + /** + * 是否在操作完成后退出连接 + */ needExit?: boolean; }; export class Pm2Manager { @@ -226,6 +229,9 @@ export class Pm2Manager { this.pm2Connect.checkDisconnect(runOpts); } } + async remove(runOpts?: RunOptions) { + this.deleteProcess(runOpts); + } async deleteProcess(runOpts?: RunOptions) { try { await this.pm2Connect.checkConnect(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1f231f..faee78f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -208,6 +208,9 @@ importers: supports-color: specifier: ^10.2.2 version: 10.2.2 + table: + specifier: ^6.9.0 + version: 6.9.0 ws: specifier: npm:@kevisual/ws version: '@kevisual/ws@8.0.0' @@ -2314,6 +2317,9 @@ packages: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + amp-message@0.1.2: resolution: {integrity: sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==} @@ -2392,6 +2398,10 @@ packages: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true @@ -3082,6 +3092,9 @@ packages: fast-json-patch@3.1.1: resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -3557,6 +3570,9 @@ packages: engines: {node: '>=6'} hasBin: true + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -3692,6 +3708,9 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -4487,6 +4506,10 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + require-in-the-middle@5.2.0: resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} engines: {node: '>=6'} @@ -4651,6 +4674,10 @@ packages: engines: {node: '>=14.0.0', npm: '>=6.0.0'} hasBin: true + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -4792,6 +4819,10 @@ packages: os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} + tailwind-merge@3.4.0: resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} @@ -7771,6 +7802,13 @@ snapshots: agent-base@7.1.3: {} + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + amp-message@0.1.2: dependencies: amp: 0.3.1 @@ -7902,6 +7940,8 @@ snapshots: dependencies: tslib: 2.8.1 + astral-regex@2.0.0: {} + astring@1.9.0: {} astro@5.16.6(@types/node@25.0.3)(idb-keyval@6.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.43.0)(typescript@5.8.2): @@ -8661,6 +8701,8 @@ snapshots: fast-json-patch@3.1.1: {} + fast-uri@3.1.0: {} + fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -9244,6 +9286,8 @@ snapshots: jsesc@3.1.0: {} + json-schema-traverse@1.0.0: {} + json-stringify-safe@5.0.1: optional: true @@ -9373,6 +9417,8 @@ snapshots: lodash.once@4.1.1: {} + lodash.truncate@4.4.2: {} + lodash@4.17.21: {} longest-streak@3.1.0: {} @@ -10587,6 +10633,8 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + require-from-string@2.0.2: {} + require-in-the-middle@5.2.0(supports-color@10.2.2): dependencies: debug: 4.4.0(supports-color@10.2.2) @@ -10814,6 +10862,12 @@ snapshots: arg: 5.0.2 sax: 1.4.1 + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + smart-buffer@4.2.0: {} smol-toml@1.5.2: {} @@ -10952,6 +11006,14 @@ snapshots: systeminformation@5.25.11: optional: true + table@6.9.0: + dependencies: + ajv: 8.17.1 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + tailwind-merge@3.4.0: {} tailwindcss@4.1.18: {}