diff --git a/.github/workflows/npm-build.yml b/.github/workflows/npm-build.yml index c2c4704..2adc2d1 100644 --- a/.github/workflows/npm-build.yml +++ b/.github/workflows/npm-build.yml @@ -14,8 +14,6 @@ jobs: - uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 8 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index caad93e..1c140ae 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -15,8 +15,6 @@ jobs: - uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 8 - name: Use Node.js 20 uses: actions/setup-node@v4 with: @@ -34,8 +32,6 @@ jobs: - uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 8 - name: Use Node.js 20 uses: actions/setup-node@v4 with: diff --git a/bin/index.js b/bin/index.js index 4b12ffc..89a13c4 100755 --- a/bin/index.js +++ b/bin/index.js @@ -2,21 +2,29 @@ import { Command } from 'commander'; import chalk from 'chalk'; import { login, logout } from '../src/commands/auth.js'; -import { init } from '../src/commands/init.js'; import { startShell } from '../src/commands/shell.js'; import { PROJECT_NAME, getLatestVersion } from '../src/commons.js'; -import { appInfo, createApp, listApps, deleteApp, updateApp } from '../src/commands/apps.js'; -import inquirer from 'inquirer'; -import { initProfileModule, getProfileModule } from '../src/modules/ProfileModule.js'; +import { initProfileModule } from '../src/modules/ProfileModule.js'; import { initPuterModule } from '../src/modules/PuterModule.js'; -import { createSite, infoSite, listSites, deleteSite } from '../src/commands/sites.js'; +import { formatError, isAuthError, report } from '../src/modules/ErrorModule.js'; + +// puter.js attaches an async 'load' listener to its XHRs, so a failed request +// rejects twice: once on the promise we await, and once on a promise nothing +// holds. Without this guard Node kills the CLI and prints the rejected plain +// object as "#". Auth failures are already surfaced with a re-login +// prompt by checkLogin(), so only report what is not handled elsewhere. +process.on('unhandledRejection', (error) => { + report(error); + if (!isAuthError(error)) { + console.error(chalk.red(`Error: ${formatError(error)}`)); + console.error(chalk.dim('Run "last-error" in the shell for the full details.')); + } +}); async function main() { initProfileModule(); initPuterModule(); - const profileModule = getProfileModule(); - const version = await getLatestVersion(PROJECT_NAME); const program = new Command(); @@ -45,206 +53,12 @@ async function main() { process.exit(0); }); - program - .command('init') - .description('Initialize a new Puter app') - .action(init); - program .command('shell') .description('Start interactive shell') .action(() => startShell()); - // App commands - program - .command('apps') - .description('List all your apps') - .argument('[period]', 'period: today, yesterday, 7d, 30d, this_month, last_month') - .action(async (period) => { - await profileModule.checkLogin(); - await listApps({ - statsPeriod: period || 'all' - }); - process.exit(0); - }); - - const app = program - .command('app') - .description('App management commands'); - - app - .command('info') - .description('Get application information') - .argument('', 'Name of the application') - .action(async (app_name) => { - await profileModule.checkLogin(); - await appInfo([app_name]); - process.exit(0); - }); - - app - .command('create') - .description('Create a new app') - .argument('', 'Name of the application') - .argument('', 'Remote directory URL') - .action(async (name, remote_dir) => { - try { - await profileModule.checkLogin(); - await createApp({ - name: name, - directory: remote_dir || '', - description: '', - url: 'https://dev-center.puter.com/coming-soon.html' - }); - } catch (error) { - console.error(chalk.red(error.message)); - } - process.exit(0); - }); - - app - .command('update') - .description('Update an app') - .argument('', 'Name of the application') - .argument('[dir]', 'Directory path', '.') - .action(async (name, dir) => { - await profileModule.checkLogin(); - await updateApp([name, dir]); - process.exit(0); - }); - - app - .command('delete') - .description('Delete an app') - .argument('', 'Name of the application') - .option('-f, --force', 'Force deletion without confirmation') - .action(async (name, options) => { - await profileModule.checkLogin(); - let shouldDelete = options.force; - - if (!shouldDelete) { - const answer = await inquirer.prompt([ - { - type: 'confirm', - name: 'confirm', - message: `Are you sure you want to delete the app "${name}"?`, - default: false - } - ]); - shouldDelete = answer.confirm; - } - - if (shouldDelete) { - await deleteApp(name); - } else { - console.log(chalk.yellow('App deletion cancelled.')); - } - process.exit(0); - }); - - program - .command('sites') - .description('List sites and subdomains') - .action(async () => { - await profileModule.checkLogin(); - await listSites(); - process.exit(0); - }); - - const site = program - .command('site') - .description('Site management commands'); - - site - .command('info') - .description('Get site information by UID') - .argument('', 'Site UID') - .action(async (site_uid) => { - await profileModule.checkLogin(); - await infoSite([site_uid]); - process.exit(0); - }); - - site - .command('create') - .description('Create a static website from directory') - .argument('', 'Application name') - .argument('[dir]', 'Directory path') - .option('--subdomain ', 'Subdomain name') - .action(async (app_name, dir, options) => { - await profileModule.checkLogin(); - const args = [app_name]; - if (dir) args.push(dir); - if (options.subdomain) args.push(`--subdomain=${options.subdomain}`) - - await createSite(args) - process.exit(0); - }); - - site - .command('deploy') - .description('Deploy a local web project to Puter') - .argument('[local_dir]', 'Local directory path') - .argument('[subdomain]', 'Deployment subdomain (.puter.site)') - .action(async (local_dir, subdomain) => { - await profileModule.checkLogin(); - if (!local_dir) { - const answer = await inquirer.prompt([ - { - type: 'input', - name: 'local_dir', - message: 'Local directory path:', - default: '.' - } - ]); - local_dir = answer.local_dir; - } - - if (!subdomain) { - const answer = await inquirer.prompt([ - { - type: 'input', - name: 'subdomain', - message: 'Deployment subdomain (leave empty for random):', - } - ]); - subdomain = answer.subdomain; - } - - await startShell(`site:deploy ${local_dir}${subdomain ? ` --subdomain=${subdomain}` : ''}`) - process.exit(0); - }); - - site - .command('delete') - .description('Delete a site by UID') - .argument('', 'Site UID') - .option('-f, --force', 'Force deletion without confirmation') - .action(async (uid, options) => { - await profileModule.checkLogin(); - let shouldDelete = options.force; - - if (!shouldDelete) { - const answer = await inquirer.prompt([ - { - type: 'confirm', - name: 'confirm', - message: `Are you sure you want to delete the site with UID "${uid}"?`, - default: false - } - ]); - shouldDelete = answer.confirm; - } - - if (shouldDelete) { - await deleteSite([uid]); - } else { - console.log(chalk.yellow('Site deletion cancelled.')); - } - process.exit(0); - }); - if (process.argv.length === 2) { startShell(); } else { @@ -253,6 +67,12 @@ async function main() { } main().catch((err) => { - console.error(err); + report(err); + if (isAuthError(err)) { + console.error(chalk.red('Your session has expired or its token is no longer valid.')); + console.error(chalk.cyan('Run "puter login" to sign in again.')); + } else { + console.error(chalk.red(`Error: ${formatError(err)}`)); + } process.exit(1); }); \ No newline at end of file diff --git a/package.json b/package.json index be926c6..4472dfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@heyputer/shell", - "version": "2.1.0", + "version": "3.0.0", "description": "SSH-style shell access to your Puter files", "main": "index.js", "bin": { @@ -19,6 +19,7 @@ "engines": { "node": ">=20.0.0" }, + "packageManager": "pnpm@9.9.0", "keywords": [ "puter", "shell", @@ -29,18 +30,15 @@ "dependencies": { "@heyputer/puter.js": "^2.2.8", "chalk": "^5.3.0", - "cli-table3": "^0.6.5", "commander": "^13.0.0", "conf": "^12.0.0", - "cross-spawn": "^7.0.3", "dotenv": "^16.4.7", "glob": "^11.0.0", "inquirer": "^9.2.12", "minimatch": "^10.0.1", "node-fetch": "^3.3.2", "ora": "^8.0.1", - "uuid": "^11.0.5", - "yargs-parser": "^21.1.1" + "uuid": "^11.0.5" }, "devDependencies": { "@vitest/coverage-v8": "2.1.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87b1b52..a99590d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,330 +1,228 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -dependencies: - '@heyputer/puter.js': - specifier: ^2.2.8 - version: 2.2.8 - chalk: - specifier: ^5.3.0 - version: 5.6.2 - cli-table3: - specifier: ^0.6.5 - version: 0.6.5 - commander: - specifier: ^13.0.0 - version: 13.1.0 - conf: - specifier: ^12.0.0 - version: 12.0.0 - cross-spawn: - specifier: ^7.0.3 - version: 7.0.6 - dotenv: - specifier: ^16.4.7 - version: 16.6.1 - glob: - specifier: ^11.0.0 - version: 11.1.0 - inquirer: - specifier: ^9.2.12 - version: 9.3.8 - minimatch: - specifier: ^10.0.1 - version: 10.1.1 - node-fetch: - specifier: ^3.3.2 - version: 3.3.2 - ora: - specifier: ^8.0.1 - version: 8.2.0 - uuid: - specifier: ^11.0.5 - version: 11.1.0 - yargs-parser: - specifier: ^21.1.1 - version: 21.1.1 - -devDependencies: - '@vitest/coverage-v8': - specifier: 2.1.8 - version: 2.1.8(vitest@2.1.9) - auto-changelog: - specifier: ^2.5.0 - version: 2.5.0 - vitest: - specifier: ^2.1.8 - version: 2.1.9 +importers: + + .: + dependencies: + '@heyputer/puter.js': + specifier: ^2.2.8 + version: 2.2.8 + chalk: + specifier: ^5.3.0 + version: 5.6.2 + commander: + specifier: ^13.0.0 + version: 13.1.0 + conf: + specifier: ^12.0.0 + version: 12.0.0 + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + glob: + specifier: ^11.0.0 + version: 11.1.0 + inquirer: + specifier: ^9.2.12 + version: 9.3.8 + minimatch: + specifier: ^10.0.1 + version: 10.1.1 + node-fetch: + specifier: ^3.3.2 + version: 3.3.2 + ora: + specifier: ^8.0.1 + version: 8.2.0 + uuid: + specifier: ^11.0.5 + version: 11.1.0 + devDependencies: + '@vitest/coverage-v8': + specifier: 2.1.8 + version: 2.1.8(vitest@2.1.9) + auto-changelog: + specifier: ^2.5.0 + version: 2.5.0 + vitest: + specifier: ^2.1.8 + version: 2.1.9 packages: - /@ampproject/remapping@2.3.0: + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - dev: true - /@babel/helper-string-parser@7.27.1: + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-validator-identifier@7.28.5: + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - dev: true - /@babel/parser@7.29.0: + '@babel/parser@7.29.0': resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} engines: {node: '>=6.0.0'} hasBin: true - dependencies: - '@babel/types': 7.29.0 - dev: true - /@babel/types@7.29.0: + '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - dev: true - /@bcoe/v8-coverage@0.2.3: + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - dev: true - /@colors/colors@1.5.0: - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - requiresBuild: true - dev: false - optional: true - - /@esbuild/aix-ppc64@0.21.5: + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.21.5: + '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.21.5: + '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.21.5: + '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.21.5: + '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.21.5: + '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.21.5: + '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.21.5: + '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.21.5: + '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.21.5: + '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.21.5: + '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.21.5: + '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.21.5: + '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.21.5: + '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.21.5: + '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.21.5: + '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.21.5: + '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.21.5: + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.21.5: + '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.21.5: + '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.21.5: + '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.21.5: + '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.21.5: + '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@heyputer/kv.js@0.2.1: + '@heyputer/kv.js@0.2.1': resolution: {integrity: sha512-YhVtzz7ZA/HmuaDvzZZhhUyQWBvp3/TXeY4jULssTdLJwT+tEM4BTYHXttORX+V5auvrYinjj8dNFQnby5T82w==} - dev: false - /@heyputer/puter.js@2.2.8: + '@heyputer/puter.js@2.2.8': resolution: {integrity: sha512-94p16VYOnTLhsUerbPJMlTSKtY3j3E0p4DdtOiAIY7jnFpcGb7AgW+7nzVbGszc738XNcokWl0Z+V4ZWj82G5w==} - dependencies: - '@heyputer/kv.js': 0.2.1 - open: 10.2.0 - dev: false - /@inquirer/external-editor@1.0.3: + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} peerDependencies: @@ -332,279 +230,173 @@ packages: peerDependenciesMeta: '@types/node': optional: true - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 - dev: false - /@inquirer/figures@1.0.15: + '@inquirer/figures@1.0.15': resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} engines: {node: '>=18'} - dev: false - /@isaacs/balanced-match@4.0.1: + '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} - dev: false - /@isaacs/brace-expansion@5.0.0: + '@isaacs/brace-expansion@5.0.0': resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} engines: {node: 20 || >=22} - dependencies: - '@isaacs/balanced-match': 4.0.1 - dev: false - /@isaacs/cliui@8.0.2: + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - dependencies: - string-width: 5.1.2 - string-width-cjs: /string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: /strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: /wrap-ansi@7.0.0 - /@istanbuljs/schema@0.1.3: + '@istanbuljs/schema@0.1.3': resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - dev: true - /@jridgewell/gen-mapping@0.3.13: + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - dev: true - /@jridgewell/resolve-uri@3.1.2: + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/sourcemap-codec@1.5.5: + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - dev: true - /@jridgewell/trace-mapping@0.3.31: + '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - dev: true - /@pkgjs/parseargs@0.11.0: + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-android-arm-eabi@4.57.1: + '@rollup/rollup-android-arm-eabi@4.57.1': resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-android-arm64@4.57.1: + '@rollup/rollup-android-arm64@4.57.1': resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-arm64@4.57.1: + '@rollup/rollup-darwin-arm64@4.57.1': resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-x64@4.57.1: + '@rollup/rollup-darwin-x64@4.57.1': resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-freebsd-arm64@4.57.1: + '@rollup/rollup-freebsd-arm64@4.57.1': resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-freebsd-x64@4.57.1: + '@rollup/rollup-freebsd-x64@4.57.1': resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.57.1: + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-musleabihf@4.57.1: + '@rollup/rollup-linux-arm-musleabihf@4.57.1': resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-gnu@4.57.1: + '@rollup/rollup-linux-arm64-gnu@4.57.1': resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-musl@4.57.1: + '@rollup/rollup-linux-arm64-musl@4.57.1': resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-loong64-gnu@4.57.1: + '@rollup/rollup-linux-loong64-gnu@4.57.1': resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-loong64-musl@4.57.1: + '@rollup/rollup-linux-loong64-musl@4.57.1': resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-ppc64-gnu@4.57.1: + '@rollup/rollup-linux-ppc64-gnu@4.57.1': resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-ppc64-musl@4.57.1: + '@rollup/rollup-linux-ppc64-musl@4.57.1': resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-riscv64-gnu@4.57.1: + '@rollup/rollup-linux-riscv64-gnu@4.57.1': resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-riscv64-musl@4.57.1: + '@rollup/rollup-linux-riscv64-musl@4.57.1': resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-s390x-gnu@4.57.1: + '@rollup/rollup-linux-s390x-gnu@4.57.1': resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-gnu@4.57.1: + '@rollup/rollup-linux-x64-gnu@4.57.1': resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-musl@4.57.1: + '@rollup/rollup-linux-x64-musl@4.57.1': resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-openbsd-x64@4.57.1: + '@rollup/rollup-openbsd-x64@4.57.1': resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-openharmony-arm64@4.57.1: + '@rollup/rollup-openharmony-arm64@4.57.1': resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} cpu: [arm64] os: [openharmony] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-arm64-msvc@4.57.1: + '@rollup/rollup-win32-arm64-msvc@4.57.1': resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-ia32-msvc@4.57.1: + '@rollup/rollup-win32-ia32-msvc@4.57.1': resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-x64-gnu@4.57.1: + '@rollup/rollup-win32-x64-gnu@4.57.1': resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-x64-msvc@4.57.1: + '@rollup/rollup-win32-x64-msvc@4.57.1': resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@types/estree@1.0.8: + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - dev: true - /@vitest/coverage-v8@2.1.8(vitest@2.1.9): + '@vitest/coverage-v8@2.1.8': resolution: {integrity: sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==} peerDependencies: '@vitest/browser': 2.1.8 @@ -612,34 +404,11 @@ packages: peerDependenciesMeta: '@vitest/browser': optional: true - dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 0.2.3 - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.2.0 - magic-string: 0.30.21 - magicast: 0.3.5 - std-env: 3.10.0 - test-exclude: 7.0.1 - tinyrainbow: 1.2.0 - vitest: 2.1.9 - transitivePeerDependencies: - - supports-color - dev: true - /@vitest/expect@2.1.9: + '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} - dependencies: - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.3.3 - tinyrainbow: 1.2.0 - dev: true - /@vitest/mocker@2.1.9(vite@5.4.21): + '@vitest/mocker@2.1.9': resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} peerDependencies: msw: ^2.4.9 @@ -649,287 +418,159 @@ packages: optional: true vite: optional: true - dependencies: - '@vitest/spy': 2.1.9 - estree-walker: 3.0.3 - magic-string: 0.30.21 - vite: 5.4.21 - dev: true - /@vitest/pretty-format@2.1.9: + '@vitest/pretty-format@2.1.9': resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} - dependencies: - tinyrainbow: 1.2.0 - dev: true - /@vitest/runner@2.1.9: + '@vitest/runner@2.1.9': resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} - dependencies: - '@vitest/utils': 2.1.9 - pathe: 1.1.2 - dev: true - /@vitest/snapshot@2.1.9: + '@vitest/snapshot@2.1.9': resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} - dependencies: - '@vitest/pretty-format': 2.1.9 - magic-string: 0.30.21 - pathe: 1.1.2 - dev: true - /@vitest/spy@2.1.9: + '@vitest/spy@2.1.9': resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} - dependencies: - tinyspy: 3.0.2 - dev: true - /@vitest/utils@2.1.9: + '@vitest/utils@2.1.9': resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} - dependencies: - '@vitest/pretty-format': 2.1.9 - loupe: 3.2.1 - tinyrainbow: 1.2.0 - dev: true - /ajv-formats@2.1.1(ajv@8.17.1): + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: ajv: ^8.0.0 peerDependenciesMeta: ajv: optional: true - dependencies: - ajv: 8.17.1 - dev: false - /ajv@8.17.1: + ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - dev: false - /ansi-escapes@4.3.2: + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - dependencies: - type-fest: 0.21.3 - dev: false - /ansi-regex@5.0.1: + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - /ansi-regex@6.2.2: + ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} - /ansi-styles@4.3.0: + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - /ansi-styles@6.2.3: + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - /assertion-error@2.0.1: + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - dev: true - /atomically@2.1.0: + atomically@2.1.0: resolution: {integrity: sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==} - dependencies: - stubborn-fs: 2.0.0 - when-exit: 2.1.5 - dev: false - /auto-changelog@2.5.0: + auto-changelog@2.5.0: resolution: {integrity: sha512-UTnLjT7I9U2U/xkCUH5buDlp8C7g0SGChfib+iDrJkamcj5kaMqNKHNfbKJw1kthJUq8sUo3i3q2S6FzO/l/wA==} engines: {node: '>=8.3'} hasBin: true - dependencies: - commander: 7.2.0 - handlebars: 4.7.8 - import-cwd: 3.0.0 - node-fetch: 2.7.0 - parse-github-url: 1.0.3 - semver: 7.7.3 - transitivePeerDependencies: - - encoding - dev: true - /balanced-match@1.0.2: + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - /base64-js@1.5.1: + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false - /bl@4.1.0: + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - dev: false - /brace-expansion@2.0.2: + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - dependencies: - balanced-match: 1.0.2 - dev: true - /buffer@5.7.1: + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: false - /bundle-name@4.1.0: + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} - dependencies: - run-applescript: 7.1.0 - dev: false - /cac@6.7.14: + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - dev: true - /chai@5.3.3: + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.3 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 - dev: true - /chalk@4.1.2: + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: false - /chalk@5.6.2: + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: false - /chardet@2.1.1: + chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - dev: false - /check-error@2.1.3: + check-error@2.1.3: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} - dev: true - /cli-cursor@3.1.0: + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} - dependencies: - restore-cursor: 3.1.0 - dev: false - /cli-cursor@5.0.0: + cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} - dependencies: - restore-cursor: 5.1.0 - dev: false - /cli-spinners@2.9.2: + cli-spinners@2.9.2: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} - dev: false - - /cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 - dev: false - /cli-width@4.1.0: + cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} - dev: false - /clone@1.0.4: + clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} - dev: false - /color-convert@2.0.1: + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - /color-name@1.1.4: + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - /commander@13.1.0: + commander@13.1.0: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} - dev: false - /commander@7.2.0: + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} - dev: true - /conf@12.0.0: + conf@12.0.0: resolution: {integrity: sha512-fIWyWUXrJ45cHCIQX+Ck1hrZDIf/9DR0P0Zewn3uNht28hbt5OfGUq8rRWsxi96pZWPyBEd0eY9ama01JTaknA==} engines: {node: '>=18'} - dependencies: - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - atomically: 2.1.0 - debounce-fn: 5.1.2 - dot-prop: 8.0.2 - env-paths: 3.0.0 - json-schema-typed: 8.0.2 - semver: 7.7.3 - uint8array-extras: 0.3.0 - dev: false - /cross-spawn@7.0.6: + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - /data-uri-to-buffer@4.0.1: + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} - dev: false - /debounce-fn@5.1.2: + debounce-fn@5.1.2: resolution: {integrity: sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==} engines: {node: '>=12'} - dependencies: - mimic-fn: 4.0.0 - dev: false - /debug@4.4.3: + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} peerDependencies: @@ -937,475 +578,281 @@ packages: peerDependenciesMeta: supports-color: optional: true - dependencies: - ms: 2.1.3 - dev: true - /deep-eql@5.0.2: + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - dev: true - /default-browser-id@5.0.1: + default-browser-id@5.0.1: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} - dev: false - /default-browser@5.5.0: + default-browser@5.5.0: resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} engines: {node: '>=18'} - dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.1 - dev: false - /defaults@1.0.4: + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - dependencies: - clone: 1.0.4 - dev: false - /define-lazy-prop@3.0.0: + define-lazy-prop@3.0.0: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} - dev: false - /dot-prop@8.0.2: + dot-prop@8.0.2: resolution: {integrity: sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==} engines: {node: '>=16'} - dependencies: - type-fest: 3.13.1 - dev: false - /dotenv@16.6.1: + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - dev: false - /eastasianwidth@0.2.0: + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - /emoji-regex@10.6.0: + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - dev: false - /emoji-regex@8.0.0: + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - /emoji-regex@9.2.2: + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - /env-paths@3.0.0: + env-paths@3.0.0: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: false - /es-module-lexer@1.7.0: + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - dev: true - /esbuild@0.21.5: + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - dev: true - /estree-walker@3.0.3: + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - dependencies: - '@types/estree': 1.0.8 - dev: true - /expect-type@1.3.0: + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - dev: true - /fast-deep-equal@3.1.3: + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: false - /fast-uri@3.1.0: + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - dev: false - /fetch-blob@3.2.0: + fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - dev: false - /foreground-child@3.3.1: + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - /formdata-polyfill@4.0.10: + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} - dependencies: - fetch-blob: 3.2.0 - dev: false - /fsevents@2.3.3: + 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 - /get-east-asian-width@1.4.0: + get-east-asian-width@1.4.0: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} - dev: false - /glob@10.5.0: + glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} hasBin: true - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - dev: true - /glob@11.1.0: + glob@11.1.0: resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} engines: {node: 20 || >=22} hasBin: true - dependencies: - foreground-child: 3.3.1 - jackspeak: 4.1.1 - minimatch: 10.1.1 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.1 - dev: false - /handlebars@4.7.8: + handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} hasBin: true - dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.19.3 - dev: true - /has-flag@4.0.0: + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - /html-escaper@2.0.2: + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - dev: true - /iconv-lite@0.7.2: + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - dev: false - /ieee754@1.2.1: + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: false - /import-cwd@3.0.0: + import-cwd@3.0.0: resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} engines: {node: '>=8'} - dependencies: - import-from: 3.0.0 - dev: true - /import-from@3.0.0: + import-from@3.0.0: resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} engines: {node: '>=8'} - dependencies: - resolve-from: 5.0.0 - dev: true - /inherits@2.0.4: + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: false - /inquirer@9.3.8: + inquirer@9.3.8: resolution: {integrity: sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==} engines: {node: '>=18'} - dependencies: - '@inquirer/external-editor': 1.0.3 - '@inquirer/figures': 1.0.15 - ansi-escapes: 4.3.2 - cli-width: 4.1.0 - mute-stream: 1.0.0 - ora: 5.4.1 - run-async: 3.0.0 - rxjs: 7.8.2 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - transitivePeerDependencies: - - '@types/node' - dev: false - /is-docker@3.0.0: + is-docker@3.0.0: resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true - dev: false - /is-fullwidth-code-point@3.0.0: + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - /is-inside-container@1.0.0: + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} hasBin: true - dependencies: - is-docker: 3.0.0 - dev: false - /is-interactive@1.0.0: + is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} - dev: false - /is-interactive@2.0.0: + is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} - dev: false - /is-unicode-supported@0.1.0: + is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - dev: false - /is-unicode-supported@1.3.0: + is-unicode-supported@1.3.0: resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} engines: {node: '>=12'} - dev: false - /is-unicode-supported@2.1.0: + is-unicode-supported@2.1.0: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} - dev: false - /is-wsl@3.1.0: + is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} - dependencies: - is-inside-container: 1.0.0 - dev: false - /isexe@2.0.0: + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - /istanbul-lib-coverage@3.2.2: + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} - dev: true - /istanbul-lib-report@3.0.1: + istanbul-lib-report@3.0.1: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - dev: true - /istanbul-lib-source-maps@5.0.6: + istanbul-lib-source-maps@5.0.6: resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} engines: {node: '>=10'} - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - transitivePeerDependencies: - - supports-color - dev: true - /istanbul-reports@3.2.0: + istanbul-reports@3.2.0: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - dev: true - /jackspeak@3.4.3: + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - dev: true - /jackspeak@4.1.1: + jackspeak@4.1.1: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} - dependencies: - '@isaacs/cliui': 8.0.2 - dev: false - /json-schema-traverse@1.0.0: + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: false - /json-schema-typed@8.0.2: + json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - dev: false - /log-symbols@4.1.0: + log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - dev: false - /log-symbols@6.0.0: + log-symbols@6.0.0: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} - dependencies: - chalk: 5.6.2 - is-unicode-supported: 1.3.0 - dev: false - /loupe@3.2.1: + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - dev: true - /lru-cache@10.4.3: + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - dev: true - /lru-cache@11.2.5: + lru-cache@11.2.5: resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} engines: {node: 20 || >=22} - dev: false - /magic-string@0.30.21: + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - dev: true - /magicast@0.3.5: + magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} - dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - source-map-js: 1.2.1 - dev: true - /make-dir@4.0.0: + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - dependencies: - semver: 7.7.3 - dev: true - /mimic-fn@2.1.0: + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - dev: false - /mimic-fn@4.0.0: + mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} - dev: false - /mimic-function@5.0.1: + mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - dev: false - /minimatch@10.1.1: + minimatch@10.1.1: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} - dependencies: - '@isaacs/brace-expansion': 5.0.0 - dev: false - /minimatch@9.0.5: + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - dependencies: - brace-expansion: 2.0.2 - dev: true - /minimist@1.2.8: + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true - /minipass@7.1.2: + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - /ms@2.1.3: + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true - /mute-stream@1.0.0: + mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: false - /nanoid@3.3.11: + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: true - /neo-async@2.6.2: + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - dev: true - /node-domexception@1.0.0: + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} deprecated: Use your platform's native DOMException instead - dev: false - /node-fetch@2.7.0: + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} peerDependencies: @@ -1413,422 +860,239 @@ packages: peerDependenciesMeta: encoding: optional: true - dependencies: - whatwg-url: 5.0.0 - dev: true - /node-fetch@3.3.2: + node-fetch@3.3.2: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - dev: false - /onetime@5.1.2: + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - dependencies: - mimic-fn: 2.1.0 - dev: false - /onetime@7.0.0: + onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - dependencies: - mimic-function: 5.0.1 - dev: false - /open@10.2.0: + open@10.2.0: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} - dependencies: - default-browser: 5.5.0 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - wsl-utils: 0.1.0 - dev: false - /ora@5.4.1: + ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} - dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - dev: false - /ora@8.2.0: + ora@8.2.0: resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} engines: {node: '>=18'} - dependencies: - chalk: 5.6.2 - cli-cursor: 5.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 6.0.0 - stdin-discarder: 0.2.2 - string-width: 7.2.0 - strip-ansi: 7.1.2 - dev: false - /package-json-from-dist@1.0.1: + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - /parse-github-url@1.0.3: + parse-github-url@1.0.3: resolution: {integrity: sha512-tfalY5/4SqGaV/GIGzWyHnFjlpTPTNpENR9Ea2lLldSJ8EWXMsvacWucqY3m3I4YPtas15IxTLQVQ5NSYXPrww==} engines: {node: '>= 0.10'} hasBin: true - dev: true - /path-key@3.1.1: + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - /path-scurry@1.11.1: + path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - dev: true - /path-scurry@2.0.1: + path-scurry@2.0.1: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} - dependencies: - lru-cache: 11.2.5 - minipass: 7.1.2 - dev: false - /pathe@1.1.2: + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - dev: true - /pathval@2.0.1: + pathval@2.0.1: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} - dev: true - /picocolors@1.1.1: + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - dev: true - /postcss@8.5.6: + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - dev: true - /readable-stream@3.6.2: + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - dev: false - /require-from-string@2.0.2: + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - dev: false - /resolve-from@5.0.0: + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - dev: true - /restore-cursor@3.1.0: + restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - dev: false - /restore-cursor@5.1.0: + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - dev: false - /rollup@4.57.1: + rollup@4.57.1: resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.57.1 - '@rollup/rollup-android-arm64': 4.57.1 - '@rollup/rollup-darwin-arm64': 4.57.1 - '@rollup/rollup-darwin-x64': 4.57.1 - '@rollup/rollup-freebsd-arm64': 4.57.1 - '@rollup/rollup-freebsd-x64': 4.57.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 - '@rollup/rollup-linux-arm-musleabihf': 4.57.1 - '@rollup/rollup-linux-arm64-gnu': 4.57.1 - '@rollup/rollup-linux-arm64-musl': 4.57.1 - '@rollup/rollup-linux-loong64-gnu': 4.57.1 - '@rollup/rollup-linux-loong64-musl': 4.57.1 - '@rollup/rollup-linux-ppc64-gnu': 4.57.1 - '@rollup/rollup-linux-ppc64-musl': 4.57.1 - '@rollup/rollup-linux-riscv64-gnu': 4.57.1 - '@rollup/rollup-linux-riscv64-musl': 4.57.1 - '@rollup/rollup-linux-s390x-gnu': 4.57.1 - '@rollup/rollup-linux-x64-gnu': 4.57.1 - '@rollup/rollup-linux-x64-musl': 4.57.1 - '@rollup/rollup-openbsd-x64': 4.57.1 - '@rollup/rollup-openharmony-arm64': 4.57.1 - '@rollup/rollup-win32-arm64-msvc': 4.57.1 - '@rollup/rollup-win32-ia32-msvc': 4.57.1 - '@rollup/rollup-win32-x64-gnu': 4.57.1 - '@rollup/rollup-win32-x64-msvc': 4.57.1 - fsevents: 2.3.3 - dev: true - /run-applescript@7.1.0: + run-applescript@7.1.0: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} - dev: false - /run-async@3.0.0: + run-async@3.0.0: resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} engines: {node: '>=0.12.0'} - dev: false - /rxjs@7.8.2: + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - dependencies: - tslib: 2.8.1 - dev: false - /safe-buffer@5.2.1: + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: false - /safer-buffer@2.1.2: + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - dev: false - /semver@7.7.3: + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true - /shebang-command@2.0.0: + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - /shebang-regex@3.0.0: + shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - /siginfo@2.0.0: + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - dev: true - /signal-exit@3.0.7: + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: false - /signal-exit@4.1.0: + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - /source-map-js@1.2.1: + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - dev: true - /source-map@0.6.1: + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - dev: true - /stackback@0.0.2: + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - dev: true - /std-env@3.10.0: + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - dev: true - /stdin-discarder@0.2.2: + stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} - dev: false - /string-width@4.2.3: + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - /string-width@5.1.2: + string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - /string-width@7.2.0: + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 - dev: false - /string_decoder@1.3.0: + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - dependencies: - safe-buffer: 5.2.1 - dev: false - /strip-ansi@6.0.1: + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - /strip-ansi@7.1.2: + strip-ansi@7.1.2: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} - dependencies: - ansi-regex: 6.2.2 - /stubborn-fs@2.0.0: + stubborn-fs@2.0.0: resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==} - dependencies: - stubborn-utils: 1.0.2 - dev: false - /stubborn-utils@1.0.2: + stubborn-utils@1.0.2: resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} - dev: false - /supports-color@7.2.0: + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - /test-exclude@7.0.1: + test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 10.5.0 - minimatch: 9.0.5 - dev: true - /tinybench@2.9.0: + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - dev: true - /tinyexec@0.3.2: + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - dev: true - /tinypool@1.1.1: + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} - dev: true - /tinyrainbow@1.2.0: + tinyrainbow@1.2.0: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - dev: true - /tinyspy@3.0.2: + tinyspy@3.0.2: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - dev: true - /tr46@0.0.3: + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: true - /tslib@2.8.1: + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - dev: false - /type-fest@0.21.3: + type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - dev: false - /type-fest@3.13.1: + type-fest@3.13.1: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} - dev: false - /uglify-js@3.19.3: + uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} hasBin: true - requiresBuild: true - dev: true - optional: true - /uint8array-extras@0.3.0: + uint8array-extras@0.3.0: resolution: {integrity: sha512-erJsJwQ0tKdwuqI0359U8ijkFmfiTcq25JvvzRVc1VP+2son1NJRXhxcAKJmAW3ajM8JSGAfsAXye8g4s+znxA==} engines: {node: '>=18'} - dev: false - /util-deprecate@1.0.2: + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: false - /uuid@11.1.0: + uuid@11.1.0: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true - dev: false - /vite-node@2.1.9: + vite-node@2.1.9: resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 1.1.2 - vite: 5.4.21 - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - dev: true - /vite@5.4.21: + vite@5.4.21: resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1858,15 +1122,8 @@ packages: optional: true terser: optional: true - dependencies: - esbuild: 0.21.5 - postcss: 8.5.6 - rollup: 4.57.1 - optionalDependencies: - fsevents: 2.3.3 - dev: true - /vitest@2.1.9: + vitest@2.1.9: resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1890,123 +1147,1080 @@ packages: optional: true jsdom: optional: true - dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.21) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.3.3 - debug: 4.4.3 - expect-type: 1.3.0 - magic-string: 0.30.21 - pathe: 1.1.2 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinypool: 1.1.1 - tinyrainbow: 1.2.0 - vite: 5.4.21 - vite-node: 2.1.9 - why-is-node-running: 2.3.0 - transitivePeerDependencies: - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - dev: true - /wcwidth@1.0.1: + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - dependencies: - defaults: 1.0.4 - dev: false - /web-streams-polyfill@3.3.3: + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} - dev: false - /webidl-conversions@3.0.1: + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: true - /whatwg-url@5.0.0: + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - dev: true - /when-exit@2.1.5: + when-exit@2.1.5: resolution: {integrity: sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==} - dev: false - /which@2.0.2: + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true - dependencies: - isexe: 2.0.0 - /why-is-node-running@2.3.0: + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - dev: true - /wordwrap@1.0.0: + wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - dev: true - /wrap-ansi@6.2.0: + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: false - /wrap-ansi@7.0.0: + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - /wrap-ansi@8.1.0: + wrap-ansi@8.1.0: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - /wsl-utils@0.1.0: + wsl-utils@0.1.0: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} - dependencies: - is-wsl: 3.1.0 - dev: false - - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - dev: false - /yoctocolors-cjs@2.1.3: + yoctocolors-cjs@2.1.3: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} - dev: false + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@heyputer/kv.js@0.2.1': {} + + '@heyputer/puter.js@2.2.8': + dependencies: + '@heyputer/kv.js': 0.2.1 + open: 10.2.0 + + '@inquirer/external-editor@1.0.3': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + + '@inquirer/figures@1.0.15': {} + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + + '@types/estree@1.0.8': {} + + '@vitest/coverage-v8@2.1.8(vitest@2.1.9)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.9 + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21)': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21 + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + 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 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + assertion-error@2.0.1: {} + + atomically@2.1.0: + dependencies: + stubborn-fs: 2.0.0 + when-exit: 2.1.5 + + auto-changelog@2.5.0: + dependencies: + commander: 7.2.0 + handlebars: 4.7.8 + import-cwd: 3.0.0 + node-fetch: 2.7.0 + parse-github-url: 1.0.3 + semver: 7.7.3 + transitivePeerDependencies: + - encoding + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + cac@6.7.14: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + chardet@2.1.1: {} + + check-error@2.1.3: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + cli-width@4.1.0: {} + + clone@1.0.4: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@13.1.0: {} + + commander@7.2.0: {} + + conf@12.0.0: + dependencies: + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + atomically: 2.1.0 + debounce-fn: 5.1.2 + dot-prop: 8.0.2 + env-paths: 3.0.0 + json-schema-typed: 8.0.2 + semver: 7.7.3 + uint8array-extras: 0.3.0 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-uri-to-buffer@4.0.1: {} + + debounce-fn@5.1.2: + dependencies: + mimic-fn: 4.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-lazy-prop@3.0.0: {} + + dot-prop@8.0.2: + dependencies: + type-fest: 3.13.1 + + dotenv@16.6.1: {} + + eastasianwidth@0.2.0: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + env-paths@3.0.0: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + fsevents@2.3.3: + optional: true + + get-east-asian-width@1.4.0: {} + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@11.1.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.1.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.1 + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-flag@4.0.0: {} + + html-escaper@2.0.2: {} + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + import-cwd@3.0.0: + dependencies: + import-from: 3.0.0 + + import-from@3.0.0: + dependencies: + resolve-from: 5.0.0 + + inherits@2.0.4: {} + + inquirer@9.3.8: + dependencies: + '@inquirer/external-editor': 1.0.3 + '@inquirer/figures': 1.0.15 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + transitivePeerDependencies: + - '@types/node' + + is-docker@3.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-interactive@1.0.0: {} + + is-interactive@2.0.0: {} + + is-unicode-supported@0.1.0: {} + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + + loupe@3.2.1: {} + + lru-cache@10.4.3: {} + + lru-cache@11.2.5: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + + mimic-fn@2.1.0: {} + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + ms@2.1.3: {} + + mute-stream@1.0.0: {} + + nanoid@3.3.11: {} + + neo-async@2.6.2: {} + + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + package-json-from-dist@1.0.1: {} + + parse-github-url@1.0.3: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-scurry@2.0.1: + dependencies: + lru-cache: 11.2.5 + minipass: 7.1.2 + + pathe@1.1.2: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + require-from-string@2.0.2: {} + + resolve-from@5.0.0: {} + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + + run-applescript@7.1.0: {} + + run-async@3.0.0: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + stdin-discarder@0.2.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + stubborn-fs@2.0.0: + dependencies: + stubborn-utils: 1.0.2 + + stubborn-utils@1.0.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.5.0 + minimatch: 9.0.5 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + tr46@0.0.3: {} + + tslib@2.8.1: {} + + type-fest@0.21.3: {} + + type-fest@3.13.1: {} + + uglify-js@3.19.3: + optional: true + + uint8array-extras@0.3.0: {} + + util-deprecate@1.0.2: {} + + uuid@11.1.0: {} + + vite-node@2.1.9: + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21 + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21: + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.57.1 + optionalDependencies: + fsevents: 2.3.3 + + vitest@2.1.9: + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21 + vite-node: 2.1.9 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + web-streams-polyfill@3.3.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + when-exit@2.1.5: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wordwrap@1.0.0: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.0 + + yoctocolors-cjs@2.1.3: {} diff --git a/src/commands/apps.js b/src/commands/apps.js deleted file mode 100644 index a90c8c5..0000000 --- a/src/commands/apps.js +++ /dev/null @@ -1,356 +0,0 @@ -import path from 'path'; -import chalk from 'chalk'; -import fetch from 'node-fetch'; -import Table from 'cli-table3'; -import { displayNonNullValues, formatDate } from '../utils.js'; -import { API_BASE, getHeaders, getDefaultHomePage, isValidAppName, resolvePath } from '../commons.js'; -import { createSubdomain, getSubdomains } from './subdomains.js'; -import { deleteSite } from './sites.js'; -import { copyFile, createFile, listRemoteFiles, pathExists, removeFileOrDirectory } from './files.js'; -import { getCurrentDirectory } from './auth.js'; -import crypto from '../crypto.js'; -import { getPuter } from '../modules/PuterModule.js'; - -/** - * List all apps - * - * @param {object} options - * ```json - * { - * statsPeriod: [all (default), today, yesterday, 7d, 30d, this_month, last_month, this_year, last_year, month_to_date, year_to_date, last_12_months], - * iconSize: [16, 32, 64, 128, 256, 512] - * } - * ``` - */ -export async function listApps({ statsPeriod = 'all', iconSize = 64 } = {}) { - console.log(chalk.green(`Listing of apps during period "${chalk.cyan(statsPeriod)}" (try also: today, yesterday, 7d, 30d, this_month, last_month):\n`)); - const puter = getPuter(); - try { - const result = await puter.apps.list({ - icon_size: iconSize, - stats_period: statsPeriod - }); - if (result) { - // Create a new table instance - const table = new Table({ - head: [ - chalk.cyan('#'), - chalk.cyan('Title'), - chalk.cyan('Name'), - chalk.cyan('Created'), - chalk.cyan('Subdomain'), - // chalk.cyan('Description'), - chalk.cyan('#Open'), - chalk.cyan('#User') - ], - colWidths: [5, 20, 30, 25, 35, 8, 8], - wordWrap: false - }); - - // Populate the table with app data - let i = 0; - for (const app of result) { - table.push([ - i++, - app['title'], - app['name'], - formatDate(app['created_at']), - app['index_url']?app['index_url'].split('.')[0].split('//')[1]:'', - // app['description'].slice(0, 10) || 'N/A', - app['stats']['open_count'], - app['stats']['user_count'] - ]); - } - - // Display the table - console.log(table.toString()); - console.log(chalk.green(`You have in total: ${chalk.cyan(result.length)} application(s).`)); - } else { - console.error(chalk.red('Unable to list your apps. Please check your credentials.')); - } - } catch (error) { - console.error(chalk.red(`Failed to list apps. Error: ${error.message}`)); - } -} - -/** - * Get app informations - * - * @param {Array} List of options (only "name" is supported at the moment) - * @example: - * ```json - * const data = await appInfo("app name"); - * ``` - */ -export async function appInfo(args = []) { - if (!args || args.length == 0){ - console.log(chalk.red('Usage: app ')); - return; - } - const appName = args[0].trim() - console.log(chalk.green(`Looking for "${chalk.dim(appName)}" app informations:\n`)); - const puter = getPuter(); - try { - const result = await puter.apps.get(appName); - if (result) { - // Display the informations - displayNonNullValues(result); - } else { - console.error(chalk.red('Could not find this app.')); - } - } catch (error) { - console.error(chalk.red(`Failed to get app info. Error: ${error.message}`)); - } -} - -/** - * Create a new web application - * @param {string} name The name of the App - * @param {string} directory Optional directory path - * @param {string} description A description of the App - * @param {string} url A default coming-soon URL - * @returns {Promise} Output JSON data - */ -export async function createApp(args) { - const name = args.name; // App name (required) - if (!name || !isValidAppName(name)) { - console.log(chalk.red('Usage: app:create ')); - console.log(chalk.yellow('Example: app:create myApp .')); - console.log(chalk.yellow('Example: app:create myApp ./myApp')); - return; - } - // Use the default home page if the root directory if none specified - const localDir = args.directory ? resolvePath(getCurrentDirectory(), args.directory) : ''; - // Optional description - const description = args.description || ''; - const url = args.url || ''; - - console.log(chalk.green(`Creating app "${name}"...`)); - console.log(chalk.dim(`Directory: ${localDir || '[default]'}`)); - console.log(chalk.dim(`Description: ${description}`)); - console.log(chalk.dim(`URL: ${url}`)); - - const puter = getPuter(); - try { - // Step 1: Create the app - const createAppData = await puter.apps.create({ - name: name, - indexURL: url, - title: name, - description: description, - maximizeOnStart: false, - dedupeName: true - }); - if (!createAppData) { - console.error(chalk.red(`Failed to create app "${name}"`)); - return; - } - const appUid = createAppData.uid; - const appName = createAppData.name; - const username = createAppData.owner.username; - console.log(chalk.green(`App "${chalk.dim(name)}" created successfully!`)); - console.log(chalk.cyan(`AppName: ${chalk.dim(appName)}\nUID: ${chalk.dim(appUid)}\nUsername: ${chalk.dim(username)}`)); - - // Step 2: Create a directory for the app - const uid = crypto.randomUUID(); - const appDir = `/${username}/AppData/${appUid}`; - console.log(chalk.green(`Creating directory...\nPath: ${chalk.dim(appDir)}\nApp: ${chalk.dim(name)}\nUID: ${chalk.dim(uid)}\n`)); - const createDirData = await puter.fs.mkdir(`${appDir}/app-${uid}`, { - overwrite: true, - dedupeName: false, - createMissingParents: true, - }) - if (!createDirData || !createDirData.uid) { - console.error(chalk.red(`Failed to create directory for app "${name}"`)); - return; - } - const dirUid = createDirData.uid; - console.log(chalk.green(`Directory created successfully!`)); - console.log(chalk.cyan(`Directory UID: ${chalk.dim(dirUid)}`)); - - // Step 3: Create a subdomain for the app - const subdomainName = `${name}-${uid.split('-')[0]}`; - const remoteDir = `${appDir}/${createDirData.name}`; - console.log(chalk.green(`Linking to subdomain...\nSubdomain: "${chalk.dim(subdomainName)}"\nPath: ${chalk.dim(remoteDir)}\n`)); - const subdomainResult = await createSubdomain(subdomainName, remoteDir); - if (!subdomainResult) { - console.error(chalk.red(`Failed to create subdomain: "${subdomainName}"`)); - return; - } - console.log(chalk.green(`Subdomain created successfully!`)); - console.log(chalk.cyan(`Subdomain: ${chalk.dim(subdomainName)}`)); - - // Step 4: Create a home page - if (localDir.length > 0){ - // List files in the current "localDir" then copy them to the "remoteDir" - const files = await listRemoteFiles(localDir); - if (Array.isArray(files) && files.length > 0) { - console.log(chalk.cyan(`Copying ${chalk.dim(files.length)} files from: ${chalk.dim(localDir)}`)); - console.log(chalk.cyan(`To destination: ${chalk.dim(remoteDir)}`)); - for (const file of files) { - const fileSource = path.join(localDir, file.name); - await copyFile([fileSource, remoteDir]); - } - } else { - console.log(chalk.yellow("We could not find any file in the specified directory!")); - } - } else { - const homePageResult = await createFile([path.join(remoteDir, 'index.html'), getDefaultHomePage(appName)]); - if (!homePageResult){ - console.log(chalk.yellow("We could not create the home page file!")); - } - } - - // Step 5: Update the app's index_url to point to the subdomain - console.log(chalk.green(`Set "${chalk.dim(subdomainName)}" as a subdomain for app: "${chalk.dim(appName)}"...\n`)); - const updateAppData = await puter.apps.update(appName, { - indexURL: `https://${subdomainName}.puter.site`, - title: name - }) - if (!updateAppData) { - console.error(chalk.red(`Failed to update app "${name}" with new subdomain`)); - return; - } - console.log(chalk.green(`App deployed successfully at:`)); - console.log(chalk.cyanBright(`https://${subdomainName}.puter.site`)); - } catch (error) { - console.error(chalk.red(`Failed to create app "${name}".\nError: ${error.message}`)); - } -} - -/** - * Update an application from the directory - * @param {string} name The name of the App - * @param {string} remote_dir The remote directory - */ -export async function updateApp(args = []) { - if (args.length < 1) { - console.log(chalk.red('Usage: app:update []')); - console.log(chalk.yellow('Example: app:create myapp')); - console.log(chalk.yellow('Example: app:create myapp ./myapp')); - return; - } - const name = args[0]; // App name (required) - // Fix: Properly handle absolute paths by checking if the path starts with '/' - let remoteDir; - if (args[1] && args[1].startsWith('/')) { - remoteDir = args[1]; // Use the absolute path as-is - } else { - remoteDir = resolvePath(getCurrentDirectory(), args[1] || '.'); - } - - const remoteDirExists = await pathExists(remoteDir); - - if (!remoteDirExists){ - console.log(chalk.red(`Cannot find directory: ${chalk.dim(remoteDir)}...\n`)); - return; - } - - const puter = getPuter(); - console.log(chalk.green(`Updating app: "${chalk.dim(name)}" from directory: ${chalk.dim(remoteDir)}\n`)); - try { - // Step 1: Get the app info - const data = await puter.apps.get(name); - if (!data) { - console.error(chalk.red(`Failed to find app: "${name}"`)); - return; - } - const appUid = data.uid; - const appName = data.name; - const username = data.owner.username; - const indexUrl = data.index_url; - const appDir = `/${username}/AppData/${appUid}`; - console.log(chalk.cyan(`AppName: ${chalk.dim(appName)}\nUID: ${chalk.dim(appUid)}\nUsername: ${chalk.dim(username)}`)); - - // Step 2: Find the path from subdomain - const subdomains = await getSubdomains(); - const appSubdomain = subdomains.find(sd => sd.root_dir?.dirname?.endsWith(appUid)); - if (!appSubdomain){ - console.error(chalk.red(`Sorry! We could not find the subdomain for ${chalk.cyan(name)} application.`)); - return; - } - const subdomainDir = appSubdomain['root_dir']['path']; - if (!subdomainDir){ - console.error(chalk.red(`Sorry! We could not find the path for ${chalk.cyan(name)} application.`)); - return; - } - - // Step 3: List files in the current "remoteDir" then copy them to the "subdomainDir" - const files = await listRemoteFiles(remoteDir); - if (Array.isArray(files) && files.length > 0) { - console.log(chalk.cyan(`Copying ${chalk.dim(files.length)} files from: ${chalk.dim(remoteDir)}`)); - console.log(chalk.cyan(`To destination: ${chalk.dim(subdomainDir)}`)); - for (const file of files) { - const fileSource = path.join(remoteDir, file.name); - const fileDest = path.join(subdomainDir, file.name); - if ((await pathExists(fileDest))){ - await removeFileOrDirectory([fileDest, '-f']); - } - await copyFile([fileSource, subdomainDir]); - } - } else { - console.log(chalk.red("We could not find any file in the specified directory!")); - } - - console.log(chalk.green(`App updated successfully at:`)); - console.log(chalk.dim(indexUrl)); - } catch (error) { - console.error(chalk.red(`Failed to update app "${name}".\nError: ${error.message}`)); - console.error(error); - } -} - -/** - * Delete an app by its name - * @param {string} name The name of the app to delete - * @returns a boolean success value - */ -export async function deleteApp(name) { - if (!name || name.length == 0){ - console.log(chalk.red('Usage: app:delete ')); - return false; - } - const puter = getPuter(); - console.log(chalk.green(`Checking app "${name}"...\n`)); - try { - // Step 1: Read app details - const readData = await puter.apps.get(name); - - if (!readData) { - console.log(chalk.red(`App "${chalk.bold(name)}" not found.`)); - return false; - } - - // Show app details and confirm deletion - console.log(chalk.cyan('\nApp Details:')); - console.log(chalk.dim('----------------------------------------')); - console.log(chalk.dim(`Name: ${chalk.cyan(readData.name)}`)); - console.log(chalk.dim(`Title: ${chalk.cyan(readData.title)}`)); - console.log(chalk.dim(`Created: ${chalk.cyan(formatDate(readData.created_at))}`)); - console.log(chalk.dim(`URL: ${readData.index_url}`)); - console.log(chalk.dim('----------------------------------------')); - - // Step 2: Delete the app - console.log(chalk.green(`Deleting app "${chalk.red(name)}"...`)); - - const deleteData = await puter.apps.delete(name); - if (!deleteData) { - console.error(chalk.red(`Failed to delete app "${name}".\nP.S. Make sure to provide the 'name' attribute not the 'title'.`)); - return false; - } - - // Lookup subdomainUID then delete it - const subdomains = await getSubdomains(); - const appSubdomain = subdomains.find(sd => sd.root_dir?.dirname?.endsWith(readData.uid)); - const subdomainDeleted = await deleteSite([appSubdomain.uid]); - if (subdomainDeleted){ - console.log(chalk.green(`Subdomain: ${chalk.dim(appSubdomain.uid)} deleted.`)); - } - - console.log(chalk.green(`App "${chalk.dim(name)}" deleted successfully!`)); - } catch (error) { - console.error(chalk.red(`Failed to delete app "${name}".\nError: ${error.message}`)); - return false; - } - return true; -} diff --git a/src/commands/auth.js b/src/commands/auth.js index e5e0052..2df4900 100644 --- a/src/commands/auth.js +++ b/src/commands/auth.js @@ -1,7 +1,7 @@ import chalk from 'chalk'; import Conf from 'conf'; import ora from 'ora'; -import { PROJECT_NAME, } from '../commons.js' +import { HOME, PROJECT_NAME, expandHome } from '../commons.js' import { getProfileModule } from '../modules/ProfileModule.js'; import { getPuter } from '../modules/PuterModule.js'; const config = new Conf({ projectName: PROJECT_NAME }); @@ -29,18 +29,10 @@ export async function logout() { let spinner; try { spinner = ora('Logging out from Puter...').start(); - const token = config.get('auth_token'); const selected_profile = config.get('selected_profile'); - if (token) { - // legacy auth - config.clear(); - spinner.succeed(chalk.green('Successfully logged out from Puter!')); - } else if (selected_profile) { - // multi profile auth + if (selected_profile) { config.delete('selected_profile'); - config.delete('username'); - config.delete('cwd'); const profiles = config.get('profiles'); config.set('profiles', profiles.filter(profile => profile.uuid != selected_profile)); @@ -86,7 +78,10 @@ export async function getUserInfo() { } } export function isAuthenticated() { - return !!config.get('auth_token'); + const uuid = config.get('selected_profile'); + if (!uuid) return false; + const profiles = config.get('profiles') ?? []; + return !!profiles.find(p => p.uuid === uuid)?.token; } export function getAuthToken() { @@ -100,7 +95,7 @@ export function getCurrentUserName() { } export function getCurrentDirectory() { - return config.get('cwd'); + return expandHome(getProfileModule().getCwd()); } /** diff --git a/src/commands/deploy.js b/src/commands/deploy.js deleted file mode 100644 index da1783a..0000000 --- a/src/commands/deploy.js +++ /dev/null @@ -1,54 +0,0 @@ -import chalk from 'chalk'; -import { generateAppName } from '../commons.js'; -import { syncDirectory } from './files.js'; -import { createSite } from './sites.js'; -import { getPuter } from '../modules/PuterModule.js'; - -/** - * Deploy a local web project to Puter. - * @param {string[]} args - Command-line arguments (e.g., [--subdomain=]). - */ -export async function deploy(args = []) { - if (args.length < 1) { - console.log(chalk.red('Usage: site:deploy [--subdomain=]')); - console.log(chalk.yellow('Example: site:deploy .')); - console.log(chalk.yellow('Example: site:deploy ./dist')); - console.log(chalk.yellow('Example: site:deploy ./dist --subdomain=my-app-new')); - return; - } - const puter = getPuter(); - - const sourceDirArg = args.find(arg => !arg.startsWith('--')); - const sourceDir = sourceDirArg || '.'; - - let subdomain = args.find(arg => arg.startsWith('--subdomain='))?.split('=')[1]; - if (!subdomain) { - subdomain = generateAppName(); - } - - const remoteDir = `~/sites/${subdomain}/deployment`; - - // this will handle the increments - const directory = await puter.fs.mkdir(remoteDir, { - dedupeName: true, - createMissingParents: true - }) - - console.log(chalk.cyan(`Deploying '${sourceDir}' to '${subdomain}.puter.site'...`)); - - try { - // 1. Upload files - await syncDirectory([sourceDir, directory.path, '--delete', '-r', '--overwrite']); - - // 2. Create the site - const site = await createSite([subdomain, directory.path, `--subdomain=${subdomain}`]); - - if (site) { - console.log(chalk.green('Deployment successful!')); - } else { - console.log(chalk.yellow('Deployment successfuly updated!')); - } - } catch (error) { - console.error(chalk.red(`Deployment failed: ${error.message}`)); - } -} \ No newline at end of file diff --git a/src/commands/files.js b/src/commands/files.js index e6fbd46..ceb3b74 100644 --- a/src/commands/files.js +++ b/src/commands/files.js @@ -7,10 +7,10 @@ import { minimatch } from 'minimatch'; import chalk from 'chalk'; import Conf from 'conf'; import fetch from 'node-fetch'; -import { API_BASE, BASE_URL, PROJECT_NAME, getHeaders, showDiskSpaceUsage, resolvePath, resolveRemotePath } from '../commons.js'; +import { API_BASE, BASE_URL, HOME, PROJECT_NAME, expandHome, getHeaders, isAbsolutePath, showDiskSpaceUsage, resolvePath, resolveRemotePath } from '../commons.js'; import { formatDateTime, formatSize, getSystemEditor } from '../utils.js'; import inquirer from 'inquirer'; -import { getAuthToken, getCurrentDirectory, getCurrentUserName } from './auth.js'; +import { getAuthToken, getCurrentDirectory } from './auth.js'; import { updatePrompt } from './shell.js'; import crypto from '../crypto.js'; import { getPuter } from '../modules/PuterModule.js'; @@ -85,7 +85,7 @@ export async function makeDirectory(args = []) { const puter = getPuter(); try { - const data = await puter.fs.mkdir(`${getCurrentDirectory()}/${directoryName}`, { + const data = await puter.fs.mkdir(resolvePath(getCurrentDirectory(), directoryName), { overwrite: false, dedupeName: true, createMissingParents: false @@ -314,7 +314,7 @@ export async function removeFileOrDirectory(args = []) { const uid = statData.uid; // Step 4.2: Perform the move operation to Trash - const moveData = await puter.fs.move(uid, `/${getCurrentUserName()}/Trash`, { + const moveData = await puter.fs.move(uid, expandHome(`${HOME}/Trash`), { overwrite: false, newName: uid, createMissingParents: false, @@ -385,7 +385,7 @@ export async function deleteFolder(folderPath, skipConfirmation = false) { * @param {boolean} skipConfirmation - Whether to skip the confirmation prompt. */ export async function emptyTrash(skipConfirmation = true) { - const trashPath = `/${getCurrentUserName()}/Trash`; + const trashPath = expandHome(`${HOME}/Trash`); await deleteFolder(trashPath, skipConfirmation); } @@ -398,7 +398,7 @@ export async function getInfo(args = []) { const puter = getPuter(); for (let name of names) try { - name = `${getCurrentDirectory()}/${name}`; + name = resolvePath(getCurrentDirectory(), name); console.log(chalk.green(`Getting stat info for: "${name}"...\n`)); const data = await puter.fs.stat(name); if (data) { @@ -425,7 +425,7 @@ export async function getInfo(args = []) { * Show the current working directory */ export async function showCwd() { - console.log(chalk.green(`${config.get('cwd')}`)); + console.log(chalk.green(`${getCurrentDirectory()}`)); } /** @@ -434,7 +434,7 @@ export async function showCwd() { * @returns void */ export async function changeDirectory(args) { - let currentPath = config.get('cwd'); + let currentPath = getCurrentDirectory(); // If no arguments, print the current directory if (!args.length) { console.log(chalk.green(currentPath)); @@ -443,17 +443,15 @@ export async function changeDirectory(args) { const puter = getPuter(); const path = args[0]; - // Handle "/","~",".." and deeper navigation - const newPath = path.startsWith('/')? path: (path === '~'? `/${getCurrentUserName()}` :resolvePath(currentPath, path)); + // resolvePath handles "/", "~", "~/...", "." and ".." in one place. + const newPath = resolvePath(currentPath, path); try { // Check if the new path is a valid directory const data = await puter.fs.stat(newPath); if (data && data.is_dir) { - // Update the newPath to use the correct name from the response - const arrayDirs = newPath.split('/'); - arrayDirs.pop(); - arrayDirs.push(data.name); - updatePrompt(arrayDirs.join('/')); // Update the shell prompt + // Adopt the server's canonical path, which is already rooted at the + // account's current home directory. + updatePrompt(data.path || newPath); // Update the shell prompt } else { console.log(chalk.red(`"${newPath}" is not a directory`)); } @@ -519,7 +517,7 @@ export async function createFile(args = []) { const filePath = args[0]; // File path (e.g., "app/index.html") const content = args.length > 1 ? args.slice(1).join(' ') : ''; // Optional content let fullPath = filePath; - if (!filePath.startsWith(`/${getCurrentUserName()}/`)){ + if (!isAbsolutePath(filePath)) { fullPath = resolvePath(getCurrentDirectory(), filePath); // Resolve the full path } const dirName = path.dirname(fullPath); // Extract the directory name @@ -763,8 +761,8 @@ export async function copyFile(args = []) { return; } - const sourcePath = args[0].startsWith(`/${getCurrentUserName()}`) ? args[0] : resolvePath(getCurrentDirectory(), args[0]); // Resolve the source path - const destinationPath = args[1].startsWith(`/${getCurrentUserName()}`) ? args[1] : resolvePath(getCurrentDirectory(), args[1]); // Resolve the destination path + const sourcePath = resolvePath(getCurrentDirectory(), args[0]); // Resolve the source path + const destinationPath = resolvePath(getCurrentDirectory(), args[1]); // Resolve the destination path console.log(chalk.green(`Copy: "${chalk.dim(sourcePath)}" to: "${chalk.dim(destinationPath)}"...\n`)); const puter = getPuter(); diff --git a/src/commands/init.js b/src/commands/init.js deleted file mode 100644 index 54b9a7a..0000000 --- a/src/commands/init.js +++ /dev/null @@ -1,322 +0,0 @@ -import inquirer from 'inquirer'; -import chalk from 'chalk'; -import ora from 'ora'; -import { promises as fs } from 'fs'; -import path from 'path'; -import { generateAppName, getDefaultHomePage } from '../commons.js'; -import { getProfileModule } from '../modules/ProfileModule.js'; - -const JS_BUNDLERS = ['Vite', 'Webpack', 'Parcel', 'esbuild', 'Farm']; -const FULLSTACK_FRAMEWORKS = ['Next', 'Nuxt', 'SvelteKit', 'Astro']; -const JS_LIBRARIES = ['React', 'Vue', 'Angular', 'Svelte', 'jQuery']; -const CSS_LIBRARIES = ['Bootstrap', 'Bulma', 'shadcn', 'Tailwind', 'Material-UI', 'Semantic UI', 'AntDesign', 'Element-Plus', 'PostCSS', 'AutoPrefixer']; - -export async function init() { - const profileModule = getProfileModule(); - await profileModule.checkLogin(); - - const answers = await inquirer.prompt([ - { - type: 'input', - name: 'name', - message: 'What is your app name?', - default: `${generateAppName()}` - }, - { - type: 'list', - name: 'useBundler', - message: 'Do you want to use a JavaScript bundler?', - choices: ['Yes', 'No (Use CDN)'] - } - ]); - - let jsFiles = []; - let jsDevFiles = []; - let cssFiles = []; - let jsExtraLibraries = []; - let extraFiles = []; - let bundlerAnswers = null; - let frameworkAnswers = null; - - if (answers.useBundler === 'Yes') { - bundlerAnswers = await inquirer.prompt([ - { - type: 'list', - name: 'bundler', - message: 'Select a JavaScript bundler:', - choices: JS_BUNDLERS - }, - { - type: 'list', - name: 'frameworkType', - message: 'Do you want to use a full-stack framework or custom libraries?', - choices: ['Full-stack framework', 'Custom libraries'] - } - ]); - - if (bundlerAnswers.frameworkType === 'Full-stack framework') { - frameworkAnswers = await inquirer.prompt([ - { - type: 'list', - name: 'framework', - message: 'Select a full-stack framework:', - choices: FULLSTACK_FRAMEWORKS - } - ]); - - switch (frameworkAnswers.framework) { - case FULLSTACK_FRAMEWORKS[0]: - jsFiles.push('next@latest'); - extraFiles.push({ - path: 'src/index.tsx', - content: `export default function Home() { return (

${answers.name}

) }` - }); - break; - case FULLSTACK_FRAMEWORKS[1]: - jsFiles.push('nuxt@latest'); - extraFiles.push({ - path: 'src/app.vue', - content: `` - }); - break; - case FULLSTACK_FRAMEWORKS[2]: - jsFiles.push('svelte@latest', 'sveltekit@latest'); - extraFiles.push({ - path: 'src/app.vue', - content: `` - }); - break; - case FULLSTACK_FRAMEWORKS[3]: - jsFiles.push('astro@latest', 'astro@latest'); - extraFiles.push({ - path: 'src/pages/index.astro', - content: `---\n\n

${answers.name}

` - }); - break; - } - } else { - const libraryAnswers = await inquirer.prompt([ - { - type: 'list', - name: 'library', - message: 'Select a JavaScript library/framework:', - choices: JS_LIBRARIES - } - ]); - - switch (libraryAnswers.library) { - case JS_LIBRARIES[0]: - jsFiles.push('react@latest', 'react-dom@latest'); - const reactLibs = await inquirer.prompt([ - { - type: 'checkbox', - name: 'reactLibraries', - message: 'Select React libraries:', - choices: CSS_LIBRARIES.concat(['react-router-dom', 'react-redux', 'react-bootstrap', '@chakra-ui/react', 'semantic-ui-react']) - } - ]); - jsFiles.push(...reactLibs.reactLibraries); - extraFiles.push({ - path: 'src/App.jsx', - content: `export default function Home() { return (

${answers.name}

) }` - }); - break; - case JS_LIBRARIES[1]: - jsFiles.push('vue@latest'); - jsDevFiles.push('@vitejs/plugin-vue'); - const vueLibs = await inquirer.prompt([ - { - type: 'checkbox', - name: 'vueLibraries', - message: 'Select Vue libraries:', - choices: CSS_LIBRARIES.concat(['shadcn-vue', 'UnoCSS', 'NaiveUI', 'bootstrap-vue-next', 'buefy', 'vue-router', 'pinia']) - } - ]); - jsFiles.push(...vueLibs.vueLibraries); - extraFiles.push( - { - path: 'src/App.vue', - content: `` - }, - { - path: 'vite.config.js', - content: `import { defineConfig } from 'vite'; -import vue from '@vitejs/plugin-vue'; - -export default defineConfig({ - plugins: [vue()] -}) -`}, - { - path: 'main.js', - content: `import { createApp } from 'vue' -import './style.css'; -import App from './App.vue'; - -const app = createApp(App); -app.mount('#app'); - `}, - ); - break; - case JS_LIBRARIES[2]: - jsFiles.push('@angular/core@latest'); - extraFiles.push({ - path: 'src/index.controller.js', - content: `(function () { angular.module('app', [])})` - }); - break; - case JS_LIBRARIES[3]: - jsFiles.push('svelte@latest'); - break; - case JS_LIBRARIES[4]: - jsFiles.push('jquery@latest'); - extraFiles.push({ - path: 'src/main.js', - content: `$(function(){})` - }); - break; - } - } - } else { - - const cdnAnswers = await inquirer.prompt([ - { - type: 'list', - name: 'jsFramework', - message: 'Select a JavaScript framework/library (CDN):', - choices: JS_LIBRARIES - }, - { - type: 'list', - name: 'cssFramework', - message: 'Select a CSS framework/library (CDN):', - choices: CSS_LIBRARIES //'Tailwind', 'Bootstrap', 'Bulma'... - } - ]); - - switch (cdnAnswers.jsFramework) { - case JS_LIBRARIES[0]: - jsFiles.push('https://unpkg.com/react@latest/umd/react.production.min.js'); - jsFiles.push('https://unpkg.com/react-dom@latest/umd/react-dom.production.min.js'); - break; - case JS_LIBRARIES[1]: - jsFiles.push('https://unpkg.com/vue@latest/dist/vue.global.js'); - break; - case JS_LIBRARIES[2]: - jsFiles.push('https://unpkg.com/@angular/core@latest/bundles/core.umd.js'); - break; - case JS_LIBRARIES[3]: - jsFiles.push('https://unpkg.com/svelte@latest/compiled/svelte.js'); - break; - case JS_LIBRARIES[4]: - jsFiles.push('https://code.jquery.com/jquery-latest.min.js'); - break; - } - - switch (cdnAnswers.cssFramework) { - case CSS_LIBRARIES[0]: - cssFiles.push('https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css'); - break; - case CSS_LIBRARIES[1]: - cssFiles.push('https://cdn.jsdelivr.net/npm/bulma@latest/css/bulma.min.css'); - break; - case CSS_LIBRARIES[2]: - cssFiles.push('https://cdn.tailwindcss.com'); - break; - } - } - - - const spinner = ora('Creating Puter app...').start(); - - try { - const useBundler = answers.useBundler === 'Yes'; - // Create basic app structure - await createAppStructure(answers.name, useBundler, bundlerAnswers, frameworkAnswers, jsFiles, jsDevFiles, cssFiles, extraFiles); - spinner.succeed(chalk.green('Successfully created Puter app!')); - - console.log('\nNext steps:'); - console.log(chalk.cyan('1. cd'), answers.name); - if (useBundler) { - console.log(chalk.cyan('2. npm install')); - console.log(chalk.cyan('3. npm start')); - } else { - console.log(chalk.cyan('2. Open index.html in your browser')); - } - } catch (error) { - spinner.fail(chalk.red('Failed to create app')); - console.error(error); - } -} - -async function createAppStructure(name, useBundler, bundlerAnswers, frameworkAnswers, jsFiles, jsDevFiles, cssFiles, extraFiles) { - // Create project directory - await fs.mkdir(name, { recursive: true }); - - // Generate default home page - const homePage = useBundler?getDefaultHomePage(name): getDefaultHomePage(name, jsFiles, cssFiles); - - // Create basic files - const files = { - '.env': `APP_NAME=${name}\nPUTER_API_KEY=`, - 'index.html': homePage, - 'styles.css': `body { - font-family: 'Segoe UI', Roboto, sans-serif; - margin: 0 auto; - padding: 10px; - }`, - 'app.js': `// Initialize Puter app -console.log('Puter app initialized!');`, - 'README.md': `# ${name}\n\nA Puter app created with puter-cli` - }; - - for (const [filename, content] of Object.entries(files)) { - await fs.writeFile(path.join(name, filename), content); - } - - // If using a bundler, create a package.json - // if (jsFiles.some(file => !file.startsWith('http'))) { - if (useBundler) { - - const useFullStackFramework = bundlerAnswers.frameworkType === 'Full-stack framework'; - const bundler = bundlerAnswers.bundler.toString().toLowerCase(); - const framework = useFullStackFramework?frameworkAnswers.framework.toLowerCase():null; - - const scripts = { - start: `${useFullStackFramework?`${framework} dev`:bundler} dev`, - build: `${useFullStackFramework?`${framework} build`:bundler} build`, - }; - - const packageJson = { - name: name, - version: '1.0.0', - type: 'module', - scripts, - dependencies: {}, - devDependencies: {} - }; - - - jsFiles.forEach(lib => { - if (!lib.startsWith('http')) { - packageJson.dependencies[lib.split('@')[0].toString().toLowerCase()] = lib.split('@')[1] || 'latest'; - } - }); - - jsDevFiles.forEach(lib => { - packageJson.devDependencies[lib] = 'latest'; - }); - - packageJson.devDependencies[bundler] = 'latest'; - - await fs.writeFile(path.join(name, 'package.json'), JSON.stringify(packageJson, null, 2)); - - extraFiles.forEach(async (extraFile) => { - const fullPath = path.join(name, extraFile.path); - // Create directories recursively if they don't exist - await fs.mkdir(path.dirname(fullPath), { recursive: true }); - await fs.writeFile(fullPath, extraFile.content); - }); - - } -} \ No newline at end of file diff --git a/src/commands/shell.js b/src/commands/shell.js index 1c688a8..f68f8f6 100644 --- a/src/commands/shell.js +++ b/src/commands/shell.js @@ -4,6 +4,7 @@ import Conf from 'conf'; import { execCommand, getPrompt } from '../executor.js'; import { PROJECT_NAME } from '../commons.js'; import { getProfileModule } from '../modules/ProfileModule.js'; +import { report, formatError } from '../modules/ErrorModule.js'; const config = new Conf({ projectName: PROJECT_NAME }); @@ -13,7 +14,7 @@ export let rl; * Update the current shell prompt */ export function updatePrompt(currentPath) { - config.set('cwd', currentPath); + getProfileModule().setCwd(currentPath); rl.setPrompt(getPrompt()); } @@ -37,7 +38,7 @@ export async function startShell(command) { }) try { - console.log(chalk.green('Welcome to Puter-CLI! Type "help" for available commands.')); + console.log(chalk.green('Welcome to Puter Shell! Type "help" for available commands.')); rl.setPrompt(getPrompt()); rl.prompt(); @@ -47,7 +48,8 @@ export async function startShell(command) { try { await execCommand(trimmedLine); } catch (error) { - console.error(chalk.red(error.message)); + report(error); + console.error(chalk.red(formatError(error))); } } rl.prompt(); diff --git a/src/commands/sites.js b/src/commands/sites.js deleted file mode 100644 index 52c5b2c..0000000 --- a/src/commands/sites.js +++ /dev/null @@ -1,169 +0,0 @@ -import chalk from 'chalk'; -import Table from 'cli-table3'; -import { getCurrentUserName, getCurrentDirectory } from './auth.js'; -import { resolveRemotePath, isValidAppName } from '../commons.js'; -import { displayNonNullValues, formatDate, isValidAppUuid } from '../utils.js'; -import { getSubdomains, createSubdomain, deleteSubdomain, updateSubdomain } from './subdomains.js'; -import { getPuter } from '../modules/PuterModule.js'; -import { report } from '../modules/ErrorModule.js'; - -/** - * Listing subdomains - */ -export async function listSites(args = {}) { - try { - const result = await getSubdomains(args); - - // Create table instance - const table = new Table({ - head: [ - chalk.cyan('#'), - chalk.cyan('UID'), - chalk.cyan('Subdomain'), - chalk.cyan('Created'), - chalk.cyan('Protected'), - // chalk.cyan('Owner'), - chalk.cyan('Directory') - ], - wordWrap: false - }); - - // Format and add data to table - let i = 0; - result.forEach(domain => { - let appDir = domain?.root_dir?.path.split('/').pop().split('-'); - table.push([ - i++, - domain.uid, - chalk.green(`${chalk.dim(domain.subdomain)}.puter.site`), - formatDate(domain.created_at).split(',')[0], - domain.protected ? chalk.red('Yes') : chalk.green('No'), - // domain.owner['username'], - appDir && (isValidAppUuid(appDir.join('-'))?`${appDir[0]}-...-${appDir.slice(-1)}`:appDir.join('-')) - ]); - }); - - // Print table - if (result.length === 0) { - console.log(chalk.yellow('No subdomains found')); - } else { - console.log(chalk.bold('\nYour Sites:')); - console.log(table.toString()); - console.log(chalk.dim(`Total Sites: ${result.length}`)); - } - - } catch (error) { - report(error); - console.error(chalk.red('Error listing sites:'), error.message); - throw error; - } -} - -/** - * Get Site info - * @param {any[]} args Array of site uuid - */ -export async function infoSite(args = []) { - if (args.length < 1){ - console.log(chalk.red('Usage: site ')); - return; - } - const puter = getPuter(); - for (const subdomain of args) - try { - const result = await puter.hosting.get(subdomain); - displayNonNullValues(result); - } catch (error) { - console.error(chalk.red('Error getting site info:'), error.message); - } - } - - /** - * Delete hosted web site - * @param {any[]} args Array of subdomain - */ - export async function deleteSite(args = []) { - if (args.length < 1){ - console.log(chalk.red('Usage: site:delete ')); - return false; - } - await deleteSubdomain(args); - return true; - } - - /** - * Create a static web app from the current directory to Puter cloud. - * @param {string[]} args - Command-line arguments (e.g., [name, --subdomain=]). - */ - export async function createSite(args = []) { - if (args.length < 1 || !isValidAppName(args[0])) { - console.log(chalk.red('Usage: site:create [] [--subdomain=]')); - console.log(chalk.yellow('Example: site:create mysite')); - console.log(chalk.yellow('Example: site:create mysite ./mysite')); - console.log(chalk.yellow('Example: site:create mysite --subdomain=mysite')); - return; - } - - const appName = args[0]; // Site name (required) - const subdomainOption = args.find(arg => arg.toLocaleLowerCase().startsWith('--subdomain='))?.split('=')[1]; // Optional subdomain - const remoteDirArg = (args[1] && !args[1].startsWith('--')) ? args[1] : '.'; - - // Use the current directory as the root directory if none specified - const remoteDir = resolveRemotePath(getCurrentDirectory(), remoteDirArg); - - console.log(chalk.dim(`Creating site ${chalk.green(appName)} from: ${chalk.green(remoteDir)}...\n`)); - try { - // Step 1: Determine the subdomain - let subdomain; - if (subdomainOption) { - subdomain = subdomainOption; // Use the provided subdomain - } else { - subdomain = appName; // Default to the app name as the subdomain - } - - // Step 2: Check if the subdomain already exists - const subdomains = await getSubdomains();; - const subdomainObj = subdomains.find(sd => sd.subdomain === subdomain); - if (subdomainObj) { - console.error(chalk.cyan(`The subdomain "${subdomain}" is already in use and owned by: "${subdomainObj.owner['username']}"`)); - if (subdomainObj.owner['username'] === getCurrentUserName()){ - console.log(chalk.green(`It's yours, and linked to: ${subdomainObj.root_dir?.path}`)); - if (subdomainObj.root_dir?.path === remoteDir){ - console.log(chalk.cyan(`Which is already the selected directory, and created at:`)); - console.log(chalk.green(`https://${subdomain}.puter.site`)); - return; - } else { - console.log(chalk.yellow(`However, It's linked to different directory at: ${subdomainObj.root_dir?.path}`)); - console.log(chalk.cyan(`Updating this subdomain directory...`)); - const result = await updateSubdomain(subdomain, remoteDir); - if (result) { - console.log(chalk.green('Updating subdomain directory successful.')); - return; - } else { - console.log(chalk.red('Could not update this subdomain directory.')); - return; - } - } - } - } - - // Use the chosen "subdomain" - console.log(chalk.cyan(`New generated subdomain: "${subdomain}" will be used if its not already in use.`)); - - // Step 3: Host the current directory under the subdomain - console.log(chalk.cyan(`Hosting site "${appName}" under subdomain "${subdomain}"...`)); - const site = await createSubdomain(subdomain, remoteDir); - if (!site){ - console.error(chalk.red(`Failed to create subdomain: "${chalk.red(subdomain)}"`)); - return; - } - - console.log(chalk.green(`Site ${chalk.dim(appName)} created successfully and accessible at:`)); - console.log(chalk.cyan(`https://${site.subdomain}.puter.site`)); - return site; - } catch (error) { - console.error(chalk.red('Failed to create site.')); - console.error(chalk.red(`Error: ${error.message}`)); - return null; - } - } diff --git a/src/commands/subdomains.js b/src/commands/subdomains.js deleted file mode 100644 index c26725a..0000000 --- a/src/commands/subdomains.js +++ /dev/null @@ -1,95 +0,0 @@ -import chalk from 'chalk'; -import fetch from 'node-fetch'; -import { API_BASE, getHeaders } from '../commons.js'; -import { getPuter } from '../modules/PuterModule.js'; - -/** - * Get list of subdomains. - * @param {Object} args - Options for the query. - * @returns {Array} - Array of subdomains. - */ -export async function getSubdomains(args = {}) { - const puter = getPuter(); - let result; - - try { - result = await puter.hosting.list(); - } catch (error) { - console.log(chalk.red(`Error when getting subdomains.\nError: ${error?.message}`)); - } - - return result; -} - -/** - * Delete a subdomain by id - * @param {Array} subdomain IDs - * @return {boolean} Result of the operation - */ -export async function deleteSubdomain(args = []) { - if (args.length < 1){ - console.log(chalk.red('Usage: domain:delete ')); - return false; - } - const puter = getPuter(); - const subdomains = args; - for (const subdomain of subdomains) - try { - const success = await puter.hosting.delete(subdomain); - - if (!success) { - console.log(chalk.red(`Failed to delete subdomain: ${data.error?.message}`)); - return false; - } - console.log(chalk.green('Subdomain deleted successfully')); - } catch (error) { - if (error.error?.code === 'entity_not_found') { - console.log(chalk.red(`Subdomain: "${subdomain}" not found`)); - return false; - } - console.error(chalk.red('Error deleting subdomain:'), error.message); - } - return true; -} - -/** - * Create a new subdomain into remote directory - * @param {string} subdomain - Subdomain name. - * @param {string} remoteDir - Remote directory path. - * @returns {Object} - Hosting details (e.g., subdomain). - */ -export async function createSubdomain(subdomain, remoteDir) { - const puter = getPuter(); - let result; - - try { - result = await puter.hosting.create(subdomain, remoteDir); - } catch (error) { - if (error?.error?.code === 'already_in_use') { - console.log(chalk.yellow(`Subdomain already taken!\nMessage: ${error?.error?.message}`)); - return false; - } - console.log(chalk.red(`Error when creating "${subdomain}".\nError: ${error?.error?.message}\nCode: ${error?.error?.code}`)); - } - return result; -} - -/** - * Update a subdomain into remote directory - * @param {string} subdomain - Subdomain name. - * @param {string} remoteDir - Remote directory path. - * @returns {Object} - Hosting details (e.g., subdomain). - */ -export async function updateSubdomain(subdomain, remoteDir) { - const puter = getPuter(); - let result; - - try { - result = await puter.hosting.update(subdomain, remoteDir); - } catch (error) { - console.log(chalk.red(`Error when updating "${subdomain}".\nError: ${error?.message}`)); - return null; - } - - return result; -} diff --git a/src/commons.js b/src/commons.js index 7e8fd3e..9429846 100644 --- a/src/commons.js +++ b/src/commons.js @@ -8,11 +8,10 @@ import dotenv from 'dotenv'; dotenv.config(); -export const PROJECT_NAME = 'puter-cli'; +export const PROJECT_NAME = 'puter-sh'; // If you haven't defined your own values in .env file, we'll assume you're running Puter on a local instance: export let API_BASE = process.env.PUTER_API_BASE || 'https://api.puter.com'; export let BASE_URL = process.env.PUTER_BASE_URL || 'https://puter.com'; -export const NULL_UUID = '00000000-0000-0000-0000-000000000000'; export const reconfigureURLs = ({ api, base }) => { API_BASE = api; @@ -39,64 +38,6 @@ export function getHeaders(contentType = 'application/json') { } } -/** - * Generate a random app name - * @returns a random app name or null if it fails - * @see: [randName](https://github.com/HeyPuter/puter/blob/06a67a3b223a6cbd7ec2e16853b6d2304f621a88/src/puter-js/src/index.js#L389) - */ -export function generateAppName(separateWith = '-'){ - console.log(chalk.cyan('Generating random name...')); - try { - const first_adj = ['helpful','sensible', 'loyal', 'honest', 'clever', 'capable','calm', 'smart', 'genius', 'bright', 'charming', 'creative', 'diligent', 'elegant', 'fancy', - 'colorful', 'avid', 'active', 'gentle', 'happy', 'intelligent', 'jolly', 'kind', 'lively', 'merry', 'nice', 'optimistic', 'polite', - 'quiet', 'relaxed', 'silly', 'victorious', 'witty', 'young', 'zealous', 'strong', 'brave', 'agile', 'bold']; - - const nouns = ['street', 'roof', 'floor', 'tv', 'idea', 'morning', 'game', 'wheel', 'shoe', 'bag', 'clock', 'pencil', 'pen', - 'magnet', 'chair', 'table', 'house', 'dog', 'room', 'book', 'car', 'cat', 'tree', - 'flower', 'bird', 'fish', 'sun', 'moon', 'star', 'cloud', 'rain', 'snow', 'wind', 'mountain', - 'river', 'lake', 'sea', 'ocean', 'island', 'bridge', 'road', 'train', 'plane', 'ship', 'bicycle', - 'horse', 'elephant', 'lion', 'tiger', 'bear', 'zebra', 'giraffe', 'monkey', 'snake', 'rabbit', 'duck', - 'goose', 'penguin', 'frog', 'crab', 'shrimp', 'whale', 'octopus', 'spider', 'ant', 'bee', 'butterfly', 'dragonfly', - 'ladybug', 'snail', 'camel', 'kangaroo', 'koala', 'panda', 'piglet', 'sheep', 'wolf', 'fox', 'deer', 'mouse', 'seal', - 'chicken', 'cow', 'dinosaur', 'puppy', 'kitten', 'circle', 'square', 'garden', 'otter', 'bunny', 'meerkat', 'harp'] - - // return a random combination of first_adj + noun + number (between 0 and 9999) - // e.g. clever-idea-123 - const appName = first_adj[Math.floor(Math.random() * first_adj.length)] + separateWith + nouns[Math.floor(Math.random() * nouns.length)] + separateWith + Math.floor(Math.random() * 10000); - console.log(chalk.green(`Name: "${appName}"`)); - return appName; - } catch (error) { - console.error(`Error: ${error.message}`); - return null; - } -} - -/** - * Display data in a structured format - * @param {Array} data - The data to display - * @param {Object} options - Display options - * @param {Array} options.headers - Headers for the table - * @param {Array} options.columns - Columns to display - * @param {number} options.columnWidth - Width of each column - */ -export function displayTable(data, options = {}) { - const { headers = [], columns = [], columnWidth = 20 } = options; - - // Create the header row - const headerRow = headers.map(header => chalk.cyan(header.padEnd(columnWidth))).join(' | '); - console.log(headerRow); - console.log(chalk.dim('-'.repeat(headerRow.length))); - - // Create and display each row of data - data.forEach(item => { - const row = columns.map(col => { - const value = item[col] || 'N/A'; - return value.toString().padEnd(columnWidth); - }).join(' | '); - console.log(row); - }); -} - /** * Display structured ouput of disk usage informations */ @@ -114,225 +55,109 @@ export function showDiskSpaceUsage(data) { } /** - * Resolve a relative path to an absolute path + * The home anchor as typed by the user. + */ +export const HOME = '~'; + +/** + * The concrete home directory ("/") for the active profile. + * + * Resolved from the token at login and refreshed on every run -- never read + * from a cached username -- so it always names the account's current home. + * Null until a profile is selected. + */ +export let HOME_PATH = null; + +/** + * Point the home anchor at the current account's home directory. + * @param {string} homePath - The concrete home path, e.g. "/alice" + */ +export const setHomePath = (homePath) => { + HOME_PATH = homePath; +}; + +/** + * Expand a leading "~" to the resolved home directory. Paths that are not + * home-anchored are returned untouched, as are all paths when no profile is + * active yet (nothing to expand against). + * @param {string} p - The path to expand + * @returns {string} The expanded path + */ +export function expandHome(p) { + if (!HOME_PATH || typeof p !== 'string') return p; + if (p === HOME) return HOME_PATH; + if (p.startsWith('~/')) return `${HOME_PATH}${p.slice(1)}`; + return p; +} + +/** + * Check whether a path is already fully-qualified (root- or home-anchored) and + * therefore must NOT be resolved against the current working directory. + * @param {string} p - The path to test + * @returns {boolean} True if the path is absolute or home-anchored + */ +export function isAbsolutePath(p) { + if (typeof p !== 'string') return false; + return p === HOME || p.startsWith('~/') || p.startsWith('/'); +} + +/** + * Resolve a path against the current working directory. + * + * If `relativePath` is itself absolute ("/...") or home-anchored ("~", "~/..."), + * it replaces `currentPath` entirely instead of being appended to it. + * * @param {string} currentPath - The current working directory - * @param {string} relativePath - The relative path to resolve - * @returns {string} The resolved absolute path + * @param {string} relativePath - The path to resolve + * @returns {string} The resolved path, preserving a "~" root if present */ export function resolvePath(currentPath, relativePath) { - // Normalize the current path (remove trailing slashes) - currentPath = currentPath.replace(/\/+$/, ''); + // A fully-qualified path re-roots the resolution rather than extending it. + if (isAbsolutePath(relativePath)) { + currentPath = relativePath.startsWith('~') ? HOME : '/'; + relativePath = relativePath.replace(/^~/, ''); + } + + // Track whether we are anchored at home so "~" survives normalization. + const atHome = currentPath === HOME || currentPath.startsWith('~/'); + const root = atHome ? HOME : ''; - // Split the relative path into parts - const parts = relativePath.split('/').filter(p => p); // Remove empty parts + // Strip the root and any trailing slashes, leaving bare segments. + let parts = currentPath + .replace(/^~/, '') + .split('/') + .filter(p => p); - // Handle each part of the relative path - for (const part of parts) { + for (const part of relativePath.split('/').filter(p => p)) { if (part === '..') { - // Move one level up - const currentParts = currentPath.split('/').filter(p => p); - if (currentParts.length > 0) { - currentParts.pop(); // Remove the last part - } - currentPath = '/' + currentParts.join('/'); + // Clamp at the root: "~/.." stays at home, "/.." stays at "/". + parts.pop(); } else if (part === '.') { - // Stay in the current directory (no change) continue; } else { - // Move into a subdirectory - currentPath += `/${part}`; + parts.push(part); } } - // Normalize the final path (remove duplicate slashes) - currentPath = currentPath.replace(/\/+/g, '/'); - - // Ensure the path ends with a slash if it's the root - if (currentPath === '') { - currentPath = '/'; - } - - return currentPath; + const joined = parts.join('/'); + // Expand "~" so callers and the API always see a concrete "/" path. + if (!joined) return expandHome(root || '/'); + return expandHome(`${root}/${joined}`); } /** - * Resolve a remote path to an absolute path, handling both absolute and relative paths. + * Resolve a remote path to a fully-qualified path. * @param {string} currentPath - The current working directory. * @param {string} remotePath - The remote path to resolve. - * @returns {string} The resolved absolute path. + * @returns {string} The resolved path. */ export function resolveRemotePath(currentPath, remotePath) { - if (remotePath.startsWith('/')) { - return remotePath; + if (isAbsolutePath(remotePath)) { + return expandHome(remotePath); } return resolvePath(currentPath, remotePath); } -/** -* Checks if a given string is a valid app name. -* The name must: -* - Not be '.' or '..' -* - Not contain path separators ('/' or '\\') -* - Not contain wildcard characters ('*') -* - (Optional) Contain only allowed characters (letters, numbers, spaces, underscores, hyphens) -* -* @param {string} name - The app name to validate. -* @returns {boolean} - Returns true if valid, false otherwise. -*/ -export function isValidAppName(name) { - // Ensure the name is a non-empty string - if (typeof name !== 'string' || name.trim().length === 0) { - return false; - } - - // Trim whitespace from both ends - const trimmedName = name.trim(); - - // Reject reserved names - if (trimmedName === '.' || trimmedName === '..') { - return false; - } - - // Regex patterns for invalid characters - const invalidPattern = /[\/\\*]/; // Disallow /, \, and * - - if (invalidPattern.test(trimmedName)) { - return false; - } - - // Optional: Define allowed characters pattern - // Uncomment the following lines if you want to enforce allowed characters - /* - const allowedPattern = /^[A-Za-z0-9 _-]+$/; - if (!allowedPattern.test(trimmedName)) { - return false; - } - */ - - // All checks passed - return true; -} - -/** - * Generate the default home page for a new web application - * @param {string} appName The name of the web application - * @returns HTML template of the app - */ -export function getDefaultHomePage(appName, jsFiles = [], cssFiles= []) { - const defaultIndexContent = ` - - - - - ${appName} - ${cssFiles.map(css => ``).join('\n ')} - - - -
-

🚀 Welcome to ${appName}!

- -

This is your new website powered by Puter. You can start customizing it right away!

- -
- Quick Tip: Replace this content with your own by editing the index.html file. -
- -

🌟 Getting Started

- -

Here's a simple example using Puter.js:

- -
-<script src="https://js.puter.com/v2/"></script> -<script> - // Create a new file in the cloud - puter.fs.write('hello.txt', 'Hello, Puter!') - .then(file => console.log(\`File created at: \${file.path}\`)); -</script> -
- -

💡 Key Features

-
    -
  • Cloud Storage
  • -
  • AI Services (GPT-4, DALL-E)
  • -
  • Static Website Hosting
  • -
  • Key-Value Store
  • -
  • Authentication
  • -
- - -
- -
- © 2025 ${appName}. All rights reserved. -
- -
-${jsFiles.map(js => -`` -).join('\n ')} - -`; - - return defaultIndexContent; -} - - /** * Read latest package from package file */ diff --git a/src/executor.js b/src/executor.js index be7884c..345a388 100644 --- a/src/executor.js +++ b/src/executor.js @@ -1,21 +1,17 @@ import chalk from 'chalk'; import Conf from 'conf'; -import { listApps, appInfo, createApp, updateApp, deleteApp } from './commands/apps.js'; -import { listSites, createSite, deleteSite, infoSite } from './commands/sites.js'; import { listFiles, makeDirectory, renameFileOrDirectory, removeFileOrDirectory, emptyTrash, changeDirectory, showCwd, getInfo, getDiskUsage, createFile, readFile, uploadFile, downloadFile, copyFile, syncDirectory, editFile } from './commands/files.js'; -import { getUserInfo, getUsageInfo, login } from './commands/auth.js'; -import { deploy } from './commands/deploy.js'; -import { PROJECT_NAME, API_BASE, getHeaders } from './commons.js'; -import inquirer from 'inquirer'; +import { getUserInfo, getUsageInfo, login, getCurrentDirectory } from './commands/auth.js'; +import { PROJECT_NAME, API_BASE, HOME, expandHome, getHeaders } from './commons.js'; import { exec } from 'node:child_process'; -import { parseArgs, getSystemEditor } from './utils.js'; +import { getSystemEditor } from './utils.js'; import { rl } from './commands/shell.js'; -import { showLast } from './modules/ErrorModule.js' +import { showLast, report, formatError, isAuthError } from './modules/ErrorModule.js' const config = new Conf({ projectName: PROJECT_NAME }); @@ -27,7 +23,7 @@ const commandHistory = []; * @returns The current prompt */ export function getPrompt() { - return chalk.cyan(`puter@${config.get('cwd').slice(1)}> `); + return chalk.cyan(`puter@${getCurrentDirectory().slice(1)}> `); } const commands = { @@ -40,12 +36,6 @@ const commands = { login: login, whoami: getUserInfo, stat: getInfo, - apps: async (args) => { - await listApps({ - statsPeriod: args[0] || 'all' - }); - }, - app: appInfo, history: async (args) => { const lineNumber = parseInt(args[0]); @@ -67,64 +57,6 @@ const commands = { } }, 'last-error': showLast, - 'app:create': async (rawArgs) => { - try { - const args = parseArgs(rawArgs.join(' ')); - // Consider using explicit argument definition if necessary - // const args = parseArgs(rawArgs.join(' '), {string: ['description', 'url'], - // alias: { d: 'description', u: 'url', }, - // }); - - // NOTE: Keep the check for now at the function level, move the check here so in the future we'll use the function for non-interactive command mode. - await createApp({ - name: args._[0], - directory: args._[1] || '', - description: args.description || '', - url: args.url || 'https://dev-center.puter.com/coming-soon.html' - }); - } catch (error) { - console.error(chalk.red(error.message)); - } - }, - 'app:update': async (args) => { - if (args.length < 1) { - console.log(chalk.red('Usage: app:update ')); - return; - } - await updateApp(args); - }, - 'app:delete': async (rawArgs) => { - const args = parseArgs(rawArgs.join(' '), { - string: ['_'], - boolean: ['f'], - configuration: { - 'populate--': true - } - }); - if (args._.length < 1) { - console.log(chalk.red('You must specify the app name:')); - console.log(chalk.yellow('Example: app:delete ')); - return; - } - const name = args._[0]; - const force = !!args.f; - - if (!force) { - const { confirm } = await inquirer.prompt([ - { - type: 'confirm', - name: 'confirm', - message: chalk.yellow(`Are you sure you want to delete "${name}"?`), - default: false - } - ]); - if (!confirm) { - console.log(chalk.yellow('Operation cancelled.')); - return false; - } - } - await deleteApp(name); - }, ls: listFiles, cd: async (args) => { await changeDirectory(args); @@ -144,11 +76,6 @@ const commands = { pull: downloadFile, update: syncDirectory, edit: editFile, - sites: listSites, - site: infoSite, - 'site:delete': deleteSite, - 'site:create': createSite, - 'site:deploy': deploy, }; /** @@ -190,7 +117,13 @@ export async function execCommand(input) { try { await commands[cmd](args); } catch (error) { - console.error(chalk.red(`Error executing command: ${error.message}`)); + report(error); + if (isAuthError(error)) { + console.error(chalk.red('Your session has expired or its token is no longer valid.')); + console.error(chalk.cyan('Type "login" to sign in again.')); + } else { + console.error(chalk.red(`Error executing command: ${formatError(error)}`)); + } } return; } @@ -238,32 +171,6 @@ function showHelp(command) { ${chalk.cyan('usage')} Show usage information. `, - apps: ` - ${chalk.cyan('apps [period]')} - List all your apps. - period: today, yesterday, 7d, 30d, this_month, last_month - Example: apps today - `, - app: ` - ${chalk.cyan('app ')} - Get application information. - Example: app myapp - `, - 'app:create': ` - ${chalk.cyan('app:create ')} - Create a new app. - Example: app:create myapp https://myapp.puter.site - `, - 'app:update': ` - ${chalk.cyan('app:update [dir]')} - Update an app. - Example: app:update myapp . - `, - 'app:delete': ` - ${chalk.cyan('app:delete ')} - Delete an app. - Example: app:delete myapp - `, ls: ` ${chalk.cyan('ls [dir]')} List files and directories. @@ -334,30 +241,6 @@ function showHelp(command) { System editor: ${chalk.green(getSystemEditor())} `, - sites: ` - ${chalk.cyan('sites')} - List sites and subdomains. - `, - site: ` - ${chalk.cyan('site ')} - Get site information by UID. - Example: site sd-123456 - `, - 'site:delete': ` - ${chalk.cyan('site:delete ')} - Delete a site by UID. - Example: site:delete sd-123456 - `, - 'site:create': ` - ${chalk.cyan('site:create [] [--subdomain=]')} - Create a static website from directory. - Example: site:create mywebsite /path/to/dir --subdomain=mywebsite - `, - 'site:deploy': ` - ${chalk.cyan('site:deploy [] [--subdomain=]')} - Deploy a local web project to Puter. - Example: site:deploy ./my-app --subdomain my-app - `, '!': ` ${chalk.cyan('!')} Execute a command on the host machine. diff --git a/src/modules/ErrorModule.js b/src/modules/ErrorModule.js index 8cfaed2..8a31cec 100644 --- a/src/modules/ErrorModule.js +++ b/src/modules/ErrorModule.js @@ -17,4 +17,65 @@ export const showLast = () => { } else { console.log('No errors to report'); } -} \ No newline at end of file +} + +/** + * The Puter API rejects with plain objects rather than Error instances, e.g. + * `{ status: 401, message: 'Unauthorized' }`. Node renders such a value as the + * useless "#", so normalize everything to a real Error before it is + * shown or rethrown. + * + * @param {*} error - Any thrown or rejected value + * @returns {Error} An Error carrying the original `status`/`code` when present + */ +export const normalizeError = (error) => { + if (error instanceof Error) return error; + + if (error && typeof error === 'object') { + // Puter nests driver failures one level down as `{ error: { code, message } }`. + const detail = error.error && typeof error.error === 'object' ? error.error : error; + const message = detail.message || detail.code || JSON.stringify(error); + const normalized = new Error(message); + if (detail.code !== undefined) normalized.code = detail.code; + if (error.status !== undefined) normalized.status = error.status; + return normalized; + } + + return new Error(String(error ?? 'Unknown error')); +}; + +// Server codes that mean "this token will not work again", as opposed to a +// transient failure worth retrying. +const AUTH_ERROR_CODES = [ + 'token_auth_failed', + 'token_unsupported', + 'invalid_token', + 'auth_failed', +]; + +/** + * Whether an error means the stored token is no longer usable and the user has + * to log in again. + * + * @param {*} error - Any thrown or rejected value + * @returns {boolean} True if the session is invalid + */ +export const isAuthError = (error) => { + const status = error?.status ?? error?.response?.status; + if (status === 401 || status === 403) return true; + + const code = String(error?.code ?? error?.error?.code ?? '').toLowerCase(); + if (AUTH_ERROR_CODES.includes(code)) return true; + + const message = String(error?.message ?? '').toLowerCase(); + return AUTH_ERROR_CODES.some(c => message.includes(c)) + || message.includes('unauthorized') + || message.includes('invalid token'); +}; + +/** + * A single-line, human-readable description of any thrown value. + * @param {*} error - Any thrown or rejected value + * @returns {string} The message to display + */ +export const formatError = (error) => normalizeError(error).message; diff --git a/src/modules/ProfileModule.js b/src/modules/ProfileModule.js index 6536fe0..9c7ee4b 100644 --- a/src/modules/ProfileModule.js +++ b/src/modules/ProfileModule.js @@ -7,16 +7,21 @@ import {getAuthToken} from "@heyputer/puter.js/src/init.cjs"; import { puter } from "@heyputer/puter.js"; // project -import { BASE_URL, NULL_UUID, PROJECT_NAME, getHeaders, reconfigureURLs } from '../commons.js' +import { HOME, PROJECT_NAME, getHeaders, reconfigureURLs, setHomePath } from '../commons.js' // builtin import fs from 'node:fs'; -import crypto from 'node:crypto'; import { initPuterModule } from './PuterModule.js'; +import { isAuthError } from './ErrorModule.js'; // initializations const config = new Conf({ projectName: PROJECT_NAME }); +// Outcomes of a session check against the server. +const SESSION_OK = 'ok'; // token works; identity refreshed +const SESSION_INVALID = 'invalid'; // token rejected; the user must log in again +const SESSION_UNKNOWN = 'unknown'; // unreachable or unexpected reply; assume usable + let profileModule; function toApiSubdomain(inputUrl) { @@ -36,9 +41,6 @@ function toApiSubdomain(inputUrl) { class ProfileModule { async checkLogin() { - if (config.get('auth_token')) { - this.migrateLegacyConfig(); - } if (!config.get('selected_profile')) { console.log(chalk.cyan('Please login first (or use CTRL+C to exit):')); await this.switchProfileWizard(); @@ -46,30 +48,106 @@ class ProfileModule { initPuterModule(); } this.applyProfileToGlobals(); + + if (await this.refreshIdentity() === SESSION_INVALID) { + console.log(chalk.yellow('Your session has expired or its token is no longer valid.')); + console.log(chalk.cyan('Please log in again (or use CTRL+C to exit):')); + this.dropCurrentProfile(); + await this.switchProfileWizard(); + initPuterModule(); + this.applyProfileToGlobals(); + } } - migrateLegacyConfig() { - const auth_token = config.get('auth_token'); - const username = config.get('username'); - - this.addProfile({ - host: BASE_URL, - username, - cwd: `/${username}`, - token: auth_token, - uuid: NULL_UUID, - }); - config.delete('auth_token'); + /** + * Forget the selected profile, whose token the server has rejected. Keeping + * a dead token around only makes the next command fail the same way. + */ + dropCurrentProfile() { + const selected = config.get('selected_profile'); + config.set('profiles', this.getProfiles().filter(p => p.uuid !== selected)); + config.delete('selected_profile'); config.delete('username'); + config.delete('cwd'); } - getDefaultProfile() { - const auth_token = config.get('auth_token'); - if (!auth_token) return; - return { - host: 'puter.com', - username: config.get('username'), - token: auth_token, - }; + + + /** + * Re-read the identity from the server using the current token. + * + * A username is a mutable display label that the user can change at any + * time; the token is the identity. Nothing may depend on the cached username + * beyond display, and the cache is refreshed here on every run so it never + * silently drifts. + */ + async refreshIdentity() { + const profile = this.getCurrentProfile(); + if (!profile?.token) return SESSION_INVALID; + + try { + puter.setAuthToken(profile.token); + const userInfo = await puter.auth.getUser(); + if (!userInfo?.username) return SESSION_UNKNOWN; + + if (userInfo.username !== profile.username) { + this.rehomePaths(profile.username, userInfo.username); + this.updateProfile(profile.uuid, { username: userInfo.username }); + } + setHomePath(`/${userInfo.username}`); + return SESSION_OK; + } catch (error) { + // A rejected token will not start working again, so say so and let + // the caller re-authenticate. + if (isAuthError(error)) return SESSION_INVALID; + + // Offline or a transient API failure. Keep the cached label and + // carry on rather than forcing a login the user cannot complete. + return SESSION_UNKNOWN; + } + } + + + /** + * Ask the server who a token belongs to. + * @param {string} token - The auth token to identify + * @returns {Promise<{username: string, uuid: string}>} The account identity + */ + async fetchIdentity(token) { + puter.setAuthToken(token); + const userInfo = await puter.auth.getUser(); + if (!userInfo?.username) { + throw new Error('The server did not return an account for this token.'); + } + return userInfo; + } + + /** + * The selected profile's current working directory. + * @returns {string} The stored cwd, or the home directory as a fallback + */ + getCwd() { + return this.getCurrentProfile()?.cwd || HOME; + } + + /** + * Record the selected profile's current working directory. + * @param {string} cwd - The new working directory + */ + setCwd(cwd) { + const profile = this.getCurrentProfile(); + if (profile) this.updateProfile(profile.uuid, { cwd }); + } + + /** + * Merge a patch into a stored profile, matched by its id. + * @param {string} profileId - The `uuid` of the profile to patch + * @param {Object} patch - Fields to merge into the profile + */ + updateProfile(profileId, patch) { + const profiles = this.getProfiles().map(p => ( + p.uuid === profileId ? { ...p, ...patch } : p + )); + config.set('profiles', profiles); } getProfiles() { const profiles = config.get('profiles') ?? []; @@ -77,15 +155,18 @@ class ProfileModule { } addProfile(newProfile) { const profiles = [ - ...this.getProfiles().filter(p => !p.transient), + // Profiles are keyed by account id, so re-authenticating replaces the + // existing entry rather than adding a second one for the same account. + ...this.getProfiles().filter(p => !p.transient && p.uuid !== newProfile.uuid), newProfile, ]; config.set('profiles', profiles); } selectProfile(profile) { config.set('selected_profile', profile.uuid); - config.set('username', `${profile.username}`); - config.set('cwd', `/${profile.username}`); + if (!profile.cwd) { + this.updateProfile(profile.uuid, { cwd: `/${profile.username}` }); + } this.applyProfileToGlobals(profile); } getCurrentProfile() { @@ -99,6 +180,36 @@ class ProfileModule { base: profile.host, api: toApiSubdomain(profile.host), }); + // Provisional home from the cached label, so paths resolve before the + // network round-trip; refreshIdentity() re-points it at the live value. + setHomePath(`/${profile.username}`); + } + + /** + * Re-point any stored path that lives under the old home directory at the + * new one, after the account was renamed. + * + * Only the home prefix is rewritten: a cwd under some other user's tree is + * left alone, since that path did not move. + * + * @param {string} oldUsername - The username the paths were written with + * @param {string} newUsername - The account's current username + */ + rehomePaths(oldUsername, newUsername) { + if (!oldUsername || oldUsername === newUsername) return; + + const oldHome = `/${oldUsername}`; + const newHome = `/${newUsername}`; + const rehome = p => ( + p === oldHome || p?.startsWith(`${oldHome}/`) + ? `${newHome}${p.slice(oldHome.length)}` + : p + ); + + const profile = this.getCurrentProfile(); + if (profile?.cwd) { + this.updateProfile(profile.uuid, { cwd: rehome(profile.cwd) }); + } } getAuthToken() { const uuid = config.get('selected_profile'); @@ -179,13 +290,14 @@ class ProfileModule { puter.setAuthToken(authToken); const userInfo = await puter.auth.getUser(); - const profileUUID = crypto.randomUUID(); const profile = { host, + // Display label only; re-resolved from the token on every run. username: userInfo.username, cwd: `/${userInfo.username}`, token: authToken, - uuid: profileUUID, + // The account's own id, as reported by the server. + uuid: userInfo.uuid, }; this.addProfile(profile); @@ -263,12 +375,12 @@ class ProfileModule { const otpData = await otpResponse.json(); if (otpData.token) { - this.createProfileFromToken(otpData.token, answers.username, host, spinner, save); + await this.createProfileFromToken(otpData.token, answers.username, host, spinner, save); } else { spinner.fail(chalk.red('2FA verification failed.')); } } else if (data.token) { - this.createProfileFromToken(data.token, answers.username, host, spinner, save); + await this.createProfileFromToken(data.token, answers.username, host, spinner, save); } else { spinner.fail(chalk.red(data.error?.message || 'Login failed. Please check your credentials.')); } @@ -281,19 +393,23 @@ class ProfileModule { } } - createProfileFromToken(token, username, host, spinner, save) { - const profileUUID = crypto.randomUUID(); + async createProfileFromToken(token, username, host, spinner, save) { + // The server is the authority on both the account id and the spelling of + // the username, so ask it rather than trusting what was typed. + const userInfo = await this.fetchIdentity(token); const profile = { host, - username, - cwd: `/${username}`, + // Display label only; re-resolved from the token on every run. + username: userInfo.username, + cwd: `/${userInfo.username}`, token, - uuid: profileUUID, + // The account's own id, as reported by the server. + uuid: userInfo.uuid, }; this.addProfile(profile); this.selectProfile(profile); - spinner.succeed(chalk.green(`Successfully logged in as ${username}!`)); + spinner.succeed(chalk.green(`Successfully logged in as ${userInfo.username}!`)); // Handle --save option this.saveTokenToEnv(token, save); diff --git a/src/utils.js b/src/utils.js index cdf28c6..7cd9fac 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,24 +1,3 @@ -import chalk from 'chalk'; -import yargsParser from 'yargs-parser'; - -/** - * Convert "2024-10-07T15:03:53.000Z" to "10/7/2024, 15:03:53" - * @param {Date} value date value - * @returns formatted date string - */ -export function formatDate(value) { - const date = new Date(value); - return date.toLocaleString("en-US", { - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - hour12: false, - timeZone: 'UTC' - }); -} /** * Format timestamp to date or time @@ -52,76 +31,6 @@ export function formatSize(size) { return `${size.toFixed(1)} ${units[unit]}`; } -/** - * Display non null values in formatted table - * @param {Object} data Object to display - * @returns null - */ -export function displayNonNullValues(data) { - if (typeof data !== 'object' || data === null) { - console.error("Invalid input: Input must be a non-null object."); - return; - } - const tableData = []; - function flattenObject(obj, parentKey = '') { - for (const key in obj) { - const value = obj[key]; - const newKey = parentKey ? `${parentKey}.${key}` : key; - if (value !== null) { - if (typeof value === 'object') { - flattenObject(value, newKey); - } else { - tableData.push({ key: newKey, value: value }); - } - } - } - } - - flattenObject(data); - // Determine max key length for formatting - const maxKeyLength = tableData.reduce((max, item) => Math.max(max, item.key.length), 0); - // Format and output the table - console.log(chalk.cyan('-'.repeat(maxKeyLength*3))); - console.log(chalk.cyan(`| ${'Key'.padEnd(maxKeyLength)} | Value`)); - console.log(chalk.cyan('-'.repeat(maxKeyLength*3))); - tableData.forEach(item => { - const key = item.key.padEnd(maxKeyLength); - const value = String(item.value); - console.log(chalk.green(`| ${chalk.dim(key)} | ${value}`)); - }); - console.log(chalk.cyan('-'.repeat(maxKeyLength*3))); - console.log(chalk.cyan(`You have ${chalk.green(tableData.length)} key/value pair(s).`)); - } - - /** - * Parse command line arguments including quoted strings - * @param {string} input Raw command line input - * @returns {Object} Parsed arguments - */ -export function parseArgs(input, options = {}) { - const result = yargsParser(input, options); - return result; -} - -/** - * Checks if a given string is a valid UUID of any version - * @param {string} uuid - The string to validate. - * @returns {boolean} - True if the string is a valid UUID, false otherwise. - */ -export function isValidAppUuid (uuid) { - return uuid.startsWith('app-') && is_valid_uuid4(uuid.slice(4)); -} - -/** - * Checks if a given string is a valid UUID version 4. - * @param {string} uuid - The string to validate. - * @returns {boolean} - True if the string is a valid UUID version 4, false otherwise. - */ -export function is_valid_uuid4 (uuid) { - const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; - return uuidV4Regex.test(uuid); -} - /** * Get system editor * @returns {string} - System editor diff --git a/tests/ErrorModule.test.js b/tests/ErrorModule.test.js index d582bbb..ae3c50a 100644 --- a/tests/ErrorModule.test.js +++ b/tests/ErrorModule.test.js @@ -4,6 +4,7 @@ vi.spyOn(console, "log").mockImplementation(() => { }); vi.spyOn(console, "error").mockImplementation(() => { }); let errors, report, ERROR_BUFFER_LIMIT, showLast; +let normalizeError, isAuthError, formatError; beforeEach(async () => { vi.resetModules(); @@ -12,6 +13,9 @@ beforeEach(async () => { report = module.report; ERROR_BUFFER_LIMIT = module.ERROR_BUFFER_LIMIT; showLast = module.showLast; + normalizeError = module.normalizeError; + isAuthError = module.isAuthError; + formatError = module.formatError; }); describe("report", () => { @@ -39,4 +43,72 @@ describe("showLast", () => { showLast(); expect(console.error).toHaveBeenCalledWith(expect.stringContaining("hehe")); }) -}) \ No newline at end of file +}) + +describe('normalizeError', () => { + it('should turn a plain rejected object into a real Error', () => { + // The shape puter.js rejects with, which Node renders as "#". + const err = normalizeError({ status: 401, message: 'Unauthorized' }); + + expect(err).toBeInstanceOf(Error); + expect(err.message).toBe('Unauthorized'); + expect(err.status).toBe(401); + }); + + it('should unwrap a nested driver error', () => { + const err = normalizeError({ error: { code: 'permission_denied', message: 'Nope' } }); + + expect(err.message).toBe('Nope'); + expect(err.code).toBe('permission_denied'); + }); + + it('should fall back to the code when there is no message', () => { + expect(normalizeError({ code: 'subject_does_not_exist' }).message) + .toBe('subject_does_not_exist'); + }); + + it('should never render an object as "#"', () => { + const err = normalizeError({ status: 500 }); + + expect(err.message).not.toContain('#'); + expect(err.message).toBe('{"status":500}'); + }); + + it('should pass an Error through untouched', () => { + const original = new Error('boom'); + expect(normalizeError(original)).toBe(original); + }); + + it('should handle non-object throws', () => { + expect(normalizeError('bad thing').message).toBe('bad thing'); + expect(normalizeError(undefined).message).toBe('Unknown error'); + }); +}); + +describe('isAuthError', () => { + it('should detect the 401 that puter.js rejects with', () => { + expect(isAuthError({ status: 401, message: 'Unauthorized' })).toBe(true); + }); + + it('should detect a 403', () => { + expect(isAuthError({ status: 403, message: 'Forbidden' })).toBe(true); + }); + + it('should detect token failure codes', () => { + expect(isAuthError({ code: 'token_auth_failed' })).toBe(true); + expect(isAuthError({ error: { code: 'invalid_token' } })).toBe(true); + }); + + it('should not flag unrelated failures', () => { + expect(isAuthError({ status: 500, message: 'Server error' })).toBe(false); + expect(isAuthError({ code: 'subject_does_not_exist' })).toBe(false); + expect(isAuthError(new Error('ENOTFOUND api.puter.com'))).toBe(false); + expect(isAuthError(undefined)).toBe(false); + }); +}); + +describe('formatError', () => { + it('should give a readable message for a rejected plain object', () => { + expect(formatError({ status: 401, message: 'Unauthorized' })).toBe('Unauthorized'); + }); +}); diff --git a/tests/ProfileModule.test.js b/tests/ProfileModule.test.js index 8d079f8..bd1839c 100644 --- a/tests/ProfileModule.test.js +++ b/tests/ProfileModule.test.js @@ -17,11 +17,12 @@ vi.mock('conf', () => { }); vi.mock('../src/commons.js', () => ({ - BASE_URL: 'https://puter.com', - NULL_UUID: '00000000-0000-0000-0000-000000000000', - PROJECT_NAME: 'puter-cli', + HOME: '~', + PROJECT_NAME: 'puter-sh', getHeaders: vi.fn(() => ({ 'Content-Type': 'application/json' })), reconfigureURLs: vi.fn(), + setHomePath: vi.fn(), + expandHome: vi.fn((p) => p), })); vi.mock('./PuterModule.js', () => ({ @@ -137,8 +138,12 @@ describe('ProfileModule.selectProfile', () => { profileModule.selectProfile(profile); expect(mockConfig.set).toHaveBeenCalledWith('selected_profile', 'test-uuid'); - expect(mockConfig.set).toHaveBeenCalledWith('username', 'testuser'); - expect(mockConfig.set).toHaveBeenCalledWith('cwd', '/testuser'); + // username and cwd live on the profile now, not at the top level. + expect(mockConfig.set).not.toHaveBeenCalledWith('username', expect.anything()); + expect(mockConfig.set).not.toHaveBeenCalledWith('cwd', expect.anything()); + expect(mockConfig.set).toHaveBeenCalledWith('profiles', [ + expect.objectContaining({ uuid: 'test-uuid', cwd: '/testuser' }), + ]); }); }); @@ -216,59 +221,68 @@ describe('ProfileModule.getAuthToken', () => { }); }); -describe('ProfileModule.getDefaultProfile', () => { - it('should return default profile if auth_token exists', () => { +describe('ProfileModule.rehomePaths', () => { + const setup = (cwd, profile) => { + const stored = { ...profile, cwd }; mockConfig.get.mockImplementation((key) => { - if (key === 'auth_token') return 'legacy-token'; - if (key === 'username') return 'legacyuser'; + if (key === 'profiles') return [stored]; + if (key === 'selected_profile') return stored.uuid; return undefined; }); - initProfileModule(); - const profileModule = getProfileModule(); - const defaultProfile = profileModule.getDefaultProfile(); + return getProfileModule(); + }; - expect(defaultProfile).toEqual({ - host: 'puter.com', - username: 'legacyuser', - token: 'legacy-token', - }); + it('should re-point the cwd at the new home after a rename', () => { + const profile = { uuid: 'p1', username: 'oldname', cwd: '/oldname', host: 'https://puter.com' }; + const profileModule = setup('/oldname/Desktop/notes', profile); + + profileModule.rehomePaths('oldname', 'newname'); + + expect(mockConfig.set).toHaveBeenCalledWith('profiles', [ + expect.objectContaining({ cwd: '/newname/Desktop/notes' }), + ]); }); - it('should return undefined if no auth_token exists', () => { - mockConfig.get.mockReturnValue(undefined); + it('should re-point a cwd sitting exactly at the old home', () => { + const profile = { uuid: 'p1', username: 'oldname', cwd: '/oldname', host: 'https://puter.com' }; + const profileModule = setup('/oldname', profile); - initProfileModule(); - const profileModule = getProfileModule(); - const defaultProfile = profileModule.getDefaultProfile(); + profileModule.rehomePaths('oldname', 'newname'); - expect(defaultProfile).toBeUndefined(); + expect(mockConfig.set).toHaveBeenCalledWith('profiles', [ + expect.objectContaining({ cwd: '/newname' }), + ]); }); -}); -describe('ProfileModule.migrateLegacyConfig', () => { - it('should migrate legacy config to profile format', () => { - mockConfig.get.mockImplementation((key) => { - if (key === 'auth_token') return 'legacy-token'; - if (key === 'username') return 'legacyuser'; - if (key === 'profiles') return []; - return undefined; - }); + it('should leave a cwd under another user\'s tree alone', () => { + const profile = { uuid: 'p1', username: 'oldname', cwd: '/oldname', host: 'https://puter.com' }; + const profileModule = setup('/someoneelse/public', profile); - initProfileModule(); - const profileModule = getProfileModule(); - profileModule.migrateLegacyConfig(); + profileModule.rehomePaths('oldname', 'newname'); expect(mockConfig.set).toHaveBeenCalledWith('profiles', [ - { - host: 'https://puter.com', - username: 'legacyuser', - cwd: '/legacyuser', - token: 'legacy-token', - uuid: '00000000-0000-0000-0000-000000000000', - }, + expect.objectContaining({ cwd: '/someoneelse/public' }), ]); - expect(mockConfig.delete).toHaveBeenCalledWith('auth_token'); - expect(mockConfig.delete).toHaveBeenCalledWith('username'); + }); + + it('should not rewrite a prefix that only partially matches', () => { + const profile = { uuid: 'p1', username: 'bob', cwd: '/bob', host: 'https://puter.com' }; + const profileModule = setup('/bobby/files', profile); + + profileModule.rehomePaths('bob', 'robert'); + + expect(mockConfig.set).toHaveBeenCalledWith('profiles', [ + expect.objectContaining({ cwd: '/bobby/files' }), + ]); + }); + + it('should do nothing when the username is unchanged', () => { + const profile = { uuid: 'p1', username: 'bob', cwd: '/bob', host: 'https://puter.com' }; + const profileModule = setup('/bob/files', profile); + + profileModule.rehomePaths('bob', 'bob'); + + expect(mockConfig.set).not.toHaveBeenCalled(); }); }); diff --git a/tests/apps.test.js b/tests/apps.test.js deleted file mode 100644 index 1c9fe5d..0000000 --- a/tests/apps.test.js +++ /dev/null @@ -1,194 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { listApps, appInfo, createApp, updateApp, deleteApp } from '../src/commands/apps.js'; -import chalk from 'chalk'; -import Table from 'cli-table3'; -import * as PuterModule from '../src/modules/PuterModule.js'; -import * as subdomains from '../src/commands/subdomains.js'; -import * as sites from '../src/commands/sites.js'; -import * as files from '../src/commands/files.js'; -import * as auth from '../src/commands/auth.js'; -import * as commons from '../src/commons.js'; -import * as utils from '../src/utils.js'; -import crypto from '../src/crypto.js'; - -// Mock console to prevent actual logging -vi.spyOn(console, 'log').mockImplementation(() => {}); -vi.spyOn(console, 'error').mockImplementation(() => {}); - -vi.mock("conf", () => { - const Conf = vi.fn(() => ({ - get: vi.fn(), - set: vi.fn(), - clear: vi.fn(), - })); - return { default: Conf }; -}); - -// Mock dependencies -vi.mock('chalk', () => ({ - default: { - green: vi.fn(text => text), - red: vi.fn(text => text), - dim: vi.fn(text => text), - yellow: vi.fn(text => text), - cyan: vi.fn(text => text), - cyanBright: vi.fn(text => text), - bold: vi.fn(text => text), - } -})); -vi.mock('cli-table3'); -vi.mock('node-fetch'); -vi.mock('../src/modules/PuterModule.js'); -vi.mock('../src/commands/subdomains.js'); -vi.mock('../src/commands/sites.js'); -vi.mock('../src/commands/files.js'); -vi.mock('../src/commands/auth.js'); -vi.mock('../src/commons.js'); -vi.mock('../src/utils.js'); -vi.mock('../src/crypto.js'); - -const mockPuter = { - apps: { - list: vi.fn(), - get: vi.fn(), - create: vi.fn(), - update: vi.fn(), - delete: vi.fn(), - }, - fs: { - mkdir: vi.fn(), - }, -}; - -describe('apps.js', () => { - let mockTable; - - beforeEach(() => { - vi.clearAllMocks(); - vi.spyOn(PuterModule, 'getPuter').mockReturnValue(mockPuter); - vi.spyOn(auth, 'getCurrentDirectory').mockReturnValue('/testuser'); - vi.spyOn(commons, 'isValidAppName').mockReturnValue(true); - vi.spyOn(commons, 'resolvePath').mockImplementation((_, newPath) => newPath); - vi.spyOn(crypto, 'randomUUID').mockReturnValue('mock-uuid'); - - mockTable = { - push: vi.fn(), - toString: vi.fn(() => 'table string'), - }; - Table.mockImplementation(() => mockTable); - }); - - describe('listApps', () => { - it('should list apps successfully', async () => { - const mockApps = [ - { title: 'App 1', name: 'app-1', created_at: new Date().toISOString(), index_url: 'https://app-1.puter.site', stats: { open_count: 10, user_count: 5 } }, - { title: 'App 2', name: 'app-2', created_at: new Date().toISOString(), index_url: 'https://app-2.puter.site', stats: { open_count: 20, user_count: 15 } }, - ]; - mockPuter.apps.list.mockResolvedValue(mockApps); - vi.spyOn(utils, 'formatDate').mockReturnValue('formatted-date'); - - await listApps(); - - expect(mockPuter.apps.list).toHaveBeenCalled(); - expect(mockTable.push).toHaveBeenCalledTimes(2); - expect(console.log).toHaveBeenCalledWith('table string'); - }); - - it('should handle API error when listing apps', async () => { - mockPuter.apps.list.mockRejectedValue(new Error('API Error')); - await listApps(); - expect(console.error).toHaveBeenCalledWith('Failed to list apps. Error: API Error'); - }); - }); - - describe('appInfo', () => { - it('should show app info successfully', async () => { - const mockApp = { name: 'test-app', title: 'Test App' }; - mockPuter.apps.get.mockResolvedValue(mockApp); - vi.spyOn(utils, 'displayNonNullValues').mockImplementation(() => {}); - - await appInfo(['test-app']); - - expect(mockPuter.apps.get).toHaveBeenCalledWith('test-app'); - expect(utils.displayNonNullValues).toHaveBeenCalledWith(mockApp); - }); - - it('should show usage if no app name is provided', async () => { - await appInfo([]); - expect(console.log).toHaveBeenCalledWith(chalk.red('Usage: app ')); - }); - - it('should handle app not found', async () => { - mockPuter.apps.get.mockResolvedValue(null); - await appInfo(['non-existent-app']); - expect(console.error).toHaveBeenCalledWith(chalk.red('Could not find this app.')); - }); - }); - - describe('createApp', () => { - beforeEach(() => { - mockPuter.apps.create.mockResolvedValue({ uid: 'app-uid', name: 'new-app', owner: { username: 'testuser' } }); - mockPuter.fs.mkdir.mockResolvedValue({ uid: 'dir-uid', name: 'app-mock-uuid' }); - vi.spyOn(subdomains, 'createSubdomain').mockResolvedValue(true); - vi.spyOn(files, 'createFile').mockResolvedValue(true); - mockPuter.apps.update.mockResolvedValue(true); - }) - it('should create an app successfully', async () => { - await createApp({ name: 'new-app' }); - - expect(mockPuter.apps.create).toHaveBeenCalled(); - expect(mockPuter.fs.mkdir).toHaveBeenCalled(); - expect(subdomains.createSubdomain).toHaveBeenCalled(); - expect(files.createFile).toHaveBeenCalled(); - expect(mockPuter.apps.update).toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith(chalk.green('App deployed successfully at:')); - }); - - it('should show usage if app name is invalid', async () => { - vi.spyOn(commons, 'isValidAppName').mockReturnValue(false); - await createApp({ name: 'invalid-' }); - expect(console.log).toHaveBeenCalledWith(chalk.red('Usage: app:create ')); - }); - }); - - describe('updateApp', () => { - it('should update an app successfully', async () => { - mockPuter.apps.get.mockResolvedValue({ uid: 'app-uid', name: 'test-app', owner: { username: 'testuser' }, index_url: 'https://test.puter.site' }); - vi.spyOn(files, 'pathExists').mockResolvedValue(true); - vi.spyOn(subdomains, 'getSubdomains').mockResolvedValue([{ root_dir: { dirname: 'app-uid', path: '/path/to/app' }, uid: 'sub-uid' }]); - vi.spyOn(files, 'listRemoteFiles').mockResolvedValue([{ name: 'index.html' }]); - vi.spyOn(files, 'copyFile').mockResolvedValue(true); - vi.spyOn(files, 'removeFileOrDirectory').mockResolvedValue(true); - - await updateApp(['test-app', '.']); - - expect(mockPuter.apps.get).toHaveBeenCalledWith('test-app'); - expect(files.listRemoteFiles).toHaveBeenCalled(); - expect(files.copyFile).toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith(chalk.green('App updated successfully at:')); - }); - }); - - describe('deleteApp', () => { - it('should delete an app successfully', async () => { - mockPuter.apps.get.mockResolvedValue({ uid: 'app-uid', name: 'test-app', title: 'Test App', created_at: new Date().toISOString() }); - mockPuter.apps.delete.mockResolvedValue(true); - vi.spyOn(subdomains, 'getSubdomains').mockResolvedValue([{ root_dir: { dirname: 'app-uid' }, uid: 'sub-uid' }]); - vi.spyOn(sites, 'deleteSite').mockResolvedValue(true); - - const result = await deleteApp('test-app'); - - expect(result).toBe(true); - expect(mockPuter.apps.delete).toHaveBeenCalledWith('test-app'); - expect(sites.deleteSite).toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith(chalk.green('App "test-app" deleted successfully!')); - }); - - it('should return false if app not found', async () => { - mockPuter.apps.get.mockResolvedValue(null); - const result = await deleteApp('non-existent-app'); - expect(result).toBe(false); - expect(console.log).toHaveBeenCalledWith(chalk.red('App "non-existent-app" not found.')); - }); - }); -}); \ No newline at end of file diff --git a/tests/commons.test.js b/tests/commons.test.js index fef8ca3..8e1f039 100644 --- a/tests/commons.test.js +++ b/tests/commons.test.js @@ -29,11 +29,7 @@ beforeEach(async () => { describe('constants', () => { it('should export PROJECT_NAME', () => { - expect(commons.PROJECT_NAME).toBe('puter-cli'); - }); - - it('should export NULL_UUID', () => { - expect(commons.NULL_UUID).toBe('00000000-0000-0000-0000-000000000000'); + expect(commons.PROJECT_NAME).toBe('puter-sh'); }); it('should have default API_BASE', () => { @@ -86,65 +82,6 @@ describe('getHeaders', () => { }); }); -describe('generateAppName', () => { - it('should generate a name with default separator', () => { - const name = commons.generateAppName(); - - expect(name).toMatch(/^[a-z]+-[a-z]+-\d+$/); - }); - - it('should generate a name with custom separator', () => { - const name = commons.generateAppName('_'); - - expect(name).toMatch(/^[a-z]+_[a-z]+_\d+$/); - }); -}); - -describe('displayTable', () => { - it('should display a table with headers and data', () => { - const consoleLogSpy = vi.spyOn(console, 'log'); - - const data = [ - { name: 'App1', status: 'running' }, - { name: 'App2', status: 'stopped' }, - ]; - - commons.displayTable(data, { - headers: ['Name', 'Status'], - columns: ['name', 'status'], - columnWidth: 15, - }); - - expect(consoleLogSpy).toHaveBeenCalled(); - }); - - it('should handle empty data', () => { - const consoleLogSpy = vi.spyOn(console, 'log'); - - commons.displayTable([], { - headers: ['Name'], - columns: ['name'], - }); - - expect(consoleLogSpy).toHaveBeenCalledTimes(2); // header + separator - }); - - it('should display N/A for missing values', () => { - const consoleLogSpy = vi.spyOn(console, 'log'); - - const data = [{ name: 'App1' }]; - - commons.displayTable(data, { - headers: ['Name', 'Status'], - columns: ['name', 'status'], - columnWidth: 10, - }); - - const calls = consoleLogSpy.mock.calls.flat(); - expect(calls.some(call => call.includes('N/A'))).toBe(true); - }); -}); - describe('showDiskSpaceUsage', () => { it('should display disk usage information', () => { const consoleLogSpy = vi.spyOn(console, 'log'); @@ -209,94 +146,99 @@ describe('resolvePath', () => { it('should normalize duplicate slashes', () => { expect(commons.resolvePath('/home//user', 'documents')).toBe('/home/user/documents'); }); -}); -describe('resolveRemotePath', () => { - it('should return absolute path as-is', () => { - expect(commons.resolveRemotePath('/home/user', '/absolute/path')).toBe('/absolute/path'); + it('should re-root on an absolute path instead of appending it', () => { + expect(commons.resolvePath('/home/user', '/other/place')).toBe('/other/place'); }); - it('should resolve relative path', () => { - expect(commons.resolveRemotePath('/home/user', 'relative/path')).toBe('/home/user/relative/path'); + it('should leave "~" unexpanded when no home is resolved yet', () => { + commons.setHomePath(null); + expect(commons.resolvePath('/home/user', '~/Desktop')).toBe('~/Desktop'); + expect(commons.resolvePath('/home/user', '~')).toBe('~'); }); -}); -describe('isValidAppName', () => { - it('should return true for valid app name', () => { - expect(commons.isValidAppName('my-app')).toBe(true); + it('should walk up within a home-anchored path', () => { + commons.setHomePath(null); + expect(commons.resolvePath('~/Desktop/notes', '..')).toBe('~/Desktop'); }); - it('should return true for app name with spaces', () => { - expect(commons.isValidAppName('my app')).toBe(true); + it('should clamp at the home anchor rather than escaping to root', () => { + commons.setHomePath(null); + expect(commons.resolvePath('~/Desktop', '../../..')).toBe('~'); }); +}); - it('should return false for empty string', () => { - expect(commons.isValidAppName('')).toBe(false); - }); +describe('expandHome', () => { + afterEach(() => commons.setHomePath(null)); - it('should return false for whitespace only', () => { - expect(commons.isValidAppName(' ')).toBe(false); + it('should expand "~" and "~/..." to the resolved home', () => { + commons.setHomePath('/alice'); + expect(commons.expandHome('~')).toBe('/alice'); + expect(commons.expandHome('~/Desktop')).toBe('/alice/Desktop'); }); - it('should return false for reserved name "."', () => { - expect(commons.isValidAppName('.')).toBe(false); + it('should leave non-home paths untouched', () => { + commons.setHomePath('/alice'); + expect(commons.expandHome('/bob/public')).toBe('/bob/public'); + expect(commons.expandHome('notes.txt')).toBe('notes.txt'); }); - it('should return false for reserved name ".."', () => { - expect(commons.isValidAppName('..')).toBe(false); + it('should be a no-op before a home is resolved', () => { + commons.setHomePath(null); + expect(commons.expandHome('~/Desktop')).toBe('~/Desktop'); }); - it('should return false for name with forward slash', () => { - expect(commons.isValidAppName('my/app')).toBe(false); + it('should follow a username change without touching stored paths', () => { + commons.setHomePath('/alice'); + expect(commons.expandHome('~/Desktop')).toBe('/alice/Desktop'); + commons.setHomePath('/alice-renamed'); + expect(commons.expandHome('~/Desktop')).toBe('/alice-renamed/Desktop'); }); +}); - it('should return false for name with backslash', () => { - expect(commons.isValidAppName('my\\app')).toBe(false); - }); +describe('resolvePath with a resolved home', () => { + afterEach(() => commons.setHomePath(null)); - it('should return false for name with wildcard', () => { - expect(commons.isValidAppName('my*app')).toBe(false); + it('should resolve "~" to the concrete home directory', () => { + commons.setHomePath('/alice'); + expect(commons.resolvePath('/anywhere', '~')).toBe('/alice'); + expect(commons.resolvePath('/anywhere', '~/Desktop')).toBe('/alice/Desktop'); }); - it('should return false for non-string input', () => { - expect(commons.isValidAppName(123)).toBe(false); - expect(commons.isValidAppName(null)).toBe(false); - expect(commons.isValidAppName(undefined)).toBe(false); + it('should resolve relative paths against a concrete cwd', () => { + commons.setHomePath('/alice'); + expect(commons.resolvePath('/alice', 'Desktop')).toBe('/alice/Desktop'); + expect(commons.resolvePath('/alice/Desktop', '..')).toBe('/alice'); }); }); -describe('getDefaultHomePage', () => { - it('should generate HTML with app name', () => { - const html = commons.getDefaultHomePage('TestApp'); - - expect(html).toContain('TestApp'); - expect(html).toContain('Welcome to TestApp!'); +describe('isAbsolutePath', () => { + it('should accept root- and home-anchored paths', () => { + expect(commons.isAbsolutePath('/a/b')).toBe(true); + expect(commons.isAbsolutePath('~')).toBe(true); + expect(commons.isAbsolutePath('~/a')).toBe(true); }); - it('should include CSS files when provided', () => { - const html = commons.getDefaultHomePage('TestApp', [], ['style.css', 'theme.css']); - - expect(html).toContain(''); - expect(html).toContain(''); + it('should reject relative paths and non-strings', () => { + expect(commons.isAbsolutePath('a/b')).toBe(false); + expect(commons.isAbsolutePath('..')).toBe(false); + expect(commons.isAbsolutePath(undefined)).toBe(false); }); +}); - it('should include JS files when provided', () => { - const html = commons.getDefaultHomePage('TestApp', ['app.js', 'utils.js']); - - expect(html).toContain(''); - expect(html).toContain(''); +describe('resolveRemotePath', () => { + it('should return absolute path as-is', () => { + expect(commons.resolveRemotePath('/home/user', '/absolute/path')).toBe('/absolute/path'); }); - it('should use id="root" when react is included', () => { - const html = commons.getDefaultHomePage('TestApp', ['react.js']); - - expect(html).toContain('id="root"'); + it('should resolve relative path', () => { + expect(commons.resolveRemotePath('/home/user', 'relative/path')).toBe('/home/user/relative/path'); }); - it('should use id="app" when no react', () => { - const html = commons.getDefaultHomePage('TestApp', ['vanilla.js']); - - expect(html).toContain('id="app"'); + it('should expand a home-anchored path against the resolved home', () => { + commons.setHomePath('/alice'); + expect(commons.resolveRemotePath('/home/user', '~/site')).toBe('/alice/site'); + commons.setHomePath(null); }); }); @@ -340,7 +282,7 @@ describe('getLatestVersion', () => { json: () => Promise.resolve({ version: '1.0.0' }), }); - const result = await commons.getLatestVersion('puter-cli'); + const result = await commons.getLatestVersion('puter-sh'); expect(result).toBe('v1.0.0 (up-to-date)'); }); @@ -352,7 +294,7 @@ describe('getLatestVersion', () => { json: () => Promise.resolve({ version: '2.0.0' }), }); - const result = await commons.getLatestVersion('puter-cli'); + const result = await commons.getLatestVersion('puter-sh'); expect(result).toBe('v1.0.0 (latest: 2.0.0)'); }); @@ -361,7 +303,7 @@ describe('getLatestVersion', () => { vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.0.0' })); vi.mocked(global.fetch).mockRejectedValueOnce(new Error('Network error')); - const result = await commons.getLatestVersion('puter-cli'); + const result = await commons.getLatestVersion('puter-sh'); expect(result).toBe('v1.0.0 (offline)'); }); @@ -373,7 +315,7 @@ describe('getLatestVersion', () => { json: () => Promise.resolve({ version: '2.0.0' }), }); - const result = await commons.getLatestVersion('puter-cli'); + const result = await commons.getLatestVersion('puter-sh'); expect(result).toBe('vunknown (latest: 2.0.0)'); }); diff --git a/tests/deploy.test.js b/tests/deploy.test.js deleted file mode 100644 index 4b11549..0000000 --- a/tests/deploy.test.js +++ /dev/null @@ -1,84 +0,0 @@ -import { describe, vi, expect, it, beforeEach } from "vitest"; - -vi.mock('conf', () => ({ - default: vi.fn(() => ({ - get: vi.fn(), - })), -})); - -vi.mock("../src/commands/files"); -vi.mock("../src/commands/sites"); -vi.mock("../src/modules/PuterModule"); - -vi.spyOn(console, "log").mockImplementation(() => { }); - -let deploy; -let syncDirectory; -let createSite; -let getPuter; - -beforeEach(async () => { - vi.resetModules(); - vi.clearAllMocks(); - - const deployModule = await import("../src/commands/deploy"); - deploy = deployModule.deploy; - - const filesModule = await import("../src/commands/files"); - syncDirectory = vi.mocked(filesModule.syncDirectory); - - const sitesModule = await import("../src/commands/sites"); - createSite = vi.mocked(sitesModule.createSite); - - const puterModule = await import("../src/modules/PuterModule"); - getPuter = vi.mocked(puterModule.getPuter); -}); - -describe("deploy", () => { - it("should show usage when no args provided", async () => { - await deploy([]); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Usage:")); - }); - - it("should deploy successfully", async () => { - const mockMkdir = vi.fn().mockResolvedValue({ - path: "~/sites/test-app/deployment" - }); - getPuter.mockReturnValue({ - fs: { - mkdir: mockMkdir - } - }); - syncDirectory.mockResolvedValue(); - createSite.mockResolvedValue({ - subdomain: "test-app.puter.site" - }); - - await deploy(["./dist", "--subdomain=test-app"]); - - expect(mockMkdir).toHaveBeenCalledWith("~/sites/test-app/deployment", { - dedupeName: true, - createMissingParents: true - }); - expect(syncDirectory).toHaveBeenCalled(); - expect(createSite).toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Deployment successful!")); - }); - - it("should show updated message when site already exists", async () => { - const mockMkdir = vi.fn().mockResolvedValue({ - path: "~/sites/test-app/deployment" - }); - getPuter.mockReturnValue({ - fs: { - mkdir: mockMkdir - } - }); - syncDirectory.mockResolvedValue(); - createSite.mockResolvedValue(null); - - await deploy(["./dist", "--subdomain=test-app"]); - - expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Deployment successfuly updated!")); - }); -}); diff --git a/tests/executor.test.js b/tests/executor.test.js index c638d87..daa44af 100644 --- a/tests/executor.test.js +++ b/tests/executor.test.js @@ -11,6 +11,14 @@ vi.mock('conf', () => ({ vi.mock('node:child_process'); +vi.mock('../src/modules/ProfileModule.js', () => ({ + initProfileModule: vi.fn(), + getProfileModule: vi.fn(() => ({ + getCwd: () => '/mockuser', + setCwd: vi.fn(), + })), +})); + vi.spyOn(console, 'log').mockImplementation(() => {}); vi.spyOn(console, 'error').mockImplementation(() => {}); diff --git a/tests/files.test.js b/tests/files.test.js index 1f6d83b..809b6de 100644 --- a/tests/files.test.js +++ b/tests/files.test.js @@ -459,6 +459,9 @@ describe("renameFileOrDirectory", () => { describe("getInfo", () => { beforeEach(() => { vi.clearAllMocks(); + // Undo the naive resolvePath stub other suites install: getInfo relies on + // real "~" and absolute-path resolution. + if (vi.isMockFunction(commons.resolvePath)) commons.resolvePath.mockRestore(); vi.spyOn(PuterModule, "getPuter").mockReturnValue(mockPuter); vi.spyOn(auth, "getCurrentDirectory").mockReturnValue("/testuser/files"); vi.spyOn(utils, "formatSize").mockImplementation((size) => `${size}B`); @@ -515,6 +518,23 @@ describe("getInfo", () => { ); }); + it("should resolve the argument instead of concatenating it", async () => { + mockPuter.fs.stat.mockResolvedValue({ + name: "files", + path: "/testuser/files", + is_dir: true, + owner: { username: "testuser" }, + }); + // Raw concatenation used to produce "/testuser/files/~/Desktop"; delegating + // to resolvePath is what makes "~" and absolute paths work here. + vi.spyOn(commons, "resolvePath").mockReturnValue("/resolved/path"); + + await getInfo(["~/Desktop"]); + + expect(commons.resolvePath).toHaveBeenCalledWith("/testuser/files", "~/Desktop"); + expect(mockPuter.fs.stat).toHaveBeenCalledWith("/resolved/path"); + }); + it("should use current directory with default argument", async () => { mockPuter.fs.stat.mockResolvedValue({ name: "files", @@ -529,7 +549,7 @@ describe("getInfo", () => { await getInfo([]); - expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files/."); + expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files"); }); }); @@ -538,8 +558,8 @@ describe("showCwd", () => { vi.clearAllMocks(); }); - it("should display current working directory from config", async () => { - mockConfigStore.cwd = "/testuser/documents"; + it("should display the selected profile's working directory", async () => { + vi.spyOn(auth, "getCurrentDirectory").mockReturnValue("/testuser/documents"); await showCwd(); diff --git a/tests/shell.test.js b/tests/shell.test.js index 88a9cd0..f8f65e4 100644 --- a/tests/shell.test.js +++ b/tests/shell.test.js @@ -27,6 +27,7 @@ beforeEach(async () => { vi.mocked(getPrompt).mockReturnValue('puter@/> '); vi.mocked(getProfileModule).mockReturnValue({ checkLogin: vi.fn(), + setCwd: vi.fn(), }); const mockOn = vi.fn().mockReturnThis(); @@ -97,7 +98,7 @@ describe('startShell', () => { it('should display welcome message', async () => { await startShell(); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Welcome to Puter-CLI')); + expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Welcome to Puter Shell')); }); it('should set prompt and call prompt()', async () => { diff --git a/tests/sites.test.js b/tests/sites.test.js deleted file mode 100644 index 7246f35..0000000 --- a/tests/sites.test.js +++ /dev/null @@ -1,67 +0,0 @@ -import { describe, vi, expect, it } from "vitest"; -import { createSite, deleteSite, infoSite, listSites } from "../src/commands/sites"; -import { createSubdomain, deleteSubdomain, getSubdomains } from "../src/commands/subdomains"; -import { getPuter } from "../src/modules/PuterModule.js"; -import { getCurrentDirectory } from "../src/commands/auth.js"; - -vi.mock("../src/commands/subdomains") -vi.mock("../src/modules/PuterModule") -vi.mock('../src/commands/auth.js'); - -vi.spyOn(console, "log").mockImplementation(() => { }); - -describe("listSites", () => { - it("should list sites successfully", async () => { - vi.mocked(getSubdomains).mockResolvedValue([{ - uid: "123", - subdomain: "hehe.puter.site", - root_dir: { path: "/some/path" }, - }]) - await listSites(); - expect(getSubdomains).toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Total Sites: 1")) - }) -}) - -describe("infoSite", () => { - it("should get site info successfully", async () => { - const mockHostingGet = vi.fn().mockResolvedValue({ - uid: "123", - subdomain: "hehe.puter.site", - root_dir: { path: "/some/path" }, - }); - vi.mocked(getPuter).mockReturnValue({ - hosting: { - get: mockHostingGet - } - }) - - await infoSite(["hehe.puter.site"]) - expect(getPuter).toHaveBeenCalled(); - expect(mockHostingGet).toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining("hehe.puter.site")) - }) -}) - -describe("deleteSite", () => { - it("should delete site successfully", async () => { - vi.mocked(deleteSubdomain) - const result = await deleteSite(["hehe.puter.site"]); - expect(result).toBe(true); - }) -}) - -describe("createSite", () => { - it("should create site successfully", async () => { - vi.mocked(createSubdomain).mockResolvedValue({ - uid: "123", - subdomain: "hehe.puter.site", - root_dir: { path: "/some/path" }, - }) - vi.mocked(getCurrentDirectory).mockReturnValue('/testuser'); - const result = await createSite(["hehe hehe --subdomain=hehe"]); - expect(result).toMatchObject({ - subdomain: "hehe.puter.site" - }) - }) -}) diff --git a/tests/subdomains.test.js b/tests/subdomains.test.js deleted file mode 100644 index 2d955da..0000000 --- a/tests/subdomains.test.js +++ /dev/null @@ -1,90 +0,0 @@ -import { vi } from "vitest"; -import { it } from "vitest"; -import { describe } from "vitest"; -import { createSubdomain, deleteSubdomain, getSubdomains, updateSubdomain } from "../src/commands/subdomains"; -import { expect } from "vitest"; -import { getPuter } from "../src/modules/PuterModule"; - -vi.mock("../src/modules/PuterModule") -vi.mock("conf", () => { - const Conf = vi.fn(() => ({ - get: vi.fn(), - set: vi.fn(), - clear: vi.fn(), - })); - return { default: Conf }; -}); - -vi.spyOn(console, "log").mockImplementation(() => { }); - -describe("getSubdomains", () => { - it("should get subdomains successfully", async () => { - const mockHostingList = vi.fn().mockResolvedValue([{ - uid: "123", - subdomain: "hehe.puter.site", - root_dir: { path: "/some/path" }, - }]); - vi.mocked(getPuter).mockReturnValue({ - hosting: { - list: mockHostingList - } - }) - const result = await getSubdomains(); - expect(getPuter).toHaveBeenCalled(); - expect(result).toHaveLength(1) - }) -}) - -describe("deleteSubdomain", () => { - it("should delete subdomain successfully", async () => { - const mockHostingDelete = vi.fn().mockResolvedValue(true); - vi.mocked(getPuter).mockReturnValue({ - hosting: { - delete: mockHostingDelete - } - }) - const result = await deleteSubdomain(["hehe.puter.site"]); - expect(result).toBe(true); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Subdomain deleted successfully")); - }) -}) - -describe("createSubdomain", () => { - it("should create subdomain successfully", async () => { - const mockHostingCreate = vi.fn().mockResolvedValue({ - uid: "123", - subdomain: "hehe.puter.site", - root_dir: { path: "/some/path" }, - }); - vi.mocked(getPuter).mockReturnValue({ - hosting: { - create: mockHostingCreate - } - }) - const result = await createSubdomain("hehe", "/mydir") - expect(result).toMatchObject({ - subdomain: "hehe.puter.site" - }) - }) -}) - -describe("updateSubdomain", () => { - it("should update subdomain successfully", async () => { - const mockHostingUpdate = vi.fn().mockResolvedValue({ - uid: "123", - subdomain: "hehe.puter.site", - root_dir: { path: "/newdir" }, - }); - vi.mocked(getPuter).mockReturnValue({ - hosting: { - update: mockHostingUpdate - } - }) - const result = await updateSubdomain("hehe", "/newdir") - expect(result).toMatchObject({ - root_dir: { - path: "/newdir" - } - }) - }) -}) diff --git a/tests/utils.test.js b/tests/utils.test.js index c35f00a..e539877 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -1,30 +1,5 @@ import { describe, it, expect, vi } from 'vitest'; -import { formatDate, formatDateTime, formatSize, displayNonNullValues, parseArgs, isValidAppUuid, is_valid_uuid4 } from '../src/utils.js'; - -describe('formatDate', () => { - it('should format a date string correctly', () => { - const dateString = '2024-10-07T15:03:53.000Z'; - const expected = '10/07/2024, 15:03:53'; - expect(formatDate(dateString)).toBe(expected); - }); - - it('should format a date object correctly', () => { - const dateObject = new Date(Date.UTC(2024, 9, 7, 15, 3, 53)); // Month is 0-indexed - const expected = '10/07/2024, 15:03:53'; - expect(formatDate(dateObject)).toBe(expected); - }); - - it('should handle different date and time', () => { - const dateString = '2023-01-01T01:30:05.000Z'; - const expected = '01/01/2023, 01:30:05'; - expect(formatDate(dateString)).toBe(expected); - }); - - it('should handle invalid date', () => { - const dateString = 'invalid-date'; - expect(formatDate(dateString)).toBe('Invalid Date'); - }); -}); +import { formatDateTime, formatSize } from '../src/utils.js'; describe('formatDateTime', () => { it('should format as time if within 24 hours', () => { @@ -76,118 +51,3 @@ describe('formatSize', () => { expect(formatSize(undefined)).toBe('0'); }); }); - -describe('displayNonNullValues', () => { - it('should display non-null values in a formatted table', () => { - const data = { - name: 'John Doe', - age: 30, - address: { - street: '123 Main St', - city: 'Anytown', - zip: null - }, - email: null - }; - - const consoleLogSpy = vi.spyOn(console, 'log'); - displayNonNullValues(data); - expect(consoleLogSpy).toHaveBeenCalled(); - consoleLogSpy.mockRestore(); - }); - - it('should handle empty object', () => { - const data = {}; - const consoleLogSpy = vi.spyOn(console, 'log'); - displayNonNullValues(data); - expect(consoleLogSpy).toHaveBeenCalled(); - consoleLogSpy.mockRestore(); - }); - - it('should handle nested objects with all null values', () => { - const data = { a: null, b: { c: null, d: null } }; - const consoleLogSpy = vi.spyOn(console, 'log'); - displayNonNullValues(data); - expect(consoleLogSpy).toHaveBeenCalledTimes(5); - consoleLogSpy.mockRestore(); - }); - - it('should handle non-object input', () => { - const data = "not an object"; - const consoleErrorSpy = vi.spyOn(console, 'error'); - displayNonNullValues(data); - expect(consoleErrorSpy).toHaveBeenCalledWith("Invalid input: Input must be a non-null object."); - consoleErrorSpy.mockRestore(); - }); -}); - -describe('parseArgs', () => { - it('should parse simple arguments', () => { - const input = 'command --arg1 val1 --arg2 val2'; - const expected = { _: ['command'], arg1: 'val1', arg2: 'val2' }; - expect(parseArgs(input)).toEqual(expect.objectContaining(expected)); - }); - - it('should parse command line arguments with different types', () => { - const input = 'command --name="John Doe" --age=30'; - const result = parseArgs(input); - expect(result).toEqual({ _: ['command'], name: 'John Doe', age: 30 }); - }); - - it('should parse quoted arguments', () => { - const input = 'command --arg "quoted value"'; - const expected = { _: ['command'], arg: 'quoted value' }; - expect(parseArgs(input)).toEqual(expect.objectContaining(expected)); - }); - - it('should parse arguments with equals sign', () => { - const input = 'command --arg1=val1 --arg2=val2'; - const expected = { _: ['command'], arg1: 'val1', arg2: 'val2' }; - expect(parseArgs(input)).toEqual(expect.objectContaining(expected)); - }); - - it('should handle empty input', () => { - const result = parseArgs(''); - expect(result).toEqual({ _: []}); - }); - - it('should parse empty arguments', () => { - const input = ''; - const expected = { _: [] }; - expect(parseArgs(input)).toEqual(expect.objectContaining(expected)); - }); -}); - -describe('isValidAppUuid', () => { - it('should return true for a valid app UUID', () => { - const uuid = 'app-a1b2c3d4-e5f6-4789-8abc-def012345678'; - expect(isValidAppUuid(uuid)).toBe(true); - }); - - it('should return false if UUID does not start with "app-"', () => { - const uuid = 'a1b2c3d4-e5f6-4789-8abc-def012345678'; - expect(isValidAppUuid(uuid)).toBe(false); - }); - - it('should return false for an invalid UUID after "app-"', () => { - const uuid = 'app-invalid-uuid'; - expect(isValidAppUuid(uuid)).toBe(false); - }); -}); - -describe('is_valid_uuid4', () => { - it('should return true for a valid UUID v4', () => { - const uuid = 'a1b2c3d4-e5f6-4789-8abc-def012345678'; - expect(is_valid_uuid4(uuid)).toBe(true); - }); - - it('should return false for an invalid UUID v4', () => { - const uuid = 'a1b2c3d4-e5f6-5789-8abc-def012345678'; // Invalid version - expect(is_valid_uuid4(uuid)).toBe(false); - }); - - it('should return false for a completely invalid UUID', () => { - const uuid = 'invalid-uuid'; - expect(is_valid_uuid4(uuid)).toBe(false); - }); -}); \ No newline at end of file